1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-01-25 00:07:47 +01:00

refactor: remove change requests from project insights api (#6685)

This commit is contained in:
Mateusz Kwasniewski 2024-03-25 14:44:32 +01:00 committed by GitHub
parent 501da974d6
commit d4f52cdb54
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 62 additions and 363 deletions

View File

@ -12,6 +12,18 @@ const setupEnterpriseApi = () => {
current: { enterprise: 'present' },
},
});
testServerRoute(
server,
'/api/admin/projects/default/change-requests/count',
{
total: 14,
approved: 2,
applied: 0,
rejected: 0,
reviewRequired: 10,
scheduled: 2,
},
);
};
const setupOssApi = () => {
@ -22,23 +34,11 @@ const setupOssApi = () => {
});
};
const changeRequests = {
applied: 0,
total: 0,
approved: 0,
scheduled: 0,
reviewRequired: 0,
rejected: 0,
};
test('Show enterprise hints', async () => {
setupOssApi();
render(
<Routes>
<Route
path={'/projects/:projectId'}
element={<ChangeRequests changeRequests={changeRequests} />}
/>
<Route path={'/projects/:projectId'} element={<ChangeRequests />} />
</Routes>,
{
route: '/projects/default',
@ -52,10 +52,7 @@ test('Show change requests info', async () => {
setupEnterpriseApi();
render(
<Routes>
<Route
path={'/projects/:projectId'}
element={<ChangeRequests changeRequests={changeRequests} />}
/>
<Route path={'/projects/:projectId'} element={<ChangeRequests />} />
</Routes>,
{
route: '/projects/default',
@ -63,4 +60,7 @@ test('Show change requests info', async () => {
);
await screen.findByText('To be applied');
await screen.findByText('10');
await screen.findByText('4');
await screen.findByText('14');
});

View File

@ -4,8 +4,7 @@ import { Link } from 'react-router-dom';
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
import { PremiumFeature } from 'component/common/PremiumFeature/PremiumFeature';
import type { ProjectInsightsSchemaChangeRequests } from '../../../../../openapi';
import type { FC } from 'react';
import { useChangeRequestsCount } from 'hooks/api/getters/useChangeRequestsCount/useChangeRequestsCount';
const Container = styled(Box)(({ theme }) => ({
display: 'flex',
@ -83,14 +82,13 @@ const BigNumber = styled(Typography)(({ theme }) => ({
color: theme.palette.text.primary,
}));
export const ChangeRequests: FC<{
changeRequests: ProjectInsightsSchemaChangeRequests;
}> = ({ changeRequests }) => {
export const ChangeRequests = () => {
const projectId = useRequiredPathParam('projectId');
const { isOss, isPro } = useUiConfig();
const { data } = useChangeRequestsCount(projectId);
const { total, applied, rejected, reviewRequired, scheduled, approved } =
changeRequests;
data;
const toBeApplied = scheduled + approved;
if (isOss() || isPro()) {

View File

@ -61,9 +61,7 @@ export const ProjectInsights = () => {
<ProjectMembers projectId={projectId} members={data.members} />
</NarrowContainer>
<WideContainer>
{data.changeRequests && (
<ChangeRequests changeRequests={data.changeRequests} />
)}
<ChangeRequests />
</WideContainer>
</Grid>
);

View File

@ -48,7 +48,7 @@ export const ProjectOverviewChangeRequests: FC<{ project: string }> = ({
useChangeRequestsEnabled(project);
const { data } = useChangeRequestsCount(project);
if (!isChangeRequestConfiguredInAnyEnv) {
if (!isChangeRequestConfiguredInAnyEnv()) {
return null;
}

View File

@ -49,14 +49,6 @@ const placeholderData: ProjectInsightsSchema = {
currentMembers: 0,
change: 0,
},
changeRequests: {
total: 0,
applied: 0,
approved: 0,
rejected: 0,
reviewRequired: 0,
scheduled: 0,
},
};
export const useProjectInsights = (projectId: string) => {

View File

@ -879,7 +879,6 @@ export * from './projectCreatedSchemaMode';
export * from './projectDoraMetricsSchema';
export * from './projectEnvironmentSchema';
export * from './projectInsightsSchema';
export * from './projectInsightsSchemaChangeRequests';
export * from './projectInsightsSchemaHealth';
export * from './projectInsightsSchemaMembers';
export * from './projectOverviewSchema';

View File

@ -3,7 +3,6 @@
* Do not edit manually.
* See `gen:api` script in package.json
*/
import type { ProjectInsightsSchemaChangeRequests } from './projectInsightsSchemaChangeRequests';
import type { FeatureTypeCountSchema } from './featureTypeCountSchema';
import type { ProjectInsightsSchemaHealth } from './projectInsightsSchemaHealth';
import type { ProjectDoraMetricsSchema } from './projectDoraMetricsSchema';
@ -14,8 +13,6 @@ import type { ProjectStatsSchema } from './projectStatsSchema';
* A high-level overview of a project insights. It contains information such as project statistics, overall health, types of flags, members overview, change requests overview.
*/
export interface ProjectInsightsSchema {
/** Count of change requests in different stages of the [process](https://docs.getunleash.io/reference/change-requests#change-request-flow). Only for enterprise users. */
changeRequests?: ProjectInsightsSchemaChangeRequests;
/** The number of features of each type */
featureTypeCounts: FeatureTypeCountSchema[];
/** Health summary of the project */

View File

@ -1,23 +0,0 @@
/**
* Generated by Orval
* Do not edit manually.
* See `gen:api` script in package.json
*/
/**
* Count of change requests in different stages of the [process](https://docs.getunleash.io/reference/change-requests#change-request-flow). Only for enterprise users.
*/
export type ProjectInsightsSchemaChangeRequests = {
/** The number of applied change requests */
applied: number;
/** The number of approved change requests */
approved: number;
/** The number of rejected change requests */
rejected: number;
/** The number of change requests awaiting the review */
reviewRequired: number;
/** The number of scheduled change requests */
scheduled: number;
/** The number of total change requests in this project */
total: number;
};

View File

@ -8,8 +8,6 @@ import FeatureTypeStore from '../../db/feature-type-store';
import FakeFeatureTypeStore from '../../../test/fixtures/fake-feature-type-store';
import { ProjectInsightsService } from './project-insights-service';
import ProjectStore from '../project/project-store';
import { ProjectInsightsReadModel } from './project-insights-read-model';
import { FakeProjectInsightsReadModel } from './fake-project-insights-read-model';
import FeatureStrategiesStore from '../feature-toggle/feature-toggle-strategies-store';
import FakeFeatureStrategiesStore from '../feature-toggle/fakes/fake-feature-strategies-store';
@ -39,18 +37,14 @@ export const createProjectInsightsService = (
getLogger,
flagResolver,
);
const projectInsightsReadModel = new ProjectInsightsReadModel(db);
return new ProjectInsightsService(
{
projectStore,
featureToggleStore,
featureTypeStore,
projectStatsStore,
featureStrategiesStore,
},
projectInsightsReadModel,
);
return new ProjectInsightsService({
projectStore,
featureToggleStore,
featureTypeStore,
projectStatsStore,
featureStrategiesStore,
});
};
export const createFakeProjectInsightsService = () => {
@ -59,23 +53,18 @@ export const createFakeProjectInsightsService = () => {
const featureTypeStore = new FakeFeatureTypeStore();
const projectStatsStore = new FakeProjectStatsStore();
const featureStrategiesStore = new FakeFeatureStrategiesStore();
const projectInsightsReadModel = new FakeProjectInsightsReadModel();
const projectInsightsService = new ProjectInsightsService(
{
projectStore,
featureToggleStore,
featureTypeStore,
projectStatsStore,
featureStrategiesStore,
},
projectInsightsReadModel,
);
const projectInsightsService = new ProjectInsightsService({
projectStore,
featureToggleStore,
featureTypeStore,
projectStatsStore,
featureStrategiesStore,
});
return {
projectInsightsService,
projectStatsStore,
featureToggleStore,
projectStore,
projectInsightsReadModel,
};
};

View File

@ -1,25 +0,0 @@
import type {
ChangeRequestCounts,
IProjectInsightsReadModel,
} from './project-insights-read-model-type';
const changeRequestCounts: ChangeRequestCounts = {
total: 0,
approved: 0,
applied: 0,
rejected: 0,
reviewRequired: 0,
scheduled: 0,
};
export class FakeProjectInsightsReadModel implements IProjectInsightsReadModel {
private counts: Record<string, ChangeRequestCounts> = {};
async getChangeRequests(projectId: string): Promise<ChangeRequestCounts> {
return this.counts[projectId] ?? changeRequestCounts;
}
async setChangeRequests(projectId: string, counts: ChangeRequestCounts) {
this.counts[projectId] = counts;
}
}

View File

@ -1,12 +0,0 @@
export type ChangeRequestCounts = {
total: number;
approved: number;
applied: number;
rejected: number;
reviewRequired: number;
scheduled: number;
};
export interface IProjectInsightsReadModel {
getChangeRequests(projectId: string): Promise<ChangeRequestCounts>;
}

View File

@ -1,66 +0,0 @@
import dbInit, { type ITestDb } from '../../../test/e2e/helpers/database-init';
import getLogger from '../../../test/fixtures/no-logger';
import type { IUser } from '../../types';
import type { IProjectInsightsReadModel } from './project-insights-read-model-type';
import {
type ChangeRequestDBState,
ProjectInsightsReadModel,
} from './project-insights-read-model';
let projectInsightsReadModel: IProjectInsightsReadModel;
let user: IUser;
let db: ITestDb;
const projectId = 'default';
beforeAll(async () => {
db = await dbInit('project_insights_read_model', getLogger);
projectInsightsReadModel = new ProjectInsightsReadModel(db.rawDatabase);
user = await db.stores.userStore.insert({
username: 'test',
});
});
afterAll(async () => {
await db.destroy();
});
beforeEach(async () => {
await db.rawDatabase.table('change_requests').delete();
});
const createChangeRequest = (id: number, state: string) =>
db.rawDatabase.table('change_requests').insert({
id,
state,
environment: 'default',
project: projectId,
created_by: user.id,
});
test('can read change request status counts', async () => {
const states: ChangeRequestDBState[] = [
'Approved',
'Approved',
'Applied',
'Rejected',
'Scheduled',
'In review',
'Draft',
'Cancelled',
];
await Promise.all(
states.map((state, id) => createChangeRequest(id, state)),
);
const changeRequests =
await projectInsightsReadModel.getChangeRequests(projectId);
expect(changeRequests).toEqual({
total: 6,
approved: 2,
applied: 1,
rejected: 1,
reviewRequired: 1,
scheduled: 1,
});
});

View File

@ -1,61 +0,0 @@
import type {
ChangeRequestCounts,
IProjectInsightsReadModel,
} from './project-insights-read-model-type';
import type { Db } from '../../db/db';
export type ChangeRequestDBState =
| 'Draft'
| 'Cancelled'
| 'Approved'
| 'In review'
| 'Applied'
| 'Scheduled'
| 'Rejected';
export class ProjectInsightsReadModel implements IProjectInsightsReadModel {
private db: Db;
constructor(db: Db) {
this.db = db;
}
async getChangeRequests(projectId: string): Promise<ChangeRequestCounts> {
const changeRequestCounts: ChangeRequestCounts = {
total: 0,
approved: 0,
applied: 0,
rejected: 0,
reviewRequired: 0,
scheduled: 0,
};
const rows: Array<{ state: ChangeRequestDBState; count: string }> =
await this.db('change_requests')
.select('state')
.count('* as count')
.where('project', '=', projectId)
.groupBy('state');
return rows.reduce((acc, current) => {
if (current.state === 'Applied') {
acc.applied = Number(current.count);
acc.total += Number(current.count);
} else if (current.state === 'Approved') {
acc.approved = Number(current.count);
acc.total += Number(current.count);
} else if (current.state === 'Rejected') {
acc.rejected = Number(current.count);
acc.total += Number(current.count);
} else if (current.state === 'In review') {
acc.reviewRequired = Number(current.count);
acc.total += Number(current.count);
} else if (current.state === 'Scheduled') {
acc.scheduled = Number(current.count);
acc.total += Number(current.count);
}
return acc;
}, changeRequestCounts);
}
}

View File

@ -6,21 +6,12 @@ test('Return basic insights', async () => {
projectStatsStore,
featureToggleStore,
projectStore,
projectInsightsReadModel,
} = createFakeProjectInsightsService();
await featureToggleStore.create('default', {
name: 'irrelevant',
createdByUserId: 1,
type: 'release',
});
await projectInsightsReadModel.setChangeRequests('default', {
total: 5,
approved: 1,
applied: 1,
rejected: 1,
reviewRequired: 1,
scheduled: 1,
});
await projectStore.create({
id: 'default',
name: 'irrelevant',
@ -57,14 +48,6 @@ test('Return basic insights', async () => {
rating: 100,
},
leadTime: { features: [], projectAverage: 0 },
changeRequests: {
total: 5,
approved: 1,
applied: 1,
rejected: 1,
reviewRequired: 1,
scheduled: 1,
},
members: { currentMembers: 0, change: 0 },
});
});

View File

@ -13,7 +13,6 @@ import type {
ProjectInsightsSchema,
} from '../../openapi';
import { calculateProjectHealth } from '../../domain/project-health/project-health';
import type { IProjectInsightsReadModel } from './project-insights-read-model-type';
import { subDays } from 'date-fns';
export class ProjectInsightsService {
@ -27,31 +26,25 @@ export class ProjectInsightsService {
private projectStatsStore: IProjectStatsStore;
private projectInsightsReadModel: IProjectInsightsReadModel;
constructor(
{
projectStore,
featureToggleStore,
featureTypeStore,
projectStatsStore,
featureStrategiesStore,
}: Pick<
IUnleashStores,
| 'projectStore'
| 'featureToggleStore'
| 'projectStatsStore'
| 'featureTypeStore'
| 'featureStrategiesStore'
>,
projectInsightsReadModel: IProjectInsightsReadModel,
) {
constructor({
projectStore,
featureToggleStore,
featureTypeStore,
projectStatsStore,
featureStrategiesStore,
}: Pick<
IUnleashStores,
| 'projectStore'
| 'featureToggleStore'
| 'projectStatsStore'
| 'featureTypeStore'
| 'featureStrategiesStore'
>) {
this.projectStore = projectStore;
this.featureToggleStore = featureToggleStore;
this.featureTypeStore = featureTypeStore;
this.featureStrategiesStore = featureStrategiesStore;
this.projectStatsStore = projectStatsStore;
this.projectInsightsReadModel = projectInsightsReadModel;
}
async getDoraMetrics(projectId: string): Promise<ProjectDoraMetricsSchema> {
@ -143,31 +136,23 @@ export class ProjectInsightsService {
}
async getProjectInsights(projectId: string) {
const [
stats,
featureTypeCounts,
health,
leadTime,
changeRequests,
members,
] = await Promise.all([
this.projectStatsStore.getProjectStats(projectId),
this.featureToggleStore.getFeatureTypeCounts({
projectId,
archived: false,
}),
this.getHealthInsights(projectId),
this.getDoraMetrics(projectId),
this.projectInsightsReadModel.getChangeRequests(projectId),
this.getProjectMembers(projectId),
]);
const [stats, featureTypeCounts, health, leadTime, members] =
await Promise.all([
this.projectStatsStore.getProjectStats(projectId),
this.featureToggleStore.getFeatureTypeCounts({
projectId,
archived: false,
}),
this.getHealthInsights(projectId),
this.getDoraMetrics(projectId),
this.getProjectMembers(projectId),
]);
return {
stats,
featureTypeCounts,
health,
leadTime,
changeRequests,
members,
};
}

View File

@ -53,13 +53,5 @@ test('project insights happy path', async () => {
staleCount: 0,
rating: 100,
},
changeRequests: {
total: 0,
approved: 0,
applied: 0,
rejected: 0,
reviewRequired: 0,
scheduled: 0,
},
});
});

View File

@ -80,53 +80,6 @@ export const projectInsightsSchema = {
},
description: 'Active/inactive users summary',
},
changeRequests: {
type: 'object',
required: [
'total',
'applied',
'rejected',
'reviewRequired',
'approved',
'scheduled',
],
properties: {
total: {
type: 'number',
description:
'The number of total change requests in this project',
example: 10,
},
applied: {
type: 'number',
description: 'The number of applied change requests',
example: 5,
},
rejected: {
type: 'number',
description: 'The number of rejected change requests',
example: 2,
},
reviewRequired: {
type: 'number',
description:
'The number of change requests awaiting the review',
example: 2,
},
approved: {
type: 'number',
description: 'The number of approved change requests',
example: 1,
},
scheduled: {
type: 'number',
description: 'The number of scheduled change requests',
example: 1,
},
},
description:
'Count of change requests in different stages of the [process](https://docs.getunleash.io/reference/change-requests#change-request-flow). Only for enterprise users.',
},
},
components: {
schemas: {