2020-01-14 10:34:48 -08:00
|
|
|
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 } = {
|
|
|
|
'&': '&',
|
|
|
|
'<': '<',
|
|
|
|
'>': '>',
|
|
|
|
'"': '"',
|
|
|
|
"'": ''',
|
|
|
|
'/': '/',
|
|
|
|
};
|
|
|
|
|
|
|
|
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('&')}`;
|
|
|
|
};
|
2020-01-27 01:27:43 -08:00
|
|
|
|
|
|
|
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));
|
|
|
|
};
|