diff --git a/devenv/dev-dashboards/panel-datagrid/datagrid_metric_values.json b/devenv/dev-dashboards/panel-datagrid/datagrid_metric_values.json
index 9ee8da0fd5d..6fb2f433102 100644
--- a/devenv/dev-dashboards/panel-datagrid/datagrid_metric_values.json
+++ b/devenv/dev-dashboards/panel-datagrid/datagrid_metric_values.json
@@ -75,5 +75,6 @@
"timezone": "",
"title": "Datagrid example",
"version": 0,
+ "uid": "c01bf42b-b783-4447-a304-8554cee1843b",
"weekStart": ""
}
\ No newline at end of file
diff --git a/e2e/datagrid-suite/datagrid-data-change.spec.ts b/e2e/datagrid-suite/datagrid-data-change.spec.ts
index df61fca4751..f4409e32220 100644
--- a/e2e/datagrid-suite/datagrid-data-change.spec.ts
+++ b/e2e/datagrid-suite/datagrid-data-change.spec.ts
@@ -1,6 +1,6 @@
import { e2e } from '@grafana/e2e';
-const DASHBOARD_ID = 'a70ecb44-6c31-412d-ae74-d6306303ce37';
+const DASHBOARD_ID = 'c01bf42b-b783-4447-a304-8554cee1843b';
const DATAGRID_SELECT_SERIES = 'Datagrid Select series';
e2e.scenario({
@@ -27,8 +27,10 @@ e2e.scenario({
// Edit datagrid which triggers a snapshot query
cy.get('.dvn-scroller').click(200, 100);
cy.get('[data-testid="glide-cell-2-1"]').should('have.attr', 'aria-selected', 'true');
- cy.get('body').type('123455{enter}', { delay: 1000 });
+ cy.get('body').type('12{enter}', { delay: 500 });
- cy.get('[data-testid="query-editor-row"]').contains('Spreadsheet or snapshot');
+ cy.get('[aria-label="Confirm Modal Danger Button"]').click();
+
+ cy.get('[data-testid="query-editor-row"]').contains('Snapshot');
},
});
diff --git a/e2e/datagrid-suite/datagrid-editing-features.spec.ts b/e2e/datagrid-suite/datagrid-editing-features.spec.ts
index f1972fd6457..e71d1f31967 100644
--- a/e2e/datagrid-suite/datagrid-editing-features.spec.ts
+++ b/e2e/datagrid-suite/datagrid-editing-features.spec.ts
@@ -1,6 +1,6 @@
import { e2e } from '@grafana/e2e';
-const DASHBOARD_ID = 'a70ecb44-6c31-412d-ae74-d6306303ce37';
+const DASHBOARD_ID = 'c01bf42b-b783-4447-a304-8554cee1843b';
const DATAGRID_CANVAS = 'data-grid-canvas';
e2e.scenario({
@@ -15,7 +15,9 @@ e2e.scenario({
// Edit datagrid which triggers a snapshot query
cy.get('.dvn-scroller').click(200, 100);
cy.get('[data-testid="glide-cell-2-1"]').should('have.attr', 'aria-selected', 'true');
- cy.get('body').type('1{enter}');
+ cy.get('body').type('123{enter}', { delay: 500 });
+
+ cy.get('[aria-label="Confirm Modal Danger Button"]').click();
// Delete a cell
cy.get('.dvn-scroller').click(200, 200);
@@ -55,6 +57,7 @@ e2e.scenario({
cy.get('.dvn-scroller').click(20, 190, { waitForAnimations: true });
cy.get('.dvn-scroller').click(20, 90, { shiftKey: true, waitForAnimations: true }); // with shift to select all rows between clicks
cy.get('body').type('{del}');
+ cy.get('[aria-label="Confirm Modal Danger Button"]').click();
cy.get('[data-testid="glide-cell-1-4"]').should('have.text', '');
cy.get('[data-testid="glide-cell-1-3"]').should('have.text', '');
cy.get('[data-testid="glide-cell-1-2"]').should('have.text', '');
@@ -68,6 +71,9 @@ e2e.scenario({
cy.get('.dvn-scroller').click(20, 190, { waitForAnimations: true });
cy.get('.dvn-scroller').click(20, 90, { commandKey: true, waitForAnimations: true }); // with cmd to select only clicked rows
cy.get('body').type('{del}');
+
+ cy.get('[aria-label="Confirm Modal Danger Button"]').click();
+
cy.get('[data-testid="glide-cell-1-1"]').should('have.text', '');
cy.get('[data-testid="glide-cell-2-1"]').should('have.text', 0);
cy.get('[data-testid="glide-cell-2-4"]').should('have.text', 0);
@@ -83,6 +89,7 @@ e2e.scenario({
// Delete column through header dropdown menu
cy.get('.dvn-scroller').click(250, 15); // click header dropdown
cy.get('body').click(450, 420); // click delete column
+ cy.get('[aria-label="Confirm Modal Danger Button"]').click();
cy.get(`[data-testid="${DATAGRID_CANVAS}"] th`).should('have.length', 1);
// Delete row through context menu
@@ -101,6 +108,7 @@ e2e.scenario({
cy.get('.dvn-scroller').click(20, 90, { commandKey: true, waitForAnimations: true }); // with shift to select all rows between clicks
cy.get('.dvn-scroller').rightclick(40, 90);
cy.get('[aria-label="Context menu"]').click(10, 10);
+ cy.get('[aria-label="Confirm Modal Danger Button"]').click();
cy.get(`[data-testid="${DATAGRID_CANVAS}"] tbody tr`).should('have.length', 5); // there are 5 data rows + 1 for the add new row btns
// Delete column through context menu
@@ -113,6 +121,7 @@ e2e.scenario({
// Add a new column
cy.get('body').click(350, 200).type('New Column{enter}');
+ cy.get('[aria-label="Confirm Modal Danger Button"]').click();
cy.get('body')
.click(350, 230)
.type('Value 1{enter}')
diff --git a/public/app/plugins/datasource/grafana/utils.ts b/public/app/plugins/datasource/grafana/utils.ts
index ec9b6ff321c..1e7972f5888 100644
--- a/public/app/plugins/datasource/grafana/utils.ts
+++ b/public/app/plugins/datasource/grafana/utils.ts
@@ -22,7 +22,7 @@ export function onUpdatePanelSnapshotData(panel: PanelModel, frames: DataFrame[]
appEvents.publish(
new ShowConfirmModalEvent({
title: 'Change to panel embedded data',
- text: 'If you want to change the data shown in this panel Grafana will need to remove the panels current query and replace it with a snapshot of the current data. This enabled you to edit the data',
+ text: 'If you want to change the data shown in this panel Grafana will need to remove the panels current query and replace it with a snapshot of the current data. This enables you to edit the data.',
yesText: 'Continue',
icon: 'pen',
onConfirm: () => {
diff --git a/public/app/plugins/panel/datagrid/DataGridPanel.test.tsx b/public/app/plugins/panel/datagrid/DataGridPanel.test.tsx
index 49bc1ffae58..d2e13269bf7 100644
--- a/public/app/plugins/panel/datagrid/DataGridPanel.test.tsx
+++ b/public/app/plugins/panel/datagrid/DataGridPanel.test.tsx
@@ -1,14 +1,14 @@
import { render, screen, fireEvent, act, waitFor } from '@testing-library/react';
import * as React from 'react';
-import { ArrayVector, DataFrame, dateTime, EventBus, FieldType, LoadingState, MutableDataFrame } from '@grafana/data';
+import { ArrayVector, DataFrame, dateTime, EventBus, Field, FieldType, LoadingState } from '@grafana/data';
import { DataGridPanel, DataGridProps } from './DataGridPanel';
import * as utils from './utils';
jest.mock('./featureFlagUtils', () => {
return {
- isDatagridEditEnabled: jest.fn().mockReturnValue(true),
+ isDatagridEnabled: jest.fn().mockReturnValue(true),
};
});
@@ -18,7 +18,7 @@ jest.mock('./utils', () => {
...originalModule,
deleteRows: jest.fn(),
clearCellsFromRangeSelection: jest.fn(),
- publishSnapshot: jest.fn(),
+ updateSnapshot: jest.fn(),
};
});
@@ -192,7 +192,7 @@ describe('DataGrid', () => {
});
it('editing a cell triggers publishing the snapshot', async () => {
- const spy = jest.spyOn(utils, 'publishSnapshot');
+ const spy = jest.spyOn(utils, 'updateSnapshot');
jest.useFakeTimers();
render(, {
wrapper: Context,
@@ -215,7 +215,7 @@ describe('DataGrid', () => {
const expectedField = {
...props.data.series[0].fields[0],
};
- expectedField.values = new ArrayVector([1, 9, 3, 4]);
+ expectedField.values = [1, 9, 3, 4];
await waitFor(() => {
const overlay = screen.getByDisplayValue('9');
@@ -234,7 +234,7 @@ describe('DataGrid', () => {
expect.objectContaining({
fields: expect.arrayContaining([expectedField]),
}),
- 1
+ undefined
);
});
});
@@ -272,7 +272,7 @@ describe('DataGrid', () => {
});
});
it('should add a new column', async () => {
- const spy = jest.spyOn(utils, 'publishSnapshot');
+ const spy = jest.spyOn(utils, 'updateSnapshot');
jest.useFakeTimers();
render(, {
wrapper: Context,
@@ -301,11 +301,12 @@ describe('DataGrid', () => {
}),
]),
}),
- 1
+ undefined
);
});
+
it('should not add a new column if input is empty', async () => {
- const spy = jest.spyOn(utils, 'publishSnapshot');
+ const spy = jest.spyOn(utils, 'updateSnapshot');
jest.useFakeTimers();
render(, {
wrapper: Context,
@@ -322,8 +323,9 @@ describe('DataGrid', () => {
expect(spy).not.toBeCalled();
});
+
it('should add a new row', async () => {
- const spy = jest.spyOn(utils, 'publishSnapshot');
+ const spy = jest.spyOn(utils, 'updateSnapshot');
jest.useFakeTimers();
render(, {
wrapper: Context,
@@ -368,9 +370,9 @@ describe('DataGrid', () => {
});
it('should clear cell when cell is selected and delete button clicked', async () => {
+ const spy = jest.spyOn(utils, 'updateSnapshot');
const spyClearingCells = jest.spyOn(utils, 'clearCellsFromRangeSelection');
const spyDeleteRows = jest.spyOn(utils, 'deleteRows');
- const spy = jest.spyOn(utils, 'publishSnapshot');
jest.useFakeTimers();
render(, {
@@ -398,9 +400,9 @@ describe('DataGrid', () => {
});
it('should clear row when row is selected delete button clicked', async () => {
+ const spy = jest.spyOn(utils, 'updateSnapshot');
const spyClearingCells = jest.spyOn(utils, 'clearCellsFromRangeSelection');
const spyDeleteRows = jest.spyOn(utils, 'deleteRows');
- const spy = jest.spyOn(utils, 'publishSnapshot');
jest.useFakeTimers();
render(, {
@@ -424,8 +426,7 @@ describe('DataGrid', () => {
});
it('should move column when column dragged and dropped', async () => {
- const spy = jest.spyOn(utils, 'publishSnapshot');
-
+ const spy = jest.spyOn(utils, 'updateSnapshot');
jest.useFakeTimers();
render(, {
wrapper: Context,
@@ -446,17 +447,18 @@ describe('DataGrid', () => {
fireEvent.mouseUp(canvas);
- const df = new MutableDataFrame(props.data.series[0]);
+ const df = {
+ ...props.data.series[0],
+ };
df.fields = [df.fields[1], df.fields[0], df.fields[2]];
- const received = spy.mock.calls[spy.mock.calls.length - 1][0].fields.map((f) => f.name);
+ const received = spy.mock.calls[spy.mock.calls.length - 1][0].fields.map((f: Field) => f.name);
expect(received).toEqual(df.fields.map((f) => f.name));
});
it('should move row when row dragged and dropped', async () => {
- const spy = jest.spyOn(utils, 'publishSnapshot');
-
+ const spy = jest.spyOn(utils, 'updateSnapshot');
jest.useFakeTimers();
render(, {
wrapper: Context,
diff --git a/public/app/plugins/panel/datagrid/DataGridPanel.tsx b/public/app/plugins/panel/datagrid/DataGridPanel.tsx
index f99370c49ad..dc72717ce5c 100644
--- a/public/app/plugins/panel/datagrid/DataGridPanel.tsx
+++ b/public/app/plugins/panel/datagrid/DataGridPanel.tsx
@@ -10,16 +10,16 @@ import DataEditor, {
} from '@glideapps/glide-data-grid';
import React, { useEffect, useReducer } from 'react';
-import { Field, PanelProps, FieldType } from '@grafana/data';
+import { Field, PanelProps, FieldType, DataFrame } from '@grafana/data';
import { PanelDataErrorView } from '@grafana/runtime';
-import { useTheme2 } from '@grafana/ui';
+import { usePanelContext, useTheme2 } from '@grafana/ui';
import '@glideapps/glide-data-grid/dist/index.css';
import { AddColumn } from './components/AddColumn';
import { DatagridContextMenu } from './components/DatagridContextMenu';
import { RenameColumnCell } from './components/RenameColumnCell';
-import { isDatagridEditEnabled } from './featureFlagUtils';
+import { isDatagridEnabled } from './featureFlagUtils';
import { PanelOptions } from './panelcfg.gen';
import { DatagridActionType, datagridReducer, initialState } from './state';
import {
@@ -28,19 +28,21 @@ import {
EMPTY_CELL,
getGridCellKind,
getGridTheme,
- publishSnapshot,
RIGHT_ELEMENT_PROPS,
TRAILING_ROW_OPTIONS,
getStyles,
ROW_MARKER_BOTH,
ROW_MARKER_NUMBER,
hasGridSelection,
+ updateSnapshot,
} from './utils';
export interface DataGridProps extends PanelProps {}
export function DataGridPanel({ options, data, id, fieldConfig, width, height }: DataGridProps) {
const [state, dispatch] = useReducer(datagridReducer, initialState);
+ const { onUpdateData } = usePanelContext();
+
const {
columns,
contextMenuData,
@@ -74,9 +76,23 @@ export function DataGridPanel({ options, data, id, fieldConfig, width, height }:
return getGridCellKind(field, row, hasGridSelection(gridSelection));
};
- const onCellEdited = (cell: Item, newValue: EditableGridCell) => {
+ const onCellEdited = async (cell: Item, newValue: EditableGridCell) => {
+ // if there are rows selected, return early, we don't want to edit any cell
+ if (hasGridSelection(gridSelection)) {
+ return;
+ }
+
const [col, row] = cell;
- const field: Field = frame.fields[col];
+ const frameCopy = {
+ ...frame,
+ fields: frame.fields.map((f) => {
+ return {
+ ...f,
+ values: [...f.values],
+ };
+ }),
+ };
+ const field: Field = frameCopy.fields[col];
if (!field) {
return;
@@ -85,14 +101,14 @@ export function DataGridPanel({ options, data, id, fieldConfig, width, height }:
const values = field.values.toArray();
values[row] = newValue.data;
- field.values = values;
+ field.values = [...values];
- publishSnapshot(frame, id);
+ updateSnapshot(frameCopy, onUpdateData);
};
const onColumnInputBlur = (columnName: string) => {
const len = frame.length ?? 0;
- publishSnapshot(
+ updateSnapshot(
{
...frame,
fields: [
@@ -105,7 +121,7 @@ export function DataGridPanel({ options, data, id, fieldConfig, width, height }:
},
],
},
- id
+ onUpdateData
);
};
@@ -115,7 +131,8 @@ export function DataGridPanel({ options, data, id, fieldConfig, width, height }:
values.push(null);
return { ...f, values };
});
- publishSnapshot({ ...frame, fields, length: frame.length + 1 }, id);
+
+ updateSnapshot({ ...frame, fields, length: frame.length + 1 }, onUpdateData);
};
const onColumnResize = (column: GridColumn, width: number, columnIndex: number, newSizeWithGrow: number) => {
@@ -133,12 +150,12 @@ export function DataGridPanel({ options, data, id, fieldConfig, width, height }:
const onDeletePressed = (selection: GridSelection) => {
if (selection.current && selection.current.range) {
- publishSnapshot(clearCellsFromRangeSelection(frame, selection.current.range), id);
+ updateSnapshot(clearCellsFromRangeSelection(frame, selection.current.range), onUpdateData);
return true;
}
if (selection.rows) {
- publishSnapshot(deleteRows(frame, selection.rows.toArray()), id);
+ updateSnapshot(deleteRows(frame, selection.rows.toArray()), onUpdateData);
return true;
}
@@ -162,14 +179,17 @@ export function DataGridPanel({ options, data, id, fieldConfig, width, height }:
});
};
- const onColumnMove = (from: number, to: number) => {
+ const onColumnMove = async (from: number, to: number) => {
const fields = frame.fields.map((f) => f);
const field = fields[from];
fields.splice(from, 1);
fields.splice(to, 0, field);
- dispatch({ type: DatagridActionType.columnMove, payload: { from, to } });
- publishSnapshot({ ...frame, fields }, id);
+ const hasUpdated = await updateSnapshot({ ...frame, fields }, onUpdateData);
+
+ if (hasUpdated) {
+ dispatch({ type: DatagridActionType.columnMove, payload: { from, to } });
+ }
};
const onRowMove = (from: number, to: number) => {
@@ -181,7 +201,7 @@ export function DataGridPanel({ options, data, id, fieldConfig, width, height }:
field.values.splice(to, 0, value);
}
- publishSnapshot({ ...frame, fields }, id);
+ updateSnapshot({ ...frame, fields }, onUpdateData);
};
const onColumnRename = () => {
@@ -193,7 +213,8 @@ export function DataGridPanel({ options, data, id, fieldConfig, width, height }:
fields[columnIdx].name = columnName;
dispatch({ type: DatagridActionType.hideColumnRenameInput });
- publishSnapshot({ ...frame, fields }, id);
+
+ updateSnapshot({ ...frame, fields }, onUpdateData);
};
const onSearchClose = () => {
@@ -204,10 +225,18 @@ export function DataGridPanel({ options, data, id, fieldConfig, width, height }:
dispatch({ type: DatagridActionType.multipleCellsSelected, payload: { selection } });
};
+ const onContextMenuSave = (data: DataFrame) => {
+ updateSnapshot(data, onUpdateData);
+ };
+
if (!frame) {
return ;
}
+ if (!isDatagridEnabled()) {
+ return ;
+ }
+
if (!document.getElementById('portal')) {
const portal = document.createElement('div');
portal.id = 'portal';
@@ -230,37 +259,37 @@ export function DataGridPanel({ options, data, id, fieldConfig, width, height }:
smoothScrollX
smoothScrollY
overscrollY={50}
- onCellEdited={isDatagridEditEnabled() ? onCellEdited : undefined}
- getCellsForSelection={isDatagridEditEnabled() ? true : undefined}
- showSearch={isDatagridEditEnabled() ? toggleSearch : false}
+ onCellEdited={isDatagridEnabled() ? onCellEdited : undefined}
+ getCellsForSelection={isDatagridEnabled() ? true : undefined}
+ showSearch={isDatagridEnabled() ? toggleSearch : false}
onSearchClose={onSearchClose}
- onPaste={isDatagridEditEnabled() ? true : undefined}
+ onPaste={isDatagridEnabled() ? true : undefined}
gridSelection={gridSelection}
- onGridSelectionChange={isDatagridEditEnabled() ? onGridSelectionChange : undefined}
- onRowAppended={isDatagridEditEnabled() ? addNewRow : undefined}
- onDelete={isDatagridEditEnabled() ? onDeletePressed : undefined}
- rowMarkers={isDatagridEditEnabled() ? ROW_MARKER_BOTH : ROW_MARKER_NUMBER}
+ onGridSelectionChange={isDatagridEnabled() ? onGridSelectionChange : undefined}
+ onRowAppended={isDatagridEnabled() ? addNewRow : undefined}
+ onDelete={isDatagridEnabled() ? onDeletePressed : undefined}
+ rowMarkers={isDatagridEnabled() ? ROW_MARKER_BOTH : ROW_MARKER_NUMBER}
onColumnResize={onColumnResize}
onColumnResizeEnd={onColumnResizeEnd}
- onCellContextMenu={isDatagridEditEnabled() ? onCellContextMenu : undefined}
- onHeaderContextMenu={isDatagridEditEnabled() ? onHeaderContextMenu : undefined}
- onHeaderMenuClick={isDatagridEditEnabled() ? onHeaderMenuClick : undefined}
+ onCellContextMenu={isDatagridEnabled() ? onCellContextMenu : undefined}
+ onHeaderContextMenu={isDatagridEnabled() ? onHeaderContextMenu : undefined}
+ onHeaderMenuClick={isDatagridEnabled() ? onHeaderMenuClick : undefined}
trailingRowOptions={TRAILING_ROW_OPTIONS}
rightElement={
- isDatagridEditEnabled() ? (
+ isDatagridEnabled() ? (
) : null
}
rightElementProps={RIGHT_ELEMENT_PROPS}
freezeColumns={columnFreezeIndex}
- onRowMoved={isDatagridEditEnabled() ? onRowMove : undefined}
- onColumnMoved={isDatagridEditEnabled() ? onColumnMove : undefined}
+ onRowMoved={isDatagridEnabled() ? onRowMove : undefined}
+ onColumnMoved={isDatagridEnabled() ? onColumnMove : undefined}
/>
{contextMenuData.isContextMenuOpen && (
publishSnapshot(data, id)}
+ saveData={onContextMenuSave}
closeContextMenu={closeContextMenu}
dispatch={dispatch}
gridSelection={gridSelection}
diff --git a/public/app/plugins/panel/datagrid/featureFlagUtils.tsx b/public/app/plugins/panel/datagrid/featureFlagUtils.tsx
index e2fed546cf0..0b41f53c114 100644
--- a/public/app/plugins/panel/datagrid/featureFlagUtils.tsx
+++ b/public/app/plugins/panel/datagrid/featureFlagUtils.tsx
@@ -1,5 +1,5 @@
import { config } from '@grafana/runtime';
-export const isDatagridEditEnabled = () => {
+export const isDatagridEnabled = () => {
return config.featureToggles.enableDatagridEditing;
};
diff --git a/public/app/plugins/panel/datagrid/state.ts b/public/app/plugins/panel/datagrid/state.ts
index 28d7db07807..65840df7afe 100644
--- a/public/app/plugins/panel/datagrid/state.ts
+++ b/public/app/plugins/panel/datagrid/state.ts
@@ -10,7 +10,7 @@ import {
import { DataFrame, Field, FieldType, getFieldDisplayName } from '@grafana/data';
-import { isDatagridEditEnabled } from './featureFlagUtils';
+import { isDatagridEnabled } from './featureFlagUtils';
import {
DatagridContextMenuData,
DEFAULT_CONTEXT_MENU,
@@ -224,7 +224,7 @@ export const datagridReducer = (state: DatagridState, action: DatagridAction): D
title: displayName,
width: state.columns[index]?.width ?? getCellWidth(field),
icon: typeToIconMap.get(field.type),
- hasMenu: isDatagridEditEnabled(),
+ hasMenu: isDatagridEnabled(),
trailingRowOptions: { targetColumn: --index },
};
}),
diff --git a/public/app/plugins/panel/datagrid/utils.test.ts b/public/app/plugins/panel/datagrid/utils.test.ts
index 3d493c2106a..79144a21426 100644
--- a/public/app/plugins/panel/datagrid/utils.test.ts
+++ b/public/app/plugins/panel/datagrid/utils.test.ts
@@ -54,7 +54,7 @@ describe('when deleting rows', () => {
expect(newDf.fields[2].values.toArray()).toEqual(['a', 'c', 'e']);
expect(newDf.length).toEqual(3);
- newDf = deleteRows(df, [2], true);
+ newDf = deleteRows(newDf, [2], true);
expect(newDf.fields[0].values.toArray()).toEqual(['a', 'c']);
expect(newDf.fields[1].values.toArray()).toEqual([1, 3]);
diff --git a/public/app/plugins/panel/datagrid/utils.ts b/public/app/plugins/panel/datagrid/utils.ts
index 5cb6cf08724..3545dc3af86 100644
--- a/public/app/plugins/panel/datagrid/utils.ts
+++ b/public/app/plugins/panel/datagrid/utils.ts
@@ -1,11 +1,9 @@
import { css } from '@emotion/css';
import { CompactSelection, GridCell, GridCellKind, GridSelection, Theme } from '@glideapps/glide-data-grid';
-import { ArrayVector, DataFrame, DataFrameJSON, dataFrameToJSON, Field, GrafanaTheme2, FieldType } from '@grafana/data';
-import { getDashboardSrv } from 'app/features/dashboard/services/DashboardSrv';
-import { GrafanaQuery, GrafanaQueryType } from 'app/plugins/datasource/grafana/types';
+import { DataFrame, Field, GrafanaTheme2, FieldType } from '@grafana/data';
-import { isDatagridEditEnabled } from './featureFlagUtils';
+import { isDatagridEnabled } from './featureFlagUtils';
const HEADER_FONT_FAMILY = '600 13px Inter';
const CELL_FONT_FAMILY = '400 13px Inter';
@@ -25,11 +23,6 @@ export const EMPTY_DF = {
length: 0,
};
-export const GRAFANA_DS = {
- type: 'grafana',
- uid: 'grafana',
-};
-
export const EMPTY_CELL: GridCell = {
kind: GridCellKind.Text,
data: '',
@@ -79,6 +72,17 @@ interface CellRange {
height: number;
}
+export async function updateSnapshot(
+ frame: DataFrame,
+ updateData?: (frames: DataFrame[]) => Promise
+): Promise {
+ if (updateData && isDatagridEnabled()) {
+ return await updateData([frame]);
+ }
+
+ return false;
+}
+
export const getTextWidth = (text: string, isHeader = false): number => {
const context = TEXT_CANVAS.getContext('2d');
context!.font = isHeader ? HEADER_FONT_FAMILY : CELL_FONT_FAMILY;
@@ -107,7 +111,12 @@ export const getCellWidth = (field: Field): number => {
};
export const deleteRows = (gridData: DataFrame, rows: number[], hardDelete = false): DataFrame => {
- for (const field of gridData.fields) {
+ const copy = {
+ ...gridData,
+ fields: gridData.fields.map((field) => ({ ...field, values: field.values.slice() })),
+ };
+
+ for (const field of copy.fields) {
const valuesArray = field.values.toArray();
//delete from the end of the array to avoid index shifting
@@ -119,13 +128,12 @@ export const deleteRows = (gridData: DataFrame, rows: number[], hardDelete = fal
}
}
- field.values = new ArrayVector(valuesArray);
+ field.values = [...valuesArray];
}
return {
- ...gridData,
- fields: [...gridData.fields],
- length: gridData.fields[0]?.values.length ?? 0,
+ ...copy,
+ length: copy.fields[0]?.values.length ?? 0,
};
};
@@ -133,50 +141,29 @@ export const clearCellsFromRangeSelection = (gridData: DataFrame, range: CellRan
const colFrom: number = range.x;
const rowFrom: number = range.y;
const colTo: number = range.x + range.width - 1;
+ const copy = {
+ ...gridData,
+ fields: gridData.fields.map((field) => ({ ...field, values: field.values.slice() })),
+ };
for (let i = colFrom; i <= colTo; i++) {
- const field = gridData.fields[i];
+ const field = copy.fields[i];
const valuesArray = field.values.toArray();
valuesArray.splice(rowFrom, range.height, ...new Array(range.height).fill(null));
- field.values = new ArrayVector(valuesArray);
+ field.values = [...valuesArray];
}
return {
- ...gridData,
- fields: [...gridData.fields],
- length: gridData.fields[0]?.values.length ?? 0,
- };
-};
-
-export const publishSnapshot = (data: DataFrame, panelID: number): void => {
- if (!isDatagridEditEnabled()) {
- return;
- }
-
- const snapshot: DataFrameJSON[] = [dataFrameToJSON(data)];
- const dashboard = getDashboardSrv().getCurrent();
- const panelModel = dashboard?.getPanelById(panelID);
-
- const query: GrafanaQuery = {
- refId: 'A',
- queryType: GrafanaQueryType.Snapshot,
- snapshot,
- datasource: GRAFANA_DS,
+ ...copy,
+ length: copy.fields[0]?.values.length ?? 0,
};
-
- panelModel!.updateQueries({
- dataSource: GRAFANA_DS,
- queries: [query],
- });
-
- panelModel!.refresh();
};
//Converting an array of nulls or undefineds returns them as strings and prints them in the cells instead of empty cells. Thus the cleanup func
export const cleanStringFieldAfterConversion = (field: Field): void => {
const valuesArray = field.values.toArray();
- field.values = new ArrayVector(valuesArray.map((val) => (val === 'undefined' || val === 'null' ? null : val)));
+ field.values = valuesArray.map((val) => (val === 'undefined' || val === 'null' ? null : val));
return;
};
@@ -217,7 +204,7 @@ export const getGridCellKind = (field: Field, row: number, hasGridSelection = fa
return {
kind: GridCellKind.Number,
data: value ? value : 0,
- allowOverlay: isDatagridEditEnabled()! && !hasGridSelection,
+ allowOverlay: isDatagridEnabled()! && !hasGridSelection,
readonly: false,
displayData: value !== null && value !== undefined ? value.toString() : '',
};
@@ -225,7 +212,7 @@ export const getGridCellKind = (field: Field, row: number, hasGridSelection = fa
return {
kind: GridCellKind.Text,
data: value ? value : '',
- allowOverlay: isDatagridEditEnabled()! && !hasGridSelection,
+ allowOverlay: isDatagridEnabled()! && !hasGridSelection,
readonly: false,
displayData: value !== null && value !== undefined ? value.toString() : '',
};
@@ -233,7 +220,7 @@ export const getGridCellKind = (field: Field, row: number, hasGridSelection = fa
return {
kind: GridCellKind.Text,
data: value ? value : '',
- allowOverlay: isDatagridEditEnabled()! && !hasGridSelection,
+ allowOverlay: isDatagridEnabled()! && !hasGridSelection,
readonly: false,
displayData: value !== null && value !== undefined ? value.toString() : '',
};
@@ -301,9 +288,16 @@ export const getStyles = (theme: GrafanaTheme2, isResizeInProgress: boolean) =>
};
export const hasGridSelection = (gridSelection: GridSelection): boolean => {
- if (!gridSelection.current) {
+ if (gridSelection.rows.length || gridSelection.columns.length) {
+ return true;
+ }
+
+ if (gridSelection.current === undefined) {
return false;
}
- return gridSelection.current.range && gridSelection.current.range.height > 1 && gridSelection.current.range.width > 1;
+ return (
+ gridSelection.current.range &&
+ !(gridSelection.current.range.height === 1 && gridSelection.current.range.width === 1)
+ );
};