-
-
Notifications
You must be signed in to change notification settings - Fork 1k
Bulk actions 2.0 (and switch all run listing to ClickHouse) #2264
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
d1c2007
c8d490c
4278b75
d437b34
57d9560
a920cf4
dbcbd5c
03d411a
746af29
30ca840
7b2fb10
abd31dc
588c538
f9f18c0
1dd6c97
8516ea0
be683b9
2648cbc
c5617ac
b869345
7557871
13f44c6
413476a
aea732f
aa2fdfc
498fb31
06ac716
faf2101
866d78a
32fdfb8
cc519e8
35e1d32
1a81247
0224b9f
2cc4233
24159fe
1cfbdab
52e247e
5e4ea1c
1b0acf5
f8444b3
e00a246
c45f406
e74be66
4637981
91c1ba2
2cc8f1a
dabb78b
5c421ad
b17251d
c0702ba
0bff001
f14cf0f
352cf0a
73fc4ee
19ab6c7
3a127f6
4f6adcb
5e2bdf2
1426ad1
be5585b
39e004e
a7b41e5
020e9f4
3b8e95f
53db053
aaab539
932de42
678e9ee
72c131b
a1f48c9
4022627
3268e0a
6c43a87
ccdf385
5fb35cb
7edc015
00058a1
63c0b1b
c5ce66f
096317d
f39bf84
625f6a4
34e4d8f
dbb577a
8dc87bd
f4a713c
ca00ab4
23ee423
28ef8f3
077dad2
1f274c5
93febfe
df5abd2
b8bcc31
c096d52
fb338e7
015e197
92c929c
a8368e6
4b4aa60
72d6d09
180d9be
be492c9
4656e5e
8688534
460479b
a8c3a84
02e3024
a759903
49918fe
395ce81
e272ce3
8800e2d
b24b7de
8491bd0
3ed5f06
04b5509
a761931
94f6538
c511b62
5beb2a7
1beed6f
d962ed3
8d63cc1
0e57231
b66516d
ff74318
3fa16a1
f33516a
117c774
66b8a97
317f4ba
48bb7d7
2ae73ac
87a5db6
0d3c507
719fea2
71b60ac
6010fd6
37a95fa
fb0684a
e69edad
3b54687
ba186d9
b154a4c
698fbe1
998254d
208a7fe
67e209e
af57ddf
d814408
41fe0c8
e8a6618
7c832e1
cc0dffc
0fd224f
cc204de
fae499c
3a8ec13
0e1cdaa
3061728
e533e34
320d93c
a58f66e
5b3d725
2000484
ff56de1
86864dd
aecceec
e50a882
a5c2dd0
e808156
10377de
027b4f4
d92e80e
632bbee
2b8bae8
0dfab94
6a4d71b
6e025bd
62bed7e
99d842f
edd4ecf
b968e14
f7fabfb
b91f1eb
1ae7f0c
ed7547b
e81b05e
474b46f
9f5bb44
4637b43
b24a0c5
ec14e03
400b0b2
2fa4bd8
26d5d5c
5c58659
9d88b84
8739b05
0413dab
fed987e
e823159
00d7121
885e2bf
14915d6
d80af53
f8733b3
3a4a622
e74b750
7e4c91d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
- Loading branch information
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,62 @@ | ||
| import { getUsername } from "~/utils/username"; | ||
| import { BasePresenter } from "./basePresenter.server"; | ||
|
|
||
| type BulkActionListOptions = { | ||
| environmentId: string; | ||
| page?: number; | ||
| }; | ||
|
|
||
| const DEFAULT_PAGE_SIZE = 25; | ||
|
|
||
| export type BulkActionListItem = Awaited< | ||
| ReturnType<BulkActionListPresenter["call"]> | ||
| >["bulkActions"][number]; | ||
|
|
||
| export class BulkActionListPresenter extends BasePresenter { | ||
| public async call({ environmentId, page }: BulkActionListOptions) { | ||
| const totalCount = await this._replica.bulkActionGroup.count({ | ||
| where: { | ||
| environmentId, | ||
| }, | ||
| }); | ||
|
Comment on lines
+17
to
+21
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I wonder if this is still covered by this composite index: (needs to be tested once deployed with a larger table)
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👍 yep we should test this, locally it will always do a sequential scan |
||
|
|
||
| const bulkActions = await this._replica.bulkActionGroup.findMany({ | ||
| select: { | ||
| friendlyId: true, | ||
| name: true, | ||
| status: true, | ||
| type: true, | ||
| createdAt: true, | ||
| completedAt: true, | ||
| totalCount: true, | ||
| user: { | ||
| select: { | ||
| name: true, | ||
| displayName: true, | ||
| avatarUrl: true, | ||
| }, | ||
| }, | ||
| }, | ||
| where: { | ||
| environmentId, | ||
| }, | ||
| orderBy: { | ||
| createdAt: "desc", | ||
| }, | ||
| skip: ((page ?? 1) - 1) * DEFAULT_PAGE_SIZE, | ||
| take: DEFAULT_PAGE_SIZE, | ||
| }); | ||
|
|
||
| return { | ||
| currentPage: page ?? 1, | ||
| totalPages: Math.ceil(totalCount / DEFAULT_PAGE_SIZE), | ||
| totalCount: totalCount, | ||
| bulkActions: bulkActions.map((bulkAction) => ({ | ||
| ...bulkAction, | ||
| user: bulkAction.user | ||
| ? { name: getUsername(bulkAction.user), avatarUrl: bulkAction.user.avatarUrl } | ||
| : undefined, | ||
| })), | ||
| }; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,45 @@ | ||
| import { getUsername } from "~/utils/username"; | ||
| import { BasePresenter } from "./basePresenter.server"; | ||
|
|
||
| type BulkActionOptions = { | ||
| environmentId: string; | ||
| bulkActionId: string; | ||
| }; | ||
|
|
||
| export class BulkActionPresenter extends BasePresenter { | ||
| public async call({ environmentId, bulkActionId }: BulkActionOptions) { | ||
| const bulkAction = await this._replica.bulkActionGroup.findFirst({ | ||
| select: { | ||
| friendlyId: true, | ||
| name: true, | ||
| status: true, | ||
| type: true, | ||
| createdAt: true, | ||
| completedAt: true, | ||
| totalCount: true, | ||
| user: { | ||
| select: { | ||
| name: true, | ||
| displayName: true, | ||
| avatarUrl: true, | ||
| }, | ||
| }, | ||
| }, | ||
| where: { | ||
| environmentId, | ||
| friendlyId: bulkActionId, | ||
| }, | ||
| }); | ||
|
|
||
| if (!bulkAction) { | ||
| throw new Error("Bulk action not found"); | ||
| } | ||
|
|
||
| return { | ||
| ...bulkAction, | ||
| user: bulkAction.user | ||
| ? { name: getUsername(bulkAction.user), avatarUrl: bulkAction.user.avatarUrl } | ||
| : undefined, | ||
| }; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,185 @@ | ||
| import { ArrowPathIcon, BookOpenIcon } from "@heroicons/react/20/solid"; | ||
| import { type LoaderFunctionArgs } from "@remix-run/server-runtime"; | ||
| import { tryCatch } from "@trigger.dev/core"; | ||
| import { typedjson, useTypedLoaderData } from "remix-typedjson"; | ||
| import { z } from "zod"; | ||
| import { ExitIcon } from "~/assets/icons/ExitIcon"; | ||
| import { RunsIcon } from "~/assets/icons/RunsIcon"; | ||
| import { InlineCode } from "~/components/code/InlineCode"; | ||
| import { EnvironmentCombo } from "~/components/environments/EnvironmentLabel"; | ||
| import { LinkButton } from "~/components/primitives/Buttons"; | ||
| import { DateTime } from "~/components/primitives/DateTime"; | ||
| import { Header2, Header3 } from "~/components/primitives/Headers"; | ||
| import { Paragraph } from "~/components/primitives/Paragraph"; | ||
| import * as Property from "~/components/primitives/PropertyTable"; | ||
| import { | ||
| Table, | ||
| TableBlankRow, | ||
| TableBody, | ||
| TableCell, | ||
| TableHeader, | ||
| TableHeaderCell, | ||
| TableRow, | ||
| } from "~/components/primitives/Table"; | ||
| import { BulkActionTypeCombo } from "~/components/runs/v3/BulkAction"; | ||
| import { EnabledStatus } from "~/components/runs/v3/EnabledStatus"; | ||
| import { ScheduleTypeCombo } from "~/components/runs/v3/ScheduleType"; | ||
| import { UserAvatar } from "~/components/UserProfilePhoto"; | ||
| import { useEnvironment } from "~/hooks/useEnvironment"; | ||
| import { useOrganization } from "~/hooks/useOrganizations"; | ||
| import { useProject } from "~/hooks/useProject"; | ||
| import { findProjectBySlug } from "~/models/project.server"; | ||
| import { findEnvironmentBySlug } from "~/models/runtimeEnvironment.server"; | ||
| import { BulkActionPresenter } from "~/presenters/v3/BulkActionPresenter.server"; | ||
| import { requireUserId } from "~/services/session.server"; | ||
| import { cn } from "~/utils/cn"; | ||
| import { | ||
| EnvironmentParamSchema, | ||
| v3BulkActionsPath, | ||
| v3CreateBulkActionPath, | ||
| v3RunsPath, | ||
| } from "~/utils/pathBuilder"; | ||
|
|
||
| const BulkActionParamSchema = EnvironmentParamSchema.extend({ | ||
| bulkActionParam: z.string(), | ||
| }); | ||
|
|
||
| export const loader = async ({ request, params }: LoaderFunctionArgs) => { | ||
| const userId = await requireUserId(request); | ||
|
|
||
| const { organizationSlug, projectParam, envParam, bulkActionParam } = | ||
| BulkActionParamSchema.parse(params); | ||
|
|
||
| const project = await findProjectBySlug(organizationSlug, projectParam, userId); | ||
| if (!project) { | ||
| throw new Response("Not Found", { status: 404 }); | ||
| } | ||
|
|
||
| const environment = await findEnvironmentBySlug(project.id, envParam, userId); | ||
| if (!environment) { | ||
| throw new Response("Not Found", { status: 404 }); | ||
| } | ||
|
|
||
| try { | ||
| const presenter = new BulkActionPresenter(); | ||
| const [error, data] = await tryCatch( | ||
| presenter.call({ | ||
| environmentId: environment.id, | ||
| bulkActionId: bulkActionParam, | ||
| }) | ||
| ); | ||
|
|
||
| if (error) { | ||
| throw new Error(error.message); | ||
| } | ||
|
|
||
| return typedjson({ bulkAction: data }); | ||
| } catch (error) { | ||
| console.error(error); | ||
| throw new Response(undefined, { | ||
| status: 400, | ||
| statusText: "Something went wrong, if this problem persists please contact support.", | ||
| }); | ||
| } | ||
| }; | ||
|
|
||
| export default function Page() { | ||
| const { bulkAction } = useTypedLoaderData<typeof loader>(); | ||
| const organization = useOrganization(); | ||
| const project = useProject(); | ||
| const environment = useEnvironment(); | ||
|
|
||
| return ( | ||
| <div className="grid h-full max-h-full grid-rows-[2.5rem_1fr_3.25rem] overflow-hidden bg-background-bright"> | ||
| <div className="mx-3 flex items-center justify-between gap-2 border-b border-grid-dimmed"> | ||
| <Header2 className={cn("whitespace-nowrap")}> | ||
| {bulkAction.name || bulkAction.friendlyId} | ||
| </Header2> | ||
| <LinkButton | ||
| to={v3BulkActionsPath(organization, project, environment)} | ||
| variant="minimal/small" | ||
| TrailingIcon={ExitIcon} | ||
| shortcut={{ key: "esc" }} | ||
| shortcutPosition="before-trailing-icon" | ||
| className="pl-1" | ||
| /> | ||
| </div> | ||
| <div className="overflow-y-scroll scrollbar-thin scrollbar-track-transparent scrollbar-thumb-charcoal-600"> | ||
| <div className="space-y-3"> | ||
| <div className="p-3"> | ||
| <Property.Table> | ||
| <Property.Item> | ||
| <Property.Label>ID</Property.Label> | ||
| <Property.Value>{bulkAction.friendlyId}</Property.Value> | ||
| </Property.Item> | ||
| <Property.Item> | ||
| <Property.Label>Bulk action</Property.Label> | ||
| <Property.Value> | ||
| <BulkActionTypeCombo type={bulkAction.type} /> | ||
| </Property.Value> | ||
| </Property.Item> | ||
| <Property.Item> | ||
| <Property.Label>User</Property.Label> | ||
| <Property.Value> | ||
| {bulkAction.user ? ( | ||
| <div className="flex items-center gap-1"> | ||
| <UserAvatar | ||
| name={bulkAction.user.name} | ||
| avatarUrl={bulkAction.user.avatarUrl} | ||
| className="h-4 w-4" | ||
| /> | ||
| <Paragraph variant="extra-small">{bulkAction.user.name}</Paragraph> | ||
| </div> | ||
| ) : ( | ||
| "–" | ||
| )} | ||
| </Property.Value> | ||
| </Property.Item> | ||
| <Property.Item> | ||
| <Property.Label>Created</Property.Label> | ||
| <Property.Value> | ||
| <DateTime date={bulkAction.createdAt} /> | ||
| </Property.Value> | ||
| </Property.Item> | ||
| <Property.Item> | ||
| <Property.Label>Completed</Property.Label> | ||
| <Property.Value> | ||
| {bulkAction.completedAt ? <DateTime date={bulkAction.completedAt} /> : "–"} | ||
| </Property.Value> | ||
| </Property.Item> | ||
| </Property.Table> | ||
| </div> | ||
| </div> | ||
| </div> | ||
| <div className="flex items-center justify-between gap-2 border-t border-grid-dimmed px-2"> | ||
| <LinkButton | ||
| to={v3CreateBulkActionPath( | ||
| organization, | ||
| project, | ||
| environment, | ||
| { | ||
| bulkId: bulkAction.friendlyId, | ||
| }, | ||
| "filters", | ||
| "replay" | ||
| )} | ||
| variant="tertiary/medium" | ||
| LeadingIcon={ArrowPathIcon} | ||
| leadingIconClassName="text-indigo-500" | ||
| > | ||
| Replay runs | ||
| </LinkButton> | ||
|
|
||
| <LinkButton | ||
| variant="tertiary/medium" | ||
| to={v3RunsPath(organization, project, environment, { | ||
| bulkId: bulkAction.friendlyId, | ||
| })} | ||
| LeadingIcon={RunsIcon} | ||
| > | ||
| View runs | ||
| </LinkButton> | ||
| </div> | ||
| </div> | ||
| ); | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.