Skip to content

Commit 77e36fc

Browse files
Merge branch 'master' into feat/schedule-create-pr09b-overlap-policy
Resolve conflicts with merged PR09 advanced accordion: adopt upstream field ID/description constants while keeping overlap policy cluster, schema defaults, and transform wiring. Co-authored-by: Cursor <cursoragent@cursor.com>
2 parents a3cf6eb + 7185aca commit 77e36fc

11 files changed

Lines changed: 181 additions & 35 deletions

File tree

src/utils/__tests__/sort-by.test.ts

Lines changed: 69 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,72 @@
1-
import { toggleSortOrder } from '../sort-by';
1+
import sortBy, { getSortCompareFunction, toggleSortOrder } from '../sort-by';
2+
3+
describe(getSortCompareFunction.name, () => {
4+
it('sorts strings using locale-aware comparison in ASC order', () => {
5+
const compare = getSortCompareFunction<{ v: string }>(
6+
(item) => item.v,
7+
'ASC'
8+
);
9+
const items = [{ v: 'b' }, { v: 'a' }];
10+
11+
expect([...items].sort(compare)).toEqual([{ v: 'a' }, { v: 'b' }]);
12+
});
13+
14+
it('sorts numbers in ASC order', () => {
15+
const compare = getSortCompareFunction<{ v: number }>(
16+
(item) => item.v,
17+
'ASC'
18+
);
19+
const items = [{ v: 3 }, { v: 1 }, { v: 2 }];
20+
21+
expect([...items].sort(compare)).toEqual([{ v: 1 }, { v: 2 }, { v: 3 }]);
22+
});
23+
24+
it('inverts non-equal comparisons for DESC order', () => {
25+
const compare = getSortCompareFunction<{ v: number }>(
26+
(item) => item.v,
27+
'DESC'
28+
);
29+
const items = [{ v: 1 }, { v: 3 }, { v: 2 }];
30+
31+
expect([...items].sort(compare)).toEqual([{ v: 3 }, { v: 2 }, { v: 1 }]);
32+
});
33+
34+
it('returns zero when sort values are equal', () => {
35+
const compare = getSortCompareFunction<{ v: number }>(
36+
(item) => item.v,
37+
'DESC'
38+
);
39+
40+
expect(compare({ v: 1 }, { v: 1 })).toBe(0);
41+
});
42+
43+
it('places undefined and null before other values with undefined first', () => {
44+
const compare = getSortCompareFunction<{ v: number | null | undefined }>(
45+
(item) => item.v,
46+
'ASC'
47+
);
48+
const items = [{ v: 2 }, { v: null }, { v: undefined }, { v: 1 }];
49+
50+
expect([...items].sort(compare)).toEqual([
51+
{ v: undefined },
52+
{ v: null },
53+
{ v: 1 },
54+
{ v: 2 },
55+
]);
56+
});
57+
});
58+
59+
describe(sortBy.name, () => {
60+
it('sorts a copy of the input array', () => {
61+
const items = [{ v: 2 }, { v: 1 }];
62+
63+
expect(sortBy(items, (item) => item.v, 'ASC')).toEqual([
64+
{ v: 1 },
65+
{ v: 2 },
66+
]);
67+
expect(items).toEqual([{ v: 2 }, { v: 1 }]);
68+
});
69+
});
270

371
describe(toggleSortOrder.name, () => {
472
it('inverts sort order of current column (ASC -> DESC)', () => {

src/utils/config/__tests__/get-config-value.node.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,4 +38,21 @@ describe('getConfigValue', () => {
3838
expect(mockFn).toHaveBeenCalledWith(undefined);
3939
expect(result).toBe('resolvedValue');
4040
});
41+
42+
it('throws when resolver arguments fail validation', async () => {
43+
// @ts-expect-error COMPUTED is not a loaded config
44+
await expect(getConfigValue('COMPUTED', { invalid: true })).rejects.toThrow(
45+
"Failed to parse config 'COMPUTED' arguments:"
46+
);
47+
});
48+
49+
it('throws when resolver return value fails validation', async () => {
50+
// @ts-expect-error COMPUTED doesn't exist in the original loaded configs but it exists in the mocks
51+
const mockFn = getLoadedGlobalConfigs().COMPUTED as jest.Mock;
52+
mockFn.mockResolvedValue(123);
53+
// @ts-expect-error COMPUTED is not a loaded config
54+
await expect(getConfigValue('COMPUTED')).rejects.toThrow(
55+
"Failed to parse config 'COMPUTED' resolved value:"
56+
);
57+
});
4158
});

src/utils/merge-sorted-arrays.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
export default function mergeSortedArrays<T>({
2121
sortedArrays,
2222
itemsCount,
23-
compareFunc = (a: T, b: T): number => (a < b ? -1 : 1),
23+
compareFunc,
2424
}: {
2525
sortedArrays: Array<Array<T>>;
2626
itemsCount: number;

src/views/domain-batch-actions/domain-batch-actions-new-action-params/schemas/batch-action-params-schema.ts

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,10 @@
11
import { z } from 'zod';
22

3+
import { rpsSchema } from '@/views/domain-batch-actions/schemas/rps-schema';
4+
35
const batchActionParamsSchema = z.object({
46
description: z.string().min(1, 'Description is required'),
5-
rps: z
6-
.number()
7-
.int()
8-
.min(1, 'Must be between 1 and 999')
9-
.max(999, 'Must be between 1 and 999'),
7+
rps: rpsSchema,
108
});
119

1210
export default batchActionParamsSchema;

src/views/domain-batch-actions/domain-batch-actions.constants.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ export const BATCH_ACTION_DEFAULT_QUERY_HINT =
2828
export const BATCH_ACTION_TASK_LIST = 'cadence-sys-batcher-tasklist';
2929
export const BATCH_ACTION_EXECUTION_TIMEOUT_SECONDS = 20 * 365 * 24 * 60 * 60;
3030

31+
// Signal sent to a running batcher workflow to adjust its RPS on the fly.
32+
export const BATCH_ACTION_TUNE_SIGNAL_NAME = 'cadence-sys-batch-tune-signal';
33+
3134
export const BATCH_ACTION_DETAIL_REFETCH_INTERVAL = 10000;
3235

3336
// Tooltip shown on a disabled per-row checkbox while "select all" is active.
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { z } from 'zod';
2+
3+
// Shared RPS validation used by both the new-batch-action draft form and the
4+
// Edit RPS modal, so the two never drift.
5+
export const rpsSchema = z
6+
.number()
7+
.int()
8+
.min(1, 'Must be between 1 and 999')
9+
.max(999, 'Must be between 1 and 999');

src/views/domain-schedules/domain-schedules-create-advanced-form/domain-schedules-create-advanced-form.constants.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,29 @@
11
import { ScheduleOverlapPolicy } from '@/__generated__/proto-ts/uber/cadence/api/v1/ScheduleOverlapPolicy';
22
import { SCHEDULE_OVERLAP_POLICIES } from '@/route-handlers/create-schedule/create-schedule.constants';
33

4+
/** Stable ids for advanced create-schedule horizontal fields. */
5+
export const CREATE_SCHEDULE_ADVANCED_FIELD_IDS = {
6+
scheduleId: 'domain-schedules-create-form-schedule-id',
7+
jitterSeconds: 'domain-schedules-create-form-jitter-seconds',
8+
workflowIdPrefix: 'domain-schedules-create-form-workflow-id-prefix',
9+
bufferLimit: 'domain-schedules-create-form-buffer-limit',
10+
concurrencyLimit: 'domain-schedules-create-form-concurrency-limit',
11+
} as const;
12+
13+
export const CREATE_SCHEDULE_ADVANCED_FIELD_DESCRIPTIONS = {
14+
scheduleId: 'Unique name provided by users to name the schedule.',
15+
overlapPolicy:
16+
'Policy that controls what the scheduler should do if the previous action is still running.',
17+
bufferLimit:
18+
'Max number of pending workflows allowed when using Buffer overlap policy.',
19+
concurrencyLimit:
20+
'Max number of concurrently running workflows allowed for Concurrent overlap policy.',
21+
jitterSeconds:
22+
'Time range to distribute starting workflows across. This helps avoiding burst of workflow creations in a single point of time.',
23+
workflowIdPrefix:
24+
'Prefix text to add into started workflows. Ids are formed as `${Prefix}+{auto generated postfix}`.',
25+
} as const;
26+
427
const overlapPolicyLabels: Record<
528
(typeof SCHEDULE_OVERLAP_POLICIES)[number],
629
string

src/views/domain-schedules/domain-schedules-create-advanced-form/domain-schedules-create-advanced-form.tsx

Lines changed: 34 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,11 @@ import { ScheduleOverlapPolicy } from '@/__generated__/proto-ts/uber/cadence/api
1515
import DomainSchedulesHorizontalField from '@/views/domain-schedules/domain-schedules-horizontal-field/domain-schedules-horizontal-field';
1616
import getFieldErrorMessage from '@/views/workflow-actions/workflow-action-start-form/helpers/get-field-error-message';
1717

18-
import { OVERLAP_POLICY_OPTIONS } from './domain-schedules-create-advanced-form.constants';
18+
import {
19+
CREATE_SCHEDULE_ADVANCED_FIELD_DESCRIPTIONS,
20+
CREATE_SCHEDULE_ADVANCED_FIELD_IDS,
21+
OVERLAP_POLICY_OPTIONS,
22+
} from './domain-schedules-create-advanced-form.constants';
1923
import {
2024
overrides,
2125
styled,
@@ -64,18 +68,18 @@ export default function DomainSchedulesCreateAdvancedForm({
6468
<>
6569
<DomainSchedulesHorizontalField
6670
label="Schedule Id"
67-
description="Unique name provided by users to name the schedule."
68-
htmlFor="create-schedule-form-schedule-id"
71+
description={CREATE_SCHEDULE_ADVANCED_FIELD_DESCRIPTIONS.scheduleId}
72+
htmlFor={CREATE_SCHEDULE_ADVANCED_FIELD_IDS.scheduleId}
6973
error={getFieldErrorMessage(fieldErrors, 'scheduleId')}
7074
>
7175
<Controller
7276
name="scheduleId"
7377
control={control}
78+
defaultValue=""
7479
render={({ field: { ref, ...field } }) => (
7580
<Input
7681
{...field}
77-
value={field.value ?? ''}
78-
id="create-schedule-form-schedule-id"
82+
id={CREATE_SCHEDULE_ADVANCED_FIELD_IDS.scheduleId}
7983
// @ts-expect-error - inputRef expects ref object while ref is a callback. It should support both.
8084
inputRef={ref}
8185
aria-label="Schedule Id"
@@ -91,7 +95,9 @@ export default function DomainSchedulesCreateAdvancedForm({
9195

9296
<DomainSchedulesHorizontalField
9397
label="Overlap Policy"
94-
description="Policy that controls what the scheduler should do if the previous action is still running."
98+
description={
99+
CREATE_SCHEDULE_ADVANCED_FIELD_DESCRIPTIONS.overlapPolicy
100+
}
95101
error={getFieldErrorMessage(fieldErrors, 'overlapPolicy')}
96102
>
97103
<Controller
@@ -122,8 +128,10 @@ export default function DomainSchedulesCreateAdvancedForm({
122128
<DomainSchedulesHorizontalField
123129
subfield={true}
124130
label="Buffer limit"
125-
description="Max number of pending workflows allowed when using Buffer overlap policy."
126-
htmlFor="create-schedule-form-buffer-limit"
131+
description={
132+
CREATE_SCHEDULE_ADVANCED_FIELD_DESCRIPTIONS.bufferLimit
133+
}
134+
htmlFor={CREATE_SCHEDULE_ADVANCED_FIELD_IDS.bufferLimit}
127135
caption="Defaults to 0 (unlimited)"
128136
error={getFieldErrorMessage(fieldErrors, 'bufferLimit')}
129137
>
@@ -133,7 +141,7 @@ export default function DomainSchedulesCreateAdvancedForm({
133141
render={({ field: { ref, ...field } }) => (
134142
<Input
135143
{...field}
136-
id="create-schedule-form-buffer-limit"
144+
id={CREATE_SCHEDULE_ADVANCED_FIELD_IDS.bufferLimit}
137145
// @ts-expect-error - inputRef expects ref object while ref is a callback. It should support both.
138146
inputRef={ref}
139147
aria-label="Buffer limit"
@@ -156,8 +164,10 @@ export default function DomainSchedulesCreateAdvancedForm({
156164
<DomainSchedulesHorizontalField
157165
subfield={true}
158166
label="Concurrency limit"
159-
description="Max number of concurrently running workflows allowed for Concurrent overlap policy."
160-
htmlFor="create-schedule-form-concurrency-limit"
167+
description={
168+
CREATE_SCHEDULE_ADVANCED_FIELD_DESCRIPTIONS.concurrencyLimit
169+
}
170+
htmlFor={CREATE_SCHEDULE_ADVANCED_FIELD_IDS.concurrencyLimit}
161171
caption="Defaults to 0 (unlimited)"
162172
error={getFieldErrorMessage(fieldErrors, 'concurrencyLimit')}
163173
>
@@ -167,7 +177,7 @@ export default function DomainSchedulesCreateAdvancedForm({
167177
render={({ field: { ref, ...field } }) => (
168178
<Input
169179
{...field}
170-
id="create-schedule-form-concurrency-limit"
180+
id={CREATE_SCHEDULE_ADVANCED_FIELD_IDS.concurrencyLimit}
171181
// @ts-expect-error - inputRef expects ref object while ref is a callback. It should support both.
172182
inputRef={ref}
173183
aria-label="Concurrency limit"
@@ -187,17 +197,20 @@ export default function DomainSchedulesCreateAdvancedForm({
187197

188198
<DomainSchedulesHorizontalField
189199
label="Jitter duration"
190-
description="Time range to distribute starting workflows across. This helps avoiding burst of workflow creations in a single point of time."
191-
htmlFor="create-schedule-form-jitter"
200+
description={
201+
CREATE_SCHEDULE_ADVANCED_FIELD_DESCRIPTIONS.jitterSeconds
202+
}
203+
htmlFor={CREATE_SCHEDULE_ADVANCED_FIELD_IDS.jitterSeconds}
192204
error={getFieldErrorMessage(fieldErrors, 'jitterSeconds')}
193205
>
194206
<Controller
195207
name="jitterSeconds"
196208
control={control}
209+
defaultValue=""
197210
render={({ field: { ref, ...field } }) => (
198211
<Input
199212
{...field}
200-
id="create-schedule-form-jitter"
213+
id={CREATE_SCHEDULE_ADVANCED_FIELD_IDS.jitterSeconds}
201214
// @ts-expect-error - inputRef expects ref object while ref is a callback. It should support both.
202215
inputRef={ref}
203216
aria-label="Jitter duration"
@@ -217,18 +230,20 @@ export default function DomainSchedulesCreateAdvancedForm({
217230

218231
<DomainSchedulesHorizontalField
219232
label="Workflow Id Prefix"
220-
description="Prefix text to add into started workflows. Ids are formed as `${Prefix}+{auto generated postfix}`."
221-
htmlFor="create-schedule-form-workflow-id-prefix"
233+
description={
234+
CREATE_SCHEDULE_ADVANCED_FIELD_DESCRIPTIONS.workflowIdPrefix
235+
}
236+
htmlFor={CREATE_SCHEDULE_ADVANCED_FIELD_IDS.workflowIdPrefix}
222237
error={getFieldErrorMessage(fieldErrors, 'workflowIdPrefix')}
223238
>
224239
<Controller
225240
name="workflowIdPrefix"
226241
control={control}
242+
defaultValue=""
227243
render={({ field: { ref, ...field } }) => (
228244
<Input
229245
{...field}
230-
value={field.value ?? ''}
231-
id="create-schedule-form-workflow-id-prefix"
246+
id={CREATE_SCHEDULE_ADVANCED_FIELD_IDS.workflowIdPrefix}
232247
// @ts-expect-error - inputRef expects ref object while ref is a callback. It should support both.
233248
inputRef={ref}
234249
aria-label="Workflow Id Prefix"

src/views/domain-schedules/domain-schedules-create-modal/__tests__/transform-domain-schedules-create-form-to-body.test.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,15 +56,28 @@ describe(transformDomainSchedulesCreateFormToBody.name, () => {
5656
expect(result.startWorkflow.input).toBeUndefined();
5757
});
5858

59-
it('trims workflow type and task list names', () => {
59+
it('trims text fields', () => {
6060
const result = transformDomainSchedulesCreateFormToBody({
6161
...baseForm,
6262
workflowType: { name: ' DemoWorkflow ' },
6363
taskList: { name: ' demo-tl ' },
64+
scheduleId: ' my-schedule ',
65+
workflowIdPrefix: ' wf-prefix ',
6466
});
6567

6668
expect(result.startWorkflow.workflowType.name).toBe('DemoWorkflow');
6769
expect(result.startWorkflow.taskList.name).toBe('demo-tl');
70+
expect(result.scheduleId).toBe('my-schedule');
71+
expect(result.startWorkflow.workflowIdPrefix).toBe('wf-prefix');
72+
});
73+
74+
it('passes 0 seconds as jitter', () => {
75+
const result = transformDomainSchedulesCreateFormToBody({
76+
...baseForm,
77+
jitterSeconds: '0',
78+
});
79+
80+
expect(result.jitterSeconds).toBe(0);
6881
});
6982

7083
it('maps optional simple advanced fields only when provided', () => {

src/views/domain-schedules/domain-schedules-create-modal/domain-schedules-create-modal.tsx

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,12 @@
33
import React, { useEffect, useRef } from 'react';
44

55
import { zodResolver } from '@hookform/resolvers/zod';
6-
import { Banner } from 'baseui/banner';
6+
import { Banner, HIERARCHY, KIND as BANNER_KIND } from 'baseui/banner';
77
import { Modal, ModalButton } from 'baseui/modal';
88
import { useSnackbar } from 'baseui/snackbar';
99
import { useForm } from 'react-hook-form';
1010
import { MdCheckCircle, MdErrorOutline } from 'react-icons/md';
1111

12-
import { ScheduleOverlapPolicy } from '@/__generated__/proto-ts/uber/cadence/api/v1/ScheduleOverlapPolicy';
1312
import useCreateSchedule from '@/views/shared/hooks/use-create-schedule/use-create-schedule';
1413

1514
import DomainSchedulesCreateForm from '../domain-schedules-create-form/domain-schedules-create-form';
@@ -44,9 +43,6 @@ export default function DomainSchedulesCreateModal({
4443
resolver: zodResolver(createScheduleFormSchema),
4544
mode: 'onSubmit',
4645
reValidateMode: 'onChange',
47-
defaultValues: {
48-
overlapPolicy: ScheduleOverlapPolicy.SCHEDULE_OVERLAP_POLICY_CONCURRENT,
49-
},
5046
});
5147

5248
useEffect(() => {

0 commit comments

Comments
 (0)