@oneuptime/common 11.0.10 → 11.0.13
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/CephCluster.ts +134 -0
- package/Models/DatabaseModels/CloudResource.ts +134 -0
- package/Models/DatabaseModels/DockerHost.ts +134 -0
- package/Models/DatabaseModels/DockerSwarmCluster.ts +134 -0
- package/Models/DatabaseModels/Host.ts +134 -0
- package/Models/DatabaseModels/KubernetesCluster.ts +134 -0
- package/Models/DatabaseModels/PodmanHost.ts +134 -0
- package/Models/DatabaseModels/ProxmoxCluster.ts +134 -0
- package/Models/DatabaseModels/RumApplication.ts +134 -0
- package/Models/DatabaseModels/ServerlessFunction.ts +134 -0
- package/Models/DatabaseModels/Service.ts +134 -0
- package/Models/DatabaseModels/StatusPage.ts +0 -42
- package/README.md +1 -1
- package/Scripts/generate-service-worker.js +46 -36
- package/Server/Infrastructure/Postgres/DataSourceOptions.ts +4 -2
- package/Server/Infrastructure/Postgres/SchemaMigrations/1782600000000-AddArchiveToResources.ts +331 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1782700000000-RemoveDeprecatedEnableSubscribersFromStatusPage.ts +35 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +4 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Readme.md +3 -3
- package/Server/Infrastructure/Queue.ts +122 -0
- package/Server/Services/AIAgentService.ts +9 -4
- package/Server/Services/AnalyticsDatabaseService.ts +89 -56
- package/Server/Services/CephClusterService.ts +6 -4
- package/Server/Services/CloudResourceService.ts +6 -4
- package/Server/Services/DatabaseService.ts +126 -0
- package/Server/Services/DockerHostService.ts +6 -4
- package/Server/Services/DockerSwarmClusterService.ts +6 -4
- package/Server/Services/HostService.ts +6 -4
- package/Server/Services/KubernetesClusterService.ts +6 -4
- package/Server/Services/PodmanHostService.ts +6 -4
- package/Server/Services/ProbeService.ts +10 -4
- package/Server/Services/ProxmoxClusterService.ts +6 -4
- package/Server/Services/RumApplicationService.ts +6 -4
- package/Server/Services/ServerlessFunctionService.ts +6 -4
- package/Server/Services/ServiceService.ts +17 -4
- package/Server/Types/Markdown.ts +103 -0
- package/Server/Utils/AnalyticsDatabase/StatementGenerator.ts +5 -2
- package/Server/Utils/CodeRepository/CodeRepository.ts +78 -0
- package/Server/Utils/Execute.ts +6 -0
- package/Server/Utils/Logger.ts +66 -0
- package/Server/Utils/Monitor/MonitorResource.ts +24 -10
- package/Server/Utils/Telemetry/EntityRegistry.ts +10 -5
- package/Server/Utils/Workspace/Slack/app-manifest.json +86 -92
- package/Tests/Server/Services/UpdateColumnsByIdWithoutHooks.test.ts +154 -0
- package/Tests/Server/Utils/AnalyticsDatabase/StatementGenerator.test.ts +87 -9
- package/Tests/Types/DiskSize.test.ts +49 -0
- package/Types/Dashboard/DashboardComponents/DashboardTableComponent.ts +11 -0
- package/Types/Icon/IconProp.ts +1 -0
- package/Types/Monitor/MonitorType.ts +15 -31
- package/Types/Monitor/schema.json +32 -35
- package/UI/Components/BulkUpdate/BulkArchiveActions.tsx +150 -0
- package/UI/Components/Icon/Icon.tsx +42 -126
- package/UI/Components/Navbar/NavBarMenuModal.tsx +2 -5
- package/UI/esbuild-config.js +136 -110
- package/Utils/ClientAPI/README.md +1 -1
- package/build/dist/Models/DatabaseModels/CephCluster.js +136 -0
- package/build/dist/Models/DatabaseModels/CephCluster.js.map +1 -1
- package/build/dist/Models/DatabaseModels/CloudResource.js +136 -0
- package/build/dist/Models/DatabaseModels/CloudResource.js.map +1 -1
- package/build/dist/Models/DatabaseModels/DockerHost.js +136 -0
- package/build/dist/Models/DatabaseModels/DockerHost.js.map +1 -1
- package/build/dist/Models/DatabaseModels/DockerSwarmCluster.js +136 -0
- package/build/dist/Models/DatabaseModels/DockerSwarmCluster.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Host.js +136 -0
- package/build/dist/Models/DatabaseModels/Host.js.map +1 -1
- package/build/dist/Models/DatabaseModels/KubernetesCluster.js +136 -0
- package/build/dist/Models/DatabaseModels/KubernetesCluster.js.map +1 -1
- package/build/dist/Models/DatabaseModels/PodmanHost.js +136 -0
- package/build/dist/Models/DatabaseModels/PodmanHost.js.map +1 -1
- package/build/dist/Models/DatabaseModels/ProxmoxCluster.js +136 -0
- package/build/dist/Models/DatabaseModels/ProxmoxCluster.js.map +1 -1
- package/build/dist/Models/DatabaseModels/RumApplication.js +136 -0
- package/build/dist/Models/DatabaseModels/RumApplication.js.map +1 -1
- package/build/dist/Models/DatabaseModels/ServerlessFunction.js +136 -0
- package/build/dist/Models/DatabaseModels/ServerlessFunction.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Service.js +136 -0
- package/build/dist/Models/DatabaseModels/Service.js.map +1 -1
- package/build/dist/Models/DatabaseModels/StatusPage.js +0 -44
- package/build/dist/Models/DatabaseModels/StatusPage.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/DataSourceOptions.js +4 -2
- package/build/dist/Server/Infrastructure/Postgres/DataSourceOptions.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1782600000000-AddArchiveToResources.js +120 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1782600000000-AddArchiveToResources.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1782700000000-RemoveDeprecatedEnableSubscribersFromStatusPage.js +28 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1782700000000-RemoveDeprecatedEnableSubscribersFromStatusPage.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/Infrastructure/Queue.js +63 -0
- package/build/dist/Server/Infrastructure/Queue.js.map +1 -1
- package/build/dist/Server/Services/AIAgentService.js +9 -4
- package/build/dist/Server/Services/AIAgentService.js.map +1 -1
- package/build/dist/Server/Services/AnalyticsDatabaseService.js +52 -17
- package/build/dist/Server/Services/AnalyticsDatabaseService.js.map +1 -1
- package/build/dist/Server/Services/CephClusterService.js +6 -4
- package/build/dist/Server/Services/CephClusterService.js.map +1 -1
- package/build/dist/Server/Services/CloudResourceService.js +6 -4
- package/build/dist/Server/Services/CloudResourceService.js.map +1 -1
- package/build/dist/Server/Services/DatabaseService.js +101 -0
- package/build/dist/Server/Services/DatabaseService.js.map +1 -1
- package/build/dist/Server/Services/DockerHostService.js +6 -4
- package/build/dist/Server/Services/DockerHostService.js.map +1 -1
- package/build/dist/Server/Services/DockerSwarmClusterService.js +6 -4
- package/build/dist/Server/Services/DockerSwarmClusterService.js.map +1 -1
- package/build/dist/Server/Services/HostService.js +6 -4
- package/build/dist/Server/Services/HostService.js.map +1 -1
- package/build/dist/Server/Services/KubernetesClusterService.js +6 -4
- package/build/dist/Server/Services/KubernetesClusterService.js.map +1 -1
- package/build/dist/Server/Services/PodmanHostService.js +6 -4
- package/build/dist/Server/Services/PodmanHostService.js.map +1 -1
- package/build/dist/Server/Services/ProbeService.js +10 -4
- package/build/dist/Server/Services/ProbeService.js.map +1 -1
- package/build/dist/Server/Services/ProxmoxClusterService.js +6 -4
- package/build/dist/Server/Services/ProxmoxClusterService.js.map +1 -1
- package/build/dist/Server/Services/RumApplicationService.js +6 -4
- package/build/dist/Server/Services/RumApplicationService.js.map +1 -1
- package/build/dist/Server/Services/ServerlessFunctionService.js +6 -4
- package/build/dist/Server/Services/ServerlessFunctionService.js.map +1 -1
- package/build/dist/Server/Services/ServiceService.js +17 -4
- package/build/dist/Server/Services/ServiceService.js.map +1 -1
- package/build/dist/Server/Types/Markdown.js +87 -0
- package/build/dist/Server/Types/Markdown.js.map +1 -1
- package/build/dist/Server/Utils/AnalyticsDatabase/StatementGenerator.js +3 -0
- package/build/dist/Server/Utils/AnalyticsDatabase/StatementGenerator.js.map +1 -1
- package/build/dist/Server/Utils/CodeRepository/CodeRepository.js +60 -0
- package/build/dist/Server/Utils/CodeRepository/CodeRepository.js.map +1 -1
- package/build/dist/Server/Utils/Execute.js +1 -3
- package/build/dist/Server/Utils/Execute.js.map +1 -1
- package/build/dist/Server/Utils/Logger.js +49 -1
- package/build/dist/Server/Utils/Logger.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorResource.js +24 -10
- package/build/dist/Server/Utils/Monitor/MonitorResource.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/EntityRegistry.js +10 -5
- package/build/dist/Server/Utils/Telemetry/EntityRegistry.js.map +1 -1
- package/build/dist/Server/Utils/Workspace/Slack/app-manifest.json +3 -9
- package/build/dist/Types/Dashboard/DashboardComponents/DashboardTableComponent.js.map +1 -1
- package/build/dist/Types/Icon/IconProp.js +1 -0
- package/build/dist/Types/Icon/IconProp.js.map +1 -1
- package/build/dist/Types/Monitor/MonitorType.js +15 -31
- package/build/dist/Types/Monitor/MonitorType.js.map +1 -1
- package/build/dist/UI/Components/BulkUpdate/BulkArchiveActions.js +96 -0
- package/build/dist/UI/Components/BulkUpdate/BulkArchiveActions.js.map +1 -0
- package/build/dist/UI/Components/Icon/Icon.js +17 -41
- package/build/dist/UI/Components/Icon/Icon.js.map +1 -1
- package/build/dist/UI/Components/Navbar/NavBarMenuModal.js +2 -3
- package/build/dist/UI/Components/Navbar/NavBarMenuModal.js.map +1 -1
- package/jest.config.json +47 -66
- package/package.json +1 -1
- package/tsconfig.json +109 -113
|
@@ -1,97 +1,91 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
2
|
+
"display_information": {
|
|
3
|
+
"name": "OneUptime",
|
|
4
|
+
"description": "The Complete Open-Source Observability Platform",
|
|
5
|
+
"background_color": "#000000",
|
|
6
|
+
"long_description": "OneUptime is a comprehensive solution for monitoring and managing your online services. Whether you need to check the availability of your website, dashboard, API, or any other online resource, OneUptime can alert your team when downtime happens and keep your customers informed with a status page. OneUptime also helps you handle incidents, set up on-call rotations, run tests, secure your services, analyze logs, track performance, and debug errors."
|
|
7
|
+
},
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
},
|
|
19
|
-
"shortcuts": [
|
|
20
|
-
{
|
|
21
|
-
"name": "Create Scheduled Event",
|
|
22
|
-
"type": "global",
|
|
23
|
-
"callback_id": "/maintenance",
|
|
24
|
-
"description": "Create a new scheduled event in OneUptime"
|
|
25
|
-
},
|
|
26
|
-
{
|
|
27
|
-
"name": "Create New Incident",
|
|
28
|
-
"type": "global",
|
|
29
|
-
"callback_id": "/incident",
|
|
30
|
-
"description": "Creates a new incident in OneUptime"
|
|
31
|
-
}
|
|
32
|
-
],
|
|
33
|
-
"slash_commands": [
|
|
34
|
-
{
|
|
35
|
-
"command": "/incident",
|
|
36
|
-
"url": "{{SERVER_URL}}/api/slack/interactive",
|
|
37
|
-
"description": "Create a new Incident in OneUptime",
|
|
38
|
-
"usage_hint": "[incident title]",
|
|
39
|
-
"should_escape": false
|
|
40
|
-
},
|
|
41
|
-
{
|
|
42
|
-
"command": "/maintenance",
|
|
43
|
-
"url": "{{SERVER_URL}}/api/slack/interactive",
|
|
44
|
-
"description": "Create a new Scheduled Maintenance in OneUptime",
|
|
45
|
-
"usage_hint": "[maintenance title]",
|
|
46
|
-
"should_escape": false
|
|
47
|
-
}
|
|
48
|
-
]
|
|
9
|
+
"features": {
|
|
10
|
+
"app_home": {
|
|
11
|
+
"home_tab_enabled": false,
|
|
12
|
+
"messages_tab_enabled": true,
|
|
13
|
+
"messages_tab_read_only_enabled": false
|
|
14
|
+
},
|
|
15
|
+
"bot_user": {
|
|
16
|
+
"display_name": "OneUptime",
|
|
17
|
+
"always_online": true
|
|
49
18
|
},
|
|
50
|
-
"
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
"
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
19
|
+
"shortcuts": [
|
|
20
|
+
{
|
|
21
|
+
"name": "Create Scheduled Event",
|
|
22
|
+
"type": "global",
|
|
23
|
+
"callback_id": "/maintenance",
|
|
24
|
+
"description": "Create a new scheduled event in OneUptime"
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"name": "Create New Incident",
|
|
28
|
+
"type": "global",
|
|
29
|
+
"callback_id": "/incident",
|
|
30
|
+
"description": "Creates a new incident in OneUptime"
|
|
31
|
+
}
|
|
32
|
+
],
|
|
33
|
+
"slash_commands": [
|
|
34
|
+
{
|
|
35
|
+
"command": "/incident",
|
|
36
|
+
"url": "{{SERVER_URL}}/api/slack/interactive",
|
|
37
|
+
"description": "Create a new Incident in OneUptime",
|
|
38
|
+
"usage_hint": "[incident title]",
|
|
39
|
+
"should_escape": false
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
"command": "/maintenance",
|
|
43
|
+
"url": "{{SERVER_URL}}/api/slack/interactive",
|
|
44
|
+
"description": "Create a new Scheduled Maintenance in OneUptime",
|
|
45
|
+
"usage_hint": "[maintenance title]",
|
|
46
|
+
"should_escape": false
|
|
47
|
+
}
|
|
48
|
+
]
|
|
49
|
+
},
|
|
50
|
+
"oauth_config": {
|
|
51
|
+
"redirect_urls": ["{{SERVER_URL}}/api/slack/auth"],
|
|
52
|
+
"scopes": {
|
|
53
|
+
"user": ["users:read"],
|
|
54
|
+
"bot": [
|
|
55
|
+
"commands",
|
|
56
|
+
"channels:history",
|
|
57
|
+
"channels:join",
|
|
58
|
+
"channels:manage",
|
|
59
|
+
"channels:read",
|
|
60
|
+
"channels:write.invites",
|
|
61
|
+
"channels:write.topic",
|
|
62
|
+
"chat:write",
|
|
63
|
+
"users:read",
|
|
64
|
+
"groups:read",
|
|
65
|
+
"groups:write",
|
|
66
|
+
"groups:history",
|
|
67
|
+
"im:read",
|
|
68
|
+
"im:write",
|
|
69
|
+
"im:history",
|
|
70
|
+
"mpim:read",
|
|
71
|
+
"mpim:write",
|
|
72
|
+
"mpim:history",
|
|
73
|
+
"reactions:read"
|
|
74
|
+
]
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
"settings": {
|
|
78
|
+
"interactivity": {
|
|
79
|
+
"is_enabled": true,
|
|
80
|
+
"request_url": "{{SERVER_URL}}/api/slack/interactive",
|
|
81
|
+
"message_menu_options_url": "{{SERVER_URL}}/api/slack/options-load"
|
|
80
82
|
},
|
|
81
|
-
"
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
"org_deploy_enabled": true,
|
|
88
|
-
"socket_mode_enabled": false,
|
|
89
|
-
"token_rotation_enabled": false,
|
|
90
|
-
"event_subscriptions": {
|
|
91
|
-
"request_url": "{{SERVER_URL}}/api/slack/events",
|
|
92
|
-
"bot_events": [
|
|
93
|
-
"reaction_added"
|
|
94
|
-
]
|
|
95
|
-
}
|
|
83
|
+
"org_deploy_enabled": true,
|
|
84
|
+
"socket_mode_enabled": false,
|
|
85
|
+
"token_rotation_enabled": false,
|
|
86
|
+
"event_subscriptions": {
|
|
87
|
+
"request_url": "{{SERVER_URL}}/api/slack/events",
|
|
88
|
+
"bot_events": ["reaction_added"]
|
|
96
89
|
}
|
|
97
|
-
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import ServiceService from "../../../Server/Services/ServiceService";
|
|
2
|
+
import ObjectID from "../../../Types/ObjectID";
|
|
3
|
+
import { describe, expect, test, afterEach } from "@jest/globals";
|
|
4
|
+
|
|
5
|
+
/*
|
|
6
|
+
* DatabaseService.updateColumnsByIdWithoutHooks is the fast heartbeat write
|
|
7
|
+
* path behind ServiceService.updateLastSeen. It exists to keep the single
|
|
8
|
+
* hottest UPDATE in the system (lastSeenAt, bumped on every telemetry batch)
|
|
9
|
+
* out of the full updateOneById pipeline, which holds the row's write lock
|
|
10
|
+
* across several round trips and was observed heading a Postgres lock convoy
|
|
11
|
+
* that starved the connection pool.
|
|
12
|
+
*
|
|
13
|
+
* These tests mock the TypeORM manager (no Postgres) and lock in the two
|
|
14
|
+
* invariants that make the fix work:
|
|
15
|
+
* - it emits exactly ONE parameterized UPDATE, and
|
|
16
|
+
* - it NEVER rewrites the optimistic-lock `version` column (a regression
|
|
17
|
+
* here would re-introduce the contention the fix removed).
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
type QueryCall = [string, Array<unknown>];
|
|
21
|
+
|
|
22
|
+
const SERVICE_COLUMNS: Record<string, string> = {
|
|
23
|
+
lastSeenAt: "lastSeenAt",
|
|
24
|
+
serviceVersion: "serviceVersion",
|
|
25
|
+
deploymentEnvironment: "deploymentEnvironment",
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
/*
|
|
29
|
+
* Stand-in for the TypeORM driver's persist path. The real method applies
|
|
30
|
+
* column transformers (ObjectID -> uuid string, JSON.stringify, etc.); we
|
|
31
|
+
* model that here by wrapping a string value in `prepared(...)` so a test
|
|
32
|
+
* can assert the column value was routed through it rather than bound raw.
|
|
33
|
+
*/
|
|
34
|
+
function fakePreparePersistentValue(value: unknown): unknown {
|
|
35
|
+
return typeof value === "string" ? `prepared(${value})` : value;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function mockRepository(result: unknown = []): jest.Mock {
|
|
39
|
+
const query: jest.Mock = jest.fn().mockResolvedValue(result);
|
|
40
|
+
jest.spyOn(ServiceService, "getRepository").mockReturnValue({
|
|
41
|
+
manager: {
|
|
42
|
+
query,
|
|
43
|
+
connection: {
|
|
44
|
+
driver: { preparePersistentValue: fakePreparePersistentValue },
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
metadata: {
|
|
48
|
+
tableName: "Service",
|
|
49
|
+
primaryColumns: [{ databaseName: "_id" }],
|
|
50
|
+
updateDateColumn: { databaseName: "updatedAt" },
|
|
51
|
+
findColumnWithPropertyName: (propertyName: string) => {
|
|
52
|
+
return SERVICE_COLUMNS[propertyName]
|
|
53
|
+
? { databaseName: SERVICE_COLUMNS[propertyName] }
|
|
54
|
+
: undefined;
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
} as any);
|
|
58
|
+
return query;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
describe("DatabaseService.updateColumnsByIdWithoutHooks", () => {
|
|
62
|
+
afterEach(() => {
|
|
63
|
+
jest.restoreAllMocks();
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
test("emits a single UPDATE and never bumps the version column", async () => {
|
|
67
|
+
const query: jest.Mock = mockRepository();
|
|
68
|
+
const id: ObjectID = ObjectID.generate();
|
|
69
|
+
const lastSeenAt: Date = new Date();
|
|
70
|
+
|
|
71
|
+
await ServiceService.updateColumnsByIdWithoutHooks({
|
|
72
|
+
id,
|
|
73
|
+
data: { lastSeenAt } as never,
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
expect(query).toHaveBeenCalledTimes(1);
|
|
77
|
+
const [sql, params] = query.mock.calls[0] as QueryCall;
|
|
78
|
+
|
|
79
|
+
// The whole point of the fix: no optimistic-lock churn on the hot row.
|
|
80
|
+
expect(sql).not.toContain("version");
|
|
81
|
+
// updatedAt is still refreshed, exactly as the normal update path does.
|
|
82
|
+
expect(sql).toContain(`"updatedAt" = CURRENT_TIMESTAMP`);
|
|
83
|
+
expect(sql).toBe(
|
|
84
|
+
`UPDATE "Service" SET "lastSeenAt" = $1, "updatedAt" = CURRENT_TIMESTAMP WHERE "_id" = $2`,
|
|
85
|
+
);
|
|
86
|
+
expect(params).toEqual([lastSeenAt, id.toString()]);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
test("maps multiple entity properties to columns in stable param order", async () => {
|
|
90
|
+
const query: jest.Mock = mockRepository();
|
|
91
|
+
const id: ObjectID = ObjectID.generate();
|
|
92
|
+
const lastSeenAt: Date = new Date();
|
|
93
|
+
|
|
94
|
+
await ServiceService.updateColumnsByIdWithoutHooks({
|
|
95
|
+
id,
|
|
96
|
+
data: {
|
|
97
|
+
lastSeenAt,
|
|
98
|
+
serviceVersion: "1.2.3",
|
|
99
|
+
deploymentEnvironment: "production",
|
|
100
|
+
} as never,
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
const [sql, params] = query.mock.calls[0] as QueryCall;
|
|
104
|
+
expect(sql).toBe(
|
|
105
|
+
`UPDATE "Service" SET "lastSeenAt" = $1, "serviceVersion" = $2, ` +
|
|
106
|
+
`"deploymentEnvironment" = $3, "updatedAt" = CURRENT_TIMESTAMP WHERE "_id" = $4`,
|
|
107
|
+
);
|
|
108
|
+
/*
|
|
109
|
+
* Column values are routed through the driver's persist path (here the
|
|
110
|
+
* `prepared(...)` stand-in), so transformers/JSON/coercion apply exactly
|
|
111
|
+
* as save() would. The `_id` in the WHERE clause is bound raw.
|
|
112
|
+
*/
|
|
113
|
+
expect(params).toEqual([
|
|
114
|
+
lastSeenAt,
|
|
115
|
+
"prepared(1.2.3)",
|
|
116
|
+
"prepared(production)",
|
|
117
|
+
id.toString(),
|
|
118
|
+
]);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
test("throws on an unknown column instead of silently writing nothing", async () => {
|
|
122
|
+
mockRepository();
|
|
123
|
+
await expect(
|
|
124
|
+
ServiceService.updateColumnsByIdWithoutHooks({
|
|
125
|
+
id: ObjectID.generate(),
|
|
126
|
+
data: { notAColumn: "x" } as never,
|
|
127
|
+
}),
|
|
128
|
+
).rejects.toThrow(/unknown column "notAColumn"/);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
test("rejects SQL-expression (function) values it cannot bind", async () => {
|
|
132
|
+
const query: jest.Mock = mockRepository();
|
|
133
|
+
await expect(
|
|
134
|
+
ServiceService.updateColumnsByIdWithoutHooks({
|
|
135
|
+
id: ObjectID.generate(),
|
|
136
|
+
data: {
|
|
137
|
+
lastSeenAt: (() => {
|
|
138
|
+
return "NOW()";
|
|
139
|
+
}) as never,
|
|
140
|
+
} as never,
|
|
141
|
+
}),
|
|
142
|
+
).rejects.toThrow(/SQL-expression values are not supported/);
|
|
143
|
+
expect(query).not.toHaveBeenCalled();
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
test("does not issue a query when there are no columns to update", async () => {
|
|
147
|
+
const query: jest.Mock = mockRepository();
|
|
148
|
+
await ServiceService.updateColumnsByIdWithoutHooks({
|
|
149
|
+
id: ObjectID.generate(),
|
|
150
|
+
data: {} as never,
|
|
151
|
+
});
|
|
152
|
+
expect(query).not.toHaveBeenCalled();
|
|
153
|
+
});
|
|
154
|
+
});
|
|
@@ -111,12 +111,15 @@ describe("StatementGenerator", () => {
|
|
|
111
111
|
expect(jest.mocked(logger.debug)).toHaveBeenNthCalledWith(2, statement);
|
|
112
112
|
|
|
113
113
|
/* eslint-disable prettier/prettier */
|
|
114
|
-
|
|
115
|
-
|
|
114
|
+
expectStatement(
|
|
115
|
+
statement,
|
|
116
|
+
SQL`
|
|
117
|
+
ALTER TABLE ${"oneuptime"}.${"<table-name>"}
|
|
116
118
|
UPDATE <set-statement>
|
|
117
119
|
WHERE TRUE <where-statement>
|
|
118
|
-
|
|
119
|
-
|
|
120
|
+
`,
|
|
121
|
+
);
|
|
122
|
+
/* eslint-enable prettier/prettier */
|
|
120
123
|
});
|
|
121
124
|
});
|
|
122
125
|
|
|
@@ -633,6 +636,81 @@ describe("StatementGenerator", () => {
|
|
|
633
636
|
});
|
|
634
637
|
});
|
|
635
638
|
|
|
639
|
+
describe("toCreateStatement", () => {
|
|
640
|
+
/*
|
|
641
|
+
* Regression test for the escapeStringLiteral undefined/null guard.
|
|
642
|
+
* An ArrayText (Array(String)) column whose value array carried
|
|
643
|
+
* undefined/null members at runtime used to throw
|
|
644
|
+
* "TypeError: Cannot read properties of undefined (reading 'replace')"
|
|
645
|
+
* inside escapeStringLiteral while building the INSERT. The member is
|
|
646
|
+
* now rendered as the empty string literal '' so the statement still
|
|
647
|
+
* serializes to a valid ClickHouse Array(String).
|
|
648
|
+
*/
|
|
649
|
+
class ArrayTextModel extends AnalyticsBaseModel {
|
|
650
|
+
public constructor() {
|
|
651
|
+
super({
|
|
652
|
+
tableName: "<array-create-table>",
|
|
653
|
+
singularName: "<singular>",
|
|
654
|
+
pluralName: "<plural>",
|
|
655
|
+
tableColumns: [
|
|
656
|
+
new AnalyticsTableColumn({
|
|
657
|
+
key: "_id",
|
|
658
|
+
title: "<title>",
|
|
659
|
+
description: "<description>",
|
|
660
|
+
required: true,
|
|
661
|
+
type: TableColumnType.ObjectID,
|
|
662
|
+
}),
|
|
663
|
+
new AnalyticsTableColumn({
|
|
664
|
+
key: "entityKeys",
|
|
665
|
+
title: "<title>",
|
|
666
|
+
description: "<description>",
|
|
667
|
+
required: true,
|
|
668
|
+
defaultValue: [],
|
|
669
|
+
type: TableColumnType.ArrayText,
|
|
670
|
+
}),
|
|
671
|
+
],
|
|
672
|
+
crudApiPath: new Route("route"),
|
|
673
|
+
primaryKeys: ["_id"],
|
|
674
|
+
sortKeys: ["_id"],
|
|
675
|
+
partitionKey: "_id",
|
|
676
|
+
tableEngine: AnalyticsTableEngine.MergeTree,
|
|
677
|
+
});
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
let arrayTextGenerator: StatementGenerator<ArrayTextModel>;
|
|
682
|
+
beforeEach(() => {
|
|
683
|
+
arrayTextGenerator = new StatementGenerator<ArrayTextModel>({
|
|
684
|
+
modelType: ArrayTextModel,
|
|
685
|
+
database: ClickhouseAppInstance,
|
|
686
|
+
});
|
|
687
|
+
});
|
|
688
|
+
|
|
689
|
+
test("renders undefined/null ArrayText elements as '' instead of throwing", () => {
|
|
690
|
+
const item: ArrayTextModel = new ArrayTextModel();
|
|
691
|
+
item.setColumnValue("_id", "210dac24142f1baa");
|
|
692
|
+
/*
|
|
693
|
+
* Simulate runtime data where the Array(String) column carries
|
|
694
|
+
* undefined/null members. The type system models the column as
|
|
695
|
+
* string[], hence the cast — this is exactly what crashed before
|
|
696
|
+
* the guard was added.
|
|
697
|
+
*/
|
|
698
|
+
item.setColumnValue("entityKeys", [
|
|
699
|
+
"alpha",
|
|
700
|
+
undefined,
|
|
701
|
+
null,
|
|
702
|
+
"beta",
|
|
703
|
+
] as unknown as Array<string>);
|
|
704
|
+
|
|
705
|
+
let statement: string = "";
|
|
706
|
+
expect(() => {
|
|
707
|
+
statement = arrayTextGenerator.toCreateStatement({ item: [item] });
|
|
708
|
+
}).not.toThrow();
|
|
709
|
+
|
|
710
|
+
expect(statement).toContain("['alpha', '', '', 'beta']");
|
|
711
|
+
});
|
|
712
|
+
});
|
|
713
|
+
|
|
636
714
|
describe("toSelectStatement", () => {
|
|
637
715
|
test("should return the contents of a SELECT statement", () => {
|
|
638
716
|
const { statement, columns } = generator.toSelectStatement({
|
|
@@ -790,18 +868,18 @@ describe("StatementGenerator", () => {
|
|
|
790
868
|
expect(jest.mocked(logger.debug)).toHaveBeenNthCalledWith(2, statement);
|
|
791
869
|
|
|
792
870
|
/* eslint-disable prettier/prettier */
|
|
793
|
-
|
|
794
|
-
CREATE TABLE IF NOT EXISTS ${
|
|
871
|
+
const expectedStatement: Statement = SQL`
|
|
872
|
+
CREATE TABLE IF NOT EXISTS ${"oneuptime"}.${"<table-name>"}
|
|
795
873
|
(
|
|
796
874
|
<columns-create-statement>
|
|
797
875
|
)
|
|
798
876
|
ENGINE = MergeTree
|
|
799
877
|
PARTITION BY (column_ObjectID)
|
|
800
878
|
|
|
801
|
-
PRIMARY KEY (${
|
|
802
|
-
ORDER BY (${
|
|
879
|
+
PRIMARY KEY (${"column_ObjectID"})
|
|
880
|
+
ORDER BY (${"column_ObjectID"})
|
|
803
881
|
`;
|
|
804
|
-
|
|
882
|
+
/* eslint-enable prettier/prettier */
|
|
805
883
|
|
|
806
884
|
// Normalize whitespace for comparison to avoid formatting issues
|
|
807
885
|
const normalizeWhitespace: (s: string) => string = (
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import BadDataException from "../../Types/Exception/BadDataException";
|
|
2
|
+
import DiskSize from "../../Types/DiskSize";
|
|
3
|
+
|
|
4
|
+
describe("DiskSize", () => {
|
|
5
|
+
describe("convertToDecimalPlaces", () => {
|
|
6
|
+
test("rounds to 2 decimal places by default", () => {
|
|
7
|
+
expect(DiskSize.convertToDecimalPlaces(3.14159)).toBe(3.14);
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
test("rounds to the requested number of decimal places", () => {
|
|
11
|
+
expect(DiskSize.convertToDecimalPlaces(3.14159, 3)).toBe(3.142);
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
test("returns the ceiling when decimalPlaces is 0", () => {
|
|
15
|
+
expect(DiskSize.convertToDecimalPlaces(4.2, 0)).toBe(5);
|
|
16
|
+
expect(DiskSize.convertToDecimalPlaces(4, 0)).toBe(4);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
test("parses a string value before rounding", () => {
|
|
20
|
+
expect(
|
|
21
|
+
DiskSize.convertToDecimalPlaces("2.71828" as unknown as number),
|
|
22
|
+
).toBe(2.72);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
test("throws a BadDataException when decimalPlaces is negative", () => {
|
|
26
|
+
expect(() => {
|
|
27
|
+
return DiskSize.convertToDecimalPlaces(1.5, -1);
|
|
28
|
+
}).toThrow(BadDataException);
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
describe("byteSizeToGB", () => {
|
|
33
|
+
test("converts bytes to gigabytes", () => {
|
|
34
|
+
expect(DiskSize.byteSizeToGB(1024 * 1024 * 1024)).toBe(1);
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
describe("byteSizeToMB", () => {
|
|
39
|
+
test("converts bytes to megabytes", () => {
|
|
40
|
+
expect(DiskSize.byteSizeToMB(1024 * 1024)).toBe(1);
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
describe("byteSizeToKB", () => {
|
|
45
|
+
test("converts bytes to kilobytes", () => {
|
|
46
|
+
expect(DiskSize.byteSizeToKB(1024)).toBe(1);
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
});
|
|
@@ -2,6 +2,8 @@ import MetricFormulaConfigData from "../../Metrics/MetricFormulaConfigData";
|
|
|
2
2
|
import MetricQueryConfigData from "../../Metrics/MetricQueryConfigData";
|
|
3
3
|
import MetricsAggregationType from "../../Metrics/MetricsAggregationType";
|
|
4
4
|
import ObjectID from "../../ObjectID";
|
|
5
|
+
import Dictionary from "../../Dictionary";
|
|
6
|
+
import { DictionaryEntryValue } from "../../../UI/Components/Dictionary/DictionaryFilterOperator";
|
|
5
7
|
import DashboardComponentType from "../DashboardComponentType";
|
|
6
8
|
import BaseComponent from "./DashboardBaseComponent";
|
|
7
9
|
|
|
@@ -76,6 +78,15 @@ export default interface DashboardTableComponent extends BaseComponent {
|
|
|
76
78
|
maxRows?: number | undefined;
|
|
77
79
|
reduce?: TableReduce | undefined;
|
|
78
80
|
decimals?: number | undefined;
|
|
81
|
+
/*
|
|
82
|
+
* Widget-level attribute filter applied to EVERY metric column's query
|
|
83
|
+
* (e.g. deployment.environment = production, so the table only shows
|
|
84
|
+
* hosts/services in that environment). Stored in the same operator-aware
|
|
85
|
+
* shape the chart widget writes into filterData.attributes, so it flows
|
|
86
|
+
* straight through MetricUtil.fetchResults' sanitizeAttributeFilters with
|
|
87
|
+
* no backend changes. Empty/undefined = no filter (every series shown).
|
|
88
|
+
*/
|
|
89
|
+
attributeFilters?: Dictionary<DictionaryEntryValue> | undefined;
|
|
79
90
|
/*
|
|
80
91
|
* Legacy widget shape from before the columns/groupByAttributes
|
|
81
92
|
* redesign — read-only at render time, never written by the editor.
|
package/Types/Icon/IconProp.ts
CHANGED
|
@@ -93,36 +93,20 @@ export class MonitorTypeHelper {
|
|
|
93
93
|
monitorTypes: [MonitorType.IncomingRequest, MonitorType.IncomingEmail],
|
|
94
94
|
},
|
|
95
95
|
{
|
|
96
|
-
label: "
|
|
96
|
+
label: "Network",
|
|
97
97
|
monitorTypes: [MonitorType.SNMP],
|
|
98
98
|
},
|
|
99
99
|
{
|
|
100
|
-
label: "
|
|
101
|
-
monitorTypes: [
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
},
|
|
111
|
-
{
|
|
112
|
-
label: "Podman",
|
|
113
|
-
monitorTypes: [MonitorType.Podman],
|
|
114
|
-
},
|
|
115
|
-
{
|
|
116
|
-
label: "Docker Swarm",
|
|
117
|
-
monitorTypes: [MonitorType.DockerSwarm],
|
|
118
|
-
},
|
|
119
|
-
{
|
|
120
|
-
label: "Proxmox",
|
|
121
|
-
monitorTypes: [MonitorType.Proxmox],
|
|
122
|
-
},
|
|
123
|
-
{
|
|
124
|
-
label: "Ceph",
|
|
125
|
-
monitorTypes: [MonitorType.Ceph],
|
|
100
|
+
label: "Infrastructure",
|
|
101
|
+
monitorTypes: [
|
|
102
|
+
MonitorType.Host,
|
|
103
|
+
MonitorType.Kubernetes,
|
|
104
|
+
MonitorType.Docker,
|
|
105
|
+
MonitorType.Podman,
|
|
106
|
+
MonitorType.DockerSwarm,
|
|
107
|
+
MonitorType.Proxmox,
|
|
108
|
+
MonitorType.Ceph,
|
|
109
|
+
],
|
|
126
110
|
},
|
|
127
111
|
{
|
|
128
112
|
label: "Telemetry",
|
|
@@ -225,28 +209,28 @@ export class MonitorTypeHelper {
|
|
|
225
209
|
title: "Podman Container",
|
|
226
210
|
description:
|
|
227
211
|
"This monitor type lets you monitor Podman containers — CPU, memory, network, restarts, and lifecycle events.",
|
|
228
|
-
icon: IconProp.
|
|
212
|
+
icon: IconProp.Podman,
|
|
229
213
|
},
|
|
230
214
|
{
|
|
231
215
|
monitorType: MonitorType.DockerSwarm,
|
|
232
216
|
title: "Docker Swarm",
|
|
233
217
|
description:
|
|
234
218
|
"This monitor type lets you monitor Docker Swarm clusters — per-task container CPU, memory, network, process count, and uptime across the cluster's services and nodes.",
|
|
235
|
-
icon: IconProp.
|
|
219
|
+
icon: IconProp.DockerSwarm,
|
|
236
220
|
},
|
|
237
221
|
{
|
|
238
222
|
monitorType: MonitorType.Proxmox,
|
|
239
223
|
title: "Proxmox",
|
|
240
224
|
description:
|
|
241
225
|
"This monitor type lets you monitor Proxmox VE clusters — node availability, VM and container (guest) health, CPU, memory, storage, and HA state.",
|
|
242
|
-
icon: IconProp.
|
|
226
|
+
icon: IconProp.Proxmox,
|
|
243
227
|
},
|
|
244
228
|
{
|
|
245
229
|
monitorType: MonitorType.Ceph,
|
|
246
230
|
title: "Ceph",
|
|
247
231
|
description:
|
|
248
232
|
"This monitor type lets you monitor Ceph storage clusters — cluster health, monitor quorum, OSD availability, pool capacity, and placement group states.",
|
|
249
|
-
icon: IconProp.
|
|
233
|
+
icon: IconProp.Ceph,
|
|
250
234
|
},
|
|
251
235
|
{
|
|
252
236
|
monitorType: MonitorType.IP,
|