Skip to content

Commit

Permalink
[vega] Handle removal of deprecated date histogram interval (#109090)
Browse files Browse the repository at this point in the history
* [vega] Handle removal of deprecated date histogram interval

Fixes: #106352

* fix CI

* add deprecation_interval_info

* add test

* Update vega_info_message.tsx

* fix types

* Update es_query_parser.ts

* apply comments

* fix error

Co-authored-by: Kibana Machine <[email protected]>
Co-authored-by: Uladzislau Lasitsa <[email protected]>
  • Loading branch information
3 people authored Sep 22, 2021
1 parent a753f83 commit 61410a3
Show file tree
Hide file tree
Showing 11 changed files with 332 additions and 75 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,13 @@ describe('parseEsInterval', () => {
expect(parseEsInterval('1y')).toEqual({ value: 1, unit: 'y', type: 'calendar' });
});

it('should correctly parse an user-friendly intervals', () => {
expect(parseEsInterval('minute')).toEqual({ value: 1, unit: 'm', type: 'calendar' });
expect(parseEsInterval('hour')).toEqual({ value: 1, unit: 'h', type: 'calendar' });
expect(parseEsInterval('month')).toEqual({ value: 1, unit: 'M', type: 'calendar' });
expect(parseEsInterval('year')).toEqual({ value: 1, unit: 'y', type: 'calendar' });
});

it('should correctly parse an interval containing unit and multiple value', () => {
expect(parseEsInterval('250ms')).toEqual({ value: 250, unit: 'ms', type: 'fixed' });
expect(parseEsInterval('90s')).toEqual({ value: 90, unit: 's', type: 'fixed' });
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,37 @@
*/

import dateMath, { Unit } from '@elastic/datemath';

import { InvalidEsCalendarIntervalError } from './invalid_es_calendar_interval_error';
import { InvalidEsIntervalFormatError } from './invalid_es_interval_format_error';

const ES_INTERVAL_STRING_REGEX = new RegExp(
'^([1-9][0-9]*)\\s*(' + dateMath.units.join('|') + ')$'
);

export type ParsedInterval = ReturnType<typeof parseEsInterval>;

/** ES allows to work at user-friendly intervals.
* This method matches between these intervals and the intervals accepted by parseEsInterval.
* @internal **/
const mapToEquivalentInterval = (interval: string) => {
switch (interval) {
case 'minute':
return '1m';
case 'hour':
return '1h';
case 'day':
return '1d';
case 'week':
return '1w';
case 'month':
return '1M';
case 'quarter':
return '1q';
case 'year':
return '1y';
}
return interval;
};

/**
* Extracts interval properties from an ES interval string. Disallows unrecognized interval formats
* and fractional values. Converts some intervals from "calendar" to "fixed" when the number of
Expand All @@ -37,7 +58,7 @@ export type ParsedInterval = ReturnType<typeof parseEsInterval>;
*
*/
export function parseEsInterval(interval: string) {
const matches = String(interval).trim().match(ES_INTERVAL_STRING_REGEX);
const matches = String(mapToEquivalentInterval(interval)).trim().match(ES_INTERVAL_STRING_REGEX);

if (!matches) {
throw new InvalidEsIntervalFormatError(interval);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

import { shouldShowDeprecatedHistogramIntervalInfo } from './deprecated_interval_info';

describe('shouldShowDeprecatedHistogramIntervalInfo', () => {
test('should show deprecated histogram interval', () => {
expect(
shouldShowDeprecatedHistogramIntervalInfo({
data: {
url: {
body: {
aggs: {
test: {
date_histogram: {
interval: 'day',
},
},
},
},
},
},
})
).toBeTruthy();

expect(
shouldShowDeprecatedHistogramIntervalInfo({
data: [
{
url: {
body: {
aggs: {
test: {
date_histogram: {
interval: 'day',
},
},
},
},
},
},
{
url: {
body: {
aggs: {
test: {
date_histogram: {
calendar_interval: 'day',
},
},
},
},
},
},
],
})
).toBeTruthy();
});

test('should not show deprecated histogram interval', () => {
expect(
shouldShowDeprecatedHistogramIntervalInfo({
data: {
url: {
body: {
aggs: {
test: {
date_histogram: {
interval: { '%autointerval%': true },
},
},
},
},
},
},
})
).toBeFalsy();

expect(
shouldShowDeprecatedHistogramIntervalInfo({
data: {
url: {
body: {
aggs: {
test: {
auto_date_histogram: {
field: 'bytes',
},
},
},
},
},
},
})
).toBeFalsy();

expect(
shouldShowDeprecatedHistogramIntervalInfo({
data: [
{
url: {
body: {
aggs: {
test: {
date_histogram: {
calendar_interval: 'week',
},
},
},
},
},
},
{
url: {
body: {
aggs: {
test: {
date_histogram: {
fixed_interval: '23d',
},
},
},
},
},
},
],
})
).toBeFalsy();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

import React from 'react';
import { EuiCallOut, EuiButtonIcon } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import { VegaSpec } from '../data_model/types';
import { getDocLinks } from '../services';

import { BUCKET_TYPES } from '../../../../data/public';

export const DeprecatedHistogramIntervalInfo = () => (
<EuiCallOut
className="hide-for-sharing"
data-test-subj="deprecatedHistogramIntervalInfo"
size="s"
title={
<FormattedMessage
id="visTypeVega.deprecatedHistogramIntervalInfo.message"
defaultMessage="Combined 'interval' field has been deprecated in favor of two new,
explicit fields: 'calendar_interval' and 'fixed_interval'. {dateHistogramDoc}"
values={{
dateHistogramDoc: (
<EuiButtonIcon
iconType="popout"
href={getDocLinks().links.aggs.date_histogram}
target="_blank"
/>
),
}}
/>
}
iconType="help"
/>
);

export const shouldShowDeprecatedHistogramIntervalInfo = (spec: VegaSpec) => {
const data = Array.isArray(spec.data) ? spec?.data : [spec.data];

return data.some((dataItem = {}) => {
const aggs = dataItem.url?.body?.aggs ?? {};

return Object.keys(aggs).some((key) => {
const dateHistogram = aggs[key]?.[BUCKET_TYPES.DATE_HISTOGRAM] || {};
return 'interval' in dateHistogram && typeof dateHistogram.interval !== 'object';
});
});
};
Original file line number Diff line number Diff line change
Expand Up @@ -6,55 +6,37 @@
* Side Public License, v 1.
*/

import { parse } from 'hjson';
import React from 'react';
import { EuiCallOut, EuiLink } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import { Vis } from '../../../../visualizations/public';

function ExperimentalMapLayerInfo() {
const title = (
<FormattedMessage
id="visTypeVega.mapView.experimentalMapLayerInfo"
defaultMessage="Map layer is experimental and is not subject to the support SLA of official GA features.
For feedback, please create an issue in {githubLink}."
values={{
githubLink: (
<EuiLink
external
href="https://github.com/elastic/kibana/issues/new/choose"
target="_blank"
>
GitHub
</EuiLink>
),
}}
/>
);

return (
<EuiCallOut
className="hide-for-sharing"
data-test-subj="experimentalMapLayerInfo"
size="s"
title={title}
iconType="beaker"
/>
);
}
import type { VegaSpec } from '../data_model/types';

export const getInfoMessage = (vis: Vis) => {
if (vis.params.spec) {
try {
const spec = parse(vis.params.spec, { legacyRoot: false, keepWsc: true });

if (spec.config?.kibana?.type === 'map') {
return <ExperimentalMapLayerInfo />;
}
} catch (e) {
// spec is invalid
export const ExperimentalMapLayerInfo = () => (
<EuiCallOut
className="hide-for-sharing"
data-test-subj="experimentalMapLayerInfo"
size="s"
title={
<FormattedMessage
id="visTypeVega.mapView.experimentalMapLayerInfo"
defaultMessage="Map layer is experimental and is not subject to the support SLA of official GA features.
For feedback, please create an issue in {githubLink}."
values={{
githubLink: (
<EuiLink
external
href="https://github.com/elastic/kibana/issues/new/choose"
target="_blank"
>
GitHub
</EuiLink>
),
}}
/>
}
}
iconType="beaker"
/>
);

return null;
};
export const shouldShowMapLayerInfo = (spec: VegaSpec) => spec.config?.kibana?.type === 'map';
45 changes: 45 additions & 0 deletions src/plugins/vis_types/vega/public/components/vega_info_message.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

import React, { useMemo } from 'react';
import { parse } from 'hjson';
import { ExperimentalMapLayerInfo, shouldShowMapLayerInfo } from './experimental_map_vis_info';
import {
DeprecatedHistogramIntervalInfo,
shouldShowDeprecatedHistogramIntervalInfo,
} from './deprecated_interval_info';

import type { Vis } from '../../../../visualizations/public';
import type { VegaSpec } from '../data_model/types';

const parseSpec = (spec: string) => {
if (spec) {
try {
return parse(spec, { legacyRoot: false, keepWsc: true });
} catch (e) {
// spec is invalid
}
}
};

const InfoMessage = ({ spec }: { spec: string }) => {
const vegaSpec: VegaSpec = useMemo(() => parseSpec(spec), [spec]);

if (!vegaSpec) {
return null;
}

return (
<>
{shouldShowMapLayerInfo(vegaSpec) && <ExperimentalMapLayerInfo />}
{shouldShowDeprecatedHistogramIntervalInfo(vegaSpec) && <DeprecatedHistogramIntervalInfo />}
</>
);
};

export const getInfoMessage = (vis: Vis) => <InfoMessage spec={vis.params.spec} />;
Original file line number Diff line number Diff line change
Expand Up @@ -178,11 +178,11 @@ describe(`EsQueryParser.injectQueryContextVars`, () => {
);
test(
`%autointerval% = true`,
check({ interval: { '%autointerval%': true } }, { interval: `1h` }, ctxObj)
check({ interval: { '%autointerval%': true } }, { calendar_interval: `1h` }, ctxObj)
);
test(
`%autointerval% = 10`,
check({ interval: { '%autointerval%': 10 } }, { interval: `3h` }, ctxObj)
check({ interval: { '%autointerval%': 10 } }, { fixed_interval: `3h` }, ctxObj)
);
test(`%timefilter% = min`, check({ a: { '%timefilter%': 'min' } }, { a: rangeStart }));
test(`%timefilter% = max`, check({ a: { '%timefilter%': 'max' } }, { a: rangeEnd }));
Expand Down
Loading

0 comments on commit 61410a3

Please sign in to comment.