Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion admin/server/alerts.go
Original file line number Diff line number Diff line change
Expand Up @@ -589,7 +589,7 @@ type alertYAML struct {
RenotifyAfter uint32 `yaml:"renotify_after"`
Notify struct {
Email struct {
Recipients []string `yaml:"emails"`
Recipients []string `yaml:"recipients"`
}
Slack struct {
Users []string `yaml:"users"`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
export let timeZone: string;
export let currentExecution: V1AlertExecution | null;
export let result: V1AssertionResult;
$: console.log("result", result);
</script>

<div class="flex gap-x-2 items-center px-4 py-[10px]">
Expand Down
45 changes: 28 additions & 17 deletions web-admin/src/features/alerts/metadata/AlertMetadata.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,10 @@
useIsAlertCreatedByCode,
} from "@rilldata/web-admin/features/alerts/selectors";
import ProjectAccessControls from "@rilldata/web-admin/features/projects/ProjectAccessControls.svelte";
import EmailRecipients from "@rilldata/web-admin/features/scheduled-reports/metadata/EmailRecipients.svelte";
import MetadataLabel from "@rilldata/web-admin/features/scheduled-reports/metadata/MetadataLabel.svelte";
import MetadataList from "@rilldata/web-admin/features/scheduled-reports/metadata/MetadataList.svelte";
import MetadataValue from "@rilldata/web-admin/features/scheduled-reports/metadata/MetadataValue.svelte";
import { extractNotifier } from "@rilldata/web-admin/features/scheduled-reports/metadata/notifiers-utils";
import { IconButton } from "@rilldata/web-common/components/button";
import Button from "@rilldata/web-common/components/button/Button.svelte";
import * as DropdownMenu from "@rilldata/web-common/components/dropdown-menu";
Expand All @@ -40,13 +41,16 @@
$: dashboardTitle =
$dashboard.data?.metricsView.spec.title || $dashboardName.data;

$: alertSpec = $alertQuery.data?.resource?.alert?.spec;

$: metricsViewAggregationRequest = JSON.parse(
$alertQuery.data?.resource?.alert?.spec?.queryArgsJson ?? "{}",
alertSpec?.queryArgsJson ?? "{}",
) as V1MetricsViewAggregationRequest;

$: snoozeLabel = humaniseAlertSnoozeOption(
$alertQuery.data?.resource?.alert?.spec,
);
$: snoozeLabel = humaniseAlertSnoozeOption(alertSpec);

$: emailNotifier = extractNotifier(alertSpec?.notifiers, "email");
$: slackNotifier = extractNotifier(alertSpec?.notifiers, "slack");

// Actions
const queryClient = useQueryClient();
Expand All @@ -71,7 +75,7 @@
}
</script>

{#if $alertQuery.data?.resource?.alert?.spec}
{#if alertSpec}
<div class="flex flex-col gap-y-9 w-full max-w-full 2xl:max-w-[1200px]">
<div class="flex flex-col gap-y-2">
<!-- Header row 1 -->
Expand All @@ -83,17 +87,15 @@
<AlertOwnerBlock
{organization}
{project}
ownerId={$alertQuery.data.resource.alert.spec.annotations[
"admin_owner_user_id"
]}
ownerId={alertSpec.annotations["admin_owner_user_id"]}
/>
{/if}
</svelte:fragment>
</ProjectAccessControls>
</div>
<div class="flex gap-x-2 items-center">
<h1 class="text-gray-700 text-lg font-bold">
{$alertQuery.data.resource.alert.spec.title}
{alertSpec.title}
</h1>
<div class="grow" />
{#if !$isAlertCreatedByCode.data}
Expand Down Expand Up @@ -159,19 +161,28 @@
filters={metricsViewAggregationRequest?.having}
/>

<!-- Recipients -->
<EmailRecipients
emailRecipients={$alertQuery.data.resource.alert.spec.notifiers.find(
(n) => n.connector === "email",
)?.properties?.recipients ?? []}
/>
<!-- Slack notification -->
{#if slackNotifier}
<MetadataList
data={[...slackNotifier.channels, ...slackNotifier.users]}
label="Slack notifications"
/>
{/if}

<!-- Email notifications -->
{#if emailNotifier}
<MetadataList
data={emailNotifier.recipients}
label="Email notifications"
/>
{/if}
</div>
{/if}

{#if $alertQuery.data && $dashboard.data?.metricsView.spec}
<EditAlertDialog
open={showEditAlertDialog}
alertSpec={$alertQuery.data.resource.alert.spec}
{alertSpec}
on:close={() => (showEditAlertDialog = false)}
metricsViewName={$dashboardName.data}
/>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<script lang="ts">
import MetadataLabel from "@rilldata/web-admin/features/scheduled-reports/metadata/MetadataLabel.svelte";
import { Tag } from "@rilldata/web-common/components/tag/index.js";
export let data: string[];
export let label: string;
</script>

<div class="flex flex-col gap-y-3">
<MetadataLabel>{label} ({data.length})</MetadataLabel>
<div class="flex flex-wrap gap-2">
{#each data as datum}
<Tag>
{datum}
</Tag>
{/each}
</div>
</div>
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<script lang="ts">
import { goto } from "$app/navigation";
import EmailRecipients from "@rilldata/web-admin/features/scheduled-reports/metadata/EmailRecipients.svelte";
import MetadataList from "@rilldata/web-admin/features/scheduled-reports/metadata/MetadataList.svelte";
import { extractNotifier } from "@rilldata/web-admin/features/scheduled-reports/metadata/notifiers-utils";
import IconButton from "@rilldata/web-common/components/button/IconButton.svelte";
import * as DropdownMenu from "@rilldata/web-common/components/dropdown-menu";
import ThreeDot from "@rilldata/web-common/components/icons/ThreeDot.svelte";
Expand Down Expand Up @@ -48,6 +49,11 @@
},
);

$: emailNotifier = extractNotifier(
$reportQuery.data.resource.report.spec.notifiers,
"email",
);

// Actions
const queryClient = useQueryClient();
const deleteReport = createAdminServiceDeleteReport();
Expand Down Expand Up @@ -161,11 +167,9 @@
</div>

<!-- Recipients -->
<EmailRecipients
emailRecipients={$reportQuery.data.resource.report.spec.notifiers.find(
(n) => n.connector === "email",
)?.properties?.recipients ?? []}
/>
{#if emailNotifier}
<MetadataList data={emailNotifier.recipients} label="Recipients" />
{/if}
</div>
{/if}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import type { V1Notifier } from "@rilldata/web-common/runtime-client";

export type EmailNotifierProperties = {
recipients: string[];
};
export type SlackNotifierProperties = {
users: string[];
channels: string[];
webhooks: string[];
};

type NotifierPropsMap = {
email: EmailNotifierProperties;
slack: SlackNotifierProperties;
};

export function extractNotifier<Notifier extends keyof NotifierPropsMap>(
notifiers: V1Notifier[] | undefined,
name: Notifier,
): NotifierPropsMap[Notifier] | undefined {
if (!notifiers) return undefined;
const notifier = notifiers.find((n) => n.connector === name);
if (!notifier?.properties) return undefined;
return notifier.properties as NotifierPropsMap[Notifier];
}
11 changes: 10 additions & 1 deletion web-common/src/components/forms/FormSection.svelte
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
<script lang="ts">
import Switch from "@rilldata/web-common/components/forms/Switch.svelte";
import InfoCircle from "@rilldata/web-common/components/icons/InfoCircle.svelte";
import Tooltip from "@rilldata/web-common/components/tooltip/Tooltip.svelte";
import TooltipContent from "@rilldata/web-common/components/tooltip/TooltipContent.svelte";

export let title: string;
export let description: string = "";
export let padding = "p-3";
export let showSectionToggle = false;
export let enabled = true;
</script>

<div class="flex flex-col bg-white {padding} gap-y-3 rounded">
Expand All @@ -22,10 +25,16 @@
</TooltipContent>
</Tooltip>
{/if}
{#if showSectionToggle}
<div class="grow"></div>
<Switch bind:checked={enabled} />
{/if}
</span>
{#if description}
<span class="text-sm text-slate-600">{description}</span>
{/if}
</div>
<slot />
{#if enabled}
<slot />
{/if}
</div>
46 changes: 32 additions & 14 deletions web-common/src/components/forms/InputArray.svelte
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<script lang="ts">
import { createEventDispatcher } from "svelte";
import type { createForm } from "svelte-forms-lib";
import { slide } from "svelte/transition";
import { Button, IconButton } from "../button";
import Add from "../icons/Add.svelte";
Expand All @@ -8,24 +8,44 @@
import Tooltip from "../tooltip/Tooltip.svelte";
import TooltipContent from "../tooltip/TooltipContent.svelte";

export let id = "";
export let id: string;
export let label = "";
export let values: any[];
export let errors: any[];
export let description = "";
// The accessorKey is necessary due to the way svelte-forms-lib works with arrays.
// See: https://svelte-forms-lib-sapper-docs.vercel.app/array
export let accessorKey: string;
export let placeholder = "";
export let hint = "";
export let addItemLabel = "Add item";

const dispatch = createEventDispatcher();
export let formState: ReturnType<typeof createForm<Record<string, any>>>;
const { form, errors } = formState;
$: values = $form[id] as Record<string, string>[];
// There's a bug in how `svelte-forms-lib` types the `$errors` store for arrays.
// See: https://github.com/tjinauyeung/svelte-forms-lib/issues/154#issuecomment-1087331250
$: errs = ($errors[id] as unknown as Record<string, string>[]) ?? [];

function handleKeyDown(event: KeyboardEvent) {
if (event.key === "Enter") {
event.preventDefault();
}
}

function handleAddItem() {
$form[id] = $form[id].concat({ email: "" });
errs = errs.concat({ email: "" });
// Focus on the new input element
setTimeout(() => {
const input = document.getElementById(
`${id}.${$form[id].length - 1}.${accessorKey}`,
);
input?.focus();
}, 0);
}
function handleRemove(index: number) {
$form[id] = $form[id].filter((_, i) => i !== index);
errs = errs.filter((r, i) => i !== index);
}
</script>

<div class="flex flex-col gap-y-2.5">
Expand All @@ -42,6 +62,9 @@
</TooltipContent>
</Tooltip>
{/if}
{#if description}
<div class="text-sm text-slate-600">{description}</div>
{/if}
</div>
{/if}
<div
Expand All @@ -60,23 +83,18 @@
]?.accessorKey && 'border-red-500'}"
on:keydown={handleKeyDown}
/>
<IconButton
on:click={() =>
dispatch("remove-item", {
index: i,
})}
>
<IconButton on:click={() => handleRemove(i)}>
<Trash size="16px" className="text-gray-500 cursor-pointer" />
</IconButton>
</div>
{#if errors[i]?.[accessorKey]}
{#if errs[i]?.[accessorKey]}
<div in:slide={{ duration: 200 }} class="text-red-500 text-sm py-px">
{errors[i][accessorKey]}
{errs[i][accessorKey]}
</div>
{/if}
</div>
{/each}
<Button on:click={() => dispatch("add-item")} type="secondary" dashed>
<Button dashed on:click={handleAddItem} type="secondary">
<div class="flex gap-x-2">
<Add className="text-gray-700" />
{addItemLabel}
Expand Down
9 changes: 7 additions & 2 deletions web-common/src/features/alerts/BaseAlertForm.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,18 @@
import { DialogTitle } from "@rgossiaux/svelte-headlessui";
import * as DialogTabs from "@rilldata/web-common/components/dialog/tabs";
import { createEventDispatcher } from "svelte";
import type { createForm } from "svelte-forms-lib";
import Button from "../../components/button/Button.svelte";
import AlertDialogCriteriaTab from "./criteria-tab/AlertDialogCriteriaTab.svelte";
import AlertDialogDataTab from "./data-tab/AlertDialogDataTab.svelte";
import AlertDialogDeliveryTab from "./delivery-tab/AlertDialogDeliveryTab.svelte";
import { checkIsTabValid, FieldsByTab } from "./form-utils";
import {
type AlertFormValues,
checkIsTabValid,
FieldsByTab,
} from "./form-utils";

export let formState: any; // svelte-forms-lib's FormState
export let formState: ReturnType<typeof createForm<AlertFormValues>>;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great to see this typing 👍

export let isEditForm: boolean;

const dispatch = createEventDispatcher();
Expand Down
27 changes: 22 additions & 5 deletions web-common/src/features/alerts/CreateAlertDialog.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@
dimension = $dashboardStore.selectedDimensionName ?? "";
}

const formState = createForm({
const formState = createForm<AlertFormValues>({
initialValues: {
name: "",
measure:
Expand All @@ -70,7 +70,18 @@
],
criteriaOperation: V1Operation.OPERATION_AND,
snooze: SnoozeOptions[0].value, // Defaults to `Off`
recipients: [
enableSlackNotification: true,
slackChannels: [
{
channel: "",
},
],
slackUsers: [
{ email: $user.data?.user?.email ? $user.data.user.email : "" },
{ email: "" },
],
enableEmailNotification: true,
emailRecipients: [
{ email: $user.data?.user?.email ? $user.data.user.email : "" },
{ email: "" },
],
Expand Down Expand Up @@ -98,9 +109,15 @@
getAlertQueryArgsFromFormValues(values),
),
metricsViewName: values.metricsViewName,
emailRecipients: values.recipients
.map((r) => r.email)
.filter(Boolean),
slackChannels: values.enableSlackNotification
? values.slackChannels.map((c) => c.channel).filter(Boolean)
: undefined,
slackUsers: values.enableSlackNotification
? values.slackUsers.map((c) => c.email).filter(Boolean)
: undefined,
emailRecipients: values.enableEmailNotification
? values.emailRecipients.map((r) => r.email).filter(Boolean)
: undefined,
renotify: !!values.snooze,
renotifyAfterSeconds: values.snooze ? Number(values.snooze) : 0,
},
Expand Down
Loading