@oneuptime/common 12.0.2 → 12.0.3
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/DatabaseBaseModel/DatabaseBaseModel.ts +14 -0
- package/Models/DatabaseModels/Index.ts +2 -0
- package/Models/DatabaseModels/LogDropFilter.ts +9 -20
- package/Models/DatabaseModels/StatusPageMonitorRule.ts +911 -0
- package/Models/DatabaseModels/StatusPageResource.ts +73 -0
- package/Models/DatabaseModels/TraceDropFilter.ts +9 -20
- package/Server/API/BaseAPI.ts +13 -2
- package/Server/API/DashboardAPI.ts +39 -0
- package/Server/EnvironmentConfig.ts +31 -1
- package/Server/Infrastructure/GlobalCache.ts +115 -0
- package/Server/Infrastructure/Postgres/DataSourceOptions.ts +16 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1786005052769-AddStatusPageMonitorRule.ts +123 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +2 -0
- package/Server/Services/AlertStateTimelineService.ts +20 -0
- package/Server/Services/CephClusterService.ts +26 -86
- package/Server/Services/CloudResourceService.ts +23 -83
- package/Server/Services/DatabaseService.ts +111 -0
- package/Server/Services/DockerHostService.ts +19 -79
- package/Server/Services/DockerSwarmClusterService.ts +27 -88
- package/Server/Services/HostService.ts +46 -99
- package/Server/Services/IncidentStateTimelineService.ts +20 -0
- package/Server/Services/Index.ts +2 -0
- package/Server/Services/IoTFleetService.ts +19 -79
- package/Server/Services/KubernetesClusterService.ts +17 -78
- package/Server/Services/LabelService.ts +57 -10
- package/Server/Services/MetricTypeService.ts +84 -0
- package/Server/Services/MonitorService.ts +89 -14
- package/Server/Services/MonitorStatusTimelineService.ts +29 -0
- package/Server/Services/MonitorTemplateService.ts +65 -0
- package/Server/Services/PodmanHostService.ts +19 -79
- package/Server/Services/ProxmoxClusterService.ts +23 -83
- package/Server/Services/RumApplicationService.ts +19 -80
- package/Server/Services/ScheduledMaintenanceStateTimelineService.ts +21 -0
- package/Server/Services/ServerlessFunctionService.ts +24 -85
- package/Server/Services/ServiceService.ts +299 -73
- package/Server/Services/StatusPageMonitorRuleEngineService.ts +752 -0
- package/Server/Services/StatusPageMonitorRuleService.ts +471 -0
- package/Server/Services/TelemetryEntityRelationshipService.ts +1 -0
- package/Server/Services/TelemetryEntityService.ts +1 -0
- package/Server/Utils/Database/ProjectScopedReferenceValidator.ts +170 -39
- package/Server/Utils/Monitor/MonitorResource.ts +63 -36
- package/Server/Utils/Monitor/MonitorStatusTimeline.ts +36 -0
- package/Server/Utils/Monitor/MonitorStepsProjectValidator.ts +148 -127
- package/Server/Utils/Monitor/MonitorStepsReferenceExtractor.ts +434 -0
- package/Server/Utils/SingleFlight.ts +83 -0
- package/Server/Utils/StatusPage/MonitorRulePatternValidator.ts +46 -0
- package/Server/Utils/Telemetry/EntityRegistry.ts +107 -26
- package/Server/Utils/Telemetry/ResourceHeartbeat.ts +314 -0
- package/Server/Utils/Telemetry/Telemetry.ts +351 -122
- package/Tests/App/Dashboard/WidgetCatalog.test.ts +2 -0
- package/Tests/Models/DatabaseModels/ColumnTransformerInvariants.test.ts +306 -0
- package/Tests/Models/DatabaseModels/NumberColumnDeserialization.test.ts +333 -0
- package/Tests/Models/DatabaseModels/StatusPageMonitorRuleModel.test.ts +190 -0
- package/Tests/Server/API/DashboardPublicResourceListAPI.test.ts +26 -0
- package/Tests/Server/Infrastructure/GlobalCache.test.ts +226 -0
- package/Tests/Server/Infrastructure/PostgresLockTimeoutOptions.test.ts +181 -0
- package/Tests/Server/Services/DatabaseServiceUpdateColumnsIfUnlocked.test.ts +342 -0
- package/Tests/Server/Services/DropFilterSaveValidation.test.ts +245 -0
- package/Tests/Server/Services/HostServiceUpdateLastSeen.test.ts +163 -15
- package/Tests/Server/Services/MetricTypeAttachServices.test.ts +222 -0
- package/Tests/Server/Services/MissingReferenceWriteGuard.test.ts +614 -0
- package/Tests/Server/Services/MonitorStatusTimelineFastPath.test.ts +23 -0
- package/Tests/Server/Services/ResourceUpdateLastSeenLivenessFallback.test.ts +305 -56
- package/Tests/Server/Services/ServiceUpdateLastSeenSplitGates.test.ts +705 -0
- package/Tests/Server/Services/StatusPageMonitorRuleEngineService.test.ts +1719 -0
- package/Tests/Server/Services/StatusPageMonitorRuleHooks.test.ts +1212 -0
- package/Tests/Server/Services/StatusPageMonitorRuleMonitorCreateHook.test.ts +228 -0
- package/Tests/Server/Utils/APIKey/AccessPermission.test.ts +267 -0
- package/Tests/Server/Utils/Database/ProjectScopedReferenceValidator.test.ts +260 -13
- package/Tests/Server/Utils/EntityRegistryRowFence.test.ts +302 -0
- package/Tests/Server/Utils/MetricTypeIndexConvergence.test.ts +598 -0
- package/Tests/Server/Utils/Monitor/MonitorStatusTimelineUnusableStatus.test.ts +154 -0
- package/Tests/Server/Utils/Monitor/MonitorStepsProjectValidator.test.ts +638 -92
- package/Tests/Server/Utils/Monitor/MonitorStepsReferenceExtractor.test.ts +724 -0
- package/Tests/Server/Utils/SingleFlight.test.ts +221 -0
- package/Tests/Types/Dashboard/NetworkDashboardTemplate.test.ts +793 -0
- package/Tests/Types/Database/BigIntColumnTransformer.test.ts +147 -0
- package/Tests/Types/Database/DatabasePropertyTransformer.test.ts +140 -0
- package/Tests/Types/Database/NumericColumnValue.test.ts +127 -0
- package/Tests/Types/Decimal.test.ts +37 -0
- package/Tests/Types/Monitor/SnmpVersionParsing.test.ts +152 -0
- package/Tests/Types/Port.test.ts +38 -0
- package/Tests/UI/Components/LogsHistogram.test.tsx +276 -0
- package/Tests/UI/Components/UseLiveLogsRefresh.test.tsx +489 -0
- package/Tests/UI/Components/UseLogsHistogram.test.tsx +500 -0
- package/Tests/Utils/Dashboard/Components/DashboardComponentsUtil.test.ts +120 -0
- package/Tests/Utils/Dashboard/DashboardNetworkMapComponent.test.ts +327 -0
- package/Types/Dashboard/DashboardComponentType.ts +1 -0
- package/Types/Dashboard/DashboardComponents/ComponentArgument.ts +1 -0
- package/Types/Dashboard/DashboardComponents/DashboardNetworkMapComponent.ts +40 -0
- package/Types/Dashboard/DashboardTemplates.ts +397 -0
- package/Types/Database/BigIntColumnTransformer.ts +74 -0
- package/Types/Database/DatabaseProperty.ts +26 -0
- package/Types/Database/NumericColumnValue.ts +128 -0
- package/Types/Decimal.ts +16 -4
- package/Types/Permission.ts +48 -0
- package/Types/Port.ts +16 -4
- package/UI/Components/LogsViewer/useLiveLogsRefresh.ts +86 -0
- package/UI/Components/LogsViewer/useLogsHistogram.ts +113 -0
- package/Utils/Dashboard/Components/DashboardNetworkMapComponent.ts +136 -0
- package/Utils/Dashboard/Components/Index.ts +7 -0
- package/build/dist/Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel.js +12 -0
- package/build/dist/Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Index.js +2 -0
- package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
- package/build/dist/Models/DatabaseModels/LogDropFilter.js +9 -20
- package/build/dist/Models/DatabaseModels/LogDropFilter.js.map +1 -1
- package/build/dist/Models/DatabaseModels/StatusPageMonitorRule.js +925 -0
- package/build/dist/Models/DatabaseModels/StatusPageMonitorRule.js.map +1 -0
- package/build/dist/Models/DatabaseModels/StatusPageResource.js +72 -0
- package/build/dist/Models/DatabaseModels/StatusPageResource.js.map +1 -1
- package/build/dist/Models/DatabaseModels/TraceDropFilter.js +9 -20
- package/build/dist/Models/DatabaseModels/TraceDropFilter.js.map +1 -1
- package/build/dist/Server/API/BaseAPI.js +11 -1
- package/build/dist/Server/API/BaseAPI.js.map +1 -1
- package/build/dist/Server/API/DashboardAPI.js +39 -0
- package/build/dist/Server/API/DashboardAPI.js.map +1 -1
- package/build/dist/Server/EnvironmentConfig.js +27 -1
- package/build/dist/Server/EnvironmentConfig.js.map +1 -1
- package/build/dist/Server/Infrastructure/GlobalCache.js +91 -0
- package/build/dist/Server/Infrastructure/GlobalCache.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/DataSourceOptions.js +5 -3
- package/build/dist/Server/Infrastructure/Postgres/DataSourceOptions.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786005052769-AddStatusPageMonitorRule.js +48 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1786005052769-AddStatusPageMonitorRule.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +2 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Services/AlertStateTimelineService.js +19 -0
- package/build/dist/Server/Services/AlertStateTimelineService.js.map +1 -1
- package/build/dist/Server/Services/CephClusterService.js +26 -76
- package/build/dist/Server/Services/CephClusterService.js.map +1 -1
- package/build/dist/Server/Services/CloudResourceService.js +23 -73
- package/build/dist/Server/Services/CloudResourceService.js.map +1 -1
- package/build/dist/Server/Services/DatabaseService.js +86 -0
- package/build/dist/Server/Services/DatabaseService.js.map +1 -1
- package/build/dist/Server/Services/DockerHostService.js +19 -69
- package/build/dist/Server/Services/DockerHostService.js.map +1 -1
- package/build/dist/Server/Services/DockerSwarmClusterService.js +27 -78
- package/build/dist/Server/Services/DockerSwarmClusterService.js.map +1 -1
- package/build/dist/Server/Services/HostService.js +58 -102
- package/build/dist/Server/Services/HostService.js.map +1 -1
- package/build/dist/Server/Services/IncidentStateTimelineService.js +19 -0
- package/build/dist/Server/Services/IncidentStateTimelineService.js.map +1 -1
- package/build/dist/Server/Services/Index.js +2 -0
- package/build/dist/Server/Services/Index.js.map +1 -1
- package/build/dist/Server/Services/IoTFleetService.js +19 -69
- package/build/dist/Server/Services/IoTFleetService.js.map +1 -1
- package/build/dist/Server/Services/KubernetesClusterService.js +17 -68
- package/build/dist/Server/Services/KubernetesClusterService.js.map +1 -1
- package/build/dist/Server/Services/LabelService.js +42 -7
- package/build/dist/Server/Services/LabelService.js.map +1 -1
- package/build/dist/Server/Services/MetricTypeService.js +74 -0
- package/build/dist/Server/Services/MetricTypeService.js.map +1 -1
- package/build/dist/Server/Services/MonitorService.js +81 -9
- package/build/dist/Server/Services/MonitorService.js.map +1 -1
- package/build/dist/Server/Services/MonitorStatusTimelineService.js +28 -0
- package/build/dist/Server/Services/MonitorStatusTimelineService.js.map +1 -1
- package/build/dist/Server/Services/MonitorTemplateService.js +60 -0
- package/build/dist/Server/Services/MonitorTemplateService.js.map +1 -1
- package/build/dist/Server/Services/PodmanHostService.js +19 -69
- package/build/dist/Server/Services/PodmanHostService.js.map +1 -1
- package/build/dist/Server/Services/ProxmoxClusterService.js +23 -73
- package/build/dist/Server/Services/ProxmoxClusterService.js.map +1 -1
- package/build/dist/Server/Services/RumApplicationService.js +19 -70
- package/build/dist/Server/Services/RumApplicationService.js.map +1 -1
- package/build/dist/Server/Services/ScheduledMaintenanceStateTimelineService.js +20 -0
- package/build/dist/Server/Services/ScheduledMaintenanceStateTimelineService.js.map +1 -1
- package/build/dist/Server/Services/ServerlessFunctionService.js +24 -75
- package/build/dist/Server/Services/ServerlessFunctionService.js.map +1 -1
- package/build/dist/Server/Services/ServiceService.js +236 -61
- package/build/dist/Server/Services/ServiceService.js.map +1 -1
- package/build/dist/Server/Services/StatusPageMonitorRuleEngineService.js +603 -0
- package/build/dist/Server/Services/StatusPageMonitorRuleEngineService.js.map +1 -0
- package/build/dist/Server/Services/StatusPageMonitorRuleService.js +401 -0
- package/build/dist/Server/Services/StatusPageMonitorRuleService.js.map +1 -0
- package/build/dist/Server/Services/TelemetryEntityRelationshipService.js +1 -0
- package/build/dist/Server/Services/TelemetryEntityRelationshipService.js.map +1 -1
- package/build/dist/Server/Services/TelemetryEntityService.js +1 -0
- package/build/dist/Server/Services/TelemetryEntityService.js.map +1 -1
- package/build/dist/Server/Utils/Database/ProjectScopedReferenceValidator.js +105 -29
- package/build/dist/Server/Utils/Database/ProjectScopedReferenceValidator.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorResource.js +56 -32
- package/build/dist/Server/Utils/Monitor/MonitorResource.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorStatusTimeline.js +30 -3
- package/build/dist/Server/Utils/Monitor/MonitorStatusTimeline.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorStepsProjectValidator.js +110 -98
- package/build/dist/Server/Utils/Monitor/MonitorStepsProjectValidator.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorStepsReferenceExtractor.js +312 -0
- package/build/dist/Server/Utils/Monitor/MonitorStepsReferenceExtractor.js.map +1 -0
- package/build/dist/Server/Utils/SingleFlight.js +76 -0
- package/build/dist/Server/Utils/SingleFlight.js.map +1 -0
- package/build/dist/Server/Utils/StatusPage/MonitorRulePatternValidator.js +29 -0
- package/build/dist/Server/Utils/StatusPage/MonitorRulePatternValidator.js.map +1 -0
- package/build/dist/Server/Utils/Telemetry/EntityRegistry.js +78 -19
- package/build/dist/Server/Utils/Telemetry/EntityRegistry.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/ResourceHeartbeat.js +160 -0
- package/build/dist/Server/Utils/Telemetry/ResourceHeartbeat.js.map +1 -0
- package/build/dist/Server/Utils/Telemetry/Telemetry.js +279 -104
- package/build/dist/Server/Utils/Telemetry/Telemetry.js.map +1 -1
- package/build/dist/Types/Dashboard/DashboardComponentType.js +1 -0
- package/build/dist/Types/Dashboard/DashboardComponentType.js.map +1 -1
- package/build/dist/Types/Dashboard/DashboardComponents/ComponentArgument.js +1 -0
- package/build/dist/Types/Dashboard/DashboardComponents/ComponentArgument.js.map +1 -1
- package/build/dist/Types/Dashboard/DashboardComponents/DashboardNetworkMapComponent.js +2 -0
- package/build/dist/Types/Dashboard/DashboardComponents/DashboardNetworkMapComponent.js.map +1 -0
- package/build/dist/Types/Dashboard/DashboardTemplates.js +368 -0
- package/build/dist/Types/Dashboard/DashboardTemplates.js.map +1 -1
- package/build/dist/Types/Database/BigIntColumnTransformer.js +66 -0
- package/build/dist/Types/Database/BigIntColumnTransformer.js.map +1 -0
- package/build/dist/Types/Database/DatabaseProperty.js +25 -0
- package/build/dist/Types/Database/DatabaseProperty.js.map +1 -1
- package/build/dist/Types/Database/NumericColumnValue.js +97 -0
- package/build/dist/Types/Database/NumericColumnValue.js.map +1 -0
- package/build/dist/Types/Decimal.js +14 -3
- package/build/dist/Types/Decimal.js.map +1 -1
- package/build/dist/Types/Permission.js +42 -0
- package/build/dist/Types/Permission.js.map +1 -1
- package/build/dist/Types/Port.js +15 -5
- package/build/dist/Types/Port.js.map +1 -1
- package/build/dist/UI/Components/LogsViewer/useLiveLogsRefresh.js +44 -0
- package/build/dist/UI/Components/LogsViewer/useLiveLogsRefresh.js.map +1 -0
- package/build/dist/UI/Components/LogsViewer/useLogsHistogram.js +75 -0
- package/build/dist/UI/Components/LogsViewer/useLogsHistogram.js.map +1 -0
- package/build/dist/Utils/Dashboard/Components/DashboardNetworkMapComponent.js +114 -0
- package/build/dist/Utils/Dashboard/Components/DashboardNetworkMapComponent.js.map +1 -0
- package/build/dist/Utils/Dashboard/Components/Index.js +4 -0
- package/build/dist/Utils/Dashboard/Components/Index.js.map +1 -1
- package/package.json +1 -1
|
@@ -3,6 +3,7 @@ import Query from "../../Types/Database/Query";
|
|
|
3
3
|
import QueryHelper from "../../Types/Database/QueryHelper";
|
|
4
4
|
import Select from "../../Types/Database/Select";
|
|
5
5
|
import { LIMIT_PER_PROJECT } from "../../../Types/Database/LimitMax";
|
|
6
|
+
import Dictionary from "../../../Types/Dictionary";
|
|
6
7
|
import BadDataException from "../../../Types/Exception/BadDataException";
|
|
7
8
|
import ObjectID from "../../../Types/ObjectID";
|
|
8
9
|
import DatabaseBaseModel from "../../../Models/DatabaseModels/DatabaseBaseModel/DatabaseBaseModel";
|
|
@@ -20,12 +21,23 @@ import DatabaseBaseModel from "../../../Models/DatabaseModels/DatabaseBaseModel/
|
|
|
20
21
|
* FKs are ON DELETE NO ACTION, so Postgres raises 23503 and the API answers
|
|
21
22
|
* "This item cannot be deleted because Incident records still reference it."
|
|
22
23
|
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
24
|
+
* An id that matches NO row is rejected too (issue #3039). The REST API used to
|
|
25
|
+
* take any uuid for these columns; the write then either blew up deep inside
|
|
26
|
+
* Postgres as a raw 23503 (an opaque 500 rather than "you picked a bad id"), or
|
|
27
|
+
* — for ids that live in a JSON blob with no foreign key behind it, like
|
|
28
|
+
* monitorSteps — saved happily and only failed much later, inside the probe
|
|
29
|
+
* worker, as
|
|
30
|
+
* insert or update on table "MonitorStatusTimeline" violates foreign key
|
|
31
|
+
* constraint
|
|
32
|
+
* with nothing surfaced to the caller who typed the bad id.
|
|
33
|
+
*
|
|
34
|
+
* Rejecting a missing id is safe for the columns validated here because every
|
|
35
|
+
* one of them is a real foreign key and deletes in this codebase are hard
|
|
36
|
+
* deletes (DatabaseService._deleteBy), so a *stored* id can never dangle: the
|
|
37
|
+
* ON DELETE NO ACTION constraint blocks the delete while a row still points at
|
|
38
|
+
* it. A missing id therefore only ever comes from the payload being validated.
|
|
39
|
+
* Callers whose reference is NOT foreign-key backed (monitorSteps ids, say) can
|
|
40
|
+
* opt a single reference out with `mustExist: false`.
|
|
29
41
|
*/
|
|
30
42
|
|
|
31
43
|
export interface ProjectScopedReference {
|
|
@@ -34,6 +46,13 @@ export interface ProjectScopedReference {
|
|
|
34
46
|
id: ObjectID | string | undefined | null;
|
|
35
47
|
// The service that owns the referenced model, e.g. IncidentSeverityService.
|
|
36
48
|
service: DatabaseService<DatabaseBaseModel>;
|
|
49
|
+
/*
|
|
50
|
+
* Defaults to true: an id that matches no record is rejected. Set false for a
|
|
51
|
+
* reference that is allowed to dangle — one with no foreign key behind it,
|
|
52
|
+
* where refusing would block a user from saving their way out of a record
|
|
53
|
+
* that already points at something deleted.
|
|
54
|
+
*/
|
|
55
|
+
mustExist?: boolean | undefined;
|
|
37
56
|
}
|
|
38
57
|
|
|
39
58
|
interface ForeignReference {
|
|
@@ -41,6 +60,33 @@ interface ForeignReference {
|
|
|
41
60
|
name: string;
|
|
42
61
|
}
|
|
43
62
|
|
|
63
|
+
interface MissingReference {
|
|
64
|
+
modelName: string;
|
|
65
|
+
id: string;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// What the caller asked about, keyed by id inside one service's lookup.
|
|
69
|
+
interface RequestedReference {
|
|
70
|
+
modelName: string;
|
|
71
|
+
mustExist: boolean;
|
|
72
|
+
// The id exactly as the caller wrote it, so the message echoes their input.
|
|
73
|
+
id: string;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/*
|
|
77
|
+
* Ids are matched case-insensitively.
|
|
78
|
+
*
|
|
79
|
+
* ObjectID keeps whatever case it was handed (its validation regex is
|
|
80
|
+
* case-insensitive) and Postgres compares `uuid` values by parsed value, so a
|
|
81
|
+
* payload carrying an UPPERCASE uuid selects the row fine — but reads the id
|
|
82
|
+
* back lower-cased, because that is how Postgres renders `uuid`. Comparing the
|
|
83
|
+
* two verbatim would find no match and report a record that plainly exists as
|
|
84
|
+
* missing.
|
|
85
|
+
*/
|
|
86
|
+
function normalizeId(id: string): string {
|
|
87
|
+
return id.trim().toLowerCase();
|
|
88
|
+
}
|
|
89
|
+
|
|
44
90
|
/*
|
|
45
91
|
* The same reference reaches a service hook in several shapes: the id column
|
|
46
92
|
* (`incidentSeverityId`), a relation object (`incidentSeverity: { _id }`), an
|
|
@@ -92,11 +138,11 @@ export default class ProjectScopedReferenceValidator {
|
|
|
92
138
|
*/
|
|
93
139
|
const idsByService: Map<
|
|
94
140
|
DatabaseService<DatabaseBaseModel>,
|
|
95
|
-
Map<string,
|
|
141
|
+
Map<string, RequestedReference>
|
|
96
142
|
> = new Map();
|
|
97
143
|
|
|
98
144
|
for (const reference of data.references) {
|
|
99
|
-
const id: string = reference.id?.toString() || "";
|
|
145
|
+
const id: string = reference.id?.toString().trim() || "";
|
|
100
146
|
|
|
101
147
|
if (!id) {
|
|
102
148
|
continue;
|
|
@@ -106,7 +152,25 @@ export default class ProjectScopedReferenceValidator {
|
|
|
106
152
|
idsByService.set(reference.service, new Map());
|
|
107
153
|
}
|
|
108
154
|
|
|
109
|
-
idsByService.get(
|
|
155
|
+
const requested: Map<string, RequestedReference> = idsByService.get(
|
|
156
|
+
reference.service,
|
|
157
|
+
)!;
|
|
158
|
+
|
|
159
|
+
const key: string = normalizeId(id);
|
|
160
|
+
|
|
161
|
+
const existing: RequestedReference | undefined = requested.get(key);
|
|
162
|
+
|
|
163
|
+
requested.set(key, {
|
|
164
|
+
modelName: reference.modelName,
|
|
165
|
+
id: existing?.id || id,
|
|
166
|
+
/*
|
|
167
|
+
* The same id can arrive twice for one service (e.g. two monitor
|
|
168
|
+
* criteria pointing at the same status). If ANY of them requires the
|
|
169
|
+
* record to exist, the strictest wins.
|
|
170
|
+
*/
|
|
171
|
+
mustExist:
|
|
172
|
+
(existing?.mustExist ?? false) || (reference.mustExist ?? true),
|
|
173
|
+
});
|
|
110
174
|
}
|
|
111
175
|
|
|
112
176
|
if (idsByService.size === 0) {
|
|
@@ -114,21 +178,38 @@ export default class ProjectScopedReferenceValidator {
|
|
|
114
178
|
}
|
|
115
179
|
|
|
116
180
|
const foreignReferences: Array<ForeignReference> = [];
|
|
181
|
+
const missingReferences: Array<MissingReference> = [];
|
|
182
|
+
|
|
183
|
+
for (const [service, requestedById] of idsByService) {
|
|
184
|
+
/*
|
|
185
|
+
* Read the tenant and name columns off the model rather than assuming
|
|
186
|
+
* `projectId` / `name`. Not every referenced model is project scoped —
|
|
187
|
+
* a monitor criteria can name owner *users*, and User has no tenant
|
|
188
|
+
* column at all. Selecting a column a model does not have throws, and
|
|
189
|
+
* comparing an absent projectId against the write's project would
|
|
190
|
+
* report every such record as belonging to "a different project".
|
|
191
|
+
*/
|
|
192
|
+
const referencedModel: DatabaseBaseModel = service.getModel();
|
|
193
|
+
const tenantColumnName: string | null = referencedModel.getTenantColumn();
|
|
194
|
+
const hasNameColumn: boolean = referencedModel.hasColumn("name");
|
|
195
|
+
|
|
196
|
+
const select: Select<DatabaseBaseModel> = {
|
|
197
|
+
_id: true,
|
|
198
|
+
} as Select<DatabaseBaseModel>;
|
|
199
|
+
|
|
200
|
+
if (hasNameColumn) {
|
|
201
|
+
(select as Dictionary<boolean>)["name"] = true;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
if (tenantColumnName) {
|
|
205
|
+
(select as Dictionary<boolean>)[tenantColumnName] = true;
|
|
206
|
+
}
|
|
117
207
|
|
|
118
|
-
for (const [service, idToModelName] of idsByService) {
|
|
119
208
|
const records: Array<DatabaseBaseModel> = await service.findBy({
|
|
120
209
|
query: {
|
|
121
|
-
_id: QueryHelper.any(Array.from(
|
|
210
|
+
_id: QueryHelper.any(Array.from(requestedById.keys())),
|
|
122
211
|
},
|
|
123
|
-
|
|
124
|
-
* `name` and `projectId` are not on DatabaseBaseModel, but every model
|
|
125
|
-
* passed here is project scoped and named, so the cast is safe.
|
|
126
|
-
*/
|
|
127
|
-
select: {
|
|
128
|
-
_id: true,
|
|
129
|
-
name: true,
|
|
130
|
-
projectId: true,
|
|
131
|
-
} as Select<DatabaseBaseModel>,
|
|
212
|
+
select: select,
|
|
132
213
|
limit: LIMIT_PER_PROJECT,
|
|
133
214
|
skip: 0,
|
|
134
215
|
props: {
|
|
@@ -136,37 +217,84 @@ export default class ProjectScopedReferenceValidator {
|
|
|
136
217
|
},
|
|
137
218
|
});
|
|
138
219
|
|
|
220
|
+
const foundIds: Set<string> = new Set();
|
|
221
|
+
|
|
139
222
|
for (const record of records) {
|
|
223
|
+
const id: string = normalizeId(record._id?.toString() || "");
|
|
224
|
+
|
|
225
|
+
foundIds.add(id);
|
|
226
|
+
|
|
227
|
+
if (!tenantColumnName) {
|
|
228
|
+
// Global record (e.g. User) — there is no project to compare against.
|
|
229
|
+
continue;
|
|
230
|
+
}
|
|
231
|
+
|
|
140
232
|
const recordProjectId: ObjectID | undefined =
|
|
141
|
-
record.getValue<ObjectID>(
|
|
233
|
+
record.getValue<ObjectID>(tenantColumnName) || undefined;
|
|
142
234
|
|
|
143
235
|
if (recordProjectId?.toString() === data.projectId.toString()) {
|
|
144
236
|
continue;
|
|
145
237
|
}
|
|
146
238
|
|
|
147
|
-
const id: string = record._id?.toString() || "";
|
|
148
|
-
|
|
149
239
|
foreignReferences.push({
|
|
150
|
-
modelName:
|
|
151
|
-
name:
|
|
240
|
+
modelName: requestedById.get(id)?.modelName || "Record",
|
|
241
|
+
name:
|
|
242
|
+
(hasNameColumn && record.getValue<string>("name")?.toString()) ||
|
|
243
|
+
id,
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
for (const [id, requested] of requestedById) {
|
|
248
|
+
if (foundIds.has(id) || !requested.mustExist) {
|
|
249
|
+
continue;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
missingReferences.push({
|
|
253
|
+
modelName: requested.modelName,
|
|
254
|
+
// Echo the id as the caller wrote it, not the normalized key.
|
|
255
|
+
id: requested.id,
|
|
152
256
|
});
|
|
153
257
|
}
|
|
154
258
|
}
|
|
155
259
|
|
|
156
|
-
|
|
157
|
-
|
|
260
|
+
/*
|
|
261
|
+
* Both problems are reported in one go so a caller fixing a payload with
|
|
262
|
+
* several bad ids does not have to discover them one round-trip at a time.
|
|
263
|
+
* The wording of each clause is kept verbatim from when they were separate
|
|
264
|
+
* messages — support runbooks and tests match on it.
|
|
265
|
+
*/
|
|
266
|
+
const clauses: Array<string> = [];
|
|
267
|
+
|
|
268
|
+
if (foreignReferences.length > 0) {
|
|
269
|
+
const described: string = foreignReferences
|
|
270
|
+
.map((reference: ForeignReference) => {
|
|
271
|
+
return `${reference.modelName} "${reference.name}"`;
|
|
272
|
+
})
|
|
273
|
+
.join(", ");
|
|
274
|
+
|
|
275
|
+
clauses.push(
|
|
276
|
+
`references records that belong to a different project: ${described}. Please pick values from this project and try again.`,
|
|
277
|
+
);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
if (missingReferences.length > 0) {
|
|
281
|
+
const described: string = missingReferences
|
|
282
|
+
.map((reference: MissingReference) => {
|
|
283
|
+
return `${reference.modelName} "${reference.id}"`;
|
|
284
|
+
})
|
|
285
|
+
.join(", ");
|
|
286
|
+
|
|
287
|
+
clauses.push(
|
|
288
|
+
`references records that do not exist: ${described}. Please pick values that exist in this project and try again.`,
|
|
289
|
+
);
|
|
158
290
|
}
|
|
159
291
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
})
|
|
164
|
-
.join(", ");
|
|
292
|
+
if (clauses.length === 0) {
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
165
295
|
|
|
166
296
|
throw new BadDataException(
|
|
167
|
-
`This ${
|
|
168
|
-
data.subject || "request"
|
|
169
|
-
} references records that belong to a different project: ${described}. Please pick values from this project and try again.`,
|
|
297
|
+
`This ${data.subject || "request"} ${clauses.join(" It also ")}`,
|
|
170
298
|
);
|
|
171
299
|
}
|
|
172
300
|
|
|
@@ -178,10 +306,13 @@ export default class ProjectScopedReferenceValidator {
|
|
|
178
306
|
* there fails the whole ingest job for that monitor, so those callers ask
|
|
179
307
|
* this instead and fall back to their project's own default.
|
|
180
308
|
*
|
|
181
|
-
* Note the deliberate difference from validateReferencesBelongToProject:
|
|
182
|
-
*
|
|
183
|
-
*
|
|
184
|
-
*
|
|
309
|
+
* Note the deliberate difference from validateReferencesBelongToProject:
|
|
310
|
+
* that one refuses the write and names what is wrong, which is right when
|
|
311
|
+
* someone is typing an id in. This one answers a question and never throws,
|
|
312
|
+
* which is right when a worker is reading an id that was typed in long ago —
|
|
313
|
+
* an id monitorSteps is allowed to keep holding (the write guard exempts what
|
|
314
|
+
* a record already stores, since none of those ids has a foreign key behind
|
|
315
|
+
* it and there is no migration to clean them up).
|
|
185
316
|
*/
|
|
186
317
|
public static async isUsableInProject(data: {
|
|
187
318
|
projectId: ObjectID | undefined;
|
|
@@ -50,6 +50,7 @@ import CaptureSpan from "../Telemetry/CaptureSpan";
|
|
|
50
50
|
import ExceptionMessages from "../../../Types/Exception/ExceptionMessages";
|
|
51
51
|
import MonitorEvaluationSummary from "../../../Types/Monitor/MonitorEvaluationSummary";
|
|
52
52
|
import MonitorStatusService from "../../Services/MonitorStatusService";
|
|
53
|
+
import ProjectScopedReferenceValidator from "../Database/ProjectScopedReferenceValidator";
|
|
53
54
|
import { ProbeConnectionStatus } from "../../../Models/DatabaseModels/Probe";
|
|
54
55
|
import { LIMIT_PER_PROJECT } from "../../../Types/Database/LimitMax";
|
|
55
56
|
|
|
@@ -1063,48 +1064,74 @@ export default class MonitorResourceUtil {
|
|
|
1063
1064
|
*/
|
|
1064
1065
|
let revertedToDefaultStatus: boolean = true;
|
|
1065
1066
|
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1067
|
+
/*
|
|
1068
|
+
* The stored default status can be a status that no longer exists, or
|
|
1069
|
+
* one from another project: monitorSteps is a JSON blob with no
|
|
1070
|
+
* foreign key behind it, and before issue #3039 was fixed the API
|
|
1071
|
+
* accepted any uuid for it. Writing it raised the
|
|
1072
|
+
* "violates foreign key constraint" error from the probe/telemetry
|
|
1073
|
+
* worker and failed the whole ingest run for this monitor, losing the
|
|
1074
|
+
* monitor log and payload persisted below. Skip the revert and log,
|
|
1075
|
+
* the same way the criteria path in Utils/Monitor/MonitorStatusTimeline
|
|
1076
|
+
* does. New writes are rejected up front by
|
|
1077
|
+
* MonitorStepsProjectValidator.
|
|
1078
|
+
*/
|
|
1079
|
+
const isDefaultStatusUsable: boolean =
|
|
1080
|
+
await ProjectScopedReferenceValidator.isUsableInProject({
|
|
1081
|
+
projectId: monitor.projectId!,
|
|
1082
|
+
id: monitorSteps.data.defaultMonitorStatusId,
|
|
1083
|
+
service: MonitorStatusService,
|
|
1072
1084
|
});
|
|
1073
|
-
|
|
1074
|
-
|
|
1085
|
+
|
|
1086
|
+
if (!isDefaultStatusUsable) {
|
|
1087
|
+
logger.error(
|
|
1088
|
+
`${dataToProcess.monitorId.toString()} - The default monitor status ${monitorSteps.data.defaultMonitorStatusId.toString()} does not exist in project ${monitor.projectId?.toString()}. Skipping revert to default status. Please pick a default monitor status that exists in this project.`,
|
|
1075
1089
|
);
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
err instanceof BadDataException &&
|
|
1086
|
-
err.message === MONITOR_STATUS_SAME_AS_PREVIOUS_ERROR_MESSAGE
|
|
1087
|
-
) {
|
|
1090
|
+
revertedToDefaultStatus = false;
|
|
1091
|
+
} else {
|
|
1092
|
+
try {
|
|
1093
|
+
await MonitorStatusTimelineService.create({
|
|
1094
|
+
data: monitorStatusTimeline,
|
|
1095
|
+
props: {
|
|
1096
|
+
isRoot: true,
|
|
1097
|
+
},
|
|
1098
|
+
});
|
|
1088
1099
|
logger.debug(
|
|
1089
|
-
`${dataToProcess.monitorId.toString()} - Monitor status
|
|
1100
|
+
`${dataToProcess.monitorId.toString()} - Monitor status updated to default.`,
|
|
1090
1101
|
);
|
|
1091
|
-
}
|
|
1092
|
-
err instanceof ServerException &&
|
|
1093
|
-
err.message === MONITOR_STATUS_TIMELINE_LOCK_ERROR_MESSAGE
|
|
1094
|
-
) {
|
|
1102
|
+
} catch (err) {
|
|
1095
1103
|
/*
|
|
1096
|
-
*
|
|
1097
|
-
*
|
|
1098
|
-
*
|
|
1099
|
-
*
|
|
1100
|
-
*
|
|
1104
|
+
* Idempotent concurrency race (see MonitorStatusTimeline.ts): a
|
|
1105
|
+
* concurrent result already moved the monitor to this default status,
|
|
1106
|
+
* so onBeforeCreate's dedupe check throws this exact BadDataException.
|
|
1107
|
+
* Treat as a no-op at debug level rather than failing the job. Match the
|
|
1108
|
+
* exact message so unrelated BadDataExceptions still propagate.
|
|
1101
1109
|
*/
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1110
|
+
if (
|
|
1111
|
+
err instanceof BadDataException &&
|
|
1112
|
+
err.message === MONITOR_STATUS_SAME_AS_PREVIOUS_ERROR_MESSAGE
|
|
1113
|
+
) {
|
|
1114
|
+
logger.debug(
|
|
1115
|
+
`${dataToProcess.monitorId.toString()} - Monitor status already at default; skipping duplicate status timeline (concurrent race).`,
|
|
1116
|
+
);
|
|
1117
|
+
} else if (
|
|
1118
|
+
err instanceof ServerException &&
|
|
1119
|
+
err.message === MONITOR_STATUS_TIMELINE_LOCK_ERROR_MESSAGE
|
|
1120
|
+
) {
|
|
1121
|
+
/*
|
|
1122
|
+
* The per-monitor timeline mutex could not be acquired (fail-closed
|
|
1123
|
+
* create, see MonitorStatusTimelineService). Skipping is recoverable:
|
|
1124
|
+
* the next probe result re-evaluates the same criteria and recreates
|
|
1125
|
+
* the revert-to-default, while failing here would abort the whole
|
|
1126
|
+
* ingest run and lose the monitor log for this probe result.
|
|
1127
|
+
*/
|
|
1128
|
+
logger.error(
|
|
1129
|
+
`${dataToProcess.monitorId.toString()} - Could not acquire the monitor status timeline lock; skipping revert to default status. It will be retried on the next probe result.`,
|
|
1130
|
+
);
|
|
1131
|
+
revertedToDefaultStatus = false;
|
|
1132
|
+
} else {
|
|
1133
|
+
throw err;
|
|
1134
|
+
}
|
|
1108
1135
|
}
|
|
1109
1136
|
}
|
|
1110
1137
|
|
|
@@ -9,7 +9,9 @@ import MonitorStatusTimelineService, {
|
|
|
9
9
|
MONITOR_STATUS_SAME_AS_PREVIOUS_ERROR_MESSAGE,
|
|
10
10
|
MONITOR_STATUS_TIMELINE_LOCK_ERROR_MESSAGE,
|
|
11
11
|
} from "../../Services/MonitorStatusTimelineService";
|
|
12
|
+
import MonitorStatusService from "../../Services/MonitorStatusService";
|
|
12
13
|
import ServerException from "../../../Types/Exception/ServerException";
|
|
14
|
+
import ProjectScopedReferenceValidator from "../Database/ProjectScopedReferenceValidator";
|
|
13
15
|
import logger, { LogAttributes } from "../Logger";
|
|
14
16
|
import CaptureSpan from "../Telemetry/CaptureSpan";
|
|
15
17
|
import DataToProcess from "./DataToProcess";
|
|
@@ -112,6 +114,40 @@ export default class MonitorStatusTimelineUtil {
|
|
|
112
114
|
throw new BadDataException("Monitor status is not defined.");
|
|
113
115
|
}
|
|
114
116
|
|
|
117
|
+
/*
|
|
118
|
+
* The criteria can name a status that no longer exists, or that belongs to
|
|
119
|
+
* another project: monitorSteps is a JSON blob with no foreign key behind
|
|
120
|
+
* it, so deleting a monitor status does not rewrite the criteria that
|
|
121
|
+
* point at it, and before issue #3039 was fixed the API accepted any uuid
|
|
122
|
+
* here in the first place. Writing it anyway is what raised
|
|
123
|
+
* insert or update on table "MonitorStatusTimeline" violates foreign key
|
|
124
|
+
* constraint
|
|
125
|
+
* inside the probe/telemetry worker, failing the whole ingest run for this
|
|
126
|
+
* monitor - no status change, and no monitor log or payload persisted
|
|
127
|
+
* either, because those come after this. Skip the status change and log,
|
|
128
|
+
* the same way MonitorIncident and MonitorAlert handle an unusable
|
|
129
|
+
* severity. New writes are rejected up front by
|
|
130
|
+
* MonitorStepsProjectValidator, so this only ever fires for monitors saved
|
|
131
|
+
* before that guard existed.
|
|
132
|
+
*/
|
|
133
|
+
const isMonitorStatusUsable: boolean =
|
|
134
|
+
await ProjectScopedReferenceValidator.isUsableInProject({
|
|
135
|
+
projectId: input.monitor.projectId!,
|
|
136
|
+
id: monitorStatusId,
|
|
137
|
+
service: MonitorStatusService,
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
if (!isMonitorStatusUsable) {
|
|
141
|
+
logger.error(
|
|
142
|
+
`${input.monitor.id?.toString()} - Criteria "${
|
|
143
|
+
input.criteriaInstance.data?.name
|
|
144
|
+
}" changes the monitor status to ${monitorStatusId.toString()}, which does not exist in project ${input.monitor.projectId?.toString()}. Skipping the status change. Please pick a monitor status that exists in this project.`,
|
|
145
|
+
monitorLogAttributes,
|
|
146
|
+
);
|
|
147
|
+
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
|
|
115
151
|
//change monitor status.
|
|
116
152
|
|
|
117
153
|
// get last status of this monitor.
|