1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-01-20 00:08:02 +01:00

feat: make application usage private through project (#4786)

This commit is contained in:
Jaanus Sellin 2023-09-20 10:35:30 +03:00 committed by GitHub
parent 0d7c230af9
commit e4f8e1692a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 50 additions and 5 deletions

View File

@ -14,6 +14,7 @@ import {
getStandardResponses, getStandardResponses,
} from '../../openapi/util/standard-responses'; } from '../../openapi/util/standard-responses';
import { CreateApplicationSchema } from '../../openapi/spec/create-application-schema'; import { CreateApplicationSchema } from '../../openapi/spec/create-application-schema';
import { IAuthRequest } from '../unleash-types';
class MetricsController extends Controller { class MetricsController extends Controller {
private logger: Logger; private logger: Logger;
@ -148,14 +149,16 @@ class MetricsController extends Controller {
} }
async getApplications( async getApplications(
req: Request, req: IAuthRequest,
res: Response<ApplicationsSchema>, res: Response<ApplicationsSchema>,
): Promise<void> { ): Promise<void> {
const { user } = req;
const query = req.query.strategyName const query = req.query.strategyName
? { strategyName: req.query.strategyName as string } ? { strategyName: req.query.strategyName as string }
: {}; : {};
const applications = await this.clientInstanceService.getApplications( const applications = await this.clientInstanceService.getApplications(
query, query,
user.id,
); );
res.json({ applications }); res.json({ applications });
} }

View File

@ -3,6 +3,7 @@ import { IClientApp } from '../../types/model';
import { secondsToMilliseconds } from 'date-fns'; import { secondsToMilliseconds } from 'date-fns';
import FakeEventStore from '../../../test/fixtures/fake-event-store'; import FakeEventStore from '../../../test/fixtures/fake-event-store';
import { createTestConfig } from '../../../test/config/test-config'; 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. * 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(), eventStore: new FakeEventStore(),
}, },
config, config,
new FakePrivateProjectChecker(),
); );
const client1: IClientApp = { const client1: IClientApp = {
appName: 'test_app', appName: 'test_app',
@ -111,6 +113,7 @@ test('Multiple unique clients causes multiple registrations', async () => {
eventStore: new FakeEventStore(), eventStore: new FakeEventStore(),
}, },
config, config,
new FakePrivateProjectChecker(),
); );
const client1 = { const client1 = {
appName: 'test_app', appName: 'test_app',
@ -164,6 +167,7 @@ test('Same client registered outside of dedup interval will be registered twice'
eventStore: new FakeEventStore(), eventStore: new FakeEventStore(),
}, },
config, config,
new FakePrivateProjectChecker(),
bulkInterval, bulkInterval,
); );
const client1 = { const client1 = {
@ -217,6 +221,7 @@ test('No registrations during a time period will not call stores', async () => {
eventStore: new FakeEventStore(), eventStore: new FakeEventStore(),
}, },
config, config,
new FakePrivateProjectChecker(),
); );
jest.advanceTimersByTime(6000); jest.advanceTimersByTime(6000);
expect(appStoreSpy).toHaveBeenCalledTimes(0); expect(appStoreSpy).toHaveBeenCalledTimes(0);

View File

