1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-08-13 13:48:59 +02:00

Merge branch 'main' into fix/flagstats_nan

This commit is contained in:
andreas-unleash 2024-03-21 11:29:08 +02:00
commit c24045dc99
No known key found for this signature in database
GPG Key ID: 86EF87A739B39099
16 changed files with 235 additions and 300 deletions

View File

@ -55,7 +55,7 @@
"@types/lodash.isequal": "^4.5.8",
"@types/lodash.mapvalues": "^4.6.9",
"@types/lodash.omit": "4.5.9",
"@types/node": "18.19.23",
"@types/node": "18.19.24",
"@types/react": "17.0.76",
"@types/react-dom": "17.0.25",
"@types/react-linkify": "1.0.4",
@ -74,7 +74,7 @@
"classnames": "2.5.1",
"copy-to-clipboard": "3.3.3",
"countries-and-timezones": "^3.4.0",
"cypress": "13.6.6",
"cypress": "13.7.0",
"cypress-vite": "^1.4.0",
"date-fns": "2.30.0",
"date-fns-tz": "^2.0.0",
@ -109,17 +109,17 @@
"react-table": "7.8.0",
"react-test-renderer": "17.0.2",
"react-timeago": "7.2.0",
"sass": "1.71.1",
"sass": "1.72.0",
"semver": "7.6.0",
"swr": "2.2.5",
"tss-react": "4.9.4",
"typescript": "4.8.4",
"use-query-params": "^2.2.1",
"vanilla-jsoneditor": "^0.22.0",
"vanilla-jsoneditor": "^0.23.0",
"vite": "5.1.6",
"vite-plugin-env-compatible": "2.0.1",
"vite-plugin-svgr": "3.3.0",
"vite-tsconfig-paths": "4.3.1",
"vite-tsconfig-paths": "4.3.2",
"vitest": "1.3.1",
"whatwg-fetch": "3.6.20"
},

View File

@ -4,15 +4,12 @@ import { ProjectMembers } from './ProjectMembers';
test('Show outdated project members', async () => {
const members = {
active: 10,
totalPreviousMonth: 2,
inactive: 5,
currentMembers: 10,
change: 2,
};
render(<ProjectMembers projectId={'default'} members={members} />);
await screen.findByText('15');
await screen.findByText('+13');
await screen.findByText('10');
await screen.findByText('5');
await screen.findByText('+2');
});

View File

