Skip to content

Commit

Permalink
instance stats active users
Browse files Browse the repository at this point in the history
Co-authored-by: chriswk <[email protected]>
  • Loading branch information
Tymek and chriswk committed Aug 30, 2023
1 parent a28d81c commit 1bf8170
Show file tree
Hide file tree
Showing 10 changed files with 114 additions and 24 deletions.
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
4 changes: 4 additions & 0 deletions frontend/src/openapi/models/instanceAdminStatsSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,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;
}
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
12 changes: 12 additions & 0 deletions src/lib/openapi/spec/instance-admin-stats-schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -152,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

0 comments on commit 1bf8170

Please sign in to comment.