Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

[7.x] Convert vis_type_vega to Typescript (#68915) #71863

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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -138,9 +138,9 @@
"@kbn/babel-preset": "1.0.0",
"@kbn/config-schema": "1.0.0",
"@kbn/i18n": "1.0.0",
"@kbn/telemetry-tools": "1.0.0",
"@kbn/interpreter": "1.0.0",
"@kbn/pm": "1.0.0",
"@kbn/telemetry-tools": "1.0.0",
"@kbn/test-subj-selector": "0.2.1",
"@kbn/ui-framework": "1.0.0",
"@kbn/ui-shared-deps": "1.0.0",
Expand Down Expand Up @@ -340,6 +340,7 @@
"@types/hapi-auth-cookie": "^9.1.0",
"@types/has-ansi": "^3.0.0",
"@types/history": "^4.7.3",
"@types/hjson": "^2.4.2",
"@types/hoek": "^4.1.3",
"@types/inert": "^5.1.2",
"@types/jest": "^25.2.3",
Expand Down
8 changes: 8 additions & 0 deletions renovate.json5
Original file line number Diff line number Diff line change
Expand Up @@ -426,6 +426,14 @@
'@types/history',
],
},
{
groupSlug: 'hjson',
groupName: 'hjson related packages',
packageNames: [
'hjson',
'@types/hjson',
],
},
{
groupSlug: 'inquirer',
groupName: 'inquirer related packages',
Expand Down
1 change: 1 addition & 0 deletions src/plugins/maps_legacy/public/map/service_settings.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,4 +48,5 @@ export interface IServiceSettings {
getEMSHotLink(layer: FileLayer): Promise<string>;
getTMSServices(): Promise<TmsLayer[]>;
getFileLayers(): Promise<FileLayer[]>;
getUrlForRegionLayer(layer: FileLayer): Promise<string>;
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
import React, { useCallback } from 'react';
import { EuiCodeEditor } from '@elastic/eui';
import compactStringify from 'json-stringify-pretty-compact';
// @ts-ignore
import hjson from 'hjson';
import 'brace/mode/hjson';
import { i18n } from '@kbn/i18n';
Expand All @@ -45,7 +44,11 @@ const hjsonStringifyOptions = {
keepWsc: true,
};

function format(value: string, stringify: typeof compactStringify, options?: any) {
function format(
value: string,
stringify: typeof hjson.stringify | typeof compactStringify,
options?: any
) {
try {
const spec = hjson.parse(value, { legacyRoot: false, keepWsc: true });
return stringify(spec, options);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,22 +18,28 @@
*/

import { i18n } from '@kbn/i18n';
// @ts-ignore
import { bypassExternalUrlCheck } from '../vega_view/vega_base_view';
import { IServiceSettings, FileLayer } from '../../../maps_legacy/public';
import { Data, UrlObject, Requests } from './types';

/**
* This class processes all Vega spec customizations,
* converting url object parameters into query results.
*/
export class EmsFileParser {
constructor(serviceSettings) {
_serviceSettings: IServiceSettings;
_fileLayersP?: Promise<FileLayer[]>;

constructor(serviceSettings: IServiceSettings) {
this._serviceSettings = serviceSettings;
}

// noinspection JSMethodCanBeStatic
/**
* Update request object, expanding any context-aware keywords
*/
parseUrl(obj, url) {
parseUrl(obj: Data, url: UrlObject) {
if (typeof url.name !== 'string') {
throw new Error(
i18n.translate('visTypeVega.emsFileParser.missingNameOfFileErrorMessage', {
Expand All @@ -59,13 +65,13 @@ export class EmsFileParser {
* @param {object[]} requests each object is generated by parseUrl()
* @returns {Promise<void>}
*/
async populateData(requests) {
async populateData(requests: Requests[]) {
if (requests.length === 0) return;

const layers = await this._fileLayersP;

for (const { obj, name } of requests) {
const foundLayer = layers.find((v) => v.name === name);
const foundLayer = layers?.find((v) => v.name === name);
if (!foundLayer) {
throw new Error(
i18n.translate('visTypeVega.emsFileParser.emsFileNameDoesNotExistErrorMessage', {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,24 +19,38 @@

import moment from 'moment';
import { i18n } from '@kbn/i18n';
import { isPlainObject, cloneDeep } from 'lodash';
import { cloneDeep, isPlainObject } from 'lodash';
import { SearchParams } from 'elasticsearch';
import { TimeCache } from './time_cache';
import { SearchAPI } from './search_api';
import { Opts, Type, Data, UrlObject, Bool, Requests, Query, ContextVarsObject } from './types';

const TIMEFILTER = '%timefilter%';
const AUTOINTERVAL = '%autointerval%';
const MUST_CLAUSE = '%dashboard_context-must_clause%';
const FILTER_CLAUSE = '%dashboard_context-filter_clause%';
const MUST_NOT_CLAUSE = '%dashboard_context-must_not_clause%';
const TIMEFILTER: string = '%timefilter%';
const AUTOINTERVAL: string = '%autointerval%';
const MUST_CLAUSE: string = '%dashboard_context-must_clause%';
const MUST_NOT_CLAUSE: string = '%dashboard_context-must_not_clause%';
const FILTER_CLAUSE: string = '%dashboard_context-filter_clause%';

// These values may appear in the 'url': { ... } object
const LEGACY_CONTEXT = '%context_query%';
const CONTEXT = '%context%';
const TIMEFIELD = '%timefield%';
const LEGACY_CONTEXT: string = '%context_query%';
const CONTEXT: string = '%context%';
const TIMEFIELD: string = '%timefield%';

/**
* This class parses ES requests specified in the data.url objects.
*/
export class EsQueryParser {
constructor(timeCache, searchAPI, filters, onWarning) {
_timeCache: TimeCache;
_searchAPI: SearchAPI;
_filters: Bool;
_onWarning: (...args: string[]) => void;

constructor(
timeCache: TimeCache,
searchAPI: SearchAPI,
filters: Bool,
onWarning: (...args: string[]) => void
) {
this._timeCache = timeCache;
this._searchAPI = searchAPI;
this._filters = filters;
Expand All @@ -47,7 +61,7 @@ export class EsQueryParser {
/**
* Update request object, expanding any context-aware keywords
*/
parseUrl(dataObject, url) {
parseUrl(dataObject: Data, url: UrlObject) {
let body = url.body;
let context = url[CONTEXT];
delete url[CONTEXT];
Expand Down Expand Up @@ -167,13 +181,13 @@ export class EsQueryParser {
// Use dashboard context
const newQuery = cloneDeep(this._filters);
if (timefield) {
newQuery.bool.must.push(body.query);
newQuery.bool!.must!.push(body.query);
}
body.query = newQuery;
}
}

this._injectContextVars(body.aggs, false);
this._injectContextVars(body.aggs!, false);
return { dataObject, url };
}

Expand All @@ -182,8 +196,8 @@ export class EsQueryParser {
* @param {object[]} requests each object is generated by parseUrl()
* @returns {Promise<void>}
*/
async populateData(requests) {
const esSearches = requests.map((r) => r.url);
async populateData(requests: Requests[]) {
const esSearches = requests.map((r: Requests) => r.url);
const data$ = this._searchAPI.search(esSearches);

const results = await data$.toPromise();
Expand All @@ -198,7 +212,7 @@ export class EsQueryParser {
* @param {*} obj
* @param {boolean} isQuery - if true, the `obj` belongs to the req's query portion
*/
_injectContextVars(obj, isQuery) {
_injectContextVars(obj: Query | SearchParams['body']['aggs'], isQuery: boolean) {
if (obj && typeof obj === 'object') {
if (Array.isArray(obj)) {
// For arrays, replace MUST_CLAUSE and MUST_NOT_CLAUSE string elements
Expand Down Expand Up @@ -239,7 +253,7 @@ export class EsQueryParser {
}
} else {
for (const prop of Object.keys(obj)) {
const subObj = obj[prop];
const subObj = (obj as ContextVarsObject)[prop];
if (!subObj || typeof obj !== 'object') continue;

// replace "interval": { "%autointerval%": true|integer } with
Expand All @@ -260,7 +274,9 @@ export class EsQueryParser {
);
}
const bounds = this._timeCache.getTimeBounds();
obj.interval = EsQueryParser._roundInterval((bounds.max - bounds.min) / size);
(obj as ContextVarsObject).interval = EsQueryParser._roundInterval(
(bounds.max - bounds.min) / size
);
continue;
}

Expand All @@ -269,7 +285,7 @@ export class EsQueryParser {
case 'min':
case 'max':
// Replace {"%timefilter%": "min|max", ...} object with a timestamp
obj[prop] = this._getTimeBound(subObj, subObj[TIMEFILTER]);
(obj as ContextVarsObject)[prop] = this._getTimeBound(subObj, subObj[TIMEFILTER]);
continue;
case true:
// Replace {"%timefilter%": true, ...} object with the "range" object
Expand Down Expand Up @@ -302,7 +318,7 @@ export class EsQueryParser {
* @param {object} obj
* @return {object}
*/
_createRangeFilter(obj) {
_createRangeFilter(obj: Opts) {
obj.gte = moment(this._getTimeBound(obj, 'min')).toISOString();
obj.lte = moment(this._getTimeBound(obj, 'max')).toISOString();
obj.format = 'strict_date_optional_time';
Expand All @@ -320,9 +336,9 @@ export class EsQueryParser {
* @param {'min'|'max'} type
* @returns {*}
*/
_getTimeBound(opts, type) {
_getTimeBound(opts: Opts, type: Type): number {
const bounds = this._timeCache.getTimeBounds();
let result = bounds[type];
let result = bounds[type]?.valueOf() || 0;

if (opts.shift) {
const shift = opts.shift;
Expand Down Expand Up @@ -380,7 +396,7 @@ export class EsQueryParser {
* @param interval (ms)
* @returns {string}
*/
static _roundInterval(interval) {
static _roundInterval(interval: number): string {
switch (true) {
case interval <= 500: // <= 0.5s
return '100ms';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,37 +17,47 @@
* under the License.
*/

import { TimefilterContract } from '../../../data/public';
import { TimeRange } from '../../../data/common';
import { CacheBounds } from './types';

/**
* Optimization caching - always return the same value if queried within this time
* @type {number}
*/
const AlwaysCacheMaxAge = 40;

const AlwaysCacheMaxAge: number = 40;

/**
* This class caches timefilter's bounds to minimize number of server requests
*/
export class TimeCache {
constructor(timefilter, maxAge) {
_timefilter: TimefilterContract;
_maxAge: number;
_cachedBounds?: CacheBounds;
_cacheTS: number;
_timeRange?: TimeRange;

constructor(timefilter: TimefilterContract, maxAge: number) {
this._timefilter = timefilter;
this._maxAge = maxAge;
this._cachedBounds = null;
this._cacheTS = 0;
}

// Simplifies unit testing
// noinspection JSMethodCanBeStatic
_now() {
_now(): number {
return Date.now();
}

/**
* Get cached time range values
* @returns {{min: number, max: number}}
*/
getTimeBounds() {
getTimeBounds(): CacheBounds {
const ts = this._now();

let bounds;
let bounds: CacheBounds | null = null;
if (this._cachedBounds) {
const diff = ts - this._cacheTS;

Expand Down Expand Up @@ -76,7 +86,7 @@ export class TimeCache {
return this._cachedBounds;
}

setTimeRange(timeRange) {
setTimeRange(timeRange: TimeRange): void {
this._timeRange = timeRange;
}

Expand All @@ -85,11 +95,11 @@ export class TimeCache {
* @returns {{min: number, max: number}}
* @private
*/
_getBounds() {
const bounds = this._timefilter.calculateBounds(this._timeRange);
_getBounds(): CacheBounds {
const bounds = this._timefilter.calculateBounds(this._timeRange!);
return {
min: bounds.min.valueOf(),
max: bounds.max.valueOf(),
min: bounds.min!.valueOf(),
max: bounds.max!.valueOf(),
};
}
}
Loading