mirror of
https://github.com/grafana/grafana.git
synced 2025-02-09 23:16:16 -06:00
Merge pull request #14107 from grafana/davkal/explore-panel
Explore: collapsible result panels
This commit is contained in:
commit
d4e792dccd
@ -19,6 +19,7 @@ import NoOptionsMessage from 'app/core/components/Picker/NoOptionsMessage';
|
|||||||
import TableModel, { mergeTablesIntoModel } from 'app/core/table_model';
|
import TableModel, { mergeTablesIntoModel } from 'app/core/table_model';
|
||||||
import { DatasourceSrv } from 'app/features/plugins/datasource_srv';
|
import { DatasourceSrv } from 'app/features/plugins/datasource_srv';
|
||||||
|
|
||||||
|
import Panel from './Panel';
|
||||||
import QueryRows from './QueryRows';
|
import QueryRows from './QueryRows';
|
||||||
import Graph from './Graph';
|
import Graph from './Graph';
|
||||||
import Logs from './Logs';
|
import Logs from './Logs';
|
||||||
@ -127,6 +128,7 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> {
|
|||||||
range: initialRange,
|
range: initialRange,
|
||||||
showingGraph: true,
|
showingGraph: true,
|
||||||
showingLogs: true,
|
showingLogs: true,
|
||||||
|
showingStartPage: false,
|
||||||
showingTable: true,
|
showingTable: true,
|
||||||
supportsGraph: null,
|
supportsGraph: null,
|
||||||
supportsLogs: null,
|
supportsLogs: null,
|
||||||
@ -238,6 +240,7 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> {
|
|||||||
datasourceLoading: false,
|
datasourceLoading: false,
|
||||||
datasourceName: datasource.name,
|
datasourceName: datasource.name,
|
||||||
queries: nextQueries,
|
queries: nextQueries,
|
||||||
|
showingStartPage: Boolean(StartPage),
|
||||||
},
|
},
|
||||||
() => {
|
() => {
|
||||||
if (datasourceError === null) {
|
if (datasourceError === null) {
|
||||||
@ -329,10 +332,11 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> {
|
|||||||
onClickClear = () => {
|
onClickClear = () => {
|
||||||
this.queryExpressions = [''];
|
this.queryExpressions = [''];
|
||||||
this.setState(
|
this.setState(
|
||||||
{
|
prevState => ({
|
||||||
queries: ensureQueries(),
|
queries: ensureQueries(),
|
||||||
queryTransactions: [],
|
queryTransactions: [],
|
||||||
},
|
showingStartPage: Boolean(prevState.StartPage),
|
||||||
|
}),
|
||||||
this.saveState
|
this.saveState
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@ -563,6 +567,7 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
queryTransactions: nextQueryTransactions,
|
queryTransactions: nextQueryTransactions,
|
||||||
|
showingStartPage: false,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -789,16 +794,13 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> {
|
|||||||
range,
|
range,
|
||||||
showingGraph,
|
showingGraph,
|
||||||
showingLogs,
|
showingLogs,
|
||||||
|
showingStartPage,
|
||||||
showingTable,
|
showingTable,
|
||||||
supportsGraph,
|
supportsGraph,
|
||||||
supportsLogs,
|
supportsLogs,
|
||||||
supportsTable,
|
supportsTable,
|
||||||
} = this.state;
|
} = this.state;
|
||||||
const showingBoth = showingGraph && showingTable;
|
const graphHeight = showingGraph && showingTable ? '200px' : '400px';
|
||||||
const graphHeight = showingBoth ? '200px' : '400px';
|
|
||||||
const graphButtonActive = showingBoth || showingGraph ? 'active' : '';
|
|
||||||
const logsButtonActive = showingLogs ? 'active' : '';
|
|
||||||
const tableButtonActive = showingBoth || showingTable ? 'active' : '';
|
|
||||||
const exploreClass = split ? 'explore explore-split' : 'explore';
|
const exploreClass = split ? 'explore explore-split' : 'explore';
|
||||||
const selectedDatasource = datasource ? exploreDatasources.find(d => d.label === datasource.name) : undefined;
|
const selectedDatasource = datasource ? exploreDatasources.find(d => d.label === datasource.name) : undefined;
|
||||||
const graphRangeIntervals = getIntervals(graphRange, datasource, this.el ? this.el.offsetWidth : 0);
|
const graphRangeIntervals = getIntervals(graphRange, datasource, this.el ? this.el.offsetWidth : 0);
|
||||||
@ -823,8 +825,6 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> {
|
|||||||
)
|
)
|
||||||
: undefined;
|
: undefined;
|
||||||
const loading = queryTransactions.some(qt => !qt.done);
|
const loading = queryTransactions.some(qt => !qt.done);
|
||||||
const showStartPages = StartPage && queryTransactions.length === 0;
|
|
||||||
const viewModeCount = [supportsGraph, supportsLogs, supportsTable].filter(m => m).length;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={exploreClass} ref={this.getRef}>
|
<div className={exploreClass} ref={this.getRef}>
|
||||||
@ -913,55 +913,47 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> {
|
|||||||
/>
|
/>
|
||||||
<main className="m-t-2">
|
<main className="m-t-2">
|
||||||
<ErrorBoundary>
|
<ErrorBoundary>
|
||||||
{showStartPages && <StartPage onClickQuery={this.onClickQuery} />}
|
{showingStartPage && <StartPage onClickQuery={this.onClickQuery} />}
|
||||||
{!showStartPages && (
|
{!showingStartPage && (
|
||||||
<>
|
<>
|
||||||
{viewModeCount > 1 && (
|
{supportsGraph && (
|
||||||
<div className="result-options">
|
<Panel
|
||||||
{supportsGraph ? (
|
label="Graph"
|
||||||
<button className={`btn toggle-btn ${graphButtonActive}`} onClick={this.onClickGraphButton}>
|
isOpen={showingGraph}
|
||||||
Graph
|
loading={graphLoading}
|
||||||
</button>
|
onToggle={this.onClickGraphButton}
|
||||||
) : null}
|
>
|
||||||
{supportsTable ? (
|
|
||||||
<button className={`btn toggle-btn ${tableButtonActive}`} onClick={this.onClickTableButton}>
|
|
||||||
Table
|
|
||||||
</button>
|
|
||||||
) : null}
|
|
||||||
{supportsLogs ? (
|
|
||||||
<button className={`btn toggle-btn ${logsButtonActive}`} onClick={this.onClickLogsButton}>
|
|
||||||
Logs
|
|
||||||
</button>
|
|
||||||
) : null}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{supportsGraph &&
|
|
||||||
showingGraph && (
|
|
||||||
<Graph
|
<Graph
|
||||||
data={graphResult}
|
data={graphResult}
|
||||||
height={graphHeight}
|
height={graphHeight}
|
||||||
loading={graphLoading}
|
|
||||||
id={`explore-graph-${position}`}
|
id={`explore-graph-${position}`}
|
||||||
onChangeTime={this.onChangeTime}
|
onChangeTime={this.onChangeTime}
|
||||||
range={graphRange}
|
range={graphRange}
|
||||||
split={split}
|
split={split}
|
||||||
/>
|
/>
|
||||||
)}
|
</Panel>
|
||||||
{supportsTable && showingTable ? (
|
)}
|
||||||
<div className="panel-container m-t-2">
|
{supportsTable && (
|
||||||
|
<Panel
|
||||||
|
label="Table"
|
||||||
|
loading={tableLoading}
|
||||||
|
isOpen={showingTable}
|
||||||
|
onToggle={this.onClickTableButton}
|
||||||
|
>
|
||||||
<Table data={tableResult} loading={tableLoading} onClickCell={this.onClickTableCell} />
|
<Table data={tableResult} loading={tableLoading} onClickCell={this.onClickTableCell} />
|
||||||
</div>
|
</Panel>
|
||||||
) : null}
|
)}
|
||||||
{supportsLogs && showingLogs ? (
|
{supportsLogs && (
|
||||||
<Logs
|
<Panel label="Logs" loading={logsLoading} isOpen={showingLogs} onToggle={this.onClickLogsButton}>
|
||||||
data={logsResult}
|
<Logs
|
||||||
loading={logsLoading}
|
data={logsResult}
|
||||||
position={position}
|
loading={logsLoading}
|
||||||
onChangeTime={this.onChangeTime}
|
position={position}
|
||||||
range={range}
|
onChangeTime={this.onChangeTime}
|
||||||
/>
|
range={range}
|
||||||
) : null}
|
/>
|
||||||
|
</Panel>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</ErrorBoundary>
|
</ErrorBoundary>
|
||||||
|
@ -77,7 +77,6 @@ interface GraphProps {
|
|||||||
data: any[];
|
data: any[];
|
||||||
height?: string; // e.g., '200px'
|
height?: string; // e.g., '200px'
|
||||||
id?: string;
|
id?: string;
|
||||||
loading?: boolean;
|
|
||||||
range: RawTimeRange;
|
range: RawTimeRange;
|
||||||
split?: boolean;
|
split?: boolean;
|
||||||
size?: { width: number; height: number };
|
size?: { width: number; height: number };
|
||||||
@ -188,12 +187,11 @@ export class Graph extends PureComponent<GraphProps, GraphState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { height = '100px', id = 'graph', loading = false } = this.props;
|
const { height = '100px', id = 'graph' } = this.props;
|
||||||
const data = this.getGraphData();
|
const data = this.getGraphData();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="panel-container">
|
<>
|
||||||
{loading && <div className="explore-panel__loader" />}
|
|
||||||
{this.props.data &&
|
{this.props.data &&
|
||||||
this.props.data.length > MAX_NUMBER_OF_TIME_SERIES &&
|
this.props.data.length > MAX_NUMBER_OF_TIME_SERIES &&
|
||||||
!this.state.showAllTimeSeries && (
|
!this.state.showAllTimeSeries && (
|
||||||
@ -207,7 +205,7 @@ export class Graph extends PureComponent<GraphProps, GraphState> {
|
|||||||
)}
|
)}
|
||||||
<div id={id} className="explore-graph" style={{ height }} />
|
<div id={id} className="explore-graph" style={{ height }} />
|
||||||
<Legend data={data} />
|
<Legend data={data} />
|
||||||
</div>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -97,7 +97,7 @@ export default class Logs extends PureComponent<LogsProps, LogsState> {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="panel-container logs-options">
|
<div className="logs-options">
|
||||||
<div className="logs-controls">
|
<div className="logs-controls">
|
||||||
<Switch label="Timestamp" checked={showUtc} onChange={this.onChangeUtc} small />
|
<Switch label="Timestamp" checked={showUtc} onChange={this.onChangeUtc} small />
|
||||||
<Switch label="Local time" checked={showLocalTime} onChange={this.onChangeLocalTime} small />
|
<Switch label="Local time" checked={showLocalTime} onChange={this.onChangeLocalTime} small />
|
||||||
@ -116,33 +116,30 @@ export default class Logs extends PureComponent<LogsProps, LogsState> {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="panel-container">
|
<div className="logs-entries" style={logEntriesStyle}>
|
||||||
{loading && <div className="explore-panel__loader" />}
|
{hasData &&
|
||||||
<div className="logs-entries" style={logEntriesStyle}>
|
data.rows.map(row => (
|
||||||
{hasData &&
|
<Fragment key={row.key}>
|
||||||
data.rows.map(row => (
|
<div className={row.logLevel ? `logs-row-level logs-row-level-${row.logLevel}` : ''} />
|
||||||
<Fragment key={row.key}>
|
{showUtc && <div title={`Local: ${row.timeLocal} (${row.timeFromNow})`}>{row.timestamp}</div>}
|
||||||
<div className={row.logLevel ? `logs-row-level logs-row-level-${row.logLevel}` : ''} />
|
{showLocalTime && <div title={`${row.timestamp} (${row.timeFromNow})`}>{row.timeLocal}</div>}
|
||||||
{showUtc && <div title={`Local: ${row.timeLocal} (${row.timeFromNow})`}>{row.timestamp}</div>}
|
{showLabels && (
|
||||||
{showLocalTime && <div title={`${row.timestamp} (${row.timeFromNow})`}>{row.timeLocal}</div>}
|
<div className="max-width" title={row.labels}>
|
||||||
{showLabels && (
|
{row.labels}
|
||||||
<div className="max-width" title={row.labels}>
|
|
||||||
{row.labels}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<div>
|
|
||||||
<Highlighter
|
|
||||||
textToHighlight={row.entry}
|
|
||||||
searchWords={row.searchWords}
|
|
||||||
findChunks={findHighlightChunksInText}
|
|
||||||
highlightClassName="logs-row-match-highlight"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</Fragment>
|
)}
|
||||||
))}
|
<div>
|
||||||
</div>
|
<Highlighter
|
||||||
{!loading && !hasData && 'No data was returned.'}
|
textToHighlight={row.entry}
|
||||||
|
searchWords={row.searchWords}
|
||||||
|
findChunks={findHighlightChunksInText}
|
||||||
|
highlightClassName="logs-row-match-highlight"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Fragment>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
{!loading && !hasData && 'No data was returned.'}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
34
public/app/features/explore/Panel.tsx
Normal file
34
public/app/features/explore/Panel.tsx
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import React, { PureComponent } from 'react';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
isOpen: boolean;
|
||||||
|
label: string;
|
||||||
|
loading?: boolean;
|
||||||
|
onToggle: (isOpen: boolean) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class Panel extends PureComponent<Props> {
|
||||||
|
onClickToggle = () => this.props.onToggle(!this.props.isOpen);
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { isOpen, loading } = this.props;
|
||||||
|
const iconClass = isOpen ? 'fa fa-caret-up' : 'fa fa-caret-down';
|
||||||
|
const loaderClass = loading ? 'explore-panel__loader explore-panel__loader--active' : 'explore-panel__loader';
|
||||||
|
return (
|
||||||
|
<div className="explore-panel panel-container">
|
||||||
|
<div className="explore-panel__header" onClick={this.onClickToggle}>
|
||||||
|
<div className="explore-panel__header-buttons">
|
||||||
|
<span className={iconClass} />
|
||||||
|
</div>
|
||||||
|
<div className="explore-panel__header-label">{this.props.label}</div>
|
||||||
|
</div>
|
||||||
|
{isOpen && (
|
||||||
|
<div className="explore-panel__body">
|
||||||
|
<div className={loaderClass} />
|
||||||
|
{this.props.children}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -1,9 +1,7 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
exports[`Render should render component 1`] = `
|
exports[`Render should render component 1`] = `
|
||||||
<div
|
<Fragment>
|
||||||
className="panel-container"
|
|
||||||
>
|
|
||||||
<div
|
<div
|
||||||
className="explore-graph"
|
className="explore-graph"
|
||||||
id="graph"
|
id="graph"
|
||||||
@ -456,13 +454,11 @@ exports[`Render should render component 1`] = `
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</div>
|
</Fragment>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`Render should render component with disclaimer 1`] = `
|
exports[`Render should render component with disclaimer 1`] = `
|
||||||
<div
|
<Fragment>
|
||||||
className="panel-container"
|
|
||||||
>
|
|
||||||
<div
|
<div
|
||||||
className="time-series-disclaimer"
|
className="time-series-disclaimer"
|
||||||
>
|
>
|
||||||
@ -952,13 +948,11 @@ exports[`Render should render component with disclaimer 1`] = `
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</div>
|
</Fragment>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`Render should show query return no time series 1`] = `
|
exports[`Render should show query return no time series 1`] = `
|
||||||
<div
|
<Fragment>
|
||||||
className="panel-container"
|
|
||||||
>
|
|
||||||
<div
|
<div
|
||||||
className="explore-graph"
|
className="explore-graph"
|
||||||
id="graph"
|
id="graph"
|
||||||
@ -971,5 +965,5 @@ exports[`Render should show query return no time series 1`] = `
|
|||||||
<Legend
|
<Legend
|
||||||
data={Array []}
|
data={Array []}
|
||||||
/>
|
/>
|
||||||
</div>
|
</Fragment>
|
||||||
`;
|
`;
|
||||||
|
@ -173,6 +173,7 @@ export interface ExploreState {
|
|||||||
range: RawTimeRange;
|
range: RawTimeRange;
|
||||||
showingGraph: boolean;
|
showingGraph: boolean;
|
||||||
showingLogs: boolean;
|
showingLogs: boolean;
|
||||||
|
showingStartPage?: boolean;
|
||||||
showingTable: boolean;
|
showingTable: boolean;
|
||||||
supportsGraph: boolean | null;
|
supportsGraph: boolean | null;
|
||||||
supportsLogs: boolean | null;
|
supportsLogs: boolean | null;
|
||||||
|
@ -18,10 +18,39 @@
|
|||||||
margin-left: 15px;
|
margin-left: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Graph panel needs a bit extra padding at top
|
.explore-panel {
|
||||||
.panel-container {
|
margin-top: $panel-margin;
|
||||||
|
}
|
||||||
|
|
||||||
|
.explore-panel__body {
|
||||||
padding: $panel-padding;
|
padding: $panel-padding;
|
||||||
padding-top: 10px;
|
}
|
||||||
|
|
||||||
|
.explore-panel__header {
|
||||||
|
padding: $panel-padding;
|
||||||
|
padding-top: 5px;
|
||||||
|
padding-bottom: 0;
|
||||||
|
display: flex;
|
||||||
|
cursor: pointer;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
transition: all 0.1s linear;
|
||||||
|
}
|
||||||
|
|
||||||
|
.explore-panel__header:hover {
|
||||||
|
transform: translateY(-1px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.explore-panel__header-label {
|
||||||
|
font-weight: 500;
|
||||||
|
margin-right: $panel-margin;
|
||||||
|
font-size: $font-size-h6;
|
||||||
|
box-shadow: $text-shadow-faint;
|
||||||
|
}
|
||||||
|
|
||||||
|
.explore-panel__header-buttons {
|
||||||
|
margin-right: $panel-margin;
|
||||||
|
font-size: $font-size-lg;
|
||||||
|
line-height: $font-size-h6;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make sure wrap buttons around on small screens
|
// Make sure wrap buttons around on small screens
|
||||||
@ -91,11 +120,16 @@
|
|||||||
height: 2px;
|
height: 2px;
|
||||||
position: relative;
|
position: relative;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
background: $text-color-faint;
|
background: none;
|
||||||
margin: $panel-margin / 2;
|
margin: $panel-margin / 2;
|
||||||
|
transition: background-color 1s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.explore-panel__loader:after {
|
.explore-panel__loader--active {
|
||||||
|
background: $text-color-faint;
|
||||||
|
}
|
||||||
|
|
||||||
|
.explore-panel__loader--active:after {
|
||||||
content: ' ';
|
content: ' ';
|
||||||
display: block;
|
display: block;
|
||||||
width: 25%;
|
width: 25%;
|
||||||
@ -221,17 +255,18 @@
|
|||||||
|
|
||||||
.logs-controls {
|
.logs-controls {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
background-color: $page-bg;
|
||||||
|
padding: $panel-padding;
|
||||||
|
padding-top: 10px;
|
||||||
|
border-radius: $border-radius;
|
||||||
|
margin: 2*$panel-margin 0;
|
||||||
|
border: $panel-border;
|
||||||
|
|
||||||
> * {
|
> * {
|
||||||
margin-right: 1em;
|
margin-right: 1em;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.logs-options,
|
|
||||||
.logs-graph {
|
|
||||||
margin-bottom: $panel-margin;
|
|
||||||
}
|
|
||||||
|
|
||||||
.logs-meta {
|
.logs-meta {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
color: $text-color-weak;
|
color: $text-color-weak;
|
||||||
|
Loading…
Reference in New Issue
Block a user