diff --git a/src/lib/routes/admin-api/metrics.ts b/src/lib/routes/admin-api/metrics.ts index be753f2f6e..7bec106fcc 100644 --- a/src/lib/routes/admin-api/metrics.ts +++ b/src/lib/routes/admin-api/metrics.ts @@ -14,6 +14,7 @@ import { getStandardResponses, } from '../../openapi/util/standard-responses'; import { CreateApplicationSchema } from '../../openapi/spec/create-application-schema'; +import { IAuthRequest } from '../unleash-types'; class MetricsController extends Controller { private logger: Logger; @@ -148,14 +149,16 @@ class MetricsController extends Controller { } async getApplications( - req: Request, + req: IAuthRequest, res: Response, ): Promise { + const { user } = req; const query = req.query.strategyName ? { strategyName: req.query.strategyName as string } : {}; const applications = await this.clientInstanceService.getApplications( query, + user.id, ); res.json({ applications }); } diff --git a/src/lib/services/client-metrics/instance-service.test.ts b/src/lib/services/client-metrics/instance-service.test.ts index dc3b490780..dcb53880dc 100644 --- a/src/lib/services/client-metrics/instance-service.test.ts +++ b/src/lib/services/client-metrics/instance-service.test.ts @@ -3,6 +3,7 @@ import { IClientApp } from '../../types/model'; import { secondsToMilliseconds } from 'date-fns'; import FakeEventStore from '../../../test/fixtures/fake-event-store'; import { createTestConfig } from '../../../test/config/test-config'; +import { FakePrivateProjectChecker } from '../../features/private-project/fakePrivateProjectChecker'; /** * A utility to wait for any pending promises in the test subject code. @@ -61,6 +62,7 @@ test('Multiple registrations of same appname and instanceid within same time per eventStore: new FakeEventStore(), }, config, + new FakePrivateProjectChecker(), ); const client1: IClientApp = { appName: 'test_app', @@ -111,6 +113,7 @@ test('Multiple unique clients causes multiple registrations', async () => { eventStore: new FakeEventStore(), }, config, + new FakePrivateProjectChecker(), ); const client1 = { appName: 'test_app', @@ -164,6 +167,7 @@ test('Same client registered outside of dedup interval will be registered twice' eventStore: new FakeEventStore(), }, config, + new FakePrivateProjectChecker(), bulkInterval, ); const client1 = { @@ -217,6 +221,7 @@ test('No registrations during a time period will not call stores', async () => { eventStore: new FakeEventStore(), }, config, + new FakePrivateProjectChecker(), ); jest.advanceTimersByTime(6000); expect(appStoreSpy).toHaveBeenCalledTimes(0); diff --git a/src/lib/services/client-metrics/instance-service.ts b/src/lib/services/client-metrics/instance-service.ts index b6f7f96008..8cb503d5a7 100644 --- a/src/lib/services/client-metrics/instance-service.ts +++ b/src/lib/services/client-metrics/instance-service.ts @@ -18,6 +18,8 @@ import { minutesToMilliseconds, secondsToMilliseconds } from 'date-fns'; import { IClientMetricsStoreV2 } from '../../types/stores/client-metrics-store-v2'; import { clientMetricsSchema } from './schema'; import { PartialSome } from '../../types/partial'; +import { IPrivateProjectChecker } from '../../features/private-project/privateProjectCheckerType'; +import { IFlagResolver } from '../../types'; export default class ClientInstanceService { apps = {}; @@ -40,6 +42,10 @@ export default class ClientInstanceService { private eventStore: IEventStore; + private privateProjectChecker: IPrivateProjectChecker; + + private flagResolver: IFlagResolver; + private bulkInterval: number; private announcementInterval: number; @@ -61,7 +67,11 @@ export default class ClientInstanceService { | 'clientInstanceStore' | 'eventStore' >, - { getLogger }: Pick, + { + getLogger, + flagResolver, + }: Pick, + privateProjectChecker: IPrivateProjectChecker, bulkInterval = secondsToMilliseconds(5), announcementInterval = minutesToMilliseconds(5), ) { @@ -71,6 +81,8 @@ export default class ClientInstanceService { this.clientApplicationsStore = clientApplicationsStore; this.clientInstanceStore = clientInstanceStore; this.eventStore = eventStore; + this.privateProjectChecker = privateProjectChecker; + this.flagResolver = flagResolver; this.logger = getLogger( '/services/client-metrics/client-instance-service.ts', ); @@ -162,8 +174,27 @@ export default class ClientInstanceService { async getApplications( query: IApplicationQuery, + userId: number, ): Promise { - return this.clientApplicationsStore.getAppsForStrategy(query); + const applications = + await this.clientApplicationsStore.getAppsForStrategy(query); + if (this.flagResolver.isEnabled('privateProjects') && userId) { + const accessibleProjects = + await this.privateProjectChecker.getUserAccessibleProjects( + userId, + ); + return applications.map((application) => { + return { + ...application, + usage: application.usage?.filter( + (usageItem) => + usageItem.project === '*' || + accessibleProjects.includes(usageItem.project), + ), + }; + }); + } + return applications; } async getApplication(appName: string): Promise { diff --git a/src/lib/services/index.ts b/src/lib/services/index.ts index 8f1053a947..433f008bb9 100644 --- a/src/lib/services/index.ts +++ b/src/lib/services/index.ts @@ -162,7 +162,6 @@ export const createServices = ( const groupService = new GroupService(stores, config); const accessService = new AccessService(stores, config, groupService); const apiTokenService = new ApiTokenService(stores, config); - const clientInstanceService = new ClientInstanceService(stores, config); const lastSeenService = new LastSeenService(stores, config); const clientMetricsServiceV2 = new ClientMetricsServiceV2( stores, @@ -205,6 +204,11 @@ export const createServices = ( const privateProjectChecker = db ? createPrivateProjectChecker(db, config) : createFakePrivateProjectChecker(); + const clientInstanceService = new ClientInstanceService( + stores, + config, + privateProjectChecker, + ); const featureToggleServiceV2 = new FeatureToggleService( stores, config, diff --git a/src/server-dev.ts b/src/server-dev.ts index c4d3c9b0ca..4853f62166 100644 --- a/src/server-dev.ts +++ b/src/server-dev.ts @@ -43,7 +43,7 @@ process.nextTick(async () => { featureNamingPattern: true, doraMetrics: true, variantTypeNumber: true, - privateProjects: false, + privateProjects: true, accessOverview: true, datadogJsonTemplate: true, }, diff --git a/src/test/e2e/services/client-metrics-service.e2e.test.ts b/src/test/e2e/services/client-metrics-service.e2e.test.ts index 732e99b14c..2f258e19a8 100644 --- a/src/test/e2e/services/client-metrics-service.e2e.test.ts +++ b/src/test/e2e/services/client-metrics-service.e2e.test.ts @@ -3,6 +3,7 @@ import { IClientApp } from '../../../lib/types/model'; import { secondsToMilliseconds } from 'date-fns'; import { createTestConfig } from '../../config/test-config'; import { IUnleashConfig } from '../../../lib/types'; +import { FakePrivateProjectChecker } from '../../../lib/features/private-project/fakePrivateProjectChecker'; const faker = require('faker'); const dbInit = require('../helpers/database-init'); @@ -23,6 +24,7 @@ beforeAll(async () => { clientInstanceService = new ClientInstanceService( stores, config, + new FakePrivateProjectChecker(), bulkInterval, announcementInterval, );