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 = (obj: T): obj is NonNullable => 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((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 => { 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('&')}`; };