Merge pull request #185 from SSHari/patch-573
feat: 🎸 move get project jobs to lower level in app hierarchy
This commit is contained in:
@@ -15,7 +15,8 @@ import {
|
||||
} from "../primitives/Table";
|
||||
import { SimpleTooltip } from "../primitives/Tooltip";
|
||||
import { runStatusTitle } from "../runs/RunStatuses";
|
||||
import { ProjectJob, useProject } from "~/hooks/useProject";
|
||||
import { ProjectJob } from "~/hooks/useJobs";
|
||||
import { useProject } from "~/hooks/useProject";
|
||||
import { useOrganization } from "~/hooks/useOrganizations";
|
||||
import { JobRunStatus } from "~/models/job.server";
|
||||
import { cn } from "~/utils/cn";
|
||||
|
||||
@@ -3,6 +3,7 @@ import { useState } from "react";
|
||||
import { useJob } from "~/hooks/useJob";
|
||||
import { useOrganization } from "~/hooks/useOrganizations";
|
||||
import { useProject } from "~/hooks/useProject";
|
||||
import { useJobs } from "~/hooks/useJobs";
|
||||
import { cn } from "~/utils/cn";
|
||||
import { jobPath } from "~/utils/pathBuilder";
|
||||
import { LabelValueStack } from "../primitives/LabelValueStack";
|
||||
@@ -18,6 +19,7 @@ export function JobsMenu({ matches }: { matches: RouteMatch[] }) {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const organization = useOrganization(matches);
|
||||
const project = useProject(matches);
|
||||
const projectJobs = useJobs(matches);
|
||||
const currentJob = useJob(matches);
|
||||
|
||||
return (
|
||||
@@ -33,7 +35,7 @@ export function JobsMenu({ matches }: { matches: RouteMatch[] }) {
|
||||
>
|
||||
<PopoverSectionHeader title="Jobs" />
|
||||
<div className="flex flex-col gap-1 p-1">
|
||||
{project.jobs.map((job) => {
|
||||
{projectJobs.map((job) => {
|
||||
const isSelected = job.id === currentJob?.id;
|
||||
return (
|
||||
<Link
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ProjectJob } from "./useProject";
|
||||
import { ProjectJob } from "./useJobs";
|
||||
import { useTextFilter } from "./useTextFilter";
|
||||
|
||||
export function useFilterJobs(jobs: ProjectJob[]) {
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
import {
|
||||
UseDataFunctionReturn,
|
||||
useTypedRouteLoaderData,
|
||||
} from "remix-typedjson";
|
||||
import { UseDataFunctionReturn } from "remix-typedjson";
|
||||
import invariant from "tiny-invariant";
|
||||
import type { loader } from "~/routes/_app.orgs.$organizationSlug.projects.$projectParam.jobs.$jobParam/route";
|
||||
import { useOptionalProject } from "./useProject";
|
||||
import { useChanged } from "./useChanged";
|
||||
import { RouteMatch } from "@remix-run/react";
|
||||
import { useTypedMatchesData } from "./useTypedMatchData";
|
||||
@@ -14,18 +10,16 @@ export type MatchedJob = UseDataFunctionReturn<typeof loader>["job"];
|
||||
export const jobMatchId =
|
||||
"routes/_app.orgs.$organizationSlug.projects.$projectParam.jobs.$jobParam";
|
||||
export function useOptionalJob(matches?: RouteMatch[]) {
|
||||
const project = useOptionalProject(matches);
|
||||
const routeMatch = useTypedMatchesData<typeof loader>({
|
||||
id: jobMatchId,
|
||||
matches,
|
||||
});
|
||||
|
||||
if (!project || !routeMatch || !routeMatch.job) {
|
||||
if (!routeMatch || !routeMatch.job) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
//get the job from the list on the project
|
||||
return project.jobs.find((j) => j.id === routeMatch.job.id);
|
||||
return routeMatch.projectJobs.find((j) => j.id === routeMatch.job.id);
|
||||
}
|
||||
|
||||
export function useJob(matches?: RouteMatch[]) {
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
import { UseDataFunctionReturn } from "remix-typedjson";
|
||||
import invariant from "tiny-invariant";
|
||||
import type { loader } from "~/routes/_app.orgs.$organizationSlug.projects.$projectParam.jobs.$jobParam/route";
|
||||
import { RouteMatch } from "@remix-run/react";
|
||||
import { useTypedMatchesData } from "./useTypedMatchData";
|
||||
|
||||
export type ProjectJob = UseDataFunctionReturn<
|
||||
typeof loader
|
||||
>["projectJobs"][number];
|
||||
|
||||
export const jobsMatchId =
|
||||
"routes/_app.orgs.$organizationSlug.projects.$projectParam.jobs.$jobParam";
|
||||
export function useOptionalJobs(matches?: RouteMatch[]) {
|
||||
const routeMatch = useTypedMatchesData<typeof loader>({
|
||||
id: jobsMatchId,
|
||||
matches,
|
||||
});
|
||||
|
||||
return routeMatch?.projectJobs;
|
||||
}
|
||||
|
||||
export function useJobs(matches?: RouteMatch[]) {
|
||||
const jobs = useOptionalJobs(matches);
|
||||
invariant(jobs, "Jobs must be defined");
|
||||
return jobs;
|
||||
}
|
||||
@@ -6,7 +6,6 @@ import { useChanged } from "./useChanged";
|
||||
import { useTypedMatchesData } from "./useTypedMatchData";
|
||||
|
||||
export type MatchedProject = UseDataFunctionReturn<typeof loader>["project"];
|
||||
export type ProjectJob = MatchedProject["jobs"][number];
|
||||
|
||||
export const projectMatchId =
|
||||
"routes/_app.orgs.$organizationSlug.projects.$projectParam";
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
import { User } from "@trigger.dev/database";
|
||||
import { PrismaClient, prisma } from "~/db.server";
|
||||
import { Organization } from "~/models/organization.server";
|
||||
import { Project } from "~/models/project.server";
|
||||
|
||||
//Only get the job ids, otherwise we're just fetching data that's already been fetched
|
||||
export class IntegrationClientJobsPresenter {
|
||||
#prismaClient: PrismaClient;
|
||||
|
||||
constructor(prismaClient: PrismaClient = prisma) {
|
||||
this.#prismaClient = prismaClient;
|
||||
}
|
||||
|
||||
public async call({
|
||||
userId,
|
||||
organizationSlug,
|
||||
projectSlug,
|
||||
clientSlug,
|
||||
}: {
|
||||
userId: User["id"];
|
||||
organizationSlug: Organization["slug"];
|
||||
projectSlug: Project["slug"];
|
||||
clientSlug: string;
|
||||
}) {
|
||||
const jobs = await this.#prismaClient.job.findMany({
|
||||
select: {
|
||||
id: true,
|
||||
},
|
||||
where: {
|
||||
internal: false,
|
||||
organization: {
|
||||
slug: organizationSlug,
|
||||
members: {
|
||||
some: {
|
||||
userId,
|
||||
},
|
||||
},
|
||||
},
|
||||
project: {
|
||||
slug: projectSlug,
|
||||
},
|
||||
integrations: {
|
||||
some: {
|
||||
integration: {
|
||||
slug: clientSlug,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
orderBy: [{ title: "asc" }],
|
||||
});
|
||||
|
||||
return {
|
||||
jobs: jobs.map((j) => j),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,193 @@
|
||||
import {
|
||||
DisplayProperty,
|
||||
DisplayPropertySchema,
|
||||
EventSpecificationSchema,
|
||||
} from "@trigger.dev/internal";
|
||||
import { PrismaClient, Prisma, prisma } from "~/db.server";
|
||||
import { Organization } from "~/models/organization.server";
|
||||
import { Project } from "~/models/project.server";
|
||||
import { User } from "~/models/user.server";
|
||||
import { z } from "zod";
|
||||
|
||||
export class JobListPresenter {
|
||||
#prismaClient: PrismaClient;
|
||||
|
||||
constructor(prismaClient: PrismaClient = prisma) {
|
||||
this.#prismaClient = prismaClient;
|
||||
}
|
||||
|
||||
public async call({
|
||||
userId,
|
||||
projectSlug,
|
||||
organizationSlug,
|
||||
integrationSlug,
|
||||
}: {
|
||||
userId: User["id"];
|
||||
projectSlug: Project["slug"];
|
||||
organizationSlug?: Organization["slug"];
|
||||
integrationSlug?: string;
|
||||
}) {
|
||||
const orgWhere: Prisma.JobWhereInput["organization"] = organizationSlug
|
||||
? { slug: organizationSlug, members: { some: { userId } } }
|
||||
: { members: { some: { userId } } };
|
||||
|
||||
const integrationsWhere: Prisma.JobWhereInput["integrations"] =
|
||||
integrationSlug
|
||||
? { some: { integration: { slug: integrationSlug } } }
|
||||
: {};
|
||||
|
||||
const jobs = await this.#prismaClient.job.findMany({
|
||||
select: {
|
||||
id: true,
|
||||
slug: true,
|
||||
title: true,
|
||||
aliases: {
|
||||
select: {
|
||||
version: {
|
||||
select: {
|
||||
version: true,
|
||||
eventSpecification: true,
|
||||
properties: true,
|
||||
runs: {
|
||||
select: {
|
||||
createdAt: true,
|
||||
status: true,
|
||||
},
|
||||
take: 1,
|
||||
orderBy: [{ createdAt: "desc" }],
|
||||
},
|
||||
integrations: {
|
||||
select: {
|
||||
key: true,
|
||||
integration: {
|
||||
select: {
|
||||
slug: true,
|
||||
definition: true,
|
||||
setupStatus: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
environment: {
|
||||
select: {
|
||||
type: true,
|
||||
orgMember: {
|
||||
select: {
|
||||
userId: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
where: {
|
||||
name: "latest",
|
||||
},
|
||||
},
|
||||
dynamicTriggers: {
|
||||
select: {
|
||||
type: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
where: {
|
||||
internal: false,
|
||||
organization: orgWhere,
|
||||
project: {
|
||||
slug: projectSlug,
|
||||
},
|
||||
integrations: integrationsWhere,
|
||||
},
|
||||
orderBy: [{ title: "asc" }],
|
||||
});
|
||||
|
||||
return jobs
|
||||
.map((job) => {
|
||||
//the best alias to select:
|
||||
// 1. Logged-in user dev
|
||||
// 2. Prod
|
||||
// 3. Any other user's dev
|
||||
const sortedAliases = job.aliases.sort((a, b) => {
|
||||
if (
|
||||
a.environment.type === "DEVELOPMENT" &&
|
||||
a.environment.orgMember?.userId === userId
|
||||
) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (
|
||||
b.environment.type === "DEVELOPMENT" &&
|
||||
b.environment.orgMember?.userId === userId
|
||||
) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (a.environment.type === "PRODUCTION") {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (b.environment.type === "PRODUCTION") {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
});
|
||||
|
||||
const alias = sortedAliases.at(0);
|
||||
|
||||
if (!alias) {
|
||||
throw new Error(
|
||||
`No aliases found for job ${job.id}, this should never happen.`
|
||||
);
|
||||
}
|
||||
|
||||
const eventSpecification = EventSpecificationSchema.parse(
|
||||
alias.version.eventSpecification
|
||||
);
|
||||
|
||||
const lastRun =
|
||||
alias.version.runs[0] != null ? alias.version.runs[0] : undefined;
|
||||
|
||||
const integrations = alias.version.integrations.map((integration) => ({
|
||||
key: integration.key,
|
||||
title: integration.integration.slug,
|
||||
icon: integration.integration.definition.id,
|
||||
setupStatus: integration.integration.setupStatus,
|
||||
}));
|
||||
|
||||
let properties: DisplayProperty[] = [];
|
||||
|
||||
if (eventSpecification.properties) {
|
||||
properties = [...properties, ...eventSpecification.properties];
|
||||
}
|
||||
|
||||
if (alias.version.properties) {
|
||||
const versionProperties = z
|
||||
.array(DisplayPropertySchema)
|
||||
.parse(alias.version.properties);
|
||||
properties = [...properties, ...versionProperties];
|
||||
}
|
||||
|
||||
return {
|
||||
id: job.id,
|
||||
slug: job.slug,
|
||||
title: job.title,
|
||||
version: alias.version.version,
|
||||
dynamic: job.dynamicTriggers.length > 0,
|
||||
event: {
|
||||
title: eventSpecification.title,
|
||||
icon: eventSpecification.icon,
|
||||
source: eventSpecification.source,
|
||||
},
|
||||
integrations,
|
||||
hasIntegrationsRequiringAction: integrations.some(
|
||||
(i) => i.setupStatus === "MISSING_FIELDS"
|
||||
),
|
||||
lastRun,
|
||||
properties,
|
||||
};
|
||||
})
|
||||
.filter(Boolean);
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,6 @@
|
||||
import {
|
||||
DisplayProperty,
|
||||
DisplayPropertySchema,
|
||||
EventSpecificationSchema,
|
||||
IntegrationMetadataSchema,
|
||||
} from "@trigger.dev/internal";
|
||||
import { PrismaClient, prisma } from "~/db.server";
|
||||
import { Project } from "~/models/project.server";
|
||||
import { User } from "~/models/user.server";
|
||||
import { z } from "zod";
|
||||
|
||||
export class ProjectPresenter {
|
||||
#prismaClient: PrismaClient;
|
||||
@@ -140,95 +133,6 @@ export class ProjectPresenter {
|
||||
organizationId: project.organizationId,
|
||||
createdAt: project.createdAt,
|
||||
updatedAt: project.updatedAt,
|
||||
jobs: project.jobs
|
||||
.map((job) => {
|
||||
//the best alias to select:
|
||||
// 1. Logged-in user dev
|
||||
// 2. Prod
|
||||
// 3. Any other user's dev
|
||||
const sortedAliases = job.aliases.sort((a, b) => {
|
||||
if (
|
||||
a.environment.type === "DEVELOPMENT" &&
|
||||
a.environment.orgMember?.userId === userId
|
||||
) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (
|
||||
b.environment.type === "DEVELOPMENT" &&
|
||||
b.environment.orgMember?.userId === userId
|
||||
) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (a.environment.type === "PRODUCTION") {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (b.environment.type === "PRODUCTION") {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
});
|
||||
|
||||
const alias = sortedAliases.at(0);
|
||||
|
||||
if (!alias) {
|
||||
throw new Error(
|
||||
`No aliases found for job ${job.id}, this should never happen.`
|
||||
);
|
||||
}
|
||||
|
||||
const eventSpecification = EventSpecificationSchema.parse(
|
||||
alias.version.eventSpecification
|
||||
);
|
||||
|
||||
const lastRun =
|
||||
alias.version.runs[0] != null ? alias.version.runs[0] : undefined;
|
||||
|
||||
const integrations = alias.version.integrations.map(
|
||||
(integration) => ({
|
||||
key: integration.key,
|
||||
title: integration.integration.slug,
|
||||
icon: integration.integration.definition.id,
|
||||
setupStatus: integration.integration.setupStatus,
|
||||
})
|
||||
);
|
||||
|
||||
let properties: DisplayProperty[] = [];
|
||||
|
||||
if (eventSpecification.properties) {
|
||||
properties = [...properties, ...eventSpecification.properties];
|
||||
}
|
||||
|
||||
if (alias.version.properties) {
|
||||
const versionProperties = z
|
||||
.array(DisplayPropertySchema)
|
||||
.parse(alias.version.properties);
|
||||
properties = [...properties, ...versionProperties];
|
||||
}
|
||||
|
||||
return {
|
||||
id: job.id,
|
||||
slug: job.slug,
|
||||
title: job.title,
|
||||
version: alias.version.version,
|
||||
dynamic: job.dynamicTriggers.length > 0,
|
||||
event: {
|
||||
title: eventSpecification.title,
|
||||
icon: eventSpecification.icon,
|
||||
source: eventSpecification.source,
|
||||
},
|
||||
integrations,
|
||||
hasIntegrationsRequiringAction: integrations.some(
|
||||
(i) => i.setupStatus === "MISSING_FIELDS"
|
||||
),
|
||||
lastRun,
|
||||
properties,
|
||||
};
|
||||
})
|
||||
.filter(Boolean),
|
||||
hasInactiveExternalTriggers: project._count.sources > 0,
|
||||
hasUnconfiguredIntegrations: project.organization._count.integrations > 0,
|
||||
environments: project.environments.map((environment) => ({
|
||||
|
||||
+35
-12
@@ -1,3 +1,5 @@
|
||||
import { LoaderArgs } from "@remix-run/server-runtime";
|
||||
import { typedjson, useTypedLoaderData } from "remix-typedjson";
|
||||
import Confetti from "react-confetti";
|
||||
import { HowToSetupYourProject } from "~/components/helpContent/HelpContentText";
|
||||
import { JobsTable } from "~/components/jobs/JobsTable";
|
||||
@@ -25,8 +27,32 @@ import {
|
||||
docsPath,
|
||||
projectIntegrationsPath,
|
||||
trimTrailingSlash,
|
||||
ProjectParamSchema,
|
||||
} from "~/utils/pathBuilder";
|
||||
import { BreadcrumbLink } from "~/components/navigation/NavBar";
|
||||
import { requireUserId } from "~/services/session.server";
|
||||
import { JobListPresenter } from "~/presenters/JobListPresenter.server";
|
||||
|
||||
export const loader = async ({ request, params }: LoaderArgs) => {
|
||||
const userId = await requireUserId(request);
|
||||
const { projectParam } = ProjectParamSchema.parse(params);
|
||||
|
||||
try {
|
||||
const presenter = new JobListPresenter();
|
||||
const jobs = await presenter.call({ userId, projectSlug: projectParam });
|
||||
|
||||
return typedjson({
|
||||
jobs,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
throw new Response(undefined, {
|
||||
status: 400,
|
||||
statusText:
|
||||
"Something went wrong, if this problem persists please contact support.",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export const handle: Handle = {
|
||||
breadcrumb: (match) => (
|
||||
@@ -38,10 +64,9 @@ export const handle: Handle = {
|
||||
export default function Page() {
|
||||
const organization = useOrganization();
|
||||
const project = useProject();
|
||||
const { jobs } = useTypedLoaderData<typeof loader>();
|
||||
|
||||
const { filterText, setFilterText, filteredItems } = useFilterJobs(
|
||||
project.jobs
|
||||
);
|
||||
const { filterText, setFilterText, filteredItems } = useFilterJobs(jobs);
|
||||
|
||||
const { width, height } = useWindowSize();
|
||||
|
||||
@@ -56,7 +81,7 @@ export default function Page() {
|
||||
<PageInfoProperty
|
||||
icon={"job"}
|
||||
label={"Active Jobs"}
|
||||
value={project.jobs.length}
|
||||
value={jobs.length}
|
||||
/>
|
||||
</PageInfoGroup>
|
||||
</PageInfoRow>
|
||||
@@ -80,7 +105,7 @@ export default function Page() {
|
||||
"rgb(217 70 239)",
|
||||
]}
|
||||
/> */}
|
||||
<Help defaultOpen={project.jobs.length === 0}>
|
||||
<Help defaultOpen={jobs.length === 0}>
|
||||
{(open) => (
|
||||
<div
|
||||
className={cn(
|
||||
@@ -89,10 +114,8 @@ export default function Page() {
|
||||
)}
|
||||
>
|
||||
<div>
|
||||
{project.jobs.length > 0 &&
|
||||
project.jobs.some(
|
||||
(j) => j.hasIntegrationsRequiringAction
|
||||
) && (
|
||||
{jobs.length > 0 &&
|
||||
jobs.some((j) => j.hasIntegrationsRequiringAction) && (
|
||||
<Callout
|
||||
variant="error"
|
||||
to={projectIntegrationsPath(organization, project)}
|
||||
@@ -103,7 +126,7 @@ export default function Page() {
|
||||
</Callout>
|
||||
)}
|
||||
<div className="mb-2 flex items-center justify-between gap-x-2">
|
||||
{project.jobs.length === 0 ? (
|
||||
{jobs.length === 0 ? (
|
||||
<Header2>Jobs</Header2>
|
||||
) : (
|
||||
<Input
|
||||
@@ -117,7 +140,7 @@ export default function Page() {
|
||||
)}
|
||||
<HelpTrigger title="How do I setup my Project?" />
|
||||
</div>
|
||||
{project.jobs.length === 0 ? (
|
||||
{jobs.length === 0 ? (
|
||||
<div
|
||||
className={
|
||||
"flex w-full justify-center gap-x-4 rounded-md border border-dashed border-indigo-800 px-5 py-8"
|
||||
@@ -134,7 +157,7 @@ export default function Page() {
|
||||
noResultsText={`No Jobs match ${filterText}. Try a different search
|
||||
query.`}
|
||||
/>
|
||||
{project.jobs.length === 1 ? (
|
||||
{jobs.length === 1 ? (
|
||||
<Callout
|
||||
variant="docs"
|
||||
to={docsPath("documentation/quickstart#your-first-job")}
|
||||
|
||||
-1
@@ -125,7 +125,6 @@ export default function Page() {
|
||||
// WARNING Don't put the revalidator in the useEffect deps array or bad things will happen
|
||||
}, [events]); // eslint-disable-line react-hooks/exhaustive-deps
|
||||
|
||||
|
||||
return (
|
||||
<PageContainer>
|
||||
<PageHeader>
|
||||
|
||||
+12
-21
@@ -1,4 +1,3 @@
|
||||
import { useMatches } from "@remix-run/react";
|
||||
import { LoaderArgs } from "@remix-run/server-runtime";
|
||||
import { typedjson, useTypedLoaderData } from "remix-typedjson";
|
||||
import { HowToUseThisIntegration } from "~/components/helpContent/HelpContentText";
|
||||
@@ -11,16 +10,13 @@ import { Help, HelpContent, HelpTrigger } from "~/components/primitives/Help";
|
||||
import { Input } from "~/components/primitives/Input";
|
||||
import { useFilterJobs } from "~/hooks/useFilterJobs";
|
||||
import { useIntegrationClient } from "~/hooks/useIntegrationClient";
|
||||
import { useProject } from "~/hooks/useProject";
|
||||
import { useTypedMatchData } from "~/hooks/useTypedMatchData";
|
||||
import { IntegrationClientJobsPresenter } from "~/presenters/IntegrationClientJobsPresenter.server";
|
||||
import { JobListPresenter } from "~/presenters/JobListPresenter.server";
|
||||
import { requireUserId } from "~/services/session.server";
|
||||
import { cn } from "~/utils/cn";
|
||||
import { Handle } from "~/utils/handle";
|
||||
import {
|
||||
IntegrationClientParamSchema,
|
||||
docsIntegrationPath,
|
||||
docsRoot,
|
||||
trimTrailingSlash,
|
||||
} from "~/utils/pathBuilder";
|
||||
|
||||
@@ -29,12 +25,13 @@ export const loader = async ({ request, params }: LoaderArgs) => {
|
||||
const { organizationSlug, projectParam, clientParam } =
|
||||
IntegrationClientParamSchema.parse(params);
|
||||
|
||||
const presenter = new IntegrationClientJobsPresenter();
|
||||
const { jobs } = await presenter.call({
|
||||
userId: userId,
|
||||
organizationSlug,
|
||||
const jobsPresenter = new JobListPresenter();
|
||||
|
||||
const jobs = await jobsPresenter.call({
|
||||
userId,
|
||||
projectSlug: projectParam,
|
||||
clientSlug: clientParam,
|
||||
organizationSlug,
|
||||
integrationSlug: clientParam,
|
||||
});
|
||||
|
||||
return typedjson({ jobs });
|
||||
@@ -49,17 +46,11 @@ export const handle: Handle = {
|
||||
export default function Page() {
|
||||
const { jobs } = useTypedLoaderData<typeof loader>();
|
||||
const client = useIntegrationClient();
|
||||
const project = useProject();
|
||||
|
||||
const projectJobs = project.jobs.filter((job) =>
|
||||
jobs.map((j) => j.id).includes(job.id)
|
||||
);
|
||||
|
||||
const { filterText, setFilterText, filteredItems } =
|
||||
useFilterJobs(projectJobs);
|
||||
const { filterText, setFilterText, filteredItems } = useFilterJobs(jobs);
|
||||
|
||||
return (
|
||||
<Help defaultOpen={projectJobs.length === 0}>
|
||||
<Help defaultOpen={jobs.length === 0}>
|
||||
{(open) => (
|
||||
<div
|
||||
className={cn(
|
||||
@@ -69,7 +60,7 @@ export default function Page() {
|
||||
>
|
||||
<div className="grow">
|
||||
<div className="mb-2 flex items-center justify-between gap-x-2">
|
||||
{projectJobs.length === 0 ? (
|
||||
{jobs.length === 0 ? (
|
||||
<Header2>Jobs using this integration will appear here</Header2>
|
||||
) : (
|
||||
<Input
|
||||
@@ -83,7 +74,7 @@ export default function Page() {
|
||||
)}
|
||||
<HelpTrigger title="How do I use this integration?" />
|
||||
</div>
|
||||
{projectJobs.length === 0 ? (
|
||||
{jobs.length === 0 ? (
|
||||
<>
|
||||
<JobSkeleton />
|
||||
</>
|
||||
@@ -91,7 +82,7 @@ export default function Page() {
|
||||
<JobsTable
|
||||
jobs={filteredItems}
|
||||
noResultsText={
|
||||
projectJobs.length === 0
|
||||
jobs.length === 0
|
||||
? `No Jobs are currently using "${client.title}"`
|
||||
: `No Jobs found for "${filterText}"`
|
||||
}
|
||||
|
||||
+13
-6
@@ -24,6 +24,7 @@ import { useOrganization } from "~/hooks/useOrganizations";
|
||||
import { projectMatchId, useProject } from "~/hooks/useProject";
|
||||
import { useOptionalRun } from "~/hooks/useRun";
|
||||
import { findJobByParams } from "~/models/job.server";
|
||||
import { JobListPresenter } from "~/presenters/JobListPresenter.server";
|
||||
import { requireUserId } from "~/services/session.server";
|
||||
import { Handle } from "~/utils/handle";
|
||||
import {
|
||||
@@ -40,12 +41,17 @@ export const loader = async ({ request, params }: LoaderArgs) => {
|
||||
const { jobParam, projectParam, organizationSlug } =
|
||||
JobParamsSchema.parse(params);
|
||||
|
||||
const job = await findJobByParams({
|
||||
userId,
|
||||
slug: jobParam,
|
||||
projectSlug: projectParam,
|
||||
organizationSlug,
|
||||
});
|
||||
const jobsPresenter = new JobListPresenter();
|
||||
|
||||
const [job, projectJobs] = await Promise.all([
|
||||
findJobByParams({
|
||||
userId,
|
||||
slug: jobParam,
|
||||
projectSlug: projectParam,
|
||||
organizationSlug,
|
||||
}),
|
||||
jobsPresenter.call({ userId, projectSlug: projectParam }),
|
||||
]);
|
||||
|
||||
if (job === null) {
|
||||
throw new Response("Not Found", {
|
||||
@@ -59,6 +65,7 @@ export const loader = async ({ request, params }: LoaderArgs) => {
|
||||
|
||||
return typedjson({
|
||||
job,
|
||||
projectJobs,
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user