@oneuptime/common 11.5.13 → 11.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (156) hide show
  1. package/Models/AnalyticsModels/AnalyticsBaseModel/AnalyticsBaseModel.ts +41 -24
  2. package/Models/AnalyticsModels/MetricBaselineHourly.ts +2 -0
  3. package/Models/AnalyticsModels/MetricItemAggMV1m.ts +5 -2
  4. package/Models/AnalyticsModels/MetricItemAggMV1mByContainer.ts +5 -2
  5. package/Models/AnalyticsModels/MetricItemAggMV1mByHostV2.ts +5 -2
  6. package/Models/AnalyticsModels/MetricItemAggMV1mByK8sCluster.ts +5 -2
  7. package/Models/AnalyticsModels/MetricItemAggMV1mByService.ts +5 -2
  8. package/Models/DatabaseModels/EnterpriseLicense.ts +21 -0
  9. package/Models/DatabaseModels/EnterpriseLicenseInstance.ts +25 -0
  10. package/Models/DatabaseModels/GlobalConfig.ts +76 -0
  11. package/Server/API/EnterpriseLicenseAPI.ts +57 -0
  12. package/Server/API/GlobalConfigAPI.ts +62 -1
  13. package/Server/EnvironmentConfig.ts +22 -0
  14. package/Server/Infrastructure/Postgres/SchemaMigrations/1784659816363-AddInstanceVersionAndLatestReleaseColumns.ts +38 -0
  15. package/Server/Infrastructure/Postgres/SchemaMigrations/1784705487674-AddEnterpriseLicenseEvaluationColumns.ts +25 -0
  16. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +4 -0
  17. package/Server/Services/AnalyticsDatabaseService.ts +61 -46
  18. package/Server/Services/DatabaseService.ts +66 -1
  19. package/Server/Services/WorkflowService.ts +100 -39
  20. package/Server/Types/Workflow/Components/Schedule.ts +57 -32
  21. package/Server/Utils/AnalyticsDatabase/InsertDedupContext.ts +59 -0
  22. package/Server/Utils/AnalyticsDatabase/StatementGenerator.ts +41 -4
  23. package/Server/Utils/Express.ts +13 -0
  24. package/Server/Utils/Telemetry/TelemetryFanInWriter.ts +799 -0
  25. package/Server/Utils/Telemetry/TelemetryWriterClient.ts +263 -0
  26. package/Server/Utils/Telemetry/TelemetryWriterServer.ts +240 -0
  27. package/Server/Utils/Telemetry/TelemetryWriterShedMetrics.ts +93 -0
  28. package/Tests/Models/AnalyticsModels/AnalyticsBaseModel.test.ts +122 -0
  29. package/Tests/Server/Services/DatabaseServiceSanitizeUpdateData.test.ts +232 -0
  30. package/Tests/Server/Services/UpdateOneByIdKeepsRowId.test.ts +107 -0
  31. package/Tests/Server/Services/WorkflowService.test.ts +308 -0
  32. package/Tests/Server/Utils/AnalyticsDatabase/ClusterAwareSchema.test.ts +164 -3
  33. package/Tests/Server/Utils/AnalyticsDatabase/StatementGenerator.test.ts +99 -0
  34. package/Tests/Server/Utils/Attribution.test.ts +203 -0
  35. package/Tests/Server/Utils/Marketing/ConversionUploadProvider.test.ts +183 -0
  36. package/Tests/Server/Utils/Marketing/GoogleAds.test.ts +340 -0
  37. package/Tests/Server/Utils/Monitor/Criteria/ExceptionMonitorCriteria.test.ts +87 -0
  38. package/Tests/Server/Utils/Monitor/Criteria/LogMonitorCriteria.test.ts +165 -0
  39. package/Tests/Server/Utils/Monitor/Criteria/TraceMonitorCriteria.test.ts +86 -0
  40. package/Tests/Server/Utils/Telemetry/TelemetryFanInWriter.test.ts +1565 -0
  41. package/Tests/Server/Utils/Telemetry/TelemetryWriterClient.test.ts +313 -0
  42. package/Tests/Server/Utils/Telemetry/TelemetryWriterServer.test.ts +270 -0
  43. package/Tests/Server/Utils/Telemetry/TelemetryWriterShedMetrics.test.ts +127 -0
  44. package/Tests/Types/AI/CodeFixTaskType.test.ts +3 -0
  45. package/Tests/Types/HashCode.test.ts +41 -0
  46. package/Tests/Types/Log/LogQueryParser.test.ts +186 -0
  47. package/Tests/Types/Monitor/MonitorCriteriaMetricVariables.test.ts +228 -0
  48. package/Tests/Types/Monitor/MonitorStepDefaultTelemetryConfig.test.ts +169 -0
  49. package/Tests/Types/Monitor/MonitorStepExceptionMonitor.test.ts +289 -0
  50. package/Tests/Types/Monitor/MonitorStepLogMonitor.test.ts +231 -0
  51. package/Tests/Types/Monitor/MonitorStepMetricViewConfigUtil.test.ts +245 -0
  52. package/Tests/Types/Monitor/MonitorStepNetworkDeviceMonitor.test.ts +155 -0
  53. package/Tests/Types/Monitor/MonitorStepTelemetrySerialization.test.ts +141 -0
  54. package/Tests/Types/Monitor/MonitorStepTraceMonitor.test.ts +137 -0
  55. package/Tests/Types/Workspace/NotificationRules/NotificationRuleCondition.test.ts +311 -0
  56. package/Tests/UI/Utils/Breadcrumb/BreadcrumbTrailResolver.test.ts +461 -0
  57. package/Tests/UI/Utils/Breadcrumb/fixtures/RealBreadcrumbTrails.ts +2852 -0
  58. package/Tests/UI/Utils/Breadcrumb/fixtures/RealRoutePatterns.ts +758 -0
  59. package/Tests/UI/Utils/NotificationMethodUtil.test.ts +564 -0
  60. package/Tests/Utils/Array.test.ts +156 -0
  61. package/Tests/Utils/CronTab.test.ts +199 -0
  62. package/Tests/Utils/Dashboard/VariableInterpolation.test.ts +197 -0
  63. package/Tests/Utils/ModelImportExport.test.ts +101 -0
  64. package/Tests/Utils/Number.test.ts +179 -0
  65. package/Tests/Utils/ValueFormatter.test.ts +305 -0
  66. package/Tests/Utils/VersionUtil.test.ts +348 -0
  67. package/Tests/jest.setup.ts +1 -0
  68. package/Types/AnalyticsDatabase/TableColumn.ts +23 -0
  69. package/Types/EnterpriseLicense/EnterpriseLicenseInstanceSummary.ts +6 -0
  70. package/Types/Monitor/MonitorStep.ts +24 -4
  71. package/Types/Monitor/MonitorStepMetricViewConfigUtil.ts +105 -0
  72. package/UI/Components/EditionLabel/EditionLabel.tsx +445 -12
  73. package/UI/Components/Workflow/ArgumentsForm.tsx +68 -30
  74. package/UI/Components/Workflow/CronScheduleField.tsx +411 -0
  75. package/UI/Utils/Breadcrumb/BreadcrumbTrailResolver.ts +176 -0
  76. package/UI/Utils/NotificationMethodUtil.ts +310 -0
  77. package/Utils/CronTab.ts +823 -0
  78. package/Utils/ModelImportExport.ts +21 -6
  79. package/Utils/VersionUtil.ts +260 -0
  80. package/build/dist/Models/AnalyticsModels/AnalyticsBaseModel/AnalyticsBaseModel.js +25 -20
  81. package/build/dist/Models/AnalyticsModels/AnalyticsBaseModel/AnalyticsBaseModel.js.map +1 -1
  82. package/build/dist/Models/AnalyticsModels/MetricBaselineHourly.js +2 -0
  83. package/build/dist/Models/AnalyticsModels/MetricBaselineHourly.js.map +1 -1
  84. package/build/dist/Models/AnalyticsModels/MetricItemAggMV1m.js +5 -2
  85. package/build/dist/Models/AnalyticsModels/MetricItemAggMV1m.js.map +1 -1
  86. package/build/dist/Models/AnalyticsModels/MetricItemAggMV1mByContainer.js +5 -2
  87. package/build/dist/Models/AnalyticsModels/MetricItemAggMV1mByContainer.js.map +1 -1
  88. package/build/dist/Models/AnalyticsModels/MetricItemAggMV1mByHostV2.js +5 -2
  89. package/build/dist/Models/AnalyticsModels/MetricItemAggMV1mByHostV2.js.map +1 -1
  90. package/build/dist/Models/AnalyticsModels/MetricItemAggMV1mByK8sCluster.js +5 -2
  91. package/build/dist/Models/AnalyticsModels/MetricItemAggMV1mByK8sCluster.js.map +1 -1
  92. package/build/dist/Models/AnalyticsModels/MetricItemAggMV1mByService.js +5 -2
  93. package/build/dist/Models/AnalyticsModels/MetricItemAggMV1mByService.js.map +1 -1
  94. package/build/dist/Models/DatabaseModels/EnterpriseLicense.js +22 -0
  95. package/build/dist/Models/DatabaseModels/EnterpriseLicense.js.map +1 -1
  96. package/build/dist/Models/DatabaseModels/EnterpriseLicenseInstance.js +26 -0
  97. package/build/dist/Models/DatabaseModels/EnterpriseLicenseInstance.js.map +1 -1
  98. package/build/dist/Models/DatabaseModels/GlobalConfig.js +81 -0
  99. package/build/dist/Models/DatabaseModels/GlobalConfig.js.map +1 -1
  100. package/build/dist/Server/API/EnterpriseLicenseAPI.js +47 -0
  101. package/build/dist/Server/API/EnterpriseLicenseAPI.js.map +1 -1
  102. package/build/dist/Server/API/GlobalConfigAPI.js +52 -1
  103. package/build/dist/Server/API/GlobalConfigAPI.js.map +1 -1
  104. package/build/dist/Server/EnvironmentConfig.js +17 -0
  105. package/build/dist/Server/EnvironmentConfig.js.map +1 -1
  106. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1784659816363-AddInstanceVersionAndLatestReleaseColumns.js +18 -0
  107. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1784659816363-AddInstanceVersionAndLatestReleaseColumns.js.map +1 -0
  108. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1784705487674-AddEnterpriseLicenseEvaluationColumns.js +14 -0
  109. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1784705487674-AddEnterpriseLicenseEvaluationColumns.js.map +1 -0
  110. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +4 -0
  111. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  112. package/build/dist/Server/Services/AnalyticsDatabaseService.js +53 -17
  113. package/build/dist/Server/Services/AnalyticsDatabaseService.js.map +1 -1
  114. package/build/dist/Server/Services/DatabaseService.js +53 -1
  115. package/build/dist/Server/Services/DatabaseService.js.map +1 -1
  116. package/build/dist/Server/Services/WorkflowService.js +80 -30
  117. package/build/dist/Server/Services/WorkflowService.js.map +1 -1
  118. package/build/dist/Server/Types/Workflow/Components/Schedule.js +44 -19
  119. package/build/dist/Server/Types/Workflow/Components/Schedule.js.map +1 -1
  120. package/build/dist/Server/Utils/AnalyticsDatabase/InsertDedupContext.js +24 -0
  121. package/build/dist/Server/Utils/AnalyticsDatabase/InsertDedupContext.js.map +1 -0
  122. package/build/dist/Server/Utils/AnalyticsDatabase/StatementGenerator.js +29 -4
  123. package/build/dist/Server/Utils/AnalyticsDatabase/StatementGenerator.js.map +1 -1
  124. package/build/dist/Server/Utils/Express.js +12 -0
  125. package/build/dist/Server/Utils/Express.js.map +1 -1
  126. package/build/dist/Server/Utils/Telemetry/TelemetryFanInWriter.js +496 -0
  127. package/build/dist/Server/Utils/Telemetry/TelemetryFanInWriter.js.map +1 -0
  128. package/build/dist/Server/Utils/Telemetry/TelemetryWriterClient.js +165 -0
  129. package/build/dist/Server/Utils/Telemetry/TelemetryWriterClient.js.map +1 -0
  130. package/build/dist/Server/Utils/Telemetry/TelemetryWriterServer.js +136 -0
  131. package/build/dist/Server/Utils/Telemetry/TelemetryWriterServer.js.map +1 -0
  132. package/build/dist/Server/Utils/Telemetry/TelemetryWriterShedMetrics.js +76 -0
  133. package/build/dist/Server/Utils/Telemetry/TelemetryWriterShedMetrics.js.map +1 -0
  134. package/build/dist/Types/AnalyticsDatabase/TableColumn.js +7 -0
  135. package/build/dist/Types/AnalyticsDatabase/TableColumn.js.map +1 -1
  136. package/build/dist/Types/Monitor/MonitorStep.js +20 -4
  137. package/build/dist/Types/Monitor/MonitorStep.js.map +1 -1
  138. package/build/dist/Types/Monitor/MonitorStepMetricViewConfigUtil.js +73 -0
  139. package/build/dist/Types/Monitor/MonitorStepMetricViewConfigUtil.js.map +1 -0
  140. package/build/dist/UI/Components/EditionLabel/EditionLabel.js +261 -13
  141. package/build/dist/UI/Components/EditionLabel/EditionLabel.js.map +1 -1
  142. package/build/dist/UI/Components/Workflow/ArgumentsForm.js +30 -6
  143. package/build/dist/UI/Components/Workflow/ArgumentsForm.js.map +1 -1
  144. package/build/dist/UI/Components/Workflow/CronScheduleField.js +209 -0
  145. package/build/dist/UI/Components/Workflow/CronScheduleField.js.map +1 -0
  146. package/build/dist/UI/Utils/Breadcrumb/BreadcrumbTrailResolver.js +129 -0
  147. package/build/dist/UI/Utils/Breadcrumb/BreadcrumbTrailResolver.js.map +1 -0
  148. package/build/dist/UI/Utils/NotificationMethodUtil.js +189 -0
  149. package/build/dist/UI/Utils/NotificationMethodUtil.js.map +1 -0
  150. package/build/dist/Utils/CronTab.js +609 -0
  151. package/build/dist/Utils/CronTab.js.map +1 -0
  152. package/build/dist/Utils/ModelImportExport.js +20 -6
  153. package/build/dist/Utils/ModelImportExport.js.map +1 -1
  154. package/build/dist/Utils/VersionUtil.js +193 -0
  155. package/build/dist/Utils/VersionUtil.js.map +1 -0
  156. package/package.json +1 -1
