@dxos/plugin-automation 0.8.4-staging.60fe92afc8 → 0.9.1-main.c7dcc2e112
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/dist/lib/neutral/{AutomationArticle-GN36NUX2.mjs → AutomationArticle-CG4ZML3C.mjs} +3 -2
- package/dist/lib/neutral/{AutomationCompanion-M26WR6VP.mjs → AutomationCompanion-67LW2WZS.mjs} +12 -13
- package/dist/lib/neutral/AutomationCompanion-67LW2WZS.mjs.map +7 -0
- package/dist/lib/neutral/AutomationPlugin.mjs +1 -1
- package/dist/lib/neutral/AutomationPlugin.node.mjs +4 -4
- package/dist/lib/neutral/AutomationPlugin.node.mjs.map +3 -3
- package/dist/lib/neutral/{AutomationSettings-YXUJDRQA.mjs → AutomationSettings-2XCCFX6X.mjs} +5 -5
- package/dist/lib/neutral/{AutomationSettings-YXUJDRQA.mjs.map → AutomationSettings-2XCCFX6X.mjs.map} +3 -3
- package/dist/lib/neutral/{TriggerSettings-XCHIZPOR.mjs → TriggerSettings-ABOTKRUA.mjs} +2 -2
- package/dist/lib/neutral/{app-graph-builder-BTTHS4VK.mjs → app-graph-builder-VX54SXD6.mjs} +6 -6
- package/dist/lib/neutral/app-graph-builder-VX54SXD6.mjs.map +7 -0
- package/dist/lib/neutral/capabilities/index.mjs +3 -3
- package/dist/lib/neutral/capabilities/node.mjs +1 -1
- package/dist/lib/neutral/{chunk-2JP77CMN.mjs → chunk-73DGSL37.mjs} +2 -2
- package/dist/lib/neutral/chunk-73DGSL37.mjs.map +7 -0
- package/dist/lib/neutral/{chunk-HPUHQ3L6.mjs → chunk-77QU5RSC.mjs} +3 -3
- package/dist/lib/neutral/chunk-77QU5RSC.mjs.map +7 -0
- package/dist/lib/neutral/chunk-D4RCXOP4.mjs +13 -0
- package/dist/lib/neutral/chunk-D4RCXOP4.mjs.map +7 -0
- package/dist/lib/neutral/{chunk-SONGOJRB.mjs → chunk-EJ5J22XS.mjs} +2 -2
- package/dist/lib/neutral/chunk-HHIFH3N3.mjs +45 -0
- package/dist/lib/neutral/chunk-HHIFH3N3.mjs.map +7 -0
- package/dist/lib/neutral/chunk-HQU5QWF4.mjs +349 -0
- package/dist/lib/neutral/chunk-HQU5QWF4.mjs.map +7 -0
- package/dist/lib/neutral/{chunk-FE7YFBX7.mjs → chunk-LVUL7GIN.mjs} +151 -60
- package/dist/lib/neutral/chunk-LVUL7GIN.mjs.map +7 -0
- package/dist/lib/neutral/{chunk-GARB7S5R.mjs → chunk-YHK7FWGV.mjs} +5 -3
- package/dist/lib/neutral/chunk-YHK7FWGV.mjs.map +7 -0
- package/dist/lib/neutral/components/index.mjs +36 -2
- package/dist/lib/neutral/components/index.mjs.map +3 -3
- package/dist/lib/neutral/containers/index.mjs +4 -4
- package/dist/lib/neutral/{create-automation-OE3TNXYE.mjs → create-automation-AGYTF62E.mjs} +5 -9
- package/dist/lib/neutral/create-automation-AGYTF62E.mjs.map +7 -0
- package/dist/lib/neutral/{create-trigger-from-template-TERHKWJM.mjs → create-trigger-from-template-TYGCSR3Z.mjs} +5 -5
- package/dist/lib/neutral/create-trigger-from-template-TYGCSR3Z.mjs.map +7 -0
- package/dist/lib/neutral/index.mjs +10 -2
- package/dist/lib/neutral/meta.json +1 -1
- package/dist/lib/neutral/meta.mjs +1 -1
- package/dist/lib/neutral/{navigation-resolver-I3L5FHJO.mjs → navigation-resolver-FSJNF3DQ.mjs} +3 -3
- package/dist/lib/neutral/navigation-resolver-FSJNF3DQ.mjs.map +7 -0
- package/dist/lib/neutral/operations/index.mjs +1 -1
- package/dist/lib/neutral/plugin.mjs +2 -2
- package/dist/lib/neutral/{react-surface-IBRWUKPC.mjs → react-surface-W3J3CDHA.mjs} +2 -2
- package/dist/lib/neutral/{react-surface-IBRWUKPC.mjs.map → react-surface-W3J3CDHA.mjs.map} +3 -3
- package/dist/lib/neutral/testing.mjs +1 -1
- package/dist/lib/neutral/translations.mjs +6 -2
- package/dist/lib/neutral/translations.mjs.map +3 -3
- package/dist/lib/neutral/types/index.mjs +1 -1
- package/dist/types/dx.config.d.ts +28 -0
- package/dist/types/dx.config.d.ts.map +1 -0
- package/dist/types/src/capabilities/index.d.ts +8 -13
- package/dist/types/src/capabilities/index.d.ts.map +1 -1
- package/dist/types/src/capabilities/navigation-resolver.d.ts.map +1 -1
- package/dist/types/src/capabilities/node.d.ts +5 -5
- package/dist/types/src/capabilities/node.d.ts.map +1 -1
- package/dist/types/src/capabilities/react-surface.d.ts +2 -2
- package/dist/types/src/capabilities/react-surface.d.ts.map +1 -1
- package/dist/types/src/components/CronBuilder/CronBuilder.d.ts +16 -0
- package/dist/types/src/components/CronBuilder/CronBuilder.d.ts.map +1 -0
- package/dist/types/src/components/CronBuilder/CronBuilder.stories.d.ts +194 -0
- package/dist/types/src/components/CronBuilder/CronBuilder.stories.d.ts.map +1 -0
- package/dist/types/src/components/CronBuilder/cron.d.ts +17 -0
- package/dist/types/src/components/CronBuilder/cron.d.ts.map +1 -0
- package/dist/types/src/components/CronBuilder/cron.test.d.ts +2 -0
- package/dist/types/src/components/CronBuilder/cron.test.d.ts.map +1 -0
- package/dist/types/src/components/CronBuilder/index.d.ts +4 -0
- package/dist/types/src/components/CronBuilder/index.d.ts.map +1 -0
- package/dist/types/src/components/CronBuilder/schema.d.ts +96 -0
- package/dist/types/src/components/CronBuilder/schema.d.ts.map +1 -0
- package/dist/types/src/components/index.d.ts +1 -0
- package/dist/types/src/components/index.d.ts.map +1 -1
- package/dist/types/src/containers/AutomationArticle/AutomationArticle.d.ts +6 -0
- package/dist/types/src/containers/AutomationArticle/AutomationArticle.d.ts.map +1 -1
- package/dist/types/src/containers/AutomationArticle/AutomationArticle.stories.d.ts +120 -0
- package/dist/types/src/containers/AutomationArticle/AutomationArticle.stories.d.ts.map +1 -0
- package/dist/types/src/containers/AutomationCompanion/AutomationCompanion.d.ts.map +1 -1
- package/dist/types/src/index.d.ts +1 -0
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/meta.d.ts +28 -2
- package/dist/types/src/meta.d.ts.map +1 -1
- package/dist/types/src/paths.d.ts +2 -0
- package/dist/types/src/paths.d.ts.map +1 -1
- package/dist/types/src/translations.d.ts +9 -1
- package/dist/types/src/translations.d.ts.map +1 -1
- package/dist/types/src/types/AutomationCapabilities.d.ts +6 -1
- package/dist/types/src/types/AutomationCapabilities.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/dx.config.ts +34 -0
- package/package.json +40 -38
- package/src/AutomationPlugin.test.ts +1 -1
- package/src/AutomationPlugin.tsx +1 -1
- package/src/capabilities/app-graph-builder.ts +5 -5
- package/src/capabilities/navigation-resolver.ts +2 -2
- package/src/capabilities/react-surface.tsx +1 -1
- package/src/commands/trigger/create/queue.ts +2 -2
- package/src/commands/trigger/update/queue.ts +2 -2
- package/src/components/CreateAutomationPanel/CreateAutomationPanel.tsx +1 -1
- package/src/components/CronBuilder/CronBuilder.stories.tsx +72 -0
- package/src/components/CronBuilder/CronBuilder.tsx +94 -0
- package/src/components/CronBuilder/cron.test.ts +131 -0
- package/src/components/CronBuilder/cron.ts +138 -0
- package/src/components/CronBuilder/index.ts +7 -0
- package/src/components/CronBuilder/schema.ts +119 -0
- package/src/components/index.ts +1 -0
- package/src/containers/AutomationArticle/AutomationArticle.stories.tsx +73 -0
- package/src/containers/AutomationArticle/AutomationArticle.tsx +186 -56
- package/src/containers/AutomationCompanion/AutomationCompanion.tsx +79 -72
- package/src/containers/AutomationSettings/AutomationSettings.tsx +3 -3
- package/src/containers/TriggerSettings/TriggerSettings.tsx +1 -1
- package/src/index.ts +1 -0
- package/src/meta.ts +2 -26
- package/src/operations/create-trigger-from-template.ts +3 -3
- package/src/paths.ts +7 -2
- package/src/translations.ts +7 -2
- package/src/types/AutomationCapabilities.ts +9 -1
- package/src/types/AutomationOperation.ts +1 -1
- package/src/types/schema.ts +1 -1
- package/dist/lib/neutral/AutomationCompanion-M26WR6VP.mjs.map +0 -7
- package/dist/lib/neutral/app-graph-builder-BTTHS4VK.mjs.map +0 -7
- package/dist/lib/neutral/chunk-2JP77CMN.mjs.map +0 -7
- package/dist/lib/neutral/chunk-DUGOIM7G.mjs +0 -36
- package/dist/lib/neutral/chunk-DUGOIM7G.mjs.map +0 -7
- package/dist/lib/neutral/chunk-FE7YFBX7.mjs.map +0 -7
- package/dist/lib/neutral/chunk-GARB7S5R.mjs.map +0 -7
- package/dist/lib/neutral/chunk-HPUHQ3L6.mjs.map +0 -7
- package/dist/lib/neutral/create-automation-OE3TNXYE.mjs.map +0 -7
- package/dist/lib/neutral/create-trigger-from-template-TERHKWJM.mjs.map +0 -7
- package/dist/lib/neutral/navigation-resolver-I3L5FHJO.mjs.map +0 -7
- /package/dist/lib/neutral/{AutomationArticle-GN36NUX2.mjs.map → AutomationArticle-CG4ZML3C.mjs.map} +0 -0
- /package/dist/lib/neutral/{TriggerSettings-XCHIZPOR.mjs.map → TriggerSettings-ABOTKRUA.mjs.map} +0 -0
- /package/dist/lib/neutral/{chunk-SONGOJRB.mjs.map → chunk-EJ5J22XS.mjs.map} +0 -0
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import cronstrue from 'cronstrue';
|
|
6
|
+
|
|
7
|
+
import type { CronSpecType, DayOfWeek } from './schema';
|
|
8
|
+
|
|
9
|
+
const DOW_MAP: Record<DayOfWeek, number> = {
|
|
10
|
+
sun: 0,
|
|
11
|
+
mon: 1,
|
|
12
|
+
tue: 2,
|
|
13
|
+
wed: 3,
|
|
14
|
+
thu: 4,
|
|
15
|
+
fri: 5,
|
|
16
|
+
sat: 6,
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Derives a 5-field standard cron expression from a decomposed CronSpec value.
|
|
21
|
+
* Fields: minute hour day-of-month month day-of-week
|
|
22
|
+
*/
|
|
23
|
+
export const toCron = (value: CronSpecType): string => {
|
|
24
|
+
switch (value.frequency) {
|
|
25
|
+
case 'minutely':
|
|
26
|
+
return `*/${value.interval} * * * *`;
|
|
27
|
+
|
|
28
|
+
case 'hourly':
|
|
29
|
+
return `${value.minute} */${value.interval} * * *`;
|
|
30
|
+
|
|
31
|
+
case 'daily':
|
|
32
|
+
return `${value.minute} ${value.hour} * * *`;
|
|
33
|
+
|
|
34
|
+
case 'weekly': {
|
|
35
|
+
const dow = value.daysOfWeek.length > 0 ? value.daysOfWeek.map((d) => DOW_MAP[d]).join(',') : '*';
|
|
36
|
+
return `${value.minute} ${value.hour} * * ${dow}`;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
case 'monthly': {
|
|
40
|
+
const dom = value.daysOfMonth.length > 0 ? value.daysOfMonth.join(',') : '*';
|
|
41
|
+
return `${value.minute} ${value.hour} ${dom} * *`;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
case 'custom':
|
|
45
|
+
return value.cronExpression || '* * * * *';
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const DOW_REVERSE: Partial<Record<string, DayOfWeek>> = {
|
|
50
|
+
'0': 'sun',
|
|
51
|
+
'1': 'mon',
|
|
52
|
+
'2': 'tue',
|
|
53
|
+
'3': 'wed',
|
|
54
|
+
'4': 'thu',
|
|
55
|
+
'5': 'fri',
|
|
56
|
+
'6': 'sat',
|
|
57
|
+
// Both 0 and 7 represent Sunday in standard cron.
|
|
58
|
+
'7': 'sun',
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
/** Returns a non-negative integer if the token is a valid decimal integer, otherwise undefined. */
|
|
62
|
+
const parseUInt = (token: string): number | undefined => (/^\d+$/.test(token) ? Number(token) : undefined);
|
|
63
|
+
|
|
64
|
+
/** Returns the parsed integer if it falls within [min, max], otherwise undefined. */
|
|
65
|
+
const parseBoundedUInt = (token: string, min: number, max: number): number | undefined => {
|
|
66
|
+
const value = parseUInt(token);
|
|
67
|
+
return value !== undefined && value >= min && value <= max ? value : undefined;
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Parses a 5-field cron expression into a CronSpec value.
|
|
72
|
+
* Recognises common patterns (minutely, hourly, daily, weekly, monthly) and falls back to `custom`.
|
|
73
|
+
*/
|
|
74
|
+
export const fromCron = (cron: string): CronSpecType => {
|
|
75
|
+
const parts = cron.trim().split(/\s+/);
|
|
76
|
+
if (parts.length !== 5) {
|
|
77
|
+
return { frequency: 'custom', cronExpression: cron };
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const [minute, hour, dom, month, dow] = parts;
|
|
81
|
+
|
|
82
|
+
// minutely: */N * * * *
|
|
83
|
+
const minutelyMatch = minute.match(/^\*\/(\d+)$/);
|
|
84
|
+
if (minutelyMatch && hour === '*' && dom === '*' && month === '*' && dow === '*') {
|
|
85
|
+
return { frequency: 'minutely', interval: parseInt(minutelyMatch[1]) };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// hourly: M */N * * *
|
|
89
|
+
const hourlyMatch = hour.match(/^\*\/(\d+)$/);
|
|
90
|
+
const minuteNum = parseBoundedUInt(minute, 0, 59);
|
|
91
|
+
if (hourlyMatch && dom === '*' && month === '*' && dow === '*' && minuteNum !== undefined) {
|
|
92
|
+
return { frequency: 'hourly', interval: parseInt(hourlyMatch[1]), minute: minuteNum };
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const hourNum = parseBoundedUInt(hour, 0, 23);
|
|
96
|
+
if (minuteNum === undefined || hourNum === undefined) {
|
|
97
|
+
return { frequency: 'custom', cronExpression: cron };
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// weekly: M H * * DOW[,DOW...] (numeric DOW values only; all tokens must be valid)
|
|
101
|
+
if (dom === '*' && month === '*' && dow !== '*') {
|
|
102
|
+
const tokens = dow.split(',');
|
|
103
|
+
const days = tokens.map((d) => DOW_REVERSE[d]).filter((d): d is DayOfWeek => d != null);
|
|
104
|
+
if (days.length === tokens.length && days.length > 0) {
|
|
105
|
+
return { frequency: 'weekly', daysOfWeek: days, hour: hourNum, minute: minuteNum };
|
|
106
|
+
}
|
|
107
|
+
return { frequency: 'custom', cronExpression: cron };
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// monthly: M H DOM[,DOM...] * * (all tokens must be valid integers in 1-31)
|
|
111
|
+
if (dom !== '*' && month === '*' && dow === '*') {
|
|
112
|
+
const tokens = dom.split(',');
|
|
113
|
+
const days = tokens.map((d) => parseBoundedUInt(d, 1, 31)).filter((d): d is number => d !== undefined);
|
|
114
|
+
if (days.length === tokens.length && days.length > 0) {
|
|
115
|
+
return { frequency: 'monthly', daysOfMonth: days, hour: hourNum, minute: minuteNum };
|
|
116
|
+
}
|
|
117
|
+
return { frequency: 'custom', cronExpression: cron };
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// daily: M H * * *
|
|
121
|
+
if (dom === '*' && month === '*' && dow === '*') {
|
|
122
|
+
return { frequency: 'daily', hour: hourNum, minute: minuteNum };
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return { frequency: 'custom', cronExpression: cron };
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Returns a human-readable description of a cron expression, e.g. "At 09:00 on Monday".
|
|
130
|
+
* Falls back to the raw expression string on parse errors.
|
|
131
|
+
*/
|
|
132
|
+
export const describeCron = (cron: string): string => {
|
|
133
|
+
try {
|
|
134
|
+
return cronstrue.toString(cron, { throwExceptionOnParseError: true });
|
|
135
|
+
} catch {
|
|
136
|
+
return cron;
|
|
137
|
+
}
|
|
138
|
+
};
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import * as Schema from 'effect/Schema';
|
|
6
|
+
|
|
7
|
+
export const MinutelySpec = Schema.Struct({
|
|
8
|
+
frequency: Schema.Literal('minutely').annotations({ title: 'Frequency' }),
|
|
9
|
+
interval: Schema.Number.pipe(Schema.between(1, 59)).annotations({
|
|
10
|
+
title: 'Every (minutes)',
|
|
11
|
+
description: '1–59',
|
|
12
|
+
}),
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
export const HourlySpec = Schema.Struct({
|
|
16
|
+
frequency: Schema.Literal('hourly').annotations({ title: 'Frequency' }),
|
|
17
|
+
interval: Schema.Number.pipe(Schema.between(1, 23)).annotations({
|
|
18
|
+
title: 'Every (hours)',
|
|
19
|
+
description: '1–23',
|
|
20
|
+
}),
|
|
21
|
+
minute: Schema.Number.pipe(Schema.between(0, 59)).annotations({
|
|
22
|
+
title: 'At minute',
|
|
23
|
+
description: '0–59',
|
|
24
|
+
}),
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
export const DailySpec = Schema.Struct({
|
|
28
|
+
frequency: Schema.Literal('daily').annotations({ title: 'Frequency' }),
|
|
29
|
+
hour: Schema.Number.pipe(Schema.between(0, 23)).annotations({
|
|
30
|
+
title: 'Hour',
|
|
31
|
+
description: '0–23',
|
|
32
|
+
}),
|
|
33
|
+
minute: Schema.Number.pipe(Schema.between(0, 59)).annotations({
|
|
34
|
+
title: 'Minute',
|
|
35
|
+
description: '0–59',
|
|
36
|
+
}),
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
// Named day-of-week literals for UX clarity; cron.ts maps them to numeric DOW values.
|
|
40
|
+
export const DayOfWeek = Schema.Literal('sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat');
|
|
41
|
+
export type DayOfWeek = Schema.Schema.Type<typeof DayOfWeek>;
|
|
42
|
+
|
|
43
|
+
export const WeeklySpec = Schema.Struct({
|
|
44
|
+
frequency: Schema.Literal('weekly').annotations({ title: 'Frequency' }),
|
|
45
|
+
daysOfWeek: Schema.Array(DayOfWeek.annotations({ title: 'Day' })).annotations({ title: 'Days of week' }),
|
|
46
|
+
hour: Schema.Number.pipe(Schema.between(0, 23)).annotations({
|
|
47
|
+
title: 'Hour',
|
|
48
|
+
description: '0–23',
|
|
49
|
+
}),
|
|
50
|
+
minute: Schema.Number.pipe(Schema.between(0, 59)).annotations({
|
|
51
|
+
title: 'Minute',
|
|
52
|
+
description: '0–59',
|
|
53
|
+
}),
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
export const MonthlySpec = Schema.Struct({
|
|
57
|
+
frequency: Schema.Literal('monthly').annotations({ title: 'Frequency' }),
|
|
58
|
+
daysOfMonth: Schema.Array(Schema.Number.pipe(Schema.between(1, 31)).annotations({ title: 'Day' })).annotations({
|
|
59
|
+
title: 'Days of month',
|
|
60
|
+
}),
|
|
61
|
+
hour: Schema.Number.pipe(Schema.between(0, 23)).annotations({
|
|
62
|
+
title: 'Hour',
|
|
63
|
+
description: '0–23',
|
|
64
|
+
}),
|
|
65
|
+
minute: Schema.Number.pipe(Schema.between(0, 59)).annotations({
|
|
66
|
+
title: 'Minute',
|
|
67
|
+
description: '0–59',
|
|
68
|
+
}),
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
export const CustomSpec = Schema.Struct({
|
|
72
|
+
frequency: Schema.Literal('custom').annotations({ title: 'Frequency' }),
|
|
73
|
+
cronExpression: Schema.String.annotations({
|
|
74
|
+
title: 'Cron expression',
|
|
75
|
+
description: 'e.g. 0 9 * * MON-FRI',
|
|
76
|
+
}),
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
export const CronSpec = Schema.Union(
|
|
80
|
+
MinutelySpec,
|
|
81
|
+
HourlySpec,
|
|
82
|
+
DailySpec,
|
|
83
|
+
WeeklySpec,
|
|
84
|
+
MonthlySpec,
|
|
85
|
+
CustomSpec,
|
|
86
|
+
).annotations({ title: '' });
|
|
87
|
+
|
|
88
|
+
export type CronSpecType = Schema.Schema.Type<typeof CronSpec>;
|
|
89
|
+
export type Frequency = CronSpecType['frequency'];
|
|
90
|
+
|
|
91
|
+
export const Frequencies = [
|
|
92
|
+
'minutely',
|
|
93
|
+
'hourly',
|
|
94
|
+
'daily',
|
|
95
|
+
'weekly',
|
|
96
|
+
'monthly',
|
|
97
|
+
'custom',
|
|
98
|
+
] as const satisfies readonly Frequency[];
|
|
99
|
+
|
|
100
|
+
export const FrequencyLabels: Record<Frequency, string> = {
|
|
101
|
+
minutely: 'Every N minutes',
|
|
102
|
+
hourly: 'Hourly',
|
|
103
|
+
daily: 'Daily',
|
|
104
|
+
weekly: 'Weekly',
|
|
105
|
+
monthly: 'Monthly',
|
|
106
|
+
custom: 'Custom (cron)',
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
export const FrequencyDefaults: { [K in Frequency]: Extract<CronSpecType, { frequency: K }> } = {
|
|
110
|
+
minutely: { frequency: 'minutely', interval: 15 },
|
|
111
|
+
hourly: { frequency: 'hourly', interval: 1, minute: 0 },
|
|
112
|
+
daily: { frequency: 'daily', hour: 9, minute: 0 },
|
|
113
|
+
weekly: { frequency: 'weekly', daysOfWeek: ['mon'], hour: 9, minute: 0 },
|
|
114
|
+
monthly: { frequency: 'monthly', daysOfMonth: [1], hour: 9, minute: 0 },
|
|
115
|
+
custom: { frequency: 'custom', cronExpression: '* * * * *' },
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
export const CronBuilderSchema = Schema.Struct({ spec: CronSpec });
|
|
119
|
+
export type CronBuilderSchemaType = Schema.Schema.Type<typeof CronBuilderSchema>;
|
package/src/components/index.ts
CHANGED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2026 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { type Meta, type StoryObj } from '@storybook/react-vite';
|
|
6
|
+
import React from 'react';
|
|
7
|
+
|
|
8
|
+
import { Trigger } from '@dxos/compute';
|
|
9
|
+
import { Filter, Obj, Ref } from '@dxos/echo';
|
|
10
|
+
import { useQuery } from '@dxos/react-client/echo';
|
|
11
|
+
import { useClientStory, withClientProvider } from '@dxos/react-client/testing';
|
|
12
|
+
import { Loading, withLayout, withTheme } from '@dxos/react-ui/testing';
|
|
13
|
+
|
|
14
|
+
import { translations } from '#translations';
|
|
15
|
+
import { Automation } from '#types';
|
|
16
|
+
|
|
17
|
+
import { AutomationArticle } from './AutomationArticle';
|
|
18
|
+
|
|
19
|
+
const DefaultStory = () => {
|
|
20
|
+
const { space } = useClientStory();
|
|
21
|
+
const [automation] = useQuery(space?.db, Filter.type(Automation.Automation));
|
|
22
|
+
if (!automation) {
|
|
23
|
+
return <Loading />;
|
|
24
|
+
}
|
|
25
|
+
return <AutomationArticle role='article' subject={automation} attendableId='story' />;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const meta = {
|
|
29
|
+
title: 'plugins/plugin-automation/containers/AutomationArticle',
|
|
30
|
+
render: DefaultStory,
|
|
31
|
+
decorators: [
|
|
32
|
+
withTheme(),
|
|
33
|
+
withLayout({ layout: 'fullscreen' }),
|
|
34
|
+
withClientProvider({
|
|
35
|
+
createIdentity: true,
|
|
36
|
+
createSpace: true,
|
|
37
|
+
types: [Automation.Automation, Trigger.Trigger],
|
|
38
|
+
onCreateSpace: async ({ space }) => {
|
|
39
|
+
space.db.add(Automation.make({ name: 'Morning Report', triggers: [] }));
|
|
40
|
+
},
|
|
41
|
+
}),
|
|
42
|
+
],
|
|
43
|
+
parameters: {
|
|
44
|
+
layout: 'fullscreen',
|
|
45
|
+
translations,
|
|
46
|
+
},
|
|
47
|
+
} satisfies Meta<typeof DefaultStory>;
|
|
48
|
+
|
|
49
|
+
export default meta;
|
|
50
|
+
|
|
51
|
+
type Story = StoryObj<typeof meta>;
|
|
52
|
+
|
|
53
|
+
/** Empty automation — no trigger or action yet. Exercises the disabled enabled-switch message. */
|
|
54
|
+
export const Empty: Story = {};
|
|
55
|
+
|
|
56
|
+
/** Automation with a timer trigger pre-configured for a daily 9 AM schedule. */
|
|
57
|
+
export const WithTimerTrigger: Story = {
|
|
58
|
+
decorators: [
|
|
59
|
+
withClientProvider({
|
|
60
|
+
createIdentity: true,
|
|
61
|
+
createSpace: true,
|
|
62
|
+
types: [Automation.Automation, Trigger.Trigger],
|
|
63
|
+
onCreateSpace: async ({ space }) => {
|
|
64
|
+
const trigger = space.db.add(Trigger.make({ enabled: false, spec: { kind: 'timer', cron: '0 9 * * *' } }));
|
|
65
|
+
const automation = space.db.add(Automation.make({ name: 'Daily Digest', triggers: [] }));
|
|
66
|
+
// Wire the trigger into the automation after both are in the db.
|
|
67
|
+
Obj.update(automation, (automation) => {
|
|
68
|
+
automation.triggers = [...automation.triggers, Ref.make(trigger)];
|
|
69
|
+
});
|
|
70
|
+
},
|
|
71
|
+
}),
|
|
72
|
+
],
|
|
73
|
+
};
|