feature(dashboard): add integrations and notifications
This commit is contained in:
@@ -29,6 +29,7 @@ COPY packages/queue/package.json ./packages/queue/
|
||||
COPY packages/logger/package.json ./packages/logger/
|
||||
COPY packages/common/package.json ./packages/common/
|
||||
COPY packages/constants/package.json ./packages/constants/
|
||||
COPY packages/integrations/package.json packages/integrations/
|
||||
|
||||
# BUILD
|
||||
FROM base AS build
|
||||
@@ -70,7 +71,7 @@ COPY --from=build /app/packages/redis ./packages/redis
|
||||
COPY --from=build /app/packages/logger ./packages/logger
|
||||
COPY --from=build /app/packages/queue ./packages/queue
|
||||
COPY --from=build /app/packages/common ./packages/common
|
||||
|
||||
COPY --from=build /app/packages/integrations ./packages/integrations
|
||||
RUN pnpm db:codegen
|
||||
|
||||
WORKDIR /app/apps/worker
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
"@bull-board/express": "5.21.0",
|
||||
"@openpanel/common": "workspace:*",
|
||||
"@openpanel/db": "workspace:*",
|
||||
"@openpanel/integrations": "workspace:^",
|
||||
"@openpanel/logger": "workspace:*",
|
||||
"@openpanel/queue": "workspace:*",
|
||||
"@openpanel/redis": "workspace:*",
|
||||
|
||||
@@ -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', {
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
71
apps/worker/src/jobs/notification.ts
Normal file
71
apps/worker/src/jobs/notification.ts
Normal 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',
|
||||
),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ const options: Options = {
|
||||
entry: ['src/index.ts'],
|
||||
noExternal: [/^@openpanel\/.*$/u, /^@\/.*$/u],
|
||||
external: ['@hyperdx/node-opentelemetry', 'winston'],
|
||||
ignoreWatch: ['../../**/{.git,node_modules}/**'],
|
||||
sourcemap: true,
|
||||
splitting: false,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user