Skip to content

Commit

Permalink
Fix/last seen at by environment (#4939)
Browse files Browse the repository at this point in the history
Initial architecture for last seen at by environment.
  • Loading branch information
FredrikOseberg authored Oct 9, 2023
1 parent 34fc171 commit d896dbd
Show file tree
Hide file tree
Showing 44 changed files with 649 additions and 144 deletions.
2 changes: 2 additions & 0 deletions src/lib/__snapshots__/create-config.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ exports[`should create default config 1`] = `
"responseTimeWithAppNameKillSwitch": false,
"strictSchemaValidation": false,
"transactionalDecorator": false,
"useLastSeenRefactor": false,
"variantTypeNumber": false,
},
},
Expand Down Expand Up @@ -150,6 +151,7 @@ exports[`should create default config 1`] = `
"responseTimeWithAppNameKillSwitch": false,
"strictSchemaValidation": false,
"transactionalDecorator": false,
"useLastSeenRefactor": false,
"variantTypeNumber": false,
},
"externalResolver": {
Expand Down
2 changes: 1 addition & 1 deletion src/lib/db/feature-toggle-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { Logger, LogProvider } from '../logger';
import { FeatureToggle, FeatureToggleDTO, IVariant } from '../types/model';
import { IFeatureToggleStore } from '../types/stores/feature-toggle-store';
import { Db } from './db';
import { LastSeenInput } from '../services/client-metrics/last-seen-service';
import { LastSeenInput } from '../services/client-metrics/last-seen/last-seen-service';
import { NameExistsError } from '../error';

export type EnvironmentFeatureNames = { [key: string]: string[] };
Expand Down
2 changes: 2 additions & 0 deletions src/lib/db/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import { Db } from './db';
import { ImportTogglesStore } from '../features/export-import-toggles/import-toggles-store';
import PrivateProjectStore from '../features/private-project/privateProjectStore';
import { DependentFeaturesStore } from '../features/dependent-features/dependent-features-store';
import LastSeenStore from '../services/client-metrics/last-seen/last-seen-store';

export const createStores = (
config: IUnleashConfig,
Expand Down Expand Up @@ -132,6 +133,7 @@ export const createStores = (
importTogglesStore: new ImportTogglesStore(db),
privateProjectStore: new PrivateProjectStore(db, getLogger),
dependentFeaturesStore: new DependentFeaturesStore(db),
lastSeenStore: new LastSeenStore(db, eventBus, getLogger),
};
};

Expand Down
6 changes: 6 additions & 0 deletions src/lib/features/project/createProjectService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ import {
createPrivateProjectChecker,
} from '../private-project/createPrivateProjectChecker';
import FakeFeatureTagStore from '../../../test/fixtures/fake-feature-tag-store';
import { LastSeenAtReadModel } from '../../services/client-metrics/last-seen/last-seen-read-model';
import { FakeLastSeenReadModel } from '../../services/client-metrics/last-seen/fake-last-seen-read-model';

export const createProjectService = (
db: Db,
Expand Down Expand Up @@ -99,6 +101,7 @@ export const createProjectService = (
);

const privateProjectChecker = createPrivateProjectChecker(db, config);
const lastSeenReadModel = new LastSeenAtReadModel(db);

return new ProjectService(
{
Expand All @@ -118,6 +121,7 @@ export const createProjectService = (
favoriteService,
eventService,
privateProjectChecker,
lastSeenReadModel,
);
};

Expand Down Expand Up @@ -160,6 +164,7 @@ export const createFakeProjectService = (
);

const privateProjectChecker = createFakePrivateProjectChecker();
const fakeLastSeenReadModel = new FakeLastSeenReadModel();

return new ProjectService(
{
Expand All @@ -179,5 +184,6 @@ export const createFakeProjectService = (
favoriteService,
eventService,
privateProjectChecker,
fakeLastSeenReadModel,
);
};
3 changes: 2 additions & 1 deletion src/lib/routes/admin-api/project/api-token.ts
Original file line number Diff line number Diff line change
Expand Up @@ -149,8 +149,9 @@ export class ProjectApiTokenController extends Controller {
): Promise<void> {
const { user } = req;
const { projectId } = req.params;
await this.projectService.getProject(projectId); // Validates that the project exists

const project = await this.projectService.getProject(projectId); // Validates that the project exists
console.log('project', project);
const projectTokens = await this.accessibleTokens(user, projectId);
this.openApiService.respondWithValidation(
200,
Expand Down
18 changes: 12 additions & 6 deletions src/lib/routes/client-api/metrics.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,25 @@ import { createTestConfig } from '../../../test/config/test-config';
import { clientMetricsSchema } from '../../services/client-metrics/schema';
import { createServices } from '../../services';
import { IUnleashOptions, IUnleashServices, IUnleashStores } from '../../types';
import dbInit from '../../../test/e2e/helpers/database-init';

async function getSetup(opts?: IUnleashOptions) {
const stores = createStores();
let db;

async function getSetup(opts?: IUnleashOptions) {
const config = createTestConfig(opts);
const services = createServices(stores, config);
const app = await getApp(config, stores, services);
db = await dbInit('metrics', config.getLogger);

const services = createServices(db.stores, config, db.rawDatabase);
const app = await getApp(config, db.stores, services);

return {
request: supertest(app),
stores,
stores: db.stores,
services,
destroy: () => {
destroy: async () => {
services.versionService.destroy();
services.clientInstanceService.destroy();
await db.destroy();
},
};
}
Expand Down Expand Up @@ -207,6 +211,8 @@ test('should set lastSeen on toggle', async () => {
await services.lastSeenService.store();
const toggle = await stores.featureToggleStore.get('toggleLastSeen');

console.log(toggle);

expect(toggle.lastSeenAt).toBeTruthy();
});

Expand Down
61 changes: 0 additions & 61 deletions src/lib/services/client-metrics/last-seen-service.test.ts

This file was deleted.

34 changes: 34 additions & 0 deletions src/lib/services/client-metrics/last-seen/createLastSeenService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import FakeFeatureToggleStore from '../../../../test/fixtures/fake-feature-toggle-store';
import FeatureToggleStore from '../../../db/feature-toggle-store';
import { Db, IUnleashConfig } from '../../../server-impl';
import { FakeLastSeenStore } from './fake-last-seen-store';
import { LastSeenService } from './last-seen-service';
import LastSeenStore from './last-seen-store';

export const createLastSeenService = (
db: Db,
config: IUnleashConfig,
): LastSeenService => {
const lastSeenStore = new LastSeenStore(
db,
config.eventBus,
config.getLogger,
);

const featureToggleStore = new FeatureToggleStore(
db,
config.eventBus,
config.getLogger,
);

return new LastSeenService({ lastSeenStore, featureToggleStore }, config);
};

export const createFakeLastSeenService = (
config: IUnleashConfig,
): LastSeenService => {
const lastSeenStore = new FakeLastSeenStore();
const featureToggleStore = new FakeFeatureToggleStore();

return new LastSeenService({ lastSeenStore, featureToggleStore }, config);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { IFeatureLastSeenResults } from './last-seen-read-model';
import { ILastSeenReadModel } from './types/last-seen-read-model-type';

export class FakeLastSeenReadModel implements ILastSeenReadModel {
// eslint-disable-next-line
getForFeature(features: string[]): Promise<IFeatureLastSeenResults> {
return Promise.resolve({});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { LastSeenInput } from './last-seen-service';
import { ILastSeenStore } from './types/last-seen-store-type';

export class FakeLastSeenStore implements ILastSeenStore {
setLastSeen(data: LastSeenInput[]): Promise<void> {
data.map((lastSeen) => lastSeen);
return Promise.resolve();
}
}
39 changes: 39 additions & 0 deletions src/lib/services/client-metrics/last-seen/last-seen-mapper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { Logger } from '../../../logger';
import { IFeatureOverview } from '../../../types';
import { IFeatureLastSeenResults } from './last-seen-read-model';

export class LastSeenMapper {
mapToFeatures(
features: IFeatureOverview[],
lastSeenAtPerEnvironment: IFeatureLastSeenResults,
logger: Logger,
): IFeatureOverview[] {
return features.map((feature) => {
if (!feature.environments) {
logger.warn('Feature without environments:', feature);
return feature;
}

feature.environments = feature.environments.map((environment) => {
const noData =
!lastSeenAtPerEnvironment[feature.name] ||
!lastSeenAtPerEnvironment[feature.name][environment.name];

if (noData) {
logger.warn(
'No last seen data for environment:',
environment,
);
return environment;
}

environment.lastSeenAt = new Date(
lastSeenAtPerEnvironment[feature.name][environment.name]
.lastSeen,
);
return environment;
});
return feature;
});
}
}
41 changes: 41 additions & 0 deletions src/lib/services/client-metrics/last-seen/last-seen-read-model.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { Db } from '../../../db/db';
import { ILastSeenReadModel } from './types/last-seen-read-model-type';

const TABLE = 'last_seen_at_metrics';

export interface IFeatureLastSeenResults {
[featureName: string]: {
[environment: string]: {
lastSeen: string;
};
};
}
export class LastSeenAtReadModel implements ILastSeenReadModel {
private db: Db;

constructor(db: Db) {
this.db = db;
}

async getForFeature(features: string[]): Promise<IFeatureLastSeenResults> {
const rows = await this.db(TABLE).whereIn('feature_name', features);

const result = rows.reduce((acc, curr) => {
if (!acc[curr.feature_name]) {
acc[curr.feature_name] = {};

acc[curr.feature_name][curr.environment] = {
lastSeen: curr.last_seen_at,
};
} else {
acc[curr.feature_name][curr.environment] = {
lastSeen: curr.last_seen_at,
};
}

return acc;
}, {});

return result;
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { secondsToMilliseconds } from 'date-fns';
import { Logger } from '../../logger';
import { IUnleashConfig } from '../../server-impl';
import { IUnleashStores } from '../../types';
import { IClientMetricsEnv } from '../../types/stores/client-metrics-store-v2';
import { IFeatureToggleStore } from '../../types/stores/feature-toggle-store';
import { Logger } from '../../../logger';
import { IUnleashConfig } from '../../../server-impl';
import { IClientMetricsEnv } from '../../../types/stores/client-metrics-store-v2';
import { ILastSeenStore } from './types/last-seen-store-type';
import { IFeatureToggleStore, IUnleashStores } from '../../../../lib/types';

export type LastSeenInput = {
featureName: string;
Expand All @@ -17,17 +17,26 @@ export class LastSeenService {

private logger: Logger;

private lastSeenStore: ILastSeenStore;

private featureToggleStore: IFeatureToggleStore;

private config: IUnleashConfig;

constructor(
{ featureToggleStore }: Pick<IUnleashStores, 'featureToggleStore'>,
{
featureToggleStore,
lastSeenStore,
}: Pick<IUnleashStores, 'featureToggleStore' | 'lastSeenStore'>,
config: IUnleashConfig,
lastSeenInterval = secondsToMilliseconds(30),
) {
this.lastSeenStore = lastSeenStore;
this.featureToggleStore = featureToggleStore;
this.logger = config.getLogger(
'/services/client-metrics/last-seen-service.ts',
);
this.config = config;

this.timers.push(
setInterval(() => this.store(), lastSeenInterval).unref(),
Expand All @@ -42,7 +51,12 @@ export class LastSeenService {
this.logger.debug(
`Updating last seen for ${lastSeenToggles.length} toggles`,
);
await this.featureToggleStore.setLastSeen(lastSeenToggles);

if (this.config.flagResolver.isEnabled('useLastSeenRefactor')) {
await this.lastSeenStore.setLastSeen(lastSeenToggles);
} else {
await this.featureToggleStore.setLastSeen(lastSeenToggles);
}
}
return count;
}
Expand Down
Loading

0 comments on commit d896dbd

Please sign in to comment.