@@ -77,6 +77,15 @@ export default class AnalyticsBaseModel extends CommonModel {
77
77
  materializedViews?: Array<MaterializedView> | undefined;
78
78
  enableMCP?: boolean | undefined;
79
79
  ttlExpression?: string | undefined; // e.g. "retentionDate DELETE"
80
+ /*
81
+ * Generic event/entity tables carry `_id` and `createdAt`. Derived
82
+ * AggregatingMergeTree targets do not represent independently-addressable
83
+ * rows: their identity is exactly their aggregation key, and an arbitrary
84
+ * creation timestamp has no stable meaning after merges. Those models set
85
+ * this false so ClickHouse can enforce that every non-key column is an
86
+ * aggregate measure. Defaults true for backward compatibility.
87
+ */
88
+ includeBaseColumns?: boolean | undefined;
80
89
  /*
81
90
  * Column that `findBy` falls back to when the caller doesn't
82
91
  * specify a `sort`. Defaults to `createdAt` (matching the legacy
@@ -100,31 +109,39 @@ export default class AnalyticsBaseModel extends CommonModel {
100
109
  this.tableEngine = data.tableEngine;
101
110
  }
102
111
 
103
- columns.push(
104
- new AnalyticsTableColumn({
105
- key: "_id",
106
- title: "ID",
107
- description: "ID of this object",
108
- required: true,
109
- type: TableColumnType.ObjectID,
110
- /*
111
- * Ids are UUIDv7 (time-ordered — see ObjectID.generateTimeOrdered),
112
- * so consecutive rows share their 48-bit timestamp prefix and ZSTD
113
- * compresses them well; random v4 ids were incompressible.
114
- */
115
- codec: { codec: "ZSTD", level: 1 },
116
- }),
117
- );
112
+ if (data.includeBaseColumns !== false) {
113
+ columns.push(
114
+ new AnalyticsTableColumn({
115
+ key: "_id",
116
+ title: "ID",
117
+ description: "ID of this object",
118
+ required: true,
119
+ type: TableColumnType.ObjectID,
120
+ /*
121
+ * Ids are UUIDv7 (time-ordered see ObjectID.generateTimeOrdered),
122
+ * so consecutive rows share their 48-bit timestamp prefix and ZSTD
123
+ * compresses them well; random v4 ids were incompressible.
124
+ */
125
+ codec: { codec: "ZSTD", level: 1 },
126
+ }),
127
+ );
118
128
 
119
- columns.push(
120
- new AnalyticsTableColumn({
121
- key: "createdAt",
122
- title: "Created",
123
- description: "Date and Time when the object was created.",
124
- required: true,
125
- type: TableColumnType.Date,
126
- }),
127
- );
129
+ columns.push(
130
+ new AnalyticsTableColumn({
131
+ key: "createdAt",
132
+ title: "Created",
133
+ description: "Date and Time when the object was created.",
134
+ required: true,
135
+ type: TableColumnType.Date,
136
+ }),
137
+ );
138
+ }
139
+
140
+ if (data.includeBaseColumns === false && !data.defaultSortColumn) {
141
+ throw new BadDataException(
142
+ "defaultSortColumn is required when includeBaseColumns is false because createdAt is not part of the table schema",
143
+ );
144
+ }
128
145
 
