@oneuptime/common 10.4.13 → 10.4.15
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/AnalyticsModels/AnalyticsBaseModel/AnalyticsBaseModel.ts +49 -0
- package/Models/AnalyticsModels/AuditLog.ts +8 -0
- package/Models/AnalyticsModels/ExceptionInstance.ts +1 -0
- package/Models/AnalyticsModels/Log.ts +1 -0
- package/Models/AnalyticsModels/Metric.ts +10 -0
- package/Models/AnalyticsModels/MonitorLog.ts +1 -0
- package/Models/AnalyticsModels/Profile.ts +1 -0
- package/Models/AnalyticsModels/ProfileSample.ts +1 -0
- package/Models/AnalyticsModels/Span.ts +1 -0
- package/Models/DatabaseModels/Alert.ts +2 -0
- package/Models/DatabaseModels/AlertCustomField.ts +37 -0
- package/Models/DatabaseModels/AlertFeed.ts +1 -0
- package/Models/DatabaseModels/CallLog.ts +2 -0
- package/Models/DatabaseModels/DockerHost.ts +34 -0
- package/Models/DatabaseModels/EmailLog.ts +2 -0
- package/Models/DatabaseModels/Host.ts +34 -0
- package/Models/DatabaseModels/Incident.ts +1 -0
- package/Models/DatabaseModels/IncidentCustomField.ts +37 -0
- package/Models/DatabaseModels/IncidentFeed.ts +1 -0
- package/Models/DatabaseModels/IncidentMember.ts +9 -0
- package/Models/DatabaseModels/KubernetesCluster.ts +34 -0
- package/Models/DatabaseModels/MonitorCustomField.ts +37 -0
- package/Models/DatabaseModels/MonitorFeed.ts +1 -0
- package/Models/DatabaseModels/MonitorProbe.ts +1 -0
- package/Models/DatabaseModels/OnCallDutyPolicyCustomField.ts +37 -0
- package/Models/DatabaseModels/OnCallDutyPolicyTimeLog.ts +3 -0
- package/Models/DatabaseModels/ScheduledMaintenanceCustomField.ts +37 -0
- package/Models/DatabaseModels/SmsLog.ts +2 -0
- package/Models/DatabaseModels/StatusPageCustomField.ts +37 -0
- package/Models/DatabaseModels/StatusPageSubscriber.ts +2 -0
- package/Models/DatabaseModels/TableView.ts +40 -0
- package/Models/DatabaseModels/TeamMemberCustomField.ts +37 -0
- package/Models/DatabaseModels/TelemetryException.ts +2 -0
- package/Models/DatabaseModels/UserOnCallLog.ts +1 -0
- package/Models/DatabaseModels/WorkflowLog.ts +1 -0
- package/Server/API/BaseAnalyticsAPI.ts +128 -20
- package/Server/API/MetricAPI.ts +5 -138
- package/Server/API/ProjectAPI.ts +52 -15
- package/Server/API/StatusAPI.ts +103 -7
- package/Server/EnvironmentConfig.ts +69 -0
- package/Server/Infrastructure/Postgres/DataSourceOptions.ts +26 -1
- package/Server/Infrastructure/Postgres/SchemaMigrations/1779392865146-AddAgentVersionToKubernetesDockerHost.ts +29 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1779392970424-AddPerformanceIndexes.ts +160 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1779536271671-AddFacetsToTableView.ts +13 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1779540427366-AddIsMemberNotifiedIndex.ts +34 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1779619108628-AddDropdownOptionsToCustomFields.ts +67 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +10 -0
- package/Server/Infrastructure/PostgresDatabase.ts +2 -5
- package/Server/Middleware/ProjectAuthorization.ts +31 -53
- package/Server/Middleware/UserAuthorization.ts +106 -64
- package/Server/Services/AccessTokenService.ts +1 -1
- package/Server/Services/AnalyticsDatabaseService.ts +24 -4
- package/Server/Services/ApiKeyService.ts +100 -1
- package/Server/Services/DockerHostService.ts +5 -0
- package/Server/Services/HostService.ts +6 -0
- package/Server/Services/KubernetesClusterService.ts +33 -10
- package/Server/Services/MetricService.ts +113 -0
- package/Server/Services/MonitorService.ts +10 -3
- package/Server/Services/ProjectService.ts +93 -2
- package/Server/Services/TeamMemberService.ts +36 -0
- package/Server/Services/UserService.ts +38 -0
- package/Server/Utils/Response.ts +4 -1
- package/Server/Utils/UserPermission/UserPermission.ts +17 -1
- package/Tests/Server/Services/AnalyticsDatabaseService.test.ts +2 -2
- package/Types/API/HTTPResponse.ts +16 -0
- package/Types/BaseDatabase/ListResult.ts +6 -0
- package/Types/CustomField/CustomFieldType.ts +2 -0
- package/Types/Date.ts +9 -1
- package/Types/ListData.ts +14 -0
- package/Types/Monitor/DnsMonitor/DnsMonitorResponse.ts +3 -0
- package/Types/Monitor/DnssecMonitor/DnssecMonitorResponse.ts +5 -0
- package/Types/Monitor/DomainMonitor/DomainMonitorResponse.ts +4 -0
- package/Types/Monitor/ExternalStatusPageMonitor/ExternalStatusPageMonitorResponse.ts +4 -0
- package/Types/Monitor/SnmpMonitor/SnmpMonitorResponse.ts +3 -0
- package/Types/Probe/ProbeAttempt.ts +9 -0
- package/Types/Probe/ProbeMonitorResponse.ts +3 -0
- package/UI/Components/BulkUpdate/BulkOwnerActions.tsx +504 -0
- package/UI/Components/BulkUpdate/BulkUpdateForm.tsx +64 -54
- package/UI/Components/CustomFields/CustomFieldsDetail.tsx +38 -0
- package/UI/Components/CustomFields/DropdownOptionsInput.tsx +150 -0
- package/UI/Components/Detail/Detail.tsx +78 -11
- package/UI/Components/List/List.tsx +6 -0
- package/UI/Components/ModelTable/BaseModelTable.tsx +74 -2
- package/UI/Components/ModelTable/TableView.tsx +70 -30
- package/UI/Components/Pagination/Pagination.tsx +75 -33
- package/UI/Components/Table/Table.tsx +6 -0
- package/UI/Utils/AnalyticsModelAPI/AnalyticsModelAPI.ts +1 -0
- package/build/dist/Models/AnalyticsModels/AnalyticsBaseModel/AnalyticsBaseModel.js +33 -0
- package/build/dist/Models/AnalyticsModels/AnalyticsBaseModel/AnalyticsBaseModel.js.map +1 -1
- package/build/dist/Models/AnalyticsModels/AuditLog.js +8 -0
- package/build/dist/Models/AnalyticsModels/AuditLog.js.map +1 -1
- package/build/dist/Models/AnalyticsModels/ExceptionInstance.js +1 -0
- package/build/dist/Models/AnalyticsModels/ExceptionInstance.js.map +1 -1
- package/build/dist/Models/AnalyticsModels/Log.js +1 -0
- package/build/dist/Models/AnalyticsModels/Log.js.map +1 -1
- package/build/dist/Models/AnalyticsModels/Metric.js +10 -0
- package/build/dist/Models/AnalyticsModels/Metric.js.map +1 -1
- package/build/dist/Models/AnalyticsModels/MonitorLog.js +1 -0
- package/build/dist/Models/AnalyticsModels/MonitorLog.js.map +1 -1
- package/build/dist/Models/AnalyticsModels/Profile.js +1 -0
- package/build/dist/Models/AnalyticsModels/Profile.js.map +1 -1
- package/build/dist/Models/AnalyticsModels/ProfileSample.js +1 -0
- package/build/dist/Models/AnalyticsModels/ProfileSample.js.map +1 -1
- package/build/dist/Models/AnalyticsModels/Span.js +1 -0
- package/build/dist/Models/AnalyticsModels/Span.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Alert.js +3 -1
- package/build/dist/Models/DatabaseModels/Alert.js.map +1 -1
- package/build/dist/Models/DatabaseModels/AlertCustomField.js +38 -0
- package/build/dist/Models/DatabaseModels/AlertCustomField.js.map +1 -1
- package/build/dist/Models/DatabaseModels/AlertFeed.js +2 -1
- package/build/dist/Models/DatabaseModels/AlertFeed.js.map +1 -1
- package/build/dist/Models/DatabaseModels/CallLog.js +4 -1
- package/build/dist/Models/DatabaseModels/CallLog.js.map +1 -1
- package/build/dist/Models/DatabaseModels/DockerHost.js +35 -0
- package/build/dist/Models/DatabaseModels/DockerHost.js.map +1 -1
- package/build/dist/Models/DatabaseModels/EmailLog.js +4 -1
- package/build/dist/Models/DatabaseModels/EmailLog.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Host.js +35 -0
- package/build/dist/Models/DatabaseModels/Host.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Incident.js +2 -1
- package/build/dist/Models/DatabaseModels/Incident.js.map +1 -1
- package/build/dist/Models/DatabaseModels/IncidentCustomField.js +38 -0
- package/build/dist/Models/DatabaseModels/IncidentCustomField.js.map +1 -1
- package/build/dist/Models/DatabaseModels/IncidentFeed.js +2 -1
- package/build/dist/Models/DatabaseModels/IncidentFeed.js.map +1 -1
- package/build/dist/Models/DatabaseModels/IncidentMember.js +11 -1
- package/build/dist/Models/DatabaseModels/IncidentMember.js.map +1 -1
- package/build/dist/Models/DatabaseModels/KubernetesCluster.js +35 -0
- package/build/dist/Models/DatabaseModels/KubernetesCluster.js.map +1 -1
- package/build/dist/Models/DatabaseModels/MonitorCustomField.js +38 -0
- package/build/dist/Models/DatabaseModels/MonitorCustomField.js.map +1 -1
- package/build/dist/Models/DatabaseModels/MonitorFeed.js +2 -1
- package/build/dist/Models/DatabaseModels/MonitorFeed.js.map +1 -1
- package/build/dist/Models/DatabaseModels/MonitorProbe.js +2 -0
- package/build/dist/Models/DatabaseModels/MonitorProbe.js.map +1 -1
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicyCustomField.js +38 -0
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicyCustomField.js.map +1 -1
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicyTimeLog.js +3 -0
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicyTimeLog.js.map +1 -1
- package/build/dist/Models/DatabaseModels/ScheduledMaintenanceCustomField.js +38 -0
- package/build/dist/Models/DatabaseModels/ScheduledMaintenanceCustomField.js.map +1 -1
- package/build/dist/Models/DatabaseModels/SmsLog.js +4 -1
- package/build/dist/Models/DatabaseModels/SmsLog.js.map +1 -1
- package/build/dist/Models/DatabaseModels/StatusPageCustomField.js +38 -0
- package/build/dist/Models/DatabaseModels/StatusPageCustomField.js.map +1 -1
- package/build/dist/Models/DatabaseModels/StatusPageSubscriber.js +4 -1
- package/build/dist/Models/DatabaseModels/StatusPageSubscriber.js.map +1 -1
- package/build/dist/Models/DatabaseModels/TableView.js +40 -0
- package/build/dist/Models/DatabaseModels/TableView.js.map +1 -1
- package/build/dist/Models/DatabaseModels/TeamMemberCustomField.js +38 -0
- package/build/dist/Models/DatabaseModels/TeamMemberCustomField.js.map +1 -1
- package/build/dist/Models/DatabaseModels/TelemetryException.js +3 -1
- package/build/dist/Models/DatabaseModels/TelemetryException.js.map +1 -1
- package/build/dist/Models/DatabaseModels/UserOnCallLog.js +1 -0
- package/build/dist/Models/DatabaseModels/UserOnCallLog.js.map +1 -1
- package/build/dist/Models/DatabaseModels/WorkflowLog.js +2 -1
- package/build/dist/Models/DatabaseModels/WorkflowLog.js.map +1 -1
- package/build/dist/Server/API/BaseAnalyticsAPI.js +105 -18
- package/build/dist/Server/API/BaseAnalyticsAPI.js.map +1 -1
- package/build/dist/Server/API/MetricAPI.js +5 -113
- package/build/dist/Server/API/MetricAPI.js.map +1 -1
- package/build/dist/Server/API/ProjectAPI.js +42 -14
- package/build/dist/Server/API/ProjectAPI.js.map +1 -1
- package/build/dist/Server/API/StatusAPI.js +75 -8
- package/build/dist/Server/API/StatusAPI.js.map +1 -1
- package/build/dist/Server/EnvironmentConfig.js +41 -0
- package/build/dist/Server/EnvironmentConfig.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/DataSourceOptions.js +20 -2
- package/build/dist/Server/Infrastructure/Postgres/DataSourceOptions.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1779392865146-AddAgentVersionToKubernetesDockerHost.js +16 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1779392865146-AddAgentVersionToKubernetesDockerHost.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1779392970424-AddPerformanceIndexes.js +63 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1779392970424-AddPerformanceIndexes.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1779536271671-AddFacetsToTableView.js +12 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1779536271671-AddFacetsToTableView.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1779540427366-AddIsMemberNotifiedIndex.js +27 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1779540427366-AddIsMemberNotifiedIndex.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1779619108628-AddDropdownOptionsToCustomFields.js +28 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1779619108628-AddDropdownOptionsToCustomFields.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +10 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Infrastructure/PostgresDatabase.js +2 -2
- package/build/dist/Server/Infrastructure/PostgresDatabase.js.map +1 -1
- package/build/dist/Server/Middleware/ProjectAuthorization.js +21 -39
- package/build/dist/Server/Middleware/ProjectAuthorization.js.map +1 -1
- package/build/dist/Server/Middleware/UserAuthorization.js +83 -50
- package/build/dist/Server/Middleware/UserAuthorization.js.map +1 -1
- package/build/dist/Server/Services/AccessTokenService.js +1 -1
- package/build/dist/Server/Services/AccessTokenService.js.map +1 -1
- package/build/dist/Server/Services/AnalyticsDatabaseService.js +22 -3
- package/build/dist/Server/Services/AnalyticsDatabaseService.js.map +1 -1
- package/build/dist/Server/Services/ApiKeyService.js +86 -0
- package/build/dist/Server/Services/ApiKeyService.js.map +1 -1
- package/build/dist/Server/Services/DockerHostService.js +5 -1
- package/build/dist/Server/Services/DockerHostService.js.map +1 -1
- package/build/dist/Server/Services/HostService.js +5 -1
- package/build/dist/Server/Services/HostService.js.map +1 -1
- package/build/dist/Server/Services/KubernetesClusterService.js +21 -11
- package/build/dist/Server/Services/KubernetesClusterService.js.map +1 -1
- package/build/dist/Server/Services/MetricService.js +89 -0
- package/build/dist/Server/Services/MetricService.js.map +1 -1
- package/build/dist/Server/Services/MonitorService.js +8 -3
- package/build/dist/Server/Services/MonitorService.js.map +1 -1
- package/build/dist/Server/Services/ProjectService.js +84 -2
- package/build/dist/Server/Services/ProjectService.js.map +1 -1
- package/build/dist/Server/Services/TeamMemberService.js +24 -0
- package/build/dist/Server/Services/TeamMemberService.js.map +1 -1
- package/build/dist/Server/Services/UserService.js +36 -0
- package/build/dist/Server/Services/UserService.js.map +1 -1
- package/build/dist/Server/Utils/Response.js +6 -5
- package/build/dist/Server/Utils/Response.js.map +1 -1
- package/build/dist/Server/Utils/UserPermission/UserPermission.js +13 -1
- package/build/dist/Server/Utils/UserPermission/UserPermission.js.map +1 -1
- package/build/dist/Tests/Server/Services/AnalyticsDatabaseService.test.js +2 -2
- package/build/dist/Tests/Server/Services/AnalyticsDatabaseService.test.js.map +1 -1
- package/build/dist/Types/API/HTTPResponse.js +15 -0
- package/build/dist/Types/API/HTTPResponse.js.map +1 -1
- package/build/dist/Types/CustomField/CustomFieldType.js +2 -0
- package/build/dist/Types/CustomField/CustomFieldType.js.map +1 -1
- package/build/dist/Types/Date.js +10 -1
- package/build/dist/Types/Date.js.map +1 -1
- package/build/dist/Types/ListData.js +4 -0
- package/build/dist/Types/ListData.js.map +1 -1
- package/build/dist/Types/Probe/ProbeAttempt.js +2 -0
- package/build/dist/Types/Probe/ProbeAttempt.js.map +1 -0
- package/build/dist/UI/Components/BulkUpdate/BulkOwnerActions.js +376 -0
- package/build/dist/UI/Components/BulkUpdate/BulkOwnerActions.js.map +1 -0
- package/build/dist/UI/Components/BulkUpdate/BulkUpdateForm.js +32 -25
- package/build/dist/UI/Components/BulkUpdate/BulkUpdateForm.js.map +1 -1
- package/build/dist/UI/Components/CustomFields/CustomFieldsDetail.js +32 -0
- package/build/dist/UI/Components/CustomFields/CustomFieldsDetail.js.map +1 -1
- package/build/dist/UI/Components/CustomFields/DropdownOptionsInput.js +84 -0
- package/build/dist/UI/Components/CustomFields/DropdownOptionsInput.js.map +1 -0
- package/build/dist/UI/Components/Detail/Detail.js +34 -3
- package/build/dist/UI/Components/Detail/Detail.js.map +1 -1
- package/build/dist/UI/Components/List/List.js +1 -1
- package/build/dist/UI/Components/List/List.js.map +1 -1
- package/build/dist/UI/Components/ModelTable/BaseModelTable.js +45 -5
- package/build/dist/UI/Components/ModelTable/BaseModelTable.js.map +1 -1
- package/build/dist/UI/Components/ModelTable/TableView.js +40 -19
- package/build/dist/UI/Components/ModelTable/TableView.js.map +1 -1
- package/build/dist/UI/Components/Pagination/Pagination.js +62 -36
- package/build/dist/UI/Components/Pagination/Pagination.js.map +1 -1
- package/build/dist/UI/Components/Table/Table.js +1 -1
- package/build/dist/UI/Components/Table/Table.js.map +1 -1
- package/build/dist/UI/Utils/AnalyticsModelAPI/AnalyticsModelAPI.js +1 -0
- package/build/dist/UI/Utils/AnalyticsModelAPI/AnalyticsModelAPI.js.map +1 -1
- package/package.json +1 -1
|
@@ -4,12 +4,15 @@ import Metric from "../../Models/AnalyticsModels/Metric";
|
|
|
4
4
|
import AggregateBy, {
|
|
5
5
|
AggregateUtil,
|
|
6
6
|
} from "../Types/AnalyticsDatabase/AggregateBy";
|
|
7
|
+
import DeleteBy from "../Types/AnalyticsDatabase/DeleteBy";
|
|
8
|
+
import Query from "../Types/AnalyticsDatabase/Query";
|
|
7
9
|
import { SQL, Statement } from "../Utils/AnalyticsDatabase/Statement";
|
|
8
10
|
import AggregationType, {
|
|
9
11
|
getPercentileLevel,
|
|
10
12
|
isPercentileAggregation,
|
|
11
13
|
} from "../../Types/BaseDatabase/AggregationType";
|
|
12
14
|
import AggregationInterval from "../../Types/BaseDatabase/AggregationInterval";
|
|
15
|
+
import AnalyticsTableName from "../../Types/AnalyticsDatabase/AnalyticsTableName";
|
|
13
16
|
import TableColumnType from "../../Types/AnalyticsDatabase/TableColumnType";
|
|
14
17
|
import logger, { LogAttributes } from "../Utils/Logger";
|
|
15
18
|
|
|
@@ -18,6 +21,116 @@ export class MetricService extends AnalyticsDatabaseService<Metric> {
|
|
|
18
21
|
super({ modelType: Metric, database: clickhouseDatabase });
|
|
19
22
|
}
|
|
20
23
|
|
|
24
|
+
/*
|
|
25
|
+
* Cascade deletes from `MetricItemV2` into the aggregating
|
|
26
|
+
* materialized-view target tables.
|
|
27
|
+
*
|
|
28
|
+
* `MetricItemAggMV1m` and `MetricBaselineHourly` are AggregatingMergeTree
|
|
29
|
+
* tables populated by attached MVs that only fire on inserts —
|
|
30
|
+
* `ALTER ... DELETE` against the source table does not roll back the
|
|
31
|
+
* previously-accumulated `sumState`/`countState` rows already in the MV
|
|
32
|
+
* tables. Without a matching DELETE on each MV, dashboard widgets that
|
|
33
|
+
* read from `MetricItemAggMV1m` keep counting and averaging metrics
|
|
34
|
+
* belonging to entities (incidents, alerts) the user has just deleted.
|
|
35
|
+
* See https://github.com/OneUptime/oneuptime/issues/2419.
|
|
36
|
+
*
|
|
37
|
+
* The cascade only runs when the caller scoped the delete by
|
|
38
|
+
* `serviceId`. Global time-based purges (TTL cleanup) are handled by
|
|
39
|
+
* each MV table's own `retentionDate TTL DELETE`, so cascading those
|
|
40
|
+
* would pointlessly scan the whole MV. The per-host MV
|
|
41
|
+
* (`MetricItemAggMV1mByHost`) is keyed by `hostIdentifier` rather than
|
|
42
|
+
* `serviceId`, so a service-scoped delete has nothing to remove there —
|
|
43
|
+
* skip it.
|
|
44
|
+
*/
|
|
45
|
+
public override async deleteBy(deleteBy: DeleteBy<Metric>): Promise<void> {
|
|
46
|
+
await super.deleteBy(deleteBy);
|
|
47
|
+
|
|
48
|
+
const cascadeQuery: Query<Metric> | null = this.buildMVCascadeQuery(
|
|
49
|
+
deleteBy.query,
|
|
50
|
+
);
|
|
51
|
+
if (!cascadeQuery) {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (!this.database) {
|
|
56
|
+
this.useDefaultDatabase();
|
|
57
|
+
}
|
|
58
|
+
const databaseName: string = this.database.getDatasourceOptions().database!;
|
|
59
|
+
const whereStatement: Statement =
|
|
60
|
+
this.statementGenerator.toWhereStatement(cascadeQuery);
|
|
61
|
+
|
|
62
|
+
const cascadeTargets: ReadonlyArray<AnalyticsTableName> = [
|
|
63
|
+
AnalyticsTableName.MetricItemAggMV1m,
|
|
64
|
+
AnalyticsTableName.MetricBaselineHourly,
|
|
65
|
+
];
|
|
66
|
+
|
|
67
|
+
for (const tableName of cascadeTargets) {
|
|
68
|
+
try {
|
|
69
|
+
/*
|
|
70
|
+
* Lightweight delete — see toDeleteStatement() in
|
|
71
|
+
* AnalyticsDatabaseService for the rationale (avoids the
|
|
72
|
+
* ALTER mutations queue which is capped at 1000 per table).
|
|
73
|
+
*/
|
|
74
|
+
const statement: Statement =
|
|
75
|
+
SQL`DELETE FROM ${databaseName}.${tableName} WHERE TRUE `.append(
|
|
76
|
+
whereStatement,
|
|
77
|
+
);
|
|
78
|
+
await this.execute(statement);
|
|
79
|
+
} catch (err) {
|
|
80
|
+
logger.error(
|
|
81
|
+
`Cascade delete into ${tableName} failed; dashboard widgets reading from this MV may temporarily show stale aggregated values for the deleted entity.`,
|
|
82
|
+
);
|
|
83
|
+
logger.error(err);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
private buildMVCascadeQuery(query: Query<Metric>): Query<Metric> | null {
|
|
89
|
+
if (!query || typeof query !== "object") {
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const queryRecord: Record<string, unknown> = query as unknown as Record<
|
|
94
|
+
string,
|
|
95
|
+
unknown
|
|
96
|
+
>;
|
|
97
|
+
|
|
98
|
+
/*
|
|
99
|
+
* Cascade only when the delete is scoped by serviceId. The MV sort
|
|
100
|
+
* key is (projectId, name, serviceId, bucketTime); without serviceId
|
|
101
|
+
* the DELETE would scan a huge swath of unrelated rows and risk
|
|
102
|
+
* removing data that belongs to other entities sharing the same
|
|
103
|
+
* project.
|
|
104
|
+
*/
|
|
105
|
+
if (
|
|
106
|
+
queryRecord["serviceId"] === undefined ||
|
|
107
|
+
queryRecord["serviceId"] === null
|
|
108
|
+
) {
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/*
|
|
113
|
+
* Only project the keys the MV target tables actually expose.
|
|
114
|
+
* `time`, `attributes`, `serviceType`, and the metric-payload
|
|
115
|
+
* columns don't exist on the MV schema and would either fail
|
|
116
|
+
* where-statement generation or reference a missing column.
|
|
117
|
+
*/
|
|
118
|
+
const allowedKeys: ReadonlyArray<string> = [
|
|
119
|
+
"projectId",
|
|
120
|
+
"name",
|
|
121
|
+
"serviceId",
|
|
122
|
+
];
|
|
123
|
+
const out: Record<string, unknown> = {};
|
|
124
|
+
for (const key of allowedKeys) {
|
|
125
|
+
const value: unknown = queryRecord[key];
|
|
126
|
+
if (value !== undefined) {
|
|
127
|
+
out[key] = value;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return out as unknown as Query<Metric>;
|
|
132
|
+
}
|
|
133
|
+
|
|
21
134
|
/**
|
|
22
135
|
* Histogram-aware aggregation override.
|
|
23
136
|
*
|
|
@@ -1563,9 +1563,16 @@ ${createdItem.description?.trim() || "No description provided."}
|
|
|
1563
1563
|
return;
|
|
1564
1564
|
}
|
|
1565
1565
|
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1566
|
+
/*
|
|
1567
|
+
* Each monitor appears at most once for a given probeId (composite
|
|
1568
|
+
* unique on MonitorProbe), so concurrent refreshes operate on
|
|
1569
|
+
* disjoint rows and are safe to run in parallel.
|
|
1570
|
+
*/
|
|
1571
|
+
await Promise.all(
|
|
1572
|
+
monitorProbes.map((monitorProbe: MonitorProbe) => {
|
|
1573
|
+
return this.refreshMonitorProbeStatus(monitorProbe.monitorId!);
|
|
1574
|
+
}),
|
|
1575
|
+
);
|
|
1569
1576
|
}
|
|
1570
1577
|
|
|
1571
1578
|
@CaptureSpan()
|
|
@@ -82,6 +82,7 @@ import DatabaseConfig from "../DatabaseConfig";
|
|
|
82
82
|
import DatabaseCommonInteractionProps from "../../Types/BaseDatabase/DatabaseCommonInteractionProps";
|
|
83
83
|
import PositiveNumber from "../../Types/PositiveNumber";
|
|
84
84
|
import Semaphore, { SemaphoreMutex } from "../Infrastructure/Semaphore";
|
|
85
|
+
import InMemoryTTLCache from "../Infrastructure/InMemoryTTLCache";
|
|
85
86
|
|
|
86
87
|
export interface CurrentPlan {
|
|
87
88
|
plan: PlanType | null;
|
|
@@ -89,6 +90,30 @@ export interface CurrentPlan {
|
|
|
89
90
|
}
|
|
90
91
|
|
|
91
92
|
export class ProjectService extends DatabaseService<Model> {
|
|
93
|
+
/*
|
|
94
|
+
* Suppresses repeated `lastActive` UPDATEs from a single API node. 60s of
|
|
95
|
+
* staleness on "last seen" is acceptable; an UPDATE per request is not.
|
|
96
|
+
*/
|
|
97
|
+
private lastActiveCache: InMemoryTTLCache<true> = new InMemoryTTLCache(
|
|
98
|
+
10_000,
|
|
99
|
+
);
|
|
100
|
+
/*
|
|
101
|
+
* Caches the `requireSsoForLogin` flag per project so middleware can skip a
|
|
102
|
+
* Postgres findOneById on every authenticated request.
|
|
103
|
+
*/
|
|
104
|
+
private requireSsoForLoginCache: InMemoryTTLCache<boolean> =
|
|
105
|
+
new InMemoryTTLCache(10_000);
|
|
106
|
+
/*
|
|
107
|
+
* Caches the current billing plan per project. `getCurrentPlan` is hit
|
|
108
|
+
* by `CommonAPI.getDatabaseCommonInteractionProps` on every
|
|
109
|
+
* authenticated request when billing is enabled — without caching,
|
|
110
|
+
* that's one Postgres findOneById per API call to a billable project.
|
|
111
|
+
* Plans change rarely (subscription create / cancel / change), so a
|
|
112
|
+
* 60s staleness window is acceptable.
|
|
113
|
+
*/
|
|
114
|
+
private currentPlanCache: InMemoryTTLCache<CurrentPlan> =
|
|
115
|
+
new InMemoryTTLCache(10_000);
|
|
116
|
+
|
|
92
117
|
public constructor() {
|
|
93
118
|
super(Model);
|
|
94
119
|
}
|
|
@@ -318,6 +343,14 @@ export class ProjectService extends DatabaseService<Model> {
|
|
|
318
343
|
protected override async onBeforeUpdate(
|
|
319
344
|
updateBy: UpdateBy<Model>,
|
|
320
345
|
): Promise<OnUpdate<Model>> {
|
|
346
|
+
/*
|
|
347
|
+
* Any project field could have changed; invalidate the in-process cache
|
|
348
|
+
* of the SSO flag. Cheap to refetch on the next request.
|
|
349
|
+
*/
|
|
350
|
+
if (updateBy.data.requireSsoForLogin !== undefined) {
|
|
351
|
+
this.requireSsoForLoginCache.clear();
|
|
352
|
+
}
|
|
353
|
+
|
|
321
354
|
if (IsBillingEnabled) {
|
|
322
355
|
if (
|
|
323
356
|
updateBy.data.businessDetails ||
|
|
@@ -1251,7 +1284,21 @@ export class ProjectService extends DatabaseService<Model> {
|
|
|
1251
1284
|
|
|
1252
1285
|
@CaptureSpan()
|
|
1253
1286
|
public async updateLastActive(projectId: ObjectID): Promise<void> {
|
|
1254
|
-
|
|
1287
|
+
const key: string = projectId.toString();
|
|
1288
|
+
if (this.lastActiveCache.has(key)) {
|
|
1289
|
+
return;
|
|
1290
|
+
}
|
|
1291
|
+
/*
|
|
1292
|
+
* Set BEFORE the await so a burst of concurrent requests collapses to one
|
|
1293
|
+
* UPDATE per node per 60s window instead of all firing in parallel.
|
|
1294
|
+
*/
|
|
1295
|
+
this.lastActiveCache.set(key, true, 60_000);
|
|
1296
|
+
|
|
1297
|
+
/*
|
|
1298
|
+
* Fire-and-forget — `lastActive` is a soft-real-time field and the
|
|
1299
|
+
* caller (auth middleware) shouldn't pay a Postgres round-trip for it.
|
|
1300
|
+
*/
|
|
1301
|
+
void this.updateOneById({
|
|
1255
1302
|
id: projectId,
|
|
1256
1303
|
data: {
|
|
1257
1304
|
lastActive: OneUptimeDate.getCurrentDate(),
|
|
@@ -1259,7 +1306,41 @@ export class ProjectService extends DatabaseService<Model> {
|
|
|
1259
1306
|
props: {
|
|
1260
1307
|
isRoot: true,
|
|
1261
1308
|
},
|
|
1309
|
+
}).catch((err: Error) => {
|
|
1310
|
+
// Drop the cache entry so a retry can fire within the same TTL window.
|
|
1311
|
+
this.lastActiveCache.delete(key);
|
|
1312
|
+
logger.error(
|
|
1313
|
+
`Failed to update Project.lastActive for ${key}: ${err.message}`,
|
|
1314
|
+
);
|
|
1315
|
+
});
|
|
1316
|
+
}
|
|
1317
|
+
|
|
1318
|
+
/**
|
|
1319
|
+
* Returns whether the given project requires SSO for login. Cached for
|
|
1320
|
+
* 60s in-process — middleware calls this on every authenticated request.
|
|
1321
|
+
*/
|
|
1322
|
+
@CaptureSpan()
|
|
1323
|
+
public async getRequireSsoForLogin(projectId: ObjectID): Promise<boolean> {
|
|
1324
|
+
const key: string = projectId.toString();
|
|
1325
|
+
const cached: boolean | undefined = this.requireSsoForLoginCache.get(key);
|
|
1326
|
+
if (cached !== undefined) {
|
|
1327
|
+
return cached;
|
|
1328
|
+
}
|
|
1329
|
+
|
|
1330
|
+
const project: Model | null = await this.findOneById({
|
|
1331
|
+
id: projectId,
|
|
1332
|
+
select: { requireSsoForLogin: true },
|
|
1333
|
+
props: { isRoot: true },
|
|
1262
1334
|
});
|
|
1335
|
+
|
|
1336
|
+
if (!project) {
|
|
1337
|
+
// Don't cache "not found" — let the caller decide how to handle it.
|
|
1338
|
+
throw new BadDataException("Project not found");
|
|
1339
|
+
}
|
|
1340
|
+
|
|
1341
|
+
const value: boolean = Boolean(project.requireSsoForLogin);
|
|
1342
|
+
this.requireSsoForLoginCache.set(key, value, 60_000);
|
|
1343
|
+
return value;
|
|
1263
1344
|
}
|
|
1264
1345
|
|
|
1265
1346
|
@CaptureSpan()
|
|
@@ -1421,6 +1502,12 @@ export class ProjectService extends DatabaseService<Model> {
|
|
|
1421
1502
|
return { plan: null, isSubscriptionUnpaid: false };
|
|
1422
1503
|
}
|
|
1423
1504
|
|
|
1505
|
+
const cacheKey: string = projectId.toString();
|
|
1506
|
+
const cached: CurrentPlan | undefined = this.currentPlanCache.get(cacheKey);
|
|
1507
|
+
if (cached !== undefined) {
|
|
1508
|
+
return cached;
|
|
1509
|
+
}
|
|
1510
|
+
|
|
1424
1511
|
const project: Model | null = await this.findOneById({
|
|
1425
1512
|
id: projectId,
|
|
1426
1513
|
select: {
|
|
@@ -1435,10 +1522,12 @@ export class ProjectService extends DatabaseService<Model> {
|
|
|
1435
1522
|
});
|
|
1436
1523
|
|
|
1437
1524
|
if (!project) {
|
|
1525
|
+
// Don't cache "not found" — let the caller surface a fresh error.
|
|
1438
1526
|
throw new BadDataException("Project ID is invalid");
|
|
1439
1527
|
}
|
|
1440
1528
|
|
|
1441
1529
|
if (!project.paymentProviderPlanId) {
|
|
1530
|
+
// Don't cache "no plan" — the project may be mid-onboarding.
|
|
1442
1531
|
throw new BadDataException("Project does not have any plans");
|
|
1443
1532
|
}
|
|
1444
1533
|
|
|
@@ -1447,7 +1536,7 @@ export class ProjectService extends DatabaseService<Model> {
|
|
|
1447
1536
|
getAllEnvVars(),
|
|
1448
1537
|
);
|
|
1449
1538
|
|
|
1450
|
-
|
|
1539
|
+
const result: CurrentPlan = {
|
|
1451
1540
|
plan: plan,
|
|
1452
1541
|
isSubscriptionUnpaid:
|
|
1453
1542
|
!BillingService.isSubscriptionActive(
|
|
@@ -1457,6 +1546,8 @@ export class ProjectService extends DatabaseService<Model> {
|
|
|
1457
1546
|
project.paymentProviderMeteredSubscriptionStatus!,
|
|
1458
1547
|
),
|
|
1459
1548
|
};
|
|
1549
|
+
this.currentPlanCache.set(cacheKey, result, 60_000);
|
|
1550
|
+
return result;
|
|
1460
1551
|
}
|
|
1461
1552
|
|
|
1462
1553
|
@CaptureSpan()
|
|
@@ -37,8 +37,19 @@ import User from "../../Models/DatabaseModels/User";
|
|
|
37
37
|
import OnCallDutyPolicyTimeLogService from "./OnCallDutyPolicyTimeLogService";
|
|
38
38
|
import OneUptimeDate from "../../Types/Date";
|
|
39
39
|
import ProjectSCIMService from "./ProjectSCIMService";
|
|
40
|
+
import InMemoryTTLCache from "../Infrastructure/InMemoryTTLCache";
|
|
40
41
|
|
|
41
42
|
export class TeamMemberService extends DatabaseService<TeamMember> {
|
|
43
|
+
/*
|
|
44
|
+
* Caches the user's accepted team memberships per project. Auth middleware
|
|
45
|
+
* calls this on every authenticated request to evaluate the `Owned`
|
|
46
|
+
* permission scope; without the cache it's a Postgres findBy per request.
|
|
47
|
+
* 60s of staleness on team membership changes is acceptable; we also
|
|
48
|
+
* invalidate proactively when team membership writes happen.
|
|
49
|
+
*/
|
|
50
|
+
private teamIdsForUserCache: InMemoryTTLCache<Array<string>> =
|
|
51
|
+
new InMemoryTTLCache(10_000);
|
|
52
|
+
|
|
42
53
|
public constructor() {
|
|
43
54
|
super(TeamMember);
|
|
44
55
|
}
|
|
@@ -215,6 +226,14 @@ export class TeamMemberService extends DatabaseService<TeamMember> {
|
|
|
215
226
|
userId: ObjectID,
|
|
216
227
|
projectId: ObjectID,
|
|
217
228
|
): Promise<void> {
|
|
229
|
+
/*
|
|
230
|
+
* Invalidate the in-process cache of this user's team memberships in
|
|
231
|
+
* this project — membership just changed.
|
|
232
|
+
*/
|
|
233
|
+
this.teamIdsForUserCache.delete(
|
|
234
|
+
`${userId.toString()}:${projectId.toString()}`,
|
|
235
|
+
);
|
|
236
|
+
|
|
218
237
|
/// Refresh tokens.
|
|
219
238
|
await AccessTokenService.refreshUserGlobalAccessPermission(userId);
|
|
220
239
|
|
|
@@ -545,6 +564,15 @@ export class TeamMemberService extends DatabaseService<TeamMember> {
|
|
|
545
564
|
userId: ObjectID,
|
|
546
565
|
projectId: ObjectID,
|
|
547
566
|
): Promise<Array<ObjectID>> {
|
|
567
|
+
const cacheKey: string = `${userId.toString()}:${projectId.toString()}`;
|
|
568
|
+
const cached: Array<string> | undefined =
|
|
569
|
+
this.teamIdsForUserCache.get(cacheKey);
|
|
570
|
+
if (cached !== undefined) {
|
|
571
|
+
return cached.map((id: string) => {
|
|
572
|
+
return new ObjectID(id);
|
|
573
|
+
});
|
|
574
|
+
}
|
|
575
|
+
|
|
548
576
|
const members: Array<TeamMember> = await this.findBy({
|
|
549
577
|
query: {
|
|
550
578
|
userId: userId,
|
|
@@ -570,6 +598,14 @@ export class TeamMemberService extends DatabaseService<TeamMember> {
|
|
|
570
598
|
teamIds.push(id);
|
|
571
599
|
}
|
|
572
600
|
}
|
|
601
|
+
|
|
602
|
+
this.teamIdsForUserCache.set(
|
|
603
|
+
cacheKey,
|
|
604
|
+
teamIds.map((id: ObjectID) => {
|
|
605
|
+
return id.toString();
|
|
606
|
+
}),
|
|
607
|
+
60_000,
|
|
608
|
+
);
|
|
573
609
|
return teamIds;
|
|
574
610
|
}
|
|
575
611
|
}
|
|
@@ -39,12 +39,50 @@ import BadDataException from "../../Types/Exception/BadDataException";
|
|
|
39
39
|
import Name from "../../Types/Name";
|
|
40
40
|
import CaptureSpan from "../Utils/Telemetry/CaptureSpan";
|
|
41
41
|
import Timezone from "../../Types/Timezone";
|
|
42
|
+
import InMemoryTTLCache from "../Infrastructure/InMemoryTTLCache";
|
|
42
43
|
|
|
43
44
|
export class Service extends DatabaseService<Model> {
|
|
45
|
+
/*
|
|
46
|
+
* Suppresses repeated `lastActive` UPDATEs from a single API node. 60s of
|
|
47
|
+
* staleness on "last seen" is acceptable; an UPDATE per request is not.
|
|
48
|
+
*/
|
|
49
|
+
private lastActiveCache: InMemoryTTLCache<true> = new InMemoryTTLCache(
|
|
50
|
+
10_000,
|
|
51
|
+
);
|
|
52
|
+
|
|
44
53
|
public constructor() {
|
|
45
54
|
super(Model);
|
|
46
55
|
}
|
|
47
56
|
|
|
57
|
+
/**
|
|
58
|
+
* Debounced fire-and-forget update of `User.lastActive`. The auth
|
|
59
|
+
* middleware calls this on every authenticated request; without the cache
|
|
60
|
+
* we'd issue one Postgres UPDATE per request per user.
|
|
61
|
+
*/
|
|
62
|
+
@CaptureSpan()
|
|
63
|
+
public async updateLastActive(userId: ObjectID): Promise<void> {
|
|
64
|
+
const key: string = userId.toString();
|
|
65
|
+
if (this.lastActiveCache.has(key)) {
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
/*
|
|
69
|
+
* Set BEFORE the await so a burst of concurrent requests collapses to one
|
|
70
|
+
* UPDATE per node per 60s window.
|
|
71
|
+
*/
|
|
72
|
+
this.lastActiveCache.set(key, true, 60_000);
|
|
73
|
+
|
|
74
|
+
void this.updateOneById({
|
|
75
|
+
id: userId,
|
|
76
|
+
data: { lastActive: OneUptimeDate.getCurrentDate() },
|
|
77
|
+
props: { isRoot: true },
|
|
78
|
+
}).catch((err: Error) => {
|
|
79
|
+
this.lastActiveCache.delete(key);
|
|
80
|
+
logger.error(
|
|
81
|
+
`Failed to update User.lastActive for ${key}: ${err.message}`,
|
|
82
|
+
);
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
48
86
|
@CaptureSpan()
|
|
49
87
|
public async getUserMarkdownString(data: {
|
|
50
88
|
userId: ObjectID;
|
package/Server/Utils/Response.ts
CHANGED
|
@@ -121,6 +121,7 @@ export default class Response {
|
|
|
121
121
|
list: Array<BaseModel | AnalyticsDataModel>,
|
|
122
122
|
count: PositiveNumber | number,
|
|
123
123
|
modelType: { new (): BaseModel | AnalyticsDataModel },
|
|
124
|
+
options?: { hasMore?: boolean | undefined } | undefined,
|
|
124
125
|
): void {
|
|
125
126
|
if (!(count instanceof PositiveNumber)) {
|
|
126
127
|
count = new PositiveNumber(count);
|
|
@@ -144,7 +145,7 @@ export default class Response {
|
|
|
144
145
|
);
|
|
145
146
|
}
|
|
146
147
|
|
|
147
|
-
return this.sendJsonArrayResponse(req, res, jsonArray, count);
|
|
148
|
+
return this.sendJsonArrayResponse(req, res, jsonArray, count, options);
|
|
148
149
|
}
|
|
149
150
|
|
|
150
151
|
@CaptureSpan()
|
|
@@ -194,6 +195,7 @@ export default class Response {
|
|
|
194
195
|
res: ExpressResponse,
|
|
195
196
|
list: Array<JSONObject>,
|
|
196
197
|
count: PositiveNumber,
|
|
198
|
+
options?: { hasMore?: boolean | undefined } | undefined,
|
|
197
199
|
): void {
|
|
198
200
|
const oneUptimeRequest: OneUptimeRequest = req as OneUptimeRequest;
|
|
199
201
|
const oneUptimeResponse: OneUptimeResponse = res as OneUptimeResponse;
|
|
@@ -203,6 +205,7 @@ export default class Response {
|
|
|
203
205
|
count: new PositiveNumber(0),
|
|
204
206
|
skip: new PositiveNumber(0),
|
|
205
207
|
limit: new PositiveNumber(0),
|
|
208
|
+
hasMore: options?.hasMore,
|
|
206
209
|
});
|
|
207
210
|
|
|
208
211
|
if (!list) {
|
|
@@ -10,6 +10,22 @@ import PermissionNamespace from "../../Types/Permission/PermissionNamespace";
|
|
|
10
10
|
import CaptureSpan from "../Telemetry/CaptureSpan";
|
|
11
11
|
|
|
12
12
|
export default class UserPermissionUtil {
|
|
13
|
+
/*
|
|
14
|
+
* Build the cache key for a (user, project) tenant-permission entry.
|
|
15
|
+
* The previous shape was `userId.toString() + projectId.toString()`
|
|
16
|
+
* with no separator — two distinct ObjectID pairs could in principle
|
|
17
|
+
* collide because plain concatenation has no boundary marker. Use a
|
|
18
|
+
* delimiter so the namespace is unambiguous, and route both the GET
|
|
19
|
+
* (here) and the SET (in `AccessTokenService.refreshUserTenant
|
|
20
|
+
* AccessPermission`) through this helper so they can't drift.
|
|
21
|
+
*/
|
|
22
|
+
public static buildTenantPermissionCacheKey(
|
|
23
|
+
userId: ObjectID,
|
|
24
|
+
projectId: ObjectID,
|
|
25
|
+
): string {
|
|
26
|
+
return `${userId.toString()}:${projectId.toString()}`;
|
|
27
|
+
}
|
|
28
|
+
|
|
13
29
|
@CaptureSpan()
|
|
14
30
|
public static async getUserTenantAccessPermissionFromCache(
|
|
15
31
|
userId: ObjectID,
|
|
@@ -18,7 +34,7 @@ export default class UserPermissionUtil {
|
|
|
18
34
|
const json: UserTenantAccessPermission | null =
|
|
19
35
|
(await GlobalCache.getJSONObject(
|
|
20
36
|
PermissionNamespace.ProjectPermission,
|
|
21
|
-
|
|
37
|
+
this.buildTenantPermissionCacheKey(userId, projectId),
|
|
22
38
|
)) as UserTenantAccessPermission;
|
|
23
39
|
|
|
24
40
|
if (json) {
|
|
@@ -357,8 +357,8 @@ describe("AnalyticsDatabaseService", () => {
|
|
|
357
357
|
});
|
|
358
358
|
|
|
359
359
|
expect(statement.query).toBe(
|
|
360
|
-
"
|
|
361
|
-
"
|
|
360
|
+
"DELETE FROM {p0:Identifier}.{p1:Identifier}\n" +
|
|
361
|
+
"WHERE TRUE <where-statement>",
|
|
362
362
|
);
|
|
363
363
|
expect(statement.query_params).toStrictEqual({
|
|
364
364
|
p0: "oneuptime",
|
|
@@ -69,6 +69,19 @@ export default class HTTPResponse<
|
|
|
69
69
|
this._skip = v;
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
+
/*
|
|
73
|
+
* Optional. Set by analytics list endpoints that skip the expensive
|
|
74
|
+
* COUNT(*) and instead over-fetch by one row. When defined, `count`
|
|
75
|
+
* is only a lower bound — UI should drive next/prev from `hasMore`.
|
|
76
|
+
*/
|
|
77
|
+
private _hasMore: boolean | undefined = undefined;
|
|
78
|
+
public get hasMore(): boolean | undefined {
|
|
79
|
+
return this._hasMore;
|
|
80
|
+
}
|
|
81
|
+
public set hasMore(v: boolean | undefined) {
|
|
82
|
+
this._hasMore = v;
|
|
83
|
+
}
|
|
84
|
+
|
|
72
85
|
public constructor(
|
|
73
86
|
statusCode: number,
|
|
74
87
|
data: JSONObject | Array<JSONObject>,
|
|
@@ -86,6 +99,9 @@ export default class HTTPResponse<
|
|
|
86
99
|
this.count = data["count"] as number;
|
|
87
100
|
this.skip = data["skip"] as number;
|
|
88
101
|
this.limit = data["limit"] as number;
|
|
102
|
+
if (Object.keys(data).includes("hasMore")) {
|
|
103
|
+
this.hasMore = data["hasMore"] as boolean;
|
|
104
|
+
}
|
|
89
105
|
this.jsonData = JSONFunctions.deserializeArray(data["data"] as JSONArray);
|
|
90
106
|
} else if (Array.isArray(data)) {
|
|
91
107
|
this.jsonData = JSONFunctions.deserializeArray(data as JSONArray);
|
|
@@ -9,4 +9,10 @@ export default interface ListResult<
|
|
|
9
9
|
count: number;
|
|
10
10
|
skip: number;
|
|
11
11
|
limit: number;
|
|
12
|
+
/*
|
|
13
|
+
* Optional. When set, the analytics endpoint produced this result
|
|
14
|
+
* without a full COUNT(*) — `count` is only a lower bound. Drive
|
|
15
|
+
* pagination off `hasMore` rather than computing total pages.
|
|
16
|
+
*/
|
|
17
|
+
hasMore?: boolean | undefined;
|
|
12
18
|
}
|
package/Types/Date.ts
CHANGED
|
@@ -1554,7 +1554,15 @@ export default class OneUptimeDate {
|
|
|
1554
1554
|
.toDate();
|
|
1555
1555
|
}
|
|
1556
1556
|
|
|
1557
|
-
|
|
1557
|
+
/*
|
|
1558
|
+
* moment.js throws on non-ISO formats like TLS cert dates ("Mar 31 00:00:00 2026 GMT").
|
|
1559
|
+
* Fall back to native Date which handles this format correctly.
|
|
1560
|
+
*/
|
|
1561
|
+
try {
|
|
1562
|
+
return moment(trimmedDate).toDate();
|
|
1563
|
+
} catch (_err: unknown) {
|
|
1564
|
+
return new Date(trimmedDate);
|
|
1565
|
+
}
|
|
1558
1566
|
}
|
|
1559
1567
|
|
|
1560
1568
|
if (
|
package/Types/ListData.ts
CHANGED
|
@@ -7,17 +7,27 @@ export default class ListData {
|
|
|
7
7
|
count: PositiveNumber;
|
|
8
8
|
skip: PositiveNumber;
|
|
9
9
|
limit: PositiveNumber;
|
|
10
|
+
hasMore?: boolean | undefined;
|
|
10
11
|
}) {
|
|
11
12
|
this.data = obj.data;
|
|
12
13
|
this.count = obj.count;
|
|
13
14
|
this.skip = obj.skip;
|
|
14
15
|
this.limit = obj.limit;
|
|
16
|
+
this.hasMore = obj.hasMore;
|
|
15
17
|
}
|
|
16
18
|
|
|
17
19
|
public data: JSONArray;
|
|
18
20
|
public count: PositiveNumber;
|
|
19
21
|
public skip: PositiveNumber;
|
|
20
22
|
public limit: PositiveNumber;
|
|
23
|
+
/*
|
|
24
|
+
* When set, the response was produced without a full COUNT(*) — the
|
|
25
|
+
* server fetched LIMIT+1 rows and only knows whether at least one
|
|
26
|
+
* more page exists. `count` is still emitted as a lower-bound so
|
|
27
|
+
* older clients keep rendering, but UI that wants accurate "X of Y"
|
|
28
|
+
* should switch to `hasMore`-based pagination.
|
|
29
|
+
*/
|
|
30
|
+
public hasMore?: boolean | undefined;
|
|
21
31
|
|
|
22
32
|
public toJSON(): JSONObject {
|
|
23
33
|
const json: JSONObject = {
|
|
@@ -27,6 +37,10 @@ export default class ListData {
|
|
|
27
37
|
limit: this.limit.toNumber(),
|
|
28
38
|
};
|
|
29
39
|
|
|
40
|
+
if (this.hasMore !== undefined) {
|
|
41
|
+
json["hasMore"] = this.hasMore;
|
|
42
|
+
}
|
|
43
|
+
|
|
30
44
|
return json;
|
|
31
45
|
}
|
|
32
46
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import ProbeAttempt from "../../Probe/ProbeAttempt";
|
|
1
2
|
import DnsRecordType from "./DnsRecordType";
|
|
2
3
|
|
|
3
4
|
export interface DnsRecordResponse {
|
|
@@ -13,4 +14,6 @@ export default interface DnsMonitorResponse {
|
|
|
13
14
|
records: Array<DnsRecordResponse>;
|
|
14
15
|
isDnssecValid?: boolean | undefined;
|
|
15
16
|
isTimeout?: boolean | undefined;
|
|
17
|
+
probeAttempts?: Array<ProbeAttempt> | undefined;
|
|
18
|
+
totalAttempts?: number | undefined;
|
|
16
19
|
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import ProbeAttempt from "../../Probe/ProbeAttempt";
|
|
2
|
+
|
|
1
3
|
export interface DnssecKeyRecord {
|
|
2
4
|
flags: number;
|
|
3
5
|
algorithm: number;
|
|
@@ -66,4 +68,7 @@ export default interface DnssecMonitorResponse {
|
|
|
66
68
|
|
|
67
69
|
// Overall chain validity (DNSKEY exists, DS exists, RRSIG valid, AD across resolvers)
|
|
68
70
|
isChainValid: boolean;
|
|
71
|
+
|
|
72
|
+
probeAttempts?: Array<ProbeAttempt> | undefined;
|
|
73
|
+
totalAttempts?: number | undefined;
|
|
69
74
|
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import ProbeAttempt from "../../Probe/ProbeAttempt";
|
|
2
|
+
|
|
1
3
|
export default interface DomainMonitorResponse {
|
|
2
4
|
isOnline: boolean;
|
|
3
5
|
responseTimeInMs: number;
|
|
@@ -12,4 +14,6 @@ export default interface DomainMonitorResponse {
|
|
|
12
14
|
dnssec?: string | undefined;
|
|
13
15
|
domainStatus?: Array<string> | undefined;
|
|
14
16
|
isTimeout?: boolean | undefined;
|
|
17
|
+
probeAttempts?: Array<ProbeAttempt> | undefined;
|
|
18
|
+
totalAttempts?: number | undefined;
|
|
15
19
|
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import ProbeAttempt from "../../Probe/ProbeAttempt";
|
|
2
|
+
|
|
1
3
|
export interface ExternalStatusPageComponentStatus {
|
|
2
4
|
name: string;
|
|
3
5
|
status: string;
|
|
@@ -13,4 +15,6 @@ export default interface ExternalStatusPageMonitorResponse {
|
|
|
13
15
|
failureCause: string;
|
|
14
16
|
rawBody?: string | undefined;
|
|
15
17
|
isTimeout?: boolean | undefined;
|
|
18
|
+
probeAttempts?: Array<ProbeAttempt> | undefined;
|
|
19
|
+
totalAttempts?: number | undefined;
|
|
16
20
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import ProbeAttempt from "../../Probe/ProbeAttempt";
|
|
1
2
|
import SnmpDataType from "./SnmpDataType";
|
|
2
3
|
|
|
3
4
|
export interface SnmpOidResponse {
|
|
@@ -13,4 +14,6 @@ export default interface SnmpMonitorResponse {
|
|
|
13
14
|
failureCause: string;
|
|
14
15
|
oidResponses: Array<SnmpOidResponse>;
|
|
15
16
|
isTimeout?: boolean | undefined;
|
|
17
|
+
probeAttempts?: Array<ProbeAttempt> | undefined;
|
|
18
|
+
totalAttempts?: number | undefined;
|
|
16
19
|
}
|