Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
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
8 changes: 6 additions & 2 deletions src/lib/components/count-refresh-button.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,10 @@
interface Props {
count: number;
refresh: Writable<number>;
onRefresh?: () => void;
}

let { count, refresh }: Props = $props();
let { count, refresh, onRefresh }: Props = $props();

const duration = 300;
</script>
Expand All @@ -21,7 +22,10 @@
size="xs"
variant="ghost"
leadingIcon="retry"
on:click={() => ($refresh = Date.now())}
on:click={() => {
$refresh = Date.now();
onRefresh?.();
}}
>
{translate('common.refresh')}
<span
Expand Down
4 changes: 2 additions & 2 deletions src/lib/components/detail-list/detail-list-label.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@
const { children, href, class: className = '' }: Props = $props();
</script>

<dd class={twMerge('col-[1] text-secondary', className)}>
<dt class={twMerge('col-[1] text-secondary', className)}>
{#if href}
<Link {href} newTab>{@render children()}</Link>
{:else}
{@render children()}
{/if}
</dd>
</dt>
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<script lang="ts">
import type { Snippet } from 'svelte';
import { twMerge } from 'tailwind-merge';

import Tooltip from '$lib/holocene/tooltip.svelte';
import { relativeTime } from '$lib/stores/time-format';
Expand All @@ -13,9 +14,10 @@
timestamp: ValidTime | undefined | null;
children?: Snippet;
fallback?: string;
class?: string;
}

let { timestamp: t, children, fallback }: Props = $props();
let { timestamp: t, children, class: className, fallback }: Props = $props();

let formattedTimestamp = $derived(
t ? $timestamp(t) : fallback ? fallback : '',
Expand All @@ -31,7 +33,7 @@
</div>
{/snippet}

<DetailListValue class="font-mono">
<DetailListValue class={twMerge('font-mono', className)}>
<Tooltip
hide={!t}
text={$relativeTime ? formattedTimestamp : relativeTimestamp}
Expand Down
4 changes: 2 additions & 2 deletions src/lib/components/detail-list/detail-list-value.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
};
</script>

