@oneuptime/common 11.5.7 → 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.
Files changed (212) hide show
  1. package/Models/DatabaseModels/AIAgentTaskPullRequest.ts +15 -6
  2. package/Models/DatabaseModels/AIRun.ts +68 -0
  3. package/Models/DatabaseModels/AIRunEvent.ts +34 -1
  4. package/Models/DatabaseModels/NetworkDeviceDiscoveryScan.ts +215 -0
  5. package/Models/DatabaseModels/Project.ts +25 -0
  6. package/Models/DatabaseModels/StatusPage.ts +42 -0
  7. package/Server/API/AIAgentTaskAPI.ts +38 -12
  8. package/Server/API/AIAgentTaskLogAPI.ts +49 -0
  9. package/Server/API/AIChatAPI.ts +16 -0
  10. package/Server/API/AIReadinessAPI.ts +84 -0
  11. package/Server/API/CodeFixRunAPI.ts +190 -57
  12. package/Server/API/TelemetryExceptionAPI.ts +6 -1
  13. package/Server/Infrastructure/Postgres/SchemaMigrations/1784105912819-BackfillCodeFixTaskType.ts +41 -0
  14. package/Server/Infrastructure/Postgres/SchemaMigrations/1784124919694-AddAITaskNumberAndRunTranscript.ts +95 -0
  15. package/Server/Infrastructure/Postgres/SchemaMigrations/1784135099754-AllowNullAiAgentOnPullRequest.ts +52 -0
  16. package/Server/Infrastructure/Postgres/SchemaMigrations/1784137457184-AddEnableMcpServerToStatusPage.ts +30 -0
  17. package/Server/Infrastructure/Postgres/SchemaMigrations/1784200000000-AddSnmpV3ColumnsToNetworkDeviceDiscoveryScan.ts +49 -0
  18. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +10 -0
  19. package/Server/Middleware/MasterAdminAuthorization.ts +1 -1
  20. package/Server/Middleware/ProjectAuthorization.ts +1 -1
  21. package/Server/Services/AIRunEventService.ts +15 -1
  22. package/Server/Services/AIRunService.ts +48 -12
  23. package/Server/Services/LlmProviderService.ts +2 -0
  24. package/Server/Services/ProjectService.ts +42 -0
  25. package/Server/Services/StatusPageService.ts +87 -0
  26. package/Server/Services/TelemetryExceptionService.ts +19 -90
  27. package/Server/Types/Markdown.ts +9 -9
  28. package/Server/Types/MarkdownSlugify.ts +47 -0
  29. package/Server/Utils/AI/AIRunPrivacyFilter.ts +106 -0
  30. package/Server/Utils/AI/AIRunTranscript.ts +240 -0
  31. package/Server/Utils/AI/Chat/ChatAgentRunner.ts +9 -0
  32. package/Server/Utils/AI/Chat/ObservabilityChatPrompt.ts +119 -5
  33. package/Server/Utils/AI/CodeFix/CodeFixAgentCompletion.ts +75 -0
  34. package/Server/Utils/AI/CodeFix/CodeFixReadiness.ts +235 -0
  35. package/Server/Utils/AI/SRE/Insights/FixRouting.ts +2 -3
  36. package/Server/Utils/AI/Toolbox/CodeTools.ts +883 -0
  37. package/Server/Utils/AI/Toolbox/CodeWriteTools.ts +519 -0
  38. package/Server/Utils/AI/Toolbox/Index.ts +27 -0
  39. package/Server/Utils/AI/Toolbox/ScheduledMaintenanceTools.ts +279 -0
  40. package/Server/Utils/AnalyticsDatabase/ClickhouseCapacity.ts +19 -1
  41. package/Server/Utils/CodeRepository/GitHub/GitHub.ts +600 -0
  42. package/Server/Utils/CodeRepository/StackTraceRepoResolver.ts +7 -2
  43. package/Server/Utils/LLM/LLMService.ts +397 -18
  44. package/Server/Utils/Monitor/NetworkDeviceHydrationUtil.ts +4 -11
  45. package/Tests/Models/StatusPageEnableMcpServer.test.ts +125 -0
  46. package/Tests/Server/Services/StatusPageServiceMcp.test.ts +223 -0
  47. package/Tests/Server/Services/TelemetryExceptionAIFixReadiness.test.ts +26 -3
  48. package/Tests/Server/Services/TelemetryExceptionCodeFixRun.test.ts +12 -0
  49. package/Tests/Server/Types/Markdown.test.ts +106 -0
  50. package/Tests/Server/Utils/AI/AIRunPrivacyFilter.test.ts +295 -0
  51. package/Tests/Server/Utils/AI/AIRunTranscript.test.ts +182 -0
  52. package/Tests/Server/Utils/AI/CodeFixReadiness.test.ts +411 -0
  53. package/Tests/Server/Utils/AI/CodeTools.test.ts +596 -0
  54. package/Tests/Server/Utils/AI/CodeWriteTools.test.ts +475 -0
  55. package/Tests/Server/Utils/AI/LLMServiceModelCompatibility.test.ts +548 -0
  56. package/Tests/Server/Utils/AI/ObservabilityChatPrompt.test.ts +142 -0
  57. package/Tests/Server/Utils/AI/ScheduledMaintenanceTools.test.ts +176 -0
  58. package/Tests/Server/Utils/AnalyticsDatabase/ClickhouseCapacity.test.ts +63 -0
  59. package/Tests/Server/Utils/GitHubGetFileContent.test.ts +197 -0
  60. package/Tests/Server/Utils/Monitor/NetworkDeviceHydrationUtil.test.ts +502 -0
  61. package/Tests/Types/AI/AIAgentTaskStatus.test.ts +7 -6
  62. package/Tests/Types/AI/AIChatPageContext.test.ts +0 -0
  63. package/Tests/Types/AI/AIRunStatus.test.ts +42 -0
  64. package/Tests/Types/Log/LogSeverity.test.ts +78 -0
  65. package/Tests/UI/Components/BasicFormDropdownNormalization.test.tsx +167 -0
  66. package/Tests/UI/Components/Charts/AxisTickColor.test.tsx +66 -0
  67. package/Tests/UI/Utils/AIChatExport/ChatWidgetData.test.ts +319 -0
  68. package/Tests/UI/Utils/AIChatExport/ChatWidgetMarkdown.test.ts +188 -0
  69. package/Types/AI/AIAgentTaskStatus.ts +13 -0
  70. package/Types/AI/AIChatPageContext.ts +137 -0
  71. package/Types/AI/AIChatTypes.ts +44 -0
  72. package/Types/AI/AIFixReadiness.ts +46 -0
  73. package/Types/AI/AIRunStatus.ts +11 -0
  74. package/Types/Email/EmailTemplateType.ts +2 -0
  75. package/Types/Log/LogSeverity.ts +61 -0
  76. package/Types/Monitor/SnmpMonitor/SnmpVersion.ts +40 -0
  77. package/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.tsx +8 -2
  78. package/UI/Components/Charts/ChartLibrary/BarChart/BarChart.tsx +3 -1
  79. package/UI/Components/Charts/ChartLibrary/LineChart/LineChart.tsx +8 -2
  80. package/UI/Components/EditionLabel/EditionLabel.tsx +821 -331
  81. package/UI/Components/Forms/BasicForm.tsx +26 -8
  82. package/UI/Styles/Theme.css +1314 -0
  83. package/UI/Utils/AIChatExport/ChatWidgetData.ts +316 -0
  84. package/UI/Utils/AIChatExport/ChatWidgetMarkdown.ts +349 -0
  85. package/UI/Utils/AIChatExport/ConversationMarkdown.ts +177 -0
  86. package/UI/Utils/AIChatExport/MarkdownBlocks.ts +327 -0
  87. package/UI/Utils/AIChatExport/MarkdownSafety.ts +215 -0
  88. package/UI/Utils/DownloadFile.ts +54 -0
  89. package/build/dist/Models/DatabaseModels/AIAgentTaskPullRequest.js +15 -7
  90. package/build/dist/Models/DatabaseModels/AIAgentTaskPullRequest.js.map +1 -1
  91. package/build/dist/Models/DatabaseModels/AIRun.js +69 -0
  92. package/build/dist/Models/DatabaseModels/AIRun.js.map +1 -1
  93. package/build/dist/Models/DatabaseModels/AIRunEvent.js +31 -0
  94. package/build/dist/Models/DatabaseModels/AIRunEvent.js.map +1 -1
  95. package/build/dist/Models/DatabaseModels/NetworkDeviceDiscoveryScan.js +225 -0
  96. package/build/dist/Models/DatabaseModels/NetworkDeviceDiscoveryScan.js.map +1 -1
  97. package/build/dist/Models/DatabaseModels/Project.js +27 -0
  98. package/build/dist/Models/DatabaseModels/Project.js.map +1 -1
  99. package/build/dist/Models/DatabaseModels/StatusPage.js +43 -0
  100. package/build/dist/Models/DatabaseModels/StatusPage.js.map +1 -1
  101. package/build/dist/Server/API/AIAgentTaskAPI.js +37 -11
  102. package/build/dist/Server/API/AIAgentTaskAPI.js.map +1 -1
  103. package/build/dist/Server/API/AIAgentTaskLogAPI.js +32 -7
  104. package/build/dist/Server/API/AIAgentTaskLogAPI.js.map +1 -1
  105. package/build/dist/Server/API/AIChatAPI.js +9 -0
  106. package/build/dist/Server/API/AIChatAPI.js.map +1 -1
  107. package/build/dist/Server/API/AIReadinessAPI.js +57 -0
  108. package/build/dist/Server/API/AIReadinessAPI.js.map +1 -0
  109. package/build/dist/Server/API/CodeFixRunAPI.js +165 -51
  110. package/build/dist/Server/API/CodeFixRunAPI.js.map +1 -1
  111. package/build/dist/Server/API/TelemetryExceptionAPI.js +4 -0
  112. package/build/dist/Server/API/TelemetryExceptionAPI.js.map +1 -1
  113. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1784105912819-BackfillCodeFixTaskType.js +36 -0
  114. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1784105912819-BackfillCodeFixTaskType.js.map +1 -0
  115. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1784124919694-AddAITaskNumberAndRunTranscript.js +80 -0
  116. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1784124919694-AddAITaskNumberAndRunTranscript.js.map +1 -0
  117. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1784135099754-AllowNullAiAgentOnPullRequest.js +37 -0
  118. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1784135099754-AllowNullAiAgentOnPullRequest.js.map +1 -0
  119. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1784137457184-AddEnableMcpServerToStatusPage.js +23 -0
  120. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1784137457184-AddEnableMcpServerToStatusPage.js.map +1 -0
  121. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1784200000000-AddSnmpV3ColumnsToNetworkDeviceDiscoveryScan.js +22 -0
  122. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1784200000000-AddSnmpV3ColumnsToNetworkDeviceDiscoveryScan.js.map +1 -0
  123. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +10 -0
  124. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  125. package/build/dist/Server/Middleware/MasterAdminAuthorization.js +1 -1
  126. package/build/dist/Server/Middleware/ProjectAuthorization.js +1 -1
  127. package/build/dist/Server/Services/AIRunEventService.js +8 -0
  128. package/build/dist/Server/Services/AIRunEventService.js.map +1 -1
  129. package/build/dist/Server/Services/AIRunService.js +39 -3
  130. package/build/dist/Server/Services/AIRunService.js.map +1 -1
  131. package/build/dist/Server/Services/LlmProviderService.js +2 -0
  132. package/build/dist/Server/Services/LlmProviderService.js.map +1 -1
  133. package/build/dist/Server/Services/ProjectService.js +39 -0
  134. package/build/dist/Server/Services/ProjectService.js.map +1 -1
  135. package/build/dist/Server/Services/StatusPageService.js +81 -0
  136. package/build/dist/Server/Services/StatusPageService.js.map +1 -1
  137. package/build/dist/Server/Services/TelemetryExceptionService.js +14 -61
  138. package/build/dist/Server/Services/TelemetryExceptionService.js.map +1 -1
  139. package/build/dist/Server/Types/Markdown.js +8 -8
  140. package/build/dist/Server/Types/Markdown.js.map +1 -1
  141. package/build/dist/Server/Types/MarkdownSlugify.js +47 -0
  142. package/build/dist/Server/Types/MarkdownSlugify.js.map +1 -0
  143. package/build/dist/Server/Utils/AI/AIRunPrivacyFilter.js +82 -0
  144. package/build/dist/Server/Utils/AI/AIRunPrivacyFilter.js.map +1 -0
  145. package/build/dist/Server/Utils/AI/AIRunTranscript.js +180 -0
  146. package/build/dist/Server/Utils/AI/AIRunTranscript.js.map +1 -0
  147. package/build/dist/Server/Utils/AI/Chat/ChatAgentRunner.js +1 -0
  148. package/build/dist/Server/Utils/AI/Chat/ChatAgentRunner.js.map +1 -1
  149. package/build/dist/Server/Utils/AI/Chat/ObservabilityChatPrompt.js +103 -5
  150. package/build/dist/Server/Utils/AI/Chat/ObservabilityChatPrompt.js.map +1 -1
  151. package/build/dist/Server/Utils/AI/CodeFix/CodeFixAgentCompletion.js +55 -0
  152. package/build/dist/Server/Utils/AI/CodeFix/CodeFixAgentCompletion.js.map +1 -1
  153. package/build/dist/Server/Utils/AI/CodeFix/CodeFixReadiness.js +211 -0
  154. package/build/dist/Server/Utils/AI/CodeFix/CodeFixReadiness.js.map +1 -0
  155. package/build/dist/Server/Utils/AI/SRE/Insights/FixRouting.js.map +1 -1
  156. package/build/dist/Server/Utils/AI/Toolbox/CodeTools.js +654 -0
  157. package/build/dist/Server/Utils/AI/Toolbox/CodeTools.js.map +1 -0
  158. package/build/dist/Server/Utils/AI/Toolbox/CodeWriteTools.js +389 -0
  159. package/build/dist/Server/Utils/AI/Toolbox/CodeWriteTools.js.map +1 -0
  160. package/build/dist/Server/Utils/AI/Toolbox/Index.js +19 -0
  161. package/build/dist/Server/Utils/AI/Toolbox/Index.js.map +1 -1
  162. package/build/dist/Server/Utils/AI/Toolbox/ScheduledMaintenanceTools.js +239 -0
  163. package/build/dist/Server/Utils/AI/Toolbox/ScheduledMaintenanceTools.js.map +1 -0
  164. package/build/dist/Server/Utils/AnalyticsDatabase/ClickhouseCapacity.js +19 -1
  165. package/build/dist/Server/Utils/AnalyticsDatabase/ClickhouseCapacity.js.map +1 -1
  166. package/build/dist/Server/Utils/CodeRepository/GitHub/GitHub.js +439 -0
  167. package/build/dist/Server/Utils/CodeRepository/GitHub/GitHub.js.map +1 -1
  168. package/build/dist/Server/Utils/CodeRepository/StackTraceRepoResolver.js +1 -1
  169. package/build/dist/Server/Utils/CodeRepository/StackTraceRepoResolver.js.map +1 -1
  170. package/build/dist/Server/Utils/LLM/LLMService.js +275 -19
  171. package/build/dist/Server/Utils/LLM/LLMService.js.map +1 -1
  172. package/build/dist/Server/Utils/Monitor/NetworkDeviceHydrationUtil.js +2 -11
  173. package/build/dist/Server/Utils/Monitor/NetworkDeviceHydrationUtil.js.map +1 -1
  174. package/build/dist/Types/AI/AIAgentTaskStatus.js +12 -0
  175. package/build/dist/Types/AI/AIAgentTaskStatus.js.map +1 -1
  176. package/build/dist/Types/AI/AIChatPageContext.js +101 -0
  177. package/build/dist/Types/AI/AIChatPageContext.js.map +1 -0
  178. package/build/dist/Types/AI/AIChatTypes.js +2 -0
  179. package/build/dist/Types/AI/AIChatTypes.js.map +1 -1
  180. package/build/dist/Types/AI/AIFixReadiness.js +9 -0
  181. package/build/dist/Types/AI/AIFixReadiness.js.map +1 -0
  182. package/build/dist/Types/AI/AIRunStatus.js +11 -0
  183. package/build/dist/Types/AI/AIRunStatus.js.map +1 -1
  184. package/build/dist/Types/Email/EmailTemplateType.js +1 -0
  185. package/build/dist/Types/Email/EmailTemplateType.js.map +1 -1
  186. package/build/dist/Types/Log/LogSeverity.js +56 -0
  187. package/build/dist/Types/Log/LogSeverity.js.map +1 -1
  188. package/build/dist/Types/Monitor/SnmpMonitor/SnmpVersion.js +38 -0
  189. package/build/dist/Types/Monitor/SnmpMonitor/SnmpVersion.js.map +1 -1
  190. package/build/dist/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.js +9 -2
  191. package/build/dist/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.js.map +1 -1
  192. package/build/dist/UI/Components/Charts/ChartLibrary/BarChart/BarChart.js +3 -1
  193. package/build/dist/UI/Components/Charts/ChartLibrary/BarChart/BarChart.js.map +1 -1
  194. package/build/dist/UI/Components/Charts/ChartLibrary/LineChart/LineChart.js +9 -2
  195. package/build/dist/UI/Components/Charts/ChartLibrary/LineChart/LineChart.js.map +1 -1
  196. package/build/dist/UI/Components/EditionLabel/EditionLabel.js +424 -139
  197. package/build/dist/UI/Components/EditionLabel/EditionLabel.js.map +1 -1
  198. package/build/dist/UI/Components/Forms/BasicForm.js +24 -6
  199. package/build/dist/UI/Components/Forms/BasicForm.js.map +1 -1
  200. package/build/dist/UI/Utils/AIChatExport/ChatWidgetData.js +217 -0
  201. package/build/dist/UI/Utils/AIChatExport/ChatWidgetData.js.map +1 -0
  202. package/build/dist/UI/Utils/AIChatExport/ChatWidgetMarkdown.js +234 -0
  203. package/build/dist/UI/Utils/AIChatExport/ChatWidgetMarkdown.js.map +1 -0
  204. package/build/dist/UI/Utils/AIChatExport/ConversationMarkdown.js +111 -0
  205. package/build/dist/UI/Utils/AIChatExport/ConversationMarkdown.js.map +1 -0
  206. package/build/dist/UI/Utils/AIChatExport/MarkdownBlocks.js +233 -0
  207. package/build/dist/UI/Utils/AIChatExport/MarkdownBlocks.js.map +1 -0
  208. package/build/dist/UI/Utils/AIChatExport/MarkdownSafety.js +134 -0
  209. package/build/dist/UI/Utils/AIChatExport/MarkdownSafety.js.map +1 -0
  210. package/build/dist/UI/Utils/DownloadFile.js +41 -0
  211. package/build/dist/UI/Utils/DownloadFile.js.map +1 -0
  212. 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
+ });