mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Inspector: support custom metadata display (#20854)
This commit is contained in:
@@ -1,17 +1,88 @@
|
||||
// Libraries
|
||||
import React, { PureComponent } from 'react';
|
||||
|
||||
import { DashboardModel, PanelModel } from 'app/features/dashboard/state';
|
||||
import { Drawer, JSONFormatter } from '@grafana/ui';
|
||||
import { css } from 'emotion';
|
||||
import { getLocationSrv } from '@grafana/runtime';
|
||||
import { JSONFormatter, Drawer, Select, Table } from '@grafana/ui';
|
||||
import { getLocationSrv, getDataSourceSrv } from '@grafana/runtime';
|
||||
import { DataFrame, DataSourceApi, SelectableValue, applyFieldOverrides } from '@grafana/data';
|
||||
import { config } from 'app/core/config';
|
||||
|
||||
interface Props {
|
||||
dashboard: DashboardModel;
|
||||
panel: PanelModel;
|
||||
}
|
||||
|
||||
export class PanelInspector extends PureComponent<Props> {
|
||||
enum InspectTab {
|
||||
Data = 'data',
|
||||
Raw = 'raw',
|
||||
Issue = 'issue',
|
||||
Meta = 'meta', // When result metadata exists
|
||||
}
|
||||
|
||||
interface State {
|
||||
// The last raw response
|
||||
last?: any;
|
||||
|
||||
// Data frem the last response
|
||||
data: DataFrame[];
|
||||
|
||||
// The selected data frame
|
||||
selected: number;
|
||||
|
||||
// The Selected Tab
|
||||
tab: InspectTab;
|
||||
|
||||
// If the datasource supports custom metadata
|
||||
metaDS?: DataSourceApi;
|
||||
}
|
||||
|
||||
export class PanelInspector extends PureComponent<Props, State> {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
data: [],
|
||||
selected: 0,
|
||||
tab: InspectTab.Data,
|
||||
};
|
||||
}
|
||||
|
||||
async componentDidMount() {
|
||||
const { panel } = this.props;
|
||||
if (!panel) {
|
||||
this.onDismiss(); // Try to close the component
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO? should we get the result with an observable once?
|
||||
const lastResult = (panel.getQueryRunner() as any).lastResult;
|
||||
if (!lastResult) {
|
||||
this.onDismiss(); // Usually opened from refresh?
|
||||
return;
|
||||
}
|
||||
|
||||
// Find the first DataSource wanting to show custom metadata
|
||||
let metaDS: DataSourceApi;
|
||||
const data = lastResult?.series as DataFrame[];
|
||||
if (data) {
|
||||
for (const frame of data) {
|
||||
const key = frame.meta?.datasource;
|
||||
if (key) {
|
||||
const ds = await getDataSourceSrv().get(key);
|
||||
if (ds && ds.components.MetadataInspector) {
|
||||
metaDS = ds;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Set last result, but no metadata inspector
|
||||
this.setState({
|
||||
last: lastResult,
|
||||
data,
|
||||
metaDS,
|
||||
});
|
||||
}
|
||||
|
||||
onDismiss = () => {
|
||||
getLocationSrv().update({
|
||||
query: { inspect: null },
|
||||
@@ -19,24 +90,98 @@ export class PanelInspector extends PureComponent<Props> {
|
||||
});
|
||||
};
|
||||
|
||||
onSelectTab = (item: SelectableValue<InspectTab>) => {
|
||||
this.setState({ tab: item.value || InspectTab.Data });
|
||||
};
|
||||
|
||||
onSelectedFrameChanged = (item: SelectableValue<number>) => {
|
||||
this.setState({ selected: item.value || 0 });
|
||||
};
|
||||
|
||||
renderMetadataInspector() {
|
||||
const { metaDS, data } = this.state;
|
||||
if (!metaDS || !metaDS.components?.MetadataInspector) {
|
||||
return <div>No Metadata Inspector</div>;
|
||||
}
|
||||
return <metaDS.components.MetadataInspector datasource={metaDS} data={data} />;
|
||||
}
|
||||
|
||||
renderDataTab() {
|
||||
const { data, selected } = this.state;
|
||||
if (!data || !data.length) {
|
||||
return <div>No Data</div>;
|
||||
}
|
||||
const choices = data.map((frame, index) => {
|
||||
return {
|
||||
value: index,
|
||||
label: `${frame.name} (${index})`,
|
||||
};
|
||||
});
|
||||
|
||||
// Apply dummy styles
|
||||
const processed = applyFieldOverrides({
|
||||
data,
|
||||
theme: config.theme,
|
||||
fieldOptions: { defaults: {}, overrides: [] },
|
||||
replaceVariables: (value: string) => {
|
||||
return value;
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<div>
|
||||
{choices.length > 1 && (
|
||||
<div>
|
||||
<Select
|
||||
options={choices}
|
||||
value={choices.find(t => t.value === selected)}
|
||||
onChange={this.onSelectedFrameChanged}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div style={{ border: '1px solid #666' }}>
|
||||
<Table width={330} height={400} data={processed[selected]} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderIssueTab() {
|
||||
return <div>TODO: show issue form</div>;
|
||||
}
|
||||
|
||||
render() {
|
||||
const { panel } = this.props;
|
||||
const { last, tab } = this.state;
|
||||
if (!panel) {
|
||||
this.onDismiss(); // Try to close the component
|
||||
return null;
|
||||
}
|
||||
const bodyStyle = css`
|
||||
max-height: 70vh;
|
||||
overflow-y: scroll;
|
||||
`;
|
||||
|
||||
// TODO? should we get the result with an observable once?
|
||||
const data = (panel.getQueryRunner() as any).lastResult;
|
||||
const tabs = [
|
||||
{ label: 'Data', value: InspectTab.Data },
|
||||
{ label: 'Issue', value: InspectTab.Issue },
|
||||
{ label: 'Raw JSON', value: InspectTab.Raw },
|
||||
];
|
||||
if (this.state.metaDS) {
|
||||
tabs.push({ label: 'Meta Data', value: InspectTab.Meta });
|
||||
}
|
||||
|
||||
return (
|
||||
<Drawer title={panel.title} onClose={this.onDismiss}>
|
||||
<div className={bodyStyle}>
|
||||
<JSONFormatter json={data} open={2} />
|
||||
</div>
|
||||
<Select options={tabs} value={tabs.find(t => t.value === tab)} onChange={this.onSelectTab} />
|
||||
|
||||
{tab === InspectTab.Data && this.renderDataTab()}
|
||||
|
||||
{tab === InspectTab.Meta && this.renderMetadataInspector()}
|
||||
|
||||
{tab === InspectTab.Issue && this.renderIssueTab()}
|
||||
|
||||
{tab === InspectTab.Raw && (
|
||||
<div>
|
||||
<JSONFormatter json={last} open={2} />
|
||||
</div>
|
||||
)}
|
||||
</Drawer>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user