mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
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:
parent
d181782943
commit
dc662025cd
@ -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.
|
||||
|
@ -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', () => {
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
`}
|
||||
|
Loading…
Reference in New Issue
Block a user