diff --git a/src/lib/features/project/project-owners-read-model.type.ts b/src/lib/features/project/project-owners-read-model.type.ts index 8a68bc5e4f88..843f2208c234 100644 --- a/src/lib/features/project/project-owners-read-model.type.ts +++ b/src/lib/features/project/project-owners-read-model.type.ts @@ -1,4 +1,4 @@ -import type { TransitionalProjectData } from './project-read-model-type'; +import type { ProjectForUi } from './project-read-model-type'; export type SystemOwner = { ownerType: 'system' }; export type UserProjectOwner = { @@ -17,13 +17,13 @@ type ProjectOwners = export type ProjectOwnersDictionary = Record; -export type IProjectForUiWithOwners = TransitionalProjectData & { +export type IProjectForUiWithOwners = ProjectForUi & { owners: ProjectOwners; }; export interface IProjectOwnersReadModel { addOwners( - projects: TransitionalProjectData[], + projects: ProjectForUi[], anonymizeProjectOwners?: boolean, ): Promise; } diff --git a/src/lib/features/project/project-read-model-type.ts b/src/lib/features/project/project-read-model-type.ts index 4487d31f0839..0f131bf005e6 100644 --- a/src/lib/features/project/project-read-model-type.ts +++ b/src/lib/features/project/project-read-model-type.ts @@ -1,4 +1,4 @@ -import type { IProjectWithCount, ProjectMode } from '../../types'; +import type { ProjectMode } from '../../types'; import type { IProjectQuery } from './project-store-type'; export type ProjectForUi = { @@ -16,9 +16,6 @@ export type ProjectForUi = { lastUpdatedAt: Date | null; }; -// @todo remove with flag useProjectReadModel -export type TransitionalProjectData = ProjectForUi | IProjectWithCount; - export type ProjectForInsights = { id: string; health: number; diff --git a/src/lib/features/project/project-service.e2e.test.ts b/src/lib/features/project/project-service.e2e.test.ts index bdd3cda35f37..1171f78ee5cf 100644 --- a/src/lib/features/project/project-service.e2e.test.ts +++ b/src/lib/features/project/project-service.e2e.test.ts @@ -82,9 +82,7 @@ beforeAll(async () => { await stores.accessStore.addUserToRole(opsUser.id, 1, ''); const config = createTestConfig({ getLogger, - experimental: { - flags: { useProjectReadModel: true }, - }, + experimental: {}, }); eventService = createEventsService(db.rawDatabase, config); accessService = createAccessService(db.rawDatabase, config); diff --git a/src/lib/features/project/project-service.ts b/src/lib/features/project/project-service.ts index c476c28647d9..fbcec958bcba 100644 --- a/src/lib/features/project/project-service.ts +++ b/src/lib/features/project/project-service.ts @@ -88,7 +88,7 @@ import type { IProjectFlagCreatorsReadModel } from './project-flag-creators-read import { throwExceedsLimitError } from '../../error/exceeds-limit-error'; import type EventEmitter from 'events'; import type { ApiTokenService } from '../../services/api-token-service'; -import type { TransitionalProjectData } from './project-read-model-type'; +import type { ProjectForUi } from './project-read-model-type'; import { canGrantProjectRole } from './can-grant-project-role'; type Days = number; @@ -232,10 +232,9 @@ export default class ProjectService { async getProjects( query?: IProjectQuery, userId?: number, - ): Promise { - const getProjects = this.flagResolver.isEnabled('useProjectReadModel') - ? () => this.projectReadModel.getProjectsForAdminUi(query, userId) - : () => this.projectStore.getProjectsWithCounts(query, userId); + ): Promise { + const getProjects = () => + this.projectReadModel.getProjectsForAdminUi(query, userId); const projects = await getProjects(); @@ -257,8 +256,8 @@ export default class ProjectService { } async addOwnersToProjects( - projects: TransitionalProjectData[], - ): Promise { + projects: ProjectForUi[], + ): Promise { const anonymizeProjectOwners = this.flagResolver.isEnabled( 'anonymizeProjectOwners', ); diff --git a/src/lib/features/project/project-store-type.ts b/src/lib/features/project/project-store-type.ts index 3197c5a265b2..c5076e86ca10 100644 --- a/src/lib/features/project/project-store-type.ts +++ b/src/lib/features/project/project-store-type.ts @@ -7,7 +7,6 @@ import type { IFeatureNaming, IProject, IProjectApplications, - IProjectWithCount, ProjectMode, } from '../../types/model'; import type { Store } from '../../types/stores/store'; @@ -99,14 +98,6 @@ export interface IProjectStore extends Store { getProjectsByUser(userId: number): Promise; - /** - * @deprecated Use the appropriate method in the project read model instead. - */ - getProjectsWithCounts( - query?: IProjectQuery, - userId?: number, - ): Promise; - count(): Promise; getAll(query?: IProjectQuery): Promise; diff --git a/src/lib/features/project/project-store.ts b/src/lib/features/project/project-store.ts index d9f7162c6560..7c0fc2188771 100644 --- a/src/lib/features/project/project-store.ts +++ b/src/lib/features/project/project-store.ts @@ -1,4 +1,3 @@ -import { Knex } from 'knex'; import type { Logger, LogProvider } from '../../logger'; import NotFoundError from '../../error/notfound-error'; @@ -9,7 +8,6 @@ import type { IProjectApplication, IProjectApplications, IProjectUpdate, - IProjectWithCount, ProjectMode, } from '../../types'; import type { @@ -27,7 +25,6 @@ import metricsHelper from '../../util/metrics-helper'; import { DB_TIME } from '../../metric-events'; import type EventEmitter from 'events'; import type { Db } from '../../db/db'; -import Raw = Knex.Raw; import type { CreateFeatureStrategySchema } from '../../openapi'; import { applySearchFilters } from '../feature-search/search-utils'; @@ -117,114 +114,6 @@ class ProjectStore implements IProjectStore { return present; } - async getProjectsWithCounts( - query?: IProjectQuery, - userId?: number, - ): Promise { - const projectTimer = this.timer('getProjectsWithCount'); - let projects = this.db(TABLE) - .leftJoin('features', 'features.project', 'projects.id') - .leftJoin( - 'project_settings', - 'project_settings.project', - 'projects.id', - ) - .leftJoin('project_stats', 'project_stats.project', 'projects.id') - .orderBy('projects.name', 'asc'); - - if (query?.archived === true) { - projects = projects.whereNot(`${TABLE}.archived_at`, null); - } else { - projects = projects.where(`${TABLE}.archived_at`, null); - } - - if (query?.id) { - projects = projects.where(`${TABLE}.id`, query.id); - } - - let selectColumns = [ - this.db.raw( - 'projects.id, projects.name, projects.description, projects.health, projects.updated_at, projects.created_at, ' + - 'count(features.name) FILTER (WHERE features.archived_at is null) AS number_of_features, ' + - 'count(features.name) FILTER (WHERE features.archived_at is null and features.stale IS TRUE) AS stale_feature_count, ' + - 'count(features.name) FILTER (WHERE features.archived_at is null and features.potentially_stale IS TRUE) AS potentially_stale_feature_count', - ), - 'project_settings.default_stickiness', - 'project_settings.project_mode', - 'project_stats.avg_time_to_prod_current_window', - 'projects.archived_at', - ] as (string | Raw)[]; - - let groupByColumns = [ - 'projects.id', - 'project_settings.default_stickiness', - 'project_settings.project_mode', - 'project_stats.avg_time_to_prod_current_window', - ]; - - if (userId) { - projects = projects.leftJoin(`favorite_projects`, function () { - this.on('favorite_projects.project', 'projects.id').andOnVal( - 'favorite_projects.user_id', - '=', - userId, - ); - }); - selectColumns = [ - ...selectColumns, - this.db.raw( - 'favorite_projects.project is not null as favorite', - ), - ]; - groupByColumns = [...groupByColumns, 'favorite_projects.project']; - } - - const projectAndFeatureCount = await projects - .select(selectColumns) - .groupBy(groupByColumns); - - const projectsWithFeatureCount = projectAndFeatureCount.map( - this.mapProjectWithCountRow, - ); - projectTimer(); - const memberTimer = this.timer('getMemberCount'); - - const memberCount = await this.getMembersCount(); - memberTimer(); - const memberMap = new Map( - memberCount.map((c) => [c.project, Number(c.count)]), - ); - - return projectsWithFeatureCount.map((projectWithCount) => { - return { - ...projectWithCount, - memberCount: memberMap.get(projectWithCount.id) || 0, - }; - }); - } - - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types - mapProjectWithCountRow(row): IProjectWithCount { - return { - name: row.name, - id: row.id, - description: row.description, - health: row.health, - favorite: row.favorite, - featureCount: Number(row.number_of_features) || 0, - staleFeatureCount: Number(row.stale_feature_count) || 0, - potentiallyStaleFeatureCount: - Number(row.potentially_stale_feature_count) || 0, - memberCount: Number(row.number_of_users) || 0, - updatedAt: row.updated_at, - createdAt: row.created_at, - archivedAt: row.archived_at, - mode: row.project_mode || 'open', - defaultStickiness: row.default_stickiness || 'default', - avgTimeToProduction: row.avg_time_to_prod_current_window || 0, - }; - } - async getAll(query: IProjectQuery = {}): Promise { let projects = this.db .select(COLUMNS) diff --git a/src/lib/types/experimental.ts b/src/lib/types/experimental.ts index 764e8fd23acc..bf245a5c1df5 100644 --- a/src/lib/types/experimental.ts +++ b/src/lib/types/experimental.ts @@ -56,7 +56,6 @@ export type IFlagKey = | 'extendedMetrics' | 'removeUnsafeInlineStyleSrc' | 'originMiddleware' - | 'useProjectReadModel' | 'addonUsageMetrics' | 'onboardingMetrics' | 'onboardingUI' @@ -279,10 +278,6 @@ const flags: IFlags = { process.env.UNLEASH_EXPERIMENTAL_ORIGIN_MIDDLEWARE, false, ), - useProjectReadModel: parseEnvVarBoolean( - process.env.UNLEASH_EXPERIMENTAL_USE_PROJECT_READ_MODEL, - false, - ), addonUsageMetrics: parseEnvVarBoolean( process.env.UNLEASH_EXPERIMENTAL_ADDON_USAGE_METRICS, false, diff --git a/src/lib/types/model.ts b/src/lib/types/model.ts index 5f8799630152..6a8ddbb7c767 100644 --- a/src/lib/types/model.ts +++ b/src/lib/types/model.ts @@ -574,17 +574,6 @@ export interface ICustomRole extends IRole { description: string; } -// @deprecated Remove with flag useProjectReadModel -export interface IProjectWithCount extends IProject { - featureCount: number; - staleFeatureCount: number; - potentiallyStaleFeatureCount: number; - memberCount: number; - favorite?: boolean; - avgTimeToProduction: number; - archivedAt?: Date; -} - export interface IClientSegment { id: number; constraints: IConstraint[]; diff --git a/src/server-dev.ts b/src/server-dev.ts index 934e616af719..3517154721a7 100644 --- a/src/server-dev.ts +++ b/src/server-dev.ts @@ -51,7 +51,6 @@ process.nextTick(async () => { enableLegacyVariants: false, extendedMetrics: true, originMiddleware: true, - useProjectReadModel: true, addonUsageMetrics: true, onboardingMetrics: true, onboardingUI: true, diff --git a/src/test/fixtures/fake-project-store.ts b/src/test/fixtures/fake-project-store.ts index a3b4965f4170..04ee450db85b 100644 --- a/src/test/fixtures/fake-project-store.ts +++ b/src/test/fixtures/fake-project-store.ts @@ -3,7 +3,6 @@ import type { IProject, IProjectApplications, IProjectStore, - IProjectWithCount, } from '../../lib/types'; import NotFoundError from '../../lib/error/notfound-error'; import type { @@ -15,7 +14,6 @@ import type { IProjectApplicationsSearchParams, IProjectHealthUpdate, IProjectInsert, - IProjectQuery, ProjectEnvironment, } from '../../lib/features/project/project-store-type'; @@ -48,27 +46,6 @@ export default class FakeProjectStore implements IProjectStore { this.projectEnvironment.set(id, environments); } - async getProjectsWithCounts( - query?: IProjectQuery, - ): Promise { - return this.projects - .filter((project) => - query?.archived - ? project.archivedAt !== null - : project.archivedAt === null, - ) - .map((project) => { - return { - ...project, - memberCount: 0, - featureCount: 0, - staleFeatureCount: 0, - potentiallyStaleFeatureCount: 0, - avgTimeToProduction: 0, - }; - }); - } - private createInternal(project: IProjectInsert): IProject { const newProj: ArchivableProject = { ...project,