@ -1,5 +1,5 @@
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
import { Box, styled, Typography, useTheme } from '@mui/material';
import { Box, styled, Typography } from '@mui/material';
import { StatusBox } from '../ProjectInsightsStats/StatusBox';
import KeyboardArrowRight from '@mui/icons-material/KeyboardArrowRight';
import { Link } from 'react-router-dom';
@ -23,92 +23,17 @@ export const StyledProjectInfoWidgetContainer = styled('div')(({ theme }) => ({
gap: theme.spacing(2.5),
}));
export const BarContainer = styled('div')(({ theme }) => ({
width: '100%',
height: '20px',
display: 'flex',
}));
const ActiveBar = styled('span', {
shouldForwardProp: (prop) => prop !== 'percentage',
})<{
percentage: number;
}>(({ theme, percentage }) => ({
width: `${percentage - 1}%`,
backgroundColor: theme.palette.success.border,
borderRadius: theme.shape.borderRadius,
}));
const InactiveBar = styled('span', {
shouldForwardProp: (prop) => prop !== 'percentage',
})<{
percentage: number;
}>(({ theme, percentage }) => ({
width: `${percentage - 1}%`,
backgroundColor: theme.palette.warning.border,
marginLeft: 'auto',
borderRadius: theme.shape.borderRadius,
}));
export const CountContainer = styled('div')(({ theme }) => ({
display: 'flex',
flexDirection: 'column',
gap: theme.spacing(1.5),
}));
export const CountRow = styled(NavigationBar)(({ theme }) => ({
display: 'flex',
padding: theme.spacing(0.5, 1, 0, 2),
alignItems: 'center',
gap: theme.spacing(3),
alignSelf: 'stretch',
borderRadius: theme.shape.borderRadiusMedium,
background: '#F7F7FA',
}));
const StatusWithDot = styled(Box)(({ theme }) => ({
display: 'flex',
alignItems: 'center',
gap: theme.spacing(1),
}));
const Dot = styled('span', {
shouldForwardProp: (prop) => prop !== 'color',
})<{
color?: string;
}>(({ theme, color }) => ({
height: '15px',
width: '15px',
borderRadius: '50%',
display: 'inline-block',
backgroundColor: color,
}));
export const StyledCount = styled('span')(({ theme }) => ({
fontSize: theme.typography.h1.fontSize,
fontWeight: theme.typography.fontWeightRegular,
color: theme.palette.text.primary,
}));
export const ProjectMembers = ({
members,
projectId,
}: IProjectMembersProps) => {
const { uiConfig } = useUiConfig();
const theme = useTheme();
const link = uiConfig?.versionInfo?.current?.enterprise
? `/projects/${projectId}/settings/access`
: `/admin/users`;
const { active, totalPreviousMonth, inactive } = members;
const currentMembers = active + inactive;
const change = currentMembers - (totalPreviousMonth || 0);
const activePercentage = (active / currentMembers) * 100;
const inactivePercentage = (inactive / currentMembers) * 100;
const { currentMembers, change } = members;
return (
<StyledProjectInfoWidgetContainer>
<NavigationBar to={link}>
@ -122,28 +47,6 @@ export const ProjectMembers = ({
>
<StatusBox boxText={`${currentMembers}`} change={change} />
</Box>
<BarContainer>
<ActiveBar percentage={activePercentage} />
<InactiveBar percentage={inactivePercentage} />
</BarContainer>
<CountContainer>
<CountRow to={link}>
<StatusWithDot>
<Dot color={theme.palette.success.border} />
<Box>Active</Box>
<StyledCount>{active}</StyledCount>
</StatusWithDot>
<KeyboardArrowRight />
</CountRow>
<CountRow to={link}>
<StatusWithDot>
<Dot color={theme.palette.warning.border} />
<Box>Inactive</Box>
<StyledCount>{inactive}</StyledCount>
</StatusWithDot>
<KeyboardArrowRight />
</CountRow>
</CountContainer>
</StyledProjectInfoWidgetContainer>
);
};

View File

@ -46,9 +46,8 @@ const placeholderData: ProjectInsightsSchema = {
staleCount: 0,
},
members: {
active: 0,
inactive: 0,
totalPreviousMonth: 0,
currentMembers: 0,
change: 0,
},
changeRequests: {
total: 0,

View File

@ -8,10 +8,8 @@
* Active/inactive users summary
*/
export type ProjectInsightsSchemaMembers = {
/** The number of active project members who have used Unleash in the past 60 days */
active: number;
/** The number of inactive project members who have not used Unleash in the past 60 days */
inactive: number;
/** The number of total project members in the previous month */
totalPreviousMonth?: number;
/** The change in the number of project members compared to the previous month */
change: number;
/** The number of total project members */
currentMembers: number;
};

View File

@ -2249,10 +2249,10 @@
dependencies:
undici-types "~5.26.4"
"@types/node@18.19.23":
version "18.19.23"
resolved "https://registry.yarnpkg.com/@types/node/-/node-18.19.23.tgz#e02c759218bc9957423a3f7d585d511b17be2351"
integrity sha512-wtE3d0OUfNKtZYAqZb8HAWGxxXsImJcPUAgZNw+dWFxO6s5tIwIjyKnY76tsTatsNCLJPkVYwUpq15D38ng9Aw==
"@types/node@18.19.24":
version "18.19.24"
resolved "https://registry.yarnpkg.com/@types/node/-/node-18.19.24.tgz#707d8a4907e55901466e60e8f7a62bc6197ace95"
integrity sha512-eghAz3gnbQbvnHqB+mgB2ZR3aH6RhdEmHGS48BnV75KceQPHqabkxKI0BbUSsqhqy2Ddhc2xD/VAR9ySZd57Lw==
dependencies:
undici-types "~5.26.4"
@ -3259,10 +3259,10 @@ cypress-vite@^1.4.0:
chokidar "^3.5.3"
debug "^4.3.4"
cypress@13.6.6:
version "13.6.6"
resolved "https://registry.yarnpkg.com/cypress/-/cypress-13.6.6.tgz#5133f231ed1c6e57dc8dcbf60aade220bcd6884b"
integrity sha512-S+2S9S94611hXimH9a3EAYt81QM913ZVA03pUmGDfLTFa5gyp85NJ8dJGSlEAEmyRsYkioS1TtnWtbv/Fzt11A==
cypress@13.7.0:
version "13.7.0"
resolved "https://registry.yarnpkg.com/cypress/-/cypress-13.7.0.tgz#19e53c0bd6eca5e3bde0d6ac9e98fbf1782e3a9e"
integrity sha512-UimjRSJJYdTlvkChcdcfywKJ6tUYuwYuk/n1uMMglrvi+ZthNhoRYcxnWgTqUtkl17fXrPAsD5XT2rcQYN1xKA==
dependencies:
"@cypress/request" "^3.0.0"
"@cypress/xvfb" "^1.2.4"
@ -6358,16 +6358,7 @@ safe-stable-stringify@^1.1:
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
sass@1.71.1:
version "1.71.1"
resolved "https://registry.yarnpkg.com/sass/-/sass-1.71.1.tgz#dfb09c63ce63f89353777bbd4a88c0a38386ee54"
integrity sha512-wovtnV2PxzteLlfNzbgm1tFXPLoZILYAMJtvoXXkD7/+1uP41eKkIt1ypWq5/q2uT94qHjXehEYfmjKOvjL9sg==
dependencies:
chokidar ">=3.0.0 <4.0.0"
immutable "^4.0.0"
source-map-js ">=0.6.2 <2.0.0"
sass@^1.71.1:
sass@1.72.0, sass@^1.71.1:
version "1.72.0"
resolved "https://registry.yarnpkg.com/sass/-/sass-1.72.0.tgz#5b9978943fcfb32b25a6a5acb102fc9dabbbf41c"
integrity sha512-Gpczt3WA56Ly0Mn8Sl21Vj94s1axi9hDIzDFn9Ph9x3C3p4nNyvsqJoQyVXKou6cBlfFWEgRW4rT8Tb4i3XnVA==
@ -6906,10 +6897,10 @@ tsconfck@^2.0.1:
resolved "https://registry.yarnpkg.com/tsconfck/-/tsconfck-2.1.2.tgz#f667035874fa41d908c1fe4d765345fcb1df6e35"
integrity sha512-ghqN1b0puy3MhhviwO2kGF8SeMDNhEbnKxjK7h6+fvY9JAxqvXi8y5NAHSQv687OVboS2uZIByzGd45/YxrRHg==
tsconfck@^3.0.1:
version "3.0.2"
resolved "https://registry.yarnpkg.com/tsconfck/-/tsconfck-3.0.2.tgz#d8e279f7a049d55f207f528d13fa493e1d8e7ceb"
integrity sha512-6lWtFjwuhS3XI4HsX4Zg0izOI3FU/AI9EGVlPEUMDIhvLPMD4wkiof0WCoDgW7qY+Dy198g4d9miAqUHWHFH6Q==
tsconfck@^3.0.3:
version "3.0.3"
resolved "https://registry.yarnpkg.com/tsconfck/-/tsconfck-3.0.3.tgz#d9bda0e87d05b1c360e996c9050473c7e6f8084f"
integrity sha512-4t0noZX9t6GcPTfBAbIbbIU4pfpCwh0ueq3S4O/5qXI1VwK1outmxhe9dOiEWqMz3MW2LKgDTpqWV+37IWuVbA==
tslib@^1.14.1:
version "1.14.1"
@ -7185,10 +7176,10 @@ validator@^13.7.0:
resolved "https://registry.yarnpkg.com/validator/-/validator-13.11.0.tgz#23ab3fd59290c61248364eabf4067f04955fbb1b"
integrity sha512-Ii+sehpSfZy+At5nPdnyMhx78fEoPDkR2XW/zimHEL3MyGJQOCQ7WeP20jPYRz7ZCpcKLB21NxuXHF3bxjStBQ==
vanilla-jsoneditor@^0.22.0:
version "0.22.0"
resolved "https://registry.yarnpkg.com/vanilla-jsoneditor/-/vanilla-jsoneditor-0.22.0.tgz#0ba372d517c38c866b48b821e6fa1ba3b6d7c415"
integrity sha512-r6AN3NAWyVFb9pH6iNIa2Q1YiSGai4PGhFN+YnR4OJzXE72hu/Xu/MUbVyGvJxchhPcWj6bIPB1Ox5KSOspmFA==
vanilla-jsoneditor@^0.23.0:
version "0.23.0"
resolved "https://registry.yarnpkg.com/vanilla-jsoneditor/-/vanilla-jsoneditor-0.23.0.tgz#edecb3620886eae9db5e53082969f2a56161cb3f"
integrity sha512-8gWgvj9xqnlI5M4lXxFN0NUeDsYqT4izh6PeztViRtwaXywH8Z8J0GUWMlnnzWuhvSjnZxlrCP5Yce5V6vwqUA==
dependencies:
"@codemirror/autocomplete" "^6.13.0"
"@codemirror/commands" "^6.3.3"
@ -7278,14 +7269,14 @@ vite-plugin-svgr@3.3.0:
"@svgr/core" "^8.1.0"
"@svgr/plugin-jsx" "^8.1.0"
vite-tsconfig-paths@4.3.1:
version "4.3.1"
resolved "https://registry.yarnpkg.com/vite-tsconfig-paths/-/vite-tsconfig-paths-4.3.1.tgz#28762938151e7c80aec9d70c57e65ddce43a576f"
integrity sha512-cfgJwcGOsIxXOLU/nELPny2/LUD/lcf1IbfyeKTv2bsupVbTH/xpFtdQlBmIP1GEK2CjjLxYhFfB+QODFAx5aw==
vite-tsconfig-paths@4.3.2:
version "4.3.2"
resolved "https://registry.yarnpkg.com/vite-tsconfig-paths/-/vite-tsconfig-paths-4.3.2.tgz#321f02e4b736a90ff62f9086467faf4e2da857a9"
integrity sha512-0Vd/a6po6Q+86rPlntHye7F31zA2URZMbH8M3saAZ/xR9QoGN/L21bxEGfXdWmFdNkqPpRdxFT7nmNe12e9/uA==
dependencies:
debug "^4.1.1"
globrex "^0.1.2"
tsconfck "^3.0.1"
tsconfck "^3.0.3"
vite@5.1.6, vite@^5.0.0:
version "5.1.6"

View File

@ -153,7 +153,7 @@
"@apidevtools/swagger-parser": "10.1.0",
"@babel/core": "7.24.0",
"@biomejs/biome": "1.6.1",
"@swc/core": "1.4.6",
"@swc/core": "1.4.7",
"@swc/jest": "0.2.36",
"@types/bcryptjs": "2.4.6",
"@types/cors": "2.8.17",
@ -167,7 +167,7 @@
"@types/make-fetch-happen": "10.0.4",
"@types/memoizee": "0.4.11",
"@types/mime": "3.0.4",
"@types/node": "18.19.23",
"@types/node": "18.19.24",
"@types/nodemailer": "6.4.14",
"@types/owasp-password-strength-test": "1.3.2",
"@types/pg": "8.11.2",

View File

@ -12,6 +12,8 @@ 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';
export const createProjectInsightsService = (
db: Db,
@ -34,6 +36,7 @@ export const createProjectInsightsService = (
const featureTypeStore = new FeatureTypeStore(db, getLogger);
const projectStatsStore = new ProjectStatsStore(db, eventBus, getLogger);
const featureToggleService = createFeatureToggleService(db, config);
const projectInsightsReadModel = new ProjectInsightsReadModel(db);
return new ProjectInsightsService(
{
@ -43,6 +46,7 @@ export const createProjectInsightsService = (
projectStatsStore,
},
featureToggleService,
projectInsightsReadModel,
);
};
@ -54,6 +58,7 @@ export const createFakeProjectInsightsService = (
const featureTypeStore = new FakeFeatureTypeStore();
const projectStatsStore = new FakeProjectStatsStore();
const featureToggleService = createFakeFeatureToggleService(config);
const projectInsightsReadModel = new FakeProjectInsightsReadModel();
return new ProjectInsightsService(
{
@ -63,5 +68,6 @@ export const createFakeProjectInsightsService = (
projectStatsStore,
},
featureToggleService,
projectInsightsReadModel,
);
};

View File

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

View File

@ -0,0 +1,12 @@
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

@ -0,0 +1,59 @@
import type {
ChangeRequestCounts,
IProjectInsightsReadModel,
} from './project-insights-read-model-type';
import type { Db } from '../../db/db';
export type ChangeRequestDBState =
| '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

@ -10,6 +10,7 @@ import { calculateAverageTimeToProd } from '../feature-toggle/time-to-production
import type { IProjectStatsStore } from '../../types/stores/project-stats-store-type';
import type { ProjectDoraMetricsSchema } from '../../openapi';
import { calculateProjectHealth } from '../../domain/project-health/project-health';
import type { IProjectInsightsReadModel } from './project-insights-read-model-type';
export class ProjectInsightsService {
private projectStore: IProjectStore;
@ -22,6 +23,8 @@ export class ProjectInsightsService {
private projectStatsStore: IProjectStatsStore;
private projectInsightsReadModel: IProjectInsightsReadModel;
constructor(
{
projectStore,
@ -36,12 +39,14 @@ export class ProjectInsightsService {
| 'featureTypeStore'
>,
featureToggleService: FeatureToggleService,
projectInsightsReadModel: IProjectInsightsReadModel,
) {
this.projectStore = projectStore;
this.featureToggleStore = featureToggleStore;
this.featureTypeStore = featureTypeStore;
this.featureToggleService = featureToggleService;
this.projectStatsStore = projectStatsStore;
this.projectInsightsReadModel = projectInsightsReadModel;
}
private async getDoraMetrics(
@ -99,31 +104,31 @@ export class ProjectInsightsService {
async getProjectInsights(projectId: string) {
const result = {
members: {
active: 20,
inactive: 3,
totalPreviousMonth: 15,
},
changeRequests: {
total: 24,
approved: 5,
applied: 2,
rejected: 4,
reviewRequired: 10,
scheduled: 3,
currentMembers: 20,
change: 3,
},
};
const [stats, featureTypeCounts, health, leadTime] = await Promise.all([
this.projectStatsStore.getProjectStats(projectId),
this.featureToggleService.getFeatureTypeCounts({
projectId,
archived: false,
}),
this.getHealthInsights(projectId),
this.getDoraMetrics(projectId),
]);
const [stats, featureTypeCounts, health, leadTime, changeRequests] =
await Promise.all([
this.projectStatsStore.getProjectStats(projectId),
this.featureToggleService.getFeatureTypeCounts({
projectId,
archived: false,
}),
this.getHealthInsights(projectId),
this.getDoraMetrics(projectId),
this.projectInsightsReadModel.getChangeRequests(projectId),
]);
return { ...result, stats, featureTypeCounts, health, leadTime };
return {
...result,
stats,
featureTypeCounts,
health,
leadTime,
changeRequests,
};
}
private async getProjectHealth(

View File

@ -34,6 +34,8 @@ test('project insights happy path', async () => {
.expect('Content-Type', /json/)
.expect(200);
console.log(body);
expect(body).toMatchObject({
stats: {
avgTimeToProdCurrentWindow: 0,
@ -53,5 +55,13 @@ 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

@ -76,7 +76,6 @@ import type {
IProjectEnterpriseSettingsUpdate,
IProjectQuery,
} from './project-store-type';
import { calculateProjectHealth } from '../../domain/project-health/project-health';
const getCreatedBy = (user: IUser) => user.email || user.username || 'unknown';
@ -1240,62 +1239,6 @@ export default class ProjectService {
};
}
private async getHealthInsights(projectId: string) {
const [overview, featureTypes] = await Promise.all([
this.getProjectHealth(projectId, false, undefined),
this.featureTypeStore.getAll(),
]);
const { activeCount, potentiallyStaleCount, staleCount } =
calculateProjectHealth(overview.features, featureTypes);
return {
activeCount,
potentiallyStaleCount,
staleCount,
rating: overview.health,
};
}
async getProjectInsights(projectId: string) {
const result = {
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 },
],
},
members: {
active: 20,
inactive: 3,
totalPreviousMonth: 15,
},
changeRequests: {
total: 24,
approved: 5,
applied: 2,
rejected: 4,
reviewRequired: 10,
scheduled: 3,
},
};
const [stats, featureTypeCounts, health] = await Promise.all([
this.projectStatsStore.getProjectStats(projectId),
this.featureToggleService.getFeatureTypeCounts({
projectId,
archived: false,
}),
this.getHealthInsights(projectId),
]);
return { ...result, stats, featureTypeCounts, health };
}
async getProjectHealth(
projectId: string,
archived: boolean = false,

View File

@ -64,26 +64,19 @@ export const projectInsightsSchema = {
},
members: {
type: 'object',
required: ['active', 'inactive'],
required: ['currentMembers', 'change'],
properties: {
active: {
currentMembers: {
type: 'number',
description:
'The number of active project members who have used Unleash in the past 60 days',
description: 'The number of total project members',
example: 10,
},
inactive: {
change: {
type: 'number',
description:
'The number of inactive project members who have not used Unleash in the past 60 days',
'The change in the number of project members compared to the previous month',
example: 10,
},
totalPreviousMonth: {
type: 'number',
description:
'The number of total project members in the previous month',
example: 8,
},
},
description: 'Active/inactive users summary',
},

116
yarn.lock
View File

@ -1175,74 +1175,74 @@
p-queue "^6.6.1"
p-retry "^4.0.0"
"@swc/core-darwin-arm64@1.4.6":
version "1.4.6"
resolved "https://registry.yarnpkg.com/@swc/core-darwin-arm64/-/core-darwin-arm64-1.4.6.tgz#4465c6f1ae011d4829ba8923f6aac9aae7828416"
integrity sha512-bpggpx/BfLFyy48aUKq1PsNUxb7J6CINlpAUk0V4yXfmGnpZH80Gp1pM3GkFDQyCfq7L7IpjPrIjWQwCrL4hYw==
"@swc/core-darwin-arm64@1.4.7":
version "1.4.7"
resolved "https://registry.yarnpkg.com/@swc/core-darwin-arm64/-/core-darwin-arm64-1.4.7.tgz#158c7d1a5d1aa0d1e57f24f7a58fa23389d4b666"
integrity sha512-IhfP2Mrrh9WcdlBJQbPNBhfdOhW/SC910SiuzvxaLgJmzq1tw6TVDNUz4Zf85TbK5uzgR0emtPc9hTGxynl57A==
"@swc/core-darwin-x64@1.4.6":
version "1.4.6"
resolved "https://registry.yarnpkg.com/@swc/core-darwin-x64/-/core-darwin-x64-1.4.6.tgz#5ddda080ad9824b56d6470163c752e27e22d5a89"
integrity sha512-vJn+/ZuBTg+vtNkcmgZdH6FQpa0hFVdnB9bAeqYwKkyqP15zaPe6jfC+qL2y/cIeC7ASvHXEKrnCZgBLxfVQ9w==
"@swc/core-darwin-x64@1.4.7":
version "1.4.7"
resolved "https://registry.yarnpkg.com/@swc/core-darwin-x64/-/core-darwin-x64-1.4.7.tgz#4a7a9a935d1a23402d45a1602026e4f677983776"
integrity sha512-MO01pnxJDS6st5IiqyTnAOz9kpAPP/O4lzEUH9E80XdXBzwptS5hNTM0egBlqueWDFrPM26RI81JLtyTU7kR8w==
"@swc/core-linux-arm-gnueabihf@1.4.6":
version "1.4.6"
resolved "https://registry.yarnpkg.com/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.4.6.tgz#eb6dbd10bd4b6ad36f85ef9c99c675f1aa4f598b"
integrity sha512-hEmYcB/9XBAl02MtuVHszhNjQpjBzhk/NFulnU33tBMbNZpy2TN5yTsitezMq090QXdDz8sKIALApDyg07ZR8g==
"@swc/core-linux-arm-gnueabihf@1.4.7":
version "1.4.7"
resolved "https://registry.yarnpkg.com/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.4.7.tgz#54f80af37fae84676d50f729312bd5ff384a8c42"
integrity sha512-+cDaXW6PZqGhXIq9C4xE+/QuyUsLkXf8d8uSXep+rZYDl4YHS9Fi7HpZQnqLX6al/iVhwe3VnxHMGw50gxcr/g==
"@swc/core-linux-arm64-gnu@1.4.6":
version "1.4.6"
resolved "https://registry.yarnpkg.com/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.4.6.tgz#41ce65332b2e8d5bf69b0e95fd02a9134784d5de"
integrity sha512-/UCYIVoGpm2YVvGHZM2QOA3dexa28BjcpLAIYnoCbgH5f7ulDhE8FAIO/9pasj+kixDBsdqewHfsNXFYlgGJjQ==
"@swc/core-linux-arm64-gnu@1.4.7":
version "1.4.7"
resolved "https://registry.yarnpkg.com/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.4.7.tgz#0f0a47b6e6d2d5404c755a73ba52a71240763500"
integrity sha512-RNnVHRKhEtA3pM34wgb3Vumf5M6/XlWzFdkHEMZIkOKyNSUhZiv8X3tsEK+n1rZQWIDkvlw4YyHtB8vK18WdCA==
"@swc/core-linux-arm64-musl@1.4.6":
version "1.4.6"
resolved "https://registry.yarnpkg.com/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.4.6.tgz#0d579abd7960db3f08915beac0f3bac3cd202425"
integrity sha512-LGQsKJ8MA9zZ8xHCkbGkcPSmpkZL2O7drvwsGKynyCttHhpwVjj9lguhD4DWU3+FWIsjvho5Vu0Ggei8OYi/Lw==
"@swc/core-linux-arm64-musl@1.4.7":
version "1.4.7"
resolved "https://registry.yarnpkg.com/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.4.7.tgz#baafa993ae121f43162a00fdb774b8456c0079bc"
integrity sha512-p7Xm4Pib02d1SFS9XXMoOcCTDIkFWMspspptPX00VcjAdZYnXWujWGuD2W+KN1gq5syHB1g3TsYs9LP2dGsKqw==
"@swc/core-linux-x64-gnu@1.4.6":
version "1.4.6"
resolved "https://registry.yarnpkg.com/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.4.6.tgz#204d4a722ae8e9d4800d37583b64ef6472f6ad74"
integrity sha512-10JL2nLIreMQDKvq2TECnQe5fCuoqBHu1yW8aChqgHUyg9d7gfZX/kppUsuimqcgRBnS0AjTDAA+JF6UsG/2Yg==
"@swc/core-linux-x64-gnu@1.4.7":
version "1.4.7"
resolved "https://registry.yarnpkg.com/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.4.7.tgz#88cfbc497c997aff209b57115d9cbaf597393ff9"
integrity sha512-ViI5jy03cFYPETsye1J+oPbHE4v8oIDN34qebzvgHUlNKOXfc1ig0Zha5oQnKp3zj1rmjcSLIMqK++WR021G5A==
"@swc/core-linux-x64-musl@1.4.6":
version "1.4.6"
resolved "https://registry.yarnpkg.com/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.4.6.tgz#d2d1b1130a275a428ff144fedaffb75db8081022"
integrity sha512-EGyjFVzVY6Do89x8sfah7I3cuP4MwtwzmA6OlfD/KASqfCFf5eIaEBMbajgR41bVfMV7lK72lwAIea5xEyq1AQ==
"@swc/core-linux-x64-musl@1.4.7":
version "1.4.7"
resolved "https://registry.yarnpkg.com/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.4.7.tgz#12f6277d3f7167a698a5c87d0796dd810f8eb273"
integrity sha512-Nf3Axcx/ILl7XE44eidNNPF39rg/KIeqg2545vrOXJG02iu7pEjZuu8wm6w+23BpP4COjZJymlg9LzPT1ZBD5Q==
"@swc/core-win32-arm64-msvc@1.4.6":
version "1.4.6"
resolved "https://registry.yarnpkg.com/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.4.6.tgz#86066f9eee4e73b24826184f930eb3fb44e2b973"
integrity sha512-gfW9AuXvwSyK07Vb8Y8E9m2oJZk21WqcD+X4BZhkbKB0TCZK0zk1j/HpS2UFlr1JB2zPKPpSWLU3ll0GEHRG2A==
"@swc/core-win32-arm64-msvc@1.4.7":
version "1.4.7"
resolved "https://registry.yarnpkg.com/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.4.7.tgz#591ea5067d3ee0d1f45b313dde2b8d8fb6be5eb2"
integrity sha512-MFkJEaC59AO2HpndmHhCkaj8NJus5etjMtBphOe9em7jmmfdQ7mLenKHbZ/CspHNl8yNPO9Qzpa/at2838x+RQ==
"@swc/core-win32-ia32-msvc@1.4.6":
version "1.4.6"
resolved "https://registry.yarnpkg.com/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.4.6.tgz#238b5143b9fec2048cf0a0e24763775561a472b1"
integrity sha512-ZuQm81FhhvNVYtVb9GfZ+Du6e7fZlkisWvuCeBeRiyseNt1tcrQ8J3V67jD2nxje8CVXrwG3oUIbPcybv2rxfQ==
"@swc/core-win32-ia32-msvc@1.4.7":
version "1.4.7"
resolved "https://registry.yarnpkg.com/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.4.7.tgz#bc603df8aff3a21d4f9f2b834e751d3ec1bf45ef"
integrity sha512-nwrfERocUei9sxqd6URrWcEC3KDcTBD+beMerB9idvuzy4rcm5k1O1ClUlZ9pJOZn+vMN1tqZjLze4hJMT9STQ==
"@swc/core-win32-x64-msvc@1.4.6":
version "1.4.6"
resolved "https://registry.yarnpkg.com/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.4.6.tgz#36f8fbe6ec04c5b39c34ad41bb6803ba9a306da7"
integrity sha512-UagPb7w5V0uzWSjrXwOavGa7s9iv3wrVdEgWy+/inm0OwY4lj3zpK9qDnMWAwYLuFwkI3UG4Q3dH8wD+CUUcjw==
"@swc/core-win32-x64-msvc@1.4.7":
version "1.4.7"
resolved "https://registry.yarnpkg.com/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.4.7.tgz#b4683ad864591e127f865fd1fbca2cfbae9e672a"
integrity sha512-d5T8Z/axAml8FTA+T9RS2mwJDNIbSSz5jcEiWaGuKVDIoSZib2HpMvnMydOGsIrmjfS1Z4ZhdAawivPhAZ3M8Q==
"@swc/core@1.4.6":
version "1.4.6"
resolved "https://registry.yarnpkg.com/@swc/core/-/core-1.4.6.tgz#d2bceb1296aaf0fa6fbd85eb6f8d797fe5cd8d8c"
integrity sha512-A7iK9+1qzTCIuc3IYcS8gPHCm9bZVKUJrfNnwveZYyo6OFp3jLno4WOM2yBy5uqedgYATEiWgBYHKq37KrU6IA==
"@swc/core@1.4.7":
version "1.4.7"
resolved "https://registry.yarnpkg.com/@swc/core/-/core-1.4.7.tgz#94ac45187fa2fdbd6cbb34dbda089c174ec64c1d"
integrity sha512-I7a9sUxB+z+UCf6KudqrQH/RgLal/S+E+t4uBdbggycLyJe7WvBgPrQlcN5UpEuD9YC2PJ0CN6kgD6ARStg+pg==
dependencies:
"@swc/counter" "^0.1.2"
"@swc/types" "^0.1.5"
optionalDependencies:
"@swc/core-darwin-arm64" "1.4.6"
"@swc/core-darwin-x64" "1.4.6"
"@swc/core-linux-arm-gnueabihf" "1.4.6"
"@swc/core-linux-arm64-gnu" "1.4.6"
"@swc/core-linux-arm64-musl" "1.4.6"
"@swc/core-linux-x64-gnu" "1.4.6"
"@swc/core-linux-x64-musl" "1.4.6"
"@swc/core-win32-arm64-msvc" "1.4.6"
"@swc/core-win32-ia32-msvc" "1.4.6"
"@swc/core-win32-x64-msvc" "1.4.6"
"@swc/core-darwin-arm64" "1.4.7"
"@swc/core-darwin-x64" "1.4.7"
"@swc/core-linux-arm-gnueabihf" "1.4.7"
"@swc/core-linux-arm64-gnu" "1.4.7"
"@swc/core-linux-arm64-musl" "1.4.7"
"@swc/core-linux-x64-gnu" "1.4.7"
"@swc/core-linux-x64-musl" "1.4.7"
"@swc/core-win32-arm64-msvc" "1.4.7"
"@swc/core-win32-ia32-msvc" "1.4.7"
"@swc/core-win32-x64-msvc" "1.4.7"
"@swc/counter@^0.1.2", "@swc/counter@^0.1.3":
version "0.1.3"
@ -1525,10 +1525,10 @@
dependencies:
undici-types "~5.26.4"
"@types/node@18.19.23":
version "18.19.23"
resolved "https://registry.yarnpkg.com/@types/node/-/node-18.19.23.tgz#e02c759218bc9957423a3f7d585d511b17be2351"
integrity sha512-wtE3d0OUfNKtZYAqZb8HAWGxxXsImJcPUAgZNw+dWFxO6s5tIwIjyKnY76tsTatsNCLJPkVYwUpq15D38ng9Aw==
"@types/node@18.19.24":
version "18.19.24"
resolved "https://registry.yarnpkg.com/@types/node/-/node-18.19.24.tgz#707d8a4907e55901466e60e8f7a62bc6197ace95"
integrity sha512-eghAz3gnbQbvnHqB+mgB2ZR3aH6RhdEmHGS48BnV75KceQPHqabkxKI0BbUSsqhqy2Ddhc2xD/VAR9ySZd57Lw==
dependencies:
undici-types "~5.26.4"