129
146
  if (!data.primaryKeys || data.primaryKeys.length === 0) {
130
147
  throw new BadDataException("Primary keys are required");
@@ -234,6 +234,8 @@ GROUP BY projectId, name, primaryEntityId, day, hourOfWeek`,
234
234
  tableSettings:
235
235
  "ttl_only_drop_parts = 1, non_replicated_deduplication_window = 10000",
236
236
  ttlExpression: "day + INTERVAL 90 DAY",
237
+ includeBaseColumns: false,
238
+ defaultSortColumn: "day",
237
239
  });
238
240
  }
239
241
  }
@@ -110,9 +110,10 @@ export default class MetricItemAggMV1m extends AnalyticsBaseModel {
110
110
  key: "retentionDate",
111
111
  title: "Retention Date",
112
112
  description:
113
- "Date after which this row is eligible for TTL deletion. Computed by the MV as max(retentionDate) per bucket inherits from the source MetricItemV3 rows.",
113
+ "Latest date after which this aggregate bucket is eligible for TTL deletion. Stored as SimpleAggregateFunction(max, DateTime) so partial rows keep the maximum expiry when AggregatingMergeTree merges them.",
114
114
  required: true,
115
115
  type: TableColumnType.Date,
116
+ simpleAggregateFunction: "max",
116
117
  });
117
118
 
118
119
  super({
@@ -156,7 +157,7 @@ SELECT
156
157
  countState(toFloat64(coalesce(value, sum, 0))) AS valueCountState,
157
158
  minState(toFloat64(coalesce(value, sum, 0))) AS valueMinState,
158
159
  maxState(toFloat64(coalesce(value, sum, 0))) AS valueMaxState,
159
- max(retentionDate) AS retentionDate
160
+ maxSimpleState(retentionDate) AS retentionDate
160
161
  FROM MetricItemV3
161
162
  GROUP BY projectId, name, primaryEntityId, bucketTime`,
162
163
  },
