fix(dashboard): remove popup from slack integrations

This commit is contained in:
Carl-Gerhard Lindesvärd
2024-10-03 20:16:03 +02:00
parent f45b02407c
commit 4329960bd9
8 changed files with 87 additions and 99 deletions

View File

@@ -0,0 +1,62 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Error - OpenPanel</title>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600&display=swap" rel="stylesheet">
<style>
* {
box-sizing: border-box;
}
body {
font-family: 'Inter', sans-serif;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
margin: 0;
background-color: #f5f5f5;
padding: 1rem;
}
.error-container {
text-align: left;
padding: 2rem;
background-color: white;
border-radius: 8px;
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.1);
max-width: 350px;
}
.logo {
width: 80px;
margin-bottom: 1.5rem;
}
h1 {
color: #333;
margin-bottom: 0.5rem;
font-size: 1.5rem;
}
p {
color: #666;
font-size: 1rem;
line-height: 1.5;
}
</style>
</head>
<body>
<div class="error-container">
<img src="https://openpanel.dev/logo.svg" alt="OpenPanel Logo" class="logo">
<h1>Oops! Something went wrong</h1>
<p>We encountered an error while processing your request. Please try again later or contact support if the problem
persists.</p>
</div>
</body>
</html>

View File

@@ -224,39 +224,3 @@ export async function wsProjectNotifications(
getRedisSub().off('message', message as any);
});
}
export async function wsIntegrationsSlack(
connection: {
socket: WebSocket;
},
req: FastifyRequest<{
Querystring: {
organizationId?: string;
};
}>,
) {
const { organizationId } = req.query;
if (!organizationId) {
connection.socket.send('No organizationId provided');
connection.socket.close();
return;
}
const subscribeToEvent = 'integrations:slack';
getRedisSub().subscribe(subscribeToEvent);
const onMessage = (channel: string, message: string) => {
if (channel === subscribeToEvent) {
const parsed = getSuperJson<{ organizationId: string }>(message);
if (parsed && parsed.organizationId === organizationId) {
connection.socket.send(message);
}
}
};
getRedisSub().on('message', onMessage);
connection.socket.on('close', () => {
getRedisSub().unsubscribe(subscribeToEvent);
getRedisSub().off('message', onMessage);
});
}

View File

@@ -1,5 +1,6 @@
import fs from 'node:fs';
import path from 'node:path';
import type { WebhookEvent } from '@clerk/fastify';
import { setSuperJson } from '@openpanel/common';
import { AccessLevel, db } from '@openpanel/db';
import {
sendSlackNotification,
@@ -167,6 +168,7 @@ const paramsSchema = z.object({
const metadataSchema = z.object({
organizationId: z.string(),
projectId: z.string(),
integrationId: z.string(),
});
@@ -179,7 +181,7 @@ export async function slackWebhook(
const parsedParams = paramsSchema.safeParse(request.query);
if (!parsedParams.success) {
request.log.error('Invalid params', parsedParams);
request.log.error(parsedParams.error, 'Invalid params');
return reply.status(400).send({ error: 'Invalid params' });
}
@@ -192,7 +194,7 @@ export async function slackWebhook(
);
if (!parsedMetadata.success) {
request.log.error('Invalid metadata', parsedMetadata.error.errors);
request.log.error(parsedMetadata.error, 'Invalid metadata');
return reply.status(400).send({ error: 'Invalid metadata' });
}
@@ -217,10 +219,8 @@ export async function slackWebhook(
},
'Failed to parse slack auth response',
);
return reply
.status(400)
.header('Content-Type', 'text/html')
.send('<h1>Failed to exchange code for token</h1>');
const html = fs.readFileSync(path.join(__dirname, 'error.html'), 'utf8');
return reply.status(500).header('Content-Type', 'text/html').send(html);
}
// Send a notification first to confirm the connection
@@ -230,10 +230,12 @@ export async function slackWebhook(
'👋 Hello. You have successfully connected OpenPanel.dev to your Slack workspace.',
});
const { projectId, organizationId, integrationId } = parsedMetadata.data;
await db.integration.update({
where: {
id: parsedMetadata.data.integrationId,
organizationId: parsedMetadata.data.organizationId,
id: integrationId,
organizationId,
},
data: {
config: {
@@ -243,22 +245,12 @@ export async function slackWebhook(
},
});
getRedisPub().publish(
'integrations:slack',
setSuperJson({
organizationId: parsedMetadata.data.organizationId,
}),
return reply.redirect(
`${process.env.NEXT_PUBLIC_DASHBOARD_URL}/${organizationId}/${projectId}/settings/integrations?tab=installed`,
);
return reply
.status(200)
.header('Content-Type', 'text/html')
.send('<h1>Slack integration added. You can close this window now.</h1>');
} catch (err) {
request.log.error(err);
return reply
.status(500)
.header('Content-Type', 'text/html')
.send('<h1>Failed to exchange code for token</h1>');
const html = fs.readFileSync(path.join(__dirname, 'error.html'), 'utf8');
return reply.status(500).header('Content-Type', 'text/html').send(html);
}
}

View File

@@ -32,11 +32,6 @@ const liveRouter: FastifyPluginCallback = (fastify, opts, done) => {
{ websocket: true },
controller.wsProjectNotifications,
);
fastify.get(
'/integrations/slack',
{ websocket: true },
controller.wsIntegrationsSlack,
);
done();
});

