From 5a75093cbc3a6223f17e815961d4a8c9c306a5b5 Mon Sep 17 00:00:00 2001 From: Jaanus Sellin Date: Mon, 12 Feb 2024 16:00:59 +0200 Subject: [PATCH] feat: project applications e2e PoC (#6189) 1. Adding store layer 2. Updating schemas 3. Refactoring project files that I touched into feature oriented architecture Next steps E2E tests. --- src/lib/db/index.ts | 2 +- src/lib/db/project-stats-store.ts | 2 +- .../createExportImportService.ts | 2 +- .../createFeatureToggleService.ts | 2 +- .../createInstanceStatsService.ts | 2 +- .../instance-stats/instance-stats-service.ts | 4 +- .../createEnvironmentService.ts | 2 +- .../environment-service.ts | 2 +- .../features/project/createProjectService.ts | 2 +- .../features/project/project-controller.ts | 4 +- .../project}/project-service.ts | 66 +++++----- .../project/project-store-type.ts} | 8 +- .../{db => features/project}/project-store.ts | 114 +++++++++++++++--- src/lib/openapi/index.ts | 2 + .../spec/project-application-schema.ts | 37 ++++-- .../spec/project-application-sdk-schema.ts | 31 +++++ .../spec/project-applications-schema.test.ts | 13 +- .../spec/project-applications-schema.ts | 2 + src/lib/routes/admin-api/user/user.ts | 2 +- src/lib/services/context-service.ts | 2 +- src/lib/services/index.ts | 2 +- src/lib/services/project-health-service.ts | 4 +- src/lib/services/state-service.ts | 2 +- src/lib/types/model.ts | 16 ++- src/lib/types/services.ts | 2 +- src/lib/types/stores.ts | 2 +- .../types/stores/project-stats-store-type.ts | 2 +- .../services/api-token-service.e2e.test.ts | 2 +- .../e2e/services/project-service.e2e.test.ts | 2 +- src/test/e2e/stores/project-store.e2e.test.ts | 7 +- src/test/fixtures/fake-project-stats-store.ts | 2 +- src/test/fixtures/fake-project-store.ts | 23 +++- 32 files changed, 257 insertions(+), 110 deletions(-) rename src/lib/{services => features/project}/project-service.ts (95%) rename src/lib/{types/stores/project-store.ts => features/project/project-store-type.ts} (93%) rename src/lib/{db => features/project}/project-store.ts (86%) create mode 100644 src/lib/openapi/spec/project-application-sdk-schema.ts diff --git a/src/lib/db/index.ts b/src/lib/db/index.ts index 37c545fd7b..869e5bcb41 100644 --- a/src/lib/db/index.ts +++ b/src/lib/db/index.ts @@ -9,7 +9,7 @@ import ClientApplicationsStore from './client-applications-store'; import ContextFieldStore from './context-field-store'; import SettingStore from './setting-store'; import UserStore from './user-store'; -import ProjectStore from './project-store'; +import ProjectStore from '../features/project/project-store'; import TagStore from './tag-store'; import TagTypeStore from '../features/tag-type/tag-type-store'; import AddonStore from './addon-store'; diff --git a/src/lib/db/project-stats-store.ts b/src/lib/db/project-stats-store.ts index 51c79f7da2..627e00c682 100644 --- a/src/lib/db/project-stats-store.ts +++ b/src/lib/db/project-stats-store.ts @@ -3,7 +3,7 @@ import { Logger, LogProvider } from '../logger'; import metricsHelper from '../util/metrics-helper'; import { DB_TIME } from '../metric-events'; import EventEmitter from 'events'; -import { IProjectStats } from '../services/project-service'; +import { IProjectStats } from '../features/project/project-service'; import { ICreateEnabledDates, IProjectStatsStore, diff --git a/src/lib/features/export-import-toggles/createExportImportService.ts b/src/lib/features/export-import-toggles/createExportImportService.ts index 522da7db53..dba9f1bf6e 100644 --- a/src/lib/features/export-import-toggles/createExportImportService.ts +++ b/src/lib/features/export-import-toggles/createExportImportService.ts @@ -5,7 +5,7 @@ import { ImportTogglesStore } from './import-toggles-store'; import FeatureToggleStore from '../feature-toggle/feature-toggle-store'; import TagStore from '../../db/tag-store'; import TagTypeStore from '../tag-type/tag-type-store'; -import ProjectStore from '../../db/project-store'; +import ProjectStore from '../project/project-store'; import FeatureTagStore from '../../db/feature-tag-store'; import StrategyStore from '../../db/strategy-store'; import ContextFieldStore from '../../db/context-field-store'; diff --git a/src/lib/features/feature-toggle/createFeatureToggleService.ts b/src/lib/features/feature-toggle/createFeatureToggleService.ts index a1a9ec6f83..6c943a215a 100644 --- a/src/lib/features/feature-toggle/createFeatureToggleService.ts +++ b/src/lib/features/feature-toggle/createFeatureToggleService.ts @@ -7,7 +7,7 @@ import { import FeatureStrategiesStore from './feature-toggle-strategies-store'; import FeatureToggleStore from './feature-toggle-store'; import FeatureToggleClientStore from '../client-feature-toggles/client-feature-toggle-store'; -import ProjectStore from '../../db/project-store'; +import ProjectStore from '../project/project-store'; import { FeatureEnvironmentStore } from '../../db/feature-environment-store'; import ContextFieldStore from '../../db/context-field-store'; import GroupStore from '../../db/group-store'; diff --git a/src/lib/features/instance-stats/createInstanceStatsService.ts b/src/lib/features/instance-stats/createInstanceStatsService.ts index 7c53c75667..98b8a75cb8 100644 --- a/src/lib/features/instance-stats/createInstanceStatsService.ts +++ b/src/lib/features/instance-stats/createInstanceStatsService.ts @@ -11,7 +11,7 @@ import { IUnleashConfig } from '../../types'; import { Db } from '../../db/db'; import FeatureToggleStore from '../feature-toggle/feature-toggle-store'; import UserStore from '../../db/user-store'; -import ProjectStore from '../../db/project-store'; +import ProjectStore from '../project/project-store'; import EnvironmentStore from '../project-environments/environment-store'; import StrategyStore from '../../db/strategy-store'; import ContextFieldStore from '../../db/context-field-store'; diff --git a/src/lib/features/instance-stats/instance-stats-service.ts b/src/lib/features/instance-stats/instance-stats-service.ts index 426762bd77..656e8ac9a0 100644 --- a/src/lib/features/instance-stats/instance-stats-service.ts +++ b/src/lib/features/instance-stats/instance-stats-service.ts @@ -11,7 +11,7 @@ import { IContextFieldStore } from '../../types/stores/context-field-store'; import { IEnvironmentStore } from '../project-environments/environment-store-type'; import { IFeatureToggleStore } from '../feature-toggle/types/feature-toggle-store-type'; import { IGroupStore } from '../../types/stores/group-store'; -import { IProjectStore } from '../../types/stores/project-store'; +import { IProjectStore } from '../../features/project/project-store-type'; import { IStrategyStore } from '../../types/stores/strategy-store'; import { IUserStore } from '../../types/stores/user-store'; import { ISegmentStore } from '../../types/stores/segment-store'; @@ -25,7 +25,7 @@ import { } from '../../types'; import { CUSTOM_ROOT_ROLE_TYPE } from '../../util'; import { type GetActiveUsers } from './getActiveUsers'; -import { ProjectModeCount } from '../../db/project-store'; +import { ProjectModeCount } from '../project/project-store'; import { GetProductionChanges } from './getProductionChanges'; export type TimeRange = 'allTime' | '30d' | '7d'; diff --git a/src/lib/features/project-environments/createEnvironmentService.ts b/src/lib/features/project-environments/createEnvironmentService.ts index e4ef99b94f..8f5303723d 100644 --- a/src/lib/features/project-environments/createEnvironmentService.ts +++ b/src/lib/features/project-environments/createEnvironmentService.ts @@ -7,7 +7,7 @@ import EnvironmentService from './environment-service'; import EnvironmentStore from './environment-store'; import FeatureStrategiesStore from '../feature-toggle/feature-toggle-strategies-store'; import { FeatureEnvironmentStore } from '../../db/feature-environment-store'; -import ProjectStore from '../../db/project-store'; +import ProjectStore from '../project/project-store'; import FakeFeatureEnvironmentStore from '../../../test/fixtures/fake-feature-environment-store'; import FakeProjectStore from '../../../test/fixtures/fake-project-store'; import FakeFeatureStrategiesStore from '../feature-toggle/fakes/fake-feature-strategies-store'; diff --git a/src/lib/features/project-environments/environment-service.ts b/src/lib/features/project-environments/environment-service.ts index 028568dfbf..e5fa8d098f 100644 --- a/src/lib/features/project-environments/environment-service.ts +++ b/src/lib/features/project-environments/environment-service.ts @@ -17,7 +17,7 @@ import { BadDataError, UNIQUE_CONSTRAINT_VIOLATION } from '../../error'; import NameExistsError from '../../error/name-exists-error'; import { sortOrderSchema } from '../../services/state-schema'; import NotFoundError from '../../error/notfound-error'; -import { IProjectStore } from '../../types/stores/project-store'; +import { IProjectStore } from '../../features/project/project-store-type'; import MinimumOneEnvironmentError from '../../error/minimum-one-environment-error'; import { IFlagResolver } from '../../types/experimental'; import { CreateFeatureStrategySchema } from '../../openapi'; diff --git a/src/lib/features/project/createProjectService.ts b/src/lib/features/project/createProjectService.ts index bb8f69d1a2..efb2c781dd 100644 --- a/src/lib/features/project/createProjectService.ts +++ b/src/lib/features/project/createProjectService.ts @@ -12,7 +12,7 @@ import { } from '../../services'; import FakeGroupStore from '../../../test/fixtures/fake-group-store'; import FakeEventStore from '../../../test/fixtures/fake-event-store'; -import ProjectStore from '../../db/project-store'; +import ProjectStore from './project-store'; import FeatureToggleStore from '../feature-toggle/feature-toggle-store'; import { FeatureEnvironmentStore } from '../../db/feature-environment-store'; import ProjectStatsStore from '../../db/project-stats-store'; diff --git a/src/lib/features/project/project-controller.ts b/src/lib/features/project/project-controller.ts index dc0b6b203b..d008f4bd95 100644 --- a/src/lib/features/project/project-controller.ts +++ b/src/lib/features/project/project-controller.ts @@ -11,7 +11,7 @@ import { import ProjectFeaturesController from '../feature-toggle/feature-toggle-controller'; import EnvironmentsController from '../project-environments/environments'; import ProjectHealthReport from '../../routes/admin-api/project/health-report'; -import ProjectService from '../../services/project-service'; +import ProjectService from './project-service'; import VariantsController from '../../routes/admin-api/project/variants'; import { createResponseSchema, @@ -267,7 +267,7 @@ export default class ProjectController extends Controller { 200, res, projectApplicationsSchema.$id, - applications, + serializeDates(applications), ); } } diff --git a/src/lib/services/project-service.ts b/src/lib/features/project/project-service.ts similarity index 95% rename from src/lib/services/project-service.ts rename to src/lib/features/project/project-service.ts index b53babc286..a4f0120ea1 100644 --- a/src/lib/services/project-service.ts +++ b/src/lib/features/project/project-service.ts @@ -1,12 +1,12 @@ import { subDays } from 'date-fns'; import { ValidationError } from 'joi'; -import { IUser } from '../types/user'; -import { AccessService, AccessWithRoles } from './access-service'; -import NameExistsError from '../error/name-exists-error'; -import InvalidOperationError from '../error/invalid-operation-error'; -import { nameType } from '../routes/util'; -import { projectSchema } from './project-schema'; -import NotFoundError from '../error/notfound-error'; +import { IUser } from '../../types/user'; +import { AccessService, AccessWithRoles } from '../../services/access-service'; +import NameExistsError from '../../error/name-exists-error'; +import InvalidOperationError from '../../error/invalid-operation-error'; +import { nameType } from '../../routes/util'; +import { projectSchema } from '../../services/project-schema'; +import NotFoundError from '../../error/notfound-error'; import { DEFAULT_PROJECT, FeatureToggle, @@ -42,34 +42,32 @@ import { IProjectUpdate, IProjectHealth, SYSTEM_USER, -} from '../types'; -import { - IProjectQuery, - IProjectEnterpriseSettingsUpdate, + IProjectApplication, IProjectStore, -} from '../types/stores/project-store'; +} from '../../types'; import { IProjectAccessModel, IRoleDescriptor, -} from '../types/stores/access-store'; -import FeatureToggleService from '../features/feature-toggle/feature-toggle-service'; -import IncompatibleProjectError from '../error/incompatible-project-error'; -import ProjectWithoutOwnerError from '../error/project-without-owner-error'; -import { arraysHaveSameItems } from '../util'; -import { GroupService } from './group-service'; -import { IGroupRole } from '../types/group'; -import { FavoritesService } from './favorites-service'; -import { calculateAverageTimeToProd } from '../features/feature-toggle/time-to-production/time-to-production'; -import { IProjectStatsStore } from '../types/stores/project-stats-store-type'; -import { uniqueByKey } from '../util/unique'; -import { BadDataError, PermissionError } from '../error'; +} from '../../types/stores/access-store'; +import FeatureToggleService from '../feature-toggle/feature-toggle-service'; +import IncompatibleProjectError from '../../error/incompatible-project-error'; +import ProjectWithoutOwnerError from '../../error/project-without-owner-error'; +import { arraysHaveSameItems } from '../../util'; +import { GroupService } from '../../services/group-service'; +import { IGroupRole } from '../../types/group'; +import { FavoritesService } from '../../services/favorites-service'; +import { calculateAverageTimeToProd } from '../feature-toggle/time-to-production/time-to-production'; +import { IProjectStatsStore } from '../../types/stores/project-stats-store-type'; +import { uniqueByKey } from '../../util/unique'; +import { BadDataError, PermissionError } from '../../error'; +import { ProjectDoraMetricsSchema } from '../../openapi'; +import { checkFeatureNamingData } from '../feature-naming-pattern/feature-naming-validation'; +import { IPrivateProjectChecker } from '../private-project/privateProjectCheckerType'; +import EventService from '../events/event-service'; import { - ProjectDoraMetricsSchema, - ProjectApplicationsSchema, -} from '../openapi'; -import { checkFeatureNamingData } from '../features/feature-naming-pattern/feature-naming-validation'; -import { IPrivateProjectChecker } from '../features/private-project/privateProjectCheckerType'; -import EventService from '../features/events/event-service'; + IProjectEnterpriseSettingsUpdate, + IProjectQuery, +} from './project-store-type'; const getCreatedBy = (user: IUser) => user.email || user.username || 'unknown'; @@ -903,10 +901,10 @@ export default class ProjectService { }; } - async getApplications( - projectId: string, - ): Promise { - return []; + async getApplications(projectId: string): Promise { + const applications = + await this.projectStore.getApplicationsByProject(projectId); + return applications; } async changeRole( diff --git a/src/lib/types/stores/project-store.ts b/src/lib/features/project/project-store-type.ts similarity index 93% rename from src/lib/types/stores/project-store.ts rename to src/lib/features/project/project-store-type.ts index f084dcc361..4ad439e175 100644 --- a/src/lib/types/stores/project-store.ts +++ b/src/lib/features/project/project-store-type.ts @@ -2,15 +2,16 @@ import { IEnvironmentProjectLink, IProjectMembersCount, ProjectModeCount, -} from '../../db/project-store'; +} from './project-store'; import { IEnvironment, IFeatureNaming, IProject, + IProjectApplication, IProjectWithCount, ProjectMode, -} from '../model'; -import { Store } from './store'; +} from '../../types/model'; +import { Store } from '../../types/stores/store'; import { CreateFeatureStrategySchema } from '../../openapi'; export interface IProjectInsert { @@ -121,4 +122,5 @@ export interface IProjectStore extends Store { isFeatureLimitReached(id: string): Promise; getProjectModeCounts(): Promise; + getApplicationsByProject(projectId: string): Promise; } diff --git a/src/lib/db/project-store.ts b/src/lib/features/project/project-store.ts similarity index 86% rename from src/lib/db/project-store.ts rename to src/lib/features/project/project-store.ts index 45ed15ab4d..36b00271ed 100644 --- a/src/lib/db/project-store.ts +++ b/src/lib/features/project/project-store.ts @@ -1,15 +1,17 @@ import { Knex } from 'knex'; -import { Logger, LogProvider } from '../logger'; +import { Logger, LogProvider } from '../../logger'; -import NotFoundError from '../error/notfound-error'; +import NotFoundError from '../../error/notfound-error'; import { IEnvironment, IFlagResolver, IProject, + IProjectApplication, + IProjectApplicationSdk, IProjectUpdate, IProjectWithCount, ProjectMode, -} from '../types'; +} from '../../types'; import { IProjectHealthUpdate, IProjectInsert, @@ -18,14 +20,14 @@ import { IProjectEnterpriseSettingsUpdate, IProjectStore, ProjectEnvironment, -} from '../types/stores/project-store'; -import { DEFAULT_ENV } from '../util'; -import metricsHelper from '../util/metrics-helper'; -import { DB_TIME } from '../metric-events'; +} from '../../features/project/project-store-type'; +import { DEFAULT_ENV } from '../../util'; +import metricsHelper from '../../util/metrics-helper'; +import { DB_TIME } from '../../metric-events'; import EventEmitter from 'events'; -import { Db } from './db'; +import { Db } from '../../db/db'; import Raw = Knex.Raw; -import { CreateFeatureStrategySchema } from '../openapi'; +import { CreateFeatureStrategySchema } from '../../openapi'; const COLUMNS = [ 'id', @@ -110,11 +112,12 @@ class ProjectStore implements IProjectStore { async isFeatureLimitReached(id: string): Promise { const result = await this.db.raw( `SELECT EXISTS(SELECT 1 - FROM project_settings - LEFT JOIN features ON project_settings.project = features.project - WHERE project_settings.project = ? AND features.archived_at IS NULL - GROUP BY project_settings.project - HAVING project_settings.feature_limit <= COUNT(features.project)) AS present`, + FROM project_settings + LEFT JOIN features ON project_settings.project = features.project + WHERE project_settings.project = ? + AND features.archived_at IS NULL + GROUP BY project_settings.project + HAVING project_settings.feature_limit <= COUNT(features.project)) AS present`, [id], ); const { present } = result.rows[0]; @@ -254,9 +257,10 @@ class ProjectStore implements IProjectStore { } async updateHealth(healthUpdate: IProjectHealthUpdate): Promise { - await this.db(TABLE) - .where({ id: healthUpdate.id }) - .update({ health: healthUpdate.health, updated_at: new Date() }); + await this.db(TABLE).where({ id: healthUpdate.id }).update({ + health: healthUpdate.health, + updated_at: new Date(), + }); } async create( @@ -575,13 +579,45 @@ class ProjectStore implements IProjectStore { return Number(members.count); } + async getApplicationsByProject( + projectId: string, + ): Promise { + const query = this.db + .with('applications', (qb) => { + qb.select('project', 'app_name', 'environment') + .distinct() + .from('client_metrics_env as cme') + .leftJoin('features as f', 'cme.feature_name', 'f.name') + .where('project', projectId); + }) + .select( + 'a.app_name', + 'a.environment', + 'ci.instance_id', + 'ci.sdk_version', + ) + .from('applications as a') + .leftJoin('client_instances as ci', function () { + this.on('ci.app_name', 'a.app_name').andOn( + 'ci.environment', + 'a.environment', + ); + }); + const rows = await query; + const applications = this.getAggregatedApplicationsData(rows); + return applications; + } + async getDefaultStrategy( projectId: string, environment: string, ): Promise { const rows = await this.db(PROJECT_ENVIRONMENTS) .select('default_strategy') - .where({ project_id: projectId, environment_name: environment }); + .where({ + project_id: projectId, + environment_name: environment, + }); return rows.length > 0 ? rows[0].default_strategy : null; } @@ -595,7 +631,10 @@ class ProjectStore implements IProjectStore { .update({ default_strategy: strategy, }) - .where({ project_id: projectId, environment_name: environment }) + .where({ + project_id: projectId, + environment_name: environment, + }) .returning('default_strategy'); return rows[0].default_strategy; @@ -680,6 +719,43 @@ class ProjectStore implements IProjectStore { : row.default_strategy, }; } + + getAggregatedApplicationsData(rows): IProjectApplication[] { + const entriesMap: Map = new Map(); + const orderedEntries: IProjectApplication[] = []; + + const getSdk = (sdkParts: string[]): IProjectApplicationSdk => { + return { + name: sdkParts[0], + versions: [sdkParts[1]], + }; + }; + + rows.forEach((row) => { + let entry = entriesMap.get(row.app_name); + const sdkParts = row.sdk_version.split(':'); + + if (!entry) { + entry = { + name: row.app_name, + environments: [row.environment], + instances: [row.instance_id], + sdks: [getSdk(sdkParts)], + }; + entriesMap.set(row.feature_name, entry); + orderedEntries.push(entry); + } + + const sdk = entry.sdks.find((sdk) => sdk.name === sdkParts[0]); + if (!sdk) { + entry.sdks.push(getSdk(sdkParts)); + } else { + sdk.versions.push(sdkParts[1]); + } + }); + + return orderedEntries; + } } export default ProjectStore; diff --git a/src/lib/openapi/index.ts b/src/lib/openapi/index.ts index 0af6560b07..8e90e6758f 100644 --- a/src/lib/openapi/index.ts +++ b/src/lib/openapi/index.ts @@ -196,6 +196,7 @@ import { segmentStrategiesSchema } from './spec/segment-strategies-schema'; import { featureDependenciesSchema } from './spec/feature-dependencies-schema'; import { projectApplicationsSchema } from './spec/project-applications-schema'; import { projectApplicationSchema } from './spec/project-application-schema'; +import { projectApplicationSdkSchema } from './spec/project-application-sdk-schema'; // Schemas must have an $id property on the form "#/components/schemas/mySchema". export type SchemaId = (typeof schemas)[keyof typeof schemas]['$id']; @@ -326,6 +327,7 @@ export const schemas: UnleashSchemas = { playgroundStrategySchema, profileSchema, projectApplicationSchema, + projectApplicationSdkSchema, projectApplicationsSchema, projectEnvironmentSchema, projectSchema, diff --git a/src/lib/openapi/spec/project-application-schema.ts b/src/lib/openapi/spec/project-application-schema.ts index ae2faf57aa..97a2c8672e 100644 --- a/src/lib/openapi/spec/project-application-schema.ts +++ b/src/lib/openapi/spec/project-application-schema.ts @@ -1,34 +1,45 @@ import { FromSchema } from 'json-schema-to-ts'; +import { projectApplicationSdkSchema } from './project-application-sdk-schema'; export const projectApplicationSchema = { $id: '#/components/schemas/projectApplicationSchema', type: 'object', additionalProperties: false, - required: ['appName', 'instanceId', 'sdkVersion', 'environment'], + required: ['name', 'environments', 'instances', 'sdks'], description: 'A project application instance.', properties: { - appName: { + name: { type: 'string', description: 'Name of the application that is using the SDK. This is the same as the appName in the SDK configuration.', }, - instanceId: { - type: 'string', + environments: { description: - 'The unique identifier of the application instance. This is the same as the instanceId in the SDK configuration', + 'The environments that the application is using. This is the same as the environment in the SDK configuration.', + type: 'array', + items: { + type: 'string', + }, + example: ['development', 'production'], }, - sdkVersion: { - type: 'string', + instances: { description: - 'The version of the SDK that is being used by the application', + 'The instances of the application that are using the SDK.', + type: 'array', + items: { + type: 'string', + }, + example: ['prod-b4ca', 'prod-ac8a'], }, - environment: { - type: 'string', - description: - 'The environment that the application is running in. This is coming from token configured in the SDK configuration.', + sdks: { + type: 'array', + description: 'The SDKs that the application is using.', + items: { + $ref: '#/components/schemas/projectApplicationSdkSchema', + }, }, }, - components: {}, + components: { projectApplicationSdkSchema }, } as const; export type ProjectApplicationSchema = FromSchema< diff --git a/src/lib/openapi/spec/project-application-sdk-schema.ts b/src/lib/openapi/spec/project-application-sdk-schema.ts new file mode 100644 index 0000000000..1cc064f97f --- /dev/null +++ b/src/lib/openapi/spec/project-application-sdk-schema.ts @@ -0,0 +1,31 @@ +import { FromSchema } from 'json-schema-to-ts'; + +export const projectApplicationSdkSchema = { + $id: '#/components/schemas/projectApplicationSdkSchema', + type: 'object', + additionalProperties: false, + required: ['name', 'versions'], + description: 'A project application instance SDK.', + properties: { + name: { + type: 'string', + description: + 'Name of the SDK package that the application is using.', + example: 'unleash-client-node', + }, + versions: { + description: + 'The versions of the SDK that the application is using.', + type: 'array', + items: { + type: 'string', + }, + example: ['4.1.1'], + }, + }, + components: {}, +} as const; + +export type ProjectApplicationSdkSchema = FromSchema< + typeof projectApplicationSdkSchema +>; diff --git a/src/lib/openapi/spec/project-applications-schema.test.ts b/src/lib/openapi/spec/project-applications-schema.test.ts index 98b44e076d..cd2258c3de 100644 --- a/src/lib/openapi/spec/project-applications-schema.test.ts +++ b/src/lib/openapi/spec/project-applications-schema.test.ts @@ -4,10 +4,15 @@ import { ProjectApplicationsSchema } from './project-applications-schema'; test('projectApplicationsSchema', () => { const data: ProjectApplicationsSchema = [ { - appName: 'my-weba-app', - instanceId: 'app1:3:4', - sdkVersion: '4.1.1', - environment: 'production', + name: 'my-weba-app', + environments: ['development', 'production'], + instances: ['instance-414122'], + sdks: [ + { + name: 'unleash-client-node', + versions: ['4.1.1'], + }, + ], }, ]; diff --git a/src/lib/openapi/spec/project-applications-schema.ts b/src/lib/openapi/spec/project-applications-schema.ts index ef0e990a3e..0a1ad31064 100644 --- a/src/lib/openapi/spec/project-applications-schema.ts +++ b/src/lib/openapi/spec/project-applications-schema.ts @@ -1,5 +1,6 @@ import { FromSchema } from 'json-schema-to-ts'; import { projectApplicationSchema } from './project-application-schema'; +import { projectApplicationSdkSchema } from './project-application-sdk-schema'; export const projectApplicationsSchema = { $id: '#/components/schemas/projectApplicationsSchema', @@ -11,6 +12,7 @@ export const projectApplicationsSchema = { components: { schemas: { projectApplicationSchema, + projectApplicationSdkSchema, }, }, } as const; diff --git a/src/lib/routes/admin-api/user/user.ts b/src/lib/routes/admin-api/user/user.ts index 5a99da2c25..1cc3a0cfb9 100644 --- a/src/lib/routes/admin-api/user/user.ts +++ b/src/lib/routes/admin-api/user/user.ts @@ -23,7 +23,7 @@ import { profileSchema, ProfileSchema, } from '../../../openapi/spec/profile-schema'; -import ProjectService from '../../../services/project-service'; +import ProjectService from '../../../features/project/project-service'; class UserController extends Controller { private accessService: AccessService; diff --git a/src/lib/services/context-service.ts b/src/lib/services/context-service.ts index 73bc807e93..2d47d552f8 100644 --- a/src/lib/services/context-service.ts +++ b/src/lib/services/context-service.ts @@ -4,7 +4,7 @@ import { IContextFieldDto, IContextFieldStore, } from '../types/stores/context-field-store'; -import { IProjectStore } from '../types/stores/project-store'; +import { IProjectStore } from '../features/project/project-store-type'; import { IFeatureStrategiesStore, IUnleashStores } from '../types/stores'; import { IUnleashConfig } from '../types/option'; import { ContextFieldStrategiesSchema } from '../openapi/spec/context-field-strategies-schema'; diff --git a/src/lib/services/index.ts b/src/lib/services/index.ts index a6bdeb56f4..58c7b97963 100644 --- a/src/lib/services/index.ts +++ b/src/lib/services/index.ts @@ -3,7 +3,7 @@ import FeatureTypeService from './feature-type-service'; import EventService from '../features/events/event-service'; import HealthService from './health-service'; -import ProjectService from './project-service'; +import ProjectService from '../features/project/project-service'; import StateService from './state-service'; import ClientInstanceService from '../features/metrics/instance/instance-service'; import ClientMetricsServiceV2 from '../features/metrics/client-metrics/metrics-service-v2'; diff --git a/src/lib/services/project-health-service.ts b/src/lib/services/project-health-service.ts index bdee134713..bc937cd017 100644 --- a/src/lib/services/project-health-service.ts +++ b/src/lib/services/project-health-service.ts @@ -4,8 +4,8 @@ import { Logger } from '../logger'; import type { IProject, IProjectHealthReport } from '../types/model'; import type { IFeatureToggleStore } from '../features/feature-toggle/types/feature-toggle-store-type'; import type { IFeatureTypeStore } from '../types/stores/feature-type-store'; -import type { IProjectStore } from '../types/stores/project-store'; -import ProjectService from './project-service'; +import type { IProjectStore } from '../features/project/project-store-type'; +import ProjectService from '../features/project/project-service'; import { calculateHealthRating, calculateProjectHealth, diff --git a/src/lib/services/state-service.ts b/src/lib/services/state-service.ts index 9a099ec405..0ab906efe6 100644 --- a/src/lib/services/state-service.ts +++ b/src/lib/services/state-service.ts @@ -36,7 +36,7 @@ import { IFeatureTag, IFeatureTagStore, } from '../types/stores/feature-tag-store'; -import { IProjectStore } from '../types/stores/project-store'; +import { IProjectStore } from '../features/project/project-store-type'; import { ITagType, ITagTypeStore, diff --git a/src/lib/types/model.ts b/src/lib/types/model.ts index be9c61be7d..6231461a2e 100644 --- a/src/lib/types/model.ts +++ b/src/lib/types/model.ts @@ -3,9 +3,9 @@ import { LogProvider } from '../logger'; import { IRole } from './stores/access-store'; import { IUser } from './user'; import { ALL_OPERATORS } from '../util'; -import { IProjectStats } from '../services/project-service'; +import { IProjectStats } from '../features/project/project-service'; import { CreateFeatureStrategySchema } from '../openapi'; -import { ProjectEnvironment } from './stores/project-store'; +import { ProjectEnvironment } from '../features/project/project-store-type'; export type Operator = (typeof ALL_OPERATORS)[number]; @@ -478,6 +478,18 @@ export interface IProject { featureNaming?: IFeatureNaming; } +export interface IProjectApplication { + name: string; + environments: string[]; + instances: string[]; + sdks: IProjectApplicationSdk[]; +} + +export interface IProjectApplicationSdk { + name: string; + versions: string[]; +} + // mimics UpdateProjectSchema export interface IProjectUpdate { id: string; diff --git a/src/lib/types/services.ts b/src/lib/types/services.ts index 34930cfed6..be9933d88f 100644 --- a/src/lib/types/services.ts +++ b/src/lib/types/services.ts @@ -1,6 +1,6 @@ import { AccessService } from '../services/access-service'; import AddonService from '../services/addon-service'; -import ProjectService from '../services/project-service'; +import ProjectService from '../features/project/project-service'; import StateService from '../services/state-service'; import StrategyService from '../services/strategy-service'; import TagTypeService from '../features/tag-type/tag-type-service'; diff --git a/src/lib/types/stores.ts b/src/lib/types/stores.ts index ab30fc1b7f..d3d8b86a53 100644 --- a/src/lib/types/stores.ts +++ b/src/lib/types/stores.ts @@ -1,4 +1,4 @@ -import { IProjectStore } from './stores/project-store'; +import { IProjectStore } from '../features/project/project-store-type'; import { IEventStore } from './stores/event-store'; import { IFeatureTypeStore } from './stores/feature-type-store'; import { IStrategyStore } from './stores/strategy-store'; diff --git a/src/lib/types/stores/project-stats-store-type.ts b/src/lib/types/stores/project-stats-store-type.ts index f91a2ce3ea..baf809fa3a 100644 --- a/src/lib/types/stores/project-stats-store-type.ts +++ b/src/lib/types/stores/project-stats-store-type.ts @@ -1,5 +1,5 @@ import { DoraFeaturesSchema } from '../../openapi'; -import { IProjectStats } from '../../services/project-service'; +import { IProjectStats } from '../../features/project/project-service'; export interface ICreateEnabledDates { created: Date; diff --git a/src/test/e2e/services/api-token-service.e2e.test.ts b/src/test/e2e/services/api-token-service.e2e.test.ts index 4e8daebc4a..41c891876f 100644 --- a/src/test/e2e/services/api-token-service.e2e.test.ts +++ b/src/test/e2e/services/api-token-service.e2e.test.ts @@ -5,7 +5,7 @@ import { createTestConfig } from '../../config/test-config'; import { ApiTokenType, IApiToken } from '../../../lib/types/models/api-token'; import { DEFAULT_ENV } from '../../../lib/util/constants'; import { addDays, subDays } from 'date-fns'; -import ProjectService from '../../../lib/services/project-service'; +import ProjectService from '../../../lib/features/project/project-service'; import { createProjectService } from '../../../lib/features'; import { EventService } from '../../../lib/services'; import { IUnleashStores } from '../../../lib/types'; diff --git a/src/test/e2e/services/project-service.e2e.test.ts b/src/test/e2e/services/project-service.e2e.test.ts index 0097b5e220..a8ac148ec3 100644 --- a/src/test/e2e/services/project-service.e2e.test.ts +++ b/src/test/e2e/services/project-service.e2e.test.ts @@ -1,7 +1,7 @@ import dbInit, { ITestDb } from '../helpers/database-init'; import getLogger from '../../fixtures/no-logger'; import FeatureToggleService from '../../../lib/features/feature-toggle/feature-toggle-service'; -import ProjectService from '../../../lib/services/project-service'; +import ProjectService from '../../../lib/features/project/project-service'; import { AccessService } from '../../../lib/services/access-service'; import { MOVE_FEATURE_TOGGLE } from '../../../lib/types/permissions'; import { createTestConfig } from '../../config/test-config'; diff --git a/src/test/e2e/stores/project-store.e2e.test.ts b/src/test/e2e/stores/project-store.e2e.test.ts index c6dc004353..734f2ccfda 100644 --- a/src/test/e2e/stores/project-store.e2e.test.ts +++ b/src/test/e2e/stores/project-store.e2e.test.ts @@ -1,12 +1,9 @@ -import { - IProjectInsert, - IProjectStore, -} from '../../../lib/types/stores/project-store'; import { IEnvironmentStore } from '../../../lib/features/project-environments/environment-store-type'; import dbInit, { ITestDb } from '../helpers/database-init'; import getLogger from '../../fixtures/no-logger'; -import { IUnleashStores } from '../../../lib/types'; +import { IProjectStore, IUnleashStores } from '../../../lib/types'; +import { IProjectInsert } from '../../../lib/features/project/project-store-type'; let stores: IUnleashStores; let db: ITestDb; diff --git a/src/test/fixtures/fake-project-stats-store.ts b/src/test/fixtures/fake-project-stats-store.ts index 38206eaf11..620fa27232 100644 --- a/src/test/fixtures/fake-project-stats-store.ts +++ b/src/test/fixtures/fake-project-stats-store.ts @@ -1,4 +1,4 @@ -import { IProjectStats } from '../../lib/services/project-service'; +import { IProjectStats } from '../../lib/features/project/project-service'; import { ICreateEnabledDates, IProjectStatsStore, diff --git a/src/test/fixtures/fake-project-store.ts b/src/test/fixtures/fake-project-store.ts index 59b10cccab..3415034faa 100644 --- a/src/test/fixtures/fake-project-store.ts +++ b/src/test/fixtures/fake-project-store.ts @@ -1,17 +1,22 @@ import { - IProjectHealthUpdate, - IProjectInsert, + IEnvironment, + IProject, + IProjectApplication, IProjectStore, - ProjectEnvironment, -} from '../../lib/types/stores/project-store'; -import { IEnvironment, IProject, IProjectWithCount } from '../../lib/types'; + IProjectWithCount, +} from '../../lib/types'; import NotFoundError from '../../lib/error/notfound-error'; import { IEnvironmentProjectLink, IProjectMembersCount, ProjectModeCount, -} from '../../lib/db/project-store'; +} from '../../lib/features/project/project-store'; import { CreateFeatureStrategySchema } from '../../lib/openapi'; +import { + IProjectHealthUpdate, + IProjectInsert, + ProjectEnvironment, +} from '../../lib/features/project/project-store-type'; export default class FakeProjectStore implements IProjectStore { projects: IProject[] = []; @@ -202,4 +207,10 @@ export default class FakeProjectStore implements IProjectStore { updateProjectEnterpriseSettings(update: IProjectInsert): Promise { throw new Error('Method not implemented.'); } + // eslint-disable-next-line @typescript-eslint/no-unused-vars + getApplicationsByProject( + projectId: string, + ): Promise { + throw new Error('Method not implemented.'); + } }