diff --git a/app/pages/system/UtilizationPage.tsx b/app/pages/system/UtilizationPage.tsx index 6faaa82a1f..8a1e4a5675 100644 --- a/app/pages/system/UtilizationPage.tsx +++ b/app/pages/system/UtilizationPage.tsx @@ -24,6 +24,7 @@ import { QueryParamTabs } from '~/components/QueryParamTabs' import { useIntervalPicker } from '~/components/RefetchIntervalPicker' import { SystemMetric } from '~/components/SystemMetric' import { LinkCell } from '~/table/cells/LinkCell' +import { RowActions } from '~/table/columns/action-col' import { Listbox } from '~/ui/lib/Listbox' import { PageHeader, PageTitle } from '~/ui/lib/PageHeader' import { ResourceMeter } from '~/ui/lib/ResourceMeter' @@ -168,6 +169,7 @@ function UsageTab() { Available + @@ -177,6 +179,7 @@ function UsageTab() { CPU Memory Storage + @@ -225,6 +228,9 @@ function UsageTab() { unit="TiB" /> + + + ))} diff --git a/app/table/columns/action-col.tsx b/app/table/columns/action-col.tsx index a130f15fdf..58a348cf04 100644 --- a/app/table/columns/action-col.tsx +++ b/app/table/columns/action-col.tsx @@ -50,54 +50,66 @@ export const getActionsCol = >( // TODO: control flow here has always confused me, would like to straighten it out const actions = makeActions(row.original) const id = typeof row.original.id === 'string' ? row.original.id : null - return ( - - {/* TODO: This name should not suck; future us, make it so! */} - {/* stopPropagation prevents clicks from toggling row select in a single select table */} - e.stopPropagation()} - > - - - {/* portal fixes mysterious z-index issue where menu is behind button */} - - - {id && ( + return + }, + } +} + +type RowActionsProps = { + /** If `id` is provided, a `Copy ID` menu item will be automatically included. */ + id?: string | null + /** Use `copyIdLabel` to override the default label (`Copy ID`). */ + copyIdLabel?: string + actions?: MenuAction[] +} + +export const RowActions = ({ id, copyIdLabel = 'Copy ID', actions }: RowActionsProps) => { + return ( + + {/* TODO: This name should not suck; future us, make it so! */} + {/* stopPropagation prevents clicks from toggling row select in a single select table */} + e.stopPropagation()} + > + + + {/* portal fixes mysterious z-index issue where menu is behind button */} + + + {id && ( + { + window.navigator.clipboard.writeText(id) + }} + > + {copyIdLabel} + + )} + {actions?.map((action) => { + // TODO: Tooltip on disabled button broke, probably due to portal + return ( + } + key={kebabCase(`action-${action.label}`)} + > { - window.navigator.clipboard.writeText(id) - }} + className={cn(action.className, { + destructive: + action.label.toLowerCase() === 'delete' && !action.disabled, + })} + onSelect={action.onActivate} + disabled={!!action.disabled} > - Copy ID + {action.label} - )} - {actions.map((action) => { - // TODO: Tooltip on disabled button broke, probably due to portal - return ( - } - key={kebabCase(`action-${action.label}`)} - > - - {action.label} - - - ) - })} - - - - ) - }, - } + + ) + })} + + + + ) } diff --git a/test/e2e/images.e2e.ts b/test/e2e/images.e2e.ts index 0c34aae0b8..0c15a42811 100644 --- a/test/e2e/images.e2e.ts +++ b/test/e2e/images.e2e.ts @@ -77,8 +77,8 @@ test('can promote an image from project', async ({ page }) => { test('can copy an image ID to clipboard', async ({ page, browserName }) => { // eslint-disable-next-line playwright/no-skipped-test test.skip( - browserName === 'firefox' || browserName === 'webkit', - 'navigator.clipboard.readText() not supported in FF. Works locally in Safari but not in CI.' + browserName === 'webkit', + 'navigator.clipboard.readText() works locally in Safari but not in CI.' ) await page.goto('/images') diff --git a/test/e2e/utilization.e2e.ts b/test/e2e/utilization.e2e.ts index 187cea6a12..f37b1a17ed 100644 --- a/test/e2e/utilization.e2e.ts +++ b/test/e2e/utilization.e2e.ts @@ -7,6 +7,7 @@ */ import { clickRowAction, + clipboardText, closeToast, expect, expectRowVisible, @@ -43,6 +44,17 @@ test.describe('System utilization', () => { await page.getByRole('tab', { name: 'Metrics' }).click() }) + test('can copy silo ID', async ({ page, browserName }) => { + // eslint-disable-next-line playwright/no-skipped-test + test.skip( + browserName === 'webkit', + 'navigator.clipboard.readText() works locally in Safari but not in CI.' + ) + await page.goto('/system/utilization') + await clickRowAction(page, 'maze-war', 'Copy silo ID') + expect(await clipboardText(page)).toEqual('6d3a9c06-475e-4f75-b272-c0d0e3f980fa') + }) + test('does not appear for dev user', async ({ browser }) => { const page = await getPageAsUser(browser, 'Hans Jonas') await page.goto('/system/utilization')