From f6de8824550c82104985a2041e1fa577b2500af5 Mon Sep 17 00:00:00 2001 From: hmacr Date: Mon, 30 Oct 2023 20:02:27 +0530 Subject: [PATCH 01/10] feat: view teammates' job runs on dev env --- .../environments/EnvironmentLabel.tsx | 8 ++-- .../webapp/app/components/run/RunOverview.tsx | 10 +++-- apps/webapp/app/components/runs/RunsTable.tsx | 10 ++++- .../app/presenters/RunListPresenter.server.ts | 37 +++++++++++-------- .../app/presenters/RunPresenter.server.ts | 14 +++++++ .../route.tsx | 3 ++ .../route.tsx | 3 ++ .../route.tsx | 3 ++ .../route.tsx | 3 ++ apps/webapp/app/utils/username.ts | 22 +++++++++++ 10 files changed, 89 insertions(+), 24 deletions(-) create mode 100644 apps/webapp/app/utils/username.ts diff --git a/apps/webapp/app/components/environments/EnvironmentLabel.tsx b/apps/webapp/app/components/environments/EnvironmentLabel.tsx index e74d49def0c..e48a040026d 100644 --- a/apps/webapp/app/components/environments/EnvironmentLabel.tsx +++ b/apps/webapp/app/components/environments/EnvironmentLabel.tsx @@ -5,9 +5,11 @@ type Environment = Pick; export function EnvironmentLabel({ environment, + userName, className, }: { environment: Environment; + userName?: string; className?: string; }) { return ( @@ -18,19 +20,19 @@ export function EnvironmentLabel({ className )} > - {environmentTitle(environment)} + {environmentTitle(environment, userName)} ); } -export function environmentTitle(environment: Environment) { +export function environmentTitle(environment: Environment, username?: string) { switch (environment.type) { case "PRODUCTION": return "Prod"; case "STAGING": return "Staging"; case "DEVELOPMENT": - return "Dev"; + return username ? `Dev: ${username}` : "Dev"; case "PREVIEW": return "Preview"; } diff --git a/apps/webapp/app/components/run/RunOverview.tsx b/apps/webapp/app/components/run/RunOverview.tsx index bfb4ab4dcf1..fcae04f6654 100644 --- a/apps/webapp/app/components/run/RunOverview.tsx +++ b/apps/webapp/app/components/run/RunOverview.tsx @@ -10,7 +10,7 @@ import { useNavigate, useNavigation, } from "@remix-run/react"; -import { RuntimeEnvironmentType } from "@trigger.dev/database"; +import { RuntimeEnvironmentType, User } from "@trigger.dev/database"; import { useMemo } from "react"; import { usePathName } from "~/hooks/usePathName"; import type { RunBasicStatus } from "~/models/jobRun.server"; @@ -65,11 +65,12 @@ type RunOverviewProps = { run: string; runsPath: string; }; + currentUser: User; }; const taskPattern = /\/tasks\/(.*)/; -export function RunOverview({ run, trigger, showRerun, paths }: RunOverviewProps) { +export function RunOverview({ run, trigger, showRerun, paths, currentUser }: RunOverviewProps) { const navigate = useNavigate(); const pathName = usePathName(); @@ -89,6 +90,9 @@ export function RunOverview({ run, trigger, showRerun, paths }: RunOverviewProps } }, [pathName]); + const usernameForEnv = + currentUser.id !== run.environment.userId ? run.environment.userName : undefined; + return ( @@ -135,7 +139,7 @@ export function RunOverview({ run, trigger, showRerun, paths }: RunOverviewProps } + value={} /> @@ -84,6 +88,8 @@ export function RunsTable({ const path = showJob ? `${runsParentPath}/jobs/${run.job.slug}/runs/${run.id}/trigger` : `${runsParentPath}/${run.id}/trigger`; + const usernameForEnv = + currentUser.id !== run.environment.userId ? run.environment.userName : undefined; return ( @@ -91,7 +97,7 @@ export function RunsTable({ {showJob && {run.job.slug}} - + diff --git a/apps/webapp/app/presenters/RunListPresenter.server.ts b/apps/webapp/app/presenters/RunListPresenter.server.ts index 9b379947c93..7c4b6e752fc 100644 --- a/apps/webapp/app/presenters/RunListPresenter.server.ts +++ b/apps/webapp/app/presenters/RunListPresenter.server.ts @@ -1,6 +1,7 @@ import { z } from "zod"; import { PrismaClient, prisma } from "~/db.server"; import { DirectionSchema } from "~/routes/_app.orgs.$organizationSlug.projects.$projectParam.jobs.$jobParam._index/route"; +import { getUsername } from "~/utils/username"; export type Direction = z.infer; @@ -44,7 +45,6 @@ export class RunListPresenter { }, }); - // Find the project scoped to the organization const project = await this.#prismaClient.project.findFirstOrThrow({ where: { @@ -57,19 +57,17 @@ export class RunListPresenter { const environments = await this.#prismaClient.runtimeEnvironment.findMany({ where: { projectId: project.id, - OR: [ - { orgMember: { userId } }, - { orgMemberId: null }, - ] - } + }, }); - const job = jobSlug ? await this.#prismaClient.job.findFirstOrThrow({ - where: { - slug: jobSlug, - projectId: project.id, - }, - }) : undefined; + const job = jobSlug + ? await this.#prismaClient.job.findFirstOrThrow({ + where: { + slug: jobSlug, + projectId: project.id, + }, + }) + : undefined; const runs = await this.#prismaClient.jobRun.findMany({ select: { @@ -87,7 +85,13 @@ export class RunListPresenter { slug: true, orgMember: { select: { - userId: true, + user: { + select: { + id: true, + name: true, + displayName: true, + }, + }, }, }, }, @@ -119,8 +123,8 @@ export class RunListPresenter { skip: cursor ? 1 : 0, cursor: cursor ? { - id: cursor, - } + id: cursor, + } : undefined, }); @@ -163,7 +167,8 @@ export class RunListPresenter { environment: { type: run.environment.type, slug: run.environment.slug, - userId: run.environment.orgMember?.userId, + userId: run.environment.orgMember?.user.id, + userName: getUsername(run.environment.orgMember?.user), }, job: run.job, })), diff --git a/apps/webapp/app/presenters/RunPresenter.server.ts b/apps/webapp/app/presenters/RunPresenter.server.ts index c658d5aa60b..5c67c547a99 100644 --- a/apps/webapp/app/presenters/RunPresenter.server.ts +++ b/apps/webapp/app/presenters/RunPresenter.server.ts @@ -8,6 +8,7 @@ import { PrismaClient, prisma } from "~/db.server"; import { isRunCompleted, runBasicStatus } from "~/models/jobRun.server"; import { mergeProperties } from "~/utils/mergeProperties.server"; import { taskListToTree } from "~/utils/taskListToTree"; +import { getUsername } from "~/utils/username"; type RunOptions = { id: string; @@ -79,6 +80,8 @@ export class RunPresenter { environment: { type: run.environment.type, slug: run.environment.slug, + userId: run.environment.orgMember?.user.id, + userName: getUsername(run.environment.orgMember?.user), }, event: this.#prepareEventData(run.event), tasks, @@ -130,6 +133,17 @@ export class RunPresenter { select: { type: true, slug: true, + orgMember: { + select: { + user: { + select: { + id: true, + name: true, + displayName: true, + }, + }, + }, + }, }, }, event: { diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.jobs.$jobParam._index/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.jobs.$jobParam._index/route.tsx index 4561f60b45a..090215bb104 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.jobs.$jobParam._index/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.jobs.$jobParam._index/route.tsx @@ -10,6 +10,7 @@ import { RunsTable } from "~/components/runs/RunsTable"; import { useJob } from "~/hooks/useJob"; import { useOrganization } from "~/hooks/useOrganizations"; import { useProject } from "~/hooks/useProject"; +import { useUser } from "~/hooks/useUser"; import { RunListPresenter } from "~/presenters/RunListPresenter.server"; import { requireUserId } from "~/services/session.server"; import { cn } from "~/utils/cn"; @@ -57,6 +58,7 @@ export default function Page() { const organization = useOrganization(); const project = useProject(); const job = useJob(); + const user = useUser(); return ( <> @@ -81,6 +83,7 @@ export default function Page() { runs={list.runs} isLoading={isLoading} runsParentPath={jobRunsParentPath(organization, project, job)} + currentUser={user} /> diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.jobs.$jobParam.runs.$runParam/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.jobs.$jobParam.runs.$runParam/route.tsx index a8a382e3ba2..9e569985326 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.jobs.$jobParam.runs.$runParam/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.jobs.$jobParam.runs.$runParam/route.tsx @@ -10,6 +10,7 @@ import { jobMatchId, useJob } from "~/hooks/useJob"; import { useOrganization } from "~/hooks/useOrganizations"; import { useProject } from "~/hooks/useProject"; import { useTypedMatchData } from "~/hooks/useTypedMatchData"; +import { useUser } from "~/hooks/useUser"; import { RunPresenter } from "~/presenters/RunPresenter.server"; import { requireUserId } from "~/services/session.server"; import { Handle } from "~/utils/handle"; @@ -63,6 +64,7 @@ export default function Page() { const organization = useOrganization(); const project = useProject(); const job = useJob(); + const user = useUser(); const revalidator = useRevalidator(); const events = useEventSource(runStreamingPath(organization, project, job, run), { @@ -86,6 +88,7 @@ export default function Page() { run: runPath(organization, project, job, run), runsPath: jobRunsParentPath(organization, project, job), }} + currentUser={user} /> ); } diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers_.external.$triggerParam/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers_.external.$triggerParam/route.tsx index 0be48277848..d0456381d84 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers_.external.$triggerParam/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers_.external.$triggerParam/route.tsx @@ -44,6 +44,7 @@ import { z } from "zod"; import { ActivateSourceService } from "~/services/sources/activateSource.server"; import { redirectWithSuccessMessage } from "~/models/message.server"; import { nanoid } from "nanoid"; +import { useUser } from "~/hooks/useUser"; export const loader = async ({ request, params }: LoaderFunctionArgs) => { const user = await requireUser(request); @@ -130,6 +131,7 @@ export default function Page() { const { trigger } = useTypedLoaderData(); const organization = useOrganization(); const project = useProject(); + const user = useUser(); const navigation = useNavigation(); const lastSubmission = useActionData(); @@ -234,6 +236,7 @@ export default function Page() { total={trigger.runList.runs.length} hasFilters={false} runsParentPath={externalTriggerRunsParentPath(organization, project, trigger)} + currentUser={user} /> diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers_.external.$triggerParam_.runs.$runParam/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers_.external.$triggerParam_.runs.$runParam/route.tsx index e1a62acedc2..886dc247413 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers_.external.$triggerParam_.runs.$runParam/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers_.external.$triggerParam_.runs.$runParam/route.tsx @@ -10,6 +10,7 @@ import { prisma } from "~/db.server"; import { useOrganization } from "~/hooks/useOrganizations"; import { useProject } from "~/hooks/useProject"; import { useTypedMatchData } from "~/hooks/useTypedMatchData"; +import { useUser } from "~/hooks/useUser"; import { RunPresenter } from "~/presenters/RunPresenter.server"; import { requireUserId } from "~/services/session.server"; import { Handle } from "~/utils/handle"; @@ -97,6 +98,7 @@ export default function Page() { const { run, trigger } = useTypedLoaderData(); const organization = useOrganization(); const project = useProject(); + const user = useUser(); const revalidator = useRevalidator(); const events = useEventSource( @@ -124,6 +126,7 @@ export default function Page() { id: trigger.id, }), }} + currentUser={user} /> ); } diff --git a/apps/webapp/app/utils/username.ts b/apps/webapp/app/utils/username.ts new file mode 100644 index 00000000000..3eb077ff29d --- /dev/null +++ b/apps/webapp/app/utils/username.ts @@ -0,0 +1,22 @@ +import type { User as DBUser } from "~/models/user.server"; + +type User = Pick; + +// remove `null` from username +export function getUsername(user?: User): string | undefined { + if (!user) { + return; + } + + // user.displayName is of type `string | null` + if (user.displayName) { + return user.displayName; + } + + // user.name is of type `string | null` + if (user.name) { + return user.name; + } + + return; +} From ddd57b87f1e91aacdf8f08b3f164a1e8c0cb5538 Mon Sep 17 00:00:00 2001 From: hmacr Date: Thu, 30 Nov 2023 12:06:57 +0530 Subject: [PATCH 02/10] fix typechecks --- .../route.tsx | 3 ++ .../route.tsx | 29 ++++++++++--------- .../route.tsx | 3 ++ .../route.tsx | 8 ++++- 4 files changed, 28 insertions(+), 15 deletions(-) diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.runs/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.runs/route.tsx index 978263524b8..d39a75a9feb 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.runs/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.runs/route.tsx @@ -13,6 +13,7 @@ import { import { RunsTable } from "~/components/runs/RunsTable"; import { useOrganization } from "~/hooks/useOrganizations"; import { useProject } from "~/hooks/useProject"; +import { useUser } from "~/hooks/useUser"; import { RunListPresenter } from "~/presenters/RunListPresenter.server"; import { requireUserId } from "~/services/session.server"; import { ProjectParamSchema, docsPath, projectPath } from "~/utils/pathBuilder"; @@ -48,6 +49,7 @@ export default function Page() { const isLoading = navigation.state !== "idle"; const organization = useOrganization(); const project = useProject(); + const user = useUser(); return ( @@ -79,6 +81,7 @@ export default function Page() { runs={list.runs} isLoading={isLoading} runsParentPath={projectPath(organization, project)} + currentUser={user} /> diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers_.webhooks.$triggerParam._index/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers_.webhooks.$triggerParam._index/route.tsx index d3af8ec236d..99c48782a18 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers_.webhooks.$triggerParam._index/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers_.webhooks.$triggerParam._index/route.tsx @@ -9,6 +9,7 @@ import { Paragraph } from "~/components/primitives/Paragraph"; import { RunsTable } from "~/components/runs/RunsTable"; import { useOrganization } from "~/hooks/useOrganizations"; import { useProject } from "~/hooks/useProject"; +import { useUser } from "~/hooks/useUser"; import { useTypedMatchData } from "~/hooks/useTypedMatchData"; import { requireUser, requireUserId } from "~/services/session.server"; import { Handle } from "~/utils/handle"; @@ -105,10 +106,7 @@ export const handle: Handle = { - + ); }, @@ -118,6 +116,7 @@ export default function Page() { const { trigger } = useTypedLoaderData(); const organization = useOrganization(); const project = useProject(); + const user = useUser(); const navigation = useNavigation(); const lastSubmission = useActionData(); @@ -135,17 +134,17 @@ export default function Page() { return ( <> - Webhook Triggers need to be registered with the external service. You can see the list - of attempted registrations below. + Webhook Triggers need to be registered with the external service. You can see the list of + attempted registrations below. - {!trigger.active && + {!trigger.active && (
- - - Registration hasn't succeeded yet, check the runs below. - - {/* + + Registration hasn't succeeded yet, check the runs below. + + {/* @@ -159,8 +158,9 @@ export default function Page() { > {isLoading ? "Retrying…" : "Retry now"} */} - -
} + + + )} {trigger.runList ? ( <> @@ -170,6 +170,7 @@ export default function Page() { total={trigger.runList.runs.length} hasFilters={false} runsParentPath={webhookTriggerRunsParentPath(organization, project, trigger)} + currentUser={user} /> diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers_.webhooks.$triggerParam_.runs.$runParam/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers_.webhooks.$triggerParam_.runs.$runParam/route.tsx index facd0ec4eb7..48d3d27fcc2 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers_.webhooks.$triggerParam_.runs.$runParam/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers_.webhooks.$triggerParam_.runs.$runParam/route.tsx @@ -10,6 +10,7 @@ import { prisma } from "~/db.server"; import { useOrganization } from "~/hooks/useOrganizations"; import { useProject } from "~/hooks/useProject"; import { useTypedMatchData } from "~/hooks/useTypedMatchData"; +import { useUser } from "~/hooks/useUser"; import { RunPresenter } from "~/presenters/RunPresenter.server"; import { requireUserId } from "~/services/session.server"; import { Handle } from "~/utils/handle"; @@ -103,6 +104,7 @@ export default function Page() { const { run, trigger } = useTypedLoaderData(); const organization = useOrganization(); const project = useProject(); + const user = useUser(); const revalidator = useRevalidator(); const events = useEventSource( @@ -130,6 +132,7 @@ export default function Page() { id: trigger.id, }), }} + currentUser={user} /> ); } diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers_.webhooks.$triggerParam_.runs.delivery.$runParam/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers_.webhooks.$triggerParam_.runs.delivery.$runParam/route.tsx index f7fc7eb0f8a..16a04e8ebef 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers_.webhooks.$triggerParam_.runs.delivery.$runParam/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.triggers_.webhooks.$triggerParam_.runs.delivery.$runParam/route.tsx @@ -10,6 +10,7 @@ import { prisma } from "~/db.server"; import { useOrganization } from "~/hooks/useOrganizations"; import { useProject } from "~/hooks/useProject"; import { useTypedMatchData } from "~/hooks/useTypedMatchData"; +import { useUser } from "~/hooks/useUser"; import { RunPresenter } from "~/presenters/RunPresenter.server"; import { requireUserId } from "~/services/session.server"; import { Handle } from "~/utils/handle"; @@ -83,7 +84,10 @@ export const handle: Handle = { title={`${data.trigger.integration.title}: ${data.trigger.integration.slug}`} /> - + {data && data.run && ( (); const organization = useOrganization(); const project = useProject(); + const user = useUser(); const revalidator = useRevalidator(); const events = useEventSource( @@ -127,6 +132,7 @@ export default function Page() { id: trigger.id, }), }} + currentUser={user} /> ); } From c5f6a47639fef27996e0631a845b0710a76a2311 Mon Sep 17 00:00:00 2001 From: abhi1992002 Date: Tue, 19 Dec 2023 11:34:25 +0530 Subject: [PATCH 03/10] feat: add filter functionality on task run page --- .../route.tsx | 311 +++++++++++++++++- 1 file changed, 308 insertions(+), 3 deletions(-) diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.runs/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.runs/route.tsx index 61fa28cb493..053f009a5f2 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.runs/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.runs/route.tsx @@ -1,4 +1,4 @@ -import { useNavigation } from "@remix-run/react"; +import { useLocation, useNavigate, useNavigation } from "@remix-run/react"; import { LoaderFunctionArgs } from "@remix-run/server-runtime"; import { typedjson, useTypedLoaderData } from "remix-typedjson"; import { PageBody, PageContainer } from "~/components/layout/AppLayout"; @@ -19,6 +19,67 @@ import { requireUserId } from "~/services/session.server"; import { ProjectParamSchema, docsPath, projectPath } from "~/utils/pathBuilder"; import { ListPagination } from "../_app.orgs.$organizationSlug.projects.$projectParam.jobs.$jobParam._index/ListPagination"; import { RunListSearchSchema } from "../_app.orgs.$organizationSlug.projects.$projectParam.jobs.$jobParam._index/route"; +import { + Select, + SelectContent, + SelectGroup, + SelectItem, + SelectTrigger, + SelectValue, +} from "~/components/primitives/Select"; +import { useEffect, useState } from "react"; +import { JobRunStatus, RuntimeEnvironmentType } from "@trigger.dev/database"; +import { + CheckCircleIcon, + ClockIcon, + ExclamationTriangleIcon, + PauseCircleIcon, + XCircleIcon, +} from "@heroicons/react/24/solid"; +import { ChartBarIcon } from "@heroicons/react/20/solid"; +import { cn } from "~/utils/cn"; +import { Spinner } from "~/components/primitives/Spinner"; +import { NoSymbolIcon } from "@heroicons/react/20/solid"; + +// Filter -> status types +const ExtendedJobRunStatus = { + ALL: "ALL" as const, + ...JobRunStatus, +} as const; +type ExtendedJobRunStatusKey = keyof typeof ExtendedJobRunStatus; + +type FilterableStatus = + | "ALL" + | "QUEUED" + | "IN_PROGRESS" + | "WAITING" + | "COMPLETED" + | "FAILED" + | "CANCELED" + | "TIMEDOUT"; + +const filterableStatuses: Record = { + ALL: ["ALL"], + QUEUED: ["QUEUED", "WAITING_TO_EXECUTE", "PENDING", "WAITING_ON_CONNECTIONS"], + IN_PROGRESS: ["STARTED", "EXECUTING", "PREPROCESSING"], + WAITING: ["WAITING_TO_CONTINUE"], + COMPLETED: ["SUCCESS"], + FAILED: ["FAILURE", "UNRESOLVED_AUTH", "INVALID_PAYLOAD", "ABORTED"], + TIMEDOUT: ["TIMED_OUT"], + CANCELED: ["CANCELED"], +}; + +const statusKeys: FilterableStatus[] = Object.keys(filterableStatuses) as FilterableStatus[]; + +// Filter -> Environment types +const ExtendedRuntimeEnvironment = { + ALL: "ALL" as const, + ...RuntimeEnvironmentType, +} as const; +type ExtendedRuntimeEnvironmentType = keyof typeof ExtendedRuntimeEnvironment; +const environmentKeys: ExtendedRuntimeEnvironmentType[] = Object.keys( + ExtendedRuntimeEnvironment +) as ExtendedRuntimeEnvironmentType[]; export const loader = async ({ request, params }: LoaderFunctionArgs) => { const userId = await requireUserId(request); @@ -50,6 +111,72 @@ export default function Page() { const organization = useOrganization(); const project = useProject(); const user = useUser(); + const navigate = useNavigate(); + const location = useLocation(); + const url = new URLSearchParams(location.search); + + const [selectedEnvironment, setSelectedEnvironment] = useState( + ExtendedRuntimeEnvironment.ALL + ); + const [selectedStatus, setselectedStatus] = useState(ExtendedJobRunStatus.ALL); + const [filteredList, setFilteredList] = useState(list.runs); + + const handleFilterChange = (filterType: string, value: string) => { + url.set(filterType, value); + + const filters = ["status", "environment"]; // Add more filters as needed + const queryString = filters + .map((filter) => (url.has(filter) ? `${filter}=${url.get(filter)}` : null)) + .filter((filter) => filter !== null) + .join("&"); + + navigate(`${location.pathname}?${queryString}`); + }; + + const handleStatusChange = (value: FilterableStatus) => { + handleFilterChange("status", value); + setselectedStatus(value); + }; + + const handleEnvironmentChange = (value: string) => { + handleFilterChange("environment", value); + setSelectedEnvironment(value); + }; + + useEffect(() => { + const status = url.get("status"); + const environment = url.get("environment"); + + if (status && status in filterableStatuses) { + const statusArray = filterableStatuses[status as FilterableStatus]; + + if ( + (!status || !statusArray || statusArray.includes("ALL")) && + (!environment || environment === ExtendedRuntimeEnvironment.ALL) + ) { + setselectedStatus(ExtendedJobRunStatus.ALL); + setSelectedEnvironment(ExtendedRuntimeEnvironment.ALL); + setFilteredList(list.runs); + return; + } + + // Filter based on both status and environment + let filteredRuns = list.runs; + + if (status && statusArray && !statusArray.includes("ALL")) { + filteredRuns = filteredRuns.filter((run) => statusArray.includes(run.status)); + setselectedStatus(status); + } + + if (environment && environment !== ExtendedRuntimeEnvironment.ALL) { + filteredRuns = filteredRuns.filter((run) => run.environment.type === environment); + setSelectedEnvironment(environment); + } + + setFilteredList(filteredRuns); + } else { + } + }, [location.search, list.runs, setFilteredList, setselectedStatus, setSelectedEnvironment]); return ( @@ -71,14 +198,75 @@ export default function Page() {
-
+
+
+ {/* environment filter */} + + + + + {/* status filter */} + + + +
+
); } + +function filterEnvironmentTitle(environment: ExtendedRuntimeEnvironmentType) { + switch (environment) { + case "ALL": + return "All"; + case "PRODUCTION": + return "Prod"; + case "STAGING": + return "Staging"; + case "DEVELOPMENT": + return "Dev"; + case "PREVIEW": + return "Preview"; + } +} + +function filterEnvironmentColorClassName(environment: ExtendedRuntimeEnvironmentType) { + switch (environment) { + case "ALL": + return "bg-white"; + case "PRODUCTION": + return "bg-green-500"; + case "STAGING": + return "bg-amber-500"; + case "DEVELOPMENT": + return "bg-pink-500"; + case "PREVIEW": + return "bg-yellow-500"; + } +} + +export function FilterStatusLabel({ status }: { status: FilterableStatus }) { + return {filterStatusTitle(status)}; +} + +export function FilterStatusIcon({ + status, + className, +}: { + status: FilterableStatus; + className: string; +}) { + switch (status) { + case "ALL": + return ; + case "COMPLETED": + return ; + case "WAITING": + return ; + case "QUEUED": + return ; + case "IN_PROGRESS": + return ; + case "TIMEDOUT": + return ( + + ); + case "CANCELED": + return ; + case "FAILED": + return ; + default: { + const _exhaustiveCheck: never = status; + throw new Error(`Non-exhaustive match for value: ${status}`); + } + } +} + +export function filterStatusTitle(status: FilterableStatus): string { + switch (status) { + case "ALL": + return "All Status"; + case "QUEUED": + return "Queued"; + case "IN_PROGRESS": + return "In progress"; + case "WAITING": + return "Waiting"; + case "COMPLETED": + return "Completed"; + case "FAILED": + return "Failed"; + case "CANCELED": + return "Canceled"; + case "TIMEDOUT": + return "Timed out"; + default: { + const _exhaustiveCheck: never = status; + throw new Error(`Non-exhaustive match for value: ${status}`); + } + } +} + +export function filterStatusClassNameColor(status: FilterableStatus): string { + switch (status) { + case "ALL": + return "text-dimmed"; + case "QUEUED": + return "text-slate-500"; + case "IN_PROGRESS": + return "text-blue-500"; + case "WAITING": + return "text-blue-500"; + case "COMPLETED": + return "text-green-500"; + case "FAILED": + return "text-rose-500"; + case "CANCELED": + return "text-slate-500"; + case "TIMEDOUT": + return "text-amber-300"; + default: { + const _exhaustiveCheck: never = status; + throw new Error(`Non-exhaustive match for value: ${status}`); + } + } +} From e61aff2522f747951844c004897f635ff04158ea Mon Sep 17 00:00:00 2001 From: abhi1992002 Date: Tue, 19 Dec 2023 12:31:45 +0530 Subject: [PATCH 04/10] feat:adding filter functionality on run task page --- .../route.tsx | 53 +++++++++---------- 1 file changed, 25 insertions(+), 28 deletions(-) diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.runs/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.runs/route.tsx index 053f009a5f2..6635b7f0c04 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.runs/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.runs/route.tsx @@ -147,35 +147,32 @@ export default function Page() { const status = url.get("status"); const environment = url.get("environment"); - if (status && status in filterableStatuses) { - const statusArray = filterableStatuses[status as FilterableStatus]; - - if ( - (!status || !statusArray || statusArray.includes("ALL")) && - (!environment || environment === ExtendedRuntimeEnvironment.ALL) - ) { - setselectedStatus(ExtendedJobRunStatus.ALL); - setSelectedEnvironment(ExtendedRuntimeEnvironment.ALL); - setFilteredList(list.runs); - return; - } - - // Filter based on both status and environment - let filteredRuns = list.runs; - - if (status && statusArray && !statusArray.includes("ALL")) { - filteredRuns = filteredRuns.filter((run) => statusArray.includes(run.status)); - setselectedStatus(status); - } - - if (environment && environment !== ExtendedRuntimeEnvironment.ALL) { - filteredRuns = filteredRuns.filter((run) => run.environment.type === environment); - setSelectedEnvironment(environment); - } - - setFilteredList(filteredRuns); - } else { + const statusArray = filterableStatuses[status as FilterableStatus]; + + if ( + (!status || !statusArray || statusArray.includes("ALL")) && + (!environment || environment === ExtendedRuntimeEnvironment.ALL) + ) { + setselectedStatus(ExtendedJobRunStatus.ALL); + setSelectedEnvironment(ExtendedRuntimeEnvironment.ALL); + setFilteredList(list.runs); + return; } + + // Filter based on both status and environment + let filteredRuns = list.runs; + + if (status && statusArray && !statusArray.includes("ALL")) { + filteredRuns = filteredRuns.filter((run) => statusArray.includes(run.status)); + setselectedStatus(status); + } + + if (environment && environment !== ExtendedRuntimeEnvironment.ALL) { + filteredRuns = filteredRuns.filter((run) => run.environment.type === environment); + setSelectedEnvironment(environment); + } + + setFilteredList(filteredRuns); }, [location.search, list.runs, setFilteredList, setselectedStatus, setSelectedEnvironment]); return ( From 06527a5d110d97975c9be6f440ae9acf4ace462a Mon Sep 17 00:00:00 2001 From: abhi1992002 Date: Tue, 19 Dec 2023 18:01:57 +0530 Subject: [PATCH 05/10] changes filter from csr to ssr --- .../route.tsx | 52 ++++++++----------- 1 file changed, 22 insertions(+), 30 deletions(-) diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.runs/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.runs/route.tsx index 6635b7f0c04..49441474f3d 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.runs/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.runs/route.tsx @@ -99,13 +99,30 @@ export const loader = async ({ request, params }: LoaderFunctionArgs) => { pageSize: 25, }); + const status = url.searchParams.get("status"); + const environment = url.searchParams.get("environment"); + + const statusArray = filterableStatuses[status as FilterableStatus]; + + // Filter based on both status and environment + let filteredRuns = list.runs; + + if (status && statusArray && !statusArray.includes("ALL")) { + filteredRuns = filteredRuns.filter((run) => statusArray.includes(run.status)); + } + + if (environment && environment !== ExtendedRuntimeEnvironment.ALL) { + filteredRuns = filteredRuns.filter((run) => run.environment.type === environment); + } + return typedjson({ list, + filteredRuns, }); }; export default function Page() { - const { list } = useTypedLoaderData(); + const { list, filteredRuns } = useTypedLoaderData(); const navigation = useNavigation(); const isLoading = navigation.state !== "idle"; const organization = useOrganization(); @@ -119,7 +136,6 @@ export default function Page() { ExtendedRuntimeEnvironment.ALL ); const [selectedStatus, setselectedStatus] = useState(ExtendedJobRunStatus.ALL); - const [filteredList, setFilteredList] = useState(list.runs); const handleFilterChange = (filterType: string, value: string) => { url.set(filterType, value); @@ -147,33 +163,9 @@ export default function Page() { const status = url.get("status"); const environment = url.get("environment"); - const statusArray = filterableStatuses[status as FilterableStatus]; - - if ( - (!status || !statusArray || statusArray.includes("ALL")) && - (!environment || environment === ExtendedRuntimeEnvironment.ALL) - ) { - setselectedStatus(ExtendedJobRunStatus.ALL); - setSelectedEnvironment(ExtendedRuntimeEnvironment.ALL); - setFilteredList(list.runs); - return; - } - - // Filter based on both status and environment - let filteredRuns = list.runs; - - if (status && statusArray && !statusArray.includes("ALL")) { - filteredRuns = filteredRuns.filter((run) => statusArray.includes(run.status)); - setselectedStatus(status); - } - - if (environment && environment !== ExtendedRuntimeEnvironment.ALL) { - filteredRuns = filteredRuns.filter((run) => run.environment.type === environment); - setSelectedEnvironment(environment); - } - - setFilteredList(filteredRuns); - }, [location.search, list.runs, setFilteredList, setselectedStatus, setSelectedEnvironment]); + setselectedStatus(status as FilterableStatus); + setSelectedEnvironment(environment as ExtendedRuntimeEnvironmentType); + }); return ( @@ -263,7 +255,7 @@ export default function Page() { total={list.runs.length} hasFilters={false} showJob={true} - runs={filteredList} + runs={filteredRuns} isLoading={isLoading} runsParentPath={projectPath(organization, project)} currentUser={user} From 7c528cb828d7904655b59c8b50f33d859ab1b015 Mon Sep 17 00:00:00 2001 From: abhi1992002 Date: Tue, 19 Dec 2023 19:37:58 +0530 Subject: [PATCH 06/10] changing filter functionality from csr to ssr --- .../app/presenters/RunListPresenter.server.ts | 7 +++ .../route.tsx | 62 +++++++++++-------- 2 files changed, 42 insertions(+), 27 deletions(-) diff --git a/apps/webapp/app/presenters/RunListPresenter.server.ts b/apps/webapp/app/presenters/RunListPresenter.server.ts index 7c4b6e752fc..5508fe5dafc 100644 --- a/apps/webapp/app/presenters/RunListPresenter.server.ts +++ b/apps/webapp/app/presenters/RunListPresenter.server.ts @@ -1,3 +1,4 @@ +import { JobRunStatus, RuntimeEnvironmentType } from "@trigger.dev/database"; import { z } from "zod"; import { PrismaClient, prisma } from "~/db.server"; import { DirectionSchema } from "~/routes/_app.orgs.$organizationSlug.projects.$projectParam.jobs.$jobParam._index/route"; @@ -11,6 +12,8 @@ type RunListOptions = { organizationSlug: string; projectSlug: string; direction?: Direction; + filterStatus?: JobRunStatus[]; + filterEnvironment?: RuntimeEnvironmentType; cursor?: string; pageSize?: number; }; @@ -31,6 +34,8 @@ export class RunListPresenter { jobSlug, organizationSlug, projectSlug, + filterEnvironment, + filterStatus, direction = "forward", cursor, pageSize = DEFAULT_PAGE_SIZE, @@ -115,6 +120,8 @@ export class RunListPresenter { environmentId: { in: environments.map((environment) => environment.id), }, + status: filterStatus ? { in: filterStatus } : undefined, + environment: filterEnvironment ? { type: filterEnvironment } : undefined, }, orderBy: [{ id: "desc" }], //take an extra record to tell if there are more diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.runs/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.runs/route.tsx index 49441474f3d..bd150f159a4 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.runs/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.runs/route.tsx @@ -89,9 +89,29 @@ export const loader = async ({ request, params }: LoaderFunctionArgs) => { const s = Object.fromEntries(url.searchParams.entries()); const searchParams = RunListSearchSchema.parse(s); + const status = url.searchParams.get("status"); + const environment = url.searchParams.get("environment"); + + let filterStatus: JobRunStatus[] | undefined; + if (status && status !== "ALL") { + if (filterableStatuses.hasOwnProperty(status)) { + filterStatus = filterableStatuses[status as FilterableStatus] as JobRunStatus[]; + } + } + + let filterEnvironment: RuntimeEnvironmentType | undefined; + if (environment && environment !== "ALL") { + if (environmentKeys.includes(environment)) { + filterEnvironment = environment as RuntimeEnvironmentType; + } + } + const presenter = new RunListPresenter(); + const list = await presenter.call({ userId, + filterEnvironment: filterEnvironment, + filterStatus: filterStatus, projectSlug: projectParam, organizationSlug, direction: searchParams.direction, @@ -99,30 +119,13 @@ export const loader = async ({ request, params }: LoaderFunctionArgs) => { pageSize: 25, }); - const status = url.searchParams.get("status"); - const environment = url.searchParams.get("environment"); - - const statusArray = filterableStatuses[status as FilterableStatus]; - - // Filter based on both status and environment - let filteredRuns = list.runs; - - if (status && statusArray && !statusArray.includes("ALL")) { - filteredRuns = filteredRuns.filter((run) => statusArray.includes(run.status)); - } - - if (environment && environment !== ExtendedRuntimeEnvironment.ALL) { - filteredRuns = filteredRuns.filter((run) => run.environment.type === environment); - } - return typedjson({ list, - filteredRuns, }); }; export default function Page() { - const { list, filteredRuns } = useTypedLoaderData(); + const { list } = useTypedLoaderData(); const navigation = useNavigation(); const isLoading = navigation.state !== "idle"; const organization = useOrganization(); @@ -149,6 +152,19 @@ export default function Page() { navigate(`${location.pathname}?${queryString}`); }; + useEffect(() => { + const status = url.get("status"); + const environment = url.get("environment"); + + if (status && status !== "ALL") { + setselectedStatus(status); + } + + if (environment && environment !== "ALL") { + setSelectedEnvironment(environment!); + } + }); + const handleStatusChange = (value: FilterableStatus) => { handleFilterChange("status", value); setselectedStatus(value); @@ -159,14 +175,6 @@ export default function Page() { setSelectedEnvironment(value); }; - useEffect(() => { - const status = url.get("status"); - const environment = url.get("environment"); - - setselectedStatus(status as FilterableStatus); - setSelectedEnvironment(environment as ExtendedRuntimeEnvironmentType); - }); - return ( @@ -255,7 +263,7 @@ export default function Page() { total={list.runs.length} hasFilters={false} showJob={true} - runs={filteredRuns} + runs={list.runs} isLoading={isLoading} runsParentPath={projectPath(organization, project)} currentUser={user} From 4916b78d69f19d09bba2431a0aa70b33af1e5999 Mon Sep 17 00:00:00 2001 From: abhi1992002 Date: Tue, 19 Dec 2023 19:45:41 +0530 Subject: [PATCH 07/10] adding url.string() for navigation purpose --- .../route.tsx | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.runs/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.runs/route.tsx index bd150f159a4..adf68cc2505 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.runs/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.runs/route.tsx @@ -142,14 +142,7 @@ export default function Page() { const handleFilterChange = (filterType: string, value: string) => { url.set(filterType, value); - - const filters = ["status", "environment"]; // Add more filters as needed - const queryString = filters - .map((filter) => (url.has(filter) ? `${filter}=${url.get(filter)}` : null)) - .filter((filter) => filter !== null) - .join("&"); - - navigate(`${location.pathname}?${queryString}`); + navigate(`${location.pathname}?${url.toString()}`); }; useEffect(() => { From ca2033764da124d5fdfbb5799e75bbefc5a41ed9 Mon Sep 17 00:00:00 2001 From: abhi1992002 Date: Fri, 22 Dec 2023 19:10:50 +0530 Subject: [PATCH 08/10] improving state management in filtering --- .../route.tsx | 22 ++----------------- 1 file changed, 2 insertions(+), 20 deletions(-) diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.runs/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.runs/route.tsx index adf68cc2505..5628bc11519 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.runs/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.runs/route.tsx @@ -27,7 +27,6 @@ import { SelectTrigger, SelectValue, } from "~/components/primitives/Select"; -import { useEffect, useState } from "react"; import { JobRunStatus, RuntimeEnvironmentType } from "@trigger.dev/database"; import { CheckCircleIcon, @@ -135,37 +134,20 @@ export default function Page() { const location = useLocation(); const url = new URLSearchParams(location.search); - const [selectedEnvironment, setSelectedEnvironment] = useState( - ExtendedRuntimeEnvironment.ALL - ); - const [selectedStatus, setselectedStatus] = useState(ExtendedJobRunStatus.ALL); + const selectedEnvironment = url.get("environment") || ExtendedRuntimeEnvironment.ALL; + const selectedStatus = url.get("status") || ExtendedJobRunStatus.ALL; const handleFilterChange = (filterType: string, value: string) => { url.set(filterType, value); navigate(`${location.pathname}?${url.toString()}`); }; - useEffect(() => { - const status = url.get("status"); - const environment = url.get("environment"); - - if (status && status !== "ALL") { - setselectedStatus(status); - } - - if (environment && environment !== "ALL") { - setSelectedEnvironment(environment!); - } - }); - const handleStatusChange = (value: FilterableStatus) => { handleFilterChange("status", value); - setselectedStatus(value); }; const handleEnvironmentChange = (value: string) => { handleFilterChange("environment", value); - setSelectedEnvironment(value); }; return ( From 02451cba2f853139e38ca43b33fcc2d6ad2c0b0f Mon Sep 17 00:00:00 2001 From: Matt Aitken Date: Wed, 3 Jan 2024 13:29:24 +0000 Subject: [PATCH 09/10] When setting a filter, delete the cursor and direction --- .../route.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.runs/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.runs/route.tsx index 5628bc11519..11d462af670 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.runs/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.runs/route.tsx @@ -139,6 +139,8 @@ export default function Page() { const handleFilterChange = (filterType: string, value: string) => { url.set(filterType, value); + url.delete("cursor"); + url.delete("direction"); navigate(`${location.pathname}?${url.toString()}`); }; From 1860b90f1cea1eb7d0294e834810744138dbe108 Mon Sep 17 00:00:00 2001 From: Matt Aitken Date: Wed, 3 Jan 2024 13:32:44 +0000 Subject: [PATCH 10/10] Some design improvements for the "All" options --- .../route.tsx | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.runs/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.runs/route.tsx index 11d462af670..a197b6a3920 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.runs/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.runs/route.tsx @@ -187,8 +187,8 @@ export default function Page() { {environmentKeys.map((env) => ( - { -
+
+ {env !== "ALL" && ( {filterEnvironmentTitle(env)} - - {env === "ALL" ? env + " Environments" : env} - -
- } + )} + + {env === "ALL" ? env + " Environments" : env} + +
))}
@@ -270,7 +270,7 @@ function filterEnvironmentTitle(environment: ExtendedRuntimeEnvironmentType) { function filterEnvironmentColorClassName(environment: ExtendedRuntimeEnvironmentType) { switch (environment) { case "ALL": - return "bg-white"; + return "bg-indigo-500"; case "PRODUCTION": return "bg-green-500"; case "STAGING": @@ -295,7 +295,7 @@ export function FilterStatusIcon({ }) { switch (status) { case "ALL": - return ; + return ; case "COMPLETED": return ; case "WAITING":