Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/instance health #4586

Merged
merged 7 commits into from
Aug 30, 2023
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { useInstanceStats } from '../../../../hooks/api/getters/useInstanceStats
import { formatApiPath } from '../../../../utils/formatPath';
import { PageContent } from '../../../common/PageContent/PageContent';
import { PageHeader } from '../../../common/PageHeader/PageHeader';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';

export const InstanceStats: VFC = () => {
const { stats } = useInstanceStats();
Expand All @@ -32,6 +33,21 @@ export const InstanceStats: VFC = () => {
{ title: 'Instance Id', value: stats?.instanceId },
{ title: versionTitle, value: version },
{ title: 'Users', value: stats?.users },
{
title: 'Active past 7 days',
value: stats?.activeUsers?.last7,
offset: true,
},
{
title: 'Active past 30 days',
value: stats?.activeUsers?.last30,
offset: true,
},
{
title: 'Active past 90 days',
value: stats?.activeUsers?.last90,
offset: true,
},
{ title: 'Feature toggles', value: stats?.featureToggles },
{ title: 'Projects', value: stats?.projects },
{ title: 'Environments', value: stats?.environments },
Expand Down Expand Up @@ -64,7 +80,22 @@ export const InstanceStats: VFC = () => {
{rows.map(row => (
<TableRow key={row.title}>
<TableCell component="th" scope="row">
{row.title}
<ConditionallyRender
condition={Boolean(row.offset)}
show={
<Box
component="span"
sx={theme => ({
marginLeft: row.offset
? theme.spacing(2)
: 0,
})}
>
{row.title}
</Box>
}
elseShow={row.title}
/>
</TableCell>
<TableCell align="right">{row.value}</TableCell>
</TableRow>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,29 +2,10 @@ import useSWR from 'swr';
import { useMemo } from 'react';
import { formatApiPath } from 'utils/formatPath';
import handleErrorResponses from '../httpErrorResponseHandler';

interface InstanceStats {
instanceId: string;
timestamp: Date;
versionOSS: string;
versionEnterprise?: string;
users: number;
featureToggles: number;
projects: number;
contextFields: number;
roles: number;
groups: number;
environments: number;
segments: number;
strategies: number;
featureExports: number;
featureImports: number;
SAMLenabled: boolean;
OIDCenabled: boolean;
}
import { InstanceAdminStatsSchema } from 'openapi';

export interface IInstanceStatsResponse {
stats?: InstanceStats;
stats?: InstanceAdminStatsSchema;
refetchGroup: () => void;
loading: boolean;
error?: Error;
Expand Down
1 change: 1 addition & 0 deletions frontend/src/openapi/models/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -548,6 +548,7 @@ export * from './importTogglesSchema';
export * from './importTogglesValidateItemSchema';
export * from './importTogglesValidateSchema';
export * from './instanceAdminStatsSchema';
export * from './instanceAdminStatsSchemaActiveUsers';
export * from './instanceAdminStatsSchemaClientAppsItem';
export * from './instanceAdminStatsSchemaClientAppsItemRange';
export * from './invoicesSchema';
Expand Down
7 changes: 7 additions & 0 deletions frontend/src/openapi/models/instanceAdminStatsSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
* Do not edit manually.
* See `gen:api` script in package.json
*/
import type { InstanceAdminStatsSchemaActiveUsers } from './instanceAdminStatsSchemaActiveUsers';
import type { InstanceAdminStatsSchemaClientAppsItem } from './instanceAdminStatsSchemaClientAppsItem';

/**
Expand All @@ -19,6 +20,8 @@ export interface InstanceAdminStatsSchema {
versionEnterprise?: string;
/** The number of users this instance has */
users?: number;
/** The number of active users in the last 7, 30 and 90 days */
activeUsers?: InstanceAdminStatsSchemaActiveUsers;
/** The number of feature-toggles this instance has */
featureToggles?: number;
/** The number of projects defined in this instance. */
Expand All @@ -41,6 +44,10 @@ export interface InstanceAdminStatsSchema {
OIDCenabled?: boolean;
/** A count of connected applications in the last week, last month and all time since last restart */
clientApps?: InstanceAdminStatsSchemaClientAppsItem[];
/** The number of export operations on this instance */
featureExports?: number;
/** The number of import operations on this instance */
featureImports?: number;
/** A SHA-256 checksum of the instance statistics to be used to verify that the data in this object has not been tampered with */
sum?: string;
}
17 changes: 17 additions & 0 deletions frontend/src/openapi/models/instanceAdminStatsSchemaActiveUsers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/**
* Generated by Orval
* Do not edit manually.
* See `gen:api` script in package.json
*/

/**
* The number of active users in the last 7, 30 and 90 days
*/
export type InstanceAdminStatsSchemaActiveUsers = {
/** The number of active users in the last 7 days */
last7?: number;
/** The number of active users in the last 30 days */
last30?: number;
/** The number of active users in the last 90 days */
last90?: number;
};
25 changes: 25 additions & 0 deletions src/lib/db/user-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,31 @@ class UserStore implements IUserStore {
.then((res) => Number(res[0].count));
}

async getActiveUsersCount(): Promise<{
last7: number;
last30: number;
last90: number;
}> {
const result = await this.db.raw(
`SELECT
(SELECT COUNT(*) FROM ${TABLE} WHERE seen_at > NOW() - INTERVAL '1 week') AS last_week,
(SELECT COUNT(*) FROM ${TABLE} WHERE seen_at > NOW() - INTERVAL '1 month') AS last_month,
(SELECT COUNT(*) FROM ${TABLE} WHERE seen_at > NOW() - INTERVAL '3 months') AS last_quarter`,
);

const {
last_week: last7,
last_month: last30,
last_quarter: last90,
} = result.rows[0];

return {
last7,
last30,
last90,
};
}

destroy(): void {}

async exists(id: number): Promise<boolean> {
Expand Down
40 changes: 40 additions & 0 deletions src/lib/openapi/spec/instance-admin-stats-schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,34 @@ export const instanceAdminStatsSchema = {
example: 8,
minimum: 0,
},
activeUsers: {
type: 'object',
description:
'The number of active users in the last 7, 30 and 90 days',
properties: {
last7: {
type: 'number',
description:
'The number of active users in the last 7 days',
example: 5,
minimum: 0,
},
last30: {
type: 'number',
description:
'The number of active users in the last 30 days',
example: 10,
minimum: 0,
},
last90: {
type: 'number',
description:
'The number of active users in the last 90 days',
example: 15,
minimum: 0,
},
},
},
featureToggles: {
type: 'number',
description: 'The number of feature-toggles this instance has',
Expand Down Expand Up @@ -124,6 +152,18 @@ export const instanceAdminStatsSchema = {
},
},
},
featureExports: {
type: 'number',
description: 'The number of export operations on this instance',
example: 0,
minimum: 0,
},
featureImports: {
type: 'number',
description: 'The number of import operations on this instance',
example: 0,
minimum: 0,
},
sum: {
type: 'string',
description:
Expand Down
5 changes: 5 additions & 0 deletions src/lib/routes/admin-api/instance-admin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,11 @@ class InstanceAdminController extends Controller {
users: 10,
versionEnterprise: '5.1.7',
versionOSS: '5.1.7',
activeUsers: {
last90: 15,
last30: 10,
last7: 5,
},
};
}

Expand Down
2 changes: 1 addition & 1 deletion src/lib/services/instance-stats-service.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ test('get snapshot should not call getStats', async () => {
// subsequent calls to getStatsSnapshot don't call getStats
for (let i = 0; i < 3; i++) {
const stats = instanceStatsService.getStatsSnapshot();
expect(stats.clientApps).toStrictEqual([
expect(stats?.clientApps).toStrictEqual([
{ range: 'allTime', count: 0 },
{ range: '30d', count: 0 },
{ range: '7d', count: 0 },
Expand Down
6 changes: 5 additions & 1 deletion src/lib/services/instance-stats-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { IFeatureToggleStore } from '../types/stores/feature-toggle-store';
import { IGroupStore } from '../types/stores/group-store';
import { IProjectStore } from '../types/stores/project-store';
import { IStrategyStore } from '../types/stores/strategy-store';
import { IUserStore } from '../types/stores/user-store';
import { IActiveUsers, IUserStore } from '../types/stores/user-store';
import { ISegmentStore } from '../types/stores/segment-store';
import { IRoleStore } from '../types/stores/role-store';
import VersionService from './version-service';
Expand Down Expand Up @@ -43,6 +43,7 @@ export interface InstanceStats {
SAMLenabled: boolean;
OIDCenabled: boolean;
clientApps: { range: TimeRange; count: number }[];
activeUsers: IActiveUsers;
}

export interface InstanceStatsSigned extends InstanceStats {
Expand Down Expand Up @@ -176,6 +177,7 @@ export class InstanceStatsService {
const [
featureToggles,
users,
activeUsers,
projects,
contextFields,
groups,
Expand All @@ -193,6 +195,7 @@ export class InstanceStatsService {
] = await Promise.all([
this.getToggleCount(),
this.userStore.count(),
this.userStore.getActiveUsersCount(),
this.projectStore.count(),
this.contextFieldStore.count(),
this.groupStore.count(),
Expand All @@ -215,6 +218,7 @@ export class InstanceStatsService {
versionOSS: versionInfo.current.oss,
versionEnterprise: versionInfo.current.enterprise,
users,
activeUsers,
featureToggles,
projects,
contextFields,
Expand Down
7 changes: 7 additions & 0 deletions src/lib/types/stores/user-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,12 @@ export interface IUserUpdateFields {
email?: string;
}

export interface IActiveUsers {
last7: number;
last30: number;
last90: number;
}

export interface IUserStore extends Store<IUser, number> {
update(id: number, fields: IUserUpdateFields): Promise<IUser>;
insert(user: ICreateUser): Promise<IUser>;
Expand All @@ -32,4 +38,5 @@ export interface IUserStore extends Store<IUser, number> {
incLoginAttempts(user: IUser): Promise<void>;
successfullyLogin(user: IUser): Promise<void>;
count(): Promise<number>;
getActiveUsersCount(): Promise<IActiveUsers>;
}
21 changes: 21 additions & 0 deletions src/test/fixtures/fake-user-store.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import User, { IUser } from '../../lib/types/user';
import {
IActiveUsers,
ICreateUser,
IUserLookup,
IUserStore,
Expand Down Expand Up @@ -46,6 +47,26 @@ class UserStoreMock implements IUserStore {
return this.data.find((u) => u.id === key);
}

async getActiveUsersCount(): Promise<IActiveUsers> {
return Promise.resolve({
last7: this.data.filter(
(u) =>
u.seenAt &&
u.seenAt > new Date(Date.now() - 7 * 24 * 60 * 60 * 1000),
).length,
last30: this.data.filter(
(u) =>
u.seenAt &&
u.seenAt > new Date(Date.now() - 30 * 24 * 60 * 60 * 1000),
).length,
last90: this.data.filter(
(u) =>
u.seenAt &&
u.seenAt > new Date(Date.now() - 90 * 24 * 60 * 60 * 1000),
).length,
});
}

async insert(user: User): Promise<User> {
// eslint-disable-next-line no-param-reassign
user.id = this.idSeq;
Expand Down