feature(dashboard): add template for notification rules

This commit is contained in:
Carl-Gerhard Lindesvärd
2024-10-22 22:13:53 +02:00
parent e7d135dddb
commit 71794c802c
6 changed files with 56 additions and 3 deletions

View File

@@ -15,6 +15,7 @@ import { PureFilterItem } from '@/components/report/sidebar/filters/FilterItem';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Combobox } from '@/components/ui/combobox'; import { Combobox } from '@/components/ui/combobox';
import { ComboboxAdvanced } from '@/components/ui/combobox-advanced'; import { ComboboxAdvanced } from '@/components/ui/combobox-advanced';
import { Textarea } from '@/components/ui/textarea';
import { useAppParams } from '@/hooks/useAppParams'; import { useAppParams } from '@/hooks/useAppParams';
import { useEventNames } from '@/hooks/useEventNames'; import { useEventNames } from '@/hooks/useEventNames';
import { useEventProperties } from '@/hooks/useEventProperties'; import { useEventProperties } from '@/hooks/useEventProperties';
@@ -51,6 +52,7 @@ export default function AddNotificationRule({ rule }: Props) {
integrations: integrations:
rule?.integrations.map((integration) => integration.id) ?? [], rule?.integrations.map((integration) => integration.id) ?? [],
projectId, projectId,
template: rule?.template ?? '',
config: rule?.config ?? { config: rule?.config ?? {
type: 'events', type: 'events',
events: [ events: [
@@ -160,6 +162,16 @@ export default function AddNotificationRule({ rule }: Props) {
</div> </div>
</WithLabel> </WithLabel>
<WithLabel
label="Template"
info="Customize your notification. Exisiting variables: $EVENT_NAME, $RULE_NAME"
>
<Textarea
{...form.register('template')}
placeholder="You received a new '$EVENT_NAME' event"
/>
</WithLabel>
<Controller <Controller
control={form.control} control={form.control}
name="integrations" name="integrations"

View File

@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "notification_rules" ADD COLUMN "template" TEXT;

View File

@@ -329,6 +329,7 @@ model NotificationRule {
sendToEmail Boolean @default(false) sendToEmail Boolean @default(false)
/// [IPrismaNotificationRuleConfig] /// [IPrismaNotificationRuleConfig]
config Json config Json
template String?
createdAt DateTime @default(now()) createdAt DateTime @default(now())
updatedAt DateTime @default(now()) @updatedAt updatedAt DateTime @default(now()) @updatedAt

View File

@@ -59,6 +59,9 @@ export const BASE_INTEGRATIONS: Integration[] = [
export const isBaseIntegration = (id: string) => export const isBaseIntegration = (id: string) =>
BASE_INTEGRATIONS.find((i) => i.id === id); BASE_INTEGRATIONS.find((i) => i.id === id);
export type INotificationRuleCached = Awaited<
ReturnType<typeof getNotificationRulesByProjectId>
>[number];
export const getNotificationRulesByProjectId = cacheable( export const getNotificationRulesByProjectId = cacheable(
function getNotificationRulesByProjectId(projectId: string) { function getNotificationRulesByProjectId(projectId: string) {
return db.notificationRule.findMany({ return db.notificationRule.findMany({
@@ -71,6 +74,7 @@ export const getNotificationRulesByProjectId = cacheable(
sendToApp: true, sendToApp: true,
sendToEmail: true, sendToEmail: true,
config: true, config: true,
template: true,
integrations: { integrations: {
select: { select: {
id: true, id: true,
@@ -190,6 +194,32 @@ export function matchEvent(
return true; return true;
} }
function notificationTemplateEvent({
payload,
rule,
}: {
payload: IServiceCreateEventPayload;
rule: INotificationRuleCached;
}) {
if (!rule.template) return `You received a new "${payload.name}" event`;
return rule.template
.replaceAll('$EVENT_NAME', payload.name)
.replaceAll('$RULE_NAME', rule.name);
}
function notificationTemplateFunnel({
events,
rule,
}: {
events: IServiceEvent[];
rule: INotificationRuleCached;
}) {
if (!rule.template) return `Funnel "${rule.name}" completed`;
return rule.template
.replaceAll('$EVENT_NAME', events.map((e) => e.name).join(' -> '))
.replaceAll('$RULE_NAME', rule.name);
}
export async function checkNotificationRulesForEvent( export async function checkNotificationRulesForEvent(
payload: IServiceCreateEventPayload, payload: IServiceCreateEventPayload,
) { ) {
@@ -207,7 +237,10 @@ export async function checkNotificationRulesForEvent(
} }
const notification = { const notification = {
title: `You received a new "${payload.name}" event`, title: notificationTemplateEvent({
payload,
rule,
}),
message: project?.name ? `Project: ${project?.name}` : '', message: project?.name ? `Project: ${project?.name}` : '',
projectId: payload.projectId, projectId: payload.projectId,
payload: { payload: {
@@ -266,7 +299,7 @@ export async function checkNotificationRulesForSessionEnd(
const notificationPromises = funnelRules.flatMap((rule) => { const notificationPromises = funnelRules.flatMap((rule) => {
// Match funnel events // Match funnel events
let funnelIndex = 0; let funnelIndex = 0;
const matchedEvents = []; const matchedEvents: IServiceEvent[] = [];
for (const event of sortedEvents) { for (const event of sortedEvents) {
if (matchEvent(event, rule.config.events[funnelIndex]!)) { if (matchEvent(event, rule.config.events[funnelIndex]!)) {
matchedEvents.push(event); matchedEvents.push(event);
@@ -280,7 +313,10 @@ export async function checkNotificationRulesForSessionEnd(
// Create notification object // Create notification object
const notification = { const notification = {
title: `Funnel "${rule.name}" completed`, title: notificationTemplateFunnel({
rule,
events: matchedEvents,
}),
message: project?.name ? `Project: ${project?.name}` : '', message: project?.name ? `Project: ${project?.name}` : '',
projectId, projectId,
payload: { type: 'funnel', funnel: matchedEvents } as const, payload: { type: 'funnel', funnel: matchedEvents } as const,

View File

@@ -100,6 +100,7 @@ export const notificationRouter = createTRPCRouter({
.map((id) => ({ id })), .map((id) => ({ id })),
}, },
config: input.config, config: input.config,
template: input.template || null,
}, },
}); });
} }

View File

@@ -264,6 +264,7 @@ export type INotificationRuleConfig = z.infer<typeof zNotificationRuleConfig>;
export const zCreateNotificationRule = z.object({ export const zCreateNotificationRule = z.object({
id: z.string().optional(), id: z.string().optional(),
name: z.string().min(1), name: z.string().min(1),
template: z.string().optional(),
config: zNotificationRuleConfig, config: zNotificationRuleConfig,
integrations: z.array(z.string()), integrations: z.array(z.string()),
sendToApp: z.boolean(), sendToApp: z.boolean(),