@ -18,6 +18,8 @@ import { minutesToMilliseconds, secondsToMilliseconds } from 'date-fns';
import { IClientMetricsStoreV2 } from '../../types/stores/client-metrics-store-v2'; import { IClientMetricsStoreV2 } from '../../types/stores/client-metrics-store-v2';
import { clientMetricsSchema } from './schema'; import { clientMetricsSchema } from './schema';
import { PartialSome } from '../../types/partial'; import { PartialSome } from '../../types/partial';
import { IPrivateProjectChecker } from '../../features/private-project/privateProjectCheckerType';
import { IFlagResolver } from '../../types';
export default class ClientInstanceService { export default class ClientInstanceService {
apps = {}; apps = {};
@ -40,6 +42,10 @@ export default class ClientInstanceService {
private eventStore: IEventStore; private eventStore: IEventStore;
private privateProjectChecker: IPrivateProjectChecker;
private flagResolver: IFlagResolver;
private bulkInterval: number; private bulkInterval: number;
private announcementInterval: number; private announcementInterval: number;
@ -61,7 +67,11 @@ export default class ClientInstanceService {
| 'clientInstanceStore' | 'clientInstanceStore'
| 'eventStore' | 'eventStore'
>, >,
{ getLogger }: Pick<IUnleashConfig, 'getLogger'>, {
getLogger,
flagResolver,
}: Pick<IUnleashConfig, 'getLogger' | 'flagResolver'>,
privateProjectChecker: IPrivateProjectChecker,
bulkInterval = secondsToMilliseconds(5), bulkInterval = secondsToMilliseconds(5),
announcementInterval = minutesToMilliseconds(5), announcementInterval = minutesToMilliseconds(5),
) { ) {
@ -71,6 +81,8 @@ export default class ClientInstanceService {
this.clientApplicationsStore = clientApplicationsStore; this.clientApplicationsStore = clientApplicationsStore;
this.clientInstanceStore = clientInstanceStore; this.clientInstanceStore = clientInstanceStore;
this.eventStore = eventStore; this.eventStore = eventStore;
this.privateProjectChecker = privateProjectChecker;
this.flagResolver = flagResolver;
this.logger = getLogger( this.logger = getLogger(
'/services/client-metrics/client-instance-service.ts', '/services/client-metrics/client-instance-service.ts',
); );
@ -162,8 +174,27 @@ export default class ClientInstanceService {
async getApplications( async getApplications(
query: IApplicationQuery, query: IApplicationQuery,
userId: number,
): Promise<IClientApplication[]> { ): Promise<IClientApplication[]> {
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<IApplication> { async getApplication(appName: string): Promise<IApplication> {

View File

@ -162,7 +162,6 @@ export const createServices = (
const groupService = new GroupService(stores, config); const groupService = new GroupService(stores, config);
const accessService = new AccessService(stores, config, groupService); const accessService = new AccessService(stores, config, groupService);
const apiTokenService = new ApiTokenService(stores, config); const apiTokenService = new ApiTokenService(stores, config);
const clientInstanceService = new ClientInstanceService(stores, config);
const lastSeenService = new LastSeenService(stores, config); const lastSeenService = new LastSeenService(stores, config);
const clientMetricsServiceV2 = new ClientMetricsServiceV2( const clientMetricsServiceV2 = new ClientMetricsServiceV2(
stores, stores,
@ -205,6 +204,11 @@ export const createServices = (
const privateProjectChecker = db const privateProjectChecker = db
? createPrivateProjectChecker(db, config) ? createPrivateProjectChecker(db, config)
: createFakePrivateProjectChecker(); : createFakePrivateProjectChecker();
const clientInstanceService = new ClientInstanceService(
stores,
config,
privateProjectChecker,
);
const featureToggleServiceV2 = new FeatureToggleService( const featureToggleServiceV2 = new FeatureToggleService(
stores, stores,
config, config,

View File

@ -43,7 +43,7 @@ process.nextTick(async () => {
featureNamingPattern: true, featureNamingPattern: true,
doraMetrics: true, doraMetrics: true,
variantTypeNumber: true, variantTypeNumber: true,
privateProjects: false, privateProjects: true,
accessOverview: true, accessOverview: true,
datadogJsonTemplate: true, datadogJsonTemplate: true,
}, },

View File

@ -3,6 +3,7 @@ import { IClientApp } from '../../../lib/types/model';
import { secondsToMilliseconds } from 'date-fns'; import { secondsToMilliseconds } from 'date-fns';
import { createTestConfig } from '../../config/test-config'; import { createTestConfig } from '../../config/test-config';
import { IUnleashConfig } from '../../../lib/types'; import { IUnleashConfig } from '../../../lib/types';
import { FakePrivateProjectChecker } from '../../../lib/features/private-project/fakePrivateProjectChecker';
const faker = require('faker'); const faker = require('faker');
const dbInit = require('../helpers/database-init'); const dbInit = require('../helpers/database-init');
@ -23,6 +24,7 @@ beforeAll(async () => {
clientInstanceService = new ClientInstanceService( clientInstanceService = new ClientInstanceService(
stores, stores,
config, config,
new FakePrivateProjectChecker(),
bulkInterval, bulkInterval,
announcementInterval, announcementInterval,
); );