Skip to content

Commit

Permalink
[7.17] Vega functional tests fail when run against ES 8.0 (#123134)
Browse files Browse the repository at this point in the history
* [vega] Handle removal of deprecated date histogram interval (#109090)

* [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]>

* Updates the VEGA docs for v8.0 (#112781)

* Update VEGA docs for v8.0

* Update docs/user/dashboard/vega-reference.asciidoc

Co-authored-by: Kaarina Tungseth <[email protected]>

Co-authored-by: Kaarina Tungseth <[email protected]>
Co-authored-by: Kibana Machine <[email protected]>

* [Vega] Replacing the 'interval' property should only happen for the date_histogram aggregation (#115001)

Co-authored-by: Kibana Machine <[email protected]>
Co-authored-by: Uladzislau Lasitsa <[email protected]>
Co-authored-by: Stratoula Kalafateli <[email protected]>
Co-authored-by: Kaarina Tungseth <[email protected]>
  • Loading branch information
5 people authored Jan 17, 2022
1 parent 339a331 commit dcd6a81
Show file tree
Hide file tree
Showing 11 changed files with 346 additions and 74 deletions.
2 changes: 2 additions & 0 deletions docs/user/dashboard/vega-reference.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,8 @@ Tokens include the following:
* `"%dashboard_context-filter_clause%"`: String replaced by an object containing filters
* `"%dashboard_context-must_not_clause%"`: String replaced by an object containing filters

NOTE: Vega supports the `interval` parameter, which is unsupported {es} 8.0.0 and later. To use intervals, use `fixed_interval` or `calendar_interval` instead.

For example, the following query counts the number of documents in a specific index:

[source,yaml]
Expand Down
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} />;
Loading

0 comments on commit dcd6a81

Please sign in to comment.