@oneuptime/common 10.4.14 → 10.4.16
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/AlertCustomField.ts +37 -0
- package/Models/DatabaseModels/IncidentCustomField.ts +37 -0
- package/Models/DatabaseModels/IncidentMember.ts +9 -0
- package/Models/DatabaseModels/MonitorCustomField.ts +37 -0
- package/Models/DatabaseModels/OnCallDutyPolicyCustomField.ts +37 -0
- package/Models/DatabaseModels/ScheduledMaintenanceCustomField.ts +37 -0
- package/Models/DatabaseModels/StatusPageCustomField.ts +37 -0
- package/Models/DatabaseModels/TableView.ts +40 -0
- package/Models/DatabaseModels/TeamMemberCustomField.ts +37 -0
- package/Server/API/BaseAnalyticsAPI.ts +128 -20
- package/Server/API/MetricAPI.ts +5 -138
- package/Server/API/StatusAPI.ts +103 -7
- 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 +6 -0
- package/Server/Services/AccessTokenService.ts +1 -1
- package/Server/Services/AnalyticsDatabaseService.ts +24 -4
- package/Server/Services/MetricService.ts +113 -0
- package/Server/Services/ProjectService.ts +21 -1
- 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 +74 -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/AlertCustomField.js +38 -0
- package/build/dist/Models/DatabaseModels/AlertCustomField.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/IncidentMember.js +11 -1
- package/build/dist/Models/DatabaseModels/IncidentMember.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/OnCallDutyPolicyCustomField.js +38 -0
- package/build/dist/Models/DatabaseModels/OnCallDutyPolicyCustomField.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/StatusPageCustomField.js +38 -0
- package/build/dist/Models/DatabaseModels/StatusPageCustomField.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/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/StatusAPI.js +75 -8
- package/build/dist/Server/API/StatusAPI.js.map +1 -1
- 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 +6 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.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/MetricService.js +89 -0
- package/build/dist/Server/Services/MetricService.js.map +1 -1
- package/build/dist/Server/Services/ProjectService.js +19 -1
- package/build/dist/Server/Services/ProjectService.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 +44 -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
|
*
|
|
@@ -103,6 +103,16 @@ export class ProjectService extends DatabaseService<Model> {
|
|
|
103
103
|
*/
|
|
104
104
|
private requireSsoForLoginCache: InMemoryTTLCache<boolean> =
|
|
105
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);
|
|
106
116
|
|
|
107
117
|
public constructor() {
|
|
108
118
|
super(Model);
|
|
@@ -1492,6 +1502,12 @@ export class ProjectService extends DatabaseService<Model> {
|
|
|
1492
1502
|
return { plan: null, isSubscriptionUnpaid: false };
|
|
1493
1503
|
}
|
|
1494
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
|
+
|
|
1495
1511
|
const project: Model | null = await this.findOneById({
|
|
1496
1512
|
id: projectId,
|
|
1497
1513
|
select: {
|
|
@@ -1506,10 +1522,12 @@ export class ProjectService extends DatabaseService<Model> {
|
|
|
1506
1522
|
});
|
|
1507
1523
|
|
|
1508
1524
|
if (!project) {
|
|
1525
|
+
// Don't cache "not found" — let the caller surface a fresh error.
|
|
1509
1526
|
throw new BadDataException("Project ID is invalid");
|
|
1510
1527
|
}
|
|
1511
1528
|
|
|
1512
1529
|
if (!project.paymentProviderPlanId) {
|
|
1530
|
+
// Don't cache "no plan" — the project may be mid-onboarding.
|
|
1513
1531
|
throw new BadDataException("Project does not have any plans");
|
|
1514
1532
|
}
|
|
1515
1533
|
|
|
@@ -1518,7 +1536,7 @@ export class ProjectService extends DatabaseService<Model> {
|
|
|
1518
1536
|
getAllEnvVars(),
|
|
1519
1537
|
);
|
|
1520
1538
|
|
|
1521
|
-
|
|
1539
|
+
const result: CurrentPlan = {
|
|
1522
1540
|
plan: plan,
|
|
1523
1541
|
isSubscriptionUnpaid:
|
|
1524
1542
|
!BillingService.isSubscriptionActive(
|
|
@@ -1528,6 +1546,8 @@ export class ProjectService extends DatabaseService<Model> {
|
|
|
1528
1546
|
project.paymentProviderMeteredSubscriptionStatus!,
|
|
1529
1547
|
),
|
|
1530
1548
|
};
|
|
1549
|
+
this.currentPlanCache.set(cacheKey, result, 60_000);
|
|
1550
|
+
return result;
|
|
1531
1551
|
}
|
|
1532
1552
|
|
|
1533
1553
|
@CaptureSpan()
|
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
|
}
|
|
@@ -14,6 +14,7 @@ import ExternalStatusPageMonitorResponse from "../Monitor/ExternalStatusPageMoni
|
|
|
14
14
|
import MonitorEvaluationSummary from "../Monitor/MonitorEvaluationSummary";
|
|
15
15
|
import ObjectID from "../ObjectID";
|
|
16
16
|
import Port from "../Port";
|
|
17
|
+
import ProbeAttempt from "./ProbeAttempt";
|
|
17
18
|
import RequestFailedDetails from "./RequestFailedDetails";
|
|
18
19
|
|
|
19
20
|
export default interface ProbeMonitorResponse {
|
|
@@ -42,4 +43,6 @@ export default interface ProbeMonitorResponse {
|
|
|
42
43
|
isTimeout?: boolean | undefined;
|
|
43
44
|
ingestedAt?: Date | undefined;
|
|
44
45
|
evaluationSummary?: MonitorEvaluationSummary | undefined;
|
|
46
|
+
probeAttempts?: Array<ProbeAttempt> | undefined;
|
|
47
|
+
totalAttempts?: number | undefined;
|
|
45
48
|
}
|