Skip to content
93 linesCodeBlameRaw

Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.

Integrations are a directory of connectors, for a workspace and for each person1import { redirect } from "react-router";
2
3import { type Provider, PROVIDERS } from "@g1t/contracts";
4import { CONNECTORS, connectorsFor } from "@g1t/contracts/connectors";
5
6import type { Route } from "./+types/integrations-directory";
7import { ConnectorDirectory, ScopeNote } from "../../components/connectors";
8import type { ConnectedState } from "../../lib/connectors";
9import { githubApp } from "../../lib/github.server";
10import { integrationsSection } from "../../lib/integration-sections";
11import { page } from "../../lib/meta";
12import { integrations, webhooks } from "../../lib/services.server";
Merge branch 'browser-pass' into fast-push13import { requireUser, roleIn } from "../../lib/session.server";
Integrations are a directory of connectors, for a workspace and for each person14
15export function meta({ params, ...args }: Route.MetaArgs) {
16 return page(args, { title: `Integrations · ${params.owner} · g1t` });
17}
18
19/**
20 * The workspace's integrations: every connector g1t has or will have, by
21 * category, with what this workspace has connected first. Each one is set
22 * up on its own page (the model providers, alerts and trackers pages, the
23 * GitHub import, webhooks); this page only finds them.
24 */
25export async function loader({ params, context, request }: Route.LoaderArgs) {
Merge branch 'browser-pass' into fast-push26 // Signed out, sign in first (as Chat and Docs do), rather than a 404.
27 const viewer = requireUser(context, request);
Integrations are a directory of connectors, for a workspace and for each person28 const role = roleIn(viewer, params.owner);
29 if (!role || !viewer) throw new Response(null, { status: 404 });
30 const slug = params.owner.toLowerCase();
31
32 // An older link to one provider's form (`?add=sentry`): its own page now.
33 const add = new URL(request.url).searchParams.get("add");
34 if (add && add in PROVIDERS) {
35 throw redirect(`/${slug}/-/integrations/${integrationsSection(PROVIDERS[add as Provider].kind)}?add=${add}#add`);
36 }
37
38 const [connections, github, hooks] = await Promise.all([
39 integrations.list(slug, viewer),
40 githubApp.status(viewer, slug).catch(() => null),
41 webhooks.list(viewer, { workspace: slug }).catch(() => null),
42 ]);
43
44 const connected: Record<string, ConnectedState> = {};
45 if (connections.ok) {
46 for (const connector of CONNECTORS) {
47 if (!connector.provider) continue;
48 const mine = connections.value.filter((connection) => connection.provider === connector.provider);
49 if (mine.length === 0) continue;
50 const broken = mine.find((connection) => connection.lastError);
51 connected[connector.id] = {
52 detail: mine.length === 1 ? mine[0]!.name : `${mine.length} connections: ${mine.map((c) => c.name).join(", ")}`,
53 problem: broken ? `${broken.name}: ${broken.lastError}` : null,
54 manage: `/${slug}/-/integrations/${integrationsSection(PROVIDERS[connector.provider].kind)}`,
55 };
56 }
57 }
58 if (github?.ok && github.value.installations.length > 0) {
59 const suspended = github.value.installations.find((installation) => installation.suspended);
60 connected.github = {
61 detail: `On ${github.value.installations.map((installation) => installation.account).join(", ")}`,
62 problem: suspended ? `The installation on ${suspended.account} is suspended on GitHub.` : null,
63 manage: null,
64 };
65 }
66 const ours = hooks?.ok ? hooks.value.filter((hook) => hook.scope === "workspace") : [];
67 if (ours.length > 0) {
68 const failing = ours.filter((hook) => hook.lastStatus === "failed");
69 connected.webhooks = {
70 detail: ours.length === 1 ? ours[0]!.url : `${ours.length} webhooks`,
71 problem: failing.length > 0 ? `The last delivery to ${failing[0]!.url} failed.` : null,
72 manage: null,
73 };
74 }
75
76 return { slug, owner: role === "owner", connected };
77}
78
79export default function WorkspaceIntegrationsDirectory({ loaderData }: Route.ComponentProps) {
80 const { slug, owner, connected } = loaderData;
81 return (
82 <div>
83 <ScopeNote scope="workspace" workspace={slug} />
84 <ConnectorDirectory
85 scope="workspace"
86 views={connectorsFor("workspace")}
87 connected={connected}
88 workspace={slug}
89 canManage={owner}
90 />
91 </div>
92 );
93}

This file's history is long; its oldest lines are credited to the oldest commit read.