@@ -172,6 +173,8 @@ GROUP BY projectId, name, primaryEntityId, bucketTime`,
172
173
  tableSettings:
173
174
  "ttl_only_drop_parts = 1, non_replicated_deduplication_window = 10000",
174
175
  ttlExpression: "retentionDate DELETE",
176
+ includeBaseColumns: false,
177
+ defaultSortColumn: "bucketTime",
175
178
  });
176
179
  }
177
180
  }
@@ -111,9 +111,10 @@ export default class MetricItemAggMV1mByContainer extends AnalyticsBaseModel {
111
111
  key: "retentionDate",
112
112
  title: "Retention Date",
113
113
  description:
114
- "Date after which this row is eligible for TTL deletion. Computed by the MV as max(retentionDate) per bucket inherits from the source MetricItemV3 rows.",
114
+ "Latest date after which this aggregate bucket is eligible for TTL deletion. Stored as SimpleAggregateFunction(max, DateTime) so partial rows keep the maximum expiry when AggregatingMergeTree merges them.",
115
115
  required: true,
116
116
  type: TableColumnType.Date,
117
+ simpleAggregateFunction: "max",
117
118
  });
118
119
 
119
120
  super({
@@ -159,7 +160,7 @@ SELECT
159
160
  countState(toFloat64(coalesce(value, sum, 0))) AS valueCountState,
160
161
  minState(toFloat64(coalesce(value, sum, 0))) AS valueMinState,
161
162
  maxState(toFloat64(coalesce(value, sum, 0))) AS valueMaxState,
162
- max(retentionDate) AS retentionDate
163
+ maxSimpleState(retentionDate) AS retentionDate
163
164
  FROM MetricItemV3
164
165
  WHERE containerEntityKey != ''
165
166
  GROUP BY projectId, name, containerEntityKey, bucketTime`,
