@oneuptime/common 11.5.13 → 11.6.0
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/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 +48 -45
- 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/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/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/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/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/ModelImportExport.test.ts +101 -0
- package/Tests/Utils/Number.test.ts +179 -0
- package/Tests/Utils/VersionUtil.test.ts +348 -0
- package/Tests/jest.setup.ts +1 -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/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 +44 -16
- 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/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/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
|
@@ -5,6 +5,7 @@ import FormFieldSchemaType from "../Forms/Types/FormFieldSchemaType";
|
|
|
5
5
|
import { CustomElementProps } from "../Forms/Types/Field";
|
|
6
6
|
import FormValues from "../Forms/Types/FormValues";
|
|
7
7
|
import ComponentValuePickerModal from "./ComponentValuePickerModal";
|
|
8
|
+
import CronScheduleField from "./CronScheduleField";
|
|
8
9
|
import ModelFieldPicker from "./ModelFieldPicker";
|
|
9
10
|
import { componentInputTypeToFormFieldType } from "./Utils";
|
|
10
11
|
import VariableModal from "./VariableModal";
|
|
@@ -198,40 +199,75 @@ const ArgumentsForm: FunctionComponent<ComponentProps> = (
|
|
|
198
199
|
arg.type === ComponentInputType.Select &&
|
|
199
200
|
Boolean(component.metadata.tableName);
|
|
200
201
|
|
|
201
|
-
|
|
202
|
+
/*
|
|
203
|
+
* The Schedule trigger's CronTab argument gets a dedicated
|
|
204
|
+
* cron picker (presets, custom expression with live preview,
|
|
205
|
+
* or a variable reference) instead of a bare dropdown.
|
|
206
|
+
*/
|
|
207
|
+
const useCronPicker: boolean =
|
|
208
|
+
arg.type === ComponentInputType.CronTab;
|
|
209
|
+
|
|
210
|
+
let baseField: {
|
|
202
211
|
fieldType: import("../Forms/Types/FormFieldSchemaType").default;
|
|
203
212
|
dropdownOptions?: Array<DropdownOption> | undefined;
|
|
204
213
|
getCustomElement?: (
|
|
205
214
|
values: FormValues<JSONObject>,
|
|
206
215
|
customProps: CustomElementProps,
|
|
207
216
|
) => ReactElement | undefined;
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
if (useFieldPicker) {
|
|
220
|
+
baseField = {
|
|
221
|
+
fieldType: FormFieldSchemaType.CustomComponent,
|
|
222
|
+
getCustomElement: (
|
|
223
|
+
_values: FormValues<JSONObject>,
|
|
224
|
+
customProps: CustomElementProps,
|
|
225
|
+
): ReactElement => {
|
|
226
|
+
return (
|
|
227
|
+
<ModelFieldPicker
|
|
228
|
+
tableName={component.metadata.tableName as string}
|
|
229
|
+
initialValue={customProps.initialValue}
|
|
230
|
+
onChange={(value: string) => {
|
|
231
|
+
void customProps.onChange?.(value);
|
|
232
|
+
}}
|
|
233
|
+
placeholder={customProps.placeholder}
|
|
234
|
+
error={customProps.error}
|
|
235
|
+
tabIndex={customProps.tabIndex}
|
|
236
|
+
/>
|
|
237
|
+
);
|
|
238
|
+
},
|
|
239
|
+
};
|
|
240
|
+
} else if (useCronPicker) {
|
|
241
|
+
baseField = {
|
|
242
|
+
fieldType: FormFieldSchemaType.CustomComponent,
|
|
243
|
+
getCustomElement: (
|
|
244
|
+
_values: FormValues<JSONObject>,
|
|
245
|
+
customProps: CustomElementProps,
|
|
246
|
+
): ReactElement => {
|
|
247
|
+
return (
|
|
248
|
+
<CronScheduleField
|
|
249
|
+
workflowId={props.workflowId}
|
|
250
|
+
initialValue={
|
|
251
|
+
customProps.initialValue as string | null
|
|
252
|
+
}
|
|
253
|
+
onChange={(value: string) => {
|
|
254
|
+
void customProps.onChange?.(value);
|
|
255
|
+
}}
|
|
256
|
+
placeholder={customProps.placeholder}
|
|
257
|
+
error={customProps.error}
|
|
258
|
+
tabIndex={customProps.tabIndex}
|
|
259
|
+
/>
|
|
260
|
+
);
|
|
261
|
+
},
|
|
262
|
+
};
|
|
263
|
+
} else {
|
|
264
|
+
baseField = componentInputTypeToFormFieldType(
|
|
265
|
+
arg.type,
|
|
266
|
+
component.arguments && component.arguments[arg.id]
|
|
267
|
+
? component.arguments[arg.id]
|
|
268
|
+
: null,
|
|
269
|
+
);
|
|
270
|
+
}
|
|
235
271
|
|
|
236
272
|
/*
|
|
237
273
|
* For WorkflowSelect, inject the dynamically fetched list
|
|
@@ -244,10 +280,12 @@ const ArgumentsForm: FunctionComponent<ComponentProps> = (
|
|
|
244
280
|
/*
|
|
245
281
|
* The "pick from component / variable" footer doesn't
|
|
246
282
|
* apply to the field picker (it edits a structured object,
|
|
247
|
-
* not a free-text expression) or to
|
|
283
|
+
* not a free-text expression), to WorkflowSelect, or to the
|
|
284
|
+
* cron picker (which has its own built-in variable selector,
|
|
285
|
+
* and a schedule can't reference component return values).
|
|
248
286
|
*/
|
|
249
287
|
const showVariableFooter: boolean =
|
|
250
|
-
!isWorkflowSelect && !useFieldPicker;
|
|
288
|
+
!isWorkflowSelect && !useFieldPicker && !useCronPicker;
|
|
251
289
|
|
|
252
290
|
return {
|
|
253
291
|
title: `${arg.name}`,
|
|
@@ -0,0 +1,411 @@
|
|
|
1
|
+
import Dropdown, { DropdownOption, DropdownValue } from "../Dropdown/Dropdown";
|
|
2
|
+
import Icon from "../Icon/Icon";
|
|
3
|
+
import VariableModal from "./VariableModal";
|
|
4
|
+
import IconProp from "../../../Types/Icon/IconProp";
|
|
5
|
+
import ObjectID from "../../../Types/ObjectID";
|
|
6
|
+
import CronTab from "../../../Utils/CronTab";
|
|
7
|
+
import React, {
|
|
8
|
+
FunctionComponent,
|
|
9
|
+
ReactElement,
|
|
10
|
+
useMemo,
|
|
11
|
+
useState,
|
|
12
|
+
} from "react";
|
|
13
|
+
|
|
14
|
+
export interface ComponentProps {
|
|
15
|
+
workflowId: ObjectID;
|
|
16
|
+
initialValue?: string | null | undefined;
|
|
17
|
+
onChange: (value: string) => void;
|
|
18
|
+
error?: string | undefined;
|
|
19
|
+
placeholder?: string | undefined;
|
|
20
|
+
tabIndex?: number | undefined;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
type ScheduleMode = "preset" | "custom" | "variable";
|
|
24
|
+
|
|
25
|
+
const PRESET_OPTIONS: Array<DropdownOption> = CronTab.PRESETS.map(
|
|
26
|
+
(preset: { label: string; value: string }) => {
|
|
27
|
+
return {
|
|
28
|
+
label: preset.label,
|
|
29
|
+
value: preset.value,
|
|
30
|
+
};
|
|
31
|
+
},
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
const PRESET_VALUES: Set<string> = new Set(
|
|
35
|
+
CronTab.PRESETS.map((preset: { value: string }) => {
|
|
36
|
+
return preset.value;
|
|
37
|
+
}),
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
const normalizeInitialValue: (value: string | null | undefined) => string = (
|
|
41
|
+
value: string | null | undefined,
|
|
42
|
+
): string => {
|
|
43
|
+
if (value === null || value === undefined) {
|
|
44
|
+
return "";
|
|
45
|
+
}
|
|
46
|
+
return `${value}`;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const inferInitialMode: (value: string) => ScheduleMode = (
|
|
50
|
+
value: string,
|
|
51
|
+
): ScheduleMode => {
|
|
52
|
+
const trimmed: string = value.trim();
|
|
53
|
+
|
|
54
|
+
if (CronTab.isVariableExpression(trimmed)) {
|
|
55
|
+
return "variable";
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (trimmed !== "" && !PRESET_VALUES.has(trimmed)) {
|
|
59
|
+
return "custom";
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return "preset";
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const formatUtc: (date: Date) => string = (date: Date): string => {
|
|
66
|
+
try {
|
|
67
|
+
return (
|
|
68
|
+
new Intl.DateTimeFormat("en-US", {
|
|
69
|
+
timeZone: "UTC",
|
|
70
|
+
weekday: "short",
|
|
71
|
+
year: "numeric",
|
|
72
|
+
month: "short",
|
|
73
|
+
day: "numeric",
|
|
74
|
+
hour: "2-digit",
|
|
75
|
+
minute: "2-digit",
|
|
76
|
+
hour12: false,
|
|
77
|
+
}).format(date) + " UTC"
|
|
78
|
+
);
|
|
79
|
+
} catch {
|
|
80
|
+
return date.toUTCString();
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* A rich picker for the workflow "Schedule" trigger. It lets the user choose a
|
|
86
|
+
* common schedule from a preset list, write any custom cron expression (with a
|
|
87
|
+
* live human-readable description, next-run preview and validation), or point
|
|
88
|
+
* the schedule at a workflow / global variable that resolves to a cron
|
|
89
|
+
* expression when the workflow is saved.
|
|
90
|
+
*/
|
|
91
|
+
const CronScheduleField: FunctionComponent<ComponentProps> = (
|
|
92
|
+
props: ComponentProps,
|
|
93
|
+
): ReactElement => {
|
|
94
|
+
const initialValue: string = useMemo(() => {
|
|
95
|
+
return normalizeInitialValue(props.initialValue);
|
|
96
|
+
}, []);
|
|
97
|
+
|
|
98
|
+
const [value, setValue] = useState<string>(initialValue);
|
|
99
|
+
const [mode, setMode] = useState<ScheduleMode>(
|
|
100
|
+
inferInitialMode(initialValue),
|
|
101
|
+
);
|
|
102
|
+
const [showVariableModal, setShowVariableModal] = useState<boolean>(false);
|
|
103
|
+
|
|
104
|
+
const emit: (next: string) => void = (next: string): void => {
|
|
105
|
+
setValue(next);
|
|
106
|
+
props.onChange(next);
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
const trimmedValue: string = value.trim();
|
|
110
|
+
const isVariable: boolean = CronTab.isVariableExpression(trimmedValue);
|
|
111
|
+
|
|
112
|
+
const validationError: string | null = useMemo(() => {
|
|
113
|
+
if (trimmedValue === "" || isVariable) {
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
return CronTab.getValidationError(trimmedValue);
|
|
117
|
+
}, [trimmedValue, isVariable]);
|
|
118
|
+
|
|
119
|
+
const description: string | null = useMemo(() => {
|
|
120
|
+
if (trimmedValue === "" || isVariable || validationError) {
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
return CronTab.getHumanReadableDescription(trimmedValue);
|
|
124
|
+
}, [trimmedValue, isVariable, validationError]);
|
|
125
|
+
|
|
126
|
+
const nextRuns: Array<Date> = useMemo(() => {
|
|
127
|
+
if (trimmedValue === "" || isVariable || validationError) {
|
|
128
|
+
return [];
|
|
129
|
+
}
|
|
130
|
+
try {
|
|
131
|
+
return CronTab.getNextExecutionTimes(trimmedValue, 3, new Date());
|
|
132
|
+
} catch {
|
|
133
|
+
return [];
|
|
134
|
+
}
|
|
135
|
+
}, [trimmedValue, isVariable, validationError]);
|
|
136
|
+
|
|
137
|
+
const selectedPresetOption: DropdownOption | undefined = PRESET_OPTIONS.find(
|
|
138
|
+
(option: DropdownOption) => {
|
|
139
|
+
return option.value === trimmedValue;
|
|
140
|
+
},
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
const renderModeTab: (args: {
|
|
144
|
+
tabMode: ScheduleMode;
|
|
145
|
+
label: string;
|
|
146
|
+
icon: IconProp;
|
|
147
|
+
}) => ReactElement = (args: {
|
|
148
|
+
tabMode: ScheduleMode;
|
|
149
|
+
label: string;
|
|
150
|
+
icon: IconProp;
|
|
151
|
+
}): ReactElement => {
|
|
152
|
+
const isActive: boolean = mode === args.tabMode;
|
|
153
|
+
return (
|
|
154
|
+
<button
|
|
155
|
+
type="button"
|
|
156
|
+
onClick={() => {
|
|
157
|
+
setMode(args.tabMode);
|
|
158
|
+
}}
|
|
159
|
+
className={`inline-flex items-center gap-1.5 rounded-md px-3 py-1.5 text-xs font-medium transition-colors ${
|
|
160
|
+
isActive
|
|
161
|
+
? "bg-white text-indigo-700 shadow-sm ring-1 ring-gray-200"
|
|
162
|
+
: "text-gray-500 hover:text-gray-700"
|
|
163
|
+
}`}
|
|
164
|
+
>
|
|
165
|
+
<Icon
|
|
166
|
+
icon={args.icon}
|
|
167
|
+
className={`h-3.5 w-3.5 ${
|
|
168
|
+
isActive ? "text-indigo-600" : "text-gray-400"
|
|
169
|
+
}`}
|
|
170
|
+
/>
|
|
171
|
+
{args.label}
|
|
172
|
+
</button>
|
|
173
|
+
);
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
const renderPreview: () => ReactElement | null = (): ReactElement | null => {
|
|
177
|
+
if (isVariable) {
|
|
178
|
+
return null;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (validationError) {
|
|
182
|
+
return (
|
|
183
|
+
<div className="flex items-start gap-2 rounded-md border border-red-200 bg-red-50/60 px-3 py-2 text-xs text-red-800">
|
|
184
|
+
<Icon
|
|
185
|
+
icon={IconProp.Error}
|
|
186
|
+
className="h-4 w-4 text-red-500 mt-0.5 flex-shrink-0"
|
|
187
|
+
/>
|
|
188
|
+
<div>
|
|
189
|
+
<p className="font-semibold">Not a valid cron expression</p>
|
|
190
|
+
<p className="text-red-700">{validationError}</p>
|
|
191
|
+
</div>
|
|
192
|
+
</div>
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if (!description) {
|
|
197
|
+
return null;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
return (
|
|
201
|
+
<div className="rounded-md border border-gray-200 bg-gray-50/60 px-3 py-2.5">
|
|
202
|
+
<div className="flex items-start gap-2">
|
|
203
|
+
<Icon
|
|
204
|
+
icon={IconProp.Clock}
|
|
205
|
+
className="h-4 w-4 text-indigo-500 mt-0.5 flex-shrink-0"
|
|
206
|
+
/>
|
|
207
|
+
<div className="min-w-0">
|
|
208
|
+
<p className="text-sm font-medium text-gray-800">{description}</p>
|
|
209
|
+
{nextRuns.length > 0 && (
|
|
210
|
+
<div className="mt-1.5">
|
|
211
|
+
<p className="text-[10px] font-semibold uppercase tracking-wider text-gray-400">
|
|
212
|
+
Next runs
|
|
213
|
+
</p>
|
|
214
|
+
<ul className="mt-0.5 space-y-0.5">
|
|
215
|
+
{nextRuns.map((run: Date, index: number) => {
|
|
216
|
+
return (
|
|
217
|
+
<li
|
|
218
|
+
key={index}
|
|
219
|
+
className="text-xs text-gray-600 font-mono"
|
|
220
|
+
>
|
|
221
|
+
{formatUtc(run)}
|
|
222
|
+
</li>
|
|
223
|
+
);
|
|
224
|
+
})}
|
|
225
|
+
</ul>
|
|
226
|
+
</div>
|
|
227
|
+
)}
|
|
228
|
+
</div>
|
|
229
|
+
</div>
|
|
230
|
+
</div>
|
|
231
|
+
);
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
return (
|
|
235
|
+
<div className="space-y-3">
|
|
236
|
+
{/* Mode selector */}
|
|
237
|
+
<div className="inline-flex items-center gap-1 rounded-lg bg-gray-100 p-1">
|
|
238
|
+
{renderModeTab({
|
|
239
|
+
tabMode: "preset",
|
|
240
|
+
label: "Common schedules",
|
|
241
|
+
icon: IconProp.Clock,
|
|
242
|
+
})}
|
|
243
|
+
{renderModeTab({
|
|
244
|
+
tabMode: "custom",
|
|
245
|
+
label: "Custom cron",
|
|
246
|
+
icon: IconProp.Code,
|
|
247
|
+
})}
|
|
248
|
+
{renderModeTab({
|
|
249
|
+
tabMode: "variable",
|
|
250
|
+
label: "Variable",
|
|
251
|
+
icon: IconProp.Variable,
|
|
252
|
+
})}
|
|
253
|
+
</div>
|
|
254
|
+
|
|
255
|
+
{mode === "preset" && (
|
|
256
|
+
<div className="space-y-3">
|
|
257
|
+
<Dropdown
|
|
258
|
+
options={PRESET_OPTIONS}
|
|
259
|
+
value={selectedPresetOption}
|
|
260
|
+
placeholder="Select a schedule"
|
|
261
|
+
tabIndex={props.tabIndex}
|
|
262
|
+
onChange={(
|
|
263
|
+
newValue: DropdownValue | Array<DropdownValue> | null,
|
|
264
|
+
) => {
|
|
265
|
+
if (typeof newValue === "string") {
|
|
266
|
+
emit(newValue);
|
|
267
|
+
}
|
|
268
|
+
}}
|
|
269
|
+
/>
|
|
270
|
+
{!selectedPresetOption && trimmedValue !== "" && !isVariable && (
|
|
271
|
+
<p className="text-xs text-gray-500">
|
|
272
|
+
Your current schedule{" "}
|
|
273
|
+
<code className="rounded bg-gray-100 px-1 py-0.5 font-mono text-gray-700">
|
|
274
|
+
{trimmedValue}
|
|
275
|
+
</code>{" "}
|
|
276
|
+
is not one of the presets. Switch to{" "}
|
|
277
|
+
<button
|
|
278
|
+
type="button"
|
|
279
|
+
className="text-indigo-600 underline hover:text-indigo-700"
|
|
280
|
+
onClick={() => {
|
|
281
|
+
setMode("custom");
|
|
282
|
+
}}
|
|
283
|
+
>
|
|
284
|
+
Custom cron
|
|
285
|
+
</button>{" "}
|
|
286
|
+
to edit it.
|
|
287
|
+
</p>
|
|
288
|
+
)}
|
|
289
|
+
{renderPreview()}
|
|
290
|
+
</div>
|
|
291
|
+
)}
|
|
292
|
+
|
|
293
|
+
{mode === "custom" && (
|
|
294
|
+
<div className="space-y-3">
|
|
295
|
+
<input
|
|
296
|
+
type="text"
|
|
297
|
+
value={value}
|
|
298
|
+
spellCheck={false}
|
|
299
|
+
autoComplete="off"
|
|
300
|
+
tabIndex={props.tabIndex}
|
|
301
|
+
placeholder={props.placeholder || "e.g. 0 */18 * * *"}
|
|
302
|
+
onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
|
|
303
|
+
emit(event.target.value);
|
|
304
|
+
}}
|
|
305
|
+
className={`block w-full rounded-md border bg-white px-3 py-2 font-mono text-sm placeholder-gray-400 focus:outline-none focus:ring-1 ${
|
|
306
|
+
validationError
|
|
307
|
+
? "border-red-300 focus:border-red-500 focus:ring-red-500"
|
|
308
|
+
: "border-gray-300 focus:border-indigo-500 focus:ring-indigo-500"
|
|
309
|
+
}`}
|
|
310
|
+
/>
|
|
311
|
+
<p className="text-xs text-gray-400 font-mono">
|
|
312
|
+
minute hour day-of-month month day-of-week
|
|
313
|
+
</p>
|
|
314
|
+
{renderPreview()}
|
|
315
|
+
</div>
|
|
316
|
+
)}
|
|
317
|
+
|
|
318
|
+
{mode === "variable" && (
|
|
319
|
+
<div className="space-y-3">
|
|
320
|
+
{isVariable ? (
|
|
321
|
+
<div className="flex items-center justify-between gap-3 rounded-md border border-indigo-200 bg-indigo-50/50 px-3 py-2">
|
|
322
|
+
<div className="flex items-center gap-2 min-w-0">
|
|
323
|
+
<Icon
|
|
324
|
+
icon={IconProp.Variable}
|
|
325
|
+
className="h-4 w-4 text-indigo-500 flex-shrink-0"
|
|
326
|
+
/>
|
|
327
|
+
<code className="truncate font-mono text-sm text-indigo-800">
|
|
328
|
+
{trimmedValue}
|
|
329
|
+
</code>
|
|
330
|
+
</div>
|
|
331
|
+
<div className="flex items-center gap-2 flex-shrink-0">
|
|
332
|
+
<button
|
|
333
|
+
type="button"
|
|
334
|
+
className="text-xs font-medium text-indigo-600 hover:text-indigo-700"
|
|
335
|
+
onClick={() => {
|
|
336
|
+
setShowVariableModal(true);
|
|
337
|
+
}}
|
|
338
|
+
>
|
|
339
|
+
Change
|
|
340
|
+
</button>
|
|
341
|
+
<span className="text-gray-300">·</span>
|
|
342
|
+
<button
|
|
343
|
+
type="button"
|
|
344
|
+
className="text-xs font-medium text-gray-500 hover:text-gray-700"
|
|
345
|
+
onClick={() => {
|
|
346
|
+
emit("");
|
|
347
|
+
setMode("preset");
|
|
348
|
+
}}
|
|
349
|
+
>
|
|
350
|
+
Clear
|
|
351
|
+
</button>
|
|
352
|
+
</div>
|
|
353
|
+
</div>
|
|
354
|
+
) : (
|
|
355
|
+
<button
|
|
356
|
+
type="button"
|
|
357
|
+
onClick={() => {
|
|
358
|
+
setShowVariableModal(true);
|
|
359
|
+
}}
|
|
360
|
+
className="inline-flex items-center gap-1.5 rounded-md border border-gray-300 bg-white px-3 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 hover:border-gray-400"
|
|
361
|
+
>
|
|
362
|
+
<Icon
|
|
363
|
+
icon={IconProp.Variable}
|
|
364
|
+
className="h-4 w-4 text-gray-500"
|
|
365
|
+
/>
|
|
366
|
+
Select a variable
|
|
367
|
+
</button>
|
|
368
|
+
)}
|
|
369
|
+
|
|
370
|
+
<div className="flex items-start gap-2 rounded-md border border-gray-200 bg-gray-50/60 px-3 py-2 text-xs text-gray-600">
|
|
371
|
+
<Icon
|
|
372
|
+
icon={IconProp.Info}
|
|
373
|
+
className="h-4 w-4 text-gray-400 mt-0.5 flex-shrink-0"
|
|
374
|
+
/>
|
|
375
|
+
<p>
|
|
376
|
+
The variable is resolved when the workflow is saved. It must
|
|
377
|
+
contain a valid cron expression (for example{" "}
|
|
378
|
+
<code className="rounded bg-gray-100 px-1 py-0.5 font-mono text-gray-700">
|
|
379
|
+
0 */18 * * *
|
|
380
|
+
</code>
|
|
381
|
+
). Component return values can't be used here because the
|
|
382
|
+
schedule is set up before the workflow runs.
|
|
383
|
+
</p>
|
|
384
|
+
</div>
|
|
385
|
+
</div>
|
|
386
|
+
)}
|
|
387
|
+
|
|
388
|
+
{props.error && (
|
|
389
|
+
<p className="text-xs text-red-600" data-testid="error-message">
|
|
390
|
+
{props.error}
|
|
391
|
+
</p>
|
|
392
|
+
)}
|
|
393
|
+
|
|
394
|
+
{showVariableModal && (
|
|
395
|
+
<VariableModal
|
|
396
|
+
workflowId={props.workflowId}
|
|
397
|
+
onClose={() => {
|
|
398
|
+
setShowVariableModal(false);
|
|
399
|
+
}}
|
|
400
|
+
onSave={(variableId: string) => {
|
|
401
|
+
setShowVariableModal(false);
|
|
402
|
+
setMode("variable");
|
|
403
|
+
emit(variableId);
|
|
404
|
+
}}
|
|
405
|
+
/>
|
|
406
|
+
)}
|
|
407
|
+
</div>
|
|
408
|
+
);
|
|
409
|
+
};
|
|
410
|
+
|
|
411
|
+
export default CronScheduleField;
|