<dt class={twMerge('col-[2] flex', className)}>
<dd class={twMerge('col-[2] flex', className)}>
{@render children()}
{#if copyable}
<!--
Expand All @@ -43,4 +43,4 @@
/>
</div>
{/if}
</dt>
</dd>
2 changes: 1 addition & 1 deletion src/lib/components/detail-list/detail-list.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

interface Props {
children: Snippet;
'aria-label': string;
'aria-label'?: string;
rowCount: number;
}
let { children, 'aria-label': ariaLabel, rowCount }: Props = $props();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
<script lang="ts">
import { writable } from 'svelte/store';

import { addHours, isBefore, startOfDay } from 'date-fns';
import { utcToZonedTime } from 'date-fns-tz';

import DatePicker from '$lib/holocene/date-picker.svelte';
import Icon from '$lib/holocene/icon/icon.svelte';
import Modal from '$lib/holocene/modal.svelte';
import RadioCard from '$lib/holocene/radio-input/radio-card.svelte';
import RadioGroup from '$lib/holocene/radio-input/radio-group.svelte';
import TimePicker from '$lib/holocene/time-picker.svelte';
import { translate } from '$lib/i18n/translate';
import {
actionPending,
clearConfirmationModalActionTimeout,
closeConfirmationModal,
confirmationModal,
serverError,
submitBackfillSchedule,
} from '$lib/stores/schedules';
import type { OverlapPolicy } from '$lib/types/schedule';
import { getIdentity } from '$lib/utilities/core-context';

import { getOverlapPolicyContent } from '../constants';
import { dateAndTimeToTimestamp } from '../utilities/date';

interface Props {
scheduleOverlapPolicy?: OverlapPolicy;
scheduleId: string;
namespace: string;
timezoneName?: string;
}

const overlapPolicyContent = getOverlapPolicyContent();
const identity = getIdentity();

let { scheduleOverlapPolicy, scheduleId, namespace, timezoneName }: Props =
$props();

// svelte-ignore state_referenced_locally
const timezone = timezoneName || 'UTC';

// svelte-ignore state_referenced_locally
let selectedOverlapPolicy = writable<OverlapPolicy>(
scheduleOverlapPolicy ?? 'Skip',
);

const zonedNow = utcToZonedTime(new Date(), timezone);
const anHourAhead = addHours(zonedNow, 1);

let startDate = $state(startOfDay(zonedNow));
let startHour = $state(zonedNow.getHours().toString());
let startMinute = $state(zonedNow.getMinutes().toString());
let startSecond = $state(zonedNow.getSeconds().toString());

let endDate = $state(startOfDay(anHourAhead));
let endHour = $state(anHourAhead.getHours().toString());
let endMinute = $state(anHourAhead.getMinutes().toString());
let endSecond = $state(anHourAhead.getSeconds().toString());

const startTimestamp = $derived(
dateAndTimeToTimestamp(
startDate,
startHour,
startMinute,
startSecond,
timezone,
),
);
const endTimestamp = $derived(
dateAndTimeToTimestamp(endDate, endHour, endMinute, endSecond, timezone),
);
const invalidEndTime = $derived(endTimestamp <= startTimestamp);

$effect(() => {
return () => clearConfirmationModalActionTimeout('backfill');
});
</script>

<Modal
id="backfill-schedule-modal"
large
open={$confirmationModal === 'backfill'}
confirmType="primary"
confirmText={translate('schedules.schedule')}
cancelText={translate('common.cancel')}
loading={$actionPending}
error={$serverError}
confirmDisabled={invalidEndTime}
on:confirmModal={() =>
submitBackfillSchedule(
{
startTime: startTimestamp,
endTime: endTimestamp,
overlapPolicy: $selectedOverlapPolicy,
},
{
identity,
scheduleId,
namespace,
},
)}
on:cancelModal={() => {
closeConfirmationModal('backfill');
}}
>
<h3 slot="title">
{translate('schedules.schedule')}
{translate('schedules.backfill')}
</h3>
<div slot="content">
<div class="flex flex-col gap-6 p-2">
<div class="flex w-full flex-col gap-2">
<div class="sm:max-w-[round(up,calc(66.6666%_-_0.25rem))]">
<DatePicker
id="backfill-start-date"
label={translate('common.start')}
onDateChange={(d) => (startDate = startOfDay(d))}
selected={startDate}
todayLabel={translate('common.today')}
closeLabel={translate('common.close')}
clearLabel={translate('common.clear-input-button-label')}
/>
</div>
<TimePicker
idPrefix="backfill-start-"
bind:hour={startHour}
bind:minute={startMinute}
bind:second={startSecond}
twelveHourClock={false}
/>
</div>
<div class="flex w-full flex-col gap-2">
<div class="sm:max-w-[round(up,calc(66.6666%_-_0.25rem))]">
<DatePicker
id="backfill-end-date"
label={translate('common.end')}
onDateChange={(d) => (endDate = startOfDay(d))}
selected={endDate}
todayLabel={translate('common.today')}
closeLabel={translate('common.close')}
clearLabel={translate('common.clear-input-button-label')}
isAllowed={(d) => !isBefore(d, startDate)}
/>
</div>
<TimePicker
idPrefix="backfill-end-"
bind:hour={endHour}
bind:minute={endMinute}
bind:second={endSecond}
twelveHourClock={false}
error={invalidEndTime}
/>
</div>
{#if invalidEndTime}
<span class="text-xs text-danger" role="alert">
{translate('schedules.backfill-end-before-start')}
</span>
{/if}
<div class="flex w-full flex-row items-center gap-2">
<Icon name="clock" aria-hidden="true" />
<span class="text-xs font-normal text-slate-500"
>{translate('common.based-on-time-preface')}
{timezone === 'UTC'
? translate('common.universal-standard-time')
: timezone}
</span>
</div>
</div>

<hr tabindex="-1" aria-hidden="true" class="my-4 w-full border-subtle" />

<RadioGroup name="overlap-policy" group={selectedOverlapPolicy}>
{#each Object.entries(overlapPolicyContent) as [value, content] (value)}
<RadioCard
id="overlap-policy-{value}"
value={value as OverlapPolicy}
label={[
content.label,
value === scheduleOverlapPolicy &&
translate('schedules.overlap-schedule-policy-suffix'),
]
.filter(Boolean)
.join(' ')}
description={content.description}
labelContainerClass="border-transparent p-0"
/>
{/each}
</RadioGroup>
</div>
</Modal>
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
<script lang="ts">
import Input from '$lib/holocene/input/input.svelte';
import Modal from '$lib/holocene/modal.svelte';
import { translate } from '$lib/i18n/translate';
import {
actionPending,
clearConfirmationModalActionTimeout,
closeConfirmationModal,
confirmationModal,
serverError,
submitPauseSchedule,
} from '$lib/stores/schedules';
import { getIdentity } from '$lib/utilities/core-context';

interface Props {
isSchedulePaused?: boolean;
scheduleId: string;
namespace: string;
}

const identity = getIdentity();

let { isSchedulePaused, scheduleId, namespace }: Props = $props();

let reason = $state('');

$effect(() => {
return () => clearConfirmationModalActionTimeout('pause');
});
</script>

<Modal
id="pause-schedule-modal"
open={$confirmationModal === 'pause'}
confirmType="primary"
confirmText={isSchedulePaused
? translate('schedules.unpause')
: translate('schedules.pause')}
cancelText={translate('common.cancel')}
confirmDisabled={!reason}
loading={$actionPending}
error={$serverError}
on:cancelModal={() => closeConfirmationModal('pause')}
on:confirmModal={() =>
submitPauseSchedule(reason, {
identity,
scheduleId,
namespace,
isPaused: isSchedulePaused,
})}
>
<h3 slot="title">
{isSchedulePaused
? translate('schedules.unpause-modal-title')
: translate('schedules.pause-modal-title')}
</h3>
<div slot="content">
<p>
{isSchedulePaused
? translate('schedules.unpause-modal-confirmation', {
schedule: scheduleId,
})
: translate('schedules.pause-modal-confirmation', {
schedule: scheduleId,
})}
</p>
<p class="my-4">
{isSchedulePaused
? translate('schedules.unpause-reason')
: translate('schedules.pause-reason')}
</p>
<Input
id="pause-reason"
bind:value={reason}
placeholder={translate('common.reason')}
label={translate('common.reason')}
labelHidden
/>
</div>
</Modal>
Loading
Loading