reviewflow 3.4.0 → 3.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +24 -0
- package/dist/cli/parseCliArgs.d.ts +6 -1
- package/dist/cli/parseCliArgs.d.ts.map +1 -1
- package/dist/cli/parseCliArgs.js +11 -1
- package/dist/cli/parseCliArgs.js.map +1 -1
- package/dist/config/projectConfig.d.ts +6 -0
- package/dist/config/projectConfig.d.ts.map +1 -1
- package/dist/config/projectConfig.js +13 -0
- package/dist/config/projectConfig.js.map +1 -1
- package/dist/entities/language/language.schema.d.ts +7 -0
- package/dist/entities/language/language.schema.d.ts.map +1 -0
- package/dist/entities/language/language.schema.js +3 -0
- package/dist/entities/language/language.schema.js.map +1 -0
- package/dist/frameworks/claude/claudeInvoker.d.ts +1 -1
- package/dist/frameworks/claude/claudeInvoker.d.ts.map +1 -1
- package/dist/frameworks/claude/claudeInvoker.js +6 -3
- package/dist/frameworks/claude/claudeInvoker.js.map +1 -1
- package/dist/frameworks/claude/languageDirective.d.ts +3 -0
- package/dist/frameworks/claude/languageDirective.d.ts.map +1 -0
- package/dist/frameworks/claude/languageDirective.js +9 -0
- package/dist/frameworks/claude/languageDirective.js.map +1 -0
- package/dist/frameworks/queue/pQueueAdapter.d.ts +2 -0
- package/dist/frameworks/queue/pQueueAdapter.d.ts.map +1 -1
- package/dist/frameworks/queue/pQueueAdapter.js +1 -1
- package/dist/frameworks/queue/pQueueAdapter.js.map +1 -1
- package/dist/frameworks/settings/runtimeSettings.d.ts +4 -0
- package/dist/frameworks/settings/runtimeSettings.d.ts.map +1 -1
- package/dist/frameworks/settings/runtimeSettings.js +8 -1
- package/dist/frameworks/settings/runtimeSettings.js.map +1 -1
- package/dist/interface-adapters/controllers/http/settings.routes.d.ts.map +1 -1
- package/dist/interface-adapters/controllers/http/settings.routes.js +12 -1
- package/dist/interface-adapters/controllers/http/settings.routes.js.map +1 -1
- package/dist/interface-adapters/controllers/webhook/github.controller.d.ts.map +1 -1
- package/dist/interface-adapters/controllers/webhook/github.controller.js +2 -1
- package/dist/interface-adapters/controllers/webhook/github.controller.js.map +1 -1
- package/dist/interface-adapters/controllers/webhook/gitlab.controller.d.ts.map +1 -1
- package/dist/interface-adapters/controllers/webhook/gitlab.controller.js +2 -1
- package/dist/interface-adapters/controllers/webhook/gitlab.controller.js.map +1 -1
- package/dist/interface-adapters/views/dashboard/index.html +974 -393
- package/dist/interface-adapters/views/dashboard/modules/assignee.d.ts +7 -0
- package/dist/interface-adapters/views/dashboard/modules/assignee.d.ts.map +1 -0
- package/dist/interface-adapters/views/dashboard/modules/assignee.js +47 -0
- package/dist/interface-adapters/views/dashboard/modules/assignee.js.map +1 -0
- package/dist/interface-adapters/views/dashboard/modules/constants.d.ts +7 -0
- package/dist/interface-adapters/views/dashboard/modules/constants.d.ts.map +1 -0
- package/dist/interface-adapters/views/dashboard/modules/constants.js +6 -0
- package/dist/interface-adapters/views/dashboard/modules/constants.js.map +1 -0
- package/dist/interface-adapters/views/dashboard/modules/desktopNotifications.d.ts +23 -0
- package/dist/interface-adapters/views/dashboard/modules/desktopNotifications.d.ts.map +1 -0
- package/dist/interface-adapters/views/dashboard/modules/desktopNotifications.js +37 -0
- package/dist/interface-adapters/views/dashboard/modules/desktopNotifications.js.map +1 -0
- package/dist/interface-adapters/views/dashboard/modules/formatting.d.ts +23 -0
- package/dist/interface-adapters/views/dashboard/modules/formatting.d.ts.map +1 -0
- package/dist/interface-adapters/views/dashboard/modules/formatting.js +57 -0
- package/dist/interface-adapters/views/dashboard/modules/formatting.js.map +1 -0
- package/dist/interface-adapters/views/dashboard/modules/html.d.ts +16 -0
- package/dist/interface-adapters/views/dashboard/modules/html.d.ts.map +1 -0
- package/dist/interface-adapters/views/dashboard/modules/html.js +68 -0
- package/dist/interface-adapters/views/dashboard/modules/html.js.map +1 -0
- package/dist/interface-adapters/views/dashboard/modules/i18n.d.ts +11 -0
- package/dist/interface-adapters/views/dashboard/modules/i18n.d.ts.map +1 -0
- package/dist/interface-adapters/views/dashboard/modules/i18n.js +500 -0
- package/dist/interface-adapters/views/dashboard/modules/i18n.js.map +1 -0
- package/dist/interface-adapters/views/dashboard/modules/icons.d.ts +13 -0
- package/dist/interface-adapters/views/dashboard/modules/icons.d.ts.map +1 -0
- package/dist/interface-adapters/views/dashboard/modules/icons.js +29 -0
- package/dist/interface-adapters/views/dashboard/modules/icons.js.map +1 -0
- package/dist/interface-adapters/views/dashboard/modules/loading.d.ts +30 -0
- package/dist/interface-adapters/views/dashboard/modules/loading.d.ts.map +1 -0
- package/dist/interface-adapters/views/dashboard/modules/loading.js +48 -0
- package/dist/interface-adapters/views/dashboard/modules/loading.js.map +1 -0
- package/dist/interface-adapters/views/dashboard/modules/notifications.d.ts +39 -0
- package/dist/interface-adapters/views/dashboard/modules/notifications.d.ts.map +1 -0
- package/dist/interface-adapters/views/dashboard/modules/notifications.js +131 -0
- package/dist/interface-adapters/views/dashboard/modules/notifications.js.map +1 -0
- package/dist/interface-adapters/views/dashboard/modules/priority.d.ts +10 -0
- package/dist/interface-adapters/views/dashboard/modules/priority.d.ts.map +1 -0
- package/dist/interface-adapters/views/dashboard/modules/priority.js +86 -0
- package/dist/interface-adapters/views/dashboard/modules/priority.js.map +1 -0
- package/dist/interface-adapters/views/dashboard/modules/quality.d.ts +30 -0
- package/dist/interface-adapters/views/dashboard/modules/quality.d.ts.map +1 -0
- package/dist/interface-adapters/views/dashboard/modules/quality.js +83 -0
- package/dist/interface-adapters/views/dashboard/modules/quality.js.map +1 -0
- package/dist/interface-adapters/views/dashboard/modules/queueLanes.d.ts +22 -0
- package/dist/interface-adapters/views/dashboard/modules/queueLanes.d.ts.map +1 -0
- package/dist/interface-adapters/views/dashboard/modules/queueLanes.js +27 -0
- package/dist/interface-adapters/views/dashboard/modules/queueLanes.js.map +1 -0
- package/dist/interface-adapters/views/dashboard/modules/sessionMetrics.d.ts +54 -0
- package/dist/interface-adapters/views/dashboard/modules/sessionMetrics.d.ts.map +1 -0
- package/dist/interface-adapters/views/dashboard/modules/sessionMetrics.js +120 -0
- package/dist/interface-adapters/views/dashboard/modules/sessionMetrics.js.map +1 -0
- package/dist/interface-adapters/views/dashboard/styles.css +1031 -93
- package/dist/main/cli.d.ts +41 -1
- package/dist/main/cli.d.ts.map +1 -1
- package/dist/main/cli.js +228 -88
- package/dist/main/cli.js.map +1 -1
- package/dist/tests/factories/reviewJob.factory.d.ts.map +1 -1
- package/dist/tests/factories/reviewJob.factory.js +1 -0
- package/dist/tests/factories/reviewJob.factory.js.map +1 -1
- package/dist/tests/units/cli/parseCliArgs.test.js +14 -0
- package/dist/tests/units/cli/parseCliArgs.test.js.map +1 -1
- package/dist/tests/units/config/projectConfig.test.d.ts +2 -0
- package/dist/tests/units/config/projectConfig.test.d.ts.map +1 -0
- package/dist/tests/units/config/projectConfig.test.js +69 -0
- package/dist/tests/units/config/projectConfig.test.js.map +1 -0
- package/dist/tests/units/entities/language/language.schema.test.d.ts +2 -0
- package/dist/tests/units/entities/language/language.schema.test.d.ts.map +1 -0
- package/dist/tests/units/entities/language/language.schema.test.js +17 -0
- package/dist/tests/units/entities/language/language.schema.test.js.map +1 -0
- package/dist/tests/units/frameworks/claude/languageDirective.test.d.ts +2 -0
- package/dist/tests/units/frameworks/claude/languageDirective.test.d.ts.map +1 -0
- package/dist/tests/units/frameworks/claude/languageDirective.test.js +13 -0
- package/dist/tests/units/frameworks/claude/languageDirective.test.js.map +1 -0
- package/dist/tests/units/frameworks/settings/runtimeSettings.test.d.ts +2 -0
- package/dist/tests/units/frameworks/settings/runtimeSettings.test.d.ts.map +1 -0
- package/dist/tests/units/frameworks/settings/runtimeSettings.test.js +20 -0
- package/dist/tests/units/frameworks/settings/runtimeSettings.test.js.map +1 -0
- package/dist/tests/units/interface-adapters/controllers/webhook/gitlab.controller.test.js +1 -0
- package/dist/tests/units/interface-adapters/controllers/webhook/gitlab.controller.test.js.map +1 -1
- package/dist/tests/units/interface-adapters/views/dashboard/modules/assignee.test.d.ts +2 -0
- package/dist/tests/units/interface-adapters/views/dashboard/modules/assignee.test.d.ts.map +1 -0
- package/dist/tests/units/interface-adapters/views/dashboard/modules/assignee.test.js +35 -0
- package/dist/tests/units/interface-adapters/views/dashboard/modules/assignee.test.js.map +1 -0
- package/dist/tests/units/interface-adapters/views/dashboard/modules/constants.test.d.ts +2 -0
- package/dist/tests/units/interface-adapters/views/dashboard/modules/constants.test.d.ts.map +1 -0
- package/dist/tests/units/interface-adapters/views/dashboard/modules/constants.test.js +17 -0
- package/dist/tests/units/interface-adapters/views/dashboard/modules/constants.test.js.map +1 -0
- package/dist/tests/units/interface-adapters/views/dashboard/modules/desktopNotifications.test.d.ts +2 -0
- package/dist/tests/units/interface-adapters/views/dashboard/modules/desktopNotifications.test.d.ts.map +1 -0
- package/dist/tests/units/interface-adapters/views/dashboard/modules/desktopNotifications.test.js +54 -0
- package/dist/tests/units/interface-adapters/views/dashboard/modules/desktopNotifications.test.js.map +1 -0
- package/dist/tests/units/interface-adapters/views/dashboard/modules/formatting.test.d.ts +2 -0
- package/dist/tests/units/interface-adapters/views/dashboard/modules/formatting.test.d.ts.map +1 -0
- package/dist/tests/units/interface-adapters/views/dashboard/modules/formatting.test.js +95 -0
- package/dist/tests/units/interface-adapters/views/dashboard/modules/formatting.test.js.map +1 -0
- package/dist/tests/units/interface-adapters/views/dashboard/modules/html.test.d.ts +2 -0
- package/dist/tests/units/interface-adapters/views/dashboard/modules/html.test.d.ts.map +1 -0
- package/dist/tests/units/interface-adapters/views/dashboard/modules/html.test.js +55 -0
- package/dist/tests/units/interface-adapters/views/dashboard/modules/html.test.js.map +1 -0
- package/dist/tests/units/interface-adapters/views/dashboard/modules/i18n.test.d.ts +2 -0
- package/dist/tests/units/interface-adapters/views/dashboard/modules/i18n.test.d.ts.map +1 -0
- package/dist/tests/units/interface-adapters/views/dashboard/modules/i18n.test.js +98 -0
- package/dist/tests/units/interface-adapters/views/dashboard/modules/i18n.test.js.map +1 -0
- package/dist/tests/units/interface-adapters/views/dashboard/modules/icons.test.d.ts +2 -0
- package/dist/tests/units/interface-adapters/views/dashboard/modules/icons.test.d.ts.map +1 -0
- package/dist/tests/units/interface-adapters/views/dashboard/modules/icons.test.js +28 -0
- package/dist/tests/units/interface-adapters/views/dashboard/modules/icons.test.js.map +1 -0
- package/dist/tests/units/interface-adapters/views/dashboard/modules/loading.test.d.ts +2 -0
- package/dist/tests/units/interface-adapters/views/dashboard/modules/loading.test.d.ts.map +1 -0
- package/dist/tests/units/interface-adapters/views/dashboard/modules/loading.test.js +51 -0
- package/dist/tests/units/interface-adapters/views/dashboard/modules/loading.test.js.map +1 -0
- package/dist/tests/units/interface-adapters/views/dashboard/modules/notifications.test.d.ts +2 -0
- package/dist/tests/units/interface-adapters/views/dashboard/modules/notifications.test.d.ts.map +1 -0
- package/dist/tests/units/interface-adapters/views/dashboard/modules/notifications.test.js +43 -0
- package/dist/tests/units/interface-adapters/views/dashboard/modules/notifications.test.js.map +1 -0
- package/dist/tests/units/interface-adapters/views/dashboard/modules/priority.test.d.ts +2 -0
- package/dist/tests/units/interface-adapters/views/dashboard/modules/priority.test.d.ts.map +1 -0
- package/dist/tests/units/interface-adapters/views/dashboard/modules/priority.test.js +78 -0
- package/dist/tests/units/interface-adapters/views/dashboard/modules/priority.test.js.map +1 -0
- package/dist/tests/units/interface-adapters/views/dashboard/modules/quality.test.d.ts +2 -0
- package/dist/tests/units/interface-adapters/views/dashboard/modules/quality.test.d.ts.map +1 -0
- package/dist/tests/units/interface-adapters/views/dashboard/modules/quality.test.js +87 -0
- package/dist/tests/units/interface-adapters/views/dashboard/modules/quality.test.js.map +1 -0
- package/dist/tests/units/interface-adapters/views/dashboard/modules/queueLanes.test.d.ts +2 -0
- package/dist/tests/units/interface-adapters/views/dashboard/modules/queueLanes.test.d.ts.map +1 -0
- package/dist/tests/units/interface-adapters/views/dashboard/modules/queueLanes.test.js +29 -0
- package/dist/tests/units/interface-adapters/views/dashboard/modules/queueLanes.test.js.map +1 -0
- package/dist/tests/units/interface-adapters/views/dashboard/modules/sessionMetrics.test.d.ts +2 -0
- package/dist/tests/units/interface-adapters/views/dashboard/modules/sessionMetrics.test.d.ts.map +1 -0
- package/dist/tests/units/interface-adapters/views/dashboard/modules/sessionMetrics.test.js +60 -0
- package/dist/tests/units/interface-adapters/views/dashboard/modules/sessionMetrics.test.js.map +1 -0
- package/dist/tests/units/main/executeDiscover.test.d.ts +2 -0
- package/dist/tests/units/main/executeDiscover.test.d.ts.map +1 -0
- package/dist/tests/units/main/executeDiscover.test.js +106 -0
- package/dist/tests/units/main/executeDiscover.test.js.map +1 -0
- package/dist/tests/units/main/executeInit.test.d.ts +2 -0
- package/dist/tests/units/main/executeInit.test.d.ts.map +1 -0
- package/dist/tests/units/main/executeInit.test.js +290 -0
- package/dist/tests/units/main/executeInit.test.js.map +1 -0
- package/dist/tests/units/usecases/cli/addRepositoriesToConfig.usecase.test.d.ts +2 -0
- package/dist/tests/units/usecases/cli/addRepositoriesToConfig.usecase.test.d.ts.map +1 -0
- package/dist/tests/units/usecases/cli/addRepositoriesToConfig.usecase.test.js +127 -0
- package/dist/tests/units/usecases/cli/addRepositoriesToConfig.usecase.test.js.map +1 -0
- package/dist/tests/units/usecases/cli/checkInitPrerequisites.test.d.ts +2 -0
- package/dist/tests/units/usecases/cli/checkInitPrerequisites.test.d.ts.map +1 -0
- package/dist/tests/units/usecases/cli/checkInitPrerequisites.test.js +57 -0
- package/dist/tests/units/usecases/cli/checkInitPrerequisites.test.js.map +1 -0
- package/dist/usecases/cli/addRepositoriesToConfig.usecase.d.ts +27 -0
- package/dist/usecases/cli/addRepositoriesToConfig.usecase.d.ts.map +1 -0
- package/dist/usecases/cli/addRepositoriesToConfig.usecase.js +34 -0
- package/dist/usecases/cli/addRepositoriesToConfig.usecase.js.map +1 -0
- package/dist/usecases/cli/checkInitPrerequisites.d.ts +16 -0
- package/dist/usecases/cli/checkInitPrerequisites.d.ts.map +1 -0
- package/dist/usecases/cli/checkInitPrerequisites.js +23 -0
- package/dist/usecases/cli/checkInitPrerequisites.js.map +1 -0
- package/dist/usecases/cli/configureMcp.usecase.d.ts +1 -1
- package/dist/usecases/cli/configureMcp.usecase.d.ts.map +1 -1
- package/dist/usecases/cli/validateConfig.usecase.d.ts +1 -1
- package/dist/usecases/cli/validateConfig.usecase.d.ts.map +1 -1
- package/dist/usecases/triggerReview.usecase.d.ts +2 -0
- package/dist/usecases/triggerReview.usecase.d.ts.map +1 -1
- package/dist/usecases/triggerReview.usecase.js +1 -0
- package/dist/usecases/triggerReview.usecase.js.map +1 -1
- package/package.json +1 -1
- package/templates/SETUP.md +23 -8
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @returns {{ initialized: boolean, activeStatusById: Record<string, { status: string, jobType: string | null }>, seenRecentKeys: string[] }}
|
|
3
|
+
*/
|
|
4
|
+
export function createReviewNotificationState(): {
|
|
5
|
+
initialized: boolean;
|
|
6
|
+
activeStatusById: Record<string, {
|
|
7
|
+
status: string;
|
|
8
|
+
jobType: string | null;
|
|
9
|
+
}>;
|
|
10
|
+
seenRecentKeys: string[];
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* @param {{ initialized: boolean, activeStatusById: Record<string, { status: string, jobType: string | null }>, seenRecentKeys: string[] }} state
|
|
14
|
+
* @param {Record<string, unknown>[]} activeReviews
|
|
15
|
+
* @param {Record<string, unknown>[]} recentReviews
|
|
16
|
+
* @returns {{ nextState: { initialized: boolean, activeStatusById: Record<string, { status: string, jobType: string | null }>, seenRecentKeys: string[] }, notifications: Array<{ kind: string, review: Record<string, unknown> }> }}
|
|
17
|
+
*/
|
|
18
|
+
export function collectReviewNotifications(state: {
|
|
19
|
+
initialized: boolean;
|
|
20
|
+
activeStatusById: Record<string, {
|
|
21
|
+
status: string;
|
|
22
|
+
jobType: string | null;
|
|
23
|
+
}>;
|
|
24
|
+
seenRecentKeys: string[];
|
|
25
|
+
}, activeReviews: Record<string, unknown>[], recentReviews: Record<string, unknown>[]): {
|
|
26
|
+
nextState: {
|
|
27
|
+
initialized: boolean;
|
|
28
|
+
activeStatusById: Record<string, {
|
|
29
|
+
status: string;
|
|
30
|
+
jobType: string | null;
|
|
31
|
+
}>;
|
|
32
|
+
seenRecentKeys: string[];
|
|
33
|
+
};
|
|
34
|
+
notifications: Array<{
|
|
35
|
+
kind: string;
|
|
36
|
+
review: Record<string, unknown>;
|
|
37
|
+
}>;
|
|
38
|
+
};
|
|
39
|
+
//# sourceMappingURL=notifications.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"notifications.d.ts","sourceRoot":"","sources":["../../../../../src/interface-adapters/views/dashboard/modules/notifications.js"],"names":[],"mappings":"AAEA;;GAEG;AACH,iDAFa;IAAE,WAAW,EAAE,OAAO,CAAC;IAAC,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,CAAC,CAAC;IAAC,cAAc,EAAE,MAAM,EAAE,CAAA;CAAE,CAQ5I;AAmDD;;;;;GAKG;AACH,kDALW;IAAE,WAAW,EAAE,OAAO,CAAC;IAAC,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,CAAC,CAAC;IAAC,cAAc,EAAE,MAAM,EAAE,CAAA;CAAE,iBAChI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,iBACzB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,GACvB;IAAE,SAAS,EAAE;QAAE,WAAW,EAAE,OAAO,CAAC;QAAC,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAAE;YAAE,MAAM,EAAE,MAAM,CAAC;YAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;SAAE,CAAC,CAAC;QAAC,cAAc,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC;IAAC,aAAa,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KAAE,CAAC,CAAA;CAAE,CAgEpO"}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
const MAX_RECENT_KEYS = 500;
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @returns {{ initialized: boolean, activeStatusById: Record<string, { status: string, jobType: string | null }>, seenRecentKeys: string[] }}
|
|
5
|
+
*/
|
|
6
|
+
export function createReviewNotificationState() {
|
|
7
|
+
return {
|
|
8
|
+
initialized: false,
|
|
9
|
+
activeStatusById: {},
|
|
10
|
+
seenRecentKeys: [],
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @param {Record<string, unknown>} review
|
|
16
|
+
* @returns {string}
|
|
17
|
+
*/
|
|
18
|
+
function getReviewIdentifier(review) {
|
|
19
|
+
if (typeof review.id === 'string' && review.id.length > 0) return review.id;
|
|
20
|
+
if (typeof review.filename === 'string' && review.filename.length > 0) return review.filename;
|
|
21
|
+
if (typeof review.mrNumber === 'number') return String(review.mrNumber);
|
|
22
|
+
return '';
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* @param {Record<string, unknown>} review
|
|
27
|
+
* @returns {string}
|
|
28
|
+
*/
|
|
29
|
+
function getRecentReviewKey(review) {
|
|
30
|
+
const identifier = getReviewIdentifier(review);
|
|
31
|
+
const timeMarker = typeof review.completedAt === 'string'
|
|
32
|
+
? review.completedAt
|
|
33
|
+
: typeof review.date === 'string'
|
|
34
|
+
? review.date
|
|
35
|
+
: typeof review.timestamp === 'string'
|
|
36
|
+
? review.timestamp
|
|
37
|
+
: '';
|
|
38
|
+
const typeMarker = typeof review.type === 'string'
|
|
39
|
+
? review.type
|
|
40
|
+
: typeof review.jobType === 'string'
|
|
41
|
+
? review.jobType
|
|
42
|
+
: '';
|
|
43
|
+
return `${identifier}::${timeMarker}::${typeMarker}`;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* @param {Record<string, unknown>[]} activeReviews
|
|
48
|
+
* @returns {Record<string, { status: string, jobType: string | null }>}
|
|
49
|
+
*/
|
|
50
|
+
function buildActiveStatusMap(activeReviews) {
|
|
51
|
+
const activeStatusById = {};
|
|
52
|
+
activeReviews.forEach((review) => {
|
|
53
|
+
const identifier = getReviewIdentifier(review);
|
|
54
|
+
if (!identifier) return;
|
|
55
|
+
activeStatusById[identifier] = {
|
|
56
|
+
status: typeof review.status === 'string' ? review.status : '',
|
|
57
|
+
jobType: typeof review.jobType === 'string' ? review.jobType : null,
|
|
58
|
+
};
|
|
59
|
+
});
|
|
60
|
+
return activeStatusById;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* @param {{ initialized: boolean, activeStatusById: Record<string, { status: string, jobType: string | null }>, seenRecentKeys: string[] }} state
|
|
65
|
+
* @param {Record<string, unknown>[]} activeReviews
|
|
66
|
+
* @param {Record<string, unknown>[]} recentReviews
|
|
67
|
+
* @returns {{ nextState: { initialized: boolean, activeStatusById: Record<string, { status: string, jobType: string | null }>, seenRecentKeys: string[] }, notifications: Array<{ kind: string, review: Record<string, unknown> }> }}
|
|
68
|
+
*/
|
|
69
|
+
export function collectReviewNotifications(state, activeReviews, recentReviews) {
|
|
70
|
+
const nextActiveStatusById = buildActiveStatusMap(activeReviews);
|
|
71
|
+
const nextSeenRecentKeysSet = new Set(state.seenRecentKeys);
|
|
72
|
+
const notifications = [];
|
|
73
|
+
|
|
74
|
+
if (!state.initialized) {
|
|
75
|
+
recentReviews.forEach((review) => nextSeenRecentKeysSet.add(getRecentReviewKey(review)));
|
|
76
|
+
return {
|
|
77
|
+
nextState: {
|
|
78
|
+
initialized: true,
|
|
79
|
+
activeStatusById: nextActiveStatusById,
|
|
80
|
+
seenRecentKeys: Array.from(nextSeenRecentKeysSet).slice(-MAX_RECENT_KEYS),
|
|
81
|
+
},
|
|
82
|
+
notifications,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
activeReviews.forEach((review) => {
|
|
87
|
+
const identifier = getReviewIdentifier(review);
|
|
88
|
+
if (!identifier) return;
|
|
89
|
+
const previousStatus = state.activeStatusById[identifier]?.status ?? '';
|
|
90
|
+
const currentStatus = typeof review.status === 'string' ? review.status : '';
|
|
91
|
+
const isRunningTransition = currentStatus === 'running' && previousStatus !== 'running';
|
|
92
|
+
if (!isRunningTransition) return;
|
|
93
|
+
|
|
94
|
+
const jobType = typeof review.jobType === 'string' ? review.jobType : '';
|
|
95
|
+
notifications.push({
|
|
96
|
+
kind: jobType === 'followup' ? 'followupStarted' : 'reviewStarted',
|
|
97
|
+
review,
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
recentReviews.forEach((review) => {
|
|
102
|
+
const reviewKey = getRecentReviewKey(review);
|
|
103
|
+
if (nextSeenRecentKeysSet.has(reviewKey)) return;
|
|
104
|
+
nextSeenRecentKeysSet.add(reviewKey);
|
|
105
|
+
|
|
106
|
+
const status = typeof review.status === 'string' ? review.status : '';
|
|
107
|
+
if (status === 'failed') {
|
|
108
|
+
notifications.push({ kind: 'reviewFailed', review });
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const reviewType = typeof review.type === 'string'
|
|
113
|
+
? review.type
|
|
114
|
+
: typeof review.jobType === 'string'
|
|
115
|
+
? review.jobType
|
|
116
|
+
: '';
|
|
117
|
+
notifications.push({
|
|
118
|
+
kind: reviewType === 'followup' ? 'followupCompleted' : 'reviewCompleted',
|
|
119
|
+
review,
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
return {
|
|
124
|
+
nextState: {
|
|
125
|
+
initialized: true,
|
|
126
|
+
activeStatusById: nextActiveStatusById,
|
|
127
|
+
seenRecentKeys: Array.from(nextSeenRecentKeysSet).slice(-MAX_RECENT_KEYS),
|
|
128
|
+
},
|
|
129
|
+
notifications,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"notifications.js","sourceRoot":"","sources":["../../../../../src/interface-adapters/views/dashboard/modules/notifications.js"],"names":[],"mappings":"AAAA,MAAM,eAAe,GAAG,GAAG,CAAC;AAE5B;;GAEG;AACH,MAAM,UAAU,6BAA6B;IAC3C,OAAO;QACL,WAAW,EAAE,KAAK;QAClB,gBAAgB,EAAE,EAAE;QACpB,cAAc,EAAE,EAAE;KACnB,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,SAAS,mBAAmB,CAAC,MAAM;IACjC,IAAI,OAAO,MAAM,CAAC,EAAE,KAAK,QAAQ,IAAI,MAAM,CAAC,EAAE,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,MAAM,CAAC,EAAE,CAAC;IAC5E,IAAI,OAAO,MAAM,CAAC,QAAQ,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,MAAM,CAAC,QAAQ,CAAC;IAC9F,IAAI,OAAO,MAAM,CAAC,QAAQ,KAAK,QAAQ;QAAE,OAAO,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;IACxE,OAAO,EAAE,CAAC;AACZ,CAAC;AAED;;;GAGG;AACH,SAAS,kBAAkB,CAAC,MAAM;IAChC,MAAM,UAAU,GAAG,mBAAmB,CAAC,MAAM,CAAC,CAAC;IAC/C,MAAM,UAAU,GAAG,OAAO,MAAM,CAAC,WAAW,KAAK,QAAQ;QACvD,CAAC,CAAC,MAAM,CAAC,WAAW;QACpB,CAAC,CAAC,OAAO,MAAM,CAAC,IAAI,KAAK,QAAQ;YAC/B,CAAC,CAAC,MAAM,CAAC,IAAI;YACb,CAAC,CAAC,OAAO,MAAM,CAAC,SAAS,KAAK,QAAQ;gBACpC,CAAC,CAAC,MAAM,CAAC,SAAS;gBAClB,CAAC,CAAC,EAAE,CAAC;IACX,MAAM,UAAU,GAAG,OAAO,MAAM,CAAC,IAAI,KAAK,QAAQ;QAChD,CAAC,CAAC,MAAM,CAAC,IAAI;QACb,CAAC,CAAC,OAAO,MAAM,CAAC,OAAO,KAAK,QAAQ;YAClC,CAAC,CAAC,MAAM,CAAC,OAAO;YAChB,CAAC,CAAC,EAAE,CAAC;IACT,OAAO,GAAG,UAAU,KAAK,UAAU,KAAK,UAAU,EAAE,CAAC;AACvD,CAAC;AAED;;;GAGG;AACH,SAAS,oBAAoB,CAAC,aAAa;IACzC,MAAM,gBAAgB,GAAG,EAAE,CAAC;IAC5B,aAAa,CAAC,OAAO,CAAC,CAAC,MAAM,EAAE,EAAE;QAC/B,MAAM,UAAU,GAAG,mBAAmB,CAAC,MAAM,CAAC,CAAC;QAC/C,IAAI,CAAC,UAAU;YAAE,OAAO;QACxB,gBAAgB,CAAC,UAAU,CAAC,GAAG;YAC7B,MAAM,EAAE,OAAO,MAAM,CAAC,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE;YAC9D,OAAO,EAAE,OAAO,MAAM,CAAC,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI;SACpE,CAAC;IACJ,CAAC,CAAC,CAAC;IACH,OAAO,gBAAgB,CAAC;AAC1B,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,0BAA0B,CAAC,KAAK,EAAE,aAAa,EAAE,aAAa;IAC5E,MAAM,oBAAoB,GAAG,oBAAoB,CAAC,aAAa,CAAC,CAAC;IACjE,MAAM,qBAAqB,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,cAAc,CAAC,CAAC;IAC5D,MAAM,aAAa,GAAG,EAAE,CAAC;IAEzB,IAAI,CAAC,KAAK,CAAC,WAAW,EAAE,CAAC;QACvB,aAAa,CAAC,OAAO,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,qBAAqB,CAAC,GAAG,CAAC,kBAAkB,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;QACzF,OAAO;YACL,SAAS,EAAE;gBACT,WAAW,EAAE,IAAI;gBACjB,gBAAgB,EAAE,oBAAoB;gBACtC,cAAc,EAAE,KAAK,CAAC,IAAI,CAAC,qBAAqB,CAAC,CAAC,KAAK,CAAC,CAAC,eAAe,CAAC;aAC1E;YACD,aAAa;SACd,CAAC;IACJ,CAAC;IAED,aAAa,CAAC,OAAO,CAAC,CAAC,MAAM,EAAE,EAAE;QAC/B,MAAM,UAAU,GAAG,mBAAmB,CAAC,MAAM,CAAC,CAAC;QAC/C,IAAI,CAAC,UAAU;YAAE,OAAO;QACxB,MAAM,cAAc,GAAG,KAAK,CAAC,gBAAgB,CAAC,UAAU,CAAC,EAAE,MAAM,IAAI,EAAE,CAAC;QACxE,MAAM,aAAa,GAAG,OAAO,MAAM,CAAC,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC;QAC7E,MAAM,mBAAmB,GAAG,aAAa,KAAK,SAAS,IAAI,cAAc,KAAK,SAAS,CAAC;QACxF,IAAI,CAAC,mBAAmB;YAAE,OAAO;QAEjC,MAAM,OAAO,GAAG,OAAO,MAAM,CAAC,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC;QACzE,aAAa,CAAC,IAAI,CAAC;YACjB,IAAI,EAAE,OAAO,KAAK,UAAU,CAAC,CAAC,CAAC,iBAAiB,CAAC,CAAC,CAAC,eAAe;YAClE,MAAM;SACP,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,aAAa,CAAC,OAAO,CAAC,CAAC,MAAM,EAAE,EAAE;QAC/B,MAAM,SAAS,GAAG,kBAAkB,CAAC,MAAM,CAAC,CAAC;QAC7C,IAAI,qBAAqB,CAAC,GAAG,CAAC,SAAS,CAAC;YAAE,OAAO;QACjD,qBAAqB,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;QAErC,MAAM,MAAM,GAAG,OAAO,MAAM,CAAC,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC;QACtE,IAAI,MAAM,KAAK,QAAQ,EAAE,CAAC;YACxB,aAAa,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,cAAc,EAAE,MAAM,EAAE,CAAC,CAAC;YACrD,OAAO;QACT,CAAC;QAED,MAAM,UAAU,GAAG,OAAO,MAAM,CAAC,IAAI,KAAK,QAAQ;YAChD,CAAC,CAAC,MAAM,CAAC,IAAI;YACb,CAAC,CAAC,OAAO,MAAM,CAAC,OAAO,KAAK,QAAQ;gBAClC,CAAC,CAAC,MAAM,CAAC,OAAO;gBAChB,CAAC,CAAC,EAAE,CAAC;QACT,aAAa,CAAC,IAAI,CAAC;YACjB,IAAI,EAAE,UAAU,KAAK,UAAU,CAAC,CAAC,CAAC,mBAAmB,CAAC,CAAC,CAAC,iBAAiB;YACzE,MAAM;SACP,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,OAAO;QACL,SAAS,EAAE;YACT,WAAW,EAAE,IAAI;YACjB,gBAAgB,EAAE,oBAAoB;YACtC,cAAc,EAAE,KAAK,CAAC,IAAI,CAAC,qBAAqB,CAAC,CAAC,KAAK,CAAC,CAAC,eAAe,CAAC;SAC1E;QACD,aAAa;KACd,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @param {Array<Record<string, unknown>>} pendingFix
|
|
3
|
+
* @param {{ nowIsoDate?: string, qualityTarget?: number }} [options]
|
|
4
|
+
* @returns {Array<Record<string, unknown>>}
|
|
5
|
+
*/
|
|
6
|
+
export function rankPendingFixForNowLane(pendingFix: Array<Record<string, unknown>>, options?: {
|
|
7
|
+
nowIsoDate?: string;
|
|
8
|
+
qualityTarget?: number;
|
|
9
|
+
}): Array<Record<string, unknown>>;
|
|
10
|
+
//# sourceMappingURL=priority.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"priority.d.ts","sourceRoot":"","sources":["../../../../../src/interface-adapters/views/dashboard/modules/priority.js"],"names":[],"mappings":"AAiDA;;;;GAIG;AACH,qDAJW,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,YAC9B;IAAE,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,aAAa,CAAC,EAAE,MAAM,CAAA;CAAE,GAC7C,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAiC1C"}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { QUALITY_TARGET_SCORE } from './constants.js';
|
|
2
|
+
|
|
3
|
+
const HOUR_IN_MILLISECONDS = 60 * 60 * 1000;
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @param {unknown} value
|
|
7
|
+
* @returns {number | null}
|
|
8
|
+
*/
|
|
9
|
+
function toNullableNumber(value) {
|
|
10
|
+
return typeof value === 'number' && Number.isFinite(value) ? value : null;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @param {Record<string, unknown>} mergeRequest
|
|
15
|
+
* @returns {number}
|
|
16
|
+
*/
|
|
17
|
+
function getReferenceTimestamp(mergeRequest) {
|
|
18
|
+
const preferredDate = typeof mergeRequest.lastReviewAt === 'string'
|
|
19
|
+
? mergeRequest.lastReviewAt
|
|
20
|
+
: typeof mergeRequest.createdAt === 'string'
|
|
21
|
+
? mergeRequest.createdAt
|
|
22
|
+
: '';
|
|
23
|
+
|
|
24
|
+
const parsedTimestamp = preferredDate ? Date.parse(preferredDate) : Number.NaN;
|
|
25
|
+
return Number.isFinite(parsedTimestamp) ? parsedTimestamp : Number.MAX_SAFE_INTEGER;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* @param {Record<string, unknown>} mergeRequest
|
|
30
|
+
* @param {number} qualityTarget
|
|
31
|
+
* @param {number} nowTimestamp
|
|
32
|
+
* @returns {number}
|
|
33
|
+
*/
|
|
34
|
+
function computeUrgencyScore(mergeRequest, qualityTarget, nowTimestamp) {
|
|
35
|
+
const openThreads = toNullableNumber(mergeRequest.openThreads) ?? 0;
|
|
36
|
+
const latestScore = toNullableNumber(mergeRequest.latestScore);
|
|
37
|
+
const referenceTimestamp = getReferenceTimestamp(mergeRequest);
|
|
38
|
+
const ageHours = referenceTimestamp === Number.MAX_SAFE_INTEGER
|
|
39
|
+
? 0
|
|
40
|
+
: Math.max(0, (nowTimestamp - referenceTimestamp) / HOUR_IN_MILLISECONDS);
|
|
41
|
+
|
|
42
|
+
const threadScore = openThreads * 12;
|
|
43
|
+
const qualityGap = latestScore === null ? 2 : Math.max(0, qualityTarget - latestScore);
|
|
44
|
+
const qualityScore = qualityGap * 6;
|
|
45
|
+
const ageScore = Math.min(12, ageHours * 0.2);
|
|
46
|
+
|
|
47
|
+
return threadScore + qualityScore + ageScore;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* @param {Array<Record<string, unknown>>} pendingFix
|
|
52
|
+
* @param {{ nowIsoDate?: string, qualityTarget?: number }} [options]
|
|
53
|
+
* @returns {Array<Record<string, unknown>>}
|
|
54
|
+
*/
|
|
55
|
+
export function rankPendingFixForNowLane(pendingFix, options = {}) {
|
|
56
|
+
const nowTimestamp = options.nowIsoDate ? Date.parse(options.nowIsoDate) : Date.now();
|
|
57
|
+
const normalizedNowTimestamp = Number.isFinite(nowTimestamp) ? nowTimestamp : Date.now();
|
|
58
|
+
const qualityTarget = typeof options.qualityTarget === 'number' && Number.isFinite(options.qualityTarget)
|
|
59
|
+
? options.qualityTarget
|
|
60
|
+
: QUALITY_TARGET_SCORE;
|
|
61
|
+
|
|
62
|
+
return [...pendingFix].sort((leftMergeRequest, rightMergeRequest) => {
|
|
63
|
+
const leftScore = computeUrgencyScore(leftMergeRequest, qualityTarget, normalizedNowTimestamp);
|
|
64
|
+
const rightScore = computeUrgencyScore(rightMergeRequest, qualityTarget, normalizedNowTimestamp);
|
|
65
|
+
|
|
66
|
+
if (leftScore !== rightScore) {
|
|
67
|
+
return rightScore - leftScore;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const leftTimestamp = getReferenceTimestamp(leftMergeRequest);
|
|
71
|
+
const rightTimestamp = getReferenceTimestamp(rightMergeRequest);
|
|
72
|
+
if (leftTimestamp !== rightTimestamp) {
|
|
73
|
+
return leftTimestamp - rightTimestamp;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const leftNumber = toNullableNumber(leftMergeRequest.mrNumber) ?? Number.MAX_SAFE_INTEGER;
|
|
77
|
+
const rightNumber = toNullableNumber(rightMergeRequest.mrNumber) ?? Number.MAX_SAFE_INTEGER;
|
|
78
|
+
if (leftNumber !== rightNumber) {
|
|
79
|
+
return leftNumber - rightNumber;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const leftId = String(leftMergeRequest.id ?? '');
|
|
83
|
+
const rightId = String(rightMergeRequest.id ?? '');
|
|
84
|
+
return leftId.localeCompare(rightId);
|
|
85
|
+
});
|
|
86
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"priority.js","sourceRoot":"","sources":["../../../../../src/interface-adapters/views/dashboard/modules/priority.js"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @param {number | null} qualityScore
|
|
3
|
+
* @param {number} [qualityTarget]
|
|
4
|
+
* @returns {{
|
|
5
|
+
* qualityScore: number | null,
|
|
6
|
+
* qualityTarget: number,
|
|
7
|
+
* progressPercent: number | null,
|
|
8
|
+
* clampedProgressPercent: number,
|
|
9
|
+
* targetDelta: number | null,
|
|
10
|
+
* targetDeltaLabel: string
|
|
11
|
+
* }}
|
|
12
|
+
*/
|
|
13
|
+
export function getQualityProgress(qualityScore: number | null, qualityTarget?: number): {
|
|
14
|
+
qualityScore: number | null;
|
|
15
|
+
qualityTarget: number;
|
|
16
|
+
progressPercent: number | null;
|
|
17
|
+
clampedProgressPercent: number;
|
|
18
|
+
targetDelta: number | null;
|
|
19
|
+
targetDeltaLabel: string;
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* @param {Record<string, unknown>} mergeRequest
|
|
23
|
+
* @returns {{ direction: 'up' | 'down' | 'flat' | 'unknown', delta: number | null, label: string }}
|
|
24
|
+
*/
|
|
25
|
+
export function getQualityTrend(mergeRequest: Record<string, unknown>): {
|
|
26
|
+
direction: "up" | "down" | "flat" | "unknown";
|
|
27
|
+
delta: number | null;
|
|
28
|
+
label: string;
|
|
29
|
+
};
|
|
30
|
+
//# sourceMappingURL=quality.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"quality.d.ts","sourceRoot":"","sources":["../../../../../src/interface-adapters/views/dashboard/modules/quality.js"],"names":[],"mappings":"AAmBA;;;;;;;;;;;GAWG;AACH,iDAXW,MAAM,GAAG,IAAI,kBACb,MAAM,GACJ;IACR,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,aAAa,EAAE,MAAM,CAAC;IACtB,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,sBAAsB,EAAE,MAAM,CAAC;IAC/B,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,gBAAgB,EAAE,MAAM,CAAA;CACzB,CA0BH;AAED;;;GAGG;AACH,8CAHW,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GACrB;IAAE,SAAS,EAAE,IAAI,GAAG,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;IAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAuBlG"}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { QUALITY_TARGET_SCORE } from './constants.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @param {number} value
|
|
5
|
+
* @returns {number}
|
|
6
|
+
*/
|
|
7
|
+
function roundOneDecimal(value) {
|
|
8
|
+
return Number(value.toFixed(1));
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* @param {number} value
|
|
13
|
+
* @returns {string}
|
|
14
|
+
*/
|
|
15
|
+
function formatSignedLabel(value) {
|
|
16
|
+
if (value > 0) return `+${value.toFixed(1)}`;
|
|
17
|
+
return value.toFixed(1);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* @param {number | null} qualityScore
|
|
22
|
+
* @param {number} [qualityTarget]
|
|
23
|
+
* @returns {{
|
|
24
|
+
* qualityScore: number | null,
|
|
25
|
+
* qualityTarget: number,
|
|
26
|
+
* progressPercent: number | null,
|
|
27
|
+
* clampedProgressPercent: number,
|
|
28
|
+
* targetDelta: number | null,
|
|
29
|
+
* targetDeltaLabel: string
|
|
30
|
+
* }}
|
|
31
|
+
*/
|
|
32
|
+
export function getQualityProgress(qualityScore, qualityTarget = QUALITY_TARGET_SCORE) {
|
|
33
|
+
if (typeof qualityScore !== 'number' || !Number.isFinite(qualityScore)) {
|
|
34
|
+
return {
|
|
35
|
+
qualityScore: null,
|
|
36
|
+
qualityTarget,
|
|
37
|
+
progressPercent: null,
|
|
38
|
+
clampedProgressPercent: 0,
|
|
39
|
+
targetDelta: null,
|
|
40
|
+
targetDeltaLabel: '',
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const progressPercent = (qualityScore / qualityTarget) * 100;
|
|
45
|
+
const clampedProgressPercent = Math.max(0, Math.min(100, progressPercent));
|
|
46
|
+
const targetDelta = qualityScore - qualityTarget;
|
|
47
|
+
|
|
48
|
+
return {
|
|
49
|
+
qualityScore,
|
|
50
|
+
qualityTarget,
|
|
51
|
+
progressPercent,
|
|
52
|
+
clampedProgressPercent,
|
|
53
|
+
targetDelta: roundOneDecimal(targetDelta),
|
|
54
|
+
targetDeltaLabel: formatSignedLabel(roundOneDecimal(targetDelta)),
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* @param {Record<string, unknown>} mergeRequest
|
|
60
|
+
* @returns {{ direction: 'up' | 'down' | 'flat' | 'unknown', delta: number | null, label: string }}
|
|
61
|
+
*/
|
|
62
|
+
export function getQualityTrend(mergeRequest) {
|
|
63
|
+
const reviews = Array.isArray(mergeRequest.reviews) ? mergeRequest.reviews : [];
|
|
64
|
+
const scoredReviews = reviews
|
|
65
|
+
.filter((review) => typeof review.score === 'number' && Number.isFinite(review.score))
|
|
66
|
+
.slice(-2);
|
|
67
|
+
|
|
68
|
+
if (scoredReviews.length < 2) {
|
|
69
|
+
return { direction: 'unknown', delta: null, label: '' };
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const previousReview = scoredReviews[0];
|
|
73
|
+
const latestReview = scoredReviews[1];
|
|
74
|
+
const delta = roundOneDecimal(latestReview.score - previousReview.score);
|
|
75
|
+
|
|
76
|
+
if (delta > 0) {
|
|
77
|
+
return { direction: 'up', delta, label: formatSignedLabel(delta) };
|
|
78
|
+
}
|
|
79
|
+
if (delta < 0) {
|
|
80
|
+
return { direction: 'down', delta, label: formatSignedLabel(delta) };
|
|
81
|
+
}
|
|
82
|
+
return { direction: 'flat', delta, label: delta.toFixed(1) };
|
|
83
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"quality.js","sourceRoot":"","sources":["../../../../../src/interface-adapters/views/dashboard/modules/quality.js"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @template MergeRequest
|
|
3
|
+
* @param {MergeRequest[]} rankedPendingFix
|
|
4
|
+
* @param {MergeRequest[]} pendingApproval
|
|
5
|
+
* @returns {{
|
|
6
|
+
* nowLaneItem: MergeRequest | null,
|
|
7
|
+
* needsFixItems: MergeRequest[],
|
|
8
|
+
* readyToApproveItems: MergeRequest[],
|
|
9
|
+
* nowLaneCount: number,
|
|
10
|
+
* needsFixCount: number,
|
|
11
|
+
* readyToApproveCount: number
|
|
12
|
+
* }}
|
|
13
|
+
*/
|
|
14
|
+
export function buildQueueLanesModel<MergeRequest>(rankedPendingFix: MergeRequest[], pendingApproval: MergeRequest[]): {
|
|
15
|
+
nowLaneItem: MergeRequest | null;
|
|
16
|
+
needsFixItems: MergeRequest[];
|
|
17
|
+
readyToApproveItems: MergeRequest[];
|
|
18
|
+
nowLaneCount: number;
|
|
19
|
+
needsFixCount: number;
|
|
20
|
+
readyToApproveCount: number;
|
|
21
|
+
};
|
|
22
|
+
//# sourceMappingURL=queueLanes.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"queueLanes.d.ts","sourceRoot":"","sources":["../../../../../src/interface-adapters/views/dashboard/modules/queueLanes.js"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,qCAZa,YAAY,oBACd,YAAY,EAAE,mBACd,YAAY,EAAE,GACZ;IACR,WAAW,EAAE,YAAY,GAAG,IAAI,CAAC;IACjC,aAAa,EAAE,YAAY,EAAE,CAAC;IAC9B,mBAAmB,EAAE,YAAY,EAAE,CAAC;IACpC,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,CAAC;IACtB,mBAAmB,EAAE,MAAM,CAAA;CAC5B,CAeH"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @template MergeRequest
|
|
3
|
+
* @param {MergeRequest[]} rankedPendingFix
|
|
4
|
+
* @param {MergeRequest[]} pendingApproval
|
|
5
|
+
* @returns {{
|
|
6
|
+
* nowLaneItem: MergeRequest | null,
|
|
7
|
+
* needsFixItems: MergeRequest[],
|
|
8
|
+
* readyToApproveItems: MergeRequest[],
|
|
9
|
+
* nowLaneCount: number,
|
|
10
|
+
* needsFixCount: number,
|
|
11
|
+
* readyToApproveCount: number
|
|
12
|
+
* }}
|
|
13
|
+
*/
|
|
14
|
+
export function buildQueueLanesModel(rankedPendingFix, pendingApproval) {
|
|
15
|
+
const nowLaneItem = rankedPendingFix[0] ?? null;
|
|
16
|
+
const needsFixItems = rankedPendingFix.slice(1);
|
|
17
|
+
const readyToApproveItems = [...pendingApproval];
|
|
18
|
+
|
|
19
|
+
return {
|
|
20
|
+
nowLaneItem,
|
|
21
|
+
needsFixItems,
|
|
22
|
+
readyToApproveItems,
|
|
23
|
+
nowLaneCount: nowLaneItem ? 1 : 0,
|
|
24
|
+
needsFixCount: needsFixItems.length,
|
|
25
|
+
readyToApproveCount: readyToApproveItems.length,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"queueLanes.js","sourceRoot":"","sources":["../../../../../src/interface-adapters/views/dashboard/modules/queueLanes.js"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,oBAAoB,CAAC,gBAAgB,EAAE,eAAe;IACpE,MAAM,WAAW,GAAG,gBAAgB,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC;IAChD,MAAM,aAAa,GAAG,gBAAgB,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IAChD,MAAM,mBAAmB,GAAG,CAAC,GAAG,eAAe,CAAC,CAAC;IAEjD,OAAO;QACL,WAAW;QACX,aAAa;QACb,mBAAmB;QACnB,YAAY,EAAE,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QACjC,aAAa,EAAE,aAAa,CAAC,MAAM;QACnC,mBAAmB,EAAE,mBAAmB,CAAC,MAAM;KAChD,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @param {number} [nowMs]
|
|
3
|
+
* @returns {{
|
|
4
|
+
* sessionStartedAt: number,
|
|
5
|
+
* firstUsefulActionDelayMs: number | null,
|
|
6
|
+
* actionCount: number,
|
|
7
|
+
* actionBreakdown: Record<string, number>,
|
|
8
|
+
* currentPriorityItemId: string | null,
|
|
9
|
+
* currentPriorityItemStartedAt: number | null,
|
|
10
|
+
* resolvedPriorityDurationsMs: number[],
|
|
11
|
+
* }}
|
|
12
|
+
*/
|
|
13
|
+
export function createSessionMetricsState(nowMs?: number): {
|
|
14
|
+
sessionStartedAt: number;
|
|
15
|
+
firstUsefulActionDelayMs: number | null;
|
|
16
|
+
actionCount: number;
|
|
17
|
+
actionBreakdown: Record<string, number>;
|
|
18
|
+
currentPriorityItemId: string | null;
|
|
19
|
+
currentPriorityItemStartedAt: number | null;
|
|
20
|
+
resolvedPriorityDurationsMs: number[];
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* @param {ReturnType<typeof createSessionMetricsState>} state
|
|
24
|
+
* @param {'followup' | 'open' | 'approve' | 'cancelReview' | 'syncThreads' | 'other' | string} actionType
|
|
25
|
+
* @param {number} [nowMs]
|
|
26
|
+
* @returns {ReturnType<typeof createSessionMetricsState>}
|
|
27
|
+
*/
|
|
28
|
+
export function trackSessionAction(state: ReturnType<typeof createSessionMetricsState>, actionType: "followup" | "open" | "approve" | "cancelReview" | "syncThreads" | "other" | string, nowMs?: number): ReturnType<typeof createSessionMetricsState>;
|
|
29
|
+
/**
|
|
30
|
+
* @param {ReturnType<typeof createSessionMetricsState>} state
|
|
31
|
+
* @param {{ nowLaneItemId: string | null, pendingFixIds: string[], nowMs?: number }} input
|
|
32
|
+
* @returns {ReturnType<typeof createSessionMetricsState>}
|
|
33
|
+
*/
|
|
34
|
+
export function updatePriorityItemTracking(state: ReturnType<typeof createSessionMetricsState>, input: {
|
|
35
|
+
nowLaneItemId: string | null;
|
|
36
|
+
pendingFixIds: string[];
|
|
37
|
+
nowMs?: number;
|
|
38
|
+
}): ReturnType<typeof createSessionMetricsState>;
|
|
39
|
+
/**
|
|
40
|
+
* @param {ReturnType<typeof createSessionMetricsState>} state
|
|
41
|
+
* @returns {{
|
|
42
|
+
* firstUsefulActionDelayMs: number | null,
|
|
43
|
+
* actionCount: number,
|
|
44
|
+
* actionBreakdown: Record<string, number>,
|
|
45
|
+
* averagePriorityResolutionMs: number | null,
|
|
46
|
+
* }}
|
|
47
|
+
*/
|
|
48
|
+
export function getSessionMetricsSnapshot(state: ReturnType<typeof createSessionMetricsState>): {
|
|
49
|
+
firstUsefulActionDelayMs: number | null;
|
|
50
|
+
actionCount: number;
|
|
51
|
+
actionBreakdown: Record<string, number>;
|
|
52
|
+
averagePriorityResolutionMs: number | null;
|
|
53
|
+
};
|
|
54
|
+
//# sourceMappingURL=sessionMetrics.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sessionMetrics.d.ts","sourceRoot":"","sources":["../../../../../src/interface-adapters/views/dashboard/modules/sessionMetrics.js"],"names":[],"mappings":"AASA;;;;;;;;;;;GAWG;AACH,kDAXW,MAAM,GACJ;IACR,gBAAgB,EAAE,MAAM,CAAC;IACzB,wBAAwB,EAAE,MAAM,GAAG,IAAI,CAAC;IACxC,WAAW,EAAE,MAAM,CAAC;IACpB,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACxC,qBAAqB,EAAE,MAAM,GAAG,IAAI,CAAC;IACrC,4BAA4B,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5C,2BAA2B,EAAE,MAAM,EAAE,CAAC;CACvC,CAYH;AAED;;;;;GAKG;AACH,0CALW,UAAU,CAAC,OAAO,yBAAyB,CAAC,cAC5C,UAAU,GAAG,MAAM,GAAG,SAAS,GAAG,cAAc,GAAG,aAAa,GAAG,OAAO,GAAG,MAAM,UACnF,MAAM,GACJ,UAAU,CAAC,OAAO,yBAAyB,CAAC,CAiBxD;AAED;;;;GAIG;AACH,kDAJW,UAAU,CAAC,OAAO,yBAAyB,CAAC,SAC5C;IAAE,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,aAAa,EAAE,MAAM,EAAE,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,GACvE,UAAU,CAAC,OAAO,yBAAyB,CAAC,CAsCxD;AAED;;;;;;;;GAQG;AACH,iDARW,UAAU,CAAC,OAAO,yBAAyB,CAAC,GAC1C;IACR,wBAAwB,EAAE,MAAM,GAAG,IAAI,CAAC;IACxC,WAAW,EAAE,MAAM,CAAC;IACpB,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACxC,2BAA2B,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5C,CAaH"}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
const DEFAULT_ACTION_BREAKDOWN = {
|
|
2
|
+
followup: 0,
|
|
3
|
+
open: 0,
|
|
4
|
+
approve: 0,
|
|
5
|
+
cancelReview: 0,
|
|
6
|
+
syncThreads: 0,
|
|
7
|
+
other: 0,
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @param {number} [nowMs]
|
|
12
|
+
* @returns {{
|
|
13
|
+
* sessionStartedAt: number,
|
|
14
|
+
* firstUsefulActionDelayMs: number | null,
|
|
15
|
+
* actionCount: number,
|
|
16
|
+
* actionBreakdown: Record<string, number>,
|
|
17
|
+
* currentPriorityItemId: string | null,
|
|
18
|
+
* currentPriorityItemStartedAt: number | null,
|
|
19
|
+
* resolvedPriorityDurationsMs: number[],
|
|
20
|
+
* }}
|
|
21
|
+
*/
|
|
22
|
+
export function createSessionMetricsState(nowMs = Date.now()) {
|
|
23
|
+
return {
|
|
24
|
+
sessionStartedAt: nowMs,
|
|
25
|
+
firstUsefulActionDelayMs: null,
|
|
26
|
+
actionCount: 0,
|
|
27
|
+
actionBreakdown: { ...DEFAULT_ACTION_BREAKDOWN },
|
|
28
|
+
currentPriorityItemId: null,
|
|
29
|
+
currentPriorityItemStartedAt: null,
|
|
30
|
+
resolvedPriorityDurationsMs: [],
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* @param {ReturnType<typeof createSessionMetricsState>} state
|
|
36
|
+
* @param {'followup' | 'open' | 'approve' | 'cancelReview' | 'syncThreads' | 'other' | string} actionType
|
|
37
|
+
* @param {number} [nowMs]
|
|
38
|
+
* @returns {ReturnType<typeof createSessionMetricsState>}
|
|
39
|
+
*/
|
|
40
|
+
export function trackSessionAction(state, actionType, nowMs = Date.now()) {
|
|
41
|
+
const normalizedActionType = actionType in state.actionBreakdown ? actionType : 'other';
|
|
42
|
+
const firstUsefulActionDelayMs = state.firstUsefulActionDelayMs === null
|
|
43
|
+
? Math.max(0, nowMs - state.sessionStartedAt)
|
|
44
|
+
: state.firstUsefulActionDelayMs;
|
|
45
|
+
|
|
46
|
+
return {
|
|
47
|
+
...state,
|
|
48
|
+
firstUsefulActionDelayMs,
|
|
49
|
+
actionCount: state.actionCount + 1,
|
|
50
|
+
actionBreakdown: {
|
|
51
|
+
...state.actionBreakdown,
|
|
52
|
+
[normalizedActionType]: state.actionBreakdown[normalizedActionType] + 1,
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* @param {ReturnType<typeof createSessionMetricsState>} state
|
|
59
|
+
* @param {{ nowLaneItemId: string | null, pendingFixIds: string[], nowMs?: number }} input
|
|
60
|
+
* @returns {ReturnType<typeof createSessionMetricsState>}
|
|
61
|
+
*/
|
|
62
|
+
export function updatePriorityItemTracking(state, input) {
|
|
63
|
+
const nowMs = input.nowMs ?? Date.now();
|
|
64
|
+
let nextState = { ...state };
|
|
65
|
+
|
|
66
|
+
const trackedItemId = state.currentPriorityItemId;
|
|
67
|
+
const trackedStartAt = state.currentPriorityItemStartedAt;
|
|
68
|
+
const trackedItemResolved = trackedItemId !== null && trackedStartAt !== null && !input.pendingFixIds.includes(trackedItemId);
|
|
69
|
+
if (trackedItemResolved) {
|
|
70
|
+
nextState = {
|
|
71
|
+
...nextState,
|
|
72
|
+
resolvedPriorityDurationsMs: [
|
|
73
|
+
...nextState.resolvedPriorityDurationsMs,
|
|
74
|
+
Math.max(0, nowMs - trackedStartAt),
|
|
75
|
+
],
|
|
76
|
+
currentPriorityItemId: null,
|
|
77
|
+
currentPriorityItemStartedAt: null,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (input.nowLaneItemId === null) {
|
|
82
|
+
return {
|
|
83
|
+
...nextState,
|
|
84
|
+
currentPriorityItemId: null,
|
|
85
|
+
currentPriorityItemStartedAt: null,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (nextState.currentPriorityItemId !== input.nowLaneItemId) {
|
|
90
|
+
return {
|
|
91
|
+
...nextState,
|
|
92
|
+
currentPriorityItemId: input.nowLaneItemId,
|
|
93
|
+
currentPriorityItemStartedAt: nowMs,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return nextState;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* @param {ReturnType<typeof createSessionMetricsState>} state
|
|
102
|
+
* @returns {{
|
|
103
|
+
* firstUsefulActionDelayMs: number | null,
|
|
104
|
+
* actionCount: number,
|
|
105
|
+
* actionBreakdown: Record<string, number>,
|
|
106
|
+
* averagePriorityResolutionMs: number | null,
|
|
107
|
+
* }}
|
|
108
|
+
*/
|
|
109
|
+
export function getSessionMetricsSnapshot(state) {
|
|
110
|
+
const averagePriorityResolutionMs = state.resolvedPriorityDurationsMs.length === 0
|
|
111
|
+
? null
|
|
112
|
+
: state.resolvedPriorityDurationsMs.reduce((total, duration) => total + duration, 0) / state.resolvedPriorityDurationsMs.length;
|
|
113
|
+
|
|
114
|
+
return {
|
|
115
|
+
firstUsefulActionDelayMs: state.firstUsefulActionDelayMs,
|
|
116
|
+
actionCount: state.actionCount,
|
|
117
|
+
actionBreakdown: { ...state.actionBreakdown },
|
|
118
|
+
averagePriorityResolutionMs,
|
|
119
|
+
};
|
|
120
|
+
}
|