View File

@@ -19,49 +19,20 @@ export function SlackIntegrationForm({
defaultValues?: RouterOutputs['integration']['get'];
onSuccess: () => void;
}) {
const { organizationId } = useAppParams();
useWS(`/live/integrations/slack?organizationId=${organizationId}`, (res) => {
// @ts-expect-error
console.log('3. slack integration done', window.slackPopup);
// @ts-expect-error
if (window.slackPopup && typeof window.slackPopup.close === 'function') {
console.log('4. close popup');
// @ts-expect-error
window.slackPopup.close();
}
onSuccess();
});
const { organizationId, projectId } = useAppParams();
const form = useForm<IForm>({
defaultValues: {
id: defaultValues?.id,
organizationId,
projectId,
name: defaultValues?.name ?? '',
},
resolver: zodResolver(zCreateSlackIntegration),
});
const mutation = api.integration.createOrUpdateSlack.useMutation({
async onSuccess(res) {
console.log('1. onSuccess', res);
const url = res.slackInstallUrl;
const width = 600;
const height = 800;
const left = window.screenX + (window.outerWidth - width) / 2;
const top = window.screenY + (window.outerHeight - height) / 2.5;
console.log('2. open popup');
// @ts-expect-error
window.slackPopup = window.open(
url,
'',
`toolbar=no, location=no, directories=no, status=no, menubar=no, scrollbars=no, resizable=no, copyhistory=no, width=${width}, height=${height}, top=${top}, left=${left}`,
);
// The popup might have been blocked, so we redirect the user to the URL instead
//
// @ts-expect-error
if (!window.slackPopup) {
window.location.href = url;
}
window.location.href = res.slackInstallUrl;
},
onError() {
toast.error('Failed to create integration');

View File

@@ -18,7 +18,8 @@ export const slackInstaller = new InstallProvider({
export const getSlackInstallUrl = ({
integrationId,
organizationId,
}: { integrationId: string; organizationId: string }) => {
projectId,
}: { integrationId: string; organizationId: string; projectId: string }) => {
return slackInstaller.generateInstallUrl({
scopes: [
'incoming-webhook',
@@ -27,7 +28,7 @@ export const getSlackInstallUrl = ({
'team:read',
],
redirectUri: SLACK_OAUTH_REDIRECT_URL,
metadata: JSON.stringify({ integrationId, organizationId }),
metadata: JSON.stringify({ integrationId, organizationId, projectId }),
});
};

View File

@@ -66,6 +66,7 @@ export const integrationRouter = createTRPCRouter({
slackInstallUrl: await getSlackInstallUrl({
integrationId: res.id,
organizationId: input.organizationId,
projectId: input.projectId,
}),
};
}
@@ -84,6 +85,7 @@ export const integrationRouter = createTRPCRouter({
slackInstallUrl: await getSlackInstallUrl({
integrationId: res.id,
organizationId: input.organizationId,
projectId: input.projectId,
}),
};
}),

View File

@@ -214,6 +214,7 @@ const zCreateIntegration = z.object({
id: z.string().optional(),
name: z.string().min(1),
organizationId: z.string().min(1),
projectId: z.string().min(1),
});
export const zCreateSlackIntegration = zCreateIntegration;