Panel Inspect: Allow CSV download for Excel (#27284)

* fix: Use locale to find delimiter for CSV export

* Add sep= Excel header to CSV exporter

* Add modal for Excel export

* Move Excel download to 'Data options' as toggle

* Add 'Download for Excel' documentation
This commit is contained in:
Tom Daly 2020-10-09 09:42:57 +01:00 committed by GitHub
parent d181782943
commit dc662025cd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 50 additions and 6 deletions

View File

@ -62,7 +62,9 @@ Grafana generates a CSV file in your default browser download location. You can
1. Open the panel inspector.
1. Inspect the raw query results as described above. Adjust settings until you see the raw data that you want to export.
1. Click **Download CSV**.
To download a CSV file specifically formatted for Excel, expand the **Data options** panel and enable the **Download for Excel** toggle before clicking **Download CSV**.
### Inspect query performance
The Stats tab displays statistics that tell you how long your query takes, how many queries you send, and the number of rows returned. This information can help you troubleshoot your queries, especially if any of the numbers are unexpectedly high or low.

View File

@ -88,6 +88,25 @@ describe('write csv', () => {
expect(getDataFrameRow(f[0], 0)).toEqual(firstRow);
expect(fields.map(f => f.name).join(',')).toEqual('a,b,c'); // the names
});
it('should add Excel header given config', () => {
const dataFrame = new MutableDataFrame({
fields: [
{ name: 'Time', values: [1598784913123, 1598784914123] },
{ name: 'Value', values: ['1234', '5678'] },
],
});
const csv = toCSV([dataFrame], { useExcelHeader: true });
expect(csv).toMatchInlineSnapshot(`
"sep=,
\\"Time\\",\\"Value\\"
1598784913123,1234
1598784914123,5678
"
`);
});
});
describe('DataFrame to CSV', () => {

View File

@ -21,6 +21,7 @@ export interface CSVConfig {
newline?: string; // default: "\r\n"
quoteChar?: string; // default: '"'
encoding?: string; // default: "",
useExcelHeader?: boolean; // default: false
headerStyle?: CSVHeaderStyle;
}
@ -246,19 +247,28 @@ function getHeaderLine(key: string, fields: Field[], config: CSVConfig): string
return '';
}
function getLocaleDelimiter(): string {
const arr = ['x', 'y'];
if (arr.toLocaleString) {
return arr.toLocaleString().charAt(1);
}
return ',';
}
export function toCSV(data: DataFrame[], config?: CSVConfig): string {
if (!data) {
return '';
}
let csv = '';
config = defaults(config, {
delimiter: ',',
delimiter: getLocaleDelimiter(),
newline: '\r\n',
quoteChar: '"',
encoding: '',
headerStyle: CSVHeaderStyle.name,
useExcelHeader: false,
});
let csv = config.useExcelHeader ? `sep=${config.delimiter}${config.newline}` : '';
for (const series of data) {
const { fields } = series;

View File

@ -12,6 +12,7 @@ import {
transformDataFrame,
} from '@grafana/data';
import { Button, Container, Field, HorizontalGroup, Icon, Select, Switch, Table, VerticalGroup } from '@grafana/ui';
import { CSVConfig } from '@grafana/data';
import { selectors } from '@grafana/e2e-selectors';
import { getPanelInspectorStyles } from './styles';
@ -39,6 +40,7 @@ interface State {
dataFrameIndex: number;
transformationOptions: Array<SelectableValue<DataTransformerID>>;
transformedData: DataFrame[];
downloadForExcel: boolean;
}
export class InspectDataTab extends PureComponent<Props, State> {
@ -51,6 +53,7 @@ export class InspectDataTab extends PureComponent<Props, State> {
transformId: DataTransformerID.noop,
transformationOptions: buildTransformationOptions(),
transformedData: props.data ?? [],
downloadForExcel: false,
};
}
@ -82,11 +85,11 @@ export class InspectDataTab extends PureComponent<Props, State> {
}
}
exportCsv = (dataFrame: DataFrame) => {
exportCsv = (dataFrame: DataFrame, csvConfig: CSVConfig = {}) => {
const { panel } = this.props;
const { transformId } = this.state;
const dataFrameCsv = toCSV([dataFrame]);
const dataFrameCsv = toCSV([dataFrame], csvConfig);
const blob = new Blob([String.fromCharCode(0xfeff), dataFrameCsv], {
type: 'text/csv;charset=utf-8',
@ -156,6 +159,10 @@ export class InspectDataTab extends PureComponent<Props, State> {
}
}
if (this.state.downloadForExcel) {
parts.push('Excel header');
}
return parts.join(', ');
}
@ -233,6 +240,12 @@ export class InspectDataTab extends PureComponent<Props, State> {
/>
</Field>
)}
<Field label="Download for Excel" description="Adds header to CSV for use with Excel">
<Switch
value={this.state.downloadForExcel}
onChange={() => this.setState({ downloadForExcel: !this.state.downloadForExcel })}
/>
</Field>
</HorizontalGroup>
</VerticalGroup>
</div>
@ -269,7 +282,7 @@ export class InspectDataTab extends PureComponent<Props, State> {
<div className={styles.dataDisplayOptions}>{this.renderDataOptions(dataFrames)}</div>
<Button
variant="primary"
onClick={() => this.exportCsv(dataFrames[dataFrameIndex])}
onClick={() => this.exportCsv(dataFrames[dataFrameIndex], { useExcelHeader: this.state.downloadForExcel })}
className={css`
margin-bottom: 10px;
`}