@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.
- package/Models/AnalyticsModels/AnalyticsBaseModel/AnalyticsBaseModel.ts +41 -24
- package/Models/AnalyticsModels/MetricBaselineHourly.ts +2 -0
- package/Models/AnalyticsModels/MetricItemAggMV1m.ts +5 -2
- package/Models/AnalyticsModels/MetricItemAggMV1mByContainer.ts +5 -2
- package/Models/AnalyticsModels/MetricItemAggMV1mByHostV2.ts +5 -2
- package/Models/AnalyticsModels/MetricItemAggMV1mByK8sCluster.ts +5 -2
- package/Models/AnalyticsModels/MetricItemAggMV1mByService.ts +5 -2
- package/Models/DatabaseModels/EnterpriseLicense.ts +21 -0
- package/Models/DatabaseModels/EnterpriseLicenseInstance.ts +25 -0
- package/Models/DatabaseModels/GlobalConfig.ts +76 -0
- package/Server/API/EnterpriseLicenseAPI.ts +57 -0
- package/Server/API/GlobalConfigAPI.ts +62 -1
- package/Server/EnvironmentConfig.ts +22 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1784659816363-AddInstanceVersionAndLatestReleaseColumns.ts +38 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1784705487674-AddEnterpriseLicenseEvaluationColumns.ts +25 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +4 -0
- package/Server/Services/AnalyticsDatabaseService.ts +61 -46
- package/Server/Services/DatabaseService.ts +66 -1
- package/Server/Services/WorkflowService.ts +100 -39
- package/Server/Types/Workflow/Components/Schedule.ts +57 -32
- package/Server/Utils/AnalyticsDatabase/InsertDedupContext.ts +59 -0
- package/Server/Utils/AnalyticsDatabase/StatementGenerator.ts +41 -4
- package/Server/Utils/Express.ts +13 -0
- package/Server/Utils/Telemetry/TelemetryFanInWriter.ts +799 -0
- package/Server/Utils/Telemetry/TelemetryWriterClient.ts +263 -0
- package/Server/Utils/Telemetry/TelemetryWriterServer.ts +240 -0
- package/Server/Utils/Telemetry/TelemetryWriterShedMetrics.ts +93 -0
- package/Tests/Models/AnalyticsModels/AnalyticsBaseModel.test.ts +122 -0
- package/Tests/Server/Services/DatabaseServiceSanitizeUpdateData.test.ts +232 -0
- package/Tests/Server/Services/UpdateOneByIdKeepsRowId.test.ts +107 -0
- package/Tests/Server/Services/WorkflowService.test.ts +308 -0
- package/Tests/Server/Utils/AnalyticsDatabase/ClusterAwareSchema.test.ts +164 -3
- package/Tests/Server/Utils/AnalyticsDatabase/StatementGenerator.test.ts +99 -0
- package/Tests/Server/Utils/Attribution.test.ts +203 -0
- package/Tests/Server/Utils/Marketing/ConversionUploadProvider.test.ts +183 -0
- package/Tests/Server/Utils/Marketing/GoogleAds.test.ts +340 -0
- package/Tests/Server/Utils/Monitor/Criteria/ExceptionMonitorCriteria.test.ts +87 -0
- package/Tests/Server/Utils/Monitor/Criteria/LogMonitorCriteria.test.ts +165 -0
- package/Tests/Server/Utils/Monitor/Criteria/TraceMonitorCriteria.test.ts +86 -0
- package/Tests/Server/Utils/Telemetry/TelemetryFanInWriter.test.ts +1565 -0
- package/Tests/Server/Utils/Telemetry/TelemetryWriterClient.test.ts +313 -0
- package/Tests/Server/Utils/Telemetry/TelemetryWriterServer.test.ts +270 -0
- package/Tests/Server/Utils/Telemetry/TelemetryWriterShedMetrics.test.ts +127 -0
- package/Tests/Types/AI/CodeFixTaskType.test.ts +3 -0
- package/Tests/Types/HashCode.test.ts +41 -0
- package/Tests/Types/Log/LogQueryParser.test.ts +186 -0
- package/Tests/Types/Monitor/MonitorCriteriaMetricVariables.test.ts +228 -0
- package/Tests/Types/Monitor/MonitorStepDefaultTelemetryConfig.test.ts +169 -0
- package/Tests/Types/Monitor/MonitorStepExceptionMonitor.test.ts +289 -0
- package/Tests/Types/Monitor/MonitorStepLogMonitor.test.ts +231 -0
- package/Tests/Types/Monitor/MonitorStepMetricViewConfigUtil.test.ts +245 -0
- package/Tests/Types/Monitor/MonitorStepNetworkDeviceMonitor.test.ts +155 -0
- package/Tests/Types/Monitor/MonitorStepTelemetrySerialization.test.ts +141 -0
- package/Tests/Types/Monitor/MonitorStepTraceMonitor.test.ts +137 -0
- package/Tests/Types/Workspace/NotificationRules/NotificationRuleCondition.test.ts +311 -0
- package/Tests/UI/Utils/Breadcrumb/BreadcrumbTrailResolver.test.ts +461 -0
- package/Tests/UI/Utils/Breadcrumb/fixtures/RealBreadcrumbTrails.ts +2852 -0
- package/Tests/UI/Utils/Breadcrumb/fixtures/RealRoutePatterns.ts +758 -0
- package/Tests/UI/Utils/NotificationMethodUtil.test.ts +564 -0
- package/Tests/Utils/Array.test.ts +156 -0
- package/Tests/Utils/CronTab.test.ts +199 -0
- package/Tests/Utils/Dashboard/VariableInterpolation.test.ts +197 -0
- package/Tests/Utils/ModelImportExport.test.ts +101 -0
- package/Tests/Utils/Number.test.ts +179 -0
- package/Tests/Utils/ValueFormatter.test.ts +305 -0
- package/Tests/Utils/VersionUtil.test.ts +348 -0
- package/Tests/jest.setup.ts +1 -0
- package/Types/AnalyticsDatabase/TableColumn.ts +23 -0
- package/Types/EnterpriseLicense/EnterpriseLicenseInstanceSummary.ts +6 -0
- package/Types/Monitor/MonitorStep.ts +24 -4
- package/Types/Monitor/MonitorStepMetricViewConfigUtil.ts +105 -0
- package/UI/Components/EditionLabel/EditionLabel.tsx +445 -12
- package/UI/Components/Workflow/ArgumentsForm.tsx +68 -30
- package/UI/Components/Workflow/CronScheduleField.tsx +411 -0
- package/UI/Utils/Breadcrumb/BreadcrumbTrailResolver.ts +176 -0
- package/UI/Utils/NotificationMethodUtil.ts +310 -0
- package/Utils/CronTab.ts +823 -0
- package/Utils/ModelImportExport.ts +21 -6
- package/Utils/VersionUtil.ts +260 -0
- package/build/dist/Models/AnalyticsModels/AnalyticsBaseModel/AnalyticsBaseModel.js +25 -20
- package/build/dist/Models/AnalyticsModels/AnalyticsBaseModel/AnalyticsBaseModel.js.map +1 -1
- package/build/dist/Models/AnalyticsModels/MetricBaselineHourly.js +2 -0
- package/build/dist/Models/AnalyticsModels/MetricBaselineHourly.js.map +1 -1
- package/build/dist/Models/AnalyticsModels/MetricItemAggMV1m.js +5 -2
- package/build/dist/Models/AnalyticsModels/MetricItemAggMV1m.js.map +1 -1
- package/build/dist/Models/AnalyticsModels/MetricItemAggMV1mByContainer.js +5 -2
- package/build/dist/Models/AnalyticsModels/MetricItemAggMV1mByContainer.js.map +1 -1
- package/build/dist/Models/AnalyticsModels/MetricItemAggMV1mByHostV2.js +5 -2
- package/build/dist/Models/AnalyticsModels/MetricItemAggMV1mByHostV2.js.map +1 -1
- package/build/dist/Models/AnalyticsModels/MetricItemAggMV1mByK8sCluster.js +5 -2
- package/build/dist/Models/AnalyticsModels/MetricItemAggMV1mByK8sCluster.js.map +1 -1
- package/build/dist/Models/AnalyticsModels/MetricItemAggMV1mByService.js +5 -2
- package/build/dist/Models/AnalyticsModels/MetricItemAggMV1mByService.js.map +1 -1
- package/build/dist/Models/DatabaseModels/EnterpriseLicense.js +22 -0
- package/build/dist/Models/DatabaseModels/EnterpriseLicense.js.map +1 -1
- package/build/dist/Models/DatabaseModels/EnterpriseLicenseInstance.js +26 -0
- package/build/dist/Models/DatabaseModels/EnterpriseLicenseInstance.js.map +1 -1
- package/build/dist/Models/DatabaseModels/GlobalConfig.js +81 -0
- package/build/dist/Models/DatabaseModels/GlobalConfig.js.map +1 -1
- package/build/dist/Server/API/EnterpriseLicenseAPI.js +47 -0
- package/build/dist/Server/API/EnterpriseLicenseAPI.js.map +1 -1
- package/build/dist/Server/API/GlobalConfigAPI.js +52 -1
- package/build/dist/Server/API/GlobalConfigAPI.js.map +1 -1
- package/build/dist/Server/EnvironmentConfig.js +17 -0
- package/build/dist/Server/EnvironmentConfig.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1784659816363-AddInstanceVersionAndLatestReleaseColumns.js +18 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1784659816363-AddInstanceVersionAndLatestReleaseColumns.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1784705487674-AddEnterpriseLicenseEvaluationColumns.js +14 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1784705487674-AddEnterpriseLicenseEvaluationColumns.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +4 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Services/AnalyticsDatabaseService.js +53 -17
- package/build/dist/Server/Services/AnalyticsDatabaseService.js.map +1 -1
- package/build/dist/Server/Services/DatabaseService.js +53 -1
- package/build/dist/Server/Services/DatabaseService.js.map +1 -1
- package/build/dist/Server/Services/WorkflowService.js +80 -30
- package/build/dist/Server/Services/WorkflowService.js.map +1 -1
- package/build/dist/Server/Types/Workflow/Components/Schedule.js +44 -19
- package/build/dist/Server/Types/Workflow/Components/Schedule.js.map +1 -1
- package/build/dist/Server/Utils/AnalyticsDatabase/InsertDedupContext.js +24 -0
- package/build/dist/Server/Utils/AnalyticsDatabase/InsertDedupContext.js.map +1 -0
- package/build/dist/Server/Utils/AnalyticsDatabase/StatementGenerator.js +29 -4
- package/build/dist/Server/Utils/AnalyticsDatabase/StatementGenerator.js.map +1 -1
- package/build/dist/Server/Utils/Express.js +12 -0
- package/build/dist/Server/Utils/Express.js.map +1 -1
- package/build/dist/Server/Utils/Telemetry/TelemetryFanInWriter.js +496 -0
- package/build/dist/Server/Utils/Telemetry/TelemetryFanInWriter.js.map +1 -0
- package/build/dist/Server/Utils/Telemetry/TelemetryWriterClient.js +165 -0
- package/build/dist/Server/Utils/Telemetry/TelemetryWriterClient.js.map +1 -0
- package/build/dist/Server/Utils/Telemetry/TelemetryWriterServer.js +136 -0
- package/build/dist/Server/Utils/Telemetry/TelemetryWriterServer.js.map +1 -0
- package/build/dist/Server/Utils/Telemetry/TelemetryWriterShedMetrics.js +76 -0
- package/build/dist/Server/Utils/Telemetry/TelemetryWriterShedMetrics.js.map +1 -0
- package/build/dist/Types/AnalyticsDatabase/TableColumn.js +7 -0
- package/build/dist/Types/AnalyticsDatabase/TableColumn.js.map +1 -1
- package/build/dist/Types/Monitor/MonitorStep.js +20 -4
- package/build/dist/Types/Monitor/MonitorStep.js.map +1 -1
- package/build/dist/Types/Monitor/MonitorStepMetricViewConfigUtil.js +73 -0
- package/build/dist/Types/Monitor/MonitorStepMetricViewConfigUtil.js.map +1 -0
- package/build/dist/UI/Components/EditionLabel/EditionLabel.js +261 -13
- package/build/dist/UI/Components/EditionLabel/EditionLabel.js.map +1 -1
- package/build/dist/UI/Components/Workflow/ArgumentsForm.js +30 -6
- package/build/dist/UI/Components/Workflow/ArgumentsForm.js.map +1 -1
- package/build/dist/UI/Components/Workflow/CronScheduleField.js +209 -0
- package/build/dist/UI/Components/Workflow/CronScheduleField.js.map +1 -0
- package/build/dist/UI/Utils/Breadcrumb/BreadcrumbTrailResolver.js +129 -0
- package/build/dist/UI/Utils/Breadcrumb/BreadcrumbTrailResolver.js.map +1 -0
- package/build/dist/UI/Utils/NotificationMethodUtil.js +189 -0
- package/build/dist/UI/Utils/NotificationMethodUtil.js.map +1 -0
- package/build/dist/Utils/CronTab.js +609 -0
- package/build/dist/Utils/CronTab.js.map +1 -0
- package/build/dist/Utils/ModelImportExport.js +20 -6
- package/build/dist/Utils/ModelImportExport.js.map +1 -1
- package/build/dist/Utils/VersionUtil.js +193 -0
- package/build/dist/Utils/VersionUtil.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import ArrayUtil from "../../Utils/Array";
|
|
2
|
+
import ObjectID from "../../Types/ObjectID";
|
|
3
|
+
|
|
4
|
+
describe("ArrayUtil", () => {
|
|
5
|
+
describe("removeDuplicates", () => {
|
|
6
|
+
it("keeps the first occurrence of each value in order", () => {
|
|
7
|
+
expect(ArrayUtil.removeDuplicates([1, 2, 2, 3, 1])).toEqual([1, 2, 3]);
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
it("returns an empty array unchanged", () => {
|
|
11
|
+
expect(ArrayUtil.removeDuplicates([])).toEqual([]);
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it("de-duplicates strings", () => {
|
|
15
|
+
expect(ArrayUtil.removeDuplicates(["a", "b", "a"])).toEqual(["a", "b"]);
|
|
16
|
+
});
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
describe("mergeStringArrays", () => {
|
|
20
|
+
it("concatenates and removes duplicates", () => {
|
|
21
|
+
expect(ArrayUtil.mergeStringArrays(["a", "b"], ["b", "c"])).toEqual([
|
|
22
|
+
"a",
|
|
23
|
+
"b",
|
|
24
|
+
"c",
|
|
25
|
+
]);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it("handles empty inputs on either side", () => {
|
|
29
|
+
expect(ArrayUtil.mergeStringArrays([], ["x"])).toEqual(["x"]);
|
|
30
|
+
expect(ArrayUtil.mergeStringArrays(["x"], [])).toEqual(["x"]);
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
describe("isEqual / isStringArrayEqual", () => {
|
|
35
|
+
it("is true for arrays with the same members regardless of order", () => {
|
|
36
|
+
expect(ArrayUtil.isEqual([1, 2, 3], [3, 2, 1])).toBe(true);
|
|
37
|
+
expect(ArrayUtil.isStringArrayEqual(["a", "b"], ["b", "a"])).toBe(true);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("is false for different lengths", () => {
|
|
41
|
+
expect(ArrayUtil.isEqual([1, 2], [1, 2, 3])).toBe(false);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it("is false for different members", () => {
|
|
45
|
+
expect(ArrayUtil.isEqual([1, 2, 3], [1, 2, 4])).toBe(false);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it("is true for two empty arrays", () => {
|
|
49
|
+
expect(ArrayUtil.isEqual([], [])).toBe(true);
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
describe("shuffle", () => {
|
|
54
|
+
it("returns a new array with the same members", () => {
|
|
55
|
+
const input: Array<number> = [1, 2, 3, 4, 5];
|
|
56
|
+
const shuffled: Array<number> = ArrayUtil.shuffle(input);
|
|
57
|
+
expect(shuffled).not.toBe(input);
|
|
58
|
+
expect([...shuffled].sort()).toEqual([...input].sort());
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("does not mutate the input array", () => {
|
|
62
|
+
const input: Array<number> = [1, 2, 3];
|
|
63
|
+
const copy: Array<number> = [...input];
|
|
64
|
+
ArrayUtil.shuffle(input);
|
|
65
|
+
expect(input).toEqual(copy);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it("handles empty and single-element arrays", () => {
|
|
69
|
+
expect(ArrayUtil.shuffle([])).toEqual([]);
|
|
70
|
+
expect(ArrayUtil.shuffle([9])).toEqual([9]);
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
describe("removeDuplicatesFromObjectIDArray", () => {
|
|
75
|
+
it("de-duplicates by string value", () => {
|
|
76
|
+
const id1: ObjectID = new ObjectID(
|
|
77
|
+
"aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa",
|
|
78
|
+
);
|
|
79
|
+
const id1Dup: ObjectID = new ObjectID(
|
|
80
|
+
"aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa",
|
|
81
|
+
);
|
|
82
|
+
const id2: ObjectID = new ObjectID(
|
|
83
|
+
"bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb",
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
const result: Array<ObjectID> =
|
|
87
|
+
ArrayUtil.removeDuplicatesFromObjectIDArray([id1, id1Dup, id2]);
|
|
88
|
+
|
|
89
|
+
expect(result).toHaveLength(2);
|
|
90
|
+
expect(
|
|
91
|
+
result.map((id: ObjectID) => {
|
|
92
|
+
return id.toString();
|
|
93
|
+
}),
|
|
94
|
+
).toEqual([id1.toString(), id2.toString()]);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it("returns an empty array unchanged", () => {
|
|
98
|
+
expect(ArrayUtil.removeDuplicatesFromObjectIDArray([])).toEqual([]);
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
describe("sortByFieldName", () => {
|
|
103
|
+
it("sorts objects ascending by the given field", () => {
|
|
104
|
+
const rows: Array<{ name: string; age: number }> = [
|
|
105
|
+
{ name: "c", age: 3 },
|
|
106
|
+
{ name: "a", age: 1 },
|
|
107
|
+
{ name: "b", age: 2 },
|
|
108
|
+
];
|
|
109
|
+
rows.sort(ArrayUtil.sortByFieldName("age"));
|
|
110
|
+
expect(
|
|
111
|
+
rows.map((r: { name: string }) => {
|
|
112
|
+
return r.name;
|
|
113
|
+
}),
|
|
114
|
+
).toEqual(["a", "b", "c"]);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it("returns 0 for equal field values", () => {
|
|
118
|
+
const comparator: (a: any, b: any) => number =
|
|
119
|
+
ArrayUtil.sortByFieldName("age");
|
|
120
|
+
expect(comparator({ age: 5 }, { age: 5 })).toBe(0);
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
describe("selectItemByRandom", () => {
|
|
125
|
+
it("returns a member of the array", () => {
|
|
126
|
+
const arr: Array<string> = ["x", "y", "z"];
|
|
127
|
+
for (let i: number = 0; i < 100; i++) {
|
|
128
|
+
expect(arr).toContain(ArrayUtil.selectItemByRandom(arr));
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it("returns the only element of a single-element array", () => {
|
|
133
|
+
expect(ArrayUtil.selectItemByRandom(["only"])).toBe("only");
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
describe("distinctByFieldName", () => {
|
|
138
|
+
it("keeps the first object for each distinct field value", () => {
|
|
139
|
+
const rows: Array<{ group: string; id: number }> = [
|
|
140
|
+
{ group: "a", id: 1 },
|
|
141
|
+
{ group: "b", id: 2 },
|
|
142
|
+
{ group: "a", id: 3 },
|
|
143
|
+
];
|
|
144
|
+
const result: Array<{ group: string; id: number }> =
|
|
145
|
+
ArrayUtil.distinctByFieldName(rows, "group");
|
|
146
|
+
expect(result).toEqual([
|
|
147
|
+
{ group: "a", id: 1 },
|
|
148
|
+
{ group: "b", id: 2 },
|
|
149
|
+
]);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it("returns an empty array unchanged", () => {
|
|
153
|
+
expect(ArrayUtil.distinctByFieldName([], "group")).toEqual([]);
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
});
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import CronTab, { CronPreset } from "../../Utils/CronTab";
|
|
2
|
+
import cronParser from "cron-parser";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* These tests cross-check our dependency-free CronTab engine against
|
|
6
|
+
* `cron-parser` (the library BullMQ actually uses to fire repeatable jobs), so
|
|
7
|
+
* the picker's preview and validation stay faithful to real firing behavior.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const FIXED_FROM: Date = new Date(Date.UTC(2026, 0, 1, 0, 0, 30));
|
|
11
|
+
|
|
12
|
+
function cronParserNextTimes(
|
|
13
|
+
expression: string,
|
|
14
|
+
count: number,
|
|
15
|
+
from: Date,
|
|
16
|
+
): Array<number> {
|
|
17
|
+
const interval: {
|
|
18
|
+
next: () => { toDate: () => Date };
|
|
19
|
+
} = cronParser.parseExpression(expression, {
|
|
20
|
+
currentDate: from,
|
|
21
|
+
tz: "UTC",
|
|
22
|
+
}) as unknown as { next: () => { toDate: () => Date } };
|
|
23
|
+
|
|
24
|
+
const times: Array<number> = [];
|
|
25
|
+
for (let i: number = 0; i < count; i++) {
|
|
26
|
+
times.push(interval.next().toDate().getTime());
|
|
27
|
+
}
|
|
28
|
+
return times;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
describe("CronTab", () => {
|
|
32
|
+
describe("isVariableExpression", () => {
|
|
33
|
+
test("detects workflow variable references", () => {
|
|
34
|
+
expect(CronTab.isVariableExpression("{{local.variables.schedule}}")).toBe(
|
|
35
|
+
true,
|
|
36
|
+
);
|
|
37
|
+
expect(
|
|
38
|
+
CronTab.isVariableExpression("* */{{local.variables.h}} * * *"),
|
|
39
|
+
).toBe(true);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test("returns false for plain cron expressions", () => {
|
|
43
|
+
expect(CronTab.isVariableExpression("0 */18 * * *")).toBe(false);
|
|
44
|
+
expect(CronTab.isVariableExpression("* * * * *")).toBe(false);
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
describe("validation", () => {
|
|
49
|
+
const validExpressions: Array<string> = [
|
|
50
|
+
"* * * * *",
|
|
51
|
+
"*/5 * * * *",
|
|
52
|
+
"0 */18 * * *",
|
|
53
|
+
"0 0 * * *",
|
|
54
|
+
"30 9 * * 1-5",
|
|
55
|
+
"0 0 1 */3 *",
|
|
56
|
+
"0 0 1 1 *",
|
|
57
|
+
"15,45 * * * *",
|
|
58
|
+
"0 0 * * MON",
|
|
59
|
+
"0 0 1 JAN *",
|
|
60
|
+
"0 0 * * 7",
|
|
61
|
+
"*/15 9-17 * * MON-FRI",
|
|
62
|
+
"30 2 * * * ", // trailing space
|
|
63
|
+
"0 0 * * SUN,SAT",
|
|
64
|
+
"5/10 * * * *",
|
|
65
|
+
];
|
|
66
|
+
|
|
67
|
+
test.each(validExpressions)(
|
|
68
|
+
"accepts valid expression %s",
|
|
69
|
+
(expr: string) => {
|
|
70
|
+
expect(CronTab.getValidationError(expr)).toBeNull();
|
|
71
|
+
expect(CronTab.isValid(expr)).toBe(true);
|
|
72
|
+
},
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
const invalidExpressions: Array<string> = [
|
|
76
|
+
"",
|
|
77
|
+
"* * * *", // 4 fields
|
|
78
|
+
"* * * * * * *", // 7 fields
|
|
79
|
+
"60 * * * *", // minute out of range
|
|
80
|
+
"* 24 * * *", // hour out of range
|
|
81
|
+
"* * 32 * *", // dom out of range
|
|
82
|
+
"* * * 13 *", // month out of range
|
|
83
|
+
"* * * * 8", // dow out of range
|
|
84
|
+
"*/0 * * * *", // step 0
|
|
85
|
+
"abc * * * *", // not a number
|
|
86
|
+
"5-1 * * * *", // reversed range
|
|
87
|
+
"* * * * FUN", // bad day name
|
|
88
|
+
];
|
|
89
|
+
|
|
90
|
+
test.each(invalidExpressions)(
|
|
91
|
+
"rejects invalid expression %s",
|
|
92
|
+
(expr: string) => {
|
|
93
|
+
expect(CronTab.getValidationError(expr)).not.toBeNull();
|
|
94
|
+
expect(CronTab.isValid(expr)).toBe(false);
|
|
95
|
+
},
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
test("variable expressions are not flagged as validation errors", () => {
|
|
99
|
+
expect(
|
|
100
|
+
CronTab.getValidationError("{{local.variables.schedule}}"),
|
|
101
|
+
).toBeNull();
|
|
102
|
+
// ...but they are not "valid" concrete crons either.
|
|
103
|
+
expect(CronTab.isValid("{{local.variables.schedule}}")).toBe(false);
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
describe("all presets are valid and describable", () => {
|
|
108
|
+
test.each(
|
|
109
|
+
CronTab.PRESETS.map((p: CronPreset) => {
|
|
110
|
+
return p.value;
|
|
111
|
+
}),
|
|
112
|
+
)("preset %s", (value: string) => {
|
|
113
|
+
expect(CronTab.isValid(value)).toBe(true);
|
|
114
|
+
expect(CronTab.getHumanReadableDescription(value)).toBeTruthy();
|
|
115
|
+
// cron-parser must also accept it (guards against BullMQ rejecting).
|
|
116
|
+
expect(() => {
|
|
117
|
+
return cronParser.parseExpression(value, { tz: "UTC" });
|
|
118
|
+
}).not.toThrow();
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
describe("next execution times match cron-parser", () => {
|
|
123
|
+
const expressions: Array<string> = [
|
|
124
|
+
"* * * * *",
|
|
125
|
+
"*/5 * * * *",
|
|
126
|
+
"*/30 * * * *",
|
|
127
|
+
"0 * * * *",
|
|
128
|
+
"0 */3 * * *",
|
|
129
|
+
"0 */18 * * *",
|
|
130
|
+
"0 0 * * *",
|
|
131
|
+
"30 9 * * *",
|
|
132
|
+
"0 0 * * 0",
|
|
133
|
+
"0 0 * * 1-5",
|
|
134
|
+
"0 0 1 * *",
|
|
135
|
+
"0 0 1 */3 *",
|
|
136
|
+
"0 0 29 2 *", // leap-day only — sparse, exercises month/day jumps
|
|
137
|
+
"15,45 8-10 * * *",
|
|
138
|
+
"0 12 1,15 * *",
|
|
139
|
+
];
|
|
140
|
+
|
|
141
|
+
test.each(expressions)("next 8 runs for %s", (expr: string) => {
|
|
142
|
+
const ours: Array<number> = CronTab.getNextExecutionTimes(
|
|
143
|
+
expr,
|
|
144
|
+
8,
|
|
145
|
+
FIXED_FROM,
|
|
146
|
+
).map((d: Date) => {
|
|
147
|
+
return d.getTime();
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
const theirs: Array<number> = cronParserNextTimes(expr, 8, FIXED_FROM);
|
|
151
|
+
|
|
152
|
+
expect(ours).toEqual(theirs);
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
describe("human-readable descriptions", () => {
|
|
157
|
+
const cases: Array<[string, string]> = [
|
|
158
|
+
["* * * * *", "Every minute"],
|
|
159
|
+
["*/5 * * * *", "Every 5 minutes"],
|
|
160
|
+
["*/30 * * * *", "Every 30 minutes"],
|
|
161
|
+
["0 * * * *", "Every hour, on the hour"],
|
|
162
|
+
["0 */2 * * *", "Every 2 hours"],
|
|
163
|
+
["0 */18 * * *", "Every 18 hours"],
|
|
164
|
+
["0 0 * * *", "Every day at 00:00 (UTC)"],
|
|
165
|
+
["30 9 * * *", "Every day at 09:30 (UTC)"],
|
|
166
|
+
["0 9 * * 1", "Every week on Monday at 09:00 (UTC)"],
|
|
167
|
+
["0 0 * * 0", "Every week on Sunday at 00:00 (UTC)"],
|
|
168
|
+
["0 0 1 * *", "Every month on the 1st at 00:00 (UTC)"],
|
|
169
|
+
["0 0 1 */3 *", "Every 3 months on the 1st at 00:00 (UTC)"],
|
|
170
|
+
["0 0 1 1 *", "On the 1st of January at 00:00 (UTC)"],
|
|
171
|
+
];
|
|
172
|
+
|
|
173
|
+
test.each(cases)("describes %s as %s", (expr: string, expected: string) => {
|
|
174
|
+
expect(CronTab.getHumanReadableDescription(expr)).toBe(expected);
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
test("returns null for invalid expressions", () => {
|
|
178
|
+
expect(CronTab.getHumanReadableDescription("nonsense")).toBeNull();
|
|
179
|
+
});
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
describe("next execution times are in the future and ascending", () => {
|
|
183
|
+
test("returns the requested count in strictly increasing order", () => {
|
|
184
|
+
const times: Array<Date> = CronTab.getNextExecutionTimes(
|
|
185
|
+
"*/15 * * * *",
|
|
186
|
+
5,
|
|
187
|
+
FIXED_FROM,
|
|
188
|
+
);
|
|
189
|
+
|
|
190
|
+
expect(times).toHaveLength(5);
|
|
191
|
+
for (let i: number = 0; i < times.length; i++) {
|
|
192
|
+
expect(times[i]!.getTime()).toBeGreaterThan(FIXED_FROM.getTime());
|
|
193
|
+
if (i > 0) {
|
|
194
|
+
expect(times[i]!.getTime()).toBeGreaterThan(times[i - 1]!.getTime());
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
});
|
|
199
|
+
});
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import Includes from "../../../Types/BaseDatabase/Includes";
|
|
2
|
+
import DashboardVariable, {
|
|
3
|
+
DashboardVariableType,
|
|
4
|
+
} from "../../../Types/Dashboard/DashboardVariable";
|
|
5
|
+
import DashboardVariableInterpolation from "../../../Utils/Dashboard/VariableInterpolation";
|
|
6
|
+
|
|
7
|
+
function makeVariable(
|
|
8
|
+
overrides: Partial<DashboardVariable>,
|
|
9
|
+
): DashboardVariable {
|
|
10
|
+
return {
|
|
11
|
+
id: "var-1",
|
|
12
|
+
name: "cluster",
|
|
13
|
+
type: DashboardVariableType.TelemetryAttribute,
|
|
14
|
+
...overrides,
|
|
15
|
+
} as DashboardVariable;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
describe("DashboardVariableInterpolation", () => {
|
|
19
|
+
describe("resolveValue", () => {
|
|
20
|
+
test("returns scalar for a single-select with selectedValue", () => {
|
|
21
|
+
const variable: DashboardVariable = makeVariable({
|
|
22
|
+
selectedValue: "prod",
|
|
23
|
+
});
|
|
24
|
+
expect(DashboardVariableInterpolation.resolveValue(variable)).toEqual({
|
|
25
|
+
scalar: "prod",
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
test("falls back to defaultValue when selectedValue is unset", () => {
|
|
30
|
+
const variable: DashboardVariable = makeVariable({
|
|
31
|
+
defaultValue: "staging",
|
|
32
|
+
});
|
|
33
|
+
expect(DashboardVariableInterpolation.resolveValue(variable)).toEqual({
|
|
34
|
+
scalar: "staging",
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test("prefers selectedValue over defaultValue", () => {
|
|
39
|
+
const variable: DashboardVariable = makeVariable({
|
|
40
|
+
selectedValue: "prod",
|
|
41
|
+
defaultValue: "staging",
|
|
42
|
+
});
|
|
43
|
+
expect(DashboardVariableInterpolation.resolveValue(variable)).toEqual({
|
|
44
|
+
scalar: "prod",
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test("returns undefined when nothing is selected", () => {
|
|
49
|
+
expect(
|
|
50
|
+
DashboardVariableInterpolation.resolveValue(makeVariable({})),
|
|
51
|
+
).toBeUndefined();
|
|
52
|
+
expect(
|
|
53
|
+
DashboardVariableInterpolation.resolveValue(
|
|
54
|
+
makeVariable({ selectedValue: "" }),
|
|
55
|
+
),
|
|
56
|
+
).toBeUndefined();
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test("returns multi for a multi-select with values", () => {
|
|
60
|
+
const variable: DashboardVariable = makeVariable({
|
|
61
|
+
isMultiSelect: true,
|
|
62
|
+
selectedValues: ["a", "b"],
|
|
63
|
+
});
|
|
64
|
+
expect(DashboardVariableInterpolation.resolveValue(variable)).toEqual({
|
|
65
|
+
multi: ["a", "b"],
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
test("filters out empty values in multi-select", () => {
|
|
70
|
+
const variable: DashboardVariable = makeVariable({
|
|
71
|
+
isMultiSelect: true,
|
|
72
|
+
selectedValues: ["a", "", "b"],
|
|
73
|
+
});
|
|
74
|
+
expect(DashboardVariableInterpolation.resolveValue(variable)).toEqual({
|
|
75
|
+
multi: ["a", "b"],
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
test("multi-select with no picks falls through to scalar default", () => {
|
|
80
|
+
const variable: DashboardVariable = makeVariable({
|
|
81
|
+
isMultiSelect: true,
|
|
82
|
+
selectedValues: [],
|
|
83
|
+
defaultValue: "d",
|
|
84
|
+
});
|
|
85
|
+
expect(DashboardVariableInterpolation.resolveValue(variable)).toEqual({
|
|
86
|
+
scalar: "d",
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
describe("applyToAttributes", () => {
|
|
92
|
+
test("returns the same reference when there are no variables", () => {
|
|
93
|
+
const attrs: Record<string, unknown> = { foo: "bar" };
|
|
94
|
+
expect(
|
|
95
|
+
DashboardVariableInterpolation.applyToAttributes(attrs, undefined),
|
|
96
|
+
).toBe(attrs);
|
|
97
|
+
expect(DashboardVariableInterpolation.applyToAttributes(attrs, [])).toBe(
|
|
98
|
+
attrs,
|
|
99
|
+
);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
test("injects a scalar attribute for a selected variable", () => {
|
|
103
|
+
const variable: DashboardVariable = makeVariable({
|
|
104
|
+
attributeKey: "k8s.cluster.name",
|
|
105
|
+
selectedValue: "prod",
|
|
106
|
+
});
|
|
107
|
+
const result: Record<string, unknown> =
|
|
108
|
+
DashboardVariableInterpolation.applyToAttributes({}, [variable]);
|
|
109
|
+
expect(result).toEqual({ "k8s.cluster.name": "prod" });
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
test("emits an Includes operator for multi-select", () => {
|
|
113
|
+
const variable: DashboardVariable = makeVariable({
|
|
114
|
+
attributeKey: "k8s.cluster.name",
|
|
115
|
+
isMultiSelect: true,
|
|
116
|
+
selectedValues: ["a", "b"],
|
|
117
|
+
});
|
|
118
|
+
const result: Record<string, unknown> =
|
|
119
|
+
DashboardVariableInterpolation.applyToAttributes({}, [variable]);
|
|
120
|
+
const value: unknown = result["k8s.cluster.name"];
|
|
121
|
+
expect(value).toBeInstanceOf(Includes);
|
|
122
|
+
expect((value as Includes).values).toEqual(["a", "b"]);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
test("removes a previously-set filter when selection is cleared (All)", () => {
|
|
126
|
+
const variable: DashboardVariable = makeVariable({
|
|
127
|
+
attributeKey: "k8s.cluster.name",
|
|
128
|
+
selectedValue: "",
|
|
129
|
+
});
|
|
130
|
+
const result: Record<string, unknown> =
|
|
131
|
+
DashboardVariableInterpolation.applyToAttributes(
|
|
132
|
+
{ "k8s.cluster.name": "prod", other: "keep" },
|
|
133
|
+
[variable],
|
|
134
|
+
);
|
|
135
|
+
expect(result).toEqual({ other: "keep" });
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
test("ignores non-TelemetryAttribute variables", () => {
|
|
139
|
+
const attrs: Record<string, unknown> = { foo: "bar" };
|
|
140
|
+
const variable: DashboardVariable = makeVariable({
|
|
141
|
+
type: DashboardVariableType.TextInput,
|
|
142
|
+
attributeKey: "k8s.cluster.name",
|
|
143
|
+
selectedValue: "prod",
|
|
144
|
+
});
|
|
145
|
+
expect(
|
|
146
|
+
DashboardVariableInterpolation.applyToAttributes(attrs, [variable]),
|
|
147
|
+
).toBe(attrs);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
test("ignores TelemetryAttribute variables without an attributeKey", () => {
|
|
151
|
+
const attrs: Record<string, unknown> = { foo: "bar" };
|
|
152
|
+
const variable: DashboardVariable = makeVariable({
|
|
153
|
+
selectedValue: "prod",
|
|
154
|
+
});
|
|
155
|
+
expect(
|
|
156
|
+
DashboardVariableInterpolation.applyToAttributes(attrs, [variable]),
|
|
157
|
+
).toBe(attrs);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
test("returns same reference when scalar value is already set", () => {
|
|
161
|
+
const attrs: Record<string, unknown> = { "k8s.cluster.name": "prod" };
|
|
162
|
+
const variable: DashboardVariable = makeVariable({
|
|
163
|
+
attributeKey: "k8s.cluster.name",
|
|
164
|
+
selectedValue: "prod",
|
|
165
|
+
});
|
|
166
|
+
expect(
|
|
167
|
+
DashboardVariableInterpolation.applyToAttributes(attrs, [variable]),
|
|
168
|
+
).toBe(attrs);
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
test("handles undefined attributes map", () => {
|
|
172
|
+
const variable: DashboardVariable = makeVariable({
|
|
173
|
+
attributeKey: "k8s.cluster.name",
|
|
174
|
+
selectedValue: "prod",
|
|
175
|
+
});
|
|
176
|
+
const result: Record<string, unknown> =
|
|
177
|
+
DashboardVariableInterpolation.applyToAttributes(undefined, [variable]);
|
|
178
|
+
expect(result).toEqual({ "k8s.cluster.name": "prod" });
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
test("applies multiple variables at once", () => {
|
|
182
|
+
const v1: DashboardVariable = makeVariable({
|
|
183
|
+
id: "v1",
|
|
184
|
+
attributeKey: "cluster",
|
|
185
|
+
selectedValue: "prod",
|
|
186
|
+
});
|
|
187
|
+
const v2: DashboardVariable = makeVariable({
|
|
188
|
+
id: "v2",
|
|
189
|
+
attributeKey: "namespace",
|
|
190
|
+
selectedValue: "default",
|
|
191
|
+
});
|
|
192
|
+
const result: Record<string, unknown> =
|
|
193
|
+
DashboardVariableInterpolation.applyToAttributes({}, [v1, v2]);
|
|
194
|
+
expect(result).toEqual({ cluster: "prod", namespace: "default" });
|
|
195
|
+
});
|
|
196
|
+
});
|
|
197
|
+
});
|
|
@@ -2,6 +2,7 @@ import Dashboard from "../../Models/DatabaseModels/Dashboard";
|
|
|
2
2
|
import Monitor from "../../Models/DatabaseModels/Monitor";
|
|
3
3
|
import ScheduledMaintenanceTemplate from "../../Models/DatabaseModels/ScheduledMaintenanceTemplate";
|
|
4
4
|
import StatusPage from "../../Models/DatabaseModels/StatusPage";
|
|
5
|
+
import Workflow from "../../Models/DatabaseModels/Workflow";
|
|
5
6
|
import EventInterval from "../../Types/Events/EventInterval";
|
|
6
7
|
import Recurring from "../../Types/Events/Recurring";
|
|
7
8
|
import BadDataException from "../../Types/Exception/BadDataException";
|
|
@@ -86,6 +87,35 @@ describe("ModelImportExport", () => {
|
|
|
86
87
|
expect(columns).not.toContain("embeddedOverallStatusToken");
|
|
87
88
|
});
|
|
88
89
|
|
|
90
|
+
test("should include the workflow graph and its own settings", () => {
|
|
91
|
+
const columns: Array<string> =
|
|
92
|
+
ModelImportExport.getImportExportableColumnNames(Workflow);
|
|
93
|
+
|
|
94
|
+
expect(columns).toContain("name");
|
|
95
|
+
expect(columns).toContain("description");
|
|
96
|
+
expect(columns).toContain("graph");
|
|
97
|
+
expect(columns).toContain("isEnabled");
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
test("should exclude workflow secrets and server-derived trigger state", () => {
|
|
101
|
+
const columns: Array<string> =
|
|
102
|
+
ModelImportExport.getImportExportableColumnNames(Workflow);
|
|
103
|
+
|
|
104
|
+
/*
|
|
105
|
+
* These all carry create: [] access control - they are derived from the
|
|
106
|
+
* graph or generated on create, and re-importing them would either leak
|
|
107
|
+
* the source workflow's webhook secret or point the runner at stale
|
|
108
|
+
* trigger state.
|
|
109
|
+
*/
|
|
110
|
+
expect(columns).not.toContain("webhookSecretKey");
|
|
111
|
+
expect(columns).not.toContain("triggerId");
|
|
112
|
+
expect(columns).not.toContain("triggerArguments");
|
|
113
|
+
expect(columns).not.toContain("repeatableJobKey");
|
|
114
|
+
expect(columns).not.toContain("slug");
|
|
115
|
+
expect(columns).not.toContain("projectId");
|
|
116
|
+
expect(columns).not.toContain("labels");
|
|
117
|
+
});
|
|
118
|
+
|
|
89
119
|
test("should exclude monitor runtime-state columns", () => {
|
|
90
120
|
const columns: Array<string> =
|
|
91
121
|
ModelImportExport.getImportExportableColumnNames(Monitor);
|
|
@@ -267,6 +297,77 @@ describe("ModelImportExport", () => {
|
|
|
267
297
|
expect(imported._id).toBeUndefined();
|
|
268
298
|
});
|
|
269
299
|
|
|
300
|
+
test("should round-trip a workflow graph and drop its webhook secret", () => {
|
|
301
|
+
const graph: JSONObject = {
|
|
302
|
+
nodes: [
|
|
303
|
+
{
|
|
304
|
+
id: "node-1",
|
|
305
|
+
data: {
|
|
306
|
+
metadataId: "Webhook",
|
|
307
|
+
componentType: "Trigger",
|
|
308
|
+
nodeType: "Node",
|
|
309
|
+
arguments: {},
|
|
310
|
+
},
|
|
311
|
+
},
|
|
312
|
+
],
|
|
313
|
+
edges: [],
|
|
314
|
+
};
|
|
315
|
+
|
|
316
|
+
const original: Workflow = new Workflow();
|
|
317
|
+
original.name = "Deploy Notifier";
|
|
318
|
+
original.description = "Posts to chat on deploy";
|
|
319
|
+
original.isEnabled = true;
|
|
320
|
+
original.graph = graph;
|
|
321
|
+
original.webhookSecretKey = "super-secret-key";
|
|
322
|
+
original.triggerId = "Webhook";
|
|
323
|
+
original.slug = "deploy-notifier";
|
|
324
|
+
|
|
325
|
+
const envelope: JSONObject = ModelImportExport.buildExportEnvelope({
|
|
326
|
+
modelType: Workflow,
|
|
327
|
+
items: [original],
|
|
328
|
+
exportedAt: new Date(),
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
const exported: JSONObject = (envelope["items"] as Array<JSONObject>)[0]!;
|
|
332
|
+
|
|
333
|
+
expect(exported["graph"]).toEqual(graph);
|
|
334
|
+
expect(exported["isEnabled"]).toBe(true);
|
|
335
|
+
expect(exported["webhookSecretKey"]).toBeUndefined();
|
|
336
|
+
expect(exported["triggerId"]).toBeUndefined();
|
|
337
|
+
expect(exported["slug"]).toBeUndefined();
|
|
338
|
+
|
|
339
|
+
const imported: Workflow = ModelImportExport.fromImportJSON({
|
|
340
|
+
json: exported,
|
|
341
|
+
modelType: Workflow,
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
expect(imported.name).toBe("Deploy Notifier");
|
|
345
|
+
expect(imported.description).toBe("Posts to chat on deploy");
|
|
346
|
+
expect(imported.graph).toEqual(graph);
|
|
347
|
+
expect(imported.webhookSecretKey).toBeUndefined();
|
|
348
|
+
expect(imported.triggerId).toBeUndefined();
|
|
349
|
+
expect(imported._id).toBeUndefined();
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
test("should import workflows disabled even when exported enabled", () => {
|
|
353
|
+
/*
|
|
354
|
+
* An imported workflow can reference resources that do not exist here,
|
|
355
|
+
* and enabling it starts it firing - so it must land disabled and be
|
|
356
|
+
* turned on deliberately.
|
|
357
|
+
*/
|
|
358
|
+
const imported: Workflow = ModelImportExport.fromImportJSON({
|
|
359
|
+
json: {
|
|
360
|
+
name: "Pager Escalation",
|
|
361
|
+
isEnabled: true,
|
|
362
|
+
graph: { nodes: [], edges: [] },
|
|
363
|
+
},
|
|
364
|
+
modelType: Workflow,
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
expect(imported.name).toBe("Pager Escalation");
|
|
368
|
+
expect(imported.isEnabled).toBe(false);
|
|
369
|
+
});
|
|
370
|
+
|
|
270
371
|
test("should advance past recurring dates for scheduled maintenance templates", () => {
|
|
271
372
|
const dayInMs: number = 24 * 60 * 60 * 1000;
|
|
272
373
|
|