feature(dashboard): add integrations and notifications

This commit is contained in:
Carl-Gerhard Lindesvärd
2024-10-02 22:12:05 +02:00
parent d920f6951c
commit f65a633403
94 changed files with 3692 additions and 127 deletions

View File

@@ -7,11 +7,17 @@ import express from 'express';
import { createInitialSalts } from '@openpanel/db';
import type { CronQueueType } from '@openpanel/queue';
import { cronQueue, eventsQueue, sessionsQueue } from '@openpanel/queue';
import {
cronQueue,
eventsQueue,
notificationQueue,
sessionsQueue,
} from '@openpanel/queue';
import { getRedisQueue } from '@openpanel/redis';
import { cronJob } from './jobs/cron';
import { eventsJob } from './jobs/events';
import { notificationJob } from './jobs/notification';
import { sessionsJob } from './jobs/sessions';
import { register } from './metrics';
import { logger } from './utils/logger';
@@ -34,12 +40,18 @@ async function start() {
workerOptions,
);
const cronWorker = new Worker(cronQueue.name, cronJob, workerOptions);
const notificationWorker = new Worker(
notificationQueue.name,
notificationJob,
workerOptions,
);
createBullBoard({
queues: [
new BullMQAdapter(eventsQueue),
new BullMQAdapter(sessionsQueue),
new BullMQAdapter(cronQueue),
new BullMQAdapter(notificationQueue),
],
serverAdapter: serverAdapter,
});
@@ -62,7 +74,12 @@ async function start() {
console.log(`For the UI, open http://localhost:${PORT}/`);
});
const workers = [sessionsWorker, eventsWorker, cronWorker];
const workers = [
sessionsWorker,
eventsWorker,
cronWorker,
notificationWorker,
];
workers.forEach((worker) => {
worker.on('error', (error) => {
logger.error('worker error', {

View File

@@ -6,6 +6,7 @@ import { getTime } from '@openpanel/common';
import {
type IServiceEvent,
TABLE_NAMES,
checkNotificationRulesForSessionEnd,
createEvent,
eventBuffer,
getEvents,
@@ -138,6 +139,8 @@ export async function createSessionEnd(
throw new Error('No last event found');
}
await checkNotificationRulesForSessionEnd(events);
return createEvent({
...sessionStart,
properties: {

View File

@@ -7,17 +7,17 @@ import { v4 as uuid } from 'uuid';
import { logger } from '@/utils/logger';
import { getTime, isSameDomain, parsePath } from '@openpanel/common';
import type { IServiceCreateEventPayload } from '@openpanel/db';
import { createEvent } from '@openpanel/db';
import { checkNotificationRulesForEvent, createEvent } from '@openpanel/db';
import { getLastScreenViewFromProfileId } from '@openpanel/db/src/services/event.service';
import type {
EventsQueuePayloadCreateSessionEnd,
EventsQueuePayloadIncomingEvent,
} from '@openpanel/queue';
import {
findJobByPrefix,
sessionsQueue,
sessionsQueueEvents,
} from '@openpanel/queue';
import type {
EventsQueuePayloadCreateSessionEnd,
EventsQueuePayloadIncomingEvent,
} from '@openpanel/queue';
import { getRedisQueue } from '@openpanel/redis';
const GLOBAL_PROPERTIES = ['__path', '__referrer'];
@@ -101,6 +101,8 @@ export async function incomingEvent(job: Job<EventsQueuePayloadIncomingEvent>) {
sdkVersion,
};
await checkNotificationRulesForEvent(payload);
return createEvent(payload);
}
@@ -185,6 +187,8 @@ export async function incomingEvent(job: Job<EventsQueuePayloadIncomingEvent>) {
});
}
await checkNotificationRulesForEvent(payload);
return createEvent(payload);
}

View File

@@ -0,0 +1,71 @@
import type { Job } from 'bullmq';
import { setSuperJson } from '@openpanel/common';
import { db } from '@openpanel/db';
import { sendDiscordNotification } from '@openpanel/integrations/src/discord';
import { sendSlackNotification } from '@openpanel/integrations/src/slack';
import type { NotificationQueuePayload } from '@openpanel/queue';
import { getRedisPub } from '@openpanel/redis';
export async function notificationJob(job: Job<NotificationQueuePayload>) {
switch (job.data.type) {
case 'sendNotification': {
const { notification } = job.data.payload;
if (notification.sendToApp) {
getRedisPub().publish('notification', setSuperJson(notification));
// empty for now
return;
}
if (notification.sendToEmail) {
// empty for now
return;
}
if (!notification.integrationId) {
throw new Error('No integrationId provided');
}
const integration = await db.integration.findUniqueOrThrow({
where: {
id: notification.integrationId,
},
});
switch (integration.config.type) {
case 'webhook': {
return fetch(integration.config.url, {
method: 'POST',
headers: {
...(integration.config.headers ?? {}),
'Content-Type': 'application/json',
},
body: JSON.stringify({
title: notification.title,
message: notification.message,
}),
});
}
case 'discord': {
return sendDiscordNotification({
webhookUrl: integration.config.url,
message: [
`🔔 **${notification.title}**`,
notification.message,
].join('\n'),
});
}
case 'slack': {
return sendSlackNotification({
webhookUrl: integration.config.incoming_webhook.url,
message: [`🔔 *${notification.title}*`, notification.message].join(
'\n',
),
});
}
}
}
}
}