prometheus/web/ui/react-app/src/utils/index.ts

213 lines
6.1 KiB
TypeScript
Raw Normal View History

import moment from 'moment-timezone';
import { PanelOptions, PanelType, PanelDefaultOptions } from '../pages/graph/Panel';
import { PanelMeta } from '../pages/graph/PanelList';
export const generateID = () => {
return `_${Math.random()
.toString(36)
.substr(2, 9)}`;
};
export const byEmptyString = (p: string) => p.length > 0;
export const isPresent = <T>(obj: T): obj is NonNullable<T> => obj !== null && obj !== undefined;
export const escapeHTML = (str: string): string => {
const entityMap: { [key: string]: string } = {
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
'"': '&quot;',
"'": '&#39;',
'/': '&#x2F;',
};
return String(str).replace(/[&<>"'/]/g, function(s) {
return entityMap[s];
});
};
export const metricToSeriesName = (labels: { [key: string]: string }) => {
if (labels === null) {
return 'scalar';
}
let tsName = (labels.__name__ || '') + '{';
const labelStrings: string[] = [];
for (const label in labels) {
if (label !== '__name__') {
labelStrings.push(label + '="' + labels[label] + '"');
}
}
tsName += labelStrings.join(', ') + '}';
return tsName;
};
const rangeUnits: { [unit: string]: number } = {
y: 60 * 60 * 24 * 365,
w: 60 * 60 * 24 * 7,
d: 60 * 60 * 24,
h: 60 * 60,
m: 60,
s: 1,
};
export function parseRange(rangeText: string): number | null {
const rangeRE = new RegExp('^([0-9]+)([ywdhms]+)$');
const matches = rangeText.match(rangeRE);
if (!matches || matches.length !== 3) {
return null;
}
const value = parseInt(matches[1]);
const unit = matches[2];
return value * rangeUnits[unit];
}
export function formatRange(range: number): string {
for (const unit of Object.keys(rangeUnits)) {
if (range % rangeUnits[unit] === 0) {
return range / rangeUnits[unit] + unit;
}
}
return range + 's';
}
export function parseTime(timeText: string): number {
return moment.utc(timeText).valueOf();
}
export function formatTime(time: number): string {
return moment.utc(time).format('YYYY-MM-DD HH:mm:ss');
}
export const now = (): number => moment().valueOf();
export const humanizeDuration = (milliseconds: number): string => {
const sign = milliseconds < 0 ? '-' : '';
const unsignedMillis = milliseconds < 0 ? -1 * milliseconds : milliseconds;
const duration = moment.duration(unsignedMillis, 'ms');
const ms = Math.floor(duration.milliseconds());
const s = Math.floor(duration.seconds());
const m = Math.floor(duration.minutes());
const h = Math.floor(duration.hours());
const d = Math.floor(duration.asDays());
if (d !== 0) {
return `${sign}${d}d ${h}h ${m}m ${s}s`;
}
if (h !== 0) {
return `${sign}${h}h ${m}m ${s}s`;
}
if (m !== 0) {
return `${sign}${m}m ${s}s`;
}
if (s !== 0) {
return `${sign}${s}.${ms}s`;
}
if (unsignedMillis > 0) {
return `${sign}${unsignedMillis.toFixed(3)}ms`;
}
return '0s';
};
export const formatRelative = (startStr: string, end: number): string => {
const start = parseTime(startStr);
if (start < 0) {
return 'Never';
}
return humanizeDuration(end - start);
};
const paramFormat = /^g\d+\..+=.+$/;
export const decodePanelOptionsFromQueryString = (query: string): PanelMeta[] => {
if (query === '') {
return [];
}
const urlParams = query.substring(1).split('&');
return urlParams.reduce<PanelMeta[]>((panels, urlParam, i) => {
const panelsCount = panels.length;
const prefix = `g${panelsCount}.`;
if (urlParam.startsWith(`${prefix}expr=`)) {
const prefixLen = prefix.length;
return [
...panels,
{
id: generateID(),
key: `${panelsCount}`,
options: urlParams.slice(i).reduce((opts, param) => {
return param.startsWith(prefix) && paramFormat.test(param)
? { ...opts, ...parseOption(param.substring(prefixLen)) }
: opts;
}, PanelDefaultOptions),
},
];
}
return panels;
}, []);
};
export const parseOption = (param: string): Partial<PanelOptions> => {
const [opt, val] = param.split('=');
const decodedValue = decodeURIComponent(val.replace(/\+/g, ' '));
switch (opt) {
case 'expr':
return { expr: decodedValue };
case 'tab':
return { type: decodedValue === '0' ? PanelType.Graph : PanelType.Table };
case 'stacked':
return { stacked: decodedValue === '1' };
case 'range_input':
const range = parseRange(decodedValue);
return isPresent(range) ? { range } : {};
case 'end_input':
case 'moment_input':
return { endTime: parseTime(decodedValue) };
case 'step_input':
const resolution = parseInt(decodedValue);
return resolution > 0 ? { resolution } : {};
}
return {};
};
export const formatParam = (key: string) => (paramName: string, value: number | string | boolean) => {
return `g${key}.${paramName}=${encodeURIComponent(value)}`;
};
export const toQueryString = ({ key, options }: PanelMeta) => {
const formatWithKey = formatParam(key);
const { expr, type, stacked, range, endTime, resolution } = options;
const time = isPresent(endTime) ? formatTime(endTime) : false;
const urlParams = [
formatWithKey('expr', expr),
formatWithKey('tab', type === PanelType.Graph ? 0 : 1),
formatWithKey('stacked', stacked ? 1 : 0),
formatWithKey('range_input', formatRange(range)),
time ? `${formatWithKey('end_input', time)}&${formatWithKey('moment_input', time)}` : '',
isPresent(resolution) ? formatWithKey('step_input', resolution) : '',
];
return urlParams.filter(byEmptyString).join('&');
};
export const encodePanelOptionsToQueryString = (panels: PanelMeta[]) => {
return `?${panels.map(toQueryString).join('&')}`;
};
export const createExpressionLink = (expr: string) => {
return `../graph?g0.expr=${encodeURIComponent(expr)}&g0.tab=1&g0.stacked=0&g0.range_input=1h`;
};
2020-02-03 06:14:25 -08:00
export const mapObjEntries = <T, key extends keyof T, Z>(
o: T,
cb: ([k, v]: [string, T[key]], i: number, arr: [string, T[key]][]) => Z
) => Object.entries(o).map(cb);
export const callAll = (...fns: Array<(...args: any) => void>) => (...args: any) => {
// eslint-disable-next-line prefer-spread
fns.filter(Boolean).forEach(fn => fn.apply(null, args));
};