Skip to content

Commit c2909e7

Browse files
authored
Add table row action to copy silo ID from system utilization page (#2414)
1 parent 12fc862 commit c2909e7

File tree

4 files changed

+79
-49
lines changed

4 files changed

+79
-49
lines changed

app/pages/system/UtilizationPage.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import { QueryParamTabs } from '~/components/QueryParamTabs'
2424
import { useIntervalPicker } from '~/components/RefetchIntervalPicker'
2525
import { SystemMetric } from '~/components/SystemMetric'
2626
import { LinkCell } from '~/table/cells/LinkCell'
27+
import { RowActions } from '~/table/columns/action-col'
2728
import { Listbox } from '~/ui/lib/Listbox'
2829
import { PageHeader, PageTitle } from '~/ui/lib/PageHeader'
2930
import { ResourceMeter } from '~/ui/lib/ResourceMeter'
@@ -168,6 +169,7 @@ function UsageTab() {
168169
<Table.HeadCell colSpan={3} data-test-ignore>
169170
Available
170171
</Table.HeadCell>
172+
<Table.HeadCell data-test-ignore></Table.HeadCell>
171173
</Table.HeaderRow>
172174
<Table.HeaderRow>
173175
<Table.HeadCell data-test-ignore></Table.HeadCell>
@@ -177,6 +179,7 @@ function UsageTab() {
177179
<Table.HeadCell>CPU</Table.HeadCell>
178180
<Table.HeadCell>Memory</Table.HeadCell>
179181
<Table.HeadCell>Storage</Table.HeadCell>
182+
<Table.HeadCell data-test-ignore></Table.HeadCell>
180183
</Table.HeaderRow>
181184
</Table.Header>
182185
<Table.Body>
@@ -225,6 +228,9 @@ function UsageTab() {
225228
unit="TiB"
226229
/>
227230
</Table.Cell>
231+
<Table.Cell className="action-col w-10 children:p-0" height="large">
232+
<RowActions id={silo.siloId} copyIdLabel="Copy silo ID" />
233+
</Table.Cell>
228234
</Table.Row>
229235
))}
230236
</Table.Body>

app/table/columns/action-col.tsx

Lines changed: 59 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -50,54 +50,66 @@ export const getActionsCol = <TData extends Record<string, unknown>>(
5050
// TODO: control flow here has always confused me, would like to straighten it out
5151
const actions = makeActions(row.original)
5252
const id = typeof row.original.id === 'string' ? row.original.id : null
53-
return (
54-
<DropdownMenu.Root>
55-
{/* TODO: This name should not suck; future us, make it so! */}
56-
{/* stopPropagation prevents clicks from toggling row select in a single select table */}
57-
<DropdownMenu.Trigger
58-
className="flex h-full w-10 items-center justify-center"
59-
aria-label="Row actions"
60-
onClick={(e) => e.stopPropagation()}
61-
>
62-
<More12Icon className="text-tertiary" />
63-
</DropdownMenu.Trigger>
64-
{/* portal fixes mysterious z-index issue where menu is behind button */}
65-
<DropdownMenu.Portal>
66-
<DropdownMenu.Content align="end" className="-mt-3 mr-2">
67-
{id && (
53+
return <RowActions id={id} actions={actions} />
54+
},
55+
}
56+
}
57+
58+
type RowActionsProps = {
59+
/** If `id` is provided, a `Copy ID` menu item will be automatically included. */
60+
id?: string | null
61+
/** Use `copyIdLabel` to override the default label (`Copy ID`). */
62+
copyIdLabel?: string
63+
actions?: MenuAction[]
64+
}
65+
66+
export const RowActions = ({ id, copyIdLabel = 'Copy ID', actions }: RowActionsProps) => {
67+
return (
68+
<DropdownMenu.Root>
69+
{/* TODO: This name should not suck; future us, make it so! */}
70+
{/* stopPropagation prevents clicks from toggling row select in a single select table */}
71+
<DropdownMenu.Trigger
72+
className="flex h-full w-10 items-center justify-center"
73+
aria-label="Row actions"
74+
onClick={(e) => e.stopPropagation()}
75+
>
76+
<More12Icon className="text-tertiary" />
77+
</DropdownMenu.Trigger>
78+
{/* portal fixes mysterious z-index issue where menu is behind button */}
79+
<DropdownMenu.Portal>
80+
<DropdownMenu.Content align="end" className="-mt-3 mr-2">
81+
{id && (
82+
<DropdownMenu.Item
83+
onSelect={() => {
84+
window.navigator.clipboard.writeText(id)
85+
}}
86+
>
87+
{copyIdLabel}
88+
</DropdownMenu.Item>
89+
)}
90+
{actions?.map((action) => {
91+
// TODO: Tooltip on disabled button broke, probably due to portal
92+
return (
93+
<Wrap
94+
when={!!action.disabled}
95+
with={<Tooltip content={action.disabled} />}
96+
key={kebabCase(`action-${action.label}`)}
97+
>
6898
<DropdownMenu.Item
69-
onSelect={() => {
70-
window.navigator.clipboard.writeText(id)
71-
}}
99+
className={cn(action.className, {
100+
destructive:
101+
action.label.toLowerCase() === 'delete' && !action.disabled,
102+
})}
103+
onSelect={action.onActivate}
104+
disabled={!!action.disabled}
72105
>
73-
Copy ID
106+
{action.label}
74107
</DropdownMenu.Item>
75-
)}
76-
{actions.map((action) => {
77-
// TODO: Tooltip on disabled button broke, probably due to portal
78-
return (
79-
<Wrap
80-
when={!!action.disabled}
81-
with={<Tooltip content={action.disabled} />}
82-
key={kebabCase(`action-${action.label}`)}
83-
>
84-
<DropdownMenu.Item
85-
className={cn(action.className, {
86-
destructive:
87-
action.label.toLowerCase() === 'delete' && !action.disabled,
88-
})}
89-
onSelect={action.onActivate}
90-
disabled={!!action.disabled}
91-
>
92-
{action.label}
93-
</DropdownMenu.Item>
94-
</Wrap>
95-
)
96-
})}
97-
</DropdownMenu.Content>
98-
</DropdownMenu.Portal>
99-
</DropdownMenu.Root>
100-
)
101-
},
102-
}
108+
</Wrap>
109+
)
110+
})}
111+
</DropdownMenu.Content>
112+
</DropdownMenu.Portal>
113+
</DropdownMenu.Root>
114+
)
103115
}

