refactor(webhooks): make admin webhooks view-only, remove write actions#1754
refactor(webhooks): make admin webhooks view-only, remove write actions#1754Shreyag02 wants to merge 2 commits into
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (13)
💤 Files with no reviewable changes (7)
📝 WalkthroughSummary by CodeRabbit
WalkthroughChangesWebhook management simplification
Estimated code review effort: 4 (Complex) | ~45 minutes Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 2✅ Passed checks (2 passed)
✨ Finishing Touches⚔️ Resolve merge conflicts
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 0445ed10-2956-4c1c-b75a-211d48b2d3bf
📒 Files selected for processing (7)
pkg/server/config.gopkg/server/server.goweb/apps/admin/configs.dev.jsonweb/apps/admin/src/pages/webhooks/WebhooksPage.tsxweb/apps/admin/src/utils/constants.tsweb/sdk/admin/views/webhooks/webhooks/columns.tsxweb/sdk/admin/views/webhooks/webhooks/index.tsx
| const actionColumn: DataTableColumnDef<Webhook, unknown> = { | ||
| header: "Action", | ||
| accessorKey: "id", | ||
| classNames: { cell: styles.actionColumn, header: styles.actionColumn }, | ||
| cell: ({ getValue, row }) => { | ||
| const ActionCell = () => { | ||
| const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false); | ||
| const webhookId = getValue() as string; | ||
| const webhook = row.original; | ||
|
|
||
| return ( | ||
| <> | ||
| {/* @ts-ignore */} | ||
| <Menu style={{ padding: "0 !important" }}> | ||
| <Menu.Trigger | ||
| render={<DotsVerticalIcon style={{ cursor: "pointer" }} />} | ||
| /> | ||
| <Menu.Content> | ||
| <Menu.Group style={{ padding: 0 }}> | ||
| <Menu.Item style={{ padding: 0 }}> | ||
| <Flex | ||
| style={{ padding: "12px" }} | ||
| gap={3} | ||
| data-test-id="admin-webhook-update-btn" | ||
| onClick={() => openEditPage(webhookId)} | ||
| > | ||
| <UpdateIcon /> | ||
| Update | ||
| </Flex> | ||
| </Menu.Item> | ||
| <Menu.Item style={{ padding: 0 }}> | ||
| <Flex | ||
| className={styles.deleteMenuItem} | ||
| gap={3} | ||
| data-test-id="admin-webhook-delete-btn" | ||
| onClick={() => setIsDeleteDialogOpen(true)} | ||
| > | ||
| <TrashIcon /> | ||
| Delete | ||
| </Flex> | ||
| </Menu.Item> | ||
| </Menu.Group> | ||
| </Menu.Content> | ||
| </Menu> | ||
|
|
||
| <DeleteWebhookDialog | ||
| isOpen={isDeleteDialogOpen} | ||
| onOpenChange={setIsDeleteDialogOpen} | ||
| webhookId={webhookId} | ||
| webhookDescription={webhook.description} | ||
| deleteWebhookMutation={deleteWebhookMutation} | ||
| /> | ||
| </> | ||
| ); | ||
| }; | ||
|
|
||
| return <ActionCell />; | ||
| }, | ||
| }; |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
ActionCell defined inside cell causes state loss on re-renders.
ActionCell is declared as a function component inside the cell callback. Every table re-render (data refetch, sort, filter) creates a new function reference, so React unmounts and remounts the component — discarding isDeleteDialogOpen and the Menu's open state. The delete dialog and action menu will close unexpectedly mid-interaction.
Extract ActionCell outside getColumns so its identity is stable:
♻️ Proposed fix
+interface ActionCellProps {
+ getValue: () => unknown;
+ row: { original: Webhook };
+ openEditPage: (id: string) => void;
+ deleteWebhookMutation: ReturnType<typeof useMutation>;
+}
+
+function ActionCell({
+ getValue,
+ row,
+ openEditPage,
+ deleteWebhookMutation,
+}: ActionCellProps) {
+ const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false);
+ const webhookId = getValue() as string;
+ const webhook = row.original;
+
+ return (
+ <>
+ {/* `@ts-ignore` */}
+ <Menu style={{ padding: "0 !important" }}>
+ <Menu.Trigger
+ render={<DotsVerticalIcon style={{ cursor: "pointer" }} />}
+ />
+ <Menu.Content>
+ <Menu.Group style={{ padding: 0 }}>
+ <Menu.Item style={{ padding: 0 }}>
+ <Flex
+ style={{ padding: "12px" }}
+ gap={3}
+ data-test-id="admin-webhook-update-btn"
+ onClick={() => openEditPage(webhookId)}
+ >
+ <UpdateIcon />
+ Update
+ </Flex>
+ </Menu.Item>
+ <Menu.Item style={{ padding: 0 }}>
+ <Flex
+ className={styles.deleteMenuItem}
+ gap={3}
+ data-test-id="admin-webhook-delete-btn"
+ onClick={() => setIsDeleteDialogOpen(true)}
+ >
+ <TrashIcon />
+ Delete
+ </Flex>
+ </Menu.Item>
+ </Menu.Group>
+ </Menu.Content>
+ </Menu>
+
+ <DeleteWebhookDialog
+ isOpen={isDeleteDialogOpen}
+ onOpenChange={setIsDeleteDialogOpen}
+ webhookId={webhookId}
+ webhookDescription={webhook.description}
+ deleteWebhookMutation={deleteWebhookMutation}
+ />
+ </>
+ );
+}
+
export const getColumns: (
opt: getColumnsOptions,
) => DataTableColumnDef<Webhook, unknown>[] = ({ openEditPage, deleteWebhookMutation, enableActions }) => {
const actionColumn: DataTableColumnDef<Webhook, unknown> = {
header: "Action",
accessorKey: "id",
classNames: { cell: styles.actionColumn, header: styles.actionColumn },
- cell: ({ getValue, row }) => {
- const ActionCell = () => {
- const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false);
- const webhookId = getValue() as string;
- const webhook = row.original;
-
- return (
- <>
- {/* `@ts-ignore` */}
- <Menu style={{ padding: "0 !important" }}>
- <Menu.Trigger
- render={<DotsVerticalIcon style={{ cursor: "pointer" }} />}
- />
- <Menu.Content>
- <Menu.Group style={{ padding: 0 }}>
- <Menu.Item style={{ padding: 0 }}>
- <Flex
- style={{ padding: "12px" }}
- gap={3}
- data-test-id="admin-webhook-update-btn"
- onClick={() => openEditPage(webhookId)}
- >
- <UpdateIcon />
- Update
- </Flex>
- </Menu.Item>
- <Menu.Item style={{ padding: 0 }}>
- <Flex
- className={styles.deleteMenuItem}
- gap={3}
- data-test-id="admin-webhook-delete-btn"
- onClick={() => setIsDeleteDialogOpen(true)}
- >
- <TrashIcon />
- Delete
- </Flex>
- </Menu.Item>
- </Menu.Group>
- </Menu.Content>
- </Menu>
-
- <DeleteWebhookDialog
- isOpen={isDeleteDialogOpen}
- onOpenChange={setIsDeleteDialogOpen}
- webhookId={webhookId}
- webhookDescription={webhook.description}
- deleteWebhookMutation={deleteWebhookMutation}
- />
- </>
- );
- };
-
- return <ActionCell />;
- },
+ cell: ({ getValue, row }) => (
+ <ActionCell
+ getValue={getValue}
+ row={row}
+ openEditPage={openEditPage}
+ deleteWebhookMutation={deleteWebhookMutation}
+ />
+ ),
};
return [📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const actionColumn: DataTableColumnDef<Webhook, unknown> = { | |
| header: "Action", | |
| accessorKey: "id", | |
| classNames: { cell: styles.actionColumn, header: styles.actionColumn }, | |
| cell: ({ getValue, row }) => { | |
| const ActionCell = () => { | |
| const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false); | |
| const webhookId = getValue() as string; | |
| const webhook = row.original; | |
| return ( | |
| <> | |
| {/* @ts-ignore */} | |
| <Menu style={{ padding: "0 !important" }}> | |
| <Menu.Trigger | |
| render={<DotsVerticalIcon style={{ cursor: "pointer" }} />} | |
| /> | |
| <Menu.Content> | |
| <Menu.Group style={{ padding: 0 }}> | |
| <Menu.Item style={{ padding: 0 }}> | |
| <Flex | |
| style={{ padding: "12px" }} | |
| gap={3} | |
| data-test-id="admin-webhook-update-btn" | |
| onClick={() => openEditPage(webhookId)} | |
| > | |
| <UpdateIcon /> | |
| Update | |
| </Flex> | |
| </Menu.Item> | |
| <Menu.Item style={{ padding: 0 }}> | |
| <Flex | |
| className={styles.deleteMenuItem} | |
| gap={3} | |
| data-test-id="admin-webhook-delete-btn" | |
| onClick={() => setIsDeleteDialogOpen(true)} | |
| > | |
| <TrashIcon /> | |
| Delete | |
| </Flex> | |
| </Menu.Item> | |
| </Menu.Group> | |
| </Menu.Content> | |
| </Menu> | |
| <DeleteWebhookDialog | |
| isOpen={isDeleteDialogOpen} | |
| onOpenChange={setIsDeleteDialogOpen} | |
| webhookId={webhookId} | |
| webhookDescription={webhook.description} | |
| deleteWebhookMutation={deleteWebhookMutation} | |
| /> | |
| </> | |
| ); | |
| }; | |
| return <ActionCell />; | |
| }, | |
| }; | |
| interface ActionCellProps { | |
| getValue: () => unknown; | |
| row: { original: Webhook }; | |
| openEditPage: (id: string) => void; | |
| deleteWebhookMutation: ReturnType<typeof useMutation>; | |
| } | |
| function ActionCell({ | |
| getValue, | |
| row, | |
| openEditPage, | |
| deleteWebhookMutation, | |
| }: ActionCellProps) { | |
| const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false); | |
| const webhookId = getValue() as string; | |
| const webhook = row.original; | |
| return ( | |
| <> | |
| {/* `@ts-ignore` */} | |
| <Menu style={{ padding: "0 !important" }}> | |
| <Menu.Trigger | |
| render={<DotsVerticalIcon style={{ cursor: "pointer" }} />} | |
| /> | |
| <Menu.Content> | |
| <Menu.Group style={{ padding: 0 }}> | |
| <Menu.Item style={{ padding: 0 }}> | |
| <Flex | |
| style={{ padding: "12px" }} | |
| gap={3} | |
| data-test-id="admin-webhook-update-btn" | |
| onClick={() => openEditPage(webhookId)} | |
| > | |
| <UpdateIcon /> | |
| Update | |
| </Flex> | |
| </Menu.Item> | |
| <Menu.Item style={{ padding: 0 }}> | |
| <Flex | |
| className={styles.deleteMenuItem} | |
| gap={3} | |
| data-test-id="admin-webhook-delete-btn" | |
| onClick={() => setIsDeleteDialogOpen(true)} | |
| > | |
| <TrashIcon /> | |
| Delete | |
| </Flex> | |
| </Menu.Item> | |
| </Menu.Group> | |
| </Menu.Content> | |
| </Menu> | |
| <DeleteWebhookDialog | |
| isOpen={isDeleteDialogOpen} | |
| onOpenChange={setIsDeleteDialogOpen} | |
| webhookId={webhookId} | |
| webhookDescription={webhook.description} | |
| deleteWebhookMutation={deleteWebhookMutation} | |
| /> | |
| </> | |
| ); | |
| } | |
| export const getColumns: ( | |
| opt: getColumnsOptions, | |
| ) => DataTableColumnDef<Webhook, unknown>[] = ({ | |
| openEditPage, | |
| deleteWebhookMutation, | |
| enableActions, | |
| }) => { | |
| const actionColumn: DataTableColumnDef<Webhook, unknown> = { | |
| header: "Action", | |
| accessorKey: "id", | |
| classNames: { cell: styles.actionColumn, header: styles.actionColumn }, | |
| cell: ({ getValue, row }) => ( | |
| <ActionCell | |
| getValue={getValue} | |
| row={row} | |
| openEditPage={openEditPage} | |
| deleteWebhookMutation={deleteWebhookMutation} | |
| /> | |
| ), | |
| }; |
Coverage Report for CI Build 29269745070Coverage decreased (-0.002%) to 44.874%Details
Uncovered Changes
Coverage RegressionsNo coverage regressions found. Coverage Stats
💛 - Coveralls |
Summary
Makes the admin console Webhooks page permanently view-only. All write actions (create, update, delete) are removed from the admin UI entirely — no config flag, no gating. The page now shows a read-only list of webhooks.
Changes
create/,update/, anddelete/components and the delete mutation from the query hook.WebhooksViewto a read-only table; its only prop is nowicon.webhooks/createandwebhooks/:webhookIdroutes.enable_actions/enable_delete) from the admin app config types, dev config, and the Go server (WebhooksConfig+/configsresponse).Technical Details
web/sdk/admin/views/webhooks/webhooks/):WebhooksViewrenders only the Description / State / URL / Created-at columns.getColumns()takes no arguments;useWebhookQueriesexposes only the list query.WebhooksPage.tsx,routes.tsx): renders<WebhooksView />with just an icon; create/detail routes removed.constants.ts,configs.dev.json):WebhooksConfigand thewebhooksblock removed.pkg/server/config.go,server.go):WebhooksConfigand its/configsfield removed. The/configsresponse no longer emits awebhooksobject.createWebhook/updateWebhook/deleteWebhook) are unchanged — only the admin UI surface was removed.Test Plan
tsc+ Vite production build, SDKtsupbuild, ESLint, andgo build/go vet/go test ./pkg/server/...all clean.SQL Safety (if your PR touches
*_repository.goorgoqu.*)N/A