Skip to content

Commit a275827

Browse files
committed
graphing/designer: scroll to added rows
Change-Id: If8e0655513a4e689e8b64437985b0dec10075f79
1 parent 443035d commit a275827

3 files changed

Lines changed: 69 additions & 6 deletions

File tree

packages/cmk-frontend-vue/src/graphing/designer/components/MetricsTable.vue

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import CmkIcon from 'cmk-ui-library/components/CmkIcon'
1212
import CmkScrollContainer from 'cmk-ui-library/components/CmkScrollContainer.vue'
1313
import CmkInput from 'cmk-ui-library/components/user-input/CmkInput.vue'
1414
import usei18n from 'cmk-ui-library/lib/i18n'
15-
import { computed, ref } from 'vue'
15+
import { computed, nextTick, ref, useTemplateRef } from 'vue'
1616
1717
import EditableTable from '@/monitoring/shared/components/EditableTable.vue'
1818
import type { CellAction } from '@/monitoring/shared/components/cell/ActionsCell.vue'
@@ -78,6 +78,13 @@ const titleMacroHelp = renderTitleMacroHelp(titleMacros)
7878
const rowSelection = ref<RowSelectionState>({})
7979
const expandedRows = ref<Record<string, boolean>>({})
8080
81+
const table = useTemplateRef<{ scrollToRow: (key: ItemId) => void }>('table')
82+
83+
async function scrollToRow(id: ItemId): Promise<void> {
84+
await nextTick()
85+
table.value?.scrollToRow(id)
86+
}
87+
8188
const columns: ColumnDef<DesignerItem>[] = [
8289
{ id: 'drag', header: '', meta: { justify: 'center' } },
8390
{ id: 'visibility', header: '', meta: { justify: 'center' } },
@@ -140,6 +147,7 @@ function onAddSource(value: string): void {
140147
}
141148
})
142149
expandedRows.value = { ...expandedRows.value, [id]: true }
150+
void scrollToRow(id)
143151
}
144152
145153
const rowActions: CellAction[] = [
@@ -171,7 +179,10 @@ const rowDelete = useDeleteWithDependents(store, () => {
171179
172180
function onRowAction(row: DesignerItem, action: CellAction): void {
173181
if (action.id === 'clone') {
174-
store.clone([row.id])
182+
const [created] = store.clone([row.id])
183+
if (created !== undefined) {
184+
void scrollToRow(created)
185+
}
175186
} else if (action.id === 'delete') {
176187
rowDelete.request([row.id])
177188
} else if (action.id === 'add-rule' && row.type === 'metric_backend' && isComplete(row)) {
@@ -180,8 +191,11 @@ function onRowAction(row: DesignerItem, action: CellAction): void {
180191
}
181192
182193
function onBulkClone(): void {
183-
store.clone(selectedIds.value)
194+
const [firstCreated] = store.clone(selectedIds.value)
184195
rowSelection.value = {}
196+
if (firstCreated !== undefined) {
197+
void scrollToRow(firstCreated)
198+
}
185199
}
186200
187201
function onLineStyleChange(row: DesignerItem, value: string | null): void {
@@ -224,6 +238,7 @@ function onTitleChange(row: DesignerItem, title: string | undefined): void {
224238
:style="{ overflow: 'var(--graphing-designer-body-table-overflow, auto)' }"
225239
>
226240
<EditableTable
241+
ref="table"
227242
v-model:row-selection="rowSelection"
228243
:rows="[...store.items.value]"
229244
:columns="columns"

packages/cmk-frontend-vue/src/monitoring/shared/components/EditableTable.vue

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import {
1212
useVueTable
1313
} from '@tanstack/vue-table'
1414
import useDragging from 'cmk-ui-library/lib/useDragging'
15-
import { computed, provide, ref } from 'vue'
15+
import { type ComponentPublicInstance, computed, provide, ref } from 'vue'
1616
1717
import {
1818
COLUMN_LAYOUT_KEY,
@@ -123,6 +123,23 @@ provide(ROW_DRAG_KEY, {
123123
function isRowExpanded(row: T, index: number): boolean {
124124
return props.expandedRows?.[String(props.getRowKey(row, index))] === true
125125
}
126+
127+
const rowGroups = new Map<string, HTMLElement>()
128+
129+
function registerRowGroup(key: string, element: Element | ComponentPublicInstance | null): void {
130+
if (element instanceof HTMLElement) {
131+
rowGroups.set(key, element)
132+
} else {
133+
rowGroups.delete(key)
134+
}
135+
}
136+
137+
defineExpose({
138+
/** Scroll the row with this key into view, together with its expansion. */
139+
scrollToRow: (key: string | number): void => {
140+
rowGroups.get(String(key))?.scrollIntoView({ block: 'center', behavior: 'smooth' })
141+
}
142+
})
126143
</script>
127144

128145
<template>
@@ -146,6 +163,7 @@ function isRowExpanded(row: T, index: number): boolean {
146163
<tbody
147164
v-for="(row, index) in rows"
148165
:key="getRowKey(row, index)"
166+
:ref="(element) => registerRowGroup(String(getRowKey(row, index)), element)"
149167
class="monitoring-editable-table__row-group"
150168
:class="{ 'monitoring-editable-table__row-group--dragging': draggedRowIndex === index }"
151169
>

packages/cmk-frontend-vue/tests/graphing/designer/components/MetricsTable.test.ts

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@
33
* This file is part of Checkmk (https://checkmk.com). It is subject to the terms and
44
* conditions defined in the file COPYING, which is part of this source code package.
55
*/
6-
import { fireEvent, render, screen, waitFor } from '@testing-library/vue'
6+
import { fireEvent, render, screen, waitFor, within } from '@testing-library/vue'
77
import type { TitleMacroGroup } from 'cmk-shared-typing/typescript/custom_graph_designer'
8-
import { vi } from 'vitest'
8+
import { type MockInstance, vi } from 'vitest'
99

1010
import MetricsTable from '@/graphing/designer/components/MetricsTable.vue'
1111
import { useGraphItems } from '@/graphing/designer/composables/useGraphItems'
@@ -25,6 +25,10 @@ vi.mock('@/graphing/designer/components/MetricBackendRuleSlideIn.vue', () => ({
2525
}
2626
}))
2727

28+
afterEach(() => {
29+
vi.restoreAllMocks()
30+
})
31+
2832
const ADD_RULE_LABEL = 'Add rule: Metric backend (Custom query)'
2933

3034
const PALETTE: readonly string[] = ['#28a2f3', '#ff8400', '#ec48b6', '#ffd703']
@@ -33,6 +37,15 @@ const TITLE_MACROS: TitleMacroGroup[] = [
3337
{ source_type: 'rrd_metric', macros: ['$DEFAULT_TITLE$', '$METRIC_NAME$'] }
3438
]
3539

40+
/** Waits for the row group holding `id` to have been scrolled into view. */
41+
async function expectScrolledToRow(scrollIntoView: MockInstance, id: string): Promise<void> {
42+
await waitFor(() => {
43+
const scrolled = scrollIntoView.mock.contexts.at(-1)
44+
expect(scrolled).toBeInstanceOf(HTMLElement)
45+
expect(within(scrolled as HTMLElement).getByText(id)).toBeInTheDocument()
46+
})
47+
}
48+
3649
function renderTable(
3750
seed: DesignerItem[] = [],
3851
metricBackendAvailable = true,
@@ -63,6 +76,23 @@ test('adding a source appends an auto-expanded draft row', async () => {
6376
expect(await screen.findByText('Single metric')).toBeInTheDocument()
6477
})
6578

79+
test('the added row is scrolled into view', async () => {
80+
const scrollIntoView = vi.spyOn(window.HTMLElement.prototype, 'scrollIntoView')
81+
renderTable([rrdMetricItem('A')])
82+
await fireEvent.click(screen.getByRole('combobox', { name: 'Add source' }))
83+
await fireEvent.click(await screen.findByRole('option', { name: 'Checkmk RRD' }))
84+
85+
await expectScrolledToRow(scrollIntoView, 'B')
86+
})
87+
88+
test('a cloned row is scrolled into view', async () => {
89+
const scrollIntoView = vi.spyOn(window.HTMLElement.prototype, 'scrollIntoView')
90+
renderTable([rrdMetricItem('A')])
91+
await fireEvent.click(screen.getByRole('button', { name: 'Clone' }))
92+
93+
await expectScrolledToRow(scrollIntoView, 'B')
94+
})
95+
6696
test('adding a constant line opens the constant form', async () => {
6797
const { store } = renderTable()
6898
await fireEvent.click(screen.getByRole('combobox', { name: 'Add source' }))

0 commit comments

Comments
 (0)