RunsTable displaying for registration attempts
This commit is contained in:
@@ -21,7 +21,7 @@ import {
|
||||
projectIntegrationsPath,
|
||||
projectPath,
|
||||
projectTriggersPath,
|
||||
runDashboardPath,
|
||||
jobRunDashboardPath,
|
||||
} from "~/utils/pathBuilder";
|
||||
import { BreadcrumbIcon } from "../primitives/BreadcrumbIcon";
|
||||
import { JobsMenu } from "./JobsMenu";
|
||||
@@ -29,7 +29,7 @@ import { BreadcrumbLink } from "./NavBar";
|
||||
import { ProjectsMenu } from "./ProjectsMenu";
|
||||
|
||||
export type Breadcrumb = {
|
||||
slug:
|
||||
slug?:
|
||||
| "projects"
|
||||
| "jobs"
|
||||
| "integrations"
|
||||
@@ -45,22 +45,12 @@ export type Breadcrumb = {
|
||||
| "runs"
|
||||
| "run";
|
||||
link?: {
|
||||
to: string;
|
||||
title: string;
|
||||
};
|
||||
};
|
||||
|
||||
function useBreadcrumbs(): Breadcrumb[] {
|
||||
const matches = useMatches();
|
||||
|
||||
return matches
|
||||
.filter((match) => match.handle)
|
||||
.filter((match) => match.handle!.breadcrumb)
|
||||
.map((match) => match.handle!.breadcrumb as Breadcrumb);
|
||||
}
|
||||
|
||||
export function Breadcrumb() {
|
||||
const breadcrumbs = useBreadcrumbs();
|
||||
const matches = useMatches();
|
||||
const organization = useOptionalOrganization();
|
||||
const project = useOptionalProject();
|
||||
const job = useOptionalJob();
|
||||
@@ -69,19 +59,32 @@ export function Breadcrumb() {
|
||||
|
||||
return (
|
||||
<div className="hidden items-center md:flex">
|
||||
{breadcrumbs.map((breadcrumb, index) => (
|
||||
<Fragment key={breadcrumb.slug}>
|
||||
<BreadcrumbIcon />
|
||||
<BreadcrumbItem
|
||||
breadcrumb={breadcrumb}
|
||||
organization={organization}
|
||||
project={project}
|
||||
job={job}
|
||||
run={run}
|
||||
client={client}
|
||||
/>
|
||||
</Fragment>
|
||||
))}
|
||||
{matches.map((match) => {
|
||||
if (!match.handle || !match.handle.breadcrumb) return null;
|
||||
|
||||
const breadcrumb = match.handle.breadcrumb as Breadcrumb;
|
||||
|
||||
return (
|
||||
<Fragment key={match.id}>
|
||||
<BreadcrumbIcon />
|
||||
{breadcrumb.link ? (
|
||||
<BreadcrumbLink
|
||||
to={match.pathname}
|
||||
title={breadcrumb.link.title}
|
||||
/>
|
||||
) : (
|
||||
<BreadcrumbItem
|
||||
breadcrumb={breadcrumb}
|
||||
organization={organization}
|
||||
project={project}
|
||||
job={job}
|
||||
run={run}
|
||||
client={client}
|
||||
/>
|
||||
)}
|
||||
</Fragment>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -226,7 +229,7 @@ function BreadcrumbItem({
|
||||
/>
|
||||
<BreadcrumbIcon />
|
||||
<BreadcrumbLink
|
||||
to={runDashboardPath(organization, project, job, run)}
|
||||
to={jobRunDashboardPath(organization, project, job, run)}
|
||||
title={`Run #${run.number}`}
|
||||
/>
|
||||
</Fragment>
|
||||
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
BuildingOffice2Icon,
|
||||
CalendarDaysIcon,
|
||||
ChatBubbleLeftEllipsisIcon,
|
||||
CheckCircleIcon,
|
||||
CheckIcon,
|
||||
ChevronDownIcon,
|
||||
ChevronLeftIcon,
|
||||
@@ -47,6 +48,7 @@ import {
|
||||
UserIcon,
|
||||
UserPlusIcon,
|
||||
WrenchScrewdriverIcon,
|
||||
XCircleIcon,
|
||||
XMarkIcon,
|
||||
} from "@heroicons/react/24/solid";
|
||||
import { ScheduleIcon } from "~/assets/icons/ScheduleIcon";
|
||||
@@ -61,6 +63,9 @@ const icons = {
|
||||
account: (className: string) => (
|
||||
<UserCircleIcon className={cn("text-slate-400", className)} />
|
||||
),
|
||||
active: (className: string) => (
|
||||
<CheckCircleIcon className={cn("text-green-500", className)} />
|
||||
),
|
||||
"arrow-right": (className: string) => (
|
||||
<ArrowRightIcon className={cn("text-white", className)} />
|
||||
),
|
||||
@@ -142,6 +147,9 @@ const icons = {
|
||||
id: (className: string) => (
|
||||
<FingerPrintIcon className={cn("text-rose-200", className)} />
|
||||
),
|
||||
inactive: (className: string) => (
|
||||
<XCircleIcon className={cn("text-rose-500", className)} />
|
||||
),
|
||||
info: (className: string) => (
|
||||
<InformationCircleIcon className={cn("text-blue-500", className)} />
|
||||
),
|
||||
|
||||
@@ -5,7 +5,12 @@ import { useOrganization } from "~/hooks/useOrganizations";
|
||||
import { useProject } from "~/hooks/useProject";
|
||||
import { RunList } from "~/presenters/RunListPresenter.server";
|
||||
import { formatDuration } from "~/utils";
|
||||
import { runDashboardPath } from "~/utils/pathBuilder";
|
||||
import {
|
||||
JobForPath,
|
||||
OrgForPath,
|
||||
ProjectForPath,
|
||||
jobRunDashboardPath,
|
||||
} from "~/utils/pathBuilder";
|
||||
import { EnvironmentLabel } from "../environments/EnvironmentLabel";
|
||||
import { Callout } from "../primitives/Callout";
|
||||
import { DateTime } from "../primitives/DateTime";
|
||||
@@ -21,22 +26,37 @@ import {
|
||||
TableRow,
|
||||
} from "../primitives/Table";
|
||||
import { RunStatus } from "./RunStatuses";
|
||||
import { JobRunStatus, RuntimeEnvironmentType } from "@trigger.dev/database";
|
||||
|
||||
type RunTableItem = {
|
||||
id: string;
|
||||
number: number;
|
||||
environment: {
|
||||
type: RuntimeEnvironmentType;
|
||||
};
|
||||
status: JobRunStatus;
|
||||
startedAt: Date | null;
|
||||
completedAt: Date | null;
|
||||
createdAt: Date | null;
|
||||
version: string;
|
||||
isTest: boolean;
|
||||
};
|
||||
|
||||
type RunsTableProps = {
|
||||
total: number;
|
||||
hasFilters: boolean;
|
||||
runs: RunTableItem[];
|
||||
isLoading?: boolean;
|
||||
runsParentPath: string;
|
||||
};
|
||||
|
||||
export function RunsTable({
|
||||
total,
|
||||
hasFilters,
|
||||
runs,
|
||||
isLoading = false,
|
||||
}: {
|
||||
total: number;
|
||||
hasFilters: boolean;
|
||||
runs: RunList["runs"];
|
||||
isLoading?: boolean;
|
||||
}) {
|
||||
const organization = useOrganization();
|
||||
const project = useProject();
|
||||
const job = useJob();
|
||||
|
||||
runsParentPath,
|
||||
}: RunsTableProps) {
|
||||
return (
|
||||
<Table>
|
||||
<TableHeader>
|
||||
@@ -65,7 +85,7 @@ export function RunsTable({
|
||||
</TableBlankRow>
|
||||
) : (
|
||||
runs.map((run) => {
|
||||
const path = runDashboardPath(organization, project, job, run);
|
||||
const path = `${runsParentPath}/${run.id}/trigger`;
|
||||
return (
|
||||
<TableRow key={run.id}>
|
||||
<TableCell to={path}>#{run.number}</TableCell>
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
import { TriggerSource, User } from "@trigger.dev/database";
|
||||
import { PrismaClient, prisma } from "~/db.server";
|
||||
import { Organization } from "~/models/organization.server";
|
||||
import { Project } from "~/models/project.server";
|
||||
|
||||
export class DynamicTriggerSourcePresenter {
|
||||
#prismaClient: PrismaClient;
|
||||
|
||||
constructor(prismaClient: PrismaClient = prisma) {
|
||||
this.#prismaClient = prismaClient;
|
||||
}
|
||||
|
||||
public async call({
|
||||
userId,
|
||||
projectSlug,
|
||||
organizationSlug,
|
||||
triggerSourceId,
|
||||
}: {
|
||||
userId: User["id"];
|
||||
projectSlug: Project["slug"];
|
||||
organizationSlug: Organization["slug"];
|
||||
triggerSourceId: TriggerSource["id"];
|
||||
}) {
|
||||
const trigger = await this.#prismaClient.triggerSource.findUnique({
|
||||
select: {
|
||||
id: true,
|
||||
active: true,
|
||||
dynamicTrigger: true,
|
||||
integration: {
|
||||
select: {
|
||||
id: true,
|
||||
title: true,
|
||||
slug: true,
|
||||
definitionId: true,
|
||||
setupStatus: true,
|
||||
},
|
||||
},
|
||||
environment: {
|
||||
select: {
|
||||
type: true,
|
||||
},
|
||||
},
|
||||
createdAt: true,
|
||||
updatedAt: true,
|
||||
params: true,
|
||||
registrations: true,
|
||||
sourceRegistrationJob: true,
|
||||
},
|
||||
where: {
|
||||
id: triggerSourceId,
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
trigger,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
import { TriggerSource, User } from "@trigger.dev/database";
|
||||
import { PrismaClient, prisma } from "~/db.server";
|
||||
import { Organization } from "~/models/organization.server";
|
||||
import { Project } from "~/models/project.server";
|
||||
|
||||
export class TriggerSourcePresenter {
|
||||
#prismaClient: PrismaClient;
|
||||
|
||||
constructor(prismaClient: PrismaClient = prisma) {
|
||||
this.#prismaClient = prismaClient;
|
||||
}
|
||||
|
||||
public async call({
|
||||
userId,
|
||||
projectSlug,
|
||||
organizationSlug,
|
||||
triggerSourceId,
|
||||
}: {
|
||||
userId: User["id"];
|
||||
projectSlug: Project["slug"];
|
||||
organizationSlug: Organization["slug"];
|
||||
triggerSourceId: TriggerSource["id"];
|
||||
}) {
|
||||
const trigger = await this.#prismaClient.triggerSource.findUnique({
|
||||
select: {
|
||||
id: true,
|
||||
active: true,
|
||||
integration: {
|
||||
select: {
|
||||
id: true,
|
||||
title: true,
|
||||
slug: true,
|
||||
definitionId: true,
|
||||
setupStatus: true,
|
||||
},
|
||||
},
|
||||
environment: {
|
||||
select: {
|
||||
type: true,
|
||||
},
|
||||
},
|
||||
createdAt: true,
|
||||
updatedAt: true,
|
||||
params: true,
|
||||
sourceRegistrationJob: {
|
||||
select: {
|
||||
job: {
|
||||
select: {
|
||||
id: true,
|
||||
slug: true,
|
||||
},
|
||||
},
|
||||
runs: {
|
||||
select: {
|
||||
id: true,
|
||||
number: true,
|
||||
environment: {
|
||||
select: {
|
||||
type: true,
|
||||
},
|
||||
},
|
||||
status: true,
|
||||
startedAt: true,
|
||||
completedAt: true,
|
||||
createdAt: true,
|
||||
version: {
|
||||
select: {
|
||||
version: true,
|
||||
},
|
||||
},
|
||||
isTest: true,
|
||||
},
|
||||
orderBy: {
|
||||
id: "desc",
|
||||
},
|
||||
take: 20,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
where: {
|
||||
id: triggerSourceId,
|
||||
},
|
||||
});
|
||||
|
||||
if (!trigger) {
|
||||
throw new Error("Trigger source not found");
|
||||
}
|
||||
|
||||
return {
|
||||
trigger: {
|
||||
id: trigger.id,
|
||||
active: trigger.active,
|
||||
integration: trigger.integration,
|
||||
environment: trigger.environment,
|
||||
createdAt: trigger.createdAt,
|
||||
updatedAt: trigger.updatedAt,
|
||||
params: trigger.params,
|
||||
registrationJob: trigger.sourceRegistrationJob
|
||||
? {
|
||||
id: trigger.sourceRegistrationJob.job.id,
|
||||
slug: trigger.sourceRegistrationJob.job.slug,
|
||||
runs: trigger.sourceRegistrationJob.runs.map((r) => ({
|
||||
id: r.id,
|
||||
number: r.number,
|
||||
environment: r.environment,
|
||||
status: r.status,
|
||||
startedAt: r.startedAt,
|
||||
completedAt: r.completedAt,
|
||||
createdAt: r.createdAt,
|
||||
version: r.version.version,
|
||||
isTest: r.isTest,
|
||||
})),
|
||||
}
|
||||
: undefined,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
+6
-1
@@ -9,7 +9,11 @@ import { RunListPresenter } from "~/presenters/RunListPresenter.server";
|
||||
import { requireUserId } from "~/services/session.server";
|
||||
import { cn } from "~/utils/cn";
|
||||
import { Handle } from "~/utils/handle";
|
||||
import { JobParamsSchema, projectIntegrationsPath } from "~/utils/pathBuilder";
|
||||
import {
|
||||
JobParamsSchema,
|
||||
projectIntegrationsPath,
|
||||
jobRunsParentPath,
|
||||
} from "~/utils/pathBuilder";
|
||||
import { ListPagination } from "./ListPagination";
|
||||
import { Callout } from "~/components/primitives/Callout";
|
||||
import { useOrganization } from "~/hooks/useOrganizations";
|
||||
@@ -97,6 +101,7 @@ export default function Page() {
|
||||
hasFilters={false}
|
||||
runs={list.runs}
|
||||
isLoading={isLoading}
|
||||
runsParentPath={jobRunsParentPath(organization, project, job)}
|
||||
/>
|
||||
<ListPagination list={list} className="mt-2 justify-end" />
|
||||
</div>
|
||||
|
||||
+3
-3
@@ -58,7 +58,7 @@ import {
|
||||
RunParamsSchema,
|
||||
jobPath,
|
||||
runCompletedPath,
|
||||
runDashboardPath,
|
||||
jobRunDashboardPath,
|
||||
runStreamingPath,
|
||||
runTaskPath,
|
||||
runTriggerPath,
|
||||
@@ -121,7 +121,7 @@ export const action: ActionFunction = async ({ request, params }) => {
|
||||
}
|
||||
|
||||
return redirectWithSuccessMessage(
|
||||
runDashboardPath(
|
||||
jobRunDashboardPath(
|
||||
{ slug: organizationSlug },
|
||||
{ slug: projectParam },
|
||||
{ slug: jobParam },
|
||||
@@ -135,7 +135,7 @@ export const action: ActionFunction = async ({ request, params }) => {
|
||||
await continueService.call({ runId: runParam });
|
||||
|
||||
return redirectWithSuccessMessage(
|
||||
runDashboardPath(
|
||||
jobRunDashboardPath(
|
||||
{ slug: organizationSlug },
|
||||
{ slug: projectParam },
|
||||
{ slug: jobParam },
|
||||
|
||||
+2
-2
@@ -31,7 +31,7 @@ import { TestJobService } from "~/services/jobs/testJob.server";
|
||||
import { requireUserId } from "~/services/session.server";
|
||||
import { cn } from "~/utils/cn";
|
||||
import { Handle } from "~/utils/handle";
|
||||
import { JobParamsSchema, runDashboardPath } from "~/utils/pathBuilder";
|
||||
import { JobParamsSchema, jobRunDashboardPath } from "~/utils/pathBuilder";
|
||||
|
||||
export const loader = async ({ request, params }: LoaderArgs) => {
|
||||
const userId = await requireUserId(request);
|
||||
@@ -102,7 +102,7 @@ export const action: ActionFunction = async ({ request, params }) => {
|
||||
}
|
||||
|
||||
return redirectWithSuccessMessage(
|
||||
runDashboardPath(
|
||||
jobRunDashboardPath(
|
||||
{ slug: organizationSlug },
|
||||
{ slug: projectParam },
|
||||
{ slug: jobParam },
|
||||
|
||||
+79
-71
@@ -17,6 +17,7 @@ import {
|
||||
import { Paragraph } from "~/components/primitives/Paragraph";
|
||||
import {
|
||||
Table,
|
||||
TableBlankRow,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableCellChevron,
|
||||
@@ -53,7 +54,9 @@ export const loader = async ({ request, params }: LoaderArgs) => {
|
||||
|
||||
export const handle: Handle = {
|
||||
breadcrumb: {
|
||||
slug: "triggers",
|
||||
link: {
|
||||
title: "External",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -64,7 +67,6 @@ export default function Integrations() {
|
||||
|
||||
return (
|
||||
<>
|
||||
<Header2 spacing>External Triggers</Header2>
|
||||
<Paragraph variant="small" spacing>
|
||||
Webhooks are connected as external Triggers
|
||||
</Paragraph>
|
||||
@@ -79,75 +81,81 @@ export default function Integrations() {
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{triggers.map((t) => {
|
||||
const path = triggerSourcePath(organization, project, t);
|
||||
return (
|
||||
<TableRow
|
||||
key={t.id}
|
||||
className={cn(!t.active && "bg-rose-500/30")}
|
||||
>
|
||||
<TableCell to={path}>
|
||||
<div className="flex items-center gap-1">
|
||||
<NamedIcon
|
||||
name={t.integration.definitionId}
|
||||
className="h-8 w-8"
|
||||
/>
|
||||
<LabelValueStack
|
||||
label={t.integration.title}
|
||||
value={t.integration.slug}
|
||||
variant="primary"
|
||||
/>
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell to={path}>
|
||||
{t.params && (
|
||||
<SimpleTooltip
|
||||
button={
|
||||
<div className="flex max-w-[200px] items-start justify-start gap-5 truncate">
|
||||
{Object.entries(t.params).map(
|
||||
([label, value], index) => (
|
||||
<LabelValueStack
|
||||
key={index}
|
||||
label={label}
|
||||
value={value}
|
||||
className="last:truncate"
|
||||
/>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
}
|
||||
content={
|
||||
<div className="flex flex-col gap-2">
|
||||
{Object.entries(t.params).map(
|
||||
([label, value], index) => (
|
||||
<LabelValueStack
|
||||
key={index}
|
||||
label={label}
|
||||
value={value}
|
||||
/>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell to={path}>
|
||||
{t.active ? (
|
||||
<CheckCircleIcon className="h-6 w-6 text-green-500" />
|
||||
) : (
|
||||
<XCircleIcon className="h-6 w-6 text-rose-500" />
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell to={path}>
|
||||
<span className="flex">
|
||||
<EnvironmentLabel environment={t.environment} />
|
||||
</span>
|
||||
</TableCell>
|
||||
<TableCellChevron to={path} />
|
||||
</TableRow>
|
||||
);
|
||||
})}
|
||||
{triggers.length > 0 ? (
|
||||
triggers.map((t) => {
|
||||
const path = triggerSourcePath(organization, project, t);
|
||||
return (
|
||||
<TableRow
|
||||
key={t.id}
|
||||
className={cn(!t.active && "bg-rose-500/30")}
|
||||
>
|
||||
<TableCell to={path}>
|
||||
<div className="flex items-center gap-1">
|
||||
<NamedIcon
|
||||
name={t.integration.definitionId}
|
||||
className="h-8 w-8"
|
||||
/>
|
||||
<LabelValueStack
|
||||
label={t.integration.title}
|
||||
value={t.integration.slug}
|
||||
variant="primary"
|
||||
/>
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell to={path}>
|
||||
{t.params && (
|
||||
<SimpleTooltip
|
||||
button={
|
||||
<div className="flex max-w-[200px] items-start justify-start gap-5 truncate">
|
||||
{Object.entries(t.params).map(
|
||||
([label, value], index) => (
|
||||
<LabelValueStack
|
||||
key={index}
|
||||
label={label}
|
||||
value={value}
|
||||
className="last:truncate"
|
||||
/>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
}
|
||||
content={
|
||||
<div className="flex flex-col gap-2">
|
||||
{Object.entries(t.params).map(
|
||||
([label, value], index) => (
|
||||
<LabelValueStack
|
||||
key={index}
|
||||
label={label}
|
||||
value={value}
|
||||
/>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell to={path}>
|
||||
{t.active ? (
|
||||
<CheckCircleIcon className="h-6 w-6 text-green-500" />
|
||||
) : (
|
||||
<XCircleIcon className="h-6 w-6 text-rose-500" />
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell to={path}>
|
||||
<span className="flex">
|
||||
<EnvironmentLabel environment={t.environment} />
|
||||
</span>
|
||||
</TableCell>
|
||||
<TableCellChevron to={path} />
|
||||
</TableRow>
|
||||
);
|
||||
})
|
||||
) : (
|
||||
<TableBlankRow colSpan={5}>
|
||||
<Paragraph>No External triggers</Paragraph>
|
||||
</TableBlankRow>
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</>
|
||||
|
||||
+3
-6
@@ -10,10 +10,7 @@ import {
|
||||
import { useOrganization } from "~/hooks/useOrganizations";
|
||||
import { useProject } from "~/hooks/useProject";
|
||||
import { Handle } from "~/utils/handle";
|
||||
import {
|
||||
projectScheduledTriggersPath,
|
||||
projectTriggersPath,
|
||||
} from "~/utils/pathBuilder";
|
||||
import { projectTriggersPath } from "~/utils/pathBuilder";
|
||||
|
||||
export const handle: Handle = {
|
||||
breadcrumb: {
|
||||
@@ -27,7 +24,7 @@ export default function Integrations() {
|
||||
|
||||
return (
|
||||
<PageContainer>
|
||||
<PageHeader>
|
||||
<PageHeader hideBorder>
|
||||
<PageTitleRow>
|
||||
<PageTitle title="Triggers" />
|
||||
</PageTitleRow>
|
||||
@@ -37,7 +34,7 @@ export default function Integrations() {
|
||||
<PageTabs
|
||||
tabs={[
|
||||
{
|
||||
label: "External",
|
||||
label: "External Triggers",
|
||||
to: projectTriggersPath(organization, project),
|
||||
},
|
||||
// {
|
||||
|
||||
+41
-90
@@ -1,7 +1,10 @@
|
||||
import { Response } from "@remix-run/node";
|
||||
import type { LoaderArgs } from "@remix-run/server-runtime";
|
||||
import { typedjson, useTypedLoaderData } from "remix-typedjson";
|
||||
import { EnvironmentLabel } from "~/components/environments/EnvironmentLabel";
|
||||
import { PageBody, PageContainer } from "~/components/layout/AppLayout";
|
||||
import { Callout } from "~/components/primitives/Callout";
|
||||
import { Header2 } from "~/components/primitives/Headers";
|
||||
import {
|
||||
PageHeader,
|
||||
PageInfoGroup,
|
||||
@@ -10,14 +13,17 @@ import {
|
||||
PageTitle,
|
||||
PageTitleRow,
|
||||
} from "~/components/primitives/PageHeader";
|
||||
import { Paragraph } from "~/components/primitives/Paragraph";
|
||||
import { RunsTable } from "~/components/runs/RunsTable";
|
||||
import { useOrganization } from "~/hooks/useOrganizations";
|
||||
import { useProject } from "~/hooks/useProject";
|
||||
import { DynamicTriggerSourcePresenter } from "~/presenters/DynamicTriggerSourcePresenter.server";
|
||||
import { TriggerSourcePresenter } from "~/presenters/TriggerSourcePresenter.server";
|
||||
import { requireUser } from "~/services/session.server";
|
||||
import { Handle } from "~/utils/handle";
|
||||
import {
|
||||
TriggerSourceParamSchema,
|
||||
projectTriggersPath,
|
||||
triggerSourceRunsPath,
|
||||
} from "~/utils/pathBuilder";
|
||||
|
||||
export const loader = async ({ request, params }: LoaderArgs) => {
|
||||
@@ -25,7 +31,7 @@ export const loader = async ({ request, params }: LoaderArgs) => {
|
||||
const { organizationSlug, projectParam, triggerParam } =
|
||||
TriggerSourceParamSchema.parse(params);
|
||||
|
||||
const presenter = new DynamicTriggerSourcePresenter();
|
||||
const presenter = new TriggerSourcePresenter();
|
||||
const { trigger } = await presenter.call({
|
||||
userId: user.id,
|
||||
organizationSlug,
|
||||
@@ -49,8 +55,6 @@ export const handle: Handle = {
|
||||
},
|
||||
};
|
||||
|
||||
//todo outlet somewhere, look at how I did this with the integration client page.
|
||||
|
||||
export default function Integrations() {
|
||||
const { trigger } = useTypedLoaderData<typeof loader>();
|
||||
const organization = useOrganization();
|
||||
@@ -61,7 +65,7 @@ export default function Integrations() {
|
||||
<PageHeader>
|
||||
<PageTitleRow>
|
||||
<PageTitle
|
||||
title="Trigger detail"
|
||||
title={`${trigger.integration.title}: ${trigger.integration.slug}`}
|
||||
backButton={{
|
||||
to: projectTriggersPath(organization, project),
|
||||
text: "Triggers",
|
||||
@@ -72,97 +76,44 @@ export default function Integrations() {
|
||||
<PageInfoGroup>
|
||||
<PageInfoProperty
|
||||
icon={trigger.integration.definitionId}
|
||||
label="Integration"
|
||||
value={trigger.integration.title}
|
||||
label={trigger.integration.title ?? ""}
|
||||
value={trigger.integration.slug}
|
||||
/>
|
||||
<PageInfoProperty
|
||||
icon={trigger.active ? "active" : "inactive"}
|
||||
label="Active"
|
||||
value={trigger.active ? "Yes" : "No"}
|
||||
/>
|
||||
<PageInfoProperty
|
||||
label="Environment"
|
||||
value={<EnvironmentLabel environment={trigger.environment} />}
|
||||
/>
|
||||
</PageInfoGroup>
|
||||
</PageInfoRow>
|
||||
</PageHeader>
|
||||
|
||||
<PageBody scrollable={false}>
|
||||
{/* <div className="h-full overflow-y-auto p-4 scrollbar-thin scrollbar-track-transparent scrollbar-thumb-slate-700">
|
||||
<div>
|
||||
<Header2 spacing>External Triggers</Header2>
|
||||
<Paragraph variant="small" spacing>
|
||||
Webhooks are connected as external Triggers
|
||||
</Paragraph>
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHeaderCell>Integration</TableHeaderCell>
|
||||
<TableHeaderCell>Properties</TableHeaderCell>
|
||||
<TableHeaderCell>Active</TableHeaderCell>
|
||||
<TableHeaderCell>Environment</TableHeaderCell>
|
||||
<TableHeaderCell hiddenLabel>Go to page</TableHeaderCell>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{triggers.map((t) => {
|
||||
const path = triggerSourcePath(organization, project, t);
|
||||
return (
|
||||
<TableRow key={t.id}>
|
||||
<TableCell to={path}>
|
||||
<span className="flex items-center gap-1">
|
||||
<NamedIcon
|
||||
name={t.integration.definitionId}
|
||||
className="h-6 w-6"
|
||||
/>
|
||||
{t.integration.title}
|
||||
</span>
|
||||
</TableCell>
|
||||
<TableCell to={path}>
|
||||
{t.params && (
|
||||
<SimpleTooltip
|
||||
button={
|
||||
<div className="flex max-w-[200px] items-start justify-start gap-5 truncate">
|
||||
{Object.entries(t.params).map(
|
||||
([label, value], index) => (
|
||||
<LabelValueStack
|
||||
key={index}
|
||||
label={label}
|
||||
value={value}
|
||||
className="last:truncate"
|
||||
/>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
}
|
||||
content={
|
||||
<div className="flex flex-col gap-2">
|
||||
{Object.entries(t.params).map(
|
||||
([label, value], index) => (
|
||||
<LabelValueStack
|
||||
key={index}
|
||||
label={label}
|
||||
value={value}
|
||||
/>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell to={path}>
|
||||
{t.active ? (
|
||||
<CheckCircleIcon className="h-6 w-6 text-green-500" />
|
||||
) : (
|
||||
<XCircleIcon className="h-6 w-6 text-rose-500" />
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell to={path}>
|
||||
<span className="flex">
|
||||
<EnvironmentLabel environment={t.environment} />
|
||||
</span>
|
||||
</TableCell>
|
||||
<TableCellChevron to={path} />
|
||||
</TableRow>
|
||||
);
|
||||
})}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
</div> */}
|
||||
<div className="h-full overflow-y-auto p-4 scrollbar-thin scrollbar-track-transparent scrollbar-thumb-slate-700">
|
||||
<Header2 spacing>Trigger registration runs</Header2>
|
||||
<Paragraph variant="small" spacing>
|
||||
External Triggers need to be registered with the external service.
|
||||
You can see the list of attempted registrations below.
|
||||
</Paragraph>
|
||||
{trigger.registrationJob ? (
|
||||
<RunsTable
|
||||
runs={trigger.registrationJob.runs}
|
||||
total={trigger.registrationJob.runs.length}
|
||||
hasFilters={false}
|
||||
runsParentPath={triggerSourceRunsPath(
|
||||
organization,
|
||||
project,
|
||||
trigger
|
||||
)}
|
||||
/>
|
||||
) : (
|
||||
<Callout variant="warning">No registration runs found</Callout>
|
||||
)}
|
||||
</div>
|
||||
</PageBody>
|
||||
</PageContainer>
|
||||
);
|
||||
|
||||
@@ -4,12 +4,12 @@ import { Job } from "~/models/job.server";
|
||||
import type { Organization } from "~/models/organization.server";
|
||||
import type { Project } from "~/models/project.server";
|
||||
|
||||
type OrgForPath = Pick<Organization, "slug">;
|
||||
type ProjectForPath = Pick<Project, "slug">;
|
||||
type JobForPath = Pick<Job, "slug">;
|
||||
type RunForPath = Pick<Job, "id">;
|
||||
type IntegrationForPath = Pick<Integration, "slug">;
|
||||
type TriggerForPath = Pick<TriggerSource, "id">;
|
||||
export type OrgForPath = Pick<Organization, "slug">;
|
||||
export type ProjectForPath = Pick<Project, "slug">;
|
||||
export type JobForPath = Pick<Job, "slug">;
|
||||
export type RunForPath = Pick<Job, "id">;
|
||||
export type IntegrationForPath = Pick<Integration, "slug">;
|
||||
export type TriggerForPath = Pick<TriggerSource, "id">;
|
||||
|
||||
export const OrganizationParamsSchema = z.object({
|
||||
organizationSlug: z.string(),
|
||||
@@ -186,6 +186,14 @@ export function triggerSourcePath(
|
||||
)}/triggersources/${triggerSourceParam(trigger)}`;
|
||||
}
|
||||
|
||||
export function triggerSourceRunsPath(
|
||||
organization: OrgForPath,
|
||||
project: ProjectForPath,
|
||||
trigger: TriggerForPath
|
||||
) {
|
||||
return `${triggerSourcePath(organization, project, trigger)}/runs`;
|
||||
}
|
||||
|
||||
function triggerSourceParam(trigger: TriggerForPath) {
|
||||
return trigger.id;
|
||||
}
|
||||
@@ -228,16 +236,24 @@ export function jobParam(job: JobForPath) {
|
||||
}
|
||||
|
||||
// Run
|
||||
export function jobRunsParentPath(
|
||||
organization: OrgForPath,
|
||||
project: ProjectForPath,
|
||||
job: JobForPath
|
||||
) {
|
||||
return `${jobPath(organization, project, job)}/runs`;
|
||||
}
|
||||
|
||||
function runPath(
|
||||
organization: OrgForPath,
|
||||
project: ProjectForPath,
|
||||
job: JobForPath,
|
||||
run: RunForPath
|
||||
) {
|
||||
return `${jobPath(organization, project, job)}/runs/${runParam(run)}`;
|
||||
return `${jobRunsParentPath(organization, project, job)}/${runParam(run)}`;
|
||||
}
|
||||
|
||||
export function runDashboardPath(
|
||||
export function jobRunDashboardPath(
|
||||
organization: OrgForPath,
|
||||
project: ProjectForPath,
|
||||
job: JobForPath,
|
||||
|
||||
@@ -51,7 +51,7 @@ new Job(client, {
|
||||
version: "0.1.1",
|
||||
trigger: typeform.onFormResponse({
|
||||
uid: "QQnotGJM",
|
||||
tag: "tag3",
|
||||
tag: "tag4",
|
||||
}),
|
||||
run: async (payload, io, ctx) => {},
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user