import React, { PureComponent } from 'react'; import AutoSizer from 'react-virtualized-auto-sizer'; import { applyFieldOverrides, applyRawFieldOverrides, CSVConfig, DataFrame, DataTransformerID, dateTimeFormat, getFrameDisplayName, SelectableValue, toCSV, transformDataFrame, } from '@grafana/data'; import { Button, Container, Field, HorizontalGroup, Select, Spinner, Switch, Table, VerticalGroup } from '@grafana/ui'; import { selectors } from '@grafana/e2e-selectors'; import { getPanelInspectorStyles } from './styles'; import { config } from 'app/core/config'; import { saveAs } from 'file-saver'; import { css } from 'emotion'; import { GetDataOptions } from 'app/features/query/state/PanelQueryRunner'; import { QueryOperationRow } from 'app/core/components/QueryOperationRow/QueryOperationRow'; import { PanelModel } from 'app/features/dashboard/state'; import { DetailText } from 'app/features/inspector/DetailText'; interface Props { isLoading: boolean; options: GetDataOptions; data?: DataFrame[]; panel?: PanelModel; onOptionsChange?: (options: GetDataOptions) => void; } interface State { /** The string is seriesToColumns transformation. Otherwise it is a dataframe index */ selectedDataFrame: number | DataTransformerID; transformId: DataTransformerID; dataFrameIndex: number; transformationOptions: Array>; transformedData: DataFrame[]; downloadForExcel: boolean; } export class InspectDataTab extends PureComponent { constructor(props: Props) { super(props); this.state = { selectedDataFrame: 0, dataFrameIndex: 0, transformId: DataTransformerID.noop, transformationOptions: buildTransformationOptions(), transformedData: props.data ?? [], downloadForExcel: false, }; } componentDidUpdate(prevProps: Props, prevState: State) { if (!this.props.data) { this.setState({ transformedData: [] }); return; } if (this.props.options.withTransforms) { this.setState({ transformedData: this.props.data }); return; } if (prevProps.data !== this.props.data || prevState.transformId !== this.state.transformId) { const currentTransform = this.state.transformationOptions.find((item) => item.value === this.state.transformId); if (currentTransform && currentTransform.transformer.id !== DataTransformerID.noop) { const selectedDataFrame = this.state.selectedDataFrame; const dataFrameIndex = this.state.dataFrameIndex; const subscription = transformDataFrame([currentTransform.transformer], this.props.data).subscribe((data) => { this.setState({ transformedData: data, selectedDataFrame, dataFrameIndex }, () => subscription.unsubscribe()); }); return; } this.setState({ transformedData: this.props.data }); return; } } exportCsv = (dataFrame: DataFrame, csvConfig: CSVConfig = {}) => { const { panel } = this.props; const { transformId } = this.state; const dataFrameCsv = toCSV([dataFrame], csvConfig); const blob = new Blob([String.fromCharCode(0xfeff), dataFrameCsv], { type: 'text/csv;charset=utf-8', }); const displayTitle = panel ? panel.getDisplayTitle() : 'Explore'; const transformation = transformId !== DataTransformerID.noop ? '-as-' + transformId.toLocaleLowerCase() : ''; const fileName = `${displayTitle}-data${transformation}-${dateTimeFormat(new Date())}.csv`; saveAs(blob, fileName); }; onDataFrameChange = (item: SelectableValue) => { this.setState({ transformId: item.value === DataTransformerID.seriesToColumns ? DataTransformerID.seriesToColumns : DataTransformerID.noop, dataFrameIndex: typeof item.value === 'number' ? item.value : 0, selectedDataFrame: item.value!, }); }; getProcessedData(): DataFrame[] { const { options, panel } = this.props; const data = this.state.transformedData; if (!options.withFieldConfig || !panel) { return applyRawFieldOverrides(data); } // We need to apply field config even though it was already applied in the PanelQueryRunner. // That's because transformers create new fields and data frames, so i.e. display processor is no longer there return applyFieldOverrides({ data, theme: config.theme, fieldConfig: panel.fieldConfig, replaceVariables: (value: string) => { return value; }, }); } getActiveString() { const { selectedDataFrame } = this.state; const { options, data } = this.props; let activeString = ''; if (!data) { return activeString; } const parts: string[] = []; if (selectedDataFrame === DataTransformerID.seriesToColumns) { parts.push('Series joined by time'); } else if (data.length > 1) { parts.push(getFrameDisplayName(data[selectedDataFrame as number])); } if (options.withTransforms || options.withFieldConfig) { if (options.withTransforms) { parts.push('Panel transforms'); } if (options.withTransforms && options.withFieldConfig) { } if (options.withFieldConfig) { parts.push('Formatted data'); } } if (this.state.downloadForExcel) { parts.push('Excel header'); } return parts.join(', '); } renderDataOptions(dataFrames: DataFrame[]) { const { options, onOptionsChange, panel, data } = this.props; const { transformId, transformationOptions, selectedDataFrame } = this.state; const styles = getPanelInspectorStyles(); const panelTransformations = panel?.getTransformations(); const showPanelTransformationsOption = Boolean(panelTransformations?.length) && (transformId as any) !== 'join by time'; const showFieldConfigsOption = panel && !panel.plugin?.fieldConfigRegistry.isEmpty(); let dataSelect = dataFrames; if (selectedDataFrame === DataTransformerID.seriesToColumns) { dataSelect = data!; } const choices = dataSelect.map((frame, index) => { return { value: index, label: `${getFrameDisplayName(frame)} (${index})`, } as SelectableValue; }); const selectableOptions = [...transformationOptions, ...choices]; return ( {this.getActiveString()}} isOpen={false} >
{data!.length > 1 && (