Openstatus www.openstatus.dev

chore: slack webhook support url (#957)

authored by

Maximilian Kaske and committed by
GitHub
9c9285d9 822a137e

+35 -122
+1 -1
.vscode/settings.json
··· 1 1 { 2 2 "editor.codeActionsOnSave": {}, 3 3 "editor.defaultFormatter": "biomejs.biome", 4 - "editor.formatOnSave": false, 4 + "editor.formatOnSave": true, 5 5 "typescript.tsdk": "node_modules/typescript/lib", 6 6 7 7 "typescript.enablePromptUseWorkspaceTsdk": true,
+2 -2
apps/web/.env.example
··· 72 72 AUTH_GOOGLE_ID= 73 73 AUTH_GOOGLE_SECRET= 74 74 75 - PLAIN_API_KEY= 76 - 77 75 PAGERDUTY_APP_ID= 76 + 77 + SLACK_SUPPORT_WEBHOOK_URL=
-1
apps/web/package.json
··· 43 43 "@tailwindcss/container-queries": "0.1.1", 44 44 "@tailwindcss/typography": "0.5.10", 45 45 "@tanstack/react-table": "8.10.3", 46 - "@team-plain/typescript-sdk": "4.2.3", 47 46 "@tremor/react": "3.17.4", 48 47 "@trpc/client": "10.45.2", 49 48 "@trpc/next": "10.45.2",
+27 -61
apps/web/src/components/support/action.ts
··· 1 1 "use server"; 2 2 3 - import { PlainClient } from "@team-plain/typescript-sdk"; 4 - 5 3 import { env } from "@/env"; 6 - 7 4 import type { FormValues } from "./contact-form"; 8 5 9 - const labelTypeIds = { 10 - bug: "lt_01HZDA8FCA0CMPCJ2YSTVE78XN", 11 - demo: "lt_01HZDA8N33R1A9AN97RGXPZ93F", 12 - feature: "lt_01HZDA8V56431V27GFDAVV58FX", 13 - question: "lt_01HZDA8XWTY0SWY4MKNY6F4EVK", 14 - security: "lt_01HZDA91M7KVR4529FHVHE18MG", 15 - }; 16 - 17 - function getPriority(type: FormValues["type"], isBlocking: boolean) { 18 - if (type === "security") return 0; 19 - if (type === "bug" && isBlocking) return 1; 20 - return 2; 21 - } 22 - 23 - function isString(s: unknown): s is string { 24 - return typeof s === "string"; 25 - } 26 - 27 - const client = new PlainClient({ 28 - apiKey: env.PLAIN_API_KEY || "", 29 - }); 30 - 31 - export async function handlePlainSupport(values: FormValues) { 32 - const upsertCustomerRes = await client.upsertCustomer({ 33 - identifier: { 34 - emailAddress: values.email, 35 - }, 36 - onCreate: { 37 - fullName: values.name, 38 - email: { 39 - email: values.email, 40 - isVerified: true, 41 - }, 42 - }, 43 - onUpdate: {}, 44 - }); 45 - 46 - if (upsertCustomerRes.error) { 47 - console.error(upsertCustomerRes.error); 48 - return { error: upsertCustomerRes.error.message }; 49 - } 50 - 51 - console.log(`Customer upserted ${upsertCustomerRes.data.customer.id}`); 52 - 53 - const createThreadRes = await client.createThread({ 54 - customerIdentifier: { 55 - customerId: upsertCustomerRes.data.customer.id, 56 - }, 57 - title: "Support request!", 58 - components: [{ componentText: { text: values.message } }], 59 - labelTypeIds: [labelTypeIds[values.type]].filter(isString), 60 - priority: getPriority(values.type, values.blocker), 61 - }); 6 + export async function handleSlackWebhookSupport(values: FormValues) { 7 + console.log(env); 8 + if (!env.SLACK_SUPPORT_WEBHOOK_URL) return { error: "Missing webhook URL." }; 62 9 63 - if (createThreadRes.error) { 64 - console.error(createThreadRes.error); 65 - return { error: createThreadRes.error.message }; 10 + try { 11 + await fetch(env.SLACK_SUPPORT_WEBHOOK_URL, { 12 + method: "POST", 13 + body: JSON.stringify({ 14 + blocks: [ 15 + { 16 + type: "section", 17 + text: { 18 + type: "mrkdwn", 19 + text: `*New Support Request!* [${process.env.NODE_ENV.toUpperCase()}]\n\n 20 + *Name:* ${values.name}\n 21 + *Type:* ${values.type}\n 22 + *Blocker:* ${values.blocker ? "Yes" : "No"}\n 23 + *Email:* ${values.email}\n 24 + *Message:* ${values.message}\n 25 + `, 26 + }, 27 + }, 28 + ], 29 + }), 30 + }); 31 + } catch (e) { 32 + console.log(e); 33 + return { error: "Something went wrong. Please try again." }; 66 34 } 67 - 68 - console.log(`Thread created ${createThreadRes.data.id}`); 69 35 70 36 return { error: null }; 71 37 }
+3 -4
apps/web/src/components/support/contact-form.tsx
··· 25 25 26 26 import { toast } from "@/lib/toast"; 27 27 import { LoadingAnimation } from "../loading-animation"; 28 - import { handlePlainSupport } from "./action"; 28 + import { handleSlackWebhookSupport } from "./action"; 29 29 30 30 export const types = [ 31 31 { ··· 77 77 78 78 async function onSubmit(data: FormValues) { 79 79 startTransition(async () => { 80 - const result = await handlePlainSupport(data); 80 + const result = await handleSlackWebhookSupport(data); 81 81 if (result.error) { 82 - console.error(result.error); 83 - toast.error("Something went wrong. Please try again."); 82 + toast.error(result.error); 84 83 } else { 85 84 handleSubmit?.(); 86 85 toast.success(
+2 -2
apps/web/src/env.ts
··· 23 23 CLICKHOUSE_URL: z.string(), 24 24 CLICKHOUSE_USERNAME: z.string(), 25 25 CLICKHOUSE_PASSWORD: z.string(), 26 - PLAIN_API_KEY: z.string().optional(), 27 26 PAGERDUTY_APP_ID: z.string().optional(), 27 + SLACK_SUPPORT_WEBHOOK_URL: z.string().optional(), 28 28 }, 29 29 client: { 30 30 NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY: z.string(), ··· 57 57 CLICKHOUSE_URL: process.env.CLICKHOUSE_URL, 58 58 CLICKHOUSE_USERNAME: process.env.CLICKHOUSE_USERNAME, 59 59 CLICKHOUSE_PASSWORD: process.env.CLICKHOUSE_PASSWORD, 60 - PLAIN_API_KEY: process.env.PLAIN_API_KEY, 61 60 PAGERDUTY_APP_ID: process.env.PAGERDUTY_APP_ID, 61 + SLACK_SUPPORT_WEBHOOK_URL: process.env.SLACK_SUPPORT_WEBHOOK_URL, 62 62 }, 63 63 skipValidation: true, 64 64 });
-51
pnpm-lock.yaml
··· 332 332 '@tanstack/react-table': 333 333 specifier: 8.10.3 334 334 version: 8.10.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) 335 - '@team-plain/typescript-sdk': 336 - specifier: 4.2.3 337 - version: 4.2.3 338 335 '@tremor/react': 339 336 specifier: 3.17.4 340 337 version: 3.17.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(tailwindcss@3.4.3(ts-node@10.9.2(@types/node@20.14.8)(typescript@5.5.2))) ··· 3072 3069 resolution: {integrity: sha512-vwj0jzkHNgY/WvqrNt763QfO3mRHIO1GSFW22scOneI5pcu5sMb/JPN+P+cDPTEXRNPdZCaZ/HicLHZNxbseyA==} 3073 3070 engines: {node: '>=v14'} 3074 3071 3075 - '@graphql-typed-document-node/core@3.2.0': 3076 - resolution: {integrity: sha512-mB9oAsNCm9aM3/SOv4YtBMqZbYj10R7dkq8byBqxGY/ncFwhf2oQzMV+LCRlWoDSEBJ3COiR1yeDvMtsoOsuFQ==} 3077 - peerDependencies: 3078 - graphql: ^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 3079 - 3080 3072 '@grpc/grpc-js@1.9.7': 3081 3073 resolution: {integrity: sha512-yMaA/cIsRhGzW3ymCNpdlPcInXcovztlgu/rirThj2b87u3RzWUszliOqZ/pldy7yhmJPS8uwog+kZSTa4A0PQ==} 3082 3074 engines: {node: ^8.13.0 || >=10.10.0} ··· 4928 4920 peerDependencies: 4929 4921 vue: ^2.7.0 || ^3.0.0 4930 4922 4931 - '@team-plain/typescript-sdk@4.2.3': 4932 - resolution: {integrity: sha512-aJjN7qXc07t4aJzf55CsiRd6o5OD7hLAEvsG0qMWOSCBq2O802SSucPmHTufAMo5r5/TVLpFVz+aR29nXwW4xg==} 4933 - 4934 4923 '@testing-library/dom@10.1.0': 4935 4924 resolution: {integrity: sha512-wdsYKy5zupPyLCW2Je5DLHSxSfbIp6h80WoHOQc+RPtmPGA52O9x5MJEkv92Sjonpq+poOAtUKhh1kBGAXBrNA==} 4936 4925 engines: {node: '>=18'} ··· 5396 5385 ajv: 5397 5386 optional: true 5398 5387 5399 - ajv-formats@2.1.1: 5400 - resolution: {integrity: sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==} 5401 - peerDependencies: 5402 - ajv: ^8.0.0 5403 - peerDependenciesMeta: 5404 - ajv: 5405 - optional: true 5406 - 5407 5388 ajv-formats@3.0.1: 5408 5389 resolution: {integrity: sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==} 5409 5390 peerDependencies: ··· 5411 5392 peerDependenciesMeta: 5412 5393 ajv: 5413 5394 optional: true 5414 - 5415 - ajv@8.14.0: 5416 - resolution: {integrity: sha512-oYs1UUtO97ZO2lJ4bwnWeQW8/zvOIQLGKcvPTsWmvc2SYgBb+upuNS5NxoLaMU4h8Ju3Nbj6Cq8mD2LQoqVKFA==} 5417 5395 5418 5396 ajv@8.16.0: 5419 5397 resolution: {integrity: sha512-F0twR8U1ZU67JIEtekUcLkXkoO5mMMmgGD8sK/xUFzJ805jxHQl92hImFAqqXMyMYjSPOyUPAwHYhB72g5sTXw==} ··· 6947 6925 gradient-string@2.0.2: 6948 6926 resolution: {integrity: sha512-rEDCuqUQ4tbD78TpzsMtt5OIf0cBCSDWSJtUDaF6JsAh+k0v9r++NzxNEG87oDZx9ZwGhD8DaezR2L/yrw0Jdw==} 6949 6927 engines: {node: '>=10'} 6950 - 6951 - graphql@16.8.1: 6952 - resolution: {integrity: sha512-59LZHPdGZVh695Ud9lRzPBVTtlX9ZCV150Er2W43ro37wVof0ctenSaskPPjN7lVTIN8mSZt8PHUNKZuNQUuxw==} 6953 - engines: {node: ^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0} 6954 6928 6955 6929 gray-matter@4.0.3: 6956 6930 resolution: {integrity: sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q==} ··· 12625 12599 - encoding 12626 12600 - supports-color 12627 12601 12628 - '@graphql-typed-document-node/core@3.2.0(graphql@16.8.1)': 12629 - dependencies: 12630 - graphql: 16.8.1 12631 - 12632 12602 '@grpc/grpc-js@1.9.7': 12633 12603 dependencies: 12634 12604 '@grpc/proto-loader': 0.7.10 ··· 15045 15015 '@tanstack/virtual-core': 3.8.3 15046 15016 vue: 3.4.31(typescript@5.5.2) 15047 15017 15048 - '@team-plain/typescript-sdk@4.2.3': 15049 - dependencies: 15050 - '@graphql-typed-document-node/core': 3.2.0(graphql@16.8.1) 15051 - ajv: 8.14.0 15052 - ajv-formats: 2.1.1(ajv@8.14.0) 15053 - graphql: 16.8.1 15054 - zod: 3.22.4 15055 - 15056 15018 '@testing-library/dom@10.1.0': 15057 15019 dependencies: 15058 15020 '@babel/code-frame': 7.24.7 ··· 15602 15564 optionalDependencies: 15603 15565 ajv: 8.16.0 15604 15566 15605 - ajv-formats@2.1.1(ajv@8.14.0): 15606 - optionalDependencies: 15607 - ajv: 8.14.0 15608 - 15609 15567 ajv-formats@3.0.1(ajv@8.16.0): 15610 15568 optionalDependencies: 15611 15569 ajv: 8.16.0 15612 - 15613 - ajv@8.14.0: 15614 - dependencies: 15615 - fast-deep-equal: 3.1.3 15616 - json-schema-traverse: 1.0.0 15617 - require-from-string: 2.0.2 15618 - uri-js: 4.4.1 15619 15570 15620 15571 ajv@8.16.0: 15621 15572 dependencies: ··· 17277 17228 dependencies: 17278 17229 chalk: 4.1.2 17279 17230 tinygradient: 1.1.5 17280 - 17281 - graphql@16.8.1: {} 17282 17231 17283 17232 gray-matter@4.0.3: 17284 17233 dependencies: