@oneuptime/common 11.5.8 → 11.5.9
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/AIAgentTaskPullRequest.ts +15 -6
- package/Models/DatabaseModels/AIRun.ts +37 -0
- package/Models/DatabaseModels/AIRunEvent.ts +34 -1
- package/Models/DatabaseModels/NetworkDeviceDiscoveryScan.ts +215 -0
- package/Models/DatabaseModels/Project.ts +25 -0
- package/Models/DatabaseModels/StatusPage.ts +42 -0
- package/Server/API/AIAgentTaskAPI.ts +38 -12
- package/Server/API/AIAgentTaskLogAPI.ts +49 -0
- package/Server/API/AIChatAPI.ts +16 -0
- package/Server/API/AIReadinessAPI.ts +84 -0
- package/Server/API/CodeFixRunAPI.ts +182 -0
- package/Server/API/TelemetryExceptionAPI.ts +6 -1
- package/Server/Infrastructure/Postgres/SchemaMigrations/1784124919694-AddAITaskNumberAndRunTranscript.ts +95 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1784135099754-AllowNullAiAgentOnPullRequest.ts +52 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1784137457184-AddEnableMcpServerToStatusPage.ts +30 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1784200000000-AddSnmpV3ColumnsToNetworkDeviceDiscoveryScan.ts +49 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +8 -0
- package/Server/Services/AIRunEventService.ts +15 -1
- package/Server/Services/AIRunService.ts +38 -1
- package/Server/Services/LlmProviderService.ts +2 -0
- package/Server/Services/ProjectService.ts +42 -0
- package/Server/Services/StatusPageService.ts +87 -0
- package/Server/Services/TelemetryExceptionService.ts +19 -90
- package/Server/Types/Markdown.ts +9 -9
- package/Server/Types/MarkdownSlugify.ts +47 -0
- package/Server/Utils/AI/AIRunTranscript.ts +240 -0
- package/Server/Utils/AI/Chat/ChatAgentRunner.ts +9 -0
- package/Server/Utils/AI/Chat/ObservabilityChatPrompt.ts +119 -5
- package/Server/Utils/AI/CodeFix/CodeFixAgentCompletion.ts +75 -0
- package/Server/Utils/AI/CodeFix/CodeFixReadiness.ts +235 -0
- package/Server/Utils/AI/SRE/Insights/FixRouting.ts +2 -3
- package/Server/Utils/AI/Toolbox/CodeTools.ts +883 -0
- package/Server/Utils/AI/Toolbox/CodeWriteTools.ts +519 -0
- package/Server/Utils/AI/Toolbox/Index.ts +27 -0
- package/Server/Utils/AI/Toolbox/ScheduledMaintenanceTools.ts +279 -0
- package/Server/Utils/AnalyticsDatabase/ClickhouseCapacity.ts +19 -1
- package/Server/Utils/CodeRepository/GitHub/GitHub.ts +600 -0
- package/Server/Utils/CodeRepository/StackTraceRepoResolver.ts +7 -2
- package/Server/Utils/LLM/LLMService.ts +397 -18
- package/Server/Utils/Monitor/NetworkDeviceHydrationUtil.ts +4 -11
- package/Tests/Models/StatusPageEnableMcpServer.test.ts +125 -0
- package/Tests/Server/Services/StatusPageServiceMcp.test.ts +223 -0
- package/Tests/Server/Services/TelemetryExceptionAIFixReadiness.test.ts +26 -3
- package/Tests/Server/Services/TelemetryExceptionCodeFixRun.test.ts +12 -0
- package/Tests/Server/Types/Markdown.test.ts +106 -0
- package/Tests/Server/Utils/AI/AIRunTranscript.test.ts +182 -0
- package/Tests/Server/Utils/AI/CodeFixReadiness.test.ts +411 -0
- package/Tests/Server/Utils/AI/CodeTools.test.ts +596 -0
- package/Tests/Server/Utils/AI/CodeWriteTools.test.ts +475 -0
- package/Tests/Server/Utils/AI/LLMServiceModelCompatibility.test.ts +548 -0
- package/Tests/Server/Utils/AI/ObservabilityChatPrompt.test.ts +142 -0
- package/Tests/Server/Utils/AI/ScheduledMaintenanceTools.test.ts +176 -0
- package/Tests/Server/Utils/AnalyticsDatabase/ClickhouseCapacity.test.ts +63 -0
- package/Tests/Server/Utils/GitHubGetFileContent.test.ts +197 -0
- package/Tests/Server/Utils/Monitor/NetworkDeviceHydrationUtil.test.ts +502 -0
- package/Tests/Types/AI/AIAgentTaskStatus.test.ts +7 -6
- package/Tests/Types/AI/AIChatPageContext.test.ts +0 -0
- package/Tests/Types/AI/AIRunStatus.test.ts +42 -0
- package/Tests/Types/Log/LogSeverity.test.ts +78 -0
- package/Tests/UI/Components/BasicFormDropdownNormalization.test.tsx +167 -0
- package/Tests/UI/Components/Charts/AxisTickColor.test.tsx +66 -0
- package/Tests/UI/Utils/AIChatExport/ChatWidgetData.test.ts +319 -0
- package/Tests/UI/Utils/AIChatExport/ChatWidgetMarkdown.test.ts +188 -0
- package/Types/AI/AIAgentTaskStatus.ts +13 -0
- package/Types/AI/AIChatPageContext.ts +137 -0
- package/Types/AI/AIChatTypes.ts +44 -0
- package/Types/AI/AIFixReadiness.ts +46 -0
- package/Types/AI/AIRunStatus.ts +11 -0
- package/Types/Log/LogSeverity.ts +61 -0
- package/Types/Monitor/SnmpMonitor/SnmpVersion.ts +40 -0
- package/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.tsx +8 -2
- package/UI/Components/Charts/ChartLibrary/BarChart/BarChart.tsx +3 -1
- package/UI/Components/Charts/ChartLibrary/LineChart/LineChart.tsx +8 -2
- package/UI/Components/Forms/BasicForm.tsx +26 -8
- package/UI/Styles/Theme.css +1314 -0
- package/UI/Utils/AIChatExport/ChatWidgetData.ts +316 -0
- package/UI/Utils/AIChatExport/ChatWidgetMarkdown.ts +349 -0
- package/UI/Utils/AIChatExport/ConversationMarkdown.ts +177 -0
- package/UI/Utils/AIChatExport/MarkdownBlocks.ts +327 -0
- package/UI/Utils/AIChatExport/MarkdownSafety.ts +215 -0
- package/UI/Utils/DownloadFile.ts +54 -0
- package/build/dist/Models/DatabaseModels/AIAgentTaskPullRequest.js +15 -7
- package/build/dist/Models/DatabaseModels/AIAgentTaskPullRequest.js.map +1 -1
- package/build/dist/Models/DatabaseModels/AIRun.js +38 -0
- package/build/dist/Models/DatabaseModels/AIRun.js.map +1 -1
- package/build/dist/Models/DatabaseModels/AIRunEvent.js +31 -0
- package/build/dist/Models/DatabaseModels/AIRunEvent.js.map +1 -1
- package/build/dist/Models/DatabaseModels/NetworkDeviceDiscoveryScan.js +225 -0
- package/build/dist/Models/DatabaseModels/NetworkDeviceDiscoveryScan.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Project.js +27 -0
- package/build/dist/Models/DatabaseModels/Project.js.map +1 -1
- package/build/dist/Models/DatabaseModels/StatusPage.js +43 -0
- package/build/dist/Models/DatabaseModels/StatusPage.js.map +1 -1
- package/build/dist/Server/API/AIAgentTaskAPI.js +37 -11
- package/build/dist/Server/API/AIAgentTaskAPI.js.map +1 -1
- package/build/dist/Server/API/AIAgentTaskLogAPI.js +32 -7
- package/build/dist/Server/API/AIAgentTaskLogAPI.js.map +1 -1
- package/build/dist/Server/API/AIChatAPI.js +9 -0
- package/build/dist/Server/API/AIChatAPI.js.map +1 -1
- package/build/dist/Server/API/AIReadinessAPI.js +57 -0
- package/build/dist/Server/API/AIReadinessAPI.js.map +1 -0
- package/build/dist/Server/API/CodeFixRunAPI.js +142 -0
- package/build/dist/Server/API/CodeFixRunAPI.js.map +1 -1
- package/build/dist/Server/API/TelemetryExceptionAPI.js +4 -0
- package/build/dist/Server/API/TelemetryExceptionAPI.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1784124919694-AddAITaskNumberAndRunTranscript.js +80 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1784124919694-AddAITaskNumberAndRunTranscript.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1784135099754-AllowNullAiAgentOnPullRequest.js +37 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1784135099754-AllowNullAiAgentOnPullRequest.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1784137457184-AddEnableMcpServerToStatusPage.js +23 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1784137457184-AddEnableMcpServerToStatusPage.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1784200000000-AddSnmpV3ColumnsToNetworkDeviceDiscoveryScan.js +22 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1784200000000-AddSnmpV3ColumnsToNetworkDeviceDiscoveryScan.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +8 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Services/AIRunEventService.js +8 -0
- package/build/dist/Server/Services/AIRunEventService.js.map +1 -1
- package/build/dist/Server/Services/AIRunService.js +29 -0
- package/build/dist/Server/Services/AIRunService.js.map +1 -1
- package/build/dist/Server/Services/LlmProviderService.js +2 -0
- package/build/dist/Server/Services/LlmProviderService.js.map +1 -1
- package/build/dist/Server/Services/ProjectService.js +39 -0
- package/build/dist/Server/Services/ProjectService.js.map +1 -1
- package/build/dist/Server/Services/StatusPageService.js +81 -0
- package/build/dist/Server/Services/StatusPageService.js.map +1 -1
- package/build/dist/Server/Services/TelemetryExceptionService.js +14 -61
- package/build/dist/Server/Services/TelemetryExceptionService.js.map +1 -1
- package/build/dist/Server/Types/Markdown.js +8 -8
- package/build/dist/Server/Types/Markdown.js.map +1 -1
- package/build/dist/Server/Types/MarkdownSlugify.js +47 -0
- package/build/dist/Server/Types/MarkdownSlugify.js.map +1 -0
- package/build/dist/Server/Utils/AI/AIRunTranscript.js +180 -0
- package/build/dist/Server/Utils/AI/AIRunTranscript.js.map +1 -0
- package/build/dist/Server/Utils/AI/Chat/ChatAgentRunner.js +1 -0
- package/build/dist/Server/Utils/AI/Chat/ChatAgentRunner.js.map +1 -1
- package/build/dist/Server/Utils/AI/Chat/ObservabilityChatPrompt.js +103 -5
- package/build/dist/Server/Utils/AI/Chat/ObservabilityChatPrompt.js.map +1 -1
- package/build/dist/Server/Utils/AI/CodeFix/CodeFixAgentCompletion.js +55 -0
- package/build/dist/Server/Utils/AI/CodeFix/CodeFixAgentCompletion.js.map +1 -1
- package/build/dist/Server/Utils/AI/CodeFix/CodeFixReadiness.js +211 -0
- package/build/dist/Server/Utils/AI/CodeFix/CodeFixReadiness.js.map +1 -0
- package/build/dist/Server/Utils/AI/SRE/Insights/FixRouting.js.map +1 -1
- package/build/dist/Server/Utils/AI/Toolbox/CodeTools.js +654 -0
- package/build/dist/Server/Utils/AI/Toolbox/CodeTools.js.map +1 -0
- package/build/dist/Server/Utils/AI/Toolbox/CodeWriteTools.js +389 -0
- package/build/dist/Server/Utils/AI/Toolbox/CodeWriteTools.js.map +1 -0
- package/build/dist/Server/Utils/AI/Toolbox/Index.js +19 -0
- package/build/dist/Server/Utils/AI/Toolbox/Index.js.map +1 -1
- package/build/dist/Server/Utils/AI/Toolbox/ScheduledMaintenanceTools.js +239 -0
- package/build/dist/Server/Utils/AI/Toolbox/ScheduledMaintenanceTools.js.map +1 -0
- package/build/dist/Server/Utils/AnalyticsDatabase/ClickhouseCapacity.js +19 -1
- package/build/dist/Server/Utils/AnalyticsDatabase/ClickhouseCapacity.js.map +1 -1
- package/build/dist/Server/Utils/CodeRepository/GitHub/GitHub.js +439 -0
- package/build/dist/Server/Utils/CodeRepository/GitHub/GitHub.js.map +1 -1
- package/build/dist/Server/Utils/CodeRepository/StackTraceRepoResolver.js +1 -1
- package/build/dist/Server/Utils/CodeRepository/StackTraceRepoResolver.js.map +1 -1
- package/build/dist/Server/Utils/LLM/LLMService.js +275 -19
- package/build/dist/Server/Utils/LLM/LLMService.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/NetworkDeviceHydrationUtil.js +2 -11
- package/build/dist/Server/Utils/Monitor/NetworkDeviceHydrationUtil.js.map +1 -1
- package/build/dist/Types/AI/AIAgentTaskStatus.js +12 -0
- package/build/dist/Types/AI/AIAgentTaskStatus.js.map +1 -1
- package/build/dist/Types/AI/AIChatPageContext.js +101 -0
- package/build/dist/Types/AI/AIChatPageContext.js.map +1 -0
- package/build/dist/Types/AI/AIChatTypes.js +2 -0
- package/build/dist/Types/AI/AIChatTypes.js.map +1 -1
- package/build/dist/Types/AI/AIFixReadiness.js +9 -0
- package/build/dist/Types/AI/AIFixReadiness.js.map +1 -0
- package/build/dist/Types/AI/AIRunStatus.js +11 -0
- package/build/dist/Types/AI/AIRunStatus.js.map +1 -1
- package/build/dist/Types/Log/LogSeverity.js +56 -0
- package/build/dist/Types/Log/LogSeverity.js.map +1 -1
- package/build/dist/Types/Monitor/SnmpMonitor/SnmpVersion.js +38 -0
- package/build/dist/Types/Monitor/SnmpMonitor/SnmpVersion.js.map +1 -1
- package/build/dist/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.js +9 -2
- package/build/dist/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.js.map +1 -1
- package/build/dist/UI/Components/Charts/ChartLibrary/BarChart/BarChart.js +3 -1
- package/build/dist/UI/Components/Charts/ChartLibrary/BarChart/BarChart.js.map +1 -1
- package/build/dist/UI/Components/Charts/ChartLibrary/LineChart/LineChart.js +9 -2
- package/build/dist/UI/Components/Charts/ChartLibrary/LineChart/LineChart.js.map +1 -1
- package/build/dist/UI/Components/Forms/BasicForm.js +24 -6
- package/build/dist/UI/Components/Forms/BasicForm.js.map +1 -1
- package/build/dist/UI/Utils/AIChatExport/ChatWidgetData.js +217 -0
- package/build/dist/UI/Utils/AIChatExport/ChatWidgetData.js.map +1 -0
- package/build/dist/UI/Utils/AIChatExport/ChatWidgetMarkdown.js +234 -0
- package/build/dist/UI/Utils/AIChatExport/ChatWidgetMarkdown.js.map +1 -0
- package/build/dist/UI/Utils/AIChatExport/ConversationMarkdown.js +111 -0
- package/build/dist/UI/Utils/AIChatExport/ConversationMarkdown.js.map +1 -0
- package/build/dist/UI/Utils/AIChatExport/MarkdownBlocks.js +233 -0
- package/build/dist/UI/Utils/AIChatExport/MarkdownBlocks.js.map +1 -0
- package/build/dist/UI/Utils/AIChatExport/MarkdownSafety.js +134 -0
- package/build/dist/UI/Utils/AIChatExport/MarkdownSafety.js.map +1 -0
- package/build/dist/UI/Utils/DownloadFile.js +41 -0
- package/build/dist/UI/Utils/DownloadFile.js.map +1 -0
- package/package.json +5 -1
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import { QueryScheduledMaintenanceTool } from "../../../../Server/Utils/AI/Toolbox/ScheduledMaintenanceTools";
|
|
2
|
+
import {
|
|
3
|
+
ToolContext,
|
|
4
|
+
ToolExecutionResult,
|
|
5
|
+
} from "../../../../Server/Utils/AI/Toolbox/ToolTypes";
|
|
6
|
+
import ScheduledMaintenanceService from "../../../../Server/Services/ScheduledMaintenanceService";
|
|
7
|
+
import ScheduledMaintenance from "../../../../Models/DatabaseModels/ScheduledMaintenance";
|
|
8
|
+
import ScheduledMaintenanceState from "../../../../Models/DatabaseModels/ScheduledMaintenanceState";
|
|
9
|
+
import Monitor from "../../../../Models/DatabaseModels/Monitor";
|
|
10
|
+
import { AIChatCitationTargetType } from "../../../../Types/AI/AIChatTypes";
|
|
11
|
+
import { JSONObject } from "../../../../Types/JSON";
|
|
12
|
+
import ObjectID from "../../../../Types/ObjectID";
|
|
13
|
+
import { afterEach, describe, expect, test } from "@jest/globals";
|
|
14
|
+
|
|
15
|
+
/*
|
|
16
|
+
* query_scheduled_maintenance closes the gap where "this maintenance event"
|
|
17
|
+
* had no fetch tool. These tests lock in what the model relies on: the detail
|
|
18
|
+
* mode returns the window + affected monitors for one event, the list mode
|
|
19
|
+
* respects its clamps, empty results are honest (rowCount 0), and citations
|
|
20
|
+
* deep-link to the right dashboard page.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
const ctx: ToolContext = {
|
|
24
|
+
projectId: ObjectID.generate(),
|
|
25
|
+
props: { isRoot: true },
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const EVENT_ID: ObjectID = ObjectID.generate();
|
|
29
|
+
|
|
30
|
+
function buildEvent(data?: {
|
|
31
|
+
id?: ObjectID;
|
|
32
|
+
number?: number;
|
|
33
|
+
title?: string;
|
|
34
|
+
state?: string;
|
|
35
|
+
monitors?: Array<string>;
|
|
36
|
+
}): ScheduledMaintenance {
|
|
37
|
+
const event: ScheduledMaintenance = new ScheduledMaintenance();
|
|
38
|
+
event._id = (data?.id ?? EVENT_ID).toString();
|
|
39
|
+
event.title = data?.title ?? "Database upgrade";
|
|
40
|
+
event.description = "Upgrading postgres to 17.";
|
|
41
|
+
event.scheduledMaintenanceNumber = data?.number ?? 7;
|
|
42
|
+
event.startsAt = new Date("2026-07-20T01:00:00Z");
|
|
43
|
+
event.endsAt = new Date("2026-07-20T03:00:00Z");
|
|
44
|
+
|
|
45
|
+
const state: ScheduledMaintenanceState = new ScheduledMaintenanceState();
|
|
46
|
+
state.name = data?.state ?? "Scheduled";
|
|
47
|
+
event.currentScheduledMaintenanceState = state;
|
|
48
|
+
|
|
49
|
+
event.monitors = (data?.monitors ?? ["Payments API"]).map(
|
|
50
|
+
(name: string): Monitor => {
|
|
51
|
+
const monitor: Monitor = new Monitor();
|
|
52
|
+
monitor.name = name;
|
|
53
|
+
return monitor;
|
|
54
|
+
},
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
return event;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
afterEach(() => {
|
|
61
|
+
jest.restoreAllMocks();
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
describe("query_scheduled_maintenance — detail mode", () => {
|
|
65
|
+
test("returns the event's window and affected monitors with a view citation", async () => {
|
|
66
|
+
jest
|
|
67
|
+
.spyOn(ScheduledMaintenanceService, "findOneById")
|
|
68
|
+
.mockResolvedValue(buildEvent() as never);
|
|
69
|
+
|
|
70
|
+
const result: ToolExecutionResult =
|
|
71
|
+
await QueryScheduledMaintenanceTool.execute(
|
|
72
|
+
{ scheduledMaintenanceId: EVENT_ID.toString() },
|
|
73
|
+
ctx,
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
expect(result.rowCount).toBe(1);
|
|
77
|
+
expect(result.dataForLlm).toContain("Database upgrade");
|
|
78
|
+
expect(result.dataForLlm).toContain("Payments API");
|
|
79
|
+
expect(result.dataForLlm).toContain("2026-07-20");
|
|
80
|
+
expect(result.citationLabel).toBe("Scheduled maintenance #7");
|
|
81
|
+
expect(result.citationTarget).toEqual({
|
|
82
|
+
type: AIChatCitationTargetType.ScheduledMaintenanceView,
|
|
83
|
+
params: { scheduledMaintenanceId: EVENT_ID.toString() },
|
|
84
|
+
});
|
|
85
|
+
expect(result.widget).toBeDefined();
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
test("a missing event is honest: zero rows, no widget", async () => {
|
|
89
|
+
jest
|
|
90
|
+
.spyOn(ScheduledMaintenanceService, "findOneById")
|
|
91
|
+
.mockResolvedValue(null as never);
|
|
92
|
+
|
|
93
|
+
const result: ToolExecutionResult =
|
|
94
|
+
await QueryScheduledMaintenanceTool.execute(
|
|
95
|
+
{ scheduledMaintenanceId: EVENT_ID.toString() },
|
|
96
|
+
ctx,
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
expect(result.rowCount).toBe(0);
|
|
100
|
+
expect(result.widget).toBeUndefined();
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
describe("query_scheduled_maintenance — list mode", () => {
|
|
105
|
+
test("lists events overlapping the window, most recent first", async () => {
|
|
106
|
+
const findBySpy: jest.SpyInstance = jest
|
|
107
|
+
.spyOn(ScheduledMaintenanceService, "findBy")
|
|
108
|
+
.mockResolvedValue([
|
|
109
|
+
buildEvent({ number: 7 }),
|
|
110
|
+
buildEvent({
|
|
111
|
+
id: ObjectID.generate(),
|
|
112
|
+
number: 8,
|
|
113
|
+
title: "CDN failover drill",
|
|
114
|
+
state: "Ongoing",
|
|
115
|
+
monitors: [],
|
|
116
|
+
}),
|
|
117
|
+
] as never);
|
|
118
|
+
|
|
119
|
+
const result: ToolExecutionResult =
|
|
120
|
+
await QueryScheduledMaintenanceTool.execute({}, ctx);
|
|
121
|
+
|
|
122
|
+
expect(result.rowCount).toBe(2);
|
|
123
|
+
expect(result.dataForLlm).toContain("Database upgrade");
|
|
124
|
+
expect(result.dataForLlm).toContain("CDN failover drill");
|
|
125
|
+
expect(result.citationLabel).toBe(
|
|
126
|
+
"Scheduled maintenance, -30d/+30d (2 found)",
|
|
127
|
+
);
|
|
128
|
+
expect(result.citationTarget).toEqual({
|
|
129
|
+
type: AIChatCitationTargetType.ScheduledMaintenanceEvents,
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
// The query must run under the requesting user's props, never as root+.
|
|
133
|
+
const callArgs: JSONObject = findBySpy.mock.calls[0]?.[0] as JSONObject;
|
|
134
|
+
expect(callArgs["props"]).toBe(ctx.props);
|
|
135
|
+
expect(callArgs["limit"]).toBe(10);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
test("clamps the window and limit arguments", async () => {
|
|
139
|
+
const findBySpy: jest.SpyInstance = jest
|
|
140
|
+
.spyOn(ScheduledMaintenanceService, "findBy")
|
|
141
|
+
.mockResolvedValue([] as never);
|
|
142
|
+
|
|
143
|
+
const result: ToolExecutionResult =
|
|
144
|
+
await QueryScheduledMaintenanceTool.execute(
|
|
145
|
+
{ pastDays: 10000, upcomingDays: -5, limit: 999 },
|
|
146
|
+
ctx,
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
expect(result.citationLabel).toBe(
|
|
150
|
+
"Scheduled maintenance, -365d/+0d (0 found)",
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
const callArgs: JSONObject = findBySpy.mock.calls[0]?.[0] as JSONObject;
|
|
154
|
+
expect(callArgs["limit"]).toBe(25);
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
test("an empty window is honest: zero rows and no widget", async () => {
|
|
158
|
+
jest
|
|
159
|
+
.spyOn(ScheduledMaintenanceService, "findBy")
|
|
160
|
+
.mockResolvedValue([] as never);
|
|
161
|
+
|
|
162
|
+
const result: ToolExecutionResult =
|
|
163
|
+
await QueryScheduledMaintenanceTool.execute({}, ctx);
|
|
164
|
+
|
|
165
|
+
expect(result.rowCount).toBe(0);
|
|
166
|
+
expect(result.widget).toBeUndefined();
|
|
167
|
+
});
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
describe("query_scheduled_maintenance — permissions", () => {
|
|
171
|
+
test("required permissions derive from the model read ACL", () => {
|
|
172
|
+
expect(
|
|
173
|
+
QueryScheduledMaintenanceTool.requiredPermissions.length,
|
|
174
|
+
).toBeGreaterThan(0);
|
|
175
|
+
});
|
|
176
|
+
});
|
|
@@ -1,9 +1,14 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ClickhouseAppInstance,
|
|
3
|
+
ClickhouseClient,
|
|
4
|
+
} from "../../../../Server/Infrastructure/ClickhouseDatabase";
|
|
1
5
|
import {
|
|
2
6
|
buildClickhousePruningPlan,
|
|
3
7
|
ClickhouseDiskSnapshot,
|
|
4
8
|
ClickhousePartitionCandidate,
|
|
5
9
|
ClickhousePlannedPartition,
|
|
6
10
|
ClickhousePruningPlan,
|
|
11
|
+
dropClickhousePartition,
|
|
7
12
|
} from "../../../../Server/Utils/AnalyticsDatabase/ClickhouseCapacity";
|
|
8
13
|
import "../../TestingUtils/Init";
|
|
9
14
|
|
|
@@ -156,3 +161,61 @@ describe("ClickHouse capacity pruning planner", () => {
|
|
|
156
161
|
expect(plan.targetReachable).toBe(false);
|
|
157
162
|
});
|
|
158
163
|
});
|
|
164
|
+
|
|
165
|
+
describe("dropClickhousePartition", () => {
|
|
166
|
+
const command: jest.Mock = jest.fn();
|
|
167
|
+
|
|
168
|
+
beforeEach(() => {
|
|
169
|
+
command.mockReset();
|
|
170
|
+
command.mockResolvedValue({});
|
|
171
|
+
jest.spyOn(ClickhouseAppInstance, "getDataSource").mockReturnValue({
|
|
172
|
+
command,
|
|
173
|
+
} as unknown as ClickhouseClient);
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
afterEach(() => {
|
|
177
|
+
jest.restoreAllMocks();
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
/*
|
|
181
|
+
* A day of spans on a large instance far exceeds the server's default 50 GB
|
|
182
|
+
* drop guard, which rejects the drop with TABLE_SIZE_EXCEEDS_MAX_DROP_SIZE_LIMIT
|
|
183
|
+
* unless the statement lifts it. Without the SETTINGS clause capacity pruning
|
|
184
|
+
* cannot prune the tables it exists for.
|
|
185
|
+
*/
|
|
186
|
+
test("lifts the server drop-size guard on the statement", async () => {
|
|
187
|
+
await dropClickhousePartition({
|
|
188
|
+
tableName: "SpanItemV3Local",
|
|
189
|
+
partitionId: "20260101",
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
expect(command).toHaveBeenCalledWith({
|
|
193
|
+
query:
|
|
194
|
+
"ALTER TABLE `oneuptime`.`SpanItemV3Local` ON CLUSTER 'oneuptime' " +
|
|
195
|
+
"DROP PARTITION ID '20260101' SETTINGS max_partition_size_to_drop = 0",
|
|
196
|
+
});
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
// Lifting the size guard must not widen WHAT is droppable.
|
|
200
|
+
test("refuses a table outside the pruning allowlist", async () => {
|
|
201
|
+
await expect(
|
|
202
|
+
dropClickhousePartition({
|
|
203
|
+
tableName: "AuditLogV2Local",
|
|
204
|
+
partitionId: "20260101",
|
|
205
|
+
}),
|
|
206
|
+
).rejects.toThrow("ClickHouse table is not eligible for pruning");
|
|
207
|
+
|
|
208
|
+
expect(command).not.toHaveBeenCalled();
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
test("refuses a partition id that is not a daily partition", async () => {
|
|
212
|
+
await expect(
|
|
213
|
+
dropClickhousePartition({
|
|
214
|
+
tableName: "SpanItemV3Local",
|
|
215
|
+
partitionId: "202601",
|
|
216
|
+
}),
|
|
217
|
+
).rejects.toThrow("Invalid ClickHouse daily partition id");
|
|
218
|
+
|
|
219
|
+
expect(command).not.toHaveBeenCalled();
|
|
220
|
+
});
|
|
221
|
+
});
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import GitHubUtil, {
|
|
2
|
+
GitHubFileContent,
|
|
3
|
+
} from "../../../Server/Utils/CodeRepository/GitHub/GitHub";
|
|
4
|
+
import API from "../../../Utils/API";
|
|
5
|
+
import HTTPResponse from "../../../Types/API/HTTPResponse";
|
|
6
|
+
import HTTPErrorResponse from "../../../Types/API/HTTPErrorResponse";
|
|
7
|
+
import { JSONObject } from "../../../Types/JSON";
|
|
8
|
+
import { describe, expect, test, afterEach, beforeEach } from "@jest/globals";
|
|
9
|
+
|
|
10
|
+
/*
|
|
11
|
+
* getFileContent reads one file over the GitHub Contents API with no clone —
|
|
12
|
+
* the primitive that makes reading code viable from a request-scoped caller
|
|
13
|
+
* (the chat toolbox has a 45s per-tool timeout a clone cannot fit).
|
|
14
|
+
*
|
|
15
|
+
* The subtle contract these lock in: GitHub returns `content: ""` for BOTH a
|
|
16
|
+
* legitimately empty 0-byte file AND a blob it refuses to inline (>1MB). Only
|
|
17
|
+
* the `encoding` field tells them apart ("base64" vs "none"). Keying off the
|
|
18
|
+
* empty string alone reports every empty file as "too large".
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
const ARGS: {
|
|
22
|
+
installationId: string;
|
|
23
|
+
organizationName: string;
|
|
24
|
+
repositoryName: string;
|
|
25
|
+
branchName: string;
|
|
26
|
+
filePath: string;
|
|
27
|
+
} = {
|
|
28
|
+
installationId: "12345",
|
|
29
|
+
organizationName: "acme",
|
|
30
|
+
repositoryName: "checkout",
|
|
31
|
+
branchName: "main",
|
|
32
|
+
filePath: "src/billing/charge.ts",
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
function mockContentsResponse(data: JSONObject): void {
|
|
36
|
+
jest
|
|
37
|
+
.spyOn(API, "get")
|
|
38
|
+
.mockResolvedValue(new HTTPResponse<JSONObject>(200, data, {}));
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
beforeEach(() => {
|
|
42
|
+
// Token minting is exercised elsewhere; keep these tests to the read path.
|
|
43
|
+
jest
|
|
44
|
+
.spyOn(GitHubUtil, "getInstallationAccessToken")
|
|
45
|
+
.mockResolvedValue({ token: "t", expiresAt: new Date() });
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
afterEach(() => {
|
|
49
|
+
jest.restoreAllMocks();
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
describe("GitHubUtil.getFileContent", () => {
|
|
53
|
+
test("decodes a base64 file and counts its lines", async () => {
|
|
54
|
+
mockContentsResponse({
|
|
55
|
+
type: "file",
|
|
56
|
+
size: 12,
|
|
57
|
+
encoding: "base64",
|
|
58
|
+
content: Buffer.from("alpha\nbravo\n").toString("base64"),
|
|
59
|
+
html_url: "https://github.com/acme/checkout/blob/main/x.ts",
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
const file: GitHubFileContent | null =
|
|
63
|
+
await GitHubUtil.getFileContent(ARGS);
|
|
64
|
+
|
|
65
|
+
expect(file).not.toBeNull();
|
|
66
|
+
expect(file!.content).toBe("alpha\nbravo\n");
|
|
67
|
+
// A trailing newline must not invent a phantom third line.
|
|
68
|
+
expect(file!.totalLines).toBe(2);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
test("strips the newlines GitHub embeds in the base64 payload", async () => {
|
|
72
|
+
const encoded: string = Buffer.from("hello world").toString("base64");
|
|
73
|
+
|
|
74
|
+
mockContentsResponse({
|
|
75
|
+
type: "file",
|
|
76
|
+
size: 11,
|
|
77
|
+
encoding: "base64",
|
|
78
|
+
// GitHub wraps base64 at 60 chars; a naive decode would corrupt it.
|
|
79
|
+
content: `${encoded.slice(0, 4)}\n${encoded.slice(4)}\n`,
|
|
80
|
+
html_url: "",
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
const file: GitHubFileContent | null =
|
|
84
|
+
await GitHubUtil.getFileContent(ARGS);
|
|
85
|
+
|
|
86
|
+
expect(file!.content).toBe("hello world");
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
test("an empty 0-byte file reads as empty, NOT as too large", async () => {
|
|
90
|
+
mockContentsResponse({
|
|
91
|
+
type: "file",
|
|
92
|
+
size: 0,
|
|
93
|
+
encoding: "base64",
|
|
94
|
+
content: "",
|
|
95
|
+
html_url: "",
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
const file: GitHubFileContent | null =
|
|
99
|
+
await GitHubUtil.getFileContent(ARGS);
|
|
100
|
+
|
|
101
|
+
expect(file).not.toBeNull();
|
|
102
|
+
expect(file!.content).toBe("");
|
|
103
|
+
expect(file!.totalLines).toBe(0);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
test("a blob GitHub refuses to inline is reported as too large", async () => {
|
|
107
|
+
mockContentsResponse({
|
|
108
|
+
type: "file",
|
|
109
|
+
size: 3151774,
|
|
110
|
+
encoding: "none",
|
|
111
|
+
content: "",
|
|
112
|
+
html_url: "",
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
await expect(GitHubUtil.getFileContent(ARGS)).rejects.toThrow(
|
|
116
|
+
/too large for GitHub to return inline/,
|
|
117
|
+
);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
test("a missing path returns null so the caller can self-correct", async () => {
|
|
121
|
+
jest
|
|
122
|
+
.spyOn(API, "get")
|
|
123
|
+
.mockResolvedValue(
|
|
124
|
+
new HTTPErrorResponse(404, { message: "Not Found" }, {}),
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
await expect(GitHubUtil.getFileContent(ARGS)).resolves.toBeNull();
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
test("a directory returns null rather than mojibake", async () => {
|
|
131
|
+
jest
|
|
132
|
+
.spyOn(API, "get")
|
|
133
|
+
.mockResolvedValue(
|
|
134
|
+
new HTTPResponse<JSONObject>(
|
|
135
|
+
200,
|
|
136
|
+
[{ type: "file", name: "a.ts" }] as unknown as JSONObject,
|
|
137
|
+
{},
|
|
138
|
+
),
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
await expect(GitHubUtil.getFileContent(ARGS)).resolves.toBeNull();
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
test("a binary file returns null rather than wasting context", async () => {
|
|
145
|
+
mockContentsResponse({
|
|
146
|
+
type: "file",
|
|
147
|
+
size: 4,
|
|
148
|
+
encoding: "base64",
|
|
149
|
+
content: Buffer.from([0x89, 0x50, 0x00, 0x47]).toString("base64"),
|
|
150
|
+
html_url: "",
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
await expect(GitHubUtil.getFileContent(ARGS)).resolves.toBeNull();
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
test("a submodule/symlink entry returns null", async () => {
|
|
157
|
+
mockContentsResponse({
|
|
158
|
+
type: "submodule",
|
|
159
|
+
size: 0,
|
|
160
|
+
content: "",
|
|
161
|
+
html_url: "",
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
await expect(GitHubUtil.getFileContent(ARGS)).resolves.toBeNull();
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
test("path segments are encoded without destroying the separators", async () => {
|
|
168
|
+
const spy: jest.SpiedFunction<typeof API.get> = jest
|
|
169
|
+
.spyOn(API, "get")
|
|
170
|
+
.mockResolvedValue(
|
|
171
|
+
new HTTPResponse<JSONObject>(
|
|
172
|
+
200,
|
|
173
|
+
{
|
|
174
|
+
type: "file",
|
|
175
|
+
size: 1,
|
|
176
|
+
encoding: "base64",
|
|
177
|
+
content: Buffer.from("x").toString("base64"),
|
|
178
|
+
html_url: "",
|
|
179
|
+
},
|
|
180
|
+
{},
|
|
181
|
+
),
|
|
182
|
+
);
|
|
183
|
+
|
|
184
|
+
await GitHubUtil.getFileContent({
|
|
185
|
+
...ARGS,
|
|
186
|
+
filePath: "src/my folder/charge spec.ts",
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
const calledUrl: string = (
|
|
190
|
+
spy.mock.calls[0]![0] as { url: { toString: () => string } }
|
|
191
|
+
).url.toString();
|
|
192
|
+
|
|
193
|
+
// Slashes stay slashes; spaces get encoded.
|
|
194
|
+
expect(calledUrl).toContain("/contents/src/my%20folder/charge%20spec.ts");
|
|
195
|
+
expect(calledUrl).not.toContain("%2F");
|
|
196
|
+
});
|
|
197
|
+
});
|