mirror of
https://github.com/Unleash/unleash.git
synced 2025-09-15 17:50:48 +02:00
feat: project insights resource with hardcoded data (#6610)
This commit is contained in:
parent
f3506da925
commit
03a84e2d42
@ -20,16 +20,14 @@ import {
|
|||||||
deprecatedProjectOverviewSchema,
|
deprecatedProjectOverviewSchema,
|
||||||
type ProjectDoraMetricsSchema,
|
type ProjectDoraMetricsSchema,
|
||||||
projectDoraMetricsSchema,
|
projectDoraMetricsSchema,
|
||||||
|
projectInsightsSchema,
|
||||||
|
type ProjectInsightsSchema,
|
||||||
projectOverviewSchema,
|
projectOverviewSchema,
|
||||||
type ProjectsSchema,
|
type ProjectsSchema,
|
||||||
projectsSchema,
|
projectsSchema,
|
||||||
} from '../../openapi';
|
} from '../../openapi';
|
||||||
import { getStandardResponses } from '../../openapi/util/standard-responses';
|
import { getStandardResponses } from '../../openapi/util/standard-responses';
|
||||||
import type {
|
import type { OpenApiService } from '../../services';
|
||||||
AccessService,
|
|
||||||
OpenApiService,
|
|
||||||
SettingService,
|
|
||||||
} from '../../services';
|
|
||||||
import type { IAuthRequest } from '../../routes/unleash-types';
|
import type { IAuthRequest } from '../../routes/unleash-types';
|
||||||
import { ProjectApiTokenController } from '../../routes/admin-api/project/api-token';
|
import { ProjectApiTokenController } from '../../routes/admin-api/project/api-token';
|
||||||
import ProjectArchiveController from '../../routes/admin-api/project/project-archive';
|
import ProjectArchiveController from '../../routes/admin-api/project/project-archive';
|
||||||
@ -48,10 +46,6 @@ import { normalizeQueryParams } from '../feature-search/search-utils';
|
|||||||
export default class ProjectController extends Controller {
|
export default class ProjectController extends Controller {
|
||||||
private projectService: ProjectService;
|
private projectService: ProjectService;
|
||||||
|
|
||||||
private settingService: SettingService;
|
|
||||||
|
|
||||||
private accessService: AccessService;
|
|
||||||
|
|
||||||
private openApiService: OpenApiService;
|
private openApiService: OpenApiService;
|
||||||
|
|
||||||
private flagResolver: IFlagResolver;
|
private flagResolver: IFlagResolver;
|
||||||
@ -60,8 +54,6 @@ export default class ProjectController extends Controller {
|
|||||||
super(config);
|
super(config);
|
||||||
this.projectService = services.projectService;
|
this.projectService = services.projectService;
|
||||||
this.openApiService = services.openApiService;
|
this.openApiService = services.openApiService;
|
||||||
this.settingService = services.settingService;
|
|
||||||
this.accessService = services.accessService;
|
|
||||||
this.flagResolver = config.flagResolver;
|
this.flagResolver = config.flagResolver;
|
||||||
|
|
||||||
this.route({
|
this.route({
|
||||||
@ -127,6 +119,26 @@ export default class ProjectController extends Controller {
|
|||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.route({
|
||||||
|
method: 'get',
|
||||||
|
path: '/:projectId/insights',
|
||||||
|
handler: this.getProjectInsights,
|
||||||
|
permission: NONE,
|
||||||
|
middleware: [
|
||||||
|
this.openApiService.validPath({
|
||||||
|
tags: ['Unstable'],
|
||||||
|
operationId: 'getProjectInsights',
|
||||||
|
summary: 'Get an overview of a project insights.',
|
||||||
|
description:
|
||||||
|
'This endpoint returns insights into the specified projects stats, health, lead time for changes, feature types used, members and change requests.',
|
||||||
|
responses: {
|
||||||
|
200: createResponseSchema('projectInsightsSchema'),
|
||||||
|
...getStandardResponses(401, 403, 404),
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
this.route({
|
this.route({
|
||||||
method: 'get',
|
method: 'get',
|
||||||
path: '/:projectId/dora',
|
path: '/:projectId/dora',
|
||||||
@ -232,6 +244,74 @@ export default class ProjectController extends Controller {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getProjectInsights(
|
||||||
|
req: IAuthRequest<IProjectParam, unknown, unknown, unknown>,
|
||||||
|
res: Response<ProjectInsightsSchema>,
|
||||||
|
): Promise<void> {
|
||||||
|
const result = {
|
||||||
|
stats: {
|
||||||
|
avgTimeToProdCurrentWindow: 17.1,
|
||||||
|
createdCurrentWindow: 3,
|
||||||
|
createdPastWindow: 6,
|
||||||
|
archivedCurrentWindow: 0,
|
||||||
|
archivedPastWindow: 1,
|
||||||
|
projectActivityCurrentWindow: 458,
|
||||||
|
projectActivityPastWindow: 578,
|
||||||
|
projectMembersAddedCurrentWindow: 0,
|
||||||
|
},
|
||||||
|
featureTypeCounts: [
|
||||||
|
{
|
||||||
|
type: 'experiment',
|
||||||
|
count: 4,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'permission',
|
||||||
|
count: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'release',
|
||||||
|
count: 24,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
leadTime: {
|
||||||
|
projectAverage: 17.1,
|
||||||
|
features: [
|
||||||
|
{ name: 'feature1', timeToProduction: 120 },
|
||||||
|
{ name: 'feature2', timeToProduction: 0 },
|
||||||
|
{ name: 'feature3', timeToProduction: 33 },
|
||||||
|
{ name: 'feature4', timeToProduction: 131 },
|
||||||
|
{ name: 'feature5', timeToProduction: 2 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
health: {
|
||||||
|
rating: 80,
|
||||||
|
activeCount: 23,
|
||||||
|
potentiallyStaleCount: 3,
|
||||||
|
staleCount: 5,
|
||||||
|
},
|
||||||
|
members: {
|
||||||
|
active: 20,
|
||||||
|
inactive: 3,
|
||||||
|
totalPreviousMonth: 15,
|
||||||
|
},
|
||||||
|
changeRequests: {
|
||||||
|
total: 24,
|
||||||
|
approved: 5,
|
||||||
|
applied: 2,
|
||||||
|
rejected: 4,
|
||||||
|
reviewRequired: 10,
|
||||||
|
scheduled: 3,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
this.openApiService.respondWithValidation(
|
||||||
|
200,
|
||||||
|
res,
|
||||||
|
projectInsightsSchema.$id,
|
||||||
|
serializeDates(result),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
async getProjectOverview(
|
async getProjectOverview(
|
||||||
req: IAuthRequest<IProjectParam, unknown, unknown, IArchivedQuery>,
|
req: IAuthRequest<IProjectParam, unknown, unknown, IArchivedQuery>,
|
||||||
res: Response<ProjectOverviewSchema>,
|
res: Response<ProjectOverviewSchema>,
|
||||||
|
@ -287,3 +287,15 @@ test('response should include last seen at per environment for multiple environm
|
|||||||
|
|
||||||
expect(body.features[1].lastSeenAt).toBe('2023-10-01T12:34:56.000Z');
|
expect(body.features[1].lastSeenAt).toBe('2023-10-01T12:34:56.000Z');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('project insights happy path', async () => {
|
||||||
|
const { body } = await app.request
|
||||||
|
.get('/api/admin/projects/default/insights')
|
||||||
|
.expect('Content-Type', /json/)
|
||||||
|
.expect(200);
|
||||||
|
|
||||||
|
expect(body.leadTime.features[0]).toEqual({
|
||||||
|
name: 'feature1',
|
||||||
|
timeToProduction: 120,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
@ -134,6 +134,7 @@ export * from './project-application-sdk-schema';
|
|||||||
export * from './project-applications-schema';
|
export * from './project-applications-schema';
|
||||||
export * from './project-dora-metrics-schema';
|
export * from './project-dora-metrics-schema';
|
||||||
export * from './project-environment-schema';
|
export * from './project-environment-schema';
|
||||||
|
export * from './project-insights-schema';
|
||||||
export * from './project-overview-schema';
|
export * from './project-overview-schema';
|
||||||
export * from './project-schema';
|
export * from './project-schema';
|
||||||
export * from './project-stats-schema';
|
export * from './project-stats-schema';
|
||||||
|
148
src/lib/openapi/spec/project-insights-schema.ts
Normal file
148
src/lib/openapi/spec/project-insights-schema.ts
Normal file
@ -0,0 +1,148 @@
|
|||||||
|
import type { FromSchema } from 'json-schema-to-ts';
|
||||||
|
import { projectStatsSchema } from './project-stats-schema';
|
||||||
|
import { featureTypeCountSchema } from './feature-type-count-schema';
|
||||||
|
import { doraFeaturesSchema } from './dora-features-schema';
|
||||||
|
import { projectDoraMetricsSchema } from './project-dora-metrics-schema';
|
||||||
|
|
||||||
|
export const projectInsightsSchema = {
|
||||||
|
$id: '#/components/schemas/projectInsightsSchema',
|
||||||
|
type: 'object',
|
||||||
|
additionalProperties: false,
|
||||||
|
required: ['stats', 'leadTime', 'featureTypeCounts', 'health', 'members'],
|
||||||
|
description:
|
||||||
|
'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.',
|
||||||
|
properties: {
|
||||||
|
stats: {
|
||||||
|
$ref: '#/components/schemas/projectStatsSchema',
|
||||||
|
description: 'Project statistics',
|
||||||
|
},
|
||||||
|
health: {
|
||||||
|
type: 'object',
|
||||||
|
required: [
|
||||||
|
'rating',
|
||||||
|
'activeCount',
|
||||||
|
'potentiallyStaleCount',
|
||||||
|
'staleCount',
|
||||||
|
],
|
||||||
|
properties: {
|
||||||
|
rating: {
|
||||||
|
type: 'integer',
|
||||||
|
description:
|
||||||
|
"An indicator of the [project's health](https://docs.getunleash.io/reference/technical-debt#health-rating) on a scale from 0 to 100",
|
||||||
|
example: 95,
|
||||||
|
},
|
||||||
|
activeCount: {
|
||||||
|
type: 'number',
|
||||||
|
description: 'The number of active feature toggles.',
|
||||||
|
example: 12,
|
||||||
|
},
|
||||||
|
potentiallyStaleCount: {
|
||||||
|
type: 'number',
|
||||||
|
description:
|
||||||
|
'The number of potentially stale feature toggles.',
|
||||||
|
example: 5,
|
||||||
|
},
|
||||||
|
staleCount: {
|
||||||
|
type: 'number',
|
||||||
|
description: 'The number of stale feature toggles.',
|
||||||
|
example: 10,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
description: 'Health summary of the project',
|
||||||
|
},
|
||||||
|
leadTime: {
|
||||||
|
type: 'object',
|
||||||
|
$ref: '#/components/schemas/projectDoraMetricsSchema',
|
||||||
|
description: 'Lead time (DORA) metrics',
|
||||||
|
},
|
||||||
|
featureTypeCounts: {
|
||||||
|
type: 'array',
|
||||||
|
items: {
|
||||||
|
$ref: '#/components/schemas/featureTypeCountSchema',
|
||||||
|
},
|
||||||
|
description: 'The number of features of each type',
|
||||||
|
},
|
||||||
|
members: {
|
||||||
|
type: 'object',
|
||||||
|
required: ['active', 'inactive'],
|
||||||
|
properties: {
|
||||||
|
active: {
|
||||||
|
type: 'number',
|
||||||
|
description:
|
||||||
|
'The number of active project members who have used Unleash in the past 60 days',
|
||||||
|
example: 10,
|
||||||
|
},
|
||||||
|
inactive: {
|
||||||
|
type: 'number',
|
||||||
|
description:
|
||||||
|
'The number of inactive project members who have not used Unleash in the past 60 days',
|
||||||
|
example: 10,
|
||||||
|
},
|
||||||
|
totalPreviousMonth: {
|
||||||
|
type: 'number',
|
||||||
|
description:
|
||||||
|
'The number of total project members in the previous month',
|
||||||
|
example: 8,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
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: {
|
||||||
|
projectStatsSchema,
|
||||||
|
featureTypeCountSchema,
|
||||||
|
projectDoraMetricsSchema,
|
||||||
|
doraFeaturesSchema,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export type ProjectInsightsSchema = FromSchema<typeof projectInsightsSchema>;
|
Loading…
Reference in New Issue
Block a user