Skip to content

Commit 31d2d2a

Browse files
authored
Merge pull request #608 from trycompai/main
[comp] Production Deploy
2 parents 1c75e61 + 9e19cd4 commit 31d2d2a

File tree

31 files changed

+20312
-34525
lines changed

31 files changed

+20312
-34525
lines changed

apps/app/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
"@ai-sdk/provider": "^1.1.3",
2121
"@ai-sdk/react": "^1.2.9",
2222
"@aws-sdk/client-s3": "^3.806.0",
23+
"@aws-sdk/client-sts": "^3.808.0",
2324
"@aws-sdk/s3-request-presigner": "^3.806.0",
2425
"@browserbasehq/sdk": "^2.5.0",
2526
"@comp/data": "workspace:*",
@@ -128,4 +129,4 @@
128129
"react-dom": "^19.1.0",
129130
"react-hook-form": "^7.56.3"
130131
}
131-
}
132+
}

apps/app/src/actions/integrations/delete-integration-connection.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ export const deleteIntegrationConnectionAction = authActionClient
1717
},
1818
})
1919
.action(async ({ parsedInput, ctx }) => {
20-
const { integrationId } = parsedInput;
20+
const { integrationName } = parsedInput;
2121
const { session } = ctx;
2222

2323
if (!session.activeOrganizationId) {
@@ -27,9 +27,9 @@ export const deleteIntegrationConnectionAction = authActionClient
2727
};
2828
}
2929

30-
const integration = await db.integration.findUnique({
30+
const integration = await db.integration.findFirst({
3131
where: {
32-
name: integrationId.toLowerCase(),
32+
name: integrationName,
3333
organizationId: session.activeOrganizationId,
3434
},
3535
});

apps/app/src/actions/schema.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -197,8 +197,8 @@ export const uploadTaskFileSchema = z.object({
197197

198198
// Integrations
199199
export const deleteIntegrationConnectionSchema = z.object({
200-
integrationId: z.string().min(1, {
201-
message: "Integration ID is required",
200+
integrationName: z.string().min(1, {
201+
message: "Integration name is required",
202202
}),
203203
});
204204

apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/tests/(overview)/page.tsx

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,26 @@
11
import { AppOnboarding } from "@/components/app-onboarding";
22
import { getI18n } from "@/locales/server";
3+
import { auth } from "@/utils/auth";
4+
import { db } from "@comp/db";
35
import { setStaticParamsLocale } from "next-international/server";
6+
import { headers } from "next/headers";
7+
import { redirect } from "next/navigation";
48

59
export default async function CloudTests({
610
params,
711
}: {
8-
params: Promise<{ locale: string }>;
12+
params: Promise<{ locale: string; orgId: string }>;
913
}) {
10-
const { locale } = await params;
14+
const { locale, orgId } = await params;
1115
const t = await getI18n();
1216
setStaticParamsLocale(locale);
1317

18+
const cloudProviders = await getCloudProviders();
19+
20+
if (cloudProviders.length > 0) {
21+
return redirect(`/${orgId}/tests/dashboard`);
22+
}
23+
1424
return (
1525
<div className="max-w-[1200px] m-auto">
1626
<div className="mt-8">
@@ -21,6 +31,8 @@ export default async function CloudTests({
2131
imageSrcDark="/onboarding/cloud-dark.webp"
2232
imageAlt="Cloud Management"
2333
sheetName="create-cloud-test-sheet"
34+
cta="Connect Cloud"
35+
href={`/${orgId}/integrations`}
2436
faqs={[
2537
{
2638
questionKey: t(
@@ -52,3 +64,30 @@ export default async function CloudTests({
5264
</div>
5365
);
5466
}
67+
68+
const getCloudProviders = async () => {
69+
const session = await auth.api.getSession({
70+
headers: await headers(),
71+
});
72+
73+
if (!session) {
74+
return [];
75+
}
76+
77+
const orgId = session.session?.activeOrganizationId;
78+
79+
if (!orgId) {
80+
return [];
81+
}
82+
83+
const cloudProviders = await db.integration.findMany({
84+
where: {
85+
organizationId: orgId,
86+
integrationId: {
87+
in: ["aws", "gcp", "azure"],
88+
},
89+
},
90+
});
91+
92+
return cloudProviders;
93+
};
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
"use server";
2+
3+
import { sendIntegrationResults } from "@/jobs/tasks/integration/integration-results";
4+
import { auth } from "@/utils/auth";
5+
import { db } from "@comp/db";
6+
import { runs, tasks } from "@trigger.dev/sdk/v3";
7+
import { revalidatePath } from "next/cache";
8+
import { headers } from "next/headers";
9+
10+
export const runTests = async () => {
11+
const session = await auth.api.getSession({
12+
headers: await headers(),
13+
});
14+
15+
if (!session) {
16+
return {
17+
success: false,
18+
errors: ["Unauthorized"],
19+
};
20+
}
21+
22+
const orgId = session.session?.activeOrganizationId;
23+
if (!orgId) {
24+
return {
25+
success: false,
26+
errors: ["No active organization"],
27+
};
28+
}
29+
30+
const integrations = await db.integration.findMany({
31+
where: {
32+
organizationId: orgId,
33+
integrationId: {
34+
in: ["aws", "gcp", "azure"],
35+
},
36+
},
37+
select: {
38+
id: true,
39+
name: true,
40+
integrationId: true,
41+
settings: true,
42+
userSettings: true,
43+
organization: {
44+
select: {
45+
id: true,
46+
name: true,
47+
},
48+
},
49+
},
50+
});
51+
52+
if (!integrations) {
53+
return {
54+
success: false,
55+
errors: ["No integrations found"],
56+
};
57+
}
58+
59+
const batchHandle = await tasks.batchTrigger<typeof sendIntegrationResults>(
60+
"send-integration-results",
61+
integrations.map((integration) => ({
62+
payload: {
63+
integration: {
64+
id: integration.id,
65+
name: integration.name,
66+
integration_id: integration.integrationId,
67+
settings: integration.settings,
68+
user_settings: integration.userSettings,
69+
organization: integration.organization,
70+
},
71+
},
72+
})),
73+
);
74+
75+
let existingRuns = await runs.list({
76+
status: "EXECUTING",
77+
batch: batchHandle.batchId,
78+
});
79+
80+
while (existingRuns.data.length > 0) {
81+
console.log(
82+
`Waiting for existing runs to complete: ${existingRuns.data.length}`,
83+
);
84+
await new Promise((resolve) => setTimeout(resolve, 500));
85+
existingRuns = await runs.list({
86+
status: "EXECUTING",
87+
batch: batchHandle.batchId,
88+
});
89+
}
90+
91+
revalidatePath(`/${orgId}/tests/dashboard`);
92+
return {
93+
success: true,
94+
errors: null,
95+
};
96+
};
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
"use client";
2+
3+
import {
4+
Card,
5+
CardContent,
6+
CardFooter,
7+
CardHeader,
8+
CardTitle,
9+
} from "@comp/ui/card";
10+
import { Badge } from "@comp/ui/badge";
11+
import { cn } from "@comp/ui/cn";
12+
import { BadgeProps } from "@comp/ui/badge";
13+
import { useState } from "react";
14+
import { Button } from "@comp/ui/button";
15+
import { ArrowRight } from "lucide-react";
16+
17+
const severityBadgeMap: {
18+
[key: string]: BadgeProps["variant"];
19+
} = {
20+
critical: "destructive",
21+
high: "destructive",
22+
medium: "warning",
23+
low: "default",
24+
};
25+
26+
const severityBorderMap: {
27+
[key: string]: string;
28+
} = {
29+
critical: "border-t-destructive",
30+
high: "border-t-destructive",
31+
medium: "border-t-warning",
32+
low: "border-t-primary",
33+
};
34+
35+
export function TestCard({
36+
test,
37+
}: {
38+
test: {
39+
id: string;
40+
title: string | null;
41+
description: string | null;
42+
remediation: string | null;
43+
status: string | null;
44+
severity: string | null;
45+
completedAt: Date | null;
46+
integration: {
47+
integrationId: string;
48+
};
49+
};
50+
}) {
51+
const [showRemediation, setShowRemediation] = useState(false);
52+
53+
if (showRemediation) {
54+
return (
55+
<Card
56+
key={test.id}
57+
className={cn(
58+
"flex flex-col border-t-4",
59+
severityBorderMap[
60+
test.severity?.toLocaleLowerCase() as keyof typeof severityBorderMap
61+
] || "border-t-secondary",
62+
)}
63+
>
64+
<CardHeader>
65+
<CardTitle>Remediation</CardTitle>
66+
</CardHeader>
67+
<CardContent className="break-all break-before-auto text-md">
68+
{test.remediation
69+
?.split(/\b(https?:\/\/\S+)\b/)
70+
.map((part, i) => {
71+
return /^https?:\/\/\S+$/.test(part) ? (
72+
<a
73+
key={i}
74+
href={part}
75+
target="_blank"
76+
rel="noopener noreferrer"
77+
className="underline break-all"
78+
>
79+
{part}
80+
</a>
81+
) : (
82+
<span key={i} className="break-all">
83+
{part}
84+
</span>
85+
);
86+
})}
87+
</CardContent>
88+
<CardFooter className="flex justify-end py-4">
89+
<Button
90+
size="sm"
91+
variant="outline"
92+
onClick={() => setShowRemediation(false)}
93+
>
94+
Close
95+
</Button>
96+
</CardFooter>
97+
</Card>
98+
);
99+
}
100+
101+
return (
102+
<Card
103+
key={test.id}
104+
className={cn(
105+
"flex flex-col border-t-4 break-all",
106+
severityBorderMap[
107+
test.severity?.toLocaleLowerCase() as keyof typeof severityBorderMap
108+
] || "border-t-secondary",
109+
)}
110+
>
111+
<CardHeader className="flex flex-col gap-2 break-all relative">
112+
<div className="flex flex-row items-center justify-between">
113+
<div className="text-xs text-muted-foreground">
114+
Last Checked: {test.completedAt?.toLocaleString()}
115+
</div>
116+
{test.severity && (
117+
<Badge
118+
className="select-none"
119+
variant={
120+
(severityBadgeMap[
121+
test.severity.toLowerCase() as keyof typeof severityBadgeMap
122+
] || "default") as BadgeProps["variant"]
123+
}
124+
>
125+
{test.severity}
126+
</Badge>
127+
)}
128+
</div>
129+
<CardTitle>{test.title || "Untitled Test"}</CardTitle>
130+
</CardHeader>
131+
<CardContent className="flex flex-col gap-3 text-sm text-muted-foreground h-full">
132+
{test.description && <p>{test.description}</p>}
133+
<span className="flex-grow" />
134+
</CardContent>
135+
<CardFooter className="flex justify-between items-center py-4">
136+
<div className="text-xs text-muted-foreground">
137+
Status: {test.status}
138+
</div>
139+
<Button
140+
size="sm"
141+
variant="outline"
142+
onClick={() => setShowRemediation(true)}
143+
>
144+
Remediate <ArrowRight className="ml-2 h-4 w-4" />
145+
</Button>
146+
</CardFooter>
147+
</Card>
148+
);
149+
}

0 commit comments

Comments
 (0)