Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
c1b8292
feat: add support for `scheduleStartAt` and `scheduleOffsetMinutes` i…
melsalcedo Feb 9, 2026
ffa5bf2
Update packages/api/src/controllers/alerts.ts
mlsalcedo Feb 17, 2026
7f1506c
Update packages/api/src/controllers/alerts.ts
mlsalcedo Feb 17, 2026
8408688
refactor: simplify `scheduleStartAt` validation using `fns.isValid` f…
melsalcedo Feb 17, 2026
bf4b847
feat: improve alert scheduling by updating `scheduleStartAt` validati…
melsalcedo Feb 17, 2026
799c84c
Merge branch 'main' into startTime-offset
teeohhem Feb 19, 2026
03c07cb
refactor(alerts): dedupe interval and offset validation
melsalcedo Feb 19, 2026
f8b379e
feat(app): use DateTimePicker for scheduleStartAt input
melsalcedo Feb 20, 2026
ea4ab24
feat(app): add datetime picker to dashboard alert start time
melsalcedo Feb 20, 2026
dfadc7e
refactor(alerts): share scheduleStartAt schema and parser
melsalcedo Feb 20, 2026
b903e61
chore(alerts): clarify anchor start label in forms
melsalcedo Feb 20, 2026
d1a028d
fix(alerts): handle null scheduleStartAt and add scheduling guards
melsalcedo Feb 20, 2026
29af34b
chore(alerts): address review feedback for schedule start
melsalcedo Feb 20, 2026
30c4865
chore(alerts): normalize schedule defaults and 1m offset UX
melsalcedo Feb 20, 2026
32a7267
refactor(alerts): centralize schedule fields and harden API validation
melsalcedo Feb 20, 2026
539b531
fix(alerts): avoid no-op schedule writes for existing alerts
melsalcedo Feb 20, 2026
41e0c26
refactor(alerts): share no-op schedule normalization helper
melsalcedo Feb 20, 2026
6e31044
fix(alerts): tighten scheduleStartAt validation and defaults
melsalcedo Feb 20, 2026
d1498fc
fix(alerts): guard new-alert schedule normalization
melsalcedo Feb 20, 2026
fe52938
chore(alerts): tighten copy and offset bounds
melsalcedo Feb 20, 2026
f7cceef
chore(alerts): clarify offset behavior with anchored start
melsalcedo Feb 20, 2026
6a4c407
chore(alerts): guard anchor bounds and document zod coupling
melsalcedo Feb 20, 2026
be098f9
feat(alerts): enforce anchored offset semantics across layers
melsalcedo Feb 20, 2026
7d502a2
fix(alerts): clear stale offset when scheduleStartAt is set
melsalcedo Feb 20, 2026
e718023
fix(alerts): preserve schema compatibility and harden no-op normaliza…
melsalcedo Feb 20, 2026
6c96d66
fix(common-utils): keep validated alert schemas internal
melsalcedo Feb 20, 2026
2d29bb5
fix(alerts): tighten external validation and clear stale offsets
melsalcedo Feb 20, 2026
3780acd
Merge remote-tracking branch 'upstream/main' into codex/issue-1715-st…
melsalcedo Feb 20, 2026
64052ab
fix(app): preserve explicit alert schedule resets
melsalcedo Feb 28, 2026
da9293d
Merge remote-tracking branch 'upstream/main' into codex/issue-1715-st…
melsalcedo Feb 28, 2026
2f3c582
test(api): cover omitted alert schedule updates
melsalcedo Feb 28, 2026
12d483d
feat(app): collapse alert schedule fields under Advanced Options
teeohhem Mar 2, 2026
2505234
Merge remote-tracking branch 'upstream/main' into codex/issue-1715-st…
melsalcedo Mar 3, 2026
4d199db
feat(app): tuck alert scheduling under advanced settings
melsalcedo Mar 3, 2026
a9c3a0a
Merge remote-tracking branch 'mlsalcedo/startTime-offset' into codex/…
melsalcedo Mar 3, 2026
436adb8
refactor(app): align alert schedule layouts
melsalcedo Mar 3, 2026
3acb031
refactor(api): parse alert requests in middleware
melsalcedo Mar 3, 2026
b4928d7
Merge remote-tracking branch 'upstream/main' into codex/issue-1715-st…
melsalcedo Mar 3, 2026
d1c329a
Merge remote-tracking branch 'upstream/main' into codex/issue-1715-st…
melsalcedo Mar 4, 2026
04efb18
Merge branch 'main' into startTime-offset
mlsalcedo Mar 4, 2026
b1faed3
Merge remote-tracking branch 'upstream/main' into codex/issue-1715-st…
melsalcedo Mar 5, 2026
d302304
Merge branch 'startTime-offset' of https://github.com/mlsalcedo/hyper…
melsalcedo Mar 5, 2026
05b8f25
fix: linitng
teeohhem Mar 5, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
feat(app): tuck alert scheduling under advanced settings
  • Loading branch information
