mirror of
https://github.com/prometheus/prometheus.git
synced 2025-03-05 20:59:13 -08:00
change the dynamic key with static id for react key prop
Signed-off-by: blalov <boyko.lalov@tick42.com>
This commit is contained in:
parent
23c0299d85
commit
f419fba40e
|
@ -7,42 +7,39 @@ import Panel, { PanelOptions, PanelDefaultOptions } from '../Panel';
|
||||||
import { decodePanelOptionsFromQueryString, encodePanelOptionsToQueryString } from '../utils/urlParams';
|
import { decodePanelOptionsFromQueryString, encodePanelOptionsToQueryString } from '../utils/urlParams';
|
||||||
import Checkbox from '../Checkbox';
|
import Checkbox from '../Checkbox';
|
||||||
import PathPrefixProps from '../PathPrefixProps';
|
import PathPrefixProps from '../PathPrefixProps';
|
||||||
|
import { generateID } from '../utils/func';
|
||||||
|
|
||||||
export type MetricGroup = { title: string; items: string[] };
|
export type MetricGroup = { title: string; items: string[] };
|
||||||
|
export type PanelMeta = { key: string; options: PanelOptions; id: string };
|
||||||
|
|
||||||
interface PanelListState {
|
interface PanelListState {
|
||||||
panels: {
|
panels: PanelMeta[];
|
||||||
key: string;
|
|
||||||
options: PanelOptions;
|
|
||||||
}[];
|
|
||||||
pastQueries: string[];
|
pastQueries: string[];
|
||||||
metricNames: string[];
|
metricNames: string[];
|
||||||
fetchMetricsError: string | null;
|
fetchMetricsError: string | null;
|
||||||
timeDriftError: string | null;
|
timeDriftError: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const initialPanel = {
|
||||||
|
id: generateID(),
|
||||||
|
key: '0',
|
||||||
|
options: PanelDefaultOptions,
|
||||||
|
};
|
||||||
|
|
||||||
class PanelList extends Component<RouteComponentProps & PathPrefixProps, PanelListState> {
|
class PanelList extends Component<RouteComponentProps & PathPrefixProps, PanelListState> {
|
||||||
private key = 0;
|
|
||||||
constructor(props: PathPrefixProps) {
|
constructor(props: PathPrefixProps) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
const urlPanels = decodePanelOptionsFromQueryString(window.location.search);
|
const urlPanels = decodePanelOptionsFromQueryString(window.location.search);
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
panels:
|
panels: urlPanels.length ? urlPanels : [initialPanel],
|
||||||
urlPanels.length !== 0
|
|
||||||
? urlPanels
|
|
||||||
: [
|
|
||||||
{
|
|
||||||
key: this.getKey(),
|
|
||||||
options: PanelDefaultOptions,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
pastQueries: [],
|
pastQueries: [],
|
||||||
metricNames: [],
|
metricNames: [],
|
||||||
fetchMetricsError: null,
|
fetchMetricsError: null,
|
||||||
timeDriftError: null,
|
timeDriftError: null,
|
||||||
};
|
};
|
||||||
|
!urlPanels.length && this.updateURL();
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
|
@ -84,8 +81,8 @@ class PanelList extends Component<RouteComponentProps & PathPrefixProps, PanelLi
|
||||||
|
|
||||||
window.onpopstate = () => {
|
window.onpopstate = () => {
|
||||||
const panels = decodePanelOptionsFromQueryString(window.location.search);
|
const panels = decodePanelOptionsFromQueryString(window.location.search);
|
||||||
if (panels.length !== 0) {
|
if (panels.length) {
|
||||||
this.setState({ panels: panels });
|
this.setState({ panels });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -123,46 +120,43 @@ class PanelList extends Component<RouteComponentProps & PathPrefixProps, PanelLi
|
||||||
this.updatePastQueries();
|
this.updatePastQueries();
|
||||||
};
|
};
|
||||||
|
|
||||||
getKey(): string {
|
handleOptionsChanged = (key: string, options: PanelOptions) => {
|
||||||
return (this.key++).toString();
|
const panels = this.state.panels.map(panel => {
|
||||||
|
return key === panel.key
|
||||||
|
? {
|
||||||
|
...panel,
|
||||||
|
options,
|
||||||
}
|
}
|
||||||
|
: panel;
|
||||||
handleOptionsChanged(key: string, opts: PanelOptions): void {
|
|
||||||
const newPanels = this.state.panels.map(p => {
|
|
||||||
if (key === p.key) {
|
|
||||||
return {
|
|
||||||
key: key,
|
|
||||||
options: opts,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return p;
|
|
||||||
});
|
});
|
||||||
this.setState({ panels: newPanels }, this.updateURL);
|
this.setState({ panels }, this.updateURL);
|
||||||
}
|
};
|
||||||
|
|
||||||
updateURL(): void {
|
updateURL() {
|
||||||
const query = encodePanelOptionsToQueryString(this.state.panels);
|
const query = encodePanelOptionsToQueryString(this.state.panels);
|
||||||
window.history.pushState({}, '', query);
|
window.history.pushState({}, '', query);
|
||||||
}
|
|
||||||
|
|
||||||
addPanel = (): void => {
|
|
||||||
const panels = this.state.panels.slice();
|
|
||||||
panels.push({
|
|
||||||
key: this.getKey(),
|
|
||||||
options: PanelDefaultOptions,
|
|
||||||
});
|
|
||||||
this.setState({ panels: panels }, this.updateURL);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
removePanel = (key: string): void => {
|
addPanel = () => {
|
||||||
const panels = this.state.panels.filter(panel => {
|
const { panels } = this.state;
|
||||||
return panel.key !== key;
|
const addedPanel = {
|
||||||
});
|
id: generateID(),
|
||||||
this.setState({ panels: panels }, this.updateURL);
|
key: `${panels.length}`,
|
||||||
|
options: PanelDefaultOptions,
|
||||||
|
};
|
||||||
|
this.setState({ panels: [...panels, addedPanel] }, this.updateURL);
|
||||||
|
};
|
||||||
|
|
||||||
|
removePanel = (key: string) => {
|
||||||
|
let newKey = 0;
|
||||||
|
const panels = this.state.panels.reduce<PanelMeta[]>((acc, panel) => {
|
||||||
|
return panel.key !== key ? [...acc, { ...panel, key: `${newKey++}` }] : acc;
|
||||||
|
}, []);
|
||||||
|
this.setState({ panels }, this.updateURL);
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { metricNames, pastQueries, timeDriftError, fetchMetricsError } = this.state;
|
const { metricNames, pastQueries, timeDriftError, fetchMetricsError, panels } = this.state;
|
||||||
const { pathPrefix } = this.props;
|
const { pathPrefix } = this.props;
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
@ -180,7 +174,7 @@ class PanelList extends Component<RouteComponentProps & PathPrefixProps, PanelLi
|
||||||
<Col>
|
<Col>
|
||||||
{timeDriftError && (
|
{timeDriftError && (
|
||||||
<Alert color="danger">
|
<Alert color="danger">
|
||||||
<strong>Warning:</strong> Error fetching server time: {this.state.timeDriftError}
|
<strong>Warning:</strong> Error fetching server time: {timeDriftError}
|
||||||
</Alert>
|
</Alert>
|
||||||
)}
|
)}
|
||||||
</Col>
|
</Col>
|
||||||
|
@ -189,18 +183,18 @@ class PanelList extends Component<RouteComponentProps & PathPrefixProps, PanelLi
|
||||||
<Col>
|
<Col>
|
||||||
{fetchMetricsError && (
|
{fetchMetricsError && (
|
||||||
<Alert color="danger">
|
<Alert color="danger">
|
||||||
<strong>Warning:</strong> Error fetching metrics list: {this.state.fetchMetricsError}
|
<strong>Warning:</strong> Error fetching metrics list: {fetchMetricsError}
|
||||||
</Alert>
|
</Alert>
|
||||||
)}
|
)}
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
{this.state.panels.map(p => (
|
{panels.map(({ id, options, key }) => (
|
||||||
<Panel
|
<Panel
|
||||||
onExecuteQuery={this.handleQueryHistory}
|
onExecuteQuery={this.handleQueryHistory}
|
||||||
key={p.key}
|
key={id}
|
||||||
options={p.options}
|
options={options}
|
||||||
onOptionsChanged={(opts: PanelOptions) => this.handleOptionsChanged(p.key, opts)}
|
onOptionsChanged={opts => this.handleOptionsChanged(key, opts)}
|
||||||
removePanel={() => this.removePanel(p.key)}
|
removePanel={() => this.removePanel(key)}
|
||||||
metricNames={metricNames}
|
metricNames={metricNames}
|
||||||
pastQueries={pastQueries}
|
pastQueries={pastQueries}
|
||||||
pathPrefix={pathPrefix}
|
pathPrefix={pathPrefix}
|
||||||
|
|
5
web/ui/react-app/src/utils/func.ts
Normal file
5
web/ui/react-app/src/utils/func.ts
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
export const generateID = () =>
|
||||||
|
'_' +
|
||||||
|
Math.random()
|
||||||
|
.toString(36)
|
||||||
|
.substr(2, 9);
|
|
@ -1,118 +1,89 @@
|
||||||
import { parseRange, parseTime, formatRange, formatTime } from './timeFormat';
|
import { parseRange, parseTime, formatRange, formatTime } from './timeFormat';
|
||||||
import { PanelOptions, PanelType, PanelDefaultOptions } from '../Panel';
|
import { PanelOptions, PanelType, PanelDefaultOptions } from '../Panel';
|
||||||
|
import { generateID } from './func';
|
||||||
|
import { PanelMeta } from '../pages/PanelList';
|
||||||
|
|
||||||
export function decodePanelOptionsFromQueryString(query: string): { key: string; options: PanelOptions }[] {
|
export const decodePanelOptionsFromQueryString = (query: string): PanelMeta[] => {
|
||||||
if (query === '') {
|
return query === '' ? [] : parseParams(query.substring(1).split('&'));
|
||||||
return [];
|
};
|
||||||
}
|
|
||||||
|
|
||||||
const params = query.substring(1).split('&');
|
const byParamFormat = (p: string) => /^g\d+\..+=.+$/.test(p);
|
||||||
return parseParams(params);
|
|
||||||
}
|
|
||||||
|
|
||||||
const paramFormat = /^g\d+\..+=.+$/;
|
|
||||||
|
|
||||||
interface IncompletePanelOptions {
|
|
||||||
expr?: string;
|
|
||||||
type?: PanelType;
|
|
||||||
range?: number;
|
|
||||||
endTime?: number | null;
|
|
||||||
resolution?: number | null;
|
|
||||||
stacked?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
function parseParams(params: string[]): { key: string; options: PanelOptions }[] {
|
|
||||||
const sortedParams = params
|
|
||||||
.filter(p => {
|
|
||||||
return paramFormat.test(p);
|
|
||||||
})
|
|
||||||
.sort();
|
|
||||||
|
|
||||||
const panelOpts: { key: string; options: PanelOptions }[] = [];
|
|
||||||
|
|
||||||
|
const parseParams = (params: string[]) => {
|
||||||
let key = 0;
|
let key = 0;
|
||||||
let options: IncompletePanelOptions = {};
|
return params
|
||||||
for (const p of sortedParams) {
|
.filter(byParamFormat)
|
||||||
const prefix = 'g' + key + '.';
|
.sort()
|
||||||
|
.reduce<PanelMeta[]>((panels, urlParam, i, sortedParams) => {
|
||||||
|
const prefix = `g${key}.`;
|
||||||
|
|
||||||
if (!p.startsWith(prefix)) {
|
if (urlParam.startsWith(`${prefix}expr=`)) {
|
||||||
panelOpts.push({
|
let options: Partial<PanelOptions> = {};
|
||||||
key: key.toString(),
|
|
||||||
|
for (let index = i; index < sortedParams.length; index++) {
|
||||||
|
const param = sortedParams[index];
|
||||||
|
if (!param.startsWith(prefix)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
options = { ...options, ...parseOption(param.substring(prefix.length)) };
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
...panels,
|
||||||
|
{
|
||||||
|
id: generateID(),
|
||||||
|
key: `${key++}`,
|
||||||
options: { ...PanelDefaultOptions, ...options },
|
options: { ...PanelDefaultOptions, ...options },
|
||||||
});
|
},
|
||||||
options = {};
|
];
|
||||||
key++;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
addParam(options, p.substring(prefix.length));
|
return panels;
|
||||||
}
|
}, []);
|
||||||
panelOpts.push({
|
};
|
||||||
key: key.toString(),
|
|
||||||
options: { ...PanelDefaultOptions, ...options },
|
|
||||||
});
|
|
||||||
|
|
||||||
return panelOpts;
|
const parseOption = (param: string): Partial<PanelOptions> => {
|
||||||
}
|
|
||||||
|
|
||||||
function addParam(opts: IncompletePanelOptions, param: string): void {
|
|
||||||
let [opt, val] = param.split('=');
|
let [opt, val] = param.split('=');
|
||||||
val = decodeURIComponent(val.replace(/\+/g, ' '));
|
val = decodeURIComponent(val.replace(/\+/g, ' '));
|
||||||
|
|
||||||
switch (opt) {
|
switch (opt) {
|
||||||
case 'expr':
|
case 'expr':
|
||||||
opts.expr = val;
|
return { expr: val };
|
||||||
break;
|
|
||||||
|
|
||||||
case 'tab':
|
case 'tab':
|
||||||
if (val === '0') {
|
return { type: val === '0' ? PanelType.Graph : PanelType.Table };
|
||||||
opts.type = PanelType.Graph;
|
|
||||||
} else {
|
|
||||||
opts.type = PanelType.Table;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'stacked':
|
case 'stacked':
|
||||||
opts.stacked = val === '1';
|
return { stacked: val === '1' };
|
||||||
break;
|
|
||||||
|
|
||||||
case 'range_input':
|
case 'range_input':
|
||||||
const range = parseRange(val);
|
const range = parseRange(val);
|
||||||
if (range !== null) {
|
return range ? { range } : {};
|
||||||
opts.range = range;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'end_input':
|
case 'end_input':
|
||||||
opts.endTime = parseTime(val);
|
case 'moment_input':
|
||||||
break;
|
return { endTime: parseTime(val) };
|
||||||
|
|
||||||
case 'step_input':
|
case 'step_input':
|
||||||
const res = parseInt(val);
|
const resolution = parseInt(val);
|
||||||
if (res > 0) {
|
return resolution ? { resolution } : {};
|
||||||
opts.resolution = res;
|
|
||||||
}
|
}
|
||||||
break;
|
return {};
|
||||||
|
};
|
||||||
|
|
||||||
case 'moment_input':
|
export const encodePanelOptionsToQueryString = (panels: PanelMeta[]) => {
|
||||||
opts.endTime = parseTime(val);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function encodePanelOptionsToQueryString(panels: { key: string; options: PanelOptions }[]): string {
|
|
||||||
const queryParams: string[] = [];
|
const queryParams: string[] = [];
|
||||||
|
|
||||||
panels.forEach(p => {
|
panels.forEach(({ key, options }) => {
|
||||||
const prefix = 'g' + p.key + '.';
|
const prefix = `g${key}.`;
|
||||||
const o = p.options;
|
const { expr, type, stacked, range, endTime, resolution } = options;
|
||||||
const panelParams: { [key: string]: string | undefined } = {
|
const panelParams: { [key: string]: string | undefined } = {
|
||||||
expr: o.expr,
|
expr,
|
||||||
tab: o.type === PanelType.Graph ? '0' : '1',
|
tab: type === PanelType.Graph ? '0' : '1',
|
||||||
stacked: o.stacked ? '1' : '0',
|
stacked: stacked ? '1' : '0',
|
||||||
range_input: formatRange(o.range),
|
range_input: formatRange(range),
|
||||||
end_input: o.endTime !== null ? formatTime(o.endTime) : undefined,
|
end_input: endTime ? formatTime(endTime) : undefined,
|
||||||
moment_input: o.endTime !== null ? formatTime(o.endTime) : undefined,
|
moment_input: endTime ? formatTime(endTime) : undefined,
|
||||||
step_input: o.resolution !== null ? o.resolution.toString() : undefined,
|
step_input: resolution ? resolution.toString() : undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
for (const o in panelParams) {
|
for (const o in panelParams) {
|
||||||
|
@ -124,4 +95,4 @@ export function encodePanelOptionsToQueryString(panels: { key: string; options:
|
||||||
});
|
});
|
||||||
|
|
||||||
return '?' + queryParams.join('&');
|
return '?' + queryParams.join('&');
|
||||||
}
|
};
|
||||||
|
|
Loading…
Reference in a new issue