mirror of
https://github.com/grafana/grafana.git
synced 2026-08-14 07:04:57 -05:00
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:
co-authored by
Oscar Kilhed
Christopher Moyer
Adela Almasan
parent
92a750a732
commit
4167214e35
@@ -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
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user