melsalcedo committed Mar 3, 2026
commit 4d199db6d50770427bf16fdc4b614c956e7e3135
169 changes: 115 additions & 54 deletions packages/app/src/components/AlertScheduleFields.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useEffect } from 'react';
import { useEffect, useState } from 'react';
import {
Control,
Controller,
Expand All @@ -9,11 +9,14 @@ import {
useWatch,
} from 'react-hook-form';
import { NumberInput } from 'react-hook-form-mantine';
import { Group, Text } from '@mantine/core';
import { Anchor, Button, Group, Stack, Text, Tooltip } from '@mantine/core';
import { DateTimePicker } from '@mantine/dates';
import { IconHelpCircle, IconSettings } from '@tabler/icons-react';

import { parseScheduleStartAtValue } from '@/utils/alerts';

const DATE_TIME_INPUT_FORMAT = 'YYYY-MM-DD HH:mm:ss';

type AlertScheduleFieldsProps<T extends FieldValues> = {
control: Control<T>;
setValue: UseFormSetValue<T>;
Expand All @@ -39,6 +42,11 @@ export function AlertScheduleFields<T extends FieldValues>({
name: scheduleStartAtName,
}) as string | null | undefined;
const hasScheduleStartAtAnchor = scheduleStartAtValue != null;
const hasAdvancedScheduleValues =
(scheduleOffsetMinutes ?? 0) > 0 || hasScheduleStartAtAnchor;
const [showAdvancedSettings, setShowAdvancedSettings] = useState(
hasAdvancedScheduleValues,
);

useEffect(() => {
const normalizedOffset = scheduleOffsetMinutes ?? 0;
Expand All @@ -62,61 +70,114 @@ export function AlertScheduleFields<T extends FieldValues>({
]);

return (
<>
{showScheduleOffsetInput && (
<>
<Group gap="xs" mt="xs">
<Text size="sm" opacity={0.7}>
Start offset (min)
</Text>
<NumberInput
min={0}
max={maxScheduleOffsetMinutes}
step={1}
size="xs"
w={100}
control={control}
name={scheduleOffsetName}
disabled={hasScheduleStartAtAnchor}
/>
<Text size="sm" opacity={0.7}>
{offsetWindowLabel}
</Text>
<Stack gap="xs" mt="xs">
{!showAdvancedSettings ? (
<Anchor
underline="always"
size="xs"
onClick={() => setShowAdvancedSettings(true)}
data-testid="alert-advanced-settings-toggle"
>
<Group gap="xs">
<IconSettings size={14} />
Advanced Settings
</Group>
{hasScheduleStartAtAnchor && (
<Text size="xs" opacity={0.6} mt={4}>
Start offset is ignored while an anchor start time is set.
</Text>
)}
</>
</Anchor>
) : (
<Button
size="xs"
variant="subtle"
w="fit-content"
onClick={() => setShowAdvancedSettings(false)}
data-testid="alert-advanced-settings-toggle"
>
Hide Advanced Settings
</Button>
)}
<Group gap="xs" mt="xs" align="start">
<Text size="sm" opacity={0.7} mt={6}>
Anchor start time
</Text>
<Controller
control={control}
name={scheduleStartAtName}
render={({ field, fieldState: { error } }) => (
<DateTimePicker
size="xs"
w={260}
placeholder="Pick date and time"
clearable
dropdownType="popover"
popoverProps={{ withinPortal: true, zIndex: 10050 }}
value={parseScheduleStartAtValue(
field.value as string | null | undefined,
{showAdvancedSettings && (
<Stack gap="sm" data-testid="alert-advanced-settings-panel">
{showScheduleOffsetInput && (
<Stack gap={4}>
<Group gap="xs">
<Text size="sm" opacity={0.7}>
Start offset (min)
</Text>
<Tooltip
label="Shifts each alert window forward by a fixed number of minutes inside the selected interval. For example, a 15 minute alert with offset 5 runs on windows starting at :05, :20, :35, and :50."
multiline
maw={360}
>
<IconHelpCircle size={16} />
</Tooltip>
</Group>
<Text size="xs" opacity={0.6}>
Use this to align recurring windows to a fixed offset{' '}
{offsetWindowLabel}.
</Text>
<Group gap="xs">
<NumberInput
min={0}
max={maxScheduleOffsetMinutes}
step={1}
size="xs"
w={100}
control={control}
name={scheduleOffsetName}
disabled={hasScheduleStartAtAnchor}
/>
<Text size="sm" opacity={0.7}>
{offsetWindowLabel}
</Text>
</Group>
{hasScheduleStartAtAnchor && (
<Text size="xs" opacity={0.6}>
Start offset is ignored while an anchor start time is set.
</Text>
)}
onChange={value => field.onChange(value?.toISOString() ?? null)}
error={error?.message}
/>
</Stack>
)}
/>
<Text size="xs" opacity={0.6} mt={6}>
Displayed in local time, stored as UTC
</Text>
</Group>
</>
<Stack gap={4}>
<Group gap="xs">
<Text size="sm" opacity={0.7}>
Anchor start time
</Text>
<Tooltip
label="Anchors the recurring alert schedule to an exact date and time. Future checks repeat on the alert interval from this anchor, which helps match external systems with fixed schedules."
multiline
maw={360}
>
<IconHelpCircle size={16} />
</Tooltip>
</Group>
<Text size="xs" opacity={0.6}>
Use an exact start time to repeat isolated windows on the selected
interval. Displayed in local time, stored as UTC.
</Text>
<Controller
control={control}
name={scheduleStartAtName}
render={({ field, fieldState: { error } }) => (
<DateTimePicker
size="xs"
w={260}
placeholder={DATE_TIME_INPUT_FORMAT}
valueFormat={DATE_TIME_INPUT_FORMAT}
clearable
dropdownType="popover"
popoverProps={{ withinPortal: true, zIndex: 10050 }}
value={parseScheduleStartAtValue(
field.value as string | null | undefined,
)}
onChange={value =>
field.onChange(value?.toISOString() ?? null)
}
error={error?.message}
/>
)}
/>
</Stack>
</Stack>
)}
</Stack>
);
}
20 changes: 20 additions & 0 deletions packages/app/src/components/__tests__/DBEditTimeChartForm.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -473,4 +473,24 @@ describe('DBEditTimeChartForm - Add/delete alerts for display type Number', () =
// Verify that onSave was not called
expect(onSave).not.toHaveBeenCalled();
});

it('shows alert scheduling fields inside advanced settings', async () => {
renderComponent();

await userEvent.click(screen.getByTestId('alert-button'));

expect(
screen.queryByTestId('alert-advanced-settings-panel'),
).not.toBeInTheDocument();

await userEvent.click(screen.getByTestId('alert-advanced-settings-toggle'));

expect(
screen.getByTestId('alert-advanced-settings-panel'),
).toBeInTheDocument();
expect(screen.getByText('Anchor start time')).toBeInTheDocument();
expect(
screen.getByTestId('alert-advanced-settings-toggle'),
).toHaveTextContent('Hide Advanced Settings');
});
});