Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import React, { useState } from "react";
import { IconAdjustmentsExclamation } from "@tabler/icons-react";

import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@ctrlplane/ui/dropdown-menu";

import { OverrideJobStatusDialog } from "~/app/[workspaceSlug]/(appv2)/_components/job/JobDropdownMenu";

export const EnvironmentRowDropdown: React.FC<{
jobIds: string[];
children: React.ReactNode;
}> = ({ jobIds, children }) => {
const [open, setOpen] = useState(false);

return (
<DropdownMenu open={open} onOpenChange={setOpen}>
<DropdownMenuTrigger asChild>{children}</DropdownMenuTrigger>
<DropdownMenuContent>
<OverrideJobStatusDialog jobIds={jobIds} onClose={() => setOpen(false)}>
<DropdownMenuItem
onSelect={(e) => e.preventDefault()}
className="space-x-2"
>
<IconAdjustmentsExclamation size={16} />
<p>Override Job Status</p>
</DropdownMenuItem>
</OverrideJobStatusDialog>
</DropdownMenuContent>
</DropdownMenu>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import { useFilter } from "~/app/[workspaceSlug]/(appv2)/_hooks/useFilter";
import { Sidebars } from "~/app/[workspaceSlug]/sidebars";
import { api } from "~/trpc/react";
import { EnvironmentApprovalRow } from "./EnvironmentApprovalRow";
import { EnvironmentRowDropdown } from "./EnvironmentRowDropdown";

type Trigger = RouterOutputs["job"]["config"]["byReleaseId"][number];

Expand Down Expand Up @@ -75,6 +76,7 @@ const CollapsibleTableRow: React.FC<CollapsibleTableRowProps> = ({
);

const allTriggers = Object.values(triggersByResource).flat();
const allJobIds = allTriggers.map((t) => t.job.id);
const latestStatusesByResource = Object.entries(triggersByResource).map(
([_, triggers]) => {
const sortedByCreatedAt = triggers.sort(
Expand Down Expand Up @@ -170,6 +172,12 @@ const CollapsibleTableRow: React.FC<CollapsibleTableRowProps> = ({
release={release}
/>
))}

<EnvironmentRowDropdown jobIds={allJobIds}>
<Button variant="ghost" size="icon" className="h-7 w-7">
<IconDots className="h-4 w-4" />
</Button>
</EnvironmentRowDropdown>
</div>
</div>
</TableCell>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,13 +69,13 @@ const overrideJobStatusFormSchema = z.object({
status: z.nativeEnum(JobStatus),
});

const OverrideJobStatusDialog: React.FC<{
job: { id: string; status: JobStatus };
export const OverrideJobStatusDialog: React.FC<{
jobIds: string[];
onClose: () => void;
children: React.ReactNode;
}> = ({ job, onClose, children }) => {
}> = ({ jobIds, onClose, children }) => {
const [open, setOpen] = useState(false);
const updateJob = api.job.update.useMutation();
const updateJobs = api.job.updateMany.useMutation();
const utils = api.useUtils();

const form = useForm({
Expand All @@ -84,10 +84,10 @@ const OverrideJobStatusDialog: React.FC<{
});

const onSubmit = form.handleSubmit((data) =>
updateJob
.mutateAsync({ id: job.id, data })
updateJobs
.mutateAsync({ ids: jobIds, data })
.then(() => utils.job.config.byReleaseId.invalidate())
.then(() => utils.job.config.byId.invalidate(job.id))
.then(() => jobIds.map((id) => utils.job.config.byId.invalidate(id)))
.then(() => utils.release.list.invalidate())
.then(() => setOpen(false))
.then(() => onClose()),
Expand Down Expand Up @@ -139,9 +139,9 @@ const OverrideJobStatusDialog: React.FC<{
)}
/>

{updateJob.error != null && (
{updateJobs.error != null && (
<div className="text-sm text-red-500">
{updateJob.error.message}
{updateJobs.error.message}
</div>
)}

Expand All @@ -153,7 +153,7 @@ const OverrideJobStatusDialog: React.FC<{
<Button
type="submit"
className={buttonVariants({ variant: "destructive" })}
disabled={updateJob.isPending}
disabled={updateJobs.isPending}
>
Override
</Button>
Expand Down Expand Up @@ -359,7 +359,10 @@ export const JobDropdownMenu: React.FC<{
</RedeployReleaseDialog>
)}

<OverrideJobStatusDialog job={job} onClose={() => setOpen(false)}>
<OverrideJobStatusDialog
jobIds={[job.id]}
onClose={() => setOpen(false)}
>
<DropdownMenuItem
onSelect={(e) => e.preventDefault()}
className="space-x-2"
Expand Down
19 changes: 19 additions & 0 deletions packages/api/src/router/job.ts
Original file line number Diff line number Diff line change
Expand Up @@ -782,6 +782,25 @@ export const jobRouter = createTRPCRouter({
.input(z.object({ id: z.string().uuid(), data: schema.updateJob }))
.mutation(({ ctx, input }) => updateJob(ctx.db, input.id, input.data)),

updateMany: protectedProcedure
.input(
z.object({ ids: z.array(z.string().uuid()), data: schema.updateJob }),
)
.meta({
authorizationCheck: ({ canUser, input }) => {
const jobIds: string[] = input.ids;
const authzPromises = jobIds.map((id) =>
canUser.perform(Permission.JobUpdate).on({ type: "job", id }),
);
return Promise.all(authzPromises).then((results) =>
results.every(Boolean),
);
},
})
.mutation(({ ctx, input }) =>
Promise.all(input.ids.map((id) => updateJob(ctx.db, id, input.data))),
),

config: releaseJobTriggerRouter,
agent: jobAgentRouter,
trigger: jobTriggerRouter,
Expand Down
Loading