test/e2e/images.e2e.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,8 +77,8 @@ test('can promote an image from project', async ({ page }) => {
7777
test('can copy an image ID to clipboard', async ({ page, browserName }) => {
7878
// eslint-disable-next-line playwright/no-skipped-test
7979
test.skip(
80-
browserName === 'firefox' || browserName === 'webkit',
81-
'navigator.clipboard.readText() not supported in FF. Works locally in Safari but not in CI.'
80+
browserName === 'webkit',
81+
'navigator.clipboard.readText() works locally in Safari but not in CI.'
8282
)
8383

8484
await page.goto('/images')

test/e2e/utilization.e2e.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
*/
88
import {
99
clickRowAction,
10+
clipboardText,
1011
closeToast,
1112
expect,
1213
expectRowVisible,
@@ -43,6 +44,17 @@ test.describe('System utilization', () => {
4344
await page.getByRole('tab', { name: 'Metrics' }).click()
4445
})
4546

47+
test('can copy silo ID', async ({ page, browserName }) => {
48+
// eslint-disable-next-line playwright/no-skipped-test
49+
test.skip(
50+
browserName === 'webkit',
51+
'navigator.clipboard.readText() works locally in Safari but not in CI.'
52+
)
53+
await page.goto('/system/utilization')
54+
await clickRowAction(page, 'maze-war', 'Copy silo ID')
55+
expect(await clipboardText(page)).toEqual('6d3a9c06-475e-4f75-b272-c0d0e3f980fa')
56+
})
57+
4658
test('does not appear for dev user', async ({ browser }) => {
4759
const page = await getPageAsUser(browser, 'Hans Jonas')
4860
await page.goto('/system/utilization')

0 commit comments

Comments
 (0)