@@ -173,6 +174,8 @@ GROUP BY projectId, name, containerEntityKey, bucketTime`,
173
174
  tableSettings:
174
175
  "ttl_only_drop_parts = 1, non_replicated_deduplication_window = 10000",
175
176
  ttlExpression: "retentionDate DELETE",
177
+ includeBaseColumns: false,
178
+ defaultSortColumn: "bucketTime",
176
179
  });
177
180
  }
178
181
  }
@@ -114,9 +114,10 @@ export default class MetricItemAggMV1mByHostV2 extends AnalyticsBaseModel {
114
114
  key: "retentionDate",
115
115
  title: "Retention Date",
116
116
  description:
117
- "Date after which this row is eligible for TTL deletion. Computed by the MV as max(retentionDate) per bucket inherits from the source MetricItemV3 rows.",
117
+ "Latest date after which this aggregate bucket is eligible for TTL deletion. Stored as SimpleAggregateFunction(max, DateTime) so partial rows keep the maximum expiry when AggregatingMergeTree merges them.",
118
118
  required: true,
119
119
  type: TableColumnType.Date,
120
+ simpleAggregateFunction: "max",
120
121
  });
121
122
 
122
123
  super({
@@ -162,7 +163,7 @@ SELECT
162
163
  countState(toFloat64(coalesce(value, sum, 0))) AS valueCountState,
163
164
  minState(toFloat64(coalesce(value, sum, 0))) AS valueMinState,
164
165
  maxState(toFloat64(coalesce(value, sum, 0))) AS valueMaxState,
165
- max(retentionDate) AS retentionDate
166
+ maxSimpleState(retentionDate) AS retentionDate
166
167
  FROM MetricItemV3
167
168
  WHERE hostEntityKey != ''
168
169
  GROUP BY projectId, name, hostEntityKey, bucketTime`,
@@ -176,6 +177,8 @@ GROUP BY projectId, name, hostEntityKey, bucketTime`,
176
177
  tableSettings:
177
178
  "ttl_only_drop_parts = 1, non_replicated_deduplication_window = 10000",
178
179
  ttlExpression: "retentionDate DELETE",
180
+ includeBaseColumns: false,
181
+ defaultSortColumn: "bucketTime",
179
182
  });
180
183
  }
181
184
  }
@@ -110,9 +110,10 @@ export default class MetricItemAggMV1mByK8sCluster extends AnalyticsBaseModel {
110
110
  key: "retentionDate",
111
111
  title: "Retention Date",
112
112
  description:
113
- "Date after which this row is eligible for TTL deletion. Computed by the MV as max(retentionDate) per bucket inherits from the source MetricItemV3 rows.",
113
+ "Latest date after which this aggregate bucket is eligible for TTL deletion. Stored as SimpleAggregateFunction(max, DateTime) so partial rows keep the maximum expiry when AggregatingMergeTree merges them.",
114
114
  required: true,
115
115
  type: TableColumnType.Date,
116
+ simpleAggregateFunction: "max",
116
117
  });
117
118
 
118
119
  super({
@@ -160,7 +161,7 @@ SELECT
160
161
  countState(toFloat64(coalesce(value, sum, 0))) AS valueCountState,
161
162
  minState(toFloat64(coalesce(value, sum, 0))) AS valueMinState,
162
163
  maxState(toFloat64(coalesce(value, sum, 0))) AS valueMaxState,
163
- max(retentionDate) AS retentionDate
164
+ maxSimpleState(retentionDate) AS retentionDate
164
165
  FROM MetricItemV3
165
166
  WHERE k8sClusterEntityKey != ''
166
167
  GROUP BY projectId, name, k8sClusterEntityKey, bucketTime`,
