@oneuptime/common 14.0.0 → 14.0.1
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/Models/DatabaseModels/Index.ts +4 -0
- package/Models/DatabaseModels/ServiceLevelObjectiveFeed.ts +2 -0
- package/Models/DatabaseModels/ServiceLevelObjectiveLabelRule.ts +522 -0
- package/Models/DatabaseModels/ServiceLevelObjectiveMonitorRule.ts +38 -4
- package/Models/DatabaseModels/ServiceLevelObjectiveOwnerRule.ts +603 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1794100000000-AddSloLabelAndOwnerRules.ts +248 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1794200000000-AddSloMonitorRuleMonitorType.ts +19 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +4 -0
- package/Server/Services/DatabaseService.ts +10 -0
- package/Server/Services/Index.ts +4 -0
- package/Server/Services/MonitorService.ts +3 -2
- package/Server/Services/ServiceLevelObjectiveLabelRuleEngineService.ts +385 -0
- package/Server/Services/ServiceLevelObjectiveLabelRuleService.ts +51 -0
- package/Server/Services/ServiceLevelObjectiveMonitorRuleEngineService.ts +92 -16
- package/Server/Services/ServiceLevelObjectiveMonitorRuleService.ts +36 -1
- package/Server/Services/ServiceLevelObjectiveOwnerRuleEngineService.ts +521 -0
- package/Server/Services/ServiceLevelObjectiveOwnerRuleService.ts +51 -0
- package/Server/Services/ServiceLevelObjectiveService.ts +28 -0
- package/Server/Types/Workflow/Components/Telegram/SendMessageToChat.ts +27 -1
- package/Server/Utils/AI/Toolbox/SloTools.ts +1 -0
- package/Server/Utils/Monitor/AffectedResourceList.ts +31 -73
- package/Server/Utils/Monitor/Criteria/CompareCriteria.ts +1 -1
- package/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.ts +1 -1
- package/Server/Utils/Monitor/MonitorCriteriaEvaluator.ts +181 -136
- package/Server/Utils/Monitor/RootCauseList.ts +139 -0
- package/Server/Utils/Rules/RuleRun/RuleRunRegistry.ts +28 -0
- package/Server/Utils/Slo/SloRulePatternValidator.ts +42 -0
- package/Tests/App/Dashboard/FeedFilterSortIntegration.test.tsx +1632 -0
- package/Tests/App/Dashboard/FeedOptionsLocales.test.ts +250 -0
- package/Tests/App/Dashboard/FeedOrdering.test.ts +279 -9
- package/Tests/App/Dashboard/InvestigationFeedRefresh.test.tsx +60 -1
- package/Tests/App/Dashboard/InvestigationPanel.test.tsx +594 -3
- package/Tests/App/Dashboard/ProductOrientation.test.tsx +646 -0
- package/Tests/App/Dashboard/ResourceFeed.test.tsx +612 -2
- package/Tests/App/Dashboard/ResourceFeedPageWiring.test.ts +30 -0
- package/Tests/App/Dashboard/ResourceFeedPagesFilterSort.test.tsx +1202 -0
- package/Tests/App/Dashboard/ScheduledMaintenanceFeedRefresh.test.tsx +52 -1
- package/Tests/App/Dashboard/SloFeed.test.tsx +106 -1
- package/Tests/App/Dashboard/SloMonitorRuleCriteria.test.tsx +566 -0
- package/Tests/App/Dashboard/SloNavigationWiring.test.tsx +6 -4
- package/Tests/App/Dashboard/SloNoticeBanner.test.tsx +186 -0
- package/Tests/App/Dashboard/SloOverviewKpiStrip.test.tsx +388 -44
- package/Tests/App/Dashboard/SloOverviewLayout.test.tsx +613 -0
- package/Tests/Models/DatabaseModels/ResourceFeedModels.test.ts +0 -8
- package/Tests/Models/DatabaseModels/SloLabelAndOwnerRuleModels.test.ts +404 -0
- package/Tests/Models/DatabaseModels/SloOverhaulModels.test.ts +17 -1
- package/Tests/Models/DatabaseModels/StaticRuleCriteriaInheritance.test.ts +4 -2
- package/Tests/Server/Infrastructure/Postgres/AddConfigurableRuleCriteriaMigration.test.ts +3 -0
- package/Tests/Server/Infrastructure/Postgres/AddSloLabelAndOwnerRulesMigration.test.ts +407 -0
- package/Tests/Server/Infrastructure/Postgres/AddSloMonitorRuleMonitorTypeMigration.test.ts +171 -0
- package/Tests/Server/Infrastructure/Postgres/SloProductOverhaulMigration.test.ts +88 -4
- package/Tests/Server/Services/DatabaseServiceRuleCriteriaValidation.test.ts +104 -0
- package/Tests/Server/Services/RuleRun/LabelRuleEnginesGroupBRun.test.ts +48 -1
- package/Tests/Server/Services/RuleRun/OwnerRuleEnginesGroupBRun.test.ts +30 -0
- package/Tests/Server/Services/ServiceLevelObjectiveLabelAndOwnerRuleEngines.test.ts +1170 -0
- package/Tests/Server/Services/ServiceLevelObjectiveLabelAndOwnerRuleServices.test.ts +172 -0
- package/Tests/Server/Services/ServiceLevelObjectiveMonitorRuleEngineService.test.ts +323 -0
- package/Tests/Server/Services/ServiceLevelObjectiveMonitorRuleService.test.ts +115 -0
- package/Tests/Server/Services/ServiceLevelObjectiveMonitorRuleTriggers.test.ts +21 -2
- package/Tests/Server/Services/ServiceLevelObjectiveMonitorTypePostgres.test.ts +480 -0
- package/Tests/Server/Services/ServiceLevelObjectiveService.test.ts +162 -0
- package/Tests/Server/Services/StaticRuleCriteriaRuntimeCoverage.test.ts +45 -6
- package/Tests/Server/Types/Database/FeedEventTypeFilterQuery.test.ts +745 -0
- package/Tests/Server/Types/Markdown.test.ts +2 -1
- package/Tests/Server/Types/Workflow/Components/ApiComponentSsrf.test.ts +49 -0
- package/Tests/Server/Types/Workflow/Components/TelegramSendMessageToChat.test.ts +331 -0
- package/Tests/Server/Utils/AI/SloTools.test.ts +10 -0
- package/Tests/Server/Utils/Monitor/Criteria/MetricMonitorCriteriaAnomalyUnits.test.ts +1 -1
- package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluator.test.ts +405 -0
- package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluatorBreachingSamples.test.ts +1101 -0
- package/Tests/Server/Utils/Monitor/MonitorCriteriaEvaluatorMetricUnits.test.ts +106 -80
- package/Tests/Server/Utils/Monitor/RootCauseList.test.ts +252 -0
- package/Tests/Server/Utils/Rules/RuleRun/RuleRunPermission.test.ts +154 -0
- package/Tests/Server/Utils/SSRFProtection.test.ts +111 -0
- package/Tests/Server/Utils/Slo/SloRulePatternValidator.test.ts +67 -0
- package/Tests/Server/Utils/VM/VMRunnerSsrf.test.ts +213 -0
- package/Tests/Types/API/Route.test.ts +56 -0
- package/Tests/Types/API/URLSchemeLikePath.test.ts +304 -0
- package/Tests/UI/Components/FeedOptions.test.ts +1404 -0
- package/Tests/UI/Components/FeedOptionsButton.test.tsx +2603 -0
- package/Tests/UI/Components/Icon.test.tsx +26 -0
- package/Tests/UI/Components/RuleCriteria/RuleCriteriaModelTable.test.tsx +3 -3
- package/Tests/UI/Components/useFeedItemsViewKey.test.tsx +1017 -0
- package/Tests/UI/Components/useFeedOptions.test.tsx +1107 -0
- package/Tests/Utils/LabelRuleImportExport.test.ts +102 -0
- package/Tests/Utils/Slo/SloDuration.test.ts +115 -24
- package/Tests/Utils/Slo/SloHealth.test.ts +93 -10
- package/Tests/Utils/Slo/SloMonitorRuleCriteria.test.ts +109 -0
- package/Tests/Utils/Slo/SloOverviewText.test.ts +64 -0
- package/Types/API/Route.ts +19 -2
- package/Types/API/URL.ts +26 -2
- package/Types/Monitor/MetricMonitor/MetricCriteriaContext.ts +6 -5
- package/Types/Monitor/MetricMonitor/MetricMonitorResponse.ts +9 -0
- package/Types/Permission.ts +90 -0
- package/Types/Rules/RuleCriteriaFieldRegistry.ts +11 -0
- package/Types/Rules/RuleRun.ts +4 -0
- package/UI/Components/Feed/FeedOptions.ts +427 -0
- package/UI/Components/Feed/FeedOptionsButton.tsx +692 -0
- package/UI/Components/Feed/useFeedItems.ts +28 -9
- package/UI/Components/Feed/useFeedOptions.ts +225 -0
- package/UI/Components/Icon/Icon.tsx +1 -1
- package/Utils/LabelRuleImportExport.ts +27 -9
- package/Utils/Monitor/MetricValueFormatter.ts +2 -2
- package/Utils/Rules/MonitorTypeRuleCriteria.ts +31 -0
- package/Utils/Slo/SloDuration.ts +22 -6
- package/Utils/Slo/SloHealth.ts +8 -7
- package/Utils/Slo/SloMonitorRuleCriteria.ts +33 -1
- package/Utils/Slo/SloOverviewText.ts +33 -0
- package/Utils/Slo/SloProjection.ts +4 -3
- package/build/dist/Models/DatabaseModels/Index.js +4 -0
- package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
- package/build/dist/Models/DatabaseModels/ServiceLevelObjectiveFeed.js +2 -0
- package/build/dist/Models/DatabaseModels/ServiceLevelObjectiveFeed.js.map +1 -1
- package/build/dist/Models/DatabaseModels/ServiceLevelObjectiveLabelRule.js +530 -0
- package/build/dist/Models/DatabaseModels/ServiceLevelObjectiveLabelRule.js.map +1 -0
- package/build/dist/Models/DatabaseModels/ServiceLevelObjectiveMonitorRule.js +39 -4
- package/build/dist/Models/DatabaseModels/ServiceLevelObjectiveMonitorRule.js.map +1 -1
- package/build/dist/Models/DatabaseModels/ServiceLevelObjectiveOwnerRule.js +612 -0
- package/build/dist/Models/DatabaseModels/ServiceLevelObjectiveOwnerRule.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1794100000000-AddSloLabelAndOwnerRules.js +93 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1794100000000-AddSloLabelAndOwnerRules.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1794200000000-AddSloMonitorRuleMonitorType.js +12 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1794200000000-AddSloMonitorRuleMonitorType.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +4 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Services/DatabaseService.js +7 -0
- package/build/dist/Server/Services/DatabaseService.js.map +1 -1
- package/build/dist/Server/Services/Index.js +4 -0
- package/build/dist/Server/Services/Index.js.map +1 -1
- package/build/dist/Server/Services/MonitorService.js +3 -2
- package/build/dist/Server/Services/MonitorService.js.map +1 -1
- package/build/dist/Server/Services/ServiceLevelObjectiveLabelRuleEngineService.js +295 -0
- package/build/dist/Server/Services/ServiceLevelObjectiveLabelRuleEngineService.js.map +1 -0
- package/build/dist/Server/Services/ServiceLevelObjectiveLabelRuleService.js +56 -0
- package/build/dist/Server/Services/ServiceLevelObjectiveLabelRuleService.js.map +1 -0
- package/build/dist/Server/Services/ServiceLevelObjectiveMonitorRuleEngineService.js +59 -14
- package/build/dist/Server/Services/ServiceLevelObjectiveMonitorRuleEngineService.js.map +1 -1
- package/build/dist/Server/Services/ServiceLevelObjectiveMonitorRuleService.js +22 -1
- package/build/dist/Server/Services/ServiceLevelObjectiveMonitorRuleService.js.map +1 -1
- package/build/dist/Server/Services/ServiceLevelObjectiveOwnerRuleEngineService.js +400 -0
- package/build/dist/Server/Services/ServiceLevelObjectiveOwnerRuleEngineService.js.map +1 -0
- package/build/dist/Server/Services/ServiceLevelObjectiveOwnerRuleService.js +56 -0
- package/build/dist/Server/Services/ServiceLevelObjectiveOwnerRuleService.js.map +1 -0
- package/build/dist/Server/Services/ServiceLevelObjectiveService.js +21 -0
- package/build/dist/Server/Services/ServiceLevelObjectiveService.js.map +1 -1
- package/build/dist/Server/Types/Workflow/Components/Telegram/SendMessageToChat.js +20 -1
- package/build/dist/Server/Types/Workflow/Components/Telegram/SendMessageToChat.js.map +1 -1
- package/build/dist/Server/Utils/AI/Toolbox/SloTools.js +1 -0
- package/build/dist/Server/Utils/AI/Toolbox/SloTools.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/AffectedResourceList.js +22 -51
- package/build/dist/Server/Utils/Monitor/AffectedResourceList.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/Criteria/CompareCriteria.js +1 -1
- package/build/dist/Server/Utils/Monitor/Criteria/MetricMonitorCriteria.js +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js +139 -109
- package/build/dist/Server/Utils/Monitor/MonitorCriteriaEvaluator.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/RootCauseList.js +101 -0
- package/build/dist/Server/Utils/Monitor/RootCauseList.js.map +1 -0
- package/build/dist/Server/Utils/Rules/RuleRun/RuleRunRegistry.js +28 -0
- package/build/dist/Server/Utils/Rules/RuleRun/RuleRunRegistry.js.map +1 -1
- package/build/dist/Server/Utils/Slo/SloRulePatternValidator.js +28 -0
- package/build/dist/Server/Utils/Slo/SloRulePatternValidator.js.map +1 -0
- package/build/dist/Types/API/Route.js +18 -2
- package/build/dist/Types/API/Route.js.map +1 -1
- package/build/dist/Types/API/URL.js +23 -2
- package/build/dist/Types/API/URL.js.map +1 -1
- package/build/dist/Types/Permission.js +84 -0
- package/build/dist/Types/Permission.js.map +1 -1
- package/build/dist/Types/Rules/RuleCriteriaFieldRegistry.js +11 -0
- package/build/dist/Types/Rules/RuleCriteriaFieldRegistry.js.map +1 -1
- package/build/dist/Types/Rules/RuleRun.js +4 -0
- package/build/dist/Types/Rules/RuleRun.js.map +1 -1
- package/build/dist/UI/Components/Feed/FeedOptions.js +235 -0
- package/build/dist/UI/Components/Feed/FeedOptions.js.map +1 -0
- package/build/dist/UI/Components/Feed/FeedOptionsButton.js +346 -0
- package/build/dist/UI/Components/Feed/FeedOptionsButton.js.map +1 -0
- package/build/dist/UI/Components/Feed/useFeedItems.js +17 -7
- package/build/dist/UI/Components/Feed/useFeedItems.js.map +1 -1
- package/build/dist/UI/Components/Feed/useFeedOptions.js +130 -0
- package/build/dist/UI/Components/Feed/useFeedOptions.js.map +1 -0
- package/build/dist/UI/Components/Icon/Icon.js +1 -1
- package/build/dist/UI/Components/Icon/Icon.js.map +1 -1
- package/build/dist/Utils/LabelRuleImportExport.js +22 -6
- package/build/dist/Utils/LabelRuleImportExport.js.map +1 -1
- package/build/dist/Utils/Monitor/MetricValueFormatter.js +2 -2
- package/build/dist/Utils/Rules/MonitorTypeRuleCriteria.js +18 -0
- package/build/dist/Utils/Rules/MonitorTypeRuleCriteria.js.map +1 -0
- package/build/dist/Utils/Slo/SloDuration.js +13 -5
- package/build/dist/Utils/Slo/SloDuration.js.map +1 -1
- package/build/dist/Utils/Slo/SloHealth.js +8 -7
- package/build/dist/Utils/Slo/SloHealth.js.map +1 -1
- package/build/dist/Utils/Slo/SloMonitorRuleCriteria.js +16 -1
- package/build/dist/Utils/Slo/SloMonitorRuleCriteria.js.map +1 -1
- package/build/dist/Utils/Slo/SloOverviewText.js +16 -0
- package/build/dist/Utils/Slo/SloOverviewText.js.map +1 -1
- package/build/dist/Utils/Slo/SloProjection.js.map +1 -1
- package/package.json +1 -1
|
@@ -723,7 +723,7 @@ describe("InvestigationPanel report lifecycle", () => {
|
|
|
723
723
|
expect(onAnalysisAvailable).toHaveBeenCalledTimes(1);
|
|
724
724
|
});
|
|
725
725
|
|
|
726
|
-
test("shows the no-report outcome and
|
|
726
|
+
test("shows the no-report outcome, disables the fix action and offers no rating", async () => {
|
|
727
727
|
postMock.mockResolvedValue(
|
|
728
728
|
completedResponse({
|
|
729
729
|
analysisMarkdown: null,
|
|
@@ -757,8 +757,10 @@ describe("InvestigationPanel report lifecycle", () => {
|
|
|
757
757
|
"0 telemetry queries",
|
|
758
758
|
);
|
|
759
759
|
expect(fixButton()).toBeDisabled();
|
|
760
|
-
|
|
761
|
-
expect(screen.
|
|
760
|
+
// There is no report to judge, so the rating row is left out entirely.
|
|
761
|
+
expect(screen.queryByText("Rate this investigation")).toBeNull();
|
|
762
|
+
expect(screen.queryByRole("button", { name: "Confirmed" })).toBeNull();
|
|
763
|
+
expect(screen.queryByRole("button", { name: "Rejected" })).toBeNull();
|
|
762
764
|
});
|
|
763
765
|
|
|
764
766
|
test("shows a terminal failure without completed actions", async () => {
|
|
@@ -1374,6 +1376,595 @@ describe("InvestigationPanel verdict control", () => {
|
|
|
1374
1376
|
});
|
|
1375
1377
|
});
|
|
1376
1378
|
|
|
1379
|
+
/*
|
|
1380
|
+
* A verdict judges the report on screen. The rating row used to render for
|
|
1381
|
+
* every completed run and disable both answers whenever there was no report
|
|
1382
|
+
* (still being prepared, finished without one, or dropped on a later poll),
|
|
1383
|
+
* which asked a question nobody could answer. It is now left out in all of
|
|
1384
|
+
* those states, the frame around the completed-run actions is not drawn
|
|
1385
|
+
* empty, and the only time the answers are disabled is while a save is in
|
|
1386
|
+
* flight.
|
|
1387
|
+
*/
|
|
1388
|
+
describe("InvestigationPanel rating availability", () => {
|
|
1389
|
+
function ratingHeading(): HTMLElement | null {
|
|
1390
|
+
return screen.queryByRole("heading", { name: "Rate this investigation" });
|
|
1391
|
+
}
|
|
1392
|
+
|
|
1393
|
+
function ratingGroup(): HTMLElement | null {
|
|
1394
|
+
return screen.queryByRole("group", { name: "Rate this investigation" });
|
|
1395
|
+
}
|
|
1396
|
+
|
|
1397
|
+
function actionsFrame(): HTMLElement | null {
|
|
1398
|
+
return screen.queryByTestId("investigation-actions");
|
|
1399
|
+
}
|
|
1400
|
+
|
|
1401
|
+
function verdictAnswers(): Array<HTMLButtonElement> {
|
|
1402
|
+
return screen.queryAllByRole("button", {
|
|
1403
|
+
name: /^(Confirmed|Rejected)$/,
|
|
1404
|
+
}) as Array<HTMLButtonElement>;
|
|
1405
|
+
}
|
|
1406
|
+
|
|
1407
|
+
function disabledVerdictAnswers(): Array<string> {
|
|
1408
|
+
return verdictAnswers()
|
|
1409
|
+
.filter((button: HTMLButtonElement): boolean => {
|
|
1410
|
+
return button.disabled;
|
|
1411
|
+
})
|
|
1412
|
+
.map((button: HTMLButtonElement): string => {
|
|
1413
|
+
return (button.textContent || "").trim();
|
|
1414
|
+
});
|
|
1415
|
+
}
|
|
1416
|
+
|
|
1417
|
+
function expectNoRating(): void {
|
|
1418
|
+
expect(ratingHeading()).toBeNull();
|
|
1419
|
+
expect(screen.queryByText("Rate this investigation")).toBeNull();
|
|
1420
|
+
expect(ratingGroup()).toBeNull();
|
|
1421
|
+
expect(verdictAnswers()).toEqual([]);
|
|
1422
|
+
expect(screen.queryByText(/You (confirmed|rejected) this/)).toBeNull();
|
|
1423
|
+
expect(screen.queryByRole("button", { name: "Change" })).toBeNull();
|
|
1424
|
+
expect(screen.queryByText(/Your verdict helps measure/)).toBeNull();
|
|
1425
|
+
}
|
|
1426
|
+
|
|
1427
|
+
function expectRatingOffered(): void {
|
|
1428
|
+
expect(ratingHeading()).toBeVisible();
|
|
1429
|
+
const group: HTMLElement | null = ratingGroup();
|
|
1430
|
+
expect(group).not.toBeNull();
|
|
1431
|
+
expect(
|
|
1432
|
+
within(group!).getByRole("button", { name: "Confirmed" }),
|
|
1433
|
+
).toBeEnabled();
|
|
1434
|
+
expect(
|
|
1435
|
+
within(group!).getByRole("button", { name: "Rejected" }),
|
|
1436
|
+
).toBeEnabled();
|
|
1437
|
+
expect(actionsFrame()).toContainElement(group);
|
|
1438
|
+
}
|
|
1439
|
+
|
|
1440
|
+
function noReportResponse(
|
|
1441
|
+
overrides: Partial<InvestigationPayloadOptions> = {},
|
|
1442
|
+
): ApiResponse {
|
|
1443
|
+
return completedResponse({
|
|
1444
|
+
analysisMarkdown: null,
|
|
1445
|
+
isAnalysisPending: false,
|
|
1446
|
+
codeFixRecommendation: AIRunCodeFixRecommendation.NotRecommended,
|
|
1447
|
+
...overrides,
|
|
1448
|
+
});
|
|
1449
|
+
}
|
|
1450
|
+
|
|
1451
|
+
/*
|
|
1452
|
+
* An API replica from before the report was published alongside the run
|
|
1453
|
+
* sends neither `analysisMarkdown` nor `isAnalysisPending`, so the panel
|
|
1454
|
+
* reads it as complete with nothing to show.
|
|
1455
|
+
*/
|
|
1456
|
+
function legacyCompletedResponse(): ApiResponse {
|
|
1457
|
+
return successfulResponse({
|
|
1458
|
+
run: {
|
|
1459
|
+
_id: RUN_ID,
|
|
1460
|
+
status: AIRunStatus.Completed,
|
|
1461
|
+
errorMessage: null,
|
|
1462
|
+
toolCallCount: 1,
|
|
1463
|
+
totalTokens: 10,
|
|
1464
|
+
humanVerdict: null,
|
|
1465
|
+
codeFixRecommendation: AIRunCodeFixRecommendation.NotRecommended,
|
|
1466
|
+
completedAt: COMPLETED_AT,
|
|
1467
|
+
},
|
|
1468
|
+
events: [activityEvent],
|
|
1469
|
+
});
|
|
1470
|
+
}
|
|
1471
|
+
|
|
1472
|
+
test("offers no rating while the report is being prepared, then enabled answers once it lands", async () => {
|
|
1473
|
+
postMock
|
|
1474
|
+
.mockResolvedValueOnce(
|
|
1475
|
+
completedResponse({
|
|
1476
|
+
analysisMarkdown: null,
|
|
1477
|
+
isAnalysisPending: true,
|
|
1478
|
+
codeFixRecommendation: AIRunCodeFixRecommendation.Pending,
|
|
1479
|
+
}) as never,
|
|
1480
|
+
)
|
|
1481
|
+
.mockResolvedValueOnce(
|
|
1482
|
+
completedResponse({
|
|
1483
|
+
codeFixRecommendation: AIRunCodeFixRecommendation.NotRecommended,
|
|
1484
|
+
}) as never,
|
|
1485
|
+
);
|
|
1486
|
+
|
|
1487
|
+
renderPanel();
|
|
1488
|
+
await flush();
|
|
1489
|
+
|
|
1490
|
+
expect(screen.getByText("Preparing the final report")).toBeVisible();
|
|
1491
|
+
expectNoRating();
|
|
1492
|
+
// Nothing is decided yet, so no empty frame is drawn under the notice.
|
|
1493
|
+
expect(actionsFrame()).toBeNull();
|
|
1494
|
+
expect(fixButton()).toBeNull();
|
|
1495
|
+
|
|
1496
|
+
await tick(POLL_INTERVAL_MS);
|
|
1497
|
+
|
|
1498
|
+
expect(postMock).toHaveBeenCalledTimes(2);
|
|
1499
|
+
expect(screen.getByTestId("investigation-markdown")).toHaveTextContent(
|
|
1500
|
+
"The database connection pool was exhausted.",
|
|
1501
|
+
);
|
|
1502
|
+
expectRatingOffered();
|
|
1503
|
+
});
|
|
1504
|
+
|
|
1505
|
+
test.each([
|
|
1506
|
+
["incident", INCIDENT_ID, "/ai-investigation/incident"],
|
|
1507
|
+
["alert", ALERT_ID, "/ai-investigation/alert"],
|
|
1508
|
+
] as Array<[InvestigationSubjectType, ObjectID, string]>)(
|
|
1509
|
+
"offers no rating on an %s whose run finished without a report",
|
|
1510
|
+
async (
|
|
1511
|
+
subjectType: InvestigationSubjectType,
|
|
1512
|
+
subjectId: ObjectID,
|
|
1513
|
+
path: string,
|
|
1514
|
+
) => {
|
|
1515
|
+
postMock.mockResolvedValue(noReportResponse() as never);
|
|
1516
|
+
|
|
1517
|
+
renderPanel({ subjectType, subjectId });
|
|
1518
|
+
await flush();
|
|
1519
|
+
|
|
1520
|
+
expect(requestPath(0)).toContain(path);
|
|
1521
|
+
expect(
|
|
1522
|
+
screen.getByText("Investigation completed without a report"),
|
|
1523
|
+
).toBeVisible();
|
|
1524
|
+
expect(
|
|
1525
|
+
screen.getByText("No investigation report was published."),
|
|
1526
|
+
).toBeVisible();
|
|
1527
|
+
expectNoRating();
|
|
1528
|
+
expect(actionsFrame()).toBeNull();
|
|
1529
|
+
// The run's own working is still there to read.
|
|
1530
|
+
expect(detailsToggle()).toHaveTextContent("Investigation activity");
|
|
1531
|
+
},
|
|
1532
|
+
);
|
|
1533
|
+
|
|
1534
|
+
test("does not show a verdict stored on a run that has no report", async () => {
|
|
1535
|
+
const onVerdictChange: MockFunction = getJestMockFunction();
|
|
1536
|
+
postMock.mockResolvedValue(
|
|
1537
|
+
noReportResponse({ humanVerdict: AIRunHumanVerdict.Confirmed }) as never,
|
|
1538
|
+
);
|
|
1539
|
+
|
|
1540
|
+
renderPanel({ onVerdictChange });
|
|
1541
|
+
await flush();
|
|
1542
|
+
|
|
1543
|
+
expectNoRating();
|
|
1544
|
+
expect(actionsFrame()).toBeNull();
|
|
1545
|
+
// The header leaves it out on the same terms.
|
|
1546
|
+
expect(
|
|
1547
|
+
onVerdictChange.mock.calls.filter((call: Array<unknown>): boolean => {
|
|
1548
|
+
return call[0] !== null;
|
|
1549
|
+
}),
|
|
1550
|
+
).toEqual([]);
|
|
1551
|
+
});
|
|
1552
|
+
|
|
1553
|
+
test("keeps a Recommended fix action in its frame without a rating row beside it", async () => {
|
|
1554
|
+
postMock.mockResolvedValue(
|
|
1555
|
+
completedResponse({
|
|
1556
|
+
analysisMarkdown: null,
|
|
1557
|
+
isAnalysisPending: false,
|
|
1558
|
+
codeFixRecommendation: AIRunCodeFixRecommendation.Recommended,
|
|
1559
|
+
}) as never,
|
|
1560
|
+
);
|
|
1561
|
+
|
|
1562
|
+
renderPanel();
|
|
1563
|
+
await flush();
|
|
1564
|
+
|
|
1565
|
+
const frame: HTMLElement | null = actionsFrame();
|
|
1566
|
+
expect(frame).not.toBeNull();
|
|
1567
|
+
expect(
|
|
1568
|
+
within(frame!).getByRole("heading", {
|
|
1569
|
+
name: "Act on this investigation",
|
|
1570
|
+
}),
|
|
1571
|
+
).toBeVisible();
|
|
1572
|
+
expect(within(frame!).getAllByRole("heading")).toHaveLength(1);
|
|
1573
|
+
expectNoRating();
|
|
1574
|
+
});
|
|
1575
|
+
|
|
1576
|
+
test("an API replica that predates report publication offers no rating", async () => {
|
|
1577
|
+
postMock.mockResolvedValue(legacyCompletedResponse() as never);
|
|
1578
|
+
|
|
1579
|
+
renderPanel();
|
|
1580
|
+
await flush();
|
|
1581
|
+
|
|
1582
|
+
expect(screen.getByLabelText("Investigation status")).toHaveTextContent(
|
|
1583
|
+
"Investigation complete",
|
|
1584
|
+
);
|
|
1585
|
+
expect(
|
|
1586
|
+
screen.getByText("No investigation report was published."),
|
|
1587
|
+
).toBeVisible();
|
|
1588
|
+
expectNoRating();
|
|
1589
|
+
expect(actionsFrame()).toBeNull();
|
|
1590
|
+
});
|
|
1591
|
+
|
|
1592
|
+
test("a whitespace-only report is no report to rate", async () => {
|
|
1593
|
+
postMock.mockResolvedValue(
|
|
1594
|
+
noReportResponse({ analysisMarkdown: " \n\t \n " }) as never,
|
|
1595
|
+
);
|
|
1596
|
+
|
|
1597
|
+
renderPanel();
|
|
1598
|
+
await flush();
|
|
1599
|
+
|
|
1600
|
+
expect(screen.queryByTestId("investigation-markdown")).toBeNull();
|
|
1601
|
+
expect(
|
|
1602
|
+
screen.getByText("No investigation report was published."),
|
|
1603
|
+
).toBeVisible();
|
|
1604
|
+
expectNoRating();
|
|
1605
|
+
expect(actionsFrame()).toBeNull();
|
|
1606
|
+
});
|
|
1607
|
+
|
|
1608
|
+
test.each(
|
|
1609
|
+
Object.values(AIRunStatus).filter((status: AIRunStatus): boolean => {
|
|
1610
|
+
return status !== AIRunStatus.Completed;
|
|
1611
|
+
}),
|
|
1612
|
+
)(
|
|
1613
|
+
"offers no rating for a %s run, even when a report string comes with it",
|
|
1614
|
+
async (status: AIRunStatus) => {
|
|
1615
|
+
postMock.mockResolvedValue(
|
|
1616
|
+
successfulResponse(
|
|
1617
|
+
investigationPayload({
|
|
1618
|
+
status,
|
|
1619
|
+
analysisMarkdown: ANALYSIS,
|
|
1620
|
+
humanVerdict: AIRunHumanVerdict.Rejected,
|
|
1621
|
+
codeFixRecommendation: AIRunCodeFixRecommendation.Recommended,
|
|
1622
|
+
events: [activityEvent],
|
|
1623
|
+
}),
|
|
1624
|
+
) as never,
|
|
1625
|
+
);
|
|
1626
|
+
|
|
1627
|
+
renderPanel();
|
|
1628
|
+
await flush();
|
|
1629
|
+
|
|
1630
|
+
expect(screen.getByLabelText("Investigation status")).toBeVisible();
|
|
1631
|
+
expectNoRating();
|
|
1632
|
+
expect(actionsFrame()).toBeNull();
|
|
1633
|
+
expect(fixButton()).toBeNull();
|
|
1634
|
+
},
|
|
1635
|
+
);
|
|
1636
|
+
|
|
1637
|
+
test("removes the rating when the report disappears and restores the stored verdict when it returns", async () => {
|
|
1638
|
+
postMock
|
|
1639
|
+
.mockResolvedValueOnce(
|
|
1640
|
+
completedResponse({
|
|
1641
|
+
codeFixRecommendation: AIRunCodeFixRecommendation.NotRecommended,
|
|
1642
|
+
}) as never,
|
|
1643
|
+
)
|
|
1644
|
+
.mockResolvedValueOnce(
|
|
1645
|
+
noReportResponse({ humanVerdict: AIRunHumanVerdict.Rejected }) as never,
|
|
1646
|
+
)
|
|
1647
|
+
.mockResolvedValueOnce(
|
|
1648
|
+
completedResponse({
|
|
1649
|
+
codeFixRecommendation: AIRunCodeFixRecommendation.NotRecommended,
|
|
1650
|
+
humanVerdict: AIRunHumanVerdict.Rejected,
|
|
1651
|
+
}) as never,
|
|
1652
|
+
);
|
|
1653
|
+
|
|
1654
|
+
renderPanel();
|
|
1655
|
+
await flush();
|
|
1656
|
+
expectRatingOffered();
|
|
1657
|
+
|
|
1658
|
+
await tick(SETTLED_POLL_INTERVAL_MS);
|
|
1659
|
+
|
|
1660
|
+
expect(postMock).toHaveBeenCalledTimes(2);
|
|
1661
|
+
expect(
|
|
1662
|
+
screen.getByText("No investigation report was published."),
|
|
1663
|
+
).toBeVisible();
|
|
1664
|
+
expectNoRating();
|
|
1665
|
+
expect(actionsFrame()).toBeNull();
|
|
1666
|
+
|
|
1667
|
+
await tick(SETTLED_POLL_INTERVAL_MS);
|
|
1668
|
+
|
|
1669
|
+
expect(postMock).toHaveBeenCalledTimes(3);
|
|
1670
|
+
expect(ratingHeading()).toBeVisible();
|
|
1671
|
+
expect(screen.getByText(/You rejected this analysis/)).toBeVisible();
|
|
1672
|
+
expect(screen.getByRole("button", { name: "Change" })).toBeVisible();
|
|
1673
|
+
});
|
|
1674
|
+
|
|
1675
|
+
test("a report that disappears mid-save takes the row with it, and the saved verdict shows when it returns", async () => {
|
|
1676
|
+
const save: Deferred<ApiResponse> = createDeferred<ApiResponse>();
|
|
1677
|
+
postMock
|
|
1678
|
+
.mockResolvedValueOnce(
|
|
1679
|
+
completedResponse({
|
|
1680
|
+
codeFixRecommendation: AIRunCodeFixRecommendation.NotRecommended,
|
|
1681
|
+
}) as never,
|
|
1682
|
+
)
|
|
1683
|
+
.mockReturnValueOnce(save.promise as never)
|
|
1684
|
+
.mockResolvedValueOnce(noReportResponse() as never)
|
|
1685
|
+
.mockResolvedValueOnce(
|
|
1686
|
+
completedResponse({
|
|
1687
|
+
codeFixRecommendation: AIRunCodeFixRecommendation.NotRecommended,
|
|
1688
|
+
humanVerdict: AIRunHumanVerdict.Confirmed,
|
|
1689
|
+
}) as never,
|
|
1690
|
+
);
|
|
1691
|
+
|
|
1692
|
+
renderPanel();
|
|
1693
|
+
await flush();
|
|
1694
|
+
fireEvent.click(screen.getByRole("button", { name: "Confirmed" }));
|
|
1695
|
+
await flush();
|
|
1696
|
+
|
|
1697
|
+
expect(requestPath(1)).toContain("/ai-investigation/verdict");
|
|
1698
|
+
expect(screen.getByText(/You confirmed this analysis/)).toBeVisible();
|
|
1699
|
+
|
|
1700
|
+
await tick(SETTLED_POLL_INTERVAL_MS);
|
|
1701
|
+
|
|
1702
|
+
expect(postMock).toHaveBeenCalledTimes(3);
|
|
1703
|
+
expectNoRating();
|
|
1704
|
+
expect(actionsFrame()).toBeNull();
|
|
1705
|
+
|
|
1706
|
+
await resolveDeferred(save, successfulResponse({}));
|
|
1707
|
+
|
|
1708
|
+
expectNoRating();
|
|
1709
|
+
expect(screen.queryByRole("alert")).toBeNull();
|
|
1710
|
+
|
|
1711
|
+
await tick(SETTLED_POLL_INTERVAL_MS);
|
|
1712
|
+
|
|
1713
|
+
expect(postMock).toHaveBeenCalledTimes(4);
|
|
1714
|
+
expect(screen.getByText(/You confirmed this analysis/)).toBeVisible();
|
|
1715
|
+
expect(screen.queryByRole("alert")).toBeNull();
|
|
1716
|
+
});
|
|
1717
|
+
|
|
1718
|
+
test("a failed save's error leaves with the row when the report disappears", async () => {
|
|
1719
|
+
postMock
|
|
1720
|
+
.mockResolvedValueOnce(
|
|
1721
|
+
completedResponse({
|
|
1722
|
+
codeFixRecommendation: AIRunCodeFixRecommendation.NotRecommended,
|
|
1723
|
+
}) as never,
|
|
1724
|
+
)
|
|
1725
|
+
.mockResolvedValueOnce(
|
|
1726
|
+
new HTTPErrorResponse(
|
|
1727
|
+
500,
|
|
1728
|
+
{ message: "Verdict storage is unavailable." },
|
|
1729
|
+
{},
|
|
1730
|
+
) as never,
|
|
1731
|
+
)
|
|
1732
|
+
.mockResolvedValueOnce(noReportResponse() as never);
|
|
1733
|
+
|
|
1734
|
+
renderPanel();
|
|
1735
|
+
await flush();
|
|
1736
|
+
fireEvent.click(screen.getByRole("button", { name: "Rejected" }));
|
|
1737
|
+
await flush();
|
|
1738
|
+
|
|
1739
|
+
expect(screen.getByRole("alert")).toHaveTextContent(
|
|
1740
|
+
"Could not save your verdict",
|
|
1741
|
+
);
|
|
1742
|
+
|
|
1743
|
+
await tick(SETTLED_POLL_INTERVAL_MS);
|
|
1744
|
+
|
|
1745
|
+
expect(postMock).toHaveBeenCalledTimes(3);
|
|
1746
|
+
expectNoRating();
|
|
1747
|
+
expect(screen.queryByRole("alert")).toBeNull();
|
|
1748
|
+
expect(screen.queryByText(/Verdict storage is unavailable/)).toBeNull();
|
|
1749
|
+
});
|
|
1750
|
+
|
|
1751
|
+
test("drops the previous subject's rating when navigating to a subject without a report", async () => {
|
|
1752
|
+
postMock
|
|
1753
|
+
.mockResolvedValueOnce(
|
|
1754
|
+
completedResponse({
|
|
1755
|
+
humanVerdict: AIRunHumanVerdict.Confirmed,
|
|
1756
|
+
}) as never,
|
|
1757
|
+
)
|
|
1758
|
+
.mockResolvedValueOnce(noReportResponse({ runId: NEXT_RUN_ID }) as never);
|
|
1759
|
+
|
|
1760
|
+
const view: ReturnType<typeof render> = renderPanel();
|
|
1761
|
+
await flush();
|
|
1762
|
+
expect(screen.getByText(/You confirmed this analysis/)).toBeVisible();
|
|
1763
|
+
|
|
1764
|
+
view.rerender(
|
|
1765
|
+
<InvestigationPanel subjectType="alert" subjectId={ALERT_ID} />,
|
|
1766
|
+
);
|
|
1767
|
+
await flush();
|
|
1768
|
+
|
|
1769
|
+
expect(requestPath(1)).toContain("/ai-investigation/alert");
|
|
1770
|
+
expect(
|
|
1771
|
+
screen.getByText("No investigation report was published."),
|
|
1772
|
+
).toBeVisible();
|
|
1773
|
+
expectNoRating();
|
|
1774
|
+
expect(actionsFrame()).toBeNull();
|
|
1775
|
+
});
|
|
1776
|
+
|
|
1777
|
+
test("a new run on the same subject hides the rating until its own report lands, then asks afresh", async () => {
|
|
1778
|
+
postMock
|
|
1779
|
+
.mockResolvedValueOnce(
|
|
1780
|
+
completedResponse({
|
|
1781
|
+
codeFixRecommendation: AIRunCodeFixRecommendation.NotRecommended,
|
|
1782
|
+
humanVerdict: AIRunHumanVerdict.Confirmed,
|
|
1783
|
+
}) as never,
|
|
1784
|
+
)
|
|
1785
|
+
.mockResolvedValueOnce(
|
|
1786
|
+
completedResponse({
|
|
1787
|
+
runId: NEXT_RUN_ID,
|
|
1788
|
+
analysisMarkdown: null,
|
|
1789
|
+
isAnalysisPending: true,
|
|
1790
|
+
codeFixRecommendation: AIRunCodeFixRecommendation.Pending,
|
|
1791
|
+
}) as never,
|
|
1792
|
+
)
|
|
1793
|
+
.mockResolvedValueOnce(
|
|
1794
|
+
completedResponse({
|
|
1795
|
+
runId: NEXT_RUN_ID,
|
|
1796
|
+
codeFixRecommendation: AIRunCodeFixRecommendation.NotRecommended,
|
|
1797
|
+
}) as never,
|
|
1798
|
+
);
|
|
1799
|
+
|
|
1800
|
+
renderPanel();
|
|
1801
|
+
await flush();
|
|
1802
|
+
expect(screen.getByText(/You confirmed this analysis/)).toBeVisible();
|
|
1803
|
+
|
|
1804
|
+
await tick(SETTLED_POLL_INTERVAL_MS);
|
|
1805
|
+
|
|
1806
|
+
expect(screen.getByText("Preparing the final report")).toBeVisible();
|
|
1807
|
+
expectNoRating();
|
|
1808
|
+
expect(actionsFrame()).toBeNull();
|
|
1809
|
+
|
|
1810
|
+
await tick(POLL_INTERVAL_MS);
|
|
1811
|
+
|
|
1812
|
+
expect(postMock).toHaveBeenCalledTimes(3);
|
|
1813
|
+
expectRatingOffered();
|
|
1814
|
+
// The previous run's verdict is not carried onto the new report.
|
|
1815
|
+
expect(screen.queryByText(/You confirmed this analysis/)).toBeNull();
|
|
1816
|
+
});
|
|
1817
|
+
|
|
1818
|
+
/*
|
|
1819
|
+
* The invariant behind the fix, across every completed-run shape: the
|
|
1820
|
+
* row is either absent or offers two working answers. It never sits on
|
|
1821
|
+
* the card with both disabled.
|
|
1822
|
+
*/
|
|
1823
|
+
test.each([
|
|
1824
|
+
{
|
|
1825
|
+
label: "a report",
|
|
1826
|
+
response: (): ApiResponse => {
|
|
1827
|
+
return completedResponse();
|
|
1828
|
+
},
|
|
1829
|
+
offersRating: true,
|
|
1830
|
+
},
|
|
1831
|
+
{
|
|
1832
|
+
label: "a report with no fix recommended",
|
|
1833
|
+
response: (): ApiResponse => {
|
|
1834
|
+
return completedResponse({
|
|
1835
|
+
codeFixRecommendation: AIRunCodeFixRecommendation.NotRecommended,
|
|
1836
|
+
});
|
|
1837
|
+
},
|
|
1838
|
+
offersRating: true,
|
|
1839
|
+
},
|
|
1840
|
+
{
|
|
1841
|
+
label: "a report whose recommendation is still pending",
|
|
1842
|
+
response: (): ApiResponse => {
|
|
1843
|
+
return completedResponse({
|
|
1844
|
+
codeFixRecommendation: AIRunCodeFixRecommendation.Pending,
|
|
1845
|
+
});
|
|
1846
|
+
},
|
|
1847
|
+
offersRating: true,
|
|
1848
|
+
},
|
|
1849
|
+
{
|
|
1850
|
+
label: "a report with a TL;DR and structured evidence",
|
|
1851
|
+
response: (): ApiResponse => {
|
|
1852
|
+
return completedResponse({
|
|
1853
|
+
analysisTldr: TLDR,
|
|
1854
|
+
evidence: [],
|
|
1855
|
+
references: [],
|
|
1856
|
+
});
|
|
1857
|
+
},
|
|
1858
|
+
offersRating: true,
|
|
1859
|
+
},
|
|
1860
|
+
{
|
|
1861
|
+
label: "a report being prepared",
|
|
1862
|
+
response: (): ApiResponse => {
|
|
1863
|
+
return completedResponse({
|
|
1864
|
+
analysisMarkdown: null,
|
|
1865
|
+
isAnalysisPending: true,
|
|
1866
|
+
});
|
|
1867
|
+
},
|
|
1868
|
+
offersRating: false,
|
|
1869
|
+
},
|
|
1870
|
+
{
|
|
1871
|
+
label: "a report being prepared with a stored verdict",
|
|
1872
|
+
response: (): ApiResponse => {
|
|
1873
|
+
return completedResponse({
|
|
1874
|
+
analysisMarkdown: null,
|
|
1875
|
+
isAnalysisPending: true,
|
|
1876
|
+
humanVerdict: AIRunHumanVerdict.Rejected,
|
|
1877
|
+
});
|
|
1878
|
+
},
|
|
1879
|
+
offersRating: false,
|
|
1880
|
+
},
|
|
1881
|
+
{
|
|
1882
|
+
label: "no report and a Recommended fix",
|
|
1883
|
+
response: (): ApiResponse => {
|
|
1884
|
+
return completedResponse({ analysisMarkdown: null });
|
|
1885
|
+
},
|
|
1886
|
+
offersRating: false,
|
|
1887
|
+
},
|
|
1888
|
+
{
|
|
1889
|
+
label: "no report and no fix",
|
|
1890
|
+
response: (): ApiResponse => {
|
|
1891
|
+
return noReportResponse();
|
|
1892
|
+
},
|
|
1893
|
+
offersRating: false,
|
|
1894
|
+
},
|
|
1895
|
+
{
|
|
1896
|
+
label: "no report and no recorded steps",
|
|
1897
|
+
response: (): ApiResponse => {
|
|
1898
|
+
return noReportResponse({
|
|
1899
|
+
events: [],
|
|
1900
|
+
toolCallCount: 0,
|
|
1901
|
+
totalTokens: 0,
|
|
1902
|
+
});
|
|
1903
|
+
},
|
|
1904
|
+
offersRating: false,
|
|
1905
|
+
},
|
|
1906
|
+
{
|
|
1907
|
+
label: "a whitespace-only report",
|
|
1908
|
+
response: (): ApiResponse => {
|
|
1909
|
+
return noReportResponse({ analysisMarkdown: " \n " });
|
|
1910
|
+
},
|
|
1911
|
+
offersRating: false,
|
|
1912
|
+
},
|
|
1913
|
+
{
|
|
1914
|
+
label: "an API replica that predates report publication",
|
|
1915
|
+
response: (): ApiResponse => {
|
|
1916
|
+
return legacyCompletedResponse();
|
|
1917
|
+
},
|
|
1918
|
+
offersRating: false,
|
|
1919
|
+
},
|
|
1920
|
+
])(
|
|
1921
|
+
"for $label the rating is offered=$offersRating and never disabled at rest",
|
|
1922
|
+
async (scenario: {
|
|
1923
|
+
label: string;
|
|
1924
|
+
response: () => ApiResponse;
|
|
1925
|
+
offersRating: boolean;
|
|
1926
|
+
}) => {
|
|
1927
|
+
postMock.mockResolvedValue(scenario.response() as never);
|
|
1928
|
+
|
|
1929
|
+
renderPanel();
|
|
1930
|
+
await flush();
|
|
1931
|
+
|
|
1932
|
+
expect(ratingHeading() !== null).toBe(scenario.offersRating);
|
|
1933
|
+
expect(ratingGroup() !== null).toBe(scenario.offersRating);
|
|
1934
|
+
expect(disabledVerdictAnswers()).toEqual([]);
|
|
1935
|
+
/*
|
|
1936
|
+
* The frame is drawn exactly when it holds a row: the rating, the
|
|
1937
|
+
* fix action, or both.
|
|
1938
|
+
*/
|
|
1939
|
+
expect(actionsFrame() !== null).toBe(
|
|
1940
|
+
scenario.offersRating || fixButton() !== null,
|
|
1941
|
+
);
|
|
1942
|
+
},
|
|
1943
|
+
);
|
|
1944
|
+
|
|
1945
|
+
test("the answers are disabled only while a save is in flight", async () => {
|
|
1946
|
+
const save: Deferred<ApiResponse> = createDeferred<ApiResponse>();
|
|
1947
|
+
postMock
|
|
1948
|
+
.mockResolvedValueOnce(completedResponse() as never)
|
|
1949
|
+
.mockReturnValueOnce(save.promise as never);
|
|
1950
|
+
|
|
1951
|
+
renderPanel();
|
|
1952
|
+
await flush();
|
|
1953
|
+
expect(disabledVerdictAnswers()).toEqual([]);
|
|
1954
|
+
|
|
1955
|
+
fireEvent.click(screen.getByRole("button", { name: "Rejected" }));
|
|
1956
|
+
await flush();
|
|
1957
|
+
fireEvent.click(screen.getByRole("button", { name: "Change" }));
|
|
1958
|
+
|
|
1959
|
+
expect(disabledVerdictAnswers()).toEqual(["Confirmed", "Rejected"]);
|
|
1960
|
+
|
|
1961
|
+
await resolveDeferred(save, successfulResponse({}));
|
|
1962
|
+
|
|
1963
|
+
expect(disabledVerdictAnswers()).toEqual([]);
|
|
1964
|
+
expectRatingOffered();
|
|
1965
|
+
});
|
|
1966
|
+
});
|
|
1967
|
+
|
|
1377
1968
|
describe("InvestigationPanel code-fix recommendation", () => {
|
|
1378
1969
|
test("shows the fix action only for an explicit Recommended decision", async () => {
|
|
1379
1970
|
postMock.mockResolvedValue(completedResponse() as never);
|