Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[7.9] [Metrics UI] Fix validating Metrics Explorer URL (#74311) #74403

Merged
merged 1 commit into from
Aug 5, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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