Skip to content

Commit

Permalink
feat: project insights resource with hardcoded data (#6610)
Browse files Browse the repository at this point in the history
  • Loading branch information
kwasniew committed Mar 19, 2024
1 parent f3506da commit 03a84e2
Show file tree
Hide file tree
Showing 4 changed files with 252 additions and 11 deletions.
102 changes: 91 additions & 11 deletions src/lib/features/project/project-controller.ts
Expand Up @@ -20,16 +20,14 @@ import {
deprecatedProjectOverviewSchema,
type ProjectDoraMetricsSchema,
projectDoraMetricsSchema,
projectInsightsSchema,
type ProjectInsightsSchema,
projectOverviewSchema,
type ProjectsSchema,
projectsSchema,
} from '../../openapi';
import { getStandardResponses } from '../../openapi/util/standard-responses';
import type {
AccessService,
OpenApiService,
SettingService,
} from '../../services';
import type { OpenApiService } from '../../services';
import type { IAuthRequest } from '../../routes/unleash-types';
import { ProjectApiTokenController } from '../../routes/admin-api/project/api-token';
import ProjectArchiveController from '../../routes/admin-api/project/project-archive';
Expand All @@ -48,10 +46,6 @@ import { normalizeQueryParams } from '../feature-search/search-utils';
export default class ProjectController extends Controller {
private projectService: ProjectService;

private settingService: SettingService;

private accessService: AccessService;

private openApiService: OpenApiService;

private flagResolver: IFlagResolver;
Expand All @@ -60,8 +54,6 @@ export default class ProjectController extends Controller {
super(config);
this.projectService = services.projectService;
this.openApiService = services.openApiService;
this.settingService = services.settingService;
this.accessService = services.accessService;
this.flagResolver = config.flagResolver;

this.route({
Expand Down Expand Up @@ -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({
method: 'get',
path: '/:projectId/dora',
Expand Down Expand Up @@ -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(
req: IAuthRequest<IProjectParam, unknown, unknown, IArchivedQuery>,
res: Response<ProjectOverviewSchema>,
Expand Down
12 changes: 12 additions & 0 deletions src/lib/features/project/projects.e2e.test.ts
Expand Up @@ -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');
});

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,
});
});
1 change: 1 addition & 0 deletions src/lib/openapi/spec/index.ts
Expand Up @@ -134,6 +134,7 @@ export * from './project-application-sdk-schema';
export * from './project-applications-schema';
export * from './project-dora-metrics-schema';
export * from './project-environment-schema';
export * from './project-insights-schema';
export * from './project-overview-schema';
export * from './project-schema';
export * from './project-stats-schema';
Expand Down
148 changes: 148 additions & 0 deletions src/lib/openapi/spec/project-insights-schema.ts
@@ -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>;

0 comments on commit 03a84e2

Please sign in to comment.