Panel edit: Add feature to drag & drop spreadsheet files to the grafana datasource (#60586)

Co-authored-by: Oscar Kilhed <oscar.kilhed@grafana.com>
Co-authored-by: Christopher Moyer <35463610+chri2547@users.noreply.github.com>
Co-authored-by: Adela Almasan <adela.almasan@grafana.com>
This commit is contained in:
Zoltán Bedi
2023-01-24 10:43:44 +01:00
committed by GitHub
co-authored by Oscar Kilhed Christopher Moyer Adela Almasan
parent 92a750a732
commit 4167214e35
9 changed files with 149 additions and 11 deletions
@@ -95,6 +95,7 @@ Alpha features might be changed or removed without prior notice.
| `authnService` | Use new auth service to perform authentication |
| `sessionRemoteCache` | Enable using remote cache for user sessions |
| `alertingBacktesting` | Rule backtesting API for alerting |
| `editPanelCSVDragAndDrop` | Enables drag and drop for CSV and Excel files |
| `azureMultipleResourcePicker` | Azure multiple resource picker |
## Development feature toggles
+2 -1
View File
@@ -404,7 +404,8 @@
"uuid": "9.0.0",
"vendor": "link:./public/vendor",
"visjs-network": "4.25.0",
"whatwg-fetch": "3.6.2"
"whatwg-fetch": "3.6.2",
"xlsx": "https://cdn.sheetjs.com/xlsx-0.19.1/xlsx-0.19.1.tgz"
},
"resolutions": {
"underscore": "1.13.6",
@@ -87,6 +87,7 @@ export interface FeatureToggles {
sessionRemoteCache?: boolean;
disablePrometheusExemplarSampling?: boolean;
alertingBacktesting?: boolean;
editPanelCSVDragAndDrop?: boolean;
alertingNoNormalState?: boolean;
azureMultipleResourcePicker?: boolean;
}
+6
View File
@@ -397,6 +397,12 @@ var (
Description: "Rule backtesting API for alerting",
State: FeatureStateAlpha,
},
{
Name: "editPanelCSVDragAndDrop",
Description: "Enables drag and drop for CSV and Excel files",
FrontendOnly: true,
State: FeatureStateAlpha,
},
{
Name: "alertingNoNormalState",
Description: "Stop maintaining state of alerts that are not firing",
+4
View File
@@ -291,6 +291,10 @@ const (
// Rule backtesting API for alerting
FlagAlertingBacktesting = "alertingBacktesting"
// FlagEditPanelCSVDragAndDrop
// Enables drag and drop for CSV and Excel files
FlagEditPanelCSVDragAndDrop = "editPanelCSVDragAndDrop"
// FlagAlertingNoNormalState
// Stop maintaining state of alerts that are not firing
FlagAlertingNoNormalState = "alertingNoNormalState"
+12
View File
@@ -0,0 +1,12 @@
import { read, utils } from 'xlsx';
import { ArrayDataFrame, DataFrame } from '@grafana/data';
export function readSpreadsheet(file: ArrayBuffer): DataFrame[] {
const wb = read(file, { type: 'buffer' });
return wb.SheetNames.map((name) => {
const frame = new ArrayDataFrame(utils.sheet_to_json(wb.Sheets[name]));
frame.name = name;
return frame;
});
}
@@ -1,3 +1,4 @@
import { css } from '@emotion/css';
import pluralize from 'pluralize';
import React, { PureComponent } from 'react';
@@ -8,10 +9,27 @@ import {
rangeUtil,
DataQueryRequest,
DataFrame,
DataFrameJSON,
dataFrameToJSON,
GrafanaTheme2,
getValueFormat,
formattedValueToString,
} from '@grafana/data';
import { config, getBackendSrv, getDataSourceSrv } from '@grafana/runtime';
import { InlineField, Select, Alert, Input, InlineFieldRow, InlineLabel } from '@grafana/ui';
import {
InlineField,
Select,
Alert,
Input,
InlineFieldRow,
InlineLabel,
FileDropzone,
DropzoneFile,
Themeable2,
withTheme2,
} from '@grafana/ui';
import { hasAlphaPanels } from 'app/core/config';
import { readSpreadsheet } from 'app/core/utils/sheet';
import { SearchQuery } from 'app/features/search/service';
import { GrafanaDatasource } from '../datasource';
@@ -19,7 +37,7 @@ import { defaultQuery, GrafanaQuery, GrafanaQueryType } from '../types';
import SearchEditor from './SearchEditor';
type Props = QueryEditorProps<GrafanaDatasource, GrafanaQuery>;
interface Props extends QueryEditorProps<GrafanaDatasource, GrafanaQuery>, Themeable2 {}
const labelWidth = 12;
@@ -29,7 +47,7 @@ interface State {
folders?: Array<SelectableValue<string>>;
}
export class QueryEditor extends PureComponent<Props, State> {
export class UnthemedQueryEditor extends PureComponent<Props, State> {
state: State = { channels: [], channelFields: {} };
queryTypes: Array<SelectableValue<GrafanaQueryType>> = [
@@ -60,6 +78,13 @@ export class QueryEditor extends PureComponent<Props, State> {
description: 'Search for grafana resources',
});
}
if (config.featureToggles.editPanelCSVDragAndDrop) {
this.queryTypes.push({
label: 'Spreadsheet or snapshot',
value: GrafanaQueryType.Snapshot,
description: 'Query an uploaded spreadsheet or a snapshot',
});
}
}
loadChannelInfo() {
@@ -345,15 +370,47 @@ export class QueryEditor extends PureComponent<Props, State> {
);
}
// Skip rendering the file list as we're handling that in this component instead.
fileListRenderer = (file: DropzoneFile, removeFile: (file: DropzoneFile) => void) => {
return null;
};
onDropAccepted = (files: File[]) => {
this.props.onChange({ ...this.props.query, file: { name: files[0].name, size: files[0].size } });
};
renderSnapshotQuery() {
const { query } = this.props;
const { query, theme } = this.props;
const file = query.file;
const styles = getStyles(theme);
const fileSize = getValueFormat('decbytes')(file ? file.size : 0);
return (
<InlineFieldRow>
<InlineField label="Snapshot" grow={true} labelWidth={labelWidth}>
<InlineLabel>{pluralize('frame', query.snapshot?.length ?? 0, true)}</InlineLabel>
</InlineField>
</InlineFieldRow>
<>
<InlineFieldRow>
<InlineField label="Snapshot" grow={true} labelWidth={labelWidth}>
<InlineLabel>{pluralize('frame', query.snapshot?.length ?? 0, true)}</InlineLabel>
</InlineField>
</InlineFieldRow>
{config.featureToggles.editPanelCSVDragAndDrop && (
<>
<FileDropzone
readAs="readAsArrayBuffer"
fileListRenderer={this.fileListRenderer}
options={{ onDropAccepted: this.onDropAccepted, maxSize: 200000, multiple: false }}
onLoad={this.onFileDrop}
></FileDropzone>
{file && (
<div className={styles.file}>
<span>{file?.name}</span>
<span>
<span>{formattedValueToString(fileSize)}</span>
</span>
</div>
)}
</>
)}
</>
);
}
@@ -367,6 +424,28 @@ export class QueryEditor extends PureComponent<Props, State> {
onRunQuery();
};
onFileDrop = (result: ArrayBuffer | String | null) => {
const snapshot: DataFrameJSON[] = [];
if (result) {
if (!result || result instanceof String) {
return;
}
const dataFrames = readSpreadsheet(result);
dataFrames.forEach((df) => {
const dataframeJson = dataFrameToJSON(df);
snapshot.push(dataframeJson);
});
}
this.props.onChange({
...this.props.query,
queryType: GrafanaQueryType.Snapshot,
snapshot,
});
this.props.onRunQuery();
};
render() {
const query = {
...defaultQuery,
@@ -377,7 +456,7 @@ export class QueryEditor extends PureComponent<Props, State> {
// Only show "snapshot" when it already exists
let queryTypes = this.queryTypes;
if (queryType === GrafanaQueryType.Snapshot) {
if (queryType === GrafanaQueryType.Snapshot && !config.featureToggles.editPanelCSVDragAndDrop) {
queryTypes = [
...this.queryTypes,
{
@@ -414,3 +493,21 @@ export class QueryEditor extends PureComponent<Props, State> {
);
}
}
export const QueryEditor = withTheme2(UnthemedQueryEditor);
function getStyles(theme: GrafanaTheme2) {
return {
file: css`
width: 100%;
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
padding: ${theme.spacing(2)};
border: 1px dashed ${theme.colors.border.medium};
background-color: ${theme.colors.background.secondary};
margin-top: ${theme.spacing(1)};
`,
};
}
@@ -26,6 +26,12 @@ export interface GrafanaQuery extends DataQuery {
path?: string; // for list and read
search?: SearchQuery;
snapshot?: DataFrameJSON[];
file?: GrafanaQueryFile;
}
export interface GrafanaQueryFile {
name: string;
size: number;
}
export const defaultQuery: GrafanaQuery = {
+10
View File
@@ -21903,6 +21903,7 @@ __metadata:
webpack-manifest-plugin: 5.0.0
webpack-merge: 5.8.0
whatwg-fetch: 3.6.2
xlsx: "https://cdn.sheetjs.com/xlsx-0.19.1/xlsx-0.19.1.tgz"
languageName: unknown
linkType: soft
@@ -39179,6 +39180,15 @@ __metadata:
languageName: node
linkType: hard
"xlsx@https://cdn.sheetjs.com/xlsx-0.19.1/xlsx-0.19.1.tgz":
version: 0.19.1
resolution: "xlsx@https://cdn.sheetjs.com/xlsx-0.19.1/xlsx-0.19.1.tgz"
bin:
xlsx: ./bin/xlsx.njs
checksum: a7fa1b95dfc9a6923458e19f0dcd0c64d70ce49a5959c8f38c219fd232a4fdfd48d70115dbe01b0e58e80f336ce872495dcc3d12943c4d19b041c87f8eeb69c8
languageName: node
linkType: hard
"xml-name-validator@npm:^3.0.0":
version: 3.0.0
resolution: "xml-name-validator@npm:3.0.0"