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

[Gitar] Cleaning up stale flag: useProjectReadModel with value true (#8211)

Co-authored-by: Gitar <noreply@gitar.co>
Co-authored-by: Tymoteusz Czech <2625371+Tymek@users.noreply.github.com>
This commit is contained in:
gitar-bot[bot] 2024-09-23 13:20:42 +02:00 committed by GitHub
parent 5dd0fb9f44
commit 338b5ce853
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 11 additions and 177 deletions

View File

@ -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 SystemOwner = { ownerType: 'system' };
export type UserProjectOwner = { export type UserProjectOwner = {
@ -17,13 +17,13 @@ type ProjectOwners =
export type ProjectOwnersDictionary = Record<string, ProjectOwners>; export type ProjectOwnersDictionary = Record<string, ProjectOwners>;
export type IProjectForUiWithOwners = TransitionalProjectData & { export type IProjectForUiWithOwners = ProjectForUi & {
owners: ProjectOwners; owners: ProjectOwners;
}; };
export interface IProjectOwnersReadModel { export interface IProjectOwnersReadModel {
addOwners( addOwners(
projects: TransitionalProjectData[], projects: ProjectForUi[],
anonymizeProjectOwners?: boolean, anonymizeProjectOwners?: boolean,
): Promise<IProjectForUiWithOwners[]>; ): Promise<IProjectForUiWithOwners[]>;
} }

View File

@ -1,4 +1,4 @@
import type { IProjectWithCount, ProjectMode } from '../../types'; import type { ProjectMode } from '../../types';
import type { IProjectQuery } from './project-store-type'; import type { IProjectQuery } from './project-store-type';
export type ProjectForUi = { export type ProjectForUi = {
@ -16,9 +16,6 @@ export type ProjectForUi = {
lastUpdatedAt: Date | null; lastUpdatedAt: Date | null;
}; };
// @todo remove with flag useProjectReadModel
export type TransitionalProjectData = ProjectForUi | IProjectWithCount;
export type ProjectForInsights = { export type ProjectForInsights = {
id: string; id: string;
health: number; health: number;

View File

@ -82,9 +82,7 @@ beforeAll(async () => {
await stores.accessStore.addUserToRole(opsUser.id, 1, ''); await stores.accessStore.addUserToRole(opsUser.id, 1, '');
const config = createTestConfig({ const config = createTestConfig({
getLogger, getLogger,
experimental: { experimental: {},
flags: { useProjectReadModel: true },
},
}); });
eventService = createEventsService(db.rawDatabase, config); eventService = createEventsService(db.rawDatabase, config);
accessService = createAccessService(db.rawDatabase, config); accessService = createAccessService(db.rawDatabase, config);

View File

@ -88,7 +88,7 @@ import type { IProjectFlagCreatorsReadModel } from './project-flag-creators-read
import { throwExceedsLimitError } from '../../error/exceeds-limit-error'; import { throwExceedsLimitError } from '../../error/exceeds-limit-error';
import type EventEmitter from 'events'; import type EventEmitter from 'events';
import type { ApiTokenService } from '../../services/api-token-service'; 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'; import { canGrantProjectRole } from './can-grant-project-role';
type Days = number; type Days = number;
@ -232,10 +232,9 @@ export default class ProjectService {
async getProjects( async getProjects(
query?: IProjectQuery, query?: IProjectQuery,
userId?: number, userId?: number,
): Promise<TransitionalProjectData[]> { ): Promise<ProjectForUi[]> {
const getProjects = this.flagResolver.isEnabled('useProjectReadModel') const getProjects = () =>
? () => this.projectReadModel.getProjectsForAdminUi(query, userId) this.projectReadModel.getProjectsForAdminUi(query, userId);
: () => this.projectStore.getProjectsWithCounts(query, userId);
const projects = await getProjects(); const projects = await getProjects();
@ -257,8 +256,8 @@ export default class ProjectService {
} }
async addOwnersToProjects( async addOwnersToProjects(
projects: TransitionalProjectData[], projects: ProjectForUi[],
): Promise<TransitionalProjectData[]> { ): Promise<ProjectForUi[]> {
const anonymizeProjectOwners = this.flagResolver.isEnabled( const anonymizeProjectOwners = this.flagResolver.isEnabled(
'anonymizeProjectOwners', 'anonymizeProjectOwners',
); );

View File

@ -7,7 +7,6 @@ import type {
IFeatureNaming, IFeatureNaming,
IProject, IProject,
IProjectApplications, IProjectApplications,
IProjectWithCount,
ProjectMode, ProjectMode,
} from '../../types/model'; } from '../../types/model';
import type { Store } from '../../types/stores/store'; import type { Store } from '../../types/stores/store';
@ -99,14 +98,6 @@ export interface IProjectStore extends Store<IProject, string> {
getProjectsByUser(userId: number): Promise<string[]>; getProjectsByUser(userId: number): Promise<string[]>;
/**
* @deprecated Use the appropriate method in the project read model instead.
*/
getProjectsWithCounts(
query?: IProjectQuery,
userId?: number,
): Promise<IProjectWithCount[]>;
count(): Promise<number>; count(): Promise<number>;
getAll(query?: IProjectQuery): Promise<IProject[]>; getAll(query?: IProjectQuery): Promise<IProject[]>;

View File

@ -1,4 +1,3 @@
import { Knex } from 'knex';
import type { Logger, LogProvider } from '../../logger'; import type { Logger, LogProvider } from '../../logger';
import NotFoundError from '../../error/notfound-error'; import NotFoundError from '../../error/notfound-error';
@ -9,7 +8,6 @@ import type {
IProjectApplication, IProjectApplication,
IProjectApplications, IProjectApplications,
IProjectUpdate, IProjectUpdate,
IProjectWithCount,
ProjectMode, ProjectMode,
} from '../../types'; } from '../../types';
import type { import type {
@ -27,7 +25,6 @@ import metricsHelper from '../../util/metrics-helper';
import { DB_TIME } from '../../metric-events'; import { DB_TIME } from '../../metric-events';
import type EventEmitter from 'events'; import type EventEmitter from 'events';
import type { Db } from '../../db/db'; import type { Db } from '../../db/db';
import Raw = Knex.Raw;
import type { CreateFeatureStrategySchema } from '../../openapi'; import type { CreateFeatureStrategySchema } from '../../openapi';
import { applySearchFilters } from '../feature-search/search-utils'; import { applySearchFilters } from '../feature-search/search-utils';
@ -117,114 +114,6 @@ class ProjectStore implements IProjectStore {
return present; return present;
} }
async getProjectsWithCounts(
query?: IProjectQuery,
userId?: number,
): Promise<IProjectWithCount[]> {
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<any>)[];
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<string, number>(
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<IProject[]> { async getAll(query: IProjectQuery = {}): Promise<IProject[]> {
let projects = this.db let projects = this.db
.select(COLUMNS) .select(COLUMNS)

View File

@ -56,7 +56,6 @@ export type IFlagKey =
| 'extendedMetrics' | 'extendedMetrics'
| 'removeUnsafeInlineStyleSrc' | 'removeUnsafeInlineStyleSrc'
| 'originMiddleware' | 'originMiddleware'
| 'useProjectReadModel'
| 'addonUsageMetrics' | 'addonUsageMetrics'
| 'onboardingMetrics' | 'onboardingMetrics'
| 'onboardingUI' | 'onboardingUI'
@ -279,10 +278,6 @@ const flags: IFlags = {
process.env.UNLEASH_EXPERIMENTAL_ORIGIN_MIDDLEWARE, process.env.UNLEASH_EXPERIMENTAL_ORIGIN_MIDDLEWARE,
false, false,
), ),
useProjectReadModel: parseEnvVarBoolean(
process.env.UNLEASH_EXPERIMENTAL_USE_PROJECT_READ_MODEL,
false,
),
addonUsageMetrics: parseEnvVarBoolean( addonUsageMetrics: parseEnvVarBoolean(
process.env.UNLEASH_EXPERIMENTAL_ADDON_USAGE_METRICS, process.env.UNLEASH_EXPERIMENTAL_ADDON_USAGE_METRICS,
false, false,

View File

@ -574,17 +574,6 @@ export interface ICustomRole extends IRole {
description: string; 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 { export interface IClientSegment {
id: number; id: number;
constraints: IConstraint[]; constraints: IConstraint[];

View File

@ -51,7 +51,6 @@ process.nextTick(async () => {
enableLegacyVariants: false, enableLegacyVariants: false,
extendedMetrics: true, extendedMetrics: true,
originMiddleware: true, originMiddleware: true,
useProjectReadModel: true,
addonUsageMetrics: true, addonUsageMetrics: true,
onboardingMetrics: true, onboardingMetrics: true,
onboardingUI: true, onboardingUI: true,

View File

@ -3,7 +3,6 @@ import type {
IProject, IProject,
IProjectApplications, IProjectApplications,
IProjectStore, IProjectStore,
IProjectWithCount,
} from '../../lib/types'; } from '../../lib/types';
import NotFoundError from '../../lib/error/notfound-error'; import NotFoundError from '../../lib/error/notfound-error';
import type { import type {
@ -15,7 +14,6 @@ import type {
IProjectApplicationsSearchParams, IProjectApplicationsSearchParams,
IProjectHealthUpdate, IProjectHealthUpdate,
IProjectInsert, IProjectInsert,
IProjectQuery,
ProjectEnvironment, ProjectEnvironment,
} from '../../lib/features/project/project-store-type'; } from '../../lib/features/project/project-store-type';
@ -48,27 +46,6 @@ export default class FakeProjectStore implements IProjectStore {
this.projectEnvironment.set(id, environments); this.projectEnvironment.set(id, environments);
} }
async getProjectsWithCounts(
query?: IProjectQuery,
): Promise<IProjectWithCount[]> {
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 { private createInternal(project: IProjectInsert): IProject {
const newProj: ArchivableProject = { const newProj: ArchivableProject = {
...project, ...project,