Skip to content

Commit

Permalink
[7.x] [Metrics UI] Fix validating Metrics Explorer URL (#74311) (#74402)
Browse files Browse the repository at this point in the history
  • Loading branch information
Zacqary authored Aug 5, 2020
1 parent dc982bc commit a7fc193
Show file tree
Hide file tree
Showing 3 changed files with 140 additions and 89 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

import { omit } from 'lodash';
import { mapToUrlState } from './with_metrics_explorer_options_url_state';

describe('WithMetricsExplorerOptionsUrlState', () => {
describe('mapToUrlState', () => {
it('loads a valid URL state', () => {
expect(mapToUrlState(validState)).toEqual(validState);
});
it('discards invalid properties and loads valid properties into the URL', () => {
expect(mapToUrlState(invalidState)).toEqual(omit(invalidState, 'options'));
});
});
});

const validState = {
chartOptions: {
stack: false,
type: 'line',
yAxisMode: 'fromZero',
},
options: {
aggregation: 'avg',
filterQuery: '',
groupBy: ['host.hostname'],
metrics: [
{
aggregation: 'avg',
color: 'color0',
field: 'system.cpu.user.pct',
},
{
aggregation: 'avg',
color: 'color1',
field: 'system.load.1',
},
],
source: 'url',
},
timerange: {
from: 'now-1h',
interval: '>=10s',
to: 'now',
},
};

const invalidState = {
chartOptions: {
stack: false,
type: 'line',
yAxisMode: 'fromZero',
},
options: {
aggregation: 'avg',
filterQuery: '',
groupBy: ['host.hostname'],
metrics: 'this is the wrong data type',
source: 'url',
},
timerange: {
from: 'now-1h',
interval: '>=10s',
to: 'now',
},
};
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,17 @@
*/

import { set } from '@elastic/safer-lodash-set';
import { values } from 'lodash';
import React, { useContext, useMemo } from 'react';
import * as t from 'io-ts';
import { ThrowReporter } from 'io-ts/lib/ThrowReporter';
import { MetricsExplorerColor } from '../../../common/color_palette';
import { UrlStateContainer } from '../../utils/url_state';
import {
MetricsExplorerOptions,
MetricsExplorerOptionsContainer,
MetricsExplorerTimeOptions,
MetricsExplorerYAxisMode,
MetricsExplorerChartType,
MetricsExplorerChartOptions,
metricExplorerOptionsRT,
metricsExplorerChartOptionsRT,
metricsExplorerTimeOptionsRT,
} from '../../pages/metrics/metrics_explorer/hooks/use_metrics_explorer_options';

interface MetricsExplorerUrlState {
Expand Down Expand Up @@ -74,36 +72,7 @@ export const WithMetricsExplorerOptionsUrlState = () => {
};

function isMetricExplorerOptions(subject: any): subject is MetricsExplorerOptions {
const MetricRequired = t.type({
aggregation: t.string,
});

const MetricOptional = t.partial({
field: t.string,
rate: t.boolean,
color: t.keyof(
Object.fromEntries(values(MetricsExplorerColor).map((c) => [c, null])) as Record<string, null>
),
label: t.string,
});

const Metric = t.intersection([MetricRequired, MetricOptional]);

const OptionsRequired = t.type({
aggregation: t.string,
metrics: t.array(Metric),
});

const OptionsOptional = t.partial({
limit: t.number,
groupBy: t.string,
filterQuery: t.string,
source: t.string,
});

const Options = t.intersection([OptionsRequired, OptionsOptional]);

const result = Options.decode(subject);
const result = metricExplorerOptionsRT.decode(subject);

try {
ThrowReporter.report(result);
Expand All @@ -114,22 +83,7 @@ function isMetricExplorerOptions(subject: any): subject is MetricsExplorerOption
}

function isMetricExplorerChartOptions(subject: any): subject is MetricsExplorerChartOptions {
const ChartOptions = t.type({
yAxisMode: t.keyof(
Object.fromEntries(values(MetricsExplorerYAxisMode).map((v) => [v, null])) as Record<
string,
null
>
),
type: t.keyof(
Object.fromEntries(values(MetricsExplorerChartType).map((v) => [v, null])) as Record<
string,
null
>
),
stack: t.boolean,
});
const result = ChartOptions.decode(subject);
const result = metricsExplorerChartOptionsRT.decode(subject);

try {
ThrowReporter.report(result);
Expand All @@ -140,12 +94,7 @@ function isMetricExplorerChartOptions(subject: any): subject is MetricsExplorerC
}

function isMetricExplorerTimeOption(subject: any): subject is MetricsExplorerTimeOptions {
const TimeRange = t.type({
from: t.string,
to: t.string,
interval: t.string,
});
const result = TimeRange.decode(subject);
const result = metricsExplorerTimeOptionsRT.decode(subject);
try {
ThrowReporter.report(result);
return true;
Expand All @@ -154,7 +103,7 @@ function isMetricExplorerTimeOption(subject: any): subject is MetricsExplorerTim
}
}

const mapToUrlState = (value: any): MetricsExplorerUrlState | undefined => {
export const mapToUrlState = (value: any): MetricsExplorerUrlState | undefined => {
const finalState = {};
if (value) {
if (value.options && isMetricExplorerOptions(value.options)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,29 @@
* you may not use this file except in compliance with the Elastic License.
*/

import * as t from 'io-ts';
import { values } from 'lodash';
import createContainer from 'constate';
import { useState, useEffect, useMemo, Dispatch, SetStateAction } from 'react';
import { useAlertPrefillContext } from '../../../../alerting/use_alert_prefill';
import { MetricsExplorerColor } from '../../../../../common/color_palette';
import {
MetricsExplorerAggregation,
MetricsExplorerMetric,
} from '../../../../../common/http_api/metrics_explorer';

export type MetricsExplorerOptionsMetric = MetricsExplorerMetric & {
color?: MetricsExplorerColor;
label?: string;
};
import { metricsExplorerMetricRT } from '../../../../../common/http_api/metrics_explorer';

const metricsExplorerOptionsMetricRT = t.intersection([
metricsExplorerMetricRT,
t.partial({
rate: t.boolean,
color: t.keyof(
Object.fromEntries(values(MetricsExplorerColor).map((c) => [c, null])) as Record<
MetricsExplorerColor,
null
>
),
label: t.string,
}),
]);

export type MetricsExplorerOptionsMetric = t.TypeOf<typeof metricsExplorerOptionsMetricRT>;

export enum MetricsExplorerChartType {
line = 'line',
Expand All @@ -29,28 +39,50 @@ export enum MetricsExplorerYAxisMode {
auto = 'auto',
}

export interface MetricsExplorerChartOptions {
type: MetricsExplorerChartType;
yAxisMode: MetricsExplorerYAxisMode;
stack: boolean;
}

export interface MetricsExplorerOptions {
metrics: MetricsExplorerOptionsMetric[];
limit?: number;
groupBy?: string | string[];
filterQuery?: string;
aggregation: MetricsExplorerAggregation;
forceInterval?: boolean;
dropLastBucket?: boolean;
source?: string;
}

export interface MetricsExplorerTimeOptions {
from: string;
to: string;
interval: string;
}
export const metricsExplorerChartOptionsRT = t.type({
yAxisMode: t.keyof(
Object.fromEntries(values(MetricsExplorerYAxisMode).map((v) => [v, null])) as Record<
MetricsExplorerYAxisMode,
null
>
),
type: t.keyof(
Object.fromEntries(values(MetricsExplorerChartType).map((v) => [v, null])) as Record<
MetricsExplorerChartType,
null
>
),
stack: t.boolean,
});

export type MetricsExplorerChartOptions = t.TypeOf<typeof metricsExplorerChartOptionsRT>;

const metricExplorerOptionsRequiredRT = t.type({
aggregation: t.string,
metrics: t.array(metricsExplorerOptionsMetricRT),
});

const metricExplorerOptionsOptionalRT = t.partial({
limit: t.number,
groupBy: t.union([t.string, t.array(t.string)]),
filterQuery: t.string,
source: t.string,
forceInterval: t.boolean,
dropLastBucket: t.boolean,
});
export const metricExplorerOptionsRT = t.intersection([
metricExplorerOptionsRequiredRT,
metricExplorerOptionsOptionalRT,
]);

export type MetricsExplorerOptions = t.TypeOf<typeof metricExplorerOptionsRT>;

export const metricsExplorerTimeOptionsRT = t.type({
from: t.string,
to: t.string,
interval: t.string,
});
export type MetricsExplorerTimeOptions = t.TypeOf<typeof metricsExplorerTimeOptionsRT>;

export const DEFAULT_TIMERANGE: MetricsExplorerTimeOptions = {
from: 'now-1h',
Expand Down

0 comments on commit a7fc193

Please sign in to comment.