@@ -174,6 +175,8 @@ GROUP BY projectId, name, k8sClusterEntityKey, bucketTime`,
174
175
  tableSettings:
175
176
  "ttl_only_drop_parts = 1, non_replicated_deduplication_window = 10000",
176
177
  ttlExpression: "retentionDate DELETE",
178
+ includeBaseColumns: false,
179
+ defaultSortColumn: "bucketTime",
177
180
  });
178
181
  }
179
182
  }
@@ -112,9 +112,10 @@ export default class MetricItemAggMV1mByService extends AnalyticsBaseModel {
112
112
  key: "retentionDate",
113
113
  title: "Retention Date",
114
114
  description:
115
- "Date after which this row is eligible for TTL deletion. Computed by the MV as max(retentionDate) per bucket inherits from the source MetricItemV3 rows.",
115
+ "Latest date after which this aggregate bucket is eligible for TTL deletion. Stored as SimpleAggregateFunction(max, DateTime) so partial rows keep the maximum expiry when AggregatingMergeTree merges them.",
116
116
  required: true,
117
117
  type: TableColumnType.Date,
118
+ simpleAggregateFunction: "max",
118
119
  });
119
120
 
120
121
  super({
@@ -160,7 +161,7 @@ SELECT
160
161
  countState(toFloat64(coalesce(value, sum, 0))) AS valueCountState,
161
162
  minState(toFloat64(coalesce(value, sum, 0))) AS valueMinState,
162
163
  maxState(toFloat64(coalesce(value, sum, 0))) AS valueMaxState,
163
- max(retentionDate) AS retentionDate
164
+ maxSimpleState(retentionDate) AS retentionDate
164
165
  FROM MetricItemV3
165
166
  WHERE serviceEntityKey != ''
166
167
  GROUP BY projectId, name, serviceEntityKey, bucketTime`,
@@ -174,6 +175,8 @@ GROUP BY projectId, name, serviceEntityKey, bucketTime`,
174
175
  tableSettings:
175
176
  "ttl_only_drop_parts = 1, non_replicated_deduplication_window = 10000",
176
177
  ttlExpression: "retentionDate DELETE",
178
+ includeBaseColumns: false,
179
+ defaultSortColumn: "bucketTime",
177
180
  });
178
181
  }
179
182
  }
@@ -102,6 +102,27 @@ export default class EnterpriseLicense extends BaseModel {
102
102
  })
103
103
  public annualContractValue?: number = undefined;
104
104
 
105
+ @ColumnAccessControl({
106
+ create: [],
107
+ read: [],
108
+ update: [],
109
+ })
110
+ @TableColumn({
111
+ required: true,
112
+ isDefaultValueColumn: true,
113
+ type: TableColumnType.Boolean,
114
+ title: "Evaluation License",
115
+ description:
116
+ "When enabled, this key is for evaluation and testing only — the customer's installation shows an evaluation notice and it is not meant for production use.",
117
+ defaultValue: false,
118
+ })
119
+ @Column({
120
+ nullable: false,
121
+ type: ColumnType.Boolean,
122
+ default: false,
123
+ })
124
+ public isEvaluationLicense?: boolean = undefined;
125
+
105
126
  @ColumnAccessControl({
106
127
  create: [],
107
128
  read: [],
@@ -188,4 +188,29 @@ export default class EnterpriseLicenseInstance extends BaseModel {
188
188
  nullable: true,
189
189
  })
190
190
  public masterAdminEmails?: Array<string> = undefined;
191
+
192
+ /*
193
+ * Named oneuptimeVersion, not version: DatabaseBaseModel already declares a
194
+ * `version` @VersionColumn() (the optimistic-locking row revision) on every
195
+ * model, so a second `version` property would collide in the TypeORM entity
196
+ * metadata.
197
+ */
198
+ @ColumnAccessControl({
199
+ create: [],
200
+ read: [],
201
+ update: [],
202
+ })
203
+ @TableColumn({
204
+ required: false,
205
+ type: TableColumnType.ShortText,
206
+ title: "OneUptime Version",
207
+ description:
208
+ "OneUptime version this instance last reported, for example 11.5.13. Lets support see which instances are running an outdated build.",
209
+ })
210
+ @Column({
211
+ nullable: true,
212
+ type: ColumnType.ShortText,
213
+ length: ColumnLength.ShortText,
214
+ })
215
+ public oneuptimeVersion?: string = undefined;
191
216
  }
@@ -824,6 +824,25 @@ export default class GlobalConfig extends GlobalConfigModel {
824
824
  })
825
825
  public enterpriseLicenseToken?: string = undefined;
826
826
 
827
+ @ColumnAccessControl({
828
+ create: [],
829
+ read: [],
830
+ update: [],
831
+ })
832
+ @TableColumn({
833
+ type: TableColumnType.Boolean,
834
+ title: "Enterprise License Is Evaluation",
835
+ description:
836
+ "True when the validated enterprise license is an evaluation/testing license. Drives the evaluation notice shown in the edition modal.",
837
+ defaultValue: false,
838
+ })
839
+ @Column({
840
+ type: ColumnType.Boolean,
841
+ nullable: true,
842
+ default: false,
843
+ })
844
+ public enterpriseLicenseIsEvaluation?: boolean = undefined;
845
+
827
846
  @ColumnAccessControl({
828
847
  create: [],
829
848
  read: [],
@@ -929,6 +948,63 @@ export default class GlobalConfig extends GlobalConfigModel {
929
948
  })
930
949
  public enterpriseLicenseExpiryReminderDays?: number = undefined;
931
950
 
951
+ /*
952
+ * The four latestRelease* columns cache the daily GitHub release check
953
+ * (Workers/Jobs/InstanceUpdate/CheckForNewVersion). They are a cache, not
954
+ * configuration: an air-gapped installation simply leaves them null and no
955
+ * update prompt is ever shown.
956
+ */
957
+ @ColumnAccessControl({
958
+ create: [],
959
+ read: [],
960
+ update: [],
961
+ })
962
+ @TableColumn({
963
+ type: TableColumnType.ShortText,
964
+ title: "Latest Release Version",
965
+ description:
966
+ "Latest OneUptime version published on GitHub, for example 11.6.0, as of the last update check. Null until the first successful check.",
967
+ })
968
+ @Column({
969
+ type: ColumnType.ShortText,
970
+ length: ColumnLength.ShortText,
971
+ nullable: true,
972
+ })
973
+ public latestReleaseVersion?: string = undefined;
974
+
975
+ @ColumnAccessControl({
976
+ create: [],
977
+ read: [],
978
+ update: [],
979
+ })
980
+ @TableColumn({
981
+ type: TableColumnType.Date,
982
+ title: "Latest Release Published At",
983
+ description: "When the latest OneUptime version was published on GitHub.",
984
+ })
985
+ @Column({
986
+ type: ColumnType.Date,
987
+ nullable: true,
988
+ })
989
+ public latestReleasePublishedAt?: Date = undefined;
990
+
991
+ @ColumnAccessControl({
992
+ create: [],
993
+ read: [],
994
+ update: [],
995
+ })
996
+ @TableColumn({
997
+ type: TableColumnType.Date,
998
+ title: "Latest Release Checked At",
999
+ description:
1000
+ "When this installation last successfully checked GitHub for a newer OneUptime release.",
1001
+ })
1002
+ @Column({
1003
+ type: ColumnType.Date,
1004
+ nullable: true,
1005
+ })
1006
+ public latestReleaseCheckedAt?: Date = undefined;
1007
+
932
1008
  @ColumnAccessControl({
933
1009
  create: [],
934
1010
  read: [],
@@ -5,6 +5,7 @@ import { JSONObject } from "../../Types/JSON";
5
5
  import PartialEntity from "../../Types/Database/PartialEntity";
6
6
  import EnterpriseLicenseInstanceSummary from "../../Types/EnterpriseLicense/EnterpriseLicenseInstanceSummary";
7
7
  import EnterpriseLicenseUsageUtil from "../../Utils/EnterpriseLicense/EnterpriseLicenseUsage";
8
+ import VersionUtil from "../../Utils/VersionUtil";
8
9
  import LIMIT_MAX from "../../Types/Database/LimitMax";
9
10
  import ObjectID from "../../Types/ObjectID";
10
11
  import PositiveNumber from "../../Types/PositiveNumber";
@@ -29,6 +30,8 @@ export interface LicenseInstanceUpsert {
29
30
  licenseId: ObjectID;
30
31
  instanceId: string;
31
32
  host: string | undefined;
33
+ // OneUptime version the instance is running. Absent on instances too old to report it.
34
+ oneuptimeVersion?: string | null | undefined;
32
35
  // Usage fields are only set on report-user-count, not on validate.
33
36
  userCount?: number | undefined;
34
37
  userEmailHashes?: Array<string> | undefined;
@@ -84,6 +87,7 @@ export default class EnterpriseLicenseAPI extends BaseAPI<
84
87
  userLimit: true,
85
88
  currentUserCount: true,
86
89
  userCountUpdatedAt: true,
90
+ isEvaluationLicense: true,
87
91
  },
88
92
  props: {
89
93
  isRoot: true,
@@ -117,12 +121,16 @@ export default class EnterpriseLicenseAPI extends BaseAPI<
117
121
  req.body["instanceId"],
118
122
  );
119
123
  const instanceHost: string = this.parseShortText(req.body["host"]);
124
+ const instanceVersion: string | null | undefined = this.parseVersion(
125
+ req.body["version"],
126
+ );
120
127
 
121
128
  if (instanceId) {
122
129
  await this.upsertLicenseInstance({
123
130
  licenseId: license.id!,
124
131
  instanceId: instanceId,
125
132
  host: instanceHost || undefined,
133
+ oneuptimeVersion: instanceVersion,
126
134
  });
127
135
  }
128
136
 
@@ -154,6 +162,7 @@ export default class EnterpriseLicenseAPI extends BaseAPI<
154
162
  userCountUpdatedAt: license.userCountUpdatedAt
155
163
  ? license.userCountUpdatedAt.toISOString()
156
164
  : null,
165
+ isEvaluationLicense: Boolean(license.isEvaluationLicense),
157
166
  instances: this.getInstanceSummaries(instances),
158
167
  token,
159
168
  });
@@ -196,6 +205,7 @@ export default class EnterpriseLicenseAPI extends BaseAPI<
196
205
  select: {
197
206
  _id: true,
198
207
  userLimit: true,
208
+ isEvaluationLicense: true,
199
209
  },
200
210
  props: {
201
211
  isRoot: true,
@@ -212,6 +222,9 @@ export default class EnterpriseLicenseAPI extends BaseAPI<
212
222
  req.body["instanceId"],
213
223
  );
214
224
  const instanceHost: string = this.parseShortText(req.body["host"]);
225
+ const instanceVersion: string | null | undefined = this.parseVersion(
226
+ req.body["version"],
227
+ );
215
228
  const userEmailHashes: Array<string> =
216
229
  EnterpriseLicenseUsageUtil.sanitizeUserEmailHashes(
217
230
  req.body["userEmailHashes"],
@@ -230,6 +243,7 @@ export default class EnterpriseLicenseAPI extends BaseAPI<
230
243
  licenseId: license.id!,
231
244
  instanceId: instanceId,
232
245
  host: instanceHost || undefined,
246
+ oneuptimeVersion: instanceVersion,
233
247
  userCount: userCount,
234
248
  userEmailHashes: userEmailHashes,
235
249
  masterAdminEmails: masterAdminEmails,
@@ -273,6 +287,7 @@ export default class EnterpriseLicenseAPI extends BaseAPI<
273
287
  userCountUpdatedAt: reportedAt.toISOString(),
274
288
  userLimit:
275
289
  typeof license.userLimit === "number" ? license.userLimit : null,
290
+ isEvaluationLicense: Boolean(license.isEvaluationLicense),
276
291
  instances: this.getInstanceSummaries(instances),
277
292
  });
278
293
  } catch (err) {
@@ -290,6 +305,31 @@ export default class EnterpriseLicenseAPI extends BaseAPI<
290
305
  return value.trim().substring(0, 100);
291
306
  }
292
307
 
308
+ /*
309
+ * report-user-count is unauthenticated, so the reported version is only
310
+ * stored when it is a version we could actually compare against a release —
311
+ * garbage never reaches the admin dashboard or the customer's modal.
312
+ *
313
+ * Three outcomes, and the callers depend on the distinction:
314
+ * undefined — the instance sent no version at all (it predates this
315
+ * field). Leave whatever is stored alone.
316
+ * null — it sent something, but not a version. Clear the stored
317
+ * value, so an instance rolled back to a build with no
318
+ * APP_VERSION stops advertising the version it used to run.
319
+ * string — a valid, canonical version to store.
320
+ */
321
+ private parseVersion(value: unknown): string | null | undefined {
322
+ if (value === undefined || value === null) {
323
+ return undefined;
324
+ }
325
+
326
+ /*
327
+ * Canonicalized rather than passed through, so the stored string is never
328
+ * something the UIs would render with a doubled "v".
329
+ */
330
+ return VersionUtil.canonicalize(value);
331
+ }
332
+
293
333
  private async findLicenseInstances(
294
334
  licenseId: ObjectID,
295
335
  ): Promise<Array<EnterpriseLicenseInstance>> {
@@ -304,6 +344,7 @@ export default class EnterpriseLicenseAPI extends BaseAPI<
304
344
  userCount: true,
305
345
  userEmailHashes: true,
306
346
  lastReportedAt: true,
347
+ oneuptimeVersion: true,
307
348
  },
308
349
  sort: {
309
350
  createdAt: SortOrder.Ascending,
@@ -331,6 +372,7 @@ export default class EnterpriseLicenseAPI extends BaseAPI<
331
372
  lastReportedAt: instance.lastReportedAt
332
373
  ? instance.lastReportedAt.toISOString()
333
374
  : null,
375
+ version: instance.oneuptimeVersion || null,
334
376
  };
335
377
  },
336
378
  );
@@ -370,6 +412,11 @@ export default class EnterpriseLicenseAPI extends BaseAPI<
370
412
  newInstance.host = data.host;
371
413
  }
372
414
 
415
+ // Null means "reported something that is not a version" — nothing to store.
416
+ if (data.oneuptimeVersion) {
417
+ newInstance.oneuptimeVersion = data.oneuptimeVersion;
418
+ }
419
+
373
420
  if (data.userCount !== undefined) {
374
421
  newInstance.userCount = data.userCount;
375
422
  }
@@ -434,6 +481,16 @@ export default class EnterpriseLicenseAPI extends BaseAPI<
434
481
  updateData.host = data.host;
435
482
  }
436
483
 
484
+ /*
485
+ * Null clears the column: an instance rebuilt onto a build with no
486
+ * APP_VERSION must stop advertising the version it used to run, because
487
+ * lastReportedAt is refreshed on the same request and would otherwise
488
+ * read as "confirmed running v11.5.13 as of today".
489
+ */
490
+ if (data.oneuptimeVersion !== undefined) {
491
+ updateData.oneuptimeVersion = data.oneuptimeVersion;
492
+ }
493
+
437
494
  if (data.userCount !== undefined) {
438
495
  updateData.userCount = data.userCount;
439
496
  }