mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 09:19:04 -04:00
[TSVB] use new Search API for rollup search (#83275)
* [TSVB] use new Search API for rollup search Closes: #82710 * remove unused code * rollup_search_strategy.test.js -> rollup_search_strategy.test.ts * default_search_capabilities.test.js -> default_search_capabilities.test.ts * remove getRollupService * fix CI * fix some types * update types * update codeowners * fix PR comments Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
957882a479
commit
7114db3b1d
29 changed files with 453 additions and 481 deletions
1
.github/CODEOWNERS
vendored
1
.github/CODEOWNERS
vendored
|
@ -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
|
||||
|
|
|
@ -558,6 +558,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):
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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<GetVisDataOptions> = {
|
||||
requestContext,
|
||||
...request,
|
||||
framework,
|
||||
|
|
|
@ -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<VisPayload>;
|
||||
|
||||
beforeEach(() => {
|
||||
req = {};
|
||||
req = {} as ReqFacade<VisPayload>;
|
||||
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<VisPayload>;
|
||||
|
||||
expect(defaultSearchCapabilities.searchTimezone).toEqual('UTC');
|
||||
});
|
|
@ -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<VisPayload>) => {
|
||||
return request.payload.timerange.timezone;
|
||||
};
|
||||
|
||||
export class DefaultSearchCapabilities {
|
||||
constructor(request, fieldsCapabilities = {}) {
|
||||
this.request = request;
|
||||
this.fieldsCapabilities = fieldsCapabilities;
|
||||
}
|
||||
constructor(
|
||||
public request: ReqFacade<VisPayload>,
|
||||
public fieldsCapabilities: Record<string, any> = {}
|
||||
) {}
|
||||
|
||||
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<string, any>) {
|
||||
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;
|
||||
}
|
|
@ -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))!;
|
||||
|
|
|
@ -46,16 +46,8 @@ export interface ReqFacade<T = unknown> extends FakeRequest {
|
|||
getEsShardTimeout: () => Promise<number>;
|
||||
}
|
||||
|
||||
export class AbstractSearchStrategy {
|
||||
public indexType?: string;
|
||||
public additionalParams: any;
|
||||
|
||||
constructor(type?: string, additionalParams: any = {}) {
|
||||
this.indexType = type;
|
||||
this.additionalParams = additionalParams;
|
||||
}
|
||||
|
||||
async search(req: ReqFacade<VisPayload>, bodies: any[], options = {}) {
|
||||
export abstract class AbstractSearchStrategy {
|
||||
async search(req: ReqFacade<VisPayload>, 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<VisPayload>,
|
||||
indexPattern: string
|
||||
): Promise<{ isViable: boolean; capabilities: unknown }> {
|
||||
throw new TypeError('Must override method');
|
||||
}
|
||||
|
||||
async getFieldsForWildcard<TPayload = unknown>(
|
||||
req: ReqFacade<TPayload>,
|
||||
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');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<VisPayload>;
|
||||
|
||||
beforeEach(() => {
|
||||
req = {};
|
||||
req = {} as ReqFacade<VisPayload>;
|
||||
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({
|
|
@ -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<VisPayload>) {
|
||||
return Promise.resolve({
|
||||
isViable: true,
|
||||
capabilities: new DefaultSearchCapabilities(req),
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
|
@ -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';
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
});
|
||||
});
|
|
@ -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<Unit, number> = {
|
||||
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];
|
|
@ -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();
|
||||
});
|
||||
});
|
|
@ -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<ILegacyScopedClusterClient>
|
||||
) => {
|
||||
const RollupSearchCapabilities = getRollupSearchCapabilities(DefaultSearchCapabilities);
|
||||
const RollupSearchStrategy = getRollupSearchStrategy(
|
||||
AbstractSearchStrategy,
|
||||
RollupSearchCapabilities,
|
||||
getRollupService
|
||||
);
|
||||
|
||||
addSearchStrategy(new RollupSearchStrategy());
|
||||
};
|
|
@ -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<any>(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}`;
|
||||
}
|
||||
};
|
|
@ -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<ILegacyScopedClusterClient>
|
||||
) =>
|
||||
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);
|
||||
}
|
||||
};
|
|
@ -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<void, void, any, any> {
|
|||
},
|
||||
});
|
||||
|
||||
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())
|
||||
|
|
10
x-pack/plugins/vis_type_timeseries_enhanced/README.md
Normal file
10
x-pack/plugins/vis_type_timeseries_enhanced/README.md
Normal file
|
@ -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.
|
||||
|
10
x-pack/plugins/vis_type_timeseries_enhanced/kibana.json
Normal file
10
x-pack/plugins/vis_type_timeseries_enhanced/kibana.json
Normal file
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"id": "visTypeTimeseriesEnhanced",
|
||||
"version": "8.0.0",
|
||||
"kibanaVersion": "kibana",
|
||||
"server": true,
|
||||
"ui": false,
|
||||
"requiredPlugins": [
|
||||
"visTypeTimeseries"
|
||||
]
|
||||
}
|
|
@ -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);
|
33
x-pack/plugins/vis_type_timeseries_enhanced/server/plugin.ts
Normal file
33
x-pack/plugins/vis_type_timeseries_enhanced/server/plugin.ts
Normal file
|
@ -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<void, void, VisTypeTimeseriesEnhancedSetupDependencies, any> {
|
||||
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() {}
|
||||
}
|
|
@ -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<VisPayload>;
|
||||
|
||||
let RollupSearchCapabilities;
|
||||
let fieldsCapabilities;
|
||||
let rollupSearchCaps;
|
||||
let fieldsCapabilities: Record<string, any>;
|
||||
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');
|
||||
});
|
||||
});
|
||||
});
|
|
@ -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<string, any>;
|
||||
|
||||
constructor(
|
||||
req: ReqFacade<VisPayload>,
|
||||
fieldsCapabilities: Record<string, any>,
|
||||
rollupIndex: string
|
||||
) {
|
||||
super(req, fieldsCapabilities);
|
||||
|
||||
this.rollupIndex = rollupIndex;
|
||||
this.availableMetrics = get(fieldsCapabilities, `${rollupIndex}.aggs`, {});
|
||||
}
|
||||
|
||||
public get dateHistogram() {
|
||||
const [dateHistogram] = Object.values<any>(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}`;
|
||||
}
|
||||
}
|
|
@ -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<any>;
|
||||
|
||||
const request = {
|
||||
const request = ({
|
||||
requestContext: {
|
||||
core: {
|
||||
elasticsearch: {
|
||||
|
@ -25,42 +45,10 @@ describe('Rollup Search Strategy', () => {
|
|||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
const getRollupService = jest.fn().mockImplementation(() => {
|
||||
return {
|
||||
callAsCurrentUser: async () => {
|
||||
return rollupResolvedData;
|
||||
},
|
||||
};
|
||||
});
|
||||
} as unknown) as ReqFacade<VisPayload>;
|
||||
|
||||
const indexPattern = 'indexPattern';
|
||||
|
||||
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
|
||||
);
|
||||
});
|
||||
|
||||
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<string, any>;
|
||||
|
||||
const rollupIndex = 'rollupIndex';
|
||||
|
|
@ -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<VisPayload>, 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<VisPayload>, 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<TPayload = unknown>(
|
||||
req: ReqFacade<TPayload>,
|
||||
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);
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue