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:
parent
5dd0fb9f44
commit
338b5ce853
@ -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<string, ProjectOwners>;
|
||||
|
||||
export type IProjectForUiWithOwners = TransitionalProjectData & {
|
||||
export type IProjectForUiWithOwners = ProjectForUi & {
|
||||
owners: ProjectOwners;
|
||||
};
|
||||
|
||||
export interface IProjectOwnersReadModel {
|
||||
addOwners(
|
||||
projects: TransitionalProjectData[],
|
||||
projects: ProjectForUi[],
|
||||
anonymizeProjectOwners?: boolean,
|
||||
): Promise<IProjectForUiWithOwners[]>;
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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<TransitionalProjectData[]> {
|
||||
const getProjects = this.flagResolver.isEnabled('useProjectReadModel')
|
||||
? () => this.projectReadModel.getProjectsForAdminUi(query, userId)
|
||||
: () => this.projectStore.getProjectsWithCounts(query, userId);
|
||||
): Promise<ProjectForUi[]> {
|
||||
const getProjects = () =>
|
||||
this.projectReadModel.getProjectsForAdminUi(query, userId);
|
||||
|
||||
const projects = await getProjects();
|
||||
|
||||
@ -257,8 +256,8 @@ export default class ProjectService {
|
||||
}
|
||||
|
||||
async addOwnersToProjects(
|
||||
projects: TransitionalProjectData[],
|
||||
): Promise<TransitionalProjectData[]> {
|
||||
projects: ProjectForUi[],
|
||||
): Promise<ProjectForUi[]> {
|
||||
const anonymizeProjectOwners = this.flagResolver.isEnabled(
|
||||
'anonymizeProjectOwners',
|
||||
);
|
||||
|
@ -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<IProject, 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>;
|
||||
|
||||
getAll(query?: IProjectQuery): Promise<IProject[]>;
|
||||
|
@ -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<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[]> {
|
||||
let projects = this.db
|
||||
.select(COLUMNS)
|
||||
|
@ -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,
|
||||
|
@ -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[];
|
||||
|
@ -51,7 +51,6 @@ process.nextTick(async () => {
|
||||
enableLegacyVariants: false,
|
||||
extendedMetrics: true,
|
||||
originMiddleware: true,
|
||||
useProjectReadModel: true,
|
||||
addonUsageMetrics: true,
|
||||
onboardingMetrics: true,
|
||||
onboardingUI: true,
|
||||
|
23
src/test/fixtures/fake-project-store.ts
vendored
23
src/test/fixtures/fake-project-store.ts
vendored
@ -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<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 {
|
||||
const newProj: ArchivableProject = {
|
||||
...project,
|
||||
|
Loading…
Reference in New Issue
Block a user