2022-04-22 08:33:13 -05:00
|
|
|
import { css } from '@emotion/css';
|
2023-07-10 09:27:03 -05:00
|
|
|
import { cloneDeep } from 'lodash';
|
2020-04-15 09:51:51 -05:00
|
|
|
import React, { PureComponent } from 'react';
|
2020-10-06 00:55:09 -05:00
|
|
|
import AutoSizer from 'react-virtualized-auto-sizer';
|
2022-04-22 08:33:13 -05:00
|
|
|
|
2020-04-17 07:03:21 -05:00
|
|
|
import {
|
|
|
|
applyFieldOverrides,
|
2020-09-04 04:21:24 -05:00
|
|
|
applyRawFieldOverrides,
|
2022-09-29 09:09:40 -05:00
|
|
|
CoreApp,
|
2021-03-15 02:44:13 -05:00
|
|
|
CSVConfig,
|
2020-04-17 07:03:21 -05:00
|
|
|
DataFrame,
|
|
|
|
DataTransformerID,
|
2023-07-10 09:27:03 -05:00
|
|
|
FieldConfigSource,
|
2020-04-17 07:03:21 -05:00
|
|
|
SelectableValue,
|
2021-12-02 05:07:23 -06:00
|
|
|
TimeZone,
|
2022-09-29 09:09:40 -05:00
|
|
|
transformDataFrame,
|
2020-04-17 07:03:21 -05:00
|
|
|
} from '@grafana/data';
|
2020-04-27 02:09:05 -05:00
|
|
|
import { selectors } from '@grafana/e2e-selectors';
|
2022-06-09 08:53:23 -05:00
|
|
|
import { reportInteraction } from '@grafana/runtime';
|
2022-04-22 08:33:13 -05:00
|
|
|
import { Button, Spinner, Table } from '@grafana/ui';
|
2020-04-15 09:51:51 -05:00
|
|
|
import { config } from 'app/core/config';
|
2022-10-06 10:34:04 -05:00
|
|
|
import { t, Trans } from 'app/core/internationalization';
|
2022-04-22 08:33:13 -05:00
|
|
|
import { PanelModel } from 'app/features/dashboard/state';
|
|
|
|
import { GetDataOptions } from 'app/features/query/state/PanelQueryRunner';
|
|
|
|
|
2023-07-04 06:27:52 -05:00
|
|
|
import { dataFrameToLogsModel } from '../logs/logsModel';
|
|
|
|
|
2022-04-22 08:33:13 -05:00
|
|
|
import { InspectDataOptions } from './InspectDataOptions';
|
|
|
|
import { getPanelInspectorStyles } from './styles';
|
2023-04-28 09:34:14 -05:00
|
|
|
import { downloadAsJson, downloadDataFrameAsCsv, downloadLogsModelAsTxt, downloadTraceAsJson } from './utils/download';
|
2020-04-15 09:51:51 -05:00
|
|
|
|
|
|
|
interface Props {
|
|
|
|
isLoading: boolean;
|
2020-05-13 06:03:34 -05:00
|
|
|
options: GetDataOptions;
|
2021-12-02 05:07:23 -06:00
|
|
|
timeZone: TimeZone;
|
2022-06-09 08:53:23 -05:00
|
|
|
app?: CoreApp;
|
2021-03-18 11:38:09 -05:00
|
|
|
data?: DataFrame[];
|
|
|
|
panel?: PanelModel;
|
|
|
|
onOptionsChange?: (options: GetDataOptions) => void;
|
2020-04-15 09:51:51 -05:00
|
|
|
}
|
|
|
|
|
2020-04-17 07:03:21 -05:00
|
|
|
interface State {
|
2022-08-23 12:14:03 -05:00
|
|
|
/** The string is joinByField transformation. Otherwise it is a dataframe index */
|
2020-06-29 06:31:39 -05:00
|
|
|
selectedDataFrame: number | DataTransformerID;
|
2020-04-17 07:03:21 -05:00
|
|
|
transformId: DataTransformerID;
|
|
|
|
dataFrameIndex: number;
|
2020-06-29 06:31:39 -05:00
|
|
|
transformationOptions: Array<SelectableValue<DataTransformerID>>;
|
2020-10-06 00:55:09 -05:00
|
|
|
transformedData: DataFrame[];
|
2020-10-09 03:42:57 -05:00
|
|
|
downloadForExcel: boolean;
|
2020-04-17 07:03:21 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
export class InspectDataTab extends PureComponent<Props, State> {
|
2020-04-15 09:51:51 -05:00
|
|
|
constructor(props: Props) {
|
|
|
|
super(props);
|
2020-04-17 07:03:21 -05:00
|
|
|
|
|
|
|
this.state = {
|
2020-09-09 01:47:18 -05:00
|
|
|
selectedDataFrame: 0,
|
2020-04-17 07:03:21 -05:00
|
|
|
dataFrameIndex: 0,
|
2020-09-09 01:47:18 -05:00
|
|
|
transformId: DataTransformerID.noop,
|
2020-04-17 07:03:21 -05:00
|
|
|
transformationOptions: buildTransformationOptions(),
|
2020-10-06 00:55:09 -05:00
|
|
|
transformedData: props.data ?? [],
|
2020-10-09 03:42:57 -05:00
|
|
|
downloadForExcel: false,
|
2020-04-17 07:03:21 -05:00
|
|
|
};
|
2020-04-15 09:51:51 -05:00
|
|
|
}
|
|
|
|
|
2020-10-06 00:55:09 -05:00
|
|
|
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) {
|
2021-01-20 00:59:48 -06:00
|
|
|
const currentTransform = this.state.transformationOptions.find((item) => item.value === this.state.transformId);
|
2020-10-06 00:55:09 -05:00
|
|
|
|
|
|
|
if (currentTransform && currentTransform.transformer.id !== DataTransformerID.noop) {
|
|
|
|
const selectedDataFrame = this.state.selectedDataFrame;
|
|
|
|
const dataFrameIndex = this.state.dataFrameIndex;
|
2021-01-20 00:59:48 -06:00
|
|
|
const subscription = transformDataFrame([currentTransform.transformer], this.props.data).subscribe((data) => {
|
2020-10-06 00:55:09 -05:00
|
|
|
this.setState({ transformedData: data, selectedDataFrame, dataFrameIndex }, () => subscription.unsubscribe());
|
|
|
|
});
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
this.setState({ transformedData: this.props.data });
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-10-09 03:42:57 -05:00
|
|
|
exportCsv = (dataFrame: DataFrame, csvConfig: CSVConfig = {}) => {
|
2020-05-14 13:14:23 -05:00
|
|
|
const { panel } = this.props;
|
|
|
|
const { transformId } = this.state;
|
2020-05-25 04:12:53 -05:00
|
|
|
|
2022-09-29 09:09:40 -05:00
|
|
|
downloadDataFrameAsCsv(dataFrame, panel ? panel.getDisplayTitle() : 'Explore', csvConfig, transformId);
|
2020-04-15 09:51:51 -05:00
|
|
|
};
|
|
|
|
|
2021-04-12 11:21:05 -05:00
|
|
|
exportLogsAsTxt = () => {
|
2022-06-09 08:53:23 -05:00
|
|
|
const { data, panel, app } = this.props;
|
2022-09-29 09:09:40 -05:00
|
|
|
|
2022-06-09 08:53:23 -05:00
|
|
|
reportInteraction('grafana_logs_download_logs_clicked', {
|
|
|
|
app,
|
|
|
|
format: 'logs',
|
2023-01-09 10:25:19 -06:00
|
|
|
area: 'inspector',
|
2022-06-09 08:53:23 -05:00
|
|
|
});
|
2021-04-12 11:21:05 -05:00
|
|
|
|
2023-01-24 12:10:27 -06:00
|
|
|
const logsModel = dataFrameToLogsModel(data || []);
|
2022-09-29 09:09:40 -05:00
|
|
|
downloadLogsModelAsTxt(logsModel, panel ? panel.getDisplayTitle() : 'Explore');
|
2021-04-12 11:21:05 -05:00
|
|
|
};
|
|
|
|
|
2021-09-08 08:04:27 -05:00
|
|
|
exportTracesAsJson = () => {
|
2023-01-27 07:33:27 -06:00
|
|
|
const { data, panel, app } = this.props;
|
|
|
|
|
2021-09-08 08:04:27 -05:00
|
|
|
if (!data) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (const df of data) {
|
|
|
|
// Only export traces
|
|
|
|
if (df.meta?.preferredVisualisationType !== 'trace') {
|
|
|
|
continue;
|
|
|
|
}
|
2023-04-28 09:34:14 -05:00
|
|
|
const traceFormat = downloadTraceAsJson(df, (panel ? panel.getDisplayTitle() : 'Explore') + '-traces');
|
2023-01-27 07:33:27 -06:00
|
|
|
|
|
|
|
reportInteraction('grafana_traces_download_traces_clicked', {
|
|
|
|
app,
|
|
|
|
grafana_version: config.buildInfo.version,
|
|
|
|
trace_format: traceFormat,
|
|
|
|
location: 'inspector',
|
|
|
|
});
|
2021-09-08 08:04:27 -05:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2022-06-07 07:37:19 -05:00
|
|
|
exportServiceGraph = () => {
|
2023-01-27 07:33:27 -06:00
|
|
|
const { data, panel, app } = this.props;
|
|
|
|
reportInteraction('grafana_traces_download_service_graph_clicked', {
|
|
|
|
app,
|
|
|
|
grafana_version: config.buildInfo.version,
|
|
|
|
location: 'inspector',
|
|
|
|
});
|
|
|
|
|
2022-06-07 07:37:19 -05:00
|
|
|
if (!data) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2022-09-29 09:09:40 -05:00
|
|
|
downloadAsJson(data, panel ? panel.getDisplayTitle() : 'Explore');
|
2022-06-07 07:37:19 -05:00
|
|
|
};
|
|
|
|
|
2020-06-29 06:31:39 -05:00
|
|
|
onDataFrameChange = (item: SelectableValue<DataTransformerID | number>) => {
|
|
|
|
this.setState({
|
|
|
|
transformId:
|
2022-08-23 12:14:03 -05:00
|
|
|
item.value === DataTransformerID.joinByField ? DataTransformerID.joinByField : DataTransformerID.noop,
|
2020-06-29 06:31:39 -05:00
|
|
|
dataFrameIndex: typeof item.value === 'number' ? item.value : 0,
|
Chore: Fix all Typescript strict null errors (#26204)
* Chore: Fix typescript strict null errors
* Added new limit
* Fixed ts issue
* fixed tests
* trying to fix type inference
* Fixing more ts errors
* Revert tsconfig option
* Fix
* Fixed code
* More fixes
* fix tests
* Updated snapshot
* Chore: More ts strict null fixes
* More fixes in some really messed up azure config components
* More fixes, current count: 441
* 419
* More fixes
* Fixed invalid initial state in explore
* Fixing tests
* Fixed tests
* Explore fix
* More fixes
* Progress
* Sub 300
* Now at 218
* Progress
* Update
* Progress
* Updated tests
* at 159
* fixed tests
* Progress
* YAy blow 100! at 94
* 10,9,8,7,6,5,4,3,2,1... lift off
* Fixed tests
* Fixed more type errors
Co-authored-by: Ryan McKinley <ryantxu@gmail.com>
2020-07-10 05:46:59 -05:00
|
|
|
selectedDataFrame: item.value!,
|
2020-06-29 06:31:39 -05:00
|
|
|
});
|
2020-04-17 07:03:21 -05:00
|
|
|
};
|
|
|
|
|
2021-05-27 13:58:44 -05:00
|
|
|
toggleDownloadForExcel = () => {
|
2021-04-12 11:21:05 -05:00
|
|
|
this.setState((prevState) => ({
|
|
|
|
downloadForExcel: !prevState.downloadForExcel,
|
|
|
|
}));
|
2021-05-27 13:58:44 -05:00
|
|
|
};
|
2021-04-12 11:21:05 -05:00
|
|
|
|
2020-04-17 07:03:21 -05:00
|
|
|
getProcessedData(): DataFrame[] {
|
2021-12-02 05:07:23 -06:00
|
|
|
const { options, panel, timeZone } = this.props;
|
2020-10-06 00:55:09 -05:00
|
|
|
const data = this.state.transformedData;
|
2020-07-01 01:53:29 -05:00
|
|
|
|
2021-03-18 11:38:09 -05:00
|
|
|
if (!options.withFieldConfig || !panel) {
|
2020-09-04 04:21:24 -05:00
|
|
|
return applyRawFieldOverrides(data);
|
|
|
|
}
|
|
|
|
|
2023-07-10 09:27:03 -05:00
|
|
|
const fieldConfig = this.cleanTableConfigFromFieldConfig(panel.type, panel.fieldConfig);
|
|
|
|
|
|
|
|
// We need to apply field config as it's not done by PanelQueryRunner (even when withFieldConfig is true).
|
|
|
|
// It's because transformers create new fields and data frames, and we need to clean field config of any table settings.
|
2020-04-17 07:03:21 -05:00
|
|
|
return applyFieldOverrides({
|
2020-05-13 06:03:34 -05:00
|
|
|
data,
|
2021-04-29 05:44:06 -05:00
|
|
|
theme: config.theme2,
|
2023-07-10 09:27:03 -05:00
|
|
|
fieldConfig,
|
2021-12-02 05:07:23 -06:00
|
|
|
timeZone,
|
2020-04-17 07:03:21 -05:00
|
|
|
replaceVariables: (value: string) => {
|
|
|
|
return value;
|
|
|
|
},
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2023-07-10 09:27:03 -05:00
|
|
|
// Because we visualize this data in a table we have to remove any custom table display settings
|
|
|
|
cleanTableConfigFromFieldConfig(panelPluginId: string, fieldConfig: FieldConfigSource): FieldConfigSource {
|
|
|
|
if (panelPluginId !== 'table') {
|
|
|
|
return fieldConfig;
|
|
|
|
}
|
|
|
|
|
|
|
|
fieldConfig = cloneDeep(fieldConfig);
|
|
|
|
// clear all table specific options
|
|
|
|
fieldConfig.defaults.custom = {};
|
|
|
|
|
|
|
|
// clear all table override properties
|
|
|
|
for (const override of fieldConfig.overrides) {
|
|
|
|
for (const prop of override.properties) {
|
|
|
|
if (prop.id.startsWith('custom.')) {
|
|
|
|
const index = override.properties.indexOf(prop);
|
|
|
|
override.properties.slice(index, 1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return fieldConfig;
|
|
|
|
}
|
|
|
|
|
2020-04-15 09:51:51 -05:00
|
|
|
render() {
|
2022-06-09 08:53:23 -05:00
|
|
|
const { isLoading, options, data, panel, onOptionsChange, app } = this.props;
|
2021-04-12 11:21:05 -05:00
|
|
|
const { dataFrameIndex, transformId, transformationOptions, selectedDataFrame, downloadForExcel } = this.state;
|
2020-04-15 09:51:51 -05:00
|
|
|
const styles = getPanelInspectorStyles();
|
|
|
|
|
|
|
|
if (isLoading) {
|
|
|
|
return (
|
|
|
|
<div>
|
2020-11-04 06:34:40 -06:00
|
|
|
<Spinner inline={true} /> Loading
|
2020-04-15 09:51:51 -05:00
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2020-04-17 07:03:21 -05:00
|
|
|
const dataFrames = this.getProcessedData();
|
|
|
|
|
|
|
|
if (!dataFrames || !dataFrames.length) {
|
2020-04-15 09:51:51 -05:00
|
|
|
return <div>No Data</div>;
|
|
|
|
}
|
|
|
|
|
2020-10-06 00:55:09 -05:00
|
|
|
// let's make sure we don't try to render a frame that doesn't exists
|
|
|
|
const index = !dataFrames[dataFrameIndex] ? 0 : dataFrameIndex;
|
2021-04-12 11:21:05 -05:00
|
|
|
const dataFrame = dataFrames[index];
|
|
|
|
const hasLogs = dataFrames.some((df) => df?.meta?.preferredVisualisationType === 'logs');
|
2021-09-08 08:04:27 -05:00
|
|
|
const hasTraces = dataFrames.some((df) => df?.meta?.preferredVisualisationType === 'trace');
|
2022-06-07 07:37:19 -05:00
|
|
|
const hasServiceGraph = dataFrames.some((df) => df?.meta?.preferredVisualisationType === 'nodeGraph');
|
2020-07-01 01:53:29 -05:00
|
|
|
|
2020-04-15 09:51:51 -05:00
|
|
|
return (
|
2022-03-16 11:28:09 -05:00
|
|
|
<div className={styles.wrap} aria-label={selectors.components.PanelInspector.Data.content}>
|
|
|
|
<div className={styles.toolbar}>
|
2021-04-12 11:21:05 -05:00
|
|
|
<InspectDataOptions
|
|
|
|
data={data}
|
|
|
|
panel={panel}
|
|
|
|
options={options}
|
|
|
|
dataFrames={dataFrames}
|
|
|
|
transformId={transformId}
|
|
|
|
transformationOptions={transformationOptions}
|
|
|
|
selectedDataFrame={selectedDataFrame}
|
|
|
|
downloadForExcel={downloadForExcel}
|
|
|
|
onOptionsChange={onOptionsChange}
|
|
|
|
onDataFrameChange={this.onDataFrameChange}
|
|
|
|
toggleDownloadForExcel={this.toggleDownloadForExcel}
|
|
|
|
/>
|
2020-06-29 06:31:39 -05:00
|
|
|
<Button
|
|
|
|
variant="primary"
|
2022-06-09 08:53:23 -05:00
|
|
|
onClick={() => {
|
|
|
|
if (hasLogs) {
|
|
|
|
reportInteraction('grafana_logs_download_clicked', {
|
|
|
|
app,
|
|
|
|
format: 'csv',
|
|
|
|
});
|
|
|
|
}
|
|
|
|
this.exportCsv(dataFrames[dataFrameIndex], { useExcelHeader: this.state.downloadForExcel });
|
|
|
|
}}
|
2020-06-29 06:31:39 -05:00
|
|
|
className={css`
|
|
|
|
margin-bottom: 10px;
|
|
|
|
`}
|
|
|
|
>
|
2022-10-06 10:34:04 -05:00
|
|
|
<Trans i18nKey="dashboard.inspect-data.download-csv">Download CSV</Trans>
|
2020-06-29 06:31:39 -05:00
|
|
|
</Button>
|
2021-04-12 11:21:05 -05:00
|
|
|
{hasLogs && (
|
|
|
|
<Button
|
|
|
|
variant="primary"
|
|
|
|
onClick={this.exportLogsAsTxt}
|
|
|
|
className={css`
|
|
|
|
margin-bottom: 10px;
|
|
|
|
margin-left: 10px;
|
|
|
|
`}
|
|
|
|
>
|
2022-10-06 10:34:04 -05:00
|
|
|
<Trans i18nKey="dashboard.inspect-data.download-logs">Download logs</Trans>
|
2021-04-12 11:21:05 -05:00
|
|
|
</Button>
|
|
|
|
)}
|
2021-09-08 08:04:27 -05:00
|
|
|
{hasTraces && (
|
|
|
|
<Button
|
|
|
|
variant="primary"
|
|
|
|
onClick={this.exportTracesAsJson}
|
|
|
|
className={css`
|
|
|
|
margin-bottom: 10px;
|
|
|
|
margin-left: 10px;
|
|
|
|
`}
|
|
|
|
>
|
2022-10-06 10:34:04 -05:00
|
|
|
<Trans i18nKey="dashboard.inspect-data.download-traces">Download traces</Trans>
|
2021-09-08 08:04:27 -05:00
|
|
|
</Button>
|
|
|
|
)}
|
2022-06-07 07:37:19 -05:00
|
|
|
{hasServiceGraph && (
|
|
|
|
<Button
|
|
|
|
variant="primary"
|
|
|
|
onClick={this.exportServiceGraph}
|
|
|
|
className={css`
|
|
|
|
margin-bottom: 10px;
|
|
|
|
margin-left: 10px;
|
|
|
|
`}
|
|
|
|
>
|
2022-10-06 10:34:04 -05:00
|
|
|
<Trans i18nKey="dashboard.inspect-data.download-service">Download service graph</Trans>
|
2022-06-07 07:37:19 -05:00
|
|
|
</Button>
|
|
|
|
)}
|
2020-06-29 06:31:39 -05:00
|
|
|
</div>
|
2022-03-16 11:28:09 -05:00
|
|
|
<div className={styles.content}>
|
2020-04-15 09:51:51 -05:00
|
|
|
<AutoSizer>
|
|
|
|
{({ width, height }) => {
|
|
|
|
if (width === 0) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2023-05-05 02:54:43 -05:00
|
|
|
return <Table width={width} height={height} data={dataFrame} showTypeIcons={true} />;
|
2020-04-15 09:51:51 -05:00
|
|
|
}}
|
|
|
|
</AutoSizer>
|
2022-03-16 11:28:09 -05:00
|
|
|
</div>
|
2020-04-15 09:51:51 -05:00
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
2020-04-17 07:03:21 -05:00
|
|
|
|
|
|
|
function buildTransformationOptions() {
|
2020-06-29 06:31:39 -05:00
|
|
|
const transformations: Array<SelectableValue<DataTransformerID>> = [
|
2020-04-17 07:03:21 -05:00
|
|
|
{
|
2022-08-23 12:14:03 -05:00
|
|
|
value: DataTransformerID.joinByField,
|
2022-10-06 10:34:04 -05:00
|
|
|
label: t('dashboard.inspect-data.transformation', 'Series joined by time'),
|
2020-04-17 07:03:21 -05:00
|
|
|
transformer: {
|
2022-08-23 12:14:03 -05:00
|
|
|
id: DataTransformerID.joinByField,
|
|
|
|
options: { byField: undefined }, // defaults to time field
|
2020-04-17 07:03:21 -05:00
|
|
|
},
|
|
|
|
},
|
|
|
|
];
|
|
|
|
|
|
|
|
return transformations;
|
|
|
|
}
|