From 967cb4e7fb6d60c89354aa3420b840bf930969e1 Mon Sep 17 00:00:00 2001 From: Tim Roes Date: Wed, 25 Jul 2018 07:56:31 +0200 Subject: [PATCH] TypeScriptify visualize loader (#21025) * TypeScriptify Vis loaders * Fix issue with undefined timeRange * Fix chrome typing * Fix unit tests * Fix this issue * Add missing uiState to request handler * Implement Felix's suggestions * Add timefilter listener --- .../public/discover/controllers/discover.js | 2 +- .../agg_types/buckets/date_histogram.js | 2 +- src/ui/public/chrome/index.d.ts | 5 + src/ui/public/courier/index.d.ts | 20 ++ .../public/courier/search_source/index.d.ts | 20 ++ .../courier/search_source/search_source.d.ts | 20 ++ src/ui/public/filter_bar/query_filter.d.ts | 22 ++ src/ui/public/notify/index.d.ts | 20 ++ src/ui/public/notify/toasts/index.d.ts | 20 ++ .../notify/toasts/toast_notifications.d.ts | 41 +++ src/ui/public/private/index.d.ts | 20 ++ src/ui/public/private/private.d.ts | 20 ++ .../public/registry/vis_request_handlers.d.ts | 20 ++ .../registry/vis_response_handlers.d.ts | 20 ++ .../app_state.d.ts} | 2 +- src/ui/public/timefilter/index.d.ts | 20 ++ src/ui/public/timefilter/timefilter.d.ts | 21 ++ src/ui/public/vis/agg_configs.d.ts | 20 ++ src/ui/public/vis/index.d.ts | 2 + src/ui/public/vis/request_handlers/courier.js | 8 +- src/ui/public/vis/request_handlers/index.d.ts | 24 ++ .../request_handlers/request_handlers.d.ts | 43 +++ .../public/vis/response_handlers/index.d.ts | 20 ++ .../response_handlers/response_handlers.d.ts | 27 ++ src/ui/public/vis/vis_types/vis_type.d.ts | 4 +- src/ui/public/visualize/index.ts | 21 ++ .../loader/__tests__/visualization_loader.js | 4 +- .../loader/__tests__/visualize_loader.js | 25 +- .../loader/embedded_visualize_handler.js | 233 --------------- .../loader/embedded_visualize_handler.ts | 277 ++++++++++++++++++ .../visualize/loader/{index.js => index.ts} | 0 src/ui/public/visualize/loader/types.ts | 107 +++++++ ...ion_loader.js => visualization_loader.tsx} | 44 ++- .../visualize/loader/visualize_data_loader.js | 85 ------ .../visualize/loader/visualize_data_loader.ts | 107 +++++++ .../visualize/loader/visualize_loader.js | 154 ---------- .../visualize/loader/visualize_loader.ts | 152 ++++++++++ 37 files changed, 1150 insertions(+), 502 deletions(-) create mode 100644 src/ui/public/courier/index.d.ts create mode 100644 src/ui/public/courier/search_source/index.d.ts create mode 100644 src/ui/public/courier/search_source/search_source.d.ts create mode 100644 src/ui/public/filter_bar/query_filter.d.ts create mode 100644 src/ui/public/notify/index.d.ts create mode 100644 src/ui/public/notify/toasts/index.d.ts create mode 100644 src/ui/public/notify/toasts/toast_notifications.d.ts create mode 100644 src/ui/public/private/index.d.ts create mode 100644 src/ui/public/private/private.d.ts create mode 100644 src/ui/public/registry/vis_request_handlers.d.ts create mode 100644 src/ui/public/registry/vis_response_handlers.d.ts rename src/ui/public/{visualize/index.js => state_management/app_state.d.ts} (96%) create mode 100644 src/ui/public/timefilter/index.d.ts create mode 100644 src/ui/public/timefilter/timefilter.d.ts create mode 100644 src/ui/public/vis/agg_configs.d.ts create mode 100644 src/ui/public/vis/request_handlers/index.d.ts create mode 100644 src/ui/public/vis/request_handlers/request_handlers.d.ts create mode 100644 src/ui/public/vis/response_handlers/index.d.ts create mode 100644 src/ui/public/vis/response_handlers/response_handlers.d.ts create mode 100644 src/ui/public/visualize/index.ts delete mode 100644 src/ui/public/visualize/loader/embedded_visualize_handler.js create mode 100644 src/ui/public/visualize/loader/embedded_visualize_handler.ts rename src/ui/public/visualize/loader/{index.js => index.ts} (100%) create mode 100644 src/ui/public/visualize/loader/types.ts rename src/ui/public/visualize/loader/{visualization_loader.js => visualization_loader.tsx} (61%) delete mode 100644 src/ui/public/visualize/loader/visualize_data_loader.js create mode 100644 src/ui/public/visualize/loader/visualize_data_loader.ts delete mode 100644 src/ui/public/visualize/loader/visualize_loader.js create mode 100644 src/ui/public/visualize/loader/visualize_loader.ts diff --git a/src/core_plugins/kibana/public/discover/controllers/discover.js b/src/core_plugins/kibana/public/discover/controllers/discover.js index 068ed4e4ce8fb2..4fb8065bf4ed5f 100644 --- a/src/core_plugins/kibana/public/discover/controllers/discover.js +++ b/src/core_plugins/kibana/public/discover/controllers/discover.js @@ -571,7 +571,7 @@ function discoverController( .then(resp => { $scope.visData = resp; const visEl = $element.find('#discoverHistogram')[0]; - visualizationLoader(visEl, $scope.vis, $scope.visData, $scope.uiState, { listenOnChange: true }); + visualizationLoader.render(visEl, $scope.vis, $scope.visData, $scope.uiState, { listenOnChange: true }); }); } diff --git a/src/ui/public/agg_types/buckets/date_histogram.js b/src/ui/public/agg_types/buckets/date_histogram.js index 528f73fa3904ec..0dd204b3f2ffd5 100644 --- a/src/ui/public/agg_types/buckets/date_histogram.js +++ b/src/ui/public/agg_types/buckets/date_histogram.js @@ -42,7 +42,7 @@ function getInterval(agg) { } function getBounds(vis) { - if (vis.API.timeFilter.isTimeRangeSelectorEnabled && vis.filters) { + if (vis.API.timeFilter.isTimeRangeSelectorEnabled && vis.filters && vis.filters.timeRange) { return vis.API.timeFilter.calculateBounds(vis.filters.timeRange); } } diff --git a/src/ui/public/chrome/index.d.ts b/src/ui/public/chrome/index.d.ts index 253287ea32e9a2..5363e83631f244 100644 --- a/src/ui/public/chrome/index.d.ts +++ b/src/ui/public/chrome/index.d.ts @@ -17,8 +17,13 @@ * under the License. */ +interface IInjector { + get(injectable: string): T; +} + declare class Chrome { public addBasePath(path: T): T; + public dangerouslyGetActiveInjector(): Promise; public getBasePath(): string; public getXsrfToken(): string; } diff --git a/src/ui/public/courier/index.d.ts b/src/ui/public/courier/index.d.ts new file mode 100644 index 00000000000000..72170adc2b1296 --- /dev/null +++ b/src/ui/public/courier/index.d.ts @@ -0,0 +1,20 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export * from './search_source'; diff --git a/src/ui/public/courier/search_source/index.d.ts b/src/ui/public/courier/search_source/index.d.ts new file mode 100644 index 00000000000000..dcae7b3d2ff058 --- /dev/null +++ b/src/ui/public/courier/search_source/index.d.ts @@ -0,0 +1,20 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export { SearchSource } from './search_source'; diff --git a/src/ui/public/courier/search_source/search_source.d.ts b/src/ui/public/courier/search_source/search_source.d.ts new file mode 100644 index 00000000000000..11406ff3da8249 --- /dev/null +++ b/src/ui/public/courier/search_source/search_source.d.ts @@ -0,0 +1,20 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export type SearchSource = any; diff --git a/src/ui/public/filter_bar/query_filter.d.ts b/src/ui/public/filter_bar/query_filter.d.ts new file mode 100644 index 00000000000000..b5d7742f51d460 --- /dev/null +++ b/src/ui/public/filter_bar/query_filter.d.ts @@ -0,0 +1,22 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export type QueryFilter = any; + +export const FilterBarQueryFilterProvider: () => QueryFilter; diff --git a/src/ui/public/notify/index.d.ts b/src/ui/public/notify/index.d.ts new file mode 100644 index 00000000000000..0a6f8392c0d74f --- /dev/null +++ b/src/ui/public/notify/index.d.ts @@ -0,0 +1,20 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export { toastNotifications, ToastNotifications } from './toasts'; diff --git a/src/ui/public/notify/toasts/index.d.ts b/src/ui/public/notify/toasts/index.d.ts new file mode 100644 index 00000000000000..b841aa1c8334aa --- /dev/null +++ b/src/ui/public/notify/toasts/index.d.ts @@ -0,0 +1,20 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export { toastNotifications, ToastNotifications } from './toast_notifications'; diff --git a/src/ui/public/notify/toasts/toast_notifications.d.ts b/src/ui/public/notify/toasts/toast_notifications.d.ts new file mode 100644 index 00000000000000..565c5c4ad725cf --- /dev/null +++ b/src/ui/public/notify/toasts/toast_notifications.d.ts @@ -0,0 +1,41 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +interface Toast extends ToastDescription { + id: number; +} + +interface ToastDescription { + title: string; + color?: string; + iconType?: string; + text?: string; + 'data-test-subj'?: string; +} + +export interface ToastNotifications { + onChange(changeCallback: () => void): void; + remove(toast: Toast): void; + add(toast: ToastDescription | string): Toast; + addSuccess(toast: ToastDescription | string): Toast; + addWarning(toast: ToastDescription | string): Toast; + addDanger(toast: ToastDescription | string): Toast; +} + +export const toastNotifications: ToastNotifications; diff --git a/src/ui/public/private/index.d.ts b/src/ui/public/private/index.d.ts new file mode 100644 index 00000000000000..d814b31102d597 --- /dev/null +++ b/src/ui/public/private/index.d.ts @@ -0,0 +1,20 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export { IPrivate } from './private'; diff --git a/src/ui/public/private/private.d.ts b/src/ui/public/private/private.d.ts new file mode 100644 index 00000000000000..3efc9cd5308f73 --- /dev/null +++ b/src/ui/public/private/private.d.ts @@ -0,0 +1,20 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export type IPrivate = (provider: (...injectable: any[]) => T) => T; diff --git a/src/ui/public/registry/vis_request_handlers.d.ts b/src/ui/public/registry/vis_request_handlers.d.ts new file mode 100644 index 00000000000000..79cbafdef8cd97 --- /dev/null +++ b/src/ui/public/registry/vis_request_handlers.d.ts @@ -0,0 +1,20 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export const VisRequestHandlersRegistryProvider: () => any; diff --git a/src/ui/public/registry/vis_response_handlers.d.ts b/src/ui/public/registry/vis_response_handlers.d.ts new file mode 100644 index 00000000000000..922edb200aed76 --- /dev/null +++ b/src/ui/public/registry/vis_response_handlers.d.ts @@ -0,0 +1,20 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export const VisResponseHandlersRegistryProvider: () => any; diff --git a/src/ui/public/visualize/index.js b/src/ui/public/state_management/app_state.d.ts similarity index 96% rename from src/ui/public/visualize/index.js rename to src/ui/public/state_management/app_state.d.ts index 46a8968358294e..314583746fca14 100644 --- a/src/ui/public/visualize/index.js +++ b/src/ui/public/state_management/app_state.d.ts @@ -17,4 +17,4 @@ * under the License. */ -export * from './loader'; +export type AppState = any; diff --git a/src/ui/public/timefilter/index.d.ts b/src/ui/public/timefilter/index.d.ts new file mode 100644 index 00000000000000..db6ead9d17a188 --- /dev/null +++ b/src/ui/public/timefilter/index.d.ts @@ -0,0 +1,20 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export { timefilter, Timefilter } from './timefilter'; diff --git a/src/ui/public/timefilter/timefilter.d.ts b/src/ui/public/timefilter/timefilter.d.ts new file mode 100644 index 00000000000000..4dc783f5e7dbe9 --- /dev/null +++ b/src/ui/public/timefilter/timefilter.d.ts @@ -0,0 +1,21 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export type Timefilter = any; +export const timefilter: Timefilter; diff --git a/src/ui/public/vis/agg_configs.d.ts b/src/ui/public/vis/agg_configs.d.ts new file mode 100644 index 00000000000000..c9557aef4da025 --- /dev/null +++ b/src/ui/public/vis/agg_configs.d.ts @@ -0,0 +1,20 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export type AggConfigs = any; diff --git a/src/ui/public/vis/index.d.ts b/src/ui/public/vis/index.d.ts index 6dddd51132908d..a16588c89e7084 100644 --- a/src/ui/public/vis/index.d.ts +++ b/src/ui/public/vis/index.d.ts @@ -20,3 +20,5 @@ export { AggConfig } from './agg_config'; export { Vis, VisProvider } from './vis'; export { VisualizationController, VisType } from './vis_types/vis_type'; +export * from './request_handlers'; +export * from './response_handlers'; diff --git a/src/ui/public/vis/request_handlers/courier.js b/src/ui/public/vis/request_handlers/courier.js index b894a07f5f4ecd..eb9ff172d62265 100644 --- a/src/ui/public/vis/request_handlers/courier.js +++ b/src/ui/public/vis/request_handlers/courier.js @@ -104,9 +104,11 @@ const CourierRequestHandlerProvider = function () { return aggs.onSearchRequestStart(searchSource, searchRequest); }); - timeFilterSearchSource.setField('filter', () => { - return getTime(searchSource.getField('index'), timeRange); - }); + if (timeRange) { + timeFilterSearchSource.setField('filter', () => { + return getTime(searchSource.getField('index'), timeRange); + }); + } requestSearchSource.setField('filter', filters); requestSearchSource.setField('query', query); diff --git a/src/ui/public/vis/request_handlers/index.d.ts b/src/ui/public/vis/request_handlers/index.d.ts new file mode 100644 index 00000000000000..63e0751713f59c --- /dev/null +++ b/src/ui/public/vis/request_handlers/index.d.ts @@ -0,0 +1,24 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export { + RequestHandler, + RequestHandlerDescription, + RequestHandlerParams, +} from './request_handlers'; diff --git a/src/ui/public/vis/request_handlers/request_handlers.d.ts b/src/ui/public/vis/request_handlers/request_handlers.d.ts new file mode 100644 index 00000000000000..99b7afa7ae05d5 --- /dev/null +++ b/src/ui/public/vis/request_handlers/request_handlers.d.ts @@ -0,0 +1,43 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { SearchSource } from '../../courier'; +import { QueryFilter } from '../../filter_bar/query_filter'; +import { PersistedState } from '../../persisted_state'; +import { Filters, Query, TimeRange } from '../../visualize'; +import { AggConfigs } from '../agg_configs'; +import { Vis } from '../vis'; + +export interface RequestHandlerParams { + searchSource: SearchSource; + aggs: AggConfigs; + timeRange?: TimeRange; + query?: Query; + filters?: Filters; + forceFetch: boolean; + queryFilter: QueryFilter; + uiState: PersistedState; +} + +export type RequestHandler = (vis: Vis, params: RequestHandlerParams) => T; + +export interface RequestHandlerDescription { + name: string; + handler: RequestHandler; +} diff --git a/src/ui/public/vis/response_handlers/index.d.ts b/src/ui/public/vis/response_handlers/index.d.ts new file mode 100644 index 00000000000000..8b8993bfd0497b --- /dev/null +++ b/src/ui/public/vis/response_handlers/index.d.ts @@ -0,0 +1,20 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export { ResponseHandler, ResponseHandlerDescription } from './response_handlers'; diff --git a/src/ui/public/vis/response_handlers/response_handlers.d.ts b/src/ui/public/vis/response_handlers/response_handlers.d.ts new file mode 100644 index 00000000000000..1ad92a098c7d16 --- /dev/null +++ b/src/ui/public/vis/response_handlers/response_handlers.d.ts @@ -0,0 +1,27 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { Vis } from '../vis'; + +export type ResponseHandler = (vis: Vis, response: Response) => Data; + +export interface ResponseHandlerDescription { + name: string; + handler: ResponseHandler; +} diff --git a/src/ui/public/vis/vis_types/vis_type.d.ts b/src/ui/public/vis/vis_types/vis_type.d.ts index 6e74986852196f..5537e50a6d129b 100644 --- a/src/ui/public/vis/vis_types/vis_type.d.ts +++ b/src/ui/public/vis/vis_types/vis_type.d.ts @@ -17,7 +17,7 @@ * under the License. */ -import { Vis } from '..'; +import { RequestHandler, ResponseHandler, Vis } from '..'; import { Status } from '../update_status'; export class VisualizationController { @@ -31,6 +31,8 @@ export interface VisType { title: string; visualization: typeof VisualizationController; isAccessible?: boolean; + requestHandler: string | RequestHandler; + responseHandler: string | ResponseHandler; // Since we haven't typed everything here yet, we basically "any" the rest // of that interface. This should be removed as soon as this type definition diff --git a/src/ui/public/visualize/index.ts b/src/ui/public/visualize/index.ts new file mode 100644 index 00000000000000..ed41c22aebda14 --- /dev/null +++ b/src/ui/public/visualize/index.ts @@ -0,0 +1,21 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export * from './loader'; +export { Filters, Query, TimeRange } from './loader/types'; diff --git a/src/ui/public/visualize/loader/__tests__/visualization_loader.js b/src/ui/public/visualize/loader/__tests__/visualization_loader.js index 9453f31ac7fae8..3dc23fffea3c97 100644 --- a/src/ui/public/visualize/loader/__tests__/visualization_loader.js +++ b/src/ui/public/visualize/loader/__tests__/visualization_loader.js @@ -47,8 +47,8 @@ describe('visualization loader', () => { it('should render visualization', async () => { const element = document.createElement('div'); - expect(visualizationLoader).to.be.a('function'); - visualizationLoader(element, vis); + expect(visualizationLoader.render).to.be.a('function'); + visualizationLoader.render(element, vis); expect($(element).find('.visualization').length).to.be(1); }); diff --git a/src/ui/public/visualize/loader/__tests__/visualize_loader.js b/src/ui/public/visualize/loader/__tests__/visualize_loader.js index 1a9dc3ed8673fb..240f60a6059c6c 100644 --- a/src/ui/public/visualize/loader/__tests__/visualize_loader.js +++ b/src/ui/public/visualize/loader/__tests__/visualize_loader.js @@ -32,6 +32,7 @@ import { getVisualizeLoader } from '../visualize_loader'; import { EmbeddedVisualizeHandler } from '../embedded_visualize_handler'; import { Inspector } from '../../../inspector/inspector'; import { dispatchRenderComplete } from '../../../render_complete'; +import { VisualizeDataLoader } from '../visualize_data_loader'; describe('visualize loader', () => { @@ -297,26 +298,30 @@ describe('visualize loader', () => { added: 'value', } }); - // Sync we are relying on $evalAsync we need to trigger a digest loop during tests - $rootScope.$digest(); expect(container.find('[data-test-subj="visualizationLoader"]')[0].hasAttribute('data-foo')).to.be(false); expect(container.find('[data-test-subj="visualizationLoader"]').attr('data-added')).to.be('value'); }); - it('should allow updating the time range of the visualization', () => { + it('should allow updating the time range of the visualization', async () => { + const spy = sinon.spy(VisualizeDataLoader.prototype, 'fetch'); + const handler = loader.embedVisualizationWithSavedObject(newContainer()[0], createSavedObject(), { timeRange: { from: 'now-7d', to: 'now' } }); + + // Wait for the initial fetch and render to happen + await timeout(150); + spy.resetHistory(); + handler.update({ timeRange: { from: 'now-10d/d', to: 'now' } }); - // Sync we are relying on $evalAsync we need to trigger a digest loop during tests - $rootScope.$digest(); - // This is not the best test, since it tests internal structure of our scope. - // Unfortunately we currently don't expose the timeRange in a better way. - // Once we rewrite this to a react component we should spy on the timeRange - // property in the component to match the passed in value. - expect(handler._params.timeRange).to.eql({ from: 'now-10d/d', to: 'now' }); + + // Wait for fetch debounce to happen (as soon as we use lodash 4+ we could use fake timers here for the debounce) + await timeout(150); + + sinon.assert.calledOnce(spy); + sinon.assert.calledWith(spy, sinon.match({ timeRange: { from: 'now-10d/d', to: 'now' } })); }); }); diff --git a/src/ui/public/visualize/loader/embedded_visualize_handler.js b/src/ui/public/visualize/loader/embedded_visualize_handler.js deleted file mode 100644 index 1dfc88771152dc..00000000000000 --- a/src/ui/public/visualize/loader/embedded_visualize_handler.js +++ /dev/null @@ -1,233 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { debounce } from 'lodash'; -import { EventEmitter } from 'events'; -import { visualizationLoader } from './visualization_loader'; -import { VisualizeDataLoader } from './visualize_data_loader'; -import { RenderCompleteHelper } from '../../render_complete'; -import { timefilter } from 'ui/timefilter'; - -const RENDER_COMPLETE_EVENT = 'render_complete'; - -/** - * A handler to the embedded visualization. It offers several methods to interact - * with the visualization. - */ -export class EmbeddedVisualizeHandler { - constructor(element, savedObject, params) { - const { searchSource, vis } = savedObject; - - const { - appState, - uiState, - queryFilter, - timeRange, - filters, - query, - Private, - } = params; - - const aggs = vis.getAggConfig(); - - this._element = element; - this._params = { uiState, queryFilter, searchSource, aggs, timeRange, filters, query }; - - this._listeners = new EventEmitter(); - // Listen to the first RENDER_COMPLETE_EVENT to resolve this promise - this._firstRenderComplete = new Promise(resolve => { - this._listeners.once(RENDER_COMPLETE_EVENT, resolve); - }); - - this._elementListener = () => { - this._listeners.emit(RENDER_COMPLETE_EVENT); - }; - - this._element.addEventListener('renderComplete', this._elementListener); - - this._loaded = false; - this._destroyed = false; - - this._appState = appState; - this._vis = vis; - this._vis._setUiState(uiState); - this._uiState = this._vis.getUiState(); - - this._vis.on('update', this._handleVisUpdate); - this._vis.on('reload', this._reloadVis); - this._uiState.on('change', this._fetchAndRender); - timefilter.on('autoRefreshFetch', this._reloadVis); - - this._visualize = new VisualizeDataLoader(this._vis, Private); - this._renderCompleteHelper = new RenderCompleteHelper(this._element); - - this._render(); - } - - _handleVisUpdate = () => { - const visState = this._vis.getState(); - if (this._appState) { - this._appState.vis = visState; - this._appState.save(); - } - - this._fetchAndRender(); - }; - - _reloadVis = () => { - this._fetchAndRender(true); - }; - - _fetch = (forceFetch) => { - // we need to update this before fetch - this._params.aggs = this._vis.getAggConfig(); - - return this._visualize.fetch(this._params, forceFetch); - }; - - _render = (visData) => { - return visualizationLoader(this._element, this._vis, visData, this._uiState, { listenOnChange: false }).then(() => { - if (!this._loaded) { - this._loaded = true; - this._fetchAndRender(); - } - }); - }; - - _fetchAndRender = debounce((forceFetch = false) => { - if (this._destroyed) { - return; - } - - return this._fetch(forceFetch).then(this._render); - }, 100); - - /** - * Update properties of the embedded visualization. This method does not allow - * updating all initial parameters, but only a subset of the ones allowed - * in {@link VisualizeLoaderParams}. - * - * @param {Object} [params={}] The parameters that should be updated. - * @property {Object} [timeRange] A new time range for this visualization. - * @property {Object} [filters] New filters for this visualization. - * @property {Object} [query] A new query for this visualization. - * @property {Object} [dataAttrs] An object of data attributes to modify. The - * key will be the name of the data attribute and the value the value that - * attribute will get. Use null to remove a specific data attribute from the visualization. - */ - update(params = {}) { - // Apply data- attributes to the element if specified - if (params.dataAttrs) { - Object.keys(params.dataAttrs).forEach(key => { - if (params.dataAttrs[key] === null) { - this._element.removeAttribute(`data-${key}`); - return; - } - - this._element.setAttribute(`data-${key}`, params.dataAttrs[key]); - }); - } - - let fetchRequired = false; - if (params.hasOwnProperty('timeRange')) { - fetchRequired = true; - this._params.timeRange = params.timeRange; - } - if (params.hasOwnProperty('filters')) { - fetchRequired = true; - this._params.filters = params.filters; - } - if (params.hasOwnProperty('query')) { - fetchRequired = true; - this._params.query = params.query; - } - - if (fetchRequired) { - this._fetchAndRender(); - } - } - - /** - * Destroy the underlying Angular scope of the visualization. This should be - * called whenever you remove the visualization. - */ - destroy() { - this._destroyed = true; - this._fetchAndRender.cancel(); - timefilter.off('autoRefreshFetch', this._reloadVis); - this._vis.removeListener('reload', this._reloadVis); - this._vis.removeListener('update', this._handleVisUpdate); - this._element.removeEventListener('renderComplete', this._elementListener); - this._uiState.off('change', this._fetchAndRender); - visualizationLoader.destroy(this._element); - this._renderCompleteHelper.destroy(); - } - - /** - * Return the actual DOM element (wrapped in jQuery) of the rendered visualization. - * This is especially useful if you used `append: true` in the parameters where - * the visualization will be appended to the specified container. - */ - getElement() { - return this._element; - } - - /** - * Opens the inspector for the embedded visualization. This will return an - * handler to the inspector to close and interact with it. - * @return {InspectorSession} An inspector session to interact with the opened inspector. - */ - openInspector() { - return this._vis.openInspector(); - } - - /** - * Returns a promise, that will resolve (without a value) once the first rendering of - * the visualization has finished. If you want to listen to consecutive rendering - * events, look into the `addRenderCompleteListener` method. - * - * @returns {Promise} Promise, that resolves as soon as the visualization is done rendering - * for the first time. - */ - whenFirstRenderComplete() { - return this._firstRenderComplete; - } - - /** - * Adds a listener to be called whenever the visualization finished rendering. - * This can be called multiple times, when the visualization rerenders, e.g. due - * to new data. - * - * @param {function} listener The listener to be notified about complete renders. - */ - addRenderCompleteListener(listener) { - this._listeners.addListener(RENDER_COMPLETE_EVENT, listener); - } - - /** - * Removes a previously registered render complete listener from this handler. - * This listener will no longer be called when the visualization finished rendering. - * - * @param {function} listener The listener to remove from this handler. - */ - removeRenderCompleteListener(listener) { - this._listeners.removeListener(RENDER_COMPLETE_EVENT, listener); - } - -} diff --git a/src/ui/public/visualize/loader/embedded_visualize_handler.ts b/src/ui/public/visualize/loader/embedded_visualize_handler.ts new file mode 100644 index 00000000000000..9378a28e3c6009 --- /dev/null +++ b/src/ui/public/visualize/loader/embedded_visualize_handler.ts @@ -0,0 +1,277 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { EventEmitter } from 'events'; +import { debounce } from 'lodash'; +import { Inspector } from '../../inspector'; + +import { PersistedState } from '../../persisted_state'; +import { IPrivate } from '../../private'; +import { RenderCompleteHelper } from '../../render_complete'; +import { AppState } from '../../state_management/app_state'; +import { timefilter } from '../../timefilter'; +import { RequestHandlerParams, Vis } from '../../vis'; +import { visualizationLoader } from './visualization_loader'; +import { VisualizeDataLoader } from './visualize_data_loader'; + +import { VisSavedObject, VisualizeLoaderParams, VisualizeUpdateParams } from './types'; + +interface EmbeddedVisualizeHandlerParams extends VisualizeLoaderParams { + Private: IPrivate; + queryFilter: any; +} + +const RENDER_COMPLETE_EVENT = 'render_complete'; + +/** + * A handler to the embedded visualization. It offers several methods to interact + * with the visualization. + */ +export class EmbeddedVisualizeHandler { + private vis: Vis; + private loaded: boolean = false; + private destroyed: boolean = false; + + private listeners = new EventEmitter(); + private firstRenderComplete: Promise; + private renderCompleteHelper: RenderCompleteHelper; + private onRenderCompleteListener: () => void; + private shouldForceNextFetch: boolean = false; + private debouncedFetchAndRender = debounce(() => { + if (this.destroyed) { + return; + } + + const forceFetch = this.shouldForceNextFetch; + this.shouldForceNextFetch = false; + this.fetch(forceFetch).then(this.render); + }, 100); + + private dataLoaderParams: RequestHandlerParams; + private appState: AppState; + private uiState: PersistedState; + private dataLoader: VisualizeDataLoader; + + constructor( + private readonly element: HTMLElement, + savedObject: VisSavedObject, + params: EmbeddedVisualizeHandlerParams + ) { + const { searchSource, vis } = savedObject; + + const { appState, uiState, queryFilter, timeRange, filters, query, Private } = params; + + this.dataLoaderParams = { + searchSource, + timeRange, + query, + queryFilter, + filters, + uiState, + aggs: vis.getAggConfig(), + forceFetch: false, + }; + + // Listen to the first RENDER_COMPLETE_EVENT to resolve this promise + this.firstRenderComplete = new Promise(resolve => { + this.listeners.once(RENDER_COMPLETE_EVENT, resolve); + }); + + this.onRenderCompleteListener = () => { + this.listeners.emit(RENDER_COMPLETE_EVENT); + }; + + element.addEventListener('renderComplete', this.onRenderCompleteListener); + + this.appState = appState; + this.vis = vis; + if (uiState) { + vis._setUiState(uiState); + } + this.uiState = this.vis.getUiState(); + + this.vis.on('update', this.handleVisUpdate); + this.vis.on('reload', this.reload); + this.uiState.on('change', this.fetchAndRender); + timefilter.on('autoRefreshFetch', this.reload); + + this.dataLoader = new VisualizeDataLoader(vis, Private); + this.renderCompleteHelper = new RenderCompleteHelper(element); + + this.render(); + } + + /** + * Update properties of the embedded visualization. This method does not allow + * updating all initial parameters, but only a subset of the ones allowed + * in {@link VisualizeUpdateParams}. + * + * @param params The parameters that should be updated. + */ + public update(params: VisualizeUpdateParams = {}) { + // Apply data- attributes to the element if specified + const dataAttrs = params.dataAttrs; + if (dataAttrs) { + Object.keys(dataAttrs).forEach(key => { + if (dataAttrs[key] === null) { + this.element.removeAttribute(`data-${key}`); + return; + } + + this.element.setAttribute(`data-${key}`, dataAttrs[key]); + }); + } + + let fetchRequired = false; + if (params.hasOwnProperty('timeRange')) { + fetchRequired = true; + this.dataLoaderParams.timeRange = params.timeRange; + } + if (params.hasOwnProperty('filters')) { + fetchRequired = true; + this.dataLoaderParams.filters = params.filters; + } + if (params.hasOwnProperty('query')) { + fetchRequired = true; + this.dataLoaderParams.query = params.query; + } + + if (fetchRequired) { + this.fetchAndRender(); + } + } + + /** + * Destroy the underlying Angular scope of the visualization. This should be + * called whenever you remove the visualization. + */ + public destroy(): void { + this.destroyed = true; + this.debouncedFetchAndRender.cancel(); + timefilter.off('autoRefreshFetch', this.reload); + this.vis.removeListener('reload', this.reload); + this.vis.removeListener('update', this.handleVisUpdate); + this.element.removeEventListener('renderComplete', this.onRenderCompleteListener); + this.uiState.off('change', this.fetchAndRender); + visualizationLoader.destroy(this.element); + this.renderCompleteHelper.destroy(); + } + + /** + * Return the actual DOM element (wrapped in jQuery) of the rendered visualization. + * This is especially useful if you used `append: true` in the parameters where + * the visualization will be appended to the specified container. + */ + public getElement(): HTMLElement { + return this.element; + } + + /** + * Opens the inspector for the embedded visualization. This will return an + * handler to the inspector to close and interact with it. + * @return An inspector session to interact with the opened inspector. + */ + public openInspector(): ReturnType { + return this.vis.openInspector(); + } + + /** + * Returns a promise, that will resolve (without a value) once the first rendering of + * the visualization has finished. If you want to listen to consecutive rendering + * events, look into the `addRenderCompleteListener` method. + * + * @returns Promise, that resolves as soon as the visualization is done rendering + * for the first time. + */ + public whenFirstRenderComplete(): Promise { + return this.firstRenderComplete; + } + + /** + * Adds a listener to be called whenever the visualization finished rendering. + * This can be called multiple times, when the visualization rerenders, e.g. due + * to new data. + * + * @param {function} listener The listener to be notified about complete renders. + */ + public addRenderCompleteListener(listener: () => void) { + this.listeners.addListener(RENDER_COMPLETE_EVENT, listener); + } + + /** + * Removes a previously registered render complete listener from this handler. + * This listener will no longer be called when the visualization finished rendering. + * + * @param {function} listener The listener to remove from this handler. + */ + public removeRenderCompleteListener(listener: () => void) { + this.listeners.removeListener(RENDER_COMPLETE_EVENT, listener); + } + + /** + * Fetches new data and renders the chart. This will happen debounced for a couple + * of milliseconds, to bundle fast successive calls into one fetch and render, + * e.g. while resizing the window, this will be triggered constantly on the resize + * event. + * + * @param forceFetch=false Whether the request handler should be signaled to forceFetch + * (i.e. ignore caching in case it supports it). If at least one call to this + * passed `true` the debounced fetch and render will be a force fetch. + */ + private fetchAndRender = (forceFetch = false): void => { + this.shouldForceNextFetch = forceFetch || this.shouldForceNextFetch; + this.debouncedFetchAndRender(); + }; + + private handleVisUpdate = () => { + if (this.appState) { + this.appState.vis = this.vis.getState(); + this.appState.save(); + } + + this.fetchAndRender(); + }; + + /** + * Force the fetch of new data and renders the chart again. + */ + private reload = () => { + this.fetchAndRender(true); + }; + + private fetch = (forceFetch: boolean = false) => { + this.dataLoaderParams.aggs = this.vis.getAggConfig(); + this.dataLoaderParams.forceFetch = forceFetch; + + return this.dataLoader.fetch(this.dataLoaderParams); + }; + + private render = (visData: any = null) => { + return visualizationLoader + .render(this.element, this.vis, visData, this.uiState, { + listenOnChange: false, + }) + .then(() => { + if (!this.loaded) { + this.loaded = true; + this.fetchAndRender(); + } + }); + }; +} diff --git a/src/ui/public/visualize/loader/index.js b/src/ui/public/visualize/loader/index.ts similarity index 100% rename from src/ui/public/visualize/loader/index.js rename to src/ui/public/visualize/loader/index.ts diff --git a/src/ui/public/visualize/loader/types.ts b/src/ui/public/visualize/loader/types.ts new file mode 100644 index 00000000000000..813694fb956b05 --- /dev/null +++ b/src/ui/public/visualize/loader/types.ts @@ -0,0 +1,107 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { SearchSource } from '../../courier'; +import { PersistedState } from '../../persisted_state'; +import { AppState } from '../../state_management/app_state'; +import { Vis } from '../../vis'; + +export interface TimeRange { + from: string; + to: string; +} + +export interface Filter { + meta: object; + query: object; +} + +export type Filters = Filter[]; + +export enum QueryLanguageType { + KUERY = 'kuery', + LUCENE = 'lucene', +} + +export interface Query { + language: QueryLanguageType; + query: string; +} + +export interface VisSavedObject { + vis: Vis; + description?: string; + searchSource: SearchSource; +} + +/** + * The parameters accepted by the embedVisualize calls. + */ +export interface VisualizeLoaderParams { + /** + * An object with a from/to key, that must be either a date in ISO format, or a + * valid datetime Elasticsearch expression, e.g.: { from: 'now-7d/d', to: 'now' } + */ + timeRange?: TimeRange; + /** + * If set to true, the visualization will be appended to the passed element instead + * of replacing all its content. (default: false) + */ + append?: boolean; + /** + * If specified this CSS class (or classes with space separated) will be set to + * the root visualize element. + */ + cssClass?: string; + /** + * An object of key-value pairs, that will be set as data-{key}="{value}" attributes + * on the visualization element. + */ + dataAttrs?: { [key: string]: string }; + /** + * Specifies the filters that should be applied to that visualization. + */ + filters?: Filters; + /** + * The query that should apply to that visualization. + */ + query?: Query; + /** + * The current uiState of the application. If you don't pass a uiState, the + * visualization will creates it's own uiState to store information like whether + * the legend is open or closed, but you don't have access to it from the outside. + * Pass one in if you need that access, e.g. for saving that state. + */ + uiState?: PersistedState; + /** + * The appState this visualization should use. If you don't specify it, the + * global AppState (that is decoded in the URL) will be used. Usually you don't + * need to overwrite this, unless you don't want the visualization to use the + * global AppState. + */ + appState?: AppState; +} + +/** + * The subset of properties allowed to update on an already embedded visualization. + */ +export type VisualizeUpdateParams = Pick< + VisualizeLoaderParams, + 'timeRange' | 'dataAttrs' | 'filters' | 'query' +>; diff --git a/src/ui/public/visualize/loader/visualization_loader.js b/src/ui/public/visualize/loader/visualization_loader.tsx similarity index 61% rename from src/ui/public/visualize/loader/visualization_loader.js rename to src/ui/public/visualize/loader/visualization_loader.tsx index faf99e9df4eeb4..715aea6cf79dec 100644 --- a/src/ui/public/visualize/loader/visualization_loader.js +++ b/src/ui/public/visualize/loader/visualization_loader.tsx @@ -20,22 +20,44 @@ import _ from 'lodash'; import React from 'react'; import { render, unmountComponentAtNode } from 'react-dom'; + +import { PersistedState } from '../../persisted_state'; +import { Vis } from '../../vis'; import { Visualization } from '../components/visualization'; +interface VisualizationLoaderParams { + listenOnChange?: boolean; +} -export const visualizationLoader = (element, vis, visData, uiState, params) => { +function renderVisualization( + element: HTMLElement, + vis: Vis, + visData: any, + uiState: PersistedState, + params: VisualizationLoaderParams +) { return new Promise(resolve => { const listenOnChange = _.get(params, 'listenOnChange', false); - render(, element); + render( + , + element + ); }); -}; +} + +function destroy(element?: HTMLElement) { + if (element) { + unmountComponentAtNode(element); + } +} -visualizationLoader.destroy = (element) => { - if (element) unmountComponentAtNode(element); +export const visualizationLoader = { + render: renderVisualization, + destroy, }; diff --git a/src/ui/public/visualize/loader/visualize_data_loader.js b/src/ui/public/visualize/loader/visualize_data_loader.js deleted file mode 100644 index 3cd5d5472aa704..00000000000000 --- a/src/ui/public/visualize/loader/visualize_data_loader.js +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -import { isEqual } from 'lodash'; -import { VisRequestHandlersRegistryProvider } from '../../registry/vis_request_handlers'; -import { VisResponseHandlersRegistryProvider } from '../../registry/vis_response_handlers'; - -import { - isTermSizeZeroError, -} from '../../elasticsearch_errors'; - -import { toastNotifications } from 'ui/notify'; - -function getHandler(from, name) { - if (typeof name === 'function') return name; - return from.find(handler => handler.name === name).handler; -} - -export class VisualizeDataLoader { - constructor(vis, Private) { - this._vis = vis; - - const { requestHandler, responseHandler } = this._vis.type; - - const requestHandlers = Private(VisRequestHandlersRegistryProvider); - const responseHandlers = Private(VisResponseHandlersRegistryProvider); - this._requestHandler = getHandler(requestHandlers, requestHandler); - this._responseHandler = getHandler(responseHandlers, responseHandler); - } - - fetch = async (props, forceFetch = false) => { - - this._vis.filters = { timeRange: props.timeRange }; - - const handlerParams = { ...props, forceFetch }; - - try { - // searchSource is only there for courier request handler - const requestHandlerResponse = await this._requestHandler(this._vis, handlerParams); - - //No need to call the response handler when there have been no data nor has been there changes - //in the vis-state (response handler does not depend on uiStat - const canSkipResponseHandler = ( - this._previousRequestHandlerResponse && this._previousRequestHandlerResponse === requestHandlerResponse && - this._previousVisState && isEqual(this._previousVisState, this._vis.getState()) - ); - - this._previousVisState = this._vis.getState(); - this._previousRequestHandlerResponse = requestHandlerResponse; - - if (!canSkipResponseHandler) { - this._visData = await Promise.resolve(this._responseHandler(this._vis, requestHandlerResponse)); - } - return this._visData; - } - catch (e) { - props.searchSource.cancelQueued(); - this._vis.requestError = e; - if (isTermSizeZeroError(e)) { - return toastNotifications.addDanger( - `Your visualization ('${props.vis.title}') has an error: it has a term ` + - `aggregation with a size of 0. Please set it to a number greater than 0 to resolve ` + - `the error.` - ); - } - toastNotifications.addDanger(e); - } - } - -} diff --git a/src/ui/public/visualize/loader/visualize_data_loader.ts b/src/ui/public/visualize/loader/visualize_data_loader.ts new file mode 100644 index 00000000000000..8c1146adc41933 --- /dev/null +++ b/src/ui/public/visualize/loader/visualize_data_loader.ts @@ -0,0 +1,107 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import { isEqual } from 'lodash'; + +import { VisRequestHandlersRegistryProvider as RequestHandlersProvider } from '../../registry/vis_request_handlers'; +import { VisResponseHandlersRegistryProvider as ResponseHandlerProvider } from '../../registry/vis_response_handlers'; + +import { IPrivate } from '../../private'; +import { + RequestHandler, + RequestHandlerDescription, + RequestHandlerParams, + ResponseHandler, + ResponseHandlerDescription, + Vis, +} from '../../vis'; + +// @ts-ignore No typing present +import { isTermSizeZeroError } from '../../elasticsearch_errors'; + +import { toastNotifications } from 'ui/notify'; + +function getHandler( + from: Array<{ name: string; handler: T }>, + type: string | T +): T { + if (typeof type === 'function') { + return type; + } + const handlerDesc = from.find(handler => handler.name === type); + if (!handlerDesc) { + throw new Error(`Could not find handler "${type}".`); + } + return handlerDesc.handler; +} + +export class VisualizeDataLoader { + private requestHandler: RequestHandler; + private responseHandler: ResponseHandler; + + private visData: any; + private previousVisState: any; + private previousRequestHandlerResponse: any; + + constructor(private readonly vis: Vis, Private: IPrivate) { + const { requestHandler, responseHandler } = vis.type; + + const requestHandlers: RequestHandlerDescription[] = Private(RequestHandlersProvider); + const responseHandlers: ResponseHandlerDescription[] = Private(ResponseHandlerProvider); + this.requestHandler = getHandler(requestHandlers, requestHandler); + this.responseHandler = getHandler(responseHandlers, responseHandler); + } + + public async fetch(params: RequestHandlerParams): Promise { + this.vis.filters = { timeRange: params.timeRange }; + + try { + // searchSource is only there for courier request handler + const requestHandlerResponse = await this.requestHandler(this.vis, params); + + // No need to call the response handler when there have been no data nor has been there changes + // in the vis-state (response handler does not depend on uiStat + const canSkipResponseHandler = + this.previousRequestHandlerResponse && + this.previousRequestHandlerResponse === requestHandlerResponse && + this.previousVisState && + isEqual(this.previousVisState, this.vis.getState()); + + this.previousVisState = this.vis.getState(); + this.previousRequestHandlerResponse = requestHandlerResponse; + + if (!canSkipResponseHandler) { + this.visData = await Promise.resolve( + this.responseHandler(this.vis, requestHandlerResponse) + ); + } + return this.visData; + } catch (e) { + params.searchSource.cancelQueued(); + this.vis.requestError = e; + if (isTermSizeZeroError(e)) { + return toastNotifications.addDanger( + `Your visualization ('${this.vis.title}') has an error: it has a term ` + + `aggregation with a size of 0. Please set it to a number greater than 0 to resolve ` + + `the error.` + ); + } + toastNotifications.addDanger(e); + } + } +} diff --git a/src/ui/public/visualize/loader/visualize_loader.js b/src/ui/public/visualize/loader/visualize_loader.js deleted file mode 100644 index 8cd81804610122..00000000000000 --- a/src/ui/public/visualize/loader/visualize_loader.js +++ /dev/null @@ -1,154 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -/** - * IMPORTANT: If you make changes to this API, please make sure to check that - * the docs (docs/development/visualize/development-create-visualization.asciidoc) - * are up to date. - */ -import chrome from '../../chrome'; -import '..'; -import { EmbeddedVisualizeHandler } from './embedded_visualize_handler'; -import { FilterBarQueryFilterProvider } from '../../filter_bar/query_filter'; - -/** - * The parameters accepted by the embedVisualize calls. - * @typedef {object} VisualizeLoaderParams - * @property {AppState} appState The appState this visualization should use. - * If you don't specify it, the global AppState (that is decoded in the URL) - * will be used. Usually you don't need to overwrite this, unless you don't - * want the visualization to use the global AppState. - * @property {UiState} uiState The current uiState of the application. If you - * don't pass a uiState, the visualization will creates it's own uiState to - * store information like whether the legend is open or closed, but you don't - * have access to it from the outside. Pass one in if you need that access. - * @property {object} timeRange An object with a from/to key, that must be - * either a date in ISO format, or a valid datetime Elasticsearch expression, - * e.g.: { from: 'now-7d/d', to: 'now' } - * @property {boolean} append If set to true, the visualization will be appended - * to the passed element instead of replacing all its content. (default: false) - * @property {string} cssClass If specified this CSS class (or classes with space separated) - * will be set to the root visualize element. - * @property {object} dataAttrs An object of key-value pairs, that will be set - * as data-{key}="{value}" attributes on the visualization element. - * @property {array} filters Specifies the filters that should be applied to that visualization. - * @property {object} query The query that should apply to that visualization. - */ - -const VisualizeLoaderProvider = ($compile, $rootScope, savedVisualizations, Private) => { - const renderVis = (container, savedObj, params) => { - - const { vis, description } = savedObj; - - vis.description = description; - vis.searchSource = savedObj.searchSource; - - // lets add query filter angular service to the params - params.queryFilter = Private(FilterBarQueryFilterProvider); - - // lets add Private to the params, we'll need to pass it to visualize later - params.Private = Private; - - if (!params.append) { - container.innerHTML = ''; - } - - const element = document.createElement('div'); - element.className = 'visualize'; - element.setAttribute('data-test-subj', 'visualizationLoader'); - container.appendChild(element); - - // If params specified cssClass, we will set this to the element. - if (params.cssClass) { - params.cssClass.split(' ').forEach(cssClass => { - element.classList.add(cssClass); - }); - } - - // Apply data- attributes to the element if specified - if (params.dataAttrs) { - Object.keys(params.dataAttrs).forEach(key => { - element.setAttribute(`data-${key}`, params.dataAttrs[key]); - }); - } - - return new EmbeddedVisualizeHandler(element, savedObj, params); - }; - - return { - /** - * Renders a saved visualization specified by its id into a DOM element. - * - * @param {Element} element The DOM element to render the visualization into. - * You can alternatively pass a jQuery element instead. - * @param {String} id The id of the saved visualization. This is the id of the - * saved object that is stored in the .kibana index. - * @param {VisualizeLoaderParams} params A list of parameters that will influence rendering. - * - * @return {Promise.} A promise that resolves to the - * handler for this visualization as soon as the saved object could be found. - */ - embedVisualizationWithId: async (element, savedVisualizationId, params) => { - return new Promise((resolve, reject) => { - savedVisualizations.get(savedVisualizationId).then(savedObj => { - const handler = renderVis(element, savedObj, params); - resolve(handler); - }, reject); - }); - }, - /** - * Renders a saved visualization specified by its savedObject into a DOM element. - * In most of the cases you will need this method, since it allows you to specify - * filters, handlers, queries, etc. on the savedObject before rendering. - * - * @param {Element} element The DOM element to render the visualization into. - * You can alternatively pass a jQuery element instead. - * @param {Object} savedObj The savedObject as it could be retrieved by the - * `savedVisualizations` service. - * @param {VisualizeLoaderParams} params A list of parameters that will influence rendering. - * - * @return {EmbeddedVisualizeHandler} The handler to the visualization. - */ - embedVisualizationWithSavedObject: (el, savedObj, params) => { - return renderVis(el, savedObj, params); - }, - /** - * Returns a promise, that resolves to a list of all saved visualizations. - * - * @return {Promise} Resolves with a list of all saved visualizations as - * returned by the `savedVisualizations` service in Kibana. - */ - getVisualizationList: () => { - return savedVisualizations.find().then(result => result.hits); - }, - }; -}; - -/** - * Returns a promise, that resolves with the visualize loader, once it's ready. - * @return {Promise} A promise, that resolves to the visualize loader. - */ -function getVisualizeLoader() { - return chrome.dangerouslyGetActiveInjector().then($injector => { - const Private = $injector.get('Private'); - return Private(VisualizeLoaderProvider); - }); -} - -export { getVisualizeLoader, VisualizeLoaderProvider }; diff --git a/src/ui/public/visualize/loader/visualize_loader.ts b/src/ui/public/visualize/loader/visualize_loader.ts new file mode 100644 index 00000000000000..b7877444de154c --- /dev/null +++ b/src/ui/public/visualize/loader/visualize_loader.ts @@ -0,0 +1,152 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/** + * IMPORTANT: If you make changes to this API, please make sure to check that + * the docs (docs/development/visualize/development-create-visualization.asciidoc) + * are up to date. + */ + +import chrome from '../../chrome'; +import { FilterBarQueryFilterProvider } from '../../filter_bar/query_filter'; +import { IPrivate } from '../../private'; +import { EmbeddedVisualizeHandler } from './embedded_visualize_handler'; +import { VisSavedObject, VisualizeLoaderParams } from './types'; + +class VisualizeLoader { + constructor(private readonly savedVisualizations: any, private readonly Private: IPrivate) {} + + /** + * Renders a saved visualization specified by its id into a DOM element. + * + * @param element The DOM element to render the visualization into. + * You can alternatively pass a jQuery element instead. + * @param id The id of the saved visualization. This is the id of the + * saved object that is stored in the .kibana index. + * @param params A list of parameters that will influence rendering. + * + * @return A promise that resolves to the + * handler for this visualization as soon as the saved object could be found. + */ + public async embedVisualizationWithId( + element: HTMLElement, + savedVisualizationId: string, + params: VisualizeLoaderParams + ) { + return new Promise((resolve, reject) => { + this.savedVisualizations.get(savedVisualizationId).then((savedObj: VisSavedObject) => { + const handler = this.renderVis(element, savedObj, params); + resolve(handler); + }, reject); + }); + } + + /** + * Renders a saved visualization specified by its savedObject into a DOM element. + * In most of the cases you will need this method, since it allows you to specify + * filters, handlers, queries, etc. on the savedObject before rendering. + * + * @param element The DOM element to render the visualization into. + * You can alternatively pass a jQuery element instead. + * @param savedObj The savedObject as it could be retrieved by the + * `savedVisualizations` service. + * @param params A list of parameters that will influence rendering. + * + * @return The handler to the visualization. + */ + public embedVisualizationWithSavedObject( + el: HTMLElement, + savedObj: VisSavedObject, + params: VisualizeLoaderParams + ) { + return this.renderVis(el, savedObj, params); + } + + /** + * Returns a promise, that resolves to a list of all saved visualizations. + * + * @return Resolves with a list of all saved visualizations as + * returned by the `savedVisualizations` service in Kibana. + */ + public getVisualizationList(): Promise { + return this.savedVisualizations.find().then((result: any) => result.hits); + } + + private renderVis( + container: HTMLElement, + savedObj: VisSavedObject, + params: VisualizeLoaderParams + ) { + const { vis, description, searchSource } = savedObj; + + vis.description = description; + vis.searchSource = searchSource; + + if (!params.append) { + container.innerHTML = ''; + } + + const element = document.createElement('div'); + element.className = 'visualize'; + element.setAttribute('data-test-subj', 'visualizationLoader'); + container.appendChild(element); + + // If params specified cssClass, we will set this to the element. + if (params.cssClass) { + params.cssClass.split(' ').forEach(cssClass => { + element.classList.add(cssClass); + }); + } + + // Apply data- attributes to the element if specified + const dataAttrs = params.dataAttrs; + if (dataAttrs) { + Object.keys(dataAttrs).forEach(key => { + element.setAttribute(`data-${key}`, dataAttrs[key]); + }); + } + + const handlerParams = { + ...params, + // lets add query filter angular service to the params + queryFilter: this.Private(FilterBarQueryFilterProvider), + // lets add Private to the params, we'll need to pass it to visualize later + Private: this.Private, + }; + + return new EmbeddedVisualizeHandler(element, savedObj, handlerParams); + } +} + +function VisualizeLoaderProvider(savedVisualizations: any, Private: IPrivate) { + return new VisualizeLoader(savedVisualizations, Private); +} + +/** + * Returns a promise, that resolves with the visualize loader, once it's ready. + * @return A promise, that resolves to the visualize loader. + */ +function getVisualizeLoader(): Promise { + return chrome.dangerouslyGetActiveInjector().then($injector => { + const Private: IPrivate = $injector.get('Private'); + return Private(VisualizeLoaderProvider); + }); +} + +export { getVisualizeLoader, VisualizeLoaderProvider };