@oneuptime/common 12.0.19 → 12.0.20

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 (66) hide show
  1. package/Server/API/EnterpriseLicenseAPI.ts +94 -19
  2. package/Server/EnvironmentConfig.ts +17 -0
  3. package/Server/Services/ProjectService.ts +104 -0
  4. package/Server/Utils/Response.ts +2 -2
  5. package/Server/Utils/StartServer.ts +2 -2
  6. package/Server/Views/Partials/AnalyticsConsent.ejs +533 -0
  7. package/Tests/App/Dashboard/AlertEpisodeViewFields.test.tsx +465 -0
  8. package/Tests/App/Dashboard/DashboardChartWidgetZoom.test.tsx +302 -0
  9. package/Tests/App/Dashboard/DiscoveryScanWizardValidation.test.tsx +408 -0
  10. package/Tests/App/Dashboard/IncidentEpisodeViewFields.test.tsx +474 -0
  11. package/Tests/App/Dashboard/MetricSeriesInvestigateMenu.test.tsx +368 -0
  12. package/Tests/Server/API/EnterpriseLicenseReportUserCount.test.ts +763 -0
  13. package/Tests/Server/Services/ProjectServiceChangePlan.test.ts +138 -0
  14. package/Tests/Server/Services/ProjectServiceCreateSubscriptionStarted.test.ts +516 -0
  15. package/Tests/Server/Utils/ResponseRenderAnalytics.test.ts +53 -0
  16. package/Tests/Server/Views/AnalyticsConsentPartial.test.ts +241 -0
  17. package/Tests/UI/Components/Charts/ChartTooltipSorted.test.tsx +129 -0
  18. package/Tests/UI/Components/Charts/TooltipEntries.test.ts +155 -0
  19. package/Tests/UI/Components/Detail/DetailFieldErrors.test.tsx +347 -0
  20. package/Tests/UI/Components/Forms/BasicFormStepValidation.test.tsx +392 -0
  21. package/Tests/UI/Components/Forms/ValidationFormSteps.test.ts +549 -0
  22. package/Tests/Utils/EnterpriseLicenseSync.test.ts +562 -0
  23. package/Types/Analytics/RevenueEvent.ts +1 -0
  24. package/Types/Marketing/MarketingEvent.ts +49 -6
  25. package/UI/Components/Charts/Area/AreaChart.tsx +8 -1
  26. package/UI/Components/Charts/Bar/BarChart.tsx +8 -1
  27. package/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.tsx +19 -4
  28. package/UI/Components/Charts/ChartLibrary/BarChart/BarChart.tsx +20 -2
  29. package/UI/Components/Charts/ChartLibrary/LineChart/LineChart.tsx +19 -4
  30. package/UI/Components/Charts/ChartLibrary/Utils/TooltipEntries.ts +105 -0
  31. package/UI/Components/Charts/Line/LineChart.tsx +8 -1
  32. package/UI/Components/Detail/Detail.tsx +18 -1
  33. package/Utils/EnterpriseLicense/EnterpriseLicenseSync.ts +244 -0
  34. package/build/dist/Server/API/EnterpriseLicenseAPI.js +66 -12
  35. package/build/dist/Server/API/EnterpriseLicenseAPI.js.map +1 -1
  36. package/build/dist/Server/EnvironmentConfig.js +15 -0
  37. package/build/dist/Server/EnvironmentConfig.js.map +1 -1
  38. package/build/dist/Server/Services/ProjectService.js +88 -0
  39. package/build/dist/Server/Services/ProjectService.js.map +1 -1
  40. package/build/dist/Server/Utils/Response.js +2 -2
  41. package/build/dist/Server/Utils/Response.js.map +1 -1
  42. package/build/dist/Server/Utils/StartServer.js +2 -2
  43. package/build/dist/Server/Utils/StartServer.js.map +1 -1
  44. package/build/dist/Types/Analytics/RevenueEvent.js +1 -0
  45. package/build/dist/Types/Analytics/RevenueEvent.js.map +1 -1
  46. package/build/dist/Types/Marketing/MarketingEvent.js +40 -1
  47. package/build/dist/Types/Marketing/MarketingEvent.js.map +1 -1
  48. package/build/dist/UI/Components/Charts/Area/AreaChart.js +9 -1
  49. package/build/dist/UI/Components/Charts/Area/AreaChart.js.map +1 -1
  50. package/build/dist/UI/Components/Charts/Bar/BarChart.js +9 -1
  51. package/build/dist/UI/Components/Charts/Bar/BarChart.js.map +1 -1
  52. package/build/dist/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.js +22 -13
  53. package/build/dist/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.js.map +1 -1
  54. package/build/dist/UI/Components/Charts/ChartLibrary/BarChart/BarChart.js +27 -15
  55. package/build/dist/UI/Components/Charts/ChartLibrary/BarChart/BarChart.js.map +1 -1
  56. package/build/dist/UI/Components/Charts/ChartLibrary/LineChart/LineChart.js +27 -18
  57. package/build/dist/UI/Components/Charts/ChartLibrary/LineChart/LineChart.js.map +1 -1
  58. package/build/dist/UI/Components/Charts/ChartLibrary/Utils/TooltipEntries.js +71 -0
  59. package/build/dist/UI/Components/Charts/ChartLibrary/Utils/TooltipEntries.js.map +1 -0
  60. package/build/dist/UI/Components/Charts/Line/LineChart.js +9 -1
  61. package/build/dist/UI/Components/Charts/Line/LineChart.js.map +1 -1
  62. package/build/dist/UI/Components/Detail/Detail.js +17 -1
  63. package/build/dist/UI/Components/Detail/Detail.js.map +1 -1
  64. package/build/dist/Utils/EnterpriseLicense/EnterpriseLicenseSync.js +174 -0
  65. package/build/dist/Utils/EnterpriseLicense/EnterpriseLicenseSync.js.map +1 -0
  66. package/package.json +1 -1
@@ -0,0 +1,174 @@
1
+ /*
2
+ * Maps a license-server response (from /enterprise-license/validate or
3
+ * /enterprise-license/report-user-count) onto the GlobalConfig columns a
4
+ * self-hosted installation mirrors locally.
5
+ *
6
+ * Every field is three-state, and the distinction is the whole contract:
7
+ *
8
+ * key absent - the license server did not speak about this field. That is
9
+ * what an oneuptime.com older than this build looks like, so
10
+ * the stored value is left exactly as it is. Collapsing this
11
+ * to null is how a daily report would wipe a perfectly good
12
+ * license expiry and take the installation down.
13
+ * null - the server explicitly says "there is no value". Written as
14
+ * null, so clearing a seat limit on oneuptime.com actually
15
+ * clears it on the customer's installation.
16
+ * a value - validated, then written.
17
+ *
18
+ * Anything that is neither absent, null, nor a usable value is treated as
19
+ * absent and reported as a warning: a malformed response must never be able
20
+ * to corrupt license state that was correct a moment ago.
21
+ */
22
+ export default class EnterpriseLicenseSyncUtil {
23
+ /*
24
+ * The license key is deliberately NOT synced. The installation authenticates
25
+ * with the key it already holds, and a response must never be able to swap
26
+ * it for another one.
27
+ */
28
+ static getGlobalConfigUpdateFromLicenseResponse(data) {
29
+ const payload = data.payload || {};
30
+ const updateData = {};
31
+ const warnings = [];
32
+ /*
33
+ * The seat limit. This is the field customers change on oneuptime.com and
34
+ * then wait for: without it here, buying seats never reaches the
35
+ * installation that is enforcing the old number.
36
+ */
37
+ if (this.isPresent(payload, "userLimit")) {
38
+ const rawUserLimit = payload["userLimit"];
39
+ if (rawUserLimit === null) {
40
+ updateData.enterpriseLicenseUserLimit = null;
41
+ }
42
+ else if (this.isNonNegativeInteger(rawUserLimit)) {
43
+ updateData.enterpriseLicenseUserLimit = rawUserLimit;
44
+ }
45
+ else {
46
+ warnings.push(`Ignored userLimit "${String(rawUserLimit)}" from the license server: it is not a non-negative whole number.`);
47
+ }
48
+ }
49
+ /*
50
+ * The count and its timestamp move together: a count with a stamp from a
51
+ * previous report would read as "confirmed today", which is the exact
52
+ * lie this whole sync exists to stop telling.
53
+ */
54
+ if (this.isPresent(payload, "currentUserCount")) {
55
+ const rawUserCount = payload["currentUserCount"];
56
+ if (this.isNonNegativeInteger(rawUserCount)) {
57
+ updateData.enterpriseLicenseCurrentUserCount = rawUserCount;
58
+ updateData.enterpriseLicenseUserCountUpdatedAt =
59
+ this.parseDate(payload["userCountUpdatedAt"]) || data.reportedAt;
60
+ }
61
+ else if (rawUserCount === null) {
62
+ /*
63
+ * The server has never had a count for this license. Nothing useful to
64
+ * store, and blanking the last known count would be worse than keeping
65
+ * it, so leave both columns alone.
66
+ */
67
+ warnings.push("The license server reported no user count for this license. Keeping the previously stored usage.");
68
+ }
69
+ else {
70
+ warnings.push(`Ignored currentUserCount "${String(rawUserCount)}" from the license server: it is not a non-negative whole number. Keeping the previously stored usage.`);
71
+ }
72
+ }
73
+ /*
74
+ * Expiry drives licenseValid on the installation, so a renewal that never
75
+ * arrives here shows up as a license that expires on its original date
76
+ * however much the customer paid. Null is treated as absent on purpose: a
77
+ * report is not the place to strip an installation of its expiry.
78
+ */
79
+ if (this.isPresent(payload, "expiresAt") && payload["expiresAt"] !== null) {
80
+ const expiresAt = this.parseDate(payload["expiresAt"]);
81
+ if (expiresAt) {
82
+ updateData.enterpriseLicenseExpiresAt = expiresAt;
83
+ }
84
+ else {
85
+ warnings.push(`Ignored expiresAt "${String(payload["expiresAt"])}" from the license server: it is not a date.`);
86
+ }
87
+ }
88
+ if (this.isPresent(payload, "isEvaluationLicense")) {
89
+ const rawIsEvaluation = payload["isEvaluationLicense"];
90
+ if (typeof rawIsEvaluation === "boolean") {
91
+ updateData.enterpriseLicenseIsEvaluation = rawIsEvaluation;
92
+ }
93
+ else {
94
+ warnings.push(`Ignored isEvaluationLicense "${String(rawIsEvaluation)}" from the license server: it is not a boolean.`);
95
+ }
96
+ }
97
+ if (this.isPresent(payload, "companyName")) {
98
+ const companyName = this.parseNonEmptyString(payload["companyName"]);
99
+ /*
100
+ * An empty company name is what the server sends for a license with none
101
+ * set, and overwriting a good name with "" would be a visible regression
102
+ * in the modal. Only a real name is worth writing.
103
+ */
104
+ if (companyName) {
105
+ updateData.enterpriseCompanyName = companyName;
106
+ }
107
+ }
108
+ /*
109
+ * The token is opaque here - it is signed with the license server's secret
110
+ * and this side only checks that it exists. It is never cleared: a license
111
+ * that has expired is legitimately issued no token, and clearing it on the
112
+ * way past would compound an expiry the customer may be about to renew.
113
+ */
114
+ if (this.isPresent(payload, "token")) {
115
+ const token = this.parseNonEmptyString(payload["token"]);
116
+ if (token) {
117
+ updateData.enterpriseLicenseToken = token;
118
+ }
119
+ }
120
+ if (this.isPresent(payload, "instances")) {
121
+ const rawInstances = payload["instances"];
122
+ if (Array.isArray(rawInstances)) {
123
+ updateData.enterpriseLicenseInstances =
124
+ rawInstances;
125
+ }
126
+ else {
127
+ warnings.push("Ignored the instance list from the license server: it is not an array.");
128
+ }
129
+ }
130
+ return {
131
+ updateData: updateData,
132
+ warnings: warnings,
133
+ };
134
+ }
135
+ /*
136
+ * Present means the key is on the object, even when it carries null or
137
+ * undefined. `"x" in payload` rather than a truthiness or undefined check,
138
+ * because "the server said null" and "the server said nothing" have to stay
139
+ * distinguishable all the way down.
140
+ */
141
+ static isPresent(payload, key) {
142
+ if (!Object.prototype.hasOwnProperty.call(payload, key)) {
143
+ return false;
144
+ }
145
+ // An explicit undefined is indistinguishable from silence. Treat it as one.
146
+ return payload[key] !== undefined;
147
+ }
148
+ static isNonNegativeInteger(value) {
149
+ return (typeof value === "number" &&
150
+ Number.isFinite(value) &&
151
+ Number.isInteger(value) &&
152
+ value >= 0);
153
+ }
154
+ static parseNonEmptyString(value) {
155
+ if (typeof value !== "string") {
156
+ return "";
157
+ }
158
+ return value.trim();
159
+ }
160
+ static parseDate(value) {
161
+ if (value instanceof Date) {
162
+ return Number.isNaN(value.getTime()) ? null : value;
163
+ }
164
+ if (typeof value !== "string" || !value.trim()) {
165
+ return null;
166
+ }
167
+ const parsed = new Date(value.trim());
168
+ if (Number.isNaN(parsed.getTime())) {
169
+ return null;
170
+ }
171
+ return parsed;
172
+ }
173
+ }
174
+ //# sourceMappingURL=EnterpriseLicenseSync.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"EnterpriseLicenseSync.js","sourceRoot":"","sources":["../../../../Utils/EnterpriseLicense/EnterpriseLicenseSync.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oneuptime/common",
3
- "version": "12.0.19",
3
+ "version": "12.0.20",
4
4
  "description": "",
5
5
  "repository": {
6
6
  "type": "git",