diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index af010089e4892..d92725b233e3e 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -9,6 +9,7 @@ /x-pack/plugins/discover_enhanced/ @elastic/kibana-app /x-pack/plugins/lens/ @elastic/kibana-app /x-pack/plugins/graph/ @elastic/kibana-app +/x-pack/plugins/vis_type_timeseries_enhanced/ @elastic/kibana-app /src/plugins/advanced_settings/ @elastic/kibana-app /src/plugins/charts/ @elastic/kibana-app /src/plugins/discover/ @elastic/kibana-app diff --git a/docs/developer/plugin-list.asciidoc b/docs/developer/plugin-list.asciidoc index e89b6d86361c7..952dfa0c7ac62 100644 --- a/docs/developer/plugin-list.asciidoc +++ b/docs/developer/plugin-list.asciidoc @@ -554,6 +554,10 @@ in their infrastructure. |NOTE: This plugin contains implementation of URL drilldown. For drilldowns infrastructure code refer to ui_actions_enhanced plugin. +|{kib-repo}blob/{branch}/x-pack/plugins/vis_type_timeseries_enhanced/README.md[visTypeTimeseriesEnhanced] +|The vis_type_timeseries_enhanced plugin is the x-pack counterpart to the OSS vis_type_timeseries plugin. + + |{kib-repo}blob/{branch}/x-pack/plugins/watcher/README.md[watcher] |This plugins adopts some conventions in addition to or in place of conventions in Kibana (at the time of the plugin's creation): diff --git a/src/plugins/vis_type_timeseries/server/index.ts b/src/plugins/vis_type_timeseries/server/index.ts index 333ed0ff64fdb..1037dc81b2b17 100644 --- a/src/plugins/vis_type_timeseries/server/index.ts +++ b/src/plugins/vis_type_timeseries/server/index.ts @@ -43,7 +43,9 @@ export { AbstractSearchStrategy, ReqFacade, } from './lib/search_strategies/strategies/abstract_search_strategy'; -// @ts-ignore + +export { VisPayload } from '../common/types'; + export { DefaultSearchCapabilities } from './lib/search_strategies/default_search_capabilities'; export function plugin(initializerContext: PluginInitializerContext) { diff --git a/src/plugins/vis_type_timeseries/server/lib/get_fields.ts b/src/plugins/vis_type_timeseries/server/lib/get_fields.ts index dc49e280a2bb7..8f87318222f2b 100644 --- a/src/plugins/vis_type_timeseries/server/lib/get_fields.ts +++ b/src/plugins/vis_type_timeseries/server/lib/get_fields.ts @@ -38,7 +38,7 @@ export async function getFields( // removes the need to refactor many layers of dependencies on "req", and instead just augments the top // level object passed from here. The layers should be refactored fully at some point, but for now // this works and we are still using the New Platform services for these vis data portions. - const reqFacade: ReqFacade = { + const reqFacade: ReqFacade<{}> = { requestContext, ...request, framework, diff --git a/src/plugins/vis_type_timeseries/server/lib/get_vis_data.ts b/src/plugins/vis_type_timeseries/server/lib/get_vis_data.ts index 5eef2b53e2431..fcb66d2e12fd1 100644 --- a/src/plugins/vis_type_timeseries/server/lib/get_vis_data.ts +++ b/src/plugins/vis_type_timeseries/server/lib/get_vis_data.ts @@ -64,7 +64,7 @@ export function getVisData( // removes the need to refactor many layers of dependencies on "req", and instead just augments the top // level object passed from here. The layers should be refactored fully at some point, but for now // this works and we are still using the New Platform services for these vis data portions. - const reqFacade: ReqFacade = { + const reqFacade: ReqFacade = { requestContext, ...request, framework, diff --git a/src/plugins/vis_type_timeseries/server/lib/search_strategies/default_search_capabilities.test.js b/src/plugins/vis_type_timeseries/server/lib/search_strategies/default_search_capabilities.test.ts similarity index 90% rename from src/plugins/vis_type_timeseries/server/lib/search_strategies/default_search_capabilities.test.js rename to src/plugins/vis_type_timeseries/server/lib/search_strategies/default_search_capabilities.test.ts index b9b7759711567..a570e02ada8d1 100644 --- a/src/plugins/vis_type_timeseries/server/lib/search_strategies/default_search_capabilities.test.js +++ b/src/plugins/vis_type_timeseries/server/lib/search_strategies/default_search_capabilities.test.ts @@ -17,13 +17,15 @@ * under the License. */ import { DefaultSearchCapabilities } from './default_search_capabilities'; +import { ReqFacade } from './strategies/abstract_search_strategy'; +import { VisPayload } from '../../../common/types'; describe('DefaultSearchCapabilities', () => { - let defaultSearchCapabilities; - let req; + let defaultSearchCapabilities: DefaultSearchCapabilities; + let req: ReqFacade; beforeEach(() => { - req = {}; + req = {} as ReqFacade; defaultSearchCapabilities = new DefaultSearchCapabilities(req); }); @@ -45,13 +47,13 @@ describe('DefaultSearchCapabilities', () => { }); test('should return Search Timezone', () => { - defaultSearchCapabilities.request = { + defaultSearchCapabilities.request = ({ payload: { timerange: { timezone: 'UTC', }, }, - }; + } as unknown) as ReqFacade; expect(defaultSearchCapabilities.searchTimezone).toEqual('UTC'); }); diff --git a/src/plugins/vis_type_timeseries/server/lib/search_strategies/default_search_capabilities.js b/src/plugins/vis_type_timeseries/server/lib/search_strategies/default_search_capabilities.ts similarity index 69% rename from src/plugins/vis_type_timeseries/server/lib/search_strategies/default_search_capabilities.js rename to src/plugins/vis_type_timeseries/server/lib/search_strategies/default_search_capabilities.ts index 02a710fef897f..73b701379aee0 100644 --- a/src/plugins/vis_type_timeseries/server/lib/search_strategies/default_search_capabilities.js +++ b/src/plugins/vis_type_timeseries/server/lib/search_strategies/default_search_capabilities.ts @@ -16,40 +16,43 @@ * specific language governing permissions and limitations * under the License. */ +import { Unit } from '@elastic/datemath'; import { convertIntervalToUnit, parseInterval, getSuitableUnit, } from '../vis_data/helpers/unit_to_seconds'; import { RESTRICTIONS_KEYS } from '../../../common/ui_restrictions'; +import { ReqFacade } from './strategies/abstract_search_strategy'; +import { VisPayload } from '../../../common/types'; -const getTimezoneFromRequest = (request) => { +const getTimezoneFromRequest = (request: ReqFacade) => { return request.payload.timerange.timezone; }; export class DefaultSearchCapabilities { - constructor(request, fieldsCapabilities = {}) { - this.request = request; - this.fieldsCapabilities = fieldsCapabilities; - } + constructor( + public request: ReqFacade, + public fieldsCapabilities: Record = {} + ) {} - get defaultTimeInterval() { + public get defaultTimeInterval() { return null; } - get whiteListedMetrics() { + public get whiteListedMetrics() { return this.createUiRestriction(); } - get whiteListedGroupByFields() { + public get whiteListedGroupByFields() { return this.createUiRestriction(); } - get whiteListedTimerangeModes() { + public get whiteListedTimerangeModes() { return this.createUiRestriction(); } - get uiRestrictions() { + public get uiRestrictions() { return { [RESTRICTIONS_KEYS.WHITE_LISTED_METRICS]: this.whiteListedMetrics, [RESTRICTIONS_KEYS.WHITE_LISTED_GROUP_BY_FIELDS]: this.whiteListedGroupByFields, @@ -57,36 +60,36 @@ export class DefaultSearchCapabilities { }; } - get searchTimezone() { + public get searchTimezone() { return getTimezoneFromRequest(this.request); } - createUiRestriction(restrictionsObject) { + createUiRestriction(restrictionsObject?: Record) { return { '*': !restrictionsObject, ...(restrictionsObject || {}), }; } - parseInterval(interval) { + parseInterval(interval: string) { return parseInterval(interval); } - getSuitableUnit(intervalInSeconds) { + getSuitableUnit(intervalInSeconds: string | number) { return getSuitableUnit(intervalInSeconds); } - convertIntervalToUnit(intervalString, unit) { + convertIntervalToUnit(intervalString: string, unit: Unit) { const parsedInterval = this.parseInterval(intervalString); - if (parsedInterval.unit !== unit) { + if (parsedInterval?.unit !== unit) { return convertIntervalToUnit(intervalString, unit); } return parsedInterval; } - getValidTimeInterval(intervalString) { + getValidTimeInterval(intervalString: string) { // Default search capabilities doesn't have any restrictions for the interval string return intervalString; } diff --git a/src/plugins/vis_type_timeseries/server/lib/search_strategies/search_strategies_registry.test.ts b/src/plugins/vis_type_timeseries/server/lib/search_strategies/search_strategies_registry.test.ts index 66ea4f017dd90..4c3dcbd17bbd9 100644 --- a/src/plugins/vis_type_timeseries/server/lib/search_strategies/search_strategies_registry.test.ts +++ b/src/plugins/vis_type_timeseries/server/lib/search_strategies/search_strategies_registry.test.ts @@ -27,10 +27,10 @@ import { DefaultSearchCapabilities } from './default_search_capabilities'; class MockSearchStrategy extends AbstractSearchStrategy { checkForViability() { - return { + return Promise.resolve({ isViable: true, capabilities: {}, - }; + }); } } @@ -65,7 +65,7 @@ describe('SearchStrategyRegister', () => { }); test('should add a strategy if it is an instance of AbstractSearchStrategy', () => { - const anotherSearchStrategy = new MockSearchStrategy('es'); + const anotherSearchStrategy = new MockSearchStrategy(); const addedStrategies = registry.addStrategy(anotherSearchStrategy); expect(addedStrategies.length).toEqual(2); @@ -75,7 +75,7 @@ describe('SearchStrategyRegister', () => { test('should return a MockSearchStrategy instance', async () => { const req = {}; const indexPattern = '*'; - const anotherSearchStrategy = new MockSearchStrategy('es'); + const anotherSearchStrategy = new MockSearchStrategy(); registry.addStrategy(anotherSearchStrategy); const { searchStrategy, capabilities } = (await registry.getViableStrategy(req, indexPattern))!; diff --git a/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/abstract_search_strategy.ts b/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/abstract_search_strategy.ts index b1e21edf8b588..71461d319f2b6 100644 --- a/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/abstract_search_strategy.ts +++ b/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/abstract_search_strategy.ts @@ -46,16 +46,8 @@ export interface ReqFacade extends FakeRequest { getEsShardTimeout: () => Promise; } -export class AbstractSearchStrategy { - public indexType?: string; - public additionalParams: any; - - constructor(type?: string, additionalParams: any = {}) { - this.indexType = type; - this.additionalParams = additionalParams; - } - - async search(req: ReqFacade, bodies: any[], options = {}) { +export abstract class AbstractSearchStrategy { + async search(req: ReqFacade, bodies: any[], indexType?: string) { const requests: any[] = []; const { sessionId } = req.payload; @@ -64,15 +56,13 @@ export class AbstractSearchStrategy { req.requestContext .search!.search( { + indexType, params: { ...body, - ...this.additionalParams, }, - indexType: this.indexType, }, { sessionId, - ...options, } ) .toPromise() @@ -81,7 +71,18 @@ export class AbstractSearchStrategy { return Promise.all(requests); } - async getFieldsForWildcard(req: ReqFacade, indexPattern: string, capabilities: any) { + checkForViability( + req: ReqFacade, + indexPattern: string + ): Promise<{ isViable: boolean; capabilities: unknown }> { + throw new TypeError('Must override method'); + } + + async getFieldsForWildcard( + req: ReqFacade, + indexPattern: string, + capabilities?: unknown + ) { const { indexPatternsService } = req.pre; return await indexPatternsService!.getFieldsForWildcard({ @@ -89,11 +90,4 @@ export class AbstractSearchStrategy { fieldCapsOptions: { allow_no_indices: true }, }); } - - checkForViability( - req: ReqFacade, - indexPattern: string - ): { isViable: boolean; capabilities: any } { - throw new TypeError('Must override method'); - } } diff --git a/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/default_search_strategy.test.js b/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/default_search_strategy.test.ts similarity index 79% rename from src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/default_search_strategy.test.js rename to src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/default_search_strategy.test.ts index a9994ba3e1f75..d8ea6c9c8a526 100644 --- a/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/default_search_strategy.test.js +++ b/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/default_search_strategy.test.ts @@ -17,13 +17,15 @@ * under the License. */ import { DefaultSearchStrategy } from './default_search_strategy'; +import { ReqFacade } from './abstract_search_strategy'; +import { VisPayload } from '../../../../common/types'; describe('DefaultSearchStrategy', () => { - let defaultSearchStrategy; - let req; + let defaultSearchStrategy: DefaultSearchStrategy; + let req: ReqFacade; beforeEach(() => { - req = {}; + req = {} as ReqFacade; defaultSearchStrategy = new DefaultSearchStrategy(); }); @@ -34,8 +36,8 @@ describe('DefaultSearchStrategy', () => { expect(defaultSearchStrategy.getFieldsForWildcard).toBeDefined(); }); - test('should check a strategy for viability', () => { - const value = defaultSearchStrategy.checkForViability(req); + test('should check a strategy for viability', async () => { + const value = await defaultSearchStrategy.checkForViability(req); expect(value.isViable).toBe(true); expect(value.capabilities).toEqual({ diff --git a/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/default_search_strategy.js b/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/default_search_strategy.ts similarity index 82% rename from src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/default_search_strategy.js rename to src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/default_search_strategy.ts index 8e57c117637bf..e1f519456d373 100644 --- a/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/default_search_strategy.js +++ b/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/default_search_strategy.ts @@ -17,16 +17,17 @@ * under the License. */ -import { AbstractSearchStrategy } from './abstract_search_strategy'; +import { AbstractSearchStrategy, ReqFacade } from './abstract_search_strategy'; import { DefaultSearchCapabilities } from '../default_search_capabilities'; +import { VisPayload } from '../../../../common/types'; export class DefaultSearchStrategy extends AbstractSearchStrategy { name = 'default'; - checkForViability(req) { - return { + checkForViability(req: ReqFacade) { + return Promise.resolve({ isViable: true, capabilities: new DefaultSearchCapabilities(req), - }; + }); } } diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/get_bucket_size.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/get_bucket_size.js index 53f0b84b8ec3b..c021ba3cebc66 100644 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/get_bucket_size.js +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/get_bucket_size.js @@ -42,14 +42,18 @@ const calculateBucketData = (timeInterval, capabilities) => { } // Check decimal - if (parsedInterval.value % 1 !== 0) { + if (parsedInterval && parsedInterval.value % 1 !== 0) { if (parsedInterval.unit !== 'ms') { - const { value, unit } = convertIntervalToUnit( + const converted = convertIntervalToUnit( intervalString, ASCENDING_UNIT_ORDER[ASCENDING_UNIT_ORDER.indexOf(parsedInterval.unit) - 1] ); - intervalString = value + unit; + if (converted) { + intervalString = converted.value + converted.unit; + } + + intervalString = undefined; } else { intervalString = '1ms'; } diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/unit_to_seconds.test.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/unit_to_seconds.test.ts similarity index 86% rename from src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/unit_to_seconds.test.js rename to src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/unit_to_seconds.test.ts index 5b533178949f1..278e557209a21 100644 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/unit_to_seconds.test.js +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/unit_to_seconds.test.ts @@ -16,6 +16,7 @@ * specific language governing permissions and limitations * under the License. */ +import { Unit } from '@elastic/datemath'; import { getUnitValue, @@ -51,22 +52,13 @@ describe('unit_to_seconds', () => { })); test('should not parse "gm" interval (negative)', () => - expect(parseInterval('gm')).toEqual({ - value: undefined, - unit: undefined, - })); + expect(parseInterval('gm')).toBeUndefined()); test('should not parse "-1d" interval (negative)', () => - expect(parseInterval('-1d')).toEqual({ - value: undefined, - unit: undefined, - })); + expect(parseInterval('-1d')).toBeUndefined()); test('should not parse "M" interval (negative)', () => - expect(parseInterval('M')).toEqual({ - value: undefined, - unit: undefined, - })); + expect(parseInterval('M')).toBeUndefined()); }); describe('convertIntervalToUnit()', () => { @@ -95,16 +87,10 @@ describe('unit_to_seconds', () => { })); test('should not convert "30m" interval to "0" unit (positive)', () => - expect(convertIntervalToUnit('30m', 'o')).toEqual({ - value: undefined, - unit: undefined, - })); + expect(convertIntervalToUnit('30m', 'o' as Unit)).toBeUndefined()); test('should not convert "m" interval to "s" unit (positive)', () => - expect(convertIntervalToUnit('m', 's')).toEqual({ - value: undefined, - unit: undefined, - })); + expect(convertIntervalToUnit('m', 's')).toBeUndefined()); }); describe('getSuitableUnit()', () => { @@ -155,8 +141,5 @@ describe('unit_to_seconds', () => { expect(getSuitableUnit(stringValue)).toBeUndefined(); }); - - test('should return "undefined" in case of no input value(negative)', () => - expect(getSuitableUnit()).toBeUndefined()); }); }); diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/unit_to_seconds.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/unit_to_seconds.ts similarity index 60% rename from src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/unit_to_seconds.js rename to src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/unit_to_seconds.ts index be8f1741627ba..8950e05c85d4f 100644 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/unit_to_seconds.js +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/unit_to_seconds.ts @@ -16,12 +16,15 @@ * specific language governing permissions and limitations * under the License. */ -import { INTERVAL_STRING_RE } from '../../../../common/interval_regexp'; import { sortBy, isNumber } from 'lodash'; +import { Unit } from '@elastic/datemath'; + +/** @ts-ignore */ +import { INTERVAL_STRING_RE } from '../../../../common/interval_regexp'; export const ASCENDING_UNIT_ORDER = ['ms', 's', 'm', 'h', 'd', 'w', 'M', 'y']; -const units = { +const units: Record = { ms: 0.001, s: 1, m: 60, @@ -32,49 +35,53 @@ const units = { y: 86400 * 7 * 4 * 12, // Leap year? }; -const sortedUnits = sortBy(Object.keys(units), (key) => units[key]); +const sortedUnits = sortBy(Object.keys(units), (key: Unit) => units[key]); -export const parseInterval = (intervalString) => { - let value; - let unit; +interface ParsedInterval { + value: number; + unit: Unit; +} +export const parseInterval = (intervalString: string): ParsedInterval | undefined => { if (intervalString) { const matches = intervalString.match(INTERVAL_STRING_RE); if (matches) { - value = Number(matches[1]); - unit = matches[2]; + return { + value: Number(matches[1]), + unit: matches[2] as Unit, + }; } } - - return { value, unit }; }; -export const convertIntervalToUnit = (intervalString, newUnit) => { +export const convertIntervalToUnit = ( + intervalString: string, + newUnit: Unit +): ParsedInterval | undefined => { const parsedInterval = parseInterval(intervalString); - let value; - let unit; - if (parsedInterval.value && units[newUnit]) { - value = Number( - ((parsedInterval.value * units[parsedInterval.unit]) / units[newUnit]).toFixed(2) - ); - unit = newUnit; + if (parsedInterval && units[newUnit]) { + return { + value: Number( + ((parsedInterval.value * units[parsedInterval.unit!]) / units[newUnit]).toFixed(2) + ), + unit: newUnit, + }; } - - return { value, unit }; }; -export const getSuitableUnit = (intervalInSeconds) => +export const getSuitableUnit = (intervalInSeconds: string | number) => sortedUnits.find((key, index, array) => { - const nextUnit = array[index + 1]; + const nextUnit = array[index + 1] as Unit; const isValidInput = isNumber(intervalInSeconds) && intervalInSeconds > 0; const isLastItem = index + 1 === array.length; return ( isValidInput && - ((intervalInSeconds >= units[key] && intervalInSeconds < units[nextUnit]) || isLastItem) + ((intervalInSeconds >= units[key as Unit] && intervalInSeconds < units[nextUnit]) || + isLastItem) ); - }); + }) as Unit; -export const getUnitValue = (unit) => units[unit]; +export const getUnitValue = (unit: Unit) => units[unit]; diff --git a/x-pack/plugins/rollup/server/lib/search_strategies/register_rollup_search_strategy.test.js b/x-pack/plugins/rollup/server/lib/search_strategies/register_rollup_search_strategy.test.js deleted file mode 100644 index 8672a8b8f6849..0000000000000 --- a/x-pack/plugins/rollup/server/lib/search_strategies/register_rollup_search_strategy.test.js +++ /dev/null @@ -1,22 +0,0 @@ -/* - * 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 { registerRollupSearchStrategy } from './register_rollup_search_strategy'; - -describe('Register Rollup Search Strategy', () => { - let addSearchStrategy; - let getRollupService; - - beforeEach(() => { - addSearchStrategy = jest.fn().mockName('addSearchStrategy'); - getRollupService = jest.fn().mockName('getRollupService'); - }); - - test('should run initialization', () => { - registerRollupSearchStrategy(addSearchStrategy, getRollupService); - - expect(addSearchStrategy).toHaveBeenCalled(); - }); -}); diff --git a/x-pack/plugins/rollup/server/lib/search_strategies/register_rollup_search_strategy.ts b/x-pack/plugins/rollup/server/lib/search_strategies/register_rollup_search_strategy.ts deleted file mode 100644 index 22dafbb71d802..0000000000000 --- a/x-pack/plugins/rollup/server/lib/search_strategies/register_rollup_search_strategy.ts +++ /dev/null @@ -1,28 +0,0 @@ -/* - * 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 { ILegacyScopedClusterClient } from 'src/core/server'; -import { - DefaultSearchCapabilities, - AbstractSearchStrategy, - ReqFacade, -} from '../../../../../../src/plugins/vis_type_timeseries/server'; -import { getRollupSearchStrategy } from './rollup_search_strategy'; -import { getRollupSearchCapabilities } from './rollup_search_capabilities'; - -export const registerRollupSearchStrategy = ( - addSearchStrategy: (searchStrategy: any) => void, - getRollupService: (reg: ReqFacade) => Promise -) => { - const RollupSearchCapabilities = getRollupSearchCapabilities(DefaultSearchCapabilities); - const RollupSearchStrategy = getRollupSearchStrategy( - AbstractSearchStrategy, - RollupSearchCapabilities, - getRollupService - ); - - addSearchStrategy(new RollupSearchStrategy()); -}; diff --git a/x-pack/plugins/rollup/server/lib/search_strategies/rollup_search_capabilities.ts b/x-pack/plugins/rollup/server/lib/search_strategies/rollup_search_capabilities.ts deleted file mode 100644 index 354bf641114c7..0000000000000 --- a/x-pack/plugins/rollup/server/lib/search_strategies/rollup_search_capabilities.ts +++ /dev/null @@ -1,115 +0,0 @@ -/* - * 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 { get, has } from 'lodash'; -import { KibanaRequest } from 'src/core/server'; -import { leastCommonInterval, isCalendarInterval } from './lib/interval_helper'; - -export const getRollupSearchCapabilities = (DefaultSearchCapabilities: any) => - class RollupSearchCapabilities extends DefaultSearchCapabilities { - constructor( - req: KibanaRequest, - fieldsCapabilities: { [key: string]: any }, - rollupIndex: string - ) { - super(req, fieldsCapabilities); - - this.rollupIndex = rollupIndex; - this.availableMetrics = get(fieldsCapabilities, `${rollupIndex}.aggs`, {}); - } - - public get dateHistogram() { - const [dateHistogram] = Object.values(this.availableMetrics.date_histogram); - - return dateHistogram; - } - - public get defaultTimeInterval() { - return ( - this.dateHistogram.fixed_interval || - this.dateHistogram.calendar_interval || - /* - Deprecation: [interval] on [date_histogram] is deprecated, use [fixed_interval] or [calendar_interval] in the future. - We can remove the following line only for versions > 8.x - */ - this.dateHistogram.interval || - null - ); - } - - public get searchTimezone() { - return get(this.dateHistogram, 'time_zone', null); - } - - public get whiteListedMetrics() { - const baseRestrictions = this.createUiRestriction({ - count: this.createUiRestriction(), - }); - - const getFields = (fields: { [key: string]: any }) => - Object.keys(fields).reduce( - (acc, item) => ({ - ...acc, - [item]: true, - }), - this.createUiRestriction({}) - ); - - return Object.keys(this.availableMetrics).reduce( - (acc, item) => ({ - ...acc, - [item]: getFields(this.availableMetrics[item]), - }), - baseRestrictions - ); - } - - public get whiteListedGroupByFields() { - return this.createUiRestriction({ - everything: true, - terms: has(this.availableMetrics, 'terms'), - }); - } - - public get whiteListedTimerangeModes() { - return this.createUiRestriction({ - last_value: true, - }); - } - - getValidTimeInterval(userIntervalString: string) { - const parsedRollupJobInterval = this.parseInterval(this.defaultTimeInterval); - const inRollupJobUnit = this.convertIntervalToUnit( - userIntervalString, - parsedRollupJobInterval.unit - ); - - const getValidCalendarInterval = () => { - let unit = parsedRollupJobInterval.unit; - - if (inRollupJobUnit.value > parsedRollupJobInterval.value) { - const inSeconds = this.convertIntervalToUnit(userIntervalString, 's'); - - unit = this.getSuitableUnit(inSeconds.value); - } - - return { - value: 1, - unit, - }; - }; - - const getValidFixedInterval = () => ({ - value: leastCommonInterval(inRollupJobUnit.value, parsedRollupJobInterval.value), - unit: parsedRollupJobInterval.unit, - }); - - const { value, unit } = (isCalendarInterval(parsedRollupJobInterval) - ? getValidCalendarInterval - : getValidFixedInterval)(); - - return `${value}${unit}`; - } - }; diff --git a/x-pack/plugins/rollup/server/lib/search_strategies/rollup_search_strategy.ts b/x-pack/plugins/rollup/server/lib/search_strategies/rollup_search_strategy.ts deleted file mode 100644 index dcf6629d35397..0000000000000 --- a/x-pack/plugins/rollup/server/lib/search_strategies/rollup_search_strategy.ts +++ /dev/null @@ -1,94 +0,0 @@ -/* - * 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 { keyBy, isString } from 'lodash'; -import { ILegacyScopedClusterClient } from 'src/core/server'; -import { ReqFacade } from '../../../../../../src/plugins/vis_type_timeseries/server'; - -import { - mergeCapabilitiesWithFields, - getCapabilitiesForRollupIndices, -} from '../../../../../../src/plugins/data/server'; - -const getRollupIndices = (rollupData: { [key: string]: any }) => Object.keys(rollupData); - -const isIndexPatternContainsWildcard = (indexPattern: string) => indexPattern.includes('*'); -const isIndexPatternValid = (indexPattern: string) => - indexPattern && isString(indexPattern) && !isIndexPatternContainsWildcard(indexPattern); - -export const getRollupSearchStrategy = ( - AbstractSearchStrategy: any, - RollupSearchCapabilities: any, - getRollupService: (reg: ReqFacade) => Promise -) => - class RollupSearchStrategy extends AbstractSearchStrategy { - name = 'rollup'; - - constructor() { - super('rollup', { rest_total_hits_as_int: true }); - } - - async search(req: ReqFacade, bodies: any[], options = {}) { - const rollupService = await getRollupService(req); - const requests: any[] = []; - bodies.forEach((body) => { - requests.push( - rollupService.callAsCurrentUser('rollup.search', { - ...body, - rest_total_hits_as_int: true, - }) - ); - }); - return Promise.all(requests); - } - - async getRollupData(req: ReqFacade, indexPattern: string) { - const rollupService = await getRollupService(req); - return rollupService - .callAsCurrentUser('rollup.rollupIndexCapabilities', { - indexPattern, - }) - .catch(() => Promise.resolve({})); - } - - async checkForViability(req: ReqFacade, indexPattern: string) { - let isViable = false; - let capabilities = null; - - if (isIndexPatternValid(indexPattern)) { - const rollupData = await this.getRollupData(req, indexPattern); - const rollupIndices = getRollupIndices(rollupData); - - isViable = rollupIndices.length === 1; - - if (isViable) { - const [rollupIndex] = rollupIndices; - const fieldsCapabilities = getCapabilitiesForRollupIndices(rollupData); - - capabilities = new RollupSearchCapabilities(req, fieldsCapabilities, rollupIndex); - } - } - - return { - isViable, - capabilities, - }; - } - - async getFieldsForWildcard( - req: ReqFacade, - indexPattern: string, - { - fieldsCapabilities, - rollupIndex, - }: { fieldsCapabilities: { [key: string]: any }; rollupIndex: string } - ) { - const fields = await super.getFieldsForWildcard(req, indexPattern); - const fieldsFromFieldCapsApi = keyBy(fields, 'name'); - const rollupIndexCapabilities = fieldsCapabilities[rollupIndex].aggs; - - return mergeCapabilitiesWithFields(rollupIndexCapabilities, fieldsFromFieldCapsApi); - } - }; diff --git a/x-pack/plugins/rollup/server/plugin.ts b/x-pack/plugins/rollup/server/plugin.ts index 51920af7c8cbc..3c670f56c7d8f 100644 --- a/x-pack/plugins/rollup/server/plugin.ts +++ b/x-pack/plugins/rollup/server/plugin.ts @@ -24,7 +24,6 @@ import { import { i18n } from '@kbn/i18n'; import { schema } from '@kbn/config-schema'; -import { ReqFacade } from '../../../../src/plugins/vis_type_timeseries/server'; import { PLUGIN, CONFIG_ROLLUPS } from '../common'; import { Dependencies } from './types'; import { registerApiRoutes } from './routes'; @@ -32,7 +31,6 @@ import { License } from './services'; import { registerRollupUsageCollector } from './collectors'; import { rollupDataEnricher } from './rollup_data_enricher'; import { IndexPatternsFetcher } from './shared_imports'; -import { registerRollupSearchStrategy } from './lib/search_strategies'; import { elasticsearchJsPlugin } from './client/elasticsearch_rollup'; import { isEsError } from './shared_imports'; import { formatEsError } from './lib/format_es_error'; @@ -45,6 +43,7 @@ async function getCustomEsClient(getStartServices: CoreSetup['getStartServices'] const [core] = await getStartServices(); // Extend the elasticsearchJs client with additional endpoints. const esClientConfig = { plugins: [elasticsearchJsPlugin] }; + return core.elasticsearch.legacy.createClient('rollup', esClientConfig); } @@ -128,15 +127,6 @@ export class RollupPlugin implements Plugin { }, }); - if (visTypeTimeseries) { - const getRollupService = async (request: ReqFacade) => { - this.rollupEsClient = this.rollupEsClient ?? (await getCustomEsClient(getStartServices)); - return this.rollupEsClient.asScoped(request); - }; - const { addSearchStrategy } = visTypeTimeseries; - registerRollupSearchStrategy(addSearchStrategy, getRollupService); - } - if (usageCollection) { this.globalConfig$ .pipe(first()) diff --git a/x-pack/plugins/vis_type_timeseries_enhanced/README.md b/x-pack/plugins/vis_type_timeseries_enhanced/README.md new file mode 100644 index 0000000000000..33aa16d8574ae --- /dev/null +++ b/x-pack/plugins/vis_type_timeseries_enhanced/README.md @@ -0,0 +1,10 @@ +# vis_type_timeseries_enhanced + +The `vis_type_timeseries_enhanced` plugin is the x-pack counterpart to the OSS `vis_type_timeseries` plugin. + +It exists to provide Elastic-licensed services, or parts of services, which +enhance existing OSS functionality from `vis_type_timeseries`. + +Currently the `vis_type_timeseries_enhanced` plugin doesn't return any APIs which you can +consume directly. + diff --git a/x-pack/plugins/vis_type_timeseries_enhanced/kibana.json b/x-pack/plugins/vis_type_timeseries_enhanced/kibana.json new file mode 100644 index 0000000000000..4b296856c3f97 --- /dev/null +++ b/x-pack/plugins/vis_type_timeseries_enhanced/kibana.json @@ -0,0 +1,10 @@ +{ + "id": "visTypeTimeseriesEnhanced", + "version": "8.0.0", + "kibanaVersion": "kibana", + "server": true, + "ui": false, + "requiredPlugins": [ + "visTypeTimeseries" + ] +} diff --git a/x-pack/plugins/rollup/server/lib/search_strategies/index.ts b/x-pack/plugins/vis_type_timeseries_enhanced/server/index.ts similarity index 50% rename from x-pack/plugins/rollup/server/lib/search_strategies/index.ts rename to x-pack/plugins/vis_type_timeseries_enhanced/server/index.ts index 7db0b38ea29dd..d2665ec1e2813 100644 --- a/x-pack/plugins/rollup/server/lib/search_strategies/index.ts +++ b/x-pack/plugins/vis_type_timeseries_enhanced/server/index.ts @@ -4,4 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -export { registerRollupSearchStrategy } from './register_rollup_search_strategy'; +import { PluginInitializerContext } from 'src/core/server'; +import { VisTypeTimeseriesEnhanced } from './plugin'; + +export const plugin = (initializerContext: PluginInitializerContext) => + new VisTypeTimeseriesEnhanced(initializerContext); diff --git a/x-pack/plugins/vis_type_timeseries_enhanced/server/plugin.ts b/x-pack/plugins/vis_type_timeseries_enhanced/server/plugin.ts new file mode 100644 index 0000000000000..0598a691ab7c5 --- /dev/null +++ b/x-pack/plugins/vis_type_timeseries_enhanced/server/plugin.ts @@ -0,0 +1,33 @@ +/* + * 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 { Plugin, PluginInitializerContext, Logger, CoreSetup } from 'src/core/server'; +import { VisTypeTimeseriesSetup } from 'src/plugins/vis_type_timeseries/server'; +import { RollupSearchStrategy } from './search_strategies/rollup_search_strategy'; + +interface VisTypeTimeseriesEnhancedSetupDependencies { + visTypeTimeseries: VisTypeTimeseriesSetup; +} + +export class VisTypeTimeseriesEnhanced + implements Plugin { + private logger: Logger; + + constructor(initializerContext: PluginInitializerContext) { + this.logger = initializerContext.logger.get('vis_type_timeseries_enhanced'); + } + + public async setup( + core: CoreSetup, + { visTypeTimeseries }: VisTypeTimeseriesEnhancedSetupDependencies + ) { + this.logger.debug('Starting plugin'); + + visTypeTimeseries.addSearchStrategy(new RollupSearchStrategy()); + } + + public start() {} +} diff --git a/x-pack/plugins/rollup/server/lib/search_strategies/lib/interval_helper.test.js b/x-pack/plugins/vis_type_timeseries_enhanced/server/search_strategies/lib/interval_helper.test.ts similarity index 100% rename from x-pack/plugins/rollup/server/lib/search_strategies/lib/interval_helper.test.js rename to x-pack/plugins/vis_type_timeseries_enhanced/server/search_strategies/lib/interval_helper.test.ts diff --git a/x-pack/plugins/rollup/server/lib/search_strategies/lib/interval_helper.ts b/x-pack/plugins/vis_type_timeseries_enhanced/server/search_strategies/lib/interval_helper.ts similarity index 100% rename from x-pack/plugins/rollup/server/lib/search_strategies/lib/interval_helper.ts rename to x-pack/plugins/vis_type_timeseries_enhanced/server/search_strategies/lib/interval_helper.ts diff --git a/x-pack/plugins/rollup/server/lib/search_strategies/rollup_search_capabilities.test.js b/x-pack/plugins/vis_type_timeseries_enhanced/server/search_strategies/rollup_search_capabilities.test.ts similarity index 77% rename from x-pack/plugins/rollup/server/lib/search_strategies/rollup_search_capabilities.test.js rename to x-pack/plugins/vis_type_timeseries_enhanced/server/search_strategies/rollup_search_capabilities.test.ts index 977601247594f..6c30895635fe5 100644 --- a/x-pack/plugins/rollup/server/lib/search_strategies/rollup_search_capabilities.test.js +++ b/x-pack/plugins/vis_type_timeseries_enhanced/server/search_strategies/rollup_search_capabilities.test.ts @@ -3,27 +3,21 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { getRollupSearchCapabilities } from './rollup_search_capabilities'; +import { Unit } from '@elastic/datemath'; +import { RollupSearchCapabilities } from './rollup_search_capabilities'; -class DefaultSearchCapabilities { - constructor(request, fieldsCapabilities = {}) { - this.fieldsCapabilities = fieldsCapabilities; - this.parseInterval = jest.fn((interval) => interval); - } -} +import { ReqFacade, VisPayload } from '../../../../../src/plugins/vis_type_timeseries/server'; describe('Rollup Search Capabilities', () => { const testTimeZone = 'time_zone'; const testInterval = '10s'; const rollupIndex = 'rollupIndex'; - const request = {}; + const request = ({} as unknown) as ReqFacade; - let RollupSearchCapabilities; - let fieldsCapabilities; - let rollupSearchCaps; + let fieldsCapabilities: Record; + let rollupSearchCaps: RollupSearchCapabilities; beforeEach(() => { - RollupSearchCapabilities = getRollupSearchCapabilities(DefaultSearchCapabilities); fieldsCapabilities = { [rollupIndex]: { aggs: { @@ -41,7 +35,6 @@ describe('Rollup Search Capabilities', () => { }); test('should create instance of RollupSearchRequest', () => { - expect(rollupSearchCaps).toBeInstanceOf(DefaultSearchCapabilities); expect(rollupSearchCaps.fieldsCapabilities).toBe(fieldsCapabilities); expect(rollupSearchCaps.rollupIndex).toBe(rollupIndex); }); @@ -55,9 +48,9 @@ describe('Rollup Search Capabilities', () => { }); describe('getValidTimeInterval', () => { - let rollupJobInterval; - let userInterval; - let getSuitableUnit; + let rollupJobInterval: { value: number; unit: Unit }; + let userInterval: { value: number; unit: Unit }; + let getSuitableUnit: Unit; beforeEach(() => { rollupSearchCaps.parseInterval = jest @@ -81,7 +74,7 @@ describe('Rollup Search Capabilities', () => { getSuitableUnit = 'd'; - expect(rollupSearchCaps.getValidTimeInterval()).toBe('1d'); + expect(rollupSearchCaps.getValidTimeInterval('')).toBe('1d'); }); test('should return 1w as common interval for 7d(user interval) and 1d(rollup interval) - calendar intervals', () => { @@ -96,7 +89,7 @@ describe('Rollup Search Capabilities', () => { getSuitableUnit = 'w'; - expect(rollupSearchCaps.getValidTimeInterval()).toBe('1w'); + expect(rollupSearchCaps.getValidTimeInterval('')).toBe('1w'); }); test('should return 1w as common interval for 1d(user interval) and 1w(rollup interval) - calendar intervals', () => { @@ -111,7 +104,7 @@ describe('Rollup Search Capabilities', () => { getSuitableUnit = 'w'; - expect(rollupSearchCaps.getValidTimeInterval()).toBe('1w'); + expect(rollupSearchCaps.getValidTimeInterval('')).toBe('1w'); }); test('should return 2y as common interval for 0.1y(user interval) and 2y(rollup interval) - fixed intervals', () => { @@ -124,7 +117,7 @@ describe('Rollup Search Capabilities', () => { unit: 'y', }; - expect(rollupSearchCaps.getValidTimeInterval()).toBe('2y'); + expect(rollupSearchCaps.getValidTimeInterval('')).toBe('2y'); }); test('should return 3h as common interval for 2h(user interval) and 3h(rollup interval) - fixed intervals', () => { @@ -137,7 +130,7 @@ describe('Rollup Search Capabilities', () => { unit: 'h', }; - expect(rollupSearchCaps.getValidTimeInterval()).toBe('3h'); + expect(rollupSearchCaps.getValidTimeInterval('')).toBe('3h'); }); test('should return 6m as common interval for 4m(user interval) and 3m(rollup interval) - fixed intervals', () => { @@ -150,7 +143,7 @@ describe('Rollup Search Capabilities', () => { unit: 'm', }; - expect(rollupSearchCaps.getValidTimeInterval()).toBe('6m'); + expect(rollupSearchCaps.getValidTimeInterval('')).toBe('6m'); }); }); }); diff --git a/x-pack/plugins/vis_type_timeseries_enhanced/server/search_strategies/rollup_search_capabilities.ts b/x-pack/plugins/vis_type_timeseries_enhanced/server/search_strategies/rollup_search_capabilities.ts new file mode 100644 index 0000000000000..015a371bd2a35 --- /dev/null +++ b/x-pack/plugins/vis_type_timeseries_enhanced/server/search_strategies/rollup_search_capabilities.ts @@ -0,0 +1,123 @@ +/* + * 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 { get, has } from 'lodash'; +import { leastCommonInterval, isCalendarInterval } from './lib/interval_helper'; + +import { + ReqFacade, + DefaultSearchCapabilities, + VisPayload, +} from '../../../../../src/plugins/vis_type_timeseries/server'; + +export class RollupSearchCapabilities extends DefaultSearchCapabilities { + rollupIndex: string; + availableMetrics: Record; + + constructor( + req: ReqFacade, + fieldsCapabilities: Record, + rollupIndex: string + ) { + super(req, fieldsCapabilities); + + this.rollupIndex = rollupIndex; + this.availableMetrics = get(fieldsCapabilities, `${rollupIndex}.aggs`, {}); + } + + public get dateHistogram() { + const [dateHistogram] = Object.values(this.availableMetrics.date_histogram); + + return dateHistogram; + } + + public get defaultTimeInterval() { + return ( + this.dateHistogram.fixed_interval || + this.dateHistogram.calendar_interval || + /* + Deprecation: [interval] on [date_histogram] is deprecated, use [fixed_interval] or [calendar_interval] in the future. + We can remove the following line only for versions > 8.x + */ + this.dateHistogram.interval || + null + ); + } + + public get searchTimezone() { + return get(this.dateHistogram, 'time_zone', null); + } + + public get whiteListedMetrics() { + const baseRestrictions = this.createUiRestriction({ + count: this.createUiRestriction(), + }); + + const getFields = (fields: { [key: string]: any }) => + Object.keys(fields).reduce( + (acc, item) => ({ + ...acc, + [item]: true, + }), + this.createUiRestriction({}) + ); + + return Object.keys(this.availableMetrics).reduce( + (acc, item) => ({ + ...acc, + [item]: getFields(this.availableMetrics[item]), + }), + baseRestrictions + ); + } + + public get whiteListedGroupByFields() { + return this.createUiRestriction({ + everything: true, + terms: has(this.availableMetrics, 'terms'), + }); + } + + public get whiteListedTimerangeModes() { + return this.createUiRestriction({ + last_value: true, + }); + } + + getValidTimeInterval(userIntervalString: string) { + const parsedRollupJobInterval = this.parseInterval(this.defaultTimeInterval); + const inRollupJobUnit = this.convertIntervalToUnit( + userIntervalString, + parsedRollupJobInterval!.unit + ); + + const getValidCalendarInterval = () => { + let unit = parsedRollupJobInterval!.unit; + + if (inRollupJobUnit!.value > parsedRollupJobInterval!.value) { + const inSeconds = this.convertIntervalToUnit(userIntervalString, 's'); + if (inSeconds?.value) { + unit = this.getSuitableUnit(inSeconds.value); + } + } + + return { + value: 1, + unit, + }; + }; + + const getValidFixedInterval = () => ({ + value: leastCommonInterval(inRollupJobUnit?.value, parsedRollupJobInterval?.value), + unit: parsedRollupJobInterval!.unit, + }); + + const { value, unit } = (isCalendarInterval(parsedRollupJobInterval!) + ? getValidCalendarInterval + : getValidFixedInterval)(); + + return `${value}${unit}`; + } +} diff --git a/x-pack/plugins/rollup/server/lib/search_strategies/rollup_search_strategy.test.js b/x-pack/plugins/vis_type_timeseries_enhanced/server/search_strategies/rollup_search_strategy.test.ts similarity index 56% rename from x-pack/plugins/rollup/server/lib/search_strategies/rollup_search_strategy.test.js rename to x-pack/plugins/vis_type_timeseries_enhanced/server/search_strategies/rollup_search_strategy.test.ts index f3da7ed3fdd17..ec6c91b616f5b 100644 --- a/x-pack/plugins/rollup/server/lib/search_strategies/rollup_search_strategy.test.js +++ b/x-pack/plugins/vis_type_timeseries_enhanced/server/search_strategies/rollup_search_strategy.test.ts @@ -3,15 +3,35 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { getRollupSearchStrategy } from './rollup_search_strategy'; +import { RollupSearchStrategy } from './rollup_search_strategy'; +import type { ReqFacade, VisPayload } from '../../../../../src/plugins/vis_type_timeseries/server'; + +jest.mock('../../../../../src/plugins/vis_type_timeseries/server', () => { + const actual = jest.requireActual('../../../../../src/plugins/vis_type_timeseries/server'); + class AbstractSearchStrategyMock { + getFieldsForWildcard() { + return [ + { + name: 'day_of_week.terms.value', + type: 'object', + esTypes: ['object'], + searchable: false, + aggregatable: false, + }, + ]; + } + } + + return { + ...actual, + AbstractSearchStrategy: AbstractSearchStrategyMock, + }; +}); describe('Rollup Search Strategy', () => { - let RollupSearchStrategy; - let RollupSearchCapabilities; - let callWithRequest; - let rollupResolvedData; + let rollupResolvedData: Promise; - const request = { + const request = ({ requestContext: { core: { elasticsearch: { @@ -25,41 +45,9 @@ describe('Rollup Search Strategy', () => { }, }, }, - }; - const getRollupService = jest.fn().mockImplementation(() => { - return { - callAsCurrentUser: async () => { - return rollupResolvedData; - }, - }; - }); - const indexPattern = 'indexPattern'; + } as unknown) as ReqFacade; - beforeEach(() => { - class AbstractSearchStrategy { - getCallWithRequestInstance = jest.fn(() => callWithRequest); - - getFieldsForWildcard() { - return [ - { - name: 'day_of_week.terms.value', - type: 'object', - esTypes: ['object'], - searchable: false, - aggregatable: false, - }, - ]; - } - } - - RollupSearchCapabilities = jest.fn(() => 'capabilities'); - - RollupSearchStrategy = getRollupSearchStrategy( - AbstractSearchStrategy, - RollupSearchCapabilities, - getRollupService - ); - }); + const indexPattern = 'indexPattern'; test('should create instance of RollupSearchRequest', () => { const rollupSearchStrategy = new RollupSearchStrategy(); @@ -68,68 +56,66 @@ describe('Rollup Search Strategy', () => { }); describe('checkForViability', () => { - let rollupSearchStrategy; + let rollupSearchStrategy: RollupSearchStrategy; const rollupIndex = 'rollupIndex'; beforeEach(() => { rollupSearchStrategy = new RollupSearchStrategy(); - rollupSearchStrategy.getRollupData = jest.fn(() => ({ - [rollupIndex]: { - rollup_jobs: [ - { - job_id: 'test', - rollup_index: rollupIndex, - index_pattern: 'kibana*', - fields: { - order_date: [ - { - agg: 'date_histogram', - delay: '1m', - interval: '1m', - time_zone: 'UTC', - }, - ], - day_of_week: [ - { - agg: 'terms', - }, - ], + rollupSearchStrategy.getRollupData = jest.fn(() => + Promise.resolve({ + [rollupIndex]: { + rollup_jobs: [ + { + job_id: 'test', + rollup_index: rollupIndex, + index_pattern: 'kibana*', + fields: { + order_date: [ + { + agg: 'date_histogram', + delay: '1m', + interval: '1m', + time_zone: 'UTC', + }, + ], + day_of_week: [ + { + agg: 'terms', + }, + ], + }, }, - }, - ], - }, - })); + ], + }, + }) + ); }); test('isViable should be false for invalid index', async () => { - const result = await rollupSearchStrategy.checkForViability(request, null); + const result = await rollupSearchStrategy.checkForViability( + request, + (null as unknown) as string + ); expect(result).toEqual({ isViable: false, capabilities: null, }); }); - - test('should get RollupSearchCapabilities for valid rollup index ', async () => { - await rollupSearchStrategy.checkForViability(request, rollupIndex); - - expect(RollupSearchCapabilities).toHaveBeenCalled(); - }); }); describe('getRollupData', () => { - let rollupSearchStrategy; + let rollupSearchStrategy: RollupSearchStrategy; beforeEach(() => { rollupSearchStrategy = new RollupSearchStrategy(); }); test('should return rollup data', async () => { - rollupResolvedData = Promise.resolve('data'); + rollupResolvedData = Promise.resolve({ body: 'data' }); const rollupData = await rollupSearchStrategy.getRollupData(request, indexPattern); - expect(getRollupService).toHaveBeenCalled(); expect(rollupData).toBe('data'); }); @@ -143,8 +129,8 @@ describe('Rollup Search Strategy', () => { }); describe('getFieldsForWildcard', () => { - let rollupSearchStrategy; - let fieldsCapabilities; + let rollupSearchStrategy: RollupSearchStrategy; + let fieldsCapabilities: Record; const rollupIndex = 'rollupIndex'; diff --git a/x-pack/plugins/vis_type_timeseries_enhanced/server/search_strategies/rollup_search_strategy.ts b/x-pack/plugins/vis_type_timeseries_enhanced/server/search_strategies/rollup_search_strategy.ts new file mode 100644 index 0000000000000..f1c20c318d109 --- /dev/null +++ b/x-pack/plugins/vis_type_timeseries_enhanced/server/search_strategies/rollup_search_strategy.ts @@ -0,0 +1,79 @@ +/* + * 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 { keyBy, isString } from 'lodash'; +import { + AbstractSearchStrategy, + ReqFacade, + VisPayload, +} from '../../../../../src/plugins/vis_type_timeseries/server'; + +import { + mergeCapabilitiesWithFields, + getCapabilitiesForRollupIndices, +} from '../../../../../src/plugins/data/server'; + +import { RollupSearchCapabilities } from './rollup_search_capabilities'; + +const getRollupIndices = (rollupData: { [key: string]: any }) => Object.keys(rollupData); +const isIndexPatternContainsWildcard = (indexPattern: string) => indexPattern.includes('*'); +const isIndexPatternValid = (indexPattern: string) => + indexPattern && isString(indexPattern) && !isIndexPatternContainsWildcard(indexPattern); + +export class RollupSearchStrategy extends AbstractSearchStrategy { + name = 'rollup'; + + async search(req: ReqFacade, bodies: any[]) { + return super.search(req, bodies, 'rollup'); + } + + async getRollupData(req: ReqFacade, indexPattern: string) { + return req.requestContext.core.elasticsearch.client.asCurrentUser.rollup + .getRollupIndexCaps({ + index: indexPattern, + }) + .then((data) => data.body) + .catch(() => Promise.resolve({})); + } + + async checkForViability(req: ReqFacade, indexPattern: string) { + let isViable = false; + let capabilities = null; + + if (isIndexPatternValid(indexPattern)) { + const rollupData = await this.getRollupData(req, indexPattern); + const rollupIndices = getRollupIndices(rollupData); + + isViable = rollupIndices.length === 1; + + if (isViable) { + const [rollupIndex] = rollupIndices; + const fieldsCapabilities = getCapabilitiesForRollupIndices(rollupData); + + capabilities = new RollupSearchCapabilities(req, fieldsCapabilities, rollupIndex); + } + } + + return { + isViable, + capabilities, + }; + } + + async getFieldsForWildcard( + req: ReqFacade, + indexPattern: string, + { + fieldsCapabilities, + rollupIndex, + }: { fieldsCapabilities: { [key: string]: any }; rollupIndex: string } + ) { + const fields = await super.getFieldsForWildcard(req, indexPattern); + const fieldsFromFieldCapsApi = keyBy(fields, 'name'); + const rollupIndexCapabilities = fieldsCapabilities[rollupIndex].aggs; + + return mergeCapabilitiesWithFields(rollupIndexCapabilities, fieldsFromFieldCapsApi); + } +}