mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Dashboard: Adds Logs Panel (alpha) as visualization option for Dashboards (#18641)
* WIP: intial commit * Switch: Adds tooltip * Refactor: Adds props to LogsPanelEditor * Refactor: Moves LogRowContextProvider to grafana/ui * Refactor: Moves LogRowContext and Alert to grafana/ui * Refactor: Moves LogLabelStats to grafana/ui * Refactor: Moves LogLabels and LogLabel to grafana/ui * Refactor: Moves LogMessageAnsi and ansicolor to grafana/ui * Refactor: Moves calculateFieldStats, LogsParsers and getParser to grafana/data * Refactor: Moves findHighlightChunksInText to grafana/data * Refactor: Moves LogRow to grafana/ui * Refactor: Moving ExploreGraphPanel to grafana/ui * Refactor: Copies Logs to grafana/ui * Refactor: Moves ToggleButtonGroup to grafana/ui * Refactor: Adds Logs to LogsPanel * Refactor: Moves styles to emotion * Feature: Adds LogsRows * Refactor: Introduces render limit * Styles: Moves styles to emotion * Styles: Moves styles to emotion * Styles: Moves styles to emotion * Styles: Moves styles to emotion * Refactor: Adds sorting to LogsPanelEditor * Tests: Adds tests for sorting * Refactor: Changes according to PR comments * Refactor: Changes according to PR comments * Refactor: Moves Logs and ExploreGraphPanel out of grafana/ui * Fix: Shows the Show context label again
This commit is contained in:
@@ -1,11 +1,10 @@
|
||||
import React from 'react';
|
||||
import { PanelData } from '@grafana/ui';
|
||||
import { PanelData, GraphSeriesToggler } from '@grafana/ui';
|
||||
import { GraphSeriesXY } from '@grafana/data';
|
||||
|
||||
import { getGraphSeriesModel } from './getGraphSeriesModel';
|
||||
import { Options, SeriesOptions } from './types';
|
||||
import { SeriesColorChangeHandler, SeriesAxisToggleHandler } from '@grafana/ui/src/components/Graph/GraphWithLegend';
|
||||
import { GraphSeriesToggler } from './GraphSeriesToggler';
|
||||
|
||||
interface GraphPanelControllerAPI {
|
||||
series: GraphSeriesXY[];
|
||||
|
||||
@@ -1,85 +0,0 @@
|
||||
import React from 'react';
|
||||
import { GraphSeriesXY } from '@grafana/data';
|
||||
import difference from 'lodash/difference';
|
||||
import isEqual from 'lodash/isEqual';
|
||||
|
||||
interface GraphSeriesTogglerAPI {
|
||||
onSeriesToggle: (label: string, event: React.MouseEvent<HTMLElement>) => void;
|
||||
toggledSeries: GraphSeriesXY[];
|
||||
}
|
||||
|
||||
interface GraphSeriesTogglerProps {
|
||||
children: (api: GraphSeriesTogglerAPI) => JSX.Element;
|
||||
series: GraphSeriesXY[];
|
||||
onHiddenSeriesChanged?: (hiddenSeries: string[]) => void;
|
||||
}
|
||||
|
||||
interface GraphSeriesTogglerState {
|
||||
hiddenSeries: string[];
|
||||
toggledSeries: GraphSeriesXY[];
|
||||
}
|
||||
|
||||
export class GraphSeriesToggler extends React.Component<GraphSeriesTogglerProps, GraphSeriesTogglerState> {
|
||||
constructor(props: GraphSeriesTogglerProps) {
|
||||
super(props);
|
||||
|
||||
this.onSeriesToggle = this.onSeriesToggle.bind(this);
|
||||
|
||||
this.state = {
|
||||
hiddenSeries: [],
|
||||
toggledSeries: props.series,
|
||||
};
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps: Readonly<GraphSeriesTogglerProps>) {
|
||||
const { series } = this.props;
|
||||
if (!isEqual(prevProps.series, series)) {
|
||||
this.setState({ hiddenSeries: [], toggledSeries: series });
|
||||
}
|
||||
}
|
||||
|
||||
onSeriesToggle(label: string, event: React.MouseEvent<HTMLElement>) {
|
||||
const { series, onHiddenSeriesChanged } = this.props;
|
||||
const { hiddenSeries } = this.state;
|
||||
|
||||
if (event.ctrlKey || event.metaKey || event.shiftKey) {
|
||||
// Toggling series with key makes the series itself to toggle
|
||||
const newHiddenSeries =
|
||||
hiddenSeries.indexOf(label) > -1
|
||||
? hiddenSeries.filter(series => series !== label)
|
||||
: hiddenSeries.concat([label]);
|
||||
|
||||
const toggledSeries = series.map(series => ({
|
||||
...series,
|
||||
isVisible: newHiddenSeries.indexOf(series.label) === -1,
|
||||
}));
|
||||
this.setState({ hiddenSeries: newHiddenSeries, toggledSeries }, () =>
|
||||
onHiddenSeriesChanged ? onHiddenSeriesChanged(newHiddenSeries) : undefined
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Toggling series with out key toggles all the series but the clicked one
|
||||
const allSeriesLabels = series.map(series => series.label);
|
||||
const newHiddenSeries =
|
||||
hiddenSeries.length + 1 === allSeriesLabels.length ? [] : difference(allSeriesLabels, [label]);
|
||||
const toggledSeries = series.map(series => ({
|
||||
...series,
|
||||
isVisible: newHiddenSeries.indexOf(series.label) === -1,
|
||||
}));
|
||||
|
||||
this.setState({ hiddenSeries: newHiddenSeries, toggledSeries }, () =>
|
||||
onHiddenSeriesChanged ? onHiddenSeriesChanged(newHiddenSeries) : undefined
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { children } = this.props;
|
||||
const { toggledSeries } = this.state;
|
||||
|
||||
return children({
|
||||
onSeriesToggle: this.onSeriesToggle,
|
||||
toggledSeries,
|
||||
});
|
||||
}
|
||||
}
|
||||
39
public/app/plugins/panel/logs/LogsPanel.tsx
Normal file
39
public/app/plugins/panel/logs/LogsPanel.tsx
Normal file
@@ -0,0 +1,39 @@
|
||||
import React from 'react';
|
||||
import { PanelProps, LogRows, CustomScrollbar } from '@grafana/ui';
|
||||
import { Options } from './types';
|
||||
import { LogsDedupStrategy } from '@grafana/data';
|
||||
import { dataFrameToLogsModel } from 'app/core/logs_model';
|
||||
import { sortLogsResult } from 'app/core/utils/explore';
|
||||
|
||||
interface LogsPanelProps extends PanelProps<Options> {}
|
||||
|
||||
export const LogsPanel: React.FunctionComponent<LogsPanelProps> = ({
|
||||
data,
|
||||
timeZone,
|
||||
options: { showTime, sortOrder },
|
||||
width,
|
||||
}) => {
|
||||
if (!data) {
|
||||
return (
|
||||
<div className="panel-empty">
|
||||
<p>No data found in response</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const newResults = data ? dataFrameToLogsModel(data.series, data.request.intervalMs) : null;
|
||||
const sortedNewResults = sortLogsResult(newResults, sortOrder);
|
||||
|
||||
return (
|
||||
<CustomScrollbar autoHide>
|
||||
<LogRows
|
||||
data={sortedNewResults}
|
||||
dedupStrategy={LogsDedupStrategy.none}
|
||||
highlighterExpressions={[]}
|
||||
showTime={showTime}
|
||||
showLabels={false}
|
||||
timeZone={timeZone}
|
||||
/>
|
||||
</CustomScrollbar>
|
||||
);
|
||||
};
|
||||
46
public/app/plugins/panel/logs/LogsPanelEditor.tsx
Normal file
46
public/app/plugins/panel/logs/LogsPanelEditor.tsx
Normal file
@@ -0,0 +1,46 @@
|
||||
// Libraries
|
||||
import React, { PureComponent } from 'react';
|
||||
import { PanelEditorProps, Switch, PanelOptionsGrid, PanelOptionsGroup, FormLabel, Select } from '@grafana/ui';
|
||||
|
||||
// Types
|
||||
import { Options } from './types';
|
||||
import { SortOrder } from 'app/core/utils/explore';
|
||||
import { SelectableValue } from '@grafana/data';
|
||||
|
||||
const sortOrderOptions = [
|
||||
{ value: SortOrder.Descending, label: 'Descending' },
|
||||
{ value: SortOrder.Ascending, label: 'Ascending' },
|
||||
];
|
||||
|
||||
export class LogsPanelEditor extends PureComponent<PanelEditorProps<Options>> {
|
||||
onToggleTime = () => {
|
||||
const { options, onOptionsChange } = this.props;
|
||||
const { showTime } = options;
|
||||
|
||||
onOptionsChange({ ...options, showTime: !showTime });
|
||||
};
|
||||
|
||||
onShowValuesChange = (item: SelectableValue<SortOrder>) => {
|
||||
const { options, onOptionsChange } = this.props;
|
||||
onOptionsChange({ ...options, sortOrder: item.value });
|
||||
};
|
||||
|
||||
render() {
|
||||
const { showTime, sortOrder } = this.props.options;
|
||||
const value = sortOrderOptions.filter(option => option.value === sortOrder)[0];
|
||||
|
||||
return (
|
||||
<>
|
||||
<PanelOptionsGrid>
|
||||
<PanelOptionsGroup title="Columns">
|
||||
<Switch label="Time" labelClass="width-10" checked={showTime} onChange={this.onToggleTime} />
|
||||
<div className="gf-form">
|
||||
<FormLabel>Order</FormLabel>
|
||||
<Select options={sortOrderOptions} value={value} onChange={this.onShowValuesChange} />
|
||||
</div>
|
||||
</PanelOptionsGroup>
|
||||
</PanelOptionsGrid>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
8
public/app/plugins/panel/logs/img/icn-logs-panel.svg
Normal file
8
public/app/plugins/panel/logs/img/icn-logs-panel.svg
Normal file
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="144px" height="144px" viewBox="0 0 144 144" version="1.1">
|
||||
<g id="surface736507">
|
||||
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(90.196078%,49.411765%,13.333333%);fill-opacity:1;" d="M 93 42 L 132 42 L 132 30 C 132 23.371094 126.628906 18 120 18 L 93 18 Z M 93 42 "/>
|
||||
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(92.54902%,94.117647%,94.509804%);fill-opacity:1;" d="M 120 18 L 42 18 C 35.371094 18 30 23.371094 30 30 L 30 114 C 30 120.628906 35.371094 126 42 126 L 96 126 C 102.628906 126 108 120.628906 108 114 L 108 30 C 108 23.371094 113.371094 18 120 18 Z M 120 18 "/>
|
||||
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(90.196078%,49.411765%,13.333333%);fill-opacity:1;" d="M 96 126 L 24 126 C 17.371094 126 12 120.628906 12 114 L 12 105 L 84 105 L 84 114 C 84 120.628906 89.371094 126 96 126 Z M 48 42 L 90 42 L 90 48 L 48 48 Z M 48 57 L 78 57 L 78 63 L 48 63 Z M 48 72 L 90 72 L 90 78 L 48 78 Z M 48 87 L 78 87 L 78 93 L 48 93 Z M 48 87 "/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
6
public/app/plugins/panel/logs/module.tsx
Normal file
6
public/app/plugins/panel/logs/module.tsx
Normal file
@@ -0,0 +1,6 @@
|
||||
import { PanelPlugin } from '@grafana/ui';
|
||||
import { Options, defaults } from './types';
|
||||
import { LogsPanel } from './LogsPanel';
|
||||
import { LogsPanelEditor } from './LogsPanelEditor';
|
||||
|
||||
export const plugin = new PanelPlugin<Options>(LogsPanel).setDefaults(defaults).setEditor(LogsPanelEditor);
|
||||
17
public/app/plugins/panel/logs/plugin.json
Normal file
17
public/app/plugins/panel/logs/plugin.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"type": "panel",
|
||||
"name": "Logs",
|
||||
"id": "logs",
|
||||
"state": "alpha",
|
||||
|
||||
"info": {
|
||||
"author": {
|
||||
"name": "Grafana Project",
|
||||
"url": "https://grafana.com"
|
||||
},
|
||||
"logos": {
|
||||
"small": "img/icn-logs-panel.svg",
|
||||
"large": "img/icn-logs-panel.svg"
|
||||
}
|
||||
}
|
||||
}
|
||||
11
public/app/plugins/panel/logs/types.ts
Normal file
11
public/app/plugins/panel/logs/types.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { SortOrder } from 'app/core/utils/explore';
|
||||
|
||||
export interface Options {
|
||||
showTime: boolean;
|
||||
sortOrder: SortOrder;
|
||||
}
|
||||
|
||||
export const defaults: Options = {
|
||||
showTime: true,
|
||||
sortOrder: SortOrder.Descending,
|
||||
};
|
||||
Reference in New Issue
Block a user