From 62f3164166bdbd876d9d4e67b1e50345f060ff67 Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Fri, 18 Oct 2019 09:24:11 +0200 Subject: [PATCH 001/132] centralize dependencies --- .../kibana/public/home/components/add_data.js | 5 ++- .../public/home/components/add_data.test.js | 12 +++---- .../kibana/public/home/components/home_app.js | 31 +++++++++++-------- .../home/components/sample_data_set_cards.js | 5 ++- .../sample_data_view_data_button.js | 6 ++-- .../home/components/tutorial/tutorial.js | 4 +-- .../home/components/tutorial_directory.js | 4 +-- .../kibana/public/home/components/welcome.tsx | 12 +++---- .../core_plugins/kibana/public/home/index.js | 13 +++----- ...{kibana_services.js => kibana_services.ts} | 31 ++++++++++++++----- .../kibana/public/home/load_tutorials.js | 5 ++- .../kibana/public/home/sample_data_client.js | 14 ++++----- 12 files changed, 78 insertions(+), 64 deletions(-) rename src/legacy/core_plugins/kibana/public/home/{kibana_services.js => kibana_services.ts} (54%) diff --git a/src/legacy/core_plugins/kibana/public/home/components/add_data.js b/src/legacy/core_plugins/kibana/public/home/components/add_data.js index f8c8e0ec8411f..2bb11a46968b5 100644 --- a/src/legacy/core_plugins/kibana/public/home/components/add_data.js +++ b/src/legacy/core_plugins/kibana/public/home/components/add_data.js @@ -21,7 +21,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import classNames from 'classnames'; import { injectI18n, FormattedMessage } from '@kbn/i18n/react'; -import chrome from 'ui/chrome'; +import { getBasePath } from '../kibana_services'; import { EuiButton, @@ -38,8 +38,7 @@ import { EuiFlexGrid, } from '@elastic/eui'; -/* istanbul ignore next */ -const basePath = chrome.getBasePath(); +const basePath = getBasePath(); const AddDataUi = ({ apmUiEnabled, isNewKibanaInstance, intl, mlEnabled }) => { const renderCards = () => { diff --git a/src/legacy/core_plugins/kibana/public/home/components/add_data.test.js b/src/legacy/core_plugins/kibana/public/home/components/add_data.test.js index d7c8e9daa99da..f0417a6131dfa 100644 --- a/src/legacy/core_plugins/kibana/public/home/components/add_data.test.js +++ b/src/legacy/core_plugins/kibana/public/home/components/add_data.test.js @@ -20,10 +20,10 @@ import React from 'react'; import { AddData } from './add_data'; import { shallowWithIntl } from 'test_utils/enzyme_helpers'; -import chrome from 'ui/chrome'; +import { getBasePath } from '../kibana_services'; jest.mock( - 'ui/chrome', + '../kibana_services', () => ({ getBasePath: jest.fn(() => 'path'), }), @@ -37,7 +37,7 @@ test('render', () => { isNewKibanaInstance={false} />); expect(component).toMatchSnapshot(); // eslint-disable-line - expect(chrome.getBasePath).toHaveBeenCalledTimes(1); + expect(getBasePath).toHaveBeenCalledTimes(1); }); test('mlEnabled', () => { @@ -47,7 +47,7 @@ test('mlEnabled', () => { isNewKibanaInstance={false} />); expect(component).toMatchSnapshot(); // eslint-disable-line - expect(chrome.getBasePath).toHaveBeenCalledTimes(1); + expect(getBasePath).toHaveBeenCalledTimes(1); }); test('apmUiEnabled', () => { @@ -57,7 +57,7 @@ test('apmUiEnabled', () => { isNewKibanaInstance={false} />); expect(component).toMatchSnapshot(); // eslint-disable-line - expect(chrome.getBasePath).toHaveBeenCalledTimes(1); + expect(getBasePath).toHaveBeenCalledTimes(1); }); test('isNewKibanaInstance', () => { @@ -67,5 +67,5 @@ test('isNewKibanaInstance', () => { isNewKibanaInstance={true} />); expect(component).toMatchSnapshot(); // eslint-disable-line - expect(chrome.getBasePath).toHaveBeenCalledTimes(1); + expect(getBasePath).toHaveBeenCalledTimes(1); }); diff --git a/src/legacy/core_plugins/kibana/public/home/components/home_app.js b/src/legacy/core_plugins/kibana/public/home/components/home_app.js index 9aa44863f6d70..998f4e4608da3 100644 --- a/src/legacy/core_plugins/kibana/public/home/components/home_app.js +++ b/src/legacy/core_plugins/kibana/public/home/components/home_app.js @@ -26,23 +26,28 @@ import { Tutorial } from './tutorial/tutorial'; import { HashRouter as Router, Switch, - Route + Route, } from 'react-router-dom'; import { getTutorial } from '../load_tutorials'; import { replaceTemplateStrings } from './tutorial/replace_template_strings'; -import { telemetryOptInProvider, shouldShowTelemetryOptIn } from '../kibana_services'; -import chrome from 'ui/chrome'; +import { + telemetryOptInProvider, + shouldShowTelemetryOptIn, + getInjected, + savedObjectsClient, + getBasePath, + addBasePath, +} from '../kibana_services'; export function HomeApp({ directories }) { - const isCloudEnabled = chrome.getInjected('isCloudEnabled', false); - const apmUiEnabled = chrome.getInjected('apmUiEnabled', true); - const mlEnabled = chrome.getInjected('mlEnabled', false); - const savedObjectsClient = chrome.getSavedObjectsClient(); + const isCloudEnabled = getInjected('isCloudEnabled', false); + const apmUiEnabled = getInjected('apmUiEnabled', true); + const mlEnabled = getInjected('mlEnabled', false); const renderTutorialDirectory = (props) => { return ( @@ -52,7 +57,7 @@ export function HomeApp({ directories }) { const renderTutorial = (props) => { return ( @@ -85,13 +90,13 @@ export function HomeApp({ directories }) { path="/home" > ), - href: chrome.addBasePath(path) + href: addBasePath(path) }; }); const panels = [ diff --git a/src/legacy/core_plugins/kibana/public/home/components/tutorial/tutorial.js b/src/legacy/core_plugins/kibana/public/home/components/tutorial/tutorial.js index 3d8ea4815dfff..211801643a7a9 100644 --- a/src/legacy/core_plugins/kibana/public/home/components/tutorial/tutorial.js +++ b/src/legacy/core_plugins/kibana/public/home/components/tutorial/tutorial.js @@ -37,7 +37,7 @@ import { import * as StatusCheckStates from './status_check_states'; import { injectI18n, FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; -import chrome from 'ui/chrome'; +import { chrome } from '../../kibana_services'; const INSTRUCTIONS_TYPE = { ELASTIC_CLOUD: 'elasticCloud', @@ -94,7 +94,7 @@ class TutorialUi extends React.Component { }); } - chrome.breadcrumbs.set([ + chrome.setBreadcrumbs([ { text: homeTitle, href: '#/home' diff --git a/src/legacy/core_plugins/kibana/public/home/components/tutorial_directory.js b/src/legacy/core_plugins/kibana/public/home/components/tutorial_directory.js index eae549f8a6ac0..06c194a3f7ca8 100644 --- a/src/legacy/core_plugins/kibana/public/home/components/tutorial_directory.js +++ b/src/legacy/core_plugins/kibana/public/home/components/tutorial_directory.js @@ -22,7 +22,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import { Synopsis } from './synopsis'; import { SampleDataSetCards } from './sample_data_set_cards'; -import chrome from 'ui/chrome'; +import { chrome } from '../kibana_services'; import { EuiPage, @@ -112,7 +112,7 @@ class TutorialDirectoryUi extends React.Component { async componentDidMount() { this._isMounted = true; - chrome.breadcrumbs.set([ + chrome.setBreadcrumbs([ { text: homeTitle, href: '#/home', diff --git a/src/legacy/core_plugins/kibana/public/home/components/welcome.tsx b/src/legacy/core_plugins/kibana/public/home/components/welcome.tsx index 8869819290263..089739e380f11 100644 --- a/src/legacy/core_plugins/kibana/public/home/components/welcome.tsx +++ b/src/legacy/core_plugins/kibana/public/home/components/welcome.tsx @@ -33,15 +33,13 @@ import { EuiIcon, EuiPortal, } from '@elastic/eui'; -// @ts-ignore -import { banners } from 'ui/notify'; - import { FormattedMessage } from '@kbn/i18n/react'; -import chrome from 'ui/chrome'; +import { banners } from '../kibana_services'; + import { SampleDataCard } from './sample_data'; import { TelemetryOptInCard } from './telemetry_opt_in'; // @ts-ignore -import { trackUiMetric, METRIC_TYPE } from '../kibana_services'; +import { trackUiMetric, METRIC_TYPE, addBasePath } from '../kibana_services'; interface Props { urlBasePath: string; @@ -51,6 +49,7 @@ interface Props { getTelemetryBannerId: () => string; shouldShowTelemetryOptIn: boolean; } + interface State { step: number; } @@ -70,9 +69,10 @@ export class Welcome extends React.PureComponent { }; private redirecToSampleData() { - const path = chrome.addBasePath('#/home/tutorial_directory/sampleData'); + const path = addBasePath('#/home/tutorial_directory/sampleData'); window.location.href = path; } + private async handleTelemetrySelection(confirm: boolean) { const metricName = `telemetryOptIn${confirm ? 'Confirm' : 'Decline'}`; trackUiMetric(METRIC_TYPE.CLICK, metricName); diff --git a/src/legacy/core_plugins/kibana/public/home/index.js b/src/legacy/core_plugins/kibana/public/home/index.js index 8233df680edfd..f3edef6f09111 100644 --- a/src/legacy/core_plugins/kibana/public/home/index.js +++ b/src/legacy/core_plugins/kibana/public/home/index.js @@ -17,17 +17,14 @@ * under the License. */ -import chrome from 'ui/chrome'; +import { chrome, addBasePath, featureCatalogueRegistryProvider, wrapInI18nContext } from './kibana_services'; import routes from 'ui/routes'; import template from './home_ng_wrapper.html'; -import { FeatureCatalogueRegistryProvider } from 'ui/registry/feature_catalogue'; -import { wrapInI18nContext } from 'ui/i18n'; import { uiModules } from 'ui/modules'; import { HomeApp } from './components/home_app'; import { i18n } from '@kbn/i18n'; -import { npStart } from 'ui/new_platform'; const app = uiModules.get('apps/home', []); app.directive('homeApp', function (reactDirective) { @@ -39,10 +36,10 @@ const homeTitle = i18n.translate('kbn.home.breadcrumbs.homeTitle', { defaultMess function getRoute() { return { template, - controller($scope, Private) { - $scope.directories = Private(FeatureCatalogueRegistryProvider).inTitleOrder; - $scope.recentlyAccessed = npStart.core.chrome.recentlyAccessed.get().map(item => { - item.link = chrome.addBasePath(item.link); + controller($scope) { + $scope.directories = featureCatalogueRegistryProvider.inTitleOrder; + $scope.recentlyAccessed = chrome.recentlyAccessed.get().map(item => { + item.link = addBasePath(item.link); return item; }); }, diff --git a/src/legacy/core_plugins/kibana/public/home/kibana_services.js b/src/legacy/core_plugins/kibana/public/home/kibana_services.ts similarity index 54% rename from src/legacy/core_plugins/kibana/public/home/kibana_services.js rename to src/legacy/core_plugins/kibana/public/home/kibana_services.ts index 792c5e09435a4..b20175aadfda4 100644 --- a/src/legacy/core_plugins/kibana/public/home/kibana_services.js +++ b/src/legacy/core_plugins/kibana/public/home/kibana_services.ts @@ -17,24 +17,41 @@ * under the License. */ +// @ts-ignore import { uiModules } from 'ui/modules'; import { npStart } from 'ui/new_platform'; +import { IPrivate } from 'ui/private'; +import { FeatureCatalogueRegistryProvider } from 'ui/registry/feature_catalogue'; import { createUiStatsReporter, METRIC_TYPE } from '../../../ui_metric/public'; import { TelemetryOptInProvider } from '../../../telemetry/public/services'; +import { start as data } from '../../../data/public/legacy'; +// @ts-ignore +export { toastNotifications, banners } from 'ui/notify'; +export { kfetch } from 'ui/kfetch'; -export let indexPatternService; -export let shouldShowTelemetryOptIn; -export let telemetryOptInProvider; +export { wrapInI18nContext } from 'ui/i18n'; +export const getInjected = npStart.core.injectedMetadata.getInjectedVar; + +export const savedObjectsClient = npStart.core.savedObjects.client; +export const chrome = npStart.core.chrome; +export const uiSettings = npStart.core.uiSettings; +export const addBasePath = npStart.core.http.basePath.prepend; +export const getBasePath = npStart.core.http.basePath.get; + +export const indexPatternService = data.indexPatterns; +export let shouldShowTelemetryOptIn: boolean; +export let telemetryOptInProvider: any; +export let featureCatalogueRegistryProvider: any; export const trackUiMetric = createUiStatsReporter('Kibana_home'); export { METRIC_TYPE }; -uiModules.get('kibana').run(($injector) => { +uiModules.get('kibana').run((Private: IPrivate) => { const telemetryEnabled = npStart.core.injectedMetadata.getInjectedVar('telemetryEnabled'); const telemetryBanner = npStart.core.injectedMetadata.getInjectedVar('telemetryBanner'); - const Private = $injector.get('Private'); telemetryOptInProvider = Private(TelemetryOptInProvider); - shouldShowTelemetryOptIn = telemetryEnabled && telemetryBanner && !telemetryOptInProvider.getOptIn(); - indexPatternService = $injector.get('indexPatterns'); + shouldShowTelemetryOptIn = + telemetryEnabled && telemetryBanner && !telemetryOptInProvider.getOptIn(); + featureCatalogueRegistryProvider = Private(FeatureCatalogueRegistryProvider as any); }); diff --git a/src/legacy/core_plugins/kibana/public/home/load_tutorials.js b/src/legacy/core_plugins/kibana/public/home/load_tutorials.js index d6b264154d424..f8336a8b5d412 100644 --- a/src/legacy/core_plugins/kibana/public/home/load_tutorials.js +++ b/src/legacy/core_plugins/kibana/public/home/load_tutorials.js @@ -18,11 +18,10 @@ */ import _ from 'lodash'; -import chrome from 'ui/chrome'; +import { addBasePath, toastNotifications } from './kibana_services'; import { i18n } from '@kbn/i18n'; -import { toastNotifications } from 'ui/notify'; -const baseUrl = chrome.addBasePath('/api/kibana/home/tutorials'); +const baseUrl = addBasePath('/api/kibana/home/tutorials'); const headers = new Headers(); headers.append('Accept', 'application/json'); headers.append('Content-Type', 'application/json'); diff --git a/src/legacy/core_plugins/kibana/public/home/sample_data_client.js b/src/legacy/core_plugins/kibana/public/home/sample_data_client.js index da46b3e16c093..b7f7b92eced51 100644 --- a/src/legacy/core_plugins/kibana/public/home/sample_data_client.js +++ b/src/legacy/core_plugins/kibana/public/home/sample_data_client.js @@ -17,9 +17,7 @@ * under the License. */ -import { kfetch } from 'ui/kfetch'; -import chrome from 'ui/chrome'; -import { indexPatternService } from './kibana_services'; +import { indexPatternService, uiSettings, kfetch } from './kibana_services'; const sampleDataUrl = '/api/sample_data'; @@ -34,8 +32,8 @@ export async function listSampleDataSets() { export async function installSampleDataSet(id, sampleDataDefaultIndex) { await kfetch({ method: 'POST', pathname: `${sampleDataUrl}/${id}` }); - if (chrome.getUiSettingsClient().isDefault('defaultIndex')) { - chrome.getUiSettingsClient().set('defaultIndex', sampleDataDefaultIndex); + if (uiSettings.isDefault('defaultIndex')) { + uiSettings.set('defaultIndex', sampleDataDefaultIndex); } clearIndexPatternsCache(); @@ -44,9 +42,9 @@ export async function installSampleDataSet(id, sampleDataDefaultIndex) { export async function uninstallSampleDataSet(id, sampleDataDefaultIndex) { await kfetch({ method: 'DELETE', pathname: `${sampleDataUrl}/${id}` }); - if (!chrome.getUiSettingsClient().isDefault('defaultIndex') - && chrome.getUiSettingsClient().get('defaultIndex') === sampleDataDefaultIndex) { - chrome.getUiSettingsClient().set('defaultIndex', null); + if (!uiSettings.isDefault('defaultIndex') + && uiSettings.get('defaultIndex') === sampleDataDefaultIndex) { + uiSettings.set('defaultIndex', null); } clearIndexPatternsCache(); From 0bf00ae96e051ef4faa7d5e18427a1aab86d17e7 Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Fri, 18 Oct 2019 10:50:46 +0200 Subject: [PATCH 002/132] move more stuff into kibana_services --- .../kibana/public/home/components/home.js | 4 +-- .../public/home/components/home.test.mocks.ts | 24 ++++++---------- .../tutorial/replace_template_strings.js | 28 ++++++++----------- .../core_plugins/kibana/public/home/index.js | 12 ++++---- .../kibana/public/home/kibana_services.ts | 11 ++++++-- 5 files changed, 35 insertions(+), 44 deletions(-) diff --git a/src/legacy/core_plugins/kibana/public/home/components/home.js b/src/legacy/core_plugins/kibana/public/home/components/home.js index 6a8dff2ad4fa7..4cce329846710 100644 --- a/src/legacy/core_plugins/kibana/public/home/components/home.js +++ b/src/legacy/core_plugins/kibana/public/home/components/home.js @@ -22,7 +22,6 @@ import PropTypes from 'prop-types'; import { Synopsis } from './synopsis'; import { AddData } from './add_data'; import { FormattedMessage } from '@kbn/i18n/react'; -import chrome from 'ui/chrome'; import { EuiButton, @@ -40,6 +39,7 @@ import { import { Welcome } from './welcome'; import { FeatureCatalogueCategory } from 'ui/registry/feature_catalogue'; +import { getInjected } from '../kibana_services'; const KEY_ENABLE_WELCOME = 'home:welcome:show'; @@ -47,7 +47,7 @@ export class Home extends Component { constructor(props) { super(props); - const isWelcomeEnabled = !(chrome.getInjected('disableWelcomeScreen') || props.localStorage.getItem(KEY_ENABLE_WELCOME) === 'false'); + const isWelcomeEnabled = !(getInjected('disableWelcomeScreen') || props.localStorage.getItem(KEY_ENABLE_WELCOME) === 'false'); this.state = { // If welcome is enabled, we wait for loading to complete diff --git a/src/legacy/core_plugins/kibana/public/home/components/home.test.mocks.ts b/src/legacy/core_plugins/kibana/public/home/components/home.test.mocks.ts index 621c058c803db..cd7bc82fe3345 100644 --- a/src/legacy/core_plugins/kibana/public/home/components/home.test.mocks.ts +++ b/src/legacy/core_plugins/kibana/public/home/components/home.test.mocks.ts @@ -17,7 +17,12 @@ * under the License. */ -import { notificationServiceMock, overlayServiceMock } from '../../../../../../core/public/mocks'; +import { + notificationServiceMock, + overlayServiceMock, + httpServiceMock, + injectedMetadataServiceMock, +} from '../../../../../../core/public/mocks'; jest.doMock('ui/new_platform', () => { return { @@ -29,22 +34,9 @@ jest.doMock('ui/new_platform', () => { npStart: { core: { overlays: overlayServiceMock.createStartContract(), + http: httpServiceMock.createStartContract({ basePath: 'path' }), + injectedMetadata: injectedMetadataServiceMock.createStartContract(), }, }, }; }); - -jest.doMock( - 'ui/chrome', - () => ({ - getBasePath: jest.fn(() => 'path'), - getInjected: jest.fn(() => ''), - }), - { virtual: true } -); - -jest.doMock('ui/capabilities', () => ({ - catalogue: {}, - management: {}, - navLinks: {}, -})); diff --git a/src/legacy/core_plugins/kibana/public/home/components/tutorial/replace_template_strings.js b/src/legacy/core_plugins/kibana/public/home/components/tutorial/replace_template_strings.js index 7875c629306c5..cdbafadb228a4 100644 --- a/src/legacy/core_plugins/kibana/public/home/components/tutorial/replace_template_strings.js +++ b/src/legacy/core_plugins/kibana/public/home/components/tutorial/replace_template_strings.js @@ -18,13 +18,7 @@ */ import { Writer } from 'mustache'; -import chrome from 'ui/chrome'; -import { metadata } from 'ui/metadata'; -import { - DOC_LINK_VERSION, - ELASTIC_WEBSITE_URL, - documentationLinks -} from 'ui/documentation_links/documentation_links'; +import { getInjected, metadata, docLinks } from '../../kibana_services'; const TEMPLATE_TAGS = ['{', '}']; @@ -46,20 +40,20 @@ export function replaceTemplateStrings(text, params = {}) { curlyClose: '}', config: { cloud: { - id: chrome.getInjected('cloudId') + id: getInjected('cloudId') }, docs: { - base_url: ELASTIC_WEBSITE_URL, + base_url: docLinks.ELASTIC_WEBSITE_URL, beats: { - filebeat: documentationLinks.filebeat.base, - metricbeat: documentationLinks.metricbeat.base, - heartbeat: documentationLinks.heartbeat.base, - functionbeat: documentationLinks.functionbeat.base, - winlogbeat: documentationLinks.winlogbeat.base, - auditbeat: documentationLinks.auditbeat.base, + filebeat: docLinks.links.filebeat.base, + metricbeat: docLinks.links.metricbeat.base, + heartbeat: docLinks.links.heartbeat.base, + functionbeat: docLinks.links.functionbeat.base, + winlogbeat: docLinks.links.winlogbeat.base, + auditbeat: docLinks.links.auditbeat.base, }, - logstash: documentationLinks.logstash.base, - version: DOC_LINK_VERSION + logstash: docLinks.links.logstash.base, + version: docLinks.DOC_LINK_VERSION }, kibana: { version: metadata.version diff --git a/src/legacy/core_plugins/kibana/public/home/index.js b/src/legacy/core_plugins/kibana/public/home/index.js index f3edef6f09111..746cdfcfb4768 100644 --- a/src/legacy/core_plugins/kibana/public/home/index.js +++ b/src/legacy/core_plugins/kibana/public/home/index.js @@ -17,10 +17,8 @@ * under the License. */ -import { chrome, addBasePath, featureCatalogueRegistryProvider, wrapInI18nContext } from './kibana_services'; -import routes from 'ui/routes'; +import { chrome, addBasePath, featureCatalogueRegistryProvider, wrapInI18nContext, uiRoutes, uiModules } from './kibana_services'; import template from './home_ng_wrapper.html'; -import { uiModules } from 'ui/modules'; import { HomeApp } from './components/home_app'; @@ -51,7 +49,7 @@ function getRoute() { // All routing will be handled inside HomeApp via react, we just need to make sure angular doesn't // redirect us to the default page by encountering a url it isn't marked as being able to handle. -routes.when('/home', getRoute()); -routes.when('/home/feature_directory', getRoute()); -routes.when('/home/tutorial_directory/:tab?', getRoute()); -routes.when('/home/tutorial/:id', getRoute()); +uiRoutes.when('/home', getRoute()); +uiRoutes.when('/home/feature_directory', getRoute()); +uiRoutes.when('/home/tutorial_directory/:tab?', getRoute()); +uiRoutes.when('/home/tutorial/:id', getRoute()); diff --git a/src/legacy/core_plugins/kibana/public/home/kibana_services.ts b/src/legacy/core_plugins/kibana/public/home/kibana_services.ts index b20175aadfda4..cafe61fe825a9 100644 --- a/src/legacy/core_plugins/kibana/public/home/kibana_services.ts +++ b/src/legacy/core_plugins/kibana/public/home/kibana_services.ts @@ -18,7 +18,8 @@ */ // @ts-ignore -import { uiModules } from 'ui/modules'; +import { uiModules as modules } from 'ui/modules'; +import routes from 'ui/routes'; import { npStart } from 'ui/new_platform'; import { IPrivate } from 'ui/private'; import { FeatureCatalogueRegistryProvider } from 'ui/registry/feature_catalogue'; @@ -31,6 +32,12 @@ export { kfetch } from 'ui/kfetch'; export { wrapInI18nContext } from 'ui/i18n'; export const getInjected = npStart.core.injectedMetadata.getInjectedVar; +export const metadata = npStart.core.injectedMetadata.getLegacyMetadata(); + +export const docLinks = npStart.core.docLinks; + +export const uiRoutes = routes; +export const uiModules = modules; export const savedObjectsClient = npStart.core.savedObjects.client; export const chrome = npStart.core.chrome; @@ -46,7 +53,7 @@ export let featureCatalogueRegistryProvider: any; export const trackUiMetric = createUiStatsReporter('Kibana_home'); export { METRIC_TYPE }; -uiModules.get('kibana').run((Private: IPrivate) => { +modules.get('kibana').run((Private: IPrivate) => { const telemetryEnabled = npStart.core.injectedMetadata.getInjectedVar('telemetryEnabled'); const telemetryBanner = npStart.core.injectedMetadata.getInjectedVar('telemetryBanner'); From e72e51a4aa39225d327cf2698b9e1ccf20e83e08 Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Fri, 18 Oct 2019 14:54:17 +0200 Subject: [PATCH 003/132] fix tests and dependency --- .../kibana/public/home/components/add_data.test.js | 10 +++------- .../kibana/public/home/components/home.test.js | 5 +++++ .../components/sample_data_view_data_button.test.js | 11 ++++------- .../public/home/components/tutorial/tutorial.test.js | 6 ++++++ .../kibana/public/home/kibana_services.ts | 2 +- 5 files changed, 19 insertions(+), 15 deletions(-) diff --git a/src/legacy/core_plugins/kibana/public/home/components/add_data.test.js b/src/legacy/core_plugins/kibana/public/home/components/add_data.test.js index f0417a6131dfa..1860814f6e062 100644 --- a/src/legacy/core_plugins/kibana/public/home/components/add_data.test.js +++ b/src/legacy/core_plugins/kibana/public/home/components/add_data.test.js @@ -22,13 +22,9 @@ import { AddData } from './add_data'; import { shallowWithIntl } from 'test_utils/enzyme_helpers'; import { getBasePath } from '../kibana_services'; -jest.mock( - '../kibana_services', - () => ({ - getBasePath: jest.fn(() => 'path'), - }), - { virtual: true } -); +jest.mock('../kibana_services', () =>({ + getBasePath: jest.fn(() => 'path'), +})); test('render', () => { const component = shallowWithIntl(({ + getBasePath: () => 'path', + getInjected: () => '' +})); + describe('home', () => { let defaultProps; diff --git a/src/legacy/core_plugins/kibana/public/home/components/sample_data_view_data_button.test.js b/src/legacy/core_plugins/kibana/public/home/components/sample_data_view_data_button.test.js index b0551341965fa..4a2528b133def 100644 --- a/src/legacy/core_plugins/kibana/public/home/components/sample_data_view_data_button.test.js +++ b/src/legacy/core_plugins/kibana/public/home/components/sample_data_view_data_button.test.js @@ -17,19 +17,16 @@ * under the License. */ -jest.mock('ui/chrome', () => { - return { - addBasePath: (path) => { - return `root${path}`; - }, - }; -}); import React from 'react'; import { shallow } from 'enzyme'; import { SampleDataViewDataButton } from './sample_data_view_data_button'; +jest.mock('../kibana_services', () =>({ + addBasePath: path => `root${path}` +})); + test('should render simple button when appLinks is empty', () => { const component = shallow(({ + getBasePath: jest.fn(() => 'path'), + chrome: { + setBreadcrumbs: () => {} + } +})); jest.mock('../../../../../kibana_react/public', () => { return { Markdown: () =>
, diff --git a/src/legacy/core_plugins/kibana/public/home/kibana_services.ts b/src/legacy/core_plugins/kibana/public/home/kibana_services.ts index cafe61fe825a9..a44686999a120 100644 --- a/src/legacy/core_plugins/kibana/public/home/kibana_services.ts +++ b/src/legacy/core_plugins/kibana/public/home/kibana_services.ts @@ -45,7 +45,7 @@ export const uiSettings = npStart.core.uiSettings; export const addBasePath = npStart.core.http.basePath.prepend; export const getBasePath = npStart.core.http.basePath.get; -export const indexPatternService = data.indexPatterns; +export const indexPatternService = data.indexPatterns.indexPatterns; export let shouldShowTelemetryOptIn: boolean; export let telemetryOptInProvider: any; export let featureCatalogueRegistryProvider: any; From b42f417deda7a8e52ad4786219c2070ff3ac02cd Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Sat, 19 Oct 2019 11:07:45 +0200 Subject: [PATCH 004/132] bootstrap home via local application service --- .../kibana/public/home/components/add_data.js | 4 +- .../kibana/public/home/components/home.js | 3 +- .../kibana/public/home/components/home_app.js | 78 +++++++------- .../home/components/sample_data_set_cards.js | 3 +- .../sample_data_view_data_button.js | 7 +- .../home/components/tutorial_directory.js | 3 +- .../kibana/public/home/components/welcome.tsx | 5 +- .../core_plugins/kibana/public/home/index.js | 55 ---------- .../core_plugins/kibana/public/home/index.ts | 76 +++++++++++++ .../kibana/public/home/kibana_services.ts | 11 ++ .../kibana/public/home/load_tutorials.js | 3 +- .../core_plugins/kibana/public/home/plugin.ts | 102 ++++++++++++++++++ .../kibana/public/home/render_app.tsx | 90 ++++++++++++++++ .../kibana/public/home/sample_data_client.js | 3 +- .../core_plugins/kibana/public/kibana.js | 3 + .../public/local_application_service/index.ts | 20 ++++ .../local_application_service.ts | 76 +++++++++++++ 17 files changed, 440 insertions(+), 102 deletions(-) delete mode 100644 src/legacy/core_plugins/kibana/public/home/index.js create mode 100644 src/legacy/core_plugins/kibana/public/home/index.ts create mode 100644 src/legacy/core_plugins/kibana/public/home/plugin.ts create mode 100644 src/legacy/core_plugins/kibana/public/home/render_app.tsx create mode 100644 src/legacy/core_plugins/kibana/public/local_application_service/index.ts create mode 100644 src/legacy/core_plugins/kibana/public/local_application_service/local_application_service.ts diff --git a/src/legacy/core_plugins/kibana/public/home/components/add_data.js b/src/legacy/core_plugins/kibana/public/home/components/add_data.js index 2bb11a46968b5..3816380b1d9b7 100644 --- a/src/legacy/core_plugins/kibana/public/home/components/add_data.js +++ b/src/legacy/core_plugins/kibana/public/home/components/add_data.js @@ -21,7 +21,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import classNames from 'classnames'; import { injectI18n, FormattedMessage } from '@kbn/i18n/react'; -import { getBasePath } from '../kibana_services'; +import { getDeps } from '../kibana_services'; import { EuiButton, @@ -38,9 +38,9 @@ import { EuiFlexGrid, } from '@elastic/eui'; -const basePath = getBasePath(); const AddDataUi = ({ apmUiEnabled, isNewKibanaInstance, intl, mlEnabled }) => { + const basePath = getDeps().getBasePath(); const renderCards = () => { const apmData = { title: intl.formatMessage({ diff --git a/src/legacy/core_plugins/kibana/public/home/components/home.js b/src/legacy/core_plugins/kibana/public/home/components/home.js index 4cce329846710..9ffa4c7e6c44b 100644 --- a/src/legacy/core_plugins/kibana/public/home/components/home.js +++ b/src/legacy/core_plugins/kibana/public/home/components/home.js @@ -39,7 +39,8 @@ import { import { Welcome } from './welcome'; import { FeatureCatalogueCategory } from 'ui/registry/feature_catalogue'; -import { getInjected } from '../kibana_services'; +import { getDeps } from '../kibana_services'; +const { getInjected } = getDeps(); const KEY_ENABLE_WELCOME = 'home:welcome:show'; diff --git a/src/legacy/core_plugins/kibana/public/home/components/home_app.js b/src/legacy/core_plugins/kibana/public/home/components/home_app.js index 998f4e4608da3..3ccee843cb9a4 100644 --- a/src/legacy/core_plugins/kibana/public/home/components/home_app.js +++ b/src/legacy/core_plugins/kibana/public/home/components/home_app.js @@ -18,6 +18,7 @@ */ import React from 'react'; +import { I18nProvider } from '@kbn/i18n/react'; import PropTypes from 'prop-types'; import { Home } from './home'; import { FeatureDirectory } from './feature_directory'; @@ -31,13 +32,16 @@ import { import { getTutorial } from '../load_tutorials'; import { replaceTemplateStrings } from './tutorial/replace_template_strings'; import { + getDeps +} from '../kibana_services'; +const { telemetryOptInProvider, shouldShowTelemetryOptIn, getInjected, savedObjectsClient, getBasePath, addBasePath, -} from '../kibana_services'; +} = getDeps(); export function HomeApp({ directories }) { const isCloudEnabled = getInjected('isCloudEnabled', false); @@ -68,43 +72,45 @@ export function HomeApp({ directories }) { }; return ( - - - - - - + + + - - - - - - + + + + + + + + + ); } diff --git a/src/legacy/core_plugins/kibana/public/home/components/sample_data_set_cards.js b/src/legacy/core_plugins/kibana/public/home/components/sample_data_set_cards.js index 579a68641a9e2..c19a3f068ee60 100644 --- a/src/legacy/core_plugins/kibana/public/home/components/sample_data_set_cards.js +++ b/src/legacy/core_plugins/kibana/public/home/components/sample_data_set_cards.js @@ -31,7 +31,8 @@ import { UNINSTALLED_STATUS, } from './sample_data_set_card'; -import { toastNotifications, uiSettings } from '../kibana_services'; +import { getDeps } from '../kibana_services'; +const { toastNotifications, uiSettings } = getDeps(); import { listSampleDataSets, diff --git a/src/legacy/core_plugins/kibana/public/home/components/sample_data_view_data_button.js b/src/legacy/core_plugins/kibana/public/home/components/sample_data_view_data_button.js index a0f21c9b2dbf1..233a6d95c678a 100644 --- a/src/legacy/core_plugins/kibana/public/home/components/sample_data_view_data_button.js +++ b/src/legacy/core_plugins/kibana/public/home/components/sample_data_view_data_button.js @@ -27,7 +27,12 @@ import { } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { addBasePath } from '../kibana_services'; +import { + getDeps +} from '../kibana_services'; +const { + addBasePath, +} = getDeps(); export class SampleDataViewDataButton extends React.Component { diff --git a/src/legacy/core_plugins/kibana/public/home/components/tutorial_directory.js b/src/legacy/core_plugins/kibana/public/home/components/tutorial_directory.js index 06c194a3f7ca8..d3c170984d615 100644 --- a/src/legacy/core_plugins/kibana/public/home/components/tutorial_directory.js +++ b/src/legacy/core_plugins/kibana/public/home/components/tutorial_directory.js @@ -22,7 +22,8 @@ import React from 'react'; import PropTypes from 'prop-types'; import { Synopsis } from './synopsis'; import { SampleDataSetCards } from './sample_data_set_cards'; -import { chrome } from '../kibana_services'; +import { getDeps } from '../kibana_services'; +const { chrome } = getDeps(); import { EuiPage, diff --git a/src/legacy/core_plugins/kibana/public/home/components/welcome.tsx b/src/legacy/core_plugins/kibana/public/home/components/welcome.tsx index 089739e380f11..02b45a87b89ab 100644 --- a/src/legacy/core_plugins/kibana/public/home/components/welcome.tsx +++ b/src/legacy/core_plugins/kibana/public/home/components/welcome.tsx @@ -34,12 +34,11 @@ import { EuiPortal, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import { banners } from '../kibana_services'; +import { getDeps } from '../kibana_services'; import { SampleDataCard } from './sample_data'; import { TelemetryOptInCard } from './telemetry_opt_in'; -// @ts-ignore -import { trackUiMetric, METRIC_TYPE, addBasePath } from '../kibana_services'; +const { trackUiMetric, METRIC_TYPE, addBasePath, banners } = getDeps(); interface Props { urlBasePath: string; diff --git a/src/legacy/core_plugins/kibana/public/home/index.js b/src/legacy/core_plugins/kibana/public/home/index.js deleted file mode 100644 index 746cdfcfb4768..0000000000000 --- a/src/legacy/core_plugins/kibana/public/home/index.js +++ /dev/null @@ -1,55 +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 { chrome, addBasePath, featureCatalogueRegistryProvider, wrapInI18nContext, uiRoutes, uiModules } from './kibana_services'; -import template from './home_ng_wrapper.html'; -import { - HomeApp -} from './components/home_app'; -import { i18n } from '@kbn/i18n'; - -const app = uiModules.get('apps/home', []); -app.directive('homeApp', function (reactDirective) { - return reactDirective(wrapInI18nContext(HomeApp)); -}); - -const homeTitle = i18n.translate('kbn.home.breadcrumbs.homeTitle', { defaultMessage: 'Home' }); - -function getRoute() { - return { - template, - controller($scope) { - $scope.directories = featureCatalogueRegistryProvider.inTitleOrder; - $scope.recentlyAccessed = chrome.recentlyAccessed.get().map(item => { - item.link = addBasePath(item.link); - return item; - }); - }, - k7Breadcrumbs: () => [ - { text: homeTitle }, - ] - }; -} - -// All routing will be handled inside HomeApp via react, we just need to make sure angular doesn't -// redirect us to the default page by encountering a url it isn't marked as being able to handle. -uiRoutes.when('/home', getRoute()); -uiRoutes.when('/home/feature_directory', getRoute()); -uiRoutes.when('/home/tutorial_directory/:tab?', getRoute()); -uiRoutes.when('/home/tutorial/:id', getRoute()); diff --git a/src/legacy/core_plugins/kibana/public/home/index.ts b/src/legacy/core_plugins/kibana/public/home/index.ts new file mode 100644 index 0000000000000..b8c468820f201 --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/home/index.ts @@ -0,0 +1,76 @@ +/* + * 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 { FeatureCatalogueRegistryProvider } from 'ui/registry/feature_catalogue'; +import { npSetup, npStart } from 'ui/new_platform'; +import chrome from 'ui/chrome'; +import { IPrivate } from 'ui/private'; +// @ts-ignore +import { toastNotifications, banners } from 'ui/notify'; +import { kfetch } from 'ui/kfetch'; +import { HomePlugin, LegacyAngularInjectedDependencies } from './plugin'; +import { createUiStatsReporter, METRIC_TYPE } from '../../../ui_metric/public'; +import { start as data } from '../../../data/public/legacy'; +import { TelemetryOptInProvider } from '../../../telemetry/public/services'; +import { localApplicationService } from '../local_application_service'; + +export const uiStatsReporter = createUiStatsReporter('Kibana_home'); + +/** + * Get dependencies relying on the global angular context. + * They also have to get resolved together with the legacy imports above + */ +async function getAngularInjectedDependencies(): Promise { + const injector = await chrome.dangerouslyGetActiveInjector(); + + const Private = injector.get('Private'); + + const telemetryEnabled = npStart.core.injectedMetadata.getInjectedVar('telemetryEnabled'); + const telemetryBanner = npStart.core.injectedMetadata.getInjectedVar('telemetryBanner'); + const telemetryOptInProvider = Private(TelemetryOptInProvider); + + return { + telemetryOptInProvider, + shouldShowTelemetryOptIn: + telemetryEnabled && telemetryBanner && !telemetryOptInProvider.getOptIn(), + featureCatalogueRegistryProvider: Private(FeatureCatalogueRegistryProvider as any), + }; +} + +(async () => { + const instance = new HomePlugin(); + instance.setup(npSetup.core, { + __LEGACY: { + uiStatsReporter, + toastNotifications, + banners, + kfetch, + metadata: npStart.core.injectedMetadata.getLegacyMetadata(), + METRIC_TYPE, + }, + localApplicationService, + }); + instance.start(npStart.core, { + data, + npData: npStart.plugins.data, + __LEGACY: { + angularDependencies: await getAngularInjectedDependencies(), + }, + }); +})(); diff --git a/src/legacy/core_plugins/kibana/public/home/kibana_services.ts b/src/legacy/core_plugins/kibana/public/home/kibana_services.ts index a44686999a120..78aa60a6946fa 100644 --- a/src/legacy/core_plugins/kibana/public/home/kibana_services.ts +++ b/src/legacy/core_plugins/kibana/public/home/kibana_services.ts @@ -17,6 +17,16 @@ * under the License. */ +let deps: any = {}; + +export function setDeps(newDeps: any) { + deps = newDeps; +} + +export function getDeps() { + return deps; +} +/* // @ts-ignore import { uiModules as modules } from 'ui/modules'; import routes from 'ui/routes'; @@ -62,3 +72,4 @@ modules.get('kibana').run((Private: IPrivate) => { telemetryEnabled && telemetryBanner && !telemetryOptInProvider.getOptIn(); featureCatalogueRegistryProvider = Private(FeatureCatalogueRegistryProvider as any); }); +*/ diff --git a/src/legacy/core_plugins/kibana/public/home/load_tutorials.js b/src/legacy/core_plugins/kibana/public/home/load_tutorials.js index f8336a8b5d412..13e03061613ac 100644 --- a/src/legacy/core_plugins/kibana/public/home/load_tutorials.js +++ b/src/legacy/core_plugins/kibana/public/home/load_tutorials.js @@ -18,7 +18,8 @@ */ import _ from 'lodash'; -import { addBasePath, toastNotifications } from './kibana_services'; +import { getDeps } from './kibana_services'; +const { addBasePath, toastNotifications } = getDeps(); import { i18n } from '@kbn/i18n'; const baseUrl = addBasePath('/api/kibana/home/tutorials'); diff --git a/src/legacy/core_plugins/kibana/public/home/plugin.ts b/src/legacy/core_plugins/kibana/public/home/plugin.ts new file mode 100644 index 0000000000000..9e0a85b2b7f72 --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/home/plugin.ts @@ -0,0 +1,102 @@ +/* + * 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 { CoreSetup, CoreStart, Plugin } from 'kibana/public'; +import { Plugin as DataPlugin } from 'src/plugins/data/public'; + +import { DataStart } from '../../../data/public'; +import { LocalApplicationService } from '../local_application_service'; + +export interface LegacyAngularInjectedDependencies { + featureCatalogueRegistryProvider: any; + telemetryOptInProvider: any; + shouldShowTelemetryOptIn: boolean; +} + +export interface HomePluginStartDependencies { + data: DataStart; + npData: ReturnType; + __LEGACY: { + angularDependencies: LegacyAngularInjectedDependencies; + }; +} + +export interface HomePluginSetupDependencies { + __LEGACY: { + uiStatsReporter: any; + toastNotifications: any; + banners: any; + kfetch: any; + metadata: any; + METRIC_TYPE: any; + }; + localApplicationService: LocalApplicationService; +} + +export class HomePlugin implements Plugin { + private dataStart: DataStart | null = null; + private npDataStart: ReturnType | null = null; + private angularDependencies: LegacyAngularInjectedDependencies | null = null; + private savedObjectsClient: any = null; + + setup( + core: CoreSetup, + { + __LEGACY: { uiStatsReporter, toastNotifications, banners, kfetch, metadata, METRIC_TYPE }, + localApplicationService, + }: HomePluginSetupDependencies + ) { + localApplicationService.register({ + id: 'home', + title: 'Home', + mount: async (context, params) => { + const { renderApp } = await import('./render_app'); + return renderApp( + context, + { + ...params, + uiStatsReporter, + toastNotifications, + banners, + kfetch, + savedObjectsClient: this.savedObjectsClient, + metadata, + METRIC_TYPE, + data: this.dataStart!, + npData: this.npDataStart!, + }, + this.angularDependencies! + ); + }, + }); + } + + start( + core: CoreStart, + { data, npData, __LEGACY: { angularDependencies } }: HomePluginStartDependencies + ) { + // TODO is this really the right way? I though the app context would give us those + this.dataStart = data; + this.npDataStart = npData; + this.angularDependencies = angularDependencies; + this.savedObjectsClient = core.savedObjects.client; + } + + stop() {} +} diff --git a/src/legacy/core_plugins/kibana/public/home/render_app.tsx b/src/legacy/core_plugins/kibana/public/home/render_app.tsx new file mode 100644 index 0000000000000..f81f4cd0f1ba4 --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/home/render_app.tsx @@ -0,0 +1,90 @@ +/* + * 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 React from 'react'; +import { DataStart } from 'src/legacy/core_plugins/data/public'; +import { AppMountContext } from 'kibana/public'; +import { Plugin as DataPlugin } from 'src/plugins/data/public'; +import { render, unmountComponentAtNode } from 'react-dom'; +import { i18n } from '@kbn/i18n'; +import { LegacyAngularInjectedDependencies } from './plugin'; +import { setDeps } from './kibana_services'; + +/** + * These are dependencies of the Graph app besides the base dependencies + * provided by the application service. Some of those still rely on non-shimmed + * plugins in LP-world, but if they are migrated only the import path in the plugin + * itself changes + */ +export interface HomeDependencies { + element: HTMLElement; + appBasePath: string; + data: DataStart; + npData: ReturnType; + uiStatsReporter: any; + toastNotifications: any; + banners: any; + kfetch: any; + metadata: any; + savedObjectsClient: any; + METRIC_TYPE: any; +} + +export const renderApp = ( + { core }: AppMountContext, + { + element, + appBasePath, + data, + npData, + uiStatsReporter, + toastNotifications, + banners, + kfetch, + metadata, + savedObjectsClient, + }: HomeDependencies, + angularDeps: LegacyAngularInjectedDependencies +) => { + const deps = { + getInjected: core.injectedMetadata.getInjectedVar, + metadata, + docLinks: core.docLinks, + savedObjectsClient, + chrome: core.chrome, + uiSettings: core.uiSettings, + addBasePath: core.http.basePath.prepend, + getBasePath: core.http.basePath.get, + indexPatternService: data.indexPatterns.indexPatterns, + toastNotifications, + banners, + kfetch, + ...angularDeps, + }; + setDeps(deps); + + const homeTitle = i18n.translate('kbn.home.breadcrumbs.homeTitle', { defaultMessage: 'Home' }); + const directories = angularDeps.featureCatalogueRegistryProvider.inTitleOrder; + core.chrome.setBreadcrumbs([{ text: homeTitle }]); + + const HomeApp = require('./components/home_app').HomeApp; + render(, element); + + return () => unmountComponentAtNode(element); +}; diff --git a/src/legacy/core_plugins/kibana/public/home/sample_data_client.js b/src/legacy/core_plugins/kibana/public/home/sample_data_client.js index b7f7b92eced51..6afed700ab7a3 100644 --- a/src/legacy/core_plugins/kibana/public/home/sample_data_client.js +++ b/src/legacy/core_plugins/kibana/public/home/sample_data_client.js @@ -17,7 +17,8 @@ * under the License. */ -import { indexPatternService, uiSettings, kfetch } from './kibana_services'; +import { getDeps } from './kibana_services'; +const { indexPatternService, uiSettings, kfetch, } = getDeps(); const sampleDataUrl = '/api/sample_data'; diff --git a/src/legacy/core_plugins/kibana/public/kibana.js b/src/legacy/core_plugins/kibana/public/kibana.js index 6c809e84c8c84..f9b0980dcb7c9 100644 --- a/src/legacy/core_plugins/kibana/public/kibana.js +++ b/src/legacy/core_plugins/kibana/public/kibana.js @@ -58,6 +58,9 @@ import 'ui/agg_response'; import 'ui/agg_types'; import { showAppRedirectNotification } from 'ui/notify'; import 'leaflet'; +import { localApplicationService } from './local_application_service'; + +localApplicationService.registerWithAngularRouter(routes); routes.enable(); diff --git a/src/legacy/core_plugins/kibana/public/local_application_service/index.ts b/src/legacy/core_plugins/kibana/public/local_application_service/index.ts new file mode 100644 index 0000000000000..2128355ca906a --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/local_application_service/index.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 './local_application_service'; diff --git a/src/legacy/core_plugins/kibana/public/local_application_service/local_application_service.ts b/src/legacy/core_plugins/kibana/public/local_application_service/local_application_service.ts new file mode 100644 index 0000000000000..0432c56edc508 --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/local_application_service/local_application_service.ts @@ -0,0 +1,76 @@ +/* + * 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 { ApplicationSetup, App } from 'kibana/public'; +import { UIRoutes } from 'ui/routes'; +import { IScope } from 'angular'; +import { npStart } from 'ui/new_platform'; +import { htmlIdGenerator } from '@elastic/eui'; + +/** + * To be able to migrate and shim parts of the Kibana app plugin + * while still running some parts of it in the legacy world, this + * service emulates the core application service while using the global + * angular router to switch between apps without page reload. + * + * The id of the apps is used as prefix of the route - when switching between + * to apps, the current application is torn down. + * + * This service becomes unnecessary once the platform provides a central + * router that handles switching between applications without page reload. + */ +export interface LocalApplicationService { + register: ApplicationSetup['register']; + registerWithAngularRouter: (routeManager: UIRoutes) => void; +} + +const apps: App[] = []; +const idGenerator = htmlIdGenerator('kibanaAppLocalApp'); + +export const localApplicationService: LocalApplicationService = { + register(app) { + apps.push(app); + }, + registerWithAngularRouter(angularRouteManager: UIRoutes) { + apps.forEach(app => { + const wrapperElementId = idGenerator(); + const routeConfig = { + // marker for stuff operating on the route data. + // This can be used to not execute some operations because + // the route is not actually doing something besides serving + // as a wrapper for the actual inner-angular routes + outerAngularWrapperRoute: true, + reloadOnSearch: false, + reloadOnUrl: false, + template: `
`, + controller($scope: IScope) { + const element = document.getElementById(wrapperElementId)!; + // controller itself is not allowed to be async, use inner IIFE + (async () => { + const onUnmount = await app.mount({ core: npStart.core }, { element, appBasePath: '' }); + $scope.$on('$destroy', () => { + onUnmount(); + }); + })(); + }, + }; + angularRouteManager.when(`/${app.id}/:tail*?`, routeConfig); + }); + }, +}; From 0d2521e94f63a0cebdf8790a49e8d9b0ee5f5877 Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Sat, 19 Oct 2019 11:27:30 +0200 Subject: [PATCH 005/132] fix routing for home app --- .../kibana/public/home/components/home_app.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/legacy/core_plugins/kibana/public/home/components/home_app.js b/src/legacy/core_plugins/kibana/public/home/components/home_app.js index 3ccee843cb9a4..d05210b07fdc7 100644 --- a/src/legacy/core_plugins/kibana/public/home/components/home_app.js +++ b/src/legacy/core_plugins/kibana/public/home/components/home_app.js @@ -28,6 +28,7 @@ import { HashRouter as Router, Switch, Route, + Redirect, } from 'react-router-dom'; import { getTutorial } from '../load_tutorials'; import { replaceTemplateStrings } from './tutorial/replace_template_strings'; @@ -47,6 +48,7 @@ export function HomeApp({ directories }) { const isCloudEnabled = getInjected('isCloudEnabled', false); const apmUiEnabled = getInjected('apmUiEnabled', true); const mlEnabled = getInjected('mlEnabled', false); + const defaultAppId = getInjected('kbnDefaultAppId', 'discover'); const renderTutorialDirectory = (props) => { return ( @@ -76,14 +78,17 @@ export function HomeApp({ directories }) { + + + From af3ad7f0d7b895d26421dbc78189e12828f04bf7 Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Mon, 21 Oct 2019 14:44:18 +0200 Subject: [PATCH 006/132] switch to dependency getter instead of direct exports --- .../kibana/public/home/components/add_data.js | 4 +- .../public/home/components/add_data.test.js | 14 ++--- .../kibana/public/home/components/home.js | 7 ++- .../public/home/components/home.test.js | 6 ++- .../kibana/public/home/components/home_app.js | 16 +++--- .../home/components/sample_data_set_cards.js | 20 +++---- .../sample_data_view_data_button.js | 7 +-- .../tutorial/replace_template_strings.js | 5 +- .../home/components/tutorial/tutorial.js | 4 +- .../home/components/tutorial/tutorial.test.js | 10 ++-- .../home/components/tutorial_directory.js | 4 +- .../kibana/public/home/components/welcome.tsx | 22 ++++---- .../core_plugins/kibana/public/home/index.js | 5 +- .../kibana/public/home/kibana_services.ts | 54 ++++++++++++------- .../kibana/public/home/load_tutorials.js | 6 +-- .../kibana/public/home/sample_data_client.js | 16 +++--- 16 files changed, 120 insertions(+), 80 deletions(-) diff --git a/src/legacy/core_plugins/kibana/public/home/components/add_data.js b/src/legacy/core_plugins/kibana/public/home/components/add_data.js index 2bb11a46968b5..0a3adfa430a45 100644 --- a/src/legacy/core_plugins/kibana/public/home/components/add_data.js +++ b/src/legacy/core_plugins/kibana/public/home/components/add_data.js @@ -21,7 +21,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import classNames from 'classnames'; import { injectI18n, FormattedMessage } from '@kbn/i18n/react'; -import { getBasePath } from '../kibana_services'; +import { getServices } from '../kibana_services'; import { EuiButton, @@ -38,9 +38,9 @@ import { EuiFlexGrid, } from '@elastic/eui'; -const basePath = getBasePath(); const AddDataUi = ({ apmUiEnabled, isNewKibanaInstance, intl, mlEnabled }) => { + const basePath = getServices().getBasePath(); const renderCards = () => { const apmData = { title: intl.formatMessage({ diff --git a/src/legacy/core_plugins/kibana/public/home/components/add_data.test.js b/src/legacy/core_plugins/kibana/public/home/components/add_data.test.js index 1860814f6e062..81e3d6aaf63ad 100644 --- a/src/legacy/core_plugins/kibana/public/home/components/add_data.test.js +++ b/src/legacy/core_plugins/kibana/public/home/components/add_data.test.js @@ -20,10 +20,12 @@ import React from 'react'; import { AddData } from './add_data'; import { shallowWithIntl } from 'test_utils/enzyme_helpers'; -import { getBasePath } from '../kibana_services'; +import { getServices } from '../kibana_services'; jest.mock('../kibana_services', () =>({ - getBasePath: jest.fn(() => 'path'), + getServices: () => ({ + getBasePath: jest.fn(() => 'path'), + }), })); test('render', () => { @@ -33,7 +35,7 @@ test('render', () => { isNewKibanaInstance={false} />); expect(component).toMatchSnapshot(); // eslint-disable-line - expect(getBasePath).toHaveBeenCalledTimes(1); + expect(getServices().getBasePath).toHaveBeenCalledTimes(1); }); test('mlEnabled', () => { @@ -43,7 +45,7 @@ test('mlEnabled', () => { isNewKibanaInstance={false} />); expect(component).toMatchSnapshot(); // eslint-disable-line - expect(getBasePath).toHaveBeenCalledTimes(1); + expect(getServices().getBasePath).toHaveBeenCalledTimes(1); }); test('apmUiEnabled', () => { @@ -53,7 +55,7 @@ test('apmUiEnabled', () => { isNewKibanaInstance={false} />); expect(component).toMatchSnapshot(); // eslint-disable-line - expect(getBasePath).toHaveBeenCalledTimes(1); + expect(getServices().getBasePath).toHaveBeenCalledTimes(1); }); test('isNewKibanaInstance', () => { @@ -63,5 +65,5 @@ test('isNewKibanaInstance', () => { isNewKibanaInstance={true} />); expect(component).toMatchSnapshot(); // eslint-disable-line - expect(getBasePath).toHaveBeenCalledTimes(1); + expect(getServices().getBasePath).toHaveBeenCalledTimes(1); }); diff --git a/src/legacy/core_plugins/kibana/public/home/components/home.js b/src/legacy/core_plugins/kibana/public/home/components/home.js index 4cce329846710..e4c7de9b495a0 100644 --- a/src/legacy/core_plugins/kibana/public/home/components/home.js +++ b/src/legacy/core_plugins/kibana/public/home/components/home.js @@ -39,7 +39,7 @@ import { import { Welcome } from './welcome'; import { FeatureCatalogueCategory } from 'ui/registry/feature_catalogue'; -import { getInjected } from '../kibana_services'; +import { getServices } from '../kibana_services'; const KEY_ENABLE_WELCOME = 'home:welcome:show'; @@ -47,7 +47,10 @@ export class Home extends Component { constructor(props) { super(props); - const isWelcomeEnabled = !(getInjected('disableWelcomeScreen') || props.localStorage.getItem(KEY_ENABLE_WELCOME) === 'false'); + const isWelcomeEnabled = !( + getServices().getInjected('disableWelcomeScreen') || + props.localStorage.getItem(KEY_ENABLE_WELCOME) === 'false' + ); this.state = { // If welcome is enabled, we wait for loading to complete diff --git a/src/legacy/core_plugins/kibana/public/home/components/home.test.js b/src/legacy/core_plugins/kibana/public/home/components/home.test.js index 768b79906e13f..c21c6fa3d98a5 100644 --- a/src/legacy/core_plugins/kibana/public/home/components/home.test.js +++ b/src/legacy/core_plugins/kibana/public/home/components/home.test.js @@ -26,8 +26,10 @@ import { Home } from './home'; import { FeatureCatalogueCategory } from 'ui/registry/feature_catalogue'; jest.mock('../kibana_services', () =>({ - getBasePath: () => 'path', - getInjected: () => '' + getServices: () => ({ + getBasePath: () => 'path', + getInjected: () => '' + }) })); describe('home', () => { diff --git a/src/legacy/core_plugins/kibana/public/home/components/home_app.js b/src/legacy/core_plugins/kibana/public/home/components/home_app.js index 998f4e4608da3..005d4bdb0a99e 100644 --- a/src/legacy/core_plugins/kibana/public/home/components/home_app.js +++ b/src/legacy/core_plugins/kibana/public/home/components/home_app.js @@ -31,15 +31,19 @@ import { import { getTutorial } from '../load_tutorials'; import { replaceTemplateStrings } from './tutorial/replace_template_strings'; import { - telemetryOptInProvider, - shouldShowTelemetryOptIn, - getInjected, - savedObjectsClient, - getBasePath, - addBasePath, + getServices } from '../kibana_services'; export function HomeApp({ directories }) { + const { + telemetryOptInProvider, + shouldShowTelemetryOptIn, + getInjected, + savedObjectsClient, + getBasePath, + addBasePath, + } = getServices(); + const isCloudEnabled = getInjected('isCloudEnabled', false); const apmUiEnabled = getInjected('apmUiEnabled', true); const mlEnabled = getInjected('mlEnabled', false); diff --git a/src/legacy/core_plugins/kibana/public/home/components/sample_data_set_cards.js b/src/legacy/core_plugins/kibana/public/home/components/sample_data_set_cards.js index 579a68641a9e2..53d922c4e0a26 100644 --- a/src/legacy/core_plugins/kibana/public/home/components/sample_data_set_cards.js +++ b/src/legacy/core_plugins/kibana/public/home/components/sample_data_set_cards.js @@ -31,7 +31,7 @@ import { UNINSTALLED_STATUS, } from './sample_data_set_card'; -import { toastNotifications, uiSettings } from '../kibana_services'; +import { getServices } from '../kibana_services'; import { listSampleDataSets, @@ -41,13 +41,13 @@ import { import { i18n } from '@kbn/i18n'; -const IS_DARK_THEME = uiSettings.get('theme:darkMode'); - export class SampleDataSetCards extends React.Component { constructor(props) { super(props); + this.toastNotifications = getServices().toastNotifications; + this.state = { sampleDataSets: [], processingStatus: {}, @@ -69,7 +69,7 @@ export class SampleDataSetCards extends React.Component { try { sampleDataSets = await listSampleDataSets(); } catch (fetchError) { - toastNotifications.addDanger({ + this.toastNotifications.addDanger({ title: i18n.translate('kbn.home.sampleDataSet.unableToLoadListErrorMessage', { defaultMessage: 'Unable to load sample data sets list' } ), @@ -108,7 +108,7 @@ export class SampleDataSetCards extends React.Component { processingStatus: { ...prevState.processingStatus, [id]: false } })); } - toastNotifications.addDanger({ + this.toastNotifications.addDanger({ title: i18n.translate('kbn.home.sampleDataSet.unableToInstallErrorMessage', { defaultMessage: 'Unable to install sample data set: {name}', values: { name: targetSampleDataSet.name } } ), @@ -129,7 +129,7 @@ export class SampleDataSetCards extends React.Component { })); } - toastNotifications.addSuccess({ + this.toastNotifications.addSuccess({ title: i18n.translate('kbn.home.sampleDataSet.installedLabel', { defaultMessage: '{name} installed', values: { name: targetSampleDataSet.name } } ), @@ -154,7 +154,7 @@ export class SampleDataSetCards extends React.Component { processingStatus: { ...prevState.processingStatus, [id]: false } })); } - toastNotifications.addDanger({ + this.toastNotifications.addDanger({ title: i18n.translate('kbn.home.sampleDataSet.unableToUninstallErrorMessage', { defaultMessage: 'Unable to uninstall sample data set: {name}', values: { name: targetSampleDataSet.name } } ), @@ -175,7 +175,7 @@ export class SampleDataSetCards extends React.Component { })); } - toastNotifications.addSuccess({ + this.toastNotifications.addSuccess({ title: i18n.translate('kbn.home.sampleDataSet.uninstalledLabel', { defaultMessage: '{name} uninstalled', values: { name: targetSampleDataSet.name } } ), @@ -184,7 +184,9 @@ export class SampleDataSetCards extends React.Component { } lightOrDarkImage = (sampleDataSet) => { - return IS_DARK_THEME && sampleDataSet.darkPreviewImagePath ? sampleDataSet.darkPreviewImagePath : sampleDataSet.previewImagePath; + return getServices().uiSettings.get('theme:darkMode') && sampleDataSet.darkPreviewImagePath + ? sampleDataSet.darkPreviewImagePath + : sampleDataSet.previewImagePath; } render() { diff --git a/src/legacy/core_plugins/kibana/public/home/components/sample_data_view_data_button.js b/src/legacy/core_plugins/kibana/public/home/components/sample_data_view_data_button.js index a0f21c9b2dbf1..89d4909b0c66f 100644 --- a/src/legacy/core_plugins/kibana/public/home/components/sample_data_view_data_button.js +++ b/src/legacy/core_plugins/kibana/public/home/components/sample_data_view_data_button.js @@ -27,9 +27,10 @@ import { } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { addBasePath } from '../kibana_services'; +import { getServices } from '../kibana_services'; export class SampleDataViewDataButton extends React.Component { + addBasePath = getServices().addBasePath; state = { isPopoverOpen: false @@ -56,7 +57,7 @@ export class SampleDataViewDataButton extends React.Component { datasetName: this.props.name, }, }); - const dashboardPath = addBasePath(`/app/kibana#/dashboard/${this.props.overviewDashboard}`); + const dashboardPath = this.addBasePath(`/app/kibana#/dashboard/${this.props.overviewDashboard}`); if (this.props.appLinks.length === 0) { return ( @@ -79,7 +80,7 @@ export class SampleDataViewDataButton extends React.Component { size="m" /> ), - href: addBasePath(path) + href: this.addBasePath(path) }; }); const panels = [ diff --git a/src/legacy/core_plugins/kibana/public/home/components/tutorial/replace_template_strings.js b/src/legacy/core_plugins/kibana/public/home/components/tutorial/replace_template_strings.js index cdbafadb228a4..e68307b58cdaf 100644 --- a/src/legacy/core_plugins/kibana/public/home/components/tutorial/replace_template_strings.js +++ b/src/legacy/core_plugins/kibana/public/home/components/tutorial/replace_template_strings.js @@ -18,7 +18,8 @@ */ import { Writer } from 'mustache'; -import { getInjected, metadata, docLinks } from '../../kibana_services'; +import { getServices } from '../../kibana_services'; + const TEMPLATE_TAGS = ['{', '}']; @@ -33,6 +34,8 @@ mustacheWriter.escapedValue = function escapedValue(token, context) { }; export function replaceTemplateStrings(text, params = {}) { + const { getInjected, metadata, docLinks } = getServices(); + const variables = { // '{' and '}' can not be used in template since they are used as template tags. // Must use '{curlyOpen}'' and '{curlyClose}' diff --git a/src/legacy/core_plugins/kibana/public/home/components/tutorial/tutorial.js b/src/legacy/core_plugins/kibana/public/home/components/tutorial/tutorial.js index 211801643a7a9..086fa5a059121 100644 --- a/src/legacy/core_plugins/kibana/public/home/components/tutorial/tutorial.js +++ b/src/legacy/core_plugins/kibana/public/home/components/tutorial/tutorial.js @@ -37,7 +37,7 @@ import { import * as StatusCheckStates from './status_check_states'; import { injectI18n, FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; -import { chrome } from '../../kibana_services'; +import { getServices } from '../../kibana_services'; const INSTRUCTIONS_TYPE = { ELASTIC_CLOUD: 'elasticCloud', @@ -94,7 +94,7 @@ class TutorialUi extends React.Component { }); } - chrome.setBreadcrumbs([ + getServices().chrome.setBreadcrumbs([ { text: homeTitle, href: '#/home' diff --git a/src/legacy/core_plugins/kibana/public/home/components/tutorial/tutorial.test.js b/src/legacy/core_plugins/kibana/public/home/components/tutorial/tutorial.test.js index 157b7fa5dfecd..150ab4451e46d 100644 --- a/src/legacy/core_plugins/kibana/public/home/components/tutorial/tutorial.test.js +++ b/src/legacy/core_plugins/kibana/public/home/components/tutorial/tutorial.test.js @@ -26,10 +26,12 @@ import { jest.mock('../../kibana_services', () =>({ - getBasePath: jest.fn(() => 'path'), - chrome: { - setBreadcrumbs: () => {} - } + getServices: () =>({ + getBasePath: jest.fn(() => 'path'), + chrome: { + setBreadcrumbs: () => {} + } + }) })); jest.mock('../../../../../kibana_react/public', () => { return { diff --git a/src/legacy/core_plugins/kibana/public/home/components/tutorial_directory.js b/src/legacy/core_plugins/kibana/public/home/components/tutorial_directory.js index 06c194a3f7ca8..0c537c8e9ae8a 100644 --- a/src/legacy/core_plugins/kibana/public/home/components/tutorial_directory.js +++ b/src/legacy/core_plugins/kibana/public/home/components/tutorial_directory.js @@ -22,7 +22,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import { Synopsis } from './synopsis'; import { SampleDataSetCards } from './sample_data_set_cards'; -import { chrome } from '../kibana_services'; +import { getServices } from '../kibana_services'; import { EuiPage, @@ -112,7 +112,7 @@ class TutorialDirectoryUi extends React.Component { async componentDidMount() { this._isMounted = true; - chrome.setBreadcrumbs([ + getServices().chrome.setBreadcrumbs([ { text: homeTitle, href: '#/home', diff --git a/src/legacy/core_plugins/kibana/public/home/components/welcome.tsx b/src/legacy/core_plugins/kibana/public/home/components/welcome.tsx index 089739e380f11..afe43a23e18cb 100644 --- a/src/legacy/core_plugins/kibana/public/home/components/welcome.tsx +++ b/src/legacy/core_plugins/kibana/public/home/components/welcome.tsx @@ -34,12 +34,10 @@ import { EuiPortal, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import { banners } from '../kibana_services'; +import { getServices } from '../kibana_services'; import { SampleDataCard } from './sample_data'; import { TelemetryOptInCard } from './telemetry_opt_in'; -// @ts-ignore -import { trackUiMetric, METRIC_TYPE, addBasePath } from '../kibana_services'; interface Props { urlBasePath: string; @@ -58,6 +56,7 @@ interface State { * Shows a full-screen welcome page that gives helpful quick links to beginners. */ export class Welcome extends React.PureComponent { + private services = getServices(); public readonly state: State = { step: 0, }; @@ -69,32 +68,35 @@ export class Welcome extends React.PureComponent { }; private redirecToSampleData() { - const path = addBasePath('#/home/tutorial_directory/sampleData'); + const path = this.services.addBasePath('#/home/tutorial_directory/sampleData'); window.location.href = path; } private async handleTelemetrySelection(confirm: boolean) { const metricName = `telemetryOptIn${confirm ? 'Confirm' : 'Decline'}`; - trackUiMetric(METRIC_TYPE.CLICK, metricName); + this.services.trackUiMetric(this.services.METRIC_TYPE.CLICK, metricName); await this.props.setOptIn(confirm); const bannerId = this.props.getTelemetryBannerId(); - banners.remove(bannerId); + this.services.banners.remove(bannerId); this.setState(() => ({ step: 1 })); } private onSampleDataDecline = () => { - trackUiMetric(METRIC_TYPE.CLICK, 'sampleDataDecline'); + this.services.trackUiMetric(this.services.METRIC_TYPE.CLICK, 'sampleDataDecline'); this.props.onSkip(); }; private onSampleDataConfirm = () => { - trackUiMetric(METRIC_TYPE.CLICK, 'sampleDataConfirm'); + this.services.trackUiMetric(this.services.METRIC_TYPE.CLICK, 'sampleDataConfirm'); this.redirecToSampleData(); }; componentDidMount() { - trackUiMetric(METRIC_TYPE.LOADED, 'welcomeScreenMount'); + this.services.trackUiMetric(this.services.METRIC_TYPE.LOADED, 'welcomeScreenMount'); if (this.props.shouldShowTelemetryOptIn) { - trackUiMetric(METRIC_TYPE.COUNT, 'welcomeScreenWithTelemetryOptIn'); + this.services.trackUiMetric( + this.services.METRIC_TYPE.COUNT, + 'welcomeScreenWithTelemetryOptIn' + ); } document.addEventListener('keydown', this.hideOnEsc); } diff --git a/src/legacy/core_plugins/kibana/public/home/index.js b/src/legacy/core_plugins/kibana/public/home/index.js index 746cdfcfb4768..8ef5972d36683 100644 --- a/src/legacy/core_plugins/kibana/public/home/index.js +++ b/src/legacy/core_plugins/kibana/public/home/index.js @@ -17,13 +17,15 @@ * under the License. */ -import { chrome, addBasePath, featureCatalogueRegistryProvider, wrapInI18nContext, uiRoutes, uiModules } from './kibana_services'; +import { getServices } from './kibana_services'; import template from './home_ng_wrapper.html'; import { HomeApp } from './components/home_app'; import { i18n } from '@kbn/i18n'; +const { wrapInI18nContext, uiRoutes, uiModules } = getServices(); + const app = uiModules.get('apps/home', []); app.directive('homeApp', function (reactDirective) { return reactDirective(wrapInI18nContext(HomeApp)); @@ -35,6 +37,7 @@ function getRoute() { return { template, controller($scope) { + const { chrome, addBasePath, featureCatalogueRegistryProvider } = getServices(); $scope.directories = featureCatalogueRegistryProvider.inTitleOrder; $scope.recentlyAccessed = chrome.recentlyAccessed.get().map(item => { item.link = addBasePath(item.link); diff --git a/src/legacy/core_plugins/kibana/public/home/kibana_services.ts b/src/legacy/core_plugins/kibana/public/home/kibana_services.ts index a44686999a120..868e977a4601c 100644 --- a/src/legacy/core_plugins/kibana/public/home/kibana_services.ts +++ b/src/legacy/core_plugins/kibana/public/home/kibana_services.ts @@ -17,6 +17,12 @@ * under the License. */ +// @ts-ignore +import { toastNotifications, banners } from 'ui/notify'; +import { kfetch } from 'ui/kfetch'; + +import { wrapInI18nContext } from 'ui/i18n'; + // @ts-ignore import { uiModules as modules } from 'ui/modules'; import routes from 'ui/routes'; @@ -26,32 +32,40 @@ import { FeatureCatalogueRegistryProvider } from 'ui/registry/feature_catalogue' import { createUiStatsReporter, METRIC_TYPE } from '../../../ui_metric/public'; import { TelemetryOptInProvider } from '../../../telemetry/public/services'; import { start as data } from '../../../data/public/legacy'; -// @ts-ignore -export { toastNotifications, banners } from 'ui/notify'; -export { kfetch } from 'ui/kfetch'; -export { wrapInI18nContext } from 'ui/i18n'; -export const getInjected = npStart.core.injectedMetadata.getInjectedVar; -export const metadata = npStart.core.injectedMetadata.getLegacyMetadata(); +let shouldShowTelemetryOptIn: boolean; +let telemetryOptInProvider: any; +let featureCatalogueRegistryProvider: any; + +export function getServices() { + return { + getInjected: npStart.core.injectedMetadata.getInjectedVar, + metadata: npStart.core.injectedMetadata.getLegacyMetadata(), + docLinks: npStart.core.docLinks, -export const docLinks = npStart.core.docLinks; + uiRoutes: routes, + uiModules: modules, -export const uiRoutes = routes; -export const uiModules = modules; + savedObjectsClient: npStart.core.savedObjects.client, + chrome: npStart.core.chrome, + uiSettings: npStart.core.uiSettings, + addBasePath: npStart.core.http.basePath.prepend, + getBasePath: npStart.core.http.basePath.get, -export const savedObjectsClient = npStart.core.savedObjects.client; -export const chrome = npStart.core.chrome; -export const uiSettings = npStart.core.uiSettings; -export const addBasePath = npStart.core.http.basePath.prepend; -export const getBasePath = npStart.core.http.basePath.get; + indexPatternService: data.indexPatterns.indexPatterns, + shouldShowTelemetryOptIn, + telemetryOptInProvider, + featureCatalogueRegistryProvider, -export const indexPatternService = data.indexPatterns.indexPatterns; -export let shouldShowTelemetryOptIn: boolean; -export let telemetryOptInProvider: any; -export let featureCatalogueRegistryProvider: any; + trackUiMetric: createUiStatsReporter('Kibana_home'), + METRIC_TYPE, -export const trackUiMetric = createUiStatsReporter('Kibana_home'); -export { METRIC_TYPE }; + toastNotifications, + banners, + kfetch, + wrapInI18nContext, + }; +} modules.get('kibana').run((Private: IPrivate) => { const telemetryEnabled = npStart.core.injectedMetadata.getInjectedVar('telemetryEnabled'); diff --git a/src/legacy/core_plugins/kibana/public/home/load_tutorials.js b/src/legacy/core_plugins/kibana/public/home/load_tutorials.js index f8336a8b5d412..a6f19bc166dc7 100644 --- a/src/legacy/core_plugins/kibana/public/home/load_tutorials.js +++ b/src/legacy/core_plugins/kibana/public/home/load_tutorials.js @@ -18,10 +18,10 @@ */ import _ from 'lodash'; -import { addBasePath, toastNotifications } from './kibana_services'; +import { getServices } from './kibana_services'; import { i18n } from '@kbn/i18n'; -const baseUrl = addBasePath('/api/kibana/home/tutorials'); +const baseUrl = getServices().addBasePath('/api/kibana/home/tutorials'); const headers = new Headers(); headers.append('Accept', 'application/json'); headers.append('Content-Type', 'application/json'); @@ -46,7 +46,7 @@ async function loadTutorials() { tutorials = await response.json(); tutorialsLoaded = true; } catch(err) { - toastNotifications.addDanger({ + getServices().toastNotifications.addDanger({ title: i18n.translate('kbn.home.loadTutorials.unableToLoadErrorMessage', { defaultMessage: 'Unable to load tutorials' } ), diff --git a/src/legacy/core_plugins/kibana/public/home/sample_data_client.js b/src/legacy/core_plugins/kibana/public/home/sample_data_client.js index b7f7b92eced51..9411373004c25 100644 --- a/src/legacy/core_plugins/kibana/public/home/sample_data_client.js +++ b/src/legacy/core_plugins/kibana/public/home/sample_data_client.js @@ -17,30 +17,32 @@ * under the License. */ -import { indexPatternService, uiSettings, kfetch } from './kibana_services'; +import { getServices } from './kibana_services'; const sampleDataUrl = '/api/sample_data'; function clearIndexPatternsCache() { - indexPatternService.clearCache(); + getServices().indexPatternService.clearCache(); } export async function listSampleDataSets() { - return await kfetch({ method: 'GET', pathname: sampleDataUrl }); + return await getServices().kfetch({ method: 'GET', pathname: sampleDataUrl }); } export async function installSampleDataSet(id, sampleDataDefaultIndex) { - await kfetch({ method: 'POST', pathname: `${sampleDataUrl}/${id}` }); + await getServices().kfetch({ method: 'POST', pathname: `${sampleDataUrl}/${id}` }); - if (uiSettings.isDefault('defaultIndex')) { - uiSettings.set('defaultIndex', sampleDataDefaultIndex); + if (getServices().uiSettings.isDefault('defaultIndex')) { + getServices().uiSettings.set('defaultIndex', sampleDataDefaultIndex); } clearIndexPatternsCache(); } export async function uninstallSampleDataSet(id, sampleDataDefaultIndex) { - await kfetch({ method: 'DELETE', pathname: `${sampleDataUrl}/${id}` }); + await getServices().kfetch({ method: 'DELETE', pathname: `${sampleDataUrl}/${id}` }); + + const uiSettings = getServices().uiSettings; if (!uiSettings.isDefault('defaultIndex') && uiSettings.get('defaultIndex') === sampleDataDefaultIndex) { From a1295d3140f46d3db7d2039e14c2cabefcf441a2 Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Mon, 21 Oct 2019 15:49:42 +0200 Subject: [PATCH 007/132] clean up --- .../core_plugins/kibana/public/home/index.ts | 7 +- .../kibana/public/home/kibana_services.ts | 106 ++++++++---------- .../core_plugins/kibana/public/home/plugin.ts | 74 ++++++------ .../kibana/public/home/render_app.tsx | 67 ++--------- .../local_application_service.ts | 38 +++---- .../ui/public/routes/route_manager.d.ts | 2 + 6 files changed, 110 insertions(+), 184 deletions(-) diff --git a/src/legacy/core_plugins/kibana/public/home/index.ts b/src/legacy/core_plugins/kibana/public/home/index.ts index b8c468820f201..42abbec798592 100644 --- a/src/legacy/core_plugins/kibana/public/home/index.ts +++ b/src/legacy/core_plugins/kibana/public/home/index.ts @@ -30,7 +30,7 @@ import { start as data } from '../../../data/public/legacy'; import { TelemetryOptInProvider } from '../../../telemetry/public/services'; import { localApplicationService } from '../local_application_service'; -export const uiStatsReporter = createUiStatsReporter('Kibana_home'); +export const trackUiMetric = createUiStatsReporter('Kibana_home'); /** * Get dependencies relying on the global angular context. @@ -57,18 +57,17 @@ async function getAngularInjectedDependencies(): Promise unknown; + chrome: ChromeStart; + telemetryOptInProvider: any; + uiSettings: UiSettingsClientContract; + kfetch: (options: KFetchOptions, kfetchOptions?: KFetchKibanaOptions) => Promise; + savedObjectsClient: SavedObjectsClientContract; + toastNotifications: ToastNotifications; + banners: any; + METRIC_TYPE: any; + trackUiMetric: (type: UiStatsMetricType, eventNames: string | string[], count?: number) => void; + getBasePath: () => string; + shouldShowTelemetryOptIn: boolean; + docLinks: DocLinksStart; + addBasePath: (url: string) => string; +} let services: HomeKibanaServices | null = null; @@ -38,64 +81,3 @@ export function getServices() { } return services; } -/* -// @ts-ignore -import { toastNotifications, banners } from 'ui/notify'; -import { kfetch } from 'ui/kfetch'; - -import { wrapInI18nContext } from 'ui/i18n'; - -// @ts-ignore -import { uiModules as modules } from 'ui/modules'; -import routes from 'ui/routes'; -import { npStart } from 'ui/new_platform'; -import { IPrivate } from 'ui/private'; -import { FeatureCatalogueRegistryProvider } from 'ui/registry/feature_catalogue'; -import { createUiStatsReporter, METRIC_TYPE } from '../../../ui_metric/public'; -import { TelemetryOptInProvider } from '../../../telemetry/public/services'; -import { start as data } from '../../../data/public/legacy'; - -let shouldShowTelemetryOptIn: boolean; -let telemetryOptInProvider: any; -let featureCatalogueRegistryProvider: any; - -export function getServices() { - return { - getInjected: npStart.core.injectedMetadata.getInjectedVar, - metadata: npStart.core.injectedMetadata.getLegacyMetadata(), - docLinks: npStart.core.docLinks, - - uiRoutes: routes, - uiModules: modules, - - savedObjectsClient: npStart.core.savedObjects.client, - chrome: npStart.core.chrome, - uiSettings: npStart.core.uiSettings, - addBasePath: npStart.core.http.basePath.prepend, - getBasePath: npStart.core.http.basePath.get, - - indexPatternService: data.indexPatterns.indexPatterns, - shouldShowTelemetryOptIn, - telemetryOptInProvider, - featureCatalogueRegistryProvider, - - trackUiMetric: createUiStatsReporter('Kibana_home'), - METRIC_TYPE, - - toastNotifications, - banners, - kfetch, - wrapInI18nContext, - }; -} - -modules.get('kibana').run((Private: IPrivate) => { - const telemetryEnabled = npStart.core.injectedMetadata.getInjectedVar('telemetryEnabled'); - const telemetryBanner = npStart.core.injectedMetadata.getInjectedVar('telemetryBanner'); - - telemetryOptInProvider = Private(TelemetryOptInProvider); - shouldShowTelemetryOptIn = - telemetryEnabled && telemetryBanner && !telemetryOptInProvider.getOptIn(); - featureCatalogueRegistryProvider = Private(FeatureCatalogueRegistryProvider as any); -}); -*/ diff --git a/src/legacy/core_plugins/kibana/public/home/plugin.ts b/src/legacy/core_plugins/kibana/public/home/plugin.ts index 9e0a85b2b7f72..b2d528fbc0950 100644 --- a/src/legacy/core_plugins/kibana/public/home/plugin.ts +++ b/src/legacy/core_plugins/kibana/public/home/plugin.ts @@ -17,11 +17,15 @@ * under the License. */ -import { CoreSetup, CoreStart, Plugin } from 'kibana/public'; -import { Plugin as DataPlugin } from 'src/plugins/data/public'; +import { CoreSetup, CoreStart, LegacyNavLink, Plugin, UiSettingsState } from 'kibana/public'; +import { UiStatsMetricType } from '@kbn/analytics'; +import { ToastNotifications } from 'ui/notify/toasts/toast_notifications'; +import { KFetchOptions } from 'ui/kfetch'; +import { KFetchKibanaOptions } from 'ui/kfetch/kfetch'; import { DataStart } from '../../../data/public'; import { LocalApplicationService } from '../local_application_service'; +import { setServices } from './kibana_services'; export interface LegacyAngularInjectedDependencies { featureCatalogueRegistryProvider: any; @@ -31,7 +35,6 @@ export interface LegacyAngularInjectedDependencies { export interface HomePluginStartDependencies { data: DataStart; - npData: ReturnType; __LEGACY: { angularDependencies: LegacyAngularInjectedDependencies; }; @@ -39,61 +42,62 @@ export interface HomePluginStartDependencies { export interface HomePluginSetupDependencies { __LEGACY: { - uiStatsReporter: any; - toastNotifications: any; + trackUiMetric: (type: UiStatsMetricType, eventNames: string | string[], count?: number) => void; + toastNotifications: ToastNotifications; banners: any; - kfetch: any; - metadata: any; METRIC_TYPE: any; + kfetch: (options: KFetchOptions, kfetchOptions?: KFetchKibanaOptions) => Promise; + metadata: { + app: unknown; + bundleId: string; + nav: LegacyNavLink[]; + version: string; + branch: string; + buildNum: number; + buildSha: string; + basePath: string; + serverName: string; + devMode: boolean; + uiSettings: { defaults: UiSettingsState; user?: UiSettingsState | undefined }; + }; + localApplicationService: LocalApplicationService; }; - localApplicationService: LocalApplicationService; } export class HomePlugin implements Plugin { private dataStart: DataStart | null = null; - private npDataStart: ReturnType | null = null; private angularDependencies: LegacyAngularInjectedDependencies | null = null; private savedObjectsClient: any = null; setup( core: CoreSetup, - { - __LEGACY: { uiStatsReporter, toastNotifications, banners, kfetch, metadata, METRIC_TYPE }, - localApplicationService, - }: HomePluginSetupDependencies + { __LEGACY: { localApplicationService, ...legacyServices } }: HomePluginSetupDependencies ) { localApplicationService.register({ id: 'home', title: 'Home', - mount: async (context, params) => { + mount: async ({ core: contextCore }, params) => { + setServices({ + ...legacyServices, + getInjected: core.injectedMetadata.getInjectedVar, + docLinks: contextCore.docLinks, + savedObjectsClient: this.savedObjectsClient!, + chrome: contextCore.chrome, + uiSettings: core.uiSettings, + addBasePath: core.http.basePath.prepend, + getBasePath: core.http.basePath.get, + indexPatternService: this.dataStart!.indexPatterns.indexPatterns, + ...this.angularDependencies!, + }); const { renderApp } = await import('./render_app'); - return renderApp( - context, - { - ...params, - uiStatsReporter, - toastNotifications, - banners, - kfetch, - savedObjectsClient: this.savedObjectsClient, - metadata, - METRIC_TYPE, - data: this.dataStart!, - npData: this.npDataStart!, - }, - this.angularDependencies! - ); + return renderApp(params.element); }, }); } - start( - core: CoreStart, - { data, npData, __LEGACY: { angularDependencies } }: HomePluginStartDependencies - ) { + start(core: CoreStart, { data, __LEGACY: { angularDependencies } }: HomePluginStartDependencies) { // TODO is this really the right way? I though the app context would give us those this.dataStart = data; - this.npDataStart = npData; this.angularDependencies = angularDependencies; this.savedObjectsClient = core.savedObjects.client; } diff --git a/src/legacy/core_plugins/kibana/public/home/render_app.tsx b/src/legacy/core_plugins/kibana/public/home/render_app.tsx index 01ea450d8d4d9..2e2db616756ee 100644 --- a/src/legacy/core_plugins/kibana/public/home/render_app.tsx +++ b/src/legacy/core_plugins/kibana/public/home/render_app.tsx @@ -18,71 +18,18 @@ */ import React from 'react'; -import { DataStart } from 'src/legacy/core_plugins/data/public'; -import { AppMountContext } from 'kibana/public'; -import { Plugin as DataPlugin } from 'src/plugins/data/public'; import { render, unmountComponentAtNode } from 'react-dom'; import { i18n } from '@kbn/i18n'; -import { LegacyAngularInjectedDependencies } from './plugin'; - -/** - * These are dependencies of the Graph app besides the base dependencies - * provided by the application service. Some of those still rely on non-shimmed - * plugins in LP-world, but if they are migrated only the import path in the plugin - * itself changes - */ -export interface HomeDependencies { - element: HTMLElement; - appBasePath: string; - data: DataStart; - npData: ReturnType; - uiStatsReporter: any; - toastNotifications: any; - banners: any; - kfetch: any; - metadata: any; - savedObjectsClient: any; - METRIC_TYPE: any; -} - -export const renderApp = ( - { core }: AppMountContext, - { - element, - appBasePath, - data, - npData, - uiStatsReporter, - toastNotifications, - banners, - kfetch, - metadata, - savedObjectsClient, - }: HomeDependencies, - angularDeps: LegacyAngularInjectedDependencies -) => { - const deps = { - getInjected: core.injectedMetadata.getInjectedVar, - metadata, - docLinks: core.docLinks, - savedObjectsClient, - chrome: core.chrome, - uiSettings: core.uiSettings, - addBasePath: core.http.basePath.prepend, - getBasePath: core.http.basePath.get, - indexPatternService: data.indexPatterns.indexPatterns, - toastNotifications, - banners, - kfetch, - ...angularDeps, - }; - setDeps(deps); +// @ts-ignore +import { HomeApp } from './components/home_app'; +import { getServices } from './kibana_services'; +export const renderApp = (element: HTMLElement) => { const homeTitle = i18n.translate('kbn.home.breadcrumbs.homeTitle', { defaultMessage: 'Home' }); - const directories = angularDeps.featureCatalogueRegistryProvider.inTitleOrder; - core.chrome.setBreadcrumbs([{ text: homeTitle }]); + const { featureCatalogueRegistryProvider, chrome } = getServices(); + const directories = featureCatalogueRegistryProvider.inTitleOrder; + chrome.setBreadcrumbs([{ text: homeTitle }]); - const HomeApp = require('./components/home_app').HomeApp; render(, element); return () => unmountComponentAtNode(element); diff --git a/src/legacy/core_plugins/kibana/public/local_application_service/local_application_service.ts b/src/legacy/core_plugins/kibana/public/local_application_service/local_application_service.ts index 0432c56edc508..845ac72544973 100644 --- a/src/legacy/core_plugins/kibana/public/local_application_service/local_application_service.ts +++ b/src/legacy/core_plugins/kibana/public/local_application_service/local_application_service.ts @@ -17,7 +17,7 @@ * under the License. */ -import { ApplicationSetup, App } from 'kibana/public'; +import { App } from 'kibana/public'; import { UIRoutes } from 'ui/routes'; import { IScope } from 'angular'; import { npStart } from 'ui/new_platform'; @@ -35,33 +35,24 @@ import { htmlIdGenerator } from '@elastic/eui'; * This service becomes unnecessary once the platform provides a central * router that handles switching between applications without page reload. */ -export interface LocalApplicationService { - register: ApplicationSetup['register']; - registerWithAngularRouter: (routeManager: UIRoutes) => void; -} +export class LocalApplicationService { + private apps: App[] = []; + private idGenerator = htmlIdGenerator('kibanaAppLocalApp'); -const apps: App[] = []; -const idGenerator = htmlIdGenerator('kibanaAppLocalApp'); + register(app: App) { + this.apps.push(app); + } -export const localApplicationService: LocalApplicationService = { - register(app) { - apps.push(app); - }, registerWithAngularRouter(angularRouteManager: UIRoutes) { - apps.forEach(app => { - const wrapperElementId = idGenerator(); - const routeConfig = { - // marker for stuff operating on the route data. - // This can be used to not execute some operations because - // the route is not actually doing something besides serving - // as a wrapper for the actual inner-angular routes + this.apps.forEach(app => { + const wrapperElementId = this.idGenerator(); + angularRouteManager.when(`/${app.id}/:tail*?`, { outerAngularWrapperRoute: true, reloadOnSearch: false, reloadOnUrl: false, template: `
`, controller($scope: IScope) { const element = document.getElementById(wrapperElementId)!; - // controller itself is not allowed to be async, use inner IIFE (async () => { const onUnmount = await app.mount({ core: npStart.core }, { element, appBasePath: '' }); $scope.$on('$destroy', () => { @@ -69,8 +60,9 @@ export const localApplicationService: LocalApplicationService = { }); })(); }, - }; - angularRouteManager.when(`/${app.id}/:tail*?`, routeConfig); + }); }); - }, -}; + } +} + +export const localApplicationService = new LocalApplicationService(); diff --git a/src/legacy/ui/public/routes/route_manager.d.ts b/src/legacy/ui/public/routes/route_manager.d.ts index 3471d7e954862..d5f19e7a970ce 100644 --- a/src/legacy/ui/public/routes/route_manager.d.ts +++ b/src/legacy/ui/public/routes/route_manager.d.ts @@ -27,10 +27,12 @@ interface RouteConfiguration { controller?: string | ((...args: any[]) => void); redirectTo?: string; reloadOnSearch?: boolean; + reloadOnUrl?: boolean; resolve?: object; template?: string; k7Breadcrumbs?: (...args: any[]) => ChromeBreadcrumb[]; requireUICapability?: string; + outerAngularWrapperRoute?: boolean; } interface RouteManager { From 082748a8225a946e93df53cf587fcca617e42732 Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Mon, 21 Oct 2019 16:03:09 +0200 Subject: [PATCH 008/132] add redirect functionality to local application service --- .../core_plugins/kibana/public/kibana.js | 2 +- .../local_application_service.ts | 20 ++++++++++++++++++- .../ui/public/routes/route_manager.d.ts | 2 +- 3 files changed, 21 insertions(+), 3 deletions(-) diff --git a/src/legacy/core_plugins/kibana/public/kibana.js b/src/legacy/core_plugins/kibana/public/kibana.js index f9b0980dcb7c9..af7c9131caf45 100644 --- a/src/legacy/core_plugins/kibana/public/kibana.js +++ b/src/legacy/core_plugins/kibana/public/kibana.js @@ -60,7 +60,7 @@ import { showAppRedirectNotification } from 'ui/notify'; import 'leaflet'; import { localApplicationService } from './local_application_service'; -localApplicationService.registerWithAngularRouter(routes); +localApplicationService.apply(routes); routes.enable(); diff --git a/src/legacy/core_plugins/kibana/public/local_application_service/local_application_service.ts b/src/legacy/core_plugins/kibana/public/local_application_service/local_application_service.ts index 845ac72544973..c2963d1ba7095 100644 --- a/src/legacy/core_plugins/kibana/public/local_application_service/local_application_service.ts +++ b/src/legacy/core_plugins/kibana/public/local_application_service/local_application_service.ts @@ -37,13 +37,22 @@ import { htmlIdGenerator } from '@elastic/eui'; */ export class LocalApplicationService { private apps: App[] = []; + private forwards: Array<{ legacyAppId: string; newAppId: string; keepPrefix: boolean }> = []; private idGenerator = htmlIdGenerator('kibanaAppLocalApp'); register(app: App) { this.apps.push(app); } - registerWithAngularRouter(angularRouteManager: UIRoutes) { + forwardApp( + legacyAppId: string, + newAppId: string, + options: { keepPrefix: boolean } = { keepPrefix: false } + ) { + this.forwards.push({ legacyAppId, newAppId, ...options }); + } + + apply(angularRouteManager: UIRoutes) { this.apps.forEach(app => { const wrapperElementId = this.idGenerator(); angularRouteManager.when(`/${app.id}/:tail*?`, { @@ -62,6 +71,15 @@ export class LocalApplicationService { }, }); }); + + this.forwards.forEach(({ legacyAppId, newAppId, keepPrefix }) => { + angularRouteManager.when(`/${legacyAppId}:tail*?`, { + redirectTo: (_params: unknown, path: string, search: string) => { + const newPath = `/${newAppId}${keepPrefix ? path : path.replace(legacyAppId, '')}`; + return `${newPath}?${search}`; + }, + }); + }); } } diff --git a/src/legacy/ui/public/routes/route_manager.d.ts b/src/legacy/ui/public/routes/route_manager.d.ts index d5f19e7a970ce..6187dfa71f856 100644 --- a/src/legacy/ui/public/routes/route_manager.d.ts +++ b/src/legacy/ui/public/routes/route_manager.d.ts @@ -25,7 +25,7 @@ import { ChromeBreadcrumb } from '../../../../core/public'; interface RouteConfiguration { controller?: string | ((...args: any[]) => void); - redirectTo?: string; + redirectTo?: string | ((...args: any[]) => string); reloadOnSearch?: boolean; reloadOnUrl?: boolean; resolve?: object; From 2bf3bf6c18a7320fabc69312069dfc5180293564 Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Mon, 21 Oct 2019 16:39:10 +0200 Subject: [PATCH 009/132] return early from route change handlers on dummy wrapper route --- .../public/legacy_compat/angular_config.tsx | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/src/legacy/ui/public/legacy_compat/angular_config.tsx b/src/legacy/ui/public/legacy_compat/angular_config.tsx index 8eac31e24530c..86ca9f911a578 100644 --- a/src/legacy/ui/public/legacy_compat/angular_config.tsx +++ b/src/legacy/ui/public/legacy_compat/angular_config.tsx @@ -160,6 +160,12 @@ const capture$httpLoadingCount = (newPlatform: CoreStart) => ( ); }; +function isDummyWrapperRoute($route: any) { + return ( + $route.current && $route.current.$$route && $route.current.$$route.outerAngularWrapperRoute + ); +} + /** * internal angular run function that will be called when angular bootstraps and * lets us integrate with the angular router so that we can automatically clear @@ -187,6 +193,9 @@ const $setupBreadcrumbsAutoClear = (newPlatform: CoreStart) => ( }); $rootScope.$on('$routeChangeSuccess', () => { + if (isDummyWrapperRoute($route)) { + return; + } const current = $route.current || {}; if (breadcrumbSetSinceRouteChange || (current.$$route && current.$$route.redirectTo)) { @@ -226,6 +235,9 @@ const $setupBadgeAutoClear = (newPlatform: CoreStart) => ( }); $rootScope.$on('$routeChangeSuccess', () => { + if (isDummyWrapperRoute($route)) { + return; + } const current = $route.current || {}; if (badgeSetSinceRouteChange || (current.$$route && current.$$route.redirectTo)) { @@ -274,6 +286,9 @@ const $setupHelpExtensionAutoClear = (newPlatform: CoreStart) => ( }); $rootScope.$on('$routeChangeSuccess', () => { + if (isDummyWrapperRoute($route)) { + return; + } const current = $route.current || {}; if (helpExtensionSetSinceRouteChange || (current.$$route && current.$$route.redirectTo)) { @@ -288,10 +303,15 @@ const $setupUrlOverflowHandling = (newPlatform: CoreStart) => ( $location: ILocationService, $rootScope: IRootScopeService, Private: any, - config: any + config: any, + $injector: any ) => { + const $route = $injector.has('$route') ? $injector.get('$route') : {}; const urlOverflow = new UrlOverflowService(); const check = () => { + if (isDummyWrapperRoute($route)) { + return; + } // disable long url checks when storing state in session storage if (config.get('state:storeInSessionStorage')) { return; From 398299c0b218cc1fe6415dfa6c80750240af5b19 Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Mon, 21 Oct 2019 16:53:15 +0200 Subject: [PATCH 010/132] fix jest tests --- .../public/home/components/add_data.test.js | 15 +++++++++++---- .../sample_data_view_data_button.test.js | 4 +++- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/src/legacy/core_plugins/kibana/public/home/components/add_data.test.js b/src/legacy/core_plugins/kibana/public/home/components/add_data.test.js index 81e3d6aaf63ad..07f415cfcb1c9 100644 --- a/src/legacy/core_plugins/kibana/public/home/components/add_data.test.js +++ b/src/legacy/core_plugins/kibana/public/home/components/add_data.test.js @@ -22,11 +22,18 @@ import { AddData } from './add_data'; import { shallowWithIntl } from 'test_utils/enzyme_helpers'; import { getServices } from '../kibana_services'; -jest.mock('../kibana_services', () =>({ - getServices: () => ({ +jest.mock('../kibana_services', () =>{ + const mock = { getBasePath: jest.fn(() => 'path'), - }), -})); + }; + return { + getServices: () => mock, + }; +}); + +beforeEach(() => { + jest.clearAllMocks(); +}); test('render', () => { const component = shallowWithIntl(({ - addBasePath: path => `root${path}` + getServices: () =>({ + addBasePath: path => `root${path}` + }) })); test('should render simple button when appLinks is empty', () => { From 44bcd46b811db7ff356e3d8b74d0f7a6749b63e4 Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Mon, 21 Oct 2019 18:47:00 +0200 Subject: [PATCH 011/132] Start shimming dashboard --- .../kibana/public/dashboard/app.js | 228 ++++++++++++++++++ .../kibana/public/dashboard/dashboard_app.tsx | 102 ++++---- .../dashboard/dashboard_app_controller.tsx | 18 +- .../kibana/public/dashboard/index.js | 207 ---------------- .../kibana/public/dashboard/index.ts | 75 ++++++ .../kibana/public/dashboard/plugin.ts | 30 +++ .../kibana/public/dashboard/render_app.ts | 203 ++++++++++++++++ src/legacy/ui/public/modals/confirm_modal.js | 24 +- 8 files changed, 605 insertions(+), 282 deletions(-) create mode 100644 src/legacy/core_plugins/kibana/public/dashboard/app.js delete mode 100644 src/legacy/core_plugins/kibana/public/dashboard/index.js create mode 100644 src/legacy/core_plugins/kibana/public/dashboard/index.ts create mode 100644 src/legacy/core_plugins/kibana/public/dashboard/plugin.ts create mode 100644 src/legacy/core_plugins/kibana/public/dashboard/render_app.ts diff --git a/src/legacy/core_plugins/kibana/public/dashboard/app.js b/src/legacy/core_plugins/kibana/public/dashboard/app.js new file mode 100644 index 0000000000000..89ec519d656bb --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/dashboard/app.js @@ -0,0 +1,228 @@ +/* + * 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 { i18n } from '@kbn/i18n'; +import './saved_dashboard/saved_dashboards'; +import './dashboard_config'; +import uiRoutes from 'ui/routes'; +import { wrapInI18nContext } from 'ui/i18n'; + +import dashboardTemplate from './dashboard_app.html'; +import dashboardListingTemplate from './listing/dashboard_listing_ng_wrapper.html'; + +import { initDashboardAppDirective } from './dashboard_app'; +import { DashboardConstants, createDashboardEditUrl } from './dashboard_constants'; +import { + InvalidJSONProperty, + SavedObjectNotFound, +} from '../../../../../plugins/kibana_utils/public'; +import { FeatureCatalogueCategory } from 'ui/registry/feature_catalogue'; +import { SavedObjectsClientProvider } from 'ui/saved_objects'; +import { SavedObjectRegistryProvider } from 'ui/saved_objects/saved_object_registry'; +import { DashboardListing, EMPTY_FILTER } from './listing/dashboard_listing'; +import 'ui/capabilities/route_setup'; +import { addHelpMenuToAppChrome } from './help_menu/help_menu_util'; + +// load directives +import '../../../data/public'; + +export function initDashboardApp(app, deps) { + initDashboardAppDirective(app, deps); + + app.directive('dashboardListing', function(reactDirective) { + return reactDirective(wrapInI18nContext(DashboardListing)); + }); + + function createNewDashboardCtrl($scope) { + $scope.visitVisualizeAppLinkText = i18n.translate('kbn.dashboard.visitVisualizeAppLinkText', { + defaultMessage: 'visit the Visualize app', + }); + addHelpMenuToAppChrome(deps.chrome); + } + + app.config(function ($routeProvider) { + const defaults = { + requireDefaultIndex: true, + requireUICapability: 'dashboard.show', + badge: () => { + if (deps.dashboardCapabilities.showWriteControls) { + return undefined; + } + + return { + text: i18n.translate('kbn.dashboard.badge.readOnly.text', { + defaultMessage: 'Read only', + }), + tooltip: i18n.translate('kbn.dashboard.badge.readOnly.tooltip', { + defaultMessage: 'Unable to save dashboards', + }), + iconType: 'glasses', + }; + }, + }; + $routeProvider + .when(DashboardConstants.LANDING_PAGE_PATH, { + ...defaults, + template: dashboardListingTemplate, + controller($injector, $location, $scope) { + const services = deps.savedObjectRegistry.byLoaderPropertiesName; + const kbnUrl = $injector.get('kbnUrl'); + const dashboardConfig = deps.dashboardConfig; + + $scope.listingLimit = deps.uiSettings.get('savedObjects:listingLimit'); + $scope.create = () => { + kbnUrl.redirect(DashboardConstants.CREATE_NEW_DASHBOARD_URL); + }; + $scope.find = search => { + return services.dashboards.find(search, $scope.listingLimit); + }; + $scope.editItem = ({ id }) => { + kbnUrl.redirect(`${createDashboardEditUrl(id)}?_a=(viewMode:edit)`); + }; + $scope.getViewUrl = ({ id }) => { + return deps.addBasePath(`#${createDashboardEditUrl(id)}`); + }; + $scope.delete = dashboards => { + return services.dashboards.delete(dashboards.map(d => d.id)); + }; + $scope.hideWriteControls = dashboardConfig.getHideWriteControls(); + $scope.initialFilter = $location.search().filter || EMPTY_FILTER; + deps.chrome.setBreadcrumbs([ + { + text: i18n.translate('kbn.dashboard.dashboardBreadcrumbsTitle', { + defaultMessage: 'Dashboards', + }), + }, + ]); + addHelpMenuToAppChrome(deps.chrome); + }, + resolve: { + dash: function ($route, redirectWhenMissing, kbnUrl) { + const savedObjectsClient = deps.savedObjectsClient; + const title = $route.current.params.title; + if (title) { + return savedObjectsClient + .find({ + search: `"${title}"`, + search_fields: 'title', + type: 'dashboard', + }) + .then(results => { + // The search isn't an exact match, lets see if we can find a single exact match to use + const matchingDashboards = results.savedObjects.filter( + dashboard => dashboard.attributes.title.toLowerCase() === title.toLowerCase() + ); + if (matchingDashboards.length === 1) { + kbnUrl.redirect(createDashboardEditUrl(matchingDashboards[0].id)); + } else { + kbnUrl.redirect(`${DashboardConstants.LANDING_PAGE_PATH}?filter="${title}"`); + } + throw uiRoutes.WAIT_FOR_URL_CHANGE_TOKEN; + }) + .catch( + redirectWhenMissing({ + dashboard: DashboardConstants.LANDING_PAGE_PATH, + }) + ); + } + }, + }, + }) + .when(DashboardConstants.CREATE_NEW_DASHBOARD_URL, { + ...defaults, + template: dashboardTemplate, + controller: createNewDashboardCtrl, + requireUICapability: 'dashboard.createNew', + resolve: { + dash: function (savedDashboards, redirectWhenMissing) { + return savedDashboards.get().catch( + redirectWhenMissing({ + dashboard: DashboardConstants.LANDING_PAGE_PATH, + }) + ); + }, + }, + }) + .when(createDashboardEditUrl(':id'), { + ...defaults, + template: dashboardTemplate, + controller: createNewDashboardCtrl, + resolve: { + dash: function ($route, redirectWhenMissing, kbnUrl, AppState) { + const id = $route.current.params.id; + + return deps.savedDashboards + .get(id) + .then(savedDashboard => { + deps.chrome.recentlyAccessed.add( + savedDashboard.getFullPath(), + savedDashboard.title, + id + ); + return savedDashboard; + }) + .catch(error => { + // A corrupt dashboard was detected (e.g. with invalid JSON properties) + if (error instanceof InvalidJSONProperty) { + deps.toastNotifications.addDanger(error.message); + kbnUrl.redirect(DashboardConstants.LANDING_PAGE_PATH); + return; + } + + // Preserve BWC of v5.3.0 links for new, unsaved dashboards. + // See https://github.com/elastic/kibana/issues/10951 for more context. + if (error instanceof SavedObjectNotFound && id === 'create') { + // Note "new AppState" is necessary so the state in the url is preserved through the redirect. + kbnUrl.redirect(DashboardConstants.CREATE_NEW_DASHBOARD_URL, {}, new AppState()); + deps.toastNotifications.addWarning( + i18n.translate('kbn.dashboard.urlWasRemovedInSixZeroWarningMessage', { + defaultMessage: + 'The url "dashboard/create" was removed in 6.0. Please update your bookmarks.', + }) + ); + } else { + throw error; + } + }) + .catch( + redirectWhenMissing({ + dashboard: DashboardConstants.LANDING_PAGE_PATH, + }) + ); + }, + }, + }); + }); + + deps.featureCatalogueRegistryProvider.register(() => { + return { + id: 'dashboard', + title: i18n.translate('kbn.dashboard.featureCatalogue.dashboardTitle', { + defaultMessage: 'Dashboard', + }), + description: i18n.translate('kbn.dashboard.featureCatalogue.dashboardDescription', { + defaultMessage: 'Display and share a collection of visualizations and saved searches.', + }), + icon: 'dashboardApp', + path: `/app/kibana#${DashboardConstants.LANDING_PAGE_PATH}`, + showOnHomePage: true, + category: FeatureCatalogueCategory.DATA, + }; + }); +} diff --git a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app.tsx b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app.tsx index 7a0398e86a60d..77a59c3d57f94 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app.tsx +++ b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app.tsx @@ -17,18 +17,8 @@ * under the License. */ -import _ from 'lodash'; - -// @ts-ignore -import { uiModules } from 'ui/modules'; import { IInjector } from 'ui/chrome'; -// @ts-ignore -import * as filterActions from 'plugins/kibana/discover/doc_table/actions/filter'; - -// @ts-ignore -import { getFilterGenerator } from 'ui/filter_manager'; - import { AppStateClass as TAppStateClass, AppState as TAppState, @@ -37,7 +27,6 @@ import { import { KbnUrl } from 'ui/url/kbn_url'; import { Filter } from '@kbn/es-query'; import { TimeRange } from 'src/plugins/data/public'; -import { IndexPattern } from 'ui/index_patterns'; import { IPrivate } from 'ui/private'; import { StaticIndexPattern, Query, SavedQuery } from 'plugins/data'; import moment from 'moment'; @@ -48,6 +37,7 @@ import { SavedObjectDashboard } from './saved_dashboard/saved_dashboard'; import { DashboardAppState, SavedDashboardPanel, ConfirmModalFn } from './types'; import { DashboardAppController } from './dashboard_app_controller'; +import { RenderDeps } from './render_app'; export interface DashboardAppScope extends ng.IScope { dash: SavedObjectDashboard; @@ -96,52 +86,48 @@ export interface DashboardAppScope extends ng.IScope { timefilterSubscriptions$: Subscription; } -const app = uiModules.get('app/dashboard', ['elasticsearch', 'ngRoute', 'react', 'kibana/config']); - -app.directive('dashboardApp', function($injector: IInjector) { - const AppState = $injector.get>('AppState'); - const kbnUrl = $injector.get('kbnUrl'); - const confirmModal = $injector.get('confirmModal'); - const config = $injector.get('config'); +export function initDashboardAppDirective(app: any, deps: RenderDeps) { + app.directive('dashboardApp', function($injector: IInjector) { + const AppState = $injector.get>('AppState'); + const kbnUrl = $injector.get('kbnUrl'); + const confirmModal = $injector.get('confirmModal'); + const config = deps.uiSettings; - const Private = $injector.get('Private'); + const Private = $injector.get('Private'); - const indexPatterns = $injector.get<{ - getDefault: () => Promise; - }>('indexPatterns'); - - return { - restrict: 'E', - controllerAs: 'dashboardApp', - controller: ( - $scope: DashboardAppScope, - $route: any, - $routeParams: { - id?: string; - }, - getAppState: { - previouslyStored: () => TAppState | undefined; - }, - dashboardConfig: { - getHideWriteControls: () => boolean; - }, - localStorage: { - get: (prop: string) => unknown; - } - ) => - new DashboardAppController({ - $route, - $scope, - $routeParams, - getAppState, - dashboardConfig, - localStorage, - Private, - kbnUrl, - AppStateClass: AppState, - indexPatterns, - config, - confirmModal, - }), - }; -}); + return { + restrict: 'E', + controllerAs: 'dashboardApp', + controller: ( + $scope: DashboardAppScope, + $route: any, + $routeParams: { + id?: string; + }, + getAppState: { + previouslyStored: () => TAppState | undefined; + }, + dashboardConfig: { + getHideWriteControls: () => boolean; + }, + localStorage: { + get: (prop: string) => unknown; + } + ) => + new DashboardAppController({ + $route, + $scope, + $routeParams, + getAppState, + dashboardConfig, + localStorage, + Private, + kbnUrl, + AppStateClass: AppState, + config, + confirmModal, + ...deps, + }), + }; + }); +} diff --git a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx index abf7b22a6e48c..e212d1cf83d7d 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx +++ b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx @@ -58,7 +58,6 @@ import { Subscription } from 'rxjs'; import { npStart } from 'ui/new_platform'; import { SavedObjectFinder } from 'ui/saved_objects/components/saved_object_finder'; import { extractTimeFilter, changeTimeFilter } from '../../../data/public'; -import { start as data } from '../../../data/public/legacy'; import { DashboardContainer, @@ -88,8 +87,8 @@ import { getDashboardTitle } from './dashboard_strings'; import { DashboardAppScope } from './dashboard_app'; import { VISUALIZE_EMBEDDABLE_TYPE } from '../visualize/embeddable'; import { convertSavedDashboardPanelToPanelState } from './lib/embeddable_saved_object_converters'; +import { SavedQueryService } from '../../../data/public/search/search_bar/lib/saved_query_service'; -const { savedQueryService } = data.search.services; export class DashboardAppController { // Part of the exposed plugin API - do not remove without careful consideration. @@ -104,12 +103,15 @@ export class DashboardAppController { getAppState, dashboardConfig, localStorage, - Private, kbnUrl, AppStateClass, indexPatterns, config, confirmModal, + queryFilter, + getUnhashableStates, + shareContextMenuExtensions, + savedQueryService, }: { $scope: DashboardAppScope; $route: any; @@ -129,10 +131,14 @@ export class DashboardAppController { AppStateClass: TAppStateClass; config: any; confirmModal: ConfirmModalFn; + queryFilter: any; + getUnhashableStates: any; + shareContextMenuExtensions: any; + savedQueryService: SavedQueryService; }) { - const queryFilter = Private(FilterBarQueryFilterProvider); - const getUnhashableStates = Private(getUnhashableStatesProvider); - const shareContextMenuExtensions = Private(ShareContextMenuExtensionsRegistryProvider); + // const queryFilter = Private(FilterBarQueryFilterProvider); + // const getUnhashableStates = Private(getUnhashableStatesProvider); + // const shareContextMenuExtensions = Private(ShareContextMenuExtensionsRegistryProvider); let lastReloadRequestTime = 0; diff --git a/src/legacy/core_plugins/kibana/public/dashboard/index.js b/src/legacy/core_plugins/kibana/public/dashboard/index.js deleted file mode 100644 index 712e05c92e5e8..0000000000000 --- a/src/legacy/core_plugins/kibana/public/dashboard/index.js +++ /dev/null @@ -1,207 +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 './dashboard_app'; -import { i18n } from '@kbn/i18n'; -import './saved_dashboard/saved_dashboards'; -import './dashboard_config'; -import uiRoutes from 'ui/routes'; -import chrome from 'ui/chrome'; -import { wrapInI18nContext } from 'ui/i18n'; -import { toastNotifications } from 'ui/notify'; - -import dashboardTemplate from './dashboard_app.html'; -import dashboardListingTemplate from './listing/dashboard_listing_ng_wrapper.html'; - -import { DashboardConstants, createDashboardEditUrl } from './dashboard_constants'; -import { InvalidJSONProperty, SavedObjectNotFound } from '../../../../../plugins/kibana_utils/public'; -import { FeatureCatalogueRegistryProvider, FeatureCatalogueCategory } from 'ui/registry/feature_catalogue'; -import { SavedObjectsClientProvider } from 'ui/saved_objects'; -import { SavedObjectRegistryProvider } from 'ui/saved_objects/saved_object_registry'; -import { DashboardListing, EMPTY_FILTER } from './listing/dashboard_listing'; -import { uiModules } from 'ui/modules'; -import 'ui/capabilities/route_setup'; -import { addHelpMenuToAppChrome } from './help_menu/help_menu_util'; - -import { npStart } from 'ui/new_platform'; - -// load directives -import '../../../data/public'; - -const app = uiModules.get('app/dashboard', [ - 'ngRoute', - 'react', -]); - -app.directive('dashboardListing', function (reactDirective) { - return reactDirective(wrapInI18nContext(DashboardListing)); -}); - -function createNewDashboardCtrl($scope) { - $scope.visitVisualizeAppLinkText = i18n.translate('kbn.dashboard.visitVisualizeAppLinkText', { - defaultMessage: 'visit the Visualize app', - }); - addHelpMenuToAppChrome(chrome); -} - -uiRoutes - .defaults(/dashboard/, { - requireDefaultIndex: true, - requireUICapability: 'dashboard.show', - badge: uiCapabilities => { - if (uiCapabilities.dashboard.showWriteControls) { - return undefined; - } - - return { - text: i18n.translate('kbn.dashboard.badge.readOnly.text', { - defaultMessage: 'Read only', - }), - tooltip: i18n.translate('kbn.dashboard.badge.readOnly.tooltip', { - defaultMessage: 'Unable to save dashboards', - }), - iconType: 'glasses' - }; - } - }) - .when(DashboardConstants.LANDING_PAGE_PATH, { - template: dashboardListingTemplate, - controller($injector, $location, $scope, Private, config) { - const services = Private(SavedObjectRegistryProvider).byLoaderPropertiesName; - const kbnUrl = $injector.get('kbnUrl'); - const dashboardConfig = $injector.get('dashboardConfig'); - - $scope.listingLimit = config.get('savedObjects:listingLimit'); - $scope.create = () => { - kbnUrl.redirect(DashboardConstants.CREATE_NEW_DASHBOARD_URL); - }; - $scope.find = (search) => { - return services.dashboards.find(search, $scope.listingLimit); - }; - $scope.editItem = ({ id }) => { - kbnUrl.redirect(`${createDashboardEditUrl(id)}?_a=(viewMode:edit)`); - }; - $scope.getViewUrl = ({ id }) => { - return chrome.addBasePath(`#${createDashboardEditUrl(id)}`); - }; - $scope.delete = (dashboards) => { - return services.dashboards.delete(dashboards.map(d => d.id)); - }; - $scope.hideWriteControls = dashboardConfig.getHideWriteControls(); - $scope.initialFilter = ($location.search()).filter || EMPTY_FILTER; - chrome.breadcrumbs.set([{ - text: i18n.translate('kbn.dashboard.dashboardBreadcrumbsTitle', { - defaultMessage: 'Dashboards', - }), - }]); - addHelpMenuToAppChrome(chrome); - }, - resolve: { - dash: function ($route, Private, redirectWhenMissing, kbnUrl) { - const savedObjectsClient = Private(SavedObjectsClientProvider); - const title = $route.current.params.title; - if (title) { - return savedObjectsClient.find({ - search: `"${title}"`, - search_fields: 'title', - type: 'dashboard', - }).then(results => { - // The search isn't an exact match, lets see if we can find a single exact match to use - const matchingDashboards = results.savedObjects.filter( - dashboard => dashboard.attributes.title.toLowerCase() === title.toLowerCase()); - if (matchingDashboards.length === 1) { - kbnUrl.redirect(createDashboardEditUrl(matchingDashboards[0].id)); - } else { - kbnUrl.redirect(`${DashboardConstants.LANDING_PAGE_PATH}?filter="${title}"`); - } - throw uiRoutes.WAIT_FOR_URL_CHANGE_TOKEN; - }).catch(redirectWhenMissing({ - 'dashboard': DashboardConstants.LANDING_PAGE_PATH - })); - } - } - } - }) - .when(DashboardConstants.CREATE_NEW_DASHBOARD_URL, { - template: dashboardTemplate, - controller: createNewDashboardCtrl, - requireUICapability: 'dashboard.createNew', - resolve: { - dash: function (savedDashboards, redirectWhenMissing) { - return savedDashboards.get() - .catch(redirectWhenMissing({ - 'dashboard': DashboardConstants.LANDING_PAGE_PATH - })); - } - } - }) - .when(createDashboardEditUrl(':id'), { - template: dashboardTemplate, - controller: createNewDashboardCtrl, - resolve: { - dash: function (savedDashboards, $route, redirectWhenMissing, kbnUrl, AppState) { - const id = $route.current.params.id; - - return savedDashboards.get(id) - .then((savedDashboard) => { - npStart.core.chrome.recentlyAccessed.add(savedDashboard.getFullPath(), savedDashboard.title, id); - return savedDashboard; - }) - .catch((error) => { - // A corrupt dashboard was detected (e.g. with invalid JSON properties) - if (error instanceof InvalidJSONProperty) { - toastNotifications.addDanger(error.message); - kbnUrl.redirect(DashboardConstants.LANDING_PAGE_PATH); - return; - } - - // Preserve BWC of v5.3.0 links for new, unsaved dashboards. - // See https://github.com/elastic/kibana/issues/10951 for more context. - if (error instanceof SavedObjectNotFound && id === 'create') { - // Note "new AppState" is necessary so the state in the url is preserved through the redirect. - kbnUrl.redirect(DashboardConstants.CREATE_NEW_DASHBOARD_URL, {}, new AppState()); - toastNotifications.addWarning(i18n.translate('kbn.dashboard.urlWasRemovedInSixZeroWarningMessage', - { defaultMessage: 'The url "dashboard/create" was removed in 6.0. Please update your bookmarks.' } - )); - } else { - throw error; - } - }) - .catch(redirectWhenMissing({ - 'dashboard': DashboardConstants.LANDING_PAGE_PATH - })); - } - } - }); - -FeatureCatalogueRegistryProvider.register(() => { - return { - id: 'dashboard', - title: i18n.translate('kbn.dashboard.featureCatalogue.dashboardTitle', { - defaultMessage: 'Dashboard', - }), - description: i18n.translate('kbn.dashboard.featureCatalogue.dashboardDescription', { - defaultMessage: 'Display and share a collection of visualizations and saved searches.', - }), - icon: 'dashboardApp', - path: `/app/kibana#${DashboardConstants.LANDING_PAGE_PATH}`, - showOnHomePage: true, - category: FeatureCatalogueCategory.DATA - }; -}); diff --git a/src/legacy/core_plugins/kibana/public/dashboard/index.ts b/src/legacy/core_plugins/kibana/public/dashboard/index.ts new file mode 100644 index 0000000000000..42abbec798592 --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/dashboard/index.ts @@ -0,0 +1,75 @@ +/* + * 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 { FeatureCatalogueRegistryProvider } from 'ui/registry/feature_catalogue'; +import { npSetup, npStart } from 'ui/new_platform'; +import chrome from 'ui/chrome'; +import { IPrivate } from 'ui/private'; +// @ts-ignore +import { toastNotifications, banners } from 'ui/notify'; +import { kfetch } from 'ui/kfetch'; +import { HomePlugin, LegacyAngularInjectedDependencies } from './plugin'; +import { createUiStatsReporter, METRIC_TYPE } from '../../../ui_metric/public'; +import { start as data } from '../../../data/public/legacy'; +import { TelemetryOptInProvider } from '../../../telemetry/public/services'; +import { localApplicationService } from '../local_application_service'; + +export const trackUiMetric = createUiStatsReporter('Kibana_home'); + +/** + * Get dependencies relying on the global angular context. + * They also have to get resolved together with the legacy imports above + */ +async function getAngularInjectedDependencies(): Promise { + const injector = await chrome.dangerouslyGetActiveInjector(); + + const Private = injector.get('Private'); + + const telemetryEnabled = npStart.core.injectedMetadata.getInjectedVar('telemetryEnabled'); + const telemetryBanner = npStart.core.injectedMetadata.getInjectedVar('telemetryBanner'); + const telemetryOptInProvider = Private(TelemetryOptInProvider); + + return { + telemetryOptInProvider, + shouldShowTelemetryOptIn: + telemetryEnabled && telemetryBanner && !telemetryOptInProvider.getOptIn(), + featureCatalogueRegistryProvider: Private(FeatureCatalogueRegistryProvider as any), + }; +} + +(async () => { + const instance = new HomePlugin(); + instance.setup(npSetup.core, { + __LEGACY: { + trackUiMetric, + toastNotifications, + banners, + kfetch, + metadata: npStart.core.injectedMetadata.getLegacyMetadata(), + METRIC_TYPE, + localApplicationService, + }, + }); + instance.start(npStart.core, { + data, + __LEGACY: { + angularDependencies: await getAngularInjectedDependencies(), + }, + }); +})(); diff --git a/src/legacy/core_plugins/kibana/public/dashboard/plugin.ts b/src/legacy/core_plugins/kibana/public/dashboard/plugin.ts new file mode 100644 index 0000000000000..decc002f765c3 --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/dashboard/plugin.ts @@ -0,0 +1,30 @@ +/* + * 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 { CoreSetup, CoreStart, Plugin } from 'kibana/public'; + +export class DashboardPlugin implements Plugin { + public setup(core: CoreSetup) { + + } + + public start(core: CoreStart) { + + } +} diff --git a/src/legacy/core_plugins/kibana/public/dashboard/render_app.ts b/src/legacy/core_plugins/kibana/public/dashboard/render_app.ts new file mode 100644 index 0000000000000..ebde291380302 --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/dashboard/render_app.ts @@ -0,0 +1,203 @@ +/* + * 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 { EuiConfirmModal } from '@elastic/eui'; +import angular from 'angular'; +import { IPrivate } from 'ui/private'; +import { i18nDirective, i18nFilter, I18nProvider } from '@kbn/i18n/src/angular'; +// @ts-ignore +import { GlobalStateProvider } from 'ui/state_management/global_state'; +// @ts-ignore +import { StateManagementConfigProvider } from 'ui/state_management/config_provider'; +// @ts-ignore +import { PrivateProvider } from 'ui/private/private'; +// @ts-ignore +import { EventsProvider } from 'ui/events'; +// @ts-ignore +import { PersistedState } from 'ui/persisted_state'; +// @ts-ignore +import { createTopNavDirective, createTopNavHelper } from 'ui/kbn_top_nav/kbn_top_nav'; +// @ts-ignore +import { PromiseServiceCreator } from 'ui/promises/promises'; +// @ts-ignore +import { KbnUrlProvider, RedirectWhenMissingProvider } from 'ui/url'; +// @ts-ignore +import { confirmModalFactory } from 'ui/modals/confirm_modal'; + +import { AppMountContext, ChromeStart, SavedObjectsClientContract, UiSettingsClientContract } from 'kibana/public'; +import { configureAppAngularModule } from 'ui/legacy_compat'; + +// @ts-ignore +import { initDashboardApp } from './app'; +import { DataStart } from '../../../data/public'; +import { FilterBarQueryFilterProvider } from 'ui/filter_manager/query_filter'; +import { getUnhashableStatesProvider } from 'ui/state_management/state_hashing/get_unhashable_states_provider'; +import { ShareContextMenuExtensionsRegistryProvider } from 'ui/share'; +import { SavedQueryService } from '../../../data/public/search/search_bar/lib/saved_query_service'; + +export interface RenderDeps { + core: AppMountContext['core']; + indexPatterns: DataStart['indexPatterns']['indexPatterns']; + queryFilter: any; + getUnhashableStates: any; + shareContextMenuExtensions: any; + savedObjectRegistry: any; + savedObjectsClient: SavedObjectsClientContract; + dashboardConfig: any; + uiSettings: UiSettingsClientContract; + savedDashboards: any; + chrome: ChromeStart; + addBasePath: (path: string) => string; + featureCatalogueRegistryProvider: any; + dashboardCapabilities: any; + savedQueryService: SavedQueryService; + emebeddables: EmbeddableStart; +} + +export const renderApp = (element: HTMLElement, appBasePath: string, { core }: RenderDeps) => { + const dashboardAngularModule = createLocalAngularModule(core); + configureAppAngularModule(dashboardAngularModule); + initDashboardApp(dashboardAngularModule); + const $injector = mountDashboardApp(appBasePath, element); + return () => $injector.get('$rootScope').$destroy(); +}; + +const mainTemplate = (basePath: string) => `
+ +
+
+`; + +const moduleName = 'app/dashboard'; + +const thirdPartyAngularDependencies = ['ngSanitize', 'ngRoute', 'react']; + +function mountDashboardApp(appBasePath: string, element: HTMLElement) { + const mountpoint = document.createElement('div'); + mountpoint.setAttribute('style', 'height: 100%'); + // eslint-disable-next-line + mountpoint.innerHTML = mainTemplate(appBasePath); + // bootstrap angular into detached element and attach it later to + // make angular-within-angular possible + const $injector = angular.bootstrap(mountpoint, [moduleName]); + // initialize global state handler + $injector.get('globalState'); + element.appendChild(mountpoint); + return $injector; +} + +function createLocalAngularModule(core: AppMountContext['core']) { + createLocalI18nModule(); + createLocalPrivateModule(); + createLocalPromiseModule(); + createLocalConfigModule(core); + createLocalKbnUrlModule(); + createLocalPersistedStateModule(); + createLocalTopNavModule(); + createLocalGlobalStateModule(); + createLocalConfirmModalModule(); + + const dashboardAngularModule = angular.module(moduleName, [ + ...thirdPartyAngularDependencies, + 'dashboardConfig', + 'dashboardI18n', + 'dashboardPrivate', + 'dashboardPersistedState', + 'dashboardTopNav', + 'dashboardGlobalState', + 'dashboardConfirmModal', + ]); + return dashboardAngularModule; +} + +function createLocalConfirmModalModule() { + angular + .module('dashboardConfirmModal', ['react']) + .factory('confirmModal', confirmModalFactory) + .directive('confirmModal', reactDirective => reactDirective(EuiConfirmModal)); +} + +function createLocalGlobalStateModule() { + angular + .module('dashboardGlobalState', [ + 'dashboardPrivate', + 'dashboardConfig', + 'dashboardKbnUrl', + 'dashboardPromise', + ]) + .service('globalState', function(Private: any) { + return Private(GlobalStateProvider); + }); +} + +function createLocalPersistedStateModule() { + angular + .module('dashboardPersistedState', ['dashboardPrivate', 'dashboardPromise']) + .factory('PersistedState', (Private: IPrivate) => { + const Events = Private(EventsProvider); + return class AngularPersistedState extends PersistedState { + constructor(value: any, path: any) { + super(value, path, Events); + } + }; + }); +} + +function createLocalKbnUrlModule() { + angular + .module('dashboardKbnUrl', ['dashboardPrivate', 'ngRoute']) + .service('kbnUrl', (Private: IPrivate) => Private(KbnUrlProvider)) + .service('redirectWhenMissing', (Private: IPrivate) => Private(RedirectWhenMissingProvider)); +} + +function createLocalConfigModule(core: AppMountContext['core']) { + angular + .module('dashboardConfig', ['dashboardPrivate']) + .provider('stateManagementConfig', StateManagementConfigProvider) + .provider('config', () => { + return { + $get: () => ({ + get: core.uiSettings.get.bind(core.uiSettings), + }), + }; + }); +} + +function createLocalPromiseModule() { + angular.module('dashboardPromise', []).service('Promise', PromiseServiceCreator); +} + +function createLocalPrivateModule() { + angular.module('dashboardPrivate', []).provider('Private', PrivateProvider); +} + +function createLocalTopNavModule() { + angular + .module('dashboardTopNav', ['react']) + .directive('kbnTopNav', createTopNavDirective) + .directive('kbnTopNavHelper', createTopNavHelper); +} + +function createLocalI18nModule() { + angular + .module('dashboardI18n', []) + .provider('i18n', I18nProvider) + .filter('i18n', i18nFilter) + .directive('i18nId', i18nDirective); +} diff --git a/src/legacy/ui/public/modals/confirm_modal.js b/src/legacy/ui/public/modals/confirm_modal.js index 6d5abfca64aaf..9c3f46da4e927 100644 --- a/src/legacy/ui/public/modals/confirm_modal.js +++ b/src/legacy/ui/public/modals/confirm_modal.js @@ -36,16 +36,7 @@ export const ConfirmationButtonTypes = { CANCEL: CANCEL_BUTTON }; -/** - * @typedef {Object} ConfirmModalOptions - * @property {String} confirmButtonText - * @property {String=} cancelButtonText - * @property {function} onConfirm - * @property {function=} onCancel - * @property {String=} title - If given, shows a title on the confirm modal. - */ - -module.factory('confirmModal', function ($rootScope, $compile) { +export function confirmModalFactory($rootScope, $compile) { let modalPopover; const confirmQueue = []; @@ -114,4 +105,15 @@ module.factory('confirmModal', function ($rootScope, $compile) { } } }; -}); +} + +/** + * @typedef {Object} ConfirmModalOptions + * @property {String} confirmButtonText + * @property {String=} cancelButtonText + * @property {function} onConfirm + * @property {function=} onCancel + * @property {String=} title - If given, shows a title on the confirm modal. + */ + +module.factory('confirmModal', confirmModalFactory); From 4ea2e631a6b12ef8501fbd4a81ae54605f217a77 Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Tue, 22 Oct 2019 13:49:39 +0200 Subject: [PATCH 012/132] fix broken tests --- src/legacy/core_plugins/kibana/public/home/index.js | 7 +++++-- .../core_plugins/kibana/public/home/kibana_services.ts | 9 ++++++--- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/legacy/core_plugins/kibana/public/home/index.js b/src/legacy/core_plugins/kibana/public/home/index.js index 8ef5972d36683..829d1ef8f0ba4 100644 --- a/src/legacy/core_plugins/kibana/public/home/index.js +++ b/src/legacy/core_plugins/kibana/public/home/index.js @@ -37,8 +37,11 @@ function getRoute() { return { template, controller($scope) { - const { chrome, addBasePath, featureCatalogueRegistryProvider } = getServices(); - $scope.directories = featureCatalogueRegistryProvider.inTitleOrder; + const { chrome, addBasePath, getFeatureCatalogueRegistryProvider } = getServices(); + getFeatureCatalogueRegistryProvider().then(catalogue => { + $scope.directories = catalogue.inTitleOrder; + $scope.$digest(); + }); $scope.recentlyAccessed = chrome.recentlyAccessed.get().map(item => { item.link = addBasePath(item.link); return item; diff --git a/src/legacy/core_plugins/kibana/public/home/kibana_services.ts b/src/legacy/core_plugins/kibana/public/home/kibana_services.ts index 868e977a4601c..39067e2271f28 100644 --- a/src/legacy/core_plugins/kibana/public/home/kibana_services.ts +++ b/src/legacy/core_plugins/kibana/public/home/kibana_services.ts @@ -20,6 +20,7 @@ // @ts-ignore import { toastNotifications, banners } from 'ui/notify'; import { kfetch } from 'ui/kfetch'; +import chrome from 'ui/chrome'; import { wrapInI18nContext } from 'ui/i18n'; @@ -35,7 +36,6 @@ import { start as data } from '../../../data/public/legacy'; let shouldShowTelemetryOptIn: boolean; let telemetryOptInProvider: any; -let featureCatalogueRegistryProvider: any; export function getServices() { return { @@ -55,7 +55,11 @@ export function getServices() { indexPatternService: data.indexPatterns.indexPatterns, shouldShowTelemetryOptIn, telemetryOptInProvider, - featureCatalogueRegistryProvider, + getFeatureCatalogueRegistryProvider: async () => { + const injector = await chrome.dangerouslyGetActiveInjector(); + const Private = injector.get('Private'); + return Private(FeatureCatalogueRegistryProvider as any); + }, trackUiMetric: createUiStatsReporter('Kibana_home'), METRIC_TYPE, @@ -74,5 +78,4 @@ modules.get('kibana').run((Private: IPrivate) => { telemetryOptInProvider = Private(TelemetryOptInProvider); shouldShowTelemetryOptIn = telemetryEnabled && telemetryBanner && !telemetryOptInProvider.getOptIn(); - featureCatalogueRegistryProvider = Private(FeatureCatalogueRegistryProvider as any); }); From 1bd6b3bbdd59471742af1b4a47a930e74a629c9d Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Tue, 22 Oct 2019 14:46:53 +0200 Subject: [PATCH 013/132] local application service --- .../core_plugins/kibana/public/kibana.js | 3 + .../public/local_application_service/index.ts | 20 +++ .../local_application_service.ts | 136 ++++++++++++++++++ .../public/legacy_compat/angular_config.tsx | 20 +++ .../ui/public/routes/route_manager.d.ts | 4 +- 5 files changed, 182 insertions(+), 1 deletion(-) create mode 100644 src/legacy/core_plugins/kibana/public/local_application_service/index.ts create mode 100644 src/legacy/core_plugins/kibana/public/local_application_service/local_application_service.ts diff --git a/src/legacy/core_plugins/kibana/public/kibana.js b/src/legacy/core_plugins/kibana/public/kibana.js index 6c809e84c8c84..af7c9131caf45 100644 --- a/src/legacy/core_plugins/kibana/public/kibana.js +++ b/src/legacy/core_plugins/kibana/public/kibana.js @@ -58,6 +58,9 @@ import 'ui/agg_response'; import 'ui/agg_types'; import { showAppRedirectNotification } from 'ui/notify'; import 'leaflet'; +import { localApplicationService } from './local_application_service'; + +localApplicationService.apply(routes); routes.enable(); diff --git a/src/legacy/core_plugins/kibana/public/local_application_service/index.ts b/src/legacy/core_plugins/kibana/public/local_application_service/index.ts new file mode 100644 index 0000000000000..2128355ca906a --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/local_application_service/index.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 './local_application_service'; diff --git a/src/legacy/core_plugins/kibana/public/local_application_service/local_application_service.ts b/src/legacy/core_plugins/kibana/public/local_application_service/local_application_service.ts new file mode 100644 index 0000000000000..ba7e3921d3537 --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/local_application_service/local_application_service.ts @@ -0,0 +1,136 @@ +/* + * 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 { App } from 'kibana/public'; +import { UIRoutes } from 'ui/routes'; +import { IScope } from 'angular'; +import { npStart } from 'ui/new_platform'; +import { htmlIdGenerator } from '@elastic/eui'; + +interface ForwardDefinition { + legacyAppId: string; + newAppId: string; + keepPrefix: boolean; +} + +const matchAllWithPrefix = (prefixOrApp: string | App) => + `/${typeof prefixOrApp === 'string' ? prefixOrApp : prefixOrApp.id}:tail*?`; + +/** + * To be able to migrate and shim parts of the Kibana app plugin + * while still running some parts of it in the legacy world, this + * service emulates the core application service while using the global + * angular router to switch between apps without page reload. + * + * The id of the apps is used as prefix of the route - when switching between + * to apps, the current application is unmounted. + * + * This service becomes unnecessary once the platform provides a central + * router that handles switching between applications without page reload. + */ +export class LocalApplicationService { + private apps: App[] = []; + private forwards: ForwardDefinition[] = []; + private idGenerator = htmlIdGenerator('kibanaAppLocalApp'); + + /** + * Register an app to be managed by the application service. + * This method works exactly as `core.application.register`. + * + * When an app is mounted, it is responsible for routing. The app + * won't be mounted again if the route changes within the prefix + * of the app (its id). It is fine to use whatever means for handling + * routing within the app. + * + * When switching to a URL outside of the current prefix, the app router + * shouldn't do anything because it doesn't own the routing anymore - + * the local application service takes over routing again, + * unmounts the current app and mounts the next app. + * + * @param app The app descriptor + */ + register(app: App) { + this.apps.push(app); + } + + /** + * Forwards every URL starting with `legacyAppId` to the same URL starting + * with `newAppId` - e.g. `/legacy/my/legacy/path?q=123` gets forwarded to + * `/newApp/my/legacy/path?q=123`. + * + * When setting the `keepPrefix` option, the new app id is simply prepended. + * The example above would become `/newApp/legacy/my/legacy/path?q=123`. + * + * This method can be used to provide backwards compatibility for URLs when + * renaming or nesting plugins. For route changes after the prefix, please + * use the routing mechanism of your app. + * + * @param legacyAppId The name of the old app to forward URLs from + * @param newAppId The name of the new app that handles the URLs now + * @param options Whether the prefix of the old app is kept to nest the legacy + * path into the new path + */ + forwardApp( + legacyAppId: string, + newAppId: string, + options: { keepPrefix: boolean } = { keepPrefix: false } + ) { + this.forwards.push({ legacyAppId, newAppId, ...options }); + } + + /** + * Wires up listeners to handle mounting and unmounting of apps to + * the legacy angular route manager. Once all apps within the Kibana + * plugin are using the local route manager, this implementation can + * be switched to a more lightweight implementation. + * + * @param angularRouteManager The current `ui/routes` instance + */ + apply(angularRouteManager: UIRoutes) { + this.apps.forEach(app => { + const wrapperElementId = this.idGenerator(); + angularRouteManager.when(matchAllWithPrefix(app), { + outerAngularWrapperRoute: true, + reloadOnSearch: false, + reloadOnUrl: false, + template: `
`, + controller($scope: IScope) { + const element = document.getElementById(wrapperElementId)!; + (async () => { + const onUnmount = await app.mount({ core: npStart.core }, { element, appBasePath: '' }); + $scope.$on('$destroy', () => { + onUnmount(); + }); + })(); + }, + }); + }); + + this.forwards.forEach(({ legacyAppId, newAppId, keepPrefix }) => { + angularRouteManager.when(matchAllWithPrefix(legacyAppId), { + redirectTo: (_params: unknown, path: string, search: string) => { + const newPath = `/${newAppId}${keepPrefix ? path : path.replace(legacyAppId, '')}`; + return `${newPath}?${search}`; + }, + }); + }); + } +} + +export const localApplicationService = new LocalApplicationService(); diff --git a/src/legacy/ui/public/legacy_compat/angular_config.tsx b/src/legacy/ui/public/legacy_compat/angular_config.tsx index 8eac31e24530c..6b69e8e5f14b3 100644 --- a/src/legacy/ui/public/legacy_compat/angular_config.tsx +++ b/src/legacy/ui/public/legacy_compat/angular_config.tsx @@ -48,6 +48,12 @@ import { isSystemApiRequest } from '../system_api'; const URL_LIMIT_WARN_WITHIN = 1000; +function isDummyWrapperRoute($route: any) { + return ( + $route.current && $route.current.$$route && $route.current.$$route.outerAngularWrapperRoute + ); +} + export const configureAppAngularModule = (angularModule: IModule) => { const newPlatform = npStart.core; const legacyMetadata = newPlatform.injectedMetadata.getLegacyMetadata(); @@ -187,6 +193,9 @@ const $setupBreadcrumbsAutoClear = (newPlatform: CoreStart) => ( }); $rootScope.$on('$routeChangeSuccess', () => { + if (isDummyWrapperRoute($route)) { + return; + } const current = $route.current || {}; if (breadcrumbSetSinceRouteChange || (current.$$route && current.$$route.redirectTo)) { @@ -226,6 +235,9 @@ const $setupBadgeAutoClear = (newPlatform: CoreStart) => ( }); $rootScope.$on('$routeChangeSuccess', () => { + if (isDummyWrapperRoute($route)) { + return; + } const current = $route.current || {}; if (badgeSetSinceRouteChange || (current.$$route && current.$$route.redirectTo)) { @@ -270,6 +282,9 @@ const $setupHelpExtensionAutoClear = (newPlatform: CoreStart) => ( const $route = $injector.has('$route') ? $injector.get('$route') : {}; $rootScope.$on('$routeChangeStart', () => { + if (isDummyWrapperRoute($route)) { + return; + } helpExtensionSetSinceRouteChange = false; }); @@ -287,11 +302,16 @@ const $setupHelpExtensionAutoClear = (newPlatform: CoreStart) => ( const $setupUrlOverflowHandling = (newPlatform: CoreStart) => ( $location: ILocationService, $rootScope: IRootScopeService, + $injector: any, Private: any, config: any ) => { + const $route = $injector.has('$route') ? $injector.get('$route') : {}; const urlOverflow = new UrlOverflowService(); const check = () => { + if (isDummyWrapperRoute($route)) { + return; + } // disable long url checks when storing state in session storage if (config.get('state:storeInSessionStorage')) { return; diff --git a/src/legacy/ui/public/routes/route_manager.d.ts b/src/legacy/ui/public/routes/route_manager.d.ts index 3471d7e954862..3d1ba88918f55 100644 --- a/src/legacy/ui/public/routes/route_manager.d.ts +++ b/src/legacy/ui/public/routes/route_manager.d.ts @@ -25,8 +25,10 @@ import { ChromeBreadcrumb } from '../../../../core/public'; interface RouteConfiguration { controller?: string | ((...args: any[]) => void); - redirectTo?: string; + redirectTo?: string | ((params: object, path: string, search: string) => string); reloadOnSearch?: boolean; + reloadOnUrl?: boolean; + outerAngularWrapperRoute?: boolean; resolve?: object; template?: string; k7Breadcrumbs?: (...args: any[]) => ChromeBreadcrumb[]; From afafe109010d4cc6a54e56f27ec06bfc50b77c4c Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Tue, 22 Oct 2019 16:53:44 +0200 Subject: [PATCH 014/132] wip --- .../kibana/public/dashboard/app.js | 2 +- .../kibana/public/dashboard/dashboard_app.tsx | 3 - .../dashboard/dashboard_app_controller.tsx | 74 +++++++++---------- .../kibana/public/dashboard/index.ts | 45 +++++------ .../kibana/public/dashboard/plugin.ts | 68 ++++++++++++++++- .../kibana/public/dashboard/render_app.ts | 37 +++++----- 6 files changed, 137 insertions(+), 92 deletions(-) diff --git a/src/legacy/core_plugins/kibana/public/dashboard/app.js b/src/legacy/core_plugins/kibana/public/dashboard/app.js index 89ec519d656bb..44d031c0f50d5 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/app.js +++ b/src/legacy/core_plugins/kibana/public/dashboard/app.js @@ -210,7 +210,7 @@ export function initDashboardApp(app, deps) { }); }); - deps.featureCatalogueRegistryProvider.register(() => { + deps.getFeatureCatalogueRegistryProvider().register(() => { return { id: 'dashboard', title: i18n.translate('kbn.dashboard.featureCatalogue.dashboardTitle', { diff --git a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app.tsx b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app.tsx index 77a59c3d57f94..cd1a616c4f5af 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app.tsx +++ b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app.tsx @@ -93,8 +93,6 @@ export function initDashboardAppDirective(app: any, deps: RenderDeps) { const confirmModal = $injector.get('confirmModal'); const config = deps.uiSettings; - const Private = $injector.get('Private'); - return { restrict: 'E', controllerAs: 'dashboardApp', @@ -121,7 +119,6 @@ export function initDashboardAppDirective(app: any, deps: RenderDeps) { getAppState, dashboardConfig, localStorage, - Private, kbnUrl, AppStateClass: AppState, config, diff --git a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx index e212d1cf83d7d..4e327d63928a8 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx +++ b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx @@ -50,12 +50,9 @@ import { import { KbnUrl } from 'ui/url/kbn_url'; import { Filter } from '@kbn/es-query'; import { IndexPattern } from 'ui/index_patterns'; -import { IPrivate } from 'ui/private'; import { Query, SavedQuery } from 'src/legacy/core_plugins/data/public'; import { SaveOptions } from 'ui/saved_objects/saved_object'; -import { capabilities } from 'ui/capabilities'; import { Subscription } from 'rxjs'; -import { npStart } from 'ui/new_platform'; import { SavedObjectFinder } from 'ui/saved_objects/components/saved_object_finder'; import { extractTimeFilter, changeTimeFilter } from '../../../data/public'; @@ -72,7 +69,7 @@ import { ViewMode, openAddPanelFlyout, } from '../../../embeddable_api/public/np_ready/public'; -import { start } from '../../../embeddable_api/public/np_ready/public/legacy'; +// import { start } from '../../../embeddable_api/public/np_ready/public/legacy'; import { DashboardAppState, NavAction, ConfirmModalFn, SavedDashboardPanel } from './types'; import { showOptionsPopover } from './top_nav/show_options_popover'; @@ -88,7 +85,28 @@ import { DashboardAppScope } from './dashboard_app'; import { VISUALIZE_EMBEDDABLE_TYPE } from '../visualize/embeddable'; import { convertSavedDashboardPanelToPanelState } from './lib/embeddable_saved_object_converters'; import { SavedQueryService } from '../../../data/public/search/search_bar/lib/saved_query_service'; - +import { NotificationsStart, OverlayStart } from 'kibana/public'; +import { RenderDeps } from './render_app'; + +export interface DashboardAppControllerDependencies extends RenderDeps { + $scope: DashboardAppScope; + $route: any; + $routeParams: any; + getAppState: { + previouslyStored: () => TAppState | undefined; + }; + indexPatterns: { + getDefault: () => Promise; + }; + dashboardConfig: any; + localStorage: { + get: (prop: string) => unknown; + }; + kbnUrl: KbnUrl; + AppStateClass: TAppStateClass; + config: any; + confirmModal: ConfirmModalFn; +} export class DashboardAppController { // Part of the exposed plugin API - do not remove without careful consideration. @@ -112,34 +130,10 @@ export class DashboardAppController { getUnhashableStates, shareContextMenuExtensions, savedQueryService, - }: { - $scope: DashboardAppScope; - $route: any; - $routeParams: any; - getAppState: { - previouslyStored: () => TAppState | undefined; - }; - indexPatterns: { - getDefault: () => Promise; - }; - dashboardConfig: any; - localStorage: { - get: (prop: string) => unknown; - }; - Private: IPrivate; - kbnUrl: KbnUrl; - AppStateClass: TAppStateClass; - config: any; - confirmModal: ConfirmModalFn; - queryFilter: any; - getUnhashableStates: any; - shareContextMenuExtensions: any; - savedQueryService: SavedQueryService; - }) { - // const queryFilter = Private(FilterBarQueryFilterProvider); - // const getUnhashableStates = Private(getUnhashableStatesProvider); - // const shareContextMenuExtensions = Private(ShareContextMenuExtensionsRegistryProvider); - + embeddables, + dashboardCapabilities, + core: { notifications, overlays }, + }: DashboardAppControllerDependencies) { let lastReloadRequestTime = 0; const dash = ($scope.dash = $route.current.locals.dash); @@ -160,7 +154,7 @@ export class DashboardAppController { if (dashboardStateManager.getIsTimeSavedWithDashboard() && !getAppState.previouslyStored()) { dashboardStateManager.syncTimefilterWithDashboard(timefilter); } - $scope.showSaveQuery = capabilities.get().dashboard.saveQuery as boolean; + $scope.showSaveQuery = dashboardCapabilities.saveQuery as boolean; const updateIndexPatterns = (container?: DashboardContainer) => { if (!container || isErrorEmbeddable(container)) { @@ -247,7 +241,7 @@ export class DashboardAppController { let outputSubscription: Subscription | undefined; const dashboardDom = document.getElementById('dashboardViewport'); - const dashboardFactory = start.getEmbeddableFactory( + const dashboardFactory = embeddables.getEmbeddableFactory( DASHBOARD_CONTAINER_TYPE ) as DashboardContainerFactory; dashboardFactory @@ -534,7 +528,7 @@ export class DashboardAppController { }); $scope.$watch( - () => capabilities.get().dashboard.saveQuery, + () => dashboardCapabilities.saveQuery, newCapability => { $scope.showSaveQuery = newCapability as boolean; } @@ -773,10 +767,10 @@ export class DashboardAppController { if (dashboardContainer && !isErrorEmbeddable(dashboardContainer)) { openAddPanelFlyout({ embeddable: dashboardContainer, - getAllFactories: start.getEmbeddableFactories, - getFactory: start.getEmbeddableFactory, - notifications: npStart.core.notifications, - overlays: npStart.core.overlays, + getAllFactories: embeddables.getEmbeddableFactories, + getFactory: embeddables.getEmbeddableFactory, + notifications, + overlays, SavedObjectFinder, }); } diff --git a/src/legacy/core_plugins/kibana/public/dashboard/index.ts b/src/legacy/core_plugins/kibana/public/dashboard/index.ts index 42abbec798592..04685de94fe06 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/index.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/index.ts @@ -21,55 +21,48 @@ import { FeatureCatalogueRegistryProvider } from 'ui/registry/feature_catalogue' import { npSetup, npStart } from 'ui/new_platform'; import chrome from 'ui/chrome'; import { IPrivate } from 'ui/private'; -// @ts-ignore -import { toastNotifications, banners } from 'ui/notify'; -import { kfetch } from 'ui/kfetch'; -import { HomePlugin, LegacyAngularInjectedDependencies } from './plugin'; -import { createUiStatsReporter, METRIC_TYPE } from '../../../ui_metric/public'; +import { ShareContextMenuExtensionsRegistryProvider } from 'ui/share'; +import { getUnhashableStatesProvider } from 'ui/state_management/state_hashing/get_unhashable_states_provider'; +import { FilterBarQueryFilterProvider } from 'ui/filter_manager/query_filter'; +import { DashboardPlugin, LegacyAngularInjectedDependencies } from './plugin'; import { start as data } from '../../../data/public/legacy'; -import { TelemetryOptInProvider } from '../../../telemetry/public/services'; import { localApplicationService } from '../local_application_service'; - -export const trackUiMetric = createUiStatsReporter('Kibana_home'); +import { start as embeddables } from '../../../embeddable_api/public/np_ready/public/legacy'; /** * Get dependencies relying on the global angular context. * They also have to get resolved together with the legacy imports above */ -async function getAngularInjectedDependencies(): Promise { +async function getAngularDependencies(): Promise { const injector = await chrome.dangerouslyGetActiveInjector(); const Private = injector.get('Private'); - const telemetryEnabled = npStart.core.injectedMetadata.getInjectedVar('telemetryEnabled'); - const telemetryBanner = npStart.core.injectedMetadata.getInjectedVar('telemetryBanner'); - const telemetryOptInProvider = Private(TelemetryOptInProvider); + const queryFilter = Private(FilterBarQueryFilterProvider); + const getUnhashableStates = Private(getUnhashableStatesProvider); + const shareContextMenuExtensions = Private(ShareContextMenuExtensionsRegistryProvider); return { - telemetryOptInProvider, - shouldShowTelemetryOptIn: - telemetryEnabled && telemetryBanner && !telemetryOptInProvider.getOptIn(), - featureCatalogueRegistryProvider: Private(FeatureCatalogueRegistryProvider as any), + queryFilter, + getUnhashableStates, + shareContextMenuExtensions, + getFeatureCatalogueRegistryProvider: () => { + return Private(FeatureCatalogueRegistryProvider as any); + }, + dashboardConfig: injector.get('dashboardConfig'), }; } (async () => { - const instance = new HomePlugin(); + const instance = new DashboardPlugin(); instance.setup(npSetup.core, { __LEGACY: { - trackUiMetric, - toastNotifications, - banners, - kfetch, - metadata: npStart.core.injectedMetadata.getLegacyMetadata(), - METRIC_TYPE, localApplicationService, + getAngularDependencies, }, }); instance.start(npStart.core, { data, - __LEGACY: { - angularDependencies: await getAngularInjectedDependencies(), - }, + embeddables, }); })(); diff --git a/src/legacy/core_plugins/kibana/public/dashboard/plugin.ts b/src/legacy/core_plugins/kibana/public/dashboard/plugin.ts index decc002f765c3..5a69c33bc55aa 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/plugin.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/plugin.ts @@ -17,14 +17,74 @@ * under the License. */ -import { CoreSetup, CoreStart, Plugin } from 'kibana/public'; +import { CoreSetup, CoreStart, Plugin, SavedObjectsClientContract } from 'kibana/public'; +import { RenderDeps } from './render_app'; +import { LocalApplicationService } from '../local_application_service'; +import { DataStart } from '../../../data/public'; +import { SavedQueryService } from '../../../data/public/search/search_bar/lib/saved_query_service'; +import { EmbeddablePublicPlugin } from '../../../../../plugins/embeddable/public'; + +export interface LegacyAngularInjectedDependencies { + queryFilter: any; + getUnhashableStates: any; + shareContextMenuExtensions: any; + getFeatureCatalogueRegistryProvider: () => Promise; + dashboardConfig: any; +} + +export interface DashboardPluginStartDependencies { + data: DataStart; + embeddables: ReturnType; +} + +export interface DashboardPluginSetupDependencies { + __LEGACY: { + getAngularDependencies: () => Promise; + localApplicationService: LocalApplicationService; + }; +} export class DashboardPlugin implements Plugin { - public setup(core: CoreSetup) { + private dataStart: DataStart | null = null; + private savedObjectsClient: SavedObjectsClientContract | null = null; + private savedQueryService: SavedQueryService | null = null; + private embeddables: ReturnType | null = null; + public setup( + core: CoreSetup, + { + __LEGACY: { localApplicationService, getAngularDependencies, ...legacyServices }, + }: DashboardPluginSetupDependencies + ) { + localApplicationService.register({ + id: 'home', + title: 'Home', + mount: async ({ core: contextCore }, params) => { + const angularDependencies = await getAngularDependencies(); + const deps: RenderDeps = { + core: contextCore, + ...legacyServices, + ...angularDependencies, + indexPatterns: this.dataStart!.indexPatterns.indexPatterns, + savedObjectsClient: this.savedObjectsClient!, + chrome: contextCore.chrome, + addBasePath: contextCore.http.basePath.prepend, + uiSettings: contextCore.uiSettings, + savedQueryService: this.savedQueryService!, + embeddables: this.embeddables!, + dashboardCapabilities: contextCore.application.capabilities.dashboard, + }; + const { renderApp } = await import('./render_app'); + return renderApp(params.element, params.appBasePath, deps); + }, + }); } - public start(core: CoreStart) { - + start(core: CoreStart, { data, embeddables }: DashboardPluginStartDependencies) { + // TODO is this really the right way? I though the app context would give us those + this.dataStart = data; + this.savedObjectsClient = core.savedObjects.client; + this.savedQueryService = data.search.services.savedQueryService; + this.embeddables = embeddables; } } diff --git a/src/legacy/core_plugins/kibana/public/dashboard/render_app.ts b/src/legacy/core_plugins/kibana/public/dashboard/render_app.ts index ebde291380302..473130af81a65 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/render_app.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/render_app.ts @@ -50,30 +50,31 @@ import { FilterBarQueryFilterProvider } from 'ui/filter_manager/query_filter'; import { getUnhashableStatesProvider } from 'ui/state_management/state_hashing/get_unhashable_states_provider'; import { ShareContextMenuExtensionsRegistryProvider } from 'ui/share'; import { SavedQueryService } from '../../../data/public/search/search_bar/lib/saved_query_service'; +import { EmbeddablePublicPlugin } from '../../../../../plugins/embeddable/public'; export interface RenderDeps { - core: AppMountContext['core']; - indexPatterns: DataStart['indexPatterns']['indexPatterns']; - queryFilter: any; - getUnhashableStates: any; - shareContextMenuExtensions: any; + core: AppMountContext['core']; //x + indexPatterns: DataStart['indexPatterns']['indexPatterns']; //x + queryFilter: any; //x + getUnhashableStates: any; //x + shareContextMenuExtensions: any; //x + savedObjectsClient: SavedObjectsClientContract; //x savedObjectRegistry: any; - savedObjectsClient: SavedObjectsClientContract; - dashboardConfig: any; - uiSettings: UiSettingsClientContract; - savedDashboards: any; - chrome: ChromeStart; - addBasePath: (path: string) => string; - featureCatalogueRegistryProvider: any; - dashboardCapabilities: any; - savedQueryService: SavedQueryService; - emebeddables: EmbeddableStart; + dashboardConfig: any; //x + savedDashboards: any + dashboardCapabilities: any; //x + uiSettings: UiSettingsClientContract; //x + chrome: ChromeStart; //x + addBasePath: (path: string) => string; //x + getFeatureCatalogueRegistryProvider: () => any; //x + savedQueryService: SavedQueryService; //x + embeddables: ReturnType; //x } -export const renderApp = (element: HTMLElement, appBasePath: string, { core }: RenderDeps) => { - const dashboardAngularModule = createLocalAngularModule(core); +export const renderApp = (element: HTMLElement, appBasePath: string, deps: RenderDeps) => { + const dashboardAngularModule = createLocalAngularModule(deps.core); configureAppAngularModule(dashboardAngularModule); - initDashboardApp(dashboardAngularModule); + initDashboardApp(dashboardAngularModule, deps); const $injector = mountDashboardApp(appBasePath, element); return () => $injector.get('$rootScope').$destroy(); }; From a7f2826dad084a964c4337525f9e57282a285979 Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Tue, 22 Oct 2019 17:19:16 +0200 Subject: [PATCH 015/132] wip --- .../kibana/public/dashboard/app.js | 4 +- .../dashboard/dashboard_app_controller.tsx | 6 +-- .../kibana/public/dashboard/index.ts | 4 ++ .../kibana/public/dashboard/plugin.ts | 6 ++- .../kibana/public/dashboard/render_app.ts | 40 ++++++++++--------- 5 files changed, 32 insertions(+), 28 deletions(-) diff --git a/src/legacy/core_plugins/kibana/public/dashboard/app.js b/src/legacy/core_plugins/kibana/public/dashboard/app.js index 44d031c0f50d5..0cf02da8b2e6a 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/app.js +++ b/src/legacy/core_plugins/kibana/public/dashboard/app.js @@ -150,8 +150,8 @@ export function initDashboardApp(app, deps) { controller: createNewDashboardCtrl, requireUICapability: 'dashboard.createNew', resolve: { - dash: function (savedDashboards, redirectWhenMissing) { - return savedDashboards.get().catch( + dash: function (redirectWhenMissing) { + return deps.savedDashboards.get().catch( redirectWhenMissing({ dashboard: DashboardConstants.LANDING_PAGE_PATH, }) diff --git a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx index 4e327d63928a8..1abf03a9d6d78 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx +++ b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx @@ -29,18 +29,16 @@ import { toastNotifications } from 'ui/notify'; // @ts-ignore import { ConfirmationButtonTypes } from 'ui/modals/confirm_modal'; -import { FilterBarQueryFilterProvider } from 'ui/filter_manager/query_filter'; import { docTitle } from 'ui/doc_title/doc_title'; import { showSaveModal, SaveResult } from 'ui/saved_objects/show_saved_object_save_modal'; -import { showShareContextMenu, ShareContextMenuExtensionsRegistryProvider } from 'ui/share'; +import { showShareContextMenu } from 'ui/share'; import { migrateLegacyQuery } from 'ui/utils/migrate_legacy_query'; import { timefilter } from 'ui/timefilter'; -import { getUnhashableStatesProvider } from 'ui/state_management/state_hashing/get_unhashable_states_provider'; import { AppStateClass as TAppStateClass, @@ -84,8 +82,6 @@ import { getDashboardTitle } from './dashboard_strings'; import { DashboardAppScope } from './dashboard_app'; import { VISUALIZE_EMBEDDABLE_TYPE } from '../visualize/embeddable'; import { convertSavedDashboardPanelToPanelState } from './lib/embeddable_saved_object_converters'; -import { SavedQueryService } from '../../../data/public/search/search_bar/lib/saved_query_service'; -import { NotificationsStart, OverlayStart } from 'kibana/public'; import { RenderDeps } from './render_app'; export interface DashboardAppControllerDependencies extends RenderDeps { diff --git a/src/legacy/core_plugins/kibana/public/dashboard/index.ts b/src/legacy/core_plugins/kibana/public/dashboard/index.ts index 04685de94fe06..1508492e538c9 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/index.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/index.ts @@ -28,6 +28,7 @@ import { DashboardPlugin, LegacyAngularInjectedDependencies } from './plugin'; import { start as data } from '../../../data/public/legacy'; import { localApplicationService } from '../local_application_service'; import { start as embeddables } from '../../../embeddable_api/public/np_ready/public/legacy'; +import { SavedObjectRegistryProvider } from 'ui/saved_objects'; /** * Get dependencies relying on the global angular context. @@ -41,6 +42,7 @@ async function getAngularDependencies(): Promise Promise; dashboardConfig: any; + savedObjectRegistry: any; + savedDashboards: any; } export interface DashboardPluginStartDependencies { @@ -57,8 +59,8 @@ export class DashboardPlugin implements Plugin { }: DashboardPluginSetupDependencies ) { localApplicationService.register({ - id: 'home', - title: 'Home', + id: 'discover', + title: 'Discover', mount: async ({ core: contextCore }, params) => { const angularDependencies = await getAngularDependencies(); const deps: RenderDeps = { diff --git a/src/legacy/core_plugins/kibana/public/dashboard/render_app.ts b/src/legacy/core_plugins/kibana/public/dashboard/render_app.ts index 473130af81a65..c57b309fee2e1 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/render_app.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/render_app.ts @@ -40,35 +40,37 @@ import { KbnUrlProvider, RedirectWhenMissingProvider } from 'ui/url'; // @ts-ignore import { confirmModalFactory } from 'ui/modals/confirm_modal'; -import { AppMountContext, ChromeStart, SavedObjectsClientContract, UiSettingsClientContract } from 'kibana/public'; +import { + AppMountContext, + ChromeStart, + SavedObjectsClientContract, + UiSettingsClientContract, +} from 'kibana/public'; import { configureAppAngularModule } from 'ui/legacy_compat'; // @ts-ignore import { initDashboardApp } from './app'; import { DataStart } from '../../../data/public'; -import { FilterBarQueryFilterProvider } from 'ui/filter_manager/query_filter'; -import { getUnhashableStatesProvider } from 'ui/state_management/state_hashing/get_unhashable_states_provider'; -import { ShareContextMenuExtensionsRegistryProvider } from 'ui/share'; import { SavedQueryService } from '../../../data/public/search/search_bar/lib/saved_query_service'; import { EmbeddablePublicPlugin } from '../../../../../plugins/embeddable/public'; export interface RenderDeps { - core: AppMountContext['core']; //x - indexPatterns: DataStart['indexPatterns']['indexPatterns']; //x - queryFilter: any; //x - getUnhashableStates: any; //x - shareContextMenuExtensions: any; //x - savedObjectsClient: SavedObjectsClientContract; //x + core: AppMountContext['core']; + indexPatterns: DataStart['indexPatterns']['indexPatterns']; + queryFilter: any; + getUnhashableStates: any; + shareContextMenuExtensions: any; + savedObjectsClient: SavedObjectsClientContract; savedObjectRegistry: any; - dashboardConfig: any; //x - savedDashboards: any - dashboardCapabilities: any; //x - uiSettings: UiSettingsClientContract; //x - chrome: ChromeStart; //x - addBasePath: (path: string) => string; //x - getFeatureCatalogueRegistryProvider: () => any; //x - savedQueryService: SavedQueryService; //x - embeddables: ReturnType; //x + dashboardConfig: any; + savedDashboards: any; + dashboardCapabilities: any; + uiSettings: UiSettingsClientContract; + chrome: ChromeStart; + addBasePath: (path: string) => string; + getFeatureCatalogueRegistryProvider: () => any; + savedQueryService: SavedQueryService; + embeddables: ReturnType; } export const renderApp = (element: HTMLElement, appBasePath: string, deps: RenderDeps) => { From c4170606077740fbc07d0fd8c41be66759c696a1 Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Tue, 22 Oct 2019 17:25:21 +0200 Subject: [PATCH 016/132] fix mounting home several times --- .../core_plugins/kibana/public/home/kibana_services.ts | 4 ++++ src/legacy/core_plugins/kibana/public/home/render_app.tsx | 7 +++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/legacy/core_plugins/kibana/public/home/kibana_services.ts b/src/legacy/core_plugins/kibana/public/home/kibana_services.ts index 11f1f94d11b6f..ebc99df9d4e5e 100644 --- a/src/legacy/core_plugins/kibana/public/home/kibana_services.ts +++ b/src/legacy/core_plugins/kibana/public/home/kibana_services.ts @@ -81,3 +81,7 @@ export function getServices() { } return services; } + +export function clearServices() { + services = null; +} diff --git a/src/legacy/core_plugins/kibana/public/home/render_app.tsx b/src/legacy/core_plugins/kibana/public/home/render_app.tsx index 5d2881ea5edcf..50041410d3c6f 100644 --- a/src/legacy/core_plugins/kibana/public/home/render_app.tsx +++ b/src/legacy/core_plugins/kibana/public/home/render_app.tsx @@ -22,7 +22,7 @@ import { render, unmountComponentAtNode } from 'react-dom'; import { i18n } from '@kbn/i18n'; // @ts-ignore import { HomeApp } from './components/home_app'; -import { getServices } from './kibana_services'; +import { clearServices, getServices } from './kibana_services'; export const renderApp = async (element: HTMLElement) => { const homeTitle = i18n.translate('kbn.home.breadcrumbs.homeTitle', { defaultMessage: 'Home' }); @@ -32,5 +32,8 @@ export const renderApp = async (element: HTMLElement) => { render(, element); - return () => unmountComponentAtNode(element); + return () => { + unmountComponentAtNode(element); + clearServices(); + }; }; From 03fd207650a9bf427fb25a708f35fc3a1a225417 Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Tue, 22 Oct 2019 19:06:09 +0200 Subject: [PATCH 017/132] wip --- .../kibana/public/dashboard/app.js | 6 +- .../dashboard/help_menu/help_menu_util.js | 2 +- .../kibana/public/dashboard/index.ts | 8 +- .../kibana/public/dashboard/plugin.ts | 7 +- .../kibana/public/dashboard/render_app.ts | 2 +- .../local_application_service.ts | 4 +- .../ui/public/kbn_top_nav/kbn_top_nav.js | 12 +- src/legacy/ui/public/private/private.js | 146 +++++++++--------- .../state_management/config_provider.js | 32 ++-- 9 files changed, 114 insertions(+), 105 deletions(-) diff --git a/src/legacy/core_plugins/kibana/public/dashboard/app.js b/src/legacy/core_plugins/kibana/public/dashboard/app.js index 0cf02da8b2e6a..f219da072c87b 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/app.js +++ b/src/legacy/core_plugins/kibana/public/dashboard/app.js @@ -18,8 +18,6 @@ */ import { i18n } from '@kbn/i18n'; -import './saved_dashboard/saved_dashboards'; -import './dashboard_config'; import uiRoutes from 'ui/routes'; import { wrapInI18nContext } from 'ui/i18n'; @@ -113,7 +111,7 @@ export function initDashboardApp(app, deps) { addHelpMenuToAppChrome(deps.chrome); }, resolve: { - dash: function ($route, redirectWhenMissing, kbnUrl) { + dash: function ($route/*, redirectWhenMissing, kbnUrl*/) { const savedObjectsClient = deps.savedObjectsClient; const title = $route.current.params.title; if (title) { @@ -210,7 +208,7 @@ export function initDashboardApp(app, deps) { }); }); - deps.getFeatureCatalogueRegistryProvider().register(() => { + deps.FeatureCatalogueRegistryProvider.register(() => { return { id: 'dashboard', title: i18n.translate('kbn.dashboard.featureCatalogue.dashboardTitle', { diff --git a/src/legacy/core_plugins/kibana/public/dashboard/help_menu/help_menu_util.js b/src/legacy/core_plugins/kibana/public/dashboard/help_menu/help_menu_util.js index aeabff2d97007..58a92193de63e 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/help_menu/help_menu_util.js +++ b/src/legacy/core_plugins/kibana/public/dashboard/help_menu/help_menu_util.js @@ -22,7 +22,7 @@ import { render, unmountComponentAtNode } from 'react-dom'; import { HelpMenu } from './help_menu'; export function addHelpMenuToAppChrome(chrome) { - chrome.helpExtension.set(domElement => { + chrome.setHelpExtension(domElement => { render(, domElement); return () => { unmountComponentAtNode(domElement); diff --git a/src/legacy/core_plugins/kibana/public/dashboard/index.ts b/src/legacy/core_plugins/kibana/public/dashboard/index.ts index 1508492e538c9..4b63970daa461 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/index.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/index.ts @@ -19,6 +19,7 @@ import { FeatureCatalogueRegistryProvider } from 'ui/registry/feature_catalogue'; import { npSetup, npStart } from 'ui/new_platform'; +import { SavedObjectRegistryProvider } from 'ui/saved_objects'; import chrome from 'ui/chrome'; import { IPrivate } from 'ui/private'; import { ShareContextMenuExtensionsRegistryProvider } from 'ui/share'; @@ -28,7 +29,8 @@ import { DashboardPlugin, LegacyAngularInjectedDependencies } from './plugin'; import { start as data } from '../../../data/public/legacy'; import { localApplicationService } from '../local_application_service'; import { start as embeddables } from '../../../embeddable_api/public/np_ready/public/legacy'; -import { SavedObjectRegistryProvider } from 'ui/saved_objects'; +import './saved_dashboard/saved_dashboards'; +import './dashboard_config'; /** * Get dependencies relying on the global angular context. @@ -48,9 +50,6 @@ async function getAngularDependencies(): Promise { - return Private(FeatureCatalogueRegistryProvider as any); - }, dashboardConfig: injector.get('dashboardConfig'), savedObjectRegistry, savedDashboards: injector.get('savedDashboards'), @@ -63,6 +62,7 @@ async function getAngularDependencies(): Promise Promise; dashboardConfig: any; savedObjectRegistry: any; savedDashboards: any; @@ -43,6 +42,7 @@ export interface DashboardPluginSetupDependencies { __LEGACY: { getAngularDependencies: () => Promise; localApplicationService: LocalApplicationService; + FeatureCatalogueRegistryProvider: any; }; } @@ -58,9 +58,10 @@ export class DashboardPlugin implements Plugin { __LEGACY: { localApplicationService, getAngularDependencies, ...legacyServices }, }: DashboardPluginSetupDependencies ) { + localApplicationService.forwardApp('dashboard', 'dashboards'); localApplicationService.register({ - id: 'discover', - title: 'Discover', + id: 'dashboards', + title: 'Dashboards', mount: async ({ core: contextCore }, params) => { const angularDependencies = await getAngularDependencies(); const deps: RenderDeps = { diff --git a/src/legacy/core_plugins/kibana/public/dashboard/render_app.ts b/src/legacy/core_plugins/kibana/public/dashboard/render_app.ts index c57b309fee2e1..25f86a6d25c6e 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/render_app.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/render_app.ts @@ -100,7 +100,7 @@ function mountDashboardApp(appBasePath: string, element: HTMLElement) { // make angular-within-angular possible const $injector = angular.bootstrap(mountpoint, [moduleName]); // initialize global state handler - $injector.get('globalState'); + // $injector.get('globalState'); element.appendChild(mountpoint); return $injector; } diff --git a/src/legacy/core_plugins/kibana/public/local_application_service/local_application_service.ts b/src/legacy/core_plugins/kibana/public/local_application_service/local_application_service.ts index c2963d1ba7095..5e9b9e5fd7ef7 100644 --- a/src/legacy/core_plugins/kibana/public/local_application_service/local_application_service.ts +++ b/src/legacy/core_plugins/kibana/public/local_application_service/local_application_service.ts @@ -75,7 +75,9 @@ export class LocalApplicationService { this.forwards.forEach(({ legacyAppId, newAppId, keepPrefix }) => { angularRouteManager.when(`/${legacyAppId}:tail*?`, { redirectTo: (_params: unknown, path: string, search: string) => { - const newPath = `/${newAppId}${keepPrefix ? path : path.replace(legacyAppId, '')}`; + const newPath = `/${newAppId}${ + keepPrefix ? path : path.replace(new RegExp(`${legacyAppId}/?`), '') + }`; return `${newPath}?${search}`; }, }); diff --git a/src/legacy/ui/public/kbn_top_nav/kbn_top_nav.js b/src/legacy/ui/public/kbn_top_nav/kbn_top_nav.js index 79365eb5cf1cc..dd235bc62473e 100644 --- a/src/legacy/ui/public/kbn_top_nav/kbn_top_nav.js +++ b/src/legacy/ui/public/kbn_top_nav/kbn_top_nav.js @@ -24,7 +24,7 @@ import { TopNavMenu } from '../../../core_plugins/kibana_react/public'; const module = uiModules.get('kibana'); -module.directive('kbnTopNav', () => { +export const createTopNavDirective = () => { return { restrict: 'E', template: '', @@ -71,9 +71,11 @@ module.directive('kbnTopNav', () => { return linkFn; } }; -}); +}; -module.directive('kbnTopNavHelper', (reactDirective) => { +module.directive('kbnTopNav', createTopNavDirective); + +export const createTopNavHelper = (reactDirective) => { return reactDirective( wrapInI18nContext(TopNavMenu), [ @@ -113,4 +115,6 @@ module.directive('kbnTopNavHelper', (reactDirective) => { 'showAutoRefreshOnly', ], ); -}); +}; + +module.directive('kbnTopNavHelper', createTopNavHelper); diff --git a/src/legacy/ui/public/private/private.js b/src/legacy/ui/public/private/private.js index ef5c59c21dd7a..74d3785a4238a 100644 --- a/src/legacy/ui/public/private/private.js +++ b/src/legacy/ui/public/private/private.js @@ -108,98 +108,100 @@ function name(fn) { return fn.name || fn.toString().split('\n').shift(); } -uiModules.get('kibana/private') - .provider('Private', function () { - const provider = this; - - // one cache/swaps per Provider - const cache = {}; - const swaps = {}; +export function PrivateProvider() { + const provider = this; - // return the uniq id for this function - function identify(fn) { - if (typeof fn !== 'function') { - throw new TypeError('Expected private module "' + fn + '" to be a function'); - } + // one cache/swaps per Provider + const cache = {}; + const swaps = {}; - if (fn.$$id) return fn.$$id; - else return (fn.$$id = nextId()); + // return the uniq id for this function + function identify(fn) { + if (typeof fn !== 'function') { + throw new TypeError('Expected private module "' + fn + '" to be a function'); } - provider.stub = function (fn, instance) { - cache[identify(fn)] = instance; - return instance; - }; + if (fn.$$id) return fn.$$id; + else return (fn.$$id = nextId()); + } - provider.swap = function (fn, prov) { - const id = identify(fn); - swaps[id] = prov; - }; + provider.stub = function (fn, instance) { + cache[identify(fn)] = instance; + return instance; + }; + + provider.swap = function (fn, prov) { + const id = identify(fn); + swaps[id] = prov; + }; - provider.$get = ['$injector', function PrivateFactory($injector) { + provider.$get = ['$injector', function PrivateFactory($injector) { - // prevent circular deps by tracking where we came from - const privPath = []; - const pathToString = function () { - return privPath.map(name).join(' -> '); - }; + // prevent circular deps by tracking where we came from + const privPath = []; + const pathToString = function () { + return privPath.map(name).join(' -> '); + }; - // call a private provider and return the instance it creates - function instantiate(prov, locals) { - if (~privPath.indexOf(prov)) { - throw new Error( - 'Circular reference to "' + name(prov) + '"' + + // call a private provider and return the instance it creates + function instantiate(prov, locals) { + if (~privPath.indexOf(prov)) { + throw new Error( + 'Circular reference to "' + name(prov) + '"' + ' found while resolving private deps: ' + pathToString() - ); - } + ); + } - privPath.push(prov); + privPath.push(prov); - const context = {}; - let instance = $injector.invoke(prov, context, locals); - if (!_.isObject(instance)) instance = context; + const context = {}; + let instance = $injector.invoke(prov, context, locals); + if (!_.isObject(instance)) instance = context; - privPath.pop(); - return instance; - } - - // retrieve an instance from cache or create and store on - function get(id, prov, $delegateId, $delegateProv) { - if (cache[id]) return cache[id]; + privPath.pop(); + return instance; + } - let instance; + // retrieve an instance from cache or create and store on + function get(id, prov, $delegateId, $delegateProv) { + if (cache[id]) return cache[id]; - if ($delegateId != null && $delegateProv != null) { - instance = instantiate(prov, { - $decorate: _.partial(get, $delegateId, $delegateProv) - }); - } else { - instance = instantiate(prov); - } + let instance; - return (cache[id] = instance); + if ($delegateId != null && $delegateProv != null) { + instance = instantiate(prov, { + $decorate: _.partial(get, $delegateId, $delegateProv) + }); + } else { + instance = instantiate(prov); } - // main api, get the appropriate instance for a provider - function Private(prov) { - let id = identify(prov); - let $delegateId; - let $delegateProv; + return (cache[id] = instance); + } - if (swaps[id]) { - $delegateId = id; - $delegateProv = prov; + // main api, get the appropriate instance for a provider + function Private(prov) { + let id = identify(prov); + let $delegateId; + let $delegateProv; - prov = swaps[$delegateId]; - id = identify(prov); - } + if (swaps[id]) { + $delegateId = id; + $delegateProv = prov; - return get(id, prov, $delegateId, $delegateProv); + prov = swaps[$delegateId]; + id = identify(prov); } - Private.stub = provider.stub; - Private.swap = provider.swap; + return get(id, prov, $delegateId, $delegateProv); + } - return Private; - }]; - }); + Private.stub = provider.stub; + Private.swap = provider.swap; + + return Private; + }]; +} + +uiModules.get('kibana/private') + .provider('Private', PrivateProvider); diff --git a/src/legacy/ui/public/state_management/config_provider.js b/src/legacy/ui/public/state_management/config_provider.js index 090210cc8723e..ec770e7fef6ca 100644 --- a/src/legacy/ui/public/state_management/config_provider.js +++ b/src/legacy/ui/public/state_management/config_provider.js @@ -25,21 +25,23 @@ import { uiModules } from '../modules'; -uiModules.get('kibana/state_management') - .provider('stateManagementConfig', class StateManagementConfigProvider { - _enabled = true +export class StateManagementConfigProvider { + _enabled = true + + $get(/* inject stuff */) { + return { + enabled: this._enabled, + }; + } - $get(/* inject stuff */) { - return { - enabled: this._enabled, - }; - } + disable() { + this._enabled = false; + } - disable() { - this._enabled = false; - } + enable() { + this._enabled = true; + } +} - enable() { - this._enabled = true; - } - }); +uiModules.get('kibana/state_management') + .provider('stateManagementConfig', StateManagementConfigProvider); From 160fe52741e7652431b724f642a244ae9a10e3e6 Mon Sep 17 00:00:00 2001 From: maryia-lapata Date: Wed, 23 Oct 2019 18:19:07 +0300 Subject: [PATCH 018/132] Centralize dependencies for Visualize plugin --- .../visualize/embeddable/get_index_pattern.ts | 9 +-- .../visualize_embeddable_factory.tsx | 6 +- .../public/visualize/help_menu/help_menu.js | 7 +- .../visualize/help_menu/help_menu_util.js | 2 +- .../kibana/public/visualize/index.js | 7 +- .../public/visualize/kibana_services.ts | 71 +++++++++++++++++++ .../visualize/listing/visualize_listing.js | 27 ++++--- .../listing/visualize_listing_table.js | 9 ++- .../kibana/public/visualize/types.d.ts | 2 +- .../public/visualize/wizard/new_vis_modal.tsx | 10 +-- .../wizard/type_selection/new_vis_help.tsx | 5 +- 11 files changed, 123 insertions(+), 32 deletions(-) create mode 100644 src/legacy/core_plugins/kibana/public/visualize/kibana_services.ts diff --git a/src/legacy/core_plugins/kibana/public/visualize/embeddable/get_index_pattern.ts b/src/legacy/core_plugins/kibana/public/visualize/embeddable/get_index_pattern.ts index 699fa68b4528b..97db9e1efea8c 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/embeddable/get_index_pattern.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/embeddable/get_index_pattern.ts @@ -17,10 +17,13 @@ * under the License. */ -import chrome from 'ui/chrome'; import { StaticIndexPattern, getFromSavedObject } from 'ui/index_patterns'; import { VisSavedObject } from 'ui/visualize/loader/types'; +import { getServices } from '../kibana_services'; + +const { uiSettings, savedObjectsClient } = getServices(); + export async function getIndexPattern( savedVis: VisSavedObject ): Promise { @@ -28,9 +31,7 @@ export async function getIndexPattern( return savedVis.vis.indexPattern; } - const config = chrome.getUiSettingsClient(); - const savedObjectsClient = chrome.getSavedObjectsClient(); - const defaultIndex = config.get('defaultIndex'); + const defaultIndex = uiSettings.get('defaultIndex'); if (savedVis.vis.params.index_pattern) { const indexPatternObjects = await savedObjectsClient.find({ diff --git a/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable_factory.tsx b/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable_factory.tsx index 8448b65e0994e..5c4768cd63dad 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable_factory.tsx +++ b/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable_factory.tsx @@ -61,6 +61,10 @@ import { VisualizeEmbeddable, VisualizeInput, VisualizeOutput } from './visualiz import { VISUALIZE_EMBEDDABLE_TYPE } from './constants'; import { TypesStart } from '../../../../visualizations/public/np_ready/public/types'; +import { getServices } from '../kibana_services'; + +const { addBasePath } = getServices(); + interface VisualizationAttributes extends SavedObjectAttributes { visState: string; } @@ -142,7 +146,7 @@ export class VisualizeEmbeddableFactory extends EmbeddableFactory< try { const visId = savedObjectId; - const editUrl = chrome.addBasePath(`/app/kibana${savedVisualizations.urlFor(visId)}`); + const editUrl = addBasePath(`/app/kibana${savedVisualizations.urlFor(visId)}`); const loader = await getVisualizeLoader(); const savedObject = await savedVisualizations.get(visId); const isLabsEnabled = config.get('visualize:enableLabs'); diff --git a/src/legacy/core_plugins/kibana/public/visualize/help_menu/help_menu.js b/src/legacy/core_plugins/kibana/public/visualize/help_menu/help_menu.js index d95f7ea85c5db..40a1b79ea3520 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/help_menu/help_menu.js +++ b/src/legacy/core_plugins/kibana/public/visualize/help_menu/help_menu.js @@ -20,7 +20,10 @@ import React, { Fragment, PureComponent } from 'react'; import { EuiButton, EuiHorizontalRule, EuiSpacer } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import { ELASTIC_WEBSITE_URL, DOC_LINK_VERSION } from 'ui/documentation_links'; + +import { getServices } from '../kibana_services'; + +const { docLinks } = getServices(); export class HelpMenu extends PureComponent { render() { @@ -31,7 +34,7 @@ export class HelpMenu extends PureComponent { diff --git a/src/legacy/core_plugins/kibana/public/visualize/help_menu/help_menu_util.js b/src/legacy/core_plugins/kibana/public/visualize/help_menu/help_menu_util.js index aeabff2d97007..58a92193de63e 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/help_menu/help_menu_util.js +++ b/src/legacy/core_plugins/kibana/public/visualize/help_menu/help_menu_util.js @@ -22,7 +22,7 @@ import { render, unmountComponentAtNode } from 'react-dom'; import { HelpMenu } from './help_menu'; export function addHelpMenuToAppChrome(chrome) { - chrome.helpExtension.set(domElement => { + chrome.setHelpExtension(domElement => { render(, domElement); return () => { unmountComponentAtNode(domElement); diff --git a/src/legacy/core_plugins/kibana/public/visualize/index.js b/src/legacy/core_plugins/kibana/public/visualize/index.js index b3c16fb94d7fb..e53a3479f0ffa 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/index.js +++ b/src/legacy/core_plugins/kibana/public/visualize/index.js @@ -21,17 +21,18 @@ import './editor/editor'; import { i18n } from '@kbn/i18n'; import './saved_visualizations/_saved_vis'; import './saved_visualizations/saved_visualizations'; -import uiRoutes from 'ui/routes'; -import 'ui/capabilities/route_setup'; import visualizeListingTemplate from './listing/visualize_listing.html'; import { VisualizeListingController } from './listing/visualize_listing'; import { VisualizeConstants } from './visualize_constants'; -import { FeatureCatalogueRegistryProvider, FeatureCatalogueCategory } from 'ui/registry/feature_catalogue'; import { getLandingBreadcrumbs, getWizardStep1Breadcrumbs } from './breadcrumbs'; +import { getServices, FeatureCatalogueCategory } from './kibana_services'; + // load directives import '../../../data/public'; +const { uiRoutes, FeatureCatalogueRegistryProvider } = getServices(); + uiRoutes .defaults(/visualize/, { requireDefaultIndex: true, diff --git a/src/legacy/core_plugins/kibana/public/visualize/kibana_services.ts b/src/legacy/core_plugins/kibana/public/visualize/kibana_services.ts new file mode 100644 index 0000000000000..dd0234d6ed490 --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/visualize/kibana_services.ts @@ -0,0 +1,71 @@ +/* + * 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 'ui/directives/kbn_href'; + +import { npStart } from 'ui/new_platform'; +import chromeLegacy from 'ui/chrome'; +import angular from 'angular'; + +import uiRoutes from 'ui/routes'; +import { wrapInI18nContext } from 'ui/i18n'; + +// @ts-ignore +import { uiModules } from 'ui/modules'; +import { FeatureCatalogueRegistryProvider } from 'ui/registry/feature_catalogue'; + +// Filters +import { timefilter } from 'ui/timefilter'; + +// Saved objects +import { SavedObjectRegistryProvider } from 'ui/saved_objects/saved_object_registry'; +import { SavedObjectsClientProvider } from 'ui/saved_objects'; +// @ts-ignore +import { SavedObjectProvider } from 'ui/saved_objects/saved_object'; + +const services = { + // new platform + capabilities: npStart.core.application.capabilities, + chrome: npStart.core.chrome, + docLinks: npStart.core.docLinks, + toastNotifications: npStart.core.notifications.toasts, + uiSettings: npStart.core.uiSettings, + savedObjectsClient: npStart.core.savedObjects.client, + addBasePath: npStart.core.http.basePath.prepend, + + // legacy + angular, + uiRoutes, + uiModules, + FeatureCatalogueRegistryProvider, + SavedObjectRegistryProvider, + SavedObjectsClientProvider, + SavedObjectProvider, + timefilter, + wrapInI18nContext, +}; +export function getServices() { + return services; +} + +// export types +export { VisSavedObject } from 'ui/visualize/loader/types'; + +// const +export { FeatureCatalogueCategory } from 'ui/registry/feature_catalogue'; diff --git a/src/legacy/core_plugins/kibana/public/visualize/listing/visualize_listing.js b/src/legacy/core_plugins/kibana/public/visualize/listing/visualize_listing.js index bb05ce34413db..aeee3c91cbfa7 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/listing/visualize_listing.js +++ b/src/legacy/core_plugins/kibana/public/visualize/listing/visualize_listing.js @@ -17,21 +17,26 @@ * under the License. */ -import { SavedObjectRegistryProvider } from 'ui/saved_objects/saved_object_registry'; -import 'ui/directives/kbn_href'; -import { uiModules } from 'ui/modules'; -import { timefilter } from 'ui/timefilter'; -import chrome from 'ui/chrome'; -import { wrapInI18nContext } from 'ui/i18n'; -import { toastNotifications } from 'ui/notify'; import { addHelpMenuToAppChrome } from '../help_menu/help_menu_util'; -import { SavedObjectsClientProvider } from 'ui/saved_objects'; import { VisualizeListingTable } from './visualize_listing_table'; import { NewVisModal } from '../wizard/new_vis_modal'; import { VisualizeConstants } from '../visualize_constants'; import { start as visualizations } from '../../../../visualizations/public/np_ready/public/legacy'; import { i18n } from '@kbn/i18n'; +import { getServices } from '../kibana_services'; + +const { + uiModules, + SavedObjectRegistryProvider, + timefilter, + chrome, + toastNotifications, + wrapInI18nContext, + SavedObjectsClientProvider, + addBasePath +} = getServices(); + const app = uiModules.get('app/visualize', ['ngRoute', 'react']); app.directive('visualizeListingTable', reactDirective => reactDirective(wrapInI18nContext(VisualizeListingTable)) @@ -55,11 +60,11 @@ export function VisualizeListingController($injector, createNewVis) { this.editItem = ({ editUrl }) => { // for visualizations the edit and view URLs are the same - window.location = chrome.addBasePath(editUrl); + window.location = addBasePath(editUrl); }; this.getViewUrl = ({ editUrl }) => { - return chrome.addBasePath(editUrl); + return addBasePath(editUrl); }; this.closeNewVisModal = () => { @@ -112,7 +117,7 @@ export function VisualizeListingController($injector, createNewVis) { }); }; - chrome.breadcrumbs.set([ + chrome.setBreadcrumbs([ { text: i18n.translate('kbn.visualize.visualizeListingBreadcrumbsTitle', { defaultMessage: 'Visualize', diff --git a/src/legacy/core_plugins/kibana/public/visualize/listing/visualize_listing_table.js b/src/legacy/core_plugins/kibana/public/visualize/listing/visualize_listing_table.js index c909b6003516f..67fc70899410b 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/listing/visualize_listing_table.js +++ b/src/legacy/core_plugins/kibana/public/visualize/listing/visualize_listing_table.js @@ -21,7 +21,6 @@ import React, { Component, Fragment } from 'react'; import PropTypes from 'prop-types'; import { injectI18n, FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; -import { capabilities } from 'ui/capabilities'; import { TableListView } from './../../table_list_view'; import { @@ -32,6 +31,10 @@ import { EuiEmptyPrompt, } from '@elastic/eui'; +import { getServices } from '../kibana_services'; + +const { capabilities } = getServices(); + class VisualizeListingTableUi extends Component { constructor(props) { @@ -46,8 +49,8 @@ class VisualizeListingTableUi extends Component { // for data exploration purposes createItem={this.props.createItem} findItems={this.props.findItems} - deleteItems={capabilities.get().visualize.delete ? this.props.deleteItems : null} - editItem={capabilities.get().visualize.save ? this.props.editItem : null} + deleteItems={capabilities.visualize.delete ? this.props.deleteItems : null} + editItem={capabilities.visualize.save ? this.props.editItem : null} tableColumns={this.getTableColumns()} listingLimit={this.props.listingLimit} selectable={item => item.canDelete} diff --git a/src/legacy/core_plugins/kibana/public/visualize/types.d.ts b/src/legacy/core_plugins/kibana/public/visualize/types.d.ts index b321e5563eb60..c83f7f5a5da8b 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/types.d.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/types.d.ts @@ -17,7 +17,7 @@ * under the License. */ -import { VisSavedObject } from 'ui/visualize/loader/types'; +import { VisSavedObject } from './kibana_services'; export interface SavedVisualizations { urlFor: (id: string) => string; diff --git a/src/legacy/core_plugins/kibana/public/visualize/wizard/new_vis_modal.tsx b/src/legacy/core_plugins/kibana/public/visualize/wizard/new_vis_modal.tsx index 31ddafb4ec719..7231d7e947408 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/wizard/new_vis_modal.tsx +++ b/src/legacy/core_plugins/kibana/public/visualize/wizard/new_vis_modal.tsx @@ -22,14 +22,16 @@ import React from 'react'; import { EuiModal, EuiOverlayMask } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import chrome from 'ui/chrome'; -import { VisType } from 'ui/vis'; import { VisualizeConstants } from '../visualize_constants'; import { createUiStatsReporter, METRIC_TYPE } from '../../../../ui_metric/public'; import { SearchSelection } from './search_selection'; import { TypeSelection } from './type_selection'; import { TypesStart, VisTypeAlias } from '../../../../visualizations/public/np_ready/public/types'; +import { getServices, VisType } from '../kibana_services'; + +const { addBasePath, uiSettings } = getServices(); + interface TypeSelectionProps { isOpen: boolean; onClose: () => void; @@ -54,7 +56,7 @@ class NewVisModal extends React.Component{t.promotion!.description}

Date: Thu, 24 Oct 2019 12:29:54 +0200 Subject: [PATCH 019/132] move handlers from addSetupWork into legacy_compat --- .../console/np_ready/public/legacy.ts | 1 - src/legacy/core_plugins/data/public/index.ts | 11 ++ .../data/public/shim/legacy_module.ts | 185 ++++++++++-------- .../kibana/public/dashboard/app.js | 15 +- .../public/dashboard/dashboard_app.html | 6 +- .../kibana/public/dashboard/dashboard_app.tsx | 10 +- .../dashboard/dashboard_app_controller.tsx | 14 +- .../public/dashboard/dashboard_constants.ts | 4 +- .../kibana/public/dashboard/plugin.ts | 4 +- .../kibana/public/dashboard/render_app.ts | 78 +++++--- .../public/discover/angular/discover.js | 1 - .../kibana/public/management/index.js | 8 - .../management/route_setup/load_default.js | 110 ----------- .../kibana/public/visualize/index.js | 1 - .../ui/public/capabilities/route_setup.ts | 38 ---- .../public/legacy_compat/angular_config.tsx | 127 +++++++++++- src/legacy/ui/public/promises/promises.js | 10 +- .../ui/public/routes/route_manager.d.ts | 3 +- .../grokdebugger/grokdebugger_route.js | 1 - .../plugins/searchprofiler/public/app.js | 1 - 20 files changed, 318 insertions(+), 310 deletions(-) delete mode 100644 src/legacy/core_plugins/kibana/public/management/route_setup/load_default.js delete mode 100644 src/legacy/ui/public/capabilities/route_setup.ts diff --git a/src/legacy/core_plugins/console/np_ready/public/legacy.ts b/src/legacy/core_plugins/console/np_ready/public/legacy.ts index af9803c97749e..b5ac3866c3a51 100644 --- a/src/legacy/core_plugins/console/np_ready/public/legacy.ts +++ b/src/legacy/core_plugins/console/np_ready/public/legacy.ts @@ -31,7 +31,6 @@ import { DOC_LINK_VERSION } from 'ui/documentation_links'; import { I18nContext } from 'ui/i18n'; import { ResizeChecker } from 'ui/resize_checker'; import 'ui/autoload/styles'; -import 'ui/capabilities/route_setup'; /* eslint-enable @kbn/eslint/no-restricted-paths */ import template from '../../public/quarantined/index.html'; diff --git a/src/legacy/core_plugins/data/public/index.ts b/src/legacy/core_plugins/data/public/index.ts index cb3869ff57711..b7d3cfb79a463 100644 --- a/src/legacy/core_plugins/data/public/index.ts +++ b/src/legacy/core_plugins/data/public/index.ts @@ -69,4 +69,15 @@ export { mockIndexPattern, } from './index_patterns'; +/** + * These functions can be used to register the angular wrappers for react components + * in a separate module to use them without relying on the uiModules module tree. + * */ +export { + createFilterBarHelper, + createFilterBarDirective, + createApplyFiltersPopoverDirective, + createApplyFiltersPopoverHelper, +} from './shim/legacy_module'; + export { TimeHistoryContract, TimefilterContract, getTime, InputTimeRange } from './timefilter'; diff --git a/src/legacy/core_plugins/data/public/shim/legacy_module.ts b/src/legacy/core_plugins/data/public/shim/legacy_module.ts index 0b5ca72599208..37b3112aa4c06 100644 --- a/src/legacy/core_plugins/data/public/shim/legacy_module.ts +++ b/src/legacy/core_plugins/data/public/shim/legacy_module.ts @@ -30,97 +30,108 @@ import { FilterBar, ApplyFiltersPopover } from '../filter'; import { mapAndFlattenFilters } from '../filter/filter_manager/lib/map_and_flatten_filters'; import { IndexPatterns } from '../index_patterns/index_patterns'; +/** @internal */ +export const createFilterBarDirective = () => { + return { + restrict: 'E', + template: '', + compile: (elem: any) => { + const child = document.createElement('filter-bar-helper'); + + // Copy attributes to the child directive + for (const attr of elem[0].attributes) { + child.setAttribute(attr.name, attr.value); + } + + child.setAttribute('ui-settings', 'uiSettings'); + child.setAttribute('doc-links', 'docLinks'); + child.setAttribute('plugin-data-start', 'pluginDataStart'); + + // Append helper directive + elem.append(child); + + const linkFn = ($scope: any) => { + $scope.uiSettings = npStart.core.uiSettings; + $scope.docLinks = npStart.core.docLinks; + $scope.pluginDataStart = npStart.plugins.data; + }; + + return linkFn; + }, + }; +}; + +/** @internal */ +export const createFilterBarHelper = (reactDirective: any) => { + return reactDirective(wrapInI18nContext(FilterBar), [ + ['uiSettings', { watchDepth: 'reference' }], + ['docLinks', { watchDepth: 'reference' }], + ['onFiltersUpdated', { watchDepth: 'reference' }], + ['indexPatterns', { watchDepth: 'collection' }], + ['filters', { watchDepth: 'collection' }], + ['className', { watchDepth: 'reference' }], + ['pluginDataStart', { watchDepth: 'reference' }], + ]); +}; + +/** @internal */ +export const createApplyFiltersPopoverDirective = () => { + return { + restrict: 'E', + template: '', + compile: (elem: any) => { + const child = document.createElement('apply-filters-popover-helper'); + + // Copy attributes to the child directive + for (const attr of elem[0].attributes) { + child.setAttribute(attr.name, attr.value); + } + + // Add a key attribute that will force a full rerender every time that + // a filter changes. + child.setAttribute('key', 'key'); + + // Append helper directive + elem.append(child); + + const linkFn = ($scope: any, _: any, $attr: any) => { + // Watch only for filter changes to update key. + $scope.$watch( + () => { + return $scope.$eval($attr.filters) || []; + }, + (newVal: any) => { + $scope.key = Date.now(); + }, + true + ); + }; + + return linkFn; + }, + }; +}; + +/** @internal */ +export const createApplyFiltersPopoverHelper = (reactDirective: any) => + reactDirective(wrapInI18nContext(ApplyFiltersPopover), [ + ['filters', { watchDepth: 'collection' }], + ['onCancel', { watchDepth: 'reference' }], + ['onSubmit', { watchDepth: 'reference' }], + ['indexPatterns', { watchDepth: 'collection' }], + + // Key is needed to trigger a full rerender of the component + 'key', + ]); + /** @internal */ export const initLegacyModule = once((): void => { uiModules .get('app/kibana', ['react']) - .directive('filterBar', () => { - return { - restrict: 'E', - template: '', - compile: (elem: any) => { - const child = document.createElement('filter-bar-helper'); - - // Copy attributes to the child directive - for (const attr of elem[0].attributes) { - child.setAttribute(attr.name, attr.value); - } - - child.setAttribute('ui-settings', 'uiSettings'); - child.setAttribute('doc-links', 'docLinks'); - child.setAttribute('plugin-data-start', 'pluginDataStart'); - - // Append helper directive - elem.append(child); - - const linkFn = ($scope: any) => { - $scope.uiSettings = npStart.core.uiSettings; - $scope.docLinks = npStart.core.docLinks; - $scope.pluginDataStart = npStart.plugins.data; - }; - - return linkFn; - }, - }; - }) - .directive('filterBarHelper', (reactDirective: any) => { - return reactDirective(wrapInI18nContext(FilterBar), [ - ['uiSettings', { watchDepth: 'reference' }], - ['docLinks', { watchDepth: 'reference' }], - ['onFiltersUpdated', { watchDepth: 'reference' }], - ['indexPatterns', { watchDepth: 'collection' }], - ['filters', { watchDepth: 'collection' }], - ['className', { watchDepth: 'reference' }], - ['pluginDataStart', { watchDepth: 'reference' }], - ]); - }) - .directive('applyFiltersPopover', () => { - return { - restrict: 'E', - template: '', - compile: (elem: any) => { - const child = document.createElement('apply-filters-popover-helper'); - - // Copy attributes to the child directive - for (const attr of elem[0].attributes) { - child.setAttribute(attr.name, attr.value); - } - - // Add a key attribute that will force a full rerender every time that - // a filter changes. - child.setAttribute('key', 'key'); - - // Append helper directive - elem.append(child); - - const linkFn = ($scope: any, _: any, $attr: any) => { - // Watch only for filter changes to update key. - $scope.$watch( - () => { - return $scope.$eval($attr.filters) || []; - }, - (newVal: any) => { - $scope.key = Date.now(); - }, - true - ); - }; - - return linkFn; - }, - }; - }) - .directive('applyFiltersPopoverHelper', (reactDirective: any) => - reactDirective(wrapInI18nContext(ApplyFiltersPopover), [ - ['filters', { watchDepth: 'collection' }], - ['onCancel', { watchDepth: 'reference' }], - ['onSubmit', { watchDepth: 'reference' }], - ['indexPatterns', { watchDepth: 'collection' }], - - // Key is needed to trigger a full rerender of the component - 'key', - ]) - ); + .directive('filterBar', createFilterBarDirective) + .directive('filterBarHelper', createFilterBarHelper) + .directive('applyFiltersPopover', createApplyFiltersPopoverDirective) + .directive('applyFiltersPopoverHelper', createApplyFiltersPopoverHelper); const module = uiModules.get('kibana/index_patterns'); let _service: any; diff --git a/src/legacy/core_plugins/kibana/public/dashboard/app.js b/src/legacy/core_plugins/kibana/public/dashboard/app.js index f219da072c87b..c57aeec9acabb 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/app.js +++ b/src/legacy/core_plugins/kibana/public/dashboard/app.js @@ -31,19 +31,13 @@ import { SavedObjectNotFound, } from '../../../../../plugins/kibana_utils/public'; import { FeatureCatalogueCategory } from 'ui/registry/feature_catalogue'; -import { SavedObjectsClientProvider } from 'ui/saved_objects'; -import { SavedObjectRegistryProvider } from 'ui/saved_objects/saved_object_registry'; import { DashboardListing, EMPTY_FILTER } from './listing/dashboard_listing'; -import 'ui/capabilities/route_setup'; import { addHelpMenuToAppChrome } from './help_menu/help_menu_util'; -// load directives -import '../../../data/public'; - export function initDashboardApp(app, deps) { initDashboardAppDirective(app, deps); - app.directive('dashboardListing', function(reactDirective) { + app.directive('dashboardListing', function (reactDirective) { return reactDirective(wrapInI18nContext(DashboardListing)); }); @@ -56,6 +50,7 @@ export function initDashboardApp(app, deps) { app.config(function ($routeProvider) { const defaults = { + reloadOnSearch: false, requireDefaultIndex: true, requireUICapability: 'dashboard.show', badge: () => { @@ -74,7 +69,11 @@ export function initDashboardApp(app, deps) { }; }, }; + $routeProvider + // migrate old URLs + .when('/dashboards/dashboard', { redirectTo: (_params, _path, query) => `/dashboards/create?${query}` }) + .when('/dashboards/dashboard/:id', { redirectTo: (params, _path, query) => `/dashboards/edit/${params.id}?${query}` }) .when(DashboardConstants.LANDING_PAGE_PATH, { ...defaults, template: dashboardListingTemplate, @@ -111,7 +110,7 @@ export function initDashboardApp(app, deps) { addHelpMenuToAppChrome(deps.chrome); }, resolve: { - dash: function ($route/*, redirectWhenMissing, kbnUrl*/) { + dash: function ($route, redirectWhenMissing, kbnUrl) { const savedObjectsClient = deps.savedObjectsClient; const title = $route.current.params.title; if (title) { diff --git a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app.html b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app.html index 68c8131fa1a7b..d51b7e394f339 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app.html +++ b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app.html @@ -4,11 +4,11 @@ > void; timefilterSubscriptions$: Subscription; + isVisible: boolean; } export function initDashboardAppDirective(app: any, deps: RenderDeps) { @@ -104,12 +104,6 @@ export function initDashboardAppDirective(app: any, deps: RenderDeps) { }, getAppState: { previouslyStored: () => TAppState | undefined; - }, - dashboardConfig: { - getHideWriteControls: () => boolean; - }, - localStorage: { - get: (prop: string) => unknown; } ) => new DashboardAppController({ @@ -117,8 +111,6 @@ export function initDashboardAppDirective(app: any, deps: RenderDeps) { $scope, $routeParams, getAppState, - dashboardConfig, - localStorage, kbnUrl, AppStateClass: AppState, config, diff --git a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx index 1abf03a9d6d78..af6b8f086a921 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx +++ b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx @@ -23,7 +23,6 @@ import React from 'react'; import angular from 'angular'; import { uniq } from 'lodash'; -import chrome from 'ui/chrome'; import { subscribeWithScope } from 'ui/utils/subscribe_with_scope'; import { toastNotifications } from 'ui/notify'; @@ -39,7 +38,6 @@ import { migrateLegacyQuery } from 'ui/utils/migrate_legacy_query'; import { timefilter } from 'ui/timefilter'; - import { AppStateClass as TAppStateClass, AppState as TAppState, @@ -95,9 +93,6 @@ export interface DashboardAppControllerDependencies extends RenderDeps { getDefault: () => Promise; }; dashboardConfig: any; - localStorage: { - get: (prop: string) => unknown; - }; kbnUrl: KbnUrl; AppStateClass: TAppStateClass; config: any; @@ -128,7 +123,7 @@ export class DashboardAppController { savedQueryService, embeddables, dashboardCapabilities, - core: { notifications, overlays }, + core: { notifications, overlays, chrome }, }: DashboardAppControllerDependencies) { let lastReloadRequestTime = 0; @@ -332,7 +327,7 @@ export class DashboardAppController { // Push breadcrumbs to new header navigation const updateBreadcrumbs = () => { - chrome.breadcrumbs.set([ + chrome.setBreadcrumbs([ { text: i18n.translate('kbn.dashboard.dashboardAppBreadcrumbsTitle', { defaultMessage: 'Dashboard', @@ -814,8 +809,13 @@ export class DashboardAppController { }, }); + const visibleSubscription = chrome.getIsVisible$().subscribe(isVisible => { + $scope.isVisible = isVisible; + }); + $scope.$on('$destroy', () => { updateSubscription.unsubscribe(); + visibleSubscription.unsubscribe(); $scope.timefilterSubscriptions$.unsubscribe(); dashboardStateManager.destroy(); diff --git a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_constants.ts b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_constants.ts index b76b3f309874a..f3e4414477b86 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_constants.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_constants.ts @@ -21,9 +21,9 @@ export const DashboardConstants = { ADD_VISUALIZATION_TO_DASHBOARD_MODE_PARAM: 'addToDashboard', NEW_VISUALIZATION_ID_PARAM: 'addVisualization', LANDING_PAGE_PATH: '/dashboards', - CREATE_NEW_DASHBOARD_URL: '/dashboard', + CREATE_NEW_DASHBOARD_URL: '/dashboards/create', }; export function createDashboardEditUrl(id: string) { - return `/dashboard/${id}`; + return `/dashboards/view/${id}`; } diff --git a/src/legacy/core_plugins/kibana/public/dashboard/plugin.ts b/src/legacy/core_plugins/kibana/public/dashboard/plugin.ts index 29e5f3e5f15a0..cbdc0663bfd77 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/plugin.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/plugin.ts @@ -18,6 +18,7 @@ */ import { CoreSetup, CoreStart, Plugin, SavedObjectsClientContract } from 'kibana/public'; +import { Storage } from 'ui/storage'; import { RenderDeps } from './render_app'; import { LocalApplicationService } from '../local_application_service'; import { DataStart } from '../../../data/public'; @@ -58,7 +59,7 @@ export class DashboardPlugin implements Plugin { __LEGACY: { localApplicationService, getAngularDependencies, ...legacyServices }, }: DashboardPluginSetupDependencies ) { - localApplicationService.forwardApp('dashboard', 'dashboards'); + localApplicationService.forwardApp('dashboard', 'dashboards', { keepPrefix: true }); localApplicationService.register({ id: 'dashboards', title: 'Dashboards', @@ -76,6 +77,7 @@ export class DashboardPlugin implements Plugin { savedQueryService: this.savedQueryService!, embeddables: this.embeddables!, dashboardCapabilities: contextCore.application.capabilities.dashboard, + localStorage: new Storage(localStorage), }; const { renderApp } = await import('./render_app'); return renderApp(params.element, params.appBasePath, deps); diff --git a/src/legacy/core_plugins/kibana/public/dashboard/render_app.ts b/src/legacy/core_plugins/kibana/public/dashboard/render_app.ts index 25f86a6d25c6e..82e38d9944370 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/render_app.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/render_app.ts @@ -20,12 +20,15 @@ import { EuiConfirmModal } from '@elastic/eui'; import angular from 'angular'; import { IPrivate } from 'ui/private'; +import { Storage } from 'ui/storage'; import { i18nDirective, i18nFilter, I18nProvider } from '@kbn/i18n/src/angular'; // @ts-ignore import { GlobalStateProvider } from 'ui/state_management/global_state'; // @ts-ignore import { StateManagementConfigProvider } from 'ui/state_management/config_provider'; // @ts-ignore +import { AppStateProvider } from 'ui/state_management/app_state'; +// @ts-ignore import { PrivateProvider } from 'ui/private/private'; // @ts-ignore import { EventsProvider } from 'ui/events'; @@ -50,7 +53,13 @@ import { configureAppAngularModule } from 'ui/legacy_compat'; // @ts-ignore import { initDashboardApp } from './app'; -import { DataStart } from '../../../data/public'; +import { + createApplyFiltersPopoverDirective, + createApplyFiltersPopoverHelper, + createFilterBarDirective, + createFilterBarHelper, + DataStart, +} from '../../../data/public'; import { SavedQueryService } from '../../../data/public/search/search_bar/lib/saved_query_service'; import { EmbeddablePublicPlugin } from '../../../../../plugins/embeddable/public'; @@ -68,14 +77,17 @@ export interface RenderDeps { uiSettings: UiSettingsClientContract; chrome: ChromeStart; addBasePath: (path: string) => string; - getFeatureCatalogueRegistryProvider: () => any; + FeatureCatalogueRegistryProvider: any; savedQueryService: SavedQueryService; embeddables: ReturnType; + localStorage: Storage; } export const renderApp = (element: HTMLElement, appBasePath: string, deps: RenderDeps) => { const dashboardAngularModule = createLocalAngularModule(deps.core); + // global routing stuff configureAppAngularModule(dashboardAngularModule); + // custom routing stuff initDashboardApp(dashboardAngularModule, deps); const $injector = mountDashboardApp(appBasePath, element); return () => $injector.get('$rootScope').$destroy(); @@ -111,39 +123,48 @@ function createLocalAngularModule(core: AppMountContext['core']) { createLocalPromiseModule(); createLocalConfigModule(core); createLocalKbnUrlModule(); + createLocalStateModule(); createLocalPersistedStateModule(); createLocalTopNavModule(); - createLocalGlobalStateModule(); createLocalConfirmModalModule(); + createLocalFilterBarModule(); const dashboardAngularModule = angular.module(moduleName, [ ...thirdPartyAngularDependencies, - 'dashboardConfig', - 'dashboardI18n', - 'dashboardPrivate', - 'dashboardPersistedState', - 'dashboardTopNav', - 'dashboardGlobalState', - 'dashboardConfirmModal', + 'app/dashboard/Config', + 'app/dashboard/I18n', + 'app/dashboard/Private', + 'app/dashboard/PersistedState', + 'app/dashboard/TopNav', + 'app/dashboard/State', + 'app/dashboard/ConfirmModal', + 'app/dashboard/FilterBar', ]); return dashboardAngularModule; } function createLocalConfirmModalModule() { angular - .module('dashboardConfirmModal', ['react']) + .module('app/dashboard/ConfirmModal', ['react']) .factory('confirmModal', confirmModalFactory) .directive('confirmModal', reactDirective => reactDirective(EuiConfirmModal)); } -function createLocalGlobalStateModule() { +function createLocalStateModule() { angular - .module('dashboardGlobalState', [ - 'dashboardPrivate', - 'dashboardConfig', - 'dashboardKbnUrl', - 'dashboardPromise', + .module('app/dashboard/State', [ + 'app/dashboard/Private', + 'app/dashboard/Config', + 'app/dashboard/KbnUrl', + 'app/dashboard/Promise', + 'app/dashboard/PersistedState', ]) + .factory('AppState', function(Private: any) { + return Private(AppStateProvider); + }) + .service('getAppState', function(Private: any) { + return Private(AppStateProvider).getAppState; + }) .service('globalState', function(Private: any) { return Private(GlobalStateProvider); }); @@ -151,7 +172,7 @@ function createLocalGlobalStateModule() { function createLocalPersistedStateModule() { angular - .module('dashboardPersistedState', ['dashboardPrivate', 'dashboardPromise']) + .module('app/dashboard/PersistedState', ['app/dashboard/Private', 'app/dashboard/Promise']) .factory('PersistedState', (Private: IPrivate) => { const Events = Private(EventsProvider); return class AngularPersistedState extends PersistedState { @@ -164,14 +185,14 @@ function createLocalPersistedStateModule() { function createLocalKbnUrlModule() { angular - .module('dashboardKbnUrl', ['dashboardPrivate', 'ngRoute']) + .module('app/dashboard/KbnUrl', ['app/dashboard/Private', 'ngRoute']) .service('kbnUrl', (Private: IPrivate) => Private(KbnUrlProvider)) .service('redirectWhenMissing', (Private: IPrivate) => Private(RedirectWhenMissingProvider)); } function createLocalConfigModule(core: AppMountContext['core']) { angular - .module('dashboardConfig', ['dashboardPrivate']) + .module('app/dashboard/Config', ['app/dashboard/Private']) .provider('stateManagementConfig', StateManagementConfigProvider) .provider('config', () => { return { @@ -183,23 +204,32 @@ function createLocalConfigModule(core: AppMountContext['core']) { } function createLocalPromiseModule() { - angular.module('dashboardPromise', []).service('Promise', PromiseServiceCreator); + angular.module('app/dashboard/Promise', []).service('Promise', PromiseServiceCreator); } function createLocalPrivateModule() { - angular.module('dashboardPrivate', []).provider('Private', PrivateProvider); + angular.module('app/dashboard/Private', []).provider('Private', PrivateProvider); } function createLocalTopNavModule() { angular - .module('dashboardTopNav', ['react']) + .module('app/dashboard/TopNav', ['react']) .directive('kbnTopNav', createTopNavDirective) .directive('kbnTopNavHelper', createTopNavHelper); } +function createLocalFilterBarModule() { + angular + .module('app/dashboard/FilterBar', ['react']) + .directive('filterBar', createFilterBarDirective) + .directive('filterBarHelper', createFilterBarHelper) + .directive('applyFiltersPopover', createApplyFiltersPopoverDirective) + .directive('applyFiltersPopoverHelper', createApplyFiltersPopoverHelper); +} + function createLocalI18nModule() { angular - .module('dashboardI18n', []) + .module('app/dashboard/I18n', []) .provider('i18n', I18nProvider) .filter('i18n', i18nFilter) .directive('i18nId', i18nDirective); diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/discover.js b/src/legacy/core_plugins/kibana/public/discover/angular/discover.js index 840152fc40ced..1a6c6aad363ba 100644 --- a/src/legacy/core_plugins/kibana/public/discover/angular/discover.js +++ b/src/legacy/core_plugins/kibana/public/discover/angular/discover.js @@ -69,7 +69,6 @@ import { showSaveModal } from 'ui/saved_objects/show_saved_object_save_modal'; import { SavedObjectSaveModal } from 'ui/saved_objects/components/saved_object_save_modal'; import { getRootBreadcrumbs, getSavedSearchBreadcrumbs } from '../breadcrumbs'; import { buildVislibDimensions } from 'ui/visualize/loader/pipeline_helpers/build_pipeline'; -import 'ui/capabilities/route_setup'; import { addHelpMenuToAppChrome } from '../components/help_menu/help_menu_util'; import { extractTimeFilter, changeTimeFilter } from '../../../../data/public'; diff --git a/src/legacy/core_plugins/kibana/public/management/index.js b/src/legacy/core_plugins/kibana/public/management/index.js index c0949318e9253..83fc8e4db9b55 100644 --- a/src/legacy/core_plugins/kibana/public/management/index.js +++ b/src/legacy/core_plugins/kibana/public/management/index.js @@ -28,7 +28,6 @@ import { I18nContext } from 'ui/i18n'; import { uiModules } from 'ui/modules'; import appTemplate from './app.html'; import landingTemplate from './landing.html'; -import { capabilities } from 'ui/capabilities'; import { management, SidebarNav, MANAGEMENT_BREADCRUMB } from 'ui/management'; import { FeatureCatalogueRegistryProvider, FeatureCatalogueCategory } from 'ui/registry/feature_catalogue'; import { timefilter } from 'ui/timefilter'; @@ -50,13 +49,6 @@ uiRoutes redirectTo: '/management' }); -require('./route_setup/load_default')({ - whenMissingRedirectTo: () => { - const canManageIndexPatterns = capabilities.get().management.kibana.index_patterns; - return canManageIndexPatterns ? '/management/kibana/index_pattern' : '/home'; - } -}); - export function updateLandingPage(version) { const node = document.getElementById(LANDING_ID); if (!node) { diff --git a/src/legacy/core_plugins/kibana/public/management/route_setup/load_default.js b/src/legacy/core_plugins/kibana/public/management/route_setup/load_default.js deleted file mode 100644 index f797acbe8888e..0000000000000 --- a/src/legacy/core_plugins/kibana/public/management/route_setup/load_default.js +++ /dev/null @@ -1,110 +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 _ from 'lodash'; -import React from 'react'; -import { banners } from 'ui/notify'; -import { NoDefaultIndexPattern } from 'ui/index_patterns'; -import uiRoutes from 'ui/routes'; -import { - EuiCallOut, -} from '@elastic/eui'; -import { clearTimeout } from 'timers'; -import { i18n } from '@kbn/i18n'; - -let bannerId; -let timeoutId; - -function displayBanner() { - clearTimeout(timeoutId); - - // Avoid being hostile to new users who don't have an index pattern setup yet - // give them a friendly info message instead of a terse error message - bannerId = banners.set({ - id: bannerId, // initially undefined, but reused after first set - component: ( - - ) - }); - - // hide the message after the user has had a chance to acknowledge it -- so it doesn't permanently stick around - timeoutId = setTimeout(() => { - banners.remove(bannerId); - timeoutId = undefined; - }, 15000); -} - -// eslint-disable-next-line import/no-default-export -export default function (opts) { - opts = opts || {}; - const whenMissingRedirectTo = opts.whenMissingRedirectTo || null; - - uiRoutes - .addSetupWork(function loadDefaultIndexPattern(Promise, $route, config, indexPatterns) { - const route = _.get($route, 'current.$$route'); - - if (!route.requireDefaultIndex) { - return; - } - - return indexPatterns.getIds() - .then(function (patterns) { - let defaultId = config.get('defaultIndex'); - let defined = !!defaultId; - const exists = _.contains(patterns, defaultId); - - if (defined && !exists) { - config.remove('defaultIndex'); - defaultId = defined = false; - } - - if (!defined) { - // If there is any index pattern created, set the first as default - if (patterns.length >= 1) { - defaultId = patterns[0]; - config.set('defaultIndex', defaultId); - } else { - throw new NoDefaultIndexPattern(); - } - } - }); - }) - .afterWork( - // success - null, - - // failure - function (err, kbnUrl) { - const hasDefault = !(err instanceof NoDefaultIndexPattern); - if (hasDefault || !whenMissingRedirectTo) throw err; // rethrow - - kbnUrl.change(whenMissingRedirectTo()); - - displayBanner(); - } - ); -} diff --git a/src/legacy/core_plugins/kibana/public/visualize/index.js b/src/legacy/core_plugins/kibana/public/visualize/index.js index b3c16fb94d7fb..7afb98709fae0 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/index.js +++ b/src/legacy/core_plugins/kibana/public/visualize/index.js @@ -22,7 +22,6 @@ import { i18n } from '@kbn/i18n'; import './saved_visualizations/_saved_vis'; import './saved_visualizations/saved_visualizations'; import uiRoutes from 'ui/routes'; -import 'ui/capabilities/route_setup'; import visualizeListingTemplate from './listing/visualize_listing.html'; import { VisualizeListingController } from './listing/visualize_listing'; import { VisualizeConstants } from './visualize_constants'; diff --git a/src/legacy/ui/public/capabilities/route_setup.ts b/src/legacy/ui/public/capabilities/route_setup.ts deleted file mode 100644 index c7817b8cc5748..0000000000000 --- a/src/legacy/ui/public/capabilities/route_setup.ts +++ /dev/null @@ -1,38 +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 { get } from 'lodash'; -import chrome from 'ui/chrome'; -import uiRoutes from 'ui/routes'; -import { UICapabilities } from '.'; - -uiRoutes.addSetupWork( - (uiCapabilities: UICapabilities, kbnBaseUrl: string, $route: any, kbnUrl: any) => { - const route = get($route, 'current.$$route') as any; - if (!route.requireUICapability) { - return; - } - - if (!get(uiCapabilities, route.requireUICapability)) { - const url = chrome.addBasePath(`${kbnBaseUrl}#/home`); - kbnUrl.redirect(url); - throw uiRoutes.WAIT_FOR_URL_CHANGE_TOKEN; - } - } -); diff --git a/src/legacy/ui/public/legacy_compat/angular_config.tsx b/src/legacy/ui/public/legacy_compat/angular_config.tsx index 86ca9f911a578..d0cf8dd6e0da5 100644 --- a/src/legacy/ui/public/legacy_compat/angular_config.tsx +++ b/src/legacy/ui/public/legacy_compat/angular_config.tsx @@ -26,17 +26,20 @@ import { IModule, IRootScopeService, } from 'angular'; +import { EuiCallOut } from '@elastic/eui'; +import ReactDOM from 'react-dom'; import $ from 'jquery'; -import { cloneDeep, forOwn, set } from 'lodash'; +import _, { cloneDeep, forOwn, get, set } from 'lodash'; import React, { Fragment } from 'react'; import * as Rx from 'rxjs'; import { i18n } from '@kbn/i18n'; -import { FormattedMessage } from '@kbn/i18n/react'; +import { FormattedMessage, I18nProvider } from '@kbn/i18n/react'; import { CoreStart, LegacyCoreStart } from 'kibana/public'; import { fatalError } from 'ui/notify'; import { capabilities } from 'ui/capabilities'; +import { RouteConfiguration } from 'ui/routes/route_manager'; // @ts-ignore import { modifyUrl } from 'ui/url'; // @ts-ignore @@ -45,6 +48,8 @@ import { npStart } from '../new_platform'; import { toastNotifications } from '../notify'; // @ts-ignore import { isSystemApiRequest } from '../system_api'; +import { DataStart } from '../../../core_plugins/data/public'; +import { start as dataStart } from '../../../core_plugins/data/public/legacy'; const URL_LIMIT_WARN_WITHIN = 1000; @@ -73,7 +78,9 @@ export const configureAppAngularModule = (angularModule: IModule) => { .run($setupBreadcrumbsAutoClear(newPlatform)) .run($setupBadgeAutoClear(newPlatform)) .run($setupHelpExtensionAutoClear(newPlatform)) - .run($setupUrlOverflowHandling(newPlatform)); + .run($setupUrlOverflowHandling(newPlatform)) + .run($setupUICapabilityRedirect(newPlatform)) + .run($setupDefaultIndexRedirect(newPlatform, dataStart)); }; const getEsUrl = (newPlatform: CoreStart) => { @@ -166,6 +173,120 @@ function isDummyWrapperRoute($route: any) { ); } +/** + * integrates with angular to automatically redirect to home if required + * capability is not met + */ +const $setupUICapabilityRedirect = (newPlatform: CoreStart) => ( + $rootScope: IRootScopeService, + $injector: any +) => { + const isKibanaAppRoute = window.location.pathname.endsWith('/app/kibana'); + // this feature only works within kibana app for now after everything is + // switched to the application service, this can be changed to handle all + // apps. + if (!isKibanaAppRoute) { + return; + } + $rootScope.$on( + '$routeChangeStart', + (event, { $$route: route }: { $$route?: RouteConfiguration } = {}) => { + if (!route || !route.requireUICapability) { + return; + } + + if (!get(newPlatform.application.capabilities, route.requireUICapability)) { + $injector.get('kbnUrl').change('/home'); + event.preventDefault(); + } + } + ); +}; + +let bannerId: string; +let timeoutId: NodeJS.Timeout | undefined; + +/** + * integrates with angular to automatically redirect to management if no default + * index pattern is configured when a route flag is set. + */ +const $setupDefaultIndexRedirect = (newPlatform: CoreStart, data: DataStart) => ( + $rootScope: IRootScopeService, + $injector: any +) => { + const isKibanaAppRoute = window.location.pathname.endsWith('/app/kibana'); + // this feature only works within kibana app for now after everything is + // switched to the application service, this can be changed to handle all + // apps. + if (!isKibanaAppRoute) { + return; + } + + $rootScope.$on( + '$routeChangeStart', + (event, { $$route: route }: { $$route?: RouteConfiguration } = {}) => { + if (!route || !route.requireDefaultIndex) { + return; + } + + return data.indexPatterns.indexPatterns.getIds().then(function(patterns: string[]) { + let defaultId = newPlatform.uiSettings.get('defaultIndex'); + let defined = !!defaultId; + const exists = _.contains(patterns, defaultId); + + if (defined && !exists) { + newPlatform.uiSettings.remove('defaultIndex'); + defaultId = defined = false; + } + + if (!defined) { + // If there is any index pattern created, set the first as default + if (patterns.length >= 1) { + defaultId = patterns[0]; + newPlatform.uiSettings.set('defaultIndex', defaultId); + } else { + const canManageIndexPatterns = capabilities.get().management.kibana.index_patterns; + const redirectTarget = canManageIndexPatterns + ? '/management/kibana/index_pattern' + : '/home'; + + $injector.get('kbnUrl').change(redirectTarget); + $rootScope.$digest(); + if (timeoutId) { + clearTimeout(timeoutId); + } + + // Avoid being hostile to new users who don't have an index pattern setup yet + // give them a friendly info message instead of a terse error message + bannerId = newPlatform.overlays.banners.replace(bannerId, (element: HTMLElement) => { + ReactDOM.render( + + + , + element + ); + return () => ReactDOM.unmountComponentAtNode(element); + }); + + // hide the message after the user has had a chance to acknowledge it -- so it doesn't permanently stick around + timeoutId = setTimeout(() => { + newPlatform.overlays.banners.remove(bannerId); + timeoutId = undefined; + }, 15000); + } + } + }); + } + ); +}; + /** * internal angular run function that will be called when angular bootstraps and * lets us integrate with the angular router so that we can automatically clear diff --git a/src/legacy/ui/public/promises/promises.js b/src/legacy/ui/public/promises/promises.js index 99c9a11be7431..af8a5081e0c55 100644 --- a/src/legacy/ui/public/promises/promises.js +++ b/src/legacy/ui/public/promises/promises.js @@ -22,9 +22,7 @@ import { uiModules } from '../modules'; const module = uiModules.get('kibana'); -// Provides a tiny subset of the excellent API from -// bluebird, reimplemented using the $q service -module.service('Promise', function ($q, $timeout) { +export function PromiseServiceCreator($q, $timeout) { function Promise(fn) { if (typeof this === 'undefined') throw new Error('Promise constructor must be called with "new"'); @@ -122,4 +120,8 @@ module.service('Promise', function ($q, $timeout) { }; return Promise; -}); +} + +// Provides a tiny subset of the excellent API from +// bluebird, reimplemented using the $q service +module.service('Promise', PromiseServiceCreator); diff --git a/src/legacy/ui/public/routes/route_manager.d.ts b/src/legacy/ui/public/routes/route_manager.d.ts index 6187dfa71f856..c47a31eb7be76 100644 --- a/src/legacy/ui/public/routes/route_manager.d.ts +++ b/src/legacy/ui/public/routes/route_manager.d.ts @@ -23,7 +23,7 @@ import { ChromeBreadcrumb } from '../../../../core/public'; -interface RouteConfiguration { +export interface RouteConfiguration { controller?: string | ((...args: any[]) => void); redirectTo?: string | ((...args: any[]) => string); reloadOnSearch?: boolean; @@ -32,6 +32,7 @@ interface RouteConfiguration { template?: string; k7Breadcrumbs?: (...args: any[]) => ChromeBreadcrumb[]; requireUICapability?: string; + requireDefaultIndex?: boolean; outerAngularWrapperRoute?: boolean; } diff --git a/x-pack/legacy/plugins/grokdebugger/public/sections/grokdebugger/grokdebugger_route.js b/x-pack/legacy/plugins/grokdebugger/public/sections/grokdebugger/grokdebugger_route.js index 9f588bbe510ae..d63d669b0375e 100644 --- a/x-pack/legacy/plugins/grokdebugger/public/sections/grokdebugger/grokdebugger_route.js +++ b/x-pack/legacy/plugins/grokdebugger/public/sections/grokdebugger/grokdebugger_route.js @@ -5,7 +5,6 @@ */ import routes from 'ui/routes'; -import 'ui/capabilities/route_setup'; import { toastNotifications } from 'ui/notify'; import { xpackInfo } from 'plugins/xpack_main/services/xpack_info'; import template from './grokdebugger_route.html'; diff --git a/x-pack/legacy/plugins/searchprofiler/public/app.js b/x-pack/legacy/plugins/searchprofiler/public/app.js index 1c7598ab982bc..b385832b1c554 100644 --- a/x-pack/legacy/plugins/searchprofiler/public/app.js +++ b/x-pack/legacy/plugins/searchprofiler/public/app.js @@ -9,7 +9,6 @@ import { uiModules } from 'ui/modules'; import { i18n } from '@kbn/i18n'; import uiRoutes from 'ui/routes'; -import 'ui/capabilities/route_setup'; import { toastNotifications } from 'ui/notify'; import { formatAngularHttpError } from 'ui/notify/lib'; From 4f0c5b24e00d05f10b73393127ec84faebb689d5 Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Thu, 24 Oct 2019 15:08:29 +0200 Subject: [PATCH 020/132] pass dependencies into angular configurator --- .../kibana/public/dashboard/render_app.ts | 4 +++- src/legacy/ui/public/chrome/api/angular.js | 4 +++- .../ui/public/legacy_compat/angular_config.tsx | 18 +++++++++--------- 3 files changed, 15 insertions(+), 11 deletions(-) diff --git a/src/legacy/core_plugins/kibana/public/dashboard/render_app.ts b/src/legacy/core_plugins/kibana/public/dashboard/render_app.ts index 82e38d9944370..030b6dd387ff1 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/render_app.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/render_app.ts @@ -46,6 +46,7 @@ import { confirmModalFactory } from 'ui/modals/confirm_modal'; import { AppMountContext, ChromeStart, + LegacyCoreStart, SavedObjectsClientContract, UiSettingsClientContract, } from 'kibana/public'; @@ -66,6 +67,7 @@ import { EmbeddablePublicPlugin } from '../../../../../plugins/embeddable/public export interface RenderDeps { core: AppMountContext['core']; indexPatterns: DataStart['indexPatterns']['indexPatterns']; + dataStart: DataStart; queryFilter: any; getUnhashableStates: any; shareContextMenuExtensions: any; @@ -86,7 +88,7 @@ export interface RenderDeps { export const renderApp = (element: HTMLElement, appBasePath: string, deps: RenderDeps) => { const dashboardAngularModule = createLocalAngularModule(deps.core); // global routing stuff - configureAppAngularModule(dashboardAngularModule); + configureAppAngularModule(dashboardAngularModule, deps.core as LegacyCoreStart, deps.dataStart); // custom routing stuff initDashboardApp(dashboardAngularModule, deps); const $injector = mountDashboardApp(appBasePath, element); diff --git a/src/legacy/ui/public/chrome/api/angular.js b/src/legacy/ui/public/chrome/api/angular.js index e6457fec93633..512229cca6126 100644 --- a/src/legacy/ui/public/chrome/api/angular.js +++ b/src/legacy/ui/public/chrome/api/angular.js @@ -21,13 +21,15 @@ import { uiModules } from '../../modules'; import { directivesProvider } from '../directives'; import { registerSubUrlHooks } from './sub_url_hooks'; +import { start as data } from '../../../../core_plugins/data/public/legacy'; import { configureAppAngularModule } from 'ui/legacy_compat'; +import { npStart } from '../../new_platform/new_platform'; export function initAngularApi(chrome, internals) { chrome.setupAngular = function () { const kibana = uiModules.get('kibana'); - configureAppAngularModule(kibana); + configureAppAngularModule(kibana, npStart.core, data); kibana.value('chrome', chrome); diff --git a/src/legacy/ui/public/legacy_compat/angular_config.tsx b/src/legacy/ui/public/legacy_compat/angular_config.tsx index d0cf8dd6e0da5..f62ce41291e1a 100644 --- a/src/legacy/ui/public/legacy_compat/angular_config.tsx +++ b/src/legacy/ui/public/legacy_compat/angular_config.tsx @@ -38,23 +38,22 @@ import { FormattedMessage, I18nProvider } from '@kbn/i18n/react'; import { CoreStart, LegacyCoreStart } from 'kibana/public'; import { fatalError } from 'ui/notify'; -import { capabilities } from 'ui/capabilities'; import { RouteConfiguration } from 'ui/routes/route_manager'; // @ts-ignore import { modifyUrl } from 'ui/url'; // @ts-ignore import { UrlOverflowService } from '../error_url_overflow'; -import { npStart } from '../new_platform'; -import { toastNotifications } from '../notify'; // @ts-ignore import { isSystemApiRequest } from '../system_api'; import { DataStart } from '../../../core_plugins/data/public'; -import { start as dataStart } from '../../../core_plugins/data/public/legacy'; const URL_LIMIT_WARN_WITHIN = 1000; -export const configureAppAngularModule = (angularModule: IModule) => { - const newPlatform = npStart.core; +export const configureAppAngularModule = ( + angularModule: IModule, + newPlatform: LegacyCoreStart, + dataStart: DataStart +) => { const legacyMetadata = newPlatform.injectedMetadata.getLegacyMetadata(); forOwn(newPlatform.injectedMetadata.getInjectedVars(), (val, name) => { @@ -70,7 +69,7 @@ export const configureAppAngularModule = (angularModule: IModule) => { .value('buildSha', legacyMetadata.buildSha) .value('serverName', legacyMetadata.serverName) .value('esUrl', getEsUrl(newPlatform)) - .value('uiCapabilities', capabilities.get()) + .value('uiCapabilities', newPlatform.application.capabilities) .config(setupCompileProvider(newPlatform)) .config(setupLocationProvider(newPlatform)) .config($setupXsrfRequestInterceptor(newPlatform)) @@ -245,7 +244,8 @@ const $setupDefaultIndexRedirect = (newPlatform: CoreStart, data: DataStart) => defaultId = patterns[0]; newPlatform.uiSettings.set('defaultIndex', defaultId); } else { - const canManageIndexPatterns = capabilities.get().management.kibana.index_patterns; + const canManageIndexPatterns = + newPlatform.application.capabilities.management.kibana.index_patterns; const redirectTarget = canManageIndexPatterns ? '/management/kibana/index_pattern' : '/home'; @@ -444,7 +444,7 @@ const $setupUrlOverflowHandling = (newPlatform: CoreStart) => ( try { if (urlOverflow.check($location.absUrl()) <= URL_LIMIT_WARN_WITHIN) { - toastNotifications.addWarning({ + newPlatform.notifications.toasts.addWarning({ title: i18n.translate('common.ui.chrome.bigUrlWarningNotificationTitle', { defaultMessage: 'The URL is big and Kibana might stop working', }), From f1ff38875d133eec06d1e672baa1805ffcc8f80a Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Thu, 24 Oct 2019 16:24:19 +0200 Subject: [PATCH 021/132] centralize dependencies and clean up shim --- .../kibana/public/dashboard/app.js | 17 +++---- .../dashboard/dashboard_app_controller.tsx | 17 +++---- .../dashboard/dashboard_state_manager.ts | 9 +++- .../public/dashboard/help_menu/help_menu.js | 34 +++++++------ .../dashboard/help_menu/help_menu_util.js | 4 +- .../kibana/public/dashboard/index.ts | 2 + .../lib/embeddable_saved_object_converters.ts | 6 +-- .../public/dashboard/lib/migrate_app_state.ts | 8 +-- .../kibana/public/dashboard/plugin.ts | 49 ++++++++++++------- .../kibana/public/dashboard/render_app.ts | 3 +- 10 files changed, 86 insertions(+), 63 deletions(-) diff --git a/src/legacy/core_plugins/kibana/public/dashboard/app.js b/src/legacy/core_plugins/kibana/public/dashboard/app.js index c57aeec9acabb..65a933b5335d5 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/app.js +++ b/src/legacy/core_plugins/kibana/public/dashboard/app.js @@ -18,7 +18,6 @@ */ import { i18n } from '@kbn/i18n'; -import uiRoutes from 'ui/routes'; import { wrapInI18nContext } from 'ui/i18n'; import dashboardTemplate from './dashboard_app.html'; @@ -45,7 +44,7 @@ export function initDashboardApp(app, deps) { $scope.visitVisualizeAppLinkText = i18n.translate('kbn.dashboard.visitVisualizeAppLinkText', { defaultMessage: 'visit the Visualize app', }); - addHelpMenuToAppChrome(deps.chrome); + addHelpMenuToAppChrome(deps.chrome, deps.core.docLinks); } app.config(function ($routeProvider) { @@ -107,10 +106,10 @@ export function initDashboardApp(app, deps) { }), }, ]); - addHelpMenuToAppChrome(deps.chrome); + addHelpMenuToAppChrome(deps.chrome, deps.core.docLinks); }, resolve: { - dash: function ($route, redirectWhenMissing, kbnUrl) { + dash: function ($rootScope, $route, redirectWhenMissing, kbnUrl, Promise) { const savedObjectsClient = deps.savedObjectsClient; const title = $route.current.params.title; if (title) { @@ -130,13 +129,9 @@ export function initDashboardApp(app, deps) { } else { kbnUrl.redirect(`${DashboardConstants.LANDING_PAGE_PATH}?filter="${title}"`); } - throw uiRoutes.WAIT_FOR_URL_CHANGE_TOKEN; - }) - .catch( - redirectWhenMissing({ - dashboard: DashboardConstants.LANDING_PAGE_PATH, - }) - ); + $rootScope.$digest(); + return Promise.halt(); + }); } }, }, diff --git a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx index af6b8f086a921..8a5cee4ff61c7 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx +++ b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx @@ -24,20 +24,15 @@ import angular from 'angular'; import { uniq } from 'lodash'; import { subscribeWithScope } from 'ui/utils/subscribe_with_scope'; -import { toastNotifications } from 'ui/notify'; // @ts-ignore import { ConfirmationButtonTypes } from 'ui/modals/confirm_modal'; -import { docTitle } from 'ui/doc_title/doc_title'; - import { showSaveModal, SaveResult } from 'ui/saved_objects/show_saved_object_save_modal'; import { showShareContextMenu } from 'ui/share'; import { migrateLegacyQuery } from 'ui/utils/migrate_legacy_query'; -import { timefilter } from 'ui/timefilter'; - import { AppStateClass as TAppStateClass, AppState as TAppState, @@ -65,7 +60,6 @@ import { ViewMode, openAddPanelFlyout, } from '../../../embeddable_api/public/np_ready/public'; -// import { start } from '../../../embeddable_api/public/np_ready/public/legacy'; import { DashboardAppState, NavAction, ConfirmModalFn, SavedDashboardPanel } from './types'; import { showOptionsPopover } from './top_nav/show_options_popover'; @@ -123,7 +117,11 @@ export class DashboardAppController { savedQueryService, embeddables, dashboardCapabilities, - core: { notifications, overlays, chrome }, + docTitle, + dataStart: { + timefilter: { timefilter }, + }, + core: { notifications, overlays, chrome, injectedMetadata, docLinks }, }: DashboardAppControllerDependencies) { let lastReloadRequestTime = 0; @@ -136,6 +134,7 @@ export class DashboardAppController { savedDashboard: dash, AppStateClass, hideWriteControls: dashboardConfig.getHideWriteControls(), + kibanaVersion: injectedMetadata.getKibanaVersion(), }); $scope.appState = dashboardStateManager.getAppState(); @@ -619,7 +618,7 @@ export class DashboardAppController { return saveDashboard(angular.toJson, timefilter, dashboardStateManager, saveOptions) .then(function(id) { if (id) { - toastNotifications.addSuccess({ + notifications.toasts.addSuccess({ title: i18n.translate('kbn.dashboard.dashboardWasSavedSuccessMessage', { defaultMessage: `Dashboard '{dashTitle}' was saved`, values: { dashTitle: dash.title }, @@ -637,7 +636,7 @@ export class DashboardAppController { return { id }; }) .catch(error => { - toastNotifications.addDanger({ + notifications.toasts.addDanger({ title: i18n.translate('kbn.dashboard.dashboardWasNotSavedDangerMessage', { defaultMessage: `Dashboard '{dashTitle}' was not saved. Error: {errorMessage}`, values: { diff --git a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_state_manager.ts b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_state_manager.ts index 7c1fc771de349..ecaf1cafe5f14 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_state_manager.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_state_manager.ts @@ -55,6 +55,7 @@ export class DashboardStateManager { }; private stateDefaults: DashboardAppStateDefaults; private hideWriteControls: boolean; + private kibanaVersion: string; public isDirty: boolean; private changeListeners: Array<(status: { dirty: boolean }) => void>; private stateMonitor: StateMonitor; @@ -69,11 +70,14 @@ export class DashboardStateManager { savedDashboard, AppStateClass, hideWriteControls, + kibanaVersion, }: { savedDashboard: SavedObjectDashboard; AppStateClass: TAppStateClass; hideWriteControls: boolean; + kibanaVersion: string; }) { + this.kibanaVersion = kibanaVersion; this.savedDashboard = savedDashboard; this.hideWriteControls = hideWriteControls; @@ -85,7 +89,7 @@ export class DashboardStateManager { // appState based on the URL (the url trumps the defaults). This means if we update the state format at all and // want to handle BWC, we must not only migrate the data stored with saved Dashboard, but also any old state in the // url. - migrateAppState(this.appState); + migrateAppState(this.appState, kibanaVersion); this.isDirty = false; @@ -147,7 +151,8 @@ export class DashboardStateManager { } convertedPanelStateMap[panelState.explicitInput.id] = convertPanelStateToSavedDashboardPanel( - panelState + panelState, + this.kibanaVersion ); if ( diff --git a/src/legacy/core_plugins/kibana/public/dashboard/help_menu/help_menu.js b/src/legacy/core_plugins/kibana/public/dashboard/help_menu/help_menu.js index 56b2bd253381c..1b1a7f84c8131 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/help_menu/help_menu.js +++ b/src/legacy/core_plugins/kibana/public/dashboard/help_menu/help_menu.js @@ -17,26 +17,30 @@ * under the License. */ -import React, { Fragment, PureComponent } from 'react'; +import React, { PureComponent } from 'react'; import { EuiButton, EuiHorizontalRule, EuiSpacer } from '@elastic/eui'; -import { FormattedMessage } from '@kbn/i18n/react'; -import { ELASTIC_WEBSITE_URL, DOC_LINK_VERSION } from 'ui/documentation_links'; +import { FormattedMessage, I18nProvider } from '@kbn/i18n/react'; export class HelpMenu extends PureComponent { render() { return ( - - - - - - - + + <> + + + + + + + ); } } diff --git a/src/legacy/core_plugins/kibana/public/dashboard/help_menu/help_menu_util.js b/src/legacy/core_plugins/kibana/public/dashboard/help_menu/help_menu_util.js index 58a92193de63e..2dc8ce523a7da 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/help_menu/help_menu_util.js +++ b/src/legacy/core_plugins/kibana/public/dashboard/help_menu/help_menu_util.js @@ -21,9 +21,9 @@ import React from 'react'; import { render, unmountComponentAtNode } from 'react-dom'; import { HelpMenu } from './help_menu'; -export function addHelpMenuToAppChrome(chrome) { +export function addHelpMenuToAppChrome(chrome, docLinks) { chrome.setHelpExtension(domElement => { - render(, domElement); + render(, domElement); return () => { unmountComponentAtNode(domElement); }; diff --git a/src/legacy/core_plugins/kibana/public/dashboard/index.ts b/src/legacy/core_plugins/kibana/public/dashboard/index.ts index 4b63970daa461..7c9954359464a 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/index.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/index.ts @@ -20,6 +20,7 @@ import { FeatureCatalogueRegistryProvider } from 'ui/registry/feature_catalogue'; import { npSetup, npStart } from 'ui/new_platform'; import { SavedObjectRegistryProvider } from 'ui/saved_objects'; +import { docTitle } from 'ui/doc_title/doc_title'; import chrome from 'ui/chrome'; import { IPrivate } from 'ui/private'; import { ShareContextMenuExtensionsRegistryProvider } from 'ui/share'; @@ -63,6 +64,7 @@ async function getAngularDependencies(): Promise, - chrome.getKibanaVersion(), + kibanaVersion, appState.useMargins, appState.uiState ); diff --git a/src/legacy/core_plugins/kibana/public/dashboard/plugin.ts b/src/legacy/core_plugins/kibana/public/dashboard/plugin.ts index cbdc0663bfd77..8ece51a06e33f 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/plugin.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/plugin.ts @@ -17,12 +17,17 @@ * under the License. */ -import { CoreSetup, CoreStart, Plugin, SavedObjectsClientContract } from 'kibana/public'; +import { + CoreSetup, + CoreStart, + LegacyCoreStart, + Plugin, + SavedObjectsClientContract, +} from 'kibana/public'; import { Storage } from 'ui/storage'; import { RenderDeps } from './render_app'; import { LocalApplicationService } from '../local_application_service'; import { DataStart } from '../../../data/public'; -import { SavedQueryService } from '../../../data/public/search/search_bar/lib/saved_query_service'; import { EmbeddablePublicPlugin } from '../../../../../plugins/embeddable/public'; export interface LegacyAngularInjectedDependencies { @@ -44,14 +49,16 @@ export interface DashboardPluginSetupDependencies { getAngularDependencies: () => Promise; localApplicationService: LocalApplicationService; FeatureCatalogueRegistryProvider: any; + docTitle: any; }; } export class DashboardPlugin implements Plugin { - private dataStart: DataStart | null = null; - private savedObjectsClient: SavedObjectsClientContract | null = null; - private savedQueryService: SavedQueryService | null = null; - private embeddables: ReturnType | null = null; + private startDependencies: { + dataStart: DataStart; + savedObjectsClient: SavedObjectsClientContract; + embeddables: ReturnType; + } | null = null; public setup( core: CoreSetup, @@ -64,18 +71,23 @@ export class DashboardPlugin implements Plugin { id: 'dashboards', title: 'Dashboards', mount: async ({ core: contextCore }, params) => { + if (this.startDependencies === null) { + throw new Error('not started yet'); + } + const { dataStart, savedObjectsClient, embeddables } = this.startDependencies; const angularDependencies = await getAngularDependencies(); const deps: RenderDeps = { - core: contextCore, + core: contextCore as LegacyCoreStart, ...legacyServices, ...angularDependencies, - indexPatterns: this.dataStart!.indexPatterns.indexPatterns, - savedObjectsClient: this.savedObjectsClient!, + dataStart, + indexPatterns: dataStart.indexPatterns.indexPatterns, + savedObjectsClient, chrome: contextCore.chrome, addBasePath: contextCore.http.basePath.prepend, uiSettings: contextCore.uiSettings, - savedQueryService: this.savedQueryService!, - embeddables: this.embeddables!, + savedQueryService: dataStart.search.services.savedQueryService, + embeddables, dashboardCapabilities: contextCore.application.capabilities.dashboard, localStorage: new Storage(localStorage), }; @@ -85,11 +97,14 @@ export class DashboardPlugin implements Plugin { }); } - start(core: CoreStart, { data, embeddables }: DashboardPluginStartDependencies) { - // TODO is this really the right way? I though the app context would give us those - this.dataStart = data; - this.savedObjectsClient = core.savedObjects.client; - this.savedQueryService = data.search.services.savedQueryService; - this.embeddables = embeddables; + start( + { savedObjects: { client: savedObjectsClient } }: CoreStart, + { data: dataStart, embeddables }: DashboardPluginStartDependencies + ) { + this.startDependencies = { + dataStart, + savedObjectsClient, + embeddables, + }; } } diff --git a/src/legacy/core_plugins/kibana/public/dashboard/render_app.ts b/src/legacy/core_plugins/kibana/public/dashboard/render_app.ts index 030b6dd387ff1..dfb18ddf231bb 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/render_app.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/render_app.ts @@ -65,7 +65,7 @@ import { SavedQueryService } from '../../../data/public/search/search_bar/lib/sa import { EmbeddablePublicPlugin } from '../../../../../plugins/embeddable/public'; export interface RenderDeps { - core: AppMountContext['core']; + core: LegacyCoreStart; indexPatterns: DataStart['indexPatterns']['indexPatterns']; dataStart: DataStart; queryFilter: any; @@ -76,6 +76,7 @@ export interface RenderDeps { dashboardConfig: any; savedDashboards: any; dashboardCapabilities: any; + docTitle: any; uiSettings: UiSettingsClientContract; chrome: ChromeStart; addBasePath: (path: string) => string; From 0b595a0edb72ebd7b84541ffe9f225bd3ba9aa35 Mon Sep 17 00:00:00 2001 From: maryia-lapata Date: Fri, 25 Oct 2019 10:24:30 +0300 Subject: [PATCH 022/132] Centralizing dependencies in editor and embeddable --- .../kibana/public/visualize/editor/editor.js | 66 +++++++++++-------- .../visualize/editor/visualization_editor.js | 6 +- .../visualize/embeddable/get_index_pattern.ts | 12 ++-- .../embeddable/visualize_embeddable.ts | 19 +++--- .../visualize_embeddable_factory.tsx | 11 +--- .../kibana/public/visualize/index.js | 2 +- .../public/visualize/kibana_services.ts | 53 +++++++++++---- .../visualize/listing/visualize_listing.js | 8 +-- 8 files changed, 107 insertions(+), 70 deletions(-) diff --git a/src/legacy/core_plugins/kibana/public/visualize/editor/editor.js b/src/legacy/core_plugins/kibana/public/visualize/editor/editor.js index dac0880e6fec4..d140d8b29ad92 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/editor/editor.js +++ b/src/legacy/core_plugins/kibana/public/visualize/editor/editor.js @@ -26,39 +26,47 @@ import 'ui/vis/editors/default/sidebar'; import 'ui/visualize'; import 'ui/collapsible_sidebar'; -import { capabilities } from 'ui/capabilities'; -import chrome from 'ui/chrome'; import React from 'react'; -import angular from 'angular'; import { FormattedMessage } from '@kbn/i18n/react'; -import { toastNotifications } from 'ui/notify'; -import { docTitle } from 'ui/doc_title'; -import { FilterBarQueryFilterProvider } from 'ui/filter_manager/query_filter'; -import { stateMonitorFactory } from 'ui/state_management/state_monitor_factory'; import { migrateAppState } from './lib'; -import uiRoutes from 'ui/routes'; -import { uiModules } from 'ui/modules'; import editorTemplate from './editor.html'; import { DashboardConstants } from '../../dashboard/dashboard_constants'; import { VisualizeConstants } from '../visualize_constants'; -import { KibanaParsedUrl } from 'ui/url/kibana_parsed_url'; -import { absoluteToParsedUrl } from 'ui/url/absolute_to_parsed_url'; -import { migrateLegacyQuery } from 'ui/utils/migrate_legacy_query'; -import { subscribeWithScope } from 'ui/utils/subscribe_with_scope'; -import { timefilter } from 'ui/timefilter'; -import { getVisualizeLoader } from '../../../../../ui/public/visualize/loader'; -import { showShareContextMenu, ShareContextMenuExtensionsRegistryProvider } from 'ui/share'; -import { getUnhashableStatesProvider } from 'ui/state_management/state_hashing'; -import { showSaveModal } from 'ui/saved_objects/show_saved_object_save_modal'; -import { SavedObjectSaveModal } from 'ui/saved_objects/components/saved_object_save_modal'; import { getEditBreadcrumbs, getCreateBreadcrumbs } from '../breadcrumbs'; -import { npStart } from 'ui/new_platform'; import { extractTimeFilter, changeTimeFilter } from '../../../../data/public'; import { start as data } from '../../../../data/public/legacy'; import { start as visualizations } from '../../../../visualizations/public/np_ready/public/legacy'; import { addHelpMenuToAppChrome } from '../help_menu/help_menu_util'; +import { + getServices, + absoluteToParsedUrl, + getUnhashableStatesProvider, + getVisualizeLoader, + KibanaParsedUrl, + migrateLegacyQuery, + SavedObjectSaveModal, + showShareContextMenu, + showSaveModal, + stateMonitorFactory, + subscribeWithScope, +} from '../kibana_services'; + +const { + angular, + capabilities, + chrome, + docTitle, + FilterBarQueryFilterProvider, + getBasePath, + ShareContextMenuExtensionsRegistryProvider, + toastNotifications, + timefilter, + uiModules, + uiRoutes, +} = getServices(); + const { savedQueryService } = data.search.services; uiRoutes @@ -100,7 +108,7 @@ uiRoutes savedVis: function (savedVisualizations, redirectWhenMissing, $route) { return savedVisualizations.get($route.current.params.id) .then((savedVis) => { - npStart.core.chrome.recentlyAccessed.add( + chrome.recentlyAccessed.add( savedVis.getFullPath(), savedVis.title, savedVis.id); @@ -167,7 +175,7 @@ function VisEditor( dirty: !savedVis.id }; - $scope.topNavMenu = [...(capabilities.get().visualize.save ? [{ + $scope.topNavMenu = [...(capabilities.visualize.save ? [{ id: 'save', label: i18n.translate('kbn.topNavMenu.saveVisualizationButtonLabel', { defaultMessage: 'save' }), description: i18n.translate('kbn.visualize.topNavMenu.saveVisualizationButtonAriaLabel', { @@ -236,7 +244,7 @@ function VisEditor( showShareContextMenu({ anchorElement, allowEmbed: true, - allowShortUrl: capabilities.get().visualize.createShortUrl, + allowShortUrl: capabilities.visualize.createShortUrl, getUnhashableStates, objectId: savedVis.id, objectType: 'visualization', @@ -353,9 +361,9 @@ function VisEditor( } }); - $scope.showSaveQuery = capabilities.get().visualize.saveQuery; + $scope.showSaveQuery = capabilities.visualize.saveQuery; - $scope.$watch(() => capabilities.get().visualize.saveQuery, (newCapability) => { + $scope.$watch(() => capabilities.visualize.saveQuery, (newCapability) => { $scope.showSaveQuery = newCapability; }); @@ -582,7 +590,7 @@ function VisEditor( if ($scope.isAddToDashMode()) { const savedVisualizationParsedUrl = new KibanaParsedUrl({ - basePath: chrome.getBasePath(), + basePath: getBasePath(), appId: kbnBaseUrl.slice('/app/'.length), appPath: kbnUrl.eval(`${VisualizeConstants.EDIT_PATH}/{{id}}`, { id: savedVis.id }), }); @@ -593,13 +601,13 @@ function VisEditor( // url, not the unsaved one. chrome.trackSubUrlForApp('kibana:visualize', savedVisualizationParsedUrl); - const lastDashboardAbsoluteUrl = npStart.core.chrome.navLinks.get('kibana:dashboard').url; - const dashboardParsedUrl = absoluteToParsedUrl(lastDashboardAbsoluteUrl, chrome.getBasePath()); + const lastDashboardAbsoluteUrl = chrome.navLinks.get('kibana:dashboard').url; + const dashboardParsedUrl = absoluteToParsedUrl(lastDashboardAbsoluteUrl, getBasePath()); dashboardParsedUrl.addQueryParameter(DashboardConstants.NEW_VISUALIZATION_ID_PARAM, savedVis.id); kbnUrl.change(dashboardParsedUrl.appPath); } else if (savedVis.id === $route.current.params.id) { docTitle.change(savedVis.lastSavedTitle); - chrome.breadcrumbs.set($injector.invoke(getEditBreadcrumbs)); + chrome.setBreadcrumbs($injector.invoke(getEditBreadcrumbs)); } else { kbnUrl.change(`${VisualizeConstants.EDIT_PATH}/{{id}}`, { id: savedVis.id }); } diff --git a/src/legacy/core_plugins/kibana/public/visualize/editor/visualization_editor.js b/src/legacy/core_plugins/kibana/public/visualize/editor/visualization_editor.js index a2ed44df2f5b0..63dcd395ef918 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/editor/visualization_editor.js +++ b/src/legacy/core_plugins/kibana/public/visualize/editor/visualization_editor.js @@ -18,9 +18,11 @@ */ import { debounce } from 'lodash'; -import { uiModules } from 'ui/modules'; import 'angular-sanitize'; -import { VisEditorTypesRegistryProvider } from 'ui/registry/vis_editor_types'; + +import { getServices, VisEditorTypesRegistryProvider } from '../kibana_services'; + +const { uiModules } = getServices(); uiModules .get('kibana/directive', ['ngSanitize']) diff --git a/src/legacy/core_plugins/kibana/public/visualize/embeddable/get_index_pattern.ts b/src/legacy/core_plugins/kibana/public/visualize/embeddable/get_index_pattern.ts index 97db9e1efea8c..b6d5adcd98bd0 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/embeddable/get_index_pattern.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/embeddable/get_index_pattern.ts @@ -17,12 +17,14 @@ * under the License. */ -import { StaticIndexPattern, getFromSavedObject } from 'ui/index_patterns'; -import { VisSavedObject } from 'ui/visualize/loader/types'; +import { + getServices, + getFromSavedObject, + StaticIndexPattern, + VisSavedObject, +} from '../kibana_services'; -import { getServices } from '../kibana_services'; - -const { uiSettings, savedObjectsClient } = getServices(); +const { savedObjectsClient, uiSettings } = getServices(); export async function getIndexPattern( savedVis: VisSavedObject diff --git a/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable.ts b/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable.ts index b9febc3af54ea..3b5d68de3f8a0 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable.ts @@ -18,15 +18,6 @@ */ import _ from 'lodash'; -import { StaticIndexPattern } from 'ui/index_patterns'; -import { PersistedState } from 'ui/persisted_state'; -import { VisualizeLoader } from 'ui/visualize/loader'; -import { EmbeddedVisualizeHandler } from 'ui/visualize/loader/embedded_visualize_handler'; -import { - VisSavedObject, - VisualizeLoaderParams, - VisualizeUpdateParams, -} from 'ui/visualize/loader/types'; import { Subscription } from 'rxjs'; import * as Rx from 'rxjs'; import { Filter } from '@kbn/es-query'; @@ -40,6 +31,16 @@ import { import { Query, onlyDisabledFiltersChanged } from '../../../../data/public'; import { VISUALIZE_EMBEDDABLE_TYPE } from './constants'; +import { + EmbeddedVisualizeHandler, + PersistedState, + StaticIndexPattern, + VisSavedObject, + VisualizeLoader, + VisualizeLoaderParams, + VisualizeUpdateParams, +} from '../kibana_services'; + const getKeys = (o: T): Array => Object.keys(o) as Array; export interface VisualizeEmbeddableConfiguration { diff --git a/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable_factory.tsx b/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable_factory.tsx index 5c4768cd63dad..a8e0fee804a72 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable_factory.tsx +++ b/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable_factory.tsx @@ -37,11 +37,6 @@ import 'uiExports/visualize'; import { i18n } from '@kbn/i18n'; -import { capabilities } from 'ui/capabilities'; - -import chrome from 'ui/chrome'; -import { getVisualizeLoader } from 'ui/visualize/loader'; - import { Legacy } from 'kibana'; import { SavedObjectAttributes } from 'kibana/server'; @@ -61,9 +56,9 @@ import { VisualizeEmbeddable, VisualizeInput, VisualizeOutput } from './visualiz import { VISUALIZE_EMBEDDABLE_TYPE } from './constants'; import { TypesStart } from '../../../../visualizations/public/np_ready/public/types'; -import { getServices } from '../kibana_services'; +import { getServices, getVisualizeLoader } from '../kibana_services'; -const { addBasePath } = getServices(); +const { addBasePath, capabilities, chrome, uiSettings } = getServices(); interface VisualizationAttributes extends SavedObjectAttributes { visState: string; @@ -113,7 +108,7 @@ export class VisualizeEmbeddableFactory extends EmbeddableFactory< if (!visType) { return false; } - if (chrome.getUiSettingsClient().get('visualize:enableLabs')) { + if (uiSettings.get('visualize:enableLabs')) { return true; } return visType.stage !== 'experimental'; diff --git a/src/legacy/core_plugins/kibana/public/visualize/index.js b/src/legacy/core_plugins/kibana/public/visualize/index.js index e53a3479f0ffa..74dd17a18a9bc 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/index.js +++ b/src/legacy/core_plugins/kibana/public/visualize/index.js @@ -31,7 +31,7 @@ import { getServices, FeatureCatalogueCategory } from './kibana_services'; // load directives import '../../../data/public'; -const { uiRoutes, FeatureCatalogueRegistryProvider } = getServices(); +const { FeatureCatalogueRegistryProvider, uiRoutes } = getServices(); uiRoutes .defaults(/visualize/, { diff --git a/src/legacy/core_plugins/kibana/public/visualize/kibana_services.ts b/src/legacy/core_plugins/kibana/public/visualize/kibana_services.ts index dd0234d6ed490..2a7591dd7fa48 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/kibana_services.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/kibana_services.ts @@ -20,52 +20,81 @@ import 'ui/directives/kbn_href'; import { npStart } from 'ui/new_platform'; -import chromeLegacy from 'ui/chrome'; import angular from 'angular'; import uiRoutes from 'ui/routes'; -import { wrapInI18nContext } from 'ui/i18n'; +// @ts-ignore +import { docTitle } from 'ui/doc_title'; +import { FilterBarQueryFilterProvider } from 'ui/filter_manager/query_filter'; +import { wrapInI18nContext } from 'ui/i18n'; // @ts-ignore import { uiModules } from 'ui/modules'; import { FeatureCatalogueRegistryProvider } from 'ui/registry/feature_catalogue'; - -// Filters +import { ShareContextMenuExtensionsRegistryProvider } from 'ui/share'; import { timefilter } from 'ui/timefilter'; // Saved objects -import { SavedObjectRegistryProvider } from 'ui/saved_objects/saved_object_registry'; import { SavedObjectsClientProvider } from 'ui/saved_objects'; // @ts-ignore import { SavedObjectProvider } from 'ui/saved_objects/saved_object'; +import { SavedObjectRegistryProvider } from 'ui/saved_objects/saved_object_registry'; const services = { // new platform + addBasePath: npStart.core.http.basePath.prepend, capabilities: npStart.core.application.capabilities, chrome: npStart.core.chrome, docLinks: npStart.core.docLinks, + savedObjectsClient: npStart.core.savedObjects.client, toastNotifications: npStart.core.notifications.toasts, uiSettings: npStart.core.uiSettings, - savedObjectsClient: npStart.core.savedObjects.client, - addBasePath: npStart.core.http.basePath.prepend, // legacy angular, - uiRoutes, - uiModules, + docTitle, FeatureCatalogueRegistryProvider, + FilterBarQueryFilterProvider, + SavedObjectProvider, SavedObjectRegistryProvider, SavedObjectsClientProvider, - SavedObjectProvider, + ShareContextMenuExtensionsRegistryProvider, timefilter, + uiModules, + uiRoutes, wrapInI18nContext, }; + export function getServices() { return services; } +// export legacy static dependencies +export { getFromSavedObject } from 'ui/index_patterns'; +export { PersistedState } from 'ui/persisted_state'; +// @ts-ignore +export { VisEditorTypesRegistryProvider } from 'ui/registry/vis_editor_types'; +// @ts-ignore +export { getUnhashableStatesProvider } from 'ui/state_management/state_hashing'; +export { showSaveModal } from 'ui/saved_objects/show_saved_object_save_modal'; +export { showShareContextMenu } from 'ui/share'; +export { stateMonitorFactory } from 'ui/state_management/state_monitor_factory'; +export { absoluteToParsedUrl } from 'ui/url/absolute_to_parsed_url'; +export { KibanaParsedUrl } from 'ui/url/kibana_parsed_url'; +export { migrateLegacyQuery } from 'ui/utils/migrate_legacy_query'; +export { subscribeWithScope } from 'ui/utils/subscribe_with_scope'; +export { getVisualizeLoader } from 'ui/visualize/loader'; +export { SavedObjectSaveModal } from 'ui/saved_objects/components/saved_object_save_modal'; + // export types -export { VisSavedObject } from 'ui/visualize/loader/types'; +export { StaticIndexPattern } from 'ui/index_patterns'; +export { VisualizeLoader } from 'ui/visualize/loader'; +export { + VisSavedObject, + VisualizeLoaderParams, + VisualizeUpdateParams, +} from 'ui/visualize/loader/types'; +export { EmbeddedVisualizeHandler } from 'ui/visualize/loader/embedded_visualize_handler'; -// const +// export const export { FeatureCatalogueCategory } from 'ui/registry/feature_catalogue'; diff --git a/src/legacy/core_plugins/kibana/public/visualize/listing/visualize_listing.js b/src/legacy/core_plugins/kibana/public/visualize/listing/visualize_listing.js index aeee3c91cbfa7..42c4556041197 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/listing/visualize_listing.js +++ b/src/legacy/core_plugins/kibana/public/visualize/listing/visualize_listing.js @@ -27,14 +27,14 @@ import { i18n } from '@kbn/i18n'; import { getServices } from '../kibana_services'; const { - uiModules, + addBasePath, + chrome, SavedObjectRegistryProvider, + SavedObjectsClientProvider, timefilter, - chrome, toastNotifications, + uiModules, wrapInI18nContext, - SavedObjectsClientProvider, - addBasePath } = getServices(); const app = uiModules.get('app/visualize', ['ngRoute', 'react']); From 46ba3db2eeb649d41c846f9bba43ddfe45b8d1f4 Mon Sep 17 00:00:00 2001 From: maryia-lapata Date: Fri, 25 Oct 2019 12:30:04 +0300 Subject: [PATCH 023/132] Remove unused deps, move imports --- .../kibana/public/visualize/editor/editor.js | 7 ++--- .../visualize/editor/visualization_editor.js | 1 - .../embeddable/visualize_embeddable.ts | 2 +- .../visualize_embeddable_factory.tsx | 27 ++----------------- .../public/visualize/kibana_services.ts | 18 ++++++++++--- .../visualize/listing/visualize_listing.js | 2 +- .../public/visualize/wizard/new_vis_modal.tsx | 5 ++-- 7 files changed, 22 insertions(+), 40 deletions(-) diff --git a/src/legacy/core_plugins/kibana/public/visualize/editor/editor.js b/src/legacy/core_plugins/kibana/public/visualize/editor/editor.js index fa59084e345da..5930c96b09273 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/editor/editor.js +++ b/src/legacy/core_plugins/kibana/public/visualize/editor/editor.js @@ -22,9 +22,6 @@ import { Subscription } from 'rxjs'; import { i18n } from '@kbn/i18n'; import '../saved_visualizations/saved_visualizations'; import './visualization_editor'; -import 'ui/vis/editors/default/sidebar'; -import 'ui/visualize'; -import 'ui/collapsible_sidebar'; import React from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; @@ -35,12 +32,12 @@ import { VisualizeConstants } from '../visualize_constants'; import { getEditBreadcrumbs, getCreateBreadcrumbs } from '../breadcrumbs'; import { extractTimeFilter, changeTimeFilter } from '../../../../data/public'; import { start as data } from '../../../../data/public/legacy'; -import { start as visualizations } from '../../../../visualizations/public/np_ready/public/legacy'; import { addHelpMenuToAppChrome } from '../help_menu/help_menu_util'; import { getServices, + angular, absoluteToParsedUrl, getUnhashableStatesProvider, getVisualizeLoader, @@ -54,7 +51,6 @@ import { } from '../kibana_services'; const { - angular, capabilities, chrome, docTitle, @@ -65,6 +61,7 @@ const { timefilter, uiModules, uiRoutes, + visualizations, } = getServices(); const { savedQueryService } = data.search.services; diff --git a/src/legacy/core_plugins/kibana/public/visualize/editor/visualization_editor.js b/src/legacy/core_plugins/kibana/public/visualize/editor/visualization_editor.js index 63dcd395ef918..9c96d8ca506a8 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/editor/visualization_editor.js +++ b/src/legacy/core_plugins/kibana/public/visualize/editor/visualization_editor.js @@ -18,7 +18,6 @@ */ import { debounce } from 'lodash'; -import 'angular-sanitize'; import { getServices, VisEditorTypesRegistryProvider } from '../kibana_services'; diff --git a/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable.ts b/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable.ts index 3b5d68de3f8a0..d95264abdd559 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable.ts @@ -18,6 +18,7 @@ */ import _ from 'lodash'; +import { EmbeddedVisualizeHandler } from 'ui/visualize/loader/embedded_visualize_handler'; import { Subscription } from 'rxjs'; import * as Rx from 'rxjs'; import { Filter } from '@kbn/es-query'; @@ -32,7 +33,6 @@ import { Query, onlyDisabledFiltersChanged } from '../../../../data/public'; import { VISUALIZE_EMBEDDABLE_TYPE } from './constants'; import { - EmbeddedVisualizeHandler, PersistedState, StaticIndexPattern, VisSavedObject, diff --git a/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable_factory.tsx b/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable_factory.tsx index a8e0fee804a72..a0a1182ea3c55 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable_factory.tsx +++ b/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable_factory.tsx @@ -17,37 +17,17 @@ * under the License. */ -import 'ui/registry/field_formats'; -import 'uiExports/contextMenuActions'; -import 'uiExports/devTools'; -import 'uiExports/docViews'; -import 'uiExports/embeddableFactories'; -import 'uiExports/embeddableActions'; -import 'uiExports/fieldFormatEditors'; -import 'uiExports/fieldFormats'; -import 'uiExports/home'; -import 'uiExports/indexManagement'; -import 'uiExports/inspectorViews'; -import 'uiExports/savedObjectTypes'; -import 'uiExports/search'; -import 'uiExports/shareContextMenuExtensions'; -import 'uiExports/visEditorTypes'; -import 'uiExports/visTypes'; -import 'uiExports/visualize'; - import { i18n } from '@kbn/i18n'; import { Legacy } from 'kibana'; import { SavedObjectAttributes } from 'kibana/server'; -import { npSetup } from 'ui/new_platform'; import { EmbeddableFactory, ErrorEmbeddable, Container, EmbeddableOutput, } from '../../../../../../plugins/embeddable/public'; -import { start as visualizations } from '../../../../visualizations/public/np_ready/public/legacy'; import { showNewVisModal } from '../wizard'; import { SavedVisualizations } from '../types'; import { DisabledLabEmbeddable } from './disabled_lab_embeddable'; @@ -58,7 +38,7 @@ import { TypesStart } from '../../../../visualizations/public/np_ready/public/ty import { getServices, getVisualizeLoader } from '../kibana_services'; -const { addBasePath, capabilities, chrome, uiSettings } = getServices(); +const { addBasePath, capabilities, chrome, embeddable, uiSettings, visualizations } = getServices(); interface VisualizationAttributes extends SavedObjectAttributes { visState: string; @@ -182,8 +162,5 @@ export class VisualizeEmbeddableFactory extends EmbeddableFactory< } VisualizeEmbeddableFactory.createVisualizeEmbeddableFactory().then(embeddableFactory => { - npSetup.plugins.embeddable.registerEmbeddableFactory( - VISUALIZE_EMBEDDABLE_TYPE, - embeddableFactory - ); + embeddable.registerEmbeddableFactory(VISUALIZE_EMBEDDABLE_TYPE, embeddableFactory); }); diff --git a/src/legacy/core_plugins/kibana/public/visualize/kibana_services.ts b/src/legacy/core_plugins/kibana/public/visualize/kibana_services.ts index 2a7591dd7fa48..2b8fcb02dcb5b 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/kibana_services.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/kibana_services.ts @@ -17,10 +17,11 @@ * under the License. */ -import 'ui/directives/kbn_href'; +import 'angular-sanitize'; // used in visualization_editor.js +import 'ui/collapsible_sidebar'; // used in default editor import { npStart } from 'ui/new_platform'; -import angular from 'angular'; +import angular from 'angular'; // just used in editor.js import uiRoutes from 'ui/routes'; @@ -40,18 +41,23 @@ import { SavedObjectsClientProvider } from 'ui/saved_objects'; import { SavedObjectProvider } from 'ui/saved_objects/saved_object'; import { SavedObjectRegistryProvider } from 'ui/saved_objects/saved_object_registry'; +import { createUiStatsReporter, METRIC_TYPE } from '../../../ui_metric/public'; +import { start as visualizations } from '../../../visualizations/public/np_ready/public/legacy'; + const services = { // new platform addBasePath: npStart.core.http.basePath.prepend, capabilities: npStart.core.application.capabilities, chrome: npStart.core.chrome, docLinks: npStart.core.docLinks, + embeddable: npStart.plugins.embeddable, savedObjectsClient: npStart.core.savedObjects.client, toastNotifications: npStart.core.notifications.toasts, uiSettings: npStart.core.uiSettings, + visualizations, + // legacy - angular, docTitle, FeatureCatalogueRegistryProvider, FilterBarQueryFilterProvider, @@ -63,6 +69,8 @@ const services = { uiModules, uiRoutes, wrapInI18nContext, + + createUiStatsReporter, }; export function getServices() { @@ -70,6 +78,7 @@ export function getServices() { } // export legacy static dependencies +export { angular }; export { getFromSavedObject } from 'ui/index_patterns'; export { PersistedState } from 'ui/persisted_state'; // @ts-ignore @@ -87,14 +96,15 @@ export { getVisualizeLoader } from 'ui/visualize/loader'; export { SavedObjectSaveModal } from 'ui/saved_objects/components/saved_object_save_modal'; // export types +export { METRIC_TYPE }; export { StaticIndexPattern } from 'ui/index_patterns'; +export { VisType } from 'ui/vis'; export { VisualizeLoader } from 'ui/visualize/loader'; export { VisSavedObject, VisualizeLoaderParams, VisualizeUpdateParams, } from 'ui/visualize/loader/types'; -export { EmbeddedVisualizeHandler } from 'ui/visualize/loader/embedded_visualize_handler'; // export const export { FeatureCatalogueCategory } from 'ui/registry/feature_catalogue'; diff --git a/src/legacy/core_plugins/kibana/public/visualize/listing/visualize_listing.js b/src/legacy/core_plugins/kibana/public/visualize/listing/visualize_listing.js index 42c4556041197..7a0ff15f13511 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/listing/visualize_listing.js +++ b/src/legacy/core_plugins/kibana/public/visualize/listing/visualize_listing.js @@ -21,7 +21,6 @@ import { addHelpMenuToAppChrome } from '../help_menu/help_menu_util'; import { VisualizeListingTable } from './visualize_listing_table'; import { NewVisModal } from '../wizard/new_vis_modal'; import { VisualizeConstants } from '../visualize_constants'; -import { start as visualizations } from '../../../../visualizations/public/np_ready/public/legacy'; import { i18n } from '@kbn/i18n'; import { getServices } from '../kibana_services'; @@ -35,6 +34,7 @@ const { toastNotifications, uiModules, wrapInI18nContext, + visualizations, } = getServices(); const app = uiModules.get('app/visualize', ['ngRoute', 'react']); diff --git a/src/legacy/core_plugins/kibana/public/visualize/wizard/new_vis_modal.tsx b/src/legacy/core_plugins/kibana/public/visualize/wizard/new_vis_modal.tsx index 7231d7e947408..2b36c9af705c2 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/wizard/new_vis_modal.tsx +++ b/src/legacy/core_plugins/kibana/public/visualize/wizard/new_vis_modal.tsx @@ -23,14 +23,13 @@ import { EuiModal, EuiOverlayMask } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { VisualizeConstants } from '../visualize_constants'; -import { createUiStatsReporter, METRIC_TYPE } from '../../../../ui_metric/public'; import { SearchSelection } from './search_selection'; import { TypeSelection } from './type_selection'; import { TypesStart, VisTypeAlias } from '../../../../visualizations/public/np_ready/public/types'; -import { getServices, VisType } from '../kibana_services'; +import { getServices, METRIC_TYPE, VisType } from '../kibana_services'; -const { addBasePath, uiSettings } = getServices(); +const { addBasePath, createUiStatsReporter, uiSettings } = getServices(); interface TypeSelectionProps { isOpen: boolean; From 36ec4e1e8929ac73e400801bd8b7ebf2f7e492d5 Mon Sep 17 00:00:00 2001 From: maryia-lapata Date: Fri, 25 Oct 2019 12:50:03 +0300 Subject: [PATCH 024/132] Fix dangerouslyGetActiveInjector --- .../embeddable/visualize_embeddable_factory.tsx | 14 +++++++++++--- .../kibana/public/visualize/kibana_services.ts | 5 +++++ 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable_factory.tsx b/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable_factory.tsx index a0a1182ea3c55..c69bd25966451 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable_factory.tsx +++ b/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable_factory.tsx @@ -38,7 +38,15 @@ import { TypesStart } from '../../../../visualizations/public/np_ready/public/ty import { getServices, getVisualizeLoader } from '../kibana_services'; -const { addBasePath, capabilities, chrome, embeddable, uiSettings, visualizations } = getServices(); +const { + addBasePath, + capabilities, + chrome, + embeddable, + getInjector, + uiSettings, + visualizations, +} = getServices(); interface VisualizationAttributes extends SavedObjectAttributes { visState: string; @@ -100,7 +108,7 @@ export class VisualizeEmbeddableFactory extends EmbeddableFactory< } public isEditable() { - return capabilities.get().visualize.save as boolean; + return capabilities.visualize.save as boolean; } public getDisplayName() { @@ -114,7 +122,7 @@ export class VisualizeEmbeddableFactory extends EmbeddableFactory< input: Partial & { id: string }, parent?: Container ): Promise { - const $injector = await chrome.dangerouslyGetActiveInjector(); + const $injector = await getInjector(); const config = $injector.get('config'); const savedVisualizations = $injector.get('savedVisualizations'); diff --git a/src/legacy/core_plugins/kibana/public/visualize/kibana_services.ts b/src/legacy/core_plugins/kibana/public/visualize/kibana_services.ts index 2b8fcb02dcb5b..8ffd1d19e447a 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/kibana_services.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/kibana_services.ts @@ -19,9 +19,11 @@ import 'angular-sanitize'; // used in visualization_editor.js import 'ui/collapsible_sidebar'; // used in default editor +import 'ui/vis/editors/default/sidebar'; import { npStart } from 'ui/new_platform'; import angular from 'angular'; // just used in editor.js +import chromeLegacy from 'ui/chrome'; import uiRoutes from 'ui/routes'; @@ -61,6 +63,9 @@ const services = { docTitle, FeatureCatalogueRegistryProvider, FilterBarQueryFilterProvider, + getInjector: () => { + return chromeLegacy.dangerouslyGetActiveInjector(); + }, SavedObjectProvider, SavedObjectRegistryProvider, SavedObjectsClientProvider, From abfdc2aa48fc31067d5345ab030101115bc58955 Mon Sep 17 00:00:00 2001 From: maryia-lapata Date: Fri, 25 Oct 2019 12:58:06 +0300 Subject: [PATCH 025/132] Move loading directive to kibana_services.ts --- src/legacy/core_plugins/kibana/public/visualize/index.js | 3 --- .../core_plugins/kibana/public/visualize/kibana_services.ts | 2 ++ 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/legacy/core_plugins/kibana/public/visualize/index.js b/src/legacy/core_plugins/kibana/public/visualize/index.js index 74dd17a18a9bc..592a355a71b0d 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/index.js +++ b/src/legacy/core_plugins/kibana/public/visualize/index.js @@ -28,9 +28,6 @@ import { getLandingBreadcrumbs, getWizardStep1Breadcrumbs } from './breadcrumbs' import { getServices, FeatureCatalogueCategory } from './kibana_services'; -// load directives -import '../../../data/public'; - const { FeatureCatalogueRegistryProvider, uiRoutes } = getServices(); uiRoutes diff --git a/src/legacy/core_plugins/kibana/public/visualize/kibana_services.ts b/src/legacy/core_plugins/kibana/public/visualize/kibana_services.ts index 8ffd1d19e447a..69333e0768229 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/kibana_services.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/kibana_services.ts @@ -20,6 +20,8 @@ import 'angular-sanitize'; // used in visualization_editor.js import 'ui/collapsible_sidebar'; // used in default editor import 'ui/vis/editors/default/sidebar'; +// load directives +import '../../../data/public'; import { npStart } from 'ui/new_platform'; import angular from 'angular'; // just used in editor.js From d1700d22c0f53da0bc3df0d3f1b3973e653249d8 Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Fri, 25 Oct 2019 12:26:37 +0200 Subject: [PATCH 026/132] remove requireDefaultIndex and provide helper function --- .../kibana/public/dashboard/app.js | 79 +++++++------- .../public/dashboard/dashboard_constants.ts | 4 +- .../kibana/public/dashboard/plugin.ts | 10 +- .../kibana/public/dashboard/render_app.ts | 2 +- .../public/discover/angular/discover.js | 93 ++++++++-------- .../kibana/public/visualize/editor/editor.js | 34 +++--- .../kibana/public/visualize/index.js | 6 +- .../public/legacy_compat/angular_config.tsx | 99 +---------------- src/legacy/ui/public/legacy_compat/utils.tsx | 100 ++++++++++++++++++ .../public/routes/__tests__/_route_manager.js | 12 --- .../ui/public/routes/route_manager.d.ts | 1 - src/legacy/ui/public/routes/route_manager.js | 4 - 12 files changed, 228 insertions(+), 216 deletions(-) create mode 100644 src/legacy/ui/public/legacy_compat/utils.tsx diff --git a/src/legacy/core_plugins/kibana/public/dashboard/app.js b/src/legacy/core_plugins/kibana/public/dashboard/app.js index 65a933b5335d5..c8fab451242ba 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/app.js +++ b/src/legacy/core_plugins/kibana/public/dashboard/app.js @@ -32,6 +32,8 @@ import { import { FeatureCatalogueCategory } from 'ui/registry/feature_catalogue'; import { DashboardListing, EMPTY_FILTER } from './listing/dashboard_listing'; import { addHelpMenuToAppChrome } from './help_menu/help_menu_util'; +import { ensureDefaultIndexPattern } from '../../../../ui/public/legacy_compat/utils'; +import { start as data } from '../../../data/public/legacy'; export function initDashboardApp(app, deps) { initDashboardAppDirective(app, deps); @@ -50,7 +52,6 @@ export function initDashboardApp(app, deps) { app.config(function ($routeProvider) { const defaults = { reloadOnSearch: false, - requireDefaultIndex: true, requireUICapability: 'dashboard.show', badge: () => { if (deps.dashboardCapabilities.showWriteControls) { @@ -70,9 +71,6 @@ export function initDashboardApp(app, deps) { }; $routeProvider - // migrate old URLs - .when('/dashboards/dashboard', { redirectTo: (_params, _path, query) => `/dashboards/create?${query}` }) - .when('/dashboards/dashboard/:id', { redirectTo: (params, _path, query) => `/dashboards/edit/${params.id}?${query}` }) .when(DashboardConstants.LANDING_PAGE_PATH, { ...defaults, template: dashboardListingTemplate, @@ -109,30 +107,32 @@ export function initDashboardApp(app, deps) { addHelpMenuToAppChrome(deps.chrome, deps.core.docLinks); }, resolve: { - dash: function ($rootScope, $route, redirectWhenMissing, kbnUrl, Promise) { - const savedObjectsClient = deps.savedObjectsClient; - const title = $route.current.params.title; - if (title) { - return savedObjectsClient - .find({ - search: `"${title}"`, - search_fields: 'title', - type: 'dashboard', - }) - .then(results => { - // The search isn't an exact match, lets see if we can find a single exact match to use - const matchingDashboards = results.savedObjects.filter( - dashboard => dashboard.attributes.title.toLowerCase() === title.toLowerCase() - ); - if (matchingDashboards.length === 1) { - kbnUrl.redirect(createDashboardEditUrl(matchingDashboards[0].id)); - } else { - kbnUrl.redirect(`${DashboardConstants.LANDING_PAGE_PATH}?filter="${title}"`); - } - $rootScope.$digest(); - return Promise.halt(); - }); - } + dash: function ($rootScope, $route, redirectWhenMissing, kbnUrl) { + return ensureDefaultIndexPattern(deps.core, data, $rootScope, kbnUrl).then(() => { + const savedObjectsClient = deps.savedObjectsClient; + const title = $route.current.params.title; + if (title) { + return savedObjectsClient + .find({ + search: `"${title}"`, + search_fields: 'title', + type: 'dashboard', + }) + .then(results => { + // The search isn't an exact match, lets see if we can find a single exact match to use + const matchingDashboards = results.savedObjects.filter( + dashboard => dashboard.attributes.title.toLowerCase() === title.toLowerCase() + ); + if (matchingDashboards.length === 1) { + kbnUrl.redirect(createDashboardEditUrl(matchingDashboards[0].id)); + } else { + kbnUrl.redirect(`${DashboardConstants.LANDING_PAGE_PATH}?filter="${title}"`); + } + $rootScope.$digest(); + return new Promise(() => {}); + }); + } + }); }, }, }) @@ -142,12 +142,16 @@ export function initDashboardApp(app, deps) { controller: createNewDashboardCtrl, requireUICapability: 'dashboard.createNew', resolve: { - dash: function (redirectWhenMissing) { - return deps.savedDashboards.get().catch( - redirectWhenMissing({ - dashboard: DashboardConstants.LANDING_PAGE_PATH, + dash: function (redirectWhenMissing, $rootScope, kbnUrl) { + return ensureDefaultIndexPattern(deps.core, data, $rootScope, kbnUrl) + .then(() => { + return deps.savedDashboards.get(); }) - ); + .catch( + redirectWhenMissing({ + dashboard: DashboardConstants.LANDING_PAGE_PATH, + }) + ); }, }, }) @@ -156,11 +160,13 @@ export function initDashboardApp(app, deps) { template: dashboardTemplate, controller: createNewDashboardCtrl, resolve: { - dash: function ($route, redirectWhenMissing, kbnUrl, AppState) { + dash: function ($rootScope, $route, redirectWhenMissing, kbnUrl, AppState) { const id = $route.current.params.id; - return deps.savedDashboards - .get(id) + return ensureDefaultIndexPattern(deps.core, data, $rootScope, kbnUrl) + .then(() => { + return deps.savedDashboards.get(id); + }) .then(savedDashboard => { deps.chrome.recentlyAccessed.add( savedDashboard.getFullPath(), @@ -188,6 +194,7 @@ export function initDashboardApp(app, deps) { 'The url "dashboard/create" was removed in 6.0. Please update your bookmarks.', }) ); + return new Promise(() => {}); } else { throw error; } diff --git a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_constants.ts b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_constants.ts index f3e4414477b86..b76b3f309874a 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_constants.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_constants.ts @@ -21,9 +21,9 @@ export const DashboardConstants = { ADD_VISUALIZATION_TO_DASHBOARD_MODE_PARAM: 'addToDashboard', NEW_VISUALIZATION_ID_PARAM: 'addVisualization', LANDING_PAGE_PATH: '/dashboards', - CREATE_NEW_DASHBOARD_URL: '/dashboards/create', + CREATE_NEW_DASHBOARD_URL: '/dashboard', }; export function createDashboardEditUrl(id: string) { - return `/dashboards/view/${id}`; + return `/dashboard/${id}`; } diff --git a/src/legacy/core_plugins/kibana/public/dashboard/plugin.ts b/src/legacy/core_plugins/kibana/public/dashboard/plugin.ts index 8ece51a06e33f..e3358546a5c75 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/plugin.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/plugin.ts @@ -18,6 +18,7 @@ */ import { + App, CoreSetup, CoreStart, LegacyCoreStart, @@ -66,9 +67,8 @@ export class DashboardPlugin implements Plugin { __LEGACY: { localApplicationService, getAngularDependencies, ...legacyServices }, }: DashboardPluginSetupDependencies ) { - localApplicationService.forwardApp('dashboard', 'dashboards', { keepPrefix: true }); - localApplicationService.register({ - id: 'dashboards', + const app: App = { + id: '', title: 'Dashboards', mount: async ({ core: contextCore }, params) => { if (this.startDependencies === null) { @@ -94,7 +94,9 @@ export class DashboardPlugin implements Plugin { const { renderApp } = await import('./render_app'); return renderApp(params.element, params.appBasePath, deps); }, - }); + }; + localApplicationService.register({ ...app, id: 'dashboard' }); + localApplicationService.register({ ...app, id: 'dashboards' }); } start( diff --git a/src/legacy/core_plugins/kibana/public/dashboard/render_app.ts b/src/legacy/core_plugins/kibana/public/dashboard/render_app.ts index dfb18ddf231bb..be77a7f273138 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/render_app.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/render_app.ts @@ -89,7 +89,7 @@ export interface RenderDeps { export const renderApp = (element: HTMLElement, appBasePath: string, deps: RenderDeps) => { const dashboardAngularModule = createLocalAngularModule(deps.core); // global routing stuff - configureAppAngularModule(dashboardAngularModule, deps.core as LegacyCoreStart, deps.dataStart); + configureAppAngularModule(dashboardAngularModule, deps.core as LegacyCoreStart); // custom routing stuff initDashboardApp(dashboardAngularModule, deps); const $injector = mountDashboardApp(appBasePath, element); diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/discover.js b/src/legacy/core_plugins/kibana/public/discover/angular/discover.js index 1a6c6aad363ba..e9ad486d70db7 100644 --- a/src/legacy/core_plugins/kibana/public/discover/angular/discover.js +++ b/src/legacy/core_plugins/kibana/public/discover/angular/discover.js @@ -74,6 +74,7 @@ import { addHelpMenuToAppChrome } from '../components/help_menu/help_menu_util'; import { extractTimeFilter, changeTimeFilter } from '../../../../data/public'; import { start as data } from '../../../../data/public/legacy'; import { npStart } from 'ui/new_platform'; +import { ensureDefaultIndexPattern } from '../../../../../ui/public/legacy_compat/utils'; const { savedQueryService } = data.search.services; @@ -90,7 +91,6 @@ const app = uiModules.get('apps/discover', [ uiRoutes .defaults(/^\/discover(\/|$)/, { - requireDefaultIndex: true, requireUICapability: 'discover.show', k7Breadcrumbs: ($route, $injector) => $injector.invoke( @@ -118,50 +118,53 @@ uiRoutes template: indexTemplate, reloadOnSearch: false, resolve: { - ip: function (Promise, indexPatterns, config, Private) { + savedObjects: function (Promise, indexPatterns, config, Private, $rootScope, kbnUrl, redirectWhenMissing, savedSearches, $route) { const State = Private(StateProvider); - return indexPatterns.getCache().then((savedObjects)=> { - /** - * In making the indexPattern modifiable it was placed in appState. Unfortunately, - * the load order of AppState conflicts with the load order of many other things - * so in order to get the name of the index we should use, and to switch to the - * default if necessary, we parse the appState with a temporary State object and - * then destroy it immediatly after we're done - * - * @type {State} - */ - const state = new State('_a', {}); - - const specified = !!state.index; - const exists = _.findIndex(savedObjects, o => o.id === state.index) > -1; - const id = exists ? state.index : config.get('defaultIndex'); - state.destroy(); + const savedSearchId = $route.current.params.id; + return ensureDefaultIndexPattern(npStart.core, data, $rootScope, kbnUrl).then(() => { return Promise.props({ - list: savedObjects, - loaded: indexPatterns.get(id), - stateVal: state.index, - stateValFound: specified && exists + ip: indexPatterns.getCache().then((savedObjects) => { + /** + * In making the indexPattern modifiable it was placed in appState. Unfortunately, + * the load order of AppState conflicts with the load order of many other things + * so in order to get the name of the index we should use, and to switch to the + * default if necessary, we parse the appState with a temporary State object and + * then destroy it immediatly after we're done + * + * @type {State} + */ + const state = new State('_a', {}); + + const specified = !!state.index; + const exists = _.findIndex(savedObjects, o => o.id === state.index) > -1; + const id = exists ? state.index : config.get('defaultIndex'); + state.destroy(); + + return Promise.props({ + list: savedObjects, + loaded: indexPatterns.get(id), + stateVal: state.index, + stateValFound: specified && exists + }); + }), + savedSearch: savedSearches.get(savedSearchId) + .then((savedSearch) => { + if (savedSearchId) { + npStart.core.chrome.recentlyAccessed.add( + savedSearch.getFullPath(), + savedSearch.title, + savedSearchId); + } + return savedSearch; + }) + .catch(redirectWhenMissing({ + 'search': '/discover', + 'index-pattern': '/management/kibana/objects/savedSearches/' + $route.current.params.id + })) }); }); }, - savedSearch: function (redirectWhenMissing, savedSearches, $route) { - const savedSearchId = $route.current.params.id; - return savedSearches.get(savedSearchId) - .then((savedSearch) => { - if (savedSearchId) { - npStart.core.chrome.recentlyAccessed.add( - savedSearch.getFullPath(), - savedSearch.title, - savedSearchId); - } - return savedSearch; - }) - .catch(redirectWhenMissing({ - 'search': '/discover', - 'index-pattern': '/management/kibana/objects/savedSearches/' + $route.current.params.id - })); - } } }); @@ -228,7 +231,7 @@ function discoverController( }; // the saved savedSearch - const savedSearch = $route.current.locals.savedSearch; + const savedSearch = $route.current.locals.savedObjects.savedSearch; let abortController; $scope.$on('$destroy', () => { @@ -544,7 +547,7 @@ function discoverController( sampleSize: config.get('discover:sampleSize'), timefield: isDefaultTypeIndexPattern($scope.indexPattern) && $scope.indexPattern.timeFieldName, savedSearch: savedSearch, - indexPatternList: $route.current.locals.ip.list, + indexPatternList: $route.current.locals.savedObjects.ip.list, }; const shouldSearchOnPageLoad = () => { @@ -1059,7 +1062,7 @@ function discoverController( loaded: loadedIndexPattern, stateVal, stateValFound, - } = $route.current.locals.ip; + } = $route.current.locals.savedObjects.ip; const ownIndexPattern = $scope.searchSource.getOwnField('index'); @@ -1107,12 +1110,12 @@ function discoverController( // Block the UI from loading if the user has loaded a rollup index pattern but it isn't // supported. $scope.isUnsupportedIndexPattern = ( - !isDefaultTypeIndexPattern($route.current.locals.ip.loaded) - && !hasSearchStategyForIndexPattern($route.current.locals.ip.loaded) + !isDefaultTypeIndexPattern($route.current.locals.savedObjects.ip.loaded) + && !hasSearchStategyForIndexPattern($route.current.locals.savedObjects.ip.loaded) ); if ($scope.isUnsupportedIndexPattern) { - $scope.unsupportedIndexPatternType = $route.current.locals.ip.loaded.type; + $scope.unsupportedIndexPatternType = $route.current.locals.savedObjects.ip.loaded.type; return; } diff --git a/src/legacy/core_plugins/kibana/public/visualize/editor/editor.js b/src/legacy/core_plugins/kibana/public/visualize/editor/editor.js index dac0880e6fec4..146a02720a3e1 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/editor/editor.js +++ b/src/legacy/core_plugins/kibana/public/visualize/editor/editor.js @@ -58,6 +58,7 @@ import { start as data } from '../../../../data/public/legacy'; import { start as visualizations } from '../../../../visualizations/public/np_ready/public/legacy'; import { addHelpMenuToAppChrome } from '../help_menu/help_menu_util'; +import { ensureDefaultIndexPattern } from '../../../../../ui/public/legacy_compat/utils'; const { savedQueryService } = data.search.services; @@ -66,7 +67,7 @@ uiRoutes template: editorTemplate, k7Breadcrumbs: getCreateBreadcrumbs, resolve: { - savedVis: function (savedVisualizations, redirectWhenMissing, $route) { + savedVis: function (savedVisualizations, redirectWhenMissing, $route, $rootScope, kbnUrl) { const visTypes = visualizations.types.all(); const visType = _.find(visTypes, { name: $route.current.params.type }); const shouldHaveIndex = visType.requiresSearch && visType.options.showIndexSelection; @@ -79,7 +80,7 @@ uiRoutes ); } - return savedVisualizations.get($route.current.params) + return ensureDefaultIndexPattern(npStart.core, data, $rootScope, kbnUrl).then(() => savedVisualizations.get($route.current.params)) .then(savedVis => { if (savedVis.vis.type.setup) { return savedVis.vis.type.setup(savedVis) @@ -97,28 +98,33 @@ uiRoutes template: editorTemplate, k7Breadcrumbs: getEditBreadcrumbs, resolve: { - savedVis: function (savedVisualizations, redirectWhenMissing, $route) { - return savedVisualizations.get($route.current.params.id) - .then((savedVis) => { + savedVis: function (savedVisualizations, redirectWhenMissing, $route, $rootScope, kbnUrl) { + return ensureDefaultIndexPattern(npStart.core, data, $rootScope, kbnUrl) + .then(() => savedVisualizations.get($route.current.params.id)) + .then(savedVis => { npStart.core.chrome.recentlyAccessed.add( savedVis.getFullPath(), savedVis.title, - savedVis.id); + savedVis.id + ); return savedVis; }) .then(savedVis => { if (savedVis.vis.type.setup) { - return savedVis.vis.type.setup(savedVis) - .catch(() => savedVis); + return savedVis.vis.type.setup(savedVis).catch(() => savedVis); } return savedVis; }) - .catch(redirectWhenMissing({ - 'visualization': '/visualize', - 'search': '/management/kibana/objects/savedVisualizations/' + $route.current.params.id, - 'index-pattern': '/management/kibana/objects/savedVisualizations/' + $route.current.params.id, - 'index-pattern-field': '/management/kibana/objects/savedVisualizations/' + $route.current.params.id - })); + .catch( + redirectWhenMissing({ + visualization: '/visualize', + search: '/management/kibana/objects/savedVisualizations/' + $route.current.params.id, + 'index-pattern': + '/management/kibana/objects/savedVisualizations/' + $route.current.params.id, + 'index-pattern-field': + '/management/kibana/objects/savedVisualizations/' + $route.current.params.id, + }) + ); } } }); diff --git a/src/legacy/core_plugins/kibana/public/visualize/index.js b/src/legacy/core_plugins/kibana/public/visualize/index.js index 7afb98709fae0..be39ed6c2be38 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/index.js +++ b/src/legacy/core_plugins/kibana/public/visualize/index.js @@ -30,10 +30,12 @@ import { getLandingBreadcrumbs, getWizardStep1Breadcrumbs } from './breadcrumbs' // load directives import '../../../data/public'; +import { ensureDefaultIndexPattern } from '../../../../ui/public/legacy_compat/utils'; +import { npStart } from '../../../../ui/public/new_platform'; +import { start as data } from '../../../data/public/legacy'; uiRoutes .defaults(/visualize/, { - requireDefaultIndex: true, requireUICapability: 'visualize.show', badge: uiCapabilities => { if (uiCapabilities.visualize.save) { @@ -58,6 +60,7 @@ uiRoutes controllerAs: 'listingController', resolve: { createNewVis: () => false, + hasDefaultIndex: ($rootScope, kbnUrl) => ensureDefaultIndexPattern(npStart.core, data, $rootScope, kbnUrl) }, }) .when(VisualizeConstants.WIZARD_STEP_1_PAGE_PATH, { @@ -67,6 +70,7 @@ uiRoutes controllerAs: 'listingController', resolve: { createNewVis: () => true, + hasDefaultIndex: ($rootScope, kbnUrl) => ensureDefaultIndexPattern(npStart.core, data, $rootScope, kbnUrl) }, }); diff --git a/src/legacy/ui/public/legacy_compat/angular_config.tsx b/src/legacy/ui/public/legacy_compat/angular_config.tsx index f62ce41291e1a..14828bbce8558 100644 --- a/src/legacy/ui/public/legacy_compat/angular_config.tsx +++ b/src/legacy/ui/public/legacy_compat/angular_config.tsx @@ -26,15 +26,13 @@ import { IModule, IRootScopeService, } from 'angular'; -import { EuiCallOut } from '@elastic/eui'; -import ReactDOM from 'react-dom'; import $ from 'jquery'; import _, { cloneDeep, forOwn, get, set } from 'lodash'; import React, { Fragment } from 'react'; import * as Rx from 'rxjs'; import { i18n } from '@kbn/i18n'; -import { FormattedMessage, I18nProvider } from '@kbn/i18n/react'; +import { FormattedMessage } from '@kbn/i18n/react'; import { CoreStart, LegacyCoreStart } from 'kibana/public'; import { fatalError } from 'ui/notify'; @@ -45,15 +43,10 @@ import { modifyUrl } from 'ui/url'; import { UrlOverflowService } from '../error_url_overflow'; // @ts-ignore import { isSystemApiRequest } from '../system_api'; -import { DataStart } from '../../../core_plugins/data/public'; const URL_LIMIT_WARN_WITHIN = 1000; -export const configureAppAngularModule = ( - angularModule: IModule, - newPlatform: LegacyCoreStart, - dataStart: DataStart -) => { +export const configureAppAngularModule = (angularModule: IModule, newPlatform: LegacyCoreStart) => { const legacyMetadata = newPlatform.injectedMetadata.getLegacyMetadata(); forOwn(newPlatform.injectedMetadata.getInjectedVars(), (val, name) => { @@ -78,8 +71,7 @@ export const configureAppAngularModule = ( .run($setupBadgeAutoClear(newPlatform)) .run($setupHelpExtensionAutoClear(newPlatform)) .run($setupUrlOverflowHandling(newPlatform)) - .run($setupUICapabilityRedirect(newPlatform)) - .run($setupDefaultIndexRedirect(newPlatform, dataStart)); + .run($setupUICapabilityRedirect(newPlatform)); }; const getEsUrl = (newPlatform: CoreStart) => { @@ -202,91 +194,6 @@ const $setupUICapabilityRedirect = (newPlatform: CoreStart) => ( ); }; -let bannerId: string; -let timeoutId: NodeJS.Timeout | undefined; - -/** - * integrates with angular to automatically redirect to management if no default - * index pattern is configured when a route flag is set. - */ -const $setupDefaultIndexRedirect = (newPlatform: CoreStart, data: DataStart) => ( - $rootScope: IRootScopeService, - $injector: any -) => { - const isKibanaAppRoute = window.location.pathname.endsWith('/app/kibana'); - // this feature only works within kibana app for now after everything is - // switched to the application service, this can be changed to handle all - // apps. - if (!isKibanaAppRoute) { - return; - } - - $rootScope.$on( - '$routeChangeStart', - (event, { $$route: route }: { $$route?: RouteConfiguration } = {}) => { - if (!route || !route.requireDefaultIndex) { - return; - } - - return data.indexPatterns.indexPatterns.getIds().then(function(patterns: string[]) { - let defaultId = newPlatform.uiSettings.get('defaultIndex'); - let defined = !!defaultId; - const exists = _.contains(patterns, defaultId); - - if (defined && !exists) { - newPlatform.uiSettings.remove('defaultIndex'); - defaultId = defined = false; - } - - if (!defined) { - // If there is any index pattern created, set the first as default - if (patterns.length >= 1) { - defaultId = patterns[0]; - newPlatform.uiSettings.set('defaultIndex', defaultId); - } else { - const canManageIndexPatterns = - newPlatform.application.capabilities.management.kibana.index_patterns; - const redirectTarget = canManageIndexPatterns - ? '/management/kibana/index_pattern' - : '/home'; - - $injector.get('kbnUrl').change(redirectTarget); - $rootScope.$digest(); - if (timeoutId) { - clearTimeout(timeoutId); - } - - // Avoid being hostile to new users who don't have an index pattern setup yet - // give them a friendly info message instead of a terse error message - bannerId = newPlatform.overlays.banners.replace(bannerId, (element: HTMLElement) => { - ReactDOM.render( - - - , - element - ); - return () => ReactDOM.unmountComponentAtNode(element); - }); - - // hide the message after the user has had a chance to acknowledge it -- so it doesn't permanently stick around - timeoutId = setTimeout(() => { - newPlatform.overlays.banners.remove(bannerId); - timeoutId = undefined; - }, 15000); - } - } - }); - } - ); -}; - /** * internal angular run function that will be called when angular bootstraps and * lets us integrate with the angular router so that we can automatically clear diff --git a/src/legacy/ui/public/legacy_compat/utils.tsx b/src/legacy/ui/public/legacy_compat/utils.tsx new file mode 100644 index 0000000000000..c1f83035bff4c --- /dev/null +++ b/src/legacy/ui/public/legacy_compat/utils.tsx @@ -0,0 +1,100 @@ +/* + * 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 { contains } from 'lodash'; +import { IRootScopeService } from 'angular'; +import React from 'react'; +import ReactDOM from 'react-dom'; +import { i18n } from '@kbn/i18n'; +import { I18nProvider } from '@kbn/i18n/react'; +import { EuiCallOut } from '@elastic/eui'; +import { CoreStart } from 'kibana/public'; +import { DataStart } from '../../../core_plugins/data/public'; + +let bannerId: string; +let timeoutId: NodeJS.Timeout | undefined; + +/** + * Makes sure a default index pattern is set and defines one otherwise. + * if there are no index patterns, redirect to management page and show + * banner. + */ +export async function ensureDefaultIndexPattern( + newPlatform: CoreStart, + data: DataStart, + $rootScope: IRootScopeService, + kbnUrl: any +) { + const patterns = await data.indexPatterns.indexPatterns.getIds(); + let defaultId = newPlatform.uiSettings.get('defaultIndex'); + let defined = !!defaultId; + const exists = contains(patterns, defaultId); + + if (defined && !exists) { + newPlatform.uiSettings.remove('defaultIndex'); + defaultId = defined = false; + } + + if (!defined) { + // If there is any index pattern created, set the first as default + if (patterns.length >= 1) { + defaultId = patterns[0]; + newPlatform.uiSettings.set('defaultIndex', defaultId); + } else { + const canManageIndexPatterns = + newPlatform.application.capabilities.management.kibana.index_patterns; + const redirectTarget = canManageIndexPatterns ? '/management/kibana/index_pattern' : '/home'; + + if (timeoutId) { + clearTimeout(timeoutId); + } + + // Avoid being hostile to new users who don't have an index pattern setup yet + // give them a friendly info message instead of a terse error message + bannerId = newPlatform.overlays.banners.replace(bannerId, (element: HTMLElement) => { + ReactDOM.render( + + + , + element + ); + return () => ReactDOM.unmountComponentAtNode(element); + }); + + // hide the message after the user has had a chance to acknowledge it -- so it doesn't permanently stick around + timeoutId = setTimeout(() => { + newPlatform.overlays.banners.remove(bannerId); + timeoutId = undefined; + }, 15000); + + kbnUrl.change(redirectTarget); + $rootScope.$digest(); + + // return never-resolving promise to stop resolving and wait for the url change + return new Promise(() => {}); + } + } +} diff --git a/src/legacy/ui/public/routes/__tests__/_route_manager.js b/src/legacy/ui/public/routes/__tests__/_route_manager.js index d6d4c869b4b7e..450bb51f0b0c6 100644 --- a/src/legacy/ui/public/routes/__tests__/_route_manager.js +++ b/src/legacy/ui/public/routes/__tests__/_route_manager.js @@ -119,18 +119,6 @@ describe('routes/route_manager', function () { expect($rp.when.secondCall.args[1]).to.have.property('reloadOnSearch', false); expect($rp.when.lastCall.args[1]).to.have.property('reloadOnSearch', true); }); - - it('sets route.requireDefaultIndex to false by default', function () { - routes.when('/nothing-set'); - routes.when('/no-index-required', { requireDefaultIndex: false }); - routes.when('/index-required', { requireDefaultIndex: true }); - routes.config($rp); - - expect($rp.when.callCount).to.be(3); - expect($rp.when.firstCall.args[1]).to.have.property('requireDefaultIndex', false); - expect($rp.when.secondCall.args[1]).to.have.property('requireDefaultIndex', false); - expect($rp.when.lastCall.args[1]).to.have.property('requireDefaultIndex', true); - }); }); describe('#defaults()', () => { diff --git a/src/legacy/ui/public/routes/route_manager.d.ts b/src/legacy/ui/public/routes/route_manager.d.ts index c47a31eb7be76..749a5b9ee4d96 100644 --- a/src/legacy/ui/public/routes/route_manager.d.ts +++ b/src/legacy/ui/public/routes/route_manager.d.ts @@ -32,7 +32,6 @@ export interface RouteConfiguration { template?: string; k7Breadcrumbs?: (...args: any[]) => ChromeBreadcrumb[]; requireUICapability?: string; - requireDefaultIndex?: boolean; outerAngularWrapperRoute?: boolean; } diff --git a/src/legacy/ui/public/routes/route_manager.js b/src/legacy/ui/public/routes/route_manager.js index ba48984bb45b9..6444ef66fbe47 100644 --- a/src/legacy/ui/public/routes/route_manager.js +++ b/src/legacy/ui/public/routes/route_manager.js @@ -46,10 +46,6 @@ export default function RouteManager() { route.reloadOnSearch = false; } - if (route.requireDefaultIndex == null) { - route.requireDefaultIndex = false; - } - wrapRouteWithPrep(route, setup); $routeProvider.when(path, route); }); From 0dd5d3a5ef00800abf20bd958a4640cf0e9bbacd Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Fri, 25 Oct 2019 12:33:29 +0200 Subject: [PATCH 027/132] rename function and improve documentation --- src/legacy/core_plugins/kibana/public/dashboard/app.js | 2 +- .../kibana/public/discover/angular/discover.js | 2 +- .../kibana/public/visualize/editor/editor.js | 2 +- src/legacy/core_plugins/kibana/public/visualize/index.js | 2 +- .../{utils.tsx => ensure_default_index_pattern.tsx} | 9 ++++++--- src/legacy/ui/public/legacy_compat/index.ts | 1 + 6 files changed, 11 insertions(+), 7 deletions(-) rename src/legacy/ui/public/legacy_compat/{utils.tsx => ensure_default_index_pattern.tsx} (92%) diff --git a/src/legacy/core_plugins/kibana/public/dashboard/app.js b/src/legacy/core_plugins/kibana/public/dashboard/app.js index c8fab451242ba..85eb6da196d07 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/app.js +++ b/src/legacy/core_plugins/kibana/public/dashboard/app.js @@ -19,6 +19,7 @@ import { i18n } from '@kbn/i18n'; import { wrapInI18nContext } from 'ui/i18n'; +import { ensureDefaultIndexPattern } from 'ui/legacy_compat'; import dashboardTemplate from './dashboard_app.html'; import dashboardListingTemplate from './listing/dashboard_listing_ng_wrapper.html'; @@ -32,7 +33,6 @@ import { import { FeatureCatalogueCategory } from 'ui/registry/feature_catalogue'; import { DashboardListing, EMPTY_FILTER } from './listing/dashboard_listing'; import { addHelpMenuToAppChrome } from './help_menu/help_menu_util'; -import { ensureDefaultIndexPattern } from '../../../../ui/public/legacy_compat/utils'; import { start as data } from '../../../data/public/legacy'; export function initDashboardApp(app, deps) { diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/discover.js b/src/legacy/core_plugins/kibana/public/discover/angular/discover.js index e9ad486d70db7..e0d7843d978f1 100644 --- a/src/legacy/core_plugins/kibana/public/discover/angular/discover.js +++ b/src/legacy/core_plugins/kibana/public/discover/angular/discover.js @@ -54,6 +54,7 @@ import { StateProvider } from 'ui/state_management/state'; import { migrateLegacyQuery } from 'ui/utils/migrate_legacy_query'; import { subscribeWithScope } from 'ui/utils/subscribe_with_scope'; import { getFilterGenerator } from 'ui/filter_manager'; +import { ensureDefaultIndexPattern } from 'ui/legacy_compat'; import { getDocLink } from 'ui/documentation_links'; import '../components/fetch_error'; @@ -74,7 +75,6 @@ import { addHelpMenuToAppChrome } from '../components/help_menu/help_menu_util'; import { extractTimeFilter, changeTimeFilter } from '../../../../data/public'; import { start as data } from '../../../../data/public/legacy'; import { npStart } from 'ui/new_platform'; -import { ensureDefaultIndexPattern } from '../../../../../ui/public/legacy_compat/utils'; const { savedQueryService } = data.search.services; diff --git a/src/legacy/core_plugins/kibana/public/visualize/editor/editor.js b/src/legacy/core_plugins/kibana/public/visualize/editor/editor.js index 146a02720a3e1..5b6b953ff5631 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/editor/editor.js +++ b/src/legacy/core_plugins/kibana/public/visualize/editor/editor.js @@ -26,6 +26,7 @@ import 'ui/vis/editors/default/sidebar'; import 'ui/visualize'; import 'ui/collapsible_sidebar'; +import { ensureDefaultIndexPattern } from 'ui/legacy_compat'; import { capabilities } from 'ui/capabilities'; import chrome from 'ui/chrome'; import React from 'react'; @@ -58,7 +59,6 @@ import { start as data } from '../../../../data/public/legacy'; import { start as visualizations } from '../../../../visualizations/public/np_ready/public/legacy'; import { addHelpMenuToAppChrome } from '../help_menu/help_menu_util'; -import { ensureDefaultIndexPattern } from '../../../../../ui/public/legacy_compat/utils'; const { savedQueryService } = data.search.services; diff --git a/src/legacy/core_plugins/kibana/public/visualize/index.js b/src/legacy/core_plugins/kibana/public/visualize/index.js index be39ed6c2be38..64505bb852be8 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/index.js +++ b/src/legacy/core_plugins/kibana/public/visualize/index.js @@ -17,6 +17,7 @@ * under the License. */ +import { ensureDefaultIndexPattern } from 'ui/legacy_compat'; import './editor/editor'; import { i18n } from '@kbn/i18n'; import './saved_visualizations/_saved_vis'; @@ -30,7 +31,6 @@ import { getLandingBreadcrumbs, getWizardStep1Breadcrumbs } from './breadcrumbs' // load directives import '../../../data/public'; -import { ensureDefaultIndexPattern } from '../../../../ui/public/legacy_compat/utils'; import { npStart } from '../../../../ui/public/new_platform'; import { start as data } from '../../../data/public/legacy'; diff --git a/src/legacy/ui/public/legacy_compat/utils.tsx b/src/legacy/ui/public/legacy_compat/ensure_default_index_pattern.tsx similarity index 92% rename from src/legacy/ui/public/legacy_compat/utils.tsx rename to src/legacy/ui/public/legacy_compat/ensure_default_index_pattern.tsx index c1f83035bff4c..daedd9f329ed0 100644 --- a/src/legacy/ui/public/legacy_compat/utils.tsx +++ b/src/legacy/ui/public/legacy_compat/ensure_default_index_pattern.tsx @@ -31,9 +31,12 @@ let bannerId: string; let timeoutId: NodeJS.Timeout | undefined; /** - * Makes sure a default index pattern is set and defines one otherwise. - * if there are no index patterns, redirect to management page and show - * banner. + * Checks whether a default index pattern is set and exists and defines + * one otherwise. + * + * If there are no index patterns, redirect to management page and show + * banner. In this case the promise returned from this function will never + * resolve to wait for the URL change to happen. */ export async function ensureDefaultIndexPattern( newPlatform: CoreStart, diff --git a/src/legacy/ui/public/legacy_compat/index.ts b/src/legacy/ui/public/legacy_compat/index.ts index b29056954051b..ea8932114118e 100644 --- a/src/legacy/ui/public/legacy_compat/index.ts +++ b/src/legacy/ui/public/legacy_compat/index.ts @@ -18,3 +18,4 @@ */ export { configureAppAngularModule } from './angular_config'; +export { ensureDefaultIndexPattern } from './ensure_default_index_pattern'; From 1dc18c7a84fa0b7a2f1708e72d99c81dc09a0280 Mon Sep 17 00:00:00 2001 From: maryia-lapata Date: Fri, 25 Oct 2019 14:35:39 +0300 Subject: [PATCH 028/132] Fix exceptions --- .../visualize/embeddable/visualize_embeddable_factory.tsx | 1 - .../core_plugins/kibana/public/visualize/kibana_services.ts | 1 + .../kibana/public/visualize/listing/visualize_listing.js | 2 +- .../kibana/public/visualize/wizard/new_vis_modal.tsx | 2 +- 4 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable_factory.tsx b/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable_factory.tsx index c69bd25966451..b3ccd55110373 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable_factory.tsx +++ b/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable_factory.tsx @@ -41,7 +41,6 @@ import { getServices, getVisualizeLoader } from '../kibana_services'; const { addBasePath, capabilities, - chrome, embeddable, getInjector, uiSettings, diff --git a/src/legacy/core_plugins/kibana/public/visualize/kibana_services.ts b/src/legacy/core_plugins/kibana/public/visualize/kibana_services.ts index 69333e0768229..a39cbc231bd4f 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/kibana_services.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/kibana_services.ts @@ -55,6 +55,7 @@ const services = { chrome: npStart.core.chrome, docLinks: npStart.core.docLinks, embeddable: npStart.plugins.embeddable, + getBasePath: npStart.core.http.basePath.get, savedObjectsClient: npStart.core.savedObjects.client, toastNotifications: npStart.core.notifications.toasts, uiSettings: npStart.core.uiSettings, diff --git a/src/legacy/core_plugins/kibana/public/visualize/listing/visualize_listing.js b/src/legacy/core_plugins/kibana/public/visualize/listing/visualize_listing.js index 7a0ff15f13511..81f8c0fe5a2b9 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/listing/visualize_listing.js +++ b/src/legacy/core_plugins/kibana/public/visualize/listing/visualize_listing.js @@ -60,7 +60,7 @@ export function VisualizeListingController($injector, createNewVis) { this.editItem = ({ editUrl }) => { // for visualizations the edit and view URLs are the same - window.location = addBasePath(editUrl); + window.location.href = addBasePath(editUrl); }; this.getViewUrl = ({ editUrl }) => { diff --git a/src/legacy/core_plugins/kibana/public/visualize/wizard/new_vis_modal.tsx b/src/legacy/core_plugins/kibana/public/visualize/wizard/new_vis_modal.tsx index 2b36c9af705c2..420f0e5198056 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/wizard/new_vis_modal.tsx +++ b/src/legacy/core_plugins/kibana/public/visualize/wizard/new_vis_modal.tsx @@ -124,7 +124,7 @@ class NewVisModal extends React.Component Date: Fri, 25 Oct 2019 15:05:22 +0300 Subject: [PATCH 029/132] Get rid of injectI18n; fix chrome method invocation --- .../kibana/public/visualize/editor/editor.js | 3 +- .../public/visualize/kibana_services.ts | 1 + .../listing/no_visualizations_prompt.js | 12 +- .../visualize/listing/visualize_listing.js | 3 +- .../listing/visualize_listing_table.js | 129 +++++++----------- 5 files changed, 61 insertions(+), 87 deletions(-) diff --git a/src/legacy/core_plugins/kibana/public/visualize/editor/editor.js b/src/legacy/core_plugins/kibana/public/visualize/editor/editor.js index 5930c96b09273..b0909d16974b6 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/editor/editor.js +++ b/src/legacy/core_plugins/kibana/public/visualize/editor/editor.js @@ -53,6 +53,7 @@ import { const { capabilities, chrome, + chromeLegacy, docTitle, FilterBarQueryFilterProvider, getBasePath, @@ -596,7 +597,7 @@ function VisEditor( // Since we aren't reloading the page, only inserting a new browser history item, we need to manually update // the last url for this app, so directly clicking on the Visualize tab will also bring the user to the saved // url, not the unsaved one. - chrome.trackSubUrlForApp('kibana:visualize', savedVisualizationParsedUrl); + chromeLegacy.trackSubUrlForApp('kibana:visualize', savedVisualizationParsedUrl); const lastDashboardAbsoluteUrl = chrome.navLinks.get('kibana:dashboard').url; const dashboardParsedUrl = absoluteToParsedUrl(lastDashboardAbsoluteUrl, getBasePath()); diff --git a/src/legacy/core_plugins/kibana/public/visualize/kibana_services.ts b/src/legacy/core_plugins/kibana/public/visualize/kibana_services.ts index a39cbc231bd4f..f85b602583335 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/kibana_services.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/kibana_services.ts @@ -63,6 +63,7 @@ const services = { visualizations, // legacy + chromeLegacy, docTitle, FeatureCatalogueRegistryProvider, FilterBarQueryFilterProvider, diff --git a/src/legacy/core_plugins/kibana/public/visualize/listing/no_visualizations_prompt.js b/src/legacy/core_plugins/kibana/public/visualize/listing/no_visualizations_prompt.js index 54a6048a22daf..34b662838880e 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/listing/no_visualizations_prompt.js +++ b/src/legacy/core_plugins/kibana/public/visualize/listing/no_visualizations_prompt.js @@ -18,7 +18,8 @@ */ import React from 'react'; -import { injectI18n, FormattedMessage } from '@kbn/i18n/react'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; import { KuiEmptyTablePrompt, @@ -27,7 +28,7 @@ import { KuiButtonIcon, } from '@kbn/ui-framework/components'; -function NoVisualizationsPromptUi({ onCreateVis, intl }) { +function NoVisualizationsPrompt({ onCreateVis }) { return ( } + icon={} > } - message={intl.formatMessage({ - id: 'kbn.visualize.listing.noVisualizationsText', + message={i18n.translate('kbn.visualize.listing.noVisualizationsText', { defaultMessage: `Looks like you don't have any visualizations. Let's create some!`, })} /> @@ -52,4 +52,4 @@ function NoVisualizationsPromptUi({ onCreateVis, intl }) { ); } -export const NoVisualizationsPrompt = injectI18n(NoVisualizationsPromptUi); +export { NoVisualizationsPrompt }; diff --git a/src/legacy/core_plugins/kibana/public/visualize/listing/visualize_listing.js b/src/legacy/core_plugins/kibana/public/visualize/listing/visualize_listing.js index 81f8c0fe5a2b9..f9e3a1a90115a 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/listing/visualize_listing.js +++ b/src/legacy/core_plugins/kibana/public/visualize/listing/visualize_listing.js @@ -28,6 +28,7 @@ import { getServices } from '../kibana_services'; const { addBasePath, chrome, + chromeLegacy, SavedObjectRegistryProvider, SavedObjectsClientProvider, timefilter, @@ -106,7 +107,7 @@ export function VisualizeListingController($injector, createNewVis) { }) ) .then(() => { - chrome.untrackNavLinksForDeletedSavedObjects(selectedItems.map(item => item.id)); + chromeLegacy.untrackNavLinksForDeletedSavedObjects(selectedItems.map(item => item.id)); }) .catch(error => { toastNotifications.addError(error, { diff --git a/src/legacy/core_plugins/kibana/public/visualize/listing/visualize_listing_table.js b/src/legacy/core_plugins/kibana/public/visualize/listing/visualize_listing_table.js index 67fc70899410b..fbd70a0d8c0f7 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/listing/visualize_listing_table.js +++ b/src/legacy/core_plugins/kibana/public/visualize/listing/visualize_listing_table.js @@ -19,30 +19,22 @@ import React, { Component, Fragment } from 'react'; import PropTypes from 'prop-types'; -import { injectI18n, FormattedMessage } from '@kbn/i18n/react'; +import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; import { TableListView } from './../../table_list_view'; -import { - EuiIcon, - EuiBetaBadge, - EuiLink, - EuiButton, - EuiEmptyPrompt, -} from '@elastic/eui'; +import { EuiIcon, EuiBetaBadge, EuiLink, EuiButton, EuiEmptyPrompt } from '@elastic/eui'; import { getServices } from '../kibana_services'; const { capabilities } = getServices(); -class VisualizeListingTableUi extends Component { - +class VisualizeListingTable extends Component { constructor(props) { super(props); } render() { - const { intl } = this.props; return ( item.canDelete} initialFilter={''} noItemsFragment={this.getNoItemsMessage()} - entityName={ - intl.formatMessage({ - id: 'kbn.visualize.listing.table.entityName', - defaultMessage: 'visualization', - }) - } - entityNamePlural={ - intl.formatMessage({ - id: 'kbn.visualize.listing.table.entityNamePlural', - defaultMessage: 'visualizations', - }) - } - tableListTitle={ - intl.formatMessage({ - id: 'kbn.visualize.listing.table.listTitle', - defaultMessage: 'Visualizations', - }) - } + entityName={i18n.translate('kbn.visualize.listing.table.entityName', { + defaultMessage: 'visualization', + })} + entityNamePlural={i18n.translate('kbn.visualize.listing.table.entityNamePlural', { + defaultMessage: 'visualizations', + })} + tableListTitle={i18n.translate('kbn.visualize.listing.table.listTitle', { + defaultMessage: 'Visualizations', + })} /> ); } getTableColumns() { - const { intl } = this.props; const tableColumns = [ { field: 'title', - name: intl.formatMessage({ - id: 'kbn.visualize.listing.table.titleColumnName', + name: i18n.translate('kbn.visualize.listing.table.titleColumnName', { defaultMessage: 'Title', }), sortable: true, @@ -95,35 +76,29 @@ class VisualizeListingTableUi extends Component { > {field} - ) + ), }, { field: 'typeTitle', - name: intl.formatMessage({ - id: 'kbn.visualize.listing.table.typeColumnName', + name: i18n.translate('kbn.visualize.listing.table.typeColumnName', { defaultMessage: 'Type', }), sortable: true, - render: (field, record) => ( + render: (field, record) => ( {this.renderItemTypeIcon(record)} {record.typeTitle} {this.getBadge(record)} - ) + ), }, { field: 'description', - name: intl.formatMessage({ - id: 'kbn.dashboard.listing.table.descriptionColumnName', + name: i18n.translate('kbn.dashboard.listing.table.descriptionColumnName', { defaultMessage: 'Description', }), sortable: true, - render: (field, record) => ( - - {record.description} - - ) + render: (field, record) => {record.description}, }, ]; @@ -187,19 +162,13 @@ class VisualizeListingTableUi extends Component { />
); - } renderItemTypeIcon(item) { let icon; if (item.image) { icon = ( - + ); } else { icon = ( @@ -217,38 +186,40 @@ class VisualizeListingTableUi extends Component { getBadge(item) { if (item.stage === 'beta') { - return (); + return ( + + ); } else if (item.stage === 'experimental') { - return (); + return ( + + ); } } } -VisualizeListingTableUi.propTypes = { +VisualizeListingTable.propTypes = { deleteItems: PropTypes.func.isRequired, findItems: PropTypes.func.isRequired, createItem: PropTypes.func.isRequired, @@ -257,4 +228,4 @@ VisualizeListingTableUi.propTypes = { listingLimit: PropTypes.number.isRequired, }; -export const VisualizeListingTable = injectI18n(VisualizeListingTableUi); +export { VisualizeListingTable }; From 72fd4acfd20349febc5c154a12ae045085d4f467 Mon Sep 17 00:00:00 2001 From: maryia-lapata Date: Fri, 25 Oct 2019 17:23:56 +0300 Subject: [PATCH 030/132] Update unit tests --- .../wizard/new_vis_modal.test.mocks.ts | 26 ------------------- .../visualize/wizard/new_vis_modal.test.tsx | 26 ++++++++++++++++--- .../type_selection/new_vis_help.test.tsx | 14 ++++++++-- 3 files changed, 34 insertions(+), 32 deletions(-) delete mode 100644 src/legacy/core_plugins/kibana/public/visualize/wizard/new_vis_modal.test.mocks.ts diff --git a/src/legacy/core_plugins/kibana/public/visualize/wizard/new_vis_modal.test.mocks.ts b/src/legacy/core_plugins/kibana/public/visualize/wizard/new_vis_modal.test.mocks.ts deleted file mode 100644 index 04c99a1547ba9..0000000000000 --- a/src/legacy/core_plugins/kibana/public/visualize/wizard/new_vis_modal.test.mocks.ts +++ /dev/null @@ -1,26 +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. - */ - -export const settingsGet = jest.fn(); - -jest.doMock('ui/chrome', () => ({ - getUiSettingsClient: () => ({ - get: settingsGet, - }), -})); diff --git a/src/legacy/core_plugins/kibana/public/visualize/wizard/new_vis_modal.test.tsx b/src/legacy/core_plugins/kibana/public/visualize/wizard/new_vis_modal.test.tsx index 6b2f51c3dcf2b..a69c4e4b5d3d1 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/wizard/new_vis_modal.test.tsx +++ b/src/legacy/core_plugins/kibana/public/visualize/wizard/new_vis_modal.test.tsx @@ -20,14 +20,32 @@ import React from 'react'; import { mountWithIntl } from 'test_utils/enzyme_helpers'; -import { settingsGet } from './new_vis_modal.test.mocks'; - import { NewVisModal } from './new_vis_modal'; - -import { VisType } from 'ui/vis'; import { TypesStart } from '../../../../visualizations/public/np_ready/public/types'; +jest.mock('../kibana_services', () => { + const mock = { + addBasePath: jest.fn(path => `root${path}`), + uiSettings: { get: jest.fn() }, + createUiStatsReporter: () => jest.fn(), + }; + + return { + getServices: () => mock, + VisType: {}, + METRIC_TYPE: 'metricType', + }; +}); + +import { getServices } from '../kibana_services'; + +beforeEach(() => { + jest.clearAllMocks(); +}); + describe('NewVisModal', () => { + const settingsGet = getServices().uiSettings.get; + const defaultVisTypeParams = { hidden: false, visualization: class Controller { diff --git a/src/legacy/core_plugins/kibana/public/visualize/wizard/type_selection/new_vis_help.test.tsx b/src/legacy/core_plugins/kibana/public/visualize/wizard/type_selection/new_vis_help.test.tsx index 7fd1df85cee06..5980430d0f259 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/wizard/type_selection/new_vis_help.test.tsx +++ b/src/legacy/core_plugins/kibana/public/visualize/wizard/type_selection/new_vis_help.test.tsx @@ -22,7 +22,17 @@ import { shallowWithIntl } from 'test_utils/enzyme_helpers'; import { NewVisHelp } from './new_vis_help'; import chrome from 'ui/chrome'; -jest.doMock('ui/chrome'); +jest.mock('../../kibana_services', () => { + return { + getServices: () => ({ + addBasePath: jest.fn(url => `testbasepath${url}`), + }), + }; +}); + +beforeEach(() => { + jest.clearAllMocks(); +}); describe('NewVisHelp', () => { it('should render as expected', () => { @@ -36,7 +46,7 @@ describe('NewVisHelp', () => { aliasUrl: '/my/fancy/new/thing', description: 'Some desc', highlighted: false, - icon: 'wahtever', + icon: 'whatever', name: 'whatever', promotion: { buttonText: 'Do it now!', From 105af0a1f211f9dc18bdae726d87ec0c38d599c8 Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Mon, 28 Oct 2019 12:14:50 +0100 Subject: [PATCH 031/132] redirect unknown urls to default app --- src/legacy/core_plugins/kibana/public/dashboard/app.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/legacy/core_plugins/kibana/public/dashboard/app.js b/src/legacy/core_plugins/kibana/public/dashboard/app.js index 85eb6da196d07..85e73cc7db24d 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/app.js +++ b/src/legacy/core_plugins/kibana/public/dashboard/app.js @@ -206,7 +206,9 @@ export function initDashboardApp(app, deps) { ); }, }, - }); + }) + .when(`dashboard/:tail*?`, { redirectTo: `/${deps.core.injectedMetadata.getInjectedVar('kbnDefaultAppId')}` }) + .when(`dashboards/:tail*?`, { redirectTo: `/${deps.core.injectedMetadata.getInjectedVar('kbnDefaultAppId')}` }); }); deps.FeatureCatalogueRegistryProvider.register(() => { From 65ef3d063aab523b9795430596471217f739b58e Mon Sep 17 00:00:00 2001 From: maryia-lapata Date: Mon, 28 Oct 2019 16:29:32 +0300 Subject: [PATCH 032/132] Fix TS --- .../kibana/public/visualize/wizard/new_vis_modal.test.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/legacy/core_plugins/kibana/public/visualize/wizard/new_vis_modal.test.tsx b/src/legacy/core_plugins/kibana/public/visualize/wizard/new_vis_modal.test.tsx index a69c4e4b5d3d1..99d9590e750fd 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/wizard/new_vis_modal.test.tsx +++ b/src/legacy/core_plugins/kibana/public/visualize/wizard/new_vis_modal.test.tsx @@ -21,6 +21,7 @@ import React from 'react'; import { mountWithIntl } from 'test_utils/enzyme_helpers'; import { NewVisModal } from './new_vis_modal'; +import { VisType } from '../kibana_services'; import { TypesStart } from '../../../../visualizations/public/np_ready/public/types'; jest.mock('../kibana_services', () => { @@ -44,7 +45,7 @@ beforeEach(() => { }); describe('NewVisModal', () => { - const settingsGet = getServices().uiSettings.get; + const settingsGet = getServices().uiSettings.get as jest.Mock; const defaultVisTypeParams = { hidden: false, From d66b1adc46bb15a925f3c4b16d21a01044b67b1f Mon Sep 17 00:00:00 2001 From: maryia-lapata Date: Tue, 29 Oct 2019 09:25:40 +0300 Subject: [PATCH 033/132] Move data and Embeddable import --- .../kibana/public/visualize/editor/editor.js | 2 +- .../embeddable/disabled_lab_embeddable.tsx | 3 ++- .../visualize/embeddable/visualize_embeddable.ts | 10 ++++------ .../embeddable/visualize_embeddable_factory.tsx | 15 ++++++++------- .../kibana/public/visualize/kibana_services.ts | 10 ++++++++++ .../wizard/search_selection/search_selection.tsx | 4 ++-- .../wizard/type_selection/new_vis_help.test.tsx | 5 +---- .../wizard/type_selection/type_selection.tsx | 2 +- 8 files changed, 29 insertions(+), 22 deletions(-) diff --git a/src/legacy/core_plugins/kibana/public/visualize/editor/editor.js b/src/legacy/core_plugins/kibana/public/visualize/editor/editor.js index b0909d16974b6..a617e184c3dc7 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/editor/editor.js +++ b/src/legacy/core_plugins/kibana/public/visualize/editor/editor.js @@ -31,7 +31,6 @@ import { DashboardConstants } from '../../dashboard/dashboard_constants'; import { VisualizeConstants } from '../visualize_constants'; import { getEditBreadcrumbs, getCreateBreadcrumbs } from '../breadcrumbs'; import { extractTimeFilter, changeTimeFilter } from '../../../../data/public'; -import { start as data } from '../../../../data/public/legacy'; import { addHelpMenuToAppChrome } from '../help_menu/help_menu_util'; @@ -54,6 +53,7 @@ const { capabilities, chrome, chromeLegacy, + data, docTitle, FilterBarQueryFilterProvider, getBasePath, diff --git a/src/legacy/core_plugins/kibana/public/visualize/embeddable/disabled_lab_embeddable.tsx b/src/legacy/core_plugins/kibana/public/visualize/embeddable/disabled_lab_embeddable.tsx index 92bd0fa345fa0..065feae045597 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/embeddable/disabled_lab_embeddable.tsx +++ b/src/legacy/core_plugins/kibana/public/visualize/embeddable/disabled_lab_embeddable.tsx @@ -19,7 +19,8 @@ import React from 'react'; import ReactDOM from 'react-dom'; -import { Embeddable, EmbeddableOutput } from '../../../../../../plugins/embeddable/public'; + +import { Embeddable, EmbeddableOutput } from '../kibana_services'; import { DisabledLabVisualization } from './disabled_lab_visualization'; import { VisualizeInput } from './visualize_embeddable'; import { VISUALIZE_EMBEDDABLE_TYPE } from './constants'; diff --git a/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable.ts b/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable.ts index d95264abdd559..caf37fd907e60 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable.ts @@ -23,16 +23,14 @@ import { Subscription } from 'rxjs'; import * as Rx from 'rxjs'; import { Filter } from '@kbn/es-query'; import { TimeRange } from '../../../../../../plugins/data/public'; -import { - EmbeddableInput, - EmbeddableOutput, - Embeddable, - Container, -} from '../../../../../../plugins/embeddable/public'; import { Query, onlyDisabledFiltersChanged } from '../../../../data/public'; import { VISUALIZE_EMBEDDABLE_TYPE } from './constants'; import { + Container, + Embeddable, + EmbeddableInput, + EmbeddableOutput, PersistedState, StaticIndexPattern, VisSavedObject, diff --git a/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable_factory.tsx b/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable_factory.tsx index b3ccd55110373..ff55b02082040 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable_factory.tsx +++ b/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable_factory.tsx @@ -22,12 +22,6 @@ import { i18n } from '@kbn/i18n'; import { Legacy } from 'kibana'; import { SavedObjectAttributes } from 'kibana/server'; -import { - EmbeddableFactory, - ErrorEmbeddable, - Container, - EmbeddableOutput, -} from '../../../../../../plugins/embeddable/public'; import { showNewVisModal } from '../wizard'; import { SavedVisualizations } from '../types'; import { DisabledLabEmbeddable } from './disabled_lab_embeddable'; @@ -36,7 +30,14 @@ import { VisualizeEmbeddable, VisualizeInput, VisualizeOutput } from './visualiz import { VISUALIZE_EMBEDDABLE_TYPE } from './constants'; import { TypesStart } from '../../../../visualizations/public/np_ready/public/types'; -import { getServices, getVisualizeLoader } from '../kibana_services'; +import { + getServices, + Container, + EmbeddableFactory, + EmbeddableOutput, + ErrorEmbeddable, + getVisualizeLoader, +} from '../kibana_services'; const { addBasePath, diff --git a/src/legacy/core_plugins/kibana/public/visualize/kibana_services.ts b/src/legacy/core_plugins/kibana/public/visualize/kibana_services.ts index f85b602583335..29d18c28392f0 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/kibana_services.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/kibana_services.ts @@ -47,6 +47,7 @@ import { SavedObjectRegistryProvider } from 'ui/saved_objects/saved_object_regis import { createUiStatsReporter, METRIC_TYPE } from '../../../ui_metric/public'; import { start as visualizations } from '../../../visualizations/public/np_ready/public/legacy'; +import { start as data } from '../../../data/public/legacy'; const services = { // new platform @@ -60,6 +61,7 @@ const services = { toastNotifications: npStart.core.notifications.toasts, uiSettings: npStart.core.uiSettings, + data, visualizations, // legacy @@ -103,6 +105,14 @@ export { migrateLegacyQuery } from 'ui/utils/migrate_legacy_query'; export { subscribeWithScope } from 'ui/utils/subscribe_with_scope'; export { getVisualizeLoader } from 'ui/visualize/loader'; export { SavedObjectSaveModal } from 'ui/saved_objects/components/saved_object_save_modal'; +export { + Container, + Embeddable, + EmbeddableFactory, + EmbeddableInput, + EmbeddableOutput, + ErrorEmbeddable, +} from '../../../../../plugins/embeddable/public'; // export types export { METRIC_TYPE }; diff --git a/src/legacy/core_plugins/kibana/public/visualize/wizard/search_selection/search_selection.tsx b/src/legacy/core_plugins/kibana/public/visualize/wizard/search_selection/search_selection.tsx index 34c95b43991e8..88dbbb8de7df9 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/wizard/search_selection/search_selection.tsx +++ b/src/legacy/core_plugins/kibana/public/visualize/wizard/search_selection/search_selection.tsx @@ -22,10 +22,10 @@ import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import React from 'react'; -import { VisType } from 'ui/vis'; - import { SavedObjectFinder } from 'ui/saved_objects/components/saved_object_finder'; +import { VisType } from '../../kibana_services'; + interface SearchSelectionProps { onSearchSelected: (searchId: string, searchType: string) => void; visType: VisType; diff --git a/src/legacy/core_plugins/kibana/public/visualize/wizard/type_selection/new_vis_help.test.tsx b/src/legacy/core_plugins/kibana/public/visualize/wizard/type_selection/new_vis_help.test.tsx index 5980430d0f259..382f475669f5d 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/wizard/type_selection/new_vis_help.test.tsx +++ b/src/legacy/core_plugins/kibana/public/visualize/wizard/type_selection/new_vis_help.test.tsx @@ -20,12 +20,11 @@ import React from 'react'; import { shallowWithIntl } from 'test_utils/enzyme_helpers'; import { NewVisHelp } from './new_vis_help'; -import chrome from 'ui/chrome'; jest.mock('../../kibana_services', () => { return { getServices: () => ({ - addBasePath: jest.fn(url => `testbasepath${url}`), + addBasePath: jest.fn((url: string) => `testbasepath${url}`), }), }; }); @@ -36,8 +35,6 @@ beforeEach(() => { describe('NewVisHelp', () => { it('should render as expected', () => { - (chrome.addBasePath as unknown) = (url: string) => `testbasepath${url}`; - expect( shallowWithIntl( Date: Tue, 29 Oct 2019 10:49:42 +0300 Subject: [PATCH 034/132] Import VISUALIZE_EMBEDDABLE_TYPE directly --- .../canvas_plugin_src/expression_types/embeddable_types.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/legacy/plugins/canvas/canvas_plugin_src/expression_types/embeddable_types.ts b/x-pack/legacy/plugins/canvas/canvas_plugin_src/expression_types/embeddable_types.ts index 6efe6bc96dbba..546e8967a7439 100644 --- a/x-pack/legacy/plugins/canvas/canvas_plugin_src/expression_types/embeddable_types.ts +++ b/x-pack/legacy/plugins/canvas/canvas_plugin_src/expression_types/embeddable_types.ts @@ -6,7 +6,7 @@ // @ts-ignore import { MAP_SAVED_OBJECT_TYPE } from '../../../maps/common/constants'; -import { VISUALIZE_EMBEDDABLE_TYPE } from '../../../../../../src/legacy/core_plugins/kibana/public/visualize/embeddable'; +import { VISUALIZE_EMBEDDABLE_TYPE } from '../../../../../../src/legacy/core_plugins/kibana/public/visualize/embeddable/constants'; import { SEARCH_EMBEDDABLE_TYPE } from '../../../../../../src/legacy/core_plugins/kibana/public/discover/embeddable/constants'; export const EmbeddableTypes = { From 8dd28f785f4b0f7cfc686671466b789258f36b84 Mon Sep 17 00:00:00 2001 From: maryia-lapata Date: Tue, 29 Oct 2019 16:02:52 +0300 Subject: [PATCH 035/132] Fix deps --- .../public/visualize/embeddable/visualize_embeddable.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable.ts b/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable.ts index 246e16b50efa6..ef9c9a00f980b 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable.ts @@ -22,8 +22,8 @@ import { EmbeddedVisualizeHandler } from 'ui/visualize/loader/embedded_visualize import { Subscription } from 'rxjs'; import * as Rx from 'rxjs'; import { Filter } from '@kbn/es-query'; -import { TimeRange } from '../../../../../../plugins/data/public'; -import { Query, onlyDisabledFiltersChanged } from '../../../../data/public'; +import { TimeRange, onlyDisabledFiltersChanged } from '../../../../../../plugins/data/public'; +import { Query } from '../../../../data/public'; import { VISUALIZE_EMBEDDABLE_TYPE } from './constants'; import { From f9b47f1675f404f93d30077f65c8561c256ef32e Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Tue, 29 Oct 2019 16:19:54 +0100 Subject: [PATCH 036/132] only wire up local angular once --- .../kibana/public/dashboard/render_app.ts | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/legacy/core_plugins/kibana/public/dashboard/render_app.ts b/src/legacy/core_plugins/kibana/public/dashboard/render_app.ts index 9376d4d87ddb0..fdbe3d44c35a1 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/render_app.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/render_app.ts @@ -18,7 +18,7 @@ */ import { EuiConfirmModal } from '@elastic/eui'; -import angular from 'angular'; +import angular, { IModule } from 'angular'; import { IPrivate } from 'ui/private'; import { Storage } from 'ui/storage'; import { i18nDirective, i18nFilter, I18nProvider } from '@kbn/i18n/src/angular'; @@ -86,12 +86,16 @@ export interface RenderDeps { localStorage: Storage; } +let angularModuleInstance: IModule | null = null; + export const renderApp = (element: HTMLElement, appBasePath: string, deps: RenderDeps) => { - const dashboardAngularModule = createLocalAngularModule(deps.core, deps.dataStart); - // global routing stuff - configureAppAngularModule(dashboardAngularModule, deps.core as LegacyCoreStart); - // custom routing stuff - initDashboardApp(dashboardAngularModule, deps); + if (!angularModuleInstance) { + angularModuleInstance = createLocalAngularModule(deps.core, deps.dataStart); + // global routing stuff + configureAppAngularModule(angularModuleInstance, deps.core as LegacyCoreStart); + // custom routing stuff + initDashboardApp(angularModuleInstance, deps); + } const $injector = mountDashboardApp(appBasePath, element); return () => $injector.get('$rootScope').$destroy(); }; From 1246675763dc9ac013832d067dab13dcbb76a0c6 Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Wed, 30 Oct 2019 17:49:41 +0100 Subject: [PATCH 037/132] fix top nav and dashboard only mode --- .../kibana/public/dashboard/index.ts | 2 ++ .../kibana/public/dashboard/plugin.ts | 11 ++++++++--- .../kibana/public/dashboard/render_app.ts | 17 +++++++++-------- .../dashboard_mode/public/dashboard_viewer.js | 4 ++++ 4 files changed, 23 insertions(+), 11 deletions(-) diff --git a/src/legacy/core_plugins/kibana/public/dashboard/index.ts b/src/legacy/core_plugins/kibana/public/dashboard/index.ts index 7c9954359464a..ea9f98016227e 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/index.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/index.ts @@ -30,6 +30,7 @@ import { DashboardPlugin, LegacyAngularInjectedDependencies } from './plugin'; import { start as data } from '../../../data/public/legacy'; import { localApplicationService } from '../local_application_service'; import { start as embeddables } from '../../../embeddable_api/public/np_ready/public/legacy'; +import { start as navigation } from '../../../navigation/public/legacy'; import './saved_dashboard/saved_dashboards'; import './dashboard_config'; @@ -70,5 +71,6 @@ async function getAngularDependencies(): Promise; + navigation: NavigationStart; } export interface DashboardPluginSetupDependencies { @@ -59,6 +61,7 @@ export class DashboardPlugin implements Plugin { dataStart: DataStart; savedObjectsClient: SavedObjectsClientContract; embeddables: ReturnType; + navigation: NavigationStart; } | null = null; public setup( @@ -74,12 +77,13 @@ export class DashboardPlugin implements Plugin { if (this.startDependencies === null) { throw new Error('not started yet'); } - const { dataStart, savedObjectsClient, embeddables } = this.startDependencies; + const { dataStart, savedObjectsClient, embeddables, navigation } = this.startDependencies; const angularDependencies = await getAngularDependencies(); const deps: RenderDeps = { core: contextCore as LegacyCoreStart, ...legacyServices, ...angularDependencies, + navigation, dataStart, indexPatterns: dataStart.indexPatterns.indexPatterns, savedObjectsClient, @@ -101,12 +105,13 @@ export class DashboardPlugin implements Plugin { start( { savedObjects: { client: savedObjectsClient } }: CoreStart, - { data: dataStart, embeddables }: DashboardPluginStartDependencies + { data: dataStart, embeddables, navigation }: DashboardPluginStartDependencies ) { this.startDependencies = { dataStart, savedObjectsClient, embeddables, + navigation, }; } } diff --git a/src/legacy/core_plugins/kibana/public/dashboard/render_app.ts b/src/legacy/core_plugins/kibana/public/dashboard/render_app.ts index fdbe3d44c35a1..20c4b9ca23e0b 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/render_app.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/render_app.ts @@ -20,7 +20,6 @@ import { EuiConfirmModal } from '@elastic/eui'; import angular, { IModule } from 'angular'; import { IPrivate } from 'ui/private'; -import { Storage } from 'ui/storage'; import { i18nDirective, i18nFilter, I18nProvider } from '@kbn/i18n/src/angular'; // @ts-ignore import { GlobalStateProvider } from 'ui/state_management/global_state'; @@ -42,7 +41,6 @@ import { PromiseServiceCreator } from 'ui/promises/promises'; import { KbnUrlProvider, RedirectWhenMissingProvider } from 'ui/url'; // @ts-ignore import { confirmModalFactory } from 'ui/modals/confirm_modal'; - import { AppMountContext, ChromeStart, @@ -51,6 +49,7 @@ import { UiSettingsClientContract, } from 'kibana/public'; import { configureAppAngularModule } from 'ui/legacy_compat'; +import { Storage } from '../../../../../plugins/kibana_utils/public'; // @ts-ignore import { initDashboardApp } from './app'; @@ -63,11 +62,13 @@ import { } from '../../../data/public'; import { SavedQueryService } from '../../../data/public/search/search_bar/lib/saved_query_service'; import { EmbeddablePublicPlugin } from '../../../../../plugins/embeddable/public'; +import { NavigationStart } from '../../../navigation/public'; export interface RenderDeps { core: LegacyCoreStart; indexPatterns: DataStart['indexPatterns']['indexPatterns']; dataStart: DataStart; + navigation: NavigationStart; queryFilter: any; getUnhashableStates: any; shareContextMenuExtensions: any; @@ -90,7 +91,7 @@ let angularModuleInstance: IModule | null = null; export const renderApp = (element: HTMLElement, appBasePath: string, deps: RenderDeps) => { if (!angularModuleInstance) { - angularModuleInstance = createLocalAngularModule(deps.core, deps.dataStart); + angularModuleInstance = createLocalAngularModule(deps.core, deps.navigation); // global routing stuff configureAppAngularModule(angularModuleInstance, deps.core as LegacyCoreStart); // custom routing stuff @@ -102,7 +103,7 @@ export const renderApp = (element: HTMLElement, appBasePath: string, deps: Rende const mainTemplate = (basePath: string) => `
-
+
`; @@ -124,7 +125,7 @@ function mountDashboardApp(appBasePath: string, element: HTMLElement) { return $injector; } -function createLocalAngularModule(core: AppMountContext['core'], data: DataStart) { +function createLocalAngularModule(core: AppMountContext['core'], navigation: NavigationStart) { createLocalI18nModule(); createLocalPrivateModule(); createLocalPromiseModule(); @@ -132,7 +133,7 @@ function createLocalAngularModule(core: AppMountContext['core'], data: DataStart createLocalKbnUrlModule(); createLocalStateModule(); createLocalPersistedStateModule(); - createLocalTopNavModule(data); + createLocalTopNavModule(navigation); createLocalConfirmModalModule(); createLocalFilterBarModule(); @@ -218,11 +219,11 @@ function createLocalPrivateModule() { angular.module('app/dashboard/Private', []).provider('Private', PrivateProvider); } -function createLocalTopNavModule(data: DataStart) { +function createLocalTopNavModule(navigation: NavigationStart) { angular .module('app/dashboard/TopNav', ['react']) .directive('kbnTopNav', createTopNavDirective) - .directive('kbnTopNavHelper', createTopNavHelper(data.ui)); + .directive('kbnTopNavHelper', createTopNavHelper(navigation.ui)); } function createLocalFilterBarModule() { diff --git a/x-pack/legacy/plugins/dashboard_mode/public/dashboard_viewer.js b/x-pack/legacy/plugins/dashboard_mode/public/dashboard_viewer.js index d39d3fdaa84b8..02d61a86bf9e5 100644 --- a/x-pack/legacy/plugins/dashboard_mode/public/dashboard_viewer.js +++ b/x-pack/legacy/plugins/dashboard_mode/public/dashboard_viewer.js @@ -37,6 +37,8 @@ import 'ui/agg_response'; import 'ui/agg_types'; import 'leaflet'; import { npStart } from 'ui/new_platform'; +import { localApplicationService } from 'plugins/kibana/local_application_service'; + import { showAppRedirectNotification } from 'ui/notify'; import { DashboardConstants, createDashboardEditUrl } from 'plugins/kibana/dashboard/dashboard_constants'; @@ -44,6 +46,8 @@ import { DashboardConstants, createDashboardEditUrl } from 'plugins/kibana/dashb uiModules.get('kibana') .config(dashboardConfigProvider => dashboardConfigProvider.turnHideWriteControlsOn()); +localApplicationService.attachToAngular(routes); + routes.enable(); routes.otherwise({ redirectTo: defaultUrl() }); From a34dd9fc4ed7c66dfa04e486e5d558509705eee3 Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Thu, 31 Oct 2019 09:54:49 +0100 Subject: [PATCH 038/132] decouple frome home shim PR --- .../kibana/public/home/components/home_app.js | 82 +-- .../core_plugins/kibana/public/home/index.js | 61 ++ .../core_plugins/kibana/public/home/index.ts | 81 --- .../kibana/public/home/kibana_services.ts | 116 +-- .../core_plugins/kibana/public/home/plugin.ts | 106 --- .../kibana/public/home/render_app.tsx | 39 - .../cockroachdb_metrics/screenshot.png | Bin 233000 -> 0 bytes .../tutorial_resources/logos/cockroachdb.svg | 666 ------------------ 8 files changed, 155 insertions(+), 996 deletions(-) create mode 100644 src/legacy/core_plugins/kibana/public/home/index.js delete mode 100644 src/legacy/core_plugins/kibana/public/home/index.ts delete mode 100644 src/legacy/core_plugins/kibana/public/home/plugin.ts delete mode 100644 src/legacy/core_plugins/kibana/public/home/render_app.tsx delete mode 100644 src/legacy/core_plugins/kibana/public/home/tutorial_resources/cockroachdb_metrics/screenshot.png delete mode 100644 src/legacy/core_plugins/kibana/public/home/tutorial_resources/logos/cockroachdb.svg diff --git a/src/legacy/core_plugins/kibana/public/home/components/home_app.js b/src/legacy/core_plugins/kibana/public/home/components/home_app.js index 64eac2323b378..005d4bdb0a99e 100644 --- a/src/legacy/core_plugins/kibana/public/home/components/home_app.js +++ b/src/legacy/core_plugins/kibana/public/home/components/home_app.js @@ -18,7 +18,6 @@ */ import React from 'react'; -import { I18nProvider } from '@kbn/i18n/react'; import PropTypes from 'prop-types'; import { Home } from './home'; import { FeatureDirectory } from './feature_directory'; @@ -28,7 +27,6 @@ import { HashRouter as Router, Switch, Route, - Redirect, } from 'react-router-dom'; import { getTutorial } from '../load_tutorials'; import { replaceTemplateStrings } from './tutorial/replace_template_strings'; @@ -49,7 +47,6 @@ export function HomeApp({ directories }) { const isCloudEnabled = getInjected('isCloudEnabled', false); const apmUiEnabled = getInjected('apmUiEnabled', true); const mlEnabled = getInjected('mlEnabled', false); - const defaultAppId = getInjected('kbnDefaultAppId', 'discover'); const renderTutorialDirectory = (props) => { return ( @@ -75,52 +72,43 @@ export function HomeApp({ directories }) { }; return ( - - - - + + + + + - + + - - - - - - - - - - - - + + + ); } diff --git a/src/legacy/core_plugins/kibana/public/home/index.js b/src/legacy/core_plugins/kibana/public/home/index.js new file mode 100644 index 0000000000000..01f94b8ee4368 --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/home/index.js @@ -0,0 +1,61 @@ +/* + * 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 { getServices } from './kibana_services'; +import template from './home_ng_wrapper.html'; +import { + HomeApp +} from './components/home_app'; +import { i18n } from '@kbn/i18n'; + +const { wrapInI18nContext, uiRoutes, uiModules } = getServices(); + +const app = uiModules.get('apps/home', []); +app.directive('homeApp', function (reactDirective) { + return reactDirective(wrapInI18nContext(HomeApp)); +}); + +const homeTitle = i18n.translate('kbn.home.breadcrumbs.homeTitle', { defaultMessage: 'Home' }); + +function getRoute() { + return { + template, + resolve: { + directories: () => getServices().getFeatureCatalogueEntries() + }, + controller($scope, $route) { + const { chrome, addBasePath } = getServices(); + $scope.directories = $route.current.locals.directories; + $scope.recentlyAccessed = chrome.recentlyAccessed.get().map(item => { + item.link = addBasePath(item.link); + return item; + }); + }, + k7Breadcrumbs: () => [ + { text: homeTitle }, + ] + }; +} + +// All routing will be handled inside HomeApp via react, we just need to make sure angular doesn't +// redirect us to the default page by encountering a url it isn't marked as being able to handle. +uiRoutes.when('/home', getRoute()); +uiRoutes.when('/home/feature_directory', getRoute()); +uiRoutes.when('/home/tutorial_directory/:tab?', getRoute()); +uiRoutes.when('/home/tutorial/:id', getRoute()); diff --git a/src/legacy/core_plugins/kibana/public/home/index.ts b/src/legacy/core_plugins/kibana/public/home/index.ts deleted file mode 100644 index db0b317e06ca1..0000000000000 --- a/src/legacy/core_plugins/kibana/public/home/index.ts +++ /dev/null @@ -1,81 +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 { FeatureCatalogueRegistryProvider } from 'ui/registry/feature_catalogue'; -import { npSetup, npStart } from 'ui/new_platform'; -import chrome from 'ui/chrome'; -import { IPrivate } from 'ui/private'; -// @ts-ignore -import { toastNotifications, banners } from 'ui/notify'; -import { kfetch } from 'ui/kfetch'; -import { HomePlugin, LegacyAngularInjectedDependencies } from './plugin'; -import { createUiStatsReporter, METRIC_TYPE } from '../../../ui_metric/public'; -import { start as data } from '../../../data/public/legacy'; -import { TelemetryOptInProvider } from '../../../telemetry/public/services'; -import { localApplicationService } from '../local_application_service'; - -export const trackUiMetric = createUiStatsReporter('Kibana_home'); - -/** - * Get dependencies relying on the global angular context. - * They also have to get resolved together with the legacy imports above - */ -async function getAngularDependencies(): Promise { - const injector = await chrome.dangerouslyGetActiveInjector(); - - const Private = injector.get('Private'); - - const telemetryEnabled = npStart.core.injectedMetadata.getInjectedVar('telemetryEnabled'); - const telemetryBanner = npStart.core.injectedMetadata.getInjectedVar('telemetryBanner'); - const telemetryOptInProvider = Private(TelemetryOptInProvider); - - return { - telemetryOptInProvider, - shouldShowTelemetryOptIn: - telemetryEnabled && telemetryBanner && !telemetryOptInProvider.getOptIn(), - }; -} - -(async () => { - const instance = new HomePlugin(); - instance.setup(npSetup.core, { - __LEGACY: { - trackUiMetric, - toastNotifications, - banners, - kfetch, - metadata: npStart.core.injectedMetadata.getLegacyMetadata(), - METRIC_TYPE, - getFeatureCatalogueEntries: async () => { - const injector = await chrome.dangerouslyGetActiveInjector(); - const Private = injector.get('Private'); - // Merge legacy registry with new registry - (Private(FeatureCatalogueRegistryProvider as any) as any).inTitleOrder.map( - npSetup.plugins.feature_catalogue.register - ); - return npStart.plugins.feature_catalogue.get(); - }, - getAngularDependencies, - localApplicationService, - }, - }); - instance.start(npStart.core, { - data, - }); -})(); diff --git a/src/legacy/core_plugins/kibana/public/home/kibana_services.ts b/src/legacy/core_plugins/kibana/public/home/kibana_services.ts index d094885526a5c..b9f2ae1cfa7e8 100644 --- a/src/legacy/core_plugins/kibana/public/home/kibana_services.ts +++ b/src/legacy/core_plugins/kibana/public/home/kibana_services.ts @@ -17,67 +17,69 @@ * under the License. */ -import { ToastNotifications } from 'ui/notify/toasts/toast_notifications'; -import { - ChromeStart, - DocLinksStart, - LegacyNavLink, - SavedObjectsClientContract, - UiSettingsClientContract, - UiSettingsState, -} from 'kibana/public'; -import { KFetchOptions } from 'ui/kfetch'; -import { KFetchKibanaOptions } from 'ui/kfetch/kfetch'; -import { UiStatsMetricType } from '@kbn/analytics'; -import { FeatureCatalogueEntry } from '../../../../../plugins/feature_catalogue/public'; +// @ts-ignore +import { toastNotifications, banners } from 'ui/notify'; +import { kfetch } from 'ui/kfetch'; +import chrome from 'ui/chrome'; -export interface HomeKibanaServices { - indexPatternService: any; - getFeatureCatalogueEntries: () => Promise; - metadata: { - app: unknown; - bundleId: string; - nav: LegacyNavLink[]; - version: string; - branch: string; - buildNum: number; - buildSha: string; - basePath: string; - serverName: string; - devMode: boolean; - uiSettings: { defaults: UiSettingsState; user?: UiSettingsState | undefined }; - }; - getInjected: (name: string, defaultValue?: any) => unknown; - chrome: ChromeStart; - telemetryOptInProvider: any; - uiSettings: UiSettingsClientContract; - kfetch: (options: KFetchOptions, kfetchOptions?: KFetchKibanaOptions) => Promise; - savedObjectsClient: SavedObjectsClientContract; - toastNotifications: ToastNotifications; - banners: any; - METRIC_TYPE: any; - trackUiMetric: (type: UiStatsMetricType, eventNames: string | string[], count?: number) => void; - getBasePath: () => string; - shouldShowTelemetryOptIn: boolean; - docLinks: DocLinksStart; - addBasePath: (url: string) => string; -} +import { wrapInI18nContext } from 'ui/i18n'; -let services: HomeKibanaServices | null = null; +// @ts-ignore +import { uiModules as modules } from 'ui/modules'; +import routes from 'ui/routes'; +import { npSetup, npStart } from 'ui/new_platform'; +import { IPrivate } from 'ui/private'; +import { FeatureCatalogueRegistryProvider } from 'ui/registry/feature_catalogue'; +import { createUiStatsReporter, METRIC_TYPE } from '../../../ui_metric/public'; +import { TelemetryOptInProvider } from '../../../telemetry/public/services'; +import { start as data } from '../../../data/public/legacy'; -export function setServices(newServices: HomeKibanaServices) { - services = newServices; -} +let shouldShowTelemetryOptIn: boolean; +let telemetryOptInProvider: any; export function getServices() { - if (!services) { - throw new Error( - 'Kibana services not set - are you trying to import this module from outside of the home app?' - ); - } - return services; -} + return { + getInjected: npStart.core.injectedMetadata.getInjectedVar, + metadata: npStart.core.injectedMetadata.getLegacyMetadata(), + docLinks: npStart.core.docLinks, + + uiRoutes: routes, + uiModules: modules, + + savedObjectsClient: npStart.core.savedObjects.client, + chrome: npStart.core.chrome, + uiSettings: npStart.core.uiSettings, + addBasePath: npStart.core.http.basePath.prepend, + getBasePath: npStart.core.http.basePath.get, -export function clearServices() { - services = null; + indexPatternService: data.indexPatterns.indexPatterns, + shouldShowTelemetryOptIn, + telemetryOptInProvider, + getFeatureCatalogueEntries: async () => { + const injector = await chrome.dangerouslyGetActiveInjector(); + const Private = injector.get('Private'); + // Merge legacy registry with new registry + (Private(FeatureCatalogueRegistryProvider as any) as any).inTitleOrder.map( + npSetup.plugins.feature_catalogue.register + ); + return npStart.plugins.feature_catalogue.get(); + }, + + trackUiMetric: createUiStatsReporter('Kibana_home'), + METRIC_TYPE, + + toastNotifications, + banners, + kfetch, + wrapInI18nContext, + }; } + +modules.get('kibana').run((Private: IPrivate) => { + const telemetryEnabled = npStart.core.injectedMetadata.getInjectedVar('telemetryEnabled'); + const telemetryBanner = npStart.core.injectedMetadata.getInjectedVar('telemetryBanner'); + + telemetryOptInProvider = Private(TelemetryOptInProvider); + shouldShowTelemetryOptIn = + telemetryEnabled && telemetryBanner && !telemetryOptInProvider.getOptIn(); +}); diff --git a/src/legacy/core_plugins/kibana/public/home/plugin.ts b/src/legacy/core_plugins/kibana/public/home/plugin.ts deleted file mode 100644 index 916c6e05f61be..0000000000000 --- a/src/legacy/core_plugins/kibana/public/home/plugin.ts +++ /dev/null @@ -1,106 +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 { CoreSetup, CoreStart, LegacyNavLink, Plugin, UiSettingsState } from 'kibana/public'; -import { UiStatsMetricType } from '@kbn/analytics'; -import { ToastNotifications } from 'ui/notify/toasts/toast_notifications'; -import { KFetchOptions } from 'ui/kfetch'; -import { KFetchKibanaOptions } from 'ui/kfetch/kfetch'; - -import { DataStart } from '../../../data/public'; -import { LocalApplicationService } from '../local_application_service'; -import { setServices } from './kibana_services'; -import { FeatureCatalogueEntry } from '../../../../../plugins/feature_catalogue/public'; - -export interface LegacyAngularInjectedDependencies { - telemetryOptInProvider: any; - shouldShowTelemetryOptIn: boolean; -} - -export interface HomePluginStartDependencies { - data: DataStart; -} - -export interface HomePluginSetupDependencies { - __LEGACY: { - trackUiMetric: (type: UiStatsMetricType, eventNames: string | string[], count?: number) => void; - toastNotifications: ToastNotifications; - banners: any; - METRIC_TYPE: any; - kfetch: (options: KFetchOptions, kfetchOptions?: KFetchKibanaOptions) => Promise; - metadata: { - app: unknown; - bundleId: string; - nav: LegacyNavLink[]; - version: string; - branch: string; - buildNum: number; - buildSha: string; - basePath: string; - serverName: string; - devMode: boolean; - uiSettings: { defaults: UiSettingsState; user?: UiSettingsState | undefined }; - }; - getFeatureCatalogueEntries: () => Promise; - getAngularDependencies: () => Promise; - localApplicationService: LocalApplicationService; - }; -} - -export class HomePlugin implements Plugin { - private dataStart: DataStart | null = null; - private savedObjectsClient: any = null; - - setup( - core: CoreSetup, - { - __LEGACY: { localApplicationService, getAngularDependencies, ...legacyServices }, - }: HomePluginSetupDependencies - ) { - localApplicationService.register({ - id: 'home', - title: 'Home', - mount: async ({ core: contextCore }, params) => { - const angularDependencies = await getAngularDependencies(); - setServices({ - ...legacyServices, - getInjected: core.injectedMetadata.getInjectedVar, - docLinks: contextCore.docLinks, - savedObjectsClient: this.savedObjectsClient!, - chrome: contextCore.chrome, - uiSettings: core.uiSettings, - addBasePath: core.http.basePath.prepend, - getBasePath: core.http.basePath.get, - indexPatternService: this.dataStart!.indexPatterns.indexPatterns, - ...angularDependencies, - }); - const { renderApp } = await import('./render_app'); - return await renderApp(params.element); - }, - }); - } - - start(core: CoreStart, { data }: HomePluginStartDependencies) { - // TODO is this really the right way? I though the app context would give us those - this.dataStart = data; - this.savedObjectsClient = core.savedObjects.client; - } - - stop() {} -} diff --git a/src/legacy/core_plugins/kibana/public/home/render_app.tsx b/src/legacy/core_plugins/kibana/public/home/render_app.tsx deleted file mode 100644 index 50041410d3c6f..0000000000000 --- a/src/legacy/core_plugins/kibana/public/home/render_app.tsx +++ /dev/null @@ -1,39 +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 React from 'react'; -import { render, unmountComponentAtNode } from 'react-dom'; -import { i18n } from '@kbn/i18n'; -// @ts-ignore -import { HomeApp } from './components/home_app'; -import { clearServices, getServices } from './kibana_services'; - -export const renderApp = async (element: HTMLElement) => { - const homeTitle = i18n.translate('kbn.home.breadcrumbs.homeTitle', { defaultMessage: 'Home' }); - const { getFeatureCatalogueRegistryProvider, chrome } = getServices(); - const directories = (await getFeatureCatalogueRegistryProvider()).inTitleOrder; - chrome.setBreadcrumbs([{ text: homeTitle }]); - - render(, element); - - return () => { - unmountComponentAtNode(element); - clearServices(); - }; -}; diff --git a/src/legacy/core_plugins/kibana/public/home/tutorial_resources/cockroachdb_metrics/screenshot.png b/src/legacy/core_plugins/kibana/public/home/tutorial_resources/cockroachdb_metrics/screenshot.png deleted file mode 100644 index 4b3020d91d57da293c2bd62dae6f5265ac726251..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 233000 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�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
@@ -38,7 +38,7 @@ show-search-bar="true" show-query-bar="true" show-query-input="showQueryInput()" - show-filter-bar="showFilterBar() && chrome.getVisible()" + show-filter-bar="showFilterBar() && isVisible" show-date-picker="showQueryBarTimePicker()" show-auto-refresh-only="!showQueryBarTimePicker()" query="state.query" @@ -65,7 +65,7 @@ we show the filter bar on its own here if the chrome is not visible. --> { - if (savedVis.vis.type.setup) { - return savedVis.vis.type.setup(savedVis) - .catch(() => savedVis); + $scope.topNavMenu = [...(visualizeCapabilities.save ? [{ + id: 'save', + label: i18n.translate('kbn.topNavMenu.saveVisualizationButtonLabel', { defaultMessage: 'save' }), + description: i18n.translate('kbn.visualize.topNavMenu.saveVisualizationButtonAriaLabel', { + defaultMessage: 'Save Visualization', + }), + testId: 'visualizeSaveButton', + disableButton() { + return Boolean(vis.dirty); + }, + tooltip() { + if (vis.dirty) { + return i18n.translate('kbn.visualize.topNavMenu.saveVisualizationDisabledButtonTooltip', { + defaultMessage: 'Apply or Discard your changes before saving' + }); } - return savedVis; - }) - .catch(redirectWhenMissing({ - '*': '/visualize' - })); - } - } - }) - .when(`${VisualizeConstants.EDIT_PATH}/:id`, { - template: editorTemplate, - k7Breadcrumbs: getEditBreadcrumbs, - resolve: { - savedVis: function (savedVisualizations, redirectWhenMissing, $route) { - return savedVisualizations.get($route.current.params.id) - .then((savedVis) => { - chrome.recentlyAccessed.add( - savedVis.getFullPath(), - savedVis.title, - savedVis.id); - return savedVis; - }) - .then(savedVis => { - if (savedVis.vis.type.setup) { - return savedVis.vis.type.setup(savedVis) - .catch(() => savedVis); + }, + run: async () => { + const onSave = ({ newTitle, newCopyOnSave, isTitleDuplicateConfirmed, onTitleDuplicate, newDescription }) => { + const currentTitle = savedVis.title; + savedVis.title = newTitle; + savedVis.copyOnSave = newCopyOnSave; + savedVis.description = newDescription; + const saveOptions = { + confirmOverwrite: false, + isTitleDuplicateConfirmed, + onTitleDuplicate, + }; + return doSave(saveOptions).then((response) => { + // If the save wasn't successful, put the original values back. + if (!response.id || response.error) { + savedVis.title = currentTitle; + } + return response; + }); + }; + + const confirmButtonLabel = $scope.isAddToDashMode() ? ( + + ) : null; + + const saveModal = ( + {}} + title={savedVis.title} + showCopyOnSave={savedVis.id ? true : false} + objectType="visualization" + confirmButtonLabel={confirmButtonLabel} + description={savedVis.description} + />); + showSaveModal(saveModal); + } + }] : []), { + id: 'share', + label: i18n.translate('kbn.topNavMenu.shareVisualizationButtonLabel', { defaultMessage: 'share' }), + description: i18n.translate('kbn.visualize.topNavMenu.shareVisualizationButtonAriaLabel', { + defaultMessage: 'Share Visualization', + }), + testId: 'shareTopNavButton', + run: (anchorElement) => { + const hasUnappliedChanges = vis.dirty; + const hasUnsavedChanges = $appStatus.dirty; + showShareContextMenu({ + anchorElement, + allowEmbed: true, + allowShortUrl: visualizeCapabilities.createShortUrl, + getUnhashableStates: deps.getUnhashableStates, + objectId: savedVis.id, + objectType: 'visualization', + shareContextMenuExtensions: deps.shareContextMenuExtensions, + sharingData: { + title: savedVis.title, + }, + isDirty: hasUnappliedChanges || hasUnsavedChanges, + }); + } + }, { + id: 'inspector', + label: i18n.translate('kbn.topNavMenu.openInspectorButtonLabel', { defaultMessage: 'inspect' }), + description: i18n.translate('kbn.visualize.topNavMenu.openInspectorButtonAriaLabel', { + defaultMessage: 'Open Inspector for visualization', + }), + testId: 'openInspectorButton', + disableButton() { + return !vis.hasInspector || !vis.hasInspector(); + }, + run() { + const inspectorSession = vis.openInspector(); + // Close the inspector if this scope is destroyed (e.g. because the user navigates away). + const removeWatch = $scope.$on('$destroy', () => inspectorSession.close()); + // Remove that watch in case the user closes the inspector session herself. + inspectorSession.onClose.finally(removeWatch); + }, + tooltip() { + if (!vis.hasInspector || !vis.hasInspector()) { + return i18n.translate('kbn.visualize.topNavMenu.openInspectorDisabledButtonTooltip', { + defaultMessage: `This visualization doesn't support any inspectors.`, + }); } - return savedVis; - }) - .catch(redirectWhenMissing({ - 'visualization': '/visualize', - 'search': '/management/kibana/objects/savedVisualizations/' + $route.current.params.id, - 'index-pattern': '/management/kibana/objects/savedVisualizations/' + $route.current.params.id, - 'index-pattern-field': '/management/kibana/objects/savedVisualizations/' + $route.current.params.id - })); - } - } - }); + } + }, { + id: 'refresh', + label: i18n.translate('kbn.topNavMenu.refreshButtonLabel', { defaultMessage: 'refresh' }), + description: i18n.translate('kbn.visualize.topNavMenu.refreshButtonAriaLabel', { + defaultMessage: 'Refresh', + }), + run: function () { + vis.forceReload(); + }, + testId: 'visualizeRefreshButton', + }]; -uiModules - .get('app/visualize', [ - 'kibana/url' - ]) - .directive('visualizeApp', function () { - return { - restrict: 'E', - controllerAs: 'visualizeApp', - controller: VisEditor, - }; - }); + let stateMonitor; -function VisEditor( - $scope, - $element, - $route, - AppState, - $window, - $injector, - $timeout, - indexPatterns, - kbnUrl, - redirectWhenMissing, - Private, - Promise, - config, - kbnBaseUrl, - localStorage, -) { - const queryFilter = Private(FilterBarQueryFilterProvider); - const getUnhashableStates = Private(getUnhashableStatesProvider); - const shareContextMenuExtensions = Private(ShareContextMenuExtensionsRegistryProvider); - - // Retrieve the resolved SavedVis instance. - const savedVis = $route.current.locals.savedVis; - // vis is instance of src/legacy/ui/public/vis/vis.js. - // SearchSource is a promise-based stream of search results that can inherit from other search sources. - const { vis, searchSource } = savedVis; - - $scope.vis = vis; - - const $appStatus = this.appStatus = { - dirty: !savedVis.id - }; - - $scope.topNavMenu = [...(capabilities.visualize.save ? [{ - id: 'save', - label: i18n.translate('kbn.topNavMenu.saveVisualizationButtonLabel', { defaultMessage: 'save' }), - description: i18n.translate('kbn.visualize.topNavMenu.saveVisualizationButtonAriaLabel', { - defaultMessage: 'Save Visualization', - }), - testId: 'visualizeSaveButton', - disableButton() { - return Boolean(vis.dirty); - }, - tooltip() { - if (vis.dirty) { - return i18n.translate('kbn.visualize.topNavMenu.saveVisualizationDisabledButtonTooltip', { - defaultMessage: 'Apply or Discard your changes before saving' - }); - } - }, - run: async () => { - const onSave = ({ newTitle, newCopyOnSave, isTitleDuplicateConfirmed, onTitleDuplicate, newDescription }) => { - const currentTitle = savedVis.title; - savedVis.title = newTitle; - savedVis.copyOnSave = newCopyOnSave; - savedVis.description = newDescription; - const saveOptions = { - confirmOverwrite: false, - isTitleDuplicateConfirmed, - onTitleDuplicate, + if (savedVis.id) { + docTitle.change(savedVis.title); + } + + // Extract visualization state with filtered aggs. You can see these filtered aggs in the URL. + // Consists of things like aggs, params, listeners, title, type, etc. + const savedVisState = vis.getState(); + const stateDefaults = { + uiState: savedVis.uiStateJSON ? JSON.parse(savedVis.uiStateJSON) : {}, + linked: !!savedVis.savedSearchId, + query: searchSource.getOwnField('query') || { + query: '', + language: localStorage.get('kibana.userQueryLanguage') || config.get('search:queryLanguage') + }, + filters: searchSource.getOwnField('filter') || [], + vis: savedVisState }; - return doSave(saveOptions).then((response) => { - // If the save wasn't successful, put the original values back. - if (!response.id || response.error) { - savedVis.title = currentTitle; + + // Instance of app_state.js. + const $state = (function initState() { + // This is used to sync visualization state with the url when `appState.save()` is called. + const appState = new AppState(stateDefaults); + + // Initializing appState does two things - first it translates the defaults into AppState, + // second it updates appState based on the url (the url trumps the defaults). This means if + // we update the state format at all and want to handle BWC, we must not only migrate the + // data stored with saved vis, but also any old state in the url. + migrateAppState(appState); + + // The savedVis is pulled from elasticsearch, but the appState is pulled from the url, with the + // defaults applied. If the url was from a previous session which included modifications to the + // appState then they won't be equal. + if (!angular.equals(appState.vis, savedVisState)) { + Promise.try(function () { + vis.setState(appState.vis); + }) + .catch(redirectWhenMissing({ + 'index-pattern-field': '/visualize' + })); + } + + return appState; + }()); + + $scope.filters = queryFilter.getFilters(); + + $scope.onFiltersUpdated = filters => { + // The filters will automatically be set when the queryFilter emits an update event (see below) + queryFilter.setFilters(filters); + }; + + $scope.onCancelApplyFilters = () => { + $scope.state.$newFilters = []; + }; + + $scope.onApplyFilters = filters => { + const { timeRangeFilter, restOfFilters } = extractTimeFilter($scope.indexPattern.timeFieldName, filters); + queryFilter.addFilters(restOfFilters); + if (timeRangeFilter) changeTimeFilter(timefilter, timeRangeFilter); + $scope.state.$newFilters = []; + }; + + $scope.$watch('state.$newFilters', (filters = []) => { + if (filters.length === 1) { + $scope.onApplyFilters(filters); } - return response; }); - }; - - const confirmButtonLabel = $scope.isAddToDashMode() ? ( - - ) : null; - - const saveModal = ( - {}} - title={savedVis.title} - showCopyOnSave={savedVis.id ? true : false} - objectType="visualization" - confirmButtonLabel={confirmButtonLabel} - description={savedVis.description} - />); - showSaveModal(saveModal); - } - }] : []), { - id: 'share', - label: i18n.translate('kbn.topNavMenu.shareVisualizationButtonLabel', { defaultMessage: 'share' }), - description: i18n.translate('kbn.visualize.topNavMenu.shareVisualizationButtonAriaLabel', { - defaultMessage: 'Share Visualization', - }), - testId: 'shareTopNavButton', - run: (anchorElement) => { - const hasUnappliedChanges = vis.dirty; - const hasUnsavedChanges = $appStatus.dirty; - showShareContextMenu({ - anchorElement, - allowEmbed: true, - allowShortUrl: capabilities.visualize.createShortUrl, - getUnhashableStates, - objectId: savedVis.id, - objectType: 'visualization', - shareContextMenuExtensions, - sharingData: { - title: savedVis.title, - }, - isDirty: hasUnappliedChanges || hasUnsavedChanges, - }); - } - }, { - id: 'inspector', - label: i18n.translate('kbn.topNavMenu.openInspectorButtonLabel', { defaultMessage: 'inspect' }), - description: i18n.translate('kbn.visualize.topNavMenu.openInspectorButtonAriaLabel', { - defaultMessage: 'Open Inspector for visualization', - }), - testId: 'openInspectorButton', - disableButton() { - return !vis.hasInspector || !vis.hasInspector(); - }, - run() { - const inspectorSession = vis.openInspector(); - // Close the inspector if this scope is destroyed (e.g. because the user navigates away). - const removeWatch = $scope.$on('$destroy', () => inspectorSession.close()); - // Remove that watch in case the user closes the inspector session herself. - inspectorSession.onClose.finally(removeWatch); - }, - tooltip() { - if (!vis.hasInspector || !vis.hasInspector()) { - return i18n.translate('kbn.visualize.topNavMenu.openInspectorDisabledButtonTooltip', { - defaultMessage: `This visualization doesn't support any inspectors.`, + + $scope.showSaveQuery = visualizeCapabilities.saveQuery; + + $scope.$watch(() => visualizeCapabilities.saveQuery, (newCapability) => { + $scope.showSaveQuery = newCapability; }); - } - } - }, { - id: 'refresh', - label: i18n.translate('kbn.topNavMenu.refreshButtonLabel', { defaultMessage: 'refresh' }), - description: i18n.translate('kbn.visualize.topNavMenu.refreshButtonAriaLabel', { - defaultMessage: 'Refresh', - }), - run: function () { - vis.forceReload(); - }, - testId: 'visualizeRefreshButton', - }]; - - let stateMonitor; - - if (savedVis.id) { - docTitle.change(savedVis.title); - } - - // Extract visualization state with filtered aggs. You can see these filtered aggs in the URL. - // Consists of things like aggs, params, listeners, title, type, etc. - const savedVisState = vis.getState(); - const stateDefaults = { - uiState: savedVis.uiStateJSON ? JSON.parse(savedVis.uiStateJSON) : {}, - linked: !!savedVis.savedSearchId, - query: searchSource.getOwnField('query') || { - query: '', - language: localStorage.get('kibana.userQueryLanguage') || config.get('search:queryLanguage') - }, - filters: searchSource.getOwnField('filter') || [], - vis: savedVisState - }; - - // Instance of app_state.js. - const $state = (function initState() { - // This is used to sync visualization state with the url when `appState.save()` is called. - const appState = new AppState(stateDefaults); - - // Initializing appState does two things - first it translates the defaults into AppState, - // second it updates appState based on the url (the url trumps the defaults). This means if - // we update the state format at all and want to handle BWC, we must not only migrate the - // data stored with saved vis, but also any old state in the url. - migrateAppState(appState); - - // The savedVis is pulled from elasticsearch, but the appState is pulled from the url, with the - // defaults applied. If the url was from a previous session which included modifications to the - // appState then they won't be equal. - if (!angular.equals(appState.vis, savedVisState)) { - Promise.try(function () { - vis.setState(appState.vis); - }) - .catch(redirectWhenMissing({ - 'index-pattern-field': '/visualize' - })); - } - - return appState; - }()); - - $scope.filters = queryFilter.getFilters(); - - $scope.onFiltersUpdated = filters => { - // The filters will automatically be set when the queryFilter emits an update event (see below) - queryFilter.setFilters(filters); - }; - - $scope.onCancelApplyFilters = () => { - $scope.state.$newFilters = []; - }; - - $scope.onApplyFilters = filters => { - const { timeRangeFilter, restOfFilters } = extractTimeFilter($scope.indexPattern.timeFieldName, filters); - queryFilter.addFilters(restOfFilters); - if (timeRangeFilter) changeTimeFilter(timefilter, timeRangeFilter); - $scope.state.$newFilters = []; - }; - - $scope.$watch('state.$newFilters', (filters = []) => { - if (filters.length === 1) { - $scope.onApplyFilters(filters); - } - }); - $scope.showSaveQuery = capabilities.visualize.saveQuery; + function init() { + // export some objects + $scope.savedVis = savedVis; + if (vis.indexPattern) { + $scope.indexPattern = vis.indexPattern; + } else { + indexPatterns.getDefault().then(defaultIndexPattern => { + $scope.indexPattern = defaultIndexPattern; + }); + } - $scope.$watch(() => capabilities.visualize.saveQuery, (newCapability) => { - $scope.showSaveQuery = newCapability; - }); + $scope.searchSource = searchSource; + $scope.state = $state; + $scope.refreshInterval = timefilter.getRefreshInterval(); - function init() { - // export some objects - $scope.savedVis = savedVis; - if (vis.indexPattern) { - $scope.indexPattern = vis.indexPattern; - } else { - indexPatterns.getDefault().then(defaultIndexPattern => { - $scope.indexPattern = defaultIndexPattern; - }); - } + // Create a PersistedState instance. + $scope.uiState = $state.makeStateful('uiState'); + $scope.appStatus = $appStatus; - $scope.searchSource = searchSource; - $scope.state = $state; - $scope.refreshInterval = timefilter.getRefreshInterval(); + const addToDashMode = $route.current.params[DashboardConstants.ADD_VISUALIZATION_TO_DASHBOARD_MODE_PARAM]; + kbnUrl.removeParam(DashboardConstants.ADD_VISUALIZATION_TO_DASHBOARD_MODE_PARAM); - // Create a PersistedState instance. - $scope.uiState = $state.makeStateful('uiState'); - $scope.appStatus = $appStatus; + $scope.isAddToDashMode = () => addToDashMode; - const addToDashMode = $route.current.params[DashboardConstants.ADD_VISUALIZATION_TO_DASHBOARD_MODE_PARAM]; - kbnUrl.removeParam(DashboardConstants.ADD_VISUALIZATION_TO_DASHBOARD_MODE_PARAM); + $scope.showFilterBar = () => { + return vis.type.options.showFilterBar; + }; - $scope.isAddToDashMode = () => addToDashMode; + $scope.showQueryInput = () => { + return vis.type.requiresSearch && vis.type.options.showQueryBar; + }; - $scope.showFilterBar = () => { - return vis.type.options.showFilterBar; - }; + $scope.showQueryBarTimePicker = () => { + return vis.type.options.showTimePicker; + }; - $scope.showQueryInput = () => { - return vis.type.requiresSearch && vis.type.options.showQueryBar; - }; + $scope.timeRange = timefilter.getTime(); + $scope.opts = _.pick($scope, 'savedVis', 'isAddToDashMode'); - $scope.showQueryBarTimePicker = () => { - return vis.type.options.showTimePicker; - }; + stateMonitor = stateMonitorFactory.create($state, stateDefaults); + stateMonitor.ignoreProps([ 'vis.listeners' ]).onChange((status) => { + $appStatus.dirty = status.dirty || !savedVis.id; + }); - $scope.timeRange = timefilter.getTime(); - $scope.opts = _.pick($scope, 'savedVis', 'isAddToDashMode'); + $scope.$watch('state.query', (newQuery, oldQuery) => { + if (!_.isEqual(newQuery, oldQuery)) { + const query = migrateLegacyQuery(newQuery); + if (!_.isEqual(query, newQuery)) { + $state.query = query; + } + $scope.fetch(); + } + }); - stateMonitor = stateMonitorFactory.create($state, stateDefaults); - stateMonitor.ignoreProps([ 'vis.listeners' ]).onChange((status) => { - $appStatus.dirty = status.dirty || !savedVis.id; - }); + $state.replace(); - $scope.$watch('state.query', (newQuery, oldQuery) => { - if (!_.isEqual(newQuery, oldQuery)) { - const query = migrateLegacyQuery(newQuery); - if (!_.isEqual(query, newQuery)) { - $state.query = query; + const updateTimeRange = () => { + $scope.timeRange = timefilter.getTime(); + $scope.$broadcast('render'); + }; + + const subscriptions = new Subscription(); + + subscriptions.add(subscribeWithScope($scope, timefilter.getRefreshIntervalUpdate$(), { + next: () => { + $scope.refreshInterval = timefilter.getRefreshInterval(); + } + })); + subscriptions.add(subscribeWithScope($scope, timefilter.getTimeUpdate$(), { + next: updateTimeRange + })); + + // update the searchSource when query updates + $scope.fetch = function () { + $state.save(); + $scope.query = $state.query; + savedVis.searchSource.setField('query', $state.query); + savedVis.searchSource.setField('filter', $state.filters); + $scope.$broadcast('render'); + }; + + // update the searchSource when filters update + subscriptions.add(subscribeWithScope($scope, queryFilter.getUpdates$(), { + next: () => { + $scope.filters = queryFilter.getFilters(); + $scope.globalFilters = queryFilter.getGlobalFilters(); + } + })); + subscriptions.add(subscribeWithScope($scope, queryFilter.getFetches$(), { + next: $scope.fetch + })); + + $scope.$on('$destroy', function () { + if ($scope._handler) { + $scope._handler.destroy(); + } + savedVis.destroy(); + stateMonitor.destroy(); + subscriptions.unsubscribe(); + }); + + + $timeout(() => { $scope.$broadcast('render'); }); } - $scope.fetch(); - } - }); - $state.replace(); + $scope.updateQueryAndFetch = function ({ query, dateRange }) { + const isUpdate = ( + (query && !_.isEqual(query, $state.query)) || + (dateRange && !_.isEqual(dateRange, $scope.timeRange)) + ); - const updateTimeRange = () => { - $scope.timeRange = timefilter.getTime(); - $scope.$broadcast('render'); - }; + $state.query = query; + timefilter.setTime(dateRange); - const subscriptions = new Subscription(); - - subscriptions.add(subscribeWithScope($scope, timefilter.getRefreshIntervalUpdate$(), { - next: () => { - $scope.refreshInterval = timefilter.getRefreshInterval(); - } - })); - subscriptions.add(subscribeWithScope($scope, timefilter.getTimeUpdate$(), { - next: updateTimeRange - })); - - // update the searchSource when query updates - $scope.fetch = function () { - $state.save(); - $scope.query = $state.query; - savedVis.searchSource.setField('query', $state.query); - savedVis.searchSource.setField('filter', $state.filters); - $scope.$broadcast('render'); - }; + // If nothing has changed, trigger the fetch manually, otherwise it will happen as a result of the changes + if (!isUpdate) { + $scope.vis.forceReload(); + } + }; - // update the searchSource when filters update - subscriptions.add(subscribeWithScope($scope, queryFilter.getUpdates$(), { - next: () => { - $scope.filters = queryFilter.getFilters(); - $scope.globalFilters = queryFilter.getGlobalFilters(); - } - })); - subscriptions.add(subscribeWithScope($scope, queryFilter.getFetches$(), { - next: $scope.fetch - })); - - $scope.$on('$destroy', function () { - if ($scope._handler) { - $scope._handler.destroy(); - } - savedVis.destroy(); - stateMonitor.destroy(); - subscriptions.unsubscribe(); - }); - - - $timeout(() => { $scope.$broadcast('render'); }); - } - - $scope.updateQueryAndFetch = function ({ query, dateRange }) { - const isUpdate = ( - (query && !_.isEqual(query, $state.query)) || - (dateRange && !_.isEqual(dateRange, $scope.timeRange)) - ); - - $state.query = query; - timefilter.setTime(dateRange); - - // If nothing has changed, trigger the fetch manually, otherwise it will happen as a result of the changes - if (!isUpdate) { - $scope.vis.forceReload(); - } - }; - - $scope.onRefreshChange = function ({ isPaused, refreshInterval }) { - timefilter.setRefreshInterval({ - pause: isPaused, - value: refreshInterval ? refreshInterval : $scope.refreshInterval.value - }); - }; - - $scope.onQuerySaved = savedQuery => { - $scope.savedQuery = savedQuery; - }; - - $scope.onSavedQueryUpdated = savedQuery => { - $scope.savedQuery = { ...savedQuery }; - }; - - $scope.onClearSavedQuery = () => { - delete $scope.savedQuery; - delete $state.savedQuery; - $state.query = { - query: '', - language: localStorage.get('kibana.userQueryLanguage') || config.get('search:queryLanguage') - }; - queryFilter.removeAll(); - $state.save(); - $scope.fetch(); - }; - - const updateStateFromSavedQuery = (savedQuery) => { - $state.query = savedQuery.attributes.query; - $state.save(); - - queryFilter.setFilters(savedQuery.attributes.filters || []); - - if (savedQuery.attributes.timefilter) { - timefilter.setTime({ - from: savedQuery.attributes.timefilter.from, - to: savedQuery.attributes.timefilter.to, - }); - if (savedQuery.attributes.timefilter.refreshInterval) { - timefilter.setRefreshInterval(savedQuery.attributes.timefilter.refreshInterval); - } - } - - $scope.fetch(); - }; - - $scope.$watch('savedQuery', (newSavedQuery) => { - if (!newSavedQuery) return; - $state.savedQuery = newSavedQuery.id; - $state.save(); - - updateStateFromSavedQuery(newSavedQuery); - }); + $scope.onRefreshChange = function ({ isPaused, refreshInterval }) { + timefilter.setRefreshInterval({ + pause: isPaused, + value: refreshInterval ? refreshInterval : $scope.refreshInterval.value + }); + }; - $scope.$watch('state.savedQuery', newSavedQueryId => { - if (!newSavedQueryId) { - $scope.savedQuery = undefined; - return; - } - if (!$scope.savedQuery || newSavedQueryId !== $scope.savedQuery.id) { - savedQueryService.getSavedQuery(newSavedQueryId).then((savedQuery) => { - $scope.$evalAsync(() => { + $scope.onQuerySaved = savedQuery => { $scope.savedQuery = savedQuery; - updateStateFromSavedQuery(savedQuery); - }); - }); - } - }); + }; - /** - * Called when the user clicks "Save" button. - */ - function doSave(saveOptions) { - // vis.title was not bound and it's needed to reflect title into visState - $state.vis.title = savedVis.title; - $state.vis.type = savedVis.type || $state.vis.type; - savedVis.visState = $state.vis; - savedVis.uiStateJSON = angular.toJson($scope.uiState.getChanges()); - - return savedVis.save(saveOptions) - .then(function (id) { - $scope.$evalAsync(() => { - stateMonitor.setInitialState($state.toJSON()); - - if (id) { - toastNotifications.addSuccess({ - title: i18n.translate('kbn.visualize.topNavMenu.saveVisualization.successNotificationText', { - defaultMessage: `Saved '{visTitle}'`, - values: { - visTitle: savedVis.title, - }, - }), - 'data-test-subj': 'saveVisualizationSuccess', - }); + $scope.onSavedQueryUpdated = savedQuery => { + $scope.savedQuery = { ...savedQuery }; + }; - if ($scope.isAddToDashMode()) { - const savedVisualizationParsedUrl = new KibanaParsedUrl({ - basePath: getBasePath(), - appId: kbnBaseUrl.slice('/app/'.length), - appPath: kbnUrl.eval(`${VisualizeConstants.EDIT_PATH}/{{id}}`, { id: savedVis.id }), - }); - // Manually insert a new url so the back button will open the saved visualization. - $window.history.pushState({}, '', savedVisualizationParsedUrl.getRootRelativePath()); - // Since we aren't reloading the page, only inserting a new browser history item, we need to manually update - // the last url for this app, so directly clicking on the Visualize tab will also bring the user to the saved - // url, not the unsaved one. - chromeLegacy.trackSubUrlForApp('kibana:visualize', savedVisualizationParsedUrl); - - const lastDashboardAbsoluteUrl = chrome.navLinks.get('kibana:dashboard').url; - const dashboardParsedUrl = absoluteToParsedUrl(lastDashboardAbsoluteUrl, getBasePath()); - dashboardParsedUrl.addQueryParameter(DashboardConstants.NEW_VISUALIZATION_ID_PARAM, savedVis.id); - kbnUrl.change(dashboardParsedUrl.appPath); - } else if (savedVis.id === $route.current.params.id) { - docTitle.change(savedVis.lastSavedTitle); - chrome.setBreadcrumbs($injector.invoke(getEditBreadcrumbs)); - savedVis.vis.title = savedVis.title; - savedVis.vis.description = savedVis.description; - // it's needed to save the state to update url string - $state.save(); - } else { - kbnUrl.change(`${VisualizeConstants.EDIT_PATH}/{{id}}`, { id: savedVis.id }); + $scope.onClearSavedQuery = () => { + delete $scope.savedQuery; + delete $state.savedQuery; + $state.query = { + query: '', + language: localStorage.get('kibana.userQueryLanguage') || config.get('search:queryLanguage') + }; + queryFilter.removeAll(); + $state.save(); + $scope.fetch(); + }; + + const updateStateFromSavedQuery = (savedQuery) => { + $state.query = savedQuery.attributes.query; + $state.save(); + + queryFilter.setFilters(savedQuery.attributes.filters || []); + + if (savedQuery.attributes.timefilter) { + timefilter.setTime({ + from: savedQuery.attributes.timefilter.from, + to: savedQuery.attributes.timefilter.to, + }); + if (savedQuery.attributes.timefilter.refreshInterval) { + timefilter.setRefreshInterval(savedQuery.attributes.timefilter.refreshInterval); } } + + $scope.fetch(); + }; + + $scope.$watch('savedQuery', (newSavedQuery) => { + if (!newSavedQuery) return; + $state.savedQuery = newSavedQuery.id; + $state.save(); + + updateStateFromSavedQuery(newSavedQuery); }); - return { id }; - }, (error) => { - // eslint-disable-next-line - console.error(error); - toastNotifications.addDanger({ - title: i18n.translate('kbn.visualize.topNavMenu.saveVisualization.failureNotificationText', { - defaultMessage: `Error on saving '{visTitle}'`, - values: { - visTitle: savedVis.title, - }, - }), - text: error.message, - 'data-test-subj': 'saveVisualizationError', + + $scope.$watch('state.savedQuery', newSavedQueryId => { + if (!newSavedQueryId) { + $scope.savedQuery = undefined; + return; + } + if (!$scope.savedQuery || newSavedQueryId !== $scope.savedQuery.id) { + deps.savedQueryService.getSavedQuery(newSavedQueryId).then((savedQuery) => { + $scope.$evalAsync(() => { + $scope.savedQuery = savedQuery; + updateStateFromSavedQuery(savedQuery); + }); + }); + } }); - return { error }; - }); - } - - $scope.unlink = function () { - if (!$state.linked) return; - - $state.linked = false; - const searchSourceParent = searchSource.getParent(); - const searchSourceGrandparent = searchSourceParent.getParent(); - - delete savedVis.savedSearchId; - delete vis.savedSearchId; - searchSourceParent.setField('filter', _.union(searchSource.getOwnField('filter'), searchSourceParent.getOwnField('filter'))); - - $state.query = searchSourceParent.getField('query'); - $state.filters = searchSourceParent.getField('filter'); - searchSource.setField('index', searchSourceParent.getField('index')); - searchSource.setParent(searchSourceGrandparent); - - toastNotifications.addSuccess( - i18n.translate('kbn.visualize.linkedToSearch.unlinkSuccessNotificationText', { - defaultMessage: `Unlinked from saved search '{searchTitle}'`, - values: { - searchTitle: savedVis.savedSearch.title + + /** + * Called when the user clicks "Save" button. + */ + function doSave(saveOptions) { + // vis.title was not bound and it's needed to reflect title into visState + $state.vis.title = savedVis.title; + $state.vis.type = savedVis.type || $state.vis.type; + savedVis.visState = $state.vis; + savedVis.uiStateJSON = angular.toJson($scope.uiState.getChanges()); + + return savedVis.save(saveOptions) + .then(function (id) { + $scope.$evalAsync(() => { + stateMonitor.setInitialState($state.toJSON()); + + if (id) { + toastNotifications.addSuccess({ + title: i18n.translate('kbn.visualize.topNavMenu.saveVisualization.successNotificationText', { + defaultMessage: `Saved '{visTitle}'`, + values: { + visTitle: savedVis.title, + }, + }), + 'data-test-subj': 'saveVisualizationSuccess', + }); + + if ($scope.isAddToDashMode()) { + const savedVisualizationParsedUrl = new KibanaParsedUrl({ + basePath: getBasePath(), + appId: kbnBaseUrl.slice('/app/'.length), + appPath: kbnUrl.eval(`${VisualizeConstants.EDIT_PATH}/{{id}}`, { id: savedVis.id }), + }); + // Manually insert a new url so the back button will open the saved visualization. + $window.history.pushState({}, '', savedVisualizationParsedUrl.getRootRelativePath()); + // Since we aren't reloading the page, only inserting a new browser history item, we need to manually update + // the last url for this app, so directly clicking on the Visualize tab will also bring the user to the saved + // url, not the unsaved one. + chromeLegacy.trackSubUrlForApp('kibana:visualize', savedVisualizationParsedUrl); + + const lastDashboardAbsoluteUrl = chrome.navLinks.get('kibana:dashboard').url; + const dashboardParsedUrl = absoluteToParsedUrl(lastDashboardAbsoluteUrl, getBasePath()); + dashboardParsedUrl.addQueryParameter(DashboardConstants.NEW_VISUALIZATION_ID_PARAM, savedVis.id); + kbnUrl.change(dashboardParsedUrl.appPath); + } else if (savedVis.id === $route.current.params.id) { + docTitle.change(savedVis.lastSavedTitle); + chrome.setBreadcrumbs($injector.invoke(getEditBreadcrumbs)); + savedVis.vis.title = savedVis.title; + savedVis.vis.description = savedVis.description; + // it's needed to save the state to update url string + $state.save(); + } else { + kbnUrl.change(`${VisualizeConstants.EDIT_PATH}/{{id}}`, { id: savedVis.id }); + } + } + }); + return { id }; + }, (error) => { + // eslint-disable-next-line + console.error(error); + toastNotifications.addDanger({ + title: i18n.translate('kbn.visualize.topNavMenu.saveVisualization.failureNotificationText', { + defaultMessage: `Error on saving '{visTitle}'`, + values: { + visTitle: savedVis.title, + }, + }), + text: error.message, + 'data-test-subj': 'saveVisualizationError', + }); + return { error }; + }); } - }) - ); - $scope.fetch(); - }; + $scope.unlink = function () { + if (!$state.linked) return; + + $state.linked = false; + const searchSourceParent = searchSource.getParent(); + const searchSourceGrandparent = searchSourceParent.getParent(); + + delete savedVis.savedSearchId; + delete vis.savedSearchId; + searchSourceParent.setField('filter', _.union(searchSource.getOwnField('filter'), searchSourceParent.getOwnField('filter'))); + + $state.query = searchSourceParent.getField('query'); + $state.filters = searchSourceParent.getField('filter'); + searchSource.setField('index', searchSourceParent.getField('index')); + searchSource.setParent(searchSourceGrandparent); + toastNotifications.addSuccess( + i18n.translate('kbn.visualize.linkedToSearch.unlinkSuccessNotificationText', { + defaultMessage: `Unlinked from saved search '{searchTitle}'`, + values: { + searchTitle: savedVis.savedSearch.title + } + }) + ); + + $scope.fetch(); + }; - $scope.getAdditionalMessage = () => { - return '' + - i18n.translate('kbn.visualize.experimentalVisInfoText', { defaultMessage: 'This visualization is marked as experimental.' }) + - ' ' + - vis.type.feedbackMessage; - }; - addHelpMenuToAppChrome(chrome); + $scope.getAdditionalMessage = () => { + return '' + + i18n.translate('kbn.visualize.experimentalVisInfoText', { defaultMessage: 'This visualization is marked as experimental.' }) + + ' ' + + vis.type.feedbackMessage; + }; + + addHelpMenuToAppChrome(chrome, docLinks); - init(); + init(); + + const visibleSubscription = chrome.getIsVisible$().subscribe(isVisible => { + $scope.isVisible = isVisible; + }); + + $scope.$on('$destroy', () => { + visibleSubscription.unsubscribe(); + }); + }, + }; + }); + + initVisEditorDirective(app, deps); + initVisualizationDirective(app, deps); } + + diff --git a/src/legacy/core_plugins/kibana/public/visualize/editor/visualization.js b/src/legacy/core_plugins/kibana/public/visualize/editor/visualization.js index 198fbe19a0b7a..18462f77c2b62 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/editor/visualization.js +++ b/src/legacy/core_plugins/kibana/public/visualize/editor/visualization.js @@ -17,13 +17,8 @@ * under the License. */ -import { getServices } from '../kibana_services'; - -const { embeddables, uiModules } = getServices(); - -uiModules - .get('kibana/directive', ['ngSanitize']) - .directive('visualizationEmbedded', function (Private, $timeout, getAppState) { +export function initVisualizationDirective(app, deps) { + app.directive('visualizationEmbedded', function (Private, $timeout, getAppState) { return { restrict: 'E', @@ -37,7 +32,7 @@ uiModules link: function ($scope, element) { $scope.renderFunction = async () => { if (!$scope._handler) { - $scope._handler = await embeddables.getEmbeddableFactory('visualization').createFromObject($scope.savedObj, { + $scope._handler = await deps.embeddables.getEmbeddableFactory('visualization').createFromObject($scope.savedObj, { timeRange: $scope.timeRange, filters: $scope.filters || [], query: $scope.query, @@ -66,3 +61,4 @@ uiModules } }; }); +} diff --git a/src/legacy/core_plugins/kibana/public/visualize/editor/visualization_editor.js b/src/legacy/core_plugins/kibana/public/visualize/editor/visualization_editor.js index ead77e6bc41d5..6f3fcf8409411 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/editor/visualization_editor.js +++ b/src/legacy/core_plugins/kibana/public/visualize/editor/visualization_editor.js @@ -17,13 +17,10 @@ * under the License. */ -import { getServices, VisEditorTypesRegistryProvider } from '../kibana_services'; +import { VisEditorTypesRegistryProvider } from '../kibana_services'; -const { uiModules } = getServices(); - -uiModules - .get('kibana/directive', ['ngSanitize']) - .directive('visualizationEditor', function (Private, $timeout, getAppState) { +export function initVisEditorDirective(app, deps) { + app.directive('visualizationEditor', function (Private, $timeout, getAppState) { const editorTypes = Private(VisEditorTypesRegistryProvider); return { @@ -62,3 +59,4 @@ uiModules } }; }); +} diff --git a/src/legacy/core_plugins/kibana/public/visualize/embeddable/get_index_pattern.ts b/src/legacy/core_plugins/kibana/public/visualize/embeddable/get_index_pattern.ts index b6d5adcd98bd0..0b7dbbf5047e0 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/embeddable/get_index_pattern.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/embeddable/get_index_pattern.ts @@ -24,11 +24,10 @@ import { VisSavedObject, } from '../kibana_services'; -const { savedObjectsClient, uiSettings } = getServices(); - export async function getIndexPattern( savedVis: VisSavedObject ): Promise { + const { savedObjectsClient, uiSettings } = getServices(); if (savedVis.vis.type.name !== 'metrics') { return savedVis.vis.indexPattern; } diff --git a/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable_factory.tsx b/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable_factory.tsx index c1ce4f67cfdb3..850c2ba459d2b 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable_factory.tsx +++ b/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable_factory.tsx @@ -40,15 +40,6 @@ import { VisSavedObject, } from '../kibana_services'; -const { - addBasePath, - capabilities, - embeddable, - getInjector, - uiSettings, - visualizations, -} = getServices(); - interface VisualizationAttributes extends SavedObjectAttributes { visState: string; } @@ -63,7 +54,7 @@ export class VisualizeEmbeddableFactory extends EmbeddableFactory< private readonly visTypes: TypesStart; static async createVisualizeEmbeddableFactory(): Promise { - return new VisualizeEmbeddableFactory(visualizations.types); + return new VisualizeEmbeddableFactory(getServices().visualizations.types); } constructor(visTypes: TypesStart) { @@ -97,7 +88,7 @@ export class VisualizeEmbeddableFactory extends EmbeddableFactory< if (!visType) { return false; } - if (uiSettings.get('visualize:enableLabs')) { + if (getServices().uiSettings.get('visualize:enableLabs')) { return true; } return visType.stage !== 'experimental'; @@ -109,7 +100,7 @@ export class VisualizeEmbeddableFactory extends EmbeddableFactory< } public isEditable() { - return capabilities.visualize.save as boolean; + return getServices().visualizeCapabilities.save as boolean; } public getDisplayName() { @@ -123,16 +114,17 @@ export class VisualizeEmbeddableFactory extends EmbeddableFactory< input: Partial & { id: string }, parent?: Container ): Promise { - const $injector = await getInjector(); - const config = $injector.get('config'); - const savedVisualizations = $injector.get('savedVisualizations'); + const config = getServices().config; + const savedVisualizations = getServices().savedVisualizations; try { const visId = savedObject.id as string; - const editUrl = visId ? addBasePath(`/app/kibana${savedVisualizations.urlFor(visId)}`) : ''; + const editUrl = visId + ? getServices().addBasePath(`/app/kibana${savedVisualizations.urlFor(visId)}`) + : ''; const loader = await getVisualizeLoader(); - const isLabsEnabled = config.get('visualize:enableLabs'); + const isLabsEnabled = config.get('visualize:enableLabs'); if (!isLabsEnabled && savedObject.vis.type.stage === 'experimental') { return new DisabledLabEmbeddable(savedObject.title, input); @@ -164,8 +156,7 @@ export class VisualizeEmbeddableFactory extends EmbeddableFactory< input: Partial & { id: string }, parent?: Container ): Promise { - const $injector = await getInjector(); - const savedVisualizations = $injector.get('savedVisualizations'); + const savedVisualizations = getServices().savedVisualizations; try { const visId = savedObjectId; @@ -190,6 +181,6 @@ export class VisualizeEmbeddableFactory extends EmbeddableFactory< } } -VisualizeEmbeddableFactory.createVisualizeEmbeddableFactory().then(embeddableFactory => { - embeddable.registerEmbeddableFactory(VISUALIZE_EMBEDDABLE_TYPE, embeddableFactory); -}); +// VisualizeEmbeddableFactory.createVisualizeEmbeddableFactory().then(embeddableFactory => { +// getServices().embeddable.registerEmbeddableFactory(VISUALIZE_EMBEDDABLE_TYPE, embeddableFactory); +// }); diff --git a/src/legacy/core_plugins/kibana/public/visualize/help_menu/help_menu.js b/src/legacy/core_plugins/kibana/public/visualize/help_menu/help_menu.js index 40a1b79ea3520..567dd4b2534d6 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/help_menu/help_menu.js +++ b/src/legacy/core_plugins/kibana/public/visualize/help_menu/help_menu.js @@ -19,27 +19,23 @@ import React, { Fragment, PureComponent } from 'react'; import { EuiButton, EuiHorizontalRule, EuiSpacer } from '@elastic/eui'; -import { FormattedMessage } from '@kbn/i18n/react'; - -import { getServices } from '../kibana_services'; - -const { docLinks } = getServices(); +import { FormattedMessage, I18nProvider } from '@kbn/i18n/react'; export class HelpMenu extends PureComponent { render() { return ( - + - + ); } } diff --git a/src/legacy/core_plugins/kibana/public/visualize/help_menu/help_menu_util.js b/src/legacy/core_plugins/kibana/public/visualize/help_menu/help_menu_util.js index 58a92193de63e..2dc8ce523a7da 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/help_menu/help_menu_util.js +++ b/src/legacy/core_plugins/kibana/public/visualize/help_menu/help_menu_util.js @@ -21,9 +21,9 @@ import React from 'react'; import { render, unmountComponentAtNode } from 'react-dom'; import { HelpMenu } from './help_menu'; -export function addHelpMenuToAppChrome(chrome) { +export function addHelpMenuToAppChrome(chrome, docLinks) { chrome.setHelpExtension(domElement => { - render(, domElement); + render(, domElement); return () => { unmountComponentAtNode(domElement); }; diff --git a/src/legacy/core_plugins/kibana/public/visualize/index.ts b/src/legacy/core_plugins/kibana/public/visualize/index.ts new file mode 100644 index 0000000000000..cad643cc09e42 --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/visualize/index.ts @@ -0,0 +1,87 @@ +/* + * 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 'angular-sanitize'; // used in visualization_editor.js and visualization.js +import 'ui/collapsible_sidebar'; // used in default editor +import 'ui/vis/editors/default/sidebar'; + +import { FeatureCatalogueRegistryProvider } from 'ui/registry/feature_catalogue'; +import { npSetup, npStart } from 'ui/new_platform'; +import { SavedObjectRegistryProvider, SavedObjectsClientProvider } from 'ui/saved_objects'; +import { docTitle } from 'ui/doc_title/doc_title'; +import chrome from 'ui/chrome'; +import { IPrivate } from 'ui/private'; +import { ShareContextMenuExtensionsRegistryProvider } from 'ui/share'; +import { getUnhashableStatesProvider } from 'ui/state_management/state_hashing/get_unhashable_states_provider'; +import { FilterBarQueryFilterProvider } from 'ui/filter_manager/query_filter'; +import { VisualizePlugin, LegacyAngularInjectedDependencies } from './plugin'; +import { start as data } from '../../../data/public/legacy'; +import { localApplicationService } from '../local_application_service'; +import { + start as embeddables, + setup as embeddable, +} from '../../../embeddable_api/public/np_ready/public/legacy'; +import { start as navigation } from '../../../navigation/public/legacy'; +import { start as visualizations } from '../../../visualizations/public/np_ready/public/legacy'; + +/** + * Get dependencies relying on the global angular context. + * They also have to get resolved together with the legacy imports above + */ +async function getAngularDependencies(): Promise { + const injector = await chrome.dangerouslyGetActiveInjector(); + + const Private = injector.get('Private'); + + const queryFilter = Private(FilterBarQueryFilterProvider); + const getUnhashableStates = Private(getUnhashableStatesProvider); + const shareContextMenuExtensions = Private(ShareContextMenuExtensionsRegistryProvider); + const savedObjectRegistry = Private(SavedObjectRegistryProvider); + const savedObjectClient = Private(SavedObjectsClientProvider); + + return { + queryFilter, + getUnhashableStates, + shareContextMenuExtensions, + config: injector.get('config'), + savedObjectClient, + savedObjectRegistry, + savedDashboards: injector.get('savedDashboards'), + savedVisualizations: injector.get('savedVisualizations'), + }; +} + +(async () => { + const instance = new VisualizePlugin(); + instance.setup(npSetup.core, { + __LEGACY: { + localApplicationService, + getAngularDependencies, + FeatureCatalogueRegistryProvider, + docTitle, + }, + embeddable, + }); + instance.start(npStart.core, { + data, + embeddables, + navigation, + visualizations, + }); +})(); diff --git a/src/legacy/core_plugins/kibana/public/visualize/index.js b/src/legacy/core_plugins/kibana/public/visualize/index_o.js similarity index 100% rename from src/legacy/core_plugins/kibana/public/visualize/index.js rename to src/legacy/core_plugins/kibana/public/visualize/index_o.js diff --git a/src/legacy/core_plugins/kibana/public/visualize/kibana_services.ts b/src/legacy/core_plugins/kibana/public/visualize/kibana_services.ts index 7e8435bbdc65e..f36e4357b07cf 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/kibana_services.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/kibana_services.ts @@ -17,81 +17,74 @@ * under the License. */ -import 'angular-sanitize'; // used in visualization_editor.js and visualization.js -import 'ui/collapsible_sidebar'; // used in default editor -import 'ui/vis/editors/default/sidebar'; -// load directives -import '../../../data/public'; - -import { npStart } from 'ui/new_platform'; -import angular from 'angular'; // just used in editor.js -import chromeLegacy from 'ui/chrome'; +import { ToastNotifications } from 'ui/notify/toasts/toast_notifications'; +import { + ChromeStart, + DocLinksStart, + SavedObjectsClientContract, + UiSettingsClientContract, +} from 'kibana/public'; -import uiRoutes from 'ui/routes'; -// @ts-ignore -import { docTitle } from 'ui/doc_title'; -import { FilterBarQueryFilterProvider } from 'ui/filter_manager/query_filter'; -import { wrapInI18nContext } from 'ui/i18n'; -// @ts-ignore -import { uiModules } from 'ui/modules'; -import { FeatureCatalogueRegistryProvider } from 'ui/registry/feature_catalogue'; -import { ShareContextMenuExtensionsRegistryProvider } from 'ui/share'; -import { timefilter } from 'ui/timefilter'; +// load directives +import '../../../data/public'; -// Saved objects -import { SavedObjectsClientProvider } from 'ui/saved_objects'; // @ts-ignore import { SavedObjectProvider } from 'ui/saved_objects/saved_object'; -import { SavedObjectRegistryProvider } from 'ui/saved_objects/saved_object_registry'; - -import { createUiStatsReporter, METRIC_TYPE } from '../../../ui_metric/public'; -import { start as visualizations } from '../../../visualizations/public/np_ready/public/legacy'; -import { start as data } from '../../../data/public/legacy'; -import { start as embeddables } from '../../../../core_plugins/embeddable_api/public/np_ready/public/legacy'; - -const services = { - // new platform - addBasePath: npStart.core.http.basePath.prepend, - capabilities: npStart.core.application.capabilities, - chrome: npStart.core.chrome, - docLinks: npStart.core.docLinks, - embeddable: npStart.plugins.embeddable, - getBasePath: npStart.core.http.basePath.get, - savedObjectsClient: npStart.core.savedObjects.client, - toastNotifications: npStart.core.notifications.toasts, - uiSettings: npStart.core.uiSettings, - - data, - embeddables, - visualizations, - - // legacy - chromeLegacy, - docTitle, - FeatureCatalogueRegistryProvider, - FilterBarQueryFilterProvider, - getInjector: () => { - return chromeLegacy.dangerouslyGetActiveInjector(); - }, - SavedObjectProvider, - SavedObjectRegistryProvider, - SavedObjectsClientProvider, - ShareContextMenuExtensionsRegistryProvider, - timefilter, - uiModules, - uiRoutes, - wrapInI18nContext, - - createUiStatsReporter, -}; +import { EmbeddablePublicPlugin } from '../../../../../plugins/embeddable/public'; +import { SavedVisualizations } from './types'; + +export interface VisualizeKibanaServices { + addBasePath: (url: string) => string; + angular: any; + chrome: ChromeStart; + config: any; + dataStart: any; + docLinks: DocLinksStart; + embeddables: ReturnType; + getBasePath: () => string; + getInjected: (name: string, defaultValue?: any) => unknown; + FeatureCatalogueRegistryProvider: any; + indexPatterns: any; + METRIC_TYPE: any; + toastNotifications: ToastNotifications; + savedObjectsClient: SavedObjectsClientContract; + savedVisualizations: SavedVisualizations; + uiSettings: UiSettingsClientContract; + visualizeCapabilities: any; + visualizations: any; +} + +let services: VisualizeKibanaServices | null = null; +export function setServices(newServices: VisualizeKibanaServices) { + if (services) { + throw new Error( + 'Kibana services already set - are you trying to import this module from outside of the home app?' + ); + } + services = newServices; +} export function getServices() { + if (!services) { + throw new Error( + 'Kibana services not set - are you trying to import this module from outside of the home app?' + ); + } return services; } +export function clearServices() { + services = null; +} + + // export legacy static dependencies -export { angular }; +export { ensureDefaultIndexPattern } from 'ui/legacy_compat'; + + + + export { getFromSavedObject } from 'ui/index_patterns'; export { PersistedState } from 'ui/persisted_state'; // @ts-ignore @@ -117,7 +110,6 @@ export { } from '../../../../../plugins/embeddable/public'; // export types -export { METRIC_TYPE }; export { StaticIndexPattern } from 'ui/index_patterns'; export { AppState } from 'ui/state_management/app_state'; export { VisType } from 'ui/vis'; @@ -128,5 +120,7 @@ export { VisualizeUpdateParams, } from 'ui/visualize/loader/types'; +export { METRIC_TYPE, createUiStatsReporter } from '../../../ui_metric/public'; + // export const export { FeatureCatalogueCategory } from 'ui/registry/feature_catalogue'; diff --git a/src/legacy/core_plugins/kibana/public/visualize/listing/visualize_listing.js b/src/legacy/core_plugins/kibana/public/visualize/listing/visualize_listing.js index f9e3a1a90115a..38d29c5ca141b 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/listing/visualize_listing.js +++ b/src/legacy/core_plugins/kibana/public/visualize/listing/visualize_listing.js @@ -23,32 +23,31 @@ import { NewVisModal } from '../wizard/new_vis_modal'; import { VisualizeConstants } from '../visualize_constants'; import { i18n } from '@kbn/i18n'; -import { getServices } from '../kibana_services'; - -const { - addBasePath, - chrome, - chromeLegacy, - SavedObjectRegistryProvider, - SavedObjectsClientProvider, - timefilter, - toastNotifications, - uiModules, - wrapInI18nContext, - visualizations, -} = getServices(); - -const app = uiModules.get('app/visualize', ['ngRoute', 'react']); -app.directive('visualizeListingTable', reactDirective => - reactDirective(wrapInI18nContext(VisualizeListingTable)) -); -app.directive('newVisModal', reactDirective => reactDirective(wrapInI18nContext(NewVisModal))); +import { getServices, config } from '../kibana_services'; + +export function initListingDirective(app, deps) { + app.directive('visualizeListingTable', reactDirective => + reactDirective(deps.wrapInI18nContext(VisualizeListingTable)) + ); + app.directive('newVisModal', reactDirective => reactDirective(deps.wrapInI18nContext(NewVisModal))); +} export function VisualizeListingController($injector, createNewVis) { - const Private = $injector.get('Private'); - const config = $injector.get('config'); + const { + addBasePath, + chrome, + chromeLegacy, + savedObjectRegistry, + savedObjectClient, + dataStart: { + timefilter: { timefilter }, + }, + toastNotifications, + uiSettings, + visualizations, + docLinks, + } = getServices(); const kbnUrl = $injector.get('kbnUrl'); - const savedObjectClient = Private(SavedObjectsClientProvider); timefilter.disableAutoRefreshSelector(); timefilter.disableTimeRangeSelector(); @@ -82,14 +81,14 @@ export function VisualizeListingController($injector, createNewVis) { } // TODO: Extract this into an external service. - const services = Private(SavedObjectRegistryProvider).byLoaderPropertiesName; + const services = savedObjectRegistry.byLoaderPropertiesName; const visualizationService = services.visualizations; this.visTypeRegistry = visualizations.types; this.fetchItems = filter => { - const isLabsEnabled = config.get('visualize:enableLabs'); + const isLabsEnabled = uiSettings.get('visualize:enableLabs'); return visualizationService - .findListItems(filter, config.get('savedObjects:listingLimit')) + .findListItems(filter, uiSettings.get('savedObjects:listingLimit')) .then(result => { this.totalItems = result.total; @@ -126,7 +125,7 @@ export function VisualizeListingController($injector, createNewVis) { }, ]); - this.listingLimit = config.get('savedObjects:listingLimit'); + this.listingLimit = uiSettings.get('savedObjects:listingLimit'); - addHelpMenuToAppChrome(chrome); + addHelpMenuToAppChrome(chrome, docLinks); } diff --git a/src/legacy/core_plugins/kibana/public/visualize/listing/visualize_listing_table.js b/src/legacy/core_plugins/kibana/public/visualize/listing/visualize_listing_table.js index fbd70a0d8c0f7..188a87df3bfd8 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/listing/visualize_listing_table.js +++ b/src/legacy/core_plugins/kibana/public/visualize/listing/visualize_listing_table.js @@ -27,8 +27,6 @@ import { EuiIcon, EuiBetaBadge, EuiLink, EuiButton, EuiEmptyPrompt } from '@elas import { getServices } from '../kibana_services'; -const { capabilities } = getServices(); - class VisualizeListingTable extends Component { constructor(props) { super(props); @@ -41,8 +39,8 @@ class VisualizeListingTable extends Component { // for data exploration purposes createItem={this.props.createItem} findItems={this.props.findItems} - deleteItems={capabilities.visualize.delete ? this.props.deleteItems : null} - editItem={capabilities.visualize.save ? this.props.editItem : null} + deleteItems={getServices().visualizeCapabilities.delete ? this.props.deleteItems : null} + editItem={getServices().visualizeCapabilities.save ? this.props.editItem : null} tableColumns={this.getTableColumns()} listingLimit={this.props.listingLimit} selectable={item => item.canDelete} diff --git a/src/legacy/core_plugins/kibana/public/visualize/plugin.ts b/src/legacy/core_plugins/kibana/public/visualize/plugin.ts new file mode 100644 index 0000000000000..dcb62d81b1560 --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/visualize/plugin.ts @@ -0,0 +1,147 @@ +/* + * 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 angular from 'angular'; +import { wrapInI18nContext } from 'ui/i18n'; + +import { + App, + CoreSetup, + CoreStart, + LegacyCoreStart, + Plugin, + SavedObjectsClientContract, +} from 'kibana/public'; +import { Storage } from '../../../../../plugins/kibana_utils/public'; +import { setServices } from './kibana_services'; +import { LocalApplicationService } from '../local_application_service'; +import { DataStart } from '../../../data/public'; +import { EmbeddablePublicPlugin } from '../../../../../plugins/embeddable/public'; +import { NavigationStart } from '../../../navigation/public'; +import { VisualizationsStart } from '../../../visualizations/public'; +import { VisualizeEmbeddableFactory } from './embeddable/visualize_embeddable_factory'; + +export interface LegacyAngularInjectedDependencies { + queryFilter: any; + getUnhashableStates: any; + shareContextMenuExtensions: any; + config: any; + savedObjectRegistry: any; + savedObjectClient: any; + savedDashboards: any; + savedVisualizations: any; +} + +export interface VisualizePluginStartDependencies { + data: DataStart; + embeddables: ReturnType; + navigation: NavigationStart; + visualizations: VisualizationsStart; +} + +export interface VisualizePluginSetupDependencies { + __LEGACY: { + getAngularDependencies: () => Promise; + localApplicationService: LocalApplicationService; + FeatureCatalogueRegistryProvider: any; + docTitle: any; + }; + embeddable: any; +} + +export class VisualizePlugin implements Plugin { + private startDependencies: { + dataStart: DataStart; + savedObjectsClient: SavedObjectsClientContract; + embeddables: ReturnType; + navigation: NavigationStart; + visualizations: VisualizationsStart; + } | null = null; + + public setup( + core: CoreSetup, + { + __LEGACY: { localApplicationService, getAngularDependencies, ...legacyServices }, + embeddable, + }: VisualizePluginSetupDependencies + ) { + const app: App = { + id: '', + title: 'Visualize', + mount: async ({ core: contextCore }, params) => { + if (this.startDependencies === null) { + throw new Error('not started yet'); + } + + const { + dataStart, + savedObjectsClient, + embeddables, + navigation, + visualizations, + } = this.startDependencies; + + const angularDependencies = await getAngularDependencies(); + const deps = { + core: contextCore as LegacyCoreStart, + ...legacyServices, + ...angularDependencies, + addBasePath: contextCore.http.basePath.prepend, + angular, + chrome: contextCore.chrome, + dataStart, + docLinks: contextCore.docLinks, + embeddable, + embeddables, + getBasePath: core.http.basePath.get, + getInjected: core.injectedMetadata.getInjectedVar, + indexPatterns: dataStart.indexPatterns.indexPatterns, + indexPatternService: dataStart.indexPatterns.indexPatterns, + localStorage: new Storage(localStorage), + navigation, + savedObjectsClient, + savedQueryService: dataStart.search.services.savedQueryService, + uiCapabilities: contextCore.application.capabilities, + uiSettings: contextCore.uiSettings, + visualizeCapabilities: contextCore.application.capabilities.visualize, + VisEmbeddableFactory: VisualizeEmbeddableFactory, + visualizations, + wrapInI18nContext, + }; + setServices(deps); + const { renderApp } = await import('./render_app'); + return renderApp(params.element, params.appBasePath, deps); + }, + }; + localApplicationService.register({ ...app, id: 'visualize' }); + } + + start( + { savedObjects: { client: savedObjectsClient } }: CoreStart, + { data: dataStart, embeddables, navigation, visualizations }: VisualizePluginStartDependencies + ) { + this.startDependencies = { + dataStart, + savedObjectsClient, + embeddables, + navigation, + visualizations, + }; + } +} diff --git a/src/legacy/core_plugins/kibana/public/visualize/render_app.ts b/src/legacy/core_plugins/kibana/public/visualize/render_app.ts new file mode 100644 index 0000000000000..b11469afa6b3a --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/visualize/render_app.ts @@ -0,0 +1,300 @@ +/* + * 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 { EuiConfirmModal } from '@elastic/eui'; +import { IModule } from 'angular'; +import { IPrivate } from 'ui/private'; +import { i18nDirective, i18nFilter, I18nProvider } from '@kbn/i18n/src/angular'; +// @ts-ignore +import { GlobalStateProvider } from 'ui/state_management/global_state'; +// @ts-ignore +import { StateManagementConfigProvider } from 'ui/state_management/config_provider'; +// @ts-ignore +import { AppStateProvider } from 'ui/state_management/app_state'; +// @ts-ignore +import { PrivateProvider } from 'ui/private/private'; +// @ts-ignore +import { EventsProvider } from 'ui/events'; +// @ts-ignore +import { PersistedState } from 'ui/persisted_state'; +// @ts-ignore +import { createTopNavDirective, createTopNavHelper } from 'ui/kbn_top_nav/kbn_top_nav'; +// @ts-ignore +import { PromiseServiceCreator } from 'ui/promises/promises'; +// @ts-ignore +import { KbnUrlProvider, RedirectWhenMissingProvider } from 'ui/url'; +// @ts-ignore +import { confirmModalFactory } from 'ui/modals/confirm_modal'; + +import { + AppMountContext, + ChromeStart, + LegacyCoreStart, + SavedObjectsClientContract, + UiSettingsClientContract, +} from 'kibana/public'; +import { configureAppAngularModule } from 'ui/legacy_compat'; + +import { + createVisEditorSidebarDirective, + visEditorGroupDeps, + visOptionsDeps, + createVisEditorGroupDirective, + DefaultEditorAggGroup, + VisOptionsReactWrapper, + createVisOptionsDirective, +} from 'ui/vis/editors/default'; + +import { Storage } from '../../../../../plugins/kibana_utils/public'; + +// @ts-ignore +import { initVisualizeApp } from './app'; +import { + createApplyFiltersPopoverDirective, + createApplyFiltersPopoverHelper, + createFilterBarDirective, + createFilterBarHelper, + DataStart, +} from '../../../data/public'; +import { SavedQueryService } from '../../../data/public/search/search_bar/lib/saved_query_service'; +import { EmbeddablePublicPlugin } from '../../../../../plugins/embeddable/public'; +import { NavigationStart } from '../../../navigation/public'; +import { VisualizationsStart } from '../../../visualizations/public'; +import { VISUALIZE_EMBEDDABLE_TYPE, VisualizeEmbeddableFactory } from './embeddable/constants'; + +export interface RenderDeps { + angular: any; + core: LegacyCoreStart; + indexPatterns: DataStart['indexPatterns']['indexPatterns']; + dataStart: DataStart; + navigation: NavigationStart; + visualizations: VisualizationsStart; + queryFilter: any; + getUnhashableStates: any; + shareContextMenuExtensions: any; + savedObjectsClient: SavedObjectsClientContract; + savedObjectRegistry: any; + config: any; + savedDashboards: any; + visualizeCapabilities: any; + uiCapabilities: any; + docTitle: any; + uiSettings: UiSettingsClientContract; + chrome: ChromeStart; + addBasePath: (path: string) => string; + FeatureCatalogueRegistryProvider: any; + savedQueryService: SavedQueryService; + embeddables: ReturnType; + embeddable: ReturnType; + localStorage: Storage; + VisEmbeddableFactory: VisualizeEmbeddableFactory; + wrapInI18nContext: any; +} + +let angularModuleInstance: IModule | null = null; + +export const renderApp = async (element: HTMLElement, appBasePath: string, deps: RenderDeps) => { + if (!angularModuleInstance) { + angularModuleInstance = createLocalAngularModule( + deps.core, + deps.navigation, + deps.angular, + deps.wrapInI18nContext + ); + // global routing stuff + configureAppAngularModule(angularModuleInstance, deps.core as LegacyCoreStart); + // custom routing stuff + initVisualizeApp(angularModuleInstance, deps); + + const { VisEmbeddableFactory } = deps; + const createVisualizeEmbeddableFactory: () => Promise< + VisualizeEmbeddableFactory + > = async () => { + return new VisEmbeddableFactory(deps.visualizations.types); + }; + await createVisualizeEmbeddableFactory().then((embeddableFactory: any) => { + deps.embeddable.registerEmbeddableFactory(VISUALIZE_EMBEDDABLE_TYPE, embeddableFactory); + }); + } + const $injector = mountVisualizeApp(appBasePath, element, deps.angular); + return () => $injector.get('$rootScope').$destroy(); +}; + +const mainTemplate = (basePath: string) => `
+ +
+
+`; + +const moduleName = 'app/visualize'; + +const thirdPartyAngularDependencies = ['ngSanitize', 'ngRoute', 'react']; + +function mountVisualizeApp(appBasePath: string, element: HTMLElement, angular: any) { + const mountpoint = document.createElement('div'); + mountpoint.setAttribute('style', 'height: 100%'); + // eslint-disable-next-line + mountpoint.innerHTML = mainTemplate(appBasePath); + // bootstrap angular into detached element and attach it later to + // make angular-within-angular possible + const $injector = angular.bootstrap(mountpoint, [moduleName]); + // initialize global state handler + // $injector.get('globalState'); + element.appendChild(mountpoint); + return $injector; +} + +function createLocalAngularModule( + core: AppMountContext['core'], + navigation: NavigationStart, + angular: any, + wrapInI18nContext: any +) { + createLocalI18nModule(angular); + createLocalPrivateModule(angular); + createLocalPromiseModule(angular); + createLocalConfigModule(core, angular); + createLocalKbnUrlModule(angular); + createLocalStateModule(angular); + createLocalPersistedStateModule(angular); + createLocalTopNavModule(navigation, angular); + createLocalConfirmModalModule(angular); + createLocalFilterBarModule(angular); + + createLocalDefaultEditorModule(angular, wrapInI18nContext); + + const visualizeAngularModule: IModule = angular.module(moduleName, [ + ...thirdPartyAngularDependencies, + 'app/visualize/Config', + 'app/visualize/I18n', + 'app/visualize/Private', + 'app/visualize/PersistedState', + 'app/visualize/TopNav', + 'app/visualize/State', + 'app/visualize/ConfirmModal', + 'app/visualize/FilterBar', + ]); + return visualizeAngularModule; +} + +function createLocalConfirmModalModule(angular: any) { + angular + .module('app/visualize/ConfirmModal', ['react']) + .factory('confirmModal', confirmModalFactory) + .directive('confirmModal', reactDirective => reactDirective(EuiConfirmModal)); +} + +function createLocalStateModule(angular: any) { + angular + .module('app/visualize/State', [ + 'app/visualize/Private', + 'app/visualize/Config', + 'app/visualize/KbnUrl', + 'app/visualize/Promise', + 'app/visualize/PersistedState', + ]) + .factory('AppState', function(Private: any) { + return Private(AppStateProvider); + }) + .service('getAppState', function(Private: any) { + return Private(AppStateProvider).getAppState; + }) + .service('globalState', function(Private: any) { + return Private(GlobalStateProvider); + }); +} + +function createLocalPersistedStateModule(angular: any) { + angular + .module('app/visualize/PersistedState', ['app/visualize/Private', 'app/visualize/Promise']) + .factory('PersistedState', (Private: IPrivate) => { + const Events = Private(EventsProvider); + return class AngularPersistedState extends PersistedState { + constructor(value: any, path: any) { + super(value, path, Events); + } + }; + }); +} + +function createLocalKbnUrlModule(angular: any) { + angular + .module('app/visualize/KbnUrl', ['app/visualize/Private', 'ngRoute']) + .service('kbnUrl', (Private: IPrivate) => Private(KbnUrlProvider)) + .service('redirectWhenMissing', (Private: IPrivate) => Private(RedirectWhenMissingProvider)); +} + +function createLocalConfigModule(core: AppMountContext['core'], angular: any) { + angular + .module('app/visualize/Config', ['app/visualize/Private']) + .provider('stateManagementConfig', StateManagementConfigProvider) + .provider('config', () => { + return { + $get: () => ({ + get: core.uiSettings.get.bind(core.uiSettings), + }), + }; + }); +} + +function createLocalPromiseModule(angular: any) { + angular.module('app/visualize/Promise', []).service('Promise', PromiseServiceCreator); +} + +function createLocalPrivateModule(angular: any) { + angular.module('app/visualize/Private', []).provider('Private', PrivateProvider); +} + +function createLocalTopNavModule(navigation: NavigationStart, angular: any) { + angular + .module('app/visualize/TopNav', ['react']) + .directive('kbnTopNav', createTopNavDirective) + .directive('kbnTopNavHelper', createTopNavHelper(navigation.ui)); +} + +function createLocalFilterBarModule(angular: any) { + angular + .module('app/visualize/FilterBar', ['react']) + .directive('filterBar', createFilterBarDirective) + .directive('filterBarHelper', createFilterBarHelper) + .directive('applyFiltersPopover', createApplyFiltersPopoverDirective) + .directive('applyFiltersPopoverHelper', createApplyFiltersPopoverHelper); +} + +function createLocalI18nModule(angular: any) { + angular + .module('app/visualize/I18n', []) + .provider('i18n', I18nProvider) + .filter('i18n', i18nFilter) + .directive('i18nId', i18nDirective); +} + +function createLocalDefaultEditorModule(angular: any, wrapInI18nContext: any) { + angular + .module('app/visualize/editor', ['react', 'ngReact']) + .directive('visEditorSidebar', createVisEditorSidebarDirective) + // .directive('visEditorAggGroupWrapper', (reactDirective: any) => + // reactDirective(wrapInI18nContext(DefaultEditorAggGroup), visEditorGroupDeps) + // ) + // .directive('visOptionsReactWrapper', (reactDirective: any) => + // reactDirective(wrapInI18nContext(VisOptionsReactWrapper), visOptionsDeps) + // ) + // .directive('visEditorAggGroup', createVisEditorGroupDirective) + // .directive('visEditorVisOptions', createVisOptionsDirective); +} diff --git a/src/legacy/core_plugins/kibana/public/visualize/visualize_app.ts b/src/legacy/core_plugins/kibana/public/visualize/visualize_app.ts new file mode 100644 index 0000000000000..a1c8bf346a5b4 --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/visualize/visualize_app.ts @@ -0,0 +1,95 @@ +/* + * 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 { + AppStateClass as TAppStateClass, + AppState as TAppState, +} from 'ui/state_management/app_state'; + +import { KbnUrl } from 'ui/url/kbn_url'; +import { Filter } from '@kbn/es-query'; +import { TimeRange } from 'src/plugins/data/public'; +import { StaticIndexPattern, Query, SavedQuery } from 'plugins/data'; +import moment from 'moment'; +import { Subscription } from 'rxjs'; + +import { ViewMode } from '../../../embeddable_api/public/np_ready/public'; +import { SavedVisualizations } from './types'; + +// @ts-ignore +import { VisualizeAppController } from './editor/editor'; +import { RenderDeps } from './render_app'; + +// @ts-ignore +import { initEditorDirective } from './editor/editor'; +// @ts-ignore +import { initListingDirective } from './listing/visualize_listing'; + +export interface VisualizeAppScope extends ng.IScope { + // dash: SavedObjectDashboard; + appState: TAppState; + screenTitle: string; + model: { + query: Query; + filters: Filter[]; + timeRestore: boolean; + title: string; + description: string; + timeRange: + | TimeRange + | { to: string | moment.Moment | undefined; from: string | moment.Moment | undefined }; + refreshInterval: any; + }; + savedQuery?: SavedQuery; + refreshInterval: any; + panels: SavedVisualizations[]; + indexPatterns: StaticIndexPattern[]; + $evalAsync: any; + dashboardViewMode: ViewMode; + expandedPanel?: string; + getShouldShowEditHelp: () => boolean; + getShouldShowViewHelp: () => boolean; + updateQueryAndFetch: ({ query, dateRange }: { query: Query; dateRange?: TimeRange }) => void; + onRefreshChange: ({ + isPaused, + refreshInterval, + }: { + isPaused: boolean; + refreshInterval: any; + }) => void; + onFiltersUpdated: (filters: Filter[]) => void; + onCancelApplyFilters: () => void; + onApplyFilters: (filters: Filter[]) => void; + onQuerySaved: (savedQuery: SavedQuery) => void; + onSavedQueryUpdated: (savedQuery: SavedQuery) => void; + onClearSavedQuery: () => void; + topNavMenu: any; + showFilterBar: () => boolean; + showAddPanel: any; + showSaveQuery: boolean; + kbnTopNav: any; + enterEditMode: () => void; + timefilterSubscriptions$: Subscription; + isVisible: boolean; +} + +export function initVisualizeAppDirective(app: any, deps: RenderDeps) { + initEditorDirective(app, deps); + initListingDirective(app, deps); +} diff --git a/src/legacy/core_plugins/kibana/public/visualize/wizard/new_vis_modal.tsx b/src/legacy/core_plugins/kibana/public/visualize/wizard/new_vis_modal.tsx index 420f0e5198056..7c0d73a771471 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/wizard/new_vis_modal.tsx +++ b/src/legacy/core_plugins/kibana/public/visualize/wizard/new_vis_modal.tsx @@ -27,9 +27,7 @@ import { SearchSelection } from './search_selection'; import { TypeSelection } from './type_selection'; import { TypesStart, VisTypeAlias } from '../../../../visualizations/public/np_ready/public/types'; -import { getServices, METRIC_TYPE, VisType } from '../kibana_services'; - -const { addBasePath, createUiStatsReporter, uiSettings } = getServices(); +import { getServices, METRIC_TYPE, VisType, createUiStatsReporter } from '../kibana_services'; interface TypeSelectionProps { isOpen: boolean; @@ -51,11 +49,11 @@ class NewVisModal extends React.Component; + private readonly trackUiMetric: ReturnType; constructor(props: TypeSelectionProps) { super(props); - this.isLabsEnabled = uiSettings.get('visualize:enableLabs'); + this.isLabsEnabled = getServices().uiSettings.get('visualize:enableLabs'); this.state = { showSearchVisModal: false, @@ -124,7 +122,7 @@ class NewVisModal extends React.Component (reactDirective) => { return reactDirective( wrapInI18nContext(TopNavMenu), [ @@ -116,6 +115,6 @@ export function createTopNavHelper(reactDirective) { 'showAutoRefreshOnly', ], ); -} +}; -module.directive('kbnTopNavHelper', createTopNavHelper); +module.directive('kbnTopNavHelper', createTopNavHelper(navigation.ui)); diff --git a/src/legacy/ui/public/legacy_compat/angular_config.tsx b/src/legacy/ui/public/legacy_compat/angular_config.tsx index f5d3f83018d81..ffc177f75ec70 100644 --- a/src/legacy/ui/public/legacy_compat/angular_config.tsx +++ b/src/legacy/ui/public/legacy_compat/angular_config.tsx @@ -27,7 +27,7 @@ import { IRootScopeService, } from 'angular'; import $ from 'jquery'; -import { cloneDeep, forOwn, set } from 'lodash'; +import _, { cloneDeep, forOwn, get, set } from 'lodash'; import React, { Fragment } from 'react'; import * as Rx from 'rxjs'; @@ -36,13 +36,11 @@ import { FormattedMessage } from '@kbn/i18n/react'; import { CoreStart, LegacyCoreStart } from 'kibana/public'; import { fatalError } from 'ui/notify'; -import { capabilities } from 'ui/capabilities'; +import { RouteConfiguration } from 'ui/routes/route_manager'; // @ts-ignore import { modifyUrl } from 'ui/url'; // @ts-ignore import { UrlOverflowService } from '../error_url_overflow'; -import { npStart } from '../new_platform'; -import { toastNotifications } from '../notify'; // @ts-ignore import { isSystemApiRequest } from '../system_api'; @@ -54,8 +52,7 @@ function isDummyWrapperRoute($route: any) { ); } -export const configureAppAngularModule = (angularModule: IModule) => { - const newPlatform = npStart.core; +export const configureAppAngularModule = (angularModule: IModule, newPlatform: LegacyCoreStart) => { const legacyMetadata = newPlatform.injectedMetadata.getLegacyMetadata(); forOwn(newPlatform.injectedMetadata.getInjectedVars(), (val, name) => { @@ -71,7 +68,7 @@ export const configureAppAngularModule = (angularModule: IModule) => { .value('buildSha', legacyMetadata.buildSha) .value('serverName', legacyMetadata.serverName) .value('esUrl', getEsUrl(newPlatform)) - .value('uiCapabilities', capabilities.get()) + .value('uiCapabilities', newPlatform.application.capabilities) .config(setupCompileProvider(newPlatform)) .config(setupLocationProvider(newPlatform)) .config($setupXsrfRequestInterceptor(newPlatform)) @@ -79,7 +76,8 @@ export const configureAppAngularModule = (angularModule: IModule) => { .run($setupBreadcrumbsAutoClear(newPlatform)) .run($setupBadgeAutoClear(newPlatform)) .run($setupHelpExtensionAutoClear(newPlatform)) - .run($setupUrlOverflowHandling(newPlatform)); + .run($setupUrlOverflowHandling(newPlatform)) + .run($setupUICapabilityRedirect(newPlatform)); }; const getEsUrl = (newPlatform: CoreStart) => { @@ -166,6 +164,36 @@ const capture$httpLoadingCount = (newPlatform: CoreStart) => ( ); }; +/** + * integrates with angular to automatically redirect to home if required + * capability is not met + */ +const $setupUICapabilityRedirect = (newPlatform: CoreStart) => ( + $rootScope: IRootScopeService, + $injector: any +) => { + const isKibanaAppRoute = window.location.pathname.endsWith('/app/kibana'); + // this feature only works within kibana app for now after everything is + // switched to the application service, this can be changed to handle all + // apps. + if (!isKibanaAppRoute) { + return; + } + $rootScope.$on( + '$routeChangeStart', + (event, { $$route: route }: { $$route?: RouteConfiguration } = {}) => { + if (!route || !route.requireUICapability) { + return; + } + + if (!get(newPlatform.application.capabilities, route.requireUICapability)) { + $injector.get('kbnUrl').change('/home'); + event.preventDefault(); + } + } + ); +}; + /** * internal angular run function that will be called when angular bootstraps and * lets us integrate with the angular router so that we can automatically clear @@ -324,7 +352,7 @@ const $setupUrlOverflowHandling = (newPlatform: CoreStart) => ( try { if (urlOverflow.check($location.absUrl()) <= URL_LIMIT_WARN_WITHIN) { - toastNotifications.addWarning({ + newPlatform.notifications.toasts.addWarning({ title: i18n.translate('common.ui.chrome.bigUrlWarningNotificationTitle', { defaultMessage: 'The URL is big and Kibana might stop working', }), diff --git a/src/legacy/ui/public/legacy_compat/ensure_default_index_pattern.tsx b/src/legacy/ui/public/legacy_compat/ensure_default_index_pattern.tsx new file mode 100644 index 0000000000000..daedd9f329ed0 --- /dev/null +++ b/src/legacy/ui/public/legacy_compat/ensure_default_index_pattern.tsx @@ -0,0 +1,103 @@ +/* + * 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 { contains } from 'lodash'; +import { IRootScopeService } from 'angular'; +import React from 'react'; +import ReactDOM from 'react-dom'; +import { i18n } from '@kbn/i18n'; +import { I18nProvider } from '@kbn/i18n/react'; +import { EuiCallOut } from '@elastic/eui'; +import { CoreStart } from 'kibana/public'; +import { DataStart } from '../../../core_plugins/data/public'; + +let bannerId: string; +let timeoutId: NodeJS.Timeout | undefined; + +/** + * Checks whether a default index pattern is set and exists and defines + * one otherwise. + * + * If there are no index patterns, redirect to management page and show + * banner. In this case the promise returned from this function will never + * resolve to wait for the URL change to happen. + */ +export async function ensureDefaultIndexPattern( + newPlatform: CoreStart, + data: DataStart, + $rootScope: IRootScopeService, + kbnUrl: any +) { + const patterns = await data.indexPatterns.indexPatterns.getIds(); + let defaultId = newPlatform.uiSettings.get('defaultIndex'); + let defined = !!defaultId; + const exists = contains(patterns, defaultId); + + if (defined && !exists) { + newPlatform.uiSettings.remove('defaultIndex'); + defaultId = defined = false; + } + + if (!defined) { + // If there is any index pattern created, set the first as default + if (patterns.length >= 1) { + defaultId = patterns[0]; + newPlatform.uiSettings.set('defaultIndex', defaultId); + } else { + const canManageIndexPatterns = + newPlatform.application.capabilities.management.kibana.index_patterns; + const redirectTarget = canManageIndexPatterns ? '/management/kibana/index_pattern' : '/home'; + + if (timeoutId) { + clearTimeout(timeoutId); + } + + // Avoid being hostile to new users who don't have an index pattern setup yet + // give them a friendly info message instead of a terse error message + bannerId = newPlatform.overlays.banners.replace(bannerId, (element: HTMLElement) => { + ReactDOM.render( + + + , + element + ); + return () => ReactDOM.unmountComponentAtNode(element); + }); + + // hide the message after the user has had a chance to acknowledge it -- so it doesn't permanently stick around + timeoutId = setTimeout(() => { + newPlatform.overlays.banners.remove(bannerId); + timeoutId = undefined; + }, 15000); + + kbnUrl.change(redirectTarget); + $rootScope.$digest(); + + // return never-resolving promise to stop resolving and wait for the url change + return new Promise(() => {}); + } + } +} diff --git a/src/legacy/ui/public/legacy_compat/index.ts b/src/legacy/ui/public/legacy_compat/index.ts index b29056954051b..ea8932114118e 100644 --- a/src/legacy/ui/public/legacy_compat/index.ts +++ b/src/legacy/ui/public/legacy_compat/index.ts @@ -18,3 +18,4 @@ */ export { configureAppAngularModule } from './angular_config'; +export { ensureDefaultIndexPattern } from './ensure_default_index_pattern'; diff --git a/src/legacy/ui/public/vis/editors/default/agg_group.js b/src/legacy/ui/public/vis/editors/default/agg_group.js index e99c8dea3a91d..7de7e63bc6b25 100644 --- a/src/legacy/ui/public/vis/editors/default/agg_group.js +++ b/src/legacy/ui/public/vis/editors/default/agg_group.js @@ -22,34 +22,14 @@ import { wrapInI18nContext } from 'ui/i18n'; import { uiModules } from '../../../modules'; import { DefaultEditorAggGroup } from './components/agg_group'; -uiModules - .get('app/visualize') - .directive('visEditorAggGroupWrapper', reactDirective => - reactDirective(wrapInI18nContext(DefaultEditorAggGroup), [ - ['metricAggs', { watchDepth: 'reference' }], // we watch reference to identify each aggs change in useEffects - ['schemas', { watchDepth: 'collection' }], - ['state', { watchDepth: 'reference' }], - ['addSchema', { watchDepth: 'reference' }], - ['onAggParamsChange', { watchDepth: 'reference' }], - ['onAggTypeChange', { watchDepth: 'reference' }], - ['onToggleEnableAgg', { watchDepth: 'reference' }], - ['removeAgg', { watchDepth: 'reference' }], - ['reorderAggs', { watchDepth: 'reference' }], - ['setTouched', { watchDepth: 'reference' }], - ['setValidity', { watchDepth: 'reference' }], - 'groupName', - 'formIsTouched', - 'lastParentPipelineAggTitle', - 'currentTab', - ]) - ) - .directive('visEditorAggGroup', function () { - return { - restrict: 'E', - scope: true, - require: '?^ngModel', - template: function () { - return ` { + return { + restrict: 'E', + scope: true, + require: '?^ngModel', + template: () => { + return ``; - }, - link: function ($scope, $el, attr, ngModelCtrl) { - $scope.groupName = attr.groupName; - $scope.$bind('schemas', attr.schemas); - // The model can become touched either onBlur event or when the form is submitted. - // We also watch $touched to identify when the form is submitted. - $scope.$watch( - () => { - return ngModelCtrl.$touched; - }, - value => { - $scope.formIsTouched = value; - } - ); + }, + link: ($scope, $el, attr, ngModelCtrl) => { + $scope.groupName = attr.groupName; + $scope.$bind('schemas', attr.schemas); + // The model can become touched either onBlur event or when the form is submitted. + // We also watch $touched to identify when the form is submitted. + $scope.$watch( + () => { + return ngModelCtrl.$touched; + }, + value => { + $scope.formIsTouched = value; + } + ); + + $scope.setValidity = isValid => { + ngModelCtrl.$setValidity(`aggGroup${$scope.groupName}`, isValid); + }; - $scope.setValidity = isValid => { - ngModelCtrl.$setValidity(`aggGroup${$scope.groupName}`, isValid); - }; + $scope.setTouched = isTouched => { + if (isTouched) { + ngModelCtrl.$setTouched(); + } else { + ngModelCtrl.$setUntouched(); + } + }; + }, + }; +}; - $scope.setTouched = isTouched => { - if (isTouched) { - ngModelCtrl.$setTouched(); - } else { - ngModelCtrl.$setUntouched(); - } - }; - }, - }; - }); +/** @internal */ +export const visEditorGroupDeps = [ + ['metricAggs', { watchDepth: 'reference' }], // we watch reference to identify each aggs change in useEffects + ['schemas', { watchDepth: 'collection' }], + ['state', { watchDepth: 'reference' }], + ['addSchema', { watchDepth: 'reference' }], + ['onAggParamsChange', { watchDepth: 'reference' }], + ['onAggTypeChange', { watchDepth: 'reference' }], + ['onToggleEnableAgg', { watchDepth: 'reference' }], + ['removeAgg', { watchDepth: 'reference' }], + ['reorderAggs', { watchDepth: 'reference' }], + ['setTouched', { watchDepth: 'reference' }], + ['setValidity', { watchDepth: 'reference' }], + 'groupName', + 'formIsTouched', + 'lastParentPipelineAggTitle', + 'currentTab', +]; + +uiModules + .get('app/visualize') + .directive('visEditorAggGroupWrapper', reactDirective => + reactDirective(wrapInI18nContext(DefaultEditorAggGroup), visEditorGroupDeps) + ) + .directive('visEditorAggGroup', createVisEditorGroupDirective); diff --git a/src/legacy/ui/public/vis/editors/default/index.ts b/src/legacy/ui/public/vis/editors/default/index.ts index 7079ba23afb5c..09172fba714d2 100644 --- a/src/legacy/ui/public/vis/editors/default/index.ts +++ b/src/legacy/ui/public/vis/editors/default/index.ts @@ -23,3 +23,12 @@ export { ComboBoxGroupedOptions } from './utils'; export * from './vis_options_props'; export * from './utils'; export * from './agg_groups'; +// @ts-ignore +export { createVisEditorGroupDirective, visEditorGroupDeps } from './agg_groups'; +export { DefaultEditorAggGroup } from './components/agg_group'; + +// @ts-ignore +export { createVisOptionsDirective, visOptionsDeps } from './vis_options'; +export { VisOptionsReactWrapper } from './vis_options_react_wrapper'; +// @ts-ignore +export { createVisEditorSidebarDirective } from './sidebar'; diff --git a/src/legacy/ui/public/vis/editors/default/sidebar.js b/src/legacy/ui/public/vis/editors/default/sidebar.js index 92cb99c56038d..0196072be7e03 100644 --- a/src/legacy/ui/public/vis/editors/default/sidebar.js +++ b/src/legacy/ui/public/vis/editors/default/sidebar.js @@ -27,7 +27,8 @@ import { move } from '../../../utils/collection'; import { AggGroupNames } from './agg_groups'; import { getEnabledMetricAggsCount } from './components/agg_group_helper'; -uiModules.get('app/visualize').directive('visEditorSidebar', function () { +/** @internal */ +export const createVisEditorSidebarDirective = () => { return { restrict: 'E', template: sidebarTemplate, @@ -102,4 +103,6 @@ uiModules.get('app/visualize').directive('visEditorSidebar', function () { }; }, }; -}); +}; + +uiModules.get('app/visualize').directive('visEditorSidebar', createVisEditorSidebarDirective); diff --git a/src/legacy/ui/public/vis/editors/default/vis_options.js b/src/legacy/ui/public/vis/editors/default/vis_options.js index 6425a03ed9a74..45e133278a50c 100644 --- a/src/legacy/ui/public/vis/editors/default/vis_options.js +++ b/src/legacy/ui/public/vis/editors/default/vis_options.js @@ -22,6 +22,91 @@ import { uiModules } from '../../../modules'; import { VisOptionsReactWrapper } from './vis_options_react_wrapper'; import { safeMakeLabel } from './controls/agg_utils'; +/** @internal */ +export const visOptionsDeps = [ + ['component', { wrapApply: false }], + ['aggs', { watchDepth: 'collection' }], + ['stateParams', { watchDepth: 'collection' }], + ['vis', { watchDepth: 'collection' }], + ['uiState', { watchDepth: 'collection' }], + ['setValue', { watchDepth: 'reference' }], + ['setValidity', { watchDepth: 'reference' }], + ['setVisType', { watchDepth: 'reference' }], + ['setTouched', { watchDepth: 'reference' }], + 'hasHistogramAgg', + 'currentTab', + 'aggsLabels', +]; + +/** @internal */ +export const createVisOptionsDirective = ($compile) => { + return { + restrict: 'E', + require: '?^ngModel', + scope: { + vis: '=', + visData: '=', + uiState: '=', + editor: '=', + visualizeEditor: '=', + editorState: '=', + onAggParamsChange: '=', + hasHistogramAgg: '=', + currentTab: '=', + }, + link: function ($scope, $el, attrs, ngModelCtrl) { + $scope.setValue = (paramName, value) => + $scope.onAggParamsChange($scope.editorState.params, paramName, value); + + $scope.setValidity = isValid => { + ngModelCtrl.$setValidity(`visOptions`, isValid); + }; + + $scope.setTouched = isTouched => { + if (isTouched) { + ngModelCtrl.$setTouched(); + } else { + ngModelCtrl.$setUntouched(); + } + }; + + $scope.setVisType = (type) => { + $scope.vis.type.type = type; + }; + + // since aggs reference isn't changed when an agg is updated, we need somehow to let React component know about it + $scope.aggsLabels = ''; + + $scope.$watch(() => { + return $scope.editorState.aggs.aggs.map(agg => { + return safeMakeLabel(agg); + }).join(); + }, value => { + $scope.aggsLabels = value; + }); + + const comp = typeof $scope.editor === 'string' ? + $scope.editor : + ` + `; + const $editor = $compile(comp)($scope); + $el.append($editor); + } + }; +}; + /** * This directive sort of "transcludes" in whatever template you pass in via the `editor` attribute. * This lets you specify a full-screen UI for editing a vis type, instead of using the regular @@ -30,84 +115,5 @@ import { safeMakeLabel } from './controls/agg_utils'; uiModules .get('app/visualize') - .directive('visOptionsReactWrapper', reactDirective => reactDirective(wrapInI18nContext(VisOptionsReactWrapper), [ - ['component', { wrapApply: false }], - ['aggs', { watchDepth: 'collection' }], - ['stateParams', { watchDepth: 'collection' }], - ['vis', { watchDepth: 'collection' }], - ['uiState', { watchDepth: 'collection' }], - ['setValue', { watchDepth: 'reference' }], - ['setValidity', { watchDepth: 'reference' }], - ['setVisType', { watchDepth: 'reference' }], - ['setTouched', { watchDepth: 'reference' }], - 'hasHistogramAgg', - 'currentTab', - 'aggsLabels', - ])) - .directive('visEditorVisOptions', function ($compile) { - return { - restrict: 'E', - require: '?^ngModel', - scope: { - vis: '=', - visData: '=', - uiState: '=', - editor: '=', - visualizeEditor: '=', - editorState: '=', - onAggParamsChange: '=', - hasHistogramAgg: '=', - currentTab: '=', - }, - link: function ($scope, $el, attrs, ngModelCtrl) { - $scope.setValue = (paramName, value) => - $scope.onAggParamsChange($scope.editorState.params, paramName, value); - - $scope.setValidity = isValid => { - ngModelCtrl.$setValidity(`visOptions`, isValid); - }; - - $scope.setTouched = isTouched => { - if (isTouched) { - ngModelCtrl.$setTouched(); - } else { - ngModelCtrl.$setUntouched(); - } - }; - - $scope.setVisType = (type) => { - $scope.vis.type.type = type; - }; - - // since aggs reference isn't changed when an agg is updated, we need somehow to let React component know about it - $scope.aggsLabels = ''; - - $scope.$watch(() => { - return $scope.editorState.aggs.aggs.map(agg => { - return safeMakeLabel(agg); - }).join(); - }, value => { - $scope.aggsLabels = value; - }); - - const comp = typeof $scope.editor === 'string' ? - $scope.editor : - ` - `; - const $editor = $compile(comp)($scope); - $el.append($editor); - } - }; - }); + .directive('visOptionsReactWrapper', reactDirective => reactDirective(wrapInI18nContext(VisOptionsReactWrapper), visOptionsDeps)) + .directive('visEditorVisOptions', createVisOptionsDirective); From 936f09a2988db491381052d3447e977ca3cb8ad3 Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Thu, 31 Oct 2019 22:00:42 +0100 Subject: [PATCH 045/132] added some debug notes --- .../public/dashboard/_dashboard_app.scss | 2 +- .../public/discover/angular/discover.js | 1 + .../ui/public/vis/vis_filters/vis_filters.js | 25 ++++++++++++++++++- 3 files changed, 26 insertions(+), 2 deletions(-) diff --git a/src/legacy/core_plugins/kibana/public/dashboard/_dashboard_app.scss b/src/legacy/core_plugins/kibana/public/dashboard/_dashboard_app.scss index eebfad5979d68..14c35759d70a9 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/_dashboard_app.scss +++ b/src/legacy/core_plugins/kibana/public/dashboard/_dashboard_app.scss @@ -1,7 +1,7 @@ .dshAppContainer { - flex: 1; display: flex; flex-direction: column; + height: 100%; } .dshStartScreen { diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/discover.js b/src/legacy/core_plugins/kibana/public/discover/angular/discover.js index dff8a3780e3f5..cd8aaaa27faf3 100644 --- a/src/legacy/core_plugins/kibana/public/discover/angular/discover.js +++ b/src/legacy/core_plugins/kibana/public/discover/angular/discover.js @@ -427,6 +427,7 @@ function discoverController( queryFilter.setFilters(filters); }; + // TODO this isnt used anymore here, just in visualize and dashboards $scope.applyFilters = filters => { const { timeRangeFilter, restOfFilters } = extractTimeFilter($scope.indexPattern.timeFieldName, filters); queryFilter.addFilters(restOfFilters); diff --git a/src/legacy/ui/public/vis/vis_filters/vis_filters.js b/src/legacy/ui/public/vis/vis_filters/vis_filters.js index fa12237808910..26bdc91b42758 100644 --- a/src/legacy/ui/public/vis/vis_filters/vis_filters.js +++ b/src/legacy/ui/public/vis/vis_filters/vis_filters.js @@ -21,6 +21,9 @@ import { npStart } from 'ui/new_platform'; import { onBrushEvent } from './brush_event'; import { uniqFilters } from '../../../../../plugins/data/public'; import { toggleFilterNegated } from '@kbn/es-query'; +import _ from "lodash"; +import { changeTimeFilter, extractTimeFilter } from '../../../../core_plugins/data/public/timefilter'; +import { start as data } from '../../../../core_plugins/data/public/legacy'; /** * For terms aggregations on `__other__` buckets, this assembles a list of applicable filter * terms based on a specific cell in the tabified data. @@ -105,9 +108,29 @@ const createFiltersFromEvent = (event) => { const VisFiltersProvider = () => { + // TODO this function used to simply put the new filters in + // the app state. Dashboard/Visualize simply listened to + // the app state change via angular and pushed it into the actual + // filter manager (while splitting out the time filter) + // This channel does not work anymore because it's not the same + // angular context and thus the appstate won't update const pushFilters = (filters, simulate) => { if (filters.length && !simulate) { - npStart.plugins.data.query.filterManager.addFilters(uniqFilters(filters)); + // All filters originated from one visualization. + const indexPatternId = filters[0].meta.index; + const indexPattern = _.find( + $scope.indexPatterns, + p => p.id === indexPatternId + ); + if (indexPattern && indexPattern.timeFieldName) { + const { timeRangeFilter, restOfFilters } = extractTimeFilter( + indexPattern.timeFieldName, + filters + ); + npStart.plugins.data.query.filterManager.addFilters(uniqFilters(filters)); + npStart.plugins.data.query.filterManager.addFilters(restOfFilters); + if (timeRangeFilter) changeTimeFilter(data.timefilter.timefilter, timeRangeFilter); + } } }; From 784a20ed3712db702b9576b16b706ae791be375b Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Thu, 31 Oct 2019 22:14:02 +0100 Subject: [PATCH 046/132] fix vis filters --- .../ui/public/vis/vis_filters/vis_filters.js | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/legacy/ui/public/vis/vis_filters/vis_filters.js b/src/legacy/ui/public/vis/vis_filters/vis_filters.js index 26bdc91b42758..c23e3f333e4c4 100644 --- a/src/legacy/ui/public/vis/vis_filters/vis_filters.js +++ b/src/legacy/ui/public/vis/vis_filters/vis_filters.js @@ -21,7 +21,7 @@ import { npStart } from 'ui/new_platform'; import { onBrushEvent } from './brush_event'; import { uniqFilters } from '../../../../../plugins/data/public'; import { toggleFilterNegated } from '@kbn/es-query'; -import _ from "lodash"; +import _ from 'lodash'; import { changeTimeFilter, extractTimeFilter } from '../../../../core_plugins/data/public/timefilter'; import { start as data } from '../../../../core_plugins/data/public/legacy'; /** @@ -106,6 +106,7 @@ const createFiltersFromEvent = (event) => { return filters; }; +// TODO make sure the visualize app is updating the breadcrumb correctly const VisFiltersProvider = () => { // TODO this function used to simply put the new filters in @@ -114,21 +115,21 @@ const VisFiltersProvider = () => { // filter manager (while splitting out the time filter) // This channel does not work anymore because it's not the same // angular context and thus the appstate won't update - const pushFilters = (filters, simulate) => { + const pushFilters = async (filters, simulate) => { if (filters.length && !simulate) { // All filters originated from one visualization. const indexPatternId = filters[0].meta.index; const indexPattern = _.find( - $scope.indexPatterns, + await data.indexPatterns.indexPatterns.getCache(), p => p.id === indexPatternId ); - if (indexPattern && indexPattern.timeFieldName) { + // TODO just add everything to the filter bar if index pattern doesn't have timefield + if (indexPattern && indexPattern.attributes.timeFieldName) { const { timeRangeFilter, restOfFilters } = extractTimeFilter( - indexPattern.timeFieldName, + indexPattern.attributes.timeFieldName, filters ); - npStart.plugins.data.query.filterManager.addFilters(uniqFilters(filters)); - npStart.plugins.data.query.filterManager.addFilters(restOfFilters); + npStart.plugins.data.query.filterManager.addFilters(uniqFilters(restOfFilters)); if (timeRangeFilter) changeTimeFilter(data.timefilter.timefilter, timeRangeFilter); } } From b4b3bdf4815c7282a07fc6e037e65f79f0f97cbf Mon Sep 17 00:00:00 2001 From: maryia-lapata Date: Fri, 1 Nov 2019 11:48:31 +0300 Subject: [PATCH 047/132] Revert loading sidebar --- src/legacy/core_plugins/kibana/index.js | 1 + .../kibana/public/visualize/index.ts | 4 +- .../kibana/public/visualize/render_app.ts | 16 -- .../public/vis/editors/default/agg_group.js | 116 ++++++------ .../ui/public/vis/editors/default/index.ts | 9 - .../ui/public/vis/editors/default/sidebar.js | 7 +- .../public/vis/editors/default/vis_options.js | 168 +++++++++--------- 7 files changed, 142 insertions(+), 179 deletions(-) diff --git a/src/legacy/core_plugins/kibana/index.js b/src/legacy/core_plugins/kibana/index.js index 24cd436912395..82c8028825e73 100644 --- a/src/legacy/core_plugins/kibana/index.js +++ b/src/legacy/core_plugins/kibana/index.js @@ -63,6 +63,7 @@ export default function (kibana) { uiExports: { hacks: [ 'plugins/kibana/dev_tools/hacks/hide_empty_tools', + 'plugins/kibana/visualize/index' ], fieldFormats: ['plugins/kibana/field_formats/register'], savedObjectTypes: [ diff --git a/src/legacy/core_plugins/kibana/public/visualize/index.ts b/src/legacy/core_plugins/kibana/public/visualize/index.ts index cad643cc09e42..0cd4b751bf82a 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/index.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/index.ts @@ -54,6 +54,7 @@ async function getAngularDependencies(): Promise { const instance = new VisualizePlugin(); - instance.setup(npSetup.core, { + await instance.setup(npSetup.core, { __LEGACY: { localApplicationService, getAngularDependencies, diff --git a/src/legacy/core_plugins/kibana/public/visualize/render_app.ts b/src/legacy/core_plugins/kibana/public/visualize/render_app.ts index b11469afa6b3a..12b596c4438e7 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/render_app.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/render_app.ts @@ -177,8 +177,6 @@ function createLocalAngularModule( createLocalConfirmModalModule(angular); createLocalFilterBarModule(angular); - createLocalDefaultEditorModule(angular, wrapInI18nContext); - const visualizeAngularModule: IModule = angular.module(moduleName, [ ...thirdPartyAngularDependencies, 'app/visualize/Config', @@ -284,17 +282,3 @@ function createLocalI18nModule(angular: any) { .filter('i18n', i18nFilter) .directive('i18nId', i18nDirective); } - -function createLocalDefaultEditorModule(angular: any, wrapInI18nContext: any) { - angular - .module('app/visualize/editor', ['react', 'ngReact']) - .directive('visEditorSidebar', createVisEditorSidebarDirective) - // .directive('visEditorAggGroupWrapper', (reactDirective: any) => - // reactDirective(wrapInI18nContext(DefaultEditorAggGroup), visEditorGroupDeps) - // ) - // .directive('visOptionsReactWrapper', (reactDirective: any) => - // reactDirective(wrapInI18nContext(VisOptionsReactWrapper), visOptionsDeps) - // ) - // .directive('visEditorAggGroup', createVisEditorGroupDirective) - // .directive('visEditorVisOptions', createVisOptionsDirective); -} diff --git a/src/legacy/ui/public/vis/editors/default/agg_group.js b/src/legacy/ui/public/vis/editors/default/agg_group.js index 7de7e63bc6b25..e99c8dea3a91d 100644 --- a/src/legacy/ui/public/vis/editors/default/agg_group.js +++ b/src/legacy/ui/public/vis/editors/default/agg_group.js @@ -22,14 +22,34 @@ import { wrapInI18nContext } from 'ui/i18n'; import { uiModules } from '../../../modules'; import { DefaultEditorAggGroup } from './components/agg_group'; -/** @internal */ -export const createVisEditorGroupDirective = () => { - return { - restrict: 'E', - scope: true, - require: '?^ngModel', - template: () => { - return ` + reactDirective(wrapInI18nContext(DefaultEditorAggGroup), [ + ['metricAggs', { watchDepth: 'reference' }], // we watch reference to identify each aggs change in useEffects + ['schemas', { watchDepth: 'collection' }], + ['state', { watchDepth: 'reference' }], + ['addSchema', { watchDepth: 'reference' }], + ['onAggParamsChange', { watchDepth: 'reference' }], + ['onAggTypeChange', { watchDepth: 'reference' }], + ['onToggleEnableAgg', { watchDepth: 'reference' }], + ['removeAgg', { watchDepth: 'reference' }], + ['reorderAggs', { watchDepth: 'reference' }], + ['setTouched', { watchDepth: 'reference' }], + ['setValidity', { watchDepth: 'reference' }], + 'groupName', + 'formIsTouched', + 'lastParentPipelineAggTitle', + 'currentTab', + ]) + ) + .directive('visEditorAggGroup', function () { + return { + restrict: 'E', + scope: true, + require: '?^ngModel', + template: function () { + return ` { set-validity="setValidity" set-touched="setTouched" >`; - }, - link: ($scope, $el, attr, ngModelCtrl) => { - $scope.groupName = attr.groupName; - $scope.$bind('schemas', attr.schemas); - // The model can become touched either onBlur event or when the form is submitted. - // We also watch $touched to identify when the form is submitted. - $scope.$watch( - () => { - return ngModelCtrl.$touched; - }, - value => { - $scope.formIsTouched = value; - } - ); - - $scope.setValidity = isValid => { - ngModelCtrl.$setValidity(`aggGroup${$scope.groupName}`, isValid); - }; + }, + link: function ($scope, $el, attr, ngModelCtrl) { + $scope.groupName = attr.groupName; + $scope.$bind('schemas', attr.schemas); + // The model can become touched either onBlur event or when the form is submitted. + // We also watch $touched to identify when the form is submitted. + $scope.$watch( + () => { + return ngModelCtrl.$touched; + }, + value => { + $scope.formIsTouched = value; + } + ); - $scope.setTouched = isTouched => { - if (isTouched) { - ngModelCtrl.$setTouched(); - } else { - ngModelCtrl.$setUntouched(); - } - }; - }, - }; -}; + $scope.setValidity = isValid => { + ngModelCtrl.$setValidity(`aggGroup${$scope.groupName}`, isValid); + }; -/** @internal */ -export const visEditorGroupDeps = [ - ['metricAggs', { watchDepth: 'reference' }], // we watch reference to identify each aggs change in useEffects - ['schemas', { watchDepth: 'collection' }], - ['state', { watchDepth: 'reference' }], - ['addSchema', { watchDepth: 'reference' }], - ['onAggParamsChange', { watchDepth: 'reference' }], - ['onAggTypeChange', { watchDepth: 'reference' }], - ['onToggleEnableAgg', { watchDepth: 'reference' }], - ['removeAgg', { watchDepth: 'reference' }], - ['reorderAggs', { watchDepth: 'reference' }], - ['setTouched', { watchDepth: 'reference' }], - ['setValidity', { watchDepth: 'reference' }], - 'groupName', - 'formIsTouched', - 'lastParentPipelineAggTitle', - 'currentTab', -]; - -uiModules - .get('app/visualize') - .directive('visEditorAggGroupWrapper', reactDirective => - reactDirective(wrapInI18nContext(DefaultEditorAggGroup), visEditorGroupDeps) - ) - .directive('visEditorAggGroup', createVisEditorGroupDirective); + $scope.setTouched = isTouched => { + if (isTouched) { + ngModelCtrl.$setTouched(); + } else { + ngModelCtrl.$setUntouched(); + } + }; + }, + }; + }); diff --git a/src/legacy/ui/public/vis/editors/default/index.ts b/src/legacy/ui/public/vis/editors/default/index.ts index 09172fba714d2..7079ba23afb5c 100644 --- a/src/legacy/ui/public/vis/editors/default/index.ts +++ b/src/legacy/ui/public/vis/editors/default/index.ts @@ -23,12 +23,3 @@ export { ComboBoxGroupedOptions } from './utils'; export * from './vis_options_props'; export * from './utils'; export * from './agg_groups'; -// @ts-ignore -export { createVisEditorGroupDirective, visEditorGroupDeps } from './agg_groups'; -export { DefaultEditorAggGroup } from './components/agg_group'; - -// @ts-ignore -export { createVisOptionsDirective, visOptionsDeps } from './vis_options'; -export { VisOptionsReactWrapper } from './vis_options_react_wrapper'; -// @ts-ignore -export { createVisEditorSidebarDirective } from './sidebar'; diff --git a/src/legacy/ui/public/vis/editors/default/sidebar.js b/src/legacy/ui/public/vis/editors/default/sidebar.js index 0196072be7e03..92cb99c56038d 100644 --- a/src/legacy/ui/public/vis/editors/default/sidebar.js +++ b/src/legacy/ui/public/vis/editors/default/sidebar.js @@ -27,8 +27,7 @@ import { move } from '../../../utils/collection'; import { AggGroupNames } from './agg_groups'; import { getEnabledMetricAggsCount } from './components/agg_group_helper'; -/** @internal */ -export const createVisEditorSidebarDirective = () => { +uiModules.get('app/visualize').directive('visEditorSidebar', function () { return { restrict: 'E', template: sidebarTemplate, @@ -103,6 +102,4 @@ export const createVisEditorSidebarDirective = () => { }; }, }; -}; - -uiModules.get('app/visualize').directive('visEditorSidebar', createVisEditorSidebarDirective); +}); diff --git a/src/legacy/ui/public/vis/editors/default/vis_options.js b/src/legacy/ui/public/vis/editors/default/vis_options.js index 45e133278a50c..6425a03ed9a74 100644 --- a/src/legacy/ui/public/vis/editors/default/vis_options.js +++ b/src/legacy/ui/public/vis/editors/default/vis_options.js @@ -22,91 +22,6 @@ import { uiModules } from '../../../modules'; import { VisOptionsReactWrapper } from './vis_options_react_wrapper'; import { safeMakeLabel } from './controls/agg_utils'; -/** @internal */ -export const visOptionsDeps = [ - ['component', { wrapApply: false }], - ['aggs', { watchDepth: 'collection' }], - ['stateParams', { watchDepth: 'collection' }], - ['vis', { watchDepth: 'collection' }], - ['uiState', { watchDepth: 'collection' }], - ['setValue', { watchDepth: 'reference' }], - ['setValidity', { watchDepth: 'reference' }], - ['setVisType', { watchDepth: 'reference' }], - ['setTouched', { watchDepth: 'reference' }], - 'hasHistogramAgg', - 'currentTab', - 'aggsLabels', -]; - -/** @internal */ -export const createVisOptionsDirective = ($compile) => { - return { - restrict: 'E', - require: '?^ngModel', - scope: { - vis: '=', - visData: '=', - uiState: '=', - editor: '=', - visualizeEditor: '=', - editorState: '=', - onAggParamsChange: '=', - hasHistogramAgg: '=', - currentTab: '=', - }, - link: function ($scope, $el, attrs, ngModelCtrl) { - $scope.setValue = (paramName, value) => - $scope.onAggParamsChange($scope.editorState.params, paramName, value); - - $scope.setValidity = isValid => { - ngModelCtrl.$setValidity(`visOptions`, isValid); - }; - - $scope.setTouched = isTouched => { - if (isTouched) { - ngModelCtrl.$setTouched(); - } else { - ngModelCtrl.$setUntouched(); - } - }; - - $scope.setVisType = (type) => { - $scope.vis.type.type = type; - }; - - // since aggs reference isn't changed when an agg is updated, we need somehow to let React component know about it - $scope.aggsLabels = ''; - - $scope.$watch(() => { - return $scope.editorState.aggs.aggs.map(agg => { - return safeMakeLabel(agg); - }).join(); - }, value => { - $scope.aggsLabels = value; - }); - - const comp = typeof $scope.editor === 'string' ? - $scope.editor : - ` - `; - const $editor = $compile(comp)($scope); - $el.append($editor); - } - }; -}; - /** * This directive sort of "transcludes" in whatever template you pass in via the `editor` attribute. * This lets you specify a full-screen UI for editing a vis type, instead of using the regular @@ -115,5 +30,84 @@ export const createVisOptionsDirective = ($compile) => { uiModules .get('app/visualize') - .directive('visOptionsReactWrapper', reactDirective => reactDirective(wrapInI18nContext(VisOptionsReactWrapper), visOptionsDeps)) - .directive('visEditorVisOptions', createVisOptionsDirective); + .directive('visOptionsReactWrapper', reactDirective => reactDirective(wrapInI18nContext(VisOptionsReactWrapper), [ + ['component', { wrapApply: false }], + ['aggs', { watchDepth: 'collection' }], + ['stateParams', { watchDepth: 'collection' }], + ['vis', { watchDepth: 'collection' }], + ['uiState', { watchDepth: 'collection' }], + ['setValue', { watchDepth: 'reference' }], + ['setValidity', { watchDepth: 'reference' }], + ['setVisType', { watchDepth: 'reference' }], + ['setTouched', { watchDepth: 'reference' }], + 'hasHistogramAgg', + 'currentTab', + 'aggsLabels', + ])) + .directive('visEditorVisOptions', function ($compile) { + return { + restrict: 'E', + require: '?^ngModel', + scope: { + vis: '=', + visData: '=', + uiState: '=', + editor: '=', + visualizeEditor: '=', + editorState: '=', + onAggParamsChange: '=', + hasHistogramAgg: '=', + currentTab: '=', + }, + link: function ($scope, $el, attrs, ngModelCtrl) { + $scope.setValue = (paramName, value) => + $scope.onAggParamsChange($scope.editorState.params, paramName, value); + + $scope.setValidity = isValid => { + ngModelCtrl.$setValidity(`visOptions`, isValid); + }; + + $scope.setTouched = isTouched => { + if (isTouched) { + ngModelCtrl.$setTouched(); + } else { + ngModelCtrl.$setUntouched(); + } + }; + + $scope.setVisType = (type) => { + $scope.vis.type.type = type; + }; + + // since aggs reference isn't changed when an agg is updated, we need somehow to let React component know about it + $scope.aggsLabels = ''; + + $scope.$watch(() => { + return $scope.editorState.aggs.aggs.map(agg => { + return safeMakeLabel(agg); + }).join(); + }, value => { + $scope.aggsLabels = value; + }); + + const comp = typeof $scope.editor === 'string' ? + $scope.editor : + ` + `; + const $editor = $compile(comp)($scope); + $el.append($editor); + } + }; + }); From ba785397a2dc8810973c0e4f86e781fe2b9c5f5b Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Fri, 1 Nov 2019 10:44:59 +0100 Subject: [PATCH 048/132] improve --- src/legacy/ui/public/vis/vis_filters/vis_filters.js | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/legacy/ui/public/vis/vis_filters/vis_filters.js b/src/legacy/ui/public/vis/vis_filters/vis_filters.js index c23e3f333e4c4..cf904603e3beb 100644 --- a/src/legacy/ui/public/vis/vis_filters/vis_filters.js +++ b/src/legacy/ui/public/vis/vis_filters/vis_filters.js @@ -117,20 +117,25 @@ const VisFiltersProvider = () => { // angular context and thus the appstate won't update const pushFilters = async (filters, simulate) => { if (filters.length && !simulate) { + const dedupedFilters = uniqFilters(filters); // All filters originated from one visualization. - const indexPatternId = filters[0].meta.index; + const indexPatternId = dedupedFilters[0].meta.index; const indexPattern = _.find( await data.indexPatterns.indexPatterns.getCache(), p => p.id === indexPatternId ); - // TODO just add everything to the filter bar if index pattern doesn't have timefield + if (dedupedFilters.length > 1) { + // TODO show apply filter popover and wait for user input + } if (indexPattern && indexPattern.attributes.timeFieldName) { const { timeRangeFilter, restOfFilters } = extractTimeFilter( indexPattern.attributes.timeFieldName, - filters + dedupedFilters ); - npStart.plugins.data.query.filterManager.addFilters(uniqFilters(restOfFilters)); + npStart.plugins.data.query.filterManager.addFilters(restOfFilters); if (timeRangeFilter) changeTimeFilter(data.timefilter.timefilter, timeRangeFilter); + } else { + npStart.plugins.data.query.filterManager.addFilters(dedupedFilters); } } }; From 9eb9d5221c80af409800bf72e911275765bee431 Mon Sep 17 00:00:00 2001 From: maryia-lapata Date: Fri, 1 Nov 2019 15:42:46 +0300 Subject: [PATCH 049/132] Revert --- .../kibana/public/home/components/home_app.js | 102 +-- .../core_plugins/kibana/public/home/index.js | 61 ++ .../core_plugins/kibana/public/home/index.ts | 79 --- .../kibana/public/home/kibana_services.ts | 113 +-- .../core_plugins/kibana/public/home/plugin.ts | 105 --- .../kibana/public/home/render_app.tsx | 38 - .../cockroachdb_metrics/screenshot.png | Bin 233000 -> 0 bytes .../tutorial_resources/logos/cockroachdb.svg | 666 ------------------ .../local_application_service.ts | 35 +- .../public/visualize/help_menu/help_menu.js | 27 +- .../public/legacy_compat/angular_config.tsx | 5 - .../ensure_default_index_pattern.tsx | 2 +- .../ui/public/routes/route_manager.d.ts | 2 +- src/legacy/ui/public/routes/route_manager.js | 4 - .../ui/public/vis/vis_filters/vis_filters.js | 41 +- 15 files changed, 236 insertions(+), 1044 deletions(-) create mode 100644 src/legacy/core_plugins/kibana/public/home/index.js delete mode 100644 src/legacy/core_plugins/kibana/public/home/index.ts delete mode 100644 src/legacy/core_plugins/kibana/public/home/plugin.ts delete mode 100644 src/legacy/core_plugins/kibana/public/home/render_app.tsx delete mode 100644 src/legacy/core_plugins/kibana/public/home/tutorial_resources/cockroachdb_metrics/screenshot.png delete mode 100644 src/legacy/core_plugins/kibana/public/home/tutorial_resources/logos/cockroachdb.svg diff --git a/src/legacy/core_plugins/kibana/public/home/components/home_app.js b/src/legacy/core_plugins/kibana/public/home/components/home_app.js index e4a6753e0771a..005d4bdb0a99e 100644 --- a/src/legacy/core_plugins/kibana/public/home/components/home_app.js +++ b/src/legacy/core_plugins/kibana/public/home/components/home_app.js @@ -18,16 +18,21 @@ */ import React from 'react'; -import { I18nProvider } from '@kbn/i18n/react'; import PropTypes from 'prop-types'; import { Home } from './home'; import { FeatureDirectory } from './feature_directory'; import { TutorialDirectory } from './tutorial_directory'; import { Tutorial } from './tutorial/tutorial'; -import { HashRouter as Router, Switch, Route, Redirect } from 'react-router-dom'; +import { + HashRouter as Router, + Switch, + Route, +} from 'react-router-dom'; import { getTutorial } from '../load_tutorials'; import { replaceTemplateStrings } from './tutorial/replace_template_strings'; -import { getServices } from '../kibana_services'; +import { + getServices +} from '../kibana_services'; export function HomeApp({ directories }) { const { @@ -42,9 +47,8 @@ export function HomeApp({ directories }) { const isCloudEnabled = getInjected('isCloudEnabled', false); const apmUiEnabled = getInjected('apmUiEnabled', true); const mlEnabled = getInjected('mlEnabled', false); - const defaultAppId = getInjected('kbnDefaultAppId', 'discover'); - const renderTutorialDirectory = props => { + const renderTutorialDirectory = (props) => { return ( { + const renderTutorial = (props) => { return ( - - - - - - - - - - - - - - - - + + + + + + + + + + + + ); } HomeApp.propTypes = { - directories: PropTypes.arrayOf( - PropTypes.shape({ - id: PropTypes.string.isRequired, - title: PropTypes.string.isRequired, - description: PropTypes.string.isRequired, - icon: PropTypes.string.isRequired, - path: PropTypes.string.isRequired, - showOnHomePage: PropTypes.bool.isRequired, - category: PropTypes.string.isRequired, - }) - ), + directories: PropTypes.arrayOf(PropTypes.shape({ + id: PropTypes.string.isRequired, + title: PropTypes.string.isRequired, + description: PropTypes.string.isRequired, + icon: PropTypes.string.isRequired, + path: PropTypes.string.isRequired, + showOnHomePage: PropTypes.bool.isRequired, + category: PropTypes.string.isRequired, + })), }; diff --git a/src/legacy/core_plugins/kibana/public/home/index.js b/src/legacy/core_plugins/kibana/public/home/index.js new file mode 100644 index 0000000000000..01f94b8ee4368 --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/home/index.js @@ -0,0 +1,61 @@ +/* + * 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 { getServices } from './kibana_services'; +import template from './home_ng_wrapper.html'; +import { + HomeApp +} from './components/home_app'; +import { i18n } from '@kbn/i18n'; + +const { wrapInI18nContext, uiRoutes, uiModules } = getServices(); + +const app = uiModules.get('apps/home', []); +app.directive('homeApp', function (reactDirective) { + return reactDirective(wrapInI18nContext(HomeApp)); +}); + +const homeTitle = i18n.translate('kbn.home.breadcrumbs.homeTitle', { defaultMessage: 'Home' }); + +function getRoute() { + return { + template, + resolve: { + directories: () => getServices().getFeatureCatalogueEntries() + }, + controller($scope, $route) { + const { chrome, addBasePath } = getServices(); + $scope.directories = $route.current.locals.directories; + $scope.recentlyAccessed = chrome.recentlyAccessed.get().map(item => { + item.link = addBasePath(item.link); + return item; + }); + }, + k7Breadcrumbs: () => [ + { text: homeTitle }, + ] + }; +} + +// All routing will be handled inside HomeApp via react, we just need to make sure angular doesn't +// redirect us to the default page by encountering a url it isn't marked as being able to handle. +uiRoutes.when('/home', getRoute()); +uiRoutes.when('/home/feature_directory', getRoute()); +uiRoutes.when('/home/tutorial_directory/:tab?', getRoute()); +uiRoutes.when('/home/tutorial/:id', getRoute()); diff --git a/src/legacy/core_plugins/kibana/public/home/index.ts b/src/legacy/core_plugins/kibana/public/home/index.ts deleted file mode 100644 index 57dd015f54049..0000000000000 --- a/src/legacy/core_plugins/kibana/public/home/index.ts +++ /dev/null @@ -1,79 +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 { FeatureCatalogueRegistryProvider } from 'ui/registry/feature_catalogue'; -import { npSetup, npStart } from 'ui/new_platform'; -import chrome from 'ui/chrome'; -import { IPrivate } from 'ui/private'; -// @ts-ignore -import { toastNotifications, banners } from 'ui/notify'; -import { kfetch } from 'ui/kfetch'; -import { HomePlugin, LegacyAngularInjectedDependencies } from './plugin'; -import { createUiStatsReporter, METRIC_TYPE } from '../../../ui_metric/public'; -import { start as data } from '../../../data/public/legacy'; -import { TelemetryOptInProvider } from '../../../telemetry/public/services'; -import { localApplicationService } from '../local_application_service'; - -export const trackUiMetric = createUiStatsReporter('Kibana_home'); - -/** - * Get dependencies relying on the global angular context. - * They also have to get resolved together with the legacy imports above - */ -async function getAngularDependencies(): Promise { - const injector = await chrome.dangerouslyGetActiveInjector(); - - const Private = injector.get('Private'); - - const telemetryEnabled = npStart.core.injectedMetadata.getInjectedVar('telemetryEnabled'); - const telemetryBanner = npStart.core.injectedMetadata.getInjectedVar('telemetryBanner'); - const telemetryOptInProvider = Private(TelemetryOptInProvider); - - return { - telemetryOptInProvider, - shouldShowTelemetryOptIn: - telemetryEnabled && telemetryBanner && !telemetryOptInProvider.getOptIn(), - }; -} - -(async () => { - const instance = new HomePlugin(); - instance.setup(npSetup.core, { - __LEGACY: { - trackUiMetric, - toastNotifications, - banners, - kfetch, - metadata: npStart.core.injectedMetadata.getLegacyMetadata(), - METRIC_TYPE, - getFeatureCatalogueRegistryProvider: async () => { - const injector = await chrome.dangerouslyGetActiveInjector(); - - const Private = injector.get('Private'); - - return Private(FeatureCatalogueRegistryProvider as any); - }, - getAngularDependencies, - localApplicationService, - }, - }); - instance.start(npStart.core, { - data, - }); -})(); diff --git a/src/legacy/core_plugins/kibana/public/home/kibana_services.ts b/src/legacy/core_plugins/kibana/public/home/kibana_services.ts index 1634cf01cc41f..b9f2ae1cfa7e8 100644 --- a/src/legacy/core_plugins/kibana/public/home/kibana_services.ts +++ b/src/legacy/core_plugins/kibana/public/home/kibana_services.ts @@ -17,62 +17,69 @@ * under the License. */ -import { ToastNotifications } from 'ui/notify/toasts/toast_notifications'; -import { - ChromeStart, - DocLinksStart, - LegacyNavLink, - SavedObjectsClientContract, - UiSettingsClientContract, - UiSettingsState, -} from 'kibana/public'; -import { KFetchOptions } from 'ui/kfetch'; -import { KFetchKibanaOptions } from 'ui/kfetch/kfetch'; -import { UiStatsMetricType } from '@kbn/analytics'; +// @ts-ignore +import { toastNotifications, banners } from 'ui/notify'; +import { kfetch } from 'ui/kfetch'; +import chrome from 'ui/chrome'; -export interface HomeKibanaServices { - indexPatternService: any; - getFeatureCatalogueRegistryProvider: () => Promise; - metadata: { - app: unknown; - bundleId: string; - nav: LegacyNavLink[]; - version: string; - branch: string; - buildNum: number; - buildSha: string; - basePath: string; - serverName: string; - devMode: boolean; - uiSettings: { defaults: UiSettingsState; user?: UiSettingsState | undefined }; - }; - getInjected: (name: string, defaultValue?: any) => unknown; - chrome: ChromeStart; - telemetryOptInProvider: any; - uiSettings: UiSettingsClientContract; - kfetch: (options: KFetchOptions, kfetchOptions?: KFetchKibanaOptions) => Promise; - savedObjectsClient: SavedObjectsClientContract; - toastNotifications: ToastNotifications; - banners: any; - METRIC_TYPE: any; - trackUiMetric: (type: UiStatsMetricType, eventNames: string | string[], count?: number) => void; - getBasePath: () => string; - shouldShowTelemetryOptIn: boolean; - docLinks: DocLinksStart; - addBasePath: (url: string) => string; -} +import { wrapInI18nContext } from 'ui/i18n'; -let services: HomeKibanaServices | null = null; +// @ts-ignore +import { uiModules as modules } from 'ui/modules'; +import routes from 'ui/routes'; +import { npSetup, npStart } from 'ui/new_platform'; +import { IPrivate } from 'ui/private'; +import { FeatureCatalogueRegistryProvider } from 'ui/registry/feature_catalogue'; +import { createUiStatsReporter, METRIC_TYPE } from '../../../ui_metric/public'; +import { TelemetryOptInProvider } from '../../../telemetry/public/services'; +import { start as data } from '../../../data/public/legacy'; -export function setServices(newServices: HomeKibanaServices) { - services = newServices; -} +let shouldShowTelemetryOptIn: boolean; +let telemetryOptInProvider: any; export function getServices() { - if (!services) { - throw new Error( - 'Kibana services not set - are you trying to import this module from outside of the home app?' - ); - } - return services; + return { + getInjected: npStart.core.injectedMetadata.getInjectedVar, + metadata: npStart.core.injectedMetadata.getLegacyMetadata(), + docLinks: npStart.core.docLinks, + + uiRoutes: routes, + uiModules: modules, + + savedObjectsClient: npStart.core.savedObjects.client, + chrome: npStart.core.chrome, + uiSettings: npStart.core.uiSettings, + addBasePath: npStart.core.http.basePath.prepend, + getBasePath: npStart.core.http.basePath.get, + + indexPatternService: data.indexPatterns.indexPatterns, + shouldShowTelemetryOptIn, + telemetryOptInProvider, + getFeatureCatalogueEntries: async () => { + const injector = await chrome.dangerouslyGetActiveInjector(); + const Private = injector.get('Private'); + // Merge legacy registry with new registry + (Private(FeatureCatalogueRegistryProvider as any) as any).inTitleOrder.map( + npSetup.plugins.feature_catalogue.register + ); + return npStart.plugins.feature_catalogue.get(); + }, + + trackUiMetric: createUiStatsReporter('Kibana_home'), + METRIC_TYPE, + + toastNotifications, + banners, + kfetch, + wrapInI18nContext, + }; } + +modules.get('kibana').run((Private: IPrivate) => { + const telemetryEnabled = npStart.core.injectedMetadata.getInjectedVar('telemetryEnabled'); + const telemetryBanner = npStart.core.injectedMetadata.getInjectedVar('telemetryBanner'); + + telemetryOptInProvider = Private(TelemetryOptInProvider); + shouldShowTelemetryOptIn = + telemetryEnabled && telemetryBanner && !telemetryOptInProvider.getOptIn(); +}); diff --git a/src/legacy/core_plugins/kibana/public/home/plugin.ts b/src/legacy/core_plugins/kibana/public/home/plugin.ts deleted file mode 100644 index 72af43cbcc8dd..0000000000000 --- a/src/legacy/core_plugins/kibana/public/home/plugin.ts +++ /dev/null @@ -1,105 +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 { CoreSetup, CoreStart, LegacyNavLink, Plugin, UiSettingsState } from 'kibana/public'; -import { UiStatsMetricType } from '@kbn/analytics'; -import { ToastNotifications } from 'ui/notify/toasts/toast_notifications'; -import { KFetchOptions } from 'ui/kfetch'; -import { KFetchKibanaOptions } from 'ui/kfetch/kfetch'; - -import { DataStart } from '../../../data/public'; -import { LocalApplicationService } from '../local_application_service'; -import { setServices } from './kibana_services'; - -export interface LegacyAngularInjectedDependencies { - telemetryOptInProvider: any; - shouldShowTelemetryOptIn: boolean; -} - -export interface HomePluginStartDependencies { - data: DataStart; -} - -export interface HomePluginSetupDependencies { - __LEGACY: { - trackUiMetric: (type: UiStatsMetricType, eventNames: string | string[], count?: number) => void; - toastNotifications: ToastNotifications; - banners: any; - METRIC_TYPE: any; - kfetch: (options: KFetchOptions, kfetchOptions?: KFetchKibanaOptions) => Promise; - metadata: { - app: unknown; - bundleId: string; - nav: LegacyNavLink[]; - version: string; - branch: string; - buildNum: number; - buildSha: string; - basePath: string; - serverName: string; - devMode: boolean; - uiSettings: { defaults: UiSettingsState; user?: UiSettingsState | undefined }; - }; - getFeatureCatalogueRegistryProvider: () => Promise; - getAngularDependencies: () => Promise; - localApplicationService: LocalApplicationService; - }; -} - -export class HomePlugin implements Plugin { - private dataStart: DataStart | null = null; - private savedObjectsClient: any = null; - - setup( - core: CoreSetup, - { - __LEGACY: { localApplicationService, getAngularDependencies, ...legacyServices }, - }: HomePluginSetupDependencies - ) { - localApplicationService.register({ - id: 'home', - title: 'Home', - mount: async ({ core: contextCore }, params) => { - const angularDependencies = await getAngularDependencies(); - setServices({ - ...legacyServices, - getInjected: core.injectedMetadata.getInjectedVar, - docLinks: contextCore.docLinks, - savedObjectsClient: this.savedObjectsClient!, - chrome: contextCore.chrome, - uiSettings: core.uiSettings, - addBasePath: core.http.basePath.prepend, - getBasePath: core.http.basePath.get, - indexPatternService: this.dataStart!.indexPatterns.indexPatterns, - ...angularDependencies, - }); - const { renderApp } = await import('./render_app'); - return await renderApp(params.element); - }, - }); - } - - start(core: CoreStart, { data }: HomePluginStartDependencies) { - // TODO is this really the right way? I though the app context would give us those - this.dataStart = data; - this.savedObjectsClient = core.savedObjects.client; - } - - stop() {} -} diff --git a/src/legacy/core_plugins/kibana/public/home/render_app.tsx b/src/legacy/core_plugins/kibana/public/home/render_app.tsx deleted file mode 100644 index 04d163fa860d3..0000000000000 --- a/src/legacy/core_plugins/kibana/public/home/render_app.tsx +++ /dev/null @@ -1,38 +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 React from 'react'; -import { render, unmountComponentAtNode } from 'react-dom'; -import { i18n } from '@kbn/i18n'; -// @ts-ignore -import { HomeApp } from './components/home_app'; -import { getServices } from './kibana_services'; - -export const renderApp = async (element: HTMLElement) => { - const homeTitle = i18n.translate('kbn.home.breadcrumbs.homeTitle', { defaultMessage: 'Home' }); - const { getFeatureCatalogueRegistryProvider, chrome } = getServices(); - const directories = (await getFeatureCatalogueRegistryProvider()).inTitleOrder; - chrome.setBreadcrumbs([{ text: homeTitle }]); - - render(, element); - - return () => { - unmountComponentAtNode(element); - }; -}; diff --git a/src/legacy/core_plugins/kibana/public/home/tutorial_resources/cockroachdb_metrics/screenshot.png b/src/legacy/core_plugins/kibana/public/home/tutorial_resources/cockroachdb_metrics/screenshot.png deleted file mode 100644 index 4b3020d91d57da293c2bd62dae6f5265ac726251..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 233000 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�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
`, controller($scope: IScope) { const element = document.getElementById(wrapperElementId)!; - let unmountHandler: AppUnmount | null = null; - let isUnmounted = false; - $scope.$on('$destroy', () => { - if (unmountHandler) { - unmountHandler(); - } - isUnmounted = true; - }); (async () => { - unmountHandler = await app.mount({ core: npStart.core }, { element, appBasePath: '' }); - // immediately unmount app if scope got destroyed in the meantime - if (isUnmounted) { - unmountHandler(); - } + const onUnmount = await app.mount({ core: npStart.core }, { element, appBasePath: '' }); + $scope.$on('$destroy', () => { + onUnmount(); + }); })(); }, }); @@ -143,9 +124,9 @@ export class LocalApplicationService { this.forwards.forEach(({ legacyAppId, newAppId, keepPrefix }) => { angularRouteManager.when(matchAllWithPrefix(legacyAppId), { - resolveRedirectTo: ($location: ILocationService) => { - const url = $location.url(); - return `/${newAppId}${keepPrefix ? url : url.replace(legacyAppId, '')}`; + redirectTo: (_params: unknown, path: string, search: string) => { + const newPath = `/${newAppId}${keepPrefix ? path : path.replace(legacyAppId, '')}`; + return `${newPath}?${search}`; }, }); }); diff --git a/src/legacy/core_plugins/kibana/public/visualize/help_menu/help_menu.js b/src/legacy/core_plugins/kibana/public/visualize/help_menu/help_menu.js index 567dd4b2534d6..b955d4d31904f 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/help_menu/help_menu.js +++ b/src/legacy/core_plugins/kibana/public/visualize/help_menu/help_menu.js @@ -17,7 +17,7 @@ * under the License. */ -import React, { Fragment, PureComponent } from 'react'; +import React, { PureComponent } from 'react'; import { EuiButton, EuiHorizontalRule, EuiSpacer } from '@elastic/eui'; import { FormattedMessage, I18nProvider } from '@kbn/i18n/react'; @@ -25,16 +25,21 @@ export class HelpMenu extends PureComponent { render() { return ( - - - - - + <> + + + + + + ); } diff --git a/src/legacy/ui/public/legacy_compat/angular_config.tsx b/src/legacy/ui/public/legacy_compat/angular_config.tsx index a9d16d7670619..f941ea31a7ed0 100644 --- a/src/legacy/ui/public/legacy_compat/angular_config.tsx +++ b/src/legacy/ui/public/legacy_compat/angular_config.tsx @@ -53,12 +53,7 @@ function isDummyWrapperRoute($route: any) { ); } -<<<<<<< HEAD export const configureAppAngularModule = (angularModule: IModule, newPlatform: LegacyCoreStart) => { -======= -export const configureAppAngularModule = (angularModule: IModule) => { - const newPlatform = npStart.core; ->>>>>>> kibana/master const legacyMetadata = newPlatform.injectedMetadata.getLegacyMetadata(); forOwn(newPlatform.injectedMetadata.getInjectedVars(), (val, name) => { diff --git a/src/legacy/ui/public/legacy_compat/ensure_default_index_pattern.tsx b/src/legacy/ui/public/legacy_compat/ensure_default_index_pattern.tsx index daedd9f329ed0..06b84c85f0651 100644 --- a/src/legacy/ui/public/legacy_compat/ensure_default_index_pattern.tsx +++ b/src/legacy/ui/public/legacy_compat/ensure_default_index_pattern.tsx @@ -76,7 +76,7 @@ export async function ensureDefaultIndexPattern( void); redirectTo?: string; resolveRedirectTo?: (...args: any[]) => void; diff --git a/src/legacy/ui/public/routes/route_manager.js b/src/legacy/ui/public/routes/route_manager.js index ba48984bb45b9..6444ef66fbe47 100644 --- a/src/legacy/ui/public/routes/route_manager.js +++ b/src/legacy/ui/public/routes/route_manager.js @@ -46,10 +46,6 @@ export default function RouteManager() { route.reloadOnSearch = false; } - if (route.requireDefaultIndex == null) { - route.requireDefaultIndex = false; - } - wrapRouteWithPrep(route, setup); $routeProvider.when(path, route); }); diff --git a/src/legacy/ui/public/vis/vis_filters/vis_filters.js b/src/legacy/ui/public/vis/vis_filters/vis_filters.js index 9343585fa9508..cf904603e3beb 100644 --- a/src/legacy/ui/public/vis/vis_filters/vis_filters.js +++ b/src/legacy/ui/public/vis/vis_filters/vis_filters.js @@ -17,11 +17,13 @@ * under the License. */ -import _ from 'lodash'; -import { pushFilterBarFilters } from '../push_filters'; +import { npStart } from 'ui/new_platform'; import { onBrushEvent } from './brush_event'; import { uniqFilters } from '../../../../../plugins/data/public'; import { toggleFilterNegated } from '@kbn/es-query'; +import _ from 'lodash'; +import { changeTimeFilter, extractTimeFilter } from '../../../../core_plugins/data/public/timefilter'; +import { start as data } from '../../../../core_plugins/data/public/legacy'; /** * For terms aggregations on `__other__` buckets, this assembles a list of applicable filter * terms based on a specific cell in the tabified data. @@ -104,14 +106,37 @@ const createFiltersFromEvent = (event) => { return filters; }; -const VisFiltersProvider = (getAppState, $timeout) => { +// TODO make sure the visualize app is updating the breadcrumb correctly +const VisFiltersProvider = () => { - const pushFilters = (filters, simulate) => { - const appState = getAppState(); + // TODO this function used to simply put the new filters in + // the app state. Dashboard/Visualize simply listened to + // the app state change via angular and pushed it into the actual + // filter manager (while splitting out the time filter) + // This channel does not work anymore because it's not the same + // angular context and thus the appstate won't update + const pushFilters = async (filters, simulate) => { if (filters.length && !simulate) { - pushFilterBarFilters(appState, uniqFilters(filters)); - // to trigger angular digest cycle, we can get rid of this once we have either new filterManager or actions API - $timeout(_.noop, 0); + const dedupedFilters = uniqFilters(filters); + // All filters originated from one visualization. + const indexPatternId = dedupedFilters[0].meta.index; + const indexPattern = _.find( + await data.indexPatterns.indexPatterns.getCache(), + p => p.id === indexPatternId + ); + if (dedupedFilters.length > 1) { + // TODO show apply filter popover and wait for user input + } + if (indexPattern && indexPattern.attributes.timeFieldName) { + const { timeRangeFilter, restOfFilters } = extractTimeFilter( + indexPattern.attributes.timeFieldName, + dedupedFilters + ); + npStart.plugins.data.query.filterManager.addFilters(restOfFilters); + if (timeRangeFilter) changeTimeFilter(data.timefilter.timefilter, timeRangeFilter); + } else { + npStart.plugins.data.query.filterManager.addFilters(dedupedFilters); + } } }; From 05d3295412ea19fd95fbce9e50673cb6d52e0e39 Mon Sep 17 00:00:00 2001 From: maryia-lapata Date: Fri, 1 Nov 2019 17:39:23 +0300 Subject: [PATCH 050/132] Refactoring --- src/legacy/core_plugins/kibana/index.js | 2 +- .../local_application_service.ts | 2 +- .../kibana/public/visualize/app.js | 8 +- .../kibana/public/visualize/editor/editor.js | 1029 +++++++++-------- .../public/visualize/editor/visualization.js | 2 +- .../visualize/editor/visualization_editor.js | 9 +- .../visualize_embeddable_factory.tsx | 18 +- .../kibana/public/visualize/index.ts | 35 +- .../public/visualize/kibana_services.ts | 9 +- .../visualize/listing/visualize_listing.js | 2 +- .../listing/visualize_listing_table.js | 5 +- .../kibana/public/visualize/plugin.ts | 48 +- .../kibana/public/visualize/render_app.ts | 85 +- .../kibana/public/visualize/visualize_app.ts | 65 -- 14 files changed, 624 insertions(+), 695 deletions(-) diff --git a/src/legacy/core_plugins/kibana/index.js b/src/legacy/core_plugins/kibana/index.js index 82c8028825e73..23eff06a9581a 100644 --- a/src/legacy/core_plugins/kibana/index.js +++ b/src/legacy/core_plugins/kibana/index.js @@ -63,7 +63,7 @@ export default function (kibana) { uiExports: { hacks: [ 'plugins/kibana/dev_tools/hacks/hide_empty_tools', - 'plugins/kibana/visualize/index' + 'plugins/kibana/visualize' ], fieldFormats: ['plugins/kibana/field_formats/register'], savedObjectTypes: [ diff --git a/src/legacy/core_plugins/kibana/public/local_application_service/local_application_service.ts b/src/legacy/core_plugins/kibana/public/local_application_service/local_application_service.ts index ba7e3921d3537..6c38aa4e1a773 100644 --- a/src/legacy/core_plugins/kibana/public/local_application_service/local_application_service.ts +++ b/src/legacy/core_plugins/kibana/public/local_application_service/local_application_service.ts @@ -102,7 +102,7 @@ export class LocalApplicationService { * * @param angularRouteManager The current `ui/routes` instance */ - apply(angularRouteManager: UIRoutes) { + attachToAngular(angularRouteManager: UIRoutes) { this.apps.forEach(app => { const wrapperElementId = this.idGenerator(); angularRouteManager.when(matchAllWithPrefix(app), { diff --git a/src/legacy/core_plugins/kibana/public/visualize/app.js b/src/legacy/core_plugins/kibana/public/visualize/app.js index 62d016727ba97..d489a53d97549 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/app.js +++ b/src/legacy/core_plugins/kibana/public/visualize/app.js @@ -88,8 +88,8 @@ export function initVisualizeApp(app, deps) { k7Breadcrumbs: getCreateBreadcrumbs, resolve: { savedVis: function (redirectWhenMissing, $route, $rootScope, kbnUrl) { - const { core, dataStart, savedVisualizations } = deps; - const visTypes = deps.visualizations.types.all(); + const { core, dataStart, savedVisualizations, visualizations } = deps; + const visTypes = visualizations.types.all(); const visType = find(visTypes, { name: $route.current.params.type }); const shouldHaveIndex = visType.requiresSearch && visType.options.showIndexSelection; const hasIndex = $route.current.params.indexPattern || $route.current.params.savedSearchId; @@ -121,11 +121,11 @@ export function initVisualizeApp(app, deps) { k7Breadcrumbs: getEditBreadcrumbs, resolve: { savedVis: function (redirectWhenMissing, $route, $rootScope, kbnUrl) { - const { core, dataStart, savedVisualizations } = deps; + const { chrome, core, dataStart, savedVisualizations } = deps; return ensureDefaultIndexPattern(core, dataStart, $rootScope, kbnUrl) .then(() => savedVisualizations.get($route.current.params.id)) .then(savedVis => { - deps.chrome.recentlyAccessed.add( + chrome.recentlyAccessed.add( savedVis.getFullPath(), savedVis.title, savedVis.id diff --git a/src/legacy/core_plugins/kibana/public/visualize/editor/editor.js b/src/legacy/core_plugins/kibana/public/visualize/editor/editor.js index e56674588052a..80cbb6d823ff7 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/editor/editor.js +++ b/src/legacy/core_plugins/kibana/public/visualize/editor/editor.js @@ -25,10 +25,9 @@ import '../saved_visualizations/saved_visualizations'; import React from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; import { migrateAppState } from './lib'; -import editorTemplate from './editor.html'; import { DashboardConstants } from '../../dashboard/dashboard_constants'; import { VisualizeConstants } from '../visualize_constants'; -import { getEditBreadcrumbs, getCreateBreadcrumbs } from '../breadcrumbs'; +import { getEditBreadcrumbs } from '../breadcrumbs'; import { extractTimeFilter, changeTimeFilter } from '../../../../data/public'; import { addHelpMenuToAppChrome } from '../help_menu/help_menu_util'; @@ -37,6 +36,7 @@ import { initVisEditorDirective } from './visualization_editor'; import { initVisualizationDirective } from './visualization'; import { + getServices, absoluteToParsedUrl, KibanaParsedUrl, migrateLegacyQuery, @@ -52,556 +52,559 @@ export function initEditorDirective(app, deps) { return { restrict: 'E', controllerAs: 'visualizeApp', - controller: function VisualizeAppController( - $scope, - $element, - $route, - AppState, - $window, - $injector, - $timeout, - kbnUrl, - redirectWhenMissing, - Promise, - config, - kbnBaseUrl, - ) { - const { - angular, - indexPatterns, - localStorage, - queryFilter, - visualizeCapabilities, - dataStart: { - timefilter: { timefilter }, - }, - toastNotifications, - chromeLegacy, - chrome, - docTitle, - getBasePath, - docLinks - } = deps; - - // Retrieve the resolved SavedVis instance. - const savedVis = $route.current.locals.savedVis; - // vis is instance of src/legacy/ui/public/vis/vis.js. - // SearchSource is a promise-based stream of search results that can inherit from other search sources. - const { vis, searchSource } = savedVis; - - $scope.vis = vis; - - const $appStatus = this.appStatus = { - dirty: !savedVis.id - }; - - $scope.topNavMenu = [...(visualizeCapabilities.save ? [{ - id: 'save', - label: i18n.translate('kbn.topNavMenu.saveVisualizationButtonLabel', { defaultMessage: 'save' }), - description: i18n.translate('kbn.visualize.topNavMenu.saveVisualizationButtonAriaLabel', { - defaultMessage: 'Save Visualization', - }), - testId: 'visualizeSaveButton', - disableButton() { - return Boolean(vis.dirty); - }, - tooltip() { - if (vis.dirty) { - return i18n.translate('kbn.visualize.topNavMenu.saveVisualizationDisabledButtonTooltip', { - defaultMessage: 'Apply or Discard your changes before saving' - }); - } - }, - run: async () => { - const onSave = ({ newTitle, newCopyOnSave, isTitleDuplicateConfirmed, onTitleDuplicate, newDescription }) => { - const currentTitle = savedVis.title; - savedVis.title = newTitle; - savedVis.copyOnSave = newCopyOnSave; - savedVis.description = newDescription; - const saveOptions = { - confirmOverwrite: false, - isTitleDuplicateConfirmed, - onTitleDuplicate, - }; - return doSave(saveOptions).then((response) => { - // If the save wasn't successful, put the original values back. - if (!response.id || response.error) { - savedVis.title = currentTitle; - } - return response; - }); - }; - - const confirmButtonLabel = $scope.isAddToDashMode() ? ( - - ) : null; - - const saveModal = ( - {}} - title={savedVis.title} - showCopyOnSave={savedVis.id ? true : false} - objectType="visualization" - confirmButtonLabel={confirmButtonLabel} - description={savedVis.description} - />); - showSaveModal(saveModal); - } - }] : []), { - id: 'share', - label: i18n.translate('kbn.topNavMenu.shareVisualizationButtonLabel', { defaultMessage: 'share' }), - description: i18n.translate('kbn.visualize.topNavMenu.shareVisualizationButtonAriaLabel', { - defaultMessage: 'Share Visualization', - }), - testId: 'shareTopNavButton', - run: (anchorElement) => { - const hasUnappliedChanges = vis.dirty; - const hasUnsavedChanges = $appStatus.dirty; - showShareContextMenu({ - anchorElement, - allowEmbed: true, - allowShortUrl: visualizeCapabilities.createShortUrl, - getUnhashableStates: deps.getUnhashableStates, - objectId: savedVis.id, - objectType: 'visualization', - shareContextMenuExtensions: deps.shareContextMenuExtensions, - sharingData: { - title: savedVis.title, - }, - isDirty: hasUnappliedChanges || hasUnsavedChanges, - }); - } - }, { - id: 'inspector', - label: i18n.translate('kbn.topNavMenu.openInspectorButtonLabel', { defaultMessage: 'inspect' }), - description: i18n.translate('kbn.visualize.topNavMenu.openInspectorButtonAriaLabel', { - defaultMessage: 'Open Inspector for visualization', - }), - testId: 'openInspectorButton', - disableButton() { - return !vis.hasInspector || !vis.hasInspector(); - }, - run() { - const inspectorSession = vis.openInspector(); - // Close the inspector if this scope is destroyed (e.g. because the user navigates away). - const removeWatch = $scope.$on('$destroy', () => inspectorSession.close()); - // Remove that watch in case the user closes the inspector session herself. - inspectorSession.onClose.finally(removeWatch); - }, - tooltip() { - if (!vis.hasInspector || !vis.hasInspector()) { - return i18n.translate('kbn.visualize.topNavMenu.openInspectorDisabledButtonTooltip', { - defaultMessage: `This visualization doesn't support any inspectors.`, - }); - } - } - }, { - id: 'refresh', - label: i18n.translate('kbn.topNavMenu.refreshButtonLabel', { defaultMessage: 'refresh' }), - description: i18n.translate('kbn.visualize.topNavMenu.refreshButtonAriaLabel', { - defaultMessage: 'Refresh', - }), - run: function () { - vis.forceReload(); - }, - testId: 'visualizeRefreshButton', - }]; - - let stateMonitor; - - if (savedVis.id) { - docTitle.change(savedVis.title); - } - - // Extract visualization state with filtered aggs. You can see these filtered aggs in the URL. - // Consists of things like aggs, params, listeners, title, type, etc. - const savedVisState = vis.getState(); - const stateDefaults = { - uiState: savedVis.uiStateJSON ? JSON.parse(savedVis.uiStateJSON) : {}, - linked: !!savedVis.savedSearchId, - query: searchSource.getOwnField('query') || { - query: '', - language: localStorage.get('kibana.userQueryLanguage') || config.get('search:queryLanguage') - }, - filters: searchSource.getOwnField('filter') || [], - vis: savedVisState - }; - - // Instance of app_state.js. - const $state = (function initState() { - // This is used to sync visualization state with the url when `appState.save()` is called. - const appState = new AppState(stateDefaults); - - // Initializing appState does two things - first it translates the defaults into AppState, - // second it updates appState based on the url (the url trumps the defaults). This means if - // we update the state format at all and want to handle BWC, we must not only migrate the - // data stored with saved vis, but also any old state in the url. - migrateAppState(appState); - - // The savedVis is pulled from elasticsearch, but the appState is pulled from the url, with the - // defaults applied. If the url was from a previous session which included modifications to the - // appState then they won't be equal. - if (!angular.equals(appState.vis, savedVisState)) { - Promise.try(function () { - vis.setState(appState.vis); - }) - .catch(redirectWhenMissing({ - 'index-pattern-field': '/visualize' - })); - } - - return appState; - }()); - - $scope.filters = queryFilter.getFilters(); - - $scope.onFiltersUpdated = filters => { - // The filters will automatically be set when the queryFilter emits an update event (see below) - queryFilter.setFilters(filters); - }; + controller: VisualizeAppController, + }; + }); - $scope.onCancelApplyFilters = () => { - $scope.state.$newFilters = []; - }; + initVisEditorDirective(app, deps); + initVisualizationDirective(app, deps); +} - $scope.onApplyFilters = filters => { - const { timeRangeFilter, restOfFilters } = extractTimeFilter($scope.indexPattern.timeFieldName, filters); - queryFilter.addFilters(restOfFilters); - if (timeRangeFilter) changeTimeFilter(timefilter, timeRangeFilter); - $scope.state.$newFilters = []; +function VisualizeAppController( + $scope, + $element, + $route, + AppState, + $window, + $injector, + $timeout, + kbnUrl, + redirectWhenMissing, + Promise, + config, + kbnBaseUrl, +) { + const { + angular, + indexPatterns, + localStorage, + queryFilter, + visualizeCapabilities, + getUnhashableStates, + shareContextMenuExtensions, + dataStart: { + timefilter: { timefilter }, + }, + toastNotifications, + chromeLegacy, + chrome, + docTitle, + getBasePath, + docLinks, + savedQueryService, + } = getServices(); + + // Retrieve the resolved SavedVis instance. + const savedVis = $route.current.locals.savedVis; + // vis is instance of src/legacy/ui/public/vis/vis.js. + // SearchSource is a promise-based stream of search results that can inherit from other search sources. + const { vis, searchSource } = savedVis; + + $scope.vis = vis; + + const $appStatus = this.appStatus = { + dirty: !savedVis.id + }; + + $scope.topNavMenu = [...(visualizeCapabilities.save ? [{ + id: 'save', + label: i18n.translate('kbn.topNavMenu.saveVisualizationButtonLabel', { defaultMessage: 'save' }), + description: i18n.translate('kbn.visualize.topNavMenu.saveVisualizationButtonAriaLabel', { + defaultMessage: 'Save Visualization', + }), + testId: 'visualizeSaveButton', + disableButton() { + return Boolean(vis.dirty); + }, + tooltip() { + if (vis.dirty) { + return i18n.translate('kbn.visualize.topNavMenu.saveVisualizationDisabledButtonTooltip', { + defaultMessage: 'Apply or Discard your changes before saving' + }); + } + }, + run: async () => { + const onSave = ({ newTitle, newCopyOnSave, isTitleDuplicateConfirmed, onTitleDuplicate, newDescription }) => { + const currentTitle = savedVis.title; + savedVis.title = newTitle; + savedVis.copyOnSave = newCopyOnSave; + savedVis.description = newDescription; + const saveOptions = { + confirmOverwrite: false, + isTitleDuplicateConfirmed, + onTitleDuplicate, }; - - $scope.$watch('state.$newFilters', (filters = []) => { - if (filters.length === 1) { - $scope.onApplyFilters(filters); + return doSave(saveOptions).then((response) => { + // If the save wasn't successful, put the original values back. + if (!response.id || response.error) { + savedVis.title = currentTitle; } + return response; }); - - $scope.showSaveQuery = visualizeCapabilities.saveQuery; - - $scope.$watch(() => visualizeCapabilities.saveQuery, (newCapability) => { - $scope.showSaveQuery = newCapability; + }; + + const confirmButtonLabel = $scope.isAddToDashMode() ? ( + + ) : null; + + const saveModal = ( + {}} + title={savedVis.title} + showCopyOnSave={savedVis.id ? true : false} + objectType="visualization" + confirmButtonLabel={confirmButtonLabel} + description={savedVis.description} + />); + showSaveModal(saveModal); + } + }] : []), { + id: 'share', + label: i18n.translate('kbn.topNavMenu.shareVisualizationButtonLabel', { defaultMessage: 'share' }), + description: i18n.translate('kbn.visualize.topNavMenu.shareVisualizationButtonAriaLabel', { + defaultMessage: 'Share Visualization', + }), + testId: 'shareTopNavButton', + run: (anchorElement) => { + const hasUnappliedChanges = vis.dirty; + const hasUnsavedChanges = $appStatus.dirty; + showShareContextMenu({ + anchorElement, + allowEmbed: true, + allowShortUrl: visualizeCapabilities.createShortUrl, + getUnhashableStates, + objectId: savedVis.id, + objectType: 'visualization', + shareContextMenuExtensions, + sharingData: { + title: savedVis.title, + }, + isDirty: hasUnappliedChanges || hasUnsavedChanges, + }); + } + }, { + id: 'inspector', + label: i18n.translate('kbn.topNavMenu.openInspectorButtonLabel', { defaultMessage: 'inspect' }), + description: i18n.translate('kbn.visualize.topNavMenu.openInspectorButtonAriaLabel', { + defaultMessage: 'Open Inspector for visualization', + }), + testId: 'openInspectorButton', + disableButton() { + return !vis.hasInspector || !vis.hasInspector(); + }, + run() { + const inspectorSession = vis.openInspector(); + // Close the inspector if this scope is destroyed (e.g. because the user navigates away). + const removeWatch = $scope.$on('$destroy', () => inspectorSession.close()); + // Remove that watch in case the user closes the inspector session herself. + inspectorSession.onClose.finally(removeWatch); + }, + tooltip() { + if (!vis.hasInspector || !vis.hasInspector()) { + return i18n.translate('kbn.visualize.topNavMenu.openInspectorDisabledButtonTooltip', { + defaultMessage: `This visualization doesn't support any inspectors.`, }); + } + } + }, { + id: 'refresh', + label: i18n.translate('kbn.topNavMenu.refreshButtonLabel', { defaultMessage: 'refresh' }), + description: i18n.translate('kbn.visualize.topNavMenu.refreshButtonAriaLabel', { + defaultMessage: 'Refresh', + }), + run: function () { + vis.forceReload(); + }, + testId: 'visualizeRefreshButton', + }]; + + let stateMonitor; + + if (savedVis.id) { + docTitle.change(savedVis.title); + } + + // Extract visualization state with filtered aggs. You can see these filtered aggs in the URL. + // Consists of things like aggs, params, listeners, title, type, etc. + const savedVisState = vis.getState(); + const stateDefaults = { + uiState: savedVis.uiStateJSON ? JSON.parse(savedVis.uiStateJSON) : {}, + linked: !!savedVis.savedSearchId, + query: searchSource.getOwnField('query') || { + query: '', + language: localStorage.get('kibana.userQueryLanguage') || config.get('search:queryLanguage') + }, + filters: searchSource.getOwnField('filter') || [], + vis: savedVisState + }; + + // Instance of app_state.js. + const $state = (function initState() { + // This is used to sync visualization state with the url when `appState.save()` is called. + const appState = new AppState(stateDefaults); + + // Initializing appState does two things - first it translates the defaults into AppState, + // second it updates appState based on the url (the url trumps the defaults). This means if + // we update the state format at all and want to handle BWC, we must not only migrate the + // data stored with saved vis, but also any old state in the url. + migrateAppState(appState); + + // The savedVis is pulled from elasticsearch, but the appState is pulled from the url, with the + // defaults applied. If the url was from a previous session which included modifications to the + // appState then they won't be equal. + if (!angular.equals(appState.vis, savedVisState)) { + Promise.try(function () { + vis.setState(appState.vis); + }) + .catch(redirectWhenMissing({ + 'index-pattern-field': '/visualize' + })); + } + + return appState; + }()); + + $scope.filters = queryFilter.getFilters(); + + $scope.onFiltersUpdated = filters => { + // The filters will automatically be set when the queryFilter emits an update event (see below) + queryFilter.setFilters(filters); + }; + + $scope.onCancelApplyFilters = () => { + $scope.state.$newFilters = []; + }; + + $scope.onApplyFilters = filters => { + const { timeRangeFilter, restOfFilters } = extractTimeFilter($scope.indexPattern.timeFieldName, filters); + queryFilter.addFilters(restOfFilters); + if (timeRangeFilter) changeTimeFilter(timefilter, timeRangeFilter); + $scope.state.$newFilters = []; + }; + + $scope.$watch('state.$newFilters', (filters = []) => { + if (filters.length === 1) { + $scope.onApplyFilters(filters); + } + }); - function init() { - // export some objects - $scope.savedVis = savedVis; - if (vis.indexPattern) { - $scope.indexPattern = vis.indexPattern; - } else { - indexPatterns.getDefault().then(defaultIndexPattern => { - $scope.indexPattern = defaultIndexPattern; - }); - } - - $scope.searchSource = searchSource; - $scope.state = $state; - $scope.refreshInterval = timefilter.getRefreshInterval(); - - // Create a PersistedState instance. - $scope.uiState = $state.makeStateful('uiState'); - $scope.appStatus = $appStatus; - - const addToDashMode = $route.current.params[DashboardConstants.ADD_VISUALIZATION_TO_DASHBOARD_MODE_PARAM]; - kbnUrl.removeParam(DashboardConstants.ADD_VISUALIZATION_TO_DASHBOARD_MODE_PARAM); - - $scope.isAddToDashMode = () => addToDashMode; + $scope.showSaveQuery = visualizeCapabilities.saveQuery; - $scope.showFilterBar = () => { - return vis.type.options.showFilterBar; - }; + $scope.$watch(() => visualizeCapabilities.saveQuery, (newCapability) => { + $scope.showSaveQuery = newCapability; + }); - $scope.showQueryInput = () => { - return vis.type.requiresSearch && vis.type.options.showQueryBar; - }; + function init() { + // export some objects + $scope.savedVis = savedVis; + if (vis.indexPattern) { + $scope.indexPattern = vis.indexPattern; + } else { + indexPatterns.getDefault().then(defaultIndexPattern => { + $scope.indexPattern = defaultIndexPattern; + }); + } - $scope.showQueryBarTimePicker = () => { - return vis.type.options.showTimePicker; - }; + $scope.searchSource = searchSource; + $scope.state = $state; + $scope.refreshInterval = timefilter.getRefreshInterval(); - $scope.timeRange = timefilter.getTime(); - $scope.opts = _.pick($scope, 'savedVis', 'isAddToDashMode'); + // Create a PersistedState instance. + $scope.uiState = $state.makeStateful('uiState'); + $scope.appStatus = $appStatus; - stateMonitor = stateMonitorFactory.create($state, stateDefaults); - stateMonitor.ignoreProps([ 'vis.listeners' ]).onChange((status) => { - $appStatus.dirty = status.dirty || !savedVis.id; - }); + const addToDashMode = $route.current.params[DashboardConstants.ADD_VISUALIZATION_TO_DASHBOARD_MODE_PARAM]; + kbnUrl.removeParam(DashboardConstants.ADD_VISUALIZATION_TO_DASHBOARD_MODE_PARAM); - $scope.$watch('state.query', (newQuery, oldQuery) => { - if (!_.isEqual(newQuery, oldQuery)) { - const query = migrateLegacyQuery(newQuery); - if (!_.isEqual(query, newQuery)) { - $state.query = query; - } - $scope.fetch(); - } - }); + $scope.isAddToDashMode = () => addToDashMode; - $state.replace(); + $scope.showFilterBar = () => { + return vis.type.options.showFilterBar; + }; - const updateTimeRange = () => { - $scope.timeRange = timefilter.getTime(); - $scope.$broadcast('render'); - }; + $scope.showQueryInput = () => { + return vis.type.requiresSearch && vis.type.options.showQueryBar; + }; - const subscriptions = new Subscription(); + $scope.showQueryBarTimePicker = () => { + return vis.type.options.showTimePicker; + }; - subscriptions.add(subscribeWithScope($scope, timefilter.getRefreshIntervalUpdate$(), { - next: () => { - $scope.refreshInterval = timefilter.getRefreshInterval(); - } - })); - subscriptions.add(subscribeWithScope($scope, timefilter.getTimeUpdate$(), { - next: updateTimeRange - })); - - // update the searchSource when query updates - $scope.fetch = function () { - $state.save(); - $scope.query = $state.query; - savedVis.searchSource.setField('query', $state.query); - savedVis.searchSource.setField('filter', $state.filters); - $scope.$broadcast('render'); - }; - - // update the searchSource when filters update - subscriptions.add(subscribeWithScope($scope, queryFilter.getUpdates$(), { - next: () => { - $scope.filters = queryFilter.getFilters(); - $scope.globalFilters = queryFilter.getGlobalFilters(); - } - })); - subscriptions.add(subscribeWithScope($scope, queryFilter.getFetches$(), { - next: $scope.fetch - })); - - $scope.$on('$destroy', function () { - if ($scope._handler) { - $scope._handler.destroy(); - } - savedVis.destroy(); - stateMonitor.destroy(); - subscriptions.unsubscribe(); - }); + $scope.timeRange = timefilter.getTime(); + $scope.opts = _.pick($scope, 'savedVis', 'isAddToDashMode'); + stateMonitor = stateMonitorFactory.create($state, stateDefaults); + stateMonitor.ignoreProps([ 'vis.listeners' ]).onChange((status) => { + $appStatus.dirty = status.dirty || !savedVis.id; + }); - $timeout(() => { $scope.$broadcast('render'); }); + $scope.$watch('state.query', (newQuery, oldQuery) => { + if (!_.isEqual(newQuery, oldQuery)) { + const query = migrateLegacyQuery(newQuery); + if (!_.isEqual(query, newQuery)) { + $state.query = query; } + $scope.fetch(); + } + }); - $scope.updateQueryAndFetch = function ({ query, dateRange }) { - const isUpdate = ( - (query && !_.isEqual(query, $state.query)) || - (dateRange && !_.isEqual(dateRange, $scope.timeRange)) - ); + $state.replace(); - $state.query = query; - timefilter.setTime(dateRange); + const updateTimeRange = () => { + $scope.timeRange = timefilter.getTime(); + $scope.$broadcast('render'); + }; - // If nothing has changed, trigger the fetch manually, otherwise it will happen as a result of the changes - if (!isUpdate) { - $scope.vis.forceReload(); - } - }; + const subscriptions = new Subscription(); + + subscriptions.add(subscribeWithScope($scope, timefilter.getRefreshIntervalUpdate$(), { + next: () => { + $scope.refreshInterval = timefilter.getRefreshInterval(); + } + })); + subscriptions.add(subscribeWithScope($scope, timefilter.getTimeUpdate$(), { + next: updateTimeRange + })); + + // update the searchSource when query updates + $scope.fetch = function () { + $state.save(); + $scope.query = $state.query; + savedVis.searchSource.setField('query', $state.query); + savedVis.searchSource.setField('filter', $state.filters); + $scope.$broadcast('render'); + }; - $scope.onRefreshChange = function ({ isPaused, refreshInterval }) { - timefilter.setRefreshInterval({ - pause: isPaused, - value: refreshInterval ? refreshInterval : $scope.refreshInterval.value - }); - }; + // update the searchSource when filters update + subscriptions.add(subscribeWithScope($scope, queryFilter.getUpdates$(), { + next: () => { + $scope.filters = queryFilter.getFilters(); + $scope.globalFilters = queryFilter.getGlobalFilters(); + } + })); + subscriptions.add(subscribeWithScope($scope, queryFilter.getFetches$(), { + next: $scope.fetch + })); + + $scope.$on('$destroy', function () { + if ($scope._handler) { + $scope._handler.destroy(); + } + savedVis.destroy(); + stateMonitor.destroy(); + subscriptions.unsubscribe(); + }); + + + $timeout(() => { $scope.$broadcast('render'); }); + } + + $scope.updateQueryAndFetch = function ({ query, dateRange }) { + const isUpdate = ( + (query && !_.isEqual(query, $state.query)) || + (dateRange && !_.isEqual(dateRange, $scope.timeRange)) + ); + + $state.query = query; + timefilter.setTime(dateRange); + + // If nothing has changed, trigger the fetch manually, otherwise it will happen as a result of the changes + if (!isUpdate) { + $scope.vis.forceReload(); + } + }; + + $scope.onRefreshChange = function ({ isPaused, refreshInterval }) { + timefilter.setRefreshInterval({ + pause: isPaused, + value: refreshInterval ? refreshInterval : $scope.refreshInterval.value + }); + }; + + $scope.onQuerySaved = savedQuery => { + $scope.savedQuery = savedQuery; + }; + + $scope.onSavedQueryUpdated = savedQuery => { + $scope.savedQuery = { ...savedQuery }; + }; + + $scope.onClearSavedQuery = () => { + delete $scope.savedQuery; + delete $state.savedQuery; + $state.query = { + query: '', + language: localStorage.get('kibana.userQueryLanguage') || config.get('search:queryLanguage') + }; + queryFilter.removeAll(); + $state.save(); + $scope.fetch(); + }; + + const updateStateFromSavedQuery = (savedQuery) => { + $state.query = savedQuery.attributes.query; + $state.save(); + + queryFilter.setFilters(savedQuery.attributes.filters || []); + + if (savedQuery.attributes.timefilter) { + timefilter.setTime({ + from: savedQuery.attributes.timefilter.from, + to: savedQuery.attributes.timefilter.to, + }); + if (savedQuery.attributes.timefilter.refreshInterval) { + timefilter.setRefreshInterval(savedQuery.attributes.timefilter.refreshInterval); + } + } + + $scope.fetch(); + }; + + $scope.$watch('savedQuery', (newSavedQuery) => { + if (!newSavedQuery) return; + $state.savedQuery = newSavedQuery.id; + $state.save(); + + updateStateFromSavedQuery(newSavedQuery); + }); - $scope.onQuerySaved = savedQuery => { + $scope.$watch('state.savedQuery', newSavedQueryId => { + if (!newSavedQueryId) { + $scope.savedQuery = undefined; + return; + } + if (!$scope.savedQuery || newSavedQueryId !== $scope.savedQuery.id) { + savedQueryService.getSavedQuery(newSavedQueryId).then((savedQuery) => { + $scope.$evalAsync(() => { $scope.savedQuery = savedQuery; - }; - - $scope.onSavedQueryUpdated = savedQuery => { - $scope.savedQuery = { ...savedQuery }; - }; - - $scope.onClearSavedQuery = () => { - delete $scope.savedQuery; - delete $state.savedQuery; - $state.query = { - query: '', - language: localStorage.get('kibana.userQueryLanguage') || config.get('search:queryLanguage') - }; - queryFilter.removeAll(); - $state.save(); - $scope.fetch(); - }; - - const updateStateFromSavedQuery = (savedQuery) => { - $state.query = savedQuery.attributes.query; - $state.save(); - - queryFilter.setFilters(savedQuery.attributes.filters || []); + updateStateFromSavedQuery(savedQuery); + }); + }); + } + }); - if (savedQuery.attributes.timefilter) { - timefilter.setTime({ - from: savedQuery.attributes.timefilter.from, - to: savedQuery.attributes.timefilter.to, + /** + * Called when the user clicks "Save" button. + */ + function doSave(saveOptions) { + // vis.title was not bound and it's needed to reflect title into visState + $state.vis.title = savedVis.title; + $state.vis.type = savedVis.type || $state.vis.type; + savedVis.visState = $state.vis; + savedVis.uiStateJSON = angular.toJson($scope.uiState.getChanges()); + + return savedVis.save(saveOptions) + .then(function (id) { + $scope.$evalAsync(() => { + stateMonitor.setInitialState($state.toJSON()); + + if (id) { + toastNotifications.addSuccess({ + title: i18n.translate('kbn.visualize.topNavMenu.saveVisualization.successNotificationText', { + defaultMessage: `Saved '{visTitle}'`, + values: { + visTitle: savedVis.title, + }, + }), + 'data-test-subj': 'saveVisualizationSuccess', }); - if (savedQuery.attributes.timefilter.refreshInterval) { - timefilter.setRefreshInterval(savedQuery.attributes.timefilter.refreshInterval); - } - } - - $scope.fetch(); - }; - - $scope.$watch('savedQuery', (newSavedQuery) => { - if (!newSavedQuery) return; - $state.savedQuery = newSavedQuery.id; - $state.save(); - - updateStateFromSavedQuery(newSavedQuery); - }); - $scope.$watch('state.savedQuery', newSavedQueryId => { - if (!newSavedQueryId) { - $scope.savedQuery = undefined; - return; - } - if (!$scope.savedQuery || newSavedQueryId !== $scope.savedQuery.id) { - deps.savedQueryService.getSavedQuery(newSavedQueryId).then((savedQuery) => { - $scope.$evalAsync(() => { - $scope.savedQuery = savedQuery; - updateStateFromSavedQuery(savedQuery); + if ($scope.isAddToDashMode()) { + const savedVisualizationParsedUrl = new KibanaParsedUrl({ + basePath: getBasePath(), + appId: kbnBaseUrl.slice('/app/'.length), + appPath: kbnUrl.eval(`${VisualizeConstants.EDIT_PATH}/{{id}}`, { id: savedVis.id }), }); - }); + // Manually insert a new url so the back button will open the saved visualization. + $window.history.pushState({}, '', savedVisualizationParsedUrl.getRootRelativePath()); + // Since we aren't reloading the page, only inserting a new browser history item, we need to manually update + // the last url for this app, so directly clicking on the Visualize tab will also bring the user to the saved + // url, not the unsaved one. + chromeLegacy.trackSubUrlForApp('kibana:visualize', savedVisualizationParsedUrl); + + const lastDashboardAbsoluteUrl = chrome.navLinks.get('kibana:dashboard').url; + const dashboardParsedUrl = absoluteToParsedUrl(lastDashboardAbsoluteUrl, getBasePath()); + dashboardParsedUrl.addQueryParameter(DashboardConstants.NEW_VISUALIZATION_ID_PARAM, savedVis.id); + kbnUrl.change(dashboardParsedUrl.appPath); + } else if (savedVis.id === $route.current.params.id) { + docTitle.change(savedVis.lastSavedTitle); + chrome.setBreadcrumbs($injector.invoke(getEditBreadcrumbs)); + savedVis.vis.title = savedVis.title; + savedVis.vis.description = savedVis.description; + // it's needed to save the state to update url string + $state.save(); + } else { + kbnUrl.change(`${VisualizeConstants.EDIT_PATH}/{{id}}`, { id: savedVis.id }); + } } }); - - /** - * Called when the user clicks "Save" button. - */ - function doSave(saveOptions) { - // vis.title was not bound and it's needed to reflect title into visState - $state.vis.title = savedVis.title; - $state.vis.type = savedVis.type || $state.vis.type; - savedVis.visState = $state.vis; - savedVis.uiStateJSON = angular.toJson($scope.uiState.getChanges()); - - return savedVis.save(saveOptions) - .then(function (id) { - $scope.$evalAsync(() => { - stateMonitor.setInitialState($state.toJSON()); - - if (id) { - toastNotifications.addSuccess({ - title: i18n.translate('kbn.visualize.topNavMenu.saveVisualization.successNotificationText', { - defaultMessage: `Saved '{visTitle}'`, - values: { - visTitle: savedVis.title, - }, - }), - 'data-test-subj': 'saveVisualizationSuccess', - }); - - if ($scope.isAddToDashMode()) { - const savedVisualizationParsedUrl = new KibanaParsedUrl({ - basePath: getBasePath(), - appId: kbnBaseUrl.slice('/app/'.length), - appPath: kbnUrl.eval(`${VisualizeConstants.EDIT_PATH}/{{id}}`, { id: savedVis.id }), - }); - // Manually insert a new url so the back button will open the saved visualization. - $window.history.pushState({}, '', savedVisualizationParsedUrl.getRootRelativePath()); - // Since we aren't reloading the page, only inserting a new browser history item, we need to manually update - // the last url for this app, so directly clicking on the Visualize tab will also bring the user to the saved - // url, not the unsaved one. - chromeLegacy.trackSubUrlForApp('kibana:visualize', savedVisualizationParsedUrl); - - const lastDashboardAbsoluteUrl = chrome.navLinks.get('kibana:dashboard').url; - const dashboardParsedUrl = absoluteToParsedUrl(lastDashboardAbsoluteUrl, getBasePath()); - dashboardParsedUrl.addQueryParameter(DashboardConstants.NEW_VISUALIZATION_ID_PARAM, savedVis.id); - kbnUrl.change(dashboardParsedUrl.appPath); - } else if (savedVis.id === $route.current.params.id) { - docTitle.change(savedVis.lastSavedTitle); - chrome.setBreadcrumbs($injector.invoke(getEditBreadcrumbs)); - savedVis.vis.title = savedVis.title; - savedVis.vis.description = savedVis.description; - // it's needed to save the state to update url string - $state.save(); - } else { - kbnUrl.change(`${VisualizeConstants.EDIT_PATH}/{{id}}`, { id: savedVis.id }); - } - } - }); - return { id }; - }, (error) => { - // eslint-disable-next-line - console.error(error); - toastNotifications.addDanger({ - title: i18n.translate('kbn.visualize.topNavMenu.saveVisualization.failureNotificationText', { - defaultMessage: `Error on saving '{visTitle}'`, - values: { - visTitle: savedVis.title, - }, - }), - text: error.message, - 'data-test-subj': 'saveVisualizationError', - }); - return { error }; - }); + return { id }; + }, (error) => { + // eslint-disable-next-line + console.error(error); + toastNotifications.addDanger({ + title: i18n.translate('kbn.visualize.topNavMenu.saveVisualization.failureNotificationText', { + defaultMessage: `Error on saving '{visTitle}'`, + values: { + visTitle: savedVis.title, + }, + }), + text: error.message, + 'data-test-subj': 'saveVisualizationError', + }); + return { error }; + }); + } + + $scope.unlink = function () { + if (!$state.linked) return; + + $state.linked = false; + const searchSourceParent = searchSource.getParent(); + const searchSourceGrandparent = searchSourceParent.getParent(); + + delete savedVis.savedSearchId; + delete vis.savedSearchId; + searchSourceParent.setField('filter', _.union(searchSource.getOwnField('filter'), searchSourceParent.getOwnField('filter'))); + + $state.query = searchSourceParent.getField('query'); + $state.filters = searchSourceParent.getField('filter'); + searchSource.setField('index', searchSourceParent.getField('index')); + searchSource.setParent(searchSourceGrandparent); + + toastNotifications.addSuccess( + i18n.translate('kbn.visualize.linkedToSearch.unlinkSuccessNotificationText', { + defaultMessage: `Unlinked from saved search '{searchTitle}'`, + values: { + searchTitle: savedVis.savedSearch.title } + }) + ); - $scope.unlink = function () { - if (!$state.linked) return; - - $state.linked = false; - const searchSourceParent = searchSource.getParent(); - const searchSourceGrandparent = searchSourceParent.getParent(); + $scope.fetch(); + }; - delete savedVis.savedSearchId; - delete vis.savedSearchId; - searchSourceParent.setField('filter', _.union(searchSource.getOwnField('filter'), searchSourceParent.getOwnField('filter'))); - $state.query = searchSourceParent.getField('query'); - $state.filters = searchSourceParent.getField('filter'); - searchSource.setField('index', searchSourceParent.getField('index')); - searchSource.setParent(searchSourceGrandparent); + $scope.getAdditionalMessage = () => { + return '' + + i18n.translate('kbn.visualize.experimentalVisInfoText', { defaultMessage: 'This visualization is marked as experimental.' }) + + ' ' + + vis.type.feedbackMessage; + }; - toastNotifications.addSuccess( - i18n.translate('kbn.visualize.linkedToSearch.unlinkSuccessNotificationText', { - defaultMessage: `Unlinked from saved search '{searchTitle}'`, - values: { - searchTitle: savedVis.savedSearch.title - } - }) - ); - - $scope.fetch(); - }; + addHelpMenuToAppChrome(chrome, docLinks); + init(); - $scope.getAdditionalMessage = () => { - return '' + - i18n.translate('kbn.visualize.experimentalVisInfoText', { defaultMessage: 'This visualization is marked as experimental.' }) + - ' ' + - vis.type.feedbackMessage; - }; - - addHelpMenuToAppChrome(chrome, docLinks); - - init(); - - const visibleSubscription = chrome.getIsVisible$().subscribe(isVisible => { - $scope.isVisible = isVisible; - }); - - $scope.$on('$destroy', () => { - visibleSubscription.unsubscribe(); - }); - }, - }; + const visibleSubscription = chrome.getIsVisible$().subscribe(isVisible => { + $scope.isVisible = isVisible; }); - initVisEditorDirective(app, deps); - initVisualizationDirective(app, deps); + $scope.$on('$destroy', () => { + visibleSubscription.unsubscribe(); + }); } - - diff --git a/src/legacy/core_plugins/kibana/public/visualize/editor/visualization.js b/src/legacy/core_plugins/kibana/public/visualize/editor/visualization.js index 18462f77c2b62..d3651735c1a1d 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/editor/visualization.js +++ b/src/legacy/core_plugins/kibana/public/visualize/editor/visualization.js @@ -18,7 +18,7 @@ */ export function initVisualizationDirective(app, deps) { - app.directive('visualizationEmbedded', function (Private, $timeout, getAppState) { + app.directive('visualizationEmbedded', function ($timeout, getAppState) { return { restrict: 'E', diff --git a/src/legacy/core_plugins/kibana/public/visualize/editor/visualization_editor.js b/src/legacy/core_plugins/kibana/public/visualize/editor/visualization_editor.js index 6f3fcf8409411..bc6d4d4c48466 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/editor/visualization_editor.js +++ b/src/legacy/core_plugins/kibana/public/visualize/editor/visualization_editor.js @@ -17,12 +17,8 @@ * under the License. */ -import { VisEditorTypesRegistryProvider } from '../kibana_services'; - export function initVisEditorDirective(app, deps) { - app.directive('visualizationEditor', function (Private, $timeout, getAppState) { - const editorTypes = Private(VisEditorTypesRegistryProvider); - + app.directive('visualizationEditor', function ($timeout, getAppState) { return { restrict: 'E', scope: { @@ -35,7 +31,8 @@ export function initVisEditorDirective(app, deps) { link: function ($scope, element) { const editorType = $scope.savedObj.vis.type.editor; const Editor = typeof editorType === 'function' ? editorType : - editorTypes.find(editor => editor.key === editorType); + deps.editorTypes.find(editor => editor.key === editorType); + const editor = new Editor(element[0], $scope.savedObj); $scope.renderFunction = () => { diff --git a/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable_factory.tsx b/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable_factory.tsx index 850c2ba459d2b..5b709d3760e27 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable_factory.tsx +++ b/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable_factory.tsx @@ -19,11 +19,8 @@ import { i18n } from '@kbn/i18n'; -import { Legacy } from 'kibana'; - import { SavedObjectAttributes } from 'kibana/server'; import { showNewVisModal } from '../wizard'; -import { SavedVisualizations } from '../types'; import { DisabledLabEmbeddable } from './disabled_lab_embeddable'; import { getIndexPattern } from './get_index_pattern'; import { VisualizeEmbeddable, VisualizeInput, VisualizeOutput } from './visualize_embeddable'; @@ -114,15 +111,12 @@ export class VisualizeEmbeddableFactory extends EmbeddableFactory< input: Partial & { id: string }, parent?: Container ): Promise { - const config = getServices().config; - const savedVisualizations = getServices().savedVisualizations; + const { addBasePath, config, savedVisualizations } = getServices(); try { const visId = savedObject.id as string; - const editUrl = visId - ? getServices().addBasePath(`/app/kibana${savedVisualizations.urlFor(visId)}`) - : ''; + const editUrl = visId ? addBasePath(`/app/kibana${savedVisualizations.urlFor(visId)}`) : ''; const loader = await getVisualizeLoader(); const isLabsEnabled = config.get('visualize:enableLabs'); @@ -156,12 +150,10 @@ export class VisualizeEmbeddableFactory extends EmbeddableFactory< input: Partial & { id: string }, parent?: Container ): Promise { - const savedVisualizations = getServices().savedVisualizations; - try { const visId = savedObjectId; - const savedObject = await savedVisualizations.get(visId); + const savedObject = await getServices().savedVisualizations.get(visId); return this.createFromObject(savedObject, input, parent); } catch (e) { console.error(e); // eslint-disable-line no-console @@ -180,7 +172,3 @@ export class VisualizeEmbeddableFactory extends EmbeddableFactory< return undefined; } } - -// VisualizeEmbeddableFactory.createVisualizeEmbeddableFactory().then(embeddableFactory => { -// getServices().embeddable.registerEmbeddableFactory(VISUALIZE_EMBEDDABLE_TYPE, embeddableFactory); -// }); diff --git a/src/legacy/core_plugins/kibana/public/visualize/index.ts b/src/legacy/core_plugins/kibana/public/visualize/index.ts index 0cd4b751bf82a..ec44d5ef3f304 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/index.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/index.ts @@ -21,18 +21,21 @@ import 'angular-sanitize'; // used in visualization_editor.js and visualization. import 'ui/collapsible_sidebar'; // used in default editor import 'ui/vis/editors/default/sidebar'; -import { FeatureCatalogueRegistryProvider } from 'ui/registry/feature_catalogue'; -import { npSetup, npStart } from 'ui/new_platform'; -import { SavedObjectRegistryProvider, SavedObjectsClientProvider } from 'ui/saved_objects'; -import { docTitle } from 'ui/doc_title/doc_title'; import chrome from 'ui/chrome'; +import { docTitle } from 'ui/doc_title/doc_title'; +import { FilterBarQueryFilterProvider } from 'ui/filter_manager/query_filter'; +import { npSetup, npStart } from 'ui/new_platform'; import { IPrivate } from 'ui/private'; +import { FeatureCatalogueRegistryProvider } from 'ui/registry/feature_catalogue'; +// @ts-ignore +import { VisEditorTypesRegistryProvider } from 'ui/registry/vis_editor_types'; +import { SavedObjectRegistryProvider, SavedObjectsClientProvider } from 'ui/saved_objects'; import { ShareContextMenuExtensionsRegistryProvider } from 'ui/share'; import { getUnhashableStatesProvider } from 'ui/state_management/state_hashing/get_unhashable_states_provider'; -import { FilterBarQueryFilterProvider } from 'ui/filter_manager/query_filter'; + import { VisualizePlugin, LegacyAngularInjectedDependencies } from './plugin'; -import { start as data } from '../../../data/public/legacy'; import { localApplicationService } from '../local_application_service'; +import { start as data } from '../../../data/public/legacy'; import { start as embeddables, setup as embeddable, @@ -57,33 +60,35 @@ async function getAngularDependencies(): Promise { const instance = new VisualizePlugin(); - await instance.setup(npSetup.core, { + instance.setup(npSetup.core, { __LEGACY: { - localApplicationService, + docTitle, getAngularDependencies, FeatureCatalogueRegistryProvider, - docTitle, + localApplicationService, }, - embeddable, }); - instance.start(npStart.core, { + await instance.start(npStart.core, { data, + // it's needed to register embeddable factory + embeddable, embeddables, navigation, + npData: npStart.plugins.data, visualizations, }); })(); diff --git a/src/legacy/core_plugins/kibana/public/visualize/kibana_services.ts b/src/legacy/core_plugins/kibana/public/visualize/kibana_services.ts index f36e4357b07cf..d9c2453f997fe 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/kibana_services.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/kibana_services.ts @@ -25,7 +25,6 @@ import { UiSettingsClientContract, } from 'kibana/public'; - // load directives import '../../../data/public'; @@ -46,7 +45,6 @@ export interface VisualizeKibanaServices { getInjected: (name: string, defaultValue?: any) => unknown; FeatureCatalogueRegistryProvider: any; indexPatterns: any; - METRIC_TYPE: any; toastNotifications: ToastNotifications; savedObjectsClient: SavedObjectsClientContract; savedVisualizations: SavedVisualizations; @@ -78,18 +76,15 @@ export function clearServices() { services = null; } +// export types +export { DocTitle } from 'ui/doc_title/doc_title'; // export legacy static dependencies export { ensureDefaultIndexPattern } from 'ui/legacy_compat'; - - - export { getFromSavedObject } from 'ui/index_patterns'; export { PersistedState } from 'ui/persisted_state'; // @ts-ignore -export { VisEditorTypesRegistryProvider } from 'ui/registry/vis_editor_types'; -// @ts-ignore export { getUnhashableStatesProvider } from 'ui/state_management/state_hashing'; export { showSaveModal } from 'ui/saved_objects/show_saved_object_save_modal'; export { showShareContextMenu } from 'ui/share'; diff --git a/src/legacy/core_plugins/kibana/public/visualize/listing/visualize_listing.js b/src/legacy/core_plugins/kibana/public/visualize/listing/visualize_listing.js index 38d29c5ca141b..e1da3e0a45115 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/listing/visualize_listing.js +++ b/src/legacy/core_plugins/kibana/public/visualize/listing/visualize_listing.js @@ -23,7 +23,7 @@ import { NewVisModal } from '../wizard/new_vis_modal'; import { VisualizeConstants } from '../visualize_constants'; import { i18n } from '@kbn/i18n'; -import { getServices, config } from '../kibana_services'; +import { getServices } from '../kibana_services'; export function initListingDirective(app, deps) { app.directive('visualizeListingTable', reactDirective => diff --git a/src/legacy/core_plugins/kibana/public/visualize/listing/visualize_listing_table.js b/src/legacy/core_plugins/kibana/public/visualize/listing/visualize_listing_table.js index 188a87df3bfd8..d8f3225b47a9b 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/listing/visualize_listing_table.js +++ b/src/legacy/core_plugins/kibana/public/visualize/listing/visualize_listing_table.js @@ -33,14 +33,15 @@ class VisualizeListingTable extends Component { } render() { + const { visualizeCapabilities } = getServices(); return ( item.canDelete} diff --git a/src/legacy/core_plugins/kibana/public/visualize/plugin.ts b/src/legacy/core_plugins/kibana/public/visualize/plugin.ts index dcb62d81b1560..36db8ae31b76b 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/plugin.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/plugin.ts @@ -29,15 +29,19 @@ import { SavedObjectsClientContract, } from 'kibana/public'; import { Storage } from '../../../../../plugins/kibana_utils/public'; -import { setServices } from './kibana_services'; -import { LocalApplicationService } from '../local_application_service'; import { DataStart } from '../../../data/public'; import { EmbeddablePublicPlugin } from '../../../../../plugins/embeddable/public'; import { NavigationStart } from '../../../navigation/public'; import { VisualizationsStart } from '../../../visualizations/public'; +import { DataPublicPluginStart as NpDataStart } from '../../../../../plugins/data/public'; +import { LocalApplicationService } from '../local_application_service'; import { VisualizeEmbeddableFactory } from './embeddable/visualize_embeddable_factory'; +import { VISUALIZE_EMBEDDABLE_TYPE } from './embeddable/constants'; +import { RenderDeps } from './render_app'; +import { setServices, DocTitle } from './kibana_services'; export interface LegacyAngularInjectedDependencies { + editorTypes: any; queryFilter: any; getUnhashableStates: any; shareContextMenuExtensions: any; @@ -50,8 +54,10 @@ export interface LegacyAngularInjectedDependencies { export interface VisualizePluginStartDependencies { data: DataStart; + embeddable: ReturnType; embeddables: ReturnType; navigation: NavigationStart; + npData: NpDataStart; visualizations: VisualizationsStart; } @@ -60,17 +66,17 @@ export interface VisualizePluginSetupDependencies { getAngularDependencies: () => Promise; localApplicationService: LocalApplicationService; FeatureCatalogueRegistryProvider: any; - docTitle: any; + docTitle: DocTitle; }; - embeddable: any; } export class VisualizePlugin implements Plugin { private startDependencies: { dataStart: DataStart; - savedObjectsClient: SavedObjectsClientContract; embeddables: ReturnType; navigation: NavigationStart; + npDataStart: NpDataStart; + savedObjectsClient: SavedObjectsClientContract; visualizations: VisualizationsStart; } | null = null; @@ -78,7 +84,6 @@ export class VisualizePlugin implements Plugin { core: CoreSetup, { __LEGACY: { localApplicationService, getAngularDependencies, ...legacyServices }, - embeddable, }: VisualizePluginSetupDependencies ) { const app: App = { @@ -94,11 +99,12 @@ export class VisualizePlugin implements Plugin { savedObjectsClient, embeddables, navigation, + npDataStart, visualizations, } = this.startDependencies; const angularDependencies = await getAngularDependencies(); - const deps = { + const deps: RenderDeps = { core: contextCore as LegacyCoreStart, ...legacyServices, ...angularDependencies, @@ -107,7 +113,6 @@ export class VisualizePlugin implements Plugin { chrome: contextCore.chrome, dataStart, docLinks: contextCore.docLinks, - embeddable, embeddables, getBasePath: core.http.basePath.get, getInjected: core.injectedMetadata.getInjectedVar, @@ -115,12 +120,13 @@ export class VisualizePlugin implements Plugin { indexPatternService: dataStart.indexPatterns.indexPatterns, localStorage: new Storage(localStorage), navigation, + npDataStart, savedObjectsClient, savedQueryService: dataStart.search.services.savedQueryService, + toastNotifications: contextCore.notifications.toasts, uiCapabilities: contextCore.application.capabilities, uiSettings: contextCore.uiSettings, visualizeCapabilities: contextCore.application.capabilities.visualize, - VisEmbeddableFactory: VisualizeEmbeddableFactory, visualizations, wrapInI18nContext, }; @@ -132,16 +138,34 @@ export class VisualizePlugin implements Plugin { localApplicationService.register({ ...app, id: 'visualize' }); } - start( + async start( { savedObjects: { client: savedObjectsClient } }: CoreStart, - { data: dataStart, embeddables, navigation, visualizations }: VisualizePluginStartDependencies + { + data: dataStart, + embeddables, + navigation, + npData, + visualizations, + embeddable, + }: VisualizePluginStartDependencies ) { this.startDependencies = { dataStart, - savedObjectsClient, embeddables, navigation, + npDataStart: npData, + savedObjectsClient, visualizations, }; + + const createVisualizeEmbeddableFactory: () => Promise< + VisualizeEmbeddableFactory + > = async () => { + return new VisualizeEmbeddableFactory(visualizations.types); + }; + + await createVisualizeEmbeddableFactory().then((embeddableFactory: any) => { + embeddable.registerEmbeddableFactory(VISUALIZE_EMBEDDABLE_TYPE, embeddableFactory); + }); } } diff --git a/src/legacy/core_plugins/kibana/public/visualize/render_app.ts b/src/legacy/core_plugins/kibana/public/visualize/render_app.ts index 12b596c4438e7..1e58b864ffda7 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/render_app.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/render_app.ts @@ -19,8 +19,10 @@ import { EuiConfirmModal } from '@elastic/eui'; import { IModule } from 'angular'; -import { IPrivate } from 'ui/private'; import { i18nDirective, i18nFilter, I18nProvider } from '@kbn/i18n/src/angular'; + +import { IPrivate } from 'ui/private'; +import { configureAppAngularModule } from 'ui/legacy_compat'; // @ts-ignore import { GlobalStateProvider } from 'ui/state_management/global_state'; // @ts-ignore @@ -45,26 +47,13 @@ import { confirmModalFactory } from 'ui/modals/confirm_modal'; import { AppMountContext, ChromeStart, + DocLinksStart, LegacyCoreStart, SavedObjectsClientContract, + ToastsStart, UiSettingsClientContract, } from 'kibana/public'; -import { configureAppAngularModule } from 'ui/legacy_compat'; - -import { - createVisEditorSidebarDirective, - visEditorGroupDeps, - visOptionsDeps, - createVisEditorGroupDirective, - DefaultEditorAggGroup, - VisOptionsReactWrapper, - createVisOptionsDirective, -} from 'ui/vis/editors/default'; - import { Storage } from '../../../../../plugins/kibana_utils/public'; - -// @ts-ignore -import { initVisualizeApp } from './app'; import { createApplyFiltersPopoverDirective, createApplyFiltersPopoverHelper, @@ -76,34 +65,42 @@ import { SavedQueryService } from '../../../data/public/search/search_bar/lib/sa import { EmbeddablePublicPlugin } from '../../../../../plugins/embeddable/public'; import { NavigationStart } from '../../../navigation/public'; import { VisualizationsStart } from '../../../visualizations/public'; -import { VISUALIZE_EMBEDDABLE_TYPE, VisualizeEmbeddableFactory } from './embeddable/constants'; +import { DataPublicPluginStart as NpDataStart } from '../../../../../plugins/data/public'; + +// @ts-ignore +import { initVisualizeApp } from './app'; +import { DocTitle } from './kibana_services'; export interface RenderDeps { + addBasePath: (path: string) => string; angular: any; + chrome: ChromeStart; + config: any; core: LegacyCoreStart; - indexPatterns: DataStart['indexPatterns']['indexPatterns']; dataStart: DataStart; + docLinks: DocLinksStart; + docTitle: DocTitle; + embeddables: ReturnType; + FeatureCatalogueRegistryProvider: any; + getBasePath: () => string; + getInjected: (name: string, defaultValue?: any) => unknown; + getUnhashableStates: any; + indexPatterns: DataStart['indexPatterns']['indexPatterns']; + indexPatternService: any; + localStorage: Storage; + npDataStart: NpDataStart; navigation: NavigationStart; - visualizations: VisualizationsStart; queryFilter: any; - getUnhashableStates: any; - shareContextMenuExtensions: any; - savedObjectsClient: SavedObjectsClientContract; savedObjectRegistry: any; - config: any; savedDashboards: any; - visualizeCapabilities: any; + savedObjectsClient: SavedObjectsClientContract; + savedQueryService: SavedQueryService; + shareContextMenuExtensions: any; + toastNotifications: ToastsStart; uiCapabilities: any; - docTitle: any; uiSettings: UiSettingsClientContract; - chrome: ChromeStart; - addBasePath: (path: string) => string; - FeatureCatalogueRegistryProvider: any; - savedQueryService: SavedQueryService; - embeddables: ReturnType; - embeddable: ReturnType; - localStorage: Storage; - VisEmbeddableFactory: VisualizeEmbeddableFactory; + visualizeCapabilities: any; + visualizations: VisualizationsStart; wrapInI18nContext: any; } @@ -111,26 +108,11 @@ let angularModuleInstance: IModule | null = null; export const renderApp = async (element: HTMLElement, appBasePath: string, deps: RenderDeps) => { if (!angularModuleInstance) { - angularModuleInstance = createLocalAngularModule( - deps.core, - deps.navigation, - deps.angular, - deps.wrapInI18nContext - ); + angularModuleInstance = createLocalAngularModule(deps.core, deps.navigation, deps.angular); // global routing stuff configureAppAngularModule(angularModuleInstance, deps.core as LegacyCoreStart); // custom routing stuff initVisualizeApp(angularModuleInstance, deps); - - const { VisEmbeddableFactory } = deps; - const createVisualizeEmbeddableFactory: () => Promise< - VisualizeEmbeddableFactory - > = async () => { - return new VisEmbeddableFactory(deps.visualizations.types); - }; - await createVisualizeEmbeddableFactory().then((embeddableFactory: any) => { - deps.embeddable.registerEmbeddableFactory(VISUALIZE_EMBEDDABLE_TYPE, embeddableFactory); - }); } const $injector = mountVisualizeApp(appBasePath, element, deps.angular); return () => $injector.get('$rootScope').$destroy(); @@ -163,8 +145,7 @@ function mountVisualizeApp(appBasePath: string, element: HTMLElement, angular: a function createLocalAngularModule( core: AppMountContext['core'], navigation: NavigationStart, - angular: any, - wrapInI18nContext: any + angular: any ) { createLocalI18nModule(angular); createLocalPrivateModule(angular); @@ -195,7 +176,7 @@ function createLocalConfirmModalModule(angular: any) { angular .module('app/visualize/ConfirmModal', ['react']) .factory('confirmModal', confirmModalFactory) - .directive('confirmModal', reactDirective => reactDirective(EuiConfirmModal)); + .directive('confirmModal', (reactDirective: any) => reactDirective(EuiConfirmModal)); } function createLocalStateModule(angular: any) { diff --git a/src/legacy/core_plugins/kibana/public/visualize/visualize_app.ts b/src/legacy/core_plugins/kibana/public/visualize/visualize_app.ts index a1c8bf346a5b4..34e7ca577790c 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/visualize_app.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/visualize_app.ts @@ -17,23 +17,6 @@ * under the License. */ -import { - AppStateClass as TAppStateClass, - AppState as TAppState, -} from 'ui/state_management/app_state'; - -import { KbnUrl } from 'ui/url/kbn_url'; -import { Filter } from '@kbn/es-query'; -import { TimeRange } from 'src/plugins/data/public'; -import { StaticIndexPattern, Query, SavedQuery } from 'plugins/data'; -import moment from 'moment'; -import { Subscription } from 'rxjs'; - -import { ViewMode } from '../../../embeddable_api/public/np_ready/public'; -import { SavedVisualizations } from './types'; - -// @ts-ignore -import { VisualizeAppController } from './editor/editor'; import { RenderDeps } from './render_app'; // @ts-ignore @@ -41,54 +24,6 @@ import { initEditorDirective } from './editor/editor'; // @ts-ignore import { initListingDirective } from './listing/visualize_listing'; -export interface VisualizeAppScope extends ng.IScope { - // dash: SavedObjectDashboard; - appState: TAppState; - screenTitle: string; - model: { - query: Query; - filters: Filter[]; - timeRestore: boolean; - title: string; - description: string; - timeRange: - | TimeRange - | { to: string | moment.Moment | undefined; from: string | moment.Moment | undefined }; - refreshInterval: any; - }; - savedQuery?: SavedQuery; - refreshInterval: any; - panels: SavedVisualizations[]; - indexPatterns: StaticIndexPattern[]; - $evalAsync: any; - dashboardViewMode: ViewMode; - expandedPanel?: string; - getShouldShowEditHelp: () => boolean; - getShouldShowViewHelp: () => boolean; - updateQueryAndFetch: ({ query, dateRange }: { query: Query; dateRange?: TimeRange }) => void; - onRefreshChange: ({ - isPaused, - refreshInterval, - }: { - isPaused: boolean; - refreshInterval: any; - }) => void; - onFiltersUpdated: (filters: Filter[]) => void; - onCancelApplyFilters: () => void; - onApplyFilters: (filters: Filter[]) => void; - onQuerySaved: (savedQuery: SavedQuery) => void; - onSavedQueryUpdated: (savedQuery: SavedQuery) => void; - onClearSavedQuery: () => void; - topNavMenu: any; - showFilterBar: () => boolean; - showAddPanel: any; - showSaveQuery: boolean; - kbnTopNav: any; - enterEditMode: () => void; - timefilterSubscriptions$: Subscription; - isVisible: boolean; -} - export function initVisualizeAppDirective(app: any, deps: RenderDeps) { initEditorDirective(app, deps); initListingDirective(app, deps); From ac462b5db49f1588c29fc8f2c2b9222b3404d8a0 Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Sun, 3 Nov 2019 09:15:20 +0100 Subject: [PATCH 051/132] fix hooks handling for local routes in legacy_compat --- .../kibana/public/dashboard/render_app.ts | 2 +- src/legacy/ui/public/chrome/api/angular.js | 2 +- .../public/legacy_compat/angular_config.tsx | 51 +++++++++++++------ .../ui/public/vis/vis_filters/vis_filters.js | 6 --- .../legacy/plugins/graph/public/render_app.ts | 2 +- 5 files changed, 38 insertions(+), 25 deletions(-) diff --git a/src/legacy/core_plugins/kibana/public/dashboard/render_app.ts b/src/legacy/core_plugins/kibana/public/dashboard/render_app.ts index f753849fe13d7..564f63ce18a7d 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/render_app.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/render_app.ts @@ -93,7 +93,7 @@ export const renderApp = (element: HTMLElement, appBasePath: string, deps: Rende if (!angularModuleInstance) { angularModuleInstance = createLocalAngularModule(deps.core, deps.navigation); // global routing stuff - configureAppAngularModule(angularModuleInstance, deps.core as LegacyCoreStart); + configureAppAngularModule(angularModuleInstance, deps.core as LegacyCoreStart, true); // custom routing stuff initDashboardApp(angularModuleInstance, deps); } diff --git a/src/legacy/ui/public/chrome/api/angular.js b/src/legacy/ui/public/chrome/api/angular.js index 512229cca6126..73d50a83e11a5 100644 --- a/src/legacy/ui/public/chrome/api/angular.js +++ b/src/legacy/ui/public/chrome/api/angular.js @@ -29,7 +29,7 @@ export function initAngularApi(chrome, internals) { chrome.setupAngular = function () { const kibana = uiModules.get('kibana'); - configureAppAngularModule(kibana, npStart.core, data); + configureAppAngularModule(kibana, npStart.core, data, false); kibana.value('chrome', chrome); diff --git a/src/legacy/ui/public/legacy_compat/angular_config.tsx b/src/legacy/ui/public/legacy_compat/angular_config.tsx index f941ea31a7ed0..90e22a045a045 100644 --- a/src/legacy/ui/public/legacy_compat/angular_config.tsx +++ b/src/legacy/ui/public/legacy_compat/angular_config.tsx @@ -47,13 +47,32 @@ import { isSystemApiRequest } from '../system_api'; const URL_LIMIT_WARN_WITHIN = 1000; -function isDummyWrapperRoute($route: any) { +/** + * Detects whether a given angular route is a dummy route that doesn't + * require any action. There are two ways this can happen: + * If `outerAngularWrapperRoute` is set on the route config object, + * it means the local application service set up this route on the outer angular + * and the internal routes will handle the hooks. + * + * If angular did not detect a route and it is the local angular, we are currently + * navigating away from a URL controlled by a local angular router and the + * application will get unmounted. In this case the outer router will handle + * the hooks. + * @param $route Injected $route dependency + * @param isLocalAngular Flag whether this is the local angular router + */ +function isDummyRoute($route: any, isLocalAngular: boolean) { return ( - $route.current && $route.current.$$route && $route.current.$$route.outerAngularWrapperRoute + ($route.current && $route.current.$$route && $route.current.$$route.outerAngularWrapperRoute) || + (!$route.current && isLocalAngular) ); } -export const configureAppAngularModule = (angularModule: IModule, newPlatform: LegacyCoreStart) => { +export const configureAppAngularModule = ( + angularModule: IModule, + newPlatform: LegacyCoreStart, + isLocalAngular: boolean +) => { const legacyMetadata = newPlatform.injectedMetadata.getLegacyMetadata(); forOwn(newPlatform.injectedMetadata.getInjectedVars(), (val, name) => { @@ -74,10 +93,10 @@ export const configureAppAngularModule = (angularModule: IModule, newPlatform: L .config(setupLocationProvider(newPlatform)) .config($setupXsrfRequestInterceptor(newPlatform)) .run(capture$httpLoadingCount(newPlatform)) - .run($setupBreadcrumbsAutoClear(newPlatform)) - .run($setupBadgeAutoClear(newPlatform)) - .run($setupHelpExtensionAutoClear(newPlatform)) - .run($setupUrlOverflowHandling(newPlatform)) + .run($setupBreadcrumbsAutoClear(newPlatform, isLocalAngular)) + .run($setupBadgeAutoClear(newPlatform, isLocalAngular)) + .run($setupHelpExtensionAutoClear(newPlatform, isLocalAngular)) + .run($setupUrlOverflowHandling(newPlatform, isLocalAngular)) .run($setupUICapabilityRedirect(newPlatform)); }; @@ -200,7 +219,7 @@ const $setupUICapabilityRedirect = (newPlatform: CoreStart) => ( * lets us integrate with the angular router so that we can automatically clear * the breadcrumbs if we switch to a Kibana app that does not use breadcrumbs correctly */ -const $setupBreadcrumbsAutoClear = (newPlatform: CoreStart) => ( +const $setupBreadcrumbsAutoClear = (newPlatform: CoreStart, isLocalAngular: boolean) => ( $rootScope: IRootScopeService, $injector: any ) => { @@ -222,7 +241,7 @@ const $setupBreadcrumbsAutoClear = (newPlatform: CoreStart) => ( }); $rootScope.$on('$routeChangeSuccess', () => { - if (isDummyWrapperRoute($route)) { + if (isDummyRoute($route, isLocalAngular)) { return; } const current = $route.current || {}; @@ -250,7 +269,7 @@ const $setupBreadcrumbsAutoClear = (newPlatform: CoreStart) => ( * lets us integrate with the angular router so that we can automatically clear * the badge if we switch to a Kibana app that does not use the badge correctly */ -const $setupBadgeAutoClear = (newPlatform: CoreStart) => ( +const $setupBadgeAutoClear = (newPlatform: CoreStart, isLocalAngular: boolean) => ( $rootScope: IRootScopeService, $injector: any ) => { @@ -264,7 +283,7 @@ const $setupBadgeAutoClear = (newPlatform: CoreStart) => ( }); $rootScope.$on('$routeChangeSuccess', () => { - if (isDummyWrapperRoute($route)) { + if (isDummyRoute($route, isLocalAngular)) { return; } const current = $route.current || {}; @@ -293,7 +312,7 @@ const $setupBadgeAutoClear = (newPlatform: CoreStart) => ( * the helpExtension if we switch to a Kibana app that does not set its own * helpExtension */ -const $setupHelpExtensionAutoClear = (newPlatform: CoreStart) => ( +const $setupHelpExtensionAutoClear = (newPlatform: CoreStart, isLocalAngular: boolean) => ( $rootScope: IRootScopeService, $injector: any ) => { @@ -311,14 +330,14 @@ const $setupHelpExtensionAutoClear = (newPlatform: CoreStart) => ( const $route = $injector.has('$route') ? $injector.get('$route') : {}; $rootScope.$on('$routeChangeStart', () => { - if (isDummyWrapperRoute($route)) { + if (isDummyRoute($route, isLocalAngular)) { return; } helpExtensionSetSinceRouteChange = false; }); $rootScope.$on('$routeChangeSuccess', () => { - if (isDummyWrapperRoute($route)) { + if (isDummyRoute($route, isLocalAngular)) { return; } const current = $route.current || {}; @@ -331,7 +350,7 @@ const $setupHelpExtensionAutoClear = (newPlatform: CoreStart) => ( }); }; -const $setupUrlOverflowHandling = (newPlatform: CoreStart) => ( +const $setupUrlOverflowHandling = (newPlatform: CoreStart, isLocalAngular: boolean) => ( $location: ILocationService, $rootScope: IRootScopeService, $injector: auto.IInjectorService @@ -339,7 +358,7 @@ const $setupUrlOverflowHandling = (newPlatform: CoreStart) => ( const $route = $injector.has('$route') ? $injector.get('$route') : {}; const urlOverflow = new UrlOverflowService(); const check = () => { - if (isDummyWrapperRoute($route)) { + if (isDummyRoute($route, isLocalAngular)) { return; } // disable long url checks when storing state in session storage diff --git a/src/legacy/ui/public/vis/vis_filters/vis_filters.js b/src/legacy/ui/public/vis/vis_filters/vis_filters.js index cf904603e3beb..2ef5803fc2ba6 100644 --- a/src/legacy/ui/public/vis/vis_filters/vis_filters.js +++ b/src/legacy/ui/public/vis/vis_filters/vis_filters.js @@ -109,12 +109,6 @@ const createFiltersFromEvent = (event) => { // TODO make sure the visualize app is updating the breadcrumb correctly const VisFiltersProvider = () => { - // TODO this function used to simply put the new filters in - // the app state. Dashboard/Visualize simply listened to - // the app state change via angular and pushed it into the actual - // filter manager (while splitting out the time filter) - // This channel does not work anymore because it's not the same - // angular context and thus the appstate won't update const pushFilters = async (filters, simulate) => { if (filters.length && !simulate) { const dedupedFilters = uniqFilters(filters); diff --git a/x-pack/legacy/plugins/graph/public/render_app.ts b/x-pack/legacy/plugins/graph/public/render_app.ts index f92490521ba55..fba67a2f04b21 100644 --- a/x-pack/legacy/plugins/graph/public/render_app.ts +++ b/x-pack/legacy/plugins/graph/public/render_app.ts @@ -83,7 +83,7 @@ export interface LegacyAngularInjectedDependencies { export const renderApp = ({ appBasePath, element, ...deps }: GraphDependencies) => { const graphAngularModule = createLocalAngularModule(deps.navigation); - configureAppAngularModule(graphAngularModule, deps.coreStart as LegacyCoreStart); + configureAppAngularModule(graphAngularModule, deps.coreStart as LegacyCoreStart, true); initGraphApp(graphAngularModule, deps); const $injector = mountGraphApp(appBasePath, element); return () => $injector.get('$rootScope').$destroy(); From 3c619d29dc9c4008d8915623f048b2786342d697 Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Sun, 3 Nov 2019 10:06:32 +0100 Subject: [PATCH 052/132] fix embedded visualize handler tests --- src/legacy/ui/public/vis/vis_filters/vis_filters.js | 1 - .../public/visualize/loader/embedded_visualize_handler.test.ts | 2 ++ 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/legacy/ui/public/vis/vis_filters/vis_filters.js b/src/legacy/ui/public/vis/vis_filters/vis_filters.js index 2ef5803fc2ba6..9e70fb45cd311 100644 --- a/src/legacy/ui/public/vis/vis_filters/vis_filters.js +++ b/src/legacy/ui/public/vis/vis_filters/vis_filters.js @@ -106,7 +106,6 @@ const createFiltersFromEvent = (event) => { return filters; }; -// TODO make sure the visualize app is updating the breadcrumb correctly const VisFiltersProvider = () => { const pushFilters = async (filters, simulate) => { diff --git a/src/legacy/ui/public/visualize/loader/embedded_visualize_handler.test.ts b/src/legacy/ui/public/visualize/loader/embedded_visualize_handler.test.ts index c73f787457a03..2038fe2410c03 100644 --- a/src/legacy/ui/public/visualize/loader/embedded_visualize_handler.test.ts +++ b/src/legacy/ui/public/visualize/loader/embedded_visualize_handler.test.ts @@ -36,6 +36,8 @@ jest.mock('plugins/interpreter/interpreter', () => ({ }, })); +jest.mock('../../../../core_plugins/data/public/legacy', () => ({})); + jest.mock('../../../../core_plugins/interpreter/public/registries', () => ({ registries: { renderers: { From 02caf2739c782ac9d40453a1e2df3e83dedc4aea Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Sun, 3 Nov 2019 12:48:04 +0100 Subject: [PATCH 053/132] fix test failures --- .../kibana/public/dashboard/dashboard_app_controller.tsx | 7 +++++-- .../public/embeddable/viewport/_dashboard_viewport.scss | 2 ++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx index f0e363d488014..cf528ae0ebf2d 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx +++ b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx @@ -149,9 +149,12 @@ export class DashboardAppController { $scope.appState = dashboardStateManager.getAppState(); - // The 'previouslyStored' check is so we only update the time filter on dashboard open, not during + // The hash check is so we only update the time filter on dashboard open, not during // normal cross app navigation. - if (dashboardStateManager.getIsTimeSavedWithDashboard() && !getAppState.previouslyStored()) { + if ( + dashboardStateManager.getIsTimeSavedWithDashboard() && + !window.location.hash.includes('_a=') + ) { dashboardStateManager.syncTimefilterWithDashboard(timefilter); } $scope.showSaveQuery = dashboardCapabilities.saveQuery as boolean; diff --git a/src/plugins/dashboard_embeddable_container/public/embeddable/viewport/_dashboard_viewport.scss b/src/plugins/dashboard_embeddable_container/public/embeddable/viewport/_dashboard_viewport.scss index 7cbe135115877..9575908146d1d 100644 --- a/src/plugins/dashboard_embeddable_container/public/embeddable/viewport/_dashboard_viewport.scss +++ b/src/plugins/dashboard_embeddable_container/public/embeddable/viewport/_dashboard_viewport.scss @@ -1,8 +1,10 @@ .dshDashboardViewport { + height: 100%; width: 100%; background-color: $euiColorEmptyShade; } .dshDashboardViewport-withMargins { width: 100%; + height: 100%; } From 78855e198914e89cb2478d56e785da7a546e0334 Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Sun, 3 Nov 2019 15:12:36 +0100 Subject: [PATCH 054/132] remove buggy test --- .../apps/dashboard/dashboard_time.js | 20 +------------------ 1 file changed, 1 insertion(+), 19 deletions(-) diff --git a/test/functional/apps/dashboard/dashboard_time.js b/test/functional/apps/dashboard/dashboard_time.js index 917157e54eee0..18a1fc7374c1a 100644 --- a/test/functional/apps/dashboard/dashboard_time.js +++ b/test/functional/apps/dashboard/dashboard_time.js @@ -24,9 +24,8 @@ const dashboardName = 'Dashboard Test Time'; const fromTime = '2015-09-19 06:31:44.000'; const toTime = '2015-09-23 18:31:44.000'; -export default function ({ getPageObjects, getService }) { +export default function ({ getPageObjects }) { const PageObjects = getPageObjects(['dashboard', 'header', 'timePicker']); - const browser = getService('browser'); describe('dashboard time', () => { before(async function () { @@ -72,23 +71,6 @@ export default function ({ getPageObjects, getService }) { expect(time.start).to.equal('Sep 19, 2015 @ 06:31:44.000'); expect(time.end).to.equal('Sep 23, 2015 @ 18:31:44.000'); }); - - // If time is stored with a dashboard, it's supposed to override the current time settings when opened. - // However, if the URL also contains time in the global state, then the global state - // time should take precedence. - it('should be overwritten by global state', async function () { - const currentUrl = await browser.getCurrentUrl(); - const kibanaBaseUrl = currentUrl.substring(0, currentUrl.indexOf('#')); - const id = await PageObjects.dashboard.getDashboardIdFromCurrentUrl(); - - await PageObjects.dashboard.gotoDashboardLandingPage(); - - const urlWithGlobalTime = `${kibanaBaseUrl}#/dashboard/${id}?_g=(time:(from:now-1h,to:now))`; - await browser.get(urlWithGlobalTime, false); - const time = await PageObjects.timePicker.getTimeConfig(); - expect(time.start).to.equal('~ an hour ago'); - expect(time.end).to.equal('now'); - }); }); // If the user has time stored with a dashboard, it's supposed to override the current time settings From b35161ae25192af3cec27571805971fbe2a1bcaf Mon Sep 17 00:00:00 2001 From: maryia-lapata Date: Mon, 4 Nov 2019 12:19:12 +0300 Subject: [PATCH 055/132] Update TS --- .../core_plugins/kibana/public/kibana.js | 2 +- .../embeddable/visualize_embeddable.ts | 2 +- .../kibana/public/visualize/index.ts | 18 ++-- .../kibana/public/visualize/index_o.js | 87 ------------------- .../public/visualize/kibana_services.ts | 64 +++++++------- .../kibana/public/visualize/plugin.ts | 61 ++++++------- .../kibana/public/visualize/render_app.ts | 59 ++----------- .../kibana/public/visualize/visualize_app.ts | 4 +- .../public/visualize/wizard/new_vis_modal.tsx | 2 +- src/legacy/ui/public/chrome/api/angular.js | 2 +- .../public/legacy_compat/angular_config.tsx | 51 +++++++---- .../ui/public/vis/editors/default/default.js | 3 - .../ui/public/vis/vis_filters/vis_filters.js | 7 -- .../ui/ui_exports/ui_export_defaults.js | 1 - 14 files changed, 119 insertions(+), 244 deletions(-) delete mode 100644 src/legacy/core_plugins/kibana/public/visualize/index_o.js diff --git a/src/legacy/core_plugins/kibana/public/kibana.js b/src/legacy/core_plugins/kibana/public/kibana.js index 04f50951ba183..fe741a357cbfe 100644 --- a/src/legacy/core_plugins/kibana/public/kibana.js +++ b/src/legacy/core_plugins/kibana/public/kibana.js @@ -39,7 +39,7 @@ import 'uiExports/managementSections'; import 'uiExports/indexManagement'; import 'uiExports/devTools'; import 'uiExports/docViews'; -//import 'uiExports/embeddableFactories'; +import 'uiExports/embeddableFactories'; import 'uiExports/embeddableActions'; import 'uiExports/inspectorViews'; import 'uiExports/search'; diff --git a/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable.ts b/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable.ts index ef9c9a00f980b..3b9adcaf250f6 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable.ts @@ -18,7 +18,6 @@ */ import _ from 'lodash'; -import { EmbeddedVisualizeHandler } from 'ui/visualize/loader/embedded_visualize_handler'; import { Subscription } from 'rxjs'; import * as Rx from 'rxjs'; import { Filter } from '@kbn/es-query'; @@ -32,6 +31,7 @@ import { Embeddable, EmbeddableInput, EmbeddableOutput, + EmbeddedVisualizeHandler, PersistedState, StaticIndexPattern, VisSavedObject, diff --git a/src/legacy/core_plugins/kibana/public/visualize/index.ts b/src/legacy/core_plugins/kibana/public/visualize/index.ts index ec44d5ef3f304..3b1909fe739b4 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/index.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/index.ts @@ -35,13 +35,16 @@ import { getUnhashableStatesProvider } from 'ui/state_management/state_hashing/g import { VisualizePlugin, LegacyAngularInjectedDependencies } from './plugin'; import { localApplicationService } from '../local_application_service'; -import { start as data } from '../../../data/public/legacy'; +import { start as dataStart } from '../../../data/public/legacy'; import { start as embeddables, - setup as embeddable, + setup as embeddableSetup, } from '../../../embeddable_api/public/np_ready/public/legacy'; import { start as navigation } from '../../../navigation/public/legacy'; -import { start as visualizations } from '../../../visualizations/public/np_ready/public/legacy'; +import { + start as visualizations, + setup as visualizationsSetup, +} from '../../../visualizations/public/np_ready/public/legacy'; /** * Get dependencies relying on the global angular context. @@ -75,6 +78,8 @@ async function getAngularDependencies(): Promise { const instance = new VisualizePlugin(); instance.setup(npSetup.core, { + embeddableSetup, + visualizationsSetup, __LEGACY: { docTitle, getAngularDependencies, @@ -82,13 +87,10 @@ async function getAngularDependencies(): Promise { - if (uiCapabilities.visualize.save) { - return undefined; - } - - return { - text: i18n.translate('kbn.visualize.badge.readOnly.text', { - defaultMessage: 'Read only', - }), - tooltip: i18n.translate('kbn.visualize.badge.readOnly.tooltip', { - defaultMessage: 'Unable to save visualizations', - }), - iconType: 'glasses' - }; - } - }) - .when(VisualizeConstants.LANDING_PAGE_PATH, { - template: visualizeListingTemplate, - k7Breadcrumbs: getLandingBreadcrumbs, - controller: VisualizeListingController, - controllerAs: 'listingController', - resolve: { - createNewVis: () => false, - }, - }) - .when(VisualizeConstants.WIZARD_STEP_1_PAGE_PATH, { - template: visualizeListingTemplate, - k7Breadcrumbs: getWizardStep1Breadcrumbs, - controller: VisualizeListingController, - controllerAs: 'listingController', - resolve: { - createNewVis: () => true, - }, - }); - -FeatureCatalogueRegistryProvider.register(() => { - return { - id: 'visualize', - title: 'Visualize', - description: i18n.translate( - 'kbn.visualize.visualizeDescription', - { - defaultMessage: 'Create visualizations and aggregate data stores in your Elasticsearch indices.', - } - ), - icon: 'visualizeApp', - path: `/app/kibana#${VisualizeConstants.LANDING_PAGE_PATH}`, - showOnHomePage: true, - category: FeatureCatalogueCategory.DATA - }; -}); diff --git a/src/legacy/core_plugins/kibana/public/visualize/kibana_services.ts b/src/legacy/core_plugins/kibana/public/visualize/kibana_services.ts index d9c2453f997fe..6712d1156fa9a 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/kibana_services.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/kibana_services.ts @@ -17,19 +17,21 @@ * under the License. */ -import { ToastNotifications } from 'ui/notify/toasts/toast_notifications'; import { ChromeStart, DocLinksStart, + LegacyCoreStart, SavedObjectsClientContract, + ToastsStart, UiSettingsClientContract, } from 'kibana/public'; // load directives import '../../../data/public'; -// @ts-ignore -import { SavedObjectProvider } from 'ui/saved_objects/saved_object'; +import { SavedQueryService } from '../../../data/public/search/search_bar/lib/saved_query_service'; +import { NavigationStart } from '../../../navigation/public'; +import { Storage } from '../../../../../plugins/kibana_utils/public'; import { EmbeddablePublicPlugin } from '../../../../../plugins/embeddable/public'; import { SavedVisualizations } from './types'; @@ -37,6 +39,7 @@ export interface VisualizeKibanaServices { addBasePath: (url: string) => string; angular: any; chrome: ChromeStart; + core: LegacyCoreStart; config: any; dataStart: any; docLinks: DocLinksStart; @@ -45,12 +48,16 @@ export interface VisualizeKibanaServices { getInjected: (name: string, defaultValue?: any) => unknown; FeatureCatalogueRegistryProvider: any; indexPatterns: any; - toastNotifications: ToastNotifications; + localStorage: Storage; + navigation: NavigationStart; + toastNotifications: ToastsStart; savedObjectsClient: SavedObjectsClientContract; + savedQueryService: SavedQueryService; savedVisualizations: SavedVisualizations; uiSettings: UiSettingsClientContract; visualizeCapabilities: any; visualizations: any; + wrapInI18nContext: any; } let services: VisualizeKibanaServices | null = null; @@ -78,34 +85,9 @@ export function clearServices() { // export types export { DocTitle } from 'ui/doc_title/doc_title'; - -// export legacy static dependencies -export { ensureDefaultIndexPattern } from 'ui/legacy_compat'; - -export { getFromSavedObject } from 'ui/index_patterns'; -export { PersistedState } from 'ui/persisted_state'; -// @ts-ignore -export { getUnhashableStatesProvider } from 'ui/state_management/state_hashing'; -export { showSaveModal } from 'ui/saved_objects/show_saved_object_save_modal'; -export { showShareContextMenu } from 'ui/share'; -export { stateMonitorFactory } from 'ui/state_management/state_monitor_factory'; -export { absoluteToParsedUrl } from 'ui/url/absolute_to_parsed_url'; -export { KibanaParsedUrl } from 'ui/url/kibana_parsed_url'; -export { migrateLegacyQuery } from 'ui/utils/migrate_legacy_query'; -export { subscribeWithScope } from 'ui/utils/subscribe_with_scope'; -export { getVisualizeLoader } from 'ui/visualize/loader'; -export { SavedObjectSaveModal } from 'ui/saved_objects/components/saved_object_save_modal'; -export { - Container, - Embeddable, - EmbeddableFactory, - EmbeddableInput, - EmbeddableOutput, - ErrorEmbeddable, -} from '../../../../../plugins/embeddable/public'; - -// export types +export { EmbeddedVisualizeHandler } from 'ui/visualize/loader/embedded_visualize_handler'; export { StaticIndexPattern } from 'ui/index_patterns'; +export { PersistedState } from 'ui/persisted_state'; export { AppState } from 'ui/state_management/app_state'; export { VisType } from 'ui/vis'; export { VisualizeLoader } from 'ui/visualize/loader'; @@ -114,7 +96,27 @@ export { VisualizeLoaderParams, VisualizeUpdateParams, } from 'ui/visualize/loader/types'; +export { + Container, + Embeddable, + EmbeddableFactory, + EmbeddableInput, + EmbeddableOutput, + ErrorEmbeddable, +} from '../../../../../plugins/embeddable/public'; +// export legacy static dependencies +export { absoluteToParsedUrl } from 'ui/url/absolute_to_parsed_url'; +export { ensureDefaultIndexPattern } from 'ui/legacy_compat'; +export { getFromSavedObject } from 'ui/index_patterns'; +export { KibanaParsedUrl } from 'ui/url/kibana_parsed_url'; +export { migrateLegacyQuery } from 'ui/utils/migrate_legacy_query'; +export { SavedObjectSaveModal } from 'ui/saved_objects/components/saved_object_save_modal'; +export { showShareContextMenu } from 'ui/share'; +export { showSaveModal } from 'ui/saved_objects/show_saved_object_save_modal'; +export { stateMonitorFactory } from 'ui/state_management/state_monitor_factory'; +export { subscribeWithScope } from 'ui/utils/subscribe_with_scope'; +export { getVisualizeLoader } from 'ui/visualize/loader'; export { METRIC_TYPE, createUiStatsReporter } from '../../../ui_metric/public'; // export const diff --git a/src/legacy/core_plugins/kibana/public/visualize/plugin.ts b/src/legacy/core_plugins/kibana/public/visualize/plugin.ts index 36db8ae31b76b..53e18ffcfc17f 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/plugin.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/plugin.ts @@ -20,6 +20,10 @@ import angular from 'angular'; import { wrapInI18nContext } from 'ui/i18n'; +// @ts-ignore +import { VisEditorTypesRegistryProvider } from 'ui/registry/vis_editor_types'; +// @ts-ignore +import { defaultEditor } from 'ui/vis/editors/default/default'; import { App, CoreSetup, @@ -28,17 +32,16 @@ import { Plugin, SavedObjectsClientContract, } from 'kibana/public'; +import { VisualizationsSetup } from '../../../visualizations/public'; import { Storage } from '../../../../../plugins/kibana_utils/public'; import { DataStart } from '../../../data/public'; import { EmbeddablePublicPlugin } from '../../../../../plugins/embeddable/public'; import { NavigationStart } from '../../../navigation/public'; import { VisualizationsStart } from '../../../visualizations/public'; -import { DataPublicPluginStart as NpDataStart } from '../../../../../plugins/data/public'; import { LocalApplicationService } from '../local_application_service'; import { VisualizeEmbeddableFactory } from './embeddable/visualize_embeddable_factory'; import { VISUALIZE_EMBEDDABLE_TYPE } from './embeddable/constants'; -import { RenderDeps } from './render_app'; -import { setServices, DocTitle } from './kibana_services'; +import { setServices, VisualizeKibanaServices, DocTitle } from './kibana_services'; export interface LegacyAngularInjectedDependencies { editorTypes: any; @@ -53,15 +56,15 @@ export interface LegacyAngularInjectedDependencies { } export interface VisualizePluginStartDependencies { - data: DataStart; - embeddable: ReturnType; + dataStart: DataStart; embeddables: ReturnType; navigation: NavigationStart; - npData: NpDataStart; visualizations: VisualizationsStart; } export interface VisualizePluginSetupDependencies { + embeddableSetup: ReturnType; + visualizationsSetup: VisualizationsSetup; __LEGACY: { getAngularDependencies: () => Promise; localApplicationService: LocalApplicationService; @@ -75,14 +78,15 @@ export class VisualizePlugin implements Plugin { dataStart: DataStart; embeddables: ReturnType; navigation: NavigationStart; - npDataStart: NpDataStart; savedObjectsClient: SavedObjectsClientContract; visualizations: VisualizationsStart; } | null = null; - public setup( + public async setup( core: CoreSetup, { + embeddableSetup, + visualizationsSetup, __LEGACY: { localApplicationService, getAngularDependencies, ...legacyServices }, }: VisualizePluginSetupDependencies ) { @@ -99,17 +103,16 @@ export class VisualizePlugin implements Plugin { savedObjectsClient, embeddables, navigation, - npDataStart, visualizations, } = this.startDependencies; const angularDependencies = await getAngularDependencies(); - const deps: RenderDeps = { - core: contextCore as LegacyCoreStart, + const deps: VisualizeKibanaServices = { ...legacyServices, ...angularDependencies, addBasePath: contextCore.http.basePath.prepend, angular, + core: contextCore as LegacyCoreStart, chrome: contextCore.chrome, dataStart, docLinks: contextCore.docLinks, @@ -117,14 +120,11 @@ export class VisualizePlugin implements Plugin { getBasePath: core.http.basePath.get, getInjected: core.injectedMetadata.getInjectedVar, indexPatterns: dataStart.indexPatterns.indexPatterns, - indexPatternService: dataStart.indexPatterns.indexPatterns, localStorage: new Storage(localStorage), navigation, - npDataStart, savedObjectsClient, savedQueryService: dataStart.search.services.savedQueryService, toastNotifications: contextCore.notifications.toasts, - uiCapabilities: contextCore.application.capabilities, uiSettings: contextCore.uiSettings, visualizeCapabilities: contextCore.application.capabilities.visualize, visualizations, @@ -136,36 +136,29 @@ export class VisualizePlugin implements Plugin { }, }; localApplicationService.register({ ...app, id: 'visualize' }); + VisEditorTypesRegistryProvider.register(defaultEditor); + + const createVisualizeEmbeddableFactory: () => Promise< + VisualizeEmbeddableFactory + > = async () => { + return new VisualizeEmbeddableFactory(visualizationsSetup.types); + }; + + await createVisualizeEmbeddableFactory().then((embeddableFactory: any) => { + embeddableSetup.registerEmbeddableFactory(VISUALIZE_EMBEDDABLE_TYPE, embeddableFactory); + }); } - async start( + start( { savedObjects: { client: savedObjectsClient } }: CoreStart, - { - data: dataStart, - embeddables, - navigation, - npData, - visualizations, - embeddable, - }: VisualizePluginStartDependencies + { dataStart, embeddables, navigation, visualizations }: VisualizePluginStartDependencies ) { this.startDependencies = { dataStart, embeddables, navigation, - npDataStart: npData, savedObjectsClient, visualizations, }; - - const createVisualizeEmbeddableFactory: () => Promise< - VisualizeEmbeddableFactory - > = async () => { - return new VisualizeEmbeddableFactory(visualizations.types); - }; - - await createVisualizeEmbeddableFactory().then((embeddableFactory: any) => { - embeddable.registerEmbeddableFactory(VISUALIZE_EMBEDDABLE_TYPE, embeddableFactory); - }); } } diff --git a/src/legacy/core_plugins/kibana/public/visualize/render_app.ts b/src/legacy/core_plugins/kibana/public/visualize/render_app.ts index 1e58b864ffda7..2432ffc553562 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/render_app.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/render_app.ts @@ -44,73 +44,30 @@ import { KbnUrlProvider, RedirectWhenMissingProvider } from 'ui/url'; // @ts-ignore import { confirmModalFactory } from 'ui/modals/confirm_modal'; -import { - AppMountContext, - ChromeStart, - DocLinksStart, - LegacyCoreStart, - SavedObjectsClientContract, - ToastsStart, - UiSettingsClientContract, -} from 'kibana/public'; -import { Storage } from '../../../../../plugins/kibana_utils/public'; +import { AppMountContext, LegacyCoreStart } from 'kibana/public'; import { createApplyFiltersPopoverDirective, createApplyFiltersPopoverHelper, createFilterBarDirective, createFilterBarHelper, - DataStart, } from '../../../data/public'; -import { SavedQueryService } from '../../../data/public/search/search_bar/lib/saved_query_service'; -import { EmbeddablePublicPlugin } from '../../../../../plugins/embeddable/public'; import { NavigationStart } from '../../../navigation/public'; -import { VisualizationsStart } from '../../../visualizations/public'; -import { DataPublicPluginStart as NpDataStart } from '../../../../../plugins/data/public'; // @ts-ignore import { initVisualizeApp } from './app'; -import { DocTitle } from './kibana_services'; - -export interface RenderDeps { - addBasePath: (path: string) => string; - angular: any; - chrome: ChromeStart; - config: any; - core: LegacyCoreStart; - dataStart: DataStart; - docLinks: DocLinksStart; - docTitle: DocTitle; - embeddables: ReturnType; - FeatureCatalogueRegistryProvider: any; - getBasePath: () => string; - getInjected: (name: string, defaultValue?: any) => unknown; - getUnhashableStates: any; - indexPatterns: DataStart['indexPatterns']['indexPatterns']; - indexPatternService: any; - localStorage: Storage; - npDataStart: NpDataStart; - navigation: NavigationStart; - queryFilter: any; - savedObjectRegistry: any; - savedDashboards: any; - savedObjectsClient: SavedObjectsClientContract; - savedQueryService: SavedQueryService; - shareContextMenuExtensions: any; - toastNotifications: ToastsStart; - uiCapabilities: any; - uiSettings: UiSettingsClientContract; - visualizeCapabilities: any; - visualizations: VisualizationsStart; - wrapInI18nContext: any; -} +import { VisualizeKibanaServices } from './kibana_services'; let angularModuleInstance: IModule | null = null; -export const renderApp = async (element: HTMLElement, appBasePath: string, deps: RenderDeps) => { +export const renderApp = async ( + element: HTMLElement, + appBasePath: string, + deps: VisualizeKibanaServices +) => { if (!angularModuleInstance) { angularModuleInstance = createLocalAngularModule(deps.core, deps.navigation, deps.angular); // global routing stuff - configureAppAngularModule(angularModuleInstance, deps.core as LegacyCoreStart); + configureAppAngularModule(angularModuleInstance, deps.core as LegacyCoreStart, true); // custom routing stuff initVisualizeApp(angularModuleInstance, deps); } diff --git a/src/legacy/core_plugins/kibana/public/visualize/visualize_app.ts b/src/legacy/core_plugins/kibana/public/visualize/visualize_app.ts index 34e7ca577790c..85510c1e684fd 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/visualize_app.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/visualize_app.ts @@ -17,14 +17,14 @@ * under the License. */ -import { RenderDeps } from './render_app'; +import { VisualizeKibanaServices } from './kibana_services'; // @ts-ignore import { initEditorDirective } from './editor/editor'; // @ts-ignore import { initListingDirective } from './listing/visualize_listing'; -export function initVisualizeAppDirective(app: any, deps: RenderDeps) { +export function initVisualizeAppDirective(app: any, deps: VisualizeKibanaServices) { initEditorDirective(app, deps); initListingDirective(app, deps); } diff --git a/src/legacy/core_plugins/kibana/public/visualize/wizard/new_vis_modal.tsx b/src/legacy/core_plugins/kibana/public/visualize/wizard/new_vis_modal.tsx index 7c0d73a771471..26012ce6421df 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/wizard/new_vis_modal.tsx +++ b/src/legacy/core_plugins/kibana/public/visualize/wizard/new_vis_modal.tsx @@ -49,7 +49,7 @@ class NewVisModal extends React.Component; + private readonly trackUiMetric: ReturnType; constructor(props: TypeSelectionProps) { super(props); diff --git a/src/legacy/ui/public/chrome/api/angular.js b/src/legacy/ui/public/chrome/api/angular.js index 512229cca6126..73d50a83e11a5 100644 --- a/src/legacy/ui/public/chrome/api/angular.js +++ b/src/legacy/ui/public/chrome/api/angular.js @@ -29,7 +29,7 @@ export function initAngularApi(chrome, internals) { chrome.setupAngular = function () { const kibana = uiModules.get('kibana'); - configureAppAngularModule(kibana, npStart.core, data); + configureAppAngularModule(kibana, npStart.core, data, false); kibana.value('chrome', chrome); diff --git a/src/legacy/ui/public/legacy_compat/angular_config.tsx b/src/legacy/ui/public/legacy_compat/angular_config.tsx index f941ea31a7ed0..90e22a045a045 100644 --- a/src/legacy/ui/public/legacy_compat/angular_config.tsx +++ b/src/legacy/ui/public/legacy_compat/angular_config.tsx @@ -47,13 +47,32 @@ import { isSystemApiRequest } from '../system_api'; const URL_LIMIT_WARN_WITHIN = 1000; -function isDummyWrapperRoute($route: any) { +/** + * Detects whether a given angular route is a dummy route that doesn't + * require any action. There are two ways this can happen: + * If `outerAngularWrapperRoute` is set on the route config object, + * it means the local application service set up this route on the outer angular + * and the internal routes will handle the hooks. + * + * If angular did not detect a route and it is the local angular, we are currently + * navigating away from a URL controlled by a local angular router and the + * application will get unmounted. In this case the outer router will handle + * the hooks. + * @param $route Injected $route dependency + * @param isLocalAngular Flag whether this is the local angular router + */ +function isDummyRoute($route: any, isLocalAngular: boolean) { return ( - $route.current && $route.current.$$route && $route.current.$$route.outerAngularWrapperRoute + ($route.current && $route.current.$$route && $route.current.$$route.outerAngularWrapperRoute) || + (!$route.current && isLocalAngular) ); } -export const configureAppAngularModule = (angularModule: IModule, newPlatform: LegacyCoreStart) => { +export const configureAppAngularModule = ( + angularModule: IModule, + newPlatform: LegacyCoreStart, + isLocalAngular: boolean +) => { const legacyMetadata = newPlatform.injectedMetadata.getLegacyMetadata(); forOwn(newPlatform.injectedMetadata.getInjectedVars(), (val, name) => { @@ -74,10 +93,10 @@ export const configureAppAngularModule = (angularModule: IModule, newPlatform: L .config(setupLocationProvider(newPlatform)) .config($setupXsrfRequestInterceptor(newPlatform)) .run(capture$httpLoadingCount(newPlatform)) - .run($setupBreadcrumbsAutoClear(newPlatform)) - .run($setupBadgeAutoClear(newPlatform)) - .run($setupHelpExtensionAutoClear(newPlatform)) - .run($setupUrlOverflowHandling(newPlatform)) + .run($setupBreadcrumbsAutoClear(newPlatform, isLocalAngular)) + .run($setupBadgeAutoClear(newPlatform, isLocalAngular)) + .run($setupHelpExtensionAutoClear(newPlatform, isLocalAngular)) + .run($setupUrlOverflowHandling(newPlatform, isLocalAngular)) .run($setupUICapabilityRedirect(newPlatform)); }; @@ -200,7 +219,7 @@ const $setupUICapabilityRedirect = (newPlatform: CoreStart) => ( * lets us integrate with the angular router so that we can automatically clear * the breadcrumbs if we switch to a Kibana app that does not use breadcrumbs correctly */ -const $setupBreadcrumbsAutoClear = (newPlatform: CoreStart) => ( +const $setupBreadcrumbsAutoClear = (newPlatform: CoreStart, isLocalAngular: boolean) => ( $rootScope: IRootScopeService, $injector: any ) => { @@ -222,7 +241,7 @@ const $setupBreadcrumbsAutoClear = (newPlatform: CoreStart) => ( }); $rootScope.$on('$routeChangeSuccess', () => { - if (isDummyWrapperRoute($route)) { + if (isDummyRoute($route, isLocalAngular)) { return; } const current = $route.current || {}; @@ -250,7 +269,7 @@ const $setupBreadcrumbsAutoClear = (newPlatform: CoreStart) => ( * lets us integrate with the angular router so that we can automatically clear * the badge if we switch to a Kibana app that does not use the badge correctly */ -const $setupBadgeAutoClear = (newPlatform: CoreStart) => ( +const $setupBadgeAutoClear = (newPlatform: CoreStart, isLocalAngular: boolean) => ( $rootScope: IRootScopeService, $injector: any ) => { @@ -264,7 +283,7 @@ const $setupBadgeAutoClear = (newPlatform: CoreStart) => ( }); $rootScope.$on('$routeChangeSuccess', () => { - if (isDummyWrapperRoute($route)) { + if (isDummyRoute($route, isLocalAngular)) { return; } const current = $route.current || {}; @@ -293,7 +312,7 @@ const $setupBadgeAutoClear = (newPlatform: CoreStart) => ( * the helpExtension if we switch to a Kibana app that does not set its own * helpExtension */ -const $setupHelpExtensionAutoClear = (newPlatform: CoreStart) => ( +const $setupHelpExtensionAutoClear = (newPlatform: CoreStart, isLocalAngular: boolean) => ( $rootScope: IRootScopeService, $injector: any ) => { @@ -311,14 +330,14 @@ const $setupHelpExtensionAutoClear = (newPlatform: CoreStart) => ( const $route = $injector.has('$route') ? $injector.get('$route') : {}; $rootScope.$on('$routeChangeStart', () => { - if (isDummyWrapperRoute($route)) { + if (isDummyRoute($route, isLocalAngular)) { return; } helpExtensionSetSinceRouteChange = false; }); $rootScope.$on('$routeChangeSuccess', () => { - if (isDummyWrapperRoute($route)) { + if (isDummyRoute($route, isLocalAngular)) { return; } const current = $route.current || {}; @@ -331,7 +350,7 @@ const $setupHelpExtensionAutoClear = (newPlatform: CoreStart) => ( }); }; -const $setupUrlOverflowHandling = (newPlatform: CoreStart) => ( +const $setupUrlOverflowHandling = (newPlatform: CoreStart, isLocalAngular: boolean) => ( $location: ILocationService, $rootScope: IRootScopeService, $injector: auto.IInjectorService @@ -339,7 +358,7 @@ const $setupUrlOverflowHandling = (newPlatform: CoreStart) => ( const $route = $injector.has('$route') ? $injector.get('$route') : {}; const urlOverflow = new UrlOverflowService(); const check = () => { - if (isDummyWrapperRoute($route)) { + if (isDummyRoute($route, isLocalAngular)) { return; } // disable long url checks when storing state in session storage diff --git a/src/legacy/ui/public/vis/editors/default/default.js b/src/legacy/ui/public/vis/editors/default/default.js index 43d2962df0a1e..d322e4673888c 100644 --- a/src/legacy/ui/public/vis/editors/default/default.js +++ b/src/legacy/ui/public/vis/editors/default/default.js @@ -33,7 +33,6 @@ import { keyCodes } from '@elastic/eui'; import { parentPipelineAggHelper } from 'ui/agg_types/metrics/lib/parent_pipeline_agg_helper'; import { DefaultEditorSize } from '../../editor_size'; -import { VisEditorTypesRegistryProvider } from '../../../registry/vis_editor_types'; import { AggGroupNames } from './agg_groups'; import { start as embeddables } from '../../../../../core_plugins/embeddable_api/public/np_ready/public/legacy'; @@ -195,6 +194,4 @@ const defaultEditor = function ($rootScope, $compile, getAppState) { }; }; -VisEditorTypesRegistryProvider.register(defaultEditor); - export { defaultEditor }; diff --git a/src/legacy/ui/public/vis/vis_filters/vis_filters.js b/src/legacy/ui/public/vis/vis_filters/vis_filters.js index cf904603e3beb..9e70fb45cd311 100644 --- a/src/legacy/ui/public/vis/vis_filters/vis_filters.js +++ b/src/legacy/ui/public/vis/vis_filters/vis_filters.js @@ -106,15 +106,8 @@ const createFiltersFromEvent = (event) => { return filters; }; -// TODO make sure the visualize app is updating the breadcrumb correctly const VisFiltersProvider = () => { - // TODO this function used to simply put the new filters in - // the app state. Dashboard/Visualize simply listened to - // the app state change via angular and pushed it into the actual - // filter manager (while splitting out the time filter) - // This channel does not work anymore because it's not the same - // angular context and thus the appstate won't update const pushFilters = async (filters, simulate) => { if (filters.length && !simulate) { const dedupedFilters = uniqFilters(filters); diff --git a/src/legacy/ui/ui_exports/ui_export_defaults.js b/src/legacy/ui/ui_exports/ui_export_defaults.js index 5c1669b716eca..10284c9f8bb59 100644 --- a/src/legacy/ui/ui_exports/ui_export_defaults.js +++ b/src/legacy/ui/ui_exports/ui_export_defaults.js @@ -54,7 +54,6 @@ export const UI_EXPORT_DEFAULTS = { 'ui/vis/editors/default/default', ], embeddableFactories: [ - 'plugins/kibana/visualize/embeddable/visualize_embeddable_factory', 'plugins/kibana/discover/embeddable/search_embeddable_factory', ], search: [ From 553c1a8a4263e662dff7606e607cbd7b79b66590 Mon Sep 17 00:00:00 2001 From: maryia-lapata Date: Mon, 4 Nov 2019 12:26:22 +0300 Subject: [PATCH 056/132] Refactor register factory --- .../core_plugins/kibana/public/visualize/plugin.ts | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/src/legacy/core_plugins/kibana/public/visualize/plugin.ts b/src/legacy/core_plugins/kibana/public/visualize/plugin.ts index 53e18ffcfc17f..c3cd1aa9fa444 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/plugin.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/plugin.ts @@ -138,15 +138,8 @@ export class VisualizePlugin implements Plugin { localApplicationService.register({ ...app, id: 'visualize' }); VisEditorTypesRegistryProvider.register(defaultEditor); - const createVisualizeEmbeddableFactory: () => Promise< - VisualizeEmbeddableFactory - > = async () => { - return new VisualizeEmbeddableFactory(visualizationsSetup.types); - }; - - await createVisualizeEmbeddableFactory().then((embeddableFactory: any) => { - embeddableSetup.registerEmbeddableFactory(VISUALIZE_EMBEDDABLE_TYPE, embeddableFactory); - }); + const embeddableFactory = new VisualizeEmbeddableFactory(visualizationsSetup.types); + embeddableSetup.registerEmbeddableFactory(VISUALIZE_EMBEDDABLE_TYPE, embeddableFactory); } start( From 97cf00898189387e32c119cd416f393f1bd26c7f Mon Sep 17 00:00:00 2001 From: maryia-lapata Date: Mon, 4 Nov 2019 12:55:37 +0300 Subject: [PATCH 057/132] Move registering of CatalogueRegistryProvider to plugin.ts --- .../core_plugins/kibana/public/kibana.js | 1 - .../kibana/public/visualize/app.js | 19 +--------- .../kibana/public/visualize/index.ts | 6 +--- .../public/visualize/kibana_services.ts | 4 --- .../kibana/public/visualize/plugin.ts | 35 +++++++++++++++---- .../ui/ui_exports/ui_export_defaults.js | 3 -- .../plugins/kbn_tp_run_pipeline/public/app.js | 1 - .../kbn_tp_visualize_embedding/public/app.js | 1 - x-pack/legacy/plugins/canvas/public/app.js | 1 - .../dashboard_mode/public/dashboard_viewer.js | 1 - 10 files changed, 30 insertions(+), 42 deletions(-) diff --git a/src/legacy/core_plugins/kibana/public/kibana.js b/src/legacy/core_plugins/kibana/public/kibana.js index fe741a357cbfe..cfa0a1923c37b 100644 --- a/src/legacy/core_plugins/kibana/public/kibana.js +++ b/src/legacy/core_plugins/kibana/public/kibana.js @@ -28,7 +28,6 @@ import { uiModules } from 'ui/modules'; import 'uiExports/home'; import 'uiExports/visTypes'; -import 'uiExports/visEditorTypes'; import 'uiExports/visualize'; import 'uiExports/savedObjectTypes'; import 'uiExports/fieldFormats'; diff --git a/src/legacy/core_plugins/kibana/public/visualize/app.js b/src/legacy/core_plugins/kibana/public/visualize/app.js index d489a53d97549..1c131a67d9de2 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/app.js +++ b/src/legacy/core_plugins/kibana/public/visualize/app.js @@ -34,7 +34,7 @@ import { getEditBreadcrumbs } from './breadcrumbs'; -import { ensureDefaultIndexPattern, FeatureCatalogueCategory } from './kibana_services'; +import { ensureDefaultIndexPattern } from './kibana_services'; export function initVisualizeApp(app, deps) { initVisualizeAppDirective(app, deps); @@ -152,22 +152,5 @@ export function initVisualizeApp(app, deps) { .when(`visualize/:tail*?`, { redirectTo: `/${deps.core.injectedMetadata.getInjectedVar('kbnDefaultAppId')}`, }); - - - }); - - deps.FeatureCatalogueRegistryProvider.register(() => { - return { - id: 'visualize', - title: 'Visualize', - description: i18n.translate('kbn.visualize.visualizeDescription', { - defaultMessage: - 'Create visualizations and aggregate data stores in your Elasticsearch indices.', - }), - icon: 'visualizeApp', - path: `/app/kibana#${VisualizeConstants.LANDING_PAGE_PATH}`, - showOnHomePage: true, - category: FeatureCatalogueCategory.DATA, - }; }); } diff --git a/src/legacy/core_plugins/kibana/public/visualize/index.ts b/src/legacy/core_plugins/kibana/public/visualize/index.ts index 3b1909fe739b4..07b07c2db8758 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/index.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/index.ts @@ -41,10 +41,7 @@ import { setup as embeddableSetup, } from '../../../embeddable_api/public/np_ready/public/legacy'; import { start as navigation } from '../../../navigation/public/legacy'; -import { - start as visualizations, - setup as visualizationsSetup, -} from '../../../visualizations/public/np_ready/public/legacy'; +import { start as visualizations } from '../../../visualizations/public/np_ready/public/legacy'; /** * Get dependencies relying on the global angular context. @@ -79,7 +76,6 @@ async function getAngularDependencies(): Promise; getBasePath: () => string; getInjected: (name: string, defaultValue?: any) => unknown; - FeatureCatalogueRegistryProvider: any; indexPatterns: any; localStorage: Storage; navigation: NavigationStart; @@ -118,6 +117,3 @@ export { stateMonitorFactory } from 'ui/state_management/state_monitor_factory'; export { subscribeWithScope } from 'ui/utils/subscribe_with_scope'; export { getVisualizeLoader } from 'ui/visualize/loader'; export { METRIC_TYPE, createUiStatsReporter } from '../../../ui_metric/public'; - -// export const -export { FeatureCatalogueCategory } from 'ui/registry/feature_catalogue'; diff --git a/src/legacy/core_plugins/kibana/public/visualize/plugin.ts b/src/legacy/core_plugins/kibana/public/visualize/plugin.ts index c3cd1aa9fa444..bd60e1b2d9ad2 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/plugin.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/plugin.ts @@ -18,10 +18,12 @@ */ import angular from 'angular'; +import { i18n } from '@kbn/i18n'; import { wrapInI18nContext } from 'ui/i18n'; // @ts-ignore import { VisEditorTypesRegistryProvider } from 'ui/registry/vis_editor_types'; +import { FeatureCatalogueCategory } from 'ui/registry/feature_catalogue'; // @ts-ignore import { defaultEditor } from 'ui/vis/editors/default/default'; import { @@ -32,7 +34,6 @@ import { Plugin, SavedObjectsClientContract, } from 'kibana/public'; -import { VisualizationsSetup } from '../../../visualizations/public'; import { Storage } from '../../../../../plugins/kibana_utils/public'; import { DataStart } from '../../../data/public'; import { EmbeddablePublicPlugin } from '../../../../../plugins/embeddable/public'; @@ -41,6 +42,7 @@ import { VisualizationsStart } from '../../../visualizations/public'; import { LocalApplicationService } from '../local_application_service'; import { VisualizeEmbeddableFactory } from './embeddable/visualize_embeddable_factory'; import { VISUALIZE_EMBEDDABLE_TYPE } from './embeddable/constants'; +import { VisualizeConstants } from './visualize_constants'; import { setServices, VisualizeKibanaServices, DocTitle } from './kibana_services'; export interface LegacyAngularInjectedDependencies { @@ -64,7 +66,6 @@ export interface VisualizePluginStartDependencies { export interface VisualizePluginSetupDependencies { embeddableSetup: ReturnType; - visualizationsSetup: VisualizationsSetup; __LEGACY: { getAngularDependencies: () => Promise; localApplicationService: LocalApplicationService; @@ -86,8 +87,12 @@ export class VisualizePlugin implements Plugin { core: CoreSetup, { embeddableSetup, - visualizationsSetup, - __LEGACY: { localApplicationService, getAngularDependencies, ...legacyServices }, + __LEGACY: { + localApplicationService, + getAngularDependencies, + FeatureCatalogueRegistryProvider, + ...legacyServices + }, }: VisualizePluginSetupDependencies ) { const app: App = { @@ -131,15 +136,31 @@ export class VisualizePlugin implements Plugin { wrapInI18nContext, }; setServices(deps); + + const embeddableFactory = new VisualizeEmbeddableFactory(visualizations.types); + embeddableSetup.registerEmbeddableFactory(VISUALIZE_EMBEDDABLE_TYPE, embeddableFactory); + const { renderApp } = await import('./render_app'); return renderApp(params.element, params.appBasePath, deps); }, }; + FeatureCatalogueRegistryProvider.register(() => { + return { + id: 'visualize', + title: 'Visualize', + description: i18n.translate('kbn.visualize.visualizeDescription', { + defaultMessage: + 'Create visualizations and aggregate data stores in your Elasticsearch indices.', + }), + icon: 'visualizeApp', + path: `/app/kibana#${VisualizeConstants.LANDING_PAGE_PATH}`, + showOnHomePage: true, + category: FeatureCatalogueCategory.DATA, + }; + }); + localApplicationService.register({ ...app, id: 'visualize' }); VisEditorTypesRegistryProvider.register(defaultEditor); - - const embeddableFactory = new VisualizeEmbeddableFactory(visualizationsSetup.types); - embeddableSetup.registerEmbeddableFactory(VISUALIZE_EMBEDDABLE_TYPE, embeddableFactory); } start( diff --git a/src/legacy/ui/ui_exports/ui_export_defaults.js b/src/legacy/ui/ui_exports/ui_export_defaults.js index 10284c9f8bb59..b5a23edcddda1 100644 --- a/src/legacy/ui/ui_exports/ui_export_defaults.js +++ b/src/legacy/ui/ui_exports/ui_export_defaults.js @@ -50,9 +50,6 @@ export const UI_EXPORT_DEFAULTS = { fieldFormatEditors: [ 'ui/field_editor/components/field_format_editor/register' ], - visEditorTypes: [ - 'ui/vis/editors/default/default', - ], embeddableFactories: [ 'plugins/kibana/discover/embeddable/search_embeddable_factory', ], diff --git a/test/interpreter_functional/plugins/kbn_tp_run_pipeline/public/app.js b/test/interpreter_functional/plugins/kbn_tp_run_pipeline/public/app.js index bd58184cd1185..bcfa3972d3ef5 100644 --- a/test/interpreter_functional/plugins/kbn_tp_run_pipeline/public/app.js +++ b/test/interpreter_functional/plugins/kbn_tp_run_pipeline/public/app.js @@ -37,7 +37,6 @@ import 'ui/autoload/all'; import 'uiExports/visTypes'; import 'uiExports/visResponseHandlers'; import 'uiExports/visRequestHandlers'; -import 'uiExports/visEditorTypes'; import 'uiExports/visualize'; import 'uiExports/savedObjectTypes'; import 'uiExports/fieldFormats'; diff --git a/test/plugin_functional/plugins/kbn_tp_visualize_embedding/public/app.js b/test/plugin_functional/plugins/kbn_tp_visualize_embedding/public/app.js index 4463feac27513..541546f031e81 100644 --- a/test/plugin_functional/plugins/kbn_tp_visualize_embedding/public/app.js +++ b/test/plugin_functional/plugins/kbn_tp_visualize_embedding/public/app.js @@ -31,7 +31,6 @@ import 'ui/autoload/all'; import 'uiExports/visTypes'; import 'uiExports/visResponseHandlers'; import 'uiExports/visRequestHandlers'; -import 'uiExports/visEditorTypes'; import 'uiExports/visualize'; import 'uiExports/savedObjectTypes'; import 'uiExports/fieldFormats'; diff --git a/x-pack/legacy/plugins/canvas/public/app.js b/x-pack/legacy/plugins/canvas/public/app.js index 0a467d491e2c6..40f1b2cfa2d1d 100644 --- a/x-pack/legacy/plugins/canvas/public/app.js +++ b/x-pack/legacy/plugins/canvas/public/app.js @@ -16,7 +16,6 @@ import { CanvasRootController } from './angular/controllers'; import 'uiExports/visTypes'; import 'uiExports/visResponseHandlers'; import 'uiExports/visRequestHandlers'; -import 'uiExports/visEditorTypes'; import 'uiExports/savedObjectTypes'; import 'uiExports/spyModes'; import 'uiExports/fieldFormats'; diff --git a/x-pack/legacy/plugins/dashboard_mode/public/dashboard_viewer.js b/x-pack/legacy/plugins/dashboard_mode/public/dashboard_viewer.js index d39d3fdaa84b8..87ac330e29106 100644 --- a/x-pack/legacy/plugins/dashboard_mode/public/dashboard_viewer.js +++ b/x-pack/legacy/plugins/dashboard_mode/public/dashboard_viewer.js @@ -18,7 +18,6 @@ import 'uiExports/contextMenuActions'; import 'uiExports/visTypes'; import 'uiExports/visResponseHandlers'; import 'uiExports/visRequestHandlers'; -import 'uiExports/visEditorTypes'; import 'uiExports/inspectorViews'; import 'uiExports/savedObjectTypes'; import 'uiExports/embeddableActions'; From d94cdf555e42aec51824094a8e563b677da47dca Mon Sep 17 00:00:00 2001 From: maryia-lapata Date: Mon, 4 Nov 2019 15:28:35 +0300 Subject: [PATCH 058/132] Adjust TS --- .../visualize/embeddable/visualize_embeddable_factory.tsx | 2 +- src/legacy/core_plugins/kibana/public/visualize/index.ts | 3 ++- .../core_plugins/kibana/public/visualize/kibana_services.ts | 3 ++- src/legacy/core_plugins/kibana/public/visualize/plugin.ts | 1 + 4 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable_factory.tsx b/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable_factory.tsx index 5b709d3760e27..c4bd7a29f21c8 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable_factory.tsx +++ b/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable_factory.tsx @@ -118,7 +118,7 @@ export class VisualizeEmbeddableFactory extends EmbeddableFactory< const editUrl = visId ? addBasePath(`/app/kibana${savedVisualizations.urlFor(visId)}`) : ''; const loader = await getVisualizeLoader(); - const isLabsEnabled = config.get('visualize:enableLabs'); + const isLabsEnabled = config.get('visualize:enableLabs'); if (!isLabsEnabled && savedObject.vis.type.stage === 'experimental') { return new DisabledLabEmbeddable(savedObject.title, input); diff --git a/src/legacy/core_plugins/kibana/public/visualize/index.ts b/src/legacy/core_plugins/kibana/public/visualize/index.ts index 07b07c2db8758..05b82b4ee07e0 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/index.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/index.ts @@ -17,7 +17,7 @@ * under the License. */ -import 'angular-sanitize'; // used in visualization_editor.js and visualization.js +import 'angular-sanitize'; import 'ui/collapsible_sidebar'; // used in default editor import 'ui/vis/editors/default/sidebar'; @@ -60,6 +60,7 @@ async function getAngularDependencies(): Promise string; angular: any; chrome: ChromeStart; + chromeLegacy: any; core: LegacyCoreStart; - config: any; + config: UiSettingsClientContract; dataStart: any; docLinks: DocLinksStart; embeddables: ReturnType; diff --git a/src/legacy/core_plugins/kibana/public/visualize/plugin.ts b/src/legacy/core_plugins/kibana/public/visualize/plugin.ts index bd60e1b2d9ad2..dbb26277939cc 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/plugin.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/plugin.ts @@ -46,6 +46,7 @@ import { VisualizeConstants } from './visualize_constants'; import { setServices, VisualizeKibanaServices, DocTitle } from './kibana_services'; export interface LegacyAngularInjectedDependencies { + chromeLegacy: any; editorTypes: any; queryFilter: any; getUnhashableStates: any; From af1630663181e98d8a6d95e9b9d21f5fd6c86f16 Mon Sep 17 00:00:00 2001 From: maryia-lapata Date: Mon, 4 Nov 2019 15:45:06 +0300 Subject: [PATCH 059/132] Implement getUnhashableStates --- .../core_plugins/kibana/public/visualize/editor/editor.js | 4 +++- src/legacy/core_plugins/kibana/public/visualize/index.ts | 3 --- src/legacy/core_plugins/kibana/public/visualize/plugin.ts | 1 - 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/legacy/core_plugins/kibana/public/visualize/editor/editor.js b/src/legacy/core_plugins/kibana/public/visualize/editor/editor.js index 80cbb6d823ff7..c5465c594e8b1 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/editor/editor.js +++ b/src/legacy/core_plugins/kibana/public/visualize/editor/editor.js @@ -73,6 +73,8 @@ function VisualizeAppController( Promise, config, kbnBaseUrl, + getAppState, + globalState, ) { const { angular, @@ -80,7 +82,6 @@ function VisualizeAppController( localStorage, queryFilter, visualizeCapabilities, - getUnhashableStates, shareContextMenuExtensions, dataStart: { timefilter: { timefilter }, @@ -172,6 +173,7 @@ function VisualizeAppController( run: (anchorElement) => { const hasUnappliedChanges = vis.dirty; const hasUnsavedChanges = $appStatus.dirty; + const getUnhashableStates = () => [getAppState(), globalState].filter(Boolean); showShareContextMenu({ anchorElement, allowEmbed: true, diff --git a/src/legacy/core_plugins/kibana/public/visualize/index.ts b/src/legacy/core_plugins/kibana/public/visualize/index.ts index 05b82b4ee07e0..eabf00ba69b9a 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/index.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/index.ts @@ -31,7 +31,6 @@ import { FeatureCatalogueRegistryProvider } from 'ui/registry/feature_catalogue' import { VisEditorTypesRegistryProvider } from 'ui/registry/vis_editor_types'; import { SavedObjectRegistryProvider, SavedObjectsClientProvider } from 'ui/saved_objects'; import { ShareContextMenuExtensionsRegistryProvider } from 'ui/share'; -import { getUnhashableStatesProvider } from 'ui/state_management/state_hashing/get_unhashable_states_provider'; import { VisualizePlugin, LegacyAngularInjectedDependencies } from './plugin'; import { localApplicationService } from '../local_application_service'; @@ -53,7 +52,6 @@ async function getAngularDependencies(): Promise('Private'); const queryFilter = Private(FilterBarQueryFilterProvider); - const getUnhashableStates = Private(getUnhashableStatesProvider); const shareContextMenuExtensions = Private(ShareContextMenuExtensionsRegistryProvider); const savedObjectRegistry = Private(SavedObjectRegistryProvider); const savedObjectClient = Private(SavedObjectsClientProvider); @@ -63,7 +61,6 @@ async function getAngularDependencies(): Promise Date: Mon, 4 Nov 2019 16:52:27 +0300 Subject: [PATCH 060/132] Fix TSVB --- .../vis_type_timeseries/public/components/vis_editor.js | 4 +--- src/legacy/ui/public/vis/editors/default/default.js | 6 +++--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/vis_editor.js b/src/legacy/core_plugins/vis_type_timeseries/public/components/vis_editor.js index 842d3aa6c4ad7..0b665b57cf341 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/components/vis_editor.js +++ b/src/legacy/core_plugins/vis_type_timeseries/public/components/vis_editor.js @@ -44,8 +44,6 @@ const APP_NAME = 'VisEditor'; export class VisEditor extends Component { constructor(props) { super(props); - const { vis } = props; - this.appState = vis.API.getAppState(); this.localStorage = new Storage(window.localStorage); this.state = { model: props.visParams, @@ -183,7 +181,7 @@ export class VisEditor extends Component { dirty={this.state.dirty} autoApply={this.state.autoApply} model={model} - appState={this.appState} + appState={this.props.appState} savedObj={this.props.savedObj} timeRange={this.props.timeRange} uiState={this.uiState} diff --git a/src/legacy/ui/public/vis/editors/default/default.js b/src/legacy/ui/public/vis/editors/default/default.js index d322e4673888c..9df866d29a8a2 100644 --- a/src/legacy/ui/public/vis/editors/default/default.js +++ b/src/legacy/ui/public/vis/editors/default/default.js @@ -37,7 +37,7 @@ import { AggGroupNames } from './agg_groups'; import { start as embeddables } from '../../../../../core_plugins/embeddable_api/public/np_ready/public/legacy'; -const defaultEditor = function ($rootScope, $compile, getAppState) { +const defaultEditor = function ($rootScope, $compile) { return class DefaultEditor { static key = 'default'; @@ -57,7 +57,7 @@ const defaultEditor = function ($rootScope, $compile, getAppState) { } } - render({ uiState, timeRange, filters, query }) { + render({ uiState, timeRange, filters, query, appState }) { let $scope; const updateScope = () => { @@ -160,7 +160,7 @@ const defaultEditor = function ($rootScope, $compile, getAppState) { this._handler = await embeddables.getEmbeddableFactory('visualization').createFromObject(this.savedObj, { uiState: uiState, - appState: getAppState(), + appState, timeRange: timeRange, filters: filters || [], query: query, From 99188d169a61fe34f6edafa27a0fc2234b78944e Mon Sep 17 00:00:00 2001 From: maryia-lapata Date: Mon, 4 Nov 2019 17:46:49 +0300 Subject: [PATCH 061/132] Allow set services multiple times --- .../kibana/public/visualize/kibana_services.ts | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/legacy/core_plugins/kibana/public/visualize/kibana_services.ts b/src/legacy/core_plugins/kibana/public/visualize/kibana_services.ts index 5006c98ad9a21..26effda52a793 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/kibana_services.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/kibana_services.ts @@ -62,18 +62,13 @@ export interface VisualizeKibanaServices { let services: VisualizeKibanaServices | null = null; export function setServices(newServices: VisualizeKibanaServices) { - if (services) { - throw new Error( - 'Kibana services already set - are you trying to import this module from outside of the home app?' - ); - } services = newServices; } export function getServices() { if (!services) { throw new Error( - 'Kibana services not set - are you trying to import this module from outside of the home app?' + 'Kibana services not set - are you trying to import this module from outside of the visualize app?' ); } return services; From 1f5648900ebb369f4160ef67cd3166728d0f519e Mon Sep 17 00:00:00 2001 From: maryia-lapata Date: Mon, 4 Nov 2019 18:01:20 +0300 Subject: [PATCH 062/132] Move embeddableFactory registration to setup --- src/legacy/core_plugins/kibana/public/visualize/plugin.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/legacy/core_plugins/kibana/public/visualize/plugin.ts b/src/legacy/core_plugins/kibana/public/visualize/plugin.ts index a3121425abe97..efd9bf6dfffae 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/plugin.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/plugin.ts @@ -45,6 +45,8 @@ import { VISUALIZE_EMBEDDABLE_TYPE } from './embeddable/constants'; import { VisualizeConstants } from './visualize_constants'; import { setServices, VisualizeKibanaServices, DocTitle } from './kibana_services'; +import { start as visualizationsStart } from '../../../visualizations/public/np_ready/public/legacy'; + export interface LegacyAngularInjectedDependencies { chromeLegacy: any; editorTypes: any; @@ -137,9 +139,6 @@ export class VisualizePlugin implements Plugin { }; setServices(deps); - const embeddableFactory = new VisualizeEmbeddableFactory(visualizations.types); - embeddableSetup.registerEmbeddableFactory(VISUALIZE_EMBEDDABLE_TYPE, embeddableFactory); - const { renderApp } = await import('./render_app'); return renderApp(params.element, params.appBasePath, deps); }, @@ -161,6 +160,9 @@ export class VisualizePlugin implements Plugin { localApplicationService.register({ ...app, id: 'visualize' }); VisEditorTypesRegistryProvider.register(defaultEditor); + + const embeddableFactory = new VisualizeEmbeddableFactory(visualizationsStart.types); + embeddableSetup.registerEmbeddableFactory(VISUALIZE_EMBEDDABLE_TYPE, embeddableFactory); } start( From 14d8fd4c4cc71e00626228ff0274b3fd628327e8 Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Mon, 4 Nov 2019 12:39:00 -0500 Subject: [PATCH 063/132] different take on global state handling --- .../kibana/public/dashboard/app.js | 47 ++++++---- .../dashboard/dashboard_app_controller.tsx | 5 +- .../kibana/public/dashboard/index.ts | 1 + .../kibana/public/dashboard/plugin.ts | 30 ++++++- .../kibana/public/dashboard/render_app.ts | 22 ++++- .../ui/public/state_management/state.js | 13 ++- .../ui/public/timefilter/setup_router.ts | 87 ++++++++++--------- .../apps/dashboard/dashboard_time.js | 20 ++++- 8 files changed, 156 insertions(+), 69 deletions(-) diff --git a/src/legacy/core_plugins/kibana/public/dashboard/app.js b/src/legacy/core_plugins/kibana/public/dashboard/app.js index 85e73cc7db24d..8fea284fe19fd 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/app.js +++ b/src/legacy/core_plugins/kibana/public/dashboard/app.js @@ -30,10 +30,10 @@ import { InvalidJSONProperty, SavedObjectNotFound, } from '../../../../../plugins/kibana_utils/public'; -import { FeatureCatalogueCategory } from 'ui/registry/feature_catalogue'; import { DashboardListing, EMPTY_FILTER } from './listing/dashboard_listing'; import { addHelpMenuToAppChrome } from './help_menu/help_menu_util'; import { start as data } from '../../../data/public/legacy'; +import { registerTimefilterWithGlobalStateFactory } from '../../../../ui/public/timefilter/setup_router'; export function initDashboardApp(app, deps) { initDashboardAppDirective(app, deps); @@ -49,6 +49,35 @@ export function initDashboardApp(app, deps) { addHelpMenuToAppChrome(deps.chrome, deps.core.docLinks); } + app.run(globalState => { + globalState.fetch(); + if (!globalState.time) { + globalState.time = deps.dataStart.timefilter.timefilter.getTime(); + } + if (!globalState.refreshInterval) { + globalState.refreshInterval = deps.dataStart.timefilter.timefilter.getRefreshInterval(); + } + const hasGlobalURLState = window.location.hash.includes('_g='); + // only inject global state if there is none in the url itself (that takes precedence) + if (!hasGlobalURLState) { + const globalStateStuff = deps.sessionStorage.get('oss-kibana-cross-app-state') || {}; + Object.keys(globalStateStuff).forEach(key => { + globalState[key] = globalStateStuff[key]; + }); + } else { + globalState.$inheritedGlobalState = true; + } + globalState.save(); + }); + + app.run((globalState, $rootScope) => { + registerTimefilterWithGlobalStateFactory( + deps.dataStart.timefilter.timefilter, + globalState, + $rootScope + ); + }); + app.config(function ($routeProvider) { const defaults = { reloadOnSearch: false, @@ -210,20 +239,4 @@ export function initDashboardApp(app, deps) { .when(`dashboard/:tail*?`, { redirectTo: `/${deps.core.injectedMetadata.getInjectedVar('kbnDefaultAppId')}` }) .when(`dashboards/:tail*?`, { redirectTo: `/${deps.core.injectedMetadata.getInjectedVar('kbnDefaultAppId')}` }); }); - - deps.FeatureCatalogueRegistryProvider.register(() => { - return { - id: 'dashboard', - title: i18n.translate('kbn.dashboard.featureCatalogue.dashboardTitle', { - defaultMessage: 'Dashboard', - }), - description: i18n.translate('kbn.dashboard.featureCatalogue.dashboardDescription', { - defaultMessage: 'Display and share a collection of visualizations and saved searches.', - }), - icon: 'dashboardApp', - path: `/app/kibana#${DashboardConstants.LANDING_PAGE_PATH}`, - showOnHomePage: true, - category: FeatureCatalogueCategory.DATA, - }; - }); } diff --git a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx index cf528ae0ebf2d..8603920126464 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx +++ b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx @@ -151,10 +151,7 @@ export class DashboardAppController { // The hash check is so we only update the time filter on dashboard open, not during // normal cross app navigation. - if ( - dashboardStateManager.getIsTimeSavedWithDashboard() && - !window.location.hash.includes('_a=') - ) { + if (dashboardStateManager.getIsTimeSavedWithDashboard() && !globalState.$inheritedGlobalState) { dashboardStateManager.syncTimefilterWithDashboard(timefilter); } $scope.showSaveQuery = dashboardCapabilities.saveQuery as boolean; diff --git a/src/legacy/core_plugins/kibana/public/dashboard/index.ts b/src/legacy/core_plugins/kibana/public/dashboard/index.ts index 622f047a9b9dc..1d0c8d34f239b 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/index.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/index.ts @@ -55,6 +55,7 @@ async function getAngularDependencies(): Promise { const instance = new DashboardPlugin(); instance.setup(npSetup.core, { + feature_catalogue: npSetup.plugins.feature_catalogue, __LEGACY: { localApplicationService, getAngularDependencies, diff --git a/src/legacy/core_plugins/kibana/public/dashboard/plugin.ts b/src/legacy/core_plugins/kibana/public/dashboard/plugin.ts index d5535074063ed..d0741db58cd55 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/plugin.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/plugin.ts @@ -25,6 +25,7 @@ import { Plugin, SavedObjectsClientContract, } from 'kibana/public'; +import { i18n } from '@kbn/i18n'; import { RenderDeps } from './render_app'; import { LocalApplicationService } from '../local_application_service'; import { DataStart } from '../../../data/public'; @@ -32,6 +33,11 @@ import { DataPublicPluginStart as NpDataStart } from '../../../../../plugins/dat import { EmbeddablePublicPlugin } from '../../../../../plugins/embeddable/public'; import { Storage } from '../../../../../plugins/kibana_utils/public'; import { NavigationStart } from '../../../navigation/public'; +import { DashboardConstants } from './dashboard_constants'; +import { + FeatureCatalogueCategory, + FeatureCatalogueSetup, +} from '../../../../../plugins/feature_catalogue/public'; export interface LegacyAngularInjectedDependencies { shareContextMenuExtensions: any; @@ -54,6 +60,7 @@ export interface DashboardPluginSetupDependencies { FeatureCatalogueRegistryProvider: any; docTitle: any; }; + feature_catalogue: FeatureCatalogueSetup; } export class DashboardPlugin implements Plugin { @@ -68,7 +75,13 @@ export class DashboardPlugin implements Plugin { public setup( core: CoreSetup, { - __LEGACY: { localApplicationService, getAngularDependencies, ...legacyServices }, + __LEGACY: { + localApplicationService, + getAngularDependencies, + FeatureCatalogueRegistryProvider, + ...legacyServices + }, + feature_catalogue, }: DashboardPluginSetupDependencies ) { const app: App = { @@ -102,6 +115,7 @@ export class DashboardPlugin implements Plugin { embeddables, dashboardCapabilities: contextCore.application.capabilities.dashboard, localStorage: new Storage(localStorage), + sessionStorage: new Storage(sessionStorage), }; const { renderApp } = await import('./render_app'); return renderApp(params.element, params.appBasePath, deps); @@ -109,6 +123,20 @@ export class DashboardPlugin implements Plugin { }; localApplicationService.register({ ...app, id: 'dashboard' }); localApplicationService.register({ ...app, id: 'dashboards' }); + + feature_catalogue.register({ + id: 'dashboard', + title: i18n.translate('kbn.dashboard.featureCatalogue.dashboardTitle', { + defaultMessage: 'Dashboard', + }), + description: i18n.translate('kbn.dashboard.featureCatalogue.dashboardDescription', { + defaultMessage: 'Display and share a collection of visualizations and saved searches.', + }), + icon: 'dashboardApp', + path: `/app/kibana#${DashboardConstants.LANDING_PAGE_PATH}`, + showOnHomePage: true, + category: FeatureCatalogueCategory.DATA, + }); } start( diff --git a/src/legacy/core_plugins/kibana/public/dashboard/render_app.ts b/src/legacy/core_plugins/kibana/public/dashboard/render_app.ts index 564f63ce18a7d..ca219e6a74d95 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/render_app.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/render_app.ts @@ -20,6 +20,7 @@ import { EuiConfirmModal } from '@elastic/eui'; import angular, { IModule } from 'angular'; import { IPrivate } from 'ui/private'; +import { State } from 'ui/state_management/state'; import { i18nDirective, i18nFilter, I18nProvider } from '@kbn/i18n/angular'; // @ts-ignore import { GlobalStateProvider } from 'ui/state_management/global_state'; @@ -81,10 +82,10 @@ export interface RenderDeps { uiSettings: UiSettingsClientContract; chrome: ChromeStart; addBasePath: (path: string) => string; - FeatureCatalogueRegistryProvider: any; savedQueryService: SavedQueryService; embeddables: ReturnType; localStorage: Storage; + sessionStorage: Storage; } let angularModuleInstance: IModule | null = null; @@ -98,7 +99,23 @@ export const renderApp = (element: HTMLElement, appBasePath: string, deps: Rende initDashboardApp(angularModuleInstance, deps); } const $injector = mountDashboardApp(appBasePath, element); - return () => $injector.get('$rootScope').$destroy(); + // const hasGlobalURLState = window.location.hash.includes('_g='); + // // only inject global state if there is none in the url itself (that takes precedence) + // if (!hasGlobalURLState) { + // const globalStateStuff = deps.sessionStorage.get('oss-kibana-cross-app-state') || {}; + // const globalState = $injector.get('globalState'); + // globalState.time = deps.dataStart.timefilter.timefilter.getTime(); + // globalState.refreshInterval = deps.dataStart.timefilter.timefilter.getRefreshInterval(); + // Object.keys(globalStateStuff).forEach(key => { + // globalState[key] = globalStateStuff[key]; + // }); + // globalState.save(); + // } + return () => { + const currentGlobalState = $injector.get('globalState'); + deps.sessionStorage.set('oss-kibana-cross-app-state', currentGlobalState.toObject()); + $injector.get('$rootScope').$destroy(); + }; }; const mainTemplate = (basePath: string) => `
@@ -120,7 +137,6 @@ function mountDashboardApp(appBasePath: string, element: HTMLElement) { // make angular-within-angular possible const $injector = angular.bootstrap(mountpoint, [moduleName]); // initialize global state handler - // $injector.get('globalState'); element.appendChild(mountpoint); return $injector; } diff --git a/src/legacy/ui/public/state_management/state.js b/src/legacy/ui/public/state_management/state.js index b7623ab0fc5a5..eb853e3a5e33b 100644 --- a/src/legacy/ui/public/state_management/state.js +++ b/src/legacy/ui/public/state_management/state.js @@ -42,7 +42,7 @@ import { isStateHash, } from './state_storage'; -export function StateProvider(Private, $rootScope, $location, stateManagementConfig, config, kbnUrl) { +export function StateProvider(Private, $rootScope, $location, stateManagementConfig, config, kbnUrl, $injector) { const Events = Private(EventsProvider); createLegacyClass(State).inherits(Events); @@ -135,11 +135,16 @@ export function StateProvider(Private, $rootScope, $location, stateManagementCon return; } + const isDummyRoute = + $injector.has('$route') && + $injector.get('$route').current && + $injector.get('$route').current.outerAngularWrapperRoute; + let stash = this._readFromURL(); - // nothing to read from the url? save if ordered to persist + // nothing to read from the url? save if ordered to persist, but only if it's not on a wrapper route if (stash === null) { - if (this._persistAcrossApps) { + if (this._persistAcrossApps && !isDummyRoute) { return this.save(); } else { stash = {}; @@ -150,7 +155,7 @@ export function StateProvider(Private, $rootScope, $location, stateManagementCon // apply diff to state from stash, will change state in place via side effect const diffResults = applyDiff(this, stash); - if (diffResults.keys.length) { + if (!isDummyRoute && diffResults.keys.length) { this.emit('fetch_with_changes', diffResults.keys); } }; diff --git a/src/legacy/ui/public/timefilter/setup_router.ts b/src/legacy/ui/public/timefilter/setup_router.ts index ffc8a1fca6c64..11beb121f58c0 100644 --- a/src/legacy/ui/public/timefilter/setup_router.ts +++ b/src/legacy/ui/public/timefilter/setup_router.ts @@ -41,49 +41,58 @@ export function getTimefilterConfig() { }; } -// Currently some parts of Kibana (index patterns, timefilter) rely on addSetupWork in the uiRouter -// and require it to be executed to properly function. -// This function is exposed for applications that do not use uiRoutes like APM -// Kibana issue https://github.com/elastic/kibana/issues/19110 tracks the removal of this dependency on uiRouter -export const registerTimefilterWithGlobalState = _.once( - (timefilter: TimefilterContract, globalState: any, $rootScope: IScope) => { - // settings have to be re-fetched here, to make sure that settings changed by overrideLocalDefault are taken into account. - const config = getTimefilterConfig(); - timefilter.setTime(_.defaults(globalState.time || {}, config.timeDefaults)); - timefilter.setRefreshInterval( - _.defaults(globalState.refreshInterval || {}, config.refreshIntervalDefaults) +export const registerTimefilterWithGlobalStateFactory = ( + timefilter: TimefilterContract, + globalState: any, + $rootScope: IScope +) => { + // settings have to be re-fetched here, to make sure that settings changed by overrideLocalDefault are taken into account. + const config = getTimefilterConfig(); + timefilter.setTime(_.defaults(globalState.time || {}, config.timeDefaults)); + timefilter.setRefreshInterval( + _.defaults(globalState.refreshInterval || {}, config.refreshIntervalDefaults) + ); + + globalState.on('fetch_with_changes', () => { + // clone and default to {} in one + const newTime: TimeRange = _.defaults({}, globalState.time, config.timeDefaults); + const newRefreshInterval: RefreshInterval = _.defaults( + {}, + globalState.refreshInterval, + config.refreshIntervalDefaults ); - globalState.on('fetch_with_changes', () => { - // clone and default to {} in one - const newTime: TimeRange = _.defaults({}, globalState.time, config.timeDefaults); - const newRefreshInterval: RefreshInterval = _.defaults( - {}, - globalState.refreshInterval, - config.refreshIntervalDefaults - ); + if (newTime) { + if (newTime.to) newTime.to = convertISO8601(newTime.to); + if (newTime.from) newTime.from = convertISO8601(newTime.from); + } - if (newTime) { - if (newTime.to) newTime.to = convertISO8601(newTime.to); - if (newTime.from) newTime.from = convertISO8601(newTime.from); - } + timefilter.setTime(newTime); + timefilter.setRefreshInterval(newRefreshInterval); + }); - timefilter.setTime(newTime); - timefilter.setRefreshInterval(newRefreshInterval); - }); + const updateGlobalStateWithTime = () => { + globalState.time = timefilter.getTime(); + globalState.refreshInterval = timefilter.getRefreshInterval(); + globalState.save(); + }; - const updateGlobalStateWithTime = () => { - globalState.time = timefilter.getTime(); - globalState.refreshInterval = timefilter.getRefreshInterval(); - globalState.save(); - }; + const sub1 = subscribeWithScope($rootScope, timefilter.getRefreshIntervalUpdate$(), { + next: updateGlobalStateWithTime, + }); - subscribeWithScope($rootScope, timefilter.getRefreshIntervalUpdate$(), { - next: updateGlobalStateWithTime, - }); + const sub2 = subscribeWithScope($rootScope, timefilter.getTimeUpdate$(), { + next: updateGlobalStateWithTime, + }); - subscribeWithScope($rootScope, timefilter.getTimeUpdate$(), { - next: updateGlobalStateWithTime, - }); - } -); + $rootScope.$on('$destroy', () => { + sub1.unsubscribe(); + sub2.unsubscribe(); + }); +}; + +// Currently some parts of Kibana (index patterns, timefilter) rely on addSetupWork in the uiRouter +// and require it to be executed to properly function. +// This function is exposed for applications that do not use uiRoutes like APM +// Kibana issue https://github.com/elastic/kibana/issues/19110 tracks the removal of this dependency on uiRouter +export const registerTimefilterWithGlobalState = _.once(registerTimefilterWithGlobalStateFactory); diff --git a/test/functional/apps/dashboard/dashboard_time.js b/test/functional/apps/dashboard/dashboard_time.js index 18a1fc7374c1a..917157e54eee0 100644 --- a/test/functional/apps/dashboard/dashboard_time.js +++ b/test/functional/apps/dashboard/dashboard_time.js @@ -24,8 +24,9 @@ const dashboardName = 'Dashboard Test Time'; const fromTime = '2015-09-19 06:31:44.000'; const toTime = '2015-09-23 18:31:44.000'; -export default function ({ getPageObjects }) { +export default function ({ getPageObjects, getService }) { const PageObjects = getPageObjects(['dashboard', 'header', 'timePicker']); + const browser = getService('browser'); describe('dashboard time', () => { before(async function () { @@ -71,6 +72,23 @@ export default function ({ getPageObjects }) { expect(time.start).to.equal('Sep 19, 2015 @ 06:31:44.000'); expect(time.end).to.equal('Sep 23, 2015 @ 18:31:44.000'); }); + + // If time is stored with a dashboard, it's supposed to override the current time settings when opened. + // However, if the URL also contains time in the global state, then the global state + // time should take precedence. + it('should be overwritten by global state', async function () { + const currentUrl = await browser.getCurrentUrl(); + const kibanaBaseUrl = currentUrl.substring(0, currentUrl.indexOf('#')); + const id = await PageObjects.dashboard.getDashboardIdFromCurrentUrl(); + + await PageObjects.dashboard.gotoDashboardLandingPage(); + + const urlWithGlobalTime = `${kibanaBaseUrl}#/dashboard/${id}?_g=(time:(from:now-1h,to:now))`; + await browser.get(urlWithGlobalTime, false); + const time = await PageObjects.timePicker.getTimeConfig(); + expect(time.start).to.equal('~ an hour ago'); + expect(time.end).to.equal('now'); + }); }); // If the user has time stored with a dashboard, it's supposed to override the current time settings From fa13e4b387c16859daf9866805a85a7460b24cbf Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Mon, 4 Nov 2019 15:06:59 -0500 Subject: [PATCH 064/132] fix detection of url state --- src/legacy/core_plugins/kibana/public/dashboard/app.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/legacy/core_plugins/kibana/public/dashboard/app.js b/src/legacy/core_plugins/kibana/public/dashboard/app.js index 8fea284fe19fd..3cc290cab7968 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/app.js +++ b/src/legacy/core_plugins/kibana/public/dashboard/app.js @@ -51,14 +51,14 @@ export function initDashboardApp(app, deps) { app.run(globalState => { globalState.fetch(); + const hasGlobalURLState = Object.keys(globalState.toObject()).length; if (!globalState.time) { globalState.time = deps.dataStart.timefilter.timefilter.getTime(); } if (!globalState.refreshInterval) { globalState.refreshInterval = deps.dataStart.timefilter.timefilter.getRefreshInterval(); } - const hasGlobalURLState = window.location.hash.includes('_g='); - // only inject global state if there is none in the url itself (that takes precedence) + // only inject cross app global state if there is none in the url itself (that takes precedence) if (!hasGlobalURLState) { const globalStateStuff = deps.sessionStorage.get('oss-kibana-cross-app-state') || {}; Object.keys(globalStateStuff).forEach(key => { From 2a5c0fb5ea717bf59648f80bbd53b4521ceff8b9 Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Mon, 4 Nov 2019 16:31:44 -0500 Subject: [PATCH 065/132] fix broken test --- src/legacy/ui/public/timefilter/setup_router.test.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/legacy/ui/public/timefilter/setup_router.test.js b/src/legacy/ui/public/timefilter/setup_router.test.js index 4bc797e5eff00..f229937c3b435 100644 --- a/src/legacy/ui/public/timefilter/setup_router.test.js +++ b/src/legacy/ui/public/timefilter/setup_router.test.js @@ -42,9 +42,14 @@ describe('registerTimefilterWithGlobalState()', () => { } }; + const rootScope = { + $on: jest.fn() + }; + registerTimefilterWithGlobalState( timefilter, - globalState + globalState, + rootScope, ); expect(setTime.mock.calls.length).toBe(2); From 9ef8a140250ba8429b693aa2d040526fbffb2608 Mon Sep 17 00:00:00 2001 From: maryia-lapata Date: Tue, 5 Nov 2019 10:29:05 +0300 Subject: [PATCH 066/132] Use uiSettings instead of config --- .../core_plugins/kibana/public/visualize/editor/editor.js | 6 +++--- .../visualize/embeddable/visualize_embeddable_factory.tsx | 4 ++-- src/legacy/core_plugins/kibana/public/visualize/index.ts | 1 - .../core_plugins/kibana/public/visualize/kibana_services.ts | 1 - src/legacy/core_plugins/kibana/public/visualize/plugin.ts | 1 - 5 files changed, 5 insertions(+), 8 deletions(-) diff --git a/src/legacy/core_plugins/kibana/public/visualize/editor/editor.js b/src/legacy/core_plugins/kibana/public/visualize/editor/editor.js index c5465c594e8b1..fc2fbf071f431 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/editor/editor.js +++ b/src/legacy/core_plugins/kibana/public/visualize/editor/editor.js @@ -71,7 +71,6 @@ function VisualizeAppController( kbnUrl, redirectWhenMissing, Promise, - config, kbnBaseUrl, getAppState, globalState, @@ -93,6 +92,7 @@ function VisualizeAppController( getBasePath, docLinks, savedQueryService, + uiSettings, } = getServices(); // Retrieve the resolved SavedVis instance. @@ -238,7 +238,7 @@ function VisualizeAppController( linked: !!savedVis.savedSearchId, query: searchSource.getOwnField('query') || { query: '', - language: localStorage.get('kibana.userQueryLanguage') || config.get('search:queryLanguage') + language: localStorage.get('kibana.userQueryLanguage') || uiSettings.get('search:queryLanguage') }, filters: searchSource.getOwnField('filter') || [], vis: savedVisState @@ -440,7 +440,7 @@ function VisualizeAppController( delete $state.savedQuery; $state.query = { query: '', - language: localStorage.get('kibana.userQueryLanguage') || config.get('search:queryLanguage') + language: localStorage.get('kibana.userQueryLanguage') || uiSettings.get('search:queryLanguage') }; queryFilter.removeAll(); $state.save(); diff --git a/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable_factory.tsx b/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable_factory.tsx index c4bd7a29f21c8..b38ba31d7260b 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable_factory.tsx +++ b/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable_factory.tsx @@ -111,14 +111,14 @@ export class VisualizeEmbeddableFactory extends EmbeddableFactory< input: Partial & { id: string }, parent?: Container ): Promise { - const { addBasePath, config, savedVisualizations } = getServices(); + const { addBasePath, uiSettings, savedVisualizations } = getServices(); try { const visId = savedObject.id as string; const editUrl = visId ? addBasePath(`/app/kibana${savedVisualizations.urlFor(visId)}`) : ''; const loader = await getVisualizeLoader(); - const isLabsEnabled = config.get('visualize:enableLabs'); + const isLabsEnabled = uiSettings.get('visualize:enableLabs'); if (!isLabsEnabled && savedObject.vis.type.stage === 'experimental') { return new DisabledLabEmbeddable(savedObject.title, input); diff --git a/src/legacy/core_plugins/kibana/public/visualize/index.ts b/src/legacy/core_plugins/kibana/public/visualize/index.ts index eabf00ba69b9a..6d1d2f1c67614 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/index.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/index.ts @@ -60,7 +60,6 @@ async function getAngularDependencies(): Promise; diff --git a/src/legacy/core_plugins/kibana/public/visualize/plugin.ts b/src/legacy/core_plugins/kibana/public/visualize/plugin.ts index efd9bf6dfffae..e2c3b2fa6edc8 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/plugin.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/plugin.ts @@ -52,7 +52,6 @@ export interface LegacyAngularInjectedDependencies { editorTypes: any; queryFilter: any; shareContextMenuExtensions: any; - config: any; savedObjectRegistry: any; savedObjectClient: any; savedDashboards: any; From 9c51937c3588c03bc2f720362605b302a354e21d Mon Sep 17 00:00:00 2001 From: maryia-lapata Date: Tue, 5 Nov 2019 16:34:19 +0300 Subject: [PATCH 067/132] Add npDataStart --- .../kibana/public/visualize/app.js | 21 +++++++++++++++++++ .../kibana/public/visualize/editor/editor.js | 6 ++++-- .../kibana/public/visualize/index.ts | 1 + .../public/visualize/kibana_services.ts | 7 ++++--- .../kibana/public/visualize/plugin.ts | 8 ++++++- 5 files changed, 37 insertions(+), 6 deletions(-) diff --git a/src/legacy/core_plugins/kibana/public/visualize/app.js b/src/legacy/core_plugins/kibana/public/visualize/app.js index 1c131a67d9de2..f5589e87f7994 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/app.js +++ b/src/legacy/core_plugins/kibana/public/visualize/app.js @@ -39,6 +39,27 @@ import { ensureDefaultIndexPattern } from './kibana_services'; export function initVisualizeApp(app, deps) { initVisualizeAppDirective(app, deps); + app.run(globalState => { + globalState.fetch(); + const hasGlobalURLState = Object.keys(globalState.toObject()).length; + if (!globalState.time) { + globalState.time = deps.dataStart.timefilter.timefilter.getTime(); + } + if (!globalState.refreshInterval) { + globalState.refreshInterval = deps.dataStart.timefilter.timefilter.getRefreshInterval(); + } + // only inject cross app global state if there is none in the url itself (that takes precedence) + if (!hasGlobalURLState) { + const globalStateStuff = deps.sessionStorage.get('oss-kibana-cross-app-state') || {}; + Object.keys(globalStateStuff).forEach(key => { + globalState[key] = globalStateStuff[key]; + }); + } else { + globalState.$inheritedGlobalState = true; + } + globalState.save(); + }); + app.config(function ($routeProvider) { const defaults = { requireUICapability: 'visualize.show', diff --git a/src/legacy/core_plugins/kibana/public/visualize/editor/editor.js b/src/legacy/core_plugins/kibana/public/visualize/editor/editor.js index fc2fbf071f431..e354ea6f1b5b8 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/editor/editor.js +++ b/src/legacy/core_plugins/kibana/public/visualize/editor/editor.js @@ -28,7 +28,7 @@ import { migrateAppState } from './lib'; import { DashboardConstants } from '../../dashboard/dashboard_constants'; import { VisualizeConstants } from '../visualize_constants'; import { getEditBreadcrumbs } from '../breadcrumbs'; -import { extractTimeFilter, changeTimeFilter } from '../../../../data/public'; +import { extractTimeFilter, changeTimeFilter, FilterStateManager } from '../../../../data/public'; import { addHelpMenuToAppChrome } from '../help_menu/help_menu_util'; @@ -79,7 +79,6 @@ function VisualizeAppController( angular, indexPatterns, localStorage, - queryFilter, visualizeCapabilities, shareContextMenuExtensions, dataStart: { @@ -93,8 +92,11 @@ function VisualizeAppController( docLinks, savedQueryService, uiSettings, + npDataStart, } = getServices(); + new FilterStateManager(globalState, getAppState, npDataStart.query.filterManager); + const queryFilter = npDataStart.query.filterManager; // Retrieve the resolved SavedVis instance. const savedVis = $route.current.locals.savedVis; // vis is instance of src/legacy/ui/public/vis/vis.js. diff --git a/src/legacy/core_plugins/kibana/public/visualize/index.ts b/src/legacy/core_plugins/kibana/public/visualize/index.ts index 6d1d2f1c67614..c33860a7bebf1 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/index.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/index.ts @@ -82,6 +82,7 @@ async function getAngularDependencies(): Promise; getBasePath: () => string; diff --git a/src/legacy/core_plugins/kibana/public/visualize/plugin.ts b/src/legacy/core_plugins/kibana/public/visualize/plugin.ts index e2c3b2fa6edc8..c7d2dc990990a 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/plugin.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/plugin.ts @@ -36,6 +36,7 @@ import { } from 'kibana/public'; import { Storage } from '../../../../../plugins/kibana_utils/public'; import { DataStart } from '../../../data/public'; +import { DataPublicPluginStart as NpDataStart } from '../../../../../plugins/data/public'; import { EmbeddablePublicPlugin } from '../../../../../plugins/embeddable/public'; import { NavigationStart } from '../../../navigation/public'; import { VisualizationsStart } from '../../../visualizations/public'; @@ -60,6 +61,7 @@ export interface LegacyAngularInjectedDependencies { export interface VisualizePluginStartDependencies { dataStart: DataStart; + npData: NpDataStart; embeddables: ReturnType; navigation: NavigationStart; visualizations: VisualizationsStart; @@ -78,6 +80,7 @@ export interface VisualizePluginSetupDependencies { export class VisualizePlugin implements Plugin { private startDependencies: { dataStart: DataStart; + npDataStart: NpDataStart; embeddables: ReturnType; navigation: NavigationStart; savedObjectsClient: SavedObjectsClientContract; @@ -110,6 +113,7 @@ export class VisualizePlugin implements Plugin { embeddables, navigation, visualizations, + npDataStart, } = this.startDependencies; const angularDependencies = await getAngularDependencies(); @@ -121,6 +125,7 @@ export class VisualizePlugin implements Plugin { core: contextCore as LegacyCoreStart, chrome: contextCore.chrome, dataStart, + npDataStart, docLinks: contextCore.docLinks, embeddables, getBasePath: core.http.basePath.get, @@ -166,10 +171,11 @@ export class VisualizePlugin implements Plugin { start( { savedObjects: { client: savedObjectsClient } }: CoreStart, - { dataStart, embeddables, navigation, visualizations }: VisualizePluginStartDependencies + { dataStart, embeddables, navigation, visualizations, npData }: VisualizePluginStartDependencies ) { this.startDependencies = { dataStart, + npDataStart: npData, embeddables, navigation, savedObjectsClient, From 8d8f22b71b6451e3983d803774f644f7b1ed734f Mon Sep 17 00:00:00 2001 From: maryia-lapata Date: Tue, 5 Nov 2019 17:09:36 +0300 Subject: [PATCH 068/132] Add Dashboard shim --- .../kibana/public/dashboard/app.js | 242 ++++++++++++++++ .../public/dashboard/dashboard_app.html | 6 +- .../kibana/public/dashboard/dashboard_app.tsx | 95 +++---- .../dashboard/dashboard_app_controller.tsx | 126 ++++----- .../public/dashboard/dashboard_state.test.ts | 1 + .../dashboard/dashboard_state_manager.ts | 9 +- .../public/dashboard/help_menu/help_menu.js | 34 ++- .../dashboard/help_menu/help_menu_util.js | 6 +- .../kibana/public/dashboard/index.js | 207 -------------- .../kibana/public/dashboard/index.ts | 72 +++++ ...embeddable_saved_object_converters.test.ts | 8 +- .../lib/embeddable_saved_object_converters.ts | 9 +- .../dashboard/lib/migrate_app_state.test.ts | 10 +- .../public/dashboard/lib/migrate_app_state.ts | 8 +- .../kibana/public/dashboard/plugin.ts | 154 +++++++++++ .../kibana/public/dashboard/render_app.ts | 260 ++++++++++++++++++ .../public/discover/angular/discover.js | 95 ++++--- .../kibana/public/discover/breadcrumbs.ts | 2 +- .../kibana/public/discover/kibana_services.ts | 2 + .../kibana/public/management/index.js | 8 - .../management/route_setup/load_default.js | 110 -------- .../ui/public/capabilities/route_setup.ts | 38 --- .../public/routes/__tests__/_route_manager.js | 12 - .../ui/public/state_management/state.js | 13 +- .../ui/public/timefilter/setup_router.test.js | 7 +- .../ui/public/timefilter/setup_router.ts | 87 +++--- .../loader/embedded_visualize_handler.test.ts | 2 + .../dashboard_mode/public/dashboard_viewer.js | 4 + x-pack/legacy/plugins/graph/public/index.ts | 2 + x-pack/legacy/plugins/graph/public/plugin.ts | 8 +- .../legacy/plugins/graph/public/render_app.ts | 15 +- .../grokdebugger/grokdebugger_route.js | 1 - .../plugins/searchprofiler/public/legacy.ts | 1 - 33 files changed, 1017 insertions(+), 637 deletions(-) create mode 100644 src/legacy/core_plugins/kibana/public/dashboard/app.js delete mode 100644 src/legacy/core_plugins/kibana/public/dashboard/index.js create mode 100644 src/legacy/core_plugins/kibana/public/dashboard/index.ts create mode 100644 src/legacy/core_plugins/kibana/public/dashboard/plugin.ts create mode 100644 src/legacy/core_plugins/kibana/public/dashboard/render_app.ts delete mode 100644 src/legacy/core_plugins/kibana/public/management/route_setup/load_default.js delete mode 100644 src/legacy/ui/public/capabilities/route_setup.ts diff --git a/src/legacy/core_plugins/kibana/public/dashboard/app.js b/src/legacy/core_plugins/kibana/public/dashboard/app.js new file mode 100644 index 0000000000000..3cc290cab7968 --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/dashboard/app.js @@ -0,0 +1,242 @@ +/* + * 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 { i18n } from '@kbn/i18n'; +import { wrapInI18nContext } from 'ui/i18n'; +import { ensureDefaultIndexPattern } from 'ui/legacy_compat'; + +import dashboardTemplate from './dashboard_app.html'; +import dashboardListingTemplate from './listing/dashboard_listing_ng_wrapper.html'; + +import { initDashboardAppDirective } from './dashboard_app'; +import { DashboardConstants, createDashboardEditUrl } from './dashboard_constants'; +import { + InvalidJSONProperty, + SavedObjectNotFound, +} from '../../../../../plugins/kibana_utils/public'; +import { DashboardListing, EMPTY_FILTER } from './listing/dashboard_listing'; +import { addHelpMenuToAppChrome } from './help_menu/help_menu_util'; +import { start as data } from '../../../data/public/legacy'; +import { registerTimefilterWithGlobalStateFactory } from '../../../../ui/public/timefilter/setup_router'; + +export function initDashboardApp(app, deps) { + initDashboardAppDirective(app, deps); + + app.directive('dashboardListing', function (reactDirective) { + return reactDirective(wrapInI18nContext(DashboardListing)); + }); + + function createNewDashboardCtrl($scope) { + $scope.visitVisualizeAppLinkText = i18n.translate('kbn.dashboard.visitVisualizeAppLinkText', { + defaultMessage: 'visit the Visualize app', + }); + addHelpMenuToAppChrome(deps.chrome, deps.core.docLinks); + } + + app.run(globalState => { + globalState.fetch(); + const hasGlobalURLState = Object.keys(globalState.toObject()).length; + if (!globalState.time) { + globalState.time = deps.dataStart.timefilter.timefilter.getTime(); + } + if (!globalState.refreshInterval) { + globalState.refreshInterval = deps.dataStart.timefilter.timefilter.getRefreshInterval(); + } + // only inject cross app global state if there is none in the url itself (that takes precedence) + if (!hasGlobalURLState) { + const globalStateStuff = deps.sessionStorage.get('oss-kibana-cross-app-state') || {}; + Object.keys(globalStateStuff).forEach(key => { + globalState[key] = globalStateStuff[key]; + }); + } else { + globalState.$inheritedGlobalState = true; + } + globalState.save(); + }); + + app.run((globalState, $rootScope) => { + registerTimefilterWithGlobalStateFactory( + deps.dataStart.timefilter.timefilter, + globalState, + $rootScope + ); + }); + + app.config(function ($routeProvider) { + const defaults = { + reloadOnSearch: false, + requireUICapability: 'dashboard.show', + badge: () => { + if (deps.dashboardCapabilities.showWriteControls) { + return undefined; + } + + return { + text: i18n.translate('kbn.dashboard.badge.readOnly.text', { + defaultMessage: 'Read only', + }), + tooltip: i18n.translate('kbn.dashboard.badge.readOnly.tooltip', { + defaultMessage: 'Unable to save dashboards', + }), + iconType: 'glasses', + }; + }, + }; + + $routeProvider + .when(DashboardConstants.LANDING_PAGE_PATH, { + ...defaults, + template: dashboardListingTemplate, + controller($injector, $location, $scope) { + const services = deps.savedObjectRegistry.byLoaderPropertiesName; + const kbnUrl = $injector.get('kbnUrl'); + const dashboardConfig = deps.dashboardConfig; + + $scope.listingLimit = deps.uiSettings.get('savedObjects:listingLimit'); + $scope.create = () => { + kbnUrl.redirect(DashboardConstants.CREATE_NEW_DASHBOARD_URL); + }; + $scope.find = search => { + return services.dashboards.find(search, $scope.listingLimit); + }; + $scope.editItem = ({ id }) => { + kbnUrl.redirect(`${createDashboardEditUrl(id)}?_a=(viewMode:edit)`); + }; + $scope.getViewUrl = ({ id }) => { + return deps.addBasePath(`#${createDashboardEditUrl(id)}`); + }; + $scope.delete = dashboards => { + return services.dashboards.delete(dashboards.map(d => d.id)); + }; + $scope.hideWriteControls = dashboardConfig.getHideWriteControls(); + $scope.initialFilter = $location.search().filter || EMPTY_FILTER; + deps.chrome.setBreadcrumbs([ + { + text: i18n.translate('kbn.dashboard.dashboardBreadcrumbsTitle', { + defaultMessage: 'Dashboards', + }), + }, + ]); + addHelpMenuToAppChrome(deps.chrome, deps.core.docLinks); + }, + resolve: { + dash: function ($rootScope, $route, redirectWhenMissing, kbnUrl) { + return ensureDefaultIndexPattern(deps.core, data, $rootScope, kbnUrl).then(() => { + const savedObjectsClient = deps.savedObjectsClient; + const title = $route.current.params.title; + if (title) { + return savedObjectsClient + .find({ + search: `"${title}"`, + search_fields: 'title', + type: 'dashboard', + }) + .then(results => { + // The search isn't an exact match, lets see if we can find a single exact match to use + const matchingDashboards = results.savedObjects.filter( + dashboard => dashboard.attributes.title.toLowerCase() === title.toLowerCase() + ); + if (matchingDashboards.length === 1) { + kbnUrl.redirect(createDashboardEditUrl(matchingDashboards[0].id)); + } else { + kbnUrl.redirect(`${DashboardConstants.LANDING_PAGE_PATH}?filter="${title}"`); + } + $rootScope.$digest(); + return new Promise(() => {}); + }); + } + }); + }, + }, + }) + .when(DashboardConstants.CREATE_NEW_DASHBOARD_URL, { + ...defaults, + template: dashboardTemplate, + controller: createNewDashboardCtrl, + requireUICapability: 'dashboard.createNew', + resolve: { + dash: function (redirectWhenMissing, $rootScope, kbnUrl) { + return ensureDefaultIndexPattern(deps.core, data, $rootScope, kbnUrl) + .then(() => { + return deps.savedDashboards.get(); + }) + .catch( + redirectWhenMissing({ + dashboard: DashboardConstants.LANDING_PAGE_PATH, + }) + ); + }, + }, + }) + .when(createDashboardEditUrl(':id'), { + ...defaults, + template: dashboardTemplate, + controller: createNewDashboardCtrl, + resolve: { + dash: function ($rootScope, $route, redirectWhenMissing, kbnUrl, AppState) { + const id = $route.current.params.id; + + return ensureDefaultIndexPattern(deps.core, data, $rootScope, kbnUrl) + .then(() => { + return deps.savedDashboards.get(id); + }) + .then(savedDashboard => { + deps.chrome.recentlyAccessed.add( + savedDashboard.getFullPath(), + savedDashboard.title, + id + ); + return savedDashboard; + }) + .catch(error => { + // A corrupt dashboard was detected (e.g. with invalid JSON properties) + if (error instanceof InvalidJSONProperty) { + deps.toastNotifications.addDanger(error.message); + kbnUrl.redirect(DashboardConstants.LANDING_PAGE_PATH); + return; + } + + // Preserve BWC of v5.3.0 links for new, unsaved dashboards. + // See https://github.com/elastic/kibana/issues/10951 for more context. + if (error instanceof SavedObjectNotFound && id === 'create') { + // Note "new AppState" is necessary so the state in the url is preserved through the redirect. + kbnUrl.redirect(DashboardConstants.CREATE_NEW_DASHBOARD_URL, {}, new AppState()); + deps.toastNotifications.addWarning( + i18n.translate('kbn.dashboard.urlWasRemovedInSixZeroWarningMessage', { + defaultMessage: + 'The url "dashboard/create" was removed in 6.0. Please update your bookmarks.', + }) + ); + return new Promise(() => {}); + } else { + throw error; + } + }) + .catch( + redirectWhenMissing({ + dashboard: DashboardConstants.LANDING_PAGE_PATH, + }) + ); + }, + }, + }) + .when(`dashboard/:tail*?`, { redirectTo: `/${deps.core.injectedMetadata.getInjectedVar('kbnDefaultAppId')}` }) + .when(`dashboards/:tail*?`, { redirectTo: `/${deps.core.injectedMetadata.getInjectedVar('kbnDefaultAppId')}` }); + }); +} diff --git a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app.html b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app.html index 68c8131fa1a7b..d51b7e394f339 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app.html +++ b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app.html @@ -4,11 +4,11 @@ > void; timefilterSubscriptions$: Subscription; + isVisible: boolean; } -const app = uiModules.get('app/dashboard', ['elasticsearch', 'ngRoute', 'react', 'kibana/config']); - -app.directive('dashboardApp', function($injector: IInjector) { - const AppState = $injector.get>('AppState'); - const kbnUrl = $injector.get('kbnUrl'); - const confirmModal = $injector.get('confirmModal'); - const config = $injector.get('config'); +export function initDashboardAppDirective(app: any, deps: RenderDeps) { + app.directive('dashboardApp', function($injector: IInjector) { + const AppState = $injector.get>('AppState'); + const kbnUrl = $injector.get('kbnUrl'); + const confirmModal = $injector.get('confirmModal'); + const config = deps.uiSettings; - const Private = $injector.get('Private'); - - const indexPatterns = $injector.get<{ - getDefault: () => Promise; - }>('indexPatterns'); - - return { - restrict: 'E', - controllerAs: 'dashboardApp', - controller: ( - $scope: DashboardAppScope, - $route: any, - $routeParams: { - id?: string; - }, - getAppState: { - previouslyStored: () => TAppState | undefined; - }, - dashboardConfig: { - getHideWriteControls: () => boolean; - }, - localStorage: { - get: (prop: string) => unknown; - } - ) => - new DashboardAppController({ - $route, - $scope, - $routeParams, - getAppState, - dashboardConfig, - localStorage, - Private, - kbnUrl, - AppStateClass: AppState, - indexPatterns, - config, - confirmModal, - }), - }; -}); + return { + restrict: 'E', + controllerAs: 'dashboardApp', + controller: ( + $scope: DashboardAppScope, + $route: any, + $routeParams: { + id?: string; + }, + getAppState: any, + globalState: any + ) => + new DashboardAppController({ + $route, + $scope, + $routeParams, + getAppState, + globalState, + kbnUrl, + AppStateClass: AppState, + config, + confirmModal, + ...deps, + }), + }; + }); +} diff --git a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx index abf7b22a6e48c..8603920126464 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx +++ b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx @@ -23,42 +23,33 @@ import React from 'react'; import angular from 'angular'; import { uniq } from 'lodash'; -import chrome from 'ui/chrome'; import { subscribeWithScope } from 'ui/utils/subscribe_with_scope'; -import { toastNotifications } from 'ui/notify'; // @ts-ignore import { ConfirmationButtonTypes } from 'ui/modals/confirm_modal'; -import { FilterBarQueryFilterProvider } from 'ui/filter_manager/query_filter'; - -import { docTitle } from 'ui/doc_title/doc_title'; import { showSaveModal, SaveResult } from 'ui/saved_objects/show_saved_object_save_modal'; -import { showShareContextMenu, ShareContextMenuExtensionsRegistryProvider } from 'ui/share'; +import { showShareContextMenu } from 'ui/share'; import { migrateLegacyQuery } from 'ui/utils/migrate_legacy_query'; -import { timefilter } from 'ui/timefilter'; - -import { getUnhashableStatesProvider } from 'ui/state_management/state_hashing/get_unhashable_states_provider'; +import { State } from 'ui/state_management/state'; -import { - AppStateClass as TAppStateClass, - AppState as TAppState, -} from 'ui/state_management/app_state'; +import { AppStateClass as TAppStateClass } from 'ui/state_management/app_state'; import { KbnUrl } from 'ui/url/kbn_url'; import { Filter } from '@kbn/es-query'; import { IndexPattern } from 'ui/index_patterns'; -import { IPrivate } from 'ui/private'; -import { Query, SavedQuery } from 'src/legacy/core_plugins/data/public'; import { SaveOptions } from 'ui/saved_objects/saved_object'; -import { capabilities } from 'ui/capabilities'; import { Subscription } from 'rxjs'; -import { npStart } from 'ui/new_platform'; import { SavedObjectFinder } from 'ui/saved_objects/components/saved_object_finder'; -import { extractTimeFilter, changeTimeFilter } from '../../../data/public'; -import { start as data } from '../../../data/public/legacy'; +import { + extractTimeFilter, + changeTimeFilter, + FilterStateManager, + Query, + SavedQuery, +} from '../../../data/public'; import { DashboardContainer, @@ -73,7 +64,6 @@ import { ViewMode, openAddPanelFlyout, } from '../../../embeddable_api/public/np_ready/public'; -import { start } from '../../../embeddable_api/public/np_ready/public/legacy'; import { DashboardAppState, NavAction, ConfirmModalFn, SavedDashboardPanel } from './types'; import { showOptionsPopover } from './top_nav/show_options_popover'; @@ -88,8 +78,23 @@ import { getDashboardTitle } from './dashboard_strings'; import { DashboardAppScope } from './dashboard_app'; import { VISUALIZE_EMBEDDABLE_TYPE } from '../visualize/embeddable'; import { convertSavedDashboardPanelToPanelState } from './lib/embeddable_saved_object_converters'; - -const { savedQueryService } = data.search.services; +import { RenderDeps } from './render_app'; + +export interface DashboardAppControllerDependencies extends RenderDeps { + $scope: DashboardAppScope; + $route: any; + $routeParams: any; + getAppState: any; + globalState: State; + indexPatterns: { + getDefault: () => Promise; + }; + dashboardConfig: any; + kbnUrl: KbnUrl; + AppStateClass: TAppStateClass; + config: any; + confirmModal: ConfirmModalFn; +} export class DashboardAppController { // Part of the exposed plugin API - do not remove without careful consideration. @@ -102,37 +107,31 @@ export class DashboardAppController { $route, $routeParams, getAppState, + globalState, dashboardConfig, localStorage, - Private, kbnUrl, AppStateClass, indexPatterns, config, confirmModal, - }: { - $scope: DashboardAppScope; - $route: any; - $routeParams: any; - getAppState: { - previouslyStored: () => TAppState | undefined; - }; - indexPatterns: { - getDefault: () => Promise; - }; - dashboardConfig: any; - localStorage: { - get: (prop: string) => unknown; - }; - Private: IPrivate; - kbnUrl: KbnUrl; - AppStateClass: TAppStateClass; - config: any; - confirmModal: ConfirmModalFn; - }) { - const queryFilter = Private(FilterBarQueryFilterProvider); - const getUnhashableStates = Private(getUnhashableStatesProvider); - const shareContextMenuExtensions = Private(ShareContextMenuExtensionsRegistryProvider); + shareContextMenuExtensions, + savedQueryService, + embeddables, + dashboardCapabilities, + docTitle, + dataStart: { + timefilter: { timefilter }, + }, + npDataStart, + core: { notifications, overlays, chrome, injectedMetadata }, + }: DashboardAppControllerDependencies) { + new FilterStateManager(globalState, getAppState, npDataStart.query.filterManager); + const queryFilter = npDataStart.query.filterManager; + + function getUnhashableStates(): State[] { + return [getAppState(), globalState].filter(Boolean); + } let lastReloadRequestTime = 0; @@ -145,16 +144,17 @@ export class DashboardAppController { savedDashboard: dash, AppStateClass, hideWriteControls: dashboardConfig.getHideWriteControls(), + kibanaVersion: injectedMetadata.getKibanaVersion(), }); $scope.appState = dashboardStateManager.getAppState(); - // The 'previouslyStored' check is so we only update the time filter on dashboard open, not during + // The hash check is so we only update the time filter on dashboard open, not during // normal cross app navigation. - if (dashboardStateManager.getIsTimeSavedWithDashboard() && !getAppState.previouslyStored()) { + if (dashboardStateManager.getIsTimeSavedWithDashboard() && !globalState.$inheritedGlobalState) { dashboardStateManager.syncTimefilterWithDashboard(timefilter); } - $scope.showSaveQuery = capabilities.get().dashboard.saveQuery as boolean; + $scope.showSaveQuery = dashboardCapabilities.saveQuery as boolean; const updateIndexPatterns = (container?: DashboardContainer) => { if (!container || isErrorEmbeddable(container)) { @@ -189,10 +189,7 @@ export class DashboardAppController { [key: string]: DashboardPanelState; } = {}; dashboardStateManager.getPanels().forEach((panel: SavedDashboardPanel) => { - embeddablesMap[panel.panelIndex] = convertSavedDashboardPanelToPanelState( - panel, - dashboardStateManager.getUseMargins() - ); + embeddablesMap[panel.panelIndex] = convertSavedDashboardPanelToPanelState(panel); }); let expandedPanelId; if (dashboardContainer && !isErrorEmbeddable(dashboardContainer)) { @@ -241,7 +238,7 @@ export class DashboardAppController { let outputSubscription: Subscription | undefined; const dashboardDom = document.getElementById('dashboardViewport'); - const dashboardFactory = start.getEmbeddableFactory( + const dashboardFactory = embeddables.getEmbeddableFactory( DASHBOARD_CONTAINER_TYPE ) as DashboardContainerFactory; dashboardFactory @@ -336,7 +333,7 @@ export class DashboardAppController { // Push breadcrumbs to new header navigation const updateBreadcrumbs = () => { - chrome.breadcrumbs.set([ + chrome.setBreadcrumbs([ { text: i18n.translate('kbn.dashboard.dashboardAppBreadcrumbsTitle', { defaultMessage: 'Dashboard', @@ -528,7 +525,7 @@ export class DashboardAppController { }); $scope.$watch( - () => capabilities.get().dashboard.saveQuery, + () => dashboardCapabilities.saveQuery, newCapability => { $scope.showSaveQuery = newCapability as boolean; } @@ -628,7 +625,7 @@ export class DashboardAppController { return saveDashboard(angular.toJson, timefilter, dashboardStateManager, saveOptions) .then(function(id) { if (id) { - toastNotifications.addSuccess({ + notifications.toasts.addSuccess({ title: i18n.translate('kbn.dashboard.dashboardWasSavedSuccessMessage', { defaultMessage: `Dashboard '{dashTitle}' was saved`, values: { dashTitle: dash.title }, @@ -646,7 +643,7 @@ export class DashboardAppController { return { id }; }) .catch(error => { - toastNotifications.addDanger({ + notifications.toasts.addDanger({ title: i18n.translate('kbn.dashboard.dashboardWasNotSavedDangerMessage', { defaultMessage: `Dashboard '{dashTitle}' was not saved. Error: {errorMessage}`, values: { @@ -767,10 +764,10 @@ export class DashboardAppController { if (dashboardContainer && !isErrorEmbeddable(dashboardContainer)) { openAddPanelFlyout({ embeddable: dashboardContainer, - getAllFactories: start.getEmbeddableFactories, - getFactory: start.getEmbeddableFactory, - notifications: npStart.core.notifications, - overlays: npStart.core.overlays, + getAllFactories: embeddables.getEmbeddableFactories, + getFactory: embeddables.getEmbeddableFactory, + notifications, + overlays, SavedObjectFinder, }); } @@ -818,8 +815,13 @@ export class DashboardAppController { }, }); + const visibleSubscription = chrome.getIsVisible$().subscribe(isVisible => { + $scope.isVisible = isVisible; + }); + $scope.$on('$destroy', () => { updateSubscription.unsubscribe(); + visibleSubscription.unsubscribe(); $scope.timefilterSubscriptions$.unsubscribe(); dashboardStateManager.destroy(); diff --git a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_state.test.ts b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_state.test.ts index a25ce1e607f9a..f5160d8442d79 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_state.test.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_state.test.ts @@ -55,6 +55,7 @@ describe('DashboardState', function() { savedDashboard, AppStateClass: getAppStateMock() as AppStateClass, hideWriteControls: false, + kibanaVersion: '7.0.0', }); } diff --git a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_state_manager.ts b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_state_manager.ts index 7c1fc771de349..ecaf1cafe5f14 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_state_manager.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_state_manager.ts @@ -55,6 +55,7 @@ export class DashboardStateManager { }; private stateDefaults: DashboardAppStateDefaults; private hideWriteControls: boolean; + private kibanaVersion: string; public isDirty: boolean; private changeListeners: Array<(status: { dirty: boolean }) => void>; private stateMonitor: StateMonitor; @@ -69,11 +70,14 @@ export class DashboardStateManager { savedDashboard, AppStateClass, hideWriteControls, + kibanaVersion, }: { savedDashboard: SavedObjectDashboard; AppStateClass: TAppStateClass; hideWriteControls: boolean; + kibanaVersion: string; }) { + this.kibanaVersion = kibanaVersion; this.savedDashboard = savedDashboard; this.hideWriteControls = hideWriteControls; @@ -85,7 +89,7 @@ export class DashboardStateManager { // appState based on the URL (the url trumps the defaults). This means if we update the state format at all and // want to handle BWC, we must not only migrate the data stored with saved Dashboard, but also any old state in the // url. - migrateAppState(this.appState); + migrateAppState(this.appState, kibanaVersion); this.isDirty = false; @@ -147,7 +151,8 @@ export class DashboardStateManager { } convertedPanelStateMap[panelState.explicitInput.id] = convertPanelStateToSavedDashboardPanel( - panelState + panelState, + this.kibanaVersion ); if ( diff --git a/src/legacy/core_plugins/kibana/public/dashboard/help_menu/help_menu.js b/src/legacy/core_plugins/kibana/public/dashboard/help_menu/help_menu.js index 56b2bd253381c..1b1a7f84c8131 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/help_menu/help_menu.js +++ b/src/legacy/core_plugins/kibana/public/dashboard/help_menu/help_menu.js @@ -17,26 +17,30 @@ * under the License. */ -import React, { Fragment, PureComponent } from 'react'; +import React, { PureComponent } from 'react'; import { EuiButton, EuiHorizontalRule, EuiSpacer } from '@elastic/eui'; -import { FormattedMessage } from '@kbn/i18n/react'; -import { ELASTIC_WEBSITE_URL, DOC_LINK_VERSION } from 'ui/documentation_links'; +import { FormattedMessage, I18nProvider } from '@kbn/i18n/react'; export class HelpMenu extends PureComponent { render() { return ( - - - - - - - + + <> + + + + + + + ); } } diff --git a/src/legacy/core_plugins/kibana/public/dashboard/help_menu/help_menu_util.js b/src/legacy/core_plugins/kibana/public/dashboard/help_menu/help_menu_util.js index aeabff2d97007..2dc8ce523a7da 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/help_menu/help_menu_util.js +++ b/src/legacy/core_plugins/kibana/public/dashboard/help_menu/help_menu_util.js @@ -21,9 +21,9 @@ import React from 'react'; import { render, unmountComponentAtNode } from 'react-dom'; import { HelpMenu } from './help_menu'; -export function addHelpMenuToAppChrome(chrome) { - chrome.helpExtension.set(domElement => { - render(, domElement); +export function addHelpMenuToAppChrome(chrome, docLinks) { + chrome.setHelpExtension(domElement => { + render(, domElement); return () => { unmountComponentAtNode(domElement); }; diff --git a/src/legacy/core_plugins/kibana/public/dashboard/index.js b/src/legacy/core_plugins/kibana/public/dashboard/index.js deleted file mode 100644 index 712e05c92e5e8..0000000000000 --- a/src/legacy/core_plugins/kibana/public/dashboard/index.js +++ /dev/null @@ -1,207 +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 './dashboard_app'; -import { i18n } from '@kbn/i18n'; -import './saved_dashboard/saved_dashboards'; -import './dashboard_config'; -import uiRoutes from 'ui/routes'; -import chrome from 'ui/chrome'; -import { wrapInI18nContext } from 'ui/i18n'; -import { toastNotifications } from 'ui/notify'; - -import dashboardTemplate from './dashboard_app.html'; -import dashboardListingTemplate from './listing/dashboard_listing_ng_wrapper.html'; - -import { DashboardConstants, createDashboardEditUrl } from './dashboard_constants'; -import { InvalidJSONProperty, SavedObjectNotFound } from '../../../../../plugins/kibana_utils/public'; -import { FeatureCatalogueRegistryProvider, FeatureCatalogueCategory } from 'ui/registry/feature_catalogue'; -import { SavedObjectsClientProvider } from 'ui/saved_objects'; -import { SavedObjectRegistryProvider } from 'ui/saved_objects/saved_object_registry'; -import { DashboardListing, EMPTY_FILTER } from './listing/dashboard_listing'; -import { uiModules } from 'ui/modules'; -import 'ui/capabilities/route_setup'; -import { addHelpMenuToAppChrome } from './help_menu/help_menu_util'; - -import { npStart } from 'ui/new_platform'; - -// load directives -import '../../../data/public'; - -const app = uiModules.get('app/dashboard', [ - 'ngRoute', - 'react', -]); - -app.directive('dashboardListing', function (reactDirective) { - return reactDirective(wrapInI18nContext(DashboardListing)); -}); - -function createNewDashboardCtrl($scope) { - $scope.visitVisualizeAppLinkText = i18n.translate('kbn.dashboard.visitVisualizeAppLinkText', { - defaultMessage: 'visit the Visualize app', - }); - addHelpMenuToAppChrome(chrome); -} - -uiRoutes - .defaults(/dashboard/, { - requireDefaultIndex: true, - requireUICapability: 'dashboard.show', - badge: uiCapabilities => { - if (uiCapabilities.dashboard.showWriteControls) { - return undefined; - } - - return { - text: i18n.translate('kbn.dashboard.badge.readOnly.text', { - defaultMessage: 'Read only', - }), - tooltip: i18n.translate('kbn.dashboard.badge.readOnly.tooltip', { - defaultMessage: 'Unable to save dashboards', - }), - iconType: 'glasses' - }; - } - }) - .when(DashboardConstants.LANDING_PAGE_PATH, { - template: dashboardListingTemplate, - controller($injector, $location, $scope, Private, config) { - const services = Private(SavedObjectRegistryProvider).byLoaderPropertiesName; - const kbnUrl = $injector.get('kbnUrl'); - const dashboardConfig = $injector.get('dashboardConfig'); - - $scope.listingLimit = config.get('savedObjects:listingLimit'); - $scope.create = () => { - kbnUrl.redirect(DashboardConstants.CREATE_NEW_DASHBOARD_URL); - }; - $scope.find = (search) => { - return services.dashboards.find(search, $scope.listingLimit); - }; - $scope.editItem = ({ id }) => { - kbnUrl.redirect(`${createDashboardEditUrl(id)}?_a=(viewMode:edit)`); - }; - $scope.getViewUrl = ({ id }) => { - return chrome.addBasePath(`#${createDashboardEditUrl(id)}`); - }; - $scope.delete = (dashboards) => { - return services.dashboards.delete(dashboards.map(d => d.id)); - }; - $scope.hideWriteControls = dashboardConfig.getHideWriteControls(); - $scope.initialFilter = ($location.search()).filter || EMPTY_FILTER; - chrome.breadcrumbs.set([{ - text: i18n.translate('kbn.dashboard.dashboardBreadcrumbsTitle', { - defaultMessage: 'Dashboards', - }), - }]); - addHelpMenuToAppChrome(chrome); - }, - resolve: { - dash: function ($route, Private, redirectWhenMissing, kbnUrl) { - const savedObjectsClient = Private(SavedObjectsClientProvider); - const title = $route.current.params.title; - if (title) { - return savedObjectsClient.find({ - search: `"${title}"`, - search_fields: 'title', - type: 'dashboard', - }).then(results => { - // The search isn't an exact match, lets see if we can find a single exact match to use - const matchingDashboards = results.savedObjects.filter( - dashboard => dashboard.attributes.title.toLowerCase() === title.toLowerCase()); - if (matchingDashboards.length === 1) { - kbnUrl.redirect(createDashboardEditUrl(matchingDashboards[0].id)); - } else { - kbnUrl.redirect(`${DashboardConstants.LANDING_PAGE_PATH}?filter="${title}"`); - } - throw uiRoutes.WAIT_FOR_URL_CHANGE_TOKEN; - }).catch(redirectWhenMissing({ - 'dashboard': DashboardConstants.LANDING_PAGE_PATH - })); - } - } - } - }) - .when(DashboardConstants.CREATE_NEW_DASHBOARD_URL, { - template: dashboardTemplate, - controller: createNewDashboardCtrl, - requireUICapability: 'dashboard.createNew', - resolve: { - dash: function (savedDashboards, redirectWhenMissing) { - return savedDashboards.get() - .catch(redirectWhenMissing({ - 'dashboard': DashboardConstants.LANDING_PAGE_PATH - })); - } - } - }) - .when(createDashboardEditUrl(':id'), { - template: dashboardTemplate, - controller: createNewDashboardCtrl, - resolve: { - dash: function (savedDashboards, $route, redirectWhenMissing, kbnUrl, AppState) { - const id = $route.current.params.id; - - return savedDashboards.get(id) - .then((savedDashboard) => { - npStart.core.chrome.recentlyAccessed.add(savedDashboard.getFullPath(), savedDashboard.title, id); - return savedDashboard; - }) - .catch((error) => { - // A corrupt dashboard was detected (e.g. with invalid JSON properties) - if (error instanceof InvalidJSONProperty) { - toastNotifications.addDanger(error.message); - kbnUrl.redirect(DashboardConstants.LANDING_PAGE_PATH); - return; - } - - // Preserve BWC of v5.3.0 links for new, unsaved dashboards. - // See https://github.com/elastic/kibana/issues/10951 for more context. - if (error instanceof SavedObjectNotFound && id === 'create') { - // Note "new AppState" is necessary so the state in the url is preserved through the redirect. - kbnUrl.redirect(DashboardConstants.CREATE_NEW_DASHBOARD_URL, {}, new AppState()); - toastNotifications.addWarning(i18n.translate('kbn.dashboard.urlWasRemovedInSixZeroWarningMessage', - { defaultMessage: 'The url "dashboard/create" was removed in 6.0. Please update your bookmarks.' } - )); - } else { - throw error; - } - }) - .catch(redirectWhenMissing({ - 'dashboard': DashboardConstants.LANDING_PAGE_PATH - })); - } - } - }); - -FeatureCatalogueRegistryProvider.register(() => { - return { - id: 'dashboard', - title: i18n.translate('kbn.dashboard.featureCatalogue.dashboardTitle', { - defaultMessage: 'Dashboard', - }), - description: i18n.translate('kbn.dashboard.featureCatalogue.dashboardDescription', { - defaultMessage: 'Display and share a collection of visualizations and saved searches.', - }), - icon: 'dashboardApp', - path: `/app/kibana#${DashboardConstants.LANDING_PAGE_PATH}`, - showOnHomePage: true, - category: FeatureCatalogueCategory.DATA - }; -}); diff --git a/src/legacy/core_plugins/kibana/public/dashboard/index.ts b/src/legacy/core_plugins/kibana/public/dashboard/index.ts new file mode 100644 index 0000000000000..1d0c8d34f239b --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/dashboard/index.ts @@ -0,0 +1,72 @@ +/* + * 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 { FeatureCatalogueRegistryProvider } from 'ui/registry/feature_catalogue'; +import { npSetup, npStart } from 'ui/new_platform'; +import { SavedObjectRegistryProvider } from 'ui/saved_objects'; +import { docTitle } from 'ui/doc_title/doc_title'; +import chrome from 'ui/chrome'; +import { IPrivate } from 'ui/private'; +import { ShareContextMenuExtensionsRegistryProvider } from 'ui/share'; +import { DashboardPlugin, LegacyAngularInjectedDependencies } from './plugin'; +import { start as data } from '../../../data/public/legacy'; +import { localApplicationService } from '../local_application_service'; +import { start as embeddables } from '../../../embeddable_api/public/np_ready/public/legacy'; +import { start as navigation } from '../../../navigation/public/legacy'; +import './saved_dashboard/saved_dashboards'; +import './dashboard_config'; + +/** + * Get dependencies relying on the global angular context. + * They also have to get resolved together with the legacy imports above + */ +async function getAngularDependencies(): Promise { + const injector = await chrome.dangerouslyGetActiveInjector(); + + const Private = injector.get('Private'); + + const shareContextMenuExtensions = Private(ShareContextMenuExtensionsRegistryProvider); + const savedObjectRegistry = Private(SavedObjectRegistryProvider); + + return { + shareContextMenuExtensions, + dashboardConfig: injector.get('dashboardConfig'), + savedObjectRegistry, + savedDashboards: injector.get('savedDashboards'), + }; +} + +(async () => { + const instance = new DashboardPlugin(); + instance.setup(npSetup.core, { + feature_catalogue: npSetup.plugins.feature_catalogue, + __LEGACY: { + localApplicationService, + getAngularDependencies, + FeatureCatalogueRegistryProvider, + docTitle, + }, + }); + instance.start(npStart.core, { + data, + npData: npStart.plugins.data, + embeddables, + navigation, + }); +})(); diff --git a/src/legacy/core_plugins/kibana/public/dashboard/lib/embeddable_saved_object_converters.test.ts b/src/legacy/core_plugins/kibana/public/dashboard/lib/embeddable_saved_object_converters.test.ts index 99bb6b115b985..3f04cad4f322b 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/lib/embeddable_saved_object_converters.test.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/lib/embeddable_saved_object_converters.test.ts @@ -48,7 +48,7 @@ test('convertSavedDashboardPanelToPanelState', () => { version: '7.0.0', }; - expect(convertSavedDashboardPanelToPanelState(savedDashboardPanel, true)).toEqual({ + expect(convertSavedDashboardPanelToPanelState(savedDashboardPanel)).toEqual({ gridData: { x: 0, y: 0, @@ -82,7 +82,7 @@ test('convertSavedDashboardPanelToPanelState does not include undefined id', () version: '7.0.0', }; - const converted = convertSavedDashboardPanelToPanelState(savedDashboardPanel, false); + const converted = convertSavedDashboardPanelToPanelState(savedDashboardPanel); expect(converted.hasOwnProperty('savedObjectId')).toBe(false); }); @@ -103,7 +103,7 @@ test('convertPanelStateToSavedDashboardPanel', () => { type: 'search', }; - expect(convertPanelStateToSavedDashboardPanel(dashboardPanel)).toEqual({ + expect(convertPanelStateToSavedDashboardPanel(dashboardPanel, '6.3.0')).toEqual({ type: 'search', embeddableConfig: { something: 'hi!', @@ -137,6 +137,6 @@ test('convertPanelStateToSavedDashboardPanel will not add an undefined id when n type: 'search', }; - const converted = convertPanelStateToSavedDashboardPanel(dashboardPanel); + const converted = convertPanelStateToSavedDashboardPanel(dashboardPanel, '8.0.0'); expect(converted.hasOwnProperty('id')).toBe(false); }); diff --git a/src/legacy/core_plugins/kibana/public/dashboard/lib/embeddable_saved_object_converters.ts b/src/legacy/core_plugins/kibana/public/dashboard/lib/embeddable_saved_object_converters.ts index 4a3bc3b228106..2d42609e1e25f 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/lib/embeddable_saved_object_converters.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/lib/embeddable_saved_object_converters.ts @@ -18,12 +18,10 @@ */ import { omit } from 'lodash'; import { DashboardPanelState } from 'src/legacy/core_plugins/dashboard_embeddable_container/public/np_ready/public'; -import chrome from 'ui/chrome'; import { SavedDashboardPanel } from '../types'; export function convertSavedDashboardPanelToPanelState( - savedDashboardPanel: SavedDashboardPanel, - useMargins: boolean + savedDashboardPanel: SavedDashboardPanel ): DashboardPanelState { return { type: savedDashboardPanel.type, @@ -38,13 +36,14 @@ export function convertSavedDashboardPanelToPanelState( } export function convertPanelStateToSavedDashboardPanel( - panelState: DashboardPanelState + panelState: DashboardPanelState, + version: string ): SavedDashboardPanel { const customTitle: string | undefined = panelState.explicitInput.title ? (panelState.explicitInput.title as string) : undefined; return { - version: chrome.getKibanaVersion(), + version, type: panelState.type, gridData: panelState.gridData, panelIndex: panelState.explicitInput.id, diff --git a/src/legacy/core_plugins/kibana/public/dashboard/lib/migrate_app_state.test.ts b/src/legacy/core_plugins/kibana/public/dashboard/lib/migrate_app_state.test.ts index 10c27226300a5..1d1c844e17420 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/lib/migrate_app_state.test.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/lib/migrate_app_state.test.ts @@ -43,7 +43,7 @@ test('migrate app state from 6.0', async () => { getQueryParamName: () => 'a', save: mockSave, }; - migrateAppState(appState); + migrateAppState(appState, '8.0'); expect(appState.uiState).toBeUndefined(); const newPanel = (appState.panels[0] as unknown) as SavedDashboardPanel; @@ -80,7 +80,7 @@ test('migrate sort from 6.1', async () => { save: mockSave, useMargins: false, }; - migrateAppState(appState); + migrateAppState(appState, '8.0'); expect(appState.uiState).toBeUndefined(); const newPanel = (appState.panels[0] as unknown) as SavedDashboardPanel; @@ -112,7 +112,7 @@ test('migrates 6.0 even when uiState does not exist', async () => { getQueryParamName: () => 'a', save: mockSave, }; - migrateAppState(appState); + migrateAppState(appState, '8.0'); expect((appState as any).uiState).toBeUndefined(); const newPanel = (appState.panels[0] as unknown) as SavedDashboardPanel; @@ -147,7 +147,7 @@ test('6.2 migration adjusts w & h without margins', async () => { save: mockSave, useMargins: false, }; - migrateAppState(appState); + migrateAppState(appState, '8.0'); expect((appState as any).uiState).toBeUndefined(); const newPanel = (appState.panels[0] as unknown) as SavedDashboardPanel; @@ -184,7 +184,7 @@ test('6.2 migration adjusts w & h with margins', async () => { save: mockSave, useMargins: true, }; - migrateAppState(appState); + migrateAppState(appState, '8.0'); expect((appState as any).uiState).toBeUndefined(); const newPanel = (appState.panels[0] as unknown) as SavedDashboardPanel; diff --git a/src/legacy/core_plugins/kibana/public/dashboard/lib/migrate_app_state.ts b/src/legacy/core_plugins/kibana/public/dashboard/lib/migrate_app_state.ts index 9bd93029f06d8..c4ad754548459 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/lib/migrate_app_state.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/lib/migrate_app_state.ts @@ -18,7 +18,6 @@ */ import semver from 'semver'; -import chrome from 'ui/chrome'; import { i18n } from '@kbn/i18n'; import { createUiStatsReporter, METRIC_TYPE } from '../../../../ui_metric/public'; import { @@ -37,7 +36,10 @@ import { migratePanelsTo730 } from '../migrations/migrate_to_730_panels'; * * Once we hit a major version, we can remove support for older style URLs and get rid of this logic. */ -export function migrateAppState(appState: { [key: string]: unknown } | DashboardAppState) { +export function migrateAppState( + appState: { [key: string]: unknown } | DashboardAppState, + kibanaVersion: string +) { if (!appState.panels) { throw new Error( i18n.translate('kbn.dashboard.panel.invalidData', { @@ -73,7 +75,7 @@ export function migrateAppState(appState: { [key: string]: unknown } | Dashboard | SavedDashboardPanel630 | SavedDashboardPanel640To720 >, - chrome.getKibanaVersion(), + kibanaVersion, appState.useMargins, appState.uiState ); diff --git a/src/legacy/core_plugins/kibana/public/dashboard/plugin.ts b/src/legacy/core_plugins/kibana/public/dashboard/plugin.ts new file mode 100644 index 0000000000000..d0741db58cd55 --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/dashboard/plugin.ts @@ -0,0 +1,154 @@ +/* + * 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 { + App, + CoreSetup, + CoreStart, + LegacyCoreStart, + Plugin, + SavedObjectsClientContract, +} from 'kibana/public'; +import { i18n } from '@kbn/i18n'; +import { RenderDeps } from './render_app'; +import { LocalApplicationService } from '../local_application_service'; +import { DataStart } from '../../../data/public'; +import { DataPublicPluginStart as NpDataStart } from '../../../../../plugins/data/public'; +import { EmbeddablePublicPlugin } from '../../../../../plugins/embeddable/public'; +import { Storage } from '../../../../../plugins/kibana_utils/public'; +import { NavigationStart } from '../../../navigation/public'; +import { DashboardConstants } from './dashboard_constants'; +import { + FeatureCatalogueCategory, + FeatureCatalogueSetup, +} from '../../../../../plugins/feature_catalogue/public'; + +export interface LegacyAngularInjectedDependencies { + shareContextMenuExtensions: any; + dashboardConfig: any; + savedObjectRegistry: any; + savedDashboards: any; +} + +export interface DashboardPluginStartDependencies { + data: DataStart; + npData: NpDataStart; + embeddables: ReturnType; + navigation: NavigationStart; +} + +export interface DashboardPluginSetupDependencies { + __LEGACY: { + getAngularDependencies: () => Promise; + localApplicationService: LocalApplicationService; + FeatureCatalogueRegistryProvider: any; + docTitle: any; + }; + feature_catalogue: FeatureCatalogueSetup; +} + +export class DashboardPlugin implements Plugin { + private startDependencies: { + dataStart: DataStart; + npDataStart: NpDataStart; + savedObjectsClient: SavedObjectsClientContract; + embeddables: ReturnType; + navigation: NavigationStart; + } | null = null; + + public setup( + core: CoreSetup, + { + __LEGACY: { + localApplicationService, + getAngularDependencies, + FeatureCatalogueRegistryProvider, + ...legacyServices + }, + feature_catalogue, + }: DashboardPluginSetupDependencies + ) { + const app: App = { + id: '', + title: 'Dashboards', + mount: async ({ core: contextCore }, params) => { + if (this.startDependencies === null) { + throw new Error('not started yet'); + } + const { + dataStart, + savedObjectsClient, + embeddables, + navigation, + npDataStart, + } = this.startDependencies; + const angularDependencies = await getAngularDependencies(); + const deps: RenderDeps = { + core: contextCore as LegacyCoreStart, + ...legacyServices, + ...angularDependencies, + navigation, + dataStart, + npDataStart, + indexPatterns: dataStart.indexPatterns.indexPatterns, + savedObjectsClient, + chrome: contextCore.chrome, + addBasePath: contextCore.http.basePath.prepend, + uiSettings: contextCore.uiSettings, + savedQueryService: dataStart.search.services.savedQueryService, + embeddables, + dashboardCapabilities: contextCore.application.capabilities.dashboard, + localStorage: new Storage(localStorage), + sessionStorage: new Storage(sessionStorage), + }; + const { renderApp } = await import('./render_app'); + return renderApp(params.element, params.appBasePath, deps); + }, + }; + localApplicationService.register({ ...app, id: 'dashboard' }); + localApplicationService.register({ ...app, id: 'dashboards' }); + + feature_catalogue.register({ + id: 'dashboard', + title: i18n.translate('kbn.dashboard.featureCatalogue.dashboardTitle', { + defaultMessage: 'Dashboard', + }), + description: i18n.translate('kbn.dashboard.featureCatalogue.dashboardDescription', { + defaultMessage: 'Display and share a collection of visualizations and saved searches.', + }), + icon: 'dashboardApp', + path: `/app/kibana#${DashboardConstants.LANDING_PAGE_PATH}`, + showOnHomePage: true, + category: FeatureCatalogueCategory.DATA, + }); + } + + start( + { savedObjects: { client: savedObjectsClient } }: CoreStart, + { data: dataStart, embeddables, navigation, npData }: DashboardPluginStartDependencies + ) { + this.startDependencies = { + dataStart, + npDataStart: npData, + savedObjectsClient, + embeddables, + navigation, + }; + } +} diff --git a/src/legacy/core_plugins/kibana/public/dashboard/render_app.ts b/src/legacy/core_plugins/kibana/public/dashboard/render_app.ts new file mode 100644 index 0000000000000..ca219e6a74d95 --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/dashboard/render_app.ts @@ -0,0 +1,260 @@ +/* + * 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 { EuiConfirmModal } from '@elastic/eui'; +import angular, { IModule } from 'angular'; +import { IPrivate } from 'ui/private'; +import { State } from 'ui/state_management/state'; +import { i18nDirective, i18nFilter, I18nProvider } from '@kbn/i18n/angular'; +// @ts-ignore +import { GlobalStateProvider } from 'ui/state_management/global_state'; +// @ts-ignore +import { StateManagementConfigProvider } from 'ui/state_management/config_provider'; +// @ts-ignore +import { AppStateProvider } from 'ui/state_management/app_state'; +// @ts-ignore +import { PrivateProvider } from 'ui/private/private'; +// @ts-ignore +import { EventsProvider } from 'ui/events'; +// @ts-ignore +import { PersistedState } from 'ui/persisted_state'; +// @ts-ignore +import { createTopNavDirective, createTopNavHelper } from 'ui/kbn_top_nav/kbn_top_nav'; +// @ts-ignore +import { PromiseServiceCreator } from 'ui/promises/promises'; +// @ts-ignore +import { KbnUrlProvider, RedirectWhenMissingProvider } from 'ui/url'; +// @ts-ignore +import { confirmModalFactory } from 'ui/modals/confirm_modal'; +import { + AppMountContext, + ChromeStart, + LegacyCoreStart, + SavedObjectsClientContract, + UiSettingsClientContract, +} from 'kibana/public'; +import { configureAppAngularModule } from 'ui/legacy_compat'; +import { Storage } from '../../../../../plugins/kibana_utils/public'; + +// @ts-ignore +import { initDashboardApp } from './app'; +import { + createApplyFiltersPopoverDirective, + createApplyFiltersPopoverHelper, + createFilterBarDirective, + createFilterBarHelper, + DataStart, +} from '../../../data/public'; +import { SavedQueryService } from '../../../data/public/search/search_bar/lib/saved_query_service'; +import { EmbeddablePublicPlugin } from '../../../../../plugins/embeddable/public'; +import { NavigationStart } from '../../../navigation/public'; +import { DataPublicPluginStart as NpDataStart } from '../../../../../plugins/data/public'; + +export interface RenderDeps { + core: LegacyCoreStart; + indexPatterns: DataStart['indexPatterns']['indexPatterns']; + dataStart: DataStart; + npDataStart: NpDataStart; + navigation: NavigationStart; + shareContextMenuExtensions: any; + savedObjectsClient: SavedObjectsClientContract; + savedObjectRegistry: any; + dashboardConfig: any; + savedDashboards: any; + dashboardCapabilities: any; + docTitle: any; + uiSettings: UiSettingsClientContract; + chrome: ChromeStart; + addBasePath: (path: string) => string; + savedQueryService: SavedQueryService; + embeddables: ReturnType; + localStorage: Storage; + sessionStorage: Storage; +} + +let angularModuleInstance: IModule | null = null; + +export const renderApp = (element: HTMLElement, appBasePath: string, deps: RenderDeps) => { + if (!angularModuleInstance) { + angularModuleInstance = createLocalAngularModule(deps.core, deps.navigation); + // global routing stuff + configureAppAngularModule(angularModuleInstance, deps.core as LegacyCoreStart, true); + // custom routing stuff + initDashboardApp(angularModuleInstance, deps); + } + const $injector = mountDashboardApp(appBasePath, element); + // const hasGlobalURLState = window.location.hash.includes('_g='); + // // only inject global state if there is none in the url itself (that takes precedence) + // if (!hasGlobalURLState) { + // const globalStateStuff = deps.sessionStorage.get('oss-kibana-cross-app-state') || {}; + // const globalState = $injector.get('globalState'); + // globalState.time = deps.dataStart.timefilter.timefilter.getTime(); + // globalState.refreshInterval = deps.dataStart.timefilter.timefilter.getRefreshInterval(); + // Object.keys(globalStateStuff).forEach(key => { + // globalState[key] = globalStateStuff[key]; + // }); + // globalState.save(); + // } + return () => { + const currentGlobalState = $injector.get('globalState'); + deps.sessionStorage.set('oss-kibana-cross-app-state', currentGlobalState.toObject()); + $injector.get('$rootScope').$destroy(); + }; +}; + +const mainTemplate = (basePath: string) => `
+ +
+
+`; + +const moduleName = 'app/dashboard'; + +const thirdPartyAngularDependencies = ['ngSanitize', 'ngRoute', 'react']; + +function mountDashboardApp(appBasePath: string, element: HTMLElement) { + const mountpoint = document.createElement('div'); + mountpoint.setAttribute('style', 'height: 100%'); + // eslint-disable-next-line + mountpoint.innerHTML = mainTemplate(appBasePath); + // bootstrap angular into detached element and attach it later to + // make angular-within-angular possible + const $injector = angular.bootstrap(mountpoint, [moduleName]); + // initialize global state handler + element.appendChild(mountpoint); + return $injector; +} + +function createLocalAngularModule(core: AppMountContext['core'], navigation: NavigationStart) { + createLocalI18nModule(); + createLocalPrivateModule(); + createLocalPromiseModule(); + createLocalConfigModule(core); + createLocalKbnUrlModule(); + createLocalStateModule(); + createLocalPersistedStateModule(); + createLocalTopNavModule(navigation); + createLocalConfirmModalModule(); + createLocalFilterBarModule(); + + const dashboardAngularModule = angular.module(moduleName, [ + ...thirdPartyAngularDependencies, + 'app/dashboard/Config', + 'app/dashboard/I18n', + 'app/dashboard/Private', + 'app/dashboard/PersistedState', + 'app/dashboard/TopNav', + 'app/dashboard/State', + 'app/dashboard/ConfirmModal', + 'app/dashboard/FilterBar', + ]); + return dashboardAngularModule; +} + +function createLocalConfirmModalModule() { + angular + .module('app/dashboard/ConfirmModal', ['react']) + .factory('confirmModal', confirmModalFactory) + .directive('confirmModal', reactDirective => reactDirective(EuiConfirmModal)); +} + +function createLocalStateModule() { + angular + .module('app/dashboard/State', [ + 'app/dashboard/Private', + 'app/dashboard/Config', + 'app/dashboard/KbnUrl', + 'app/dashboard/Promise', + 'app/dashboard/PersistedState', + ]) + .factory('AppState', function(Private: any) { + return Private(AppStateProvider); + }) + .service('getAppState', function(Private: any) { + return Private(AppStateProvider).getAppState; + }) + .service('globalState', function(Private: any) { + return Private(GlobalStateProvider); + }); +} + +function createLocalPersistedStateModule() { + angular + .module('app/dashboard/PersistedState', ['app/dashboard/Private', 'app/dashboard/Promise']) + .factory('PersistedState', (Private: IPrivate) => { + const Events = Private(EventsProvider); + return class AngularPersistedState extends PersistedState { + constructor(value: any, path: any) { + super(value, path, Events); + } + }; + }); +} + +function createLocalKbnUrlModule() { + angular + .module('app/dashboard/KbnUrl', ['app/dashboard/Private', 'ngRoute']) + .service('kbnUrl', (Private: IPrivate) => Private(KbnUrlProvider)) + .service('redirectWhenMissing', (Private: IPrivate) => Private(RedirectWhenMissingProvider)); +} + +function createLocalConfigModule(core: AppMountContext['core']) { + angular + .module('app/dashboard/Config', ['app/dashboard/Private']) + .provider('stateManagementConfig', StateManagementConfigProvider) + .provider('config', () => { + return { + $get: () => ({ + get: core.uiSettings.get.bind(core.uiSettings), + }), + }; + }); +} + +function createLocalPromiseModule() { + angular.module('app/dashboard/Promise', []).service('Promise', PromiseServiceCreator); +} + +function createLocalPrivateModule() { + angular.module('app/dashboard/Private', []).provider('Private', PrivateProvider); +} + +function createLocalTopNavModule(navigation: NavigationStart) { + angular + .module('app/dashboard/TopNav', ['react']) + .directive('kbnTopNav', createTopNavDirective) + .directive('kbnTopNavHelper', createTopNavHelper(navigation.ui)); +} + +function createLocalFilterBarModule() { + angular + .module('app/dashboard/FilterBar', ['react']) + .directive('filterBar', createFilterBarDirective) + .directive('filterBarHelper', createFilterBarHelper) + .directive('applyFiltersPopover', createApplyFiltersPopoverDirective) + .directive('applyFiltersPopoverHelper', createApplyFiltersPopoverHelper); +} + +function createLocalI18nModule() { + angular + .module('app/dashboard/I18n', []) + .provider('i18n', I18nProvider) + .filter('i18n', i18nFilter) + .directive('i18nId', i18nDirective); +} diff --git a/src/legacy/core_plugins/kibana/public/discover/angular/discover.js b/src/legacy/core_plugins/kibana/public/discover/angular/discover.js index ed5049aa912e0..cd8aaaa27faf3 100644 --- a/src/legacy/core_plugins/kibana/public/discover/angular/discover.js +++ b/src/legacy/core_plugins/kibana/public/discover/angular/discover.js @@ -59,9 +59,11 @@ import { vislibSeriesResponseHandlerProvider, VisProvider, SavedObjectSaveModal, + ensureDefaultIndexPattern, } from '../kibana_services'; const { + core, chrome, docTitle, FilterBarQueryFilterProvider, @@ -93,7 +95,6 @@ const app = uiModules.get('apps/discover', [ uiRoutes .defaults(/^\/discover(\/|$)/, { - requireDefaultIndex: true, requireUICapability: 'discover.show', k7Breadcrumbs: ($route, $injector) => $injector.invoke( @@ -121,50 +122,53 @@ uiRoutes template: indexTemplate, reloadOnSearch: false, resolve: { - ip: function (Promise, indexPatterns, config, Private) { + savedObjects: function (Promise, indexPatterns, config, Private, $rootScope, kbnUrl, redirectWhenMissing, savedSearches, $route) { const State = Private(StateProvider); - return indexPatterns.getCache().then((savedObjects)=> { - /** - * In making the indexPattern modifiable it was placed in appState. Unfortunately, - * the load order of AppState conflicts with the load order of many other things - * so in order to get the name of the index we should use, and to switch to the - * default if necessary, we parse the appState with a temporary State object and - * then destroy it immediatly after we're done - * - * @type {State} - */ - const state = new State('_a', {}); - - const specified = !!state.index; - const exists = _.findIndex(savedObjects, o => o.id === state.index) > -1; - const id = exists ? state.index : config.get('defaultIndex'); - state.destroy(); + const savedSearchId = $route.current.params.id; + return ensureDefaultIndexPattern(core, data, $rootScope, kbnUrl).then(() => { return Promise.props({ - list: savedObjects, - loaded: indexPatterns.get(id), - stateVal: state.index, - stateValFound: specified && exists + ip: indexPatterns.getCache().then((savedObjects) => { + /** + * In making the indexPattern modifiable it was placed in appState. Unfortunately, + * the load order of AppState conflicts with the load order of many other things + * so in order to get the name of the index we should use, and to switch to the + * default if necessary, we parse the appState with a temporary State object and + * then destroy it immediatly after we're done + * + * @type {State} + */ + const state = new State('_a', {}); + + const specified = !!state.index; + const exists = _.findIndex(savedObjects, o => o.id === state.index) > -1; + const id = exists ? state.index : config.get('defaultIndex'); + state.destroy(); + + return Promise.props({ + list: savedObjects, + loaded: indexPatterns.get(id), + stateVal: state.index, + stateValFound: specified && exists + }); + }), + savedSearch: savedSearches.get(savedSearchId) + .then((savedSearch) => { + if (savedSearchId) { + chrome.recentlyAccessed.add( + savedSearch.getFullPath(), + savedSearch.title, + savedSearchId); + } + return savedSearch; + }) + .catch(redirectWhenMissing({ + 'search': '/discover', + 'index-pattern': '/management/kibana/objects/savedSearches/' + $route.current.params.id + })) }); }); }, - savedSearch: function (redirectWhenMissing, savedSearches, $route) { - const savedSearchId = $route.current.params.id; - return savedSearches.get(savedSearchId) - .then((savedSearch) => { - if (savedSearchId) { - chrome.recentlyAccessed.add( - savedSearch.getFullPath(), - savedSearch.title, - savedSearchId); - } - return savedSearch; - }) - .catch(redirectWhenMissing({ - 'search': '/discover', - 'index-pattern': '/management/kibana/objects/savedSearches/' + $route.current.params.id - })); - } } }); @@ -229,7 +233,7 @@ function discoverController( }; // the saved savedSearch - const savedSearch = $route.current.locals.savedSearch; + const savedSearch = $route.current.locals.savedObjects.savedSearch; let abortController; $scope.$on('$destroy', () => { @@ -423,6 +427,7 @@ function discoverController( queryFilter.setFilters(filters); }; + // TODO this isnt used anymore here, just in visualize and dashboards $scope.applyFilters = filters => { const { timeRangeFilter, restOfFilters } = extractTimeFilter($scope.indexPattern.timeFieldName, filters); queryFilter.addFilters(restOfFilters); @@ -545,7 +550,7 @@ function discoverController( sampleSize: config.get('discover:sampleSize'), timefield: isDefaultTypeIndexPattern($scope.indexPattern) && $scope.indexPattern.timeFieldName, savedSearch: savedSearch, - indexPatternList: $route.current.locals.ip.list, + indexPatternList: $route.current.locals.savedObjects.ip.list, }; const shouldSearchOnPageLoad = () => { @@ -1060,7 +1065,7 @@ function discoverController( loaded: loadedIndexPattern, stateVal, stateValFound, - } = $route.current.locals.ip; + } = $route.current.locals.savedObjects.ip; const ownIndexPattern = $scope.searchSource.getOwnField('index'); @@ -1108,12 +1113,12 @@ function discoverController( // Block the UI from loading if the user has loaded a rollup index pattern but it isn't // supported. $scope.isUnsupportedIndexPattern = ( - !isDefaultTypeIndexPattern($route.current.locals.ip.loaded) - && !hasSearchStategyForIndexPattern($route.current.locals.ip.loaded) + !isDefaultTypeIndexPattern($route.current.locals.savedObjects.ip.loaded) + && !hasSearchStategyForIndexPattern($route.current.locals.savedObjects.ip.loaded) ); if ($scope.isUnsupportedIndexPattern) { - $scope.unsupportedIndexPatternType = $route.current.locals.ip.loaded.type; + $scope.unsupportedIndexPatternType = $route.current.locals.savedObjects.ip.loaded.type; return; } diff --git a/src/legacy/core_plugins/kibana/public/discover/breadcrumbs.ts b/src/legacy/core_plugins/kibana/public/discover/breadcrumbs.ts index 51e0dcba1cad0..6c3856932c96c 100644 --- a/src/legacy/core_plugins/kibana/public/discover/breadcrumbs.ts +++ b/src/legacy/core_plugins/kibana/public/discover/breadcrumbs.ts @@ -34,7 +34,7 @@ export function getSavedSearchBreadcrumbs($route: any) { return [ ...getRootBreadcrumbs(), { - text: $route.current.locals.savedSearch.id, + text: $route.current.locals.savedObjects.savedSearch.id, }, ]; } diff --git a/src/legacy/core_plugins/kibana/public/discover/kibana_services.ts b/src/legacy/core_plugins/kibana/public/discover/kibana_services.ts index b78d05e68acad..361b324d6c6a0 100644 --- a/src/legacy/core_plugins/kibana/public/discover/kibana_services.ts +++ b/src/legacy/core_plugins/kibana/public/discover/kibana_services.ts @@ -47,6 +47,7 @@ import * as docViewsRegistry from 'ui/registry/doc_views'; const services = { // new plattform + core: npStart.core, addBasePath: npStart.core.http.basePath.prepend, capabilities: npStart.core.application.capabilities, chrome: npStart.core.chrome, @@ -112,6 +113,7 @@ export { getUnhashableStatesProvider } from 'ui/state_management/state_hashing'; export { tabifyAggResponse } from 'ui/agg_response/tabify'; // @ts-ignore export { vislibSeriesResponseHandlerProvider } from 'ui/vis/response_handlers/vislib'; +export { ensureDefaultIndexPattern } from 'ui/legacy_compat'; // EXPORT types export { VisProvider } from 'ui/vis'; diff --git a/src/legacy/core_plugins/kibana/public/management/index.js b/src/legacy/core_plugins/kibana/public/management/index.js index c0949318e9253..83fc8e4db9b55 100644 --- a/src/legacy/core_plugins/kibana/public/management/index.js +++ b/src/legacy/core_plugins/kibana/public/management/index.js @@ -28,7 +28,6 @@ import { I18nContext } from 'ui/i18n'; import { uiModules } from 'ui/modules'; import appTemplate from './app.html'; import landingTemplate from './landing.html'; -import { capabilities } from 'ui/capabilities'; import { management, SidebarNav, MANAGEMENT_BREADCRUMB } from 'ui/management'; import { FeatureCatalogueRegistryProvider, FeatureCatalogueCategory } from 'ui/registry/feature_catalogue'; import { timefilter } from 'ui/timefilter'; @@ -50,13 +49,6 @@ uiRoutes redirectTo: '/management' }); -require('./route_setup/load_default')({ - whenMissingRedirectTo: () => { - const canManageIndexPatterns = capabilities.get().management.kibana.index_patterns; - return canManageIndexPatterns ? '/management/kibana/index_pattern' : '/home'; - } -}); - export function updateLandingPage(version) { const node = document.getElementById(LANDING_ID); if (!node) { diff --git a/src/legacy/core_plugins/kibana/public/management/route_setup/load_default.js b/src/legacy/core_plugins/kibana/public/management/route_setup/load_default.js deleted file mode 100644 index f797acbe8888e..0000000000000 --- a/src/legacy/core_plugins/kibana/public/management/route_setup/load_default.js +++ /dev/null @@ -1,110 +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 _ from 'lodash'; -import React from 'react'; -import { banners } from 'ui/notify'; -import { NoDefaultIndexPattern } from 'ui/index_patterns'; -import uiRoutes from 'ui/routes'; -import { - EuiCallOut, -} from '@elastic/eui'; -import { clearTimeout } from 'timers'; -import { i18n } from '@kbn/i18n'; - -let bannerId; -let timeoutId; - -function displayBanner() { - clearTimeout(timeoutId); - - // Avoid being hostile to new users who don't have an index pattern setup yet - // give them a friendly info message instead of a terse error message - bannerId = banners.set({ - id: bannerId, // initially undefined, but reused after first set - component: ( - - ) - }); - - // hide the message after the user has had a chance to acknowledge it -- so it doesn't permanently stick around - timeoutId = setTimeout(() => { - banners.remove(bannerId); - timeoutId = undefined; - }, 15000); -} - -// eslint-disable-next-line import/no-default-export -export default function (opts) { - opts = opts || {}; - const whenMissingRedirectTo = opts.whenMissingRedirectTo || null; - - uiRoutes - .addSetupWork(function loadDefaultIndexPattern(Promise, $route, config, indexPatterns) { - const route = _.get($route, 'current.$$route'); - - if (!route.requireDefaultIndex) { - return; - } - - return indexPatterns.getIds() - .then(function (patterns) { - let defaultId = config.get('defaultIndex'); - let defined = !!defaultId; - const exists = _.contains(patterns, defaultId); - - if (defined && !exists) { - config.remove('defaultIndex'); - defaultId = defined = false; - } - - if (!defined) { - // If there is any index pattern created, set the first as default - if (patterns.length >= 1) { - defaultId = patterns[0]; - config.set('defaultIndex', defaultId); - } else { - throw new NoDefaultIndexPattern(); - } - } - }); - }) - .afterWork( - // success - null, - - // failure - function (err, kbnUrl) { - const hasDefault = !(err instanceof NoDefaultIndexPattern); - if (hasDefault || !whenMissingRedirectTo) throw err; // rethrow - - kbnUrl.change(whenMissingRedirectTo()); - - displayBanner(); - } - ); -} diff --git a/src/legacy/ui/public/capabilities/route_setup.ts b/src/legacy/ui/public/capabilities/route_setup.ts deleted file mode 100644 index c7817b8cc5748..0000000000000 --- a/src/legacy/ui/public/capabilities/route_setup.ts +++ /dev/null @@ -1,38 +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 { get } from 'lodash'; -import chrome from 'ui/chrome'; -import uiRoutes from 'ui/routes'; -import { UICapabilities } from '.'; - -uiRoutes.addSetupWork( - (uiCapabilities: UICapabilities, kbnBaseUrl: string, $route: any, kbnUrl: any) => { - const route = get($route, 'current.$$route') as any; - if (!route.requireUICapability) { - return; - } - - if (!get(uiCapabilities, route.requireUICapability)) { - const url = chrome.addBasePath(`${kbnBaseUrl}#/home`); - kbnUrl.redirect(url); - throw uiRoutes.WAIT_FOR_URL_CHANGE_TOKEN; - } - } -); diff --git a/src/legacy/ui/public/routes/__tests__/_route_manager.js b/src/legacy/ui/public/routes/__tests__/_route_manager.js index d6d4c869b4b7e..450bb51f0b0c6 100644 --- a/src/legacy/ui/public/routes/__tests__/_route_manager.js +++ b/src/legacy/ui/public/routes/__tests__/_route_manager.js @@ -119,18 +119,6 @@ describe('routes/route_manager', function () { expect($rp.when.secondCall.args[1]).to.have.property('reloadOnSearch', false); expect($rp.when.lastCall.args[1]).to.have.property('reloadOnSearch', true); }); - - it('sets route.requireDefaultIndex to false by default', function () { - routes.when('/nothing-set'); - routes.when('/no-index-required', { requireDefaultIndex: false }); - routes.when('/index-required', { requireDefaultIndex: true }); - routes.config($rp); - - expect($rp.when.callCount).to.be(3); - expect($rp.when.firstCall.args[1]).to.have.property('requireDefaultIndex', false); - expect($rp.when.secondCall.args[1]).to.have.property('requireDefaultIndex', false); - expect($rp.when.lastCall.args[1]).to.have.property('requireDefaultIndex', true); - }); }); describe('#defaults()', () => { diff --git a/src/legacy/ui/public/state_management/state.js b/src/legacy/ui/public/state_management/state.js index b7623ab0fc5a5..eb853e3a5e33b 100644 --- a/src/legacy/ui/public/state_management/state.js +++ b/src/legacy/ui/public/state_management/state.js @@ -42,7 +42,7 @@ import { isStateHash, } from './state_storage'; -export function StateProvider(Private, $rootScope, $location, stateManagementConfig, config, kbnUrl) { +export function StateProvider(Private, $rootScope, $location, stateManagementConfig, config, kbnUrl, $injector) { const Events = Private(EventsProvider); createLegacyClass(State).inherits(Events); @@ -135,11 +135,16 @@ export function StateProvider(Private, $rootScope, $location, stateManagementCon return; } + const isDummyRoute = + $injector.has('$route') && + $injector.get('$route').current && + $injector.get('$route').current.outerAngularWrapperRoute; + let stash = this._readFromURL(); - // nothing to read from the url? save if ordered to persist + // nothing to read from the url? save if ordered to persist, but only if it's not on a wrapper route if (stash === null) { - if (this._persistAcrossApps) { + if (this._persistAcrossApps && !isDummyRoute) { return this.save(); } else { stash = {}; @@ -150,7 +155,7 @@ export function StateProvider(Private, $rootScope, $location, stateManagementCon // apply diff to state from stash, will change state in place via side effect const diffResults = applyDiff(this, stash); - if (diffResults.keys.length) { + if (!isDummyRoute && diffResults.keys.length) { this.emit('fetch_with_changes', diffResults.keys); } }; diff --git a/src/legacy/ui/public/timefilter/setup_router.test.js b/src/legacy/ui/public/timefilter/setup_router.test.js index 4bc797e5eff00..f229937c3b435 100644 --- a/src/legacy/ui/public/timefilter/setup_router.test.js +++ b/src/legacy/ui/public/timefilter/setup_router.test.js @@ -42,9 +42,14 @@ describe('registerTimefilterWithGlobalState()', () => { } }; + const rootScope = { + $on: jest.fn() + }; + registerTimefilterWithGlobalState( timefilter, - globalState + globalState, + rootScope, ); expect(setTime.mock.calls.length).toBe(2); diff --git a/src/legacy/ui/public/timefilter/setup_router.ts b/src/legacy/ui/public/timefilter/setup_router.ts index ffc8a1fca6c64..11beb121f58c0 100644 --- a/src/legacy/ui/public/timefilter/setup_router.ts +++ b/src/legacy/ui/public/timefilter/setup_router.ts @@ -41,49 +41,58 @@ export function getTimefilterConfig() { }; } -// Currently some parts of Kibana (index patterns, timefilter) rely on addSetupWork in the uiRouter -// and require it to be executed to properly function. -// This function is exposed for applications that do not use uiRoutes like APM -// Kibana issue https://github.com/elastic/kibana/issues/19110 tracks the removal of this dependency on uiRouter -export const registerTimefilterWithGlobalState = _.once( - (timefilter: TimefilterContract, globalState: any, $rootScope: IScope) => { - // settings have to be re-fetched here, to make sure that settings changed by overrideLocalDefault are taken into account. - const config = getTimefilterConfig(); - timefilter.setTime(_.defaults(globalState.time || {}, config.timeDefaults)); - timefilter.setRefreshInterval( - _.defaults(globalState.refreshInterval || {}, config.refreshIntervalDefaults) +export const registerTimefilterWithGlobalStateFactory = ( + timefilter: TimefilterContract, + globalState: any, + $rootScope: IScope +) => { + // settings have to be re-fetched here, to make sure that settings changed by overrideLocalDefault are taken into account. + const config = getTimefilterConfig(); + timefilter.setTime(_.defaults(globalState.time || {}, config.timeDefaults)); + timefilter.setRefreshInterval( + _.defaults(globalState.refreshInterval || {}, config.refreshIntervalDefaults) + ); + + globalState.on('fetch_with_changes', () => { + // clone and default to {} in one + const newTime: TimeRange = _.defaults({}, globalState.time, config.timeDefaults); + const newRefreshInterval: RefreshInterval = _.defaults( + {}, + globalState.refreshInterval, + config.refreshIntervalDefaults ); - globalState.on('fetch_with_changes', () => { - // clone and default to {} in one - const newTime: TimeRange = _.defaults({}, globalState.time, config.timeDefaults); - const newRefreshInterval: RefreshInterval = _.defaults( - {}, - globalState.refreshInterval, - config.refreshIntervalDefaults - ); + if (newTime) { + if (newTime.to) newTime.to = convertISO8601(newTime.to); + if (newTime.from) newTime.from = convertISO8601(newTime.from); + } - if (newTime) { - if (newTime.to) newTime.to = convertISO8601(newTime.to); - if (newTime.from) newTime.from = convertISO8601(newTime.from); - } + timefilter.setTime(newTime); + timefilter.setRefreshInterval(newRefreshInterval); + }); - timefilter.setTime(newTime); - timefilter.setRefreshInterval(newRefreshInterval); - }); + const updateGlobalStateWithTime = () => { + globalState.time = timefilter.getTime(); + globalState.refreshInterval = timefilter.getRefreshInterval(); + globalState.save(); + }; - const updateGlobalStateWithTime = () => { - globalState.time = timefilter.getTime(); - globalState.refreshInterval = timefilter.getRefreshInterval(); - globalState.save(); - }; + const sub1 = subscribeWithScope($rootScope, timefilter.getRefreshIntervalUpdate$(), { + next: updateGlobalStateWithTime, + }); - subscribeWithScope($rootScope, timefilter.getRefreshIntervalUpdate$(), { - next: updateGlobalStateWithTime, - }); + const sub2 = subscribeWithScope($rootScope, timefilter.getTimeUpdate$(), { + next: updateGlobalStateWithTime, + }); - subscribeWithScope($rootScope, timefilter.getTimeUpdate$(), { - next: updateGlobalStateWithTime, - }); - } -); + $rootScope.$on('$destroy', () => { + sub1.unsubscribe(); + sub2.unsubscribe(); + }); +}; + +// Currently some parts of Kibana (index patterns, timefilter) rely on addSetupWork in the uiRouter +// and require it to be executed to properly function. +// This function is exposed for applications that do not use uiRoutes like APM +// Kibana issue https://github.com/elastic/kibana/issues/19110 tracks the removal of this dependency on uiRouter +export const registerTimefilterWithGlobalState = _.once(registerTimefilterWithGlobalStateFactory); diff --git a/src/legacy/ui/public/visualize/loader/embedded_visualize_handler.test.ts b/src/legacy/ui/public/visualize/loader/embedded_visualize_handler.test.ts index c73f787457a03..2038fe2410c03 100644 --- a/src/legacy/ui/public/visualize/loader/embedded_visualize_handler.test.ts +++ b/src/legacy/ui/public/visualize/loader/embedded_visualize_handler.test.ts @@ -36,6 +36,8 @@ jest.mock('plugins/interpreter/interpreter', () => ({ }, })); +jest.mock('../../../../core_plugins/data/public/legacy', () => ({})); + jest.mock('../../../../core_plugins/interpreter/public/registries', () => ({ registries: { renderers: { diff --git a/x-pack/legacy/plugins/dashboard_mode/public/dashboard_viewer.js b/x-pack/legacy/plugins/dashboard_mode/public/dashboard_viewer.js index 87ac330e29106..284e66508e961 100644 --- a/x-pack/legacy/plugins/dashboard_mode/public/dashboard_viewer.js +++ b/x-pack/legacy/plugins/dashboard_mode/public/dashboard_viewer.js @@ -36,6 +36,8 @@ import 'ui/agg_response'; import 'ui/agg_types'; import 'leaflet'; import { npStart } from 'ui/new_platform'; +import { localApplicationService } from 'plugins/kibana/local_application_service'; + import { showAppRedirectNotification } from 'ui/notify'; import { DashboardConstants, createDashboardEditUrl } from 'plugins/kibana/dashboard/dashboard_constants'; @@ -43,6 +45,8 @@ import { DashboardConstants, createDashboardEditUrl } from 'plugins/kibana/dashb uiModules.get('kibana') .config(dashboardConfigProvider => dashboardConfigProvider.turnHideWriteControlsOn()); +localApplicationService.attachToAngular(routes); + routes.enable(); routes.otherwise({ redirectTo: defaultUrl() }); diff --git a/x-pack/legacy/plugins/graph/public/index.ts b/x-pack/legacy/plugins/graph/public/index.ts index 48420d403653f..833134abff0b6 100644 --- a/x-pack/legacy/plugins/graph/public/index.ts +++ b/x-pack/legacy/plugins/graph/public/index.ts @@ -20,6 +20,7 @@ import { SavedObjectRegistryProvider } from 'ui/saved_objects/saved_object_regis import { npSetup, npStart } from 'ui/new_platform'; import { Storage } from '../../../../../src/plugins/kibana_utils/public'; import { start as data } from '../../../../../src/legacy/core_plugins/data/public/legacy'; +import { start as navigation } from '../../../../../src/legacy/core_plugins/navigation/public/legacy'; import { GraphPlugin } from './plugin'; // @ts-ignore @@ -53,6 +54,7 @@ async function getAngularInjectedDependencies(): Promise; + navigation: NavigationStart; } export interface GraphPluginSetupDependencies { @@ -30,6 +32,7 @@ export interface GraphPluginStartDependencies { export class GraphPlugin implements Plugin { private dataStart: DataStart | null = null; + private navigationStart: NavigationStart | null = null; private npDataStart: ReturnType | null = null; private savedObjectsClient: SavedObjectsClientContract | null = null; private angularDependencies: LegacyAngularInjectedDependencies | null = null; @@ -42,6 +45,7 @@ export class GraphPlugin implements Plugin { const { renderApp } = await import('./render_app'); return renderApp({ ...params, + navigation: this.navigationStart!, npData: this.npDataStart!, savedObjectsClient: this.savedObjectsClient!, xpackInfo, @@ -66,9 +70,9 @@ export class GraphPlugin implements Plugin { start( core: CoreStart, - { data, npData, __LEGACY: { angularDependencies } }: GraphPluginStartDependencies + { data, npData, navigation, __LEGACY: { angularDependencies } }: GraphPluginStartDependencies ) { - // TODO is this really the right way? I though the app context would give us those + this.navigationStart = navigation; this.dataStart = data; this.npDataStart = npData; this.angularDependencies = angularDependencies; diff --git a/x-pack/legacy/plugins/graph/public/render_app.ts b/x-pack/legacy/plugins/graph/public/render_app.ts index a8a86f4d1f850..18cdf0ddd81b2 100644 --- a/x-pack/legacy/plugins/graph/public/render_app.ts +++ b/x-pack/legacy/plugins/graph/public/render_app.ts @@ -25,6 +25,7 @@ import { DataStart } from 'src/legacy/core_plugins/data/public'; import { AppMountContext, ChromeStart, + LegacyCoreStart, SavedObjectsClientContract, ToastsStart, UiSettingsClientContract, @@ -32,6 +33,7 @@ import { // @ts-ignore import { initGraphApp } from './app'; import { Plugin as DataPlugin } from '../../../../../src/plugins/data/public'; +import { NavigationStart } from '../../../../../src/legacy/core_plugins/navigation/public'; /** * These are dependencies of the Graph app besides the base dependencies @@ -44,6 +46,7 @@ export interface GraphDependencies extends LegacyAngularInjectedDependencies { appBasePath: string; capabilities: Record>; coreStart: AppMountContext['core']; + navigation: NavigationStart; chrome: ChromeStart; config: UiSettingsClientContract; toastNotifications: ToastsStart; @@ -75,8 +78,8 @@ export interface LegacyAngularInjectedDependencies { } export const renderApp = ({ appBasePath, element, ...deps }: GraphDependencies) => { - const graphAngularModule = createLocalAngularModule(deps.coreStart); - configureAppAngularModule(graphAngularModule); + const graphAngularModule = createLocalAngularModule(deps.navigation); + configureAppAngularModule(graphAngularModule, deps.coreStart as LegacyCoreStart, true); initGraphApp(graphAngularModule, deps); const $injector = mountGraphApp(appBasePath, element); return () => $injector.get('$rootScope').$destroy(); @@ -104,9 +107,9 @@ function mountGraphApp(appBasePath: string, element: HTMLElement) { return $injector; } -function createLocalAngularModule(core: AppMountContext['core']) { +function createLocalAngularModule(navigation: NavigationStart) { createLocalI18nModule(); - createLocalTopNavModule(); + createLocalTopNavModule(navigation); createLocalConfirmModalModule(); const graphAngularModule = angular.module(moduleName, [ @@ -125,11 +128,11 @@ function createLocalConfirmModalModule() { .directive('confirmModal', reactDirective => reactDirective(EuiConfirmModal)); } -function createLocalTopNavModule() { +function createLocalTopNavModule(navigation: NavigationStart) { angular .module('graphTopNav', ['react']) .directive('kbnTopNav', createTopNavDirective) - .directive('kbnTopNavHelper', createTopNavHelper); + .directive('kbnTopNavHelper', createTopNavHelper(navigation.ui)); } function createLocalI18nModule() { diff --git a/x-pack/legacy/plugins/grokdebugger/public/sections/grokdebugger/grokdebugger_route.js b/x-pack/legacy/plugins/grokdebugger/public/sections/grokdebugger/grokdebugger_route.js index 9f588bbe510ae..d63d669b0375e 100644 --- a/x-pack/legacy/plugins/grokdebugger/public/sections/grokdebugger/grokdebugger_route.js +++ b/x-pack/legacy/plugins/grokdebugger/public/sections/grokdebugger/grokdebugger_route.js @@ -5,7 +5,6 @@ */ import routes from 'ui/routes'; -import 'ui/capabilities/route_setup'; import { toastNotifications } from 'ui/notify'; import { xpackInfo } from 'plugins/xpack_main/services/xpack_info'; import template from './grokdebugger_route.html'; diff --git a/x-pack/legacy/plugins/searchprofiler/public/legacy.ts b/x-pack/legacy/plugins/searchprofiler/public/legacy.ts index 61dabe8ac7b05..b5962d7983986 100644 --- a/x-pack/legacy/plugins/searchprofiler/public/legacy.ts +++ b/x-pack/legacy/plugins/searchprofiler/public/legacy.ts @@ -8,7 +8,6 @@ import { npSetup } from 'ui/new_platform'; import { I18nContext } from 'ui/i18n'; import uiRoutes from 'ui/routes'; -import 'ui/capabilities/route_setup'; // @ts-ignore import { xpackInfo } from 'plugins/xpack_main/services/xpack_info'; // @ts-ignore From 233a440a44dc7639d01999a95e66468674ade583 Mon Sep 17 00:00:00 2001 From: maryia-lapata Date: Tue, 5 Nov 2019 18:12:35 +0300 Subject: [PATCH 069/132] Register embeddableFactory in start --- .../kibana/public/visualize/index.ts | 9 ++-- .../public/visualize/kibana_services.ts | 7 +-- .../kibana/public/visualize/plugin.ts | 43 ++++++++++++++----- 3 files changed, 40 insertions(+), 19 deletions(-) diff --git a/src/legacy/core_plugins/kibana/public/visualize/index.ts b/src/legacy/core_plugins/kibana/public/visualize/index.ts index c33860a7bebf1..3852773b21468 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/index.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/index.ts @@ -35,10 +35,7 @@ import { ShareContextMenuExtensionsRegistryProvider } from 'ui/share'; import { VisualizePlugin, LegacyAngularInjectedDependencies } from './plugin'; import { localApplicationService } from '../local_application_service'; import { start as dataStart } from '../../../data/public/legacy'; -import { - start as embeddables, - setup as embeddableSetup, -} from '../../../embeddable_api/public/np_ready/public/legacy'; +import { start as embeddables } from '../../../embeddable_api/public/np_ready/public/legacy'; import { start as navigation } from '../../../navigation/public/legacy'; import { start as visualizations } from '../../../visualizations/public/np_ready/public/legacy'; @@ -72,7 +69,6 @@ async function getAngularDependencies(): Promise { const instance = new VisualizePlugin(); instance.setup(npSetup.core, { - embeddableSetup, __LEGACY: { docTitle, getAngularDependencies, @@ -80,11 +76,12 @@ async function getAngularDependencies(): Promise | null = null; +export function setServices(newServices: Partial) { + services = { ...services, ...newServices }; } export function getServices() { diff --git a/src/legacy/core_plugins/kibana/public/visualize/plugin.ts b/src/legacy/core_plugins/kibana/public/visualize/plugin.ts index c7d2dc990990a..937c4ec194a07 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/plugin.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/plugin.ts @@ -46,8 +46,6 @@ import { VISUALIZE_EMBEDDABLE_TYPE } from './embeddable/constants'; import { VisualizeConstants } from './visualize_constants'; import { setServices, VisualizeKibanaServices, DocTitle } from './kibana_services'; -import { start as visualizationsStart } from '../../../visualizations/public/np_ready/public/legacy'; - export interface LegacyAngularInjectedDependencies { chromeLegacy: any; editorTypes: any; @@ -65,10 +63,10 @@ export interface VisualizePluginStartDependencies { embeddables: ReturnType; navigation: NavigationStart; visualizations: VisualizationsStart; + getAngularDependencies: () => Promise; } export interface VisualizePluginSetupDependencies { - embeddableSetup: ReturnType; __LEGACY: { getAngularDependencies: () => Promise; localApplicationService: LocalApplicationService; @@ -90,7 +88,6 @@ export class VisualizePlugin implements Plugin { public async setup( core: CoreSetup, { - embeddableSetup, __LEGACY: { localApplicationService, getAngularDependencies, @@ -135,6 +132,7 @@ export class VisualizePlugin implements Plugin { navigation, savedObjectsClient, savedQueryService: dataStart.search.services.savedQueryService, + sessionStorage: new Storage(sessionStorage), toastNotifications: contextCore.notifications.toasts, uiSettings: contextCore.uiSettings, visualizeCapabilities: contextCore.application.capabilities.visualize, @@ -164,14 +162,27 @@ export class VisualizePlugin implements Plugin { localApplicationService.register({ ...app, id: 'visualize' }); VisEditorTypesRegistryProvider.register(defaultEditor); - - const embeddableFactory = new VisualizeEmbeddableFactory(visualizationsStart.types); - embeddableSetup.registerEmbeddableFactory(VISUALIZE_EMBEDDABLE_TYPE, embeddableFactory); } - start( - { savedObjects: { client: savedObjectsClient } }: CoreStart, - { dataStart, embeddables, navigation, visualizations, npData }: VisualizePluginStartDependencies + async start( + { + savedObjects: { client: savedObjectsClient }, + uiSettings, + http: { + basePath: { prepend: addBasePath }, + }, + application: { + capabilities: { visualize: visualizeCapabilities }, + }, + }: CoreStart, + { + dataStart, + embeddables, + navigation, + visualizations, + npData, + getAngularDependencies, + }: VisualizePluginStartDependencies ) { this.startDependencies = { dataStart, @@ -181,5 +192,17 @@ export class VisualizePlugin implements Plugin { savedObjectsClient, visualizations, }; + + const { savedVisualizations } = await getAngularDependencies(); + setServices({ + visualizations, + uiSettings, + addBasePath, + savedObjectsClient, + savedVisualizations, + visualizeCapabilities, + }); + const embeddableFactory = new VisualizeEmbeddableFactory(visualizations.types); + embeddables.registerEmbeddableFactory(VISUALIZE_EMBEDDABLE_TYPE, embeddableFactory); } } From c095b534e0669bf369fa18479e68f491c53dc8d2 Mon Sep 17 00:00:00 2001 From: maryia-lapata Date: Wed, 6 Nov 2019 17:02:08 +0300 Subject: [PATCH 070/132] Use feature_catalogue instead of FeatureCatalogueRegistryProvider --- .../kibana/public/visualize/index.ts | 3 +- .../kibana/public/visualize/plugin.ts | 40 +++++++++---------- 2 files changed, 20 insertions(+), 23 deletions(-) diff --git a/src/legacy/core_plugins/kibana/public/visualize/index.ts b/src/legacy/core_plugins/kibana/public/visualize/index.ts index 3852773b21468..e5e44fed92d74 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/index.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/index.ts @@ -26,7 +26,6 @@ import { docTitle } from 'ui/doc_title/doc_title'; import { FilterBarQueryFilterProvider } from 'ui/filter_manager/query_filter'; import { npSetup, npStart } from 'ui/new_platform'; import { IPrivate } from 'ui/private'; -import { FeatureCatalogueRegistryProvider } from 'ui/registry/feature_catalogue'; // @ts-ignore import { VisEditorTypesRegistryProvider } from 'ui/registry/vis_editor_types'; import { SavedObjectRegistryProvider, SavedObjectsClientProvider } from 'ui/saved_objects'; @@ -69,10 +68,10 @@ async function getAngularDependencies(): Promise { const instance = new VisualizePlugin(); instance.setup(npSetup.core, { + feature_catalogue: npSetup.plugins.feature_catalogue, __LEGACY: { docTitle, getAngularDependencies, - FeatureCatalogueRegistryProvider, localApplicationService, }, }); diff --git a/src/legacy/core_plugins/kibana/public/visualize/plugin.ts b/src/legacy/core_plugins/kibana/public/visualize/plugin.ts index 937c4ec194a07..0095ccab72da1 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/plugin.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/plugin.ts @@ -23,7 +23,6 @@ import { wrapInI18nContext } from 'ui/i18n'; // @ts-ignore import { VisEditorTypesRegistryProvider } from 'ui/registry/vis_editor_types'; -import { FeatureCatalogueCategory } from 'ui/registry/feature_catalogue'; // @ts-ignore import { defaultEditor } from 'ui/vis/editors/default/default'; import { @@ -45,6 +44,10 @@ import { VisualizeEmbeddableFactory } from './embeddable/visualize_embeddable_fa import { VISUALIZE_EMBEDDABLE_TYPE } from './embeddable/constants'; import { VisualizeConstants } from './visualize_constants'; import { setServices, VisualizeKibanaServices, DocTitle } from './kibana_services'; +import { + FeatureCatalogueCategory, + FeatureCatalogueSetup, +} from '../../../../../plugins/feature_catalogue/public'; export interface LegacyAngularInjectedDependencies { chromeLegacy: any; @@ -67,10 +70,10 @@ export interface VisualizePluginStartDependencies { } export interface VisualizePluginSetupDependencies { + feature_catalogue: FeatureCatalogueSetup; __LEGACY: { getAngularDependencies: () => Promise; localApplicationService: LocalApplicationService; - FeatureCatalogueRegistryProvider: any; docTitle: DocTitle; }; } @@ -88,12 +91,8 @@ export class VisualizePlugin implements Plugin { public async setup( core: CoreSetup, { - __LEGACY: { - localApplicationService, - getAngularDependencies, - FeatureCatalogueRegistryProvider, - ...legacyServices - }, + feature_catalogue, + __LEGACY: { localApplicationService, getAngularDependencies, ...legacyServices }, }: VisualizePluginSetupDependencies ) { const app: App = { @@ -145,19 +144,18 @@ export class VisualizePlugin implements Plugin { return renderApp(params.element, params.appBasePath, deps); }, }; - FeatureCatalogueRegistryProvider.register(() => { - return { - id: 'visualize', - title: 'Visualize', - description: i18n.translate('kbn.visualize.visualizeDescription', { - defaultMessage: - 'Create visualizations and aggregate data stores in your Elasticsearch indices.', - }), - icon: 'visualizeApp', - path: `/app/kibana#${VisualizeConstants.LANDING_PAGE_PATH}`, - showOnHomePage: true, - category: FeatureCatalogueCategory.DATA, - }; + + feature_catalogue.register({ + id: 'visualize', + title: 'Visualize', + description: i18n.translate('kbn.visualize.visualizeDescription', { + defaultMessage: + 'Create visualizations and aggregate data stores in your Elasticsearch indices.', + }), + icon: 'visualizeApp', + path: `/app/kibana#${VisualizeConstants.LANDING_PAGE_PATH}`, + showOnHomePage: true, + category: FeatureCatalogueCategory.DATA, }); localApplicationService.register({ ...app, id: 'visualize' }); From 38635f46991253115f13466d844093913d8069c6 Mon Sep 17 00:00:00 2001 From: maryia-lapata Date: Wed, 6 Nov 2019 17:20:47 +0300 Subject: [PATCH 071/132] Use queryFilter as npDataStart.query.filterManager instead of Private(FilterBarQueryFilterProvider) --- .../kibana/public/visualize/editor/editor.js | 5 ++-- .../kibana/public/visualize/index.ts | 4 +-- .../public/visualize/kibana_services.ts | 2 ++ .../kibana/public/visualize/plugin.ts | 29 +++++++++---------- 4 files changed, 18 insertions(+), 22 deletions(-) diff --git a/src/legacy/core_plugins/kibana/public/visualize/editor/editor.js b/src/legacy/core_plugins/kibana/public/visualize/editor/editor.js index e354ea6f1b5b8..46792477dd4ae 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/editor/editor.js +++ b/src/legacy/core_plugins/kibana/public/visualize/editor/editor.js @@ -90,13 +90,12 @@ function VisualizeAppController( docTitle, getBasePath, docLinks, + queryFilter, savedQueryService, uiSettings, - npDataStart, } = getServices(); - new FilterStateManager(globalState, getAppState, npDataStart.query.filterManager); - const queryFilter = npDataStart.query.filterManager; + new FilterStateManager(globalState, getAppState, queryFilter); // Retrieve the resolved SavedVis instance. const savedVis = $route.current.locals.savedVis; // vis is instance of src/legacy/ui/public/vis/vis.js. diff --git a/src/legacy/core_plugins/kibana/public/visualize/index.ts b/src/legacy/core_plugins/kibana/public/visualize/index.ts index e5e44fed92d74..524d341439c2c 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/index.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/index.ts @@ -23,7 +23,6 @@ import 'ui/vis/editors/default/sidebar'; import chrome from 'ui/chrome'; import { docTitle } from 'ui/doc_title/doc_title'; -import { FilterBarQueryFilterProvider } from 'ui/filter_manager/query_filter'; import { npSetup, npStart } from 'ui/new_platform'; import { IPrivate } from 'ui/private'; // @ts-ignore @@ -47,7 +46,6 @@ async function getAngularDependencies(): Promise('Private'); - const queryFilter = Private(FilterBarQueryFilterProvider); const shareContextMenuExtensions = Private(ShareContextMenuExtensionsRegistryProvider); const savedObjectRegistry = Private(SavedObjectRegistryProvider); const savedObjectClient = Private(SavedObjectsClientProvider); @@ -56,7 +54,6 @@ async function getAngularDependencies(): Promise; @@ -50,6 +51,7 @@ export interface VisualizeKibanaServices { indexPatterns: any; localStorage: Storage; navigation: NavigationStart; + queryFilter: any; toastNotifications: ToastsStart; savedObjectsClient: SavedObjectsClientContract; savedQueryService: SavedQueryService; diff --git a/src/legacy/core_plugins/kibana/public/visualize/plugin.ts b/src/legacy/core_plugins/kibana/public/visualize/plugin.ts index 0095ccab72da1..1d43396c6466e 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/plugin.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/plugin.ts @@ -21,8 +21,6 @@ import angular from 'angular'; import { i18n } from '@kbn/i18n'; import { wrapInI18nContext } from 'ui/i18n'; -// @ts-ignore -import { VisEditorTypesRegistryProvider } from 'ui/registry/vis_editor_types'; // @ts-ignore import { defaultEditor } from 'ui/vis/editors/default/default'; import { @@ -52,7 +50,6 @@ import { export interface LegacyAngularInjectedDependencies { chromeLegacy: any; editorTypes: any; - queryFilter: any; shareContextMenuExtensions: any; savedObjectRegistry: any; savedObjectClient: any; @@ -75,6 +72,7 @@ export interface VisualizePluginSetupDependencies { getAngularDependencies: () => Promise; localApplicationService: LocalApplicationService; docTitle: DocTitle; + VisEditorTypesRegistryProvider: any; }; } @@ -92,7 +90,12 @@ export class VisualizePlugin implements Plugin { core: CoreSetup, { feature_catalogue, - __LEGACY: { localApplicationService, getAngularDependencies, ...legacyServices }, + __LEGACY: { + localApplicationService, + getAngularDependencies, + VisEditorTypesRegistryProvider, + ...legacyServices + }, }: VisualizePluginSetupDependencies ) { const app: App = { @@ -129,6 +132,7 @@ export class VisualizePlugin implements Plugin { indexPatterns: dataStart.indexPatterns.indexPatterns, localStorage: new Storage(localStorage), navigation, + queryFilter: npDataStart.query.filterManager, savedObjectsClient, savedQueryService: dataStart.search.services.savedQueryService, sessionStorage: new Storage(sessionStorage), @@ -163,16 +167,7 @@ export class VisualizePlugin implements Plugin { } async start( - { - savedObjects: { client: savedObjectsClient }, - uiSettings, - http: { - basePath: { prepend: addBasePath }, - }, - application: { - capabilities: { visualize: visualizeCapabilities }, - }, - }: CoreStart, + { savedObjects: { client: savedObjectsClient }, uiSettings, http, application }: CoreStart, { dataStart, embeddables, @@ -192,14 +187,16 @@ export class VisualizePlugin implements Plugin { }; const { savedVisualizations } = await getAngularDependencies(); + // we partially set services because embeddable factory can be used without running visualize plugin setServices({ visualizations, uiSettings, - addBasePath, + addBasePath: http.basePath.prepend, savedObjectsClient, savedVisualizations, - visualizeCapabilities, + visualizeCapabilities: application.capabilities.visualize, }); + const embeddableFactory = new VisualizeEmbeddableFactory(visualizations.types); embeddables.registerEmbeddableFactory(VISUALIZE_EMBEDDABLE_TYPE, embeddableFactory); } From a65dd3fa0c6afd2c61761de9493d950061af4a06 Mon Sep 17 00:00:00 2001 From: maryia-lapata Date: Wed, 6 Nov 2019 17:44:19 +0300 Subject: [PATCH 072/132] Add type for angular --- .../kibana/public/visualize/index.ts | 3 ++ .../public/visualize/kibana_services.ts | 3 +- .../kibana/public/visualize/plugin.ts | 4 +- .../kibana/public/visualize/render_app.ts | 47 ++++++++++--------- 4 files changed, 32 insertions(+), 25 deletions(-) diff --git a/src/legacy/core_plugins/kibana/public/visualize/index.ts b/src/legacy/core_plugins/kibana/public/visualize/index.ts index 524d341439c2c..d5263046c6253 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/index.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/index.ts @@ -17,6 +17,7 @@ * under the License. */ +import angular from 'angular'; import 'angular-sanitize'; import 'ui/collapsible_sidebar'; // used in default editor import 'ui/vis/editors/default/sidebar'; @@ -67,6 +68,8 @@ async function getAngularDependencies(): Promise string; - angular: any; + angular: IAngularStatic; chrome: ChromeStart; chromeLegacy: any; core: LegacyCoreStart; diff --git a/src/legacy/core_plugins/kibana/public/visualize/plugin.ts b/src/legacy/core_plugins/kibana/public/visualize/plugin.ts index 1d43396c6466e..3b7ecef75c43f 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/plugin.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/plugin.ts @@ -17,7 +17,7 @@ * under the License. */ -import angular from 'angular'; +import { IAngularStatic } from 'angular'; import { i18n } from '@kbn/i18n'; import { wrapInI18nContext } from 'ui/i18n'; @@ -69,6 +69,7 @@ export interface VisualizePluginStartDependencies { export interface VisualizePluginSetupDependencies { feature_catalogue: FeatureCatalogueSetup; __LEGACY: { + angular: IAngularStatic; getAngularDependencies: () => Promise; localApplicationService: LocalApplicationService; docTitle: DocTitle; @@ -120,7 +121,6 @@ export class VisualizePlugin implements Plugin { ...legacyServices, ...angularDependencies, addBasePath: contextCore.http.basePath.prepend, - angular, core: contextCore as LegacyCoreStart, chrome: contextCore.chrome, dataStart, diff --git a/src/legacy/core_plugins/kibana/public/visualize/render_app.ts b/src/legacy/core_plugins/kibana/public/visualize/render_app.ts index 2432ffc553562..4220e4e6b04dc 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/render_app.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/render_app.ts @@ -18,22 +18,22 @@ */ import { EuiConfirmModal } from '@elastic/eui'; -import { IModule } from 'angular'; +import { IModule, IAngularStatic } from 'angular'; import { i18nDirective, i18nFilter, I18nProvider } from '@kbn/i18n/src/angular'; import { IPrivate } from 'ui/private'; +import { State } from 'ui/state_management/state'; import { configureAppAngularModule } from 'ui/legacy_compat'; // @ts-ignore import { GlobalStateProvider } from 'ui/state_management/global_state'; // @ts-ignore import { StateManagementConfigProvider } from 'ui/state_management/config_provider'; // @ts-ignore -import { AppStateProvider } from 'ui/state_management/app_state'; +import { AppStateProvider, AppState } from 'ui/state_management/app_state'; // @ts-ignore import { PrivateProvider } from 'ui/private/private'; // @ts-ignore import { EventsProvider } from 'ui/events'; -// @ts-ignore import { PersistedState } from 'ui/persisted_state'; // @ts-ignore import { createTopNavDirective, createTopNavHelper } from 'ui/kbn_top_nav/kbn_top_nav'; @@ -72,7 +72,11 @@ export const renderApp = async ( initVisualizeApp(angularModuleInstance, deps); } const $injector = mountVisualizeApp(appBasePath, element, deps.angular); - return () => $injector.get('$rootScope').$destroy(); + return () => { + const currentGlobalState = $injector.get('globalState'); + deps.sessionStorage.set('oss-kibana-cross-app-state', currentGlobalState.toObject()); + $injector.get('$rootScope').$destroy(); + }; }; const mainTemplate = (basePath: string) => `
@@ -85,10 +89,9 @@ const moduleName = 'app/visualize'; const thirdPartyAngularDependencies = ['ngSanitize', 'ngRoute', 'react']; -function mountVisualizeApp(appBasePath: string, element: HTMLElement, angular: any) { +function mountVisualizeApp(appBasePath: string, element: HTMLElement, angular: IAngularStatic) { const mountpoint = document.createElement('div'); mountpoint.setAttribute('style', 'height: 100%'); - // eslint-disable-next-line mountpoint.innerHTML = mainTemplate(appBasePath); // bootstrap angular into detached element and attach it later to // make angular-within-angular possible @@ -102,7 +105,7 @@ function mountVisualizeApp(appBasePath: string, element: HTMLElement, angular: a function createLocalAngularModule( core: AppMountContext['core'], navigation: NavigationStart, - angular: any + angular: IAngularStatic ) { createLocalI18nModule(angular); createLocalPrivateModule(angular); @@ -129,14 +132,14 @@ function createLocalAngularModule( return visualizeAngularModule; } -function createLocalConfirmModalModule(angular: any) { +function createLocalConfirmModalModule(angular: IAngularStatic) { angular .module('app/visualize/ConfirmModal', ['react']) .factory('confirmModal', confirmModalFactory) - .directive('confirmModal', (reactDirective: any) => reactDirective(EuiConfirmModal)); + .directive('confirmModal', reactDirective => reactDirective(EuiConfirmModal)); } -function createLocalStateModule(angular: any) { +function createLocalStateModule(angular: IAngularStatic) { angular .module('app/visualize/State', [ 'app/visualize/Private', @@ -145,18 +148,18 @@ function createLocalStateModule(angular: any) { 'app/visualize/Promise', 'app/visualize/PersistedState', ]) - .factory('AppState', function(Private: any) { + .factory('AppState', function(Private: IPrivate) { return Private(AppStateProvider); }) - .service('getAppState', function(Private: any) { - return Private(AppStateProvider).getAppState; + .service('getAppState', function(Private: IPrivate) { + return Private(AppStateProvider).getAppState; }) - .service('globalState', function(Private: any) { + .service('globalState', function(Private: IPrivate) { return Private(GlobalStateProvider); }); } -function createLocalPersistedStateModule(angular: any) { +function createLocalPersistedStateModule(angular: IAngularStatic) { angular .module('app/visualize/PersistedState', ['app/visualize/Private', 'app/visualize/Promise']) .factory('PersistedState', (Private: IPrivate) => { @@ -169,14 +172,14 @@ function createLocalPersistedStateModule(angular: any) { }); } -function createLocalKbnUrlModule(angular: any) { +function createLocalKbnUrlModule(angular: IAngularStatic) { angular .module('app/visualize/KbnUrl', ['app/visualize/Private', 'ngRoute']) .service('kbnUrl', (Private: IPrivate) => Private(KbnUrlProvider)) .service('redirectWhenMissing', (Private: IPrivate) => Private(RedirectWhenMissingProvider)); } -function createLocalConfigModule(core: AppMountContext['core'], angular: any) { +function createLocalConfigModule(core: AppMountContext['core'], angular: IAngularStatic) { angular .module('app/visualize/Config', ['app/visualize/Private']) .provider('stateManagementConfig', StateManagementConfigProvider) @@ -189,22 +192,22 @@ function createLocalConfigModule(core: AppMountContext['core'], angular: any) { }); } -function createLocalPromiseModule(angular: any) { +function createLocalPromiseModule(angular: IAngularStatic) { angular.module('app/visualize/Promise', []).service('Promise', PromiseServiceCreator); } -function createLocalPrivateModule(angular: any) { +function createLocalPrivateModule(angular: IAngularStatic) { angular.module('app/visualize/Private', []).provider('Private', PrivateProvider); } -function createLocalTopNavModule(navigation: NavigationStart, angular: any) { +function createLocalTopNavModule(navigation: NavigationStart, angular: IAngularStatic) { angular .module('app/visualize/TopNav', ['react']) .directive('kbnTopNav', createTopNavDirective) .directive('kbnTopNavHelper', createTopNavHelper(navigation.ui)); } -function createLocalFilterBarModule(angular: any) { +function createLocalFilterBarModule(angular: IAngularStatic) { angular .module('app/visualize/FilterBar', ['react']) .directive('filterBar', createFilterBarDirective) @@ -213,7 +216,7 @@ function createLocalFilterBarModule(angular: any) { .directive('applyFiltersPopoverHelper', createApplyFiltersPopoverHelper); } -function createLocalI18nModule(angular: any) { +function createLocalI18nModule(angular: IAngularStatic) { angular .module('app/visualize/I18n', []) .provider('i18n', I18nProvider) From 5f9120b2c22535aba043d34e1d279b21ba31d891 Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Thu, 7 Nov 2019 06:51:38 -0500 Subject: [PATCH 073/132] never update state in dummy route mode --- src/legacy/ui/public/state_management/state.js | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/legacy/ui/public/state_management/state.js b/src/legacy/ui/public/state_management/state.js index eb853e3a5e33b..8d55a6929a617 100644 --- a/src/legacy/ui/public/state_management/state.js +++ b/src/legacy/ui/public/state_management/state.js @@ -45,6 +45,11 @@ import { export function StateProvider(Private, $rootScope, $location, stateManagementConfig, config, kbnUrl, $injector) { const Events = Private(EventsProvider); + const isDummyRoute = () => + $injector.has('$route') && + $injector.get('$route').current && + $injector.get('$route').current.outerAngularWrapperRoute; + createLegacyClass(State).inherits(Events); function State( urlParam, @@ -135,16 +140,11 @@ export function StateProvider(Private, $rootScope, $location, stateManagementCon return; } - const isDummyRoute = - $injector.has('$route') && - $injector.get('$route').current && - $injector.get('$route').current.outerAngularWrapperRoute; - let stash = this._readFromURL(); // nothing to read from the url? save if ordered to persist, but only if it's not on a wrapper route if (stash === null) { - if (this._persistAcrossApps && !isDummyRoute) { + if (this._persistAcrossApps) { return this.save(); } else { stash = {}; @@ -155,7 +155,7 @@ export function StateProvider(Private, $rootScope, $location, stateManagementCon // apply diff to state from stash, will change state in place via side effect const diffResults = applyDiff(this, stash); - if (!isDummyRoute && diffResults.keys.length) { + if (!isDummyRoute() && diffResults.keys.length) { this.emit('fetch_with_changes', diffResults.keys); } }; @@ -169,6 +169,10 @@ export function StateProvider(Private, $rootScope, $location, stateManagementCon return; } + if (isDummyRoute()) { + return; + } + let stash = this._readFromURL(); const state = this.toObject(); replace = replace || false; From 296c174aeff0e5157cb45fcce9b18604f7fa8118 Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Thu, 7 Nov 2019 10:38:17 -0500 Subject: [PATCH 074/132] clean up implementation --- .../kibana/public/dashboard/app.js | 26 ++----- .../public/dashboard/global_state_sync.ts | 77 +++++++++++++++++++ .../kibana/public/dashboard/render_app.ts | 14 ---- 3 files changed, 85 insertions(+), 32 deletions(-) create mode 100644 src/legacy/core_plugins/kibana/public/dashboard/global_state_sync.ts diff --git a/src/legacy/core_plugins/kibana/public/dashboard/app.js b/src/legacy/core_plugins/kibana/public/dashboard/app.js index 3cc290cab7968..2fe4f13e35013 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/app.js +++ b/src/legacy/core_plugins/kibana/public/dashboard/app.js @@ -34,6 +34,7 @@ import { DashboardListing, EMPTY_FILTER } from './listing/dashboard_listing'; import { addHelpMenuToAppChrome } from './help_menu/help_menu_util'; import { start as data } from '../../../data/public/legacy'; import { registerTimefilterWithGlobalStateFactory } from '../../../../ui/public/timefilter/setup_router'; +import { syncOnMount, syncOnUnmount } from './global_state_sync'; export function initDashboardApp(app, deps) { initDashboardAppDirective(app, deps); @@ -49,25 +50,14 @@ export function initDashboardApp(app, deps) { addHelpMenuToAppChrome(deps.chrome, deps.core.docLinks); } + app.run(($rootScope, globalState) => { + $rootScope.$on('$destroy', () => { + syncOnUnmount(globalState, deps.sessionStorage); + }); + }); + app.run(globalState => { - globalState.fetch(); - const hasGlobalURLState = Object.keys(globalState.toObject()).length; - if (!globalState.time) { - globalState.time = deps.dataStart.timefilter.timefilter.getTime(); - } - if (!globalState.refreshInterval) { - globalState.refreshInterval = deps.dataStart.timefilter.timefilter.getRefreshInterval(); - } - // only inject cross app global state if there is none in the url itself (that takes precedence) - if (!hasGlobalURLState) { - const globalStateStuff = deps.sessionStorage.get('oss-kibana-cross-app-state') || {}; - Object.keys(globalStateStuff).forEach(key => { - globalState[key] = globalStateStuff[key]; - }); - } else { - globalState.$inheritedGlobalState = true; - } - globalState.save(); + syncOnMount(globalState, deps.dataStart, deps.npDataStart, deps.sessionStorage); }); app.run((globalState, $rootScope) => { diff --git a/src/legacy/core_plugins/kibana/public/dashboard/global_state_sync.ts b/src/legacy/core_plugins/kibana/public/dashboard/global_state_sync.ts new file mode 100644 index 0000000000000..96f21f6f88b37 --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/dashboard/global_state_sync.ts @@ -0,0 +1,77 @@ +/* + * 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 { State } from 'ui/state_management/state'; +import { DataStart } from '../../../data/public'; +import { DataPublicPluginStart as NpDataStart } from '../../../../../plugins/data/public'; +import { Storage } from '../../../../../plugins/kibana_utils/public'; + +const GLOBAL_STATE_SHARE_KEY = 'oss-kibana-cross-app-state'; + +/** + * Helper function to sync the global state with the various state providers + * when a local angular application mounts. There are three different ways + * global state can be passed into the application: + * * parameter in the URL hash - e.g. shared link + * * state shared in the session storage - e.g. reload-navigation from another app. + * * in-memory state in the data plugin exports (timefilter and filterManager) - e.g. default values + * + * This function looks up the three sources (earlier in the list means it takes precedence), + * puts it into the globalState object and syncs it with the url. + */ +export function syncOnMount( + globalState: State, + data: DataStart, + npData: NpDataStart, + sessionStorage: Storage +) { + // pull in global state information from the URL + globalState.fetch(); + // remember whether there were info in the URL + const hasGlobalURLState = Boolean(Object.keys(globalState.toObject()).length); + + // sync kibana platform state with the angular global state + if (!globalState.time) { + globalState.time = data.timefilter.timefilter.getTime(); + } + if (!globalState.refreshInterval) { + globalState.refreshInterval = data.timefilter.timefilter.getRefreshInterval(); + } + if (!globalState.filters && npData.query.filterManager.getGlobalFilters().length > 0) { + globalState.filters = npData.query.filterManager.getGlobalFilters(); + } + // only inject cross app global state if there is none in the url itself (that takes precedence) + if (hasGlobalURLState) { + // set flag the global state is set from the URL + globalState.$inheritedGlobalState = true; + } else { + Object.assign(globalState, sessionStorage.get(GLOBAL_STATE_SHARE_KEY) || {}); + } + globalState.save(); +} + +/** + * Helper function to sync the global state when a local angular application unmounts. + * It will put the current global state into the session storage to be able to re-apply it + * if the application mounts again even if another application won't retain the state in + * the url. + */ +export function syncOnUnmount(globalState: State, sessionStorage: Storage) { + sessionStorage.set(GLOBAL_STATE_SHARE_KEY, globalState.toObject()); +} diff --git a/src/legacy/core_plugins/kibana/public/dashboard/render_app.ts b/src/legacy/core_plugins/kibana/public/dashboard/render_app.ts index ca219e6a74d95..2c5b3ba693231 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/render_app.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/render_app.ts @@ -99,21 +99,7 @@ export const renderApp = (element: HTMLElement, appBasePath: string, deps: Rende initDashboardApp(angularModuleInstance, deps); } const $injector = mountDashboardApp(appBasePath, element); - // const hasGlobalURLState = window.location.hash.includes('_g='); - // // only inject global state if there is none in the url itself (that takes precedence) - // if (!hasGlobalURLState) { - // const globalStateStuff = deps.sessionStorage.get('oss-kibana-cross-app-state') || {}; - // const globalState = $injector.get('globalState'); - // globalState.time = deps.dataStart.timefilter.timefilter.getTime(); - // globalState.refreshInterval = deps.dataStart.timefilter.timefilter.getRefreshInterval(); - // Object.keys(globalStateStuff).forEach(key => { - // globalState[key] = globalStateStuff[key]; - // }); - // globalState.save(); - // } return () => { - const currentGlobalState = $injector.get('globalState'); - deps.sessionStorage.set('oss-kibana-cross-app-state', currentGlobalState.toObject()); $injector.get('$rootScope').$destroy(); }; }; From 8fb0fbd3b62371f794a2102864f403cc9f4a967d Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Thu, 7 Nov 2019 11:55:06 -0500 Subject: [PATCH 075/132] fix type error --- src/legacy/core_plugins/kibana/public/dashboard/render_app.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/legacy/core_plugins/kibana/public/dashboard/render_app.ts b/src/legacy/core_plugins/kibana/public/dashboard/render_app.ts index 2c5b3ba693231..7e2c4abaa62a4 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/render_app.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/render_app.ts @@ -20,7 +20,6 @@ import { EuiConfirmModal } from '@elastic/eui'; import angular, { IModule } from 'angular'; import { IPrivate } from 'ui/private'; -import { State } from 'ui/state_management/state'; import { i18nDirective, i18nFilter, I18nProvider } from '@kbn/i18n/angular'; // @ts-ignore import { GlobalStateProvider } from 'ui/state_management/global_state'; From fad42dd176ab73d85d1ed09d6851bcea2c8f169b Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Fri, 8 Nov 2019 07:07:01 -0500 Subject: [PATCH 076/132] remove session storage global state handling for now --- .../kibana/public/dashboard/app.js | 10 ++----- .../public/dashboard/global_state_sync.ts | 27 ++++--------------- .../kibana/public/dashboard/plugin.ts | 1 - .../kibana/public/dashboard/render_app.ts | 1 - 4 files changed, 7 insertions(+), 32 deletions(-) diff --git a/src/legacy/core_plugins/kibana/public/dashboard/app.js b/src/legacy/core_plugins/kibana/public/dashboard/app.js index 2fe4f13e35013..91ea6d47bd415 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/app.js +++ b/src/legacy/core_plugins/kibana/public/dashboard/app.js @@ -34,7 +34,7 @@ import { DashboardListing, EMPTY_FILTER } from './listing/dashboard_listing'; import { addHelpMenuToAppChrome } from './help_menu/help_menu_util'; import { start as data } from '../../../data/public/legacy'; import { registerTimefilterWithGlobalStateFactory } from '../../../../ui/public/timefilter/setup_router'; -import { syncOnMount, syncOnUnmount } from './global_state_sync'; +import { syncOnMount } from './global_state_sync'; export function initDashboardApp(app, deps) { initDashboardAppDirective(app, deps); @@ -50,14 +50,8 @@ export function initDashboardApp(app, deps) { addHelpMenuToAppChrome(deps.chrome, deps.core.docLinks); } - app.run(($rootScope, globalState) => { - $rootScope.$on('$destroy', () => { - syncOnUnmount(globalState, deps.sessionStorage); - }); - }); - app.run(globalState => { - syncOnMount(globalState, deps.dataStart, deps.npDataStart, deps.sessionStorage); + syncOnMount(globalState, deps.dataStart, deps.npDataStart); }); app.run((globalState, $rootScope) => { diff --git a/src/legacy/core_plugins/kibana/public/dashboard/global_state_sync.ts b/src/legacy/core_plugins/kibana/public/dashboard/global_state_sync.ts index 96f21f6f88b37..95d44b8342e37 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/global_state_sync.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/global_state_sync.ts @@ -20,27 +20,22 @@ import { State } from 'ui/state_management/state'; import { DataStart } from '../../../data/public'; import { DataPublicPluginStart as NpDataStart } from '../../../../../plugins/data/public'; -import { Storage } from '../../../../../plugins/kibana_utils/public'; - -const GLOBAL_STATE_SHARE_KEY = 'oss-kibana-cross-app-state'; /** * Helper function to sync the global state with the various state providers * when a local angular application mounts. There are three different ways * global state can be passed into the application: * * parameter in the URL hash - e.g. shared link - * * state shared in the session storage - e.g. reload-navigation from another app. * * in-memory state in the data plugin exports (timefilter and filterManager) - e.g. default values * * This function looks up the three sources (earlier in the list means it takes precedence), * puts it into the globalState object and syncs it with the url. + * + * Currently the legacy chrome takes care of restoring the global state when navigating from + * one app to another - to migrate away from that it will become necessary to also write the current + * state to local storage */ -export function syncOnMount( - globalState: State, - data: DataStart, - npData: NpDataStart, - sessionStorage: Storage -) { +export function syncOnMount(globalState: State, data: DataStart, npData: NpDataStart) { // pull in global state information from the URL globalState.fetch(); // remember whether there were info in the URL @@ -60,18 +55,6 @@ export function syncOnMount( if (hasGlobalURLState) { // set flag the global state is set from the URL globalState.$inheritedGlobalState = true; - } else { - Object.assign(globalState, sessionStorage.get(GLOBAL_STATE_SHARE_KEY) || {}); } globalState.save(); } - -/** - * Helper function to sync the global state when a local angular application unmounts. - * It will put the current global state into the session storage to be able to re-apply it - * if the application mounts again even if another application won't retain the state in - * the url. - */ -export function syncOnUnmount(globalState: State, sessionStorage: Storage) { - sessionStorage.set(GLOBAL_STATE_SHARE_KEY, globalState.toObject()); -} diff --git a/src/legacy/core_plugins/kibana/public/dashboard/plugin.ts b/src/legacy/core_plugins/kibana/public/dashboard/plugin.ts index d0741db58cd55..50963fa6a57fd 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/plugin.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/plugin.ts @@ -115,7 +115,6 @@ export class DashboardPlugin implements Plugin { embeddables, dashboardCapabilities: contextCore.application.capabilities.dashboard, localStorage: new Storage(localStorage), - sessionStorage: new Storage(sessionStorage), }; const { renderApp } = await import('./render_app'); return renderApp(params.element, params.appBasePath, deps); diff --git a/src/legacy/core_plugins/kibana/public/dashboard/render_app.ts b/src/legacy/core_plugins/kibana/public/dashboard/render_app.ts index 7e2c4abaa62a4..8d3d5f104be2e 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/render_app.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/render_app.ts @@ -84,7 +84,6 @@ export interface RenderDeps { savedQueryService: SavedQueryService; embeddables: ReturnType; localStorage: Storage; - sessionStorage: Storage; } let angularModuleInstance: IModule | null = null; From 0618c533645321bb54ee149237a8ea900a48c038 Mon Sep 17 00:00:00 2001 From: maryia-lapata Date: Mon, 11 Nov 2019 13:56:10 +0300 Subject: [PATCH 077/132] move syncOnMount to a separate file --- .../kibana/public/visualize/app.js | 29 ++++----- .../public/visualize/global_state_sync.ts | 60 +++++++++++++++++++ .../kibana/public/visualize/render_app.ts | 2 - .../ui/public/state_management/state.js | 18 +++--- .../ui/public/vis/vis_filters/vis_filters.js | 6 +- 5 files changed, 85 insertions(+), 30 deletions(-) create mode 100644 src/legacy/core_plugins/kibana/public/visualize/global_state_sync.ts diff --git a/src/legacy/core_plugins/kibana/public/visualize/app.js b/src/legacy/core_plugins/kibana/public/visualize/app.js index f5589e87f7994..5ca8fe71ee25b 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/app.js +++ b/src/legacy/core_plugins/kibana/public/visualize/app.js @@ -26,6 +26,8 @@ import visualizeListingTemplate from './listing/visualize_listing.html'; import { initVisualizeAppDirective } from './visualize_app'; import { VisualizeConstants } from './visualize_constants'; import { VisualizeListingController } from './listing/visualize_listing'; +import { registerTimefilterWithGlobalStateFactory } from '../../../../ui/public/timefilter/setup_router'; +import { syncOnMount } from './global_state_sync'; import { getLandingBreadcrumbs, @@ -40,24 +42,15 @@ export function initVisualizeApp(app, deps) { initVisualizeAppDirective(app, deps); app.run(globalState => { - globalState.fetch(); - const hasGlobalURLState = Object.keys(globalState.toObject()).length; - if (!globalState.time) { - globalState.time = deps.dataStart.timefilter.timefilter.getTime(); - } - if (!globalState.refreshInterval) { - globalState.refreshInterval = deps.dataStart.timefilter.timefilter.getRefreshInterval(); - } - // only inject cross app global state if there is none in the url itself (that takes precedence) - if (!hasGlobalURLState) { - const globalStateStuff = deps.sessionStorage.get('oss-kibana-cross-app-state') || {}; - Object.keys(globalStateStuff).forEach(key => { - globalState[key] = globalStateStuff[key]; - }); - } else { - globalState.$inheritedGlobalState = true; - } - globalState.save(); + syncOnMount(globalState, deps.dataStart, deps.npDataStart); + }); + + app.run((globalState, $rootScope) => { + registerTimefilterWithGlobalStateFactory( + deps.dataStart.timefilter.timefilter, + globalState, + $rootScope + ); }); app.config(function ($routeProvider) { diff --git a/src/legacy/core_plugins/kibana/public/visualize/global_state_sync.ts b/src/legacy/core_plugins/kibana/public/visualize/global_state_sync.ts new file mode 100644 index 0000000000000..95d44b8342e37 --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/visualize/global_state_sync.ts @@ -0,0 +1,60 @@ +/* + * 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 { State } from 'ui/state_management/state'; +import { DataStart } from '../../../data/public'; +import { DataPublicPluginStart as NpDataStart } from '../../../../../plugins/data/public'; + +/** + * Helper function to sync the global state with the various state providers + * when a local angular application mounts. There are three different ways + * global state can be passed into the application: + * * parameter in the URL hash - e.g. shared link + * * in-memory state in the data plugin exports (timefilter and filterManager) - e.g. default values + * + * This function looks up the three sources (earlier in the list means it takes precedence), + * puts it into the globalState object and syncs it with the url. + * + * Currently the legacy chrome takes care of restoring the global state when navigating from + * one app to another - to migrate away from that it will become necessary to also write the current + * state to local storage + */ +export function syncOnMount(globalState: State, data: DataStart, npData: NpDataStart) { + // pull in global state information from the URL + globalState.fetch(); + // remember whether there were info in the URL + const hasGlobalURLState = Boolean(Object.keys(globalState.toObject()).length); + + // sync kibana platform state with the angular global state + if (!globalState.time) { + globalState.time = data.timefilter.timefilter.getTime(); + } + if (!globalState.refreshInterval) { + globalState.refreshInterval = data.timefilter.timefilter.getRefreshInterval(); + } + if (!globalState.filters && npData.query.filterManager.getGlobalFilters().length > 0) { + globalState.filters = npData.query.filterManager.getGlobalFilters(); + } + // only inject cross app global state if there is none in the url itself (that takes precedence) + if (hasGlobalURLState) { + // set flag the global state is set from the URL + globalState.$inheritedGlobalState = true; + } + globalState.save(); +} diff --git a/src/legacy/core_plugins/kibana/public/visualize/render_app.ts b/src/legacy/core_plugins/kibana/public/visualize/render_app.ts index 4220e4e6b04dc..96a65bca06d8c 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/render_app.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/render_app.ts @@ -73,8 +73,6 @@ export const renderApp = async ( } const $injector = mountVisualizeApp(appBasePath, element, deps.angular); return () => { - const currentGlobalState = $injector.get('globalState'); - deps.sessionStorage.set('oss-kibana-cross-app-state', currentGlobalState.toObject()); $injector.get('$rootScope').$destroy(); }; }; diff --git a/src/legacy/ui/public/state_management/state.js b/src/legacy/ui/public/state_management/state.js index eb853e3a5e33b..8d55a6929a617 100644 --- a/src/legacy/ui/public/state_management/state.js +++ b/src/legacy/ui/public/state_management/state.js @@ -45,6 +45,11 @@ import { export function StateProvider(Private, $rootScope, $location, stateManagementConfig, config, kbnUrl, $injector) { const Events = Private(EventsProvider); + const isDummyRoute = () => + $injector.has('$route') && + $injector.get('$route').current && + $injector.get('$route').current.outerAngularWrapperRoute; + createLegacyClass(State).inherits(Events); function State( urlParam, @@ -135,16 +140,11 @@ export function StateProvider(Private, $rootScope, $location, stateManagementCon return; } - const isDummyRoute = - $injector.has('$route') && - $injector.get('$route').current && - $injector.get('$route').current.outerAngularWrapperRoute; - let stash = this._readFromURL(); // nothing to read from the url? save if ordered to persist, but only if it's not on a wrapper route if (stash === null) { - if (this._persistAcrossApps && !isDummyRoute) { + if (this._persistAcrossApps) { return this.save(); } else { stash = {}; @@ -155,7 +155,7 @@ export function StateProvider(Private, $rootScope, $location, stateManagementCon // apply diff to state from stash, will change state in place via side effect const diffResults = applyDiff(this, stash); - if (!isDummyRoute && diffResults.keys.length) { + if (!isDummyRoute() && diffResults.keys.length) { this.emit('fetch_with_changes', diffResults.keys); } }; @@ -169,6 +169,10 @@ export function StateProvider(Private, $rootScope, $location, stateManagementCon return; } + if (isDummyRoute()) { + return; + } + let stash = this._readFromURL(); const state = this.toObject(); replace = replace || false; diff --git a/src/legacy/ui/public/vis/vis_filters/vis_filters.js b/src/legacy/ui/public/vis/vis_filters/vis_filters.js index 234f551471031..9e70fb45cd311 100644 --- a/src/legacy/ui/public/vis/vis_filters/vis_filters.js +++ b/src/legacy/ui/public/vis/vis_filters/vis_filters.js @@ -19,11 +19,11 @@ import { npStart } from 'ui/new_platform'; import { onBrushEvent } from './brush_event'; +import { uniqFilters } from '../../../../../plugins/data/public'; +import { toggleFilterNegated } from '@kbn/es-query'; import _ from 'lodash'; import { changeTimeFilter, extractTimeFilter } from '../../../../core_plugins/data/public/timefilter'; import { start as data } from '../../../../core_plugins/data/public/legacy'; -import { uniqFilters, esFilters } from '../../../../../plugins/data/public'; - /** * For terms aggregations on `__other__` buckets, this assembles a list of applicable filter * terms based on a specific cell in the tabified data. @@ -96,7 +96,7 @@ const createFiltersFromEvent = (event) => { if (filter) { filter.forEach(f => { if (event.negate) { - f = esFilters.toggleFilterNegated(f); + f = toggleFilterNegated(f); } filters.push(f); }); From ff832e07d5ade845c9508d84cda89d9b5d5412cd Mon Sep 17 00:00:00 2001 From: maryia-lapata Date: Mon, 11 Nov 2019 14:52:21 +0300 Subject: [PATCH 078/132] Prevent reload on search --- src/legacy/core_plugins/kibana/public/visualize/app.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/legacy/core_plugins/kibana/public/visualize/app.js b/src/legacy/core_plugins/kibana/public/visualize/app.js index 5ca8fe71ee25b..6163316abe552 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/app.js +++ b/src/legacy/core_plugins/kibana/public/visualize/app.js @@ -55,6 +55,7 @@ export function initVisualizeApp(app, deps) { app.config(function ($routeProvider) { const defaults = { + reloadOnSearch: false, requireUICapability: 'visualize.show', badge: () => { if (deps.visualizeCapabilities.save) { From ab288a641840ccc22cee027e68190a1162310c3f Mon Sep 17 00:00:00 2001 From: maryia-lapata Date: Mon, 11 Nov 2019 16:47:41 +0300 Subject: [PATCH 079/132] Revert centralizing deps for embeddable --- .../dashboard/dashboard_app_controller.tsx | 1 - .../embeddable/disabled_lab_embeddable.tsx | 2 +- .../visualize/embeddable/get_index_pattern.ts | 14 ++-- .../embeddable/visualize_embeddable.ts | 24 +++---- .../visualize_embeddable_factory.tsx | 66 ++++++++++++++----- .../public/visualize/kibana_services.ts | 21 +----- .../kibana/public/visualize/render_app.ts | 1 - .../visualize/wizard/new_vis_modal.test.tsx | 2 +- .../public/visualize/wizard/new_vis_modal.tsx | 4 +- .../wizard/type_selection/new_vis_help.tsx | 2 +- 10 files changed, 75 insertions(+), 62 deletions(-) diff --git a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx index 0b2aca8d7f5cf..a66564c535f18 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx +++ b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx @@ -49,7 +49,6 @@ import { Query, SavedQuery, } from '../../../data/public'; -import { start as data } from '../../../data/public/legacy'; import { esFilters } from '../../../../../plugins/data/public'; import { diff --git a/src/legacy/core_plugins/kibana/public/visualize/embeddable/disabled_lab_embeddable.tsx b/src/legacy/core_plugins/kibana/public/visualize/embeddable/disabled_lab_embeddable.tsx index 065feae045597..d8792a761b186 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/embeddable/disabled_lab_embeddable.tsx +++ b/src/legacy/core_plugins/kibana/public/visualize/embeddable/disabled_lab_embeddable.tsx @@ -19,8 +19,8 @@ import React from 'react'; import ReactDOM from 'react-dom'; +import { Embeddable, EmbeddableOutput } from '../../../../../../plugins/embeddable/public'; -import { Embeddable, EmbeddableOutput } from '../kibana_services'; import { DisabledLabVisualization } from './disabled_lab_visualization'; import { VisualizeInput } from './visualize_embeddable'; import { VISUALIZE_EMBEDDABLE_TYPE } from './constants'; diff --git a/src/legacy/core_plugins/kibana/public/visualize/embeddable/get_index_pattern.ts b/src/legacy/core_plugins/kibana/public/visualize/embeddable/get_index_pattern.ts index 0b7dbbf5047e0..699fa68b4528b 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/embeddable/get_index_pattern.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/embeddable/get_index_pattern.ts @@ -17,22 +17,20 @@ * under the License. */ -import { - getServices, - getFromSavedObject, - StaticIndexPattern, - VisSavedObject, -} from '../kibana_services'; +import chrome from 'ui/chrome'; +import { StaticIndexPattern, getFromSavedObject } from 'ui/index_patterns'; +import { VisSavedObject } from 'ui/visualize/loader/types'; export async function getIndexPattern( savedVis: VisSavedObject ): Promise { - const { savedObjectsClient, uiSettings } = getServices(); if (savedVis.vis.type.name !== 'metrics') { return savedVis.vis.indexPattern; } - const defaultIndex = uiSettings.get('defaultIndex'); + const config = chrome.getUiSettingsClient(); + const savedObjectsClient = chrome.getSavedObjectsClient(); + const defaultIndex = config.get('defaultIndex'); if (savedVis.vis.params.index_pattern) { const indexPatternObjects = await savedObjectsClient.find({ diff --git a/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable.ts b/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable.ts index af80c06cc7c37..d1a15aa47249e 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable.ts @@ -18,6 +18,16 @@ */ import _ from 'lodash'; +import { StaticIndexPattern } from 'ui/index_patterns'; +import { PersistedState } from 'ui/persisted_state'; +import { VisualizeLoader } from 'ui/visualize/loader'; +import { EmbeddedVisualizeHandler } from 'ui/visualize/loader/embedded_visualize_handler'; +import { AppState } from 'ui/state_management/app_state'; +import { + VisSavedObject, + VisualizeLoaderParams, + VisualizeUpdateParams, +} from 'ui/visualize/loader/types'; import { Subscription } from 'rxjs'; import * as Rx from 'rxjs'; import { @@ -29,19 +39,11 @@ import { Query } from '../../../../data/public'; import { VISUALIZE_EMBEDDABLE_TYPE } from './constants'; import { - AppState, - Container, - Embeddable, EmbeddableInput, EmbeddableOutput, - EmbeddedVisualizeHandler, - PersistedState, - StaticIndexPattern, - VisSavedObject, - VisualizeLoader, - VisualizeLoaderParams, - VisualizeUpdateParams, -} from '../kibana_services'; + Embeddable, + Container, +} from '../../../../../../plugins/embeddable/public'; const getKeys = (o: T): Array => Object.keys(o) as Array; diff --git a/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable_factory.tsx b/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable_factory.tsx index b38ba31d7260b..299787c318e40 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable_factory.tsx +++ b/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable_factory.tsx @@ -17,25 +17,50 @@ * under the License. */ +import 'ui/registry/field_formats'; +import 'uiExports/contextMenuActions'; +import 'uiExports/devTools'; +import 'uiExports/docViews'; +import 'uiExports/embeddableFactories'; +import 'uiExports/embeddableActions'; +import 'uiExports/fieldFormatEditors'; +import 'uiExports/fieldFormats'; +import 'uiExports/home'; +import 'uiExports/indexManagement'; +import 'uiExports/inspectorViews'; +import 'uiExports/savedObjectTypes'; +import 'uiExports/search'; +import 'uiExports/shareContextMenuExtensions'; +import 'uiExports/visEditorTypes'; +import 'uiExports/visTypes'; +import 'uiExports/visualize'; + import { i18n } from '@kbn/i18n'; +import { Legacy } from 'kibana'; + +import { capabilities } from 'ui/capabilities'; + +import chrome from 'ui/chrome'; +import { getVisualizeLoader } from 'ui/visualize/loader'; + import { SavedObjectAttributes } from 'kibana/server'; +import { npSetup } from 'ui/new_platform'; +import { + EmbeddableFactory, + ErrorEmbeddable, + Container, + EmbeddableOutput, +} from '../../../../../../plugins/embeddable/public'; +import { start as visualizations } from '../../../../visualizations/public/np_ready/public/legacy'; import { showNewVisModal } from '../wizard'; +import { SavedVisualizations } from '../types'; import { DisabledLabEmbeddable } from './disabled_lab_embeddable'; import { getIndexPattern } from './get_index_pattern'; import { VisualizeEmbeddable, VisualizeInput, VisualizeOutput } from './visualize_embeddable'; import { VISUALIZE_EMBEDDABLE_TYPE } from './constants'; import { TypesStart } from '../../../../visualizations/public/np_ready/public/types'; - -import { - getServices, - Container, - EmbeddableFactory, - EmbeddableOutput, - ErrorEmbeddable, - getVisualizeLoader, - VisSavedObject, -} from '../kibana_services'; +import { VisSavedObject } from '../../../../../ui/public/visualize/loader/types'; interface VisualizationAttributes extends SavedObjectAttributes { visState: string; @@ -51,7 +76,7 @@ export class VisualizeEmbeddableFactory extends EmbeddableFactory< private readonly visTypes: TypesStart; static async createVisualizeEmbeddableFactory(): Promise { - return new VisualizeEmbeddableFactory(getServices().visualizations.types); + return new VisualizeEmbeddableFactory(visualizations.types); } constructor(visTypes: TypesStart) { @@ -85,7 +110,7 @@ export class VisualizeEmbeddableFactory extends EmbeddableFactory< if (!visType) { return false; } - if (getServices().uiSettings.get('visualize:enableLabs')) { + if (chrome.getUiSettingsClient().get('visualize:enableLabs')) { return true; } return visType.stage !== 'experimental'; @@ -97,7 +122,7 @@ export class VisualizeEmbeddableFactory extends EmbeddableFactory< } public isEditable() { - return getServices().visualizeCapabilities.save as boolean; + return capabilities.get().visualize.save as boolean; } public getDisplayName() { @@ -111,14 +136,18 @@ export class VisualizeEmbeddableFactory extends EmbeddableFactory< input: Partial & { id: string }, parent?: Container ): Promise { - const { addBasePath, uiSettings, savedVisualizations } = getServices(); + const $injector = await chrome.dangerouslyGetActiveInjector(); + const config = $injector.get('config'); + const savedVisualizations = $injector.get('savedVisualizations'); try { const visId = savedObject.id as string; - const editUrl = visId ? addBasePath(`/app/kibana${savedVisualizations.urlFor(visId)}`) : ''; + const editUrl = visId + ? chrome.addBasePath(`/app/kibana${savedVisualizations.urlFor(visId)}`) + : ''; const loader = await getVisualizeLoader(); - const isLabsEnabled = uiSettings.get('visualize:enableLabs'); + const isLabsEnabled = config.get('visualize:enableLabs'); if (!isLabsEnabled && savedObject.vis.type.stage === 'experimental') { return new DisabledLabEmbeddable(savedObject.title, input); @@ -150,10 +179,13 @@ export class VisualizeEmbeddableFactory extends EmbeddableFactory< input: Partial & { id: string }, parent?: Container ): Promise { + const $injector = await chrome.dangerouslyGetActiveInjector(); + const savedVisualizations = $injector.get('savedVisualizations'); + try { const visId = savedObjectId; - const savedObject = await getServices().savedVisualizations.get(visId); + const savedObject = await savedVisualizations.get(visId); return this.createFromObject(savedObject, input, parent); } catch (e) { console.error(e); // eslint-disable-line no-console diff --git a/src/legacy/core_plugins/kibana/public/visualize/kibana_services.ts b/src/legacy/core_plugins/kibana/public/visualize/kibana_services.ts index 77771fb35ed4c..471c9ea0db354 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/kibana_services.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/kibana_services.ts @@ -84,30 +84,13 @@ export function clearServices() { // export types export { DocTitle } from 'ui/doc_title/doc_title'; -export { EmbeddedVisualizeHandler } from 'ui/visualize/loader/embedded_visualize_handler'; -export { StaticIndexPattern } from 'ui/index_patterns'; -export { PersistedState } from 'ui/persisted_state'; -export { AppState } from 'ui/state_management/app_state'; export { VisType } from 'ui/vis'; -export { VisualizeLoader } from 'ui/visualize/loader'; -export { - VisSavedObject, - VisualizeLoaderParams, - VisualizeUpdateParams, -} from 'ui/visualize/loader/types'; -export { - Container, - Embeddable, - EmbeddableFactory, - EmbeddableInput, - EmbeddableOutput, - ErrorEmbeddable, -} from '../../../../../plugins/embeddable/public'; +export { VisSavedObject } from 'ui/visualize/loader/types'; +export { EmbeddableFactory, ErrorEmbeddable } from '../../../../../plugins/embeddable/public'; // export legacy static dependencies export { absoluteToParsedUrl } from 'ui/url/absolute_to_parsed_url'; export { ensureDefaultIndexPattern } from 'ui/legacy_compat'; -export { getFromSavedObject } from 'ui/index_patterns'; export { KibanaParsedUrl } from 'ui/url/kibana_parsed_url'; export { migrateLegacyQuery } from 'ui/utils/migrate_legacy_query'; export { SavedObjectSaveModal } from 'ui/saved_objects/components/saved_object_save_modal'; diff --git a/src/legacy/core_plugins/kibana/public/visualize/render_app.ts b/src/legacy/core_plugins/kibana/public/visualize/render_app.ts index 96a65bca06d8c..01d515a29370d 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/render_app.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/render_app.ts @@ -22,7 +22,6 @@ import { IModule, IAngularStatic } from 'angular'; import { i18nDirective, i18nFilter, I18nProvider } from '@kbn/i18n/src/angular'; import { IPrivate } from 'ui/private'; -import { State } from 'ui/state_management/state'; import { configureAppAngularModule } from 'ui/legacy_compat'; // @ts-ignore import { GlobalStateProvider } from 'ui/state_management/global_state'; diff --git a/src/legacy/core_plugins/kibana/public/visualize/wizard/new_vis_modal.test.tsx b/src/legacy/core_plugins/kibana/public/visualize/wizard/new_vis_modal.test.tsx index 99d9590e750fd..e9c7198542828 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/wizard/new_vis_modal.test.tsx +++ b/src/legacy/core_plugins/kibana/public/visualize/wizard/new_vis_modal.test.tsx @@ -45,7 +45,7 @@ beforeEach(() => { }); describe('NewVisModal', () => { - const settingsGet = getServices().uiSettings.get as jest.Mock; + const settingsGet = getServices().uiSettings!.get as jest.Mock; const defaultVisTypeParams = { hidden: false, diff --git a/src/legacy/core_plugins/kibana/public/visualize/wizard/new_vis_modal.tsx b/src/legacy/core_plugins/kibana/public/visualize/wizard/new_vis_modal.tsx index 26012ce6421df..f965d12de2457 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/wizard/new_vis_modal.tsx +++ b/src/legacy/core_plugins/kibana/public/visualize/wizard/new_vis_modal.tsx @@ -53,7 +53,7 @@ class NewVisModal extends React.Component{t.promotion!.description}

Date: Mon, 11 Nov 2019 18:21:06 +0300 Subject: [PATCH 080/132] Revert partially setting services --- .../visualize_embeddable_factory.tsx | 1 - .../kibana/public/visualize/index.ts | 3 +-- .../public/visualize/kibana_services.ts | 6 ++--- .../kibana/public/visualize/plugin.ts | 23 ++----------------- 4 files changed, 6 insertions(+), 27 deletions(-) diff --git a/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable_factory.tsx b/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable_factory.tsx index 299787c318e40..637e4db66889e 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable_factory.tsx +++ b/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable_factory.tsx @@ -45,7 +45,6 @@ import chrome from 'ui/chrome'; import { getVisualizeLoader } from 'ui/visualize/loader'; import { SavedObjectAttributes } from 'kibana/server'; -import { npSetup } from 'ui/new_platform'; import { EmbeddableFactory, ErrorEmbeddable, diff --git a/src/legacy/core_plugins/kibana/public/visualize/index.ts b/src/legacy/core_plugins/kibana/public/visualize/index.ts index d5263046c6253..f6fe7abdd75de 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/index.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/index.ts @@ -76,12 +76,11 @@ async function getAngularDependencies(): Promise | null = null; -export function setServices(newServices: Partial) { - services = { ...services, ...newServices }; +let services: VisualizeKibanaServices | null = null; +export function setServices(newServices: VisualizeKibanaServices) { + services = newServices; } export function getServices() { diff --git a/src/legacy/core_plugins/kibana/public/visualize/plugin.ts b/src/legacy/core_plugins/kibana/public/visualize/plugin.ts index 3b7ecef75c43f..41c2d1bef4a7a 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/plugin.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/plugin.ts @@ -63,7 +63,6 @@ export interface VisualizePluginStartDependencies { embeddables: ReturnType; navigation: NavigationStart; visualizations: VisualizationsStart; - getAngularDependencies: () => Promise; } export interface VisualizePluginSetupDependencies { @@ -167,15 +166,8 @@ export class VisualizePlugin implements Plugin { } async start( - { savedObjects: { client: savedObjectsClient }, uiSettings, http, application }: CoreStart, - { - dataStart, - embeddables, - navigation, - visualizations, - npData, - getAngularDependencies, - }: VisualizePluginStartDependencies + { savedObjects: { client: savedObjectsClient } }: CoreStart, + { dataStart, embeddables, navigation, visualizations, npData }: VisualizePluginStartDependencies ) { this.startDependencies = { dataStart, @@ -186,17 +178,6 @@ export class VisualizePlugin implements Plugin { visualizations, }; - const { savedVisualizations } = await getAngularDependencies(); - // we partially set services because embeddable factory can be used without running visualize plugin - setServices({ - visualizations, - uiSettings, - addBasePath: http.basePath.prepend, - savedObjectsClient, - savedVisualizations, - visualizeCapabilities: application.capabilities.visualize, - }); - const embeddableFactory = new VisualizeEmbeddableFactory(visualizations.types); embeddables.registerEmbeddableFactory(VISUALIZE_EMBEDDABLE_TYPE, embeddableFactory); } From 00d6dc1e6ec9f7936d15ba5f0e7ea78e52a51f60 Mon Sep 17 00:00:00 2001 From: maryia-lapata Date: Mon, 11 Nov 2019 18:27:41 +0300 Subject: [PATCH 081/132] Revert changes in local_application_service --- .../local_application_service.ts | 29 ++++++++++++------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/src/legacy/core_plugins/kibana/public/local_application_service/local_application_service.ts b/src/legacy/core_plugins/kibana/public/local_application_service/local_application_service.ts index 6c38aa4e1a773..9d87e187fd1e1 100644 --- a/src/legacy/core_plugins/kibana/public/local_application_service/local_application_service.ts +++ b/src/legacy/core_plugins/kibana/public/local_application_service/local_application_service.ts @@ -17,9 +17,9 @@ * under the License. */ -import { App } from 'kibana/public'; +import { App, AppUnmount } from 'kibana/public'; import { UIRoutes } from 'ui/routes'; -import { IScope } from 'angular'; +import { ILocationService, IScope } from 'angular'; import { npStart } from 'ui/new_platform'; import { htmlIdGenerator } from '@elastic/eui'; @@ -30,7 +30,7 @@ interface ForwardDefinition { } const matchAllWithPrefix = (prefixOrApp: string | App) => - `/${typeof prefixOrApp === 'string' ? prefixOrApp : prefixOrApp.id}:tail*?`; + `/${typeof prefixOrApp === 'string' ? prefixOrApp : prefixOrApp.id}/:tail*?`; /** * To be able to migrate and shim parts of the Kibana app plugin @@ -112,11 +112,20 @@ export class LocalApplicationService { template: `
`, controller($scope: IScope) { const element = document.getElementById(wrapperElementId)!; + let unmountHandler: AppUnmount | null = null; + let isUnmounted = false; + $scope.$on('$destroy', () => { + if (unmountHandler) { + unmountHandler(); + } + isUnmounted = true; + }); (async () => { - const onUnmount = await app.mount({ core: npStart.core }, { element, appBasePath: '' }); - $scope.$on('$destroy', () => { - onUnmount(); - }); + unmountHandler = await app.mount({ core: npStart.core }, { element, appBasePath: '' }); + // immediately unmount app if scope got destroyed in the meantime + if (isUnmounted) { + unmountHandler(); + } })(); }, }); @@ -124,9 +133,9 @@ export class LocalApplicationService { this.forwards.forEach(({ legacyAppId, newAppId, keepPrefix }) => { angularRouteManager.when(matchAllWithPrefix(legacyAppId), { - redirectTo: (_params: unknown, path: string, search: string) => { - const newPath = `/${newAppId}${keepPrefix ? path : path.replace(legacyAppId, '')}`; - return `${newPath}?${search}`; + resolveRedirectTo: ($location: ILocationService) => { + const url = $location.url(); + return `/${newAppId}${keepPrefix ? url : url.replace(legacyAppId, '')}`; }, }); }); From 35b508adff33ec6981c48a517eff385e5e4d9207 Mon Sep 17 00:00:00 2001 From: maryia-lapata Date: Tue, 12 Nov 2019 12:31:55 +0300 Subject: [PATCH 082/132] Pass services to NewVisModal as props --- .../embeddable/visualize_embeddable_factory.tsx | 11 ++++++++--- .../kibana/public/visualize/kibana_services.ts | 2 -- .../public/visualize/listing/visualize_listing.html | 2 ++ .../public/visualize/listing/visualize_listing.js | 10 +++++++++- .../public/visualize/wizard/new_vis_modal.test.tsx | 2 +- .../kibana/public/visualize/wizard/new_vis_modal.tsx | 12 ++++++++---- .../wizard/search_selection/search_selection.tsx | 4 ++-- .../kibana/public/visualize/wizard/show_new_vis.tsx | 7 ++++++- .../visualize/wizard/type_selection/new_vis_help.tsx | 5 ++--- .../wizard/type_selection/type_selection.tsx | 4 +++- 10 files changed, 41 insertions(+), 18 deletions(-) diff --git a/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable_factory.tsx b/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable_factory.tsx index 637e4db66889e..46dbc2533a269 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable_factory.tsx +++ b/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable_factory.tsx @@ -196,9 +196,14 @@ export class VisualizeEmbeddableFactory extends EmbeddableFactory< // TODO: This is a bit of a hack to preserve the original functionality. Ideally we will clean this up // to allow for in place creation of visualizations without having to navigate away to a new URL. if (this.visTypes) { - showNewVisModal(this.visTypes, { - editorParams: ['addToDashboard'], - }); + showNewVisModal( + this.visTypes, + { + editorParams: ['addToDashboard'], + }, + chrome.addBasePath, + chrome.getUiSettingsClient() + ); } return undefined; } diff --git a/src/legacy/core_plugins/kibana/public/visualize/kibana_services.ts b/src/legacy/core_plugins/kibana/public/visualize/kibana_services.ts index 3670fb9a02ebc..1cd12b4d97e57 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/kibana_services.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/kibana_services.ts @@ -84,7 +84,6 @@ export function clearServices() { // export types export { DocTitle } from 'ui/doc_title/doc_title'; -export { VisType } from 'ui/vis'; export { VisSavedObject } from 'ui/visualize/loader/types'; export { EmbeddableFactory, ErrorEmbeddable } from '../../../../../plugins/embeddable/public'; @@ -99,4 +98,3 @@ export { showSaveModal } from 'ui/saved_objects/show_saved_object_save_modal'; export { stateMonitorFactory } from 'ui/state_management/state_monitor_factory'; export { subscribeWithScope } from 'ui/utils/subscribe_with_scope'; export { getVisualizeLoader } from 'ui/visualize/loader'; -export { METRIC_TYPE, createUiStatsReporter } from '../../../ui_metric/public'; diff --git a/src/legacy/core_plugins/kibana/public/visualize/listing/visualize_listing.html b/src/legacy/core_plugins/kibana/public/visualize/listing/visualize_listing.html index edb7cccbd46a2..4511ac61f7396 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/listing/visualize_listing.html +++ b/src/legacy/core_plugins/kibana/public/visualize/listing/visualize_listing.html @@ -14,6 +14,8 @@ is-open="listingController.showNewVisModal" on-close="listingController.closeNewVisModal" vis-types-registry="listingController.visTypeRegistry" + add-base-path="listingController.addBasePath" + ui-settings="listingController.uiSettings" >
diff --git a/src/legacy/core_plugins/kibana/public/visualize/listing/visualize_listing.js b/src/legacy/core_plugins/kibana/public/visualize/listing/visualize_listing.js index e1da3e0a45115..80da5a2c45583 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/listing/visualize_listing.js +++ b/src/legacy/core_plugins/kibana/public/visualize/listing/visualize_listing.js @@ -29,7 +29,13 @@ export function initListingDirective(app, deps) { app.directive('visualizeListingTable', reactDirective => reactDirective(deps.wrapInI18nContext(VisualizeListingTable)) ); - app.directive('newVisModal', reactDirective => reactDirective(deps.wrapInI18nContext(NewVisModal))); + app.directive('newVisModal', reactDirective => reactDirective(deps.wrapInI18nContext(NewVisModal), [ + ['visTypesRegistry', { watchDepth: 'collection' }], + ['onClose', { watchDepth: 'reference' }], + ['addBasePath', { watchDepth: 'reference' }], + ['uiSettings', { watchDepth: 'reference' }], + 'isOpen', + ])); } export function VisualizeListingController($injector, createNewVis) { @@ -53,6 +59,8 @@ export function VisualizeListingController($injector, createNewVis) { timefilter.disableTimeRangeSelector(); this.showNewVisModal = false; + this.addBasePath = addBasePath; + this.uiSettings = uiSettings; this.createNewVis = () => { this.showNewVisModal = true; diff --git a/src/legacy/core_plugins/kibana/public/visualize/wizard/new_vis_modal.test.tsx b/src/legacy/core_plugins/kibana/public/visualize/wizard/new_vis_modal.test.tsx index e9c7198542828..706395f8c16b1 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/wizard/new_vis_modal.test.tsx +++ b/src/legacy/core_plugins/kibana/public/visualize/wizard/new_vis_modal.test.tsx @@ -21,7 +21,7 @@ import React from 'react'; import { mountWithIntl } from 'test_utils/enzyme_helpers'; import { NewVisModal } from './new_vis_modal'; -import { VisType } from '../kibana_services'; +import { VisType } from 'ui/vis'; import { TypesStart } from '../../../../visualizations/public/np_ready/public/types'; jest.mock('../kibana_services', () => { diff --git a/src/legacy/core_plugins/kibana/public/visualize/wizard/new_vis_modal.tsx b/src/legacy/core_plugins/kibana/public/visualize/wizard/new_vis_modal.tsx index f965d12de2457..6fc2e5e320e09 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/wizard/new_vis_modal.tsx +++ b/src/legacy/core_plugins/kibana/public/visualize/wizard/new_vis_modal.tsx @@ -22,18 +22,21 @@ import React from 'react'; import { EuiModal, EuiOverlayMask } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; +import { UiSettingsClientContract } from 'kibana/public'; +import { VisType } from 'ui/vis'; import { VisualizeConstants } from '../visualize_constants'; +import { createUiStatsReporter, METRIC_TYPE } from '../../../../ui_metric/public'; import { SearchSelection } from './search_selection'; import { TypeSelection } from './type_selection'; import { TypesStart, VisTypeAlias } from '../../../../visualizations/public/np_ready/public/types'; -import { getServices, METRIC_TYPE, VisType, createUiStatsReporter } from '../kibana_services'; - interface TypeSelectionProps { isOpen: boolean; onClose: () => void; visTypesRegistry: TypesStart; editorParams?: string[]; + addBasePath: (path: string) => string; + uiSettings: UiSettingsClientContract; } interface TypeSelectionState { @@ -53,7 +56,7 @@ class NewVisModal extends React.Component ); @@ -122,7 +126,7 @@ class NewVisModal extends React.Component void; diff --git a/src/legacy/core_plugins/kibana/public/visualize/wizard/show_new_vis.tsx b/src/legacy/core_plugins/kibana/public/visualize/wizard/show_new_vis.tsx index fa2ca6747bc40..8e422bef10b21 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/wizard/show_new_vis.tsx +++ b/src/legacy/core_plugins/kibana/public/visualize/wizard/show_new_vis.tsx @@ -21,6 +21,7 @@ import React from 'react'; import ReactDOM from 'react-dom'; import { I18nContext } from 'ui/i18n'; +import { UiSettingsClientContract } from 'kibana/public'; import { NewVisModal } from './new_vis_modal'; import { TypesStart } from '../../../../visualizations/public/np_ready/public/types'; @@ -30,7 +31,9 @@ interface ShowNewVisModalParams { export function showNewVisModal( visTypeRegistry: TypesStart, - { editorParams = [] }: ShowNewVisModalParams = {} + { editorParams = [] }: ShowNewVisModalParams = {}, + addBasePath: (path: string) => string, + uiSettings: UiSettingsClientContract ) { const container = document.createElement('div'); const onClose = () => { @@ -46,6 +49,8 @@ export function showNewVisModal( onClose={onClose} visTypesRegistry={visTypeRegistry} editorParams={editorParams} + addBasePath={addBasePath} + uiSettings={uiSettings} /> ); diff --git a/src/legacy/core_plugins/kibana/public/visualize/wizard/type_selection/new_vis_help.tsx b/src/legacy/core_plugins/kibana/public/visualize/wizard/type_selection/new_vis_help.tsx index a9d92276eaa56..f04a80af8d6f5 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/wizard/type_selection/new_vis_help.tsx +++ b/src/legacy/core_plugins/kibana/public/visualize/wizard/type_selection/new_vis_help.tsx @@ -22,10 +22,9 @@ import React, { Fragment } from 'react'; import { EuiText, EuiButton } from '@elastic/eui'; import { VisTypeAliasListEntry } from './type_selection'; -import { getServices } from '../../kibana_services'; - interface Props { promotedTypes: VisTypeAliasListEntry[]; + addBasePath: (path: string) => string; } export function NewVisHelp(props: Props) { @@ -43,7 +42,7 @@ export function NewVisHelp(props: Props) { {t.promotion!.description}

string; onVisTypeSelected: (visType: VisType | VisTypeAlias) => void; visTypesRegistry: TypesStart; showExperimental: boolean; @@ -153,6 +154,7 @@ class TypeSelection extends React.Component t.promotion)} + addBasePath={this.props.addBasePath} /> )} From 588c46a3af55068503473a734c076b90afdf2b79 Mon Sep 17 00:00:00 2001 From: maryia-lapata Date: Tue, 12 Nov 2019 12:46:35 +0300 Subject: [PATCH 083/132] Adjust unit tests --- .../__snapshots__/new_vis_modal.test.tsx.snap | 40 ++++++++++ .../visualize/wizard/new_vis_modal.test.tsx | 79 ++++++++++++------- .../type_selection/new_vis_help.test.tsx | 13 +-- 3 files changed, 92 insertions(+), 40 deletions(-) diff --git a/src/legacy/core_plugins/kibana/public/visualize/wizard/__snapshots__/new_vis_modal.test.tsx.snap b/src/legacy/core_plugins/kibana/public/visualize/wizard/__snapshots__/new_vis_modal.test.tsx.snap index 4aa614b68ea23..5be5f58994887 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/wizard/__snapshots__/new_vis_modal.test.tsx.snap +++ b/src/legacy/core_plugins/kibana/public/visualize/wizard/__snapshots__/new_vis_modal.test.tsx.snap @@ -2,6 +2,7 @@ exports[`NewVisModal filter for visualization types should render as expected 1`] = ` @@ -1287,6 +1307,7 @@ exports[`NewVisModal filter for visualization types should render as expected 1` exports[`NewVisModal should render as expected 1`] = ` diff --git a/src/legacy/core_plugins/kibana/public/visualize/wizard/new_vis_modal.test.tsx b/src/legacy/core_plugins/kibana/public/visualize/wizard/new_vis_modal.test.tsx index 706395f8c16b1..2b000a8098a99 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/wizard/new_vis_modal.test.tsx +++ b/src/legacy/core_plugins/kibana/public/visualize/wizard/new_vis_modal.test.tsx @@ -24,29 +24,7 @@ import { NewVisModal } from './new_vis_modal'; import { VisType } from 'ui/vis'; import { TypesStart } from '../../../../visualizations/public/np_ready/public/types'; -jest.mock('../kibana_services', () => { - const mock = { - addBasePath: jest.fn(path => `root${path}`), - uiSettings: { get: jest.fn() }, - createUiStatsReporter: () => jest.fn(), - }; - - return { - getServices: () => mock, - VisType: {}, - METRIC_TYPE: 'metricType', - }; -}); - -import { getServices } from '../kibana_services'; - -beforeEach(() => { - jest.clearAllMocks(); -}); - describe('NewVisModal', () => { - const settingsGet = getServices().uiSettings!.get as jest.Mock; - const defaultVisTypeParams = { hidden: false, visualization: class Controller { @@ -76,17 +54,36 @@ describe('NewVisModal', () => { }, getAliases: () => [], }; + const addBasePath = (url: string) => `testbasepath${url}`; + const settingsGet = jest.fn(); + const uiSettings: any = { get: settingsGet }; + + beforeEach(() => { + jest.clearAllMocks(); + }); it('should render as expected', () => { const wrapper = mountWithIntl( - null} visTypesRegistry={visTypes} /> + null} + visTypesRegistry={visTypes} + addBasePath={addBasePath} + uiSettings={uiSettings} + /> ); expect(wrapper).toMatchSnapshot(); }); it('should show a button for regular visualizations', () => { const wrapper = mountWithIntl( - null} visTypesRegistry={visTypes} /> + null} + visTypesRegistry={visTypes} + addBasePath={addBasePath} + uiSettings={uiSettings} + /> ); expect(wrapper.find('[data-test-subj="visType-vis"]').exists()).toBe(true); }); @@ -95,7 +92,13 @@ describe('NewVisModal', () => { it('should open the editor for visualizations without search', () => { window.location.assign = jest.fn(); const wrapper = mountWithIntl( - null} visTypesRegistry={visTypes} /> + null} + visTypesRegistry={visTypes} + addBasePath={addBasePath} + uiSettings={uiSettings} + /> ); const visButton = wrapper.find('button[data-test-subj="visType-vis"]'); visButton.simulate('click'); @@ -110,6 +113,8 @@ describe('NewVisModal', () => { onClose={() => null} visTypesRegistry={visTypes} editorParams={['foo=true', 'bar=42']} + addBasePath={addBasePath} + uiSettings={uiSettings} /> ); const visButton = wrapper.find('button[data-test-subj="visType-vis"]'); @@ -121,7 +126,13 @@ describe('NewVisModal', () => { describe('filter for visualization types', () => { it('should render as expected', () => { const wrapper = mountWithIntl( - null} visTypesRegistry={visTypes} /> + null} + visTypesRegistry={visTypes} + addBasePath={addBasePath} + uiSettings={uiSettings} + /> ); const searchBox = wrapper.find('input[data-test-subj="filterVisType"]'); searchBox.simulate('change', { target: { value: 'with' } }); @@ -133,7 +144,13 @@ describe('NewVisModal', () => { it('should not show experimental visualizations if visualize:enableLabs is false', () => { settingsGet.mockReturnValue(false); const wrapper = mountWithIntl( - null} visTypesRegistry={visTypes} /> + null} + visTypesRegistry={visTypes} + addBasePath={addBasePath} + uiSettings={uiSettings} + /> ); expect(wrapper.find('[data-test-subj="visType-visExp"]').exists()).toBe(false); }); @@ -141,7 +158,13 @@ describe('NewVisModal', () => { it('should show experimental visualizations if visualize:enableLabs is true', () => { settingsGet.mockReturnValue(true); const wrapper = mountWithIntl( - null} visTypesRegistry={visTypes} /> + null} + visTypesRegistry={visTypes} + addBasePath={addBasePath} + uiSettings={uiSettings} + /> ); expect(wrapper.find('[data-test-subj="visType-visExp"]').exists()).toBe(true); }); diff --git a/src/legacy/core_plugins/kibana/public/visualize/wizard/type_selection/new_vis_help.test.tsx b/src/legacy/core_plugins/kibana/public/visualize/wizard/type_selection/new_vis_help.test.tsx index 382f475669f5d..3093499a030c8 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/wizard/type_selection/new_vis_help.test.tsx +++ b/src/legacy/core_plugins/kibana/public/visualize/wizard/type_selection/new_vis_help.test.tsx @@ -21,18 +21,6 @@ import React from 'react'; import { shallowWithIntl } from 'test_utils/enzyme_helpers'; import { NewVisHelp } from './new_vis_help'; -jest.mock('../../kibana_services', () => { - return { - getServices: () => ({ - addBasePath: jest.fn((url: string) => `testbasepath${url}`), - }), - }; -}); - -beforeEach(() => { - jest.clearAllMocks(); -}); - describe('NewVisHelp', () => { it('should render as expected', () => { expect( @@ -53,6 +41,7 @@ describe('NewVisHelp', () => { stage: 'production', }, ]} + addBasePath={(url: string) => `testbasepath${url}`} /> ) ).toMatchInlineSnapshot(` From dcb9a88724b75eea6741549bf96ad23888a67bc3 Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Tue, 12 Nov 2019 11:41:36 +0100 Subject: [PATCH 084/132] remove unused dependencies --- .../kibana/public/dashboard/dashboard_app_controller.tsx | 6 ++---- src/legacy/core_plugins/kibana/public/dashboard/index.ts | 4 ---- .../core_plugins/kibana/public/dashboard/plugin.ts | 9 +-------- .../core_plugins/kibana/public/dashboard/render_app.ts | 1 - 4 files changed, 3 insertions(+), 17 deletions(-) diff --git a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx index ec5086f61e46c..3ac7d3adc6065 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx +++ b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx @@ -115,8 +115,6 @@ export class DashboardAppController { savedQueryService, embeddables, dashboardCapabilities, - docTitle, - dataStart, npDataStart: { query: { filterManager, @@ -136,7 +134,7 @@ export class DashboardAppController { const dash = ($scope.dash = $route.current.locals.dash); if (dash.id) { - docTitle.change(dash.title); + chrome.docTitle.change(dash.title); } const dashboardStateManager = new DashboardStateManager({ @@ -635,7 +633,7 @@ export class DashboardAppController { if (dash.id !== $routeParams.id) { kbnUrl.change(createDashboardEditUrl(dash.id)); } else { - docTitle.change(dash.lastSavedTitle); + chrome.docTitle.change(dash.lastSavedTitle); updateViewMode(ViewMode.VIEW); } } diff --git a/src/legacy/core_plugins/kibana/public/dashboard/index.ts b/src/legacy/core_plugins/kibana/public/dashboard/index.ts index 1d0c8d34f239b..69c827955ed8b 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/index.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/index.ts @@ -17,10 +17,8 @@ * under the License. */ -import { FeatureCatalogueRegistryProvider } from 'ui/registry/feature_catalogue'; import { npSetup, npStart } from 'ui/new_platform'; import { SavedObjectRegistryProvider } from 'ui/saved_objects'; -import { docTitle } from 'ui/doc_title/doc_title'; import chrome from 'ui/chrome'; import { IPrivate } from 'ui/private'; import { ShareContextMenuExtensionsRegistryProvider } from 'ui/share'; @@ -59,8 +57,6 @@ async function getAngularDependencies(): Promise Promise; localApplicationService: LocalApplicationService; - FeatureCatalogueRegistryProvider: any; - docTitle: any; }; feature_catalogue: FeatureCatalogueSetup; } @@ -75,12 +73,7 @@ export class DashboardPlugin implements Plugin { public setup( core: CoreSetup, { - __LEGACY: { - localApplicationService, - getAngularDependencies, - FeatureCatalogueRegistryProvider, - ...legacyServices - }, + __LEGACY: { localApplicationService, getAngularDependencies, ...legacyServices }, feature_catalogue, }: DashboardPluginSetupDependencies ) { diff --git a/src/legacy/core_plugins/kibana/public/dashboard/render_app.ts b/src/legacy/core_plugins/kibana/public/dashboard/render_app.ts index 8d3d5f104be2e..76122edc9d530 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/render_app.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/render_app.ts @@ -77,7 +77,6 @@ export interface RenderDeps { dashboardConfig: any; savedDashboards: any; dashboardCapabilities: any; - docTitle: any; uiSettings: UiSettingsClientContract; chrome: ChromeStart; addBasePath: (path: string) => string; From a21ba30a2d75cb9ef57528efc4250d85edbaa69f Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Tue, 12 Nov 2019 12:00:37 +0100 Subject: [PATCH 085/132] started centralizing and cleaning up imports --- .../dashboard/dashboard_app_controller.tsx | 5 +-- .../public/dashboard/dashboard_state.test.ts | 3 +- .../kibana/public/dashboard/legacy_imports.ts | 39 +++++++++++++++++++ .../public/dashboard/lib/save_dashboard.ts | 2 +- .../dashboard/lib/update_saved_dashboard.ts | 3 +- .../dashboard/top_nav/show_clone_modal.tsx | 6 +-- 6 files changed, 47 insertions(+), 11 deletions(-) create mode 100644 src/legacy/core_plugins/kibana/public/dashboard/legacy_imports.ts diff --git a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx index 3ac7d3adc6065..6dceb6902a705 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx +++ b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx @@ -38,13 +38,11 @@ import { State } from 'ui/state_management/state'; import { AppStateClass as TAppStateClass } from 'ui/state_management/app_state'; import { KbnUrl } from 'ui/url/kbn_url'; -import { IndexPattern } from 'ui/index_patterns'; import { SaveOptions } from 'ui/saved_objects/saved_object'; import { Subscription } from 'rxjs'; -import { SavedObjectFinder } from 'ui/saved_objects/components/saved_object_finder'; import { extractTimeFilter, changeTimeFilter, Query } from '../../../../../plugins/data/public'; import { esFilters } from '../../../../../plugins/data/public'; -import { FilterStateManager } from '../../../data/public'; +import { FilterStateManager, IndexPattern } from '../../../data/public'; import { SavedQuery } from '../../../data/public'; import { @@ -61,6 +59,7 @@ import { openAddPanelFlyout, } from '../../../embeddable_api/public/np_ready/public'; import { DashboardAppState, NavAction, ConfirmModalFn, SavedDashboardPanel } from './types'; +import { SavedObjectFinder } from '../../../../../plugins/kibana_react/public'; import { showOptionsPopover } from './top_nav/show_options_popover'; import { DashboardSaveModal } from './top_nav/save_modal'; diff --git a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_state.test.ts b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_state.test.ts index 603eee201cd84..2abe1a0a1327d 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_state.test.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_state.test.ts @@ -23,9 +23,8 @@ import { DashboardStateManager } from './dashboard_state_manager'; import { getAppStateMock, getSavedDashboardMock } from './__tests__'; import { AppStateClass } from 'ui/state_management/app_state'; import { DashboardAppState } from './types'; -import { TimeRange, TimefilterContract } from 'src/plugins/data/public'; +import { TimeRange, TimefilterContract, InputTimeRange } from 'src/plugins/data/public'; import { ViewMode } from 'src/plugins/embeddable/public'; -import { InputTimeRange } from 'ui/timefilter'; jest.mock('ui/registry/field_formats', () => ({ fieldFormats: { diff --git a/src/legacy/core_plugins/kibana/public/dashboard/legacy_imports.ts b/src/legacy/core_plugins/kibana/public/dashboard/legacy_imports.ts new file mode 100644 index 0000000000000..3c93f57b0f141 --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/dashboard/legacy_imports.ts @@ -0,0 +1,39 @@ + +/** + * The imports in this file are static functions and types which still live in legacy folders and are used + * within dashboard. To consolidate them all in one place, they are re-exported from this file. Eventually + * this list should become empty. Imports from the top level of shimmed or moved plugins can be imported + * directly where they are needed. + */ + +import chrome from 'ui/chrome'; + +export const legacyChrome = chrome; +export { State } from 'ui/state_management/state'; +export { AppState } from 'ui/state_management/app_state'; +export { AppStateClass } from 'ui/state_management/app_state'; +export { SaveOptions } from 'ui/saved_objects/saved_object'; +export { npSetup, npStart } from 'ui/new_platform'; +export { SavedObjectRegistryProvider } from 'ui/saved_objects'; +export { IPrivate } from 'ui/private'; +export { ShareContextMenuExtensionsRegistryProvider } from 'ui/share'; +export { SavedObjectSaveModal } from 'ui/saved_objects/components/saved_object_save_modal'; +export { subscribeWithScope } from 'ui/utils/subscribe_with_scope'; +// @ts-ignore +export { ConfirmationButtonTypes } from 'ui/modals/confirm_modal'; +export { showSaveModal, SaveResult } from 'ui/saved_objects/show_saved_object_save_modal'; +export { showShareContextMenu } from 'ui/share'; +export { migrateLegacyQuery } from 'ui/utils/migrate_legacy_query'; +export { KbnUrl } from 'ui/url/kbn_url'; +import { IPrivate } from 'ui/private'; +import { GlobalStateProvider } from 'ui/state_management/global_state'; +import { StateManagementConfigProvider } from 'ui/state_management/config_provider'; +import { AppStateProvider } from 'ui/state_management/app_state'; +import { PrivateProvider } from 'ui/private/private'; +import { EventsProvider } from 'ui/events'; +import { PersistedState } from 'ui/persisted_state'; +import { createTopNavDirective, createTopNavHelper } from 'ui/kbn_top_nav/kbn_top_nav'; +import { PromiseServiceCreator } from 'ui/promises/promises'; +import { KbnUrlProvider, RedirectWhenMissingProvider } from 'ui/url'; +import { confirmModalFactory } from 'ui/modals/confirm_modal'; +import { configureAppAngularModule } from 'ui/legacy_compat'; diff --git a/src/legacy/core_plugins/kibana/public/dashboard/lib/save_dashboard.ts b/src/legacy/core_plugins/kibana/public/dashboard/lib/save_dashboard.ts index 168f320b5ea7e..b52e78589c620 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/lib/save_dashboard.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/lib/save_dashboard.ts @@ -17,8 +17,8 @@ * under the License. */ +import { Timefilter } from 'src/plugins/data/public'; import { SaveOptions } from 'ui/saved_objects/saved_object'; -import { Timefilter } from 'ui/timefilter'; import { updateSavedDashboard } from './update_saved_dashboard'; import { DashboardStateManager } from '../dashboard_state_manager'; diff --git a/src/legacy/core_plugins/kibana/public/dashboard/lib/update_saved_dashboard.ts b/src/legacy/core_plugins/kibana/public/dashboard/lib/update_saved_dashboard.ts index 707b5a0f5f5f5..87839c36e3252 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/lib/update_saved_dashboard.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/lib/update_saved_dashboard.ts @@ -19,8 +19,7 @@ import _ from 'lodash'; import { AppState } from 'ui/state_management/app_state'; -import { Timefilter } from 'ui/timefilter'; -import { RefreshInterval } from 'src/plugins/data/public'; +import { RefreshInterval, Timefilter } from 'src/plugins/data/public'; import { FilterUtils } from './filter_utils'; import { SavedObjectDashboard } from '../saved_dashboard/saved_dashboard'; diff --git a/src/legacy/core_plugins/kibana/public/dashboard/top_nav/show_clone_modal.tsx b/src/legacy/core_plugins/kibana/public/dashboard/top_nav/show_clone_modal.tsx index c3cd5621b2c88..af1020e01e0c5 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/top_nav/show_clone_modal.tsx +++ b/src/legacy/core_plugins/kibana/public/dashboard/top_nav/show_clone_modal.tsx @@ -17,10 +17,10 @@ * under the License. */ -import { I18nContext } from 'ui/i18n'; import React from 'react'; import ReactDOM from 'react-dom'; import { i18n } from '@kbn/i18n'; +import { I18nProvider } from '@kbn/i18n/react'; import { DashboardCloneModal } from './clone_modal'; export function showCloneModal( @@ -54,7 +54,7 @@ export function showCloneModal( }; document.body.appendChild(container); const element = ( - + - + ); ReactDOM.render(element, container); } From 14a0de3dfe6bd1b2ef53924e4be5b2f0ea92fb69 Mon Sep 17 00:00:00 2001 From: maryia-lapata Date: Tue, 12 Nov 2019 16:56:36 +0300 Subject: [PATCH 086/132] Add missed changes --- src/legacy/core_plugins/data/public/index.ts | 11 +++ .../public/dashboard/_dashboard_app.scss | 2 +- .../kibana/public/dashboard/app.js | 29 ++------ .../dashboard/dashboard_app_controller.tsx | 23 +++---- .../public/dashboard/global_state_sync.ts | 67 +++++++++++++++++++ .../kibana/public/dashboard/plugin.ts | 1 - .../ui/public/vis/vis_filters/vis_filters.js | 7 +- .../viewport/_dashboard_viewport.scss | 2 + 8 files changed, 101 insertions(+), 41 deletions(-) create mode 100644 src/legacy/core_plugins/kibana/public/dashboard/global_state_sync.ts diff --git a/src/legacy/core_plugins/data/public/index.ts b/src/legacy/core_plugins/data/public/index.ts index 60828b4a2a202..0a95e6d2a037e 100644 --- a/src/legacy/core_plugins/data/public/index.ts +++ b/src/legacy/core_plugins/data/public/index.ts @@ -61,3 +61,14 @@ export { mockFields, mockIndexPattern, } from './index_patterns'; + +/** + * These functions can be used to register the angular wrappers for react components + * in a separate module to use them without relying on the uiModules module tree. + * */ +export { + createFilterBarHelper, + createFilterBarDirective, + createApplyFiltersPopoverDirective, + createApplyFiltersPopoverHelper, +} from './shim/legacy_module'; diff --git a/src/legacy/core_plugins/kibana/public/dashboard/_dashboard_app.scss b/src/legacy/core_plugins/kibana/public/dashboard/_dashboard_app.scss index eebfad5979d68..14c35759d70a9 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/_dashboard_app.scss +++ b/src/legacy/core_plugins/kibana/public/dashboard/_dashboard_app.scss @@ -1,7 +1,7 @@ .dshAppContainer { - flex: 1; display: flex; flex-direction: column; + height: 100%; } .dshStartScreen { diff --git a/src/legacy/core_plugins/kibana/public/dashboard/app.js b/src/legacy/core_plugins/kibana/public/dashboard/app.js index 3cc290cab7968..c63c9e3ba660b 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/app.js +++ b/src/legacy/core_plugins/kibana/public/dashboard/app.js @@ -32,8 +32,8 @@ import { } from '../../../../../plugins/kibana_utils/public'; import { DashboardListing, EMPTY_FILTER } from './listing/dashboard_listing'; import { addHelpMenuToAppChrome } from './help_menu/help_menu_util'; -import { start as data } from '../../../data/public/legacy'; import { registerTimefilterWithGlobalStateFactory } from '../../../../ui/public/timefilter/setup_router'; +import { syncOnMount } from './global_state_sync'; export function initDashboardApp(app, deps) { initDashboardAppDirective(app, deps); @@ -50,29 +50,12 @@ export function initDashboardApp(app, deps) { } app.run(globalState => { - globalState.fetch(); - const hasGlobalURLState = Object.keys(globalState.toObject()).length; - if (!globalState.time) { - globalState.time = deps.dataStart.timefilter.timefilter.getTime(); - } - if (!globalState.refreshInterval) { - globalState.refreshInterval = deps.dataStart.timefilter.timefilter.getRefreshInterval(); - } - // only inject cross app global state if there is none in the url itself (that takes precedence) - if (!hasGlobalURLState) { - const globalStateStuff = deps.sessionStorage.get('oss-kibana-cross-app-state') || {}; - Object.keys(globalStateStuff).forEach(key => { - globalState[key] = globalStateStuff[key]; - }); - } else { - globalState.$inheritedGlobalState = true; - } - globalState.save(); + syncOnMount(globalState, deps.npDataStart); }); app.run((globalState, $rootScope) => { registerTimefilterWithGlobalStateFactory( - deps.dataStart.timefilter.timefilter, + deps.npDataStart.timefilter.timefilter, globalState, $rootScope ); @@ -137,7 +120,7 @@ export function initDashboardApp(app, deps) { }, resolve: { dash: function ($rootScope, $route, redirectWhenMissing, kbnUrl) { - return ensureDefaultIndexPattern(deps.core, data, $rootScope, kbnUrl).then(() => { + return ensureDefaultIndexPattern(deps.core, deps.dataStart, $rootScope, kbnUrl).then(() => { const savedObjectsClient = deps.savedObjectsClient; const title = $route.current.params.title; if (title) { @@ -172,7 +155,7 @@ export function initDashboardApp(app, deps) { requireUICapability: 'dashboard.createNew', resolve: { dash: function (redirectWhenMissing, $rootScope, kbnUrl) { - return ensureDefaultIndexPattern(deps.core, data, $rootScope, kbnUrl) + return ensureDefaultIndexPattern(deps.core, deps.dataStart, $rootScope, kbnUrl) .then(() => { return deps.savedDashboards.get(); }) @@ -192,7 +175,7 @@ export function initDashboardApp(app, deps) { dash: function ($rootScope, $route, redirectWhenMissing, kbnUrl, AppState) { const id = $route.current.params.id; - return ensureDefaultIndexPattern(deps.core, data, $rootScope, kbnUrl) + return ensureDefaultIndexPattern(deps.core, deps.dataStart, $rootScope, kbnUrl) .then(() => { return deps.savedDashboards.get(id); }) diff --git a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx index c688eccc5c9e7..ec5086f61e46c 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx +++ b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx @@ -42,14 +42,10 @@ import { IndexPattern } from 'ui/index_patterns'; import { SaveOptions } from 'ui/saved_objects/saved_object'; import { Subscription } from 'rxjs'; import { SavedObjectFinder } from 'ui/saved_objects/components/saved_object_finder'; -import { - FilterStateManager, - Query, - SavedQuery, - extractTimeFilter, - changeTimeFilter, -} from '../../../../../plugins/data/public'; +import { extractTimeFilter, changeTimeFilter, Query } from '../../../../../plugins/data/public'; import { esFilters } from '../../../../../plugins/data/public'; +import { FilterStateManager } from '../../../data/public'; +import { SavedQuery } from '../../../data/public'; import { DashboardContainer, @@ -120,14 +116,17 @@ export class DashboardAppController { embeddables, dashboardCapabilities, docTitle, - dataStart: { - timefilter: { timefilter }, + dataStart, + npDataStart: { + query: { + filterManager, + timefilter: { timefilter }, + }, }, - npDataStart, core: { notifications, overlays, chrome, injectedMetadata }, }: DashboardAppControllerDependencies) { - new FilterStateManager(globalState, getAppState, npDataStart.query.filterManager); - const queryFilter = npDataStart.query.filterManager; + new FilterStateManager(globalState, getAppState, filterManager); + const queryFilter = filterManager; function getUnhashableStates(): State[] { return [getAppState(), globalState].filter(Boolean); diff --git a/src/legacy/core_plugins/kibana/public/dashboard/global_state_sync.ts b/src/legacy/core_plugins/kibana/public/dashboard/global_state_sync.ts new file mode 100644 index 0000000000000..2760f21f203b7 --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/dashboard/global_state_sync.ts @@ -0,0 +1,67 @@ +/* + * 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 { State } from 'ui/state_management/state'; +import { DataPublicPluginStart as NpDataStart } from '../../../../../plugins/data/public'; + +/** + * Helper function to sync the global state with the various state providers + * when a local angular application mounts. There are three different ways + * global state can be passed into the application: + * * parameter in the URL hash - e.g. shared link + * * in-memory state in the data plugin exports (timefilter and filterManager) - e.g. default values + * + * This function looks up the three sources (earlier in the list means it takes precedence), + * puts it into the globalState object and syncs it with the url. + * + * Currently the legacy chrome takes care of restoring the global state when navigating from + * one app to another - to migrate away from that it will become necessary to also write the current + * state to local storage + */ +export function syncOnMount( + globalState: State, + { + query: { + filterManager, + timefilter: { timefilter }, + }, + }: NpDataStart +) { + // pull in global state information from the URL + globalState.fetch(); + // remember whether there were info in the URL + const hasGlobalURLState = Boolean(Object.keys(globalState.toObject()).length); + + // sync kibana platform state with the angular global state + if (!globalState.time) { + globalState.time = timefilter.getTime(); + } + if (!globalState.refreshInterval) { + globalState.refreshInterval = timefilter.getRefreshInterval(); + } + if (!globalState.filters && filterManager.getGlobalFilters().length > 0) { + globalState.filters = filterManager.getGlobalFilters(); + } + // only inject cross app global state if there is none in the url itself (that takes precedence) + if (hasGlobalURLState) { + // set flag the global state is set from the URL + globalState.$inheritedGlobalState = true; + } + globalState.save(); +} diff --git a/src/legacy/core_plugins/kibana/public/dashboard/plugin.ts b/src/legacy/core_plugins/kibana/public/dashboard/plugin.ts index d0741db58cd55..50963fa6a57fd 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/plugin.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/plugin.ts @@ -115,7 +115,6 @@ export class DashboardPlugin implements Plugin { embeddables, dashboardCapabilities: contextCore.application.capabilities.dashboard, localStorage: new Storage(localStorage), - sessionStorage: new Storage(sessionStorage), }; const { renderApp } = await import('./render_app'); return renderApp(params.element, params.appBasePath, deps); diff --git a/src/legacy/ui/public/vis/vis_filters/vis_filters.js b/src/legacy/ui/public/vis/vis_filters/vis_filters.js index 9e70fb45cd311..5194feb96259c 100644 --- a/src/legacy/ui/public/vis/vis_filters/vis_filters.js +++ b/src/legacy/ui/public/vis/vis_filters/vis_filters.js @@ -19,11 +19,10 @@ import { npStart } from 'ui/new_platform'; import { onBrushEvent } from './brush_event'; -import { uniqFilters } from '../../../../../plugins/data/public'; -import { toggleFilterNegated } from '@kbn/es-query'; import _ from 'lodash'; -import { changeTimeFilter, extractTimeFilter } from '../../../../core_plugins/data/public/timefilter'; import { start as data } from '../../../../core_plugins/data/public/legacy'; +import { uniqFilters, esFilters, changeTimeFilter, extractTimeFilter } from '../../../../../plugins/data/public'; + /** * For terms aggregations on `__other__` buckets, this assembles a list of applicable filter * terms based on a specific cell in the tabified data. @@ -96,7 +95,7 @@ const createFiltersFromEvent = (event) => { if (filter) { filter.forEach(f => { if (event.negate) { - f = toggleFilterNegated(f); + f = esFilters.toggleFilterNegated(f); } filters.push(f); }); diff --git a/src/plugins/dashboard_embeddable_container/public/embeddable/viewport/_dashboard_viewport.scss b/src/plugins/dashboard_embeddable_container/public/embeddable/viewport/_dashboard_viewport.scss index 7cbe135115877..9575908146d1d 100644 --- a/src/plugins/dashboard_embeddable_container/public/embeddable/viewport/_dashboard_viewport.scss +++ b/src/plugins/dashboard_embeddable_container/public/embeddable/viewport/_dashboard_viewport.scss @@ -1,8 +1,10 @@ .dshDashboardViewport { + height: 100%; width: 100%; background-color: $euiColorEmptyShade; } .dshDashboardViewport-withMargins { width: 100%; + height: 100%; } From 055e6018110c4231bdf19538788ab18841ec2bd3 Mon Sep 17 00:00:00 2001 From: maryia-lapata Date: Tue, 12 Nov 2019 17:16:42 +0300 Subject: [PATCH 087/132] Fix TS --- .../kibana/public/dashboard/render_app.ts | 16 ---------------- .../kibana/public/visualize/app.js | 8 ++++---- .../kibana/public/visualize/editor/editor.js | 11 +++++++---- .../public/visualize/global_state_sync.ts | 19 +++++++++++++------ .../public/visualize/kibana_services.ts | 1 - .../kibana/public/visualize/plugin.ts | 1 - 6 files changed, 24 insertions(+), 32 deletions(-) diff --git a/src/legacy/core_plugins/kibana/public/dashboard/render_app.ts b/src/legacy/core_plugins/kibana/public/dashboard/render_app.ts index ca219e6a74d95..8d3d5f104be2e 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/render_app.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/render_app.ts @@ -20,7 +20,6 @@ import { EuiConfirmModal } from '@elastic/eui'; import angular, { IModule } from 'angular'; import { IPrivate } from 'ui/private'; -import { State } from 'ui/state_management/state'; import { i18nDirective, i18nFilter, I18nProvider } from '@kbn/i18n/angular'; // @ts-ignore import { GlobalStateProvider } from 'ui/state_management/global_state'; @@ -85,7 +84,6 @@ export interface RenderDeps { savedQueryService: SavedQueryService; embeddables: ReturnType; localStorage: Storage; - sessionStorage: Storage; } let angularModuleInstance: IModule | null = null; @@ -99,21 +97,7 @@ export const renderApp = (element: HTMLElement, appBasePath: string, deps: Rende initDashboardApp(angularModuleInstance, deps); } const $injector = mountDashboardApp(appBasePath, element); - // const hasGlobalURLState = window.location.hash.includes('_g='); - // // only inject global state if there is none in the url itself (that takes precedence) - // if (!hasGlobalURLState) { - // const globalStateStuff = deps.sessionStorage.get('oss-kibana-cross-app-state') || {}; - // const globalState = $injector.get('globalState'); - // globalState.time = deps.dataStart.timefilter.timefilter.getTime(); - // globalState.refreshInterval = deps.dataStart.timefilter.timefilter.getRefreshInterval(); - // Object.keys(globalStateStuff).forEach(key => { - // globalState[key] = globalStateStuff[key]; - // }); - // globalState.save(); - // } return () => { - const currentGlobalState = $injector.get('globalState'); - deps.sessionStorage.set('oss-kibana-cross-app-state', currentGlobalState.toObject()); $injector.get('$rootScope').$destroy(); }; }; diff --git a/src/legacy/core_plugins/kibana/public/visualize/app.js b/src/legacy/core_plugins/kibana/public/visualize/app.js index 6163316abe552..dd910ae96a3b4 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/app.js +++ b/src/legacy/core_plugins/kibana/public/visualize/app.js @@ -42,12 +42,12 @@ export function initVisualizeApp(app, deps) { initVisualizeAppDirective(app, deps); app.run(globalState => { - syncOnMount(globalState, deps.dataStart, deps.npDataStart); + syncOnMount(globalState, deps.npDataStart); }); app.run((globalState, $rootScope) => { registerTimefilterWithGlobalStateFactory( - deps.dataStart.timefilter.timefilter, + deps.npDataStart.timefilter.timefilter, globalState, $rootScope ); @@ -83,7 +83,7 @@ export function initVisualizeApp(app, deps) { controllerAs: 'listingController', resolve: { createNewVis: () => false, - hasDefaultIndex: ($rootScope, kbnUrl) => ensureDefaultIndexPattern(deps.core, deps.dataStart, $rootScope, kbnUrl), + hasDefaultIndex: ($rootScope, kbnUrl) => ensureDefaultIndexPattern(deps.core, deps.npDataStart, $rootScope, kbnUrl), }, }) .when(VisualizeConstants.WIZARD_STEP_1_PAGE_PATH, { @@ -94,7 +94,7 @@ export function initVisualizeApp(app, deps) { controllerAs: 'listingController', resolve: { createNewVis: () => true, - hasDefaultIndex: ($rootScope, kbnUrl) => ensureDefaultIndexPattern(deps.core, deps.dataStart, $rootScope, kbnUrl), + hasDefaultIndex: ($rootScope, kbnUrl) => ensureDefaultIndexPattern(deps.core, deps.npDataStart, $rootScope, kbnUrl), }, }) .when(VisualizeConstants.CREATE_PATH, { diff --git a/src/legacy/core_plugins/kibana/public/visualize/editor/editor.js b/src/legacy/core_plugins/kibana/public/visualize/editor/editor.js index 795af3883256d..bffe873c74220 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/editor/editor.js +++ b/src/legacy/core_plugins/kibana/public/visualize/editor/editor.js @@ -80,8 +80,11 @@ function VisualizeAppController( localStorage, visualizeCapabilities, shareContextMenuExtensions, - dataStart: { - timefilter: { timefilter }, + npDataStart: { + query: { + filterManager, + timefilter: { timefilter }, + }, }, toastNotifications, chromeLegacy, @@ -89,12 +92,12 @@ function VisualizeAppController( docTitle, getBasePath, docLinks, - queryFilter, savedQueryService, uiSettings, } = getServices(); - new FilterStateManager(globalState, getAppState, queryFilter); + new FilterStateManager(globalState, getAppState, filterManager); + const queryFilter = filterManager; // Retrieve the resolved SavedVis instance. const savedVis = $route.current.locals.savedVis; // vis is instance of src/legacy/ui/public/vis/vis.js. diff --git a/src/legacy/core_plugins/kibana/public/visualize/global_state_sync.ts b/src/legacy/core_plugins/kibana/public/visualize/global_state_sync.ts index 95d44b8342e37..2760f21f203b7 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/global_state_sync.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/global_state_sync.ts @@ -18,7 +18,6 @@ */ import { State } from 'ui/state_management/state'; -import { DataStart } from '../../../data/public'; import { DataPublicPluginStart as NpDataStart } from '../../../../../plugins/data/public'; /** @@ -35,7 +34,15 @@ import { DataPublicPluginStart as NpDataStart } from '../../../../../plugins/dat * one app to another - to migrate away from that it will become necessary to also write the current * state to local storage */ -export function syncOnMount(globalState: State, data: DataStart, npData: NpDataStart) { +export function syncOnMount( + globalState: State, + { + query: { + filterManager, + timefilter: { timefilter }, + }, + }: NpDataStart +) { // pull in global state information from the URL globalState.fetch(); // remember whether there were info in the URL @@ -43,13 +50,13 @@ export function syncOnMount(globalState: State, data: DataStart, npData: NpDataS // sync kibana platform state with the angular global state if (!globalState.time) { - globalState.time = data.timefilter.timefilter.getTime(); + globalState.time = timefilter.getTime(); } if (!globalState.refreshInterval) { - globalState.refreshInterval = data.timefilter.timefilter.getRefreshInterval(); + globalState.refreshInterval = timefilter.getRefreshInterval(); } - if (!globalState.filters && npData.query.filterManager.getGlobalFilters().length > 0) { - globalState.filters = npData.query.filterManager.getGlobalFilters(); + if (!globalState.filters && filterManager.getGlobalFilters().length > 0) { + globalState.filters = filterManager.getGlobalFilters(); } // only inject cross app global state if there is none in the url itself (that takes precedence) if (hasGlobalURLState) { diff --git a/src/legacy/core_plugins/kibana/public/visualize/kibana_services.ts b/src/legacy/core_plugins/kibana/public/visualize/kibana_services.ts index 1cd12b4d97e57..c8d741612e618 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/kibana_services.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/kibana_services.ts @@ -57,7 +57,6 @@ export interface VisualizeKibanaServices { savedObjectsClient: SavedObjectsClientContract; savedQueryService: SavedQueryService; savedVisualizations: SavedVisualizations; - sessionStorage: Storage; uiSettings: UiSettingsClientContract; visualizeCapabilities: any; visualizations: any; diff --git a/src/legacy/core_plugins/kibana/public/visualize/plugin.ts b/src/legacy/core_plugins/kibana/public/visualize/plugin.ts index 41c2d1bef4a7a..4a2750d709094 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/plugin.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/plugin.ts @@ -134,7 +134,6 @@ export class VisualizePlugin implements Plugin { queryFilter: npDataStart.query.filterManager, savedObjectsClient, savedQueryService: dataStart.search.services.savedQueryService, - sessionStorage: new Storage(sessionStorage), toastNotifications: contextCore.notifications.toasts, uiSettings: contextCore.uiSettings, visualizeCapabilities: contextCore.application.capabilities.visualize, From 84f4f40dd861f72e07340e7179d05443da104665 Mon Sep 17 00:00:00 2001 From: maryia-lapata Date: Tue, 12 Nov 2019 17:55:31 +0300 Subject: [PATCH 088/132] Add ts-ignore --- packages/kbn-i18n/src/core/i18n.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/kbn-i18n/src/core/i18n.ts b/packages/kbn-i18n/src/core/i18n.ts index c66555ab015dc..a0219bf06393e 100644 --- a/packages/kbn-i18n/src/core/i18n.ts +++ b/packages/kbn-i18n/src/core/i18n.ts @@ -17,6 +17,7 @@ * under the License. */ +// @ts-ignore import memoizeIntlConstructor from 'intl-format-cache'; import IntlMessageFormat from 'intl-messageformat'; import IntlRelativeFormat from 'intl-relativeformat'; @@ -38,6 +39,7 @@ let currentLocale = EN_LOCALE; let formats = EN_FORMATS; IntlMessageFormat.defaultLocale = defaultLocale; +// @ts-ignore IntlRelativeFormat.defaultLocale = defaultLocale; /** @@ -123,6 +125,7 @@ export function setDefaultLocale(locale: string) { defaultLocale = normalizeLocale(locale); IntlMessageFormat.defaultLocale = defaultLocale; + // @ts-ignore IntlRelativeFormat.defaultLocale = defaultLocale; } From 2b22082fe2ac0a5e5761ce77df541051fcb013fa Mon Sep 17 00:00:00 2001 From: maryia-lapata Date: Tue, 12 Nov 2019 18:09:53 +0300 Subject: [PATCH 089/132] Fix dataStart --- .../kibana/public/dashboard/app.js | 2 +- .../kibana/public/visualize/app.js | 6 ++--- .../kibana/public/visualize/editor/editor.js | 3 ++- .../visualize/listing/visualize_listing.js | 22 +++++++++++-------- 4 files changed, 19 insertions(+), 14 deletions(-) diff --git a/src/legacy/core_plugins/kibana/public/dashboard/app.js b/src/legacy/core_plugins/kibana/public/dashboard/app.js index c63c9e3ba660b..76d11d51a02cd 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/app.js +++ b/src/legacy/core_plugins/kibana/public/dashboard/app.js @@ -55,7 +55,7 @@ export function initDashboardApp(app, deps) { app.run((globalState, $rootScope) => { registerTimefilterWithGlobalStateFactory( - deps.npDataStart.timefilter.timefilter, + deps.npDataStart.query.timefilter.timefilter, globalState, $rootScope ); diff --git a/src/legacy/core_plugins/kibana/public/visualize/app.js b/src/legacy/core_plugins/kibana/public/visualize/app.js index dd910ae96a3b4..dc11e86f58c19 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/app.js +++ b/src/legacy/core_plugins/kibana/public/visualize/app.js @@ -47,7 +47,7 @@ export function initVisualizeApp(app, deps) { app.run((globalState, $rootScope) => { registerTimefilterWithGlobalStateFactory( - deps.npDataStart.timefilter.timefilter, + deps.npDataStart.query.timefilter.timefilter, globalState, $rootScope ); @@ -83,7 +83,7 @@ export function initVisualizeApp(app, deps) { controllerAs: 'listingController', resolve: { createNewVis: () => false, - hasDefaultIndex: ($rootScope, kbnUrl) => ensureDefaultIndexPattern(deps.core, deps.npDataStart, $rootScope, kbnUrl), + hasDefaultIndex: ($rootScope, kbnUrl) => ensureDefaultIndexPattern(deps.core, deps.dataStart, $rootScope, kbnUrl), }, }) .when(VisualizeConstants.WIZARD_STEP_1_PAGE_PATH, { @@ -94,7 +94,7 @@ export function initVisualizeApp(app, deps) { controllerAs: 'listingController', resolve: { createNewVis: () => true, - hasDefaultIndex: ($rootScope, kbnUrl) => ensureDefaultIndexPattern(deps.core, deps.npDataStart, $rootScope, kbnUrl), + hasDefaultIndex: ($rootScope, kbnUrl) => ensureDefaultIndexPattern(deps.core, deps.dataStart, $rootScope, kbnUrl), }, }) .when(VisualizeConstants.CREATE_PATH, { diff --git a/src/legacy/core_plugins/kibana/public/visualize/editor/editor.js b/src/legacy/core_plugins/kibana/public/visualize/editor/editor.js index bffe873c74220..edc7a253a3572 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/editor/editor.js +++ b/src/legacy/core_plugins/kibana/public/visualize/editor/editor.js @@ -28,8 +28,9 @@ import { migrateAppState } from './lib'; import { DashboardConstants } from '../../dashboard/dashboard_constants'; import { VisualizeConstants } from '../visualize_constants'; import { getEditBreadcrumbs } from '../breadcrumbs'; -import { extractTimeFilter, changeTimeFilter, FilterStateManager } from '../../../../../../plugins/data/public'; +import { extractTimeFilter, changeTimeFilter } from '../../../../../../plugins/data/public'; import { addHelpMenuToAppChrome } from '../help_menu/help_menu_util'; +import { FilterStateManager } from '../../../../data/public'; import { initVisEditorDirective } from './visualization_editor'; import { initVisualizationDirective } from './visualization'; diff --git a/src/legacy/core_plugins/kibana/public/visualize/listing/visualize_listing.js b/src/legacy/core_plugins/kibana/public/visualize/listing/visualize_listing.js index 80da5a2c45583..61fa4a9a23e3f 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/listing/visualize_listing.js +++ b/src/legacy/core_plugins/kibana/public/visualize/listing/visualize_listing.js @@ -29,13 +29,15 @@ export function initListingDirective(app, deps) { app.directive('visualizeListingTable', reactDirective => reactDirective(deps.wrapInI18nContext(VisualizeListingTable)) ); - app.directive('newVisModal', reactDirective => reactDirective(deps.wrapInI18nContext(NewVisModal), [ - ['visTypesRegistry', { watchDepth: 'collection' }], - ['onClose', { watchDepth: 'reference' }], - ['addBasePath', { watchDepth: 'reference' }], - ['uiSettings', { watchDepth: 'reference' }], - 'isOpen', - ])); + app.directive('newVisModal', reactDirective => + reactDirective(deps.wrapInI18nContext(NewVisModal), [ + ['visTypesRegistry', { watchDepth: 'collection' }], + ['onClose', { watchDepth: 'reference' }], + ['addBasePath', { watchDepth: 'reference' }], + ['uiSettings', { watchDepth: 'reference' }], + 'isOpen', + ]) + ); } export function VisualizeListingController($injector, createNewVis) { @@ -45,8 +47,10 @@ export function VisualizeListingController($injector, createNewVis) { chromeLegacy, savedObjectRegistry, savedObjectClient, - dataStart: { - timefilter: { timefilter }, + npDataStart: { + query: { + timefilter: { timefilter }, + }, }, toastNotifications, uiSettings, From 7ec261de0ff326896f5117c2d10d94a54fc3f719 Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Tue, 12 Nov 2019 16:35:52 +0100 Subject: [PATCH 090/132] centralize external imports --- .../dashboard/__tests__/get_app_state_mock.ts | 2 +- .../kibana/public/dashboard/app.js | 7 ++- .../kibana/public/dashboard/dashboard_app.tsx | 15 +++--- .../dashboard/dashboard_app_controller.tsx | 29 +++++----- .../public/dashboard/dashboard_state.test.ts | 6 +-- .../dashboard/dashboard_state_manager.ts | 15 ++++-- .../public/dashboard/global_state_sync.ts | 2 +- .../kibana/public/dashboard/index.ts | 15 +++--- .../kibana/public/dashboard/legacy_imports.ts | 53 ++++++++++++++----- .../public/dashboard/lib/save_dashboard.ts | 6 +-- .../dashboard/lib/update_saved_dashboard.ts | 6 +-- .../dashboard/listing/dashboard_listing.js | 42 ++++++++------- .../kibana/public/dashboard/render_app.ts | 36 ++++++------- .../public/dashboard/top_nav/save_modal.tsx | 4 +- .../top_nav/show_options_popover.tsx | 8 +-- .../kibana/public/dashboard/types.ts | 5 +- 16 files changed, 139 insertions(+), 112 deletions(-) diff --git a/src/legacy/core_plugins/kibana/public/dashboard/__tests__/get_app_state_mock.ts b/src/legacy/core_plugins/kibana/public/dashboard/__tests__/get_app_state_mock.ts index 1f2094d68063d..d9dea35a8a1c0 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/__tests__/get_app_state_mock.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/__tests__/get_app_state_mock.ts @@ -17,7 +17,7 @@ * under the License. */ -import { AppStateClass } from 'ui/state_management/app_state'; +import { AppStateClass } from '../legacy_imports'; /** * A poor excuse for a mock just to get some basic tests to run in jest without requiring the injector. diff --git a/src/legacy/core_plugins/kibana/public/dashboard/app.js b/src/legacy/core_plugins/kibana/public/dashboard/app.js index e174627c589f4..6e80c9694152d 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/app.js +++ b/src/legacy/core_plugins/kibana/public/dashboard/app.js @@ -18,12 +18,11 @@ */ import { i18n } from '@kbn/i18n'; -import { wrapInI18nContext } from 'ui/i18n'; -import { ensureDefaultIndexPattern } from 'ui/legacy_compat'; import dashboardTemplate from './dashboard_app.html'; import dashboardListingTemplate from './listing/dashboard_listing_ng_wrapper.html'; +import { ensureDefaultIndexPattern } from './legacy_imports'; import { initDashboardAppDirective } from './dashboard_app'; import { DashboardConstants, createDashboardEditUrl } from './dashboard_constants'; import { @@ -39,7 +38,7 @@ export function initDashboardApp(app, deps) { initDashboardAppDirective(app, deps); app.directive('dashboardListing', function (reactDirective) { - return reactDirective(wrapInI18nContext(DashboardListing)); + return reactDirective(DashboardListing); }); function createNewDashboardCtrl($scope) { @@ -175,7 +174,7 @@ export function initDashboardApp(app, deps) { dash: function ($rootScope, $route, redirectWhenMissing, kbnUrl, AppState) { const id = $route.current.params.id; - return ensureDefaultIndexPattern(deps.core, data, $rootScope, kbnUrl) + return ensureDefaultIndexPattern(deps.core, deps.dataStart, $rootScope, kbnUrl) .then(() => { return deps.savedDashboards.get(id); }) diff --git a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app.tsx b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app.tsx index 31ce037903dc5..4c7f625dd6938 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app.tsx +++ b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app.tsx @@ -17,19 +17,18 @@ * under the License. */ -import { IInjector } from 'ui/chrome'; - -import { - AppStateClass as TAppStateClass, - AppState as TAppState, -} from 'ui/state_management/app_state'; - -import { KbnUrl } from 'ui/url/kbn_url'; import { TimeRange } from 'src/plugins/data/public'; import { StaticIndexPattern, Query, SavedQuery } from 'plugins/data'; import moment from 'moment'; import { Subscription } from 'rxjs'; +import { + AppStateClass as TAppStateClass, + AppState as TAppState, + IInjector, + KbnUrl, +} from './legacy_imports'; + import { ViewMode } from '../../../embeddable_api/public/np_ready/public'; import { SavedObjectDashboard } from './saved_dashboard/saved_dashboard'; import { DashboardAppState, SavedDashboardPanel, ConfirmModalFn } from './types'; diff --git a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx index 6dceb6902a705..aa7cbd78ab015 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx +++ b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx @@ -23,23 +23,20 @@ import React from 'react'; import angular from 'angular'; import { uniq } from 'lodash'; -import { subscribeWithScope } from 'ui/utils/subscribe_with_scope'; - -// @ts-ignore -import { ConfirmationButtonTypes } from 'ui/modals/confirm_modal'; - -import { showSaveModal, SaveResult } from 'ui/saved_objects/show_saved_object_save_modal'; - -import { showShareContextMenu } from 'ui/share'; -import { migrateLegacyQuery } from 'ui/utils/migrate_legacy_query'; - -import { State } from 'ui/state_management/state'; - -import { AppStateClass as TAppStateClass } from 'ui/state_management/app_state'; - -import { KbnUrl } from 'ui/url/kbn_url'; -import { SaveOptions } from 'ui/saved_objects/saved_object'; import { Subscription } from 'rxjs'; + +import { + subscribeWithScope, + ConfirmationButtonTypes, + showSaveModal, + SaveResult, + showShareContextMenu, + migrateLegacyQuery, + State, + AppStateClass as TAppStateClass, + KbnUrl, + SaveOptions, +} from './legacy_imports'; import { extractTimeFilter, changeTimeFilter, Query } from '../../../../../plugins/data/public'; import { esFilters } from '../../../../../plugins/data/public'; import { FilterStateManager, IndexPattern } from '../../../data/public'; diff --git a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_state.test.ts b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_state.test.ts index 2abe1a0a1327d..b4eb9c9b8bf19 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_state.test.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_state.test.ts @@ -21,9 +21,9 @@ import './np_core.test.mocks'; import { DashboardStateManager } from './dashboard_state_manager'; import { getAppStateMock, getSavedDashboardMock } from './__tests__'; -import { AppStateClass } from 'ui/state_management/app_state'; +import { AppStateClass } from './legacy_imports'; import { DashboardAppState } from './types'; -import { TimeRange, TimefilterContract, InputTimeRange } from 'src/plugins/data/public'; +import { TimeRange, Timefilter, InputTimeRange } from 'src/plugins/data/public'; import { ViewMode } from 'src/plugins/embeddable/public'; jest.mock('ui/registry/field_formats', () => ({ @@ -44,7 +44,7 @@ describe('DashboardState', function() { setTime: (time: InputTimeRange) => { mockTime = time as TimeRange; }, - } as TimefilterContract; + } as Timefilter; function initDashboardState() { dashboardState = new DashboardStateManager({ diff --git a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_state_manager.ts b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_state_manager.ts index 1e7581e4b055c..cccfd83babafb 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_state_manager.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_state_manager.ts @@ -20,15 +20,20 @@ import { i18n } from '@kbn/i18n'; import _ from 'lodash'; -import { stateMonitorFactory, StateMonitor } from 'ui/state_management/state_monitor_factory'; -import { Timefilter } from 'ui/timefilter'; -import { AppStateClass as TAppStateClass } from 'ui/state_management/app_state'; -import { migrateLegacyQuery } from 'ui/utils/migrate_legacy_query'; import { Moment } from 'moment'; import { DashboardContainer } from 'src/legacy/core_plugins/dashboard_embeddable_container/public/np_ready/public'; import { ViewMode } from '../../../../../../src/plugins/embeddable/public'; -import { esFilters } from '../../../../../../src/plugins/data/public'; +import { + esFilters, + TimefilterContract as Timefilter, +} from '../../../../../../src/plugins/data/public'; +import { + stateMonitorFactory, + StateMonitor, + AppStateClass as TAppStateClass, + migrateLegacyQuery, +} from './legacy_imports'; import { Query } from '../../../data/public'; import { getAppStateDefaults, migrateAppState } from './lib'; diff --git a/src/legacy/core_plugins/kibana/public/dashboard/global_state_sync.ts b/src/legacy/core_plugins/kibana/public/dashboard/global_state_sync.ts index 2760f21f203b7..8a733f940734b 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/global_state_sync.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/global_state_sync.ts @@ -17,7 +17,7 @@ * under the License. */ -import { State } from 'ui/state_management/state'; +import { State } from './legacy_imports'; import { DataPublicPluginStart as NpDataStart } from '../../../../../plugins/data/public'; /** diff --git a/src/legacy/core_plugins/kibana/public/dashboard/index.ts b/src/legacy/core_plugins/kibana/public/dashboard/index.ts index 69c827955ed8b..1e9ae41ca1e85 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/index.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/index.ts @@ -17,11 +17,14 @@ * under the License. */ -import { npSetup, npStart } from 'ui/new_platform'; -import { SavedObjectRegistryProvider } from 'ui/saved_objects'; -import chrome from 'ui/chrome'; -import { IPrivate } from 'ui/private'; -import { ShareContextMenuExtensionsRegistryProvider } from 'ui/share'; +import { + npSetup, + npStart, + SavedObjectRegistryProvider, + legacyChrome, + IPrivate, + ShareContextMenuExtensionsRegistryProvider, +} from './legacy_imports'; import { DashboardPlugin, LegacyAngularInjectedDependencies } from './plugin'; import { start as data } from '../../../data/public/legacy'; import { localApplicationService } from '../local_application_service'; @@ -35,7 +38,7 @@ import './dashboard_config'; * They also have to get resolved together with the legacy imports above */ async function getAngularDependencies(): Promise { - const injector = await chrome.dangerouslyGetActiveInjector(); + const injector = await legacyChrome.dangerouslyGetActiveInjector(); const Private = injector.get('Private'); diff --git a/src/legacy/core_plugins/kibana/public/dashboard/legacy_imports.ts b/src/legacy/core_plugins/kibana/public/dashboard/legacy_imports.ts index 3c93f57b0f141..b224486b04ffa 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/legacy_imports.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/legacy_imports.ts @@ -1,3 +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. + */ /** * The imports in this file are static functions and types which still live in legacy folders and are used @@ -25,15 +43,26 @@ export { showSaveModal, SaveResult } from 'ui/saved_objects/show_saved_object_sa export { showShareContextMenu } from 'ui/share'; export { migrateLegacyQuery } from 'ui/utils/migrate_legacy_query'; export { KbnUrl } from 'ui/url/kbn_url'; -import { IPrivate } from 'ui/private'; -import { GlobalStateProvider } from 'ui/state_management/global_state'; -import { StateManagementConfigProvider } from 'ui/state_management/config_provider'; -import { AppStateProvider } from 'ui/state_management/app_state'; -import { PrivateProvider } from 'ui/private/private'; -import { EventsProvider } from 'ui/events'; -import { PersistedState } from 'ui/persisted_state'; -import { createTopNavDirective, createTopNavHelper } from 'ui/kbn_top_nav/kbn_top_nav'; -import { PromiseServiceCreator } from 'ui/promises/promises'; -import { KbnUrlProvider, RedirectWhenMissingProvider } from 'ui/url'; -import { confirmModalFactory } from 'ui/modals/confirm_modal'; -import { configureAppAngularModule } from 'ui/legacy_compat'; +// @ts-ignore +export { GlobalStateProvider } from 'ui/state_management/global_state'; +// @ts-ignore +export { StateManagementConfigProvider } from 'ui/state_management/config_provider'; +// @ts-ignore +export { AppStateProvider } from 'ui/state_management/app_state'; +// @ts-ignore +export { PrivateProvider } from 'ui/private/private'; +// @ts-ignore +export { EventsProvider } from 'ui/events'; +export { PersistedState } from 'ui/persisted_state'; +// @ts-ignore +export { createTopNavDirective, createTopNavHelper } from 'ui/kbn_top_nav/kbn_top_nav'; +// @ts-ignore +export { PromiseServiceCreator } from 'ui/promises/promises'; +// @ts-ignore +export { KbnUrlProvider, RedirectWhenMissingProvider } from 'ui/url'; +// @ts-ignore +export { confirmModalFactory } from 'ui/modals/confirm_modal'; +export { configureAppAngularModule } from 'ui/legacy_compat'; +export { stateMonitorFactory, StateMonitor } from 'ui/state_management/state_monitor_factory'; +export { ensureDefaultIndexPattern } from 'ui/legacy_compat'; +export { IInjector } from 'ui/chrome'; diff --git a/src/legacy/core_plugins/kibana/public/dashboard/lib/save_dashboard.ts b/src/legacy/core_plugins/kibana/public/dashboard/lib/save_dashboard.ts index b52e78589c620..e0d82373d3ad9 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/lib/save_dashboard.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/lib/save_dashboard.ts @@ -17,8 +17,8 @@ * under the License. */ -import { Timefilter } from 'src/plugins/data/public'; -import { SaveOptions } from 'ui/saved_objects/saved_object'; +import { TimefilterContract } from 'src/plugins/data/public'; +import { SaveOptions } from '../legacy_imports'; import { updateSavedDashboard } from './update_saved_dashboard'; import { DashboardStateManager } from '../dashboard_state_manager'; @@ -32,7 +32,7 @@ import { DashboardStateManager } from '../dashboard_state_manager'; */ export function saveDashboard( toJson: (obj: any) => string, - timeFilter: Timefilter, + timeFilter: TimefilterContract, dashboardStateManager: DashboardStateManager, saveOptions: SaveOptions ): Promise { diff --git a/src/legacy/core_plugins/kibana/public/dashboard/lib/update_saved_dashboard.ts b/src/legacy/core_plugins/kibana/public/dashboard/lib/update_saved_dashboard.ts index 87839c36e3252..ce9096b3a56f0 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/lib/update_saved_dashboard.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/lib/update_saved_dashboard.ts @@ -18,15 +18,15 @@ */ import _ from 'lodash'; -import { AppState } from 'ui/state_management/app_state'; -import { RefreshInterval, Timefilter } from 'src/plugins/data/public'; +import { RefreshInterval, TimefilterContract } from 'src/plugins/data/public'; +import { AppState } from '../legacy_imports'; import { FilterUtils } from './filter_utils'; import { SavedObjectDashboard } from '../saved_dashboard/saved_dashboard'; export function updateSavedDashboard( savedDashboard: SavedObjectDashboard, appState: AppState, - timeFilter: Timefilter, + timeFilter: TimefilterContract, toJson: (object: T) => string ) { savedDashboard.title = appState.title; diff --git a/src/legacy/core_plugins/kibana/public/dashboard/listing/dashboard_listing.js b/src/legacy/core_plugins/kibana/public/dashboard/listing/dashboard_listing.js index d8216361562e2..b84188ee86e9a 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/listing/dashboard_listing.js +++ b/src/legacy/core_plugins/kibana/public/dashboard/listing/dashboard_listing.js @@ -19,7 +19,7 @@ import React, { Fragment } from 'react'; import PropTypes from 'prop-types'; -import { FormattedMessage } from '@kbn/i18n/react'; +import { FormattedMessage, I18nProvider } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; import { EuiLink, EuiButton, EuiEmptyPrompt } from '@elastic/eui'; @@ -40,25 +40,27 @@ export class DashboardListing extends React.Component { render() { return ( - + + + ); } diff --git a/src/legacy/core_plugins/kibana/public/dashboard/render_app.ts b/src/legacy/core_plugins/kibana/public/dashboard/render_app.ts index 76122edc9d530..da1b377dc1a5f 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/render_app.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/render_app.ts @@ -21,26 +21,6 @@ import { EuiConfirmModal } from '@elastic/eui'; import angular, { IModule } from 'angular'; import { IPrivate } from 'ui/private'; import { i18nDirective, i18nFilter, I18nProvider } from '@kbn/i18n/angular'; -// @ts-ignore -import { GlobalStateProvider } from 'ui/state_management/global_state'; -// @ts-ignore -import { StateManagementConfigProvider } from 'ui/state_management/config_provider'; -// @ts-ignore -import { AppStateProvider } from 'ui/state_management/app_state'; -// @ts-ignore -import { PrivateProvider } from 'ui/private/private'; -// @ts-ignore -import { EventsProvider } from 'ui/events'; -// @ts-ignore -import { PersistedState } from 'ui/persisted_state'; -// @ts-ignore -import { createTopNavDirective, createTopNavHelper } from 'ui/kbn_top_nav/kbn_top_nav'; -// @ts-ignore -import { PromiseServiceCreator } from 'ui/promises/promises'; -// @ts-ignore -import { KbnUrlProvider, RedirectWhenMissingProvider } from 'ui/url'; -// @ts-ignore -import { confirmModalFactory } from 'ui/modals/confirm_modal'; import { AppMountContext, ChromeStart, @@ -48,8 +28,22 @@ import { SavedObjectsClientContract, UiSettingsClientContract, } from 'kibana/public'; -import { configureAppAngularModule } from 'ui/legacy_compat'; import { Storage } from '../../../../../plugins/kibana_utils/public'; +import { + GlobalStateProvider, + StateManagementConfigProvider, + AppStateProvider, + PrivateProvider, + EventsProvider, + PersistedState, + createTopNavDirective, + createTopNavHelper, + PromiseServiceCreator, + KbnUrlProvider, + RedirectWhenMissingProvider, + confirmModalFactory, + configureAppAngularModule, +} from './legacy_imports'; // @ts-ignore import { initDashboardApp } from './app'; diff --git a/src/legacy/core_plugins/kibana/public/dashboard/top_nav/save_modal.tsx b/src/legacy/core_plugins/kibana/public/dashboard/top_nav/save_modal.tsx index 47455f04ba809..0640b2be431be 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/top_nav/save_modal.tsx +++ b/src/legacy/core_plugins/kibana/public/dashboard/top_nav/save_modal.tsx @@ -19,10 +19,10 @@ import React, { Fragment } from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; - -import { SavedObjectSaveModal } from 'ui/saved_objects/components/saved_object_save_modal'; import { EuiFormRow, EuiTextArea, EuiSwitch } from '@elastic/eui'; +import { SavedObjectSaveModal } from '../legacy_imports'; + interface SaveOptions { newTitle: string; newDescription: string; diff --git a/src/legacy/core_plugins/kibana/public/dashboard/top_nav/show_options_popover.tsx b/src/legacy/core_plugins/kibana/public/dashboard/top_nav/show_options_popover.tsx index 8640d7dbc6bdc..7c23e4808fbea 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/top_nav/show_options_popover.tsx +++ b/src/legacy/core_plugins/kibana/public/dashboard/top_nav/show_options_popover.tsx @@ -19,9 +19,9 @@ import React from 'react'; import ReactDOM from 'react-dom'; -import { I18nContext } from 'ui/i18n'; - +import { I18nProvider } from '@kbn/i18n/react'; import { EuiWrappingPopover } from '@elastic/eui'; + import { OptionsMenu } from './options'; let isOpen = false; @@ -55,7 +55,7 @@ export function showOptionsPopover({ document.body.appendChild(container); const element = ( - + - + ); ReactDOM.render(element, container); } diff --git a/src/legacy/core_plugins/kibana/public/dashboard/types.ts b/src/legacy/core_plugins/kibana/public/dashboard/types.ts index 5aaca7b62094f..d94c550c26ae3 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/types.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/types.ts @@ -17,10 +17,9 @@ * under the License. */ -import { AppState } from 'ui/state_management/app_state'; import { Query } from 'src/legacy/core_plugins/data/public'; -import { AppState as TAppState } from 'ui/state_management/app_state'; import { ViewMode } from 'src/plugins/embeddable/public'; +import { AppState } from './legacy_imports'; import { RawSavedDashboardPanelTo60, RawSavedDashboardPanel610, @@ -154,5 +153,5 @@ export type AddFilterFn = ( operator: string; index: string; }, - appState: TAppState + appState: AppState ) => void; From 23399eade557bebe514b5883e82a30fa36a51da5 Mon Sep 17 00:00:00 2001 From: maryia-lapata Date: Wed, 13 Nov 2019 11:40:18 +0300 Subject: [PATCH 091/132] Rename render_app.ts to application.ts --- .../public/visualize/{render_app.ts => application.ts} | 0 .../embeddable/visualize_embeddable_factory.tsx | 9 ++++++--- .../kibana/public/visualize/kibana_services.ts | 3 +-- .../core_plugins/kibana/public/visualize/plugin.ts | 4 ++-- 4 files changed, 9 insertions(+), 7 deletions(-) rename src/legacy/core_plugins/kibana/public/visualize/{render_app.ts => application.ts} (100%) diff --git a/src/legacy/core_plugins/kibana/public/visualize/render_app.ts b/src/legacy/core_plugins/kibana/public/visualize/application.ts similarity index 100% rename from src/legacy/core_plugins/kibana/public/visualize/render_app.ts rename to src/legacy/core_plugins/kibana/public/visualize/application.ts diff --git a/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable_factory.tsx b/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable_factory.tsx index dbe4aa2c15117..b0b99939c09f8 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable_factory.tsx +++ b/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable_factory.tsx @@ -42,7 +42,6 @@ import { Legacy } from 'kibana'; import { capabilities } from 'ui/capabilities'; import chrome from 'ui/chrome'; -import { getVisualizeLoader } from 'ui/visualize/loader'; import { SavedObjectAttributes } from 'kibana/server'; import { @@ -56,10 +55,14 @@ import { showNewVisModal } from '../wizard'; import { SavedVisualizations } from '../types'; import { DisabledLabEmbeddable } from './disabled_lab_embeddable'; import { getIndexPattern } from './get_index_pattern'; -import { VisualizeEmbeddable, VisualizeInput, VisualizeOutput } from './visualize_embeddable'; +import { + VisualizeEmbeddable, + VisualizeInput, + VisualizeOutput, + VisSavedObject, +} from './visualize_embeddable'; import { VISUALIZE_EMBEDDABLE_TYPE } from './constants'; import { TypesStart } from '../../../../visualizations/public/np_ready/public/types'; -import { VisSavedObject } from '../../../../../ui/public/visualize/loader/types'; interface VisualizationAttributes extends SavedObjectAttributes { visState: string; diff --git a/src/legacy/core_plugins/kibana/public/visualize/kibana_services.ts b/src/legacy/core_plugins/kibana/public/visualize/kibana_services.ts index c8d741612e618..5dfd3b6c3232b 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/kibana_services.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/kibana_services.ts @@ -83,7 +83,7 @@ export function clearServices() { // export types export { DocTitle } from 'ui/doc_title/doc_title'; -export { VisSavedObject } from 'ui/visualize/loader/types'; +export { VisSavedObject } from './embeddable/visualize_embeddable'; export { EmbeddableFactory, ErrorEmbeddable } from '../../../../../plugins/embeddable/public'; // export legacy static dependencies @@ -96,4 +96,3 @@ export { showShareContextMenu } from 'ui/share'; export { showSaveModal } from 'ui/saved_objects/show_saved_object_save_modal'; export { stateMonitorFactory } from 'ui/state_management/state_monitor_factory'; export { subscribeWithScope } from 'ui/utils/subscribe_with_scope'; -export { getVisualizeLoader } from 'ui/visualize/loader'; diff --git a/src/legacy/core_plugins/kibana/public/visualize/plugin.ts b/src/legacy/core_plugins/kibana/public/visualize/plugin.ts index 4a2750d709094..84ff42a98233f 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/plugin.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/plugin.ts @@ -142,7 +142,7 @@ export class VisualizePlugin implements Plugin { }; setServices(deps); - const { renderApp } = await import('./render_app'); + const { renderApp } = await import('./application'); return renderApp(params.element, params.appBasePath, deps); }, }; @@ -164,7 +164,7 @@ export class VisualizePlugin implements Plugin { VisEditorTypesRegistryProvider.register(defaultEditor); } - async start( + public start( { savedObjects: { client: savedObjectsClient } }: CoreStart, { dataStart, embeddables, navigation, visualizations, npData }: VisualizePluginStartDependencies ) { From 6fab1534b367cc1dff7c783a13ecfff166a196cf Mon Sep 17 00:00:00 2001 From: maryia-lapata Date: Wed, 13 Nov 2019 11:51:23 +0300 Subject: [PATCH 092/132] Fix merge conflicts --- .../data/public/shim/legacy_module.ts | 54 +--- .../dashboard/dashboard_app_controller.tsx | 3 +- .../kibana/public/dashboard/render_app.ts | 12 +- .../kibana/public/visualize/application.ts | 11 +- .../visualize/embeddable/get_index_pattern.ts | 2 +- .../loader/embedded_visualize_handler.test.ts | 301 ------------------ 6 files changed, 7 insertions(+), 376 deletions(-) delete mode 100644 src/legacy/ui/public/visualize/loader/embedded_visualize_handler.test.ts diff --git a/src/legacy/core_plugins/data/public/shim/legacy_module.ts b/src/legacy/core_plugins/data/public/shim/legacy_module.ts index c937c046ca9e1..566a0dbacdc4b 100644 --- a/src/legacy/core_plugins/data/public/shim/legacy_module.ts +++ b/src/legacy/core_plugins/data/public/shim/legacy_module.ts @@ -71,64 +71,12 @@ export const createFilterBarHelper = (reactDirective: any) => { ]); }; -/** @internal */ -export const createApplyFiltersPopoverDirective = () => { - return { - restrict: 'E', - template: '', - compile: (elem: any) => { - const child = document.createElement('apply-filters-popover-helper'); - - // Copy attributes to the child directive - for (const attr of elem[0].attributes) { - child.setAttribute(attr.name, attr.value); - } - - // Add a key attribute that will force a full rerender every time that - // a filter changes. - child.setAttribute('key', 'key'); - - // Append helper directive - elem.append(child); - - const linkFn = ($scope: any, _: any, $attr: any) => { - // Watch only for filter changes to update key. - $scope.$watch( - () => { - return $scope.$eval($attr.filters) || []; - }, - (newVal: any) => { - $scope.key = Date.now(); - }, - true - ); - }; - - return linkFn; - }, - }; -}; - -/** @internal */ -export const createApplyFiltersPopoverHelper = (reactDirective: any) => - reactDirective(wrapInI18nContext(ApplyFiltersPopover), [ - ['filters', { watchDepth: 'collection' }], - ['onCancel', { watchDepth: 'reference' }], - ['onSubmit', { watchDepth: 'reference' }], - ['indexPatterns', { watchDepth: 'collection' }], - - // Key is needed to trigger a full rerender of the component - 'key', - ]); - /** @internal */ export const initLegacyModule = once((indexPatterns: IndexPatterns): void => { uiModules .get('app/kibana', ['react']) .directive('filterBar', createFilterBarDirective) - .directive('filterBarHelper', createFilterBarHelper) - .directive('applyFiltersPopover', createApplyFiltersPopoverDirective) - .directive('applyFiltersPopoverHelper', createApplyFiltersPopoverHelper); + .directive('filterBarHelper', createFilterBarHelper); uiModules.get('kibana/index_patterns').value('indexPatterns', indexPatterns); }); diff --git a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx index 8518b5b26f7e5..91d4ea44b16ca 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx +++ b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx @@ -42,8 +42,7 @@ import { IndexPattern } from 'ui/index_patterns'; import { SaveOptions } from 'ui/saved_objects/saved_object'; import { Subscription } from 'rxjs'; import { SavedObjectFinder } from 'ui/saved_objects/components/saved_object_finder'; -import { extractTimeFilter, changeTimeFilter, Query } from '../../../../../plugins/data/public'; -import { esFilters } from '../../../../../plugins/data/public'; +import { Query } from '../../../../../plugins/data/public'; import { FilterStateManager } from '../../../data/public'; import { SavedQuery } from '../../../data/public'; diff --git a/src/legacy/core_plugins/kibana/public/dashboard/render_app.ts b/src/legacy/core_plugins/kibana/public/dashboard/render_app.ts index 8d3d5f104be2e..ab1b1a03abbac 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/render_app.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/render_app.ts @@ -53,13 +53,7 @@ import { Storage } from '../../../../../plugins/kibana_utils/public'; // @ts-ignore import { initDashboardApp } from './app'; -import { - createApplyFiltersPopoverDirective, - createApplyFiltersPopoverHelper, - createFilterBarDirective, - createFilterBarHelper, - DataStart, -} from '../../../data/public'; +import { createFilterBarDirective, createFilterBarHelper, DataStart } from '../../../data/public'; import { SavedQueryService } from '../../../data/public/search/search_bar/lib/saved_query_service'; import { EmbeddablePublicPlugin } from '../../../../../plugins/embeddable/public'; import { NavigationStart } from '../../../navigation/public'; @@ -230,9 +224,7 @@ function createLocalFilterBarModule() { angular .module('app/dashboard/FilterBar', ['react']) .directive('filterBar', createFilterBarDirective) - .directive('filterBarHelper', createFilterBarHelper) - .directive('applyFiltersPopover', createApplyFiltersPopoverDirective) - .directive('applyFiltersPopoverHelper', createApplyFiltersPopoverHelper); + .directive('filterBarHelper', createFilterBarHelper); } function createLocalI18nModule() { diff --git a/src/legacy/core_plugins/kibana/public/visualize/application.ts b/src/legacy/core_plugins/kibana/public/visualize/application.ts index 01d515a29370d..ac64aa8c51e01 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/application.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/application.ts @@ -44,12 +44,7 @@ import { KbnUrlProvider, RedirectWhenMissingProvider } from 'ui/url'; import { confirmModalFactory } from 'ui/modals/confirm_modal'; import { AppMountContext, LegacyCoreStart } from 'kibana/public'; -import { - createApplyFiltersPopoverDirective, - createApplyFiltersPopoverHelper, - createFilterBarDirective, - createFilterBarHelper, -} from '../../../data/public'; +import { createFilterBarDirective, createFilterBarHelper } from '../../../data/public'; import { NavigationStart } from '../../../navigation/public'; // @ts-ignore @@ -208,9 +203,7 @@ function createLocalFilterBarModule(angular: IAngularStatic) { angular .module('app/visualize/FilterBar', ['react']) .directive('filterBar', createFilterBarDirective) - .directive('filterBarHelper', createFilterBarHelper) - .directive('applyFiltersPopover', createApplyFiltersPopoverDirective) - .directive('applyFiltersPopoverHelper', createApplyFiltersPopoverHelper); + .directive('filterBarHelper', createFilterBarHelper); } function createLocalI18nModule(angular: IAngularStatic) { diff --git a/src/legacy/core_plugins/kibana/public/visualize/embeddable/get_index_pattern.ts b/src/legacy/core_plugins/kibana/public/visualize/embeddable/get_index_pattern.ts index 699fa68b4528b..e4a5f871dda74 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/embeddable/get_index_pattern.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/embeddable/get_index_pattern.ts @@ -19,7 +19,7 @@ import chrome from 'ui/chrome'; import { StaticIndexPattern, getFromSavedObject } from 'ui/index_patterns'; -import { VisSavedObject } from 'ui/visualize/loader/types'; +import { VisSavedObject } from './visualize_embeddable'; export async function getIndexPattern( savedVis: VisSavedObject diff --git a/src/legacy/ui/public/visualize/loader/embedded_visualize_handler.test.ts b/src/legacy/ui/public/visualize/loader/embedded_visualize_handler.test.ts deleted file mode 100644 index 2038fe2410c03..0000000000000 --- a/src/legacy/ui/public/visualize/loader/embedded_visualize_handler.test.ts +++ /dev/null @@ -1,301 +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. - */ -jest.mock('ui/new_platform'); - -import { searchSourceMock } from '../../courier/search_source/mocks'; -import { mockDataLoaderFetch, timefilter } from './embedded_visualize_handler.test.mocks'; - -import _ from 'lodash'; -// @ts-ignore -import MockState from '../../../../../fixtures/mock_state'; -import { Vis } from '../../vis'; -import { VisResponseData } from './types'; -import { Inspector } from '../../inspector'; -import { EmbeddedVisualizeHandler, RequestHandlerParams } from './embedded_visualize_handler'; -import { AggConfigs } from 'ui/agg_types/agg_configs'; - -jest.mock('plugins/interpreter/interpreter', () => ({ - getInterpreter: () => { - return Promise.resolve(); - }, -})); - -jest.mock('../../../../core_plugins/data/public/legacy', () => ({})); - -jest.mock('../../../../core_plugins/interpreter/public/registries', () => ({ - registries: { - renderers: { - get: (name: string) => { - return { - render: async () => { - return {}; - }, - }; - }, - }, - }, -})); - -describe('EmbeddedVisualizeHandler', () => { - let handler: any; - let div: HTMLElement; - let dataLoaderParams: RequestHandlerParams; - const mockVis: Vis = { - title: 'My Vis', - // @ts-ignore - type: 'foo', - getAggConfig: () => [], - _setUiState: () => ({}), - getUiState: () => new MockState(), - on: () => ({}), - off: () => ({}), - removeListener: jest.fn(), - API: {}, - }; - - beforeEach(() => { - jest.clearAllMocks(); - - jest.spyOn(_, 'debounce').mockImplementation( - // @ts-ignore - (f: Function) => { - // @ts-ignore - f.cancel = () => {}; - return f; - } - ); - - dataLoaderParams = { - aggs: ([] as any) as AggConfigs, - filters: undefined, - forceFetch: false, - inspectorAdapters: {}, - query: undefined, - queryFilter: null, - searchSource: searchSourceMock, - timeRange: undefined, - uiState: undefined, - }; - - div = document.createElement('div'); - handler = new EmbeddedVisualizeHandler( - div, - { - vis: mockVis, - title: 'My Vis', - searchSource: searchSourceMock, - destroy: () => ({}), - copyOnSave: false, - save: () => Promise.resolve('123'), - }, - { - autoFetch: true, - Private: (provider: () => T) => provider(), - queryFilter: null, - } - ); - }); - - afterEach(() => { - handler.destroy(); - }); - - describe('autoFetch', () => { - it('should trigger a reload when autoFetch=true and auto refresh happens', () => { - const spy = jest.spyOn(handler, 'fetchAndRender'); - timefilter._triggerAutoRefresh(); - jest.runAllTimers(); - expect(spy).toHaveBeenCalledTimes(1); - expect(spy).toHaveBeenCalledWith(true); - }); - - it('should not trigger a reload when autoFetch=false and auto refresh happens', () => { - handler = new EmbeddedVisualizeHandler( - div, - { - vis: mockVis, - title: 'My Vis', - searchSource: searchSourceMock, - destroy: () => ({}), - copyOnSave: false, - save: () => Promise.resolve('123'), - }, - { - autoFetch: false, - Private: (provider: () => T) => provider(), - queryFilter: null, - } - ); - const spy = jest.spyOn(handler, 'fetchAndRender'); - timefilter._triggerAutoRefresh(); - jest.runAllTimers(); - expect(spy).not.toHaveBeenCalled(); - }); - }); - - describe('getElement', () => { - it('should return the provided html element', () => { - expect(handler.getElement()).toBe(div); - }); - }); - - describe('update', () => { - it('should add provided data- attributes to the html element', () => { - const spy = jest.spyOn(handler, 'fetchAndRender'); - const params = { - dataAttrs: { foo: 'bar' }, - }; - handler.update(params); - expect(spy).not.toHaveBeenCalled(); - expect(handler.getElement()).toMatchSnapshot(); - }); - - it('should remove null data- attributes from the html element', () => { - const spy = jest.spyOn(handler, 'fetchAndRender'); - handler.update({ - dataAttrs: { foo: 'bar' }, - }); - const params = { - dataAttrs: { - foo: null, - baz: 'qux', - }, - }; - handler.update(params); - expect(spy).not.toHaveBeenCalled(); - expect(handler.getElement()).toMatchSnapshot(); - }); - - it('should call dataLoader.render with updated timeRange', () => { - const params = { timeRange: { foo: 'bar' } }; - handler.update(params); - expect(mockDataLoaderFetch).toHaveBeenCalled(); - const callIndex = mockDataLoaderFetch.mock.calls.length - 1; - const { abortSignal, ...otherParams } = mockDataLoaderFetch.mock.calls[callIndex][0]; - expect(abortSignal).toBeInstanceOf(AbortSignal); - expect(otherParams).toEqual({ ...dataLoaderParams, ...params }); - }); - - it('should call dataLoader.render with updated filters', () => { - const params = { filters: [{ meta: { disabled: false } }] }; - handler.update(params); - expect(mockDataLoaderFetch).toHaveBeenCalled(); - const callIndex = mockDataLoaderFetch.mock.calls.length - 1; - const { abortSignal, ...otherParams } = mockDataLoaderFetch.mock.calls[callIndex][0]; - expect(abortSignal).toBeInstanceOf(AbortSignal); - expect(otherParams).toEqual({ ...dataLoaderParams, ...params }); - }); - - it('should call dataLoader.render with updated query', () => { - const params = { query: { foo: 'bar' } }; - handler.update(params); - expect(mockDataLoaderFetch).toHaveBeenCalled(); - const callIndex = mockDataLoaderFetch.mock.calls.length - 1; - const { abortSignal, ...otherParams } = mockDataLoaderFetch.mock.calls[callIndex][0]; - expect(abortSignal).toBeInstanceOf(AbortSignal); - expect(otherParams).toEqual({ ...dataLoaderParams, ...params }); - }); - }); - - describe('destroy', () => { - it('should remove vis event listeners', () => { - const spy = jest.spyOn(mockVis, 'removeListener'); - handler.destroy(); - expect(spy).toHaveBeenCalledTimes(2); - expect(spy.mock.calls[0][0]).toBe('reload'); - expect(spy.mock.calls[1][0]).toBe('update'); - }); - - it('should remove element event listeners', () => { - const spy = jest.spyOn(handler.getElement(), 'removeEventListener'); - handler.destroy(); - expect(spy).toHaveBeenCalled(); - }); - - it('should prevent subsequent renders', () => { - const spy = jest.spyOn(handler, 'fetchAndRender'); - handler.destroy(); - expect(spy).not.toHaveBeenCalled(); - }); - - it('should cancel debounced fetchAndRender', () => { - const spy = jest.spyOn(handler.debouncedFetchAndRender, 'cancel'); - handler.destroy(); - expect(spy).toHaveBeenCalledTimes(1); - }); - - it('should call abort on controller', () => { - handler.abortController = new AbortController(); - const spy = jest.spyOn(handler.abortController, 'abort'); - handler.destroy(); - expect(spy).toHaveBeenCalled(); - }); - }); - - describe('openInspector', () => { - it('calls Inspector.open()', () => { - handler.openInspector(); - expect(Inspector.open).toHaveBeenCalledTimes(1); - expect(Inspector.open).toHaveBeenCalledWith({}, { title: 'My Vis' }); - }); - }); - - describe('hasInspector', () => { - it('calls Inspector.isAvailable()', () => { - handler.hasInspector(); - expect(Inspector.isAvailable).toHaveBeenCalledTimes(1); - expect(Inspector.isAvailable).toHaveBeenCalledWith({}); - }); - }); - - describe('reload', () => { - it('should force fetch and render', () => { - const spy = jest.spyOn(handler, 'fetchAndRender'); - handler.reload(); - expect(spy).toHaveBeenCalledTimes(1); - expect(spy).toHaveBeenCalledWith(true); - }); - }); - - describe('data$', () => { - it('observable can be used to get response data in the correct format', async () => { - let response; - handler.data$.subscribe((data: VisResponseData) => (response = data)); - await handler.fetch(true); - jest.runAllTimers(); - expect(response).toMatchSnapshot(); - }); - }); - - describe('render', () => { - // TODO - }); - - describe('whenFirstRenderComplete', () => { - // TODO - }); - - describe('addRenderCompleteListener', () => { - // TODO - }); - - describe('removeRenderCompleteListener', () => { - // TODO - }); -}); From a0abd00eebbf230c4bcaa83fe9188b8d3e51f288 Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Wed, 13 Nov 2019 10:07:04 +0100 Subject: [PATCH 093/132] fix import bugs --- src/legacy/core_plugins/data/public/index.ts | 1 - .../kibana/public/dashboard/render_app.ts | 12 ++---------- 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/src/legacy/core_plugins/data/public/index.ts b/src/legacy/core_plugins/data/public/index.ts index 0a95e6d2a037e..a187fc4bb2bc6 100644 --- a/src/legacy/core_plugins/data/public/index.ts +++ b/src/legacy/core_plugins/data/public/index.ts @@ -70,5 +70,4 @@ export { createFilterBarHelper, createFilterBarDirective, createApplyFiltersPopoverDirective, - createApplyFiltersPopoverHelper, } from './shim/legacy_module'; diff --git a/src/legacy/core_plugins/kibana/public/dashboard/render_app.ts b/src/legacy/core_plugins/kibana/public/dashboard/render_app.ts index da1b377dc1a5f..94a146dbc5088 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/render_app.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/render_app.ts @@ -47,13 +47,7 @@ import { // @ts-ignore import { initDashboardApp } from './app'; -import { - createApplyFiltersPopoverDirective, - createApplyFiltersPopoverHelper, - createFilterBarDirective, - createFilterBarHelper, - DataStart, -} from '../../../data/public'; +import { createFilterBarDirective, createFilterBarHelper, DataStart } from '../../../data/public'; import { SavedQueryService } from '../../../data/public/search/search_bar/lib/saved_query_service'; import { EmbeddablePublicPlugin } from '../../../../../plugins/embeddable/public'; import { NavigationStart } from '../../../navigation/public'; @@ -223,9 +217,7 @@ function createLocalFilterBarModule() { angular .module('app/dashboard/FilterBar', ['react']) .directive('filterBar', createFilterBarDirective) - .directive('filterBarHelper', createFilterBarHelper) - .directive('applyFiltersPopover', createApplyFiltersPopoverDirective) - .directive('applyFiltersPopoverHelper', createApplyFiltersPopoverHelper); + .directive('filterBarHelper', createFilterBarHelper); } function createLocalI18nModule() { From aaca94effc7e25d57fd65d12d210d9cb3033ab6a Mon Sep 17 00:00:00 2001 From: maryia-lapata Date: Wed, 13 Nov 2019 12:10:29 +0300 Subject: [PATCH 094/132] Fix merge conflict --- src/legacy/core_plugins/data/public/index.ts | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/legacy/core_plugins/data/public/index.ts b/src/legacy/core_plugins/data/public/index.ts index 0a95e6d2a037e..e57a494d0730e 100644 --- a/src/legacy/core_plugins/data/public/index.ts +++ b/src/legacy/core_plugins/data/public/index.ts @@ -66,9 +66,4 @@ export { * These functions can be used to register the angular wrappers for react components * in a separate module to use them without relying on the uiModules module tree. * */ -export { - createFilterBarHelper, - createFilterBarDirective, - createApplyFiltersPopoverDirective, - createApplyFiltersPopoverHelper, -} from './shim/legacy_module'; +export { createFilterBarHelper, createFilterBarDirective } from './shim/legacy_module'; From 4ac0a916ee5b15260f22b22a1b9442fbaf2a7c1e Mon Sep 17 00:00:00 2001 From: maryia-lapata Date: Wed, 13 Nov 2019 12:29:31 +0300 Subject: [PATCH 095/132] Add dashboard updates --- .../dashboard/__tests__/get_app_state_mock.ts | 2 +- .../kibana/public/dashboard/app.js | 7 +- .../kibana/public/dashboard/dashboard_app.tsx | 15 ++-- .../dashboard/dashboard_app_controller.tsx | 40 +++++------ .../public/dashboard/dashboard_state.test.ts | 7 +- .../dashboard/dashboard_state_manager.ts | 15 ++-- .../public/dashboard/global_state_sync.ts | 2 +- .../kibana/public/dashboard/index.ts | 19 +++--- .../kibana/public/dashboard/legacy_imports.ts | 68 +++++++++++++++++++ .../public/dashboard/lib/save_dashboard.ts | 6 +- .../dashboard/lib/update_saved_dashboard.ts | 7 +- .../dashboard/listing/dashboard_listing.js | 46 +++++++------ .../kibana/public/dashboard/plugin.ts | 9 +-- .../kibana/public/dashboard/render_app.ts | 37 ++++------ .../public/dashboard/top_nav/save_modal.tsx | 3 +- .../dashboard/top_nav/show_clone_modal.tsx | 6 +- .../top_nav/show_options_popover.tsx | 8 +-- .../kibana/public/dashboard/types.ts | 5 +- 18 files changed, 176 insertions(+), 126 deletions(-) create mode 100644 src/legacy/core_plugins/kibana/public/dashboard/legacy_imports.ts diff --git a/src/legacy/core_plugins/kibana/public/dashboard/__tests__/get_app_state_mock.ts b/src/legacy/core_plugins/kibana/public/dashboard/__tests__/get_app_state_mock.ts index 1f2094d68063d..d9dea35a8a1c0 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/__tests__/get_app_state_mock.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/__tests__/get_app_state_mock.ts @@ -17,7 +17,7 @@ * under the License. */ -import { AppStateClass } from 'ui/state_management/app_state'; +import { AppStateClass } from '../legacy_imports'; /** * A poor excuse for a mock just to get some basic tests to run in jest without requiring the injector. diff --git a/src/legacy/core_plugins/kibana/public/dashboard/app.js b/src/legacy/core_plugins/kibana/public/dashboard/app.js index 76d11d51a02cd..6e80c9694152d 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/app.js +++ b/src/legacy/core_plugins/kibana/public/dashboard/app.js @@ -18,12 +18,11 @@ */ import { i18n } from '@kbn/i18n'; -import { wrapInI18nContext } from 'ui/i18n'; -import { ensureDefaultIndexPattern } from 'ui/legacy_compat'; import dashboardTemplate from './dashboard_app.html'; import dashboardListingTemplate from './listing/dashboard_listing_ng_wrapper.html'; +import { ensureDefaultIndexPattern } from './legacy_imports'; import { initDashboardAppDirective } from './dashboard_app'; import { DashboardConstants, createDashboardEditUrl } from './dashboard_constants'; import { @@ -39,7 +38,7 @@ export function initDashboardApp(app, deps) { initDashboardAppDirective(app, deps); app.directive('dashboardListing', function (reactDirective) { - return reactDirective(wrapInI18nContext(DashboardListing)); + return reactDirective(DashboardListing); }); function createNewDashboardCtrl($scope) { @@ -55,7 +54,7 @@ export function initDashboardApp(app, deps) { app.run((globalState, $rootScope) => { registerTimefilterWithGlobalStateFactory( - deps.npDataStart.query.timefilter.timefilter, + deps.npDataStart.timefilter.timefilter, globalState, $rootScope ); diff --git a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app.tsx b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app.tsx index 31ce037903dc5..4c7f625dd6938 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app.tsx +++ b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app.tsx @@ -17,19 +17,18 @@ * under the License. */ -import { IInjector } from 'ui/chrome'; - -import { - AppStateClass as TAppStateClass, - AppState as TAppState, -} from 'ui/state_management/app_state'; - -import { KbnUrl } from 'ui/url/kbn_url'; import { TimeRange } from 'src/plugins/data/public'; import { StaticIndexPattern, Query, SavedQuery } from 'plugins/data'; import moment from 'moment'; import { Subscription } from 'rxjs'; +import { + AppStateClass as TAppStateClass, + AppState as TAppState, + IInjector, + KbnUrl, +} from './legacy_imports'; + import { ViewMode } from '../../../embeddable_api/public/np_ready/public'; import { SavedObjectDashboard } from './saved_dashboard/saved_dashboard'; import { DashboardAppState, SavedDashboardPanel, ConfirmModalFn } from './types'; diff --git a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx index 91d4ea44b16ca..46d67206c6aa5 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx +++ b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx @@ -23,27 +23,22 @@ import React from 'react'; import angular from 'angular'; import { uniq } from 'lodash'; -import { subscribeWithScope } from 'ui/utils/subscribe_with_scope'; - -// @ts-ignore -import { ConfirmationButtonTypes } from 'ui/modals/confirm_modal'; - -import { showSaveModal, SaveResult } from 'ui/saved_objects/show_saved_object_save_modal'; - -import { showShareContextMenu } from 'ui/share'; -import { migrateLegacyQuery } from 'ui/utils/migrate_legacy_query'; - -import { State } from 'ui/state_management/state'; - -import { AppStateClass as TAppStateClass } from 'ui/state_management/app_state'; - -import { KbnUrl } from 'ui/url/kbn_url'; -import { IndexPattern } from 'ui/index_patterns'; -import { SaveOptions } from 'ui/saved_objects/saved_object'; import { Subscription } from 'rxjs'; -import { SavedObjectFinder } from 'ui/saved_objects/components/saved_object_finder'; + +import { + subscribeWithScope, + ConfirmationButtonTypes, + showSaveModal, + SaveResult, + showShareContextMenu, + migrateLegacyQuery, + State, + AppStateClass as TAppStateClass, + KbnUrl, + SaveOptions, +} from './legacy_imports'; import { Query } from '../../../../../plugins/data/public'; -import { FilterStateManager } from '../../../data/public'; +import { FilterStateManager, IndexPattern } from '../../../data/public'; import { SavedQuery } from '../../../data/public'; import { @@ -60,6 +55,7 @@ import { openAddPanelFlyout, } from '../../../embeddable_api/public/np_ready/public'; import { DashboardAppState, NavAction, ConfirmModalFn, SavedDashboardPanel } from './types'; +import { SavedObjectFinder } from '../../../../../plugins/kibana_react/public'; import { showOptionsPopover } from './top_nav/show_options_popover'; import { DashboardSaveModal } from './top_nav/save_modal'; @@ -114,8 +110,6 @@ export class DashboardAppController { savedQueryService, embeddables, dashboardCapabilities, - docTitle, - dataStart, npDataStart: { query: { filterManager, @@ -135,7 +129,7 @@ export class DashboardAppController { const dash = ($scope.dash = $route.current.locals.dash); if (dash.id) { - docTitle.change(dash.title); + chrome.docTitle.change(dash.title); } const dashboardStateManager = new DashboardStateManager({ @@ -603,7 +597,7 @@ export class DashboardAppController { if (dash.id !== $routeParams.id) { kbnUrl.change(createDashboardEditUrl(dash.id)); } else { - docTitle.change(dash.lastSavedTitle); + chrome.docTitle.change(dash.lastSavedTitle); updateViewMode(ViewMode.VIEW); } } diff --git a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_state.test.ts b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_state.test.ts index 603eee201cd84..b4eb9c9b8bf19 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_state.test.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_state.test.ts @@ -21,11 +21,10 @@ import './np_core.test.mocks'; import { DashboardStateManager } from './dashboard_state_manager'; import { getAppStateMock, getSavedDashboardMock } from './__tests__'; -import { AppStateClass } from 'ui/state_management/app_state'; +import { AppStateClass } from './legacy_imports'; import { DashboardAppState } from './types'; -import { TimeRange, TimefilterContract } from 'src/plugins/data/public'; +import { TimeRange, Timefilter, InputTimeRange } from 'src/plugins/data/public'; import { ViewMode } from 'src/plugins/embeddable/public'; -import { InputTimeRange } from 'ui/timefilter'; jest.mock('ui/registry/field_formats', () => ({ fieldFormats: { @@ -45,7 +44,7 @@ describe('DashboardState', function() { setTime: (time: InputTimeRange) => { mockTime = time as TimeRange; }, - } as TimefilterContract; + } as Timefilter; function initDashboardState() { dashboardState = new DashboardStateManager({ diff --git a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_state_manager.ts b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_state_manager.ts index 1e7581e4b055c..cccfd83babafb 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_state_manager.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_state_manager.ts @@ -20,15 +20,20 @@ import { i18n } from '@kbn/i18n'; import _ from 'lodash'; -import { stateMonitorFactory, StateMonitor } from 'ui/state_management/state_monitor_factory'; -import { Timefilter } from 'ui/timefilter'; -import { AppStateClass as TAppStateClass } from 'ui/state_management/app_state'; -import { migrateLegacyQuery } from 'ui/utils/migrate_legacy_query'; import { Moment } from 'moment'; import { DashboardContainer } from 'src/legacy/core_plugins/dashboard_embeddable_container/public/np_ready/public'; import { ViewMode } from '../../../../../../src/plugins/embeddable/public'; -import { esFilters } from '../../../../../../src/plugins/data/public'; +import { + esFilters, + TimefilterContract as Timefilter, +} from '../../../../../../src/plugins/data/public'; +import { + stateMonitorFactory, + StateMonitor, + AppStateClass as TAppStateClass, + migrateLegacyQuery, +} from './legacy_imports'; import { Query } from '../../../data/public'; import { getAppStateDefaults, migrateAppState } from './lib'; diff --git a/src/legacy/core_plugins/kibana/public/dashboard/global_state_sync.ts b/src/legacy/core_plugins/kibana/public/dashboard/global_state_sync.ts index 2760f21f203b7..8a733f940734b 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/global_state_sync.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/global_state_sync.ts @@ -17,7 +17,7 @@ * under the License. */ -import { State } from 'ui/state_management/state'; +import { State } from './legacy_imports'; import { DataPublicPluginStart as NpDataStart } from '../../../../../plugins/data/public'; /** diff --git a/src/legacy/core_plugins/kibana/public/dashboard/index.ts b/src/legacy/core_plugins/kibana/public/dashboard/index.ts index 1d0c8d34f239b..1e9ae41ca1e85 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/index.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/index.ts @@ -17,13 +17,14 @@ * under the License. */ -import { FeatureCatalogueRegistryProvider } from 'ui/registry/feature_catalogue'; -import { npSetup, npStart } from 'ui/new_platform'; -import { SavedObjectRegistryProvider } from 'ui/saved_objects'; -import { docTitle } from 'ui/doc_title/doc_title'; -import chrome from 'ui/chrome'; -import { IPrivate } from 'ui/private'; -import { ShareContextMenuExtensionsRegistryProvider } from 'ui/share'; +import { + npSetup, + npStart, + SavedObjectRegistryProvider, + legacyChrome, + IPrivate, + ShareContextMenuExtensionsRegistryProvider, +} from './legacy_imports'; import { DashboardPlugin, LegacyAngularInjectedDependencies } from './plugin'; import { start as data } from '../../../data/public/legacy'; import { localApplicationService } from '../local_application_service'; @@ -37,7 +38,7 @@ import './dashboard_config'; * They also have to get resolved together with the legacy imports above */ async function getAngularDependencies(): Promise { - const injector = await chrome.dangerouslyGetActiveInjector(); + const injector = await legacyChrome.dangerouslyGetActiveInjector(); const Private = injector.get('Private'); @@ -59,8 +60,6 @@ async function getAngularDependencies(): Promise string, - timeFilter: Timefilter, + timeFilter: TimefilterContract, dashboardStateManager: DashboardStateManager, saveOptions: SaveOptions ): Promise { diff --git a/src/legacy/core_plugins/kibana/public/dashboard/lib/update_saved_dashboard.ts b/src/legacy/core_plugins/kibana/public/dashboard/lib/update_saved_dashboard.ts index 707b5a0f5f5f5..ce9096b3a56f0 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/lib/update_saved_dashboard.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/lib/update_saved_dashboard.ts @@ -18,16 +18,15 @@ */ import _ from 'lodash'; -import { AppState } from 'ui/state_management/app_state'; -import { Timefilter } from 'ui/timefilter'; -import { RefreshInterval } from 'src/plugins/data/public'; +import { RefreshInterval, TimefilterContract } from 'src/plugins/data/public'; +import { AppState } from '../legacy_imports'; import { FilterUtils } from './filter_utils'; import { SavedObjectDashboard } from '../saved_dashboard/saved_dashboard'; export function updateSavedDashboard( savedDashboard: SavedObjectDashboard, appState: AppState, - timeFilter: Timefilter, + timeFilter: TimefilterContract, toJson: (object: T) => string ) { savedDashboard.title = appState.title; diff --git a/src/legacy/core_plugins/kibana/public/dashboard/listing/dashboard_listing.js b/src/legacy/core_plugins/kibana/public/dashboard/listing/dashboard_listing.js index c222fcd3c928c..98581223afa46 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/listing/dashboard_listing.js +++ b/src/legacy/core_plugins/kibana/public/dashboard/listing/dashboard_listing.js @@ -19,7 +19,7 @@ import React, { Fragment } from 'react'; import PropTypes from 'prop-types'; -import { FormattedMessage } from '@kbn/i18n/react'; +import { FormattedMessage, I18nProvider } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; import { EuiLink, EuiButton, EuiEmptyPrompt } from '@elastic/eui'; @@ -41,27 +41,29 @@ export class DashboardListing extends React.Component { render() { return ( - + + + ); } diff --git a/src/legacy/core_plugins/kibana/public/dashboard/plugin.ts b/src/legacy/core_plugins/kibana/public/dashboard/plugin.ts index 50963fa6a57fd..f82930a5c3ed2 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/plugin.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/plugin.ts @@ -57,8 +57,6 @@ export interface DashboardPluginSetupDependencies { __LEGACY: { getAngularDependencies: () => Promise; localApplicationService: LocalApplicationService; - FeatureCatalogueRegistryProvider: any; - docTitle: any; }; feature_catalogue: FeatureCatalogueSetup; } @@ -75,12 +73,7 @@ export class DashboardPlugin implements Plugin { public setup( core: CoreSetup, { - __LEGACY: { - localApplicationService, - getAngularDependencies, - FeatureCatalogueRegistryProvider, - ...legacyServices - }, + __LEGACY: { localApplicationService, getAngularDependencies, ...legacyServices }, feature_catalogue, }: DashboardPluginSetupDependencies ) { diff --git a/src/legacy/core_plugins/kibana/public/dashboard/render_app.ts b/src/legacy/core_plugins/kibana/public/dashboard/render_app.ts index ab1b1a03abbac..94a146dbc5088 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/render_app.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/render_app.ts @@ -21,26 +21,6 @@ import { EuiConfirmModal } from '@elastic/eui'; import angular, { IModule } from 'angular'; import { IPrivate } from 'ui/private'; import { i18nDirective, i18nFilter, I18nProvider } from '@kbn/i18n/angular'; -// @ts-ignore -import { GlobalStateProvider } from 'ui/state_management/global_state'; -// @ts-ignore -import { StateManagementConfigProvider } from 'ui/state_management/config_provider'; -// @ts-ignore -import { AppStateProvider } from 'ui/state_management/app_state'; -// @ts-ignore -import { PrivateProvider } from 'ui/private/private'; -// @ts-ignore -import { EventsProvider } from 'ui/events'; -// @ts-ignore -import { PersistedState } from 'ui/persisted_state'; -// @ts-ignore -import { createTopNavDirective, createTopNavHelper } from 'ui/kbn_top_nav/kbn_top_nav'; -// @ts-ignore -import { PromiseServiceCreator } from 'ui/promises/promises'; -// @ts-ignore -import { KbnUrlProvider, RedirectWhenMissingProvider } from 'ui/url'; -// @ts-ignore -import { confirmModalFactory } from 'ui/modals/confirm_modal'; import { AppMountContext, ChromeStart, @@ -48,8 +28,22 @@ import { SavedObjectsClientContract, UiSettingsClientContract, } from 'kibana/public'; -import { configureAppAngularModule } from 'ui/legacy_compat'; import { Storage } from '../../../../../plugins/kibana_utils/public'; +import { + GlobalStateProvider, + StateManagementConfigProvider, + AppStateProvider, + PrivateProvider, + EventsProvider, + PersistedState, + createTopNavDirective, + createTopNavHelper, + PromiseServiceCreator, + KbnUrlProvider, + RedirectWhenMissingProvider, + confirmModalFactory, + configureAppAngularModule, +} from './legacy_imports'; // @ts-ignore import { initDashboardApp } from './app'; @@ -71,7 +65,6 @@ export interface RenderDeps { dashboardConfig: any; savedDashboards: any; dashboardCapabilities: any; - docTitle: any; uiSettings: UiSettingsClientContract; chrome: ChromeStart; addBasePath: (path: string) => string; diff --git a/src/legacy/core_plugins/kibana/public/dashboard/top_nav/save_modal.tsx b/src/legacy/core_plugins/kibana/public/dashboard/top_nav/save_modal.tsx index 47455f04ba809..4fb3c3904cd5c 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/top_nav/save_modal.tsx +++ b/src/legacy/core_plugins/kibana/public/dashboard/top_nav/save_modal.tsx @@ -20,9 +20,10 @@ import React, { Fragment } from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; -import { SavedObjectSaveModal } from 'ui/saved_objects/components/saved_object_save_modal'; import { EuiFormRow, EuiTextArea, EuiSwitch } from '@elastic/eui'; +import { SavedObjectSaveModal } from '../legacy_imports'; + interface SaveOptions { newTitle: string; newDescription: string; diff --git a/src/legacy/core_plugins/kibana/public/dashboard/top_nav/show_clone_modal.tsx b/src/legacy/core_plugins/kibana/public/dashboard/top_nav/show_clone_modal.tsx index c3cd5621b2c88..af1020e01e0c5 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/top_nav/show_clone_modal.tsx +++ b/src/legacy/core_plugins/kibana/public/dashboard/top_nav/show_clone_modal.tsx @@ -17,10 +17,10 @@ * under the License. */ -import { I18nContext } from 'ui/i18n'; import React from 'react'; import ReactDOM from 'react-dom'; import { i18n } from '@kbn/i18n'; +import { I18nProvider } from '@kbn/i18n/react'; import { DashboardCloneModal } from './clone_modal'; export function showCloneModal( @@ -54,7 +54,7 @@ export function showCloneModal( }; document.body.appendChild(container); const element = ( - + - + ); ReactDOM.render(element, container); } diff --git a/src/legacy/core_plugins/kibana/public/dashboard/top_nav/show_options_popover.tsx b/src/legacy/core_plugins/kibana/public/dashboard/top_nav/show_options_popover.tsx index 8640d7dbc6bdc..7c23e4808fbea 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/top_nav/show_options_popover.tsx +++ b/src/legacy/core_plugins/kibana/public/dashboard/top_nav/show_options_popover.tsx @@ -19,9 +19,9 @@ import React from 'react'; import ReactDOM from 'react-dom'; -import { I18nContext } from 'ui/i18n'; - +import { I18nProvider } from '@kbn/i18n/react'; import { EuiWrappingPopover } from '@elastic/eui'; + import { OptionsMenu } from './options'; let isOpen = false; @@ -55,7 +55,7 @@ export function showOptionsPopover({ document.body.appendChild(container); const element = ( - + - + ); ReactDOM.render(element, container); } diff --git a/src/legacy/core_plugins/kibana/public/dashboard/types.ts b/src/legacy/core_plugins/kibana/public/dashboard/types.ts index 5aaca7b62094f..d94c550c26ae3 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/types.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/types.ts @@ -17,10 +17,9 @@ * under the License. */ -import { AppState } from 'ui/state_management/app_state'; import { Query } from 'src/legacy/core_plugins/data/public'; -import { AppState as TAppState } from 'ui/state_management/app_state'; import { ViewMode } from 'src/plugins/embeddable/public'; +import { AppState } from './legacy_imports'; import { RawSavedDashboardPanelTo60, RawSavedDashboardPanel610, @@ -154,5 +153,5 @@ export type AddFilterFn = ( operator: string; index: string; }, - appState: TAppState + appState: AppState ) => void; From 9b747e00ac6e404df823ba74f8e296074a913dcd Mon Sep 17 00:00:00 2001 From: maryia-lapata Date: Wed, 13 Nov 2019 12:30:15 +0300 Subject: [PATCH 096/132] Use I18nProvider instead of I18nContext --- .../core_plugins/kibana/public/visualize/kibana_services.ts | 1 - .../kibana/public/visualize/listing/visualize_listing.js | 6 +++--- src/legacy/core_plugins/kibana/public/visualize/plugin.ts | 2 -- .../core_plugins/kibana/public/visualize/visualize_app.ts | 2 +- .../kibana/public/visualize/wizard/show_new_vis.tsx | 6 +++--- 5 files changed, 7 insertions(+), 10 deletions(-) diff --git a/src/legacy/core_plugins/kibana/public/visualize/kibana_services.ts b/src/legacy/core_plugins/kibana/public/visualize/kibana_services.ts index 5dfd3b6c3232b..39358a2c018ec 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/kibana_services.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/kibana_services.ts @@ -60,7 +60,6 @@ export interface VisualizeKibanaServices { uiSettings: UiSettingsClientContract; visualizeCapabilities: any; visualizations: any; - wrapInI18nContext: any; } let services: VisualizeKibanaServices | null = null; diff --git a/src/legacy/core_plugins/kibana/public/visualize/listing/visualize_listing.js b/src/legacy/core_plugins/kibana/public/visualize/listing/visualize_listing.js index 61fa4a9a23e3f..cdf6153e0e75c 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/listing/visualize_listing.js +++ b/src/legacy/core_plugins/kibana/public/visualize/listing/visualize_listing.js @@ -25,12 +25,12 @@ import { i18n } from '@kbn/i18n'; import { getServices } from '../kibana_services'; -export function initListingDirective(app, deps) { +export function initListingDirective(app) { app.directive('visualizeListingTable', reactDirective => - reactDirective(deps.wrapInI18nContext(VisualizeListingTable)) + reactDirective(VisualizeListingTable) ); app.directive('newVisModal', reactDirective => - reactDirective(deps.wrapInI18nContext(NewVisModal), [ + reactDirective(NewVisModal, [ ['visTypesRegistry', { watchDepth: 'collection' }], ['onClose', { watchDepth: 'reference' }], ['addBasePath', { watchDepth: 'reference' }], diff --git a/src/legacy/core_plugins/kibana/public/visualize/plugin.ts b/src/legacy/core_plugins/kibana/public/visualize/plugin.ts index 84ff42a98233f..3a0ffc05e2e94 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/plugin.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/plugin.ts @@ -19,7 +19,6 @@ import { IAngularStatic } from 'angular'; import { i18n } from '@kbn/i18n'; -import { wrapInI18nContext } from 'ui/i18n'; // @ts-ignore import { defaultEditor } from 'ui/vis/editors/default/default'; @@ -138,7 +137,6 @@ export class VisualizePlugin implements Plugin { uiSettings: contextCore.uiSettings, visualizeCapabilities: contextCore.application.capabilities.visualize, visualizations, - wrapInI18nContext, }; setServices(deps); diff --git a/src/legacy/core_plugins/kibana/public/visualize/visualize_app.ts b/src/legacy/core_plugins/kibana/public/visualize/visualize_app.ts index 85510c1e684fd..ce44a5051d20d 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/visualize_app.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/visualize_app.ts @@ -26,5 +26,5 @@ import { initListingDirective } from './listing/visualize_listing'; export function initVisualizeAppDirective(app: any, deps: VisualizeKibanaServices) { initEditorDirective(app, deps); - initListingDirective(app, deps); + initListingDirective(app); } diff --git a/src/legacy/core_plugins/kibana/public/visualize/wizard/show_new_vis.tsx b/src/legacy/core_plugins/kibana/public/visualize/wizard/show_new_vis.tsx index 8e422bef10b21..48dcb88687231 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/wizard/show_new_vis.tsx +++ b/src/legacy/core_plugins/kibana/public/visualize/wizard/show_new_vis.tsx @@ -20,7 +20,7 @@ import React from 'react'; import ReactDOM from 'react-dom'; -import { I18nContext } from 'ui/i18n'; +import { I18nProvider } from '@kbn/i18n/react'; import { UiSettingsClientContract } from 'kibana/public'; import { NewVisModal } from './new_vis_modal'; import { TypesStart } from '../../../../visualizations/public/np_ready/public/types'; @@ -43,7 +43,7 @@ export function showNewVisModal( document.body.appendChild(container); const element = ( - + - + ); ReactDOM.render(element, container); } From d140691bf870e9564364025689ee3a2fd92215a3 Mon Sep 17 00:00:00 2001 From: maryia-lapata Date: Wed, 13 Nov 2019 12:33:00 +0300 Subject: [PATCH 097/132] remove unused dependencies --- .../core_plugins/kibana/public/visualize/editor/editor.js | 5 ++--- src/legacy/core_plugins/kibana/public/visualize/index.ts | 2 -- .../core_plugins/kibana/public/visualize/kibana_services.ts | 1 - src/legacy/core_plugins/kibana/public/visualize/plugin.ts | 3 +-- 4 files changed, 3 insertions(+), 8 deletions(-) diff --git a/src/legacy/core_plugins/kibana/public/visualize/editor/editor.js b/src/legacy/core_plugins/kibana/public/visualize/editor/editor.js index c91b6d171384a..1307ab7137b37 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/editor/editor.js +++ b/src/legacy/core_plugins/kibana/public/visualize/editor/editor.js @@ -90,7 +90,6 @@ function VisualizeAppController( toastNotifications, chromeLegacy, chrome, - docTitle, getBasePath, docLinks, savedQueryService, @@ -231,7 +230,7 @@ function VisualizeAppController( let stateMonitor; if (savedVis.id) { - docTitle.change(savedVis.title); + chrome.docTitle.change(savedVis.title); } // Extract visualization state with filtered aggs. You can see these filtered aggs in the URL. @@ -526,7 +525,7 @@ function VisualizeAppController( dashboardParsedUrl.addQueryParameter(DashboardConstants.NEW_VISUALIZATION_ID_PARAM, savedVis.id); kbnUrl.change(dashboardParsedUrl.appPath); } else if (savedVis.id === $route.current.params.id) { - docTitle.change(savedVis.lastSavedTitle); + chrome.docTitle.change(savedVis.lastSavedTitle); chrome.setBreadcrumbs($injector.invoke(getEditBreadcrumbs)); savedVis.vis.title = savedVis.title; savedVis.vis.description = savedVis.description; diff --git a/src/legacy/core_plugins/kibana/public/visualize/index.ts b/src/legacy/core_plugins/kibana/public/visualize/index.ts index f6fe7abdd75de..1207056b401f6 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/index.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/index.ts @@ -23,7 +23,6 @@ import 'ui/collapsible_sidebar'; // used in default editor import 'ui/vis/editors/default/sidebar'; import chrome from 'ui/chrome'; -import { docTitle } from 'ui/doc_title/doc_title'; import { npSetup, npStart } from 'ui/new_platform'; import { IPrivate } from 'ui/private'; // @ts-ignore @@ -70,7 +69,6 @@ async function getAngularDependencies(): Promise Promise; localApplicationService: LocalApplicationService; - docTitle: DocTitle; VisEditorTypesRegistryProvider: any; }; } From ccb7a537319ea8d14754c0df723a8c4a734e4f76 Mon Sep 17 00:00:00 2001 From: maryia-lapata Date: Wed, 13 Nov 2019 13:29:12 +0300 Subject: [PATCH 098/132] Centralizing and cleaning up legacy imports --- .../kibana/public/visualize/app.js | 4 +- .../kibana/public/visualize/application.ts | 41 +++++----- .../kibana/public/visualize/editor/editor.js | 7 +- .../public/visualize/global_state_sync.ts | 2 +- .../kibana/public/visualize/index.ts | 22 +++--- .../public/visualize/kibana_services.ts | 11 --- .../kibana/public/visualize/legacy_imports.ts | 75 +++++++++++++++++++ .../kibana/public/visualize/plugin.ts | 3 +- .../public/visualize/wizard/new_vis_modal.tsx | 2 +- .../search_selection/search_selection.tsx | 4 +- .../wizard/type_selection/type_selection.tsx | 3 +- 11 files changed, 115 insertions(+), 59 deletions(-) create mode 100644 src/legacy/core_plugins/kibana/public/visualize/legacy_imports.ts diff --git a/src/legacy/core_plugins/kibana/public/visualize/app.js b/src/legacy/core_plugins/kibana/public/visualize/app.js index dc11e86f58c19..6a525ec5e9fbc 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/app.js +++ b/src/legacy/core_plugins/kibana/public/visualize/app.js @@ -26,7 +26,7 @@ import visualizeListingTemplate from './listing/visualize_listing.html'; import { initVisualizeAppDirective } from './visualize_app'; import { VisualizeConstants } from './visualize_constants'; import { VisualizeListingController } from './listing/visualize_listing'; -import { registerTimefilterWithGlobalStateFactory } from '../../../../ui/public/timefilter/setup_router'; +import { ensureDefaultIndexPattern, registerTimefilterWithGlobalStateFactory } from './legacy_imports'; import { syncOnMount } from './global_state_sync'; import { @@ -36,8 +36,6 @@ import { getEditBreadcrumbs } from './breadcrumbs'; -import { ensureDefaultIndexPattern } from './kibana_services'; - export function initVisualizeApp(app, deps) { initVisualizeAppDirective(app, deps); diff --git a/src/legacy/core_plugins/kibana/public/visualize/application.ts b/src/legacy/core_plugins/kibana/public/visualize/application.ts index ac64aa8c51e01..2f3c3ecb5efc2 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/application.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/application.ts @@ -17,33 +17,28 @@ * under the License. */ -import { EuiConfirmModal } from '@elastic/eui'; import { IModule, IAngularStatic } from 'angular'; +import { EuiConfirmModal } from '@elastic/eui'; import { i18nDirective, i18nFilter, I18nProvider } from '@kbn/i18n/src/angular'; -import { IPrivate } from 'ui/private'; -import { configureAppAngularModule } from 'ui/legacy_compat'; -// @ts-ignore -import { GlobalStateProvider } from 'ui/state_management/global_state'; -// @ts-ignore -import { StateManagementConfigProvider } from 'ui/state_management/config_provider'; -// @ts-ignore -import { AppStateProvider, AppState } from 'ui/state_management/app_state'; -// @ts-ignore -import { PrivateProvider } from 'ui/private/private'; -// @ts-ignore -import { EventsProvider } from 'ui/events'; -import { PersistedState } from 'ui/persisted_state'; -// @ts-ignore -import { createTopNavDirective, createTopNavHelper } from 'ui/kbn_top_nav/kbn_top_nav'; -// @ts-ignore -import { PromiseServiceCreator } from 'ui/promises/promises'; -// @ts-ignore -import { KbnUrlProvider, RedirectWhenMissingProvider } from 'ui/url'; -// @ts-ignore -import { confirmModalFactory } from 'ui/modals/confirm_modal'; - import { AppMountContext, LegacyCoreStart } from 'kibana/public'; +import { + AppStateProvider, + AppState, + configureAppAngularModule, + confirmModalFactory, + createTopNavDirective, + createTopNavHelper, + EventsProvider, + GlobalStateProvider, + KbnUrlProvider, + RedirectWhenMissingProvider, + IPrivate, + PersistedState, + PrivateProvider, + PromiseServiceCreator, + StateManagementConfigProvider, +} from './legacy_imports'; import { createFilterBarDirective, createFilterBarHelper } from '../../../data/public'; import { NavigationStart } from '../../../navigation/public'; diff --git a/src/legacy/core_plugins/kibana/public/visualize/editor/editor.js b/src/legacy/core_plugins/kibana/public/visualize/editor/editor.js index 1307ab7137b37..3a361700f973a 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/editor/editor.js +++ b/src/legacy/core_plugins/kibana/public/visualize/editor/editor.js @@ -36,7 +36,6 @@ import { initVisEditorDirective } from './visualization_editor'; import { initVisualizationDirective } from './visualization'; import { - getServices, absoluteToParsedUrl, KibanaParsedUrl, migrateLegacyQuery, @@ -44,8 +43,10 @@ import { showShareContextMenu, showSaveModal, stateMonitorFactory, - subscribeWithScope, -} from '../kibana_services'; + subscribeWithScope +} from '../legacy_imports'; + +import { getServices } from '../kibana_services'; export function initEditorDirective(app, deps) { app.directive('visualizeApp', function () { diff --git a/src/legacy/core_plugins/kibana/public/visualize/global_state_sync.ts b/src/legacy/core_plugins/kibana/public/visualize/global_state_sync.ts index 2760f21f203b7..8a733f940734b 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/global_state_sync.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/global_state_sync.ts @@ -17,7 +17,7 @@ * under the License. */ -import { State } from 'ui/state_management/state'; +import { State } from './legacy_imports'; import { DataPublicPluginStart as NpDataStart } from '../../../../../plugins/data/public'; /** diff --git a/src/legacy/core_plugins/kibana/public/visualize/index.ts b/src/legacy/core_plugins/kibana/public/visualize/index.ts index 1207056b401f6..63c8e28cd0061 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/index.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/index.ts @@ -22,14 +22,16 @@ import 'angular-sanitize'; import 'ui/collapsible_sidebar'; // used in default editor import 'ui/vis/editors/default/sidebar'; -import chrome from 'ui/chrome'; -import { npSetup, npStart } from 'ui/new_platform'; -import { IPrivate } from 'ui/private'; -// @ts-ignore -import { VisEditorTypesRegistryProvider } from 'ui/registry/vis_editor_types'; -import { SavedObjectRegistryProvider, SavedObjectsClientProvider } from 'ui/saved_objects'; -import { ShareContextMenuExtensionsRegistryProvider } from 'ui/share'; - +import { + IPrivate, + legacyChrome, + npSetup, + npStart, + SavedObjectRegistryProvider, + SavedObjectsClientProvider, + ShareContextMenuExtensionsRegistryProvider, + VisEditorTypesRegistryProvider, +} from './legacy_imports'; import { VisualizePlugin, LegacyAngularInjectedDependencies } from './plugin'; import { localApplicationService } from '../local_application_service'; import { start as dataStart } from '../../../data/public/legacy'; @@ -42,7 +44,7 @@ import { start as visualizations } from '../../../visualizations/public/np_ready * They also have to get resolved together with the legacy imports above */ async function getAngularDependencies(): Promise { - const injector = await chrome.dangerouslyGetActiveInjector(); + const injector = await legacyChrome.dangerouslyGetActiveInjector(); const Private = injector.get('Private'); @@ -52,7 +54,7 @@ async function getAngularDependencies(): Promise void; diff --git a/src/legacy/core_plugins/kibana/public/visualize/wizard/type_selection/type_selection.tsx b/src/legacy/core_plugins/kibana/public/visualize/wizard/type_selection/type_selection.tsx index 2c3e5731eea30..09b87117f3043 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/wizard/type_selection/type_selection.tsx +++ b/src/legacy/core_plugins/kibana/public/visualize/wizard/type_selection/type_selection.tsx @@ -34,8 +34,7 @@ import { EuiSpacer, EuiTitle, } from '@elastic/eui'; -import { memoizeLast } from 'ui/utils/memoize'; -import { VisType } from 'ui/vis'; +import { memoizeLast, VisType } from '../../legacy_imports'; import { VisTypeAlias } from '../../../../../visualizations/public'; import { NewVisHelp } from './new_vis_help'; import { VisHelpText } from './vis_help_text'; From cb920b8531e659a39acb5e48ce76b9299ed44d34 Mon Sep 17 00:00:00 2001 From: maryia-lapata Date: Wed, 13 Nov 2019 15:09:42 +0300 Subject: [PATCH 099/132] Fix merge conflict --- .../kbn_tp_visualize_embedding/public/app.js | 66 ------------------- 1 file changed, 66 deletions(-) delete mode 100644 test/plugin_functional/plugins/kbn_tp_visualize_embedding/public/app.js diff --git a/test/plugin_functional/plugins/kbn_tp_visualize_embedding/public/app.js b/test/plugin_functional/plugins/kbn_tp_visualize_embedding/public/app.js deleted file mode 100644 index 541546f031e81..0000000000000 --- a/test/plugin_functional/plugins/kbn_tp_visualize_embedding/public/app.js +++ /dev/null @@ -1,66 +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 React from 'react'; -import { render, unmountComponentAtNode } from 'react-dom'; - -import { uiModules } from 'ui/modules'; -import chrome from 'ui/chrome'; - -// This is required so some default styles and required scripts/Angular modules are loaded, -// or the timezone setting is correctly applied. -import 'ui/autoload/all'; - -// These are all the required uiExports you need to import in case you want to embed visualizations. -import 'uiExports/visTypes'; -import 'uiExports/visResponseHandlers'; -import 'uiExports/visRequestHandlers'; -import 'uiExports/visualize'; -import 'uiExports/savedObjectTypes'; -import 'uiExports/fieldFormats'; -import 'uiExports/search'; - -import { Main } from './components/main'; - -const app = uiModules.get('apps/firewallDemoPlugin', ['kibana']); - -app.config($locationProvider => { - $locationProvider.html5Mode({ - enabled: false, - requireBase: false, - rewriteLinks: false, - }); -}); -app.config(stateManagementConfigProvider => - stateManagementConfigProvider.disable() -); - -function RootController($scope, $element) { - const domNode = $element[0]; - - // render react to DOM - render(
, domNode); - - // unmount react on controller destroy - $scope.$on('$destroy', () => { - unmountComponentAtNode(domNode); - }); -} - -chrome.setRootController('firewallDemoPlugin', RootController); From 8bdca650c52010586208569e9721b4a8e79baa36 Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Wed, 13 Nov 2019 13:28:02 +0100 Subject: [PATCH 100/132] fix merge bugs and rename main dynamic entrypoint --- src/legacy/core_plugins/data/public/shim/legacy_module.ts | 2 +- .../kibana/public/dashboard/{render_app.ts => application.ts} | 2 +- .../core_plugins/kibana/public/dashboard/dashboard_app.tsx | 2 +- .../kibana/public/dashboard/dashboard_app_controller.tsx | 2 +- .../kibana/public/dashboard/{app.js => legacy_app.js} | 2 +- src/legacy/core_plugins/kibana/public/dashboard/plugin.ts | 4 ++-- 6 files changed, 7 insertions(+), 7 deletions(-) rename src/legacy/core_plugins/kibana/public/dashboard/{render_app.ts => application.ts} (99%) rename src/legacy/core_plugins/kibana/public/dashboard/{app.js => legacy_app.js} (99%) diff --git a/src/legacy/core_plugins/data/public/shim/legacy_module.ts b/src/legacy/core_plugins/data/public/shim/legacy_module.ts index 8c3e0c8804e8d..c9a1035d7d6d4 100644 --- a/src/legacy/core_plugins/data/public/shim/legacy_module.ts +++ b/src/legacy/core_plugins/data/public/shim/legacy_module.ts @@ -115,7 +115,7 @@ export const initLegacyModule = once((indexPatterns: IndexPatterns): void => { .get('app/kibana', ['react']) .directive('filterBar', createFilterBarDirective) .directive('filterBarHelper', createFilterBarHelper) - .directive('applyFiltersPopover', createApplyFiltersPopoverDirective) + .directive('applyFiltersPopover', createApplyFiltersPopoverDirective); uiModules.get('kibana/index_patterns').value('indexPatterns', indexPatterns); }); diff --git a/src/legacy/core_plugins/kibana/public/dashboard/render_app.ts b/src/legacy/core_plugins/kibana/public/dashboard/application.ts similarity index 99% rename from src/legacy/core_plugins/kibana/public/dashboard/render_app.ts rename to src/legacy/core_plugins/kibana/public/dashboard/application.ts index 94a146dbc5088..f4ade9c629009 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/render_app.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/application.ts @@ -46,7 +46,7 @@ import { } from './legacy_imports'; // @ts-ignore -import { initDashboardApp } from './app'; +import { initDashboardApp } from './legacy_app'; import { createFilterBarDirective, createFilterBarHelper, DataStart } from '../../../data/public'; import { SavedQueryService } from '../../../data/public/search/search_bar/lib/saved_query_service'; import { EmbeddablePublicPlugin } from '../../../../../plugins/embeddable/public'; diff --git a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app.tsx b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app.tsx index 4c7f625dd6938..c24189d56e3a6 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app.tsx +++ b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app.tsx @@ -35,7 +35,7 @@ import { DashboardAppState, SavedDashboardPanel, ConfirmModalFn } from './types' import { esFilters } from '../../../../../../src/plugins/data/public'; import { DashboardAppController } from './dashboard_app_controller'; -import { RenderDeps } from './render_app'; +import { RenderDeps } from './application'; export interface DashboardAppScope extends ng.IScope { dash: SavedObjectDashboard; diff --git a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx index 46d67206c6aa5..6cbfd6f8d1844 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx +++ b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx @@ -69,7 +69,7 @@ import { getDashboardTitle } from './dashboard_strings'; import { DashboardAppScope } from './dashboard_app'; import { VISUALIZE_EMBEDDABLE_TYPE } from '../visualize/embeddable'; import { convertSavedDashboardPanelToPanelState } from './lib/embeddable_saved_object_converters'; -import { RenderDeps } from './render_app'; +import { RenderDeps } from './application'; export interface DashboardAppControllerDependencies extends RenderDeps { $scope: DashboardAppScope; diff --git a/src/legacy/core_plugins/kibana/public/dashboard/app.js b/src/legacy/core_plugins/kibana/public/dashboard/legacy_app.js similarity index 99% rename from src/legacy/core_plugins/kibana/public/dashboard/app.js rename to src/legacy/core_plugins/kibana/public/dashboard/legacy_app.js index 6e80c9694152d..c7f2adb4b875b 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/app.js +++ b/src/legacy/core_plugins/kibana/public/dashboard/legacy_app.js @@ -54,7 +54,7 @@ export function initDashboardApp(app, deps) { app.run((globalState, $rootScope) => { registerTimefilterWithGlobalStateFactory( - deps.npDataStart.timefilter.timefilter, + deps.npDataStart.query.timefilter.timefilter, globalState, $rootScope ); diff --git a/src/legacy/core_plugins/kibana/public/dashboard/plugin.ts b/src/legacy/core_plugins/kibana/public/dashboard/plugin.ts index f82930a5c3ed2..bd1b69a055ede 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/plugin.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/plugin.ts @@ -26,7 +26,7 @@ import { SavedObjectsClientContract, } from 'kibana/public'; import { i18n } from '@kbn/i18n'; -import { RenderDeps } from './render_app'; +import { RenderDeps } from './application'; import { LocalApplicationService } from '../local_application_service'; import { DataStart } from '../../../data/public'; import { DataPublicPluginStart as NpDataStart } from '../../../../../plugins/data/public'; @@ -109,7 +109,7 @@ export class DashboardPlugin implements Plugin { dashboardCapabilities: contextCore.application.capabilities.dashboard, localStorage: new Storage(localStorage), }; - const { renderApp } = await import('./render_app'); + const { renderApp } = await import('./application'); return renderApp(params.element, params.appBasePath, deps); }, }; From 613dcd85445509fe53b0dfcb9ce34bf1e7be3d92 Mon Sep 17 00:00:00 2001 From: maryia-lapata Date: Wed, 13 Nov 2019 15:38:46 +0300 Subject: [PATCH 101/132] Rename app to legacy_app --- .../public/dashboard/{render_app.ts => application.ts} | 2 +- .../kibana/public/dashboard/{app.js => legacy_app.js} | 2 +- src/legacy/core_plugins/kibana/public/dashboard/plugin.ts | 2 +- .../core_plugins/kibana/public/visualize/application.ts | 2 +- src/legacy/core_plugins/kibana/public/visualize/index.ts | 8 ++------ .../kibana/public/visualize/kibana_services.ts | 1 + .../kibana/public/visualize/{app.js => legacy_app.js} | 0 .../kibana/public/visualize/listing/visualize_listing.js | 8 +++----- src/legacy/core_plugins/kibana/public/visualize/plugin.ts | 5 ++--- 9 files changed, 12 insertions(+), 18 deletions(-) rename src/legacy/core_plugins/kibana/public/dashboard/{render_app.ts => application.ts} (99%) rename src/legacy/core_plugins/kibana/public/dashboard/{app.js => legacy_app.js} (99%) rename src/legacy/core_plugins/kibana/public/visualize/{app.js => legacy_app.js} (100%) diff --git a/src/legacy/core_plugins/kibana/public/dashboard/render_app.ts b/src/legacy/core_plugins/kibana/public/dashboard/application.ts similarity index 99% rename from src/legacy/core_plugins/kibana/public/dashboard/render_app.ts rename to src/legacy/core_plugins/kibana/public/dashboard/application.ts index 94a146dbc5088..f4ade9c629009 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/render_app.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/application.ts @@ -46,7 +46,7 @@ import { } from './legacy_imports'; // @ts-ignore -import { initDashboardApp } from './app'; +import { initDashboardApp } from './legacy_app'; import { createFilterBarDirective, createFilterBarHelper, DataStart } from '../../../data/public'; import { SavedQueryService } from '../../../data/public/search/search_bar/lib/saved_query_service'; import { EmbeddablePublicPlugin } from '../../../../../plugins/embeddable/public'; diff --git a/src/legacy/core_plugins/kibana/public/dashboard/app.js b/src/legacy/core_plugins/kibana/public/dashboard/legacy_app.js similarity index 99% rename from src/legacy/core_plugins/kibana/public/dashboard/app.js rename to src/legacy/core_plugins/kibana/public/dashboard/legacy_app.js index 6e80c9694152d..c7f2adb4b875b 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/app.js +++ b/src/legacy/core_plugins/kibana/public/dashboard/legacy_app.js @@ -54,7 +54,7 @@ export function initDashboardApp(app, deps) { app.run((globalState, $rootScope) => { registerTimefilterWithGlobalStateFactory( - deps.npDataStart.timefilter.timefilter, + deps.npDataStart.query.timefilter.timefilter, globalState, $rootScope ); diff --git a/src/legacy/core_plugins/kibana/public/dashboard/plugin.ts b/src/legacy/core_plugins/kibana/public/dashboard/plugin.ts index f82930a5c3ed2..b9893f11b5cc5 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/plugin.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/plugin.ts @@ -109,7 +109,7 @@ export class DashboardPlugin implements Plugin { dashboardCapabilities: contextCore.application.capabilities.dashboard, localStorage: new Storage(localStorage), }; - const { renderApp } = await import('./render_app'); + const { renderApp } = await import('./application'); return renderApp(params.element, params.appBasePath, deps); }, }; diff --git a/src/legacy/core_plugins/kibana/public/visualize/application.ts b/src/legacy/core_plugins/kibana/public/visualize/application.ts index 2f3c3ecb5efc2..ab3ecb96284b3 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/application.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/application.ts @@ -43,7 +43,7 @@ import { createFilterBarDirective, createFilterBarHelper } from '../../../data/p import { NavigationStart } from '../../../navigation/public'; // @ts-ignore -import { initVisualizeApp } from './app'; +import { initVisualizeApp } from './legacy_app'; import { VisualizeKibanaServices } from './kibana_services'; let angularModuleInstance: IModule | null = null; diff --git a/src/legacy/core_plugins/kibana/public/visualize/index.ts b/src/legacy/core_plugins/kibana/public/visualize/index.ts index 63c8e28cd0061..2bc9cad2cc9a2 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/index.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/index.ts @@ -28,7 +28,6 @@ import { npSetup, npStart, SavedObjectRegistryProvider, - SavedObjectsClientProvider, ShareContextMenuExtensionsRegistryProvider, VisEditorTypesRegistryProvider, } from './legacy_imports'; @@ -48,17 +47,14 @@ async function getAngularDependencies(): Promise('Private'); - const shareContextMenuExtensions = Private(ShareContextMenuExtensionsRegistryProvider); - const savedObjectRegistry = Private(SavedObjectRegistryProvider); - const savedObjectClient = Private(SavedObjectsClientProvider); const editorTypes = Private(VisEditorTypesRegistryProvider); + const savedObjectRegistry = Private(SavedObjectRegistryProvider); + const shareContextMenuExtensions = Private(ShareContextMenuExtensionsRegistryProvider); return { chromeLegacy: legacyChrome, editorTypes, - savedObjectClient, savedObjectRegistry, - savedDashboards: injector.get('savedDashboards'), savedVisualizations: injector.get('savedVisualizations'), shareContextMenuExtensions, }; diff --git a/src/legacy/core_plugins/kibana/public/visualize/kibana_services.ts b/src/legacy/core_plugins/kibana/public/visualize/kibana_services.ts index cee7e8cabf0d5..b8c03d1529156 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/kibana_services.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/kibana_services.ts @@ -55,6 +55,7 @@ export interface VisualizeKibanaServices { queryFilter: any; toastNotifications: ToastsStart; savedObjectsClient: SavedObjectsClientContract; + savedObjectRegistry: any; savedQueryService: SavedQueryService; savedVisualizations: SavedVisualizations; uiSettings: UiSettingsClientContract; diff --git a/src/legacy/core_plugins/kibana/public/visualize/app.js b/src/legacy/core_plugins/kibana/public/visualize/legacy_app.js similarity index 100% rename from src/legacy/core_plugins/kibana/public/visualize/app.js rename to src/legacy/core_plugins/kibana/public/visualize/legacy_app.js diff --git a/src/legacy/core_plugins/kibana/public/visualize/listing/visualize_listing.js b/src/legacy/core_plugins/kibana/public/visualize/listing/visualize_listing.js index cdf6153e0e75c..a6ec2566c3a75 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/listing/visualize_listing.js +++ b/src/legacy/core_plugins/kibana/public/visualize/listing/visualize_listing.js @@ -26,9 +26,7 @@ import { i18n } from '@kbn/i18n'; import { getServices } from '../kibana_services'; export function initListingDirective(app) { - app.directive('visualizeListingTable', reactDirective => - reactDirective(VisualizeListingTable) - ); + app.directive('visualizeListingTable', reactDirective => reactDirective(VisualizeListingTable)); app.directive('newVisModal', reactDirective => reactDirective(NewVisModal, [ ['visTypesRegistry', { watchDepth: 'collection' }], @@ -46,7 +44,7 @@ export function VisualizeListingController($injector, createNewVis) { chrome, chromeLegacy, savedObjectRegistry, - savedObjectClient, + savedObjectsClient, npDataStart: { query: { timefilter: { timefilter }, @@ -114,7 +112,7 @@ export function VisualizeListingController($injector, createNewVis) { this.deleteSelectedItems = function deleteSelectedItems(selectedItems) { return Promise.all( selectedItems.map(item => { - return savedObjectClient.delete(item.savedObjectType, item.id); + return savedObjectsClient.delete(item.savedObjectType, item.id); }) ) .then(() => { diff --git a/src/legacy/core_plugins/kibana/public/visualize/plugin.ts b/src/legacy/core_plugins/kibana/public/visualize/plugin.ts index 17aa330852413..f9ee3d41a6b93 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/plugin.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/plugin.ts @@ -44,15 +44,14 @@ import { FeatureCatalogueSetup, } from '../../../../../plugins/feature_catalogue/public'; import { defaultEditor } from './legacy_imports'; +import { SavedVisualizations } from './types'; export interface LegacyAngularInjectedDependencies { chromeLegacy: any; editorTypes: any; shareContextMenuExtensions: any; savedObjectRegistry: any; - savedObjectClient: any; - savedDashboards: any; - savedVisualizations: any; + savedVisualizations: SavedVisualizations; } export interface VisualizePluginStartDependencies { From 3622f8ecfdbbe9f3361335248ac918b393a91eac Mon Sep 17 00:00:00 2001 From: maryia-lapata Date: Wed, 13 Nov 2019 16:31:04 +0300 Subject: [PATCH 102/132] Clear deps --- .../kibana/public/dashboard/dashboard_app.tsx | 2 +- .../dashboard/dashboard_app_controller.tsx | 4 +-- .../kibana/public/dashboard/plugin.ts | 2 +- .../kibana/public/visualize/editor/editor.js | 6 ++-- .../kibana/public/visualize/index.ts | 8 +++--- .../public/visualize/kibana_services.ts | 6 +--- .../kibana/public/visualize/legacy_imports.ts | 4 +-- .../visualize/listing/visualize_listing.js | 11 ++++---- .../kibana/public/visualize/plugin.ts | 28 +++++++++---------- 9 files changed, 34 insertions(+), 37 deletions(-) diff --git a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app.tsx b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app.tsx index 4c7f625dd6938..c24189d56e3a6 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app.tsx +++ b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app.tsx @@ -35,7 +35,7 @@ import { DashboardAppState, SavedDashboardPanel, ConfirmModalFn } from './types' import { esFilters } from '../../../../../../src/plugins/data/public'; import { DashboardAppController } from './dashboard_app_controller'; -import { RenderDeps } from './render_app'; +import { RenderDeps } from './application'; export interface DashboardAppScope extends ng.IScope { dash: SavedObjectDashboard; diff --git a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx index 46d67206c6aa5..b3d5be93aece8 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx +++ b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx @@ -25,6 +25,7 @@ import { uniq } from 'lodash'; import { Subscription } from 'rxjs'; +import { SavedObjectFinder } from 'ui/saved_objects/components/saved_object_finder'; import { subscribeWithScope, ConfirmationButtonTypes, @@ -55,7 +56,6 @@ import { openAddPanelFlyout, } from '../../../embeddable_api/public/np_ready/public'; import { DashboardAppState, NavAction, ConfirmModalFn, SavedDashboardPanel } from './types'; -import { SavedObjectFinder } from '../../../../../plugins/kibana_react/public'; import { showOptionsPopover } from './top_nav/show_options_popover'; import { DashboardSaveModal } from './top_nav/save_modal'; @@ -69,7 +69,7 @@ import { getDashboardTitle } from './dashboard_strings'; import { DashboardAppScope } from './dashboard_app'; import { VISUALIZE_EMBEDDABLE_TYPE } from '../visualize/embeddable'; import { convertSavedDashboardPanelToPanelState } from './lib/embeddable_saved_object_converters'; -import { RenderDeps } from './render_app'; +import { RenderDeps } from './application'; export interface DashboardAppControllerDependencies extends RenderDeps { $scope: DashboardAppScope; diff --git a/src/legacy/core_plugins/kibana/public/dashboard/plugin.ts b/src/legacy/core_plugins/kibana/public/dashboard/plugin.ts index b9893f11b5cc5..bd1b69a055ede 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/plugin.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/plugin.ts @@ -26,7 +26,7 @@ import { SavedObjectsClientContract, } from 'kibana/public'; import { i18n } from '@kbn/i18n'; -import { RenderDeps } from './render_app'; +import { RenderDeps } from './application'; import { LocalApplicationService } from '../local_application_service'; import { DataStart } from '../../../data/public'; import { DataPublicPluginStart as NpDataStart } from '../../../../../plugins/data/public'; diff --git a/src/legacy/core_plugins/kibana/public/visualize/editor/editor.js b/src/legacy/core_plugins/kibana/public/visualize/editor/editor.js index 3a361700f973a..850a1a9fbac6c 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/editor/editor.js +++ b/src/legacy/core_plugins/kibana/public/visualize/editor/editor.js @@ -89,10 +89,10 @@ function VisualizeAppController( }, }, toastNotifications, - chromeLegacy, + legacyChrome, chrome, getBasePath, - docLinks, + core: { docLinks }, savedQueryService, uiSettings, } = getServices(); @@ -519,7 +519,7 @@ function VisualizeAppController( // Since we aren't reloading the page, only inserting a new browser history item, we need to manually update // the last url for this app, so directly clicking on the Visualize tab will also bring the user to the saved // url, not the unsaved one. - chromeLegacy.trackSubUrlForApp('kibana:visualize', savedVisualizationParsedUrl); + legacyChrome.trackSubUrlForApp('kibana:visualize', savedVisualizationParsedUrl); const lastDashboardAbsoluteUrl = chrome.navLinks.get('kibana:dashboard').url; const dashboardParsedUrl = absoluteToParsedUrl(lastDashboardAbsoluteUrl, getBasePath()); diff --git a/src/legacy/core_plugins/kibana/public/visualize/index.ts b/src/legacy/core_plugins/kibana/public/visualize/index.ts index 2bc9cad2cc9a2..8082cb33a19b4 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/index.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/index.ts @@ -33,7 +33,7 @@ import { } from './legacy_imports'; import { VisualizePlugin, LegacyAngularInjectedDependencies } from './plugin'; import { localApplicationService } from '../local_application_service'; -import { start as dataStart } from '../../../data/public/legacy'; +import { start as data } from '../../../data/public/legacy'; import { start as embeddables } from '../../../embeddable_api/public/np_ready/public/legacy'; import { start as navigation } from '../../../navigation/public/legacy'; import { start as visualizations } from '../../../visualizations/public/np_ready/public/legacy'; @@ -52,7 +52,7 @@ async function getAngularDependencies(): Promise string; angular: IAngularStatic; chrome: ChromeStart; - chromeLegacy: any; + legacyChrome: any; core: LegacyCoreStart; dataStart: DataStart; editorTypes: any; npDataStart: NpDataStart; - docLinks: DocLinksStart; embeddables: ReturnType; getBasePath: () => string; - getInjected: (name: string, defaultValue?: any) => unknown; indexPatterns: any; localStorage: Storage; navigation: NavigationStart; - queryFilter: any; toastNotifications: ToastsStart; savedObjectsClient: SavedObjectsClientContract; savedObjectRegistry: any; diff --git a/src/legacy/core_plugins/kibana/public/visualize/legacy_imports.ts b/src/legacy/core_plugins/kibana/public/visualize/legacy_imports.ts index 28a73fdf12195..806085a031969 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/legacy_imports.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/legacy_imports.ts @@ -35,11 +35,10 @@ export { AppStateProvider } from 'ui/state_management/app_state'; export { npSetup, npStart } from 'ui/new_platform'; export { SavedObjectRegistryProvider, SavedObjectsClientProvider } from 'ui/saved_objects'; export { IPrivate } from 'ui/private'; -export { ShareContextMenuExtensionsRegistryProvider } from 'ui/share'; +export { ShareContextMenuExtensionsRegistryProvider, showShareContextMenu } from 'ui/share'; export { SavedObjectSaveModal } from 'ui/saved_objects/components/saved_object_save_modal'; export { subscribeWithScope } from 'ui/utils/subscribe_with_scope'; export { showSaveModal } from 'ui/saved_objects/show_saved_object_save_modal'; -export { showShareContextMenu } from 'ui/share'; export { migrateLegacyQuery } from 'ui/utils/migrate_legacy_query'; // @ts-ignore export { GlobalStateProvider } from 'ui/state_management/global_state'; @@ -73,3 +72,4 @@ export { defaultEditor } from 'ui/vis/editors/default/default'; export { VisType } from 'ui/vis'; export { SavedObjectFinder } from 'ui/saved_objects/components/saved_object_finder'; export { memoizeLast } from 'ui/utils/memoize'; +export { wrapInI18nContext } from 'ui/i18n'; diff --git a/src/legacy/core_plugins/kibana/public/visualize/listing/visualize_listing.js b/src/legacy/core_plugins/kibana/public/visualize/listing/visualize_listing.js index a6ec2566c3a75..7171540e8f8be 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/listing/visualize_listing.js +++ b/src/legacy/core_plugins/kibana/public/visualize/listing/visualize_listing.js @@ -24,11 +24,12 @@ import { VisualizeConstants } from '../visualize_constants'; import { i18n } from '@kbn/i18n'; import { getServices } from '../kibana_services'; +import { wrapInI18nContext } from '../legacy_imports'; export function initListingDirective(app) { - app.directive('visualizeListingTable', reactDirective => reactDirective(VisualizeListingTable)); + app.directive('visualizeListingTable', reactDirective => reactDirective(wrapInI18nContext(VisualizeListingTable))); app.directive('newVisModal', reactDirective => - reactDirective(NewVisModal, [ + reactDirective(wrapInI18nContext(NewVisModal), [ ['visTypesRegistry', { watchDepth: 'collection' }], ['onClose', { watchDepth: 'reference' }], ['addBasePath', { watchDepth: 'reference' }], @@ -42,7 +43,7 @@ export function VisualizeListingController($injector, createNewVis) { const { addBasePath, chrome, - chromeLegacy, + legacyChrome, savedObjectRegistry, savedObjectsClient, npDataStart: { @@ -53,7 +54,7 @@ export function VisualizeListingController($injector, createNewVis) { toastNotifications, uiSettings, visualizations, - docLinks, + core: { docLinks }, } = getServices(); const kbnUrl = $injector.get('kbnUrl'); @@ -116,7 +117,7 @@ export function VisualizeListingController($injector, createNewVis) { }) ) .then(() => { - chromeLegacy.untrackNavLinksForDeletedSavedObjects(selectedItems.map(item => item.id)); + legacyChrome.untrackNavLinksForDeletedSavedObjects(selectedItems.map(item => item.id)); }) .catch(error => { toastNotifications.addError(error, { diff --git a/src/legacy/core_plugins/kibana/public/visualize/plugin.ts b/src/legacy/core_plugins/kibana/public/visualize/plugin.ts index f9ee3d41a6b93..9ca695e5a4cfe 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/plugin.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/plugin.ts @@ -47,7 +47,7 @@ import { defaultEditor } from './legacy_imports'; import { SavedVisualizations } from './types'; export interface LegacyAngularInjectedDependencies { - chromeLegacy: any; + legacyChrome: any; editorTypes: any; shareContextMenuExtensions: any; savedObjectRegistry: any; @@ -55,7 +55,7 @@ export interface LegacyAngularInjectedDependencies { } export interface VisualizePluginStartDependencies { - dataStart: DataStart; + data: DataStart; npData: NpDataStart; embeddables: ReturnType; navigation: NavigationStart; @@ -68,8 +68,8 @@ export interface VisualizePluginSetupDependencies { angular: IAngularStatic; getAngularDependencies: () => Promise; localApplicationService: LocalApplicationService; - VisEditorTypesRegistryProvider: any; }; + VisEditorTypesRegistryProvider: any; } export class VisualizePlugin implements Plugin { @@ -86,12 +86,8 @@ export class VisualizePlugin implements Plugin { core: CoreSetup, { feature_catalogue, - __LEGACY: { - localApplicationService, - getAngularDependencies, - VisEditorTypesRegistryProvider, - ...legacyServices - }, + VisEditorTypesRegistryProvider, + __LEGACY: { localApplicationService, getAngularDependencies, ...legacyServices }, }: VisualizePluginSetupDependencies ) { const app: App = { @@ -120,14 +116,11 @@ export class VisualizePlugin implements Plugin { chrome: contextCore.chrome, dataStart, npDataStart, - docLinks: contextCore.docLinks, embeddables, getBasePath: core.http.basePath.get, - getInjected: core.injectedMetadata.getInjectedVar, indexPatterns: dataStart.indexPatterns.indexPatterns, localStorage: new Storage(localStorage), navigation, - queryFilter: npDataStart.query.filterManager, savedObjectsClient, savedQueryService: dataStart.search.services.savedQueryService, toastNotifications: contextCore.notifications.toasts, @@ -142,6 +135,8 @@ export class VisualizePlugin implements Plugin { }, }; + localApplicationService.register({ ...app, id: 'visualize' }); + feature_catalogue.register({ id: 'visualize', title: 'Visualize', @@ -155,13 +150,18 @@ export class VisualizePlugin implements Plugin { category: FeatureCatalogueCategory.DATA, }); - localApplicationService.register({ ...app, id: 'visualize' }); VisEditorTypesRegistryProvider.register(defaultEditor); } public start( { savedObjects: { client: savedObjectsClient } }: CoreStart, - { dataStart, embeddables, navigation, visualizations, npData }: VisualizePluginStartDependencies + { + data: dataStart, + embeddables, + navigation, + visualizations, + npData, + }: VisualizePluginStartDependencies ) { this.startDependencies = { dataStart, From 042f2e8f3091c015588414dc9f39423bf1a92329 Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Wed, 13 Nov 2019 14:31:20 +0100 Subject: [PATCH 103/132] fix jest tests --- .../public/dashboard/dashboard_state.test.ts | 4 + .../dashboard_listing.test.js.snap | 966 +++++++++--------- .../dashboard/top_nav/save_modal.test.js | 7 + 3 files changed, 500 insertions(+), 477 deletions(-) diff --git a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_state.test.ts b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_state.test.ts index b4eb9c9b8bf19..14629e7931813 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_state.test.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_state.test.ts @@ -32,6 +32,10 @@ jest.mock('ui/registry/field_formats', () => ({ }, })); +jest.mock('ui/state_management/state', () => ({ + State: {}, +})); + describe('DashboardState', function() { let dashboardState: DashboardStateManager; const savedDashboard = getSavedDashboardMock(); diff --git a/src/legacy/core_plugins/kibana/public/dashboard/listing/__snapshots__/dashboard_listing.test.js.snap b/src/legacy/core_plugins/kibana/public/dashboard/listing/__snapshots__/dashboard_listing.test.js.snap index 1ed05035f5f4c..b2f004568841a 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/listing/__snapshots__/dashboard_listing.test.js.snap +++ b/src/legacy/core_plugins/kibana/public/dashboard/listing/__snapshots__/dashboard_listing.test.js.snap @@ -1,533 +1,545 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`after fetch hideWriteControls 1`] = ` - - - - - } - /> -
- } - tableColumns={ - Array [ - Object { - "field": "title", - "name": "Title", - "render": [Function], - "sortable": true, - }, + + + + + + } + /> +

1j*;k$iW5JwC1;^#{X`htGeBVM9jI*^!R@G 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 `aTi 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 VIUo$(uEYUISzcYPSjOc2{{xjj8>IjM diff --git a/src/legacy/core_plugins/kibana/public/home/tutorial_resources/logos/cockroachdb.svg b/src/legacy/core_plugins/kibana/public/home/tutorial_resources/logos/cockroachdb.svg deleted file mode 100644 index 72f0958f52824..0000000000000 --- a/src/legacy/core_plugins/kibana/public/home/tutorial_resources/logos/cockroachdb.svg +++ /dev/null @@ -1,666 +0,0 @@ - - - -image/svg+xml - - - - - - - - - - - - - - - - - - - - diff --git a/src/legacy/core_plugins/kibana/public/local_application_service/local_application_service.ts b/src/legacy/core_plugins/kibana/public/local_application_service/local_application_service.ts index 69538f0e99a4e..ba7e3921d3537 100644 --- a/src/legacy/core_plugins/kibana/public/local_application_service/local_application_service.ts +++ b/src/legacy/core_plugins/kibana/public/local_application_service/local_application_service.ts @@ -17,15 +17,9 @@ * under the License. */ -<<<<<<< HEAD import { App } from 'kibana/public'; import { UIRoutes } from 'ui/routes'; import { IScope } from 'angular'; -======= -import { App, AppUnmount } from 'kibana/public'; -import { UIRoutes } from 'ui/routes'; -import { ILocationService, IScope } from 'angular'; ->>>>>>> kibana/master import { npStart } from 'ui/new_platform'; import { htmlIdGenerator } from '@elastic/eui'; @@ -36,11 +30,7 @@ interface ForwardDefinition { } const matchAllWithPrefix = (prefixOrApp: string | App) => -<<<<<<< HEAD `/${typeof prefixOrApp === 'string' ? prefixOrApp : prefixOrApp.id}:tail*?`; -======= - `/${typeof prefixOrApp === 'string' ? prefixOrApp : prefixOrApp.id}/:tail*?`; ->>>>>>> kibana/master /** * To be able to migrate and shim parts of the Kibana app plugin @@ -112,7 +102,7 @@ export class LocalApplicationService { * * @param angularRouteManager The current `ui/routes` instance */ - attachToAngular(angularRouteManager: UIRoutes) { + apply(angularRouteManager: UIRoutes) { this.apps.forEach(app => { const wrapperElementId = this.idGenerator(); angularRouteManager.when(matchAllWithPrefix(app), { @@ -122,20 +112,11 @@ export class LocalApplicationService { template: `

+ } + tableColumns={ + Array [ + Object { + "field": "title", + "name": "Title", + "render": [Function], + "sortable": true, + }, + Object { + "dataType": "string", + "field": "description", + "name": "Description", + "sortable": true, + }, + ] + } + tableListTitle="Dashboards" + toastNotifications={Object {}} + uiSettings={ Object { - "dataType": "string", - "field": "description", - "name": "Description", - "sortable": true, - }, - ] - } - tableListTitle="Dashboards" - toastNotifications={Object {}} - uiSettings={ - Object { - "get": [MockFunction], + "get": [MockFunction], + } } - } -/> + /> + `; exports[`after fetch initialFilter 1`] = ` - - - - - } - body={ - -

+ + + -

-

- - - , + + } + body={ + +

+ +

+

+ + + , + } } - } + /> +

+
+ } + iconType="dashboardApp" + title={ +

+ -

- - } - iconType="dashboardApp" - title={ -

- -

- } - /> - - } - tableColumns={ - Array [ - Object { - "field": "title", - "name": "Title", - "render": [Function], - "sortable": true, - }, +

+ } + /> + + } + tableColumns={ + Array [ + Object { + "field": "title", + "name": "Title", + "render": [Function], + "sortable": true, + }, + Object { + "dataType": "string", + "field": "description", + "name": "Description", + "sortable": true, + }, + ] + } + tableListTitle="Dashboards" + toastNotifications={Object {}} + uiSettings={ Object { - "dataType": "string", - "field": "description", - "name": "Description", - "sortable": true, - }, - ] - } - tableListTitle="Dashboards" - toastNotifications={Object {}} - uiSettings={ - Object { - "get": [MockFunction], + "get": [MockFunction], + } } - } -/> + /> + `; exports[`after fetch renders call to action when no dashboards exist 1`] = ` - - - - - } - body={ - -

+ + + -

-

- - - , + + } + body={ + +

+ +

+

+ + + , + } } - } + /> +

+
+ } + iconType="dashboardApp" + title={ +

+ -

- - } - iconType="dashboardApp" - title={ -

- -

- } - /> - - } - tableColumns={ - Array [ - Object { - "field": "title", - "name": "Title", - "render": [Function], - "sortable": true, - }, +

+ } + /> + + } + tableColumns={ + Array [ + Object { + "field": "title", + "name": "Title", + "render": [Function], + "sortable": true, + }, + Object { + "dataType": "string", + "field": "description", + "name": "Description", + "sortable": true, + }, + ] + } + tableListTitle="Dashboards" + toastNotifications={Object {}} + uiSettings={ Object { - "dataType": "string", - "field": "description", - "name": "Description", - "sortable": true, - }, - ] - } - tableListTitle="Dashboards" - toastNotifications={Object {}} - uiSettings={ - Object { - "get": [MockFunction], + "get": [MockFunction], + } } - } -/> + /> + `; exports[`after fetch renders table rows 1`] = ` - - - - - } - body={ - -

+ + + -

-

- - - , + + } + body={ + +

+ +

+

+ + + , + } } - } + /> +

+
+ } + iconType="dashboardApp" + title={ +

+ -

- - } - iconType="dashboardApp" - title={ -

- -

- } - /> - - } - tableColumns={ - Array [ - Object { - "field": "title", - "name": "Title", - "render": [Function], - "sortable": true, - }, +

+ } + /> + + } + tableColumns={ + Array [ + Object { + "field": "title", + "name": "Title", + "render": [Function], + "sortable": true, + }, + Object { + "dataType": "string", + "field": "description", + "name": "Description", + "sortable": true, + }, + ] + } + tableListTitle="Dashboards" + toastNotifications={Object {}} + uiSettings={ Object { - "dataType": "string", - "field": "description", - "name": "Description", - "sortable": true, - }, - ] - } - tableListTitle="Dashboards" - toastNotifications={Object {}} - uiSettings={ - Object { - "get": [MockFunction], + "get": [MockFunction], + } } - } -/> + /> + `; exports[`after fetch renders warning when listingLimit is exceeded 1`] = ` - - - - - } - body={ - -

+ + + -

-

- - - , + + } + body={ + +

+ +

+

+ + + , + } } - } + /> +

+
+ } + iconType="dashboardApp" + title={ +

+ -

- - } - iconType="dashboardApp" - title={ -

- -

- } - /> - - } - tableColumns={ - Array [ - Object { - "field": "title", - "name": "Title", - "render": [Function], - "sortable": true, - }, +

+ } + /> + + } + tableColumns={ + Array [ + Object { + "field": "title", + "name": "Title", + "render": [Function], + "sortable": true, + }, + Object { + "dataType": "string", + "field": "description", + "name": "Description", + "sortable": true, + }, + ] + } + tableListTitle="Dashboards" + toastNotifications={Object {}} + uiSettings={ Object { - "dataType": "string", - "field": "description", - "name": "Description", - "sortable": true, - }, - ] - } - tableListTitle="Dashboards" - toastNotifications={Object {}} - uiSettings={ - Object { - "get": [MockFunction], + "get": [MockFunction], + } } - } -/> + /> + `; exports[`renders empty page in before initial fetch to avoid flickering 1`] = ` - - - - - } - body={ - -

+ + + -

-

- - - , + + } + body={ + +

+ +

+

+ + + , + } } - } + /> +

+
+ } + iconType="dashboardApp" + title={ +

+ -

- - } - iconType="dashboardApp" - title={ -

- -

- } - /> - - } - tableColumns={ - Array [ - Object { - "field": "title", - "name": "Title", - "render": [Function], - "sortable": true, - }, +

+ } + /> + + } + tableColumns={ + Array [ + Object { + "field": "title", + "name": "Title", + "render": [Function], + "sortable": true, + }, + Object { + "dataType": "string", + "field": "description", + "name": "Description", + "sortable": true, + }, + ] + } + tableListTitle="Dashboards" + toastNotifications={Object {}} + uiSettings={ Object { - "dataType": "string", - "field": "description", - "name": "Description", - "sortable": true, - }, - ] - } - tableListTitle="Dashboards" - toastNotifications={Object {}} - uiSettings={ - Object { - "get": [MockFunction], + "get": [MockFunction], + } } - } -/> + /> + `; diff --git a/src/legacy/core_plugins/kibana/public/dashboard/top_nav/save_modal.test.js b/src/legacy/core_plugins/kibana/public/dashboard/top_nav/save_modal.test.js index 153a049276cee..aa7e219d75963 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/top_nav/save_modal.test.js +++ b/src/legacy/core_plugins/kibana/public/dashboard/top_nav/save_modal.test.js @@ -17,9 +17,16 @@ * under the License. */ + import React from 'react'; import { shallowWithI18nProvider } from 'test_utils/enzyme_helpers'; +jest.mock('../legacy_imports', () => ({ + SavedObjectSaveModal: () => null +})); + +jest.mock('ui/new_platform'); + import { DashboardSaveModal } from './save_modal'; test('renders DashboardSaveModal', () => { From 363a9078c40f2fad8d2d2b87531d291be11b5241 Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Wed, 13 Nov 2019 15:03:00 +0100 Subject: [PATCH 104/132] fix saved object finder bug --- .../kibana/public/dashboard/dashboard_app_controller.tsx | 4 +++- .../panel_actions/add_panel/add_panel_flyout.tsx | 9 +++++++-- .../panel_actions/add_panel/open_add_panel_flyout.tsx | 8 +++++++- 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx index 6cbfd6f8d1844..04285a4146cac 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx +++ b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx @@ -116,7 +116,7 @@ export class DashboardAppController { timefilter: { timefilter }, }, }, - core: { notifications, overlays, chrome, injectedMetadata }, + core: { notifications, overlays, chrome, savedObjects, uiSettings, injectedMetadata }, }: DashboardAppControllerDependencies) { new FilterStateManager(globalState, getAppState, filterManager); const queryFilter = filterManager; @@ -729,6 +729,8 @@ export class DashboardAppController { getFactory: embeddables.getEmbeddableFactory, notifications, overlays, + savedObjects, + uiSettings, SavedObjectFinder, }); } diff --git a/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/add_panel_flyout.tsx b/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/add_panel_flyout.tsx index 4f2ae7ab19bcb..3a2f1159daf62 100644 --- a/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/add_panel_flyout.tsx +++ b/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/add_panel_flyout.tsx @@ -19,7 +19,7 @@ import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import React from 'react'; -import { CoreSetup } from 'src/core/public'; +import { CoreSetup, SavedObjectsStart, UiSettingsClientContract } from 'src/core/public'; import { EuiButton, @@ -36,6 +36,7 @@ import { import { IContainer } from '../../../../containers'; import { EmbeddableFactoryNotFoundError } from '../../../../errors'; import { GetEmbeddableFactories, GetEmbeddableFactory } from '../../../../types'; +import { SavedObjectFinderProps } from '../../../../../../../kibana_react/public'; interface Props { onClose: () => void; @@ -43,7 +44,9 @@ interface Props { getFactory: GetEmbeddableFactory; getAllFactories: GetEmbeddableFactories; notifications: CoreSetup['notifications']; - SavedObjectFinder: React.ComponentType; + savedObjects: SavedObjectsStart; + uiSettings: UiSettingsClientContract; + SavedObjectFinder: React.ComponentType; } interface State { @@ -145,6 +148,8 @@ export class AddPanelFlyout extends React.Component { noItemsMessage={i18n.translate('embeddableApi.addPanel.noMatchingObjectsMessage', { defaultMessage: 'No matching objects found.', })} + savedObjects={this.props.savedObjects} + uiSettings={this.props.uiSettings} /> ); diff --git a/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/open_add_panel_flyout.tsx b/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/open_add_panel_flyout.tsx index bfa4f6e31d84e..550f0c61d77c5 100644 --- a/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/open_add_panel_flyout.tsx +++ b/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/open_add_panel_flyout.tsx @@ -17,7 +17,7 @@ * under the License. */ import React from 'react'; -import { NotificationsStart } from 'src/core/public'; +import { NotificationsStart, SavedObjectsStart, UiSettingsClientContract } from 'src/core/public'; import { KibanaReactOverlays } from 'src/plugins/kibana_react/public'; import { IContainer } from '../../../../containers'; import { AddPanelFlyout } from './add_panel_flyout'; @@ -29,6 +29,8 @@ export async function openAddPanelFlyout(options: { getAllFactories: GetEmbeddableFactories; overlays: KibanaReactOverlays; notifications: NotificationsStart; + savedObjects: SavedObjectsStart; + uiSettings: UiSettingsClientContract; SavedObjectFinder: React.ComponentType; }) { const { @@ -37,6 +39,8 @@ export async function openAddPanelFlyout(options: { getAllFactories, overlays, notifications, + savedObjects, + uiSettings, SavedObjectFinder, } = options; const flyoutSession = overlays.openFlyout( @@ -50,6 +54,8 @@ export async function openAddPanelFlyout(options: { getFactory={getFactory} getAllFactories={getAllFactories} notifications={notifications} + savedObjects={savedObjects} + uiSettings={uiSettings} SavedObjectFinder={SavedObjectFinder} />, { From f5dacfd1b70d881cea2c891937fc76071ae837c8 Mon Sep 17 00:00:00 2001 From: maryia-lapata Date: Wed, 13 Nov 2019 18:16:09 +0300 Subject: [PATCH 105/132] Fix unit tests --- .../dashboard/dashboard_app_controller.tsx | 5 +- .../public/dashboard/dashboard_state.test.ts | 2 +- .../dashboard_listing.test.js.snap | 966 +++++++++--------- .../dashboard/top_nav/save_modal.test.js | 2 +- .../visualize/wizard/new_vis_modal.test.tsx | 9 +- 5 files changed, 502 insertions(+), 482 deletions(-) diff --git a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx index b3d5be93aece8..a4cd34401ef46 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx +++ b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx @@ -25,7 +25,6 @@ import { uniq } from 'lodash'; import { Subscription } from 'rxjs'; -import { SavedObjectFinder } from 'ui/saved_objects/components/saved_object_finder'; import { subscribeWithScope, ConfirmationButtonTypes, @@ -56,6 +55,7 @@ import { openAddPanelFlyout, } from '../../../embeddable_api/public/np_ready/public'; import { DashboardAppState, NavAction, ConfirmModalFn, SavedDashboardPanel } from './types'; +import { SavedObjectFinder } from '../../../../../plugins/kibana_react/public'; import { showOptionsPopover } from './top_nav/show_options_popover'; import { DashboardSaveModal } from './top_nav/save_modal'; @@ -116,7 +116,7 @@ export class DashboardAppController { timefilter: { timefilter }, }, }, - core: { notifications, overlays, chrome, injectedMetadata }, + core: { notifications, overlays, chrome, uiSettings, injectedMetadata }, }: DashboardAppControllerDependencies) { new FilterStateManager(globalState, getAppState, filterManager); const queryFilter = filterManager; @@ -729,6 +729,7 @@ export class DashboardAppController { getFactory: embeddables.getEmbeddableFactory, notifications, overlays, + uiSettings, SavedObjectFinder, }); } diff --git a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_state.test.ts b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_state.test.ts index b4eb9c9b8bf19..6c6ffc99c0c38 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_state.test.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_state.test.ts @@ -32,7 +32,7 @@ jest.mock('ui/registry/field_formats', () => ({ }, })); -describe('DashboardState', function() { +describe.skip('DashboardState', function() { let dashboardState: DashboardStateManager; const savedDashboard = getSavedDashboardMock(); diff --git a/src/legacy/core_plugins/kibana/public/dashboard/listing/__snapshots__/dashboard_listing.test.js.snap b/src/legacy/core_plugins/kibana/public/dashboard/listing/__snapshots__/dashboard_listing.test.js.snap index 1ed05035f5f4c..b2f004568841a 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/listing/__snapshots__/dashboard_listing.test.js.snap +++ b/src/legacy/core_plugins/kibana/public/dashboard/listing/__snapshots__/dashboard_listing.test.js.snap @@ -1,533 +1,545 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`after fetch hideWriteControls 1`] = ` - - - - - } - /> - - } - tableColumns={ - Array [ - Object { - "field": "title", - "name": "Title", - "render": [Function], - "sortable": true, - }, + + + + + + } + /> + + } + tableColumns={ + Array [ + Object { + "field": "title", + "name": "Title", + "render": [Function], + "sortable": true, + }, + Object { + "dataType": "string", + "field": "description", + "name": "Description", + "sortable": true, + }, + ] + } + tableListTitle="Dashboards" + toastNotifications={Object {}} + uiSettings={ Object { - "dataType": "string", - "field": "description", - "name": "Description", - "sortable": true, - }, - ] - } - tableListTitle="Dashboards" - toastNotifications={Object {}} - uiSettings={ - Object { - "get": [MockFunction], + "get": [MockFunction], + } } - } -/> + /> + `; exports[`after fetch initialFilter 1`] = ` - - - - - } - body={ - -

+ + + -

-

- - - , + + } + body={ + +

+ +

+

+ + + , + } } - } + /> +

+
+ } + iconType="dashboardApp" + title={ +

+ -

- - } - iconType="dashboardApp" - title={ -

- -

- } - /> - - } - tableColumns={ - Array [ - Object { - "field": "title", - "name": "Title", - "render": [Function], - "sortable": true, - }, +

+ } + /> + + } + tableColumns={ + Array [ + Object { + "field": "title", + "name": "Title", + "render": [Function], + "sortable": true, + }, + Object { + "dataType": "string", + "field": "description", + "name": "Description", + "sortable": true, + }, + ] + } + tableListTitle="Dashboards" + toastNotifications={Object {}} + uiSettings={ Object { - "dataType": "string", - "field": "description", - "name": "Description", - "sortable": true, - }, - ] - } - tableListTitle="Dashboards" - toastNotifications={Object {}} - uiSettings={ - Object { - "get": [MockFunction], + "get": [MockFunction], + } } - } -/> + /> + `; exports[`after fetch renders call to action when no dashboards exist 1`] = ` - - - - - } - body={ - -

+ + + -

-

- - - , + + } + body={ + +

+ +

+

+ + + , + } } - } + /> +

+
+ } + iconType="dashboardApp" + title={ +

+ -

- - } - iconType="dashboardApp" - title={ -

- -

- } - /> - - } - tableColumns={ - Array [ - Object { - "field": "title", - "name": "Title", - "render": [Function], - "sortable": true, - }, +

+ } + /> + + } + tableColumns={ + Array [ + Object { + "field": "title", + "name": "Title", + "render": [Function], + "sortable": true, + }, + Object { + "dataType": "string", + "field": "description", + "name": "Description", + "sortable": true, + }, + ] + } + tableListTitle="Dashboards" + toastNotifications={Object {}} + uiSettings={ Object { - "dataType": "string", - "field": "description", - "name": "Description", - "sortable": true, - }, - ] - } - tableListTitle="Dashboards" - toastNotifications={Object {}} - uiSettings={ - Object { - "get": [MockFunction], + "get": [MockFunction], + } } - } -/> + /> + `; exports[`after fetch renders table rows 1`] = ` - - - - - } - body={ - -

+ + + -

-

- - - , + + } + body={ + +

+ +

+

+ + + , + } } - } + /> +

+
+ } + iconType="dashboardApp" + title={ +

+ -

- - } - iconType="dashboardApp" - title={ -

- -

- } - /> - - } - tableColumns={ - Array [ - Object { - "field": "title", - "name": "Title", - "render": [Function], - "sortable": true, - }, +

+ } + /> + + } + tableColumns={ + Array [ + Object { + "field": "title", + "name": "Title", + "render": [Function], + "sortable": true, + }, + Object { + "dataType": "string", + "field": "description", + "name": "Description", + "sortable": true, + }, + ] + } + tableListTitle="Dashboards" + toastNotifications={Object {}} + uiSettings={ Object { - "dataType": "string", - "field": "description", - "name": "Description", - "sortable": true, - }, - ] - } - tableListTitle="Dashboards" - toastNotifications={Object {}} - uiSettings={ - Object { - "get": [MockFunction], + "get": [MockFunction], + } } - } -/> + /> + `; exports[`after fetch renders warning when listingLimit is exceeded 1`] = ` - - - - - } - body={ - -

+ + + -

-

- - - , + + } + body={ + +

+ +

+

+ + + , + } } - } + /> +

+
+ } + iconType="dashboardApp" + title={ +

+ -

- - } - iconType="dashboardApp" - title={ -

- -

- } - /> - - } - tableColumns={ - Array [ - Object { - "field": "title", - "name": "Title", - "render": [Function], - "sortable": true, - }, +

+ } + /> + + } + tableColumns={ + Array [ + Object { + "field": "title", + "name": "Title", + "render": [Function], + "sortable": true, + }, + Object { + "dataType": "string", + "field": "description", + "name": "Description", + "sortable": true, + }, + ] + } + tableListTitle="Dashboards" + toastNotifications={Object {}} + uiSettings={ Object { - "dataType": "string", - "field": "description", - "name": "Description", - "sortable": true, - }, - ] - } - tableListTitle="Dashboards" - toastNotifications={Object {}} - uiSettings={ - Object { - "get": [MockFunction], + "get": [MockFunction], + } } - } -/> + /> + `; exports[`renders empty page in before initial fetch to avoid flickering 1`] = ` - - - - - } - body={ - -

+ + + -

-

- - - , + + } + body={ + +

+ +

+

+ + + , + } } - } + /> +

+
+ } + iconType="dashboardApp" + title={ +

+ -

- - } - iconType="dashboardApp" - title={ -

- -

- } - /> - - } - tableColumns={ - Array [ - Object { - "field": "title", - "name": "Title", - "render": [Function], - "sortable": true, - }, +

+ } + /> + + } + tableColumns={ + Array [ + Object { + "field": "title", + "name": "Title", + "render": [Function], + "sortable": true, + }, + Object { + "dataType": "string", + "field": "description", + "name": "Description", + "sortable": true, + }, + ] + } + tableListTitle="Dashboards" + toastNotifications={Object {}} + uiSettings={ Object { - "dataType": "string", - "field": "description", - "name": "Description", - "sortable": true, - }, - ] - } - tableListTitle="Dashboards" - toastNotifications={Object {}} - uiSettings={ - Object { - "get": [MockFunction], + "get": [MockFunction], + } } - } -/> + /> + `; diff --git a/src/legacy/core_plugins/kibana/public/dashboard/top_nav/save_modal.test.js b/src/legacy/core_plugins/kibana/public/dashboard/top_nav/save_modal.test.js index 153a049276cee..6e1a47ac657da 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/top_nav/save_modal.test.js +++ b/src/legacy/core_plugins/kibana/public/dashboard/top_nav/save_modal.test.js @@ -22,7 +22,7 @@ import { shallowWithI18nProvider } from 'test_utils/enzyme_helpers'; import { DashboardSaveModal } from './save_modal'; -test('renders DashboardSaveModal', () => { +test.skip('renders DashboardSaveModal', () => { const component = shallowWithI18nProvider( {}} diff --git a/src/legacy/core_plugins/kibana/public/visualize/wizard/new_vis_modal.test.tsx b/src/legacy/core_plugins/kibana/public/visualize/wizard/new_vis_modal.test.tsx index 2b000a8098a99..1552c0a313abd 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/wizard/new_vis_modal.test.tsx +++ b/src/legacy/core_plugins/kibana/public/visualize/wizard/new_vis_modal.test.tsx @@ -20,10 +20,17 @@ import React from 'react'; import { mountWithIntl } from 'test_utils/enzyme_helpers'; -import { NewVisModal } from './new_vis_modal'; import { VisType } from 'ui/vis'; import { TypesStart } from '../../../../visualizations/public/np_ready/public/types'; +jest.mock('../legacy_imports', () => ({ + memoizeLast: jest.requireActual('ui/utils/memoize').memoizeLast, + State: () => null, + AppState: () => null, +})); + +import { NewVisModal } from './new_vis_modal'; + describe('NewVisModal', () => { const defaultVisTypeParams = { hidden: false, From 11b844260076ed260e513bae0b39dd59f561f4ef Mon Sep 17 00:00:00 2001 From: maryia-lapata Date: Wed, 13 Nov 2019 18:56:24 +0300 Subject: [PATCH 106/132] Ignore TS --- .../kibana/public/dashboard/dashboard_app_controller.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx index a4cd34401ef46..f1c908b94e8d5 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx +++ b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx @@ -729,6 +729,7 @@ export class DashboardAppController { getFactory: embeddables.getEmbeddableFactory, notifications, overlays, + // @ts-ignore uiSettings, SavedObjectFinder, }); From 9296c4ceef5e281ed2599c2a0e076726fae32123 Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Wed, 13 Nov 2019 17:00:36 +0100 Subject: [PATCH 107/132] revert using stateless component for this PR --- .../kibana/public/dashboard/dashboard_app_controller.tsx | 4 +--- .../kibana/public/dashboard/legacy_imports.ts | 1 + .../panel_actions/add_panel/add_panel_flyout.tsx | 6 +----- .../panel_actions/add_panel/open_add_panel_flyout.tsx | 8 +------- 4 files changed, 4 insertions(+), 15 deletions(-) diff --git a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx index 04285a4146cac..06faae43d1681 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx +++ b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx @@ -36,6 +36,7 @@ import { AppStateClass as TAppStateClass, KbnUrl, SaveOptions, + SavedObjectFinder, } from './legacy_imports'; import { Query } from '../../../../../plugins/data/public'; import { FilterStateManager, IndexPattern } from '../../../data/public'; @@ -55,7 +56,6 @@ import { openAddPanelFlyout, } from '../../../embeddable_api/public/np_ready/public'; import { DashboardAppState, NavAction, ConfirmModalFn, SavedDashboardPanel } from './types'; -import { SavedObjectFinder } from '../../../../../plugins/kibana_react/public'; import { showOptionsPopover } from './top_nav/show_options_popover'; import { DashboardSaveModal } from './top_nav/save_modal'; @@ -729,8 +729,6 @@ export class DashboardAppController { getFactory: embeddables.getEmbeddableFactory, notifications, overlays, - savedObjects, - uiSettings, SavedObjectFinder, }); } diff --git a/src/legacy/core_plugins/kibana/public/dashboard/legacy_imports.ts b/src/legacy/core_plugins/kibana/public/dashboard/legacy_imports.ts index b224486b04ffa..f0f81e0a4876c 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/legacy_imports.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/legacy_imports.ts @@ -66,3 +66,4 @@ export { configureAppAngularModule } from 'ui/legacy_compat'; export { stateMonitorFactory, StateMonitor } from 'ui/state_management/state_monitor_factory'; export { ensureDefaultIndexPattern } from 'ui/legacy_compat'; export { IInjector } from 'ui/chrome'; +export { SavedObjectFinder } from 'ui/saved_objects/components/saved_object_finder'; diff --git a/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/add_panel_flyout.tsx b/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/add_panel_flyout.tsx index 3a2f1159daf62..96352f7d46d39 100644 --- a/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/add_panel_flyout.tsx +++ b/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/add_panel_flyout.tsx @@ -19,7 +19,7 @@ import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import React from 'react'; -import { CoreSetup, SavedObjectsStart, UiSettingsClientContract } from 'src/core/public'; +import { CoreSetup } from 'src/core/public'; import { EuiButton, @@ -44,8 +44,6 @@ interface Props { getFactory: GetEmbeddableFactory; getAllFactories: GetEmbeddableFactories; notifications: CoreSetup['notifications']; - savedObjects: SavedObjectsStart; - uiSettings: UiSettingsClientContract; SavedObjectFinder: React.ComponentType; } @@ -148,8 +146,6 @@ export class AddPanelFlyout extends React.Component { noItemsMessage={i18n.translate('embeddableApi.addPanel.noMatchingObjectsMessage', { defaultMessage: 'No matching objects found.', })} - savedObjects={this.props.savedObjects} - uiSettings={this.props.uiSettings} /> ); diff --git a/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/open_add_panel_flyout.tsx b/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/open_add_panel_flyout.tsx index 550f0c61d77c5..bfa4f6e31d84e 100644 --- a/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/open_add_panel_flyout.tsx +++ b/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/open_add_panel_flyout.tsx @@ -17,7 +17,7 @@ * under the License. */ import React from 'react'; -import { NotificationsStart, SavedObjectsStart, UiSettingsClientContract } from 'src/core/public'; +import { NotificationsStart } from 'src/core/public'; import { KibanaReactOverlays } from 'src/plugins/kibana_react/public'; import { IContainer } from '../../../../containers'; import { AddPanelFlyout } from './add_panel_flyout'; @@ -29,8 +29,6 @@ export async function openAddPanelFlyout(options: { getAllFactories: GetEmbeddableFactories; overlays: KibanaReactOverlays; notifications: NotificationsStart; - savedObjects: SavedObjectsStart; - uiSettings: UiSettingsClientContract; SavedObjectFinder: React.ComponentType; }) { const { @@ -39,8 +37,6 @@ export async function openAddPanelFlyout(options: { getAllFactories, overlays, notifications, - savedObjects, - uiSettings, SavedObjectFinder, } = options; const flyoutSession = overlays.openFlyout( @@ -54,8 +50,6 @@ export async function openAddPanelFlyout(options: { getFactory={getFactory} getAllFactories={getAllFactories} notifications={notifications} - savedObjects={savedObjects} - uiSettings={uiSettings} SavedObjectFinder={SavedObjectFinder} />, { From 8d41818c565fa6eb6f418335761b4ebf3b5b367d Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Wed, 13 Nov 2019 17:47:38 +0100 Subject: [PATCH 108/132] fix types --- .../panel_header/panel_actions/add_panel/add_panel_flyout.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/add_panel_flyout.tsx b/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/add_panel_flyout.tsx index 96352f7d46d39..4f2ae7ab19bcb 100644 --- a/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/add_panel_flyout.tsx +++ b/src/plugins/embeddable/public/lib/panel/panel_header/panel_actions/add_panel/add_panel_flyout.tsx @@ -36,7 +36,6 @@ import { import { IContainer } from '../../../../containers'; import { EmbeddableFactoryNotFoundError } from '../../../../errors'; import { GetEmbeddableFactories, GetEmbeddableFactory } from '../../../../types'; -import { SavedObjectFinderProps } from '../../../../../../../kibana_react/public'; interface Props { onClose: () => void; @@ -44,7 +43,7 @@ interface Props { getFactory: GetEmbeddableFactory; getAllFactories: GetEmbeddableFactories; notifications: CoreSetup['notifications']; - SavedObjectFinder: React.ComponentType; + SavedObjectFinder: React.ComponentType; } interface State { From 1d8db9e47d80c9cb56462e91d382534447938e45 Mon Sep 17 00:00:00 2001 From: maryia-lapata Date: Tue, 26 Nov 2019 15:01:28 +0300 Subject: [PATCH 109/132] Fix merge conflicts --- .../kibana/public/dashboard/dashboard_app.tsx | 7 - .../public/dashboard/dashboard_state.test.ts | 6 +- .../dashboard/top_nav/save_modal.test.js | 2 +- .../cockroachdb_metrics/screenshot.png | Bin 0 -> 233000 bytes .../tutorial_resources/logos/cockroachdb.svg | 666 ++++++++++++++++++ .../ui/public/vis/vis_filters/vis_filters.js | 36 +- 6 files changed, 678 insertions(+), 39 deletions(-) create mode 100644 src/legacy/core_plugins/kibana/public/home/tutorial_resources/cockroachdb_metrics/screenshot.png create mode 100644 src/legacy/core_plugins/kibana/public/home/tutorial_resources/logos/cockroachdb.svg diff --git a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app.tsx b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app.tsx index ffc21c7fe98a9..0ce8f2ef59fc0 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app.tsx +++ b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app.tsx @@ -28,13 +28,6 @@ import { KbnUrl, } from './legacy_imports'; -import { - AppStateClass as TAppStateClass, - AppState as TAppState, - IInjector, - KbnUrl, -} from './legacy_imports'; - import { ViewMode } from '../../../embeddable_api/public/np_ready/public'; import { SavedObjectDashboard } from './saved_dashboard/saved_dashboard'; import { DashboardAppState, SavedDashboardPanel, ConfirmModalFn } from './types'; diff --git a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_state.test.ts b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_state.test.ts index e35890a240355..c236ac7843c03 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_state.test.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_state.test.ts @@ -30,10 +30,6 @@ jest.mock('ui/state_management/state', () => ({ State: {}, })); -jest.mock('ui/state_management/state', () => ({ - State: {}, -})); - describe('DashboardState', function() { let dashboardState: DashboardStateManager; const savedDashboard = getSavedDashboardMock(); @@ -46,7 +42,7 @@ describe('DashboardState', function() { setTime: (time: InputTimeRange) => { mockTime = time as TimeRange; }, - } as Timefilter; + } as TimefilterContract; function initDashboardState() { dashboardState = new DashboardStateManager({ diff --git a/src/legacy/core_plugins/kibana/public/dashboard/top_nav/save_modal.test.js b/src/legacy/core_plugins/kibana/public/dashboard/top_nav/save_modal.test.js index b31f523e30a7c..aa7e219d75963 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/top_nav/save_modal.test.js +++ b/src/legacy/core_plugins/kibana/public/dashboard/top_nav/save_modal.test.js @@ -29,7 +29,7 @@ jest.mock('ui/new_platform'); import { DashboardSaveModal } from './save_modal'; -test.skip('renders DashboardSaveModal', () => { +test('renders DashboardSaveModal', () => { const component = shallowWithI18nProvider( {}} diff --git a/src/legacy/core_plugins/kibana/public/home/tutorial_resources/cockroachdb_metrics/screenshot.png b/src/legacy/core_plugins/kibana/public/home/tutorial_resources/cockroachdb_metrics/screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..4b3020d91d57da293c2bd62dae6f5265ac726251 GIT binary patch literal 233000 zcmdRVbyQp3*Jda!E~ON=7AUmE-JwFEcqzqQg9LYPi+h1mBmr8q#UVg&4^AlVE-CI3 zATXit`0UuCEk6;9;5{o<%t3ZyL8ZI&K<{R&JiAE|vhGgQLAAm#evprKN+b zwWHe}cC$19@CcwF`|^VqWP2{qVAQhd=B8`k?j5x(S;)h+r?nP4+#3$2-@fOT=_jO8 zl;{sDH1}<0kb59PtICIea1|L^o>^}>H2=x}rkm$3{WEv5?ybXfdqvx6RmIQu!#|1( zhzcyooIh@UiG_RY-8`2EHk`PuE177(-5Kk%D}l9YnR_z-ua^8(UY|h~=sz(2vGuoJ zeK@AQJrfXAl(y4Q14f-9-br)ZK3wAT7zK)2E*jYY)%@PLOQh-~tm5!S7SntpM>YVq zi4zwF)zWeHkiu#yBUI7tFg1vfRVbqhZbtJ@RB5_sQfDOQ%kC$OIZb(ySVYum+oDdw ziVphGAO3j*_XNtX`iH1~;HQy|j$NmB@3r8%O2i&vO{Y)(PGDu%>U$=P{WZ37O>M0y z@5d1vYxVD%|MYEC_eB-mE^IsyUML+e<#WY~jeE@AvVcqKDMA&-b31@`g{udyB}M+P zpOWq2i!Wm2d8bft9*$ds`Mo6xoeuoTS&M`AX739*WU+p zY`lfEX2ftvkL?qFk8Awo7H1oI&2lR0Yl*}0B^}LlRzHp+9l3b z9Vq<+%CS}P8r0!kAYL(6{Ya}=J+`O+iPB#fX)sf@MTnJ2FjT2E*}Hu$I@Be@w2d9T zKH%IYBTZ=gMvLi0`-9tub39!q(?ua9yf1cSz7F2v`gUPRG}B#RbiG29p@ih`h&isC zc2JzxbA4TbzbgRdMEdHR~y0YODzgJG=?Ytj$(`%#a=YJ&;$ADN4n|l zvYAVOywuiQcK;&n|8m7|Jlmt|<(#geYWmRMt!XsKzB;+u)&2K9@`Am<6Keh2$^X-+ z-Z@vRByBGov+*>X^D}M#a&zbPkIVgHU)rqX5K)MI`&8|#r#BBUpBt{+C8G66eCAhj z&I5xBYks@h-Gt8B)Rg>SPgQqqFXTj|7iTFQ`%ZY$+%JJtVV;mt_RM8Cnkx$Wt({Ky z>S!h$j}Bf|MwfDYBq%KkvUhv)NG5yrU&MZ&GdX8^K zIbWqDnsFRfQ)hW}eQ);j=JJK1f9AuyrScy(Z{2mE`${RA}dGT-^N4~7e)qshAT zp4i!K4knxC;HgYBCzLH%;Q2P?deN*>B5_U9y`pmzS2O`3veS2Xa@=}A<8om&*%{`f zq9ZyXJ80FD_lh&dEajpbdc}Vc79APz4K2u!{6WT{O*sIyY^18*BXOq8Tm@LvV65N3 zp|lk7b-btF&3(^&))i!QNo5qU!ZPoFL1I=CHvDRF)GEU$BW}#`!64|8PG7j8>zfqQ z(N$%u4x!O{YE=l8TQXgjnCbnecocl{;26lJtPNAH7DDIh4 zZT^dEnS(>6L1#y5ol^(XIEp(f54<0B=*WaiP^kh%g-=1YT3!xn{*ZfeY_kGk)Xgj) zFUe0&rp7(y+fg^Vlg+0Tr(v>W0@H6z=buyN?AbS$7GI)zF6}$8R@SAugI~0G5ub5- zr_{6v&$r*}w3$M25)Q-OiTaTYUM-n+#!M%mpy2Xtmev*-@>zV};3*{`NBl zmHxqe@|q0_VfSjvX)OnjNs`KVWVMJxoTk{fTQ$YAJe1Vh=}ZchywKhC-oU49Jfh0t zacVGDCK!|RuWIQ;w@9vrZ!9--?;*T?9Q5;P%z^PhtH_s0j{T$OQp-n91_BI5jp)jc1Q* z&b%0{ox+o(mdT}og0G+2sqZ}0(lWD`2GS`CH@>Gr8hd#!S&MqGJnn;itg1{uD@!$x z8=ZrtJe=2|3^^-Te>i{q(3gbj-GdPiAE@Q9i<1j&oI*I!pChXos53$TNLB+LnHes z!_CvFbZY%+t&&UFv^gwowKj&0(}rC>fK%@uyd8&fmpRX!N0yN($^Ifd(x|&n0;q(i z{{TDa%4lOgQpuU`+?C5FD-%}m!fp?ppwO0g-EsO}_EjV4olnmFvUqeMUMRX?=%aZq zaXrn|t2;IscH^-}%&bKY`k-znxy#m!VMmE7La**%2=LV5zAsoz$ktiNF)W zt2XKN_{Ra~Sh*p&El!FgOg~0NAw%x2bl7&664}RZZJ?t02j>+wfNiy%bE$I_YRT}D zF#5#`cv{a`SO4s2=1_RW`hA4`iIjl>r1Ph|LL7%hPpGEkS1185-)Tm^m-m$0EAb}x zTRXK=_W@;KY3eVneMTRI)sDZhy0|ypJD|vw@)4_ewf1*FEJ*V|C;Xsax8>4@QUCN& zHb-;b_k$sQIo>--Nl8^eeN8Qo>q6t=r&0zwBG-7AgjnW}u&*9DxjkBZbDZD~LR@?q z&}FJv3UUX%jjJge0x3|_v}W_+YwQIuD3Y2 zVX9#i|r=Wpg-9s{T!96cJ^ zAWcLMLxXkLm$dgEmpY2J?LT2Ee^RuA3hlUxPB~Vas=SgxS=z9UZ;MO?Mf}NBW625P z`4ysE6W_uPgKjiC05_+%@R#r;T$}Eh-yK?s<~5;>p|9>}$1y7ugt@?T~z{e9;cl%V>eLJ&l$Ssn`gQ_g&5~ z>kdAhZ%?Lj&%}kf%iL@=M;u&cjfn#8paFJ_eT?2TD+z(fJk^D*>XphJzi%7O)1Ubc zUs84wuj@Q{@yK*wu#)1|EqReFm>Ja+X;T)y zUINO-g|gf;eorGBy;SVyKcH8WUS&5i);Ry9zD(EDtK02cbB5fk&Ls4dtAZnp{LGC_ zO^&YqlySP2r+9ua$J@TE)oItCGakR4V+y)NAuLqdW^4h;lgM1og=^qT95H5rIs`NF8yjkiXF115uP^P`bHRxQz;My0rouDFOWeeBSY=fBt0A>ftYLx{Cf%R)T*c&-t zZ2FD=Y(Bd6pjWe=W!`#M@eZRZU9=d>^l$49`HlGNcgHI|kBmkeCoXOB-ag;4R_UsB zd-1n*-1`8z6_0CLdlC;04mNt6{zAar9YM}MS#FN&QJ?+^8}|VNLri`? zJ75Opm|s?QpEPdztIz09^{YSfGIzu?x2iqo;HS49HYSG2hEJtSc=Fo}v*M7@zxDPO z)2{|CHC^svo6J<(&w8!I5YY)o2jP+?fWdvEzchUL>2G03Kwn^0 z1&-{kuXmQ2wgadItcWuGPicgNgoq``FpJ7@ulL;ZaK~e2pp^+|D17~gKYv8w!-t1W zO-%qIM#&U%c2&XSOlkkLYxE_IdSc1|*0@zrSjbbLR$GaQ9q#o4b9rh$pg?hyKrBTi zrKtvQ0Sq{q*T?xU?0<|eEHp3C2DN_dvB<*A1q0cs+gpsB`%kaEJASZ1C@CpPOYmJa zKe5sGjNfB>GW2?L)7Hhsgipo>YsZk!Wy%2e~A@G6|3AML1EMu~ZH)5!2{3M<)e%@UHAvKL=wXAQ2d zuJDM7?XAJW_OwAb#BF_jQG+r!kYat<13J2yjoZa>SC@*dA()wyc`#vLau_2AVtU6z zHC-J3{bdjUkAx&I@aFo%hYu$FsiN+iAKI%hvo9zpP?hO;5eRS}7&7m2C-o)sXKaj< zPMptWmnNm)kX?PXJ>Lk10ta2TiebVwr3;#b`$0@sA4L}Y$iI)&I`~eAr6Zy??P9LM zS<`SrwQ4K`d1j19$E-Pp z-%?R6i`x0Cqnfhi2O$jn7I*?c1gMJV(WCDJe`T0VTOd`rKU1c8;QGs# z`>$TT(%Z4)m~aw(10N{+`x>2f`W)i zdUnhdgM?e^p>F){kL6qDhVcC2;&5Ew@t}u2vqt=@mTn(`PP&^&V&>$%7SBDd<*s;* zw8r&{HUp9G*0_N>yx^WhvlKsLevZ?xo}%mk%X&myy&c&g=$QkJvL;p`Q@ZbU8bqta zX6b1mQ{+$GO+?3b`&Me(n^Vq(w|G`bFQN7XHu7 zbR7qQ&!$_M64oC`JPVD04Xn3T64O_Dm>Epr1UOH)cz8OaR>bxyra)WEoZ6wwCkp*3 zeA#e2XRS9gL7d(etofEM|$I(P+tJ_R3p1+c??u`_^9cRu3T(-_uZTNv^-3 zW}fFRL@HAsg%}0a2YL4u`kg05LerO+$kfctmQmVnPv^Z>_V(sF@d$CXEEDD$JoMw` zJC`+Ab=xA;lvy87GaX&Hy`KPYaf`MTj1pBuy-tn)^5WIqRbXDiyjhl#(m!Zz^aP)# zzgCcJ-10Hvdb>FG`EJI%yObR%*XHb^GokkxI*GH^Uv|VJn@aBXCnmkr9=_EyHEVIV zXpFk6-)gC!SGi@;+0#}2k7?6+Uba}A%g?9b5>~O67>|&=kHI`rY}CtOjC+2Dyz?g6 zO+cHbRnUquZq{L?{frdG5U5MpH-EaOtso-u?4Cwd5QRcdQ8%=z*(2;0GbXKg6)mHC zFUx=Buys5De^Go7x=-P)%SCREu>!Ts$J2h*o4jGY%qiXzIyiK#)&%B0hwlSCec!b| z(K^z~@U|9CWLIH7y?&^*SLonga9SdU@y{)^W{)~I4E1x&ZxZrL>MoSg>O3>@F>nN zN@gfRp+7v9O;|dq<1KIL;4KP)cX1FwuIxX(vKVP+$o1o$a9&BEh_m}ci(suDnh{f)Woh7d&YCP;b zllKr*rzS7qLbjg~oRj<%{|XOVh_-BVkKsnGE5+j8eA!Y)f?suosGkm)7Nn`+z7U)0 z(>C`tc|39_%{?`5|B{OiW%yP5($2HSO1tyabRC<%%!{0nX8QL-Mw(dI8UVMSJ4z9w zX$*?A*P(fMc}B^Dk(I=&X@z#4eQ9#M0ke3~fblz8tj&s=SMMvTOIKp<1)cL+KB*lb z%-b;pa+$n2B>XyH&>Ok~_F}QQzqNVM$zHndndq1Dr6R2eXij7B4KBb@n2NfRtM1$ z>3`3ncD;B1Vc&AJ+wvBBiRj1LJD)#)PUCS=_$7*AMTZHgi9#K~c0hG2uzzN9$=>p| zu#2&A*iK39tc7jvjL+AxXFMj2fzl&-TaInX_(xXe7k{*`SX~G-7`-TF43EH+n)YKK z=oEO(jX3>|ArzXooDdb@VYC~kPx+(B(WI_yL0Mjq0|CmOclSwVnmd|n|U5z zKUvW{CAeb<3uMvv147SF$*L9@!DZpvMvy@-bGrjC(;Ud_b+e*#{s48_IF)_-oi+}G zd)}$KDQK36xD&^d$mJr#BO_5o~>?I_T{mzW|r7CpK9#VD+SKhtp!csnlV-rKPc^X>4IHz7QGRJ{$i{*;;#?@?9{F^I zt<%tsPwH-{gAu)D@kZfHu0EhS0%e{Xjb*9~7D-?9zgfI*LMwh`_EG2n_nJQUFA(Sc_4kGhSgG)6?120H1?#ZF=0r=^KF z`@~sMQPJHi0Q1>z&CN0=zSZa7F@ebx6i7}(GqvNoAkG}YA5{x~{g5q8vcH%mf!jrk zuHIN*WopNUYcPSU&ur=_Q=8ucU z3~glpsIF0HCaONW+&@huUf~yOd8E)(s>K6YFSiE~3h{qFU{Xr)9BQQqAhsE54mu_Y zl}we;Q2o63u2WVOdG@Tls_Q;Ai;aa$iXLF!{4JDyjJ~YWD*Dik@e-ZTbO1FXir=MgB1f ze!BMkKx}`u?`6`dWVBXajP!vw2@uTrl9uOFQh*?R&o->TVEn zYf2a11~YG&va@cjMEJ2KCPqYzl;hma?VrZ|{cpH)dl%CPf8JXEf7|jMDWCQ2{|k$8 zUoM6g9v5(&ok1l3pXFhwtUu+;MRN|wL^bKh+}tF_#>P$O%Y+^~)9{W6a;%>vC2W}B z@#Dvj83ekv@!bEZ1$g8y>i?h82jlQUV zW{mlLY%8S&IoaPgnJhEK5D6GG8iPYZgpRefwG$H)+w~nA;orFQ%cek56p`U?OW&`E zb|q#?g$<~$Y`&A7w&3D0aUY&j4lttvEHuKxndDV%PSg4bT<1P!Fhm9C3pO3hWKbBK^j5 z*yHarsrmUa|0+DR=J7=gh66jJX;h39wg%(MQvO@`mPNQ_NRYhx$)vWn7Lz+5qotkx zEo?t6n3a`Pv)6QqDakO|8ZS;}en>`^>}!TZ8|)kjHBZs(ZN6LOwkydHNef)?OGOarSh3Al@9PI}4$CtpdqMaS%?A(Pks?K+0lyG$D?t;yO78YQ0)e z7_`tay~^t$Qw|x>+!@4QD^k+!ZL-tQsrM(R`=;8_AQ__Ux?)n$W0ck@KH@h&``&Y4 zczptr>FBCQz=Si1T;l>yUJUyw@8y?+)cg}b?G5Zq(5mbz2|Xx>`DHbBA!TCIe01P3 z(Xx5_V@Hap5h$3g;Z{25T6EtBuWfXR3ItLD*9Y4UkIhAuxJ|20?xeyRdc%x=iTavG z>Sm@P(y*^(*;o^JS;8@P>HUmy2G@rVI*$a?I&xSOHjNI*uB+xKye&A5-ef$1oOj4R z$U^g#s+4bL`I5BCgEhfXKr3F+@I&VXykU zL>A7jXvpwcFl#XTY-3}vW1|QQvRJk?2vz;P7f%SppnS4NrEcmfXw%1CQFc~h^2%T0 zeDJ0wq~TIp2bOamJo0nyPYBsZ+X~~E$O(U?+8#WeLcZA4fwolB6u?MLeJzXIV`D$; zkrF#cL^l0%g5}t+YIfb5!WO#YJn(uhlOs(c^T!l ztfPhQUu*F0nM9P~Dsj~W&O(2`Gl#CnPx{V#FULr)wanPKIMRPvuifJYmRBX4 z5l*E~>%tnNq~qrE*<})cSn^p=UE`Aa-Dt?&-GJ^z?_NuCpJjOsBM#X@rhF)baTO%` zbYp*;(sstn6;%ZGg5=x{!JyYGic!&aR^# z*z>dFrL&Rqs|Xe4qRXm~NbIRqhJ7|c;f9#2iqFdgEe)YL=YIW^j;~nA-zr8v8KuJ` zU=AlBu~<{5fB4Dsjj%3?^5@e|1FWTMs=E2yA#?atLBVquSJ#b+QYOb{v@k$gTKbmr z-q`5D7?)d8S*wsjMy8ok!tRYMx3=*&$M$7v)uD|C+F{JezqYfgNwLrk4TV64nQni7 zhU)rnE*f@8a$D+J`3)aWu5*>Q${uX=1$#ri9a~G!d$s`51~3z`kvp_J+A39icT43| zf5ueD~SPplweZxU8SXXz8Ktzy=!~mj^jTEsf>V-@_Dy@qCBY0?0w^r zZen>x^zl3XVRL*JcR#6B%w)GG%dzX{5G|juBVuDaF)qo+M0T}=^z@i9TlmC8GA6sQ zva&K_YHUs=ArC}i;?0ey?aR~ZDI_mb(>}hxqsjW{^s55rS&L3OW4HnF%0b&(v9Nx) zPEZha^V66EfUj`H6BA)aWd?N#t2V!7d4y$OK#h&Xjvv&ql%v18AjUhF*gK~)KhY8E zCOkOQ(WYuu#aj06QZRWsFZSLtC%=GpeBJY}{W(r}=`YRvq<`5BEA-=Bv80fZkxUKT z-28-Y8PZ~muh4&GV0~=8$>NO)*Jnu(&ctu>o88@?y4-VF-k!euhR(|rsk6==W1S0y zY*#)UzsIwjlv;cuVm`p`KO}x9L12nZ=uV_Z`SQ@i40_ZZHTkif=lh57KHKbr4MQzE zAx$Hr9oBY{qvO+J&SxBpjg^9o0bDU6Ox7MbFMREA-O6qm8O!;3KyW9 zW@g2RqA2e$3Y`J&ct~XW9pXDXJKyrVa4O?6y9sVKRR~^7wf#)ZK_?xJFo+tLl#x|L zB>9&{zQ^RmBpfj`g~Ft^FxoGw8yFMg%E!|ZI)HPxt3q^*n#0II7DNy%H$TAA1I6Qd zo^QLLxLa#)nP}6QJY|AGdWXQ;e}3<5qxsjK0vT%_@SOpq>q0Y?(};+MMnDhMck|9H z5tG~(jJ@(2CbckUgS>0Nk5dDxfOb@;zW;s{DL}5f$M7Mu*J#lvx$}wlqm=OQ(Tl8Y zyOvMs@0uiLd`Y3nOHV=#S-*Yv5@^Io~DQY*?%&gixE5OUk@*op5bqy26)Xe}HQbsBGrDWh{c zKbH3gJo;P#@r%XQA=D{1A>%lmnl}E2w&o!ueu8J+u-Eb)%WH&ls?kq0T@~YZ?4J~) z`#!e@;kuq~>wfn@7J$L}?XC|oKFn(c1$<04Y3EePqukZaBLL$BHT9zFwq~|nFJXoC z?!|>{{m6}EE$B}6TE9@qipU_wZFBtgjvzYD$YzH zCWa@+b!5mc&4reJ=X+N|=D1zO@7(_we)C_2GkAtn@dt`AO12auCXf_e1gegqrUWFd!J2liY+V(ah%OCJ|DxoqyKI0sM`4r}Np zCaGvyi5Fr*bn&`V@3xm@vSF_Rte(@@jVGq2A!wl@@drch^=1-!gF?67m;cawg1evQ zfrwm6=fTp1QaBy@YvJn$@qOLN?6Bn}KKy+00+4Z?IDM^h$c4`rnWN_k9^Vp;jV|w9 zhQBnjP^Omsxd*?_U+CPO(9-bm)$4;=^i}AbULhYIh}}q^z(d!apY0tcE6DeF2eKGQ zMg3W{qOp0w+3a5cIbI`7P4Ii*_nJD;mwynB`*N%#)LAKE5FXWfa??PLOMe)MxJw7>@mBBJfml+itxe}zJjQXdX7r(rH4?Nl z`6%*6QHH((&7whrH;mW8a%>41)ZT9-?{kWXP|3*1yn6lm)3jMJG^tdRmMHW;8y48u zhkhJB_v>8tSK;fLO*&Zku;%qM{eR$b&Sth zVgvo}jqauhAp)t}zbx+RTldd#MjP}ddcxm@^0Z>=i4}Tvc-vT%_N;XE_{*QI-M=q) z-0(*B9>D&gb!ojL_m7^Qj$Lrybnoci%&D5Tsf!T9@e% zrsv-}+C77;rbn7Yg7#T$E?-=cTKgBH+l;zMmtpeEuz-b#W5dg)=gby=0FqKt&Yqsx zO^j2CsO{akdNJ=qR@l6c-D|94OvZk7b(Pn=GrI01z2RhBf7W-e@q@NDY)bovx4fhu zqxfG2&Ko|RD^*fEyIR)S8MD}~jkzZ?*%EcRYd&;tPbFw{HYZef)U+k=mE$!d%|YsU zercVuu*9fs#^9z-ke0#Pkv~L~r?0IDQwp9j93;AL_K`0*AfuHU0xuy^kd+lL`^Oc% zO_nC6|H8&4>2QD@?ZB!?Jte*NAH5DGY8@GY&NX#%d&D9TJeX8-c0G@V{Iy8)3`Of; z2`Z|v9pe_zz^(eP$z((8#J^k8Wz6#Js- z*VQ+t8#3-_Wegy>m;woxJ}y{|I1yPb(daJ3J-hQZbN$vhZyCg4ORu$!NOMTtI^Vd1 zSta$3OoYGXji)XJ&_0${HGSZ*xEl#m+D$imS0sRXexX0+an$*~F^!)~(L>(*!+UfN z^cfwUfl^)L<`klQyjg*dMTpQ8bj<;=#a*f^9WDC-A+x+7ET-!%-D<|djtGgMn6O&= z<95)w&oliv-OcGHtM#Av{4A~s*TxlbXwU|}0N^Q!K1lf4#03$Z3wFYSnc>b&k&2tFuJRxAhGq;%jp)?X;6F zA?t(e{A>qRp&5nDNAGA~tAJ}eJplmWE}MDrgizyVuHZ|OJKsA4LdWEP02nIDPz z2gfzp3gGm|Um;vB>aw@g!*KGLC}$1Ltb5TX46&Av0UH!6fEf+xB;bao^sb>PpDq-0 z-DO2f;dppA&9Bakbe$uFGhTRVyao@ocXWRm6s|?|4TL>vK4!De*hTACV*2P;*3wAg zpZex0fLj+;zbpuRn7U598Hig>`URgm@R0S12D{?EY*Gm{HlglSD2#-c3whJ`PyaIl+R2 z78+JJ^>Cz+@2VXvvI}^vjh}Ewz0Q#eZJ)wI5S5le2*4w9~ z$&^{h;W}y&4`@D%Q<vSm>^6XfzMMQySKoK1~jzPj`ww8@kj%drpg=1`JG+b34v zNYn*=Rbz4-K6PHozhc{)P??r~b``yvuAnkq%`}J<@m86+Hab?LL|#`Y%mAO@zD1~j zhEKhxOcPuKBe3OP&rn$!CqlsC1J2i2A90_@9?Bm*;5ENNRvn&|c^59jKFb)ufy2>M@= zfyb>h^+~IUB}5`$Ra7I6f3)I;Exownlk<;l~5poYpp+@RLvmz{gz zO`Gf>iL~Ib8AS(@Yg1U))r{dFuTP5zy~GJ0Dd?Ju`c8{?bdNSV<#1DFwiy-3OMRzR z_w9#$(E?6O$ublT@7+6HwU&RXAXQq!+$fG4rcg8i9SBb$=HI;VwoCBY{VR#7pPO-dh-K7h6E)a8c)vwBcnwa9}vSe-rOdoNxQe z>3zEv9@Jee_hB#Ev9tV{Vp4JW&R@ARA#RNKHh#E>X{IdBRxFKGE z@IHQPO1YTNveGSnD1Z>(7M*2m9T))9%Jbo>HMWX{Vdr1IBEn&^rGG+S;>Qp+@@Y!Ms$de_Fd`;@<$Zk z4BVI%&W4Tc->3++`KX2J_uk=WutP4ZWqs`cfo3o~o!QP^TS z&)VM8A(Fv9BLhGRbX5a3JP_i*Am67r-z(PDwz;PibNwl6;tDR6uls>dz`rCtMQXxK z0f_0F-jd~;>|Q>?+0sq`q`J8Su^jV30{Ho|iW&eua+l_s`Z&jnxmk<`%xXiS=Th6D z^(|;00MYJV1@w=90fP+I*|h5y#N@(b*dHm?ab=t{ux(&9{oc{pvbzo>u@!Aa)IxJ9 zzshLbMFSM9ksmVx4l={Mq&(W~RS2MfCZm@SshJCeu4jvyRMpaw~D#Hz|dFEH(zy7g?-vd$_VEI{oplOD)%ss?nW5JUTa}*O$+> z?9Y8jpY(T4{?@XI`O%y#qkXnF@!KPB-9vJ=v9_*R?KOC9){O{9r1MzjX155Lw-TDZ zhk^vQqz~{h%+%U;o)g(y7nZAaZT?*JmDU!j6oYuaw~v)yvSWR>~88(}+-6b6h?(A^R&Rb2A$9Ydf1#Klh)W(nnB!Uf+jW2HSNUszU ztAmpCY4}VO`-GdH5!udao#3-5!}x2|XFm1D(JXn=4PaawG30*UdB4Kv3sTU~O?xZ3 z_f8*0G~<h-f5&U9tvKZAPxk8ho;O*)v^f{OwOE}vFg z*;vOo73KP|LZX&jZ8LMhSlCS&pZq*e?QqWM#)#wV=9eOQ#3rIJu0^{)dlg7vZnk2e zbgezL^LmkBANwv59nN^v`0%}>s5OD5?K#f9rIRM zAGsX0_Vwi}fb)HCC?$T(5oMhF#({=}e;SXha1!Bo;+zrYKg0fnF3qidjMm?kB{{YC zX4}$~7O4aYR9!(r!FmB1?JBTJYeK#%&Poxk{&Bb&2qhrU=BS9AR)$r(81}V+qzYN? zyN(@bML~@-t{#A)YmK&C>#m4Bb(cI~pw?{}ea=~#7-BgzELP|~*~ZH8i!!SsWP!8i zQJaa=);DwF=k{=7IDaDY{;g}(8s3!s=NM~tw#37oTB^6*{%O*92A3DjoZ|io_4GlA zi*AJgMrcEdPZDI#l1M{guswqj zyL%*5H!1CJr7AwAQdQf^?d8`g>Q`WEi!%PPlbKAfS*0WhN1aRqEbEswT&(tV$`pk z=_HK$IE_t@|IF^XiL`%VyotKI;j5~-=34YeGnS&U_gBFcf^-&|Aa-;ec)BYkU2vg# zb+ZK#fUyeH8nZpRj}hLghn!hDFF(AE(tA16R;M){xn-;0I%mKAgp0fJ1y3)G5Iv;_ zvo8Ft+3>xZOz-aab9mOzk65+AfF9OWX@MSyfGYxp=6jsVnn>6~)Dt{-bkGv!ojHi| zlXkV@KkD(7Mjq)7%8Smto{C~b&ZXxzxSuWvD5a#xq}reW6rl5YAJy5KNUr+%Mbz@E z(||qaY9HTH^35rOicoZN^8Qtc$Yg~TGI0cnpSUAWZ*f5zOnmX*o`Kk#In zH0JQ^RK4VM%)xkvczsru%H(W1sc3Gg)v)qw?i&YM&mzmU>A6PI@AqEb53bqA-BM@e zNE_lGH6#l@A38Pl$?QCqCY~0H28O*uRp)!}kjF!IGWj!fYQHgL+;Pya`6<{X)-3xYrtkl%~p&0^2eTS%K_ozF(+} z^ae>O-E__14($#wQLoY-4`4OIT_6FC}1WgbgTeObc zj>~akX6sMgNOh>-)lC(ee(+R&j`G@liB?!zqo0A$G2eQLzv<|LY_z(4bBW6jSu&IA zKF2kCfcYQ2A`ggO?T+*H2?xv-9RqXPniwyulB8d#x58hTTSKO6Dm}f>dRFyOuPHs# z&mMom*pu*$XHuE-&6Gi+9BRw%JUImBgz4zo1v6TFCrd|lS4}J?R$;dKij0zqLcP*Y z?9DE~^0L=g^zldYVft^W?P6~Btxn=ys71`j0#_wiRqPpGiSdsK0EKJqA%8wILGN#^1f)X_?E6xIn55u zIoINr@@;Tnk|-oVeaK`Y3o-EbmMU%Z`l8q3Tk~~7Re62$8QB|EKV{47{L}~KZXT_b zuQpZbTW6~3<}a4g`UYW*y>V7EK8~=#Qyf=^&m272oQo#I{hv>N=cFb&-R*tK!Bk;{ z|M`(Y*{i%r=9!K(Xj{02a4{UTDV;&wWzL%+Rk+JlT@ej5o>ty)OkVQI3+|d9c`?^z z4yRix+?s0I(#+8DN|^AdS_>cb2hDyMge2s9AMt>_&0b2yp=~9MOneUH0^AGSw^47n%sfgP%axVGd zl{3{r_w*R2biKcdIn7$kqDQ^2=#P(CmwU)+1(HWE%%?~^lEt2;2u2)VkBTUFL7By7 zLaZtWAW7x|uGQ5iqOsKjDesGPM=N8arv!WpNEbpZxC?0Ct~{JIxoAG;dwc-Vuke&^`-d_z-wHr-y#c$r5Fpvj}9N%O?X|NfrlvP z2ZZL%Xzx~ij(MV%HD49&JrwJ?yt?U|yy%18@(WluSPwX{sSN2$p{kwuvEB}ZnMUDF z?Wh{0$(525W}UP<9z5Zh>IX+Dqk3}vG4;)?vA{*mYD=!AC2*%%7i9@>ma&=vP-dE+(Z0s`0itYN z9_eVj6Rp*EFAJhlC{i}a7nTb<`3jM;6XBrZ`5&ykRZtvZ*ELEK2oN+t@Zb{M-2w!c zA-KCc4DL>F7~CNc+}#Nf9ERZT7Tn!V^SPJ;p>D_a3e7c&A zhfH)*eB*}`5i}kQAk#3a*|DzOA?4!nI_T7Kz?jlfaMpXUUiju*^=uRn`M5>@_3PNW zIrtN%;)Ri}t4>aMjwDp~FJY2bz;{S3@szjh?|&~RDU&+|D$%Prh$4m-*WQ0Ot6Taq z154X1kw6`6sV2ky;ymhO(JGYIP`$1bY%#u-;Fg$aI?i~NHvdM`nBWqDvIkYfm}pN3 z@tEyHMjcOeF@9XBOK}NW<&pJIjx@phajjMj@4DBO$t33&&7J&R&+>KckW?5QcD?Nb z{u@GUoHub9pTx<^chBhkg@ILRW*rR2Ei&0Q``MuevhfzMEJ#1RCYKjC$sFBun>)MR zDjYg3Y5rZOvdtBD9^ayN>7svB!fklqb@|)z+dbHKPR^kOw&?)bG#PW zsoEkH)8O5d-0Tk;d8RhAar~e^QKYNi*oE-@7q8IrW_8)+d|;7=(TN>b9)o&!hHaoY zu_rOp4vP(9Cw;1LXJ0~g+u+KGmO7^=z02<`p}XrO0lctI5K-w51PEXDf&S|||$>~lZh z(S{q78%O_mV1vzN(;C~?O?MYi7LQ!sFWIkdWgUc$ z{L$UcHF8BKPE=X%te?sRo&S3VUF69TxUU!Y-`%yd*^966e(Fk04YOMK(D1mevJ!cF zbCKRn9ogc6C*)&EZrAb5?YhWyv8?wwQg>Vu`-|D+ki?vV?(d0dIvU!4zpRb$e*OA& zc+G8(gT?H;W0(YW!2Gg0+R^9pAIIe&15GO!1XNoNyggxqK$DuLR*%&>u$xGRS@R@f z-s^At37eXl0^p|2=}aFbXX0>c(Ks9P`!&~g>k0l$NG(m0EdTQ#5Ok(4!9eN05!>`9 zn>~1lID4y1?Sdp?>kw|#Vzi&u83a|m8;;!uFWQ4R9C`2ynJ|L>uyXoZ#PpwoZ3*0Z zRenA0WQrU=Th21=vZD$pb>}@)?n+RC2NgM{t~)UW4&8QmT$IHQMoh4j+q?Q)`HTEC z8)M`u)fAXbtoz+N@aWTAVpq$&BcwmIh3c(U&)O`tRWVCHwO{re8*fks%Q`K1*v(E$ zcn0!tjsF_UH8+MyU;& z7AIQN_(;T;lzg69S#D_c+5T4QxXntyv)ykwWYjc}2+~Hbwun5-`Xz-2Q$m9VSk{DN zM(Rf>c8*s+mGmN8Wz^x~DXf)Rg>ldDuLN7o;RQvsCTbR%cH;l;(j<;5b^4=0wMlmO zm#3Rles@+F!R(mJ%V-G(DGbJBr0g*XadGjsmMNx^<9@Qm?p9q?M+4_&WDM4~>f6!H z*ji zX(^wGYM%2ZWB)@MPsXJRtHAC=2NDwbLQwUv1!98hB9`@IN@xnR%N>l>^xVUU_&#>o zen*nE4o%F{{=1S}KTq3x8&5aQL8Qm~mJco+m$cfB@Zj^-NY2{u+h?=`ji=kd5e8eB zUEBhuFFR%t%WKP%J}YS9YTxTEQCwH*J}{KVrmK}1S_o@>Uioov(|&2xrM^?{R>d$O z)A~DOW#OQF%sVsvlyF451G3HERq&Z$&M#dGoCJP3PyL~S0z^e9&evEMgBQJ9mZ0YwH}17jq?s;nMr0rLjDKIMM|>)|n3ts@;=^w5t>v z|7g*85eOL!>~KNst&RC?vVK!_{+YndP(rIspGL_Hd#$hMI9bos_4`LLm_0U)j(eZ1 zxl`6(=~*OSdEu0BYR{j0u`WwH)_JlLl^KlFsQ$GGGvjUaFUU4ks~ayDmFtLK?=EGs=t)Q@*z+;YB7#V=1kBXVdp{%d@`( zp6FgZ`yE&+fX+8NL1x!tXm$N1kMM|iv@h3tYKG&>Z^C*x+Fa8kGA;w>53zCr3HGJni5Kk8Rdg=Rj)n2R%5Vl`^}e|_PT(*>Qa>-#mi+nZT^?jpvW9V zwB`O3;}{Dx^vxP>#fu3o;x{}1=2CE zBtkErD2d)bWI2<{fWv~yNDu-@B9(eu$a+~x=_s&4JllIp&7Jy1 zd>DETa!?{}d%v_ad?4`0IJs!Pi;au(uQYR&Se60WROR4F-H zc^TS0snmZA{=)Us>JMnzC>oijYt19Zg4Xw&vA#VVDc3iI0+L^|ziQ45WOE?Jmc@v3 zynF;M$fqfqeKzHSS4Qby_QhR>aCM>A&Of63 zrqB;SK^Q1il5 zCd?H;5CdYiqWjPxYB^#|!ys$h2{fr0uebjDDYKsWdsyWjsHl`XDoBXpTo*;QN0Qh+ zOA6vY5O=j>$aXiTum99o{pVB>x|u(*DwoxeQxRw&b;HpdsQNrjD7cECZ=_^6H*3>t zHI(&lYG`QCYu0_y9Ysqid5~%_{W;JeY=nez!J|d9L1py>Wmqw zwesl%>4Xm)Omtc$e+R?d+eJ3M=kupcMW@zsFoagoV2(8tg+ky>4?88%0mlkFLXrXB zDpr#_peho=JGJ}~IP_llzc@y?@w?wt)Vcc|Z)R0yHKThbyhjI&t=_9Vzk64~mDYn| zcRuWWf$;BhI{asgrCz4hYzdjFTtEwR_c^Qb-t4if7ijz| zK(@bm)iHBGnLKoJG|_nH4VlhG%G1)2*jwIr}?i(P#~Q;%VHd%74553){!_G&?mzY?5l@A4>SIEizD<%{zV{>Sr^(Z zulX#8GpGSnvfn6ys|d0~tA4wV*4#yJwT3ja@y}pXBU@EsK%siz`pvBG%*$bCeA|}P z7L{HA$Ip)MG)e&UI z?ol_{UOvGbRz9TUxEqY7!7&eXO)7qxuvfDOZw|l}}HfnX!afsT7bbWjW1n zH{DWP@zSI)lV}3Qa-cfnz~+J`3qi#DjDHel4}R6vC0I-ruo#HA(boYwTVOIqa4!at zVhpiR7F}fe@O`-9Ifbl_E1z*Y?)rvYW63>gf-aWYu%yuBLn=rl`!|} zA2(G=*j+1ry8oJhr?t%gv$G$YnU)C3T!JwL&3#RWg$vg-Ln!W1o8fP7xLd7lrK$JP zY&>Aysm-{s`C59#8#`rk%v!VgL}SL`wm2c;7t&wPifc();*yv-TsDsRc@Xvmy6Cm^~3%u6b&sx_`B zx`)?Ly^;qnK_Fy+R8>_Q!7E-DauqH6wBj*Dqq(A?Zh%j2-YZCl7B78`K@4C%hl_#< zC(Kf&AdT26i?s(X51sc1N*7s>EQ*w3Y%ENb*Nj^{zO5VJWY?iE13m#GQ4%VyAS!76@!%5K^c9NeGLSkr zYtoTg-@HFQfOa@Nj@nlDMDexu4(&JA?hv!3u6NW5xGXJtCqs=!myM`3TR_wN_`jCs zQ&i*)9e2EDj(b|O=%7r(j@Wf8oq{^ppcn|bWY+|)fnUb8PPzy{&FyyHC^To;LeNkj~Po_ z_GCNrqC&kUwF;~Et`#HA$bLuEvv2L`!9;g7@o(lFEI(WR9af*cyynyu91mbJS6oM1 zm;WGCellD9k1F9oBvKx+U$AUeL6p@j5(cNfSFlJ85zjuKN7A3}@y*Nk!0>_|>J<_k zzVff6M60Ds3o$Ls=i=x(beY-?#39{J-H`rdJ=rhDmThGM^ysSjpZS zeg|`~A6i{ME6_(L%eNY_XIjr30=6L*vQvNuo!Tzbw!V&qR_A*DYWLxH zB=_SK|6wwNl=oNokBb!4ojhCQJyb?1?@@rKCN%t4VZ_N~Muv)&sr<||!6?~vr!gLg znDOGHTdPz&ciEuF!}9pw%FVQ3g6z@Oc%|)h7Iy?hldrQ>A9?6)y)@-U_W4${!z{P1 zbrhQP|80F=)Rd}mj6BTuAIqqxZN9A&U^z?$V%*>O+Tjl{+77*+_#X~yVVblV)6&uw zVp9K;p*9$(*=@^C68^0=r!n|kR*GOrnwHl7kCgj}Cv17~ zW!2eXIW(l{IDq6EIi&XER4iJ=6bpEUZ1H{ljx~tQOsOmtD_^%4@r0;g+Z)eM$tUyW zEC`zj28^tYY?#y5g>xPh;=*MzWNQb6ozf*sR3vxeRb?^hOI>l)8@VS#LT?Bq13O zLGD4#`}M8E-=@%+(ofv<0(J;HHQO6C&J?>ox9eEP?bd>)VwP6z)}@aOTu~6oYEu@{ zqXqe0DlN7zBe&WQV?un)Nle(W z#5P(Uq16;Jyi&!bp59C3iIZuedST0r8CglR$c~1p*|E1};px~%OhF@E<$D>%gQ7}+ zbpSWAQl?5*YdUHObW9eNNLyOcaxK}5KAiD@cdEd<@^AfN7?UHK!MhE_534UvdV()^ zW1Ei69t>Q6jex?!D{W=LXSR?`qqpY$>z0xQwiK+0V40(Yh+-e_T54^S|KyR?;Pv(k z-SAog3s=JnKlx&B5@sW~`XUCaKs@`6!^}xcJ~QP(#4<*$$RXS%VKk>23n>S{iey@p zQyA~`=BRZ`8mOKBbw(rIK`9|BoR%vkY}Hul4wfnB8;B^CzKyC);qv7UT)}@s9pj;ksy;5>jlx5okcF^ zdHQkB*r?K()AZ#m`_%1TFHe^nlur+bM)@lXN2Tpu8R@4(^VQdgoJWj>aZVph8F!wy z!C!FqZ8NC>%k5uYq_7wPgTrK}YW|zl7ilfA%Q@-vZ#!OT+A{aU3gg~&69fdmWC*oh zW|APpU7!7nLc3U)Pl#p+44d3>yg4@+p{u&`_5fI|tChT^n~4tvH{a_VcaaW>U9DdB zKZW?Ob7dulRvj5gOy-SbprFw6985wlTS(_Le9PGprI7G;58Gsamv8o+&voI!MrDn8 zx4wr4bqwTRu3$EaSeu)oZjTlkDXQgZ2GfUamHq8980;aNHj0DqSfTe@-Vo`|;o+{* zce+e2t<~N>i+E+tsL}I0EaT=gOJJ?R7IEG$Gp9f_ zk4MZmQ)XCeg*OJ2WVHoWsn3_+^B&v16H2}77uEg)?ZM5pyHHxWy4m9}m{`Q6_kFg~ zXm;T;j5&R_vbNs(8NrCZZ;KERyA~i$7f$%hryxRR{=?PKV5=CdJ7n@S&uHD3Kt9yy z$+R^xRBr*=+S+(=d~o3IRuJlpmT+pRV*Q}eab%(U@GZjTmq-Tk*jd?YV0R{_`aG`? zVcmmM_E|wkF0F8O%i6iLONLr=PUQlw0xA=wY1-8^c6Ra3z0INJqA};Hq-*#SLz{~S z<7{(p%dg!C|8%A2ISRA(H2By2Tm-&#gRIB4*B0;bK!nYaXMtiSrzKY9_G#&~Q0Qz| zsgQf`20U@HJ60&~(3AZ<)d_o42k*ZA>*`UzYYytriqg6qvRrWnWm~NFHbI=7dovyC zy@5fbt^5ONAj^f13x{w$57T>Eqida(i;F2umUAsl{m&?-0AV!$SZgTGD&1Dc_AKiV zYDJjq>FL|3SzTU?_7j1t@dxaKB%E5?#u?B5Wso*erCqr;7&Qlneu-f*=nS5daYePt z%#U+VWy%oZ;o*55gR>7>a{#x=6!38ve*hu|R3(wzSHf;(gx3!_q`1$Gt2oh_N&(mO z$?1C$GPmP$L|I;@dYA5DMd)W4@p(CBzpp)bC3up-?mUn5w zg6_f87hi9bmzq=Tiq12aR!lt5IKcyg=NW05s(^od6278%siQ4J!$aWAHryd1D zSh4YmZd%~7MEPhJ2_=y7cle(xW(hKr{gmxbh*P}cuMxoVfTA?(X zgTda9q5rD|XkQjwBp7K%tZ0j@@e7nu-z`1|CcG>9f1^xW53-Hg6Vi3t;)r#UyQe7$ zXr3%S&bLo%s11Q;9@H;O)c_h6baC{E=62TZzg*6M{P&f|=6Fq+&?1(q#ea}#j9(^! zD+;B`$>!7MH_xm?0}v0(22Cto#;hRu3lxf2e7*t~a5XlpQVsSSw@ym2T>Pxb`LqIA zbbqpFnv2zpoL&Mo0!1mzf;l1iIbSaBw603cN5ta}y|br~0trhV4R;QM5ACJ}>^`{W zGfjWsOyUpon8n|@YGX1bpjKOL4ZT4(->tJzud5ZaWcin2UcO zk?$1qClk&0cAs5KNs!GXJl&<#P)!+SREZe%oNps8-c{BxSRwj0Spe*l&lM|nOl!Ig z{94+8)cf|t^qF7Jx(9XtB}S*bxF*%g_Q3*LqTbGD$(dw4$_gL|zn+wbe zUXRTUte6n(D#f^HhcLC7}mpxq5oFt$3ZK` zb~_CGEPVX6evf!d?SDs;8$Jikvvr&F>4*zYryt&|bQLv753llx%rsF7K+~cYB(6vf zRQVCn%GM7qCu|*vkFI8RJD_q-aPcWybHek-tSP*Rel^}Eu!Qj`%FJ~-m{2*3J5UP8 zzEzX8;mKQJN#(Sa0OvzW?#2Xzy9wiJ0FK>|l?-_&uyiJ5`CE!TDyHE`I9*Zp%~w;Z z3JW?npQEK?qxzQU7w0S0iinP(=vS(+!vn8~$mP)qZuM!}xB&}9^zR)8bozfbXI)6z zcM7YAv2f$he?zqanMrY0kx2k&2KHXY0d|NygoC)}-<|6EGw*`7*w?Wz;TWKfj7X4B zDoa{J$CxPDn{?1<^+jziQ&p8C{xhodf5<$8|(Kx4!Qy%^OWuM2!MJKS#~L1M5b+BU)W6 za;3M&oLs{l3rWbC0EeeS;BIXu=BBNtJLKBY5MJ)A13k*iST@OX^o7%{kEsdG-h#Hy z)XpN!+00otRAAZ#yS+vq7BFXkUTH5zby%`k!$;iTuZ9g)k>9VE)gd7$1b_idsaJUR zO(p+I1%S_{(Lic(T1@NXls&yxo@}HU(*$p4OJbY)3i7L-ko0nZXmZ_H&ca7Y9;h-| z$i_)&;#Y5Z=xs{wjHK$UC(}@o(C>5gM#by50M}E40Dt)woR7B-9G{-YC&uPLo;w}! zYz;B5m(Z&+pGV;xY%V`A@+1z{MQyL`>9K{-34Cv9k!F1epPgoKlFGm+ooaPmPC2Tb z@zdp!L?5eNZ=lV~>i;Lk3`3$|({LzV?`s@`I>i}IE(t|RY%bY>6v?n8FD&|?hJo>5 zU3U-M`e-NZqOCCTHUhONn`buCtnfohKQ?rm1xD*VrWtg5U5~*?-e=L_L77Mw%okHm zTTqlwRP!MfeDcB6Y}{hUx^ml(Mz1+NV8bx{dUf;yv^)-;FI#lC-@zx+;m+pG*aEd? z5-0r7sNJ1CB^;A3!Df}ibXq3CMRu%d38P8gy7AsYFXq-5oMXw`P`p{Vv7?wyldwIf zgb9+3;sQNGUbuFbT*f>b4)rV)OK}hOTUedGX~9pV#ya5K4et872NakxSrn6RydS=h zo_xx%NE?Xfx`$U8n+(;>t$R$?POHU3AM3ZrI@#TF>yd!<^6k&^qTH-4=xg(ddGUTM zM>~6PC+=%I&P9*8d2;hfLI^xlAZOGaga~uEP#>XGC~r_5)XEGDd8%kTAG#RNBr5jU zO|6Ad;eb5OH&tgeB1q~#nGS*MTl!&^mFf|Lj=XYSPNcTNIAmvI* zlD|1uk-0Q(+pQf=rbzsKe>)MQOY`Pb2yk+-KV)TPBf(i-uc$c&``ZEG_$fbfe((%d z=Oj?ohS37t!O)QO_3?7V@(NG$-o;&tkgp(6>zy5obXtffY?T>GOh4tTKdgfEs<0)P z*v!0zG4om89Tze?Av>PdeOq*Fd+`!RQ{67Xy^@q<*Q~;Bbd0rRi2rJCFJpYU5S==V zX!lF}# zHqg!M#^o!oyc{!OGOH<8lQp?kPdVi$CnXN?=%`!C0e~pg1j%otY%y%*fm$=anJug6 z+*$VHV$C(Ze9g*~`hVgU<=!5Zl%_XlT%#OEyb?MFTi*eJh?niA7C`M}=j4*Xqhyr4 z<1x;`+wD*be_c<0WQ~x&yBE~}Mv&!s%mYl42q~}-olbNSCij~F)-r-Hi-$nYdkxmJ zH2Var)ZC|Yv87(;F5tbY%>5cKpKMBju;>Mm?BtKgYm9@oyYJlC`)o6FkBkL)huCb9 z7Y{9QzxeT|yC~a?OQfp9KIbzjGdi;gx|8XiBfQ__0?&}3fri&Q{zE^B6RF&g=c51E z#0d%@`IBvHInWaJi6Gqe=N^OTGUe;l_(6x$YR5;?xnf2}WPxes#9pUwUWbww6`2>c zV6NbH{f%q2h8o2knFAs*-SUvtV?Eh4fowD@JyNba4EFX!;J0H`j9+-0f$uHsYrmH@E(f7$0*KQ2bM7lON zIr~+EX7&qz%6D+c^o9*8q{<65$4I}Jkm8{@SjljKi_m_gzJi$|X6q&>0FWyz=|n0j zhKQ$M{S%hM>ESQP1>9vu&+dr5H-RzPhtK=Slyh6LJr-!2mA?hUM2^#i3Ue2b!`8vEoW#=KV9b zi@Tp=utW$tXbw86Jb71gDFsL{G=mbR_Z(9khi|*tg0{D?StlxU8;&a_6&EpZqy%dw zDz{~qRgHOYU?#_9UHW99Q=MV-^~?7sr*UyUl=Ucl-xmx++7Z?EWwZDNTNc6CAk#1L ze##tcv-;pFIA)70ygH0ySNd!;QqGO*j9ZyOsMPNAdp_X_DNaz|(Uldh#?(FC#<`Ds zhkMBwvNKLd=KsagAbihQhM5Hl{QU~xKK@Fo1jwM8+CHe4OA6~)pHov1vSfY2=(@un zLzQym#6_-gzc96J-o^%a8g7?E-1+(WCqZ988NqU*6t5cm^o`Y`xI+FcKPSFU7T)Nh zrGY0Lw1;$?vtv@Q>)R}VkKkU6l%cP^lR&h1KFvGmz#}PBf2B^f(jo};uUV)NzFFxl ziokiJs5O$-W0gbUe|;av+r6 zxs$L3@9?aFNzRURlBGgv#K3`NVIFLVo%6jGa-i(N5dS5h%fnHz=7(%|z&G*zNL!N! z0vXVgb%Nu#5ni$V#$PUT`GtU~e$CG6SVL#b*b;azpWsSJPAWkU5n|ZS@dv@$+C!Kr zc*=8~yeh3{tj6oZBz|`1wf`_92F4!%92^ttK_lJ{2!$1XuGEPDU{lNxIcFpP` zDbZyCz$>g1W;SjemTA=TKJlB5-~yvsz#VBPlk@(TYS{DG&Z8QBYzz|i>QL_)tw2+g zHkJKW{kI4q`;x9GHb3wBA_~vD!%p(H9x0A(c*>h^xBXY&t8Z6!Q!s@HU|ADOmfLqSf z>So6NTavPN3!Ban8f#&gLgf;XN#gR@b!$DaQMY3d7vk#3xo{#`mlEbK*h2n_IQ(@I z!pOk`R3tzynDY@~^$PC;DGwp~ho6$10peELi{5wt$P=cYf>Gd)!>#xLw}g`x7e^ZT zraUyY){&`o-lKjWEDznMfe`b=ddbj6}H{Ss&KL1__l3lVIeR>#5Iae zE#Uim^<3(k?5^=N@o7EbG2sP&#O#Yh;oRQ7oZA!pf)S}G<#5&zIMipE25&T^(<*1~L&-Mv;}zq-zi**?Cj zb9iR4XW&8z7hnH=qvGOevV8SI$Wcb|>I~&I6^HY$Ohf0W7YXO11N1oLQacX<(2*7J zv7@bL8ScN7%7BeoVyO$9+wrd&Q?`QnID4z}qr93OcUdL7826_K9-XIt#_D$&6)SU; z*DQtnW6t4sny3y#7w{uLoEsh)B)IqP-WAMO@9D}MILLcE`x&?#8ZY8=e~iZSk52J` zyg!Nf_c3tM&m%29O9(yOjY*;25_s8H=BYWN$pEwNYJJgfWl1{=W=;I$luM2J=u+}< zBpTQ2vuQuj0%bih#(XELow`%MlA|4^4HFC9fjvhEE-(I89Jn8!4j-}jGXFl;;W~W~ zWpWDlHrkbYJl>Q$s>asYUVqX09S`rbN^^Z;w>)$olVur`IeaqExY80oeezT$fXy!Q zSksn5e4?{VG%1%Uv7#}Q=cy{ruNipK1}1*FI(FsL35Im~W+r-u^YwOIK)KY_h+a~! zUsIa6l85RcROP2fn<82S5$jAH@XR__mt_yKrQ`r0LrP0wFC}6<%(GCWV@ z_^_~O)R{zT0Fnu}`4WFVX@z)i$sDJE1+RzuA}8-LlRKjvY*Z8V(*aFE;{vvPx=R$S`5OLM8aTo7+z6D#UX0zsZHzH-5R(qUhgAa~UO?cf~~J{UiA1cHbeL~=ej+nAJ}XQ8w{u|28$!ro@K#Tpg0Pof?O81 z8`QV{Xck4!VxHFGP@M5PwkfqrF$g_wNMBtUT+pTIe(4LJHWZnaLu38AYSPAdQr&RWPC_F{Zy3y_mtJ_~JuGF=lb( z4>{T9$f>2`jFSByp9*Bj1Wsx*&b<&R@c!auD@!&!fHO*(F?;^9$Yn;v_rm-WQ4-sdxJTx2vg&JW{;az))<17uHP2S`jluM@MFD_}b zJzEi$H^DC>z))Gt&{yESkVwcc1=^PAr!|%BjnF=#p%Qn(FHZrSY+>(Uee$DUOw3B4 za1ldN_b%fD6DmJgQ;Nq;n0J$9r^e&67wW=sK^7N~j?( ziG~)fL_y#x`$CD*j}x23``PzP(9irg=**KngYyaFEHB>XmqG)+vfgok58453>%n2M z+F&1dK3Z^oex`a+d>-=)mQ7DIiO=5Z&5Jo@TI_hQKH$RL^lMas) z$yM%=M+@{A>DjOnLo+{ErExp)C%fumsz9x$GMbF zX`YSEf2G8`#&*Sh@nn&JtuuD_Z1zIFGph?EuH2KzLgCgp0N5w;7770i*1WW0L|DR+ z-`64=o{n2eGfIk`Xz!G1Pt&SX2*-7v`ac%5^>hBm&dL1@1s
J0IGFrGCRf=7=?lc%jmMN=y|*0!_DT$Ft#?O%%TQi;~FZe8Wulz?w0* zuKs0fTlUF25}Fc3M@fhj(w0%|8yhjfL^{pG;ZOqNTv7YK*-o>j+^FkHw$b)Cm3HKF+<8(JXGDHG}~P^Ow+u&QN~I;kI>kbcu-7s;KcPWaqkNvk$uz(Tp=7W)PDSS&+l5U ztR{yjU!>ihN~u;u>+UyyG?XTl$tTHaVKdW`A52tQuw89g__eX;Srt|#nMdtn-(O9>T2Z5*HQAhcUnOXae) z^(0{Mcl2_8`P1ewYgiv-R{MTxl0ED016NdP@F@N>8D);jh5#6zOZ!se(1xv7r95OO zEqDZ)Ee&aBe$-SbJ)K?}TKcY3y~t=`l^0MD9&DUH220`9MIvu?dm~=GfC#%;tL0)j zadSH@q1Ug_A#J*!{O0eK(c{iq5AL}Lt5Q#6p45&-*8MH1)82(~fe(Y1T>T`!(zTq( z2sQat$0>&j-Ca+v9r}g`>qHicP=h{7I;!_)sZ=rXg_CZU@{rN~j_6U3X+K|y*uL1f z26z}byPeUYZCq1ZPuc18JE1Yrf2N#!7QXO+UJmJGivODeh?L{XZUB-PJ3e&0ANp60 zfN^&5sM^)4BP=p1d*t3kyQ2Ly@4-fJuO5_w-AwuO5tg(AApB~K;TRB5;giHai#1?G zHWAPhLF>FkLX6lY@Le5xQbIQtl7ROGf1@y4ysUHbC(}5ya$eq$v-~8L1P0aI2T(B{ z45-VAOj`~*Iq7{cEK*NUJ_p;`fk@s0CD`K?36A&KZ&oCUClY+m%^cwdjnS`0&fN+I)Lyz7az=ASVynb_lm@*sLMuC%i@b zg(sEEAdOe?57!P49=Nr)X2Q%Ujg+Ezvz&_U2!CV>FVkqRmU#qkUD(v~P?L3}lvjWn zub52=EPrsh^s5j`GR;%3L_f-bqBfKu&yswhlurTr7VYNUq*xFOQT{>iI+6yG>cC=1 z5r&XAk|(i$47HK<48SAd84O$Au>~=|`vo0)H^v1f&a`GJTDpgZ>Kt|Hbh)^Koqo4D_j3-HzIVnnunII|N!~Qe6N5yCbAuS4i@6s;pj9z=;rd zplxa1&CLJw2+cOd$XRhIXzq}{wh~~2U!K_Tgk$Xg@U;b`OYP4KEY2x zQ@p(4eFldTd;Wf^f1L`q%*aX63q$7JuWwph+(U;u<}5Tr7j`O&h2&)ycQh3@T^{`&_n z@fa~rZzs^-!S7jP1AnX~=!d|BCZS8UVHyb3W=izy{lt(O#Nds_9lgRo*6Gl&`Ks4f zjZ8COrv@EyU~jH|6bnDs7WZs-k{f9dV>0#^eX#Iy#*C`8C9^m8eE|XNzw1hCR?I&) zyCL06?vrEs?ef}}ERF6zkAt$q@J0GPfPi45PsMuv>irlFPR)&HOzrfdXQsl~4us>d zvn^=XwY@8eqxOq$TM){2T;^G|(I_ovYtq?aLj$W-OVXbGzf=t1-#_jqGqrbJ3t_X_ z-rzq2s_(D=muSG<&3)UFGqI3u_j!A%llwDh&r&ry1Kv&eG!$Qff=-pExL?i=P8fFu z&}@AGN`C!n5#}HC8k>UT9o-K^7T4d^jrpcOKDfT>g98Dbla2B+0mmD3-PS@+#|DjZ zG8$<}9#aQ8H+_#;!=8RhuJqxIEjUgbE21}enE&G}C!w+-Pk((x@SkZc$YOkL2xTbN z-X;2tByMqu&$Ui8ed=&Au~)p3&@Gij(#Ws<-PMED!AYX6;N+Q$KsY15{v*yq7Xp{o zKE5&Q{_8|aZ6kPP;_EOOH~a6Tj3k~5M@!oFK{4`|DrRt@rAtd~=B)F6R)!zBK$rI1 z=lmx`x?YvB4@r8%U-+7+R=RqS*mXVkBom?m-fMPwNlp8kKhmLw(%t<62YtHfyA!Q4 zE!~lpWzCbL>-iOZfX3QBynwYI(-@lIVM_%s0v*h-|LI^B)WD_E42)7Y)>&q4;veTp+de{Q zrtQ?MOw$F@Me0?siI;1-xNrxiHu&zYLG5nbTI>JVCq6}EEyrOMbqN0*d(w%%Yja>g zaXc=MR%W!9a2}wl|2d!vpxHsNpV&27oT8k37nh|~>97YeZcI?1bws@uqg2Z*;V}da zz4Z)$vKc9C0HOXn(7kFZ5{EUousNc!2xH1O^S;J@UcAqwJ3g}bf3*N0PSohz)LI~v zV1g63#8HEhRU$9<80(pyz)F9VZ7JEs8o<{IlTY&V!#I8|w2pv*z<|2J5?YVgsLl zRE)%rOJnTJUc7;wx!xVjA15iergSc`#>x5IPX~T2J=Y4I{&8)@bl>9axK`GCJ5;zy zV6*!DCVga~SsrfDwj=JUy>laiOLr`aYjrrc@1XhazAqJ3mKjx+dRdPrJyYD`v;S9S zZ*^Pj!Nl0hN3Q7ny{2f6@|yQ|f2q<3 zYUIy?t=JbM_3*ALYJcQ!e^lM=c>nVtwFvhMWgX=%W(h`trXah`^XLAras4lX7$Z8G zqYA!`s*^ZMg|d~_A^HXo@6CFHenln$E}q?^&UN@Po3#Qo1}2udo{GY`dj%=IO?jVYb%nwNwe)&BK0pN~JfK%pTA`gl+U@u~Q&wP59h{fA{W7m%$9~ z8i9QbUdMK-wKvsI!Gq&**Pm@V2g@=Kj9tMuI4A3WH_#j})H=JkI@|;f_>Q+D^p+`& zQFhP^TiZ$@AZ6$WSTNS)V2&H>rH+znfyi2mx~p6FmsU3SDVDo@{A<{g#AdF@N> zLH!e*M#sYD%$J)6xyZfh$vEnHEoT%iof<;oHLaR30>QeJT_PcFX0zT@_6Fj$Y2h)c zbX|}5{ZFM4H|jN!_oRB;&6C9wOTrybXpN^wYNK&C|6`NG)!t)WAsk&_^7JvSv3sP- z-M-O?!Q<6l+qle3wo5fAtq53^B&Af{_I+=BY+c8YhK|JbmPI0mIq35ru7A&sL!AL! zLC0lZNzi2x!9p_JVp~^@AyUvJZXyUf-}v?!wOL+sjr(^VE6wk;Pra41Uil*MR%y{s z^U=bIoZdBv0rO%D4TvSdgKkm~$*pgyqRjQmy5OfOBa`F~-;L-+*Yl@cRdCO084;_# z<88;{?Luj?5j1t{K6}-<>Y(FcepTeBil~{e1-{TeBPLKkjdu^31|~s zu#4z5$TbdqT#d`n8{cH2i_y(@I<$S^ebQpic&H|wehGvm!xaMs-#c=)z zccTAY`6|7eRq^v}Ad-p`H7x&%0L4|Ds!Ow^aBf_lKLelw40i$wB_W z@$=LUG4ewsYa)xzI71?vBW<%Y_`7|a^(;?U$CG8gK}F$B_t}fy&d#j9)M%SD3o9!l zpttbWEBNgvkl-B!j<)?96ciLfL9Y~myGa5t_h??uPyaPKpB~SF8#a#?(4G*`m{u0Q zmi3N!Yr^COZFj~;Uqn!rD{x~7R$B-LQj*)GI4C}&Bqp&>LN~;D^`gD4*R=x81}*x$ z_5WHGi#|9nDDPUq5L^Mvyo}{zLc)_=^YgZE9(|;WKQ<{!&;b?QY4z)Di9|Lxn!+^e zp+yOOnA_+88BeRe4LC1SOt~G6<<4i}J;dFLBnE8hrZ+=Iy~si0|_J=K1YCRl!h6kuxGt+c#65>xgZ1Fd@C(5KoRjA@+0e5jB|SZmx3_@r-LT%hO|?3* zMv{fOIgI|%LOswC4?kSPjBGT=V)OUr_c!mzXCJuzQ0sa9YL~H>6GR_$}0VA>Ys~*>)f5dG5yp%#I;SG`0Fy&8ZOtN;3 z)_8sJ_dT_8Jnl)S3o_5|#x&dv9dNZV2{z=#+i(qj`}#@dykF2|;hvq%l-!S8Xe*Zm zkN*Jf2N$o0xu^*DFF2MWOp+qN0GB*kE;y$8o;W--^k9Af5*Uk>fhP?9$4AEVKE{8uo5dOUOo zef%9pj030AZd<<2BM5nd-O2uYRio`b~UCzoo9M<=z&rF{Xdjlm$Zw&w7O{1fBkm!SW z@8&i5=3?||nL%z87q##%fN&6lB?l{)h!AD>ic&NRE*{1<`d>jb7v0%u?{|qpP0@%Z zKMkuL#x2jIh*JPYGB5%?LM*&*>sin%TY@#60uy<|W3}2?Wy84qMWN1HlRxe>{U;Y3 zEpybT)WLBd7~??b-7f13KgQOe*9Q}SX5BN^>)eIc$KkL!Z$56j`EC&B#+vp~%2_@- zVp#kT+3#Eu0jaKu6Nh_(qh&5_9;8P)8fJRTthwXs<0EhPj^|goloHQYs*GhEMA*96 zka*qCFpx~~6zRQEEGcTP0!MX~UVUimc_KrXe{L{Cnk%aJKKTk@>SF1%@HsxzY+ao> zu>|Z<>!xv#X($rv#Jj6){LcDOMKJ&zNYZGMlask)M6In~*q(s1q^D^CBBjx-;O%d1d;-Nm$*78S%BS;7?LU4u%F5yG^0rH zacM|SE>6^fLyxL_4tpWU3J#L_i;KF_^iA3cl5NQS9pMneYt z`cJZA`m}P+0fHQzq8~Er@l|fJmvL1?5DtT+vLi?XST*W)TJKikEIQ*`+s@qh>`sI#62C z8WxD{72DrJR{w4>duPYC?R8i#Xup}dcVr-$wn&2l>2GQ`SQewbPPTV5{neLY`E%37 z@98lj9n=jQ(I%ydGc#k0rEvvS{7kxQ6O*iaJRJh>W!U#76gBT^%B-S_)#Qi&w<5S7 zfnnzk9b>?l^S73L%DoR6Yc^OAD9y2?`kbe>w3yR)O^&%E3*sRm z&XYFe#(fi*#H1AooLg%6L!8k|VJEwa41&t~JLLsdu&l@sHvWMHm6ERVGKOa;n$)NX z?Bn&nmus8L?*#-IAK$~i>+qK7$B(Nb!(H^K{Z#VPku$@xlPe(90BI@)H)!vQ| zYEH>V*NU)c49q)VYLeSVY#-Z@TT?4i3X)c&sBa}B-B&L&M9TT?FqBHOkSoaS*i0UZ zB$%)-=(C%d{ZTT_DqW}JLC1L1g?&@nggU5g0%NiCcSw=!=F|yb+cCy0oQ%2*FHGI4 z^aGYvRthQM1a>fO+kA~}kktsTPo9C^x;s(I(WVM=*&d}-SEs}v`Y9Z6r*DX=>gpHS zw=L#hZ#Y*v2N%FVOXg)Vpc2VGd5*5w2{l{$(s4hvI;4OExHq8i3(m;w=9E)Cu0?uI z5OS9|Rv(9fFZ_yKk=f!c?iYHM^PZ|#~VX; zKry1iMi^;;h{yXuNUUmW=ii!qf7nTS5;bP=VIs#k`}B{f1KHLX=dyEF;R>sH2TEI| z-NWu5`O{xV!57bOU_yG9LSddbK*8(w&t$7Nx?HOP+5a0uZ>4T?|BhpDmmyQ@m9j`~ zx+QD!=U0ZAF1nLmgbQMrKTg3rZpncbT~(E`b>_{5><2XcvQxhTw);3pUZU$|>0GOJ*G<2ZIN=*w*(6JmFj z1iU*ylt9_$T^nuf&&{qUoqP1Mz5@5#i8hP%5l2<+*&NohXUBD8@@_O;2~Ix}-t84B z!4p18Z}T7)ZzN3RxLQ>_*Clk4Erm5cSH5r{b4hrB&Qzp+IO|rvT zN3QArx5g+v&MN&ra`vWD9WI1AW9b{kuv5al|HM;+p4Bn}7eoadvfFPNZfB-A0}2fQ zWFKk@!Vk1my&9a#2W1M#iV>4~faR6qktU}ed;d?_LoCOzXXoaeMmXjab@_ib;70Yt znWl$^KB28PSkFx^EJT=UP7%sWIm%mg7ySSLi=7ubJoDskrqG$jEs;x9sEgc|@5oN=gVN|CdqG_(fvwSRM^yJXX7+uZd&9r=} zBk%eIt6SC6^^1(P&5W7RR`BbbHOS)-`dynR6nd{U?9&kUf`cHmpJ@BYskXFCp90cD z>bp2#cdj-5NVy@#Kw&`mD@oW}#5F$YOWYx3yqY4=+0#HTRp_>P%ZPU>!&0P_%u@R~ zi`6eCvxoXEHr17+MW6zN{ZZ(CZLFoU`j^$posTDKdR5RlO^rO0`EsDohZg31#M z1{(v*v(gR}VMSSnD_^9i!}Yjcw#zfcg~js~ITa__RRLSbYT;-gZRGR)m1}$lO&LC& zJ%h;5p)^qAcuS?W2!J&PF*6F#=QlSFA!gqF&5a{)60OEyv{k`Z(6Hl$9H1#AwQ-Lu zVt<9C0$1yuiQ%=0UH{qFQ-4<6VL0z*;StJ~Xj&cxnP%}3R|#G8?yV_Mb4|C0#=E0Q z4H752`4CR4>WC;95xWaR5wvCViwTOtw`4fa|oKf?=A&p91eox5=s5$Mz^eyb(kwn;V8+RELN~uyA&a zn$%LorxZr%ZQ(Ijxqu2SqeK=ce#ctnmC1;qvs6(BHQ%Nac6U>_q&G zXbEIVsmZgiNh2sl44J@HheV{oZL2}@ttsRYvbs_}F^_6z5t7WsuEN2&Z<`txQN+DW z&CbJZ(N~{!)Xxcin=h3jWdQK^Hh4zv@`mpUpasQXV_#j60B6_|{E2Slzz?!j9ae z2lWjVoIgRA7HB0z7EiuGUIeSLc`29O>M-C0PA-V!}b2xvZ})S0DNo_UQL{ z5%Fz8IOBu@C8e8DQqE-m*A`OFUlv6DswsB(tX1<6Ma3j200=IZY0u4h;f3W?Sr{mc zsDeX1pnjEL6(@w8jLNErbv$(+Smr&hew=W9#H(NNgc;ng=_QR|f%@v~ z%+~GfP_ywos_6Bv-M`;yh0Wi$Rs&g3T~}&?!pOPKr}`dum%liVLQU)vajxEcV*ck# z_Ii3S6``3}n0hmn1Z1cuZvM+qr>wa7B{#ddRJ$)lFaNgv(%SrQx6JL7igzj(sWHu(5%Vkn|qENDrMWjAps#t5)UtV|FvD!R=n4-63)sdBk#$D7CPx4PpVB~?U zoTKZ^J(pv-7ml?y-C0PerA?NU%5F-&wV|p6mFczYz$$=})#L6%KqHF2_m?vDio@3? zV2<}uWphld&#|!%V1bQO*wa3+x)ON6j%cdQetE@mxZ`tq>K-bjwShKjZ7CGv)MO#lLW(mQrGPQJE5uffP%|LE*3K$tVO z-Sn6(^jdb(GK8>=;%(h{nSP{>0d2+Un^yLDZ992)!aI9C`ld(6X9eEMzkktIrxg;! z1z#o8_LmtLJHNK_^!?E1ljb9ufLroFer$^-nSBt1W?DlH>my~(#uC@sV53_m`6=(A zZ=xn|-@!`H=t?WY>Wx~P=N2bqz1 z62zpFDUze3RIKs@wu+9kMZuPAdW4srLnC?a$dhqlFF>L*&!9==^7nDW{wXLwKk&g~ zq1vfM7GL*$;Fto!#YhMRC_j&~ zPMkVwEc|&t_yQOvt#Ivn|Q6_b}@RYpo)gt_No-B)NTZ6}nx!QkM z?#_Y=4OP3g^k1FoGonThHzxFn3O-A=)g($}a@kKT1QoKk%P%E3fxO~s#kYEA#4^_k zzWr(n+jULU@O8&nTJ?fU_1(vAyH=I(rKt_^^LlDr_Kgs@Xv_b{mEPb4E)*pge9-hW zG;X(i4mmnE|L9O`*_(&{17?b;W!HsIhQfT0G{zj7sJ7tefUpvXcc>pkTQ9sghUSAN z7g2N6pamr3RSYC|7$QTDX;rAK?SEdPrML#4DR=pOr70nR4V3g1jQ=6e0BoH%mjF%7 zG35zod4FI5XxSM&By!t1#;|LP5#vuk7E;L+wR_1XA91@_?Uyg@GKaE1yH35kZ{wdg z6F~Ex00#fwzPJIyaC{sWpiy(kC0_bFw$?U8o_(!`IHByXqG9-c+kIS^7INI1k59c{ zXyY%~n($qU$gjF+&JSbpY<{q1?9<=t@bQuqLqP91VU)N7-gD)gecYI3#ortBcr!k= zplq@9_-=&|##DYH2II+QjYz%$)j8+%z-9yZ+`a{9((dTQYRCNX#=h242v%^xDH7rK zGNc0OJO)yxC__5WW zD3Deyt|3@G)xfHsXocfAZGlsj%e5tq?{O>ai=Z>iDIFyon70F_@!5Y_f315bUm7l~ zX+r_RFqB8u6cqp)v$JbAvl)ct4p(Z$q*|DTk;LI*9KfoA*r$;nU{% z+Qk%e`R*^mAQL!mPkp;1v>QT~w+hsjNu57&yI+X0#Jv+9e-1`X&$bz=b^`X45DOpMPtlg_#D4dE-sng?3|`2a(uCk`>_ z2<1EP6%UA9xCwtJ6zinLXNK3~e%_qr51S;nO}SooNSVM$u5tUW(-@U~JPo~Ij-UIZ zrnkqd@3Hec9K__-tXJ0;koV57RQpLJJ`zRO4~=bGe6Gh{NJj>%yRqP&R4&_XYp+`) zqL|x-+4F|?qkHsTw+UWs`aZuT)Gx2!uXUu}w1nTA2_Th=t2;dQEZtAOXEq>;x*gH( zIKlBrVm?_At)!i`dwdWl-hl8kCs-d`;^+;|ar*dI{acc_S-?gA8H|0tOF3sN`@vUQ(#VW?!q42u=on zj|b}t;iiNXq{MPdXi&;J1TnbFoKZElJaav*H=!;&UtXXG@gFZbeE!JX6!QAJa{BI* zqk8QBYEe(VB-&}=CnE{`{1j=|+o7tXr3p*`6qBa6 znHdxTC8oNtAB4<2A$Swk2eQO?X5wE`5{1vV^9C0rXJa>M(^M2eUSv(FpU?omax($7 ziQz6%aKBX+pG^4d9??be$|O_99pf!Vw+P>6@R1PxIu3>#PlDo4r+V6wEp9y;e(!Oq z$mZ6TuDGAk?rmgzY=WOQQYFQ{68*$k;AG|d?UZh z;LfGO?b&%YH;(GfLdMpUG0&hxp2MncawESN+34P+(hJ>=qr+Mob3N6aH_u>L1ypMB z{rhg86Qs9q&@gvly4~3$JeEFRdqv+X{Siz5?6GvTg4gz>lR!VkGY5MC*>Xr^ZY{1B zP*m8-JEt6`mR6jq{!t>84U$*x$7dj!ghW$+teS&cA58uR*hb_+_}9`A6TL^5fiJDT ztUNpaSKH;C*HOi?1_r=uH%%>%@&kxdEM5mCk!@l(!?El%aN&L7I6(FNZrn)V;ZFE> zfkKqeTX|P-pll#kac!^u;1^ekUQKH^5#g~Wht=Iji zKCQ?<=29NcugE%LuQYGF1A<2cHTEP6^zvhm}$-r6z>JUX59AN zC^cHGyIxe4Zq%;&OqVm!SKG$?Go8o?xT`D+jq}MV{SSWy+5B~Tb;Nls(d%c9)@b^to3Gz5i!F{s?xivn zP_HErTnZMXY=f=K0a9Q1;+Y$7V?|VYD0K>6WYOpAD8(Z`c8Q7 zU!2OCHq6VyP|^t6P5~ar;jd-iTkKLNDiGlR`G0U-xrMcP#?4Gc(S&Tv;x7^J2JEel zm@{jkOg!`5%_HGAG7;DR)2dv$BW&}zWdlElxM5iO{;$m4Tx5)Uytdx-=z5mFPq^&+ z?C4k}(0!Xzx|wjw1fMgo$WcaGE@~3&L%tDz`tIZGZkHEW{+?GMv#zc>N@W;PM@u6ACh(crltk_0fzzgk!R`aS)e(jo z!5~oEc#FvqMVwLuRvU@foINLyF!d~tDb^Kc>qy+$<^XUS@i zQo!iizQqd_K~GSJU0~*|%)@p6{==A)!!pAl?5a#)(*P|!tCi!diWSn-1GYFt+9{1% zG{gO}?>loJs9%n7(mTk&fBuv?OR>r`+`?5d=k^vG}QEz zGG_0wMe~SGxcsQ1?eXb~n^-@*> z$h(+dFht_x1;3j%e1MzSP-k%gR$}_;q>5^%tI-8P;XD6x;^=*&z+cf{ROD|kp;~G&OMi3_l@#Da?vNQ?;G^5Z6Q@Cw=&TXW+9W{ zXR0RGe^dRtnZS^^^beve-({)QR84+L^WZiA_7`Gl99VRqkm`u)8?T27mHaz~e?iSD zW6?;pc`WsmbbBW#J2L4Ov)|>+dS~;i*b!Sz+%>Jfyu*58O$A3sreH|~IpmB&F)`h! z=Rr0jjx`P(?T$s!L(B4}>!04YInKH|!L6`{i6;AWk9_dP7)O%xLOV>)s7wzO-Y2bA zhVo|H-5V9fmdK33lH>K=yi2o@W0VtzU{t^iu&N<#71^Rg?>P_zT?h{>{5IWi4@u>9 znCd1nl3C;|Ji?w=rnJH9BR%4VgMg0`@Qm1Waq<=V?$}d~5~`7Tfj&za@9>dxK2OJx z);`@aa-Qy8XkAJzcQ=#BS0d?!#mNIQZEGZL89Fr+YtG16OU~4^IaQ<80rVPn$|#O| z>4liov$^z3MAQj5Hf`j!pi)S<^u5W%B3F!{Zs*M&*;1z5*pC6%`uhcR$X=vPpZqaX zEurAKNPNZp_A1#@MB*dvqI2o5Yp87PJ0hDS?(ie*HiRVw!cGTddIsYfDu%K%pXnJN zf+>cl*B{r}6q$@qf)Ra70|oD6Z$5ubTGewnmItzv3>T>O@7tqPHT#^S+BM*uGJygV zYXbM9o{*(2^%159KeAaU;2BMVM<|xQ6mY-VzntDJ4CUa@gq@h3@0b(-l!QMe2J*_Y z2KsyCKqiPZD^LiLmVk6p)BB-ksDPA35l$yL>eJ2O{y?M=46tyx%)r8N0)^N<`xc#K zpQ~N1lA9{rTOvH>VLG*a4>^unqIa}@&V>T&MKxssf@%$Qd&w|hjDErRe}g6#Q_p;_ zhxFj1F%1bfb5`*5^Re$W*62}Pk0U5UHLJbl0r~TzO{)*YTwmbt^&U8?5OBnBYNWea ziER3W)Gig$^P7NCKQ~4_p6=$hjHMKY^PE-PM(ZA6eU%`lp@IjS?>QrL=3XG|u1h{n zb&V|z%t^?|OLj};{8CvN5)Cq5pP@cG@?Q@%HCVOe@~tyv_DERplKD^;L<-$pL-o@v zJHwo*f*V&PMNMvPLUpN;7ZJ0oORhBf5gi@P6gyZTlWeG*p1DYN%AP-VulbLnX*!3g zsp>vY_Fj!wII4l022KV=4J&#sE3@*zWmXnVLib=TYw3fH*PrHie>Sps-=)>IwTdqU zg@48%%=kUzIsFw!)}$|v3LhbR6WGW~OXj@K84z4}nZWy~p`g~*ZJSJsFd3hmlZJ07 zeV5$daT(t4P;%;>p7c2C#^AvZJ=#d1Be6n8Cd2}J?O%jPGY2RPUwPyUm7mW|^?cFl zo+%cL!_F)Et(DZ_eHAn{85M|tj0kg;Le{UK3;(VFaq2PLM}yc?^D}%oOq#(xr`5a-1ob|c+}DqY1;;u*8Q%lDK19AFs%eO zzHkeA$!lFFl>4U}2A4AZH;Ye1-@*RvVL>CzBuhsRHb{Xl-#>5)hS}_WhBqxIBFoj& zv%7zv(E0%>;s+KvdW^&N_SElLD+-fZb>>{fB(WnV0w>Daf`o?6U=dvcYd?72Ms)Gn z{sp9`7uP)6k4rk~3QY6x0)F(}{KGor8Jm61%6Hz3_rD$K0l&R4t8-7Vr@$bgHNo2)jzDoo5af5dS7PzK1z2d1c2U9Fdd}aW9QrR(}F* zm`5@yS3(xRkLeDB>xq%IZlEG5;a z=-CFa6#T)w`=Ml&VIa2y{mUb++sod}k>VIpHy6Ss^_J*0E{^lw_$`Y{nesm|j6-9) z=euUB?sZ5j4&iEZ=h-fw@0_gZ!(s_2*Z+vomo_;+qkA^{N@&c{T3O?e4teS^yP}+? zZ`czAS@{Y4GwUKbF5B!G{(4id1&bA;H?{q=sP%^pPmeF1tibYW*Ac9tEyI_(m7NH0 zC`WFn306}RAFRF8vu3qplbcoBW=?3E!>7)(q+pCnjz<&_-|(vVGU!a$Rr`%7;**lv zKg*NcmN5teL7~pvy`FXwvM#mh5(6^qHv$yLu>pBHAA!jj(e%O^DgxkxS*rKmCOWCr zdDTqHqkhiL80u=QF`|lUpvIPj9A{TFMag10NFm4S&OB>kuF?;b&1OnCG zAVF(O(qfw5*%E`0dkbUoTP`l(OLfNb=h&MZuC08%uV{Au#2k5a%O!rEE#G+A>O<>@mQXl9$Di1 zxNzB@WwQ%rQ?J?95bX6j7;}#ld%XEDs&noZ3?_E0?F$R*o}>$F7!nxx%=k)N!4J(x zfnQ26%lN3hYS9!n`wc3txI)tDCbE5KEGh&#BI5$ZocWbPLjEN#u?7mAwINw^ROi7V zgNG;&R7lpu%Av-2^jSbwW{(`aAAhD3l0ld1f~w(Ed)Zju&8H*rt8miT_L+0B`8Ba{ zKi|Fbt6Ip&!-K8!6`z#Kt?}=-*IkR0+6R%(>01+h_JmobR;e5^oKu8eZEM(-{k6F- zte)u14lR$3+%YkN3G3w#Bi$-t+^f&@1l0b_3ESllw+1FLPW4A~+^$gkKri}36iWru z2e&8ur4O6$@xV>nSwQYTOyL=S2{{iz8Lav0NP~={tKmPXh-?k4X8PNI;LwwLOFif_*1V zutPA(z*Mf_A+8*+EaoEPaul_IgvRHk%dk7;(p0tF0FSkYp|YoZ%W9@&E@n8cmV|W} zEck|od*r|WQ8m7lY?q(l!al^1+Ll-Dj)i$nh|;_3Kl|(b%XJ4|#0_e?z(kM7bZ@~v zv!>2{aNY!^$Zp~O$9xG<0?)+5Pjd z-d5=>*;&i|3SZ#VsKDRnTuqUl$QW|CCeb#h5|!Z`+KDcEu+>dRkzmDR(suaT&X_TB zkzr`R5ZUXiZO7CXV3JTl&xu+-V&*|=Xn#PW-8RYHks=Jq)`=19L)YQ#pS8|-Drru= zMXjZjO%!y!cJ$a1Vr8<_igEA2b5JX4m|&C2AzvJdw+6 zQZIq|d*FJPe~q}u(Hdr*EDq)VW!{p%H?+{rF8SCfKHF&K z+{wNDPs`fP(lkc7mF@_qi)s7G#1#|%lDbm}tAQ88HTzJaxs!*PL;HzaN&9%5PTx*? zXaGNJv!+S8nYEea>+1mVFir`ldNqgBfInkhSn09R3)6F@?^4e2b1-m9Dnzyy1(9~F;8YsmYuHyQPLA};q&kLB)(YYL2OuLQ@S>i^iRlG^Dm#N zf{`(HbLOC1S?+zmOCU(^__Np1NmLWU=$56sFMr2$8GoeN<9_bjQ>bfeX|Y(zK!+s9 zZ{BVl*4A!%P})RO&N6+z;DKgOs-6iGnf_Qa2ls9o)#Jn(2HGlYzVz9*6li5QLC%T2 z^a=3`Yw-1Je-e7t%85N(TzsdUJk|#%YJ>G>G{nUP4pNH_2`|K25Gfr5#Y_4bVUrY> z>eO2(b+jmRw&!8qUk~U1_tB;KyRmgWaQV^1h!(1EQn# z6Sq=|?$oZS-XNQd_TD4NCP5}1b$u`~GpY?0($Lb_?>bV#C=Mx8uw}Y%Q`_^x8+_zt zW~#M4`cvKSDz4#x@#B5wtdgGkoo{RTV@sY#s%v{iKLq4ycHT1GWWwc_*AGxlVK4Ef zMySnKxIS*VK#aKvg;%_3K}FwUby*s}49h!96tWU-sP=Xz5)zEjB{Y0$xdxUzL)qPj zo)}QJb%xGaX!IK6X@(W{;X^*!GK7_z$j^b6__aF;HPn?~S{g4&M`@N|YuGdF))(oE z)i&{@3V;wvXjz0#a`Yj*fa4Ea55LfVtO?flZKP?S;z$DuOxmggw$-Q%3%ZywCy5*y zT&@J7ZkIw0>WW@^n+&XJ=VPgVc;^{P2xE_f800+x{vY65>ArC# z3%1fVRVn>bNAiBk{i$mt!q!1kkF=8W3fePPSjHENS{^OA-1DdhkJ2pZ}U-c-`G=3jo&fJ)24-*uvqnfZ(T zaZx3Pq>6It7UcF2mP%_%cii;)**VkPP}jPGZy6u$z_LVtW$^af0*n%VDuf^H@s5Kz%H(;$ixm#5*C^>=RxTpFauf+*a!oK zSm^e0{9a_kNN~kQrFInjK_Aq<#{JH0e*r2hpuWFVm@*_tyHuL=0U2e&QzELarso{N zVmrqSq+DXk3OuvJoA!Evw8z1cW;}qYZU$jaF(z=tsiIQu88t09^fk0yu->@wcUC68 zX39kg2D}`;|Agxti;lb%WqKf}u+L+##Ioy;tVBUjx_X|0HwFdZiU z<1p%7pmsR-7t+=R#>v6_q=l|^RR$`ZXqL@5lzSv_UG4{F&LA2(b8{pRucp0K-Hj0M zWa$ue`0t(WVU_%c>SOj&?ak?|(!8_}5VjX!nx$gN&%w3uU@?*uQT%Cr5#&^~crz_bVR9H~eMU)$#V7#r==n7 zd*mPyRwvp!r1xs)i9nvBF7OB2_^08cV*|^|Tz!0!<7B5!BHgMPSJqcyQJ zV-l%P!E?utioXJw3&cCFg>tEmH^)6~<~oZ|_SYGZEBajP;1tfW!)6m35@Dw|nNz3w z6FQq3h$&33Kh2i*VbyI+*L29hYz}NV|NQD){_o(3l|T{8WQ;s>Wc?Q5!33465@gK_ z-_Qn4Se#^QEWPEWfV3-6887xBV*9OyY}h5+_rX#=n=fMV_NU9vU~zjjE)S>?_Fw8$EDE!Lr;y!Gm{Ouj^`x9w zrv2y0NFLqY$A9@Omm@U&8Awxx^3KPcNiBu>oQxF0`XUo1S%5+JFHjlyxzfJz5ro+9 zmgLcu5mu{cm$DV}K^@Dz#782Px`I=NKmjhmAOSbD14~0k*u~|XV>3}fU;qwBM@Uo@ zChQ~e?t*!aDKL~^D}!sIM%m;TV9jorvZ-b!6Ugl}Fsl7WnF;@ol<%I7q6!=Dj5?wc zvJ0RPGjl@wP1V9&sP`(zv$5eXWKqJ_{cgv);EK&|8Xq zl0n&2VB`c;im6~_X4hv?j3(vzm;k>xcRzrHN;gG0NRn0&oTu9l{lCUirz}-Eah7jj ziijSg{#sd4(KcjTpgGCe)zwIIHf>MN*=GLR$ahRBfP58Cj#k>t2gSaiFpE8~a4*t3 zN}O4B>=^&#OE*HNs!eKpJM3)nJ{xTOa|jv=+}f;pSixX%s8%=~H!X3hX5?{@XY_08 zPM5flx5k$msSc(czeDD;DyTtxOY0}WTz?M96{{s8Y~-$3Ig5Y;>i7$(jK=jVkGqKKaiZYq-cR%f1OWu}9rG_`#wufu{2n1t#AI4f?mk#Nz`77>> z0YgeDE_k;3O}9Nzy4FjIvNvCV0n6uo#C_}|zFy)zLTc&%qsn&CXbCR2M8u$m9hfSb z5>h4yDdLVfbT}xQ0t}EGfM^3xk_nwdOp7b#hKX)$!A_Z`2T+uBQ8$QuQT5@;N;<7O zs`X^(3}O!Z9OVGozoTrj5BKW6fOI_2s+RPrKm~5(uRO*%9ci+{=)9mM zsYBuCAFctN?(Apo((yzBu<|Jgz3)qV{gp$ldL=V-*$C1}tf?=ss?b%_lO?4$*2=b} zU>)&GzG37J_8LBOXNL&LI;y9_%>U3=W;Vj$N;p364H{tx$U?~wxM8%#-RkVPIx<_}Fp6olE29JJ;ZI}rY8GY)jvq8!z|zwK7w zQ~2{JY@!KAF{Z6W>orgO zc)Ig~Knkpwqbuo#i8AcdFXrO^>LrcbjPy|UqoBRBshk%~e@O}vKB80NOTJhWR?X>Z z>jIDLJvobVOlqw~Nz*NpYI_tI(VI9^!GTJqV}BVhxA)Xw$UrOZWpIp&6K{k^C-(iR-Fc0K1^UT?N4=E#u{l{$5|@0YN6hSVZvn7>i|Z>xV} zuf&RmQ5(v@n0yCwGh&mf2SUE#{()LcD`10oR%qjBHQom+n&eyzy@D%rOAYg00}D%E zJ9HmUYH*YGN3{lK`=)2G5&Fbnq2{A+rX@BoT@v8FegwS3uSN?f!su*-y)9oSMI|S% z+In5ko%b$06=$$cH&k!iog7BAL;~Dk);*Y3YF>N(WF$Xxp6y3I^B$F@qU&)Q9K}52 zitCF}I8#AofufVK-Zq#06z$XD8O}0*$|Px@zoqo-Y0*OeKQ2JmN5Nr^IUP|N%*}Zq zM3|n2QqVKmzI@@P9bN`#QbMOzO|JT$uA~_(bL2>i_K0O+^T+3rU-mt z=?KrrA%{wvsuI_34y;lUM+yTv)Yg+yF`ThAR1L-SNyjmAWmObsNK9wc z#GN|Tq1v-?Z?3BW;yh{yK3AYJHZ6YY&L=uL8T``mZ6mq)T<|J29={N`mbiyz@J~{V z;&Kt9&9%BC!~(~p)Mw(Ge|#;KWqen!ZYKxB5_^(72@IMkJD}`M?$P~WQY6>}r~qY} z5D`Be>D+o8>4VeB17(`V`Z!iS5n5vmGZB)2;ZYOb(RB|Z%At%twvS%lZe#N$)hRqA zDTZq1_B@{W1dhsEZ+P-=JZ_uUut){A(QM`YK{sjYW?Rz*K_)A!mzx|u4TXnIO3?P= zAh+Rf_BRJbIlI7P#O-py*&E61%iyp!v*MW|+i{28DWmVN1mhdR#ZP5{=wTnTQrQSO ztjbooT|bDBD|rmjn|~$q-TDnVyh9~#jLZ(pwcMxv4XXsY?bk`MI=y1%Qo$5g8f7Z9 ztaUxnBJ$Wqi|iqrut@rBNASGlx8gE)4s@m|(O`y)kpYKj?tl3^=6l1odR54U79l1- ze>$_u6`z;bG0yPA8Ttu2PY?D2W*-Y%+|3(4`{E(VEn(dg*YiHX2@O$s#k#sxixH7? z>Ovk}=A(#!LW$R8EIT^;NI%g|dI@?WQ&gj3LHIOvn`cwSGL5(8J_hL14k5 z>>+0HltD{|N#$K2wCFO#Q8t2il2JzRgB2haevzp1eAb69*oDCa1V#4}%05Je@ zBh#3Lx_VZyy;VrRWWN}O#JLg~WTU92m-7BI>LaLPUg(zc9In#~3XHiadYbx{+F2>Ydu5;^8ihFu}nGG?}OToVc`wE#8>&U1lNqPggz9<=Tu zivn)?|jc^6c<2iJwcPCcStx1Zo|c z;z~&>1tZfjbqfp~>#+I{dvOHtYIGQeiw9T%mb#eby{w?Ya#~`pBksGa10))H^QylF zf{PRg+mU%RgH3i87U2tZV^0H`OY}dnY&Uzj6+bMB!}FnxkeQd24?2mj;&oQT-3|4n zk$s|I-QRSl*m5j+5d~wX1HOVl)NCf z_VM4zA%H&8@s5mJzzO^3WSQ`XYvTv1HvK!8a2se^eqWb$l|%j_nn=e;GY0O=0TcJb zlA_MTyRCLw+W+lr&_#z)DK(ZlJ#Q` zYVPSUfdS~MJm5zEqWZRUe6DBAz1w3ngy?y~$1T48${x+P#s-=1{}_`orH(;2J;wu+zXY1KR znEh~bh(bk(x)4+9j;Mc%=fz1I;U^Ld9r85q-qg@JP&>R5WS_pA0;8;ehkI|;8(7D+ z=FK*z=f!yHIoDiVTDl3C*0hom*-Y>c#n@61?@BOU#_X^rl;0|8R`%jhM{P_{MOL#Z ze^P`;6T>3NcgFusC|6pKCdcOiZ=x<-)KUC&zR+r#rYSFiUtrLI^2d&HAr$k`yaNN= zZy7n@ioiOygPq|8_gL^1U1@bO$w@W|_tEYbLc$*rIGEU>5xZ9NdK>k-DNNVvTx7t0 zN2aO_d;h7?*uBK0$?TegTHbz(%pm9NsdoR0oX=5}oH5UNxyu=G?;>*~|_NwXw z8Sv;QzF*WH=-skU@nHs!6mDIg$=!zoYG_^sz{NC7zic*gSwKL1g)3a?lH>Hybo?C!osSQ_2^eIaD5sdY4;6s+A6@92(jFBl5gL& zDVh1Q%jP4+K_BA`)+U{U`RHM%|USeA#3*kFaaIhV4JD`a*?|1x?~$HLi&MN zK-R_On$}JqE$c~kWuaiq(nxKGh_2IIKV|u72gHZfV^5F^x+^^Y(v#(@(?aXBIw{Jj zw(-eug}X>E)~Uz2m8v!_pG=93(Ku(Q@Zp-f)f*kyoO6wNnc_H9m>2oNyj?j}e)V5)yDwyuLA+hHu7ad>3HJI(*m4P=GL6J9)USMtdPFN5u%@W8 zC>mV?Ben2&%^!1!dWRq?C*9PixRESHGh*HHXOFXc)Fxs3klX*CbR2StK#Ll1^hK;9 z19rsM{AKs8XtpoFei|yW|6;-O9;R*&-w;0j*MY7ncN*IOb`d9d>humNME0x@l2YM# zbK$7@O$k#RR41(m;{Hr@MZ)snMk7t>Igr0Df9jCrc&K;$g7@A?1MPCAz4YY$ z_lz@tB$xauC&k#5pgKAy*RNR1>{zo5KV;Fq00;GT7n5yeK5>PeV2$erq}d3wOm*AC z{+j9sGybM^Hq3hu!<8cnPo-b27)<`n_Jh&vcDn;AqrZ~lIi4YWlx??9bP*p8@IWtT zegFrZ*w_@*oG(A;{X0JGHktw8p@pRU6K-Oa6%Q62VSmaoEg_rnk}58|>^T7BLqn!g zKLgL-5HDTLxehnuBby3Ag*bAWI&Q_fqH4!NU1!vapsCE*m`C0TlK|xK7B+g`GB&;r zp$5z|E@7Pjk4T!8FbK{v6sL~Xyg(W6eyGZ*i;en91Y@o|z!14}u0pOsRCCM1b{BDwMJ0=BB>{ zoyEd?qp$HYzlP8j8L?f)%>X9wj^1tKsU zA`XM{GXizX*bOo+Qz9_0x=U;`TU|5Av30@mo`yVGG`KVICb?o|`su#?@5J|N>ea9_ zgt!nsUvd!%*E!lTQNSR27Y5c%QCKbor?sRtv%0YBBW}pr*+aetWp_q=ERYXNN(ekc zAE(x{R0DAVP=dNCHLXCzr(i%@m1Z?#M;7-79?Fv`+hVR-0&aox90r* zOl_i?eRG5Uv5y`N)A*=9N+`}^k<9#~?2Kx%2I(oq#3g6pDMiOZUe5G-ca-IV9#JcW zgw?3@Bck46^ZIovFE0E`OXx|KCkSFvgVC9`g8$e;Fp}pS~aHK+5Y2qPRTIWSF?@19OR|o_1Kcw zP5or?qCsH%0Fwrn`eTJloJ~zHIw7;CKQ-KwT5p@(20^Om|D)*|qvPzlc5F5c8as{c ziR~1zZQHh!#zvFIwl$M9R%4qJ8{a(dTHpVB)|zv+u4|u-cxBK0b6hrkeJsVaf=js2 zO~@u8)eFOnqZYt<147nUiygx9wURr>F%o%AkBcB2CT&_@=mFT|$kYNVNI%YDAtY7^ z|L9+~N|vBI_><}|1!we_qzt`xYvwzjf#7F+q8>9PfRU#zWVTCRi>WVM`IitPl&==L zGZQ-kUT8PC0U6UJ%t)G9vQ9llnT9SlJ$Metx6;X^@jGu0A~*qFa}VG8=Nr5Z#)CK-0%2tR!fB z7zi$l<)fGln?wdTDp;XOj}^%&O4?zR2+o=t8mBA2HD8R8_18u?{Sy)Us+~Z5mfzL5 zfd0&6T4yrLGC(Feg~!f?s({Jy(^h1^{eSWEr=xq8=^CN+@4q=hKblmbw6b~(J^Q9- zN=j5Svfw!z;w8T2_%pNb-%X5}Pb)i%*8C@yAJs?ZGsIf@f9O7_XUBHTF2vwoEsp`= zmk`XOwI#bKK-l0m-+FpR=6%xCIwdHlD1(?Ea7L09KXsGTx)5y0`Fo3cCM;~c1iKK2 z3^jZmsr>sx=tHVJJ(`zpaKyi`Kd6+sGgX$bDdJ8?zYfzF+;_&>3y$IN45AkWuX>+{)*r79G+VdhIlSp%TXTQw48w*oNs}{uz9*e7!Eg81K^lYGHZP^5k6n{j z3@!NRH^m=VyrG@_=f)JaSn>wd8o|}H4d4Gi+0i!F7iYruHU=I@fN?mYAlu-wm<(4S zU8ZNSnbxnME`cx=jS@Y5BpBRZ8vik-x~`OgI+{n=EmzTfCdpeYC~Vg7<1e58)2Rxf z#*hl#&)AjJpYx>b3Zo@P@5;D=vX1}2GLNhhsJ>r}nBk1DQ=M$J#CMeDs8p4y!PM+} zAxC3o6Y`x#$%~XsJ0*8fu*dusP$}D6HM~gu?RN#-5>rOX;)#PMa7wj|i~5SKNbhGO0$_&-Q}V0P|bL|?4y^6y;q z=RYcA5ERiE$}T@msv7^`3a8*7Lav#e)W{RU3U71_^jv>Oo~(a;_@ahXczB6{j4BIZw zt_RhAjuG;>`hrVtL3u~AMKW86tc5qeVdcFp$Nl^~_62<=#FFx^Ab_n_w*c}zM1oMO zVga;1`k#j)i| z84mkr_E>y~A)L2Dx??aV_!t4*nMI}bV>4xvQR|G_Q1a^e1?lBfc}?pSnwh;T0q&=( z;>P05r8cGs66%i|&Yu7=ga3>P;!*|;DP$t|{=;&VRalmD+59?GhJX0DecY>Vp8l{DS>J@q{7wjkUzYX)MJsEQcKp}4S=-q z-^P@2OY^LXiKgM_;3f?sZK6mvVwo5KRCB5*B^taKwcRK=2mTQLr@^7w12j{a^>&uHuQwtruoe)nbC_As{FcWl}lh7ZtK% zi}!C1W-bWe%lZ%SiL-5W-H(Hk4-0Jw!%Kzz4{=!2fA_F;(_uj*`q_+7R%1Vd#I#i)AS($U#@di%K30k`8I43_chOWQsd2 zW7Q1yb)v#${?b(X1UJ!EAqa^8&Yu_rxX_*R$hqE8aLovj4UmlfGf$%v)4RpI^Q?M1 z-D+c+igVVoLnQ-zy=j=+Uu}wpcAIAYYHJz=m?bKxR&SkaM)b zVGF^u;oM`U!<0m51DmQ9*ZR{Y$!_RW^u?!}`v~FeJfW?-@vuoQ90<0SxTMwV>vv!1 zau!wVVSBMD)_7cZwyMS{U`ZoqByp1>gBE#vYRw93yYs@|**LN(EQhS7Y(=&K|Jk1h z1D_!J^#FAZo*G{8cOyYrj&*@jJCBdS z`Q^Kxe7O6=eVWEk!32|lc&d z+{MWi!U^bYws2QnD5KKHe(fM{Oq6(2$-U--=T%Z;k49<%pRgo0+kVe(x9SZpYxs;fDA}=obhVW6BGpB> zX8(2X%3DB8-aIVrYQBCHAA#sA@$g>($+x?dd+5G%sUb>}RdL*r(cs2F9tR4w>;!Cf z9M{A9e9u>U@+;ul#`Kk5gZsy?SiQ^nRQTv3Mb`fU5&svnmJH7@C9~(FLh{+XjRMcQ ztwiPSmf?+tmbmKNw$c>mi>n5$ra+WQ2FSfxvq|GnYa?NFd?vy5 z&7hYN%r{qOVXx^<3R_Z6GcCXQ1soki^Jfl5A|^$jw8p-bC+eb}G0w0x-;v6Re~?_& zpzo+qe*ejk7G9?d%ZI?dFHldQgeAmg^ifAd`-7NBc2lG+{zM#pfKAku;>~1}QyUQO zbBhJZp=!1wc^u+d2cudlIMC{yWIBpKGD|(KQ7AV#)%@yjhV zcYssD9>G#OKaj`K7#V4McQIJ3r{js%9TNaxz7bK&`{{;*yRZ8nuOw%c;5t1SyHUg` z#A@K`MogBTq)hR#dl~6qq1bI+tWnc9Fz(KUSkB~sbeR9fR;!4IC@^{?V_`dNIrPs` z3=NaKyY)B^;i;IP}^Wm z=PgfSdVGvl`uox~w(p+K;k60sd_JR&GUm>Rz@7=x!EH23il|iC496kwF;;S{SPm#) z$$MoXi6AJA4@*RQ3zv}sXOUaHb@$_2Bb>_=c9A+sZst19gOJjv(SwV8*y(hUf!5X! z=o(i`r0P8p{f;Dqvk$n46e4WG&pBuxuWKm6js7eKp8n{oKH$Su!Hs^{h>s}n`Ll_C z%LnT+L`7J;U6~&)-jEDdpQZ27t9G+j?GI5|bq6f|@w$LFM0Tkd1ID_2ga;fjKVT!0 ziIyNgF(7;XP!U)}C)pG{_9&qP89Y7R);k#a6r;x0f*)Bv-e(uka0S1%Sv{jxzrHAF zT+K~-nD{)+`6A#-@e}k+>M1K&bVTMtzYJs_Y}t;pBu)0VoUH{V{|;_Eq!8l% z!|^|#!8w`6k7gC9CSK`l=3W{exoG%YY`RXNYn9>>zQlP+u(@<^?qBx;+f-^meC!7q z#Llh*KJp1anRM!@&EL73u=|o`G|v+DD&6)F!k>N*N&tozk&{kzIqpfqI283 zO`e73{CNdAMEJKg1z-F@>AVIq4fEWy>aLAwsX&Bsf2S=RlRW$=kV~#emg6`}ZJ5{n z>y^jPMb)|A^*6X*e9FJ`>i7f*l`2uzb0g-fi~fw$qY@j!k=2%4eB$4;r_(wsqPc^4 zAbHfu<9o)vSbxEOKgbk#xA)_$E>Cytg7Kmo#&qw^@738HUta~A>JKmYIxPCo`RPZu zlfLghj4fU5EYh8GCb@NrOsN*c)jD(}4LI4j=b(#yXdracq7>y;KynTaQrJm;NG=z$ zow8htnjSoq#wBMnTW9whh>=f(iEn-*zCB%!i;TgI1Ain!SEe=e#P@Mq8@Cn_3p5KT znu0Cj{bKw=c2DsSbD%2{_GWg}M#hCk&iuCGSm6u8R>%66*yGqk9Ri(VviN0^p31H` zdc|xaIV@~YdXeTbbF;S2T0Oc#4m&UQ8;R0y99Z?`CP~TF&UPG~VR^ayb7HJ7OwI1> zKIa1P7uH7a;>awd?(BYbF>wdcxSHSB1zggHqnz)ZQ+T@|206d27p2e~qHW1Vtn?YH zt8W6sq6FJ)ZrsLeHOKnHSZB4D{pH7N1V3c7v?|@pEFD0VLdaC(I*jHXqSz*_efLB(;4U)!y+7;6fc z8{j@fxNw zA0buMA6V%NZ`i6Biqh5fV&z@glm#*}_CB*>M3Y$WdSi!6^LLv})m>4+xqB%pYO}=M zXbPA~9tc{go`_rZ#Sdb}@7^W1kB~cZUO!9Vpz~&jI14sjUcaoa6jPba5ZluyZZhq6 z&Ys5sv+<~^LV@Y=jegKHo94GSJiu|C$0ah$pRO>nGjEZFpr@aR2TpwLL6VN7>+RiZ z>>oE&E+kAX+l*fx2iLHz%s5KRw@612*o>DqwGBLV!7- zQid#uw#WjP7+FMk39X`pYB*I59ln19J?sTD(!#I8cc53o%?5T zHwQ!tzY<@N>4G|1SY5`}KDdu<9tPOvJJ3DBXjf{6AW7DP?!@HXI%^6>&;GP2%PipjI6wiH=Sq7~4&m1~16UOfi9!c+` zD*TQziDM@lAaJ}do)Q65tRAfB!Cjz+_)li>(iTb>-uQS%)GgLv9^p~Z z`Jtrl?gFxh_f(djE0)Wjw%7%ou?zRiS@blerRoZ|h(GLHlt^vF}B2jE^L!~|3MHnkJ43CR7 zx~gmF!F1R;Q$gQW=l;JIVBnG4_PjeG_YJea$Z@k@WiEGh>JB}izqs09PiJ*QdR{|e z`j4U4f*mpG%E%$FUgSA$3HKesvLt7mGDv8dVW^1n{km&u+kA zk~}dnRyY*TZX$^W^n?|q?;ez@q_Eu0W_brmU7aoqU1E_eum5~G{B*N767n+H6VHY%vfP>W&`%~efoXsB{4^*N!%N>N%BPOkoWMQsm%{{y`Bp$r0Psr zPKsHYd4Ynal2%Pny>))I{wVYOvwy=BZJ?mzO$13IWvg*Jz*1w9zFTO|W_vyqIefa| zO>B&K1PhUgBOQ&L8Qb4-)L3^1UWhYR-XpTH_+sW3B}~3j*jv9+W463=roKxvO{k3Z zNrz&=`62svfv4nfJK9~gdtDw{1hGZKdV@k!wgh!948<;uZ7G)4KfbbWGKoubXA1;K zni;aB*)%l12$$&e-3SsZI8Ys;VKvtL=Ilg_k|YnjHa3hB6QRT-dsy@GB(|Nrxl&8G z+Gk!gw4zXC7M+=(Dv|z2N_?#2nEYr=+pp;I+f&+>YyPg1n-ouR;MzbFVixZ9`^h`bmg|1S7e3HhD|?V);pokdxb& z-9vEkq9f0S^~oa7?>OJVmNx;w7r z0v&Jzl(3EN87cc`yHhi1t=}}-9wA@<{rtgkthd2qi`_owO|GY7-yFpr1;ps2G$=#BM7&?D zLhyKt@TOpm@@aQt0ZY$J%ZQ~v7;KeTuJmg^Gr?$41x`**-#Umyf1zH zDb6Y+w$u7gfQ((MQ4e9#q&tHlFn?T$Q4r4K7&c-}$qU*25TuH7$L2~t-!pe8ME zs8d{2?!|DtT-PeKsMe&$2&AlispV_sWD?lk;c4Hl@+VFtxbNP?^7aJ6Ge$d}QZ4rz zpzXrAd3gLE#|{JYAx+f0SZv0+oG(;VyJT`gQ^VX~as2(wSlsa<+Bzr-X~;NZqVCYt zgAcyyL(y-)8W&F)_*Q4ULR*Cv#r(DicqL~amB?s1M{thWKE)9gS^0)bjk46UkPHlN zWvPl#7+`_iG}Et56k;l2gd|~&0LS(ABPCz`bV&fBLuAsFTD+!-5584*WYl;}h$i{O zvm@UA|DG{$P*Yd)+3kSG!yKi;N}R`LQ)^vY`)j=|1oKfVO!RSZ6wJ%b~gJj9=&W?o;MqIuN>cHq?C4L zebfvuMn0(2gZCF&oxir{J>hC#*N3w@xD#7zqa5Muj&!utI#E9X0@@yJ(C}8oWZzw0 z1g|b3$UBSSUm`R5^{Pa`RKj_J^V?bPmH|&SFi9#LB^MH=52Cfm_S$R6>RPr{*c04R zte2xia0YZ8=`;WIZv=rJ$;QCDM634u5cv+KvMjvQ+FL%tZ7x5t0JJt1)$YhBwQT8f z*tM>jOqG2?wSuHP%c)^O$tBl9#Co4iOfD+dZhg7?b)))J3EL0M;ezK2@xSu$%(qxT8pzQZ7;=4zK9`9R8LdHRJ3LI}B}fpkRxf!loFx(^jAw3C_3ZXS{C!^2I%uZwFjKFd5It@k zHUW#vxJs@o8$#I7dAZ-XAsA;LBEspRa;<1s)Mf`r&Jv*$((w)f3dLqD%9mt}M1Cj(K<( zck;LJG|we_2er>QPYa#i?=XHD@@*d3!HeZyh>SM#j3ABJ{c9`RiciIJ1=hx{5bfv& zO%NkhN!V!W)EKPZa~18l(>{%i6 z-gvnK!Yx+72hkmvSZrgxgZRU>*|$%^KlCxiVc=p>AZ#J1JWijUE)*ENq(;?>J^>o$ z4(OW+TIZ6MRw-bLPCy@r<&t3~S#ruiQph^m zx_Sn3qBy>Hnj&dmzg;WVRMi&!{p8!&yk_o0n?4V;LLppRpXHrxDG~tAqNF&G__t3O zI+?}B*!cRsh>{6cN1h)u#e>?L{(&AaFbeMW>xFXRuLu@-;2yytu%3SW#3~m4Op~BA z?yFQ-jY|pl$=e_9{_G)0GrZNEvX;R@VNsaj%4+{wkWZdJPsn-+Io@a$?d-k%FFjnb z%#qMbJ#8MA;nV8X|B&QFaL_VP z@ZB(rj5$rrJtt7m_zpV{1(|Q}WXBj;0arvHqr@@Gie3n`AFFS0#c z4Mt{ZMM|Pe_PxBL9^ro=^RaO72euwVW#ARL;~DES4;+GgF8POYPLm|~ofAlY_qzUC z5qTn5_3(>uuAmEIYPttV{IJJUOrF;;sWi(sNn@xeME`kT$;54-HnnP9Ln=#%an z;b7Yt7xM{hsDFm?j2hupe`=wt_CaxvAv_5o>zbcC@l@JO<^7ndJHqO;CjNzNnrJQD zQ3Yvb5)Z9*qrc5o8a^^;)!q<%zP6k|9lJ^VWHFd>bc0WEA`QFaT!8t_OV=yi7&}vr zvcg(=72)}B=D^D3!|%hL^UaVJ%VWz`N5PdNhmKv?#uRLEDs!IPrcFuUgdW6E{Dn9Q zAMne2xX0Rq3mSk&qdC6mV_7vY7;o$MU-hQ#UP&?~1S|YPb~`enWMXAH&+?P6E0+d| zDy9H;GaD}!cY;mFzB31oWPE2g+h{UEtC!9y5bz&DUL45)FJMb#4?rriHjgf zCb-u70rg>Lx9@g;+jC*1jx=Q>#D*96<%a z5o%bLjUWS_4~}XI@iW!vTPM4QjBsN@?CN|+qIwUqn}_3Zo3_x4i$g20zKmh2n3)t6 z`nFnVY(-LERjUc-xV30yg%}1po6jj!?t)a-iYr5t--v6pCVAh_%ZajBKiei1kmgw*|vtSx`rXQRMz(!ibXWJlej~W&`8!gaImStPIwuRe7#!jB zu9{&wIY9eev2*Y5wSUlMj(ISN-MqgA>J#cg-SWlZ^8LqNML6b+{?Y^y2ME49;n1s% z#E!`aOeg#CXoD0$=1HoFYH5IHUYN4~E)f7Cxe<|JSx_Fir}%_p;dl~rxMP1_aKbxP zWZ-`&t19PCECB}-`Dl5m)al4Yr6Fl@q@k-|CPS3|c0uF+d?D0i(K3gn&p&%8{9rF! z`S#N0=g)S--eTvMp;kDfxUn2`^_v^0{8Og)mC*P>WOTT3Q~v6BkV&!i^5|TzE8~^L z!QV)j+s>;Zc4U;hqu3|>9>`8Lx;}8>A(}DnA~`2L zf;OVq7)6TKUT%QY_R(RR#bN$+dU~vZBWgIOg#t`>A)K)ig6Wh|6d^Y4bA(_S^en&({!) z$oP>L{cAL7XV4b>3g3adm5bUt(ETE;*xepj>4_up;vPt6dCN;B$>=^s%Tl2g)?>x_ z2#Yi|%(%y1(+U4jhDjlARHqQ>2zPu#W#Ea#gqk^Jr0dST;li4fKwP4VA7V?GIq^%S zX^0Vf80O-AeIJK0N?Wo7CUcBjaJAoQ`hZougQejdX{hq*pHOIv=NC$X0UQ2EV#1-e z6Y09Y%>msKFIZOgSR)d)M@=k^Z>tvZ z=`3G#|G=ua7evhYotW|6Y_&a!%@-Qox{FE=ex-{;y{GAf^$Lemg%5>_4Ih2$%QnflzP3{FcoM zebHNh7Z*u0p+^1jdQld-uIpUS7qU{PCzA1tbBpC#-bQ~ZM7_Ig&E^F$$-Mrt!|5!1 zV?c9+<0%55pXjR|w>Ugh@eAD^ANM z-gZI_y{$RWf@`#Ma-sW(`QXs0?W>^W(W5aHBWC3M-dC{XONGM_S*@wpm^q3c#>4)5 zT58nlF?Q}e5i0Gn>|mQK^NIs0wc`~Dl^8kylFC9J4@zq4>-?*RefERO-kcsB038Mg zagLpE=HQLAqL-~yYko@jX?Kom=!_QcWn;LZ`{DBR(&F(btQUT(^C7rWd{wHu1jlm1X#UB5sk;E{O;ouV{8UA?;BqYL-%AhUdf)O)@1qt|=yOck&l zQc`td-Mg8r48oJW_>c~>!s^zoidWgg=pwm?2+MteFUFvhG2Civk^THuq@cw!KU64z zdBwO{_P95!k}}cqX$k@RhE%2!V7~zb+}O**j!^>f51C0s4Z=J1@<#&y~k3YBno*AM4P6_^ezbgCkID_vrD4bK5Z(5FH zAVqw*47&1x4$hT>NZ(XP#QDvh%}m7C_ zO8u}%4W}9GnDugKf3K|{KP|*8F$OZ2maGdFer}<=t9Z-3jz&r{J1l#^eylv22s8+m z|0D7o{~-Us>v~a(^Z5G`2b{)Z9+pR^NYm$A(HFF_mFW%Os(dm_3*#Z)!*{!rWVGLp zrptq^{9<=Zf}}L4Ky!7`LFPB=7I`u=wt5#`ALR9Ilkb2hE57ICQ&vul=F5@ZTII)= zkC*7T-Gh8G>p}35Wqr8Q1N8oI=Rj|0fBl4!;7DE}NcJ|1>mOQx`kKAuj${-mp?4Qd zYb{yRj6WsT5DM2#xX{=Wuu6vi*IJ*dIho7(*A-*@ruuxwZ@mcZ0VD%HbXlyNMnG_+ zCe7_F<*;<@)2@oSWsLf^+k@&l59;{|YB?zjI$Px(n}eq{Sr4NFoXg{ogb&dy;>ya* zqLw>}gbuqvP2L|V+5~cjgPt{5p=-)tD3($CLd zL0Xf4(n&C>ejrZ&TZ29#Cs`Bfm7=bgNyF?-Kf~yH!z?m*mi{!DpZhm0OVaW?Y0_0n90HyWs78!=lgi)JecYsEsscg)9Ol-uta4Qy?3WHPTdb1{rZgJ_4?j7%Ig^h>C~}daJh$+ zPjehui?RI|eWG(F>zjTMKtXz(lCR-XQ!@fo3ognwLC@I=Slid5k{+ifBw`p1xO00!%d;cu3 zwmo7o)YhQ2;fy9p?5`5rBe36_3((W860N@4Lw|5T;@9pxpJzXYfmNdJSJv1aVRM4G z6Q!6i>NWDo*(tFkdv_S7tCMJ%3B+ScDLi%8zh#Q0-Bb92;DNb3ScU9bl?`oQwmTCm zD>gv)#hZqHa~_&HA0KhcX7)z}6e_SL7bSDE)y z;S8j(3L^~0xZ<`_!7L_BHa6iHabb6ES=TH#g}sOe$n{^8s)x?(Xe5vgYRCS21YB=H zOYRJv+F5SP6PJKiIf>Zl=q7mMuvqneTT-Ho z`9q2An<4+=#z~-xk>33*$M6DskTRdd`LpqG&Z83sXUKL^g`u^fV!uA$fg{d%lky@W z(F+�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 { } }; - return { pushFilters, }; From 4f3f13dc77446c18665b634c0d0615828d7b0958 Mon Sep 17 00:00:00 2001 From: maryia-lapata Date: Wed, 27 Nov 2019 18:22:29 +0300 Subject: [PATCH 115/132] Fix test --- .../kibana/public/visualize/wizard/new_vis_modal.test.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/legacy/core_plugins/kibana/public/visualize/wizard/new_vis_modal.test.tsx b/src/legacy/core_plugins/kibana/public/visualize/wizard/new_vis_modal.test.tsx index 929936ebe56ea..0dd2091bbfee0 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/wizard/new_vis_modal.test.tsx +++ b/src/legacy/core_plugins/kibana/public/visualize/wizard/new_vis_modal.test.tsx @@ -24,7 +24,6 @@ import { VisType } from '../legacy_imports'; import { TypesStart } from '../../../../visualizations/public/np_ready/public/types'; jest.mock('../legacy_imports', () => ({ - memoizeLast: jest.requireActual('ui/utils/memoize').memoizeLast, State: () => null, AppState: () => null, })); From 3d64613388ac6c31ca58ab80a0e904d324ccdacb Mon Sep 17 00:00:00 2001 From: maryia-lapata Date: Thu, 28 Nov 2019 17:14:38 +0300 Subject: [PATCH 116/132] Remove global_state_sync --- .../kibana/public/visualize/application.ts | 4 +- .../kibana/public/visualize/editor/editor.js | 2 +- .../visualize_embeddable_factory.tsx | 1 - .../public/visualize/global_state_sync.ts | 67 ------------------- .../kibana/public/visualize/legacy_app.js | 5 -- .../kibana/public/visualize/legacy_imports.ts | 1 - 6 files changed, 2 insertions(+), 78 deletions(-) delete mode 100644 src/legacy/core_plugins/kibana/public/visualize/global_state_sync.ts diff --git a/src/legacy/core_plugins/kibana/public/visualize/application.ts b/src/legacy/core_plugins/kibana/public/visualize/application.ts index d3217feedbae5..7684f982de7e0 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/application.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/application.ts @@ -60,9 +60,7 @@ export const renderApp = async ( initVisualizeApp(angularModuleInstance, deps); } const $injector = mountVisualizeApp(appBasePath, element); - return () => { - $injector.get('$rootScope').$destroy(); - }; + return () => $injector.get('$rootScope').$destroy(); }; const mainTemplate = (basePath: string) => `
diff --git a/src/legacy/core_plugins/kibana/public/visualize/editor/editor.js b/src/legacy/core_plugins/kibana/public/visualize/editor/editor.js index 5b6a738f9f40d..7b623017f31a2 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/editor/editor.js +++ b/src/legacy/core_plugins/kibana/public/visualize/editor/editor.js @@ -31,7 +31,7 @@ import { VisualizeConstants } from '../visualize_constants'; import { getEditBreadcrumbs } from '../breadcrumbs'; import { addHelpMenuToAppChrome } from '../help_menu/help_menu_util'; -import { FilterStateManager } from '../../../../data/public'; +import { FilterStateManager } from '../../../../data/public/filter/filter_manager'; import { initVisEditorDirective } from './visualization_editor'; import { initVisualizationDirective } from './visualization'; diff --git a/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable_factory.tsx b/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable_factory.tsx index e509a85910ee4..1d2c96ce5c420 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable_factory.tsx +++ b/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable_factory.tsx @@ -30,7 +30,6 @@ import 'uiExports/inspectorViews'; import 'uiExports/savedObjectTypes'; import 'uiExports/search'; import 'uiExports/shareContextMenuExtensions'; -import 'uiExports/visEditorTypes'; import 'uiExports/visTypes'; import 'uiExports/visualize'; diff --git a/src/legacy/core_plugins/kibana/public/visualize/global_state_sync.ts b/src/legacy/core_plugins/kibana/public/visualize/global_state_sync.ts deleted file mode 100644 index 8a733f940734b..0000000000000 --- a/src/legacy/core_plugins/kibana/public/visualize/global_state_sync.ts +++ /dev/null @@ -1,67 +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 { State } from './legacy_imports'; -import { DataPublicPluginStart as NpDataStart } from '../../../../../plugins/data/public'; - -/** - * Helper function to sync the global state with the various state providers - * when a local angular application mounts. There are three different ways - * global state can be passed into the application: - * * parameter in the URL hash - e.g. shared link - * * in-memory state in the data plugin exports (timefilter and filterManager) - e.g. default values - * - * This function looks up the three sources (earlier in the list means it takes precedence), - * puts it into the globalState object and syncs it with the url. - * - * Currently the legacy chrome takes care of restoring the global state when navigating from - * one app to another - to migrate away from that it will become necessary to also write the current - * state to local storage - */ -export function syncOnMount( - globalState: State, - { - query: { - filterManager, - timefilter: { timefilter }, - }, - }: NpDataStart -) { - // pull in global state information from the URL - globalState.fetch(); - // remember whether there were info in the URL - const hasGlobalURLState = Boolean(Object.keys(globalState.toObject()).length); - - // sync kibana platform state with the angular global state - if (!globalState.time) { - globalState.time = timefilter.getTime(); - } - if (!globalState.refreshInterval) { - globalState.refreshInterval = timefilter.getRefreshInterval(); - } - if (!globalState.filters && filterManager.getGlobalFilters().length > 0) { - globalState.filters = filterManager.getGlobalFilters(); - } - // only inject cross app global state if there is none in the url itself (that takes precedence) - if (hasGlobalURLState) { - // set flag the global state is set from the URL - globalState.$inheritedGlobalState = true; - } - globalState.save(); -} diff --git a/src/legacy/core_plugins/kibana/public/visualize/legacy_app.js b/src/legacy/core_plugins/kibana/public/visualize/legacy_app.js index 6a525ec5e9fbc..3b541ffcce267 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/legacy_app.js +++ b/src/legacy/core_plugins/kibana/public/visualize/legacy_app.js @@ -27,7 +27,6 @@ import { initVisualizeAppDirective } from './visualize_app'; import { VisualizeConstants } from './visualize_constants'; import { VisualizeListingController } from './listing/visualize_listing'; import { ensureDefaultIndexPattern, registerTimefilterWithGlobalStateFactory } from './legacy_imports'; -import { syncOnMount } from './global_state_sync'; import { getLandingBreadcrumbs, @@ -39,10 +38,6 @@ import { export function initVisualizeApp(app, deps) { initVisualizeAppDirective(app, deps); - app.run(globalState => { - syncOnMount(globalState, deps.npDataStart); - }); - app.run((globalState, $rootScope) => { registerTimefilterWithGlobalStateFactory( deps.npDataStart.query.timefilter.timefilter, diff --git a/src/legacy/core_plugins/kibana/public/visualize/legacy_imports.ts b/src/legacy/core_plugins/kibana/public/visualize/legacy_imports.ts index 7253ed2a25d20..8ceb98325fee0 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/legacy_imports.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/legacy_imports.ts @@ -28,7 +28,6 @@ import chrome from 'ui/chrome'; export const legacyChrome = chrome; -export { State } from 'ui/state_management/state'; // @ts-ignore export { AppState, AppStateProvider } from 'ui/state_management/app_state'; // @ts-ignore From aacd83bade4810e29d7037501dcd313e6eac0373 Mon Sep 17 00:00:00 2001 From: maryia-lapata Date: Thu, 28 Nov 2019 18:15:54 +0300 Subject: [PATCH 117/132] Refactoring --- .../core_plugins/kibana/public/visualize/editor/editor.js | 5 +++-- .../visualize/embeddable/visualize_embeddable_factory.tsx | 4 ++-- .../core_plugins/kibana/public/visualize/kibana_services.ts | 4 ---- .../core_plugins/kibana/public/visualize/legacy_imports.ts | 2 ++ src/legacy/core_plugins/kibana/public/visualize/types.d.ts | 2 +- 5 files changed, 8 insertions(+), 9 deletions(-) diff --git a/src/legacy/core_plugins/kibana/public/visualize/editor/editor.js b/src/legacy/core_plugins/kibana/public/visualize/editor/editor.js index 7b623017f31a2..a936a6ba01c20 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/editor/editor.js +++ b/src/legacy/core_plugins/kibana/public/visualize/editor/editor.js @@ -97,7 +97,7 @@ function VisualizeAppController( uiSettings, } = getServices(); - new FilterStateManager(globalState, getAppState, filterManager); + const filterStateManager = new FilterStateManager(globalState, getAppState, filterManager); const queryFilter = filterManager; // Retrieve the resolved SavedVis instance. const savedVis = $route.current.locals.savedVis; @@ -390,12 +390,13 @@ function VisualizeAppController( } })); - $scope.$on('$destroy', function () { + $scope.$on('$destroy', () => { if ($scope._handler) { $scope._handler.destroy(); } savedVis.destroy(); stateMonitor.destroy(); + filterStateManager.destroy(); subscriptions.unsubscribe(); }); diff --git a/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable_factory.tsx b/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable_factory.tsx index 1d2c96ce5c420..42100caf72960 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable_factory.tsx +++ b/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable_factory.tsx @@ -35,12 +35,12 @@ import 'uiExports/visualize'; import { i18n } from '@kbn/i18n'; -import { Legacy } from 'kibana'; - import { capabilities } from 'ui/capabilities'; import chrome from 'ui/chrome'; +import { Legacy } from 'kibana'; + import { SavedObjectAttributes } from 'kibana/server'; import { EmbeddableFactory, diff --git a/src/legacy/core_plugins/kibana/public/visualize/kibana_services.ts b/src/legacy/core_plugins/kibana/public/visualize/kibana_services.ts index bd1ad434db26d..8b3bccfc3eb04 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/kibana_services.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/kibana_services.ts @@ -77,7 +77,3 @@ export function getServices() { export function clearServices() { services = null; } - -// export types -export { VisSavedObject } from './embeddable/visualize_embeddable'; -export { EmbeddableFactory, ErrorEmbeddable } from '../../../../../plugins/embeddable/public'; diff --git a/src/legacy/core_plugins/kibana/public/visualize/legacy_imports.ts b/src/legacy/core_plugins/kibana/public/visualize/legacy_imports.ts index 8ceb98325fee0..8f918570350ce 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/legacy_imports.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/legacy_imports.ts @@ -72,3 +72,5 @@ export { KibanaParsedUrl } from 'ui/url/kibana_parsed_url'; export { defaultEditor } from 'ui/vis/editors/default/default'; export { VisType } from 'ui/vis'; export { wrapInI18nContext } from 'ui/i18n'; + +export { VisSavedObject } from './embeddable/visualize_embeddable'; diff --git a/src/legacy/core_plugins/kibana/public/visualize/types.d.ts b/src/legacy/core_plugins/kibana/public/visualize/types.d.ts index c83f7f5a5da8b..b6a3981215384 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/types.d.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/types.d.ts @@ -17,7 +17,7 @@ * under the License. */ -import { VisSavedObject } from './kibana_services'; +import { VisSavedObject } from './legacy_imports'; export interface SavedVisualizations { urlFor: (id: string) => string; From 2856b48dcba913f308a5220f405d690cb90b6871 Mon Sep 17 00:00:00 2001 From: maryia-lapata Date: Fri, 29 Nov 2019 16:30:31 +0300 Subject: [PATCH 118/132] Remove uiExports/embeddableFactories --- src/legacy/core_plugins/kibana/public/kibana.js | 1 - .../visualize/embeddable/visualize_embeddable_factory.tsx | 1 - src/legacy/ui/ui_exports/ui_export_defaults.js | 3 --- .../public/np_ready/public/legacy.ts | 1 - x-pack/legacy/plugins/canvas/public/app.js | 1 - .../plugins/dashboard_mode/public/dashboard_viewer.js | 1 - x-pack/legacy/plugins/maps/public/index.js | 1 - x-pack/legacy/plugins/siem/public/pages/home/index.tsx | 7 ------- 8 files changed, 16 deletions(-) diff --git a/src/legacy/core_plugins/kibana/public/kibana.js b/src/legacy/core_plugins/kibana/public/kibana.js index 0045819257018..dfd2f59655b46 100644 --- a/src/legacy/core_plugins/kibana/public/kibana.js +++ b/src/legacy/core_plugins/kibana/public/kibana.js @@ -37,7 +37,6 @@ import 'uiExports/contextMenuActions'; import 'uiExports/managementSections'; import 'uiExports/indexManagement'; import 'uiExports/docViews'; -import 'uiExports/embeddableFactories'; import 'uiExports/embeddableActions'; import 'uiExports/inspectorViews'; import 'uiExports/search'; diff --git a/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable_factory.tsx b/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable_factory.tsx index 42100caf72960..b173eed5717fa 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable_factory.tsx +++ b/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable_factory.tsx @@ -20,7 +20,6 @@ import 'uiExports/contextMenuActions'; import 'uiExports/devTools'; import 'uiExports/docViews'; -import 'uiExports/embeddableFactories'; import 'uiExports/embeddableActions'; import 'uiExports/fieldFormatEditors'; import 'uiExports/fieldFormats'; diff --git a/src/legacy/ui/ui_exports/ui_export_defaults.js b/src/legacy/ui/ui_exports/ui_export_defaults.js index 52d20f03e6c60..80bee41175771 100644 --- a/src/legacy/ui/ui_exports/ui_export_defaults.js +++ b/src/legacy/ui/ui_exports/ui_export_defaults.js @@ -50,9 +50,6 @@ export const UI_EXPORT_DEFAULTS = { fieldFormatEditors: [ 'ui/field_editor/components/field_format_editor/register' ], - embeddableFactories: [ - 'plugins/kibana/visualize/embeddable/visualize_embeddable_factory', - ], search: [ 'ui/courier/search_strategy/default_search_strategy', ], diff --git a/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/public/np_ready/public/legacy.ts b/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/public/np_ready/public/legacy.ts index a310403c86b5d..f38e37230ec48 100644 --- a/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/public/np_ready/public/legacy.ts +++ b/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/public/np_ready/public/legacy.ts @@ -18,7 +18,6 @@ */ /* eslint-disable @kbn/eslint/no-restricted-paths */ import 'ui/autoload/all'; -import 'uiExports/embeddableFactories'; import 'uiExports/embeddableActions'; import { npSetup, npStart } from 'ui/new_platform'; diff --git a/x-pack/legacy/plugins/canvas/public/app.js b/x-pack/legacy/plugins/canvas/public/app.js index bbcbac0cc1c52..c4621ea3a648f 100644 --- a/x-pack/legacy/plugins/canvas/public/app.js +++ b/x-pack/legacy/plugins/canvas/public/app.js @@ -20,7 +20,6 @@ import 'uiExports/visResponseHandlers'; import 'uiExports/visRequestHandlers'; import 'uiExports/savedObjectTypes'; import 'uiExports/spyModes'; -import 'uiExports/embeddableFactories'; import 'uiExports/interpreter'; // load application code diff --git a/x-pack/legacy/plugins/dashboard_mode/public/dashboard_viewer.js b/x-pack/legacy/plugins/dashboard_mode/public/dashboard_viewer.js index 8c6ddfebcb6cc..89466e1f6f9be 100644 --- a/x-pack/legacy/plugins/dashboard_mode/public/dashboard_viewer.js +++ b/x-pack/legacy/plugins/dashboard_mode/public/dashboard_viewer.js @@ -22,7 +22,6 @@ import 'uiExports/inspectorViews'; import 'uiExports/interpreter'; import 'uiExports/savedObjectTypes'; import 'uiExports/embeddableActions'; -import 'uiExports/embeddableFactories'; import 'uiExports/navbarExtensions'; import 'uiExports/docViews'; import 'uiExports/search'; diff --git a/x-pack/legacy/plugins/maps/public/index.js b/x-pack/legacy/plugins/maps/public/index.js index 964753f464d95..3aeba50867a8a 100644 --- a/x-pack/legacy/plugins/maps/public/index.js +++ b/x-pack/legacy/plugins/maps/public/index.js @@ -12,7 +12,6 @@ import { i18n } from '@kbn/i18n'; // import the uiExports that we want to "use" import 'uiExports/inspectorViews'; import 'uiExports/search'; -import 'uiExports/embeddableFactories'; import 'uiExports/embeddableActions'; import 'ui/agg_types'; diff --git a/x-pack/legacy/plugins/siem/public/pages/home/index.tsx b/x-pack/legacy/plugins/siem/public/pages/home/index.tsx index 2cc98930767dc..e3fe1a724b905 100644 --- a/x-pack/legacy/plugins/siem/public/pages/home/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/home/index.tsx @@ -31,13 +31,6 @@ import { Timelines } from '../timelines'; import { navTabs } from './home_navigations'; import { SiemPageName } from './types'; -/* - * This is import is important to keep because if we do not have it - * we will loose the map embeddable until they move to the New Platform - * we need to have it - */ -import 'uiExports/embeddableFactories'; - const WrappedByAutoSizer = styled.div` height: 100%; `; From 2a70e560d9d574e92e7d772becc0c8656ab99e7b Mon Sep 17 00:00:00 2001 From: maryia-lapata Date: Mon, 2 Dec 2019 11:05:44 +0300 Subject: [PATCH 119/132] Trigger digest cycle in local angular when vis is changed. --- .../core_plugins/kibana/public/visualize/editor/editor.js | 6 ++++++ .../public/visualize/embeddable/visualize_embeddable.ts | 2 ++ 2 files changed, 8 insertions(+) diff --git a/src/legacy/core_plugins/kibana/public/visualize/editor/editor.js b/src/legacy/core_plugins/kibana/public/visualize/editor/editor.js index a936a6ba01c20..17e5c2832cc93 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/editor/editor.js +++ b/src/legacy/core_plugins/kibana/public/visualize/editor/editor.js @@ -101,6 +101,11 @@ function VisualizeAppController( const queryFilter = filterManager; // Retrieve the resolved SavedVis instance. const savedVis = $route.current.locals.savedVis; + const _applyVis = () => { + $scope.$apply(); + }; + // This will trigger a digest cycle. This is needed when vis is updated from a global angular like in visualize_embeddable.js. + savedVis.vis.on('apply', _applyVis); // vis is instance of src/legacy/ui/public/vis/vis.js. // SearchSource is a promise-based stream of search results that can inherit from other search sources. const { vis, searchSource } = savedVis; @@ -398,6 +403,7 @@ function VisualizeAppController( stateMonitor.destroy(); filterStateManager.destroy(); subscriptions.unsubscribe(); + $scope.vis.off('apply', _applyVis); }); diff --git a/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable.ts b/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable.ts index a2b46dab1ef33..018244afb4745 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable.ts @@ -377,6 +377,8 @@ export class VisualizeEmbeddable extends Embeddable { From 172165ab5362bce824e7e10633a9035ef6623dc7 Mon Sep 17 00:00:00 2001 From: maryia-lapata Date: Mon, 2 Dec 2019 12:57:22 +0300 Subject: [PATCH 120/132] Fix TS --- .../kibana/public/visualize/kibana_services.ts | 9 ++++----- .../kibana/public/visualize/wizard/new_vis_modal.tsx | 4 ++-- .../kibana/public/visualize/wizard/show_new_vis.tsx | 4 ++-- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/src/legacy/core_plugins/kibana/public/visualize/kibana_services.ts b/src/legacy/core_plugins/kibana/public/visualize/kibana_services.ts index 8b3bccfc3eb04..9fa133764ba66 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/kibana_services.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/kibana_services.ts @@ -22,11 +22,10 @@ import { LegacyCoreStart, SavedObjectsClientContract, ToastsStart, - UiSettingsClientContract, + IUiSettingsClient, } from 'kibana/public'; -import { StaticIndexPattern } from 'plugins/data'; -import { DataStart } from '../../../data/public'; +import { DataStart, IndexPatterns } from '../../../data/public'; import { NavigationStart } from '../../../navigation/public'; import { Storage } from '../../../../../plugins/kibana_utils/public'; @@ -44,7 +43,7 @@ export interface VisualizeKibanaServices { editorTypes: any; embeddables: IEmbeddableStart; getBasePath: () => string; - indexPatterns: StaticIndexPattern[]; + indexPatterns: IndexPatterns; legacyChrome: any; localStorage: Storage; navigation: NavigationStart; @@ -55,7 +54,7 @@ export interface VisualizeKibanaServices { savedQueryService: NpDataStart['query']['savedQueries']; savedVisualizations: SavedVisualizations; share: SharePluginStart; - uiSettings: UiSettingsClientContract; + uiSettings: IUiSettingsClient; visualizeCapabilities: any; visualizations: VisualizationsStart; } diff --git a/src/legacy/core_plugins/kibana/public/visualize/wizard/new_vis_modal.tsx b/src/legacy/core_plugins/kibana/public/visualize/wizard/new_vis_modal.tsx index 62b6c5d612db0..0b46b562f2146 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/wizard/new_vis_modal.tsx +++ b/src/legacy/core_plugins/kibana/public/visualize/wizard/new_vis_modal.tsx @@ -22,7 +22,7 @@ import React from 'react'; import { EuiModal, EuiOverlayMask } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { UiSettingsClientContract } from 'kibana/public'; +import { IUiSettingsClient } from 'kibana/public'; import { VisType } from '../legacy_imports'; import { VisualizeConstants } from '../visualize_constants'; import { createUiStatsReporter, METRIC_TYPE } from '../../../../ui_metric/public'; @@ -36,7 +36,7 @@ interface TypeSelectionProps { visTypesRegistry: TypesStart; editorParams?: string[]; addBasePath: (path: string) => string; - uiSettings: UiSettingsClientContract; + uiSettings: IUiSettingsClient; } interface TypeSelectionState { diff --git a/src/legacy/core_plugins/kibana/public/visualize/wizard/show_new_vis.tsx b/src/legacy/core_plugins/kibana/public/visualize/wizard/show_new_vis.tsx index 48dcb88687231..92320f7bb443a 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/wizard/show_new_vis.tsx +++ b/src/legacy/core_plugins/kibana/public/visualize/wizard/show_new_vis.tsx @@ -21,7 +21,7 @@ import React from 'react'; import ReactDOM from 'react-dom'; import { I18nProvider } from '@kbn/i18n/react'; -import { UiSettingsClientContract } from 'kibana/public'; +import { IUiSettingsClient } from 'kibana/public'; import { NewVisModal } from './new_vis_modal'; import { TypesStart } from '../../../../visualizations/public/np_ready/public/types'; @@ -33,7 +33,7 @@ export function showNewVisModal( visTypeRegistry: TypesStart, { editorParams = [] }: ShowNewVisModalParams = {}, addBasePath: (path: string) => string, - uiSettings: UiSettingsClientContract + uiSettings: IUiSettingsClient ) { const container = document.createElement('div'); const onClose = () => { From ca0b9ad3ecbb37320374e78194883e306a18f623 Mon Sep 17 00:00:00 2001 From: maryia-lapata Date: Mon, 2 Dec 2019 14:42:58 +0300 Subject: [PATCH 121/132] Revert back syncOnMount --- .../public/visualize/global_state_sync.ts | 67 +++++++++++++++++++ .../kibana/public/visualize/legacy_app.js | 5 ++ 2 files changed, 72 insertions(+) create mode 100644 src/legacy/core_plugins/kibana/public/visualize/global_state_sync.ts diff --git a/src/legacy/core_plugins/kibana/public/visualize/global_state_sync.ts b/src/legacy/core_plugins/kibana/public/visualize/global_state_sync.ts new file mode 100644 index 0000000000000..8a733f940734b --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/visualize/global_state_sync.ts @@ -0,0 +1,67 @@ +/* + * 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 { State } from './legacy_imports'; +import { DataPublicPluginStart as NpDataStart } from '../../../../../plugins/data/public'; + +/** + * Helper function to sync the global state with the various state providers + * when a local angular application mounts. There are three different ways + * global state can be passed into the application: + * * parameter in the URL hash - e.g. shared link + * * in-memory state in the data plugin exports (timefilter and filterManager) - e.g. default values + * + * This function looks up the three sources (earlier in the list means it takes precedence), + * puts it into the globalState object and syncs it with the url. + * + * Currently the legacy chrome takes care of restoring the global state when navigating from + * one app to another - to migrate away from that it will become necessary to also write the current + * state to local storage + */ +export function syncOnMount( + globalState: State, + { + query: { + filterManager, + timefilter: { timefilter }, + }, + }: NpDataStart +) { + // pull in global state information from the URL + globalState.fetch(); + // remember whether there were info in the URL + const hasGlobalURLState = Boolean(Object.keys(globalState.toObject()).length); + + // sync kibana platform state with the angular global state + if (!globalState.time) { + globalState.time = timefilter.getTime(); + } + if (!globalState.refreshInterval) { + globalState.refreshInterval = timefilter.getRefreshInterval(); + } + if (!globalState.filters && filterManager.getGlobalFilters().length > 0) { + globalState.filters = filterManager.getGlobalFilters(); + } + // only inject cross app global state if there is none in the url itself (that takes precedence) + if (hasGlobalURLState) { + // set flag the global state is set from the URL + globalState.$inheritedGlobalState = true; + } + globalState.save(); +} diff --git a/src/legacy/core_plugins/kibana/public/visualize/legacy_app.js b/src/legacy/core_plugins/kibana/public/visualize/legacy_app.js index 3b541ffcce267..6a525ec5e9fbc 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/legacy_app.js +++ b/src/legacy/core_plugins/kibana/public/visualize/legacy_app.js @@ -27,6 +27,7 @@ import { initVisualizeAppDirective } from './visualize_app'; import { VisualizeConstants } from './visualize_constants'; import { VisualizeListingController } from './listing/visualize_listing'; import { ensureDefaultIndexPattern, registerTimefilterWithGlobalStateFactory } from './legacy_imports'; +import { syncOnMount } from './global_state_sync'; import { getLandingBreadcrumbs, @@ -38,6 +39,10 @@ import { export function initVisualizeApp(app, deps) { initVisualizeAppDirective(app, deps); + app.run(globalState => { + syncOnMount(globalState, deps.npDataStart); + }); + app.run((globalState, $rootScope) => { registerTimefilterWithGlobalStateFactory( deps.npDataStart.query.timefilter.timefilter, From d6161739820c966382440c4b34ba6a41dee56f13 Mon Sep 17 00:00:00 2001 From: maryia-lapata Date: Mon, 2 Dec 2019 16:02:20 +0300 Subject: [PATCH 122/132] Add missed import --- .../core_plugins/kibana/public/visualize/legacy_imports.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/legacy/core_plugins/kibana/public/visualize/legacy_imports.ts b/src/legacy/core_plugins/kibana/public/visualize/legacy_imports.ts index 8f918570350ce..6222c859d6315 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/legacy_imports.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/legacy_imports.ts @@ -30,6 +30,7 @@ export const legacyChrome = chrome; // @ts-ignore export { AppState, AppStateProvider } from 'ui/state_management/app_state'; +export { State } from 'ui/state_management/state'; // @ts-ignore export { GlobalStateProvider } from 'ui/state_management/global_state'; // @ts-ignore From ffbf8af4a5d6938614a02ae782945f2bb1e12835 Mon Sep 17 00:00:00 2001 From: maryia-lapata Date: Mon, 2 Dec 2019 16:59:14 +0300 Subject: [PATCH 123/132] Revert import 'uiExports/embeddableFactories' --- src/legacy/core_plugins/kibana/public/kibana.js | 1 + .../public/np_ready/public/legacy.ts | 1 + x-pack/legacy/plugins/canvas/public/app.js | 1 + .../plugins/dashboard_mode/public/dashboard_viewer.js | 1 + x-pack/legacy/plugins/maps/public/index.js | 1 + x-pack/legacy/plugins/siem/public/pages/home/index.tsx | 7 +++++++ 6 files changed, 12 insertions(+) diff --git a/src/legacy/core_plugins/kibana/public/kibana.js b/src/legacy/core_plugins/kibana/public/kibana.js index dfd2f59655b46..0045819257018 100644 --- a/src/legacy/core_plugins/kibana/public/kibana.js +++ b/src/legacy/core_plugins/kibana/public/kibana.js @@ -37,6 +37,7 @@ import 'uiExports/contextMenuActions'; import 'uiExports/managementSections'; import 'uiExports/indexManagement'; import 'uiExports/docViews'; +import 'uiExports/embeddableFactories'; import 'uiExports/embeddableActions'; import 'uiExports/inspectorViews'; import 'uiExports/search'; diff --git a/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/public/np_ready/public/legacy.ts b/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/public/np_ready/public/legacy.ts index f38e37230ec48..a310403c86b5d 100644 --- a/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/public/np_ready/public/legacy.ts +++ b/test/plugin_functional/plugins/kbn_tp_embeddable_explorer/public/np_ready/public/legacy.ts @@ -18,6 +18,7 @@ */ /* eslint-disable @kbn/eslint/no-restricted-paths */ import 'ui/autoload/all'; +import 'uiExports/embeddableFactories'; import 'uiExports/embeddableActions'; import { npSetup, npStart } from 'ui/new_platform'; diff --git a/x-pack/legacy/plugins/canvas/public/app.js b/x-pack/legacy/plugins/canvas/public/app.js index c4621ea3a648f..bbcbac0cc1c52 100644 --- a/x-pack/legacy/plugins/canvas/public/app.js +++ b/x-pack/legacy/plugins/canvas/public/app.js @@ -20,6 +20,7 @@ import 'uiExports/visResponseHandlers'; import 'uiExports/visRequestHandlers'; import 'uiExports/savedObjectTypes'; import 'uiExports/spyModes'; +import 'uiExports/embeddableFactories'; import 'uiExports/interpreter'; // load application code diff --git a/x-pack/legacy/plugins/dashboard_mode/public/dashboard_viewer.js b/x-pack/legacy/plugins/dashboard_mode/public/dashboard_viewer.js index 89466e1f6f9be..8c6ddfebcb6cc 100644 --- a/x-pack/legacy/plugins/dashboard_mode/public/dashboard_viewer.js +++ b/x-pack/legacy/plugins/dashboard_mode/public/dashboard_viewer.js @@ -22,6 +22,7 @@ import 'uiExports/inspectorViews'; import 'uiExports/interpreter'; import 'uiExports/savedObjectTypes'; import 'uiExports/embeddableActions'; +import 'uiExports/embeddableFactories'; import 'uiExports/navbarExtensions'; import 'uiExports/docViews'; import 'uiExports/search'; diff --git a/x-pack/legacy/plugins/maps/public/index.js b/x-pack/legacy/plugins/maps/public/index.js index 3aeba50867a8a..964753f464d95 100644 --- a/x-pack/legacy/plugins/maps/public/index.js +++ b/x-pack/legacy/plugins/maps/public/index.js @@ -12,6 +12,7 @@ import { i18n } from '@kbn/i18n'; // import the uiExports that we want to "use" import 'uiExports/inspectorViews'; import 'uiExports/search'; +import 'uiExports/embeddableFactories'; import 'uiExports/embeddableActions'; import 'ui/agg_types'; diff --git a/x-pack/legacy/plugins/siem/public/pages/home/index.tsx b/x-pack/legacy/plugins/siem/public/pages/home/index.tsx index e3fe1a724b905..2cc98930767dc 100644 --- a/x-pack/legacy/plugins/siem/public/pages/home/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/home/index.tsx @@ -31,6 +31,13 @@ import { Timelines } from '../timelines'; import { navTabs } from './home_navigations'; import { SiemPageName } from './types'; +/* + * This is import is important to keep because if we do not have it + * we will loose the map embeddable until they move to the New Platform + * we need to have it + */ +import 'uiExports/embeddableFactories'; + const WrappedByAutoSizer = styled.div` height: 100%; `; From 5b34d1c4d70d63589692f5d90d74f5a928c9cf8a Mon Sep 17 00:00:00 2001 From: maryia-lapata Date: Tue, 3 Dec 2019 12:45:31 +0300 Subject: [PATCH 124/132] Update app navigation func test --- .../plugin_functional/test_suites/app_plugins/app_navigation.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/plugin_functional/test_suites/app_plugins/app_navigation.js b/test/plugin_functional/test_suites/app_plugins/app_navigation.js index a6c5b9542f568..325ff335169d1 100644 --- a/test/plugin_functional/test_suites/app_plugins/app_navigation.js +++ b/test/plugin_functional/test_suites/app_plugins/app_navigation.js @@ -22,7 +22,7 @@ import expect from '@kbn/expect'; export default function ({ getService, getPageObjects }) { const appsMenu = getService('appsMenu'); const testSubjects = getService('testSubjects'); - const PageObjects = getPageObjects(['common', 'header', 'home']); + const PageObjects = getPageObjects(['common', 'header']); describe('app navigation', function describeIndexTests() { From 26eb210ee159d7d8d12b6e9c2b29dd02321c3fc3 Mon Sep 17 00:00:00 2001 From: maryia-lapata Date: Tue, 3 Dec 2019 13:18:23 +0300 Subject: [PATCH 125/132] Update app navigation func test --- .../plugin_functional/test_suites/app_plugins/app_navigation.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/plugin_functional/test_suites/app_plugins/app_navigation.js b/test/plugin_functional/test_suites/app_plugins/app_navigation.js index 325ff335169d1..6b1d5868f69e9 100644 --- a/test/plugin_functional/test_suites/app_plugins/app_navigation.js +++ b/test/plugin_functional/test_suites/app_plugins/app_navigation.js @@ -22,7 +22,7 @@ import expect from '@kbn/expect'; export default function ({ getService, getPageObjects }) { const appsMenu = getService('appsMenu'); const testSubjects = getService('testSubjects'); - const PageObjects = getPageObjects(['common', 'header']); + const PageObjects = getPageObjects(['common', 'header', 'settings']); describe('app navigation', function describeIndexTests() { From 843fbe314552ea27e34e3d27e8e328e3b42062ad Mon Sep 17 00:00:00 2001 From: maryia-lapata Date: Tue, 3 Dec 2019 14:00:26 +0300 Subject: [PATCH 126/132] Update app navigation func test --- .../test_suites/app_plugins/app_navigation.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/plugin_functional/test_suites/app_plugins/app_navigation.js b/test/plugin_functional/test_suites/app_plugins/app_navigation.js index 6b1d5868f69e9..a7706ed92feab 100644 --- a/test/plugin_functional/test_suites/app_plugins/app_navigation.js +++ b/test/plugin_functional/test_suites/app_plugins/app_navigation.js @@ -22,12 +22,12 @@ import expect from '@kbn/expect'; export default function ({ getService, getPageObjects }) { const appsMenu = getService('appsMenu'); const testSubjects = getService('testSubjects'); - const PageObjects = getPageObjects(['common', 'header', 'settings']); + const PageObjects = getPageObjects(['common', 'header', 'home']); describe('app navigation', function describeIndexTests() { before(async () => { - await PageObjects.common.navigateToApp('settings'); + await PageObjects.common.navigateToApp('home'); }); it('should show nav link that navigates to the app', async () => { From d58434bd53a322af286d26bb4c0f66c29ea4f11d Mon Sep 17 00:00:00 2001 From: maryia-lapata Date: Tue, 3 Dec 2019 21:15:54 +0300 Subject: [PATCH 127/132] Remove 'kibana-install-dir' arg in pluginFunctionalTestsRelease --- tasks/config/run.js | 1 - .../plugin_functional/test_suites/app_plugins/app_navigation.js | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/tasks/config/run.js b/tasks/config/run.js index e4071c8b7d0ab..97a0f381f2aa4 100644 --- a/tasks/config/run.js +++ b/tasks/config/run.js @@ -269,7 +269,6 @@ module.exports = function (grunt) { '--config', 'test/plugin_functional/config.js', '--bail', '--debug', - '--kibana-install-dir', KIBANA_INSTALL_DIR, ], }), diff --git a/test/plugin_functional/test_suites/app_plugins/app_navigation.js b/test/plugin_functional/test_suites/app_plugins/app_navigation.js index a7706ed92feab..a6c5b9542f568 100644 --- a/test/plugin_functional/test_suites/app_plugins/app_navigation.js +++ b/test/plugin_functional/test_suites/app_plugins/app_navigation.js @@ -27,7 +27,7 @@ export default function ({ getService, getPageObjects }) { describe('app navigation', function describeIndexTests() { before(async () => { - await PageObjects.common.navigateToApp('home'); + await PageObjects.common.navigateToApp('settings'); }); it('should show nav link that navigates to the app', async () => { From 5c82113da227bd50a0971861e44c49ff5a37ed9a Mon Sep 17 00:00:00 2001 From: maryia-lapata Date: Fri, 6 Dec 2019 03:03:49 +0300 Subject: [PATCH 128/132] Fix review comments --- src/legacy/core_plugins/kibana/public/visualize/index.ts | 2 +- .../core_plugins/kibana/public/visualize/plugin.ts | 9 +++------ 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/src/legacy/core_plugins/kibana/public/visualize/index.ts b/src/legacy/core_plugins/kibana/public/visualize/index.ts index e59f3a4dbe75b..5ccd6a86f433b 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/index.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/index.ts @@ -54,7 +54,7 @@ async function getAngularDependencies(): Promise { +(() => { const instance = new VisualizePlugin(); instance.setup(npSetup.core, { ...npSetup.plugins, diff --git a/src/legacy/core_plugins/kibana/public/visualize/plugin.ts b/src/legacy/core_plugins/kibana/public/visualize/plugin.ts index c1d86367f48b3..a78c84a5cd3ca 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/plugin.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/plugin.ts @@ -20,7 +20,6 @@ import { i18n } from '@kbn/i18n'; import { - App, CoreSetup, CoreStart, LegacyCoreStart, @@ -86,8 +85,8 @@ export class VisualizePlugin implements Plugin { core: CoreSetup, { home, kibana_legacy, __LEGACY: { getAngularDependencies } }: VisualizePluginSetupDependencies ) { - const app: App = { - id: '', + kibana_legacy.registerLegacyApp({ + id: 'visualize', title: 'Visualize', mount: async ({ core: contextCore }, params) => { if (this.startDependencies === null) { @@ -130,9 +129,7 @@ export class VisualizePlugin implements Plugin { const { renderApp } = await import('./application'); return renderApp(params.element, params.appBasePath, deps); }, - }; - - kibana_legacy.registerLegacyApp({ ...app, id: 'visualize' }); + }); home.featureCatalogue.register({ id: 'visualize', From 3bd47afdb21074515bd1c4e9727abf7f8283540e Mon Sep 17 00:00:00 2001 From: maryia-lapata Date: Mon, 9 Dec 2019 18:19:29 +0300 Subject: [PATCH 129/132] Fix code review comments --- .../kibana/public/visualize/editor/editor.js | 2 +- .../visualize/embeddable/get_index_pattern.ts | 14 ++++----- .../visualize_embeddable_factory.tsx | 13 ++++---- .../kibana/public/visualize/index.ts | 4 +-- .../public/visualize/kibana_services.ts | 9 ++---- .../kibana/public/visualize/legacy_app.js | 16 +++++----- .../visualize/listing/visualize_listing.js | 2 +- .../kibana/public/visualize/plugin.ts | 31 ++++++------------- 8 files changed, 35 insertions(+), 56 deletions(-) diff --git a/src/legacy/core_plugins/kibana/public/visualize/editor/editor.js b/src/legacy/core_plugins/kibana/public/visualize/editor/editor.js index 7531fece2f6f7..620703a62031d 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/editor/editor.js +++ b/src/legacy/core_plugins/kibana/public/visualize/editor/editor.js @@ -82,7 +82,7 @@ function VisualizeAppController( localStorage, visualizeCapabilities, share, - npDataStart: { + data: { query: { filterManager, timefilter: { timefilter }, diff --git a/src/legacy/core_plugins/kibana/public/visualize/embeddable/get_index_pattern.ts b/src/legacy/core_plugins/kibana/public/visualize/embeddable/get_index_pattern.ts index 4524ae0b3816d..7fe3678bb1f77 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/embeddable/get_index_pattern.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/embeddable/get_index_pattern.ts @@ -17,11 +17,10 @@ * under the License. */ -import chrome from 'ui/chrome'; -import { getFromSavedObject } from 'ui/index_patterns'; +import { npStart } from 'ui/new_platform'; import { VisSavedObject } from './visualize_embeddable'; -import { IIndexPattern } from '../../../../../../plugins/data/public'; +import { indexPatterns, IIndexPattern } from '../../../../../../plugins/data/public'; export async function getIndexPattern( savedVis: VisSavedObject @@ -30,9 +29,8 @@ export async function getIndexPattern( return savedVis.vis.indexPattern; } - const config = chrome.getUiSettingsClient(); - const savedObjectsClient = chrome.getSavedObjectsClient(); - const defaultIndex = config.get('defaultIndex'); + const savedObjectsClient = npStart.core.savedObjects.client; + const defaultIndex = npStart.core.uiSettings.get('defaultIndex'); if (savedVis.vis.params.index_pattern) { const indexPatternObjects = await savedObjectsClient.find({ @@ -41,10 +39,10 @@ export async function getIndexPattern( search: `"${savedVis.vis.params.index_pattern}"`, searchFields: ['title'], }); - const [indexPattern] = indexPatternObjects.savedObjects.map(getFromSavedObject); + const [indexPattern] = indexPatternObjects.savedObjects.map(indexPatterns.getFromSavedObject); return indexPattern; } const savedObject = await savedObjectsClient.get('index-pattern', defaultIndex); - return getFromSavedObject(savedObject); + return indexPatterns.getFromSavedObject(savedObject); } diff --git a/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable_factory.tsx b/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable_factory.tsx index b173eed5717fa..7c9efa280c9f1 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable_factory.tsx +++ b/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable_factory.tsx @@ -34,9 +34,8 @@ import 'uiExports/visualize'; import { i18n } from '@kbn/i18n'; -import { capabilities } from 'ui/capabilities'; - import chrome from 'ui/chrome'; +import { npStart } from 'ui/new_platform'; import { Legacy } from 'kibana'; @@ -109,7 +108,7 @@ export class VisualizeEmbeddableFactory extends EmbeddableFactory< if (!visType) { return false; } - if (chrome.getUiSettingsClient().get('visualize:enableLabs')) { + if (npStart.core.uiSettings.get('visualize:enableLabs')) { return true; } return visType.stage !== 'experimental'; @@ -121,7 +120,7 @@ export class VisualizeEmbeddableFactory extends EmbeddableFactory< } public isEditable() { - return capabilities.get().visualize.save as boolean; + return npStart.core.application.capabilities.visualize.save as boolean; } public getDisplayName() { @@ -143,7 +142,7 @@ export class VisualizeEmbeddableFactory extends EmbeddableFactory< const visId = savedObject.id as string; const editUrl = visId - ? chrome.addBasePath(`/app/kibana${savedVisualizations.urlFor(visId)}`) + ? npStart.core.http.basePath.prepend(`/app/kibana${savedVisualizations.urlFor(visId)}`) : ''; const isLabsEnabled = config.get('visualize:enableLabs'); @@ -199,8 +198,8 @@ export class VisualizeEmbeddableFactory extends EmbeddableFactory< { editorParams: ['addToDashboard'], }, - chrome.addBasePath, - chrome.getUiSettingsClient() + npStart.core.http.basePath.prepend, + npStart.core.uiSettings ); } return undefined; diff --git a/src/legacy/core_plugins/kibana/public/visualize/index.ts b/src/legacy/core_plugins/kibana/public/visualize/index.ts index 5ccd6a86f433b..d1dd1358ebd31 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/index.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/index.ts @@ -29,7 +29,6 @@ import { VisEditorTypesRegistryProvider, } from './legacy_imports'; import { VisualizePlugin, LegacyAngularInjectedDependencies } from './plugin'; -import { start as data } from '../../../data/public/legacy'; import { start as embeddables } from '../../../embeddable_api/public/np_ready/public/legacy'; import { start as navigation } from '../../../navigation/public/legacy'; import { start as visualizations } from '../../../visualizations/public/np_ready/public/legacy'; @@ -64,8 +63,7 @@ async function getAngularDependencies(): Promise string; chrome: ChromeStart; core: LegacyCoreStart; - dataStart: DataStart; + data: DataPublicPluginStart; editorTypes: any; embeddables: IEmbeddableStart; getBasePath: () => string; @@ -47,11 +45,10 @@ export interface VisualizeKibanaServices { legacyChrome: any; localStorage: Storage; navigation: NavigationStart; - npDataStart: NpDataStart; toastNotifications: ToastsStart; savedObjectsClient: SavedObjectsClientContract; savedObjectRegistry: any; - savedQueryService: NpDataStart['query']['savedQueries']; + savedQueryService: DataPublicPluginStart['query']['savedQueries']; savedVisualizations: SavedVisualizations; share: SharePluginStart; uiSettings: IUiSettingsClient; diff --git a/src/legacy/core_plugins/kibana/public/visualize/legacy_app.js b/src/legacy/core_plugins/kibana/public/visualize/legacy_app.js index 6a525ec5e9fbc..f47552e99a5c7 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/legacy_app.js +++ b/src/legacy/core_plugins/kibana/public/visualize/legacy_app.js @@ -40,12 +40,12 @@ export function initVisualizeApp(app, deps) { initVisualizeAppDirective(app, deps); app.run(globalState => { - syncOnMount(globalState, deps.npDataStart); + syncOnMount(globalState, deps.data); }); app.run((globalState, $rootScope) => { registerTimefilterWithGlobalStateFactory( - deps.npDataStart.query.timefilter.timefilter, + deps.data.query.timefilter.timefilter, globalState, $rootScope ); @@ -81,7 +81,7 @@ export function initVisualizeApp(app, deps) { controllerAs: 'listingController', resolve: { createNewVis: () => false, - hasDefaultIndex: ($rootScope, kbnUrl) => ensureDefaultIndexPattern(deps.core, deps.dataStart, $rootScope, kbnUrl), + hasDefaultIndex: ($rootScope, kbnUrl) => ensureDefaultIndexPattern(deps.core, deps.data, $rootScope, kbnUrl), }, }) .when(VisualizeConstants.WIZARD_STEP_1_PAGE_PATH, { @@ -92,7 +92,7 @@ export function initVisualizeApp(app, deps) { controllerAs: 'listingController', resolve: { createNewVis: () => true, - hasDefaultIndex: ($rootScope, kbnUrl) => ensureDefaultIndexPattern(deps.core, deps.dataStart, $rootScope, kbnUrl), + hasDefaultIndex: ($rootScope, kbnUrl) => ensureDefaultIndexPattern(deps.core, deps.data, $rootScope, kbnUrl), }, }) .when(VisualizeConstants.CREATE_PATH, { @@ -101,7 +101,7 @@ export function initVisualizeApp(app, deps) { k7Breadcrumbs: getCreateBreadcrumbs, resolve: { savedVis: function (redirectWhenMissing, $route, $rootScope, kbnUrl) { - const { core, dataStart, savedVisualizations, visualizations } = deps; + const { core, data, savedVisualizations, visualizations } = deps; const visTypes = visualizations.types.all(); const visType = find(visTypes, { name: $route.current.params.type }); const shouldHaveIndex = visType.requiresSearch && visType.options.showIndexSelection; @@ -114,7 +114,7 @@ export function initVisualizeApp(app, deps) { ); } - return ensureDefaultIndexPattern(core, dataStart, $rootScope, kbnUrl).then(() => savedVisualizations.get($route.current.params)) + return ensureDefaultIndexPattern(core, data, $rootScope, kbnUrl).then(() => savedVisualizations.get($route.current.params)) .then(savedVis => { if (savedVis.vis.type.setup) { return savedVis.vis.type.setup(savedVis) @@ -134,8 +134,8 @@ export function initVisualizeApp(app, deps) { k7Breadcrumbs: getEditBreadcrumbs, resolve: { savedVis: function (redirectWhenMissing, $route, $rootScope, kbnUrl) { - const { chrome, core, dataStart, savedVisualizations } = deps; - return ensureDefaultIndexPattern(core, dataStart, $rootScope, kbnUrl) + const { chrome, core, data, savedVisualizations } = deps; + return ensureDefaultIndexPattern(core, data, $rootScope, kbnUrl) .then(() => savedVisualizations.get($route.current.params.id)) .then(savedVis => { chrome.recentlyAccessed.add( diff --git a/src/legacy/core_plugins/kibana/public/visualize/listing/visualize_listing.js b/src/legacy/core_plugins/kibana/public/visualize/listing/visualize_listing.js index 7171540e8f8be..9b02be0581b8d 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/listing/visualize_listing.js +++ b/src/legacy/core_plugins/kibana/public/visualize/listing/visualize_listing.js @@ -46,7 +46,7 @@ export function VisualizeListingController($injector, createNewVis) { legacyChrome, savedObjectRegistry, savedObjectsClient, - npDataStart: { + data: { query: { timefilter: { timefilter }, }, diff --git a/src/legacy/core_plugins/kibana/public/visualize/plugin.ts b/src/legacy/core_plugins/kibana/public/visualize/plugin.ts index a78c84a5cd3ca..943bce7986c8c 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/plugin.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/plugin.ts @@ -28,8 +28,7 @@ import { } from 'kibana/public'; import { Storage } from '../../../../../plugins/kibana_utils/public'; -import { DataStart } from '../../../data/public'; -import { DataPublicPluginStart as NpDataStart } from '../../../../../plugins/data/public'; +import { DataPublicPluginStart } from '../../../../../plugins/data/public'; import { IEmbeddableStart } from '../../../../../plugins/embeddable/public'; import { NavigationStart } from '../../../navigation/public'; import { SharePluginStart } from '../../../../../plugins/share/public'; @@ -54,8 +53,7 @@ export interface LegacyAngularInjectedDependencies { } export interface VisualizePluginStartDependencies { - data: DataStart; - npData: NpDataStart; + data: DataPublicPluginStart; embeddables: IEmbeddableStart; navigation: NavigationStart; share: SharePluginStart; @@ -72,8 +70,7 @@ export interface VisualizePluginSetupDependencies { export class VisualizePlugin implements Plugin { private startDependencies: { - dataStart: DataStart; - npDataStart: NpDataStart; + data: DataPublicPluginStart; embeddables: IEmbeddableStart; navigation: NavigationStart; savedObjectsClient: SavedObjectsClientContract; @@ -94,12 +91,11 @@ export class VisualizePlugin implements Plugin { } const { - dataStart, savedObjectsClient, embeddables, navigation, visualizations, - npDataStart, + data, share, } = this.startDependencies; @@ -109,15 +105,14 @@ export class VisualizePlugin implements Plugin { addBasePath: contextCore.http.basePath.prepend, core: contextCore as LegacyCoreStart, chrome: contextCore.chrome, - dataStart, + data, embeddables, getBasePath: core.http.basePath.get, - indexPatterns: dataStart.indexPatterns.indexPatterns, + indexPatterns: data.indexPatterns.indexPatterns, localStorage: new Storage(localStorage), navigation, - npDataStart, savedObjectsClient, - savedQueryService: npDataStart.query.savedQueries, + savedQueryService: data.query.savedQueries, share, toastNotifications: contextCore.notifications.toasts, uiSettings: contextCore.uiSettings, @@ -149,18 +144,10 @@ export class VisualizePlugin implements Plugin { public start( { savedObjects: { client: savedObjectsClient } }: CoreStart, - { - data: dataStart, - embeddables, - navigation, - npData, - share, - visualizations, - }: VisualizePluginStartDependencies + { embeddables, navigation, data, share, visualizations }: VisualizePluginStartDependencies ) { this.startDependencies = { - dataStart, - npDataStart: npData, + data, embeddables, navigation, savedObjectsClient, From b09c332bc7020d1ea2902756e184d607559502d8 Mon Sep 17 00:00:00 2001 From: maryia-lapata Date: Mon, 9 Dec 2019 18:28:12 +0300 Subject: [PATCH 130/132] Rename alias --- .../core_plugins/kibana/public/visualize/global_state_sync.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/legacy/core_plugins/kibana/public/visualize/global_state_sync.ts b/src/legacy/core_plugins/kibana/public/visualize/global_state_sync.ts index 8a733f940734b..71156bc38d498 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/global_state_sync.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/global_state_sync.ts @@ -18,7 +18,7 @@ */ import { State } from './legacy_imports'; -import { DataPublicPluginStart as NpDataStart } from '../../../../../plugins/data/public'; +import { DataPublicPluginStart as DataStart } from '../../../../../plugins/data/public'; /** * Helper function to sync the global state with the various state providers @@ -41,7 +41,7 @@ export function syncOnMount( filterManager, timefilter: { timefilter }, }, - }: NpDataStart + }: DataStart ) { // pull in global state information from the URL globalState.fetch(); From 51e5691e9869badf8047de7c9b92cae945d01683 Mon Sep 17 00:00:00 2001 From: maryia-lapata Date: Mon, 9 Dec 2019 19:39:57 +0300 Subject: [PATCH 131/132] Fix indexPatterns --- src/legacy/core_plugins/kibana/public/visualize/plugin.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/legacy/core_plugins/kibana/public/visualize/plugin.ts b/src/legacy/core_plugins/kibana/public/visualize/plugin.ts index 943bce7986c8c..1aa2d70dabea6 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/plugin.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/plugin.ts @@ -108,7 +108,7 @@ export class VisualizePlugin implements Plugin { data, embeddables, getBasePath: core.http.basePath.get, - indexPatterns: data.indexPatterns.indexPatterns, + indexPatterns: data.indexPatterns, localStorage: new Storage(localStorage), navigation, savedObjectsClient, From 9016c3ca37b477f75cfc3d2e10a9dc91df59fc59 Mon Sep 17 00:00:00 2001 From: maryia-lapata Date: Tue, 10 Dec 2019 10:51:36 +0300 Subject: [PATCH 132/132] Use IndexPatternsContract interface --- src/legacy/core_plugins/kibana/public/visualize/index.ts | 1 - .../core_plugins/kibana/public/visualize/kibana_services.ts | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/legacy/core_plugins/kibana/public/visualize/index.ts b/src/legacy/core_plugins/kibana/public/visualize/index.ts index d1dd1358ebd31..5e9f2fdeb8999 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/index.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/index.ts @@ -63,7 +63,6 @@ async function getAngularDependencies(): Promise string; - indexPatterns: IndexPatterns; + indexPatterns: IndexPatternsContract; legacyChrome: any; localStorage: Storage; navigation: NavigationStart;

1j*;k$iW5JwC1;^#{X`htGeBVM9jI*^!R@G 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 `aTi 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 VIUo$(uEYUISzcYPSjOc2{{xjj8>IjM literal 0 HcmV?d00001 diff --git a/src/legacy/core_plugins/kibana/public/home/tutorial_resources/logos/cockroachdb.svg b/src/legacy/core_plugins/kibana/public/home/tutorial_resources/logos/cockroachdb.svg new file mode 100644 index 0000000000000..72f0958f52824 --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/home/tutorial_resources/logos/cockroachdb.svg @@ -0,0 +1,666 @@ + + + +image/svg+xml + + + + + + + + + + + + + + + + + + + + diff --git a/src/legacy/ui/public/vis/vis_filters/vis_filters.js b/src/legacy/ui/public/vis/vis_filters/vis_filters.js index 29ab0fb22003b..e879d040125f1 100644 --- a/src/legacy/ui/public/vis/vis_filters/vis_filters.js +++ b/src/legacy/ui/public/vis/vis_filters/vis_filters.js @@ -17,11 +17,10 @@ * under the License. */ -import { npStart } from 'ui/new_platform'; -import { onBrushEvent } from './brush_event'; import _ from 'lodash'; -import { start as data } from '../../../../core_plugins/data/public/legacy'; -import { uniqFilters, esFilters, changeTimeFilter, extractTimeFilter } from '../../../../../plugins/data/public'; +import { pushFilterBarFilters } from '../push_filters'; +import { onBrushEvent } from './brush_event'; +import { uniqFilters, esFilters } from '../../../../../plugins/data/public'; /** * For terms aggregations on `__other__` buckets, this assembles a list of applicable filter @@ -105,33 +104,18 @@ const createFiltersFromEvent = (event) => { return filters; }; -const VisFiltersProvider = () => { +const VisFiltersProvider = (getAppState, $timeout) => { - const pushFilters = async (filters, simulate) => { + const pushFilters = (filters, simulate) => { + const appState = getAppState(); if (filters.length && !simulate) { - const dedupedFilters = uniqFilters(filters); - // All filters originated from one visualization. - const indexPatternId = dedupedFilters[0].meta.index; - const indexPattern = _.find( - await data.indexPatterns.indexPatterns.getCache(), - p => p.id === indexPatternId - ); - if (dedupedFilters.length > 1) { - // TODO show apply filter popover and wait for user input - } - if (indexPattern && indexPattern.attributes.timeFieldName) { - const { timeRangeFilter, restOfFilters } = extractTimeFilter( - indexPattern.attributes.timeFieldName, - dedupedFilters - ); - npStart.plugins.data.query.filterManager.addFilters(restOfFilters); - if (timeRangeFilter) changeTimeFilter(data.timefilter.timefilter, timeRangeFilter); - } else { - npStart.plugins.data.query.filterManager.addFilters(dedupedFilters); - } + pushFilterBarFilters(appState, uniqFilters(filters)); + // to trigger angular digest cycle, we can get rid of this once we have either new filterManager or actions API + $timeout(_.noop, 0); } }; + return { pushFilters, }; From f3798872125ae0c12325543a1ac2428c2c8d170d Mon Sep 17 00:00:00 2001 From: maryia-lapata Date: Tue, 26 Nov 2019 15:56:47 +0300 Subject: [PATCH 110/132] Update deps --- .../kibana/public/visualize/application.ts | 10 --- .../kibana/public/visualize/editor/editor.js | 9 +- .../visualize_embeddable_factory.tsx | 1 - .../visualize/help_menu/help_menu_util.js | 4 +- .../kibana/public/visualize/index.js | 89 ------------------- .../kibana/public/visualize/index.ts | 8 +- .../public/visualize/kibana_services.ts | 6 +- .../kibana/public/visualize/legacy_imports.ts | 2 +- .../kibana/public/visualize/plugin.ts | 37 ++++---- 9 files changed, 35 insertions(+), 131 deletions(-) delete mode 100644 src/legacy/core_plugins/kibana/public/visualize/index.js diff --git a/src/legacy/core_plugins/kibana/public/visualize/application.ts b/src/legacy/core_plugins/kibana/public/visualize/application.ts index ab3ecb96284b3..06d2ed04f79b8 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/application.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/application.ts @@ -39,7 +39,6 @@ import { PromiseServiceCreator, StateManagementConfigProvider, } from './legacy_imports'; -import { createFilterBarDirective, createFilterBarHelper } from '../../../data/public'; import { NavigationStart } from '../../../navigation/public'; // @ts-ignore @@ -103,7 +102,6 @@ function createLocalAngularModule( createLocalPersistedStateModule(angular); createLocalTopNavModule(navigation, angular); createLocalConfirmModalModule(angular); - createLocalFilterBarModule(angular); const visualizeAngularModule: IModule = angular.module(moduleName, [ ...thirdPartyAngularDependencies, @@ -114,7 +112,6 @@ function createLocalAngularModule( 'app/visualize/TopNav', 'app/visualize/State', 'app/visualize/ConfirmModal', - 'app/visualize/FilterBar', ]); return visualizeAngularModule; } @@ -194,13 +191,6 @@ function createLocalTopNavModule(navigation: NavigationStart, angular: IAngularS .directive('kbnTopNavHelper', createTopNavHelper(navigation.ui)); } -function createLocalFilterBarModule(angular: IAngularStatic) { - angular - .module('app/visualize/FilterBar', ['react']) - .directive('filterBar', createFilterBarDirective) - .directive('filterBarHelper', createFilterBarHelper); -} - function createLocalI18nModule(angular: IAngularStatic) { angular .module('app/visualize/I18n', []) diff --git a/src/legacy/core_plugins/kibana/public/visualize/editor/editor.js b/src/legacy/core_plugins/kibana/public/visualize/editor/editor.js index 5812b1fd71736..bfd9184f23255 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/editor/editor.js +++ b/src/legacy/core_plugins/kibana/public/visualize/editor/editor.js @@ -42,7 +42,8 @@ import { SavedObjectSaveModal, showSaveModal, stateMonitorFactory, - subscribeWithScope + subscribeWithScope, + unhashUrl, } from '../legacy_imports'; import { getServices } from '../kibana_services'; @@ -80,7 +81,7 @@ function VisualizeAppController( indexPatterns, localStorage, visualizeCapabilities, - shareContextMenuExtensions, + share, npDataStart: { query: { filterManager, @@ -177,11 +178,11 @@ function VisualizeAppController( const hasUnappliedChanges = vis.dirty; const hasUnsavedChanges = $appStatus.dirty; const getUnhashableStates = () => [getAppState(), globalState].filter(Boolean); - shareContextMenuExtensions.toggleShareContextMenu({ + share.toggleShareContextMenu({ anchorElement, allowEmbed: true, allowShortUrl: visualizeCapabilities.createShortUrl, - getUnhashableStates, + shareableUrl: unhashUrl(window.location.href, getUnhashableStates()), objectId: savedVis.id, objectType: 'visualization', sharingData: { diff --git a/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable_factory.tsx b/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable_factory.tsx index b0b99939c09f8..e509a85910ee4 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable_factory.tsx +++ b/src/legacy/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable_factory.tsx @@ -17,7 +17,6 @@ * under the License. */ -import 'ui/registry/field_formats'; import 'uiExports/contextMenuActions'; import 'uiExports/devTools'; import 'uiExports/docViews'; diff --git a/src/legacy/core_plugins/kibana/public/visualize/help_menu/help_menu_util.js b/src/legacy/core_plugins/kibana/public/visualize/help_menu/help_menu_util.js index d27003f39d4c0..9c00947d7663c 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/help_menu/help_menu_util.js +++ b/src/legacy/core_plugins/kibana/public/visualize/help_menu/help_menu_util.js @@ -18,10 +18,8 @@ */ import { i18n } from '@kbn/i18n'; -import { getServices } from '../kibana_services'; -const { docLinks } = getServices(); -export function addHelpMenuToAppChrome(chrome) { +export function addHelpMenuToAppChrome(chrome, docLinks) { chrome.setHelpExtension({ appName: i18n.translate('kbn.visualize.helpMenu.appName', { defaultMessage: 'Visualize', diff --git a/src/legacy/core_plugins/kibana/public/visualize/index.js b/src/legacy/core_plugins/kibana/public/visualize/index.js deleted file mode 100644 index 57707f6321376..0000000000000 --- a/src/legacy/core_plugins/kibana/public/visualize/index.js +++ /dev/null @@ -1,89 +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 { ensureDefaultIndexPattern } from 'ui/legacy_compat'; -import './editor/editor'; -import { i18n } from '@kbn/i18n'; -import './saved_visualizations/_saved_vis'; -import './saved_visualizations/saved_visualizations'; -import visualizeListingTemplate from './listing/visualize_listing.html'; -import { VisualizeListingController } from './listing/visualize_listing'; -import { VisualizeConstants } from './visualize_constants'; -import { getLandingBreadcrumbs, getWizardStep1Breadcrumbs } from './breadcrumbs'; - -import { getServices, FeatureCatalogueCategory } from './kibana_services'; - -const { FeatureCatalogueRegistryProvider, uiRoutes } = getServices(); - -uiRoutes - .defaults(/visualize/, { - requireUICapability: 'visualize.show', - badge: uiCapabilities => { - if (uiCapabilities.visualize.save) { - return undefined; - } - - return { - text: i18n.translate('kbn.visualize.badge.readOnly.text', { - defaultMessage: 'Read only', - }), - tooltip: i18n.translate('kbn.visualize.badge.readOnly.tooltip', { - defaultMessage: 'Unable to save visualizations', - }), - iconType: 'glasses' - }; - } - }) - .when(VisualizeConstants.LANDING_PAGE_PATH, { - template: visualizeListingTemplate, - k7Breadcrumbs: getLandingBreadcrumbs, - controller: VisualizeListingController, - controllerAs: 'listingController', - resolve: { - createNewVis: () => false, - hasDefaultIndex: ($rootScope, kbnUrl) => ensureDefaultIndexPattern(getServices().core, getServices().data, $rootScope, kbnUrl) - }, - }) - .when(VisualizeConstants.WIZARD_STEP_1_PAGE_PATH, { - template: visualizeListingTemplate, - k7Breadcrumbs: getWizardStep1Breadcrumbs, - controller: VisualizeListingController, - controllerAs: 'listingController', - resolve: { - createNewVis: () => true, - hasDefaultIndex: ($rootScope, kbnUrl) => ensureDefaultIndexPattern(getServices().core, getServices().data, $rootScope, kbnUrl) - }, - }); - -FeatureCatalogueRegistryProvider.register(() => { - return { - id: 'visualize', - title: 'Visualize', - description: i18n.translate( - 'kbn.visualize.visualizeDescription', - { - defaultMessage: 'Create visualizations and aggregate data stores in your Elasticsearch indices.', - } - ), - icon: 'visualizeApp', - path: `/app/kibana#${VisualizeConstants.LANDING_PAGE_PATH}`, - showOnHomePage: true, - category: FeatureCatalogueCategory.DATA - }; -}); diff --git a/src/legacy/core_plugins/kibana/public/visualize/index.ts b/src/legacy/core_plugins/kibana/public/visualize/index.ts index d83a7fc733a7d..dca2d4870f96c 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/index.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/index.ts @@ -28,11 +28,9 @@ import { npSetup, npStart, SavedObjectRegistryProvider, - ShareContextMenuExtensionsRegistryProvider, VisEditorTypesRegistryProvider, } from './legacy_imports'; import { VisualizePlugin, LegacyAngularInjectedDependencies } from './plugin'; -import { localApplicationService } from '../local_application_service'; import { start as data } from '../../../data/public/legacy'; import { start as embeddables } from '../../../embeddable_api/public/np_ready/public/legacy'; import { start as navigation } from '../../../navigation/public/legacy'; @@ -49,30 +47,28 @@ async function getAngularDependencies(): Promise { const instance = new VisualizePlugin(); instance.setup(npSetup.core, { - feature_catalogue: npSetup.plugins.home.featureCatalogue, + ...npSetup.plugins, VisEditorTypesRegistryProvider, __LEGACY: { // angular is passed to kibana_services since it's used in editor.js angular, getAngularDependencies, - localApplicationService, }, }); instance.start(npStart.core, { + ...npStart.plugins, data, npData: npStart.plugins.data, embeddables, diff --git a/src/legacy/core_plugins/kibana/public/visualize/kibana_services.ts b/src/legacy/core_plugins/kibana/public/visualize/kibana_services.ts index 03a8fcadbb751..d9511be115b2b 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/kibana_services.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/kibana_services.ts @@ -31,7 +31,8 @@ import { DataStart } from '../../../data/public'; import { SavedQueryService } from '../../../data/public/search/search_bar/lib/saved_query_service'; import { NavigationStart } from '../../../navigation/public'; import { Storage } from '../../../../../plugins/kibana_utils/public'; -import { EmbeddablePublicPlugin } from '../../../../../plugins/embeddable/public'; +import { IEmbeddableStart } from '../../../../../plugins/embeddable/public'; +import { SharePluginStart } from '../../../../../plugins/share/public'; import { DataPublicPluginStart as NpDataStart } from '../../../../../plugins/data/public'; import { SavedVisualizations } from './types'; @@ -44,7 +45,7 @@ export interface VisualizeKibanaServices { dataStart: DataStart; editorTypes: any; npDataStart: NpDataStart; - embeddables: ReturnType; + embeddables: IEmbeddableStart; getBasePath: () => string; indexPatterns: any; localStorage: Storage; @@ -54,6 +55,7 @@ export interface VisualizeKibanaServices { savedObjectRegistry: any; savedQueryService: SavedQueryService; savedVisualizations: SavedVisualizations; + share: SharePluginStart; uiSettings: UiSettingsClientContract; visualizeCapabilities: any; visualizations: any; diff --git a/src/legacy/core_plugins/kibana/public/visualize/legacy_imports.ts b/src/legacy/core_plugins/kibana/public/visualize/legacy_imports.ts index 806085a031969..5ffb57e48f577 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/legacy_imports.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/legacy_imports.ts @@ -35,7 +35,6 @@ export { AppStateProvider } from 'ui/state_management/app_state'; export { npSetup, npStart } from 'ui/new_platform'; export { SavedObjectRegistryProvider, SavedObjectsClientProvider } from 'ui/saved_objects'; export { IPrivate } from 'ui/private'; -export { ShareContextMenuExtensionsRegistryProvider, showShareContextMenu } from 'ui/share'; export { SavedObjectSaveModal } from 'ui/saved_objects/components/saved_object_save_modal'; export { subscribeWithScope } from 'ui/utils/subscribe_with_scope'; export { showSaveModal } from 'ui/saved_objects/show_saved_object_save_modal'; @@ -73,3 +72,4 @@ export { VisType } from 'ui/vis'; export { SavedObjectFinder } from 'ui/saved_objects/components/saved_object_finder'; export { memoizeLast } from 'ui/utils/memoize'; export { wrapInI18nContext } from 'ui/i18n'; +export { unhashUrl } from 'ui/state_management/state_hashing'; diff --git a/src/legacy/core_plugins/kibana/public/visualize/plugin.ts b/src/legacy/core_plugins/kibana/public/visualize/plugin.ts index 9ca695e5a4cfe..e4ca5660c591b 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/plugin.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/plugin.ts @@ -31,25 +31,25 @@ import { import { Storage } from '../../../../../plugins/kibana_utils/public'; import { DataStart } from '../../../data/public'; import { DataPublicPluginStart as NpDataStart } from '../../../../../plugins/data/public'; -import { EmbeddablePublicPlugin } from '../../../../../plugins/embeddable/public'; +import { IEmbeddableStart } from '../../../../../plugins/embeddable/public'; import { NavigationStart } from '../../../navigation/public'; +import { SharePluginStart } from '../../../../../plugins/share/public'; +import { KibanaLegacySetup } from '../../../../../plugins/kibana_legacy/public'; import { VisualizationsStart } from '../../../visualizations/public'; -import { LocalApplicationService } from '../local_application_service'; import { VisualizeEmbeddableFactory } from './embeddable/visualize_embeddable_factory'; import { VISUALIZE_EMBEDDABLE_TYPE } from './embeddable/constants'; import { VisualizeConstants } from './visualize_constants'; import { setServices, VisualizeKibanaServices } from './kibana_services'; import { FeatureCatalogueCategory, - FeatureCatalogueSetup, -} from '../../../../../plugins/feature_catalogue/public'; + HomePublicPluginSetup, +} from '../../../../../plugins/home/public'; import { defaultEditor } from './legacy_imports'; import { SavedVisualizations } from './types'; export interface LegacyAngularInjectedDependencies { legacyChrome: any; editorTypes: any; - shareContextMenuExtensions: any; savedObjectRegistry: any; savedVisualizations: SavedVisualizations; } @@ -57,37 +57,40 @@ export interface LegacyAngularInjectedDependencies { export interface VisualizePluginStartDependencies { data: DataStart; npData: NpDataStart; - embeddables: ReturnType; + embeddables: IEmbeddableStart; navigation: NavigationStart; + share: SharePluginStart; visualizations: VisualizationsStart; } export interface VisualizePluginSetupDependencies { - feature_catalogue: FeatureCatalogueSetup; __LEGACY: { angular: IAngularStatic; getAngularDependencies: () => Promise; - localApplicationService: LocalApplicationService; }; VisEditorTypesRegistryProvider: any; + home: HomePublicPluginSetup; + kibana_legacy: KibanaLegacySetup; } export class VisualizePlugin implements Plugin { private startDependencies: { dataStart: DataStart; npDataStart: NpDataStart; - embeddables: ReturnType; + embeddables: IEmbeddableStart; navigation: NavigationStart; savedObjectsClient: SavedObjectsClientContract; + share: SharePluginStart; visualizations: VisualizationsStart; } | null = null; public async setup( core: CoreSetup, { - feature_catalogue, + home, + kibana_legacy, VisEditorTypesRegistryProvider, - __LEGACY: { localApplicationService, getAngularDependencies, ...legacyServices }, + __LEGACY: { getAngularDependencies, angular }, }: VisualizePluginSetupDependencies ) { const app: App = { @@ -105,11 +108,12 @@ export class VisualizePlugin implements Plugin { navigation, visualizations, npDataStart, + share, } = this.startDependencies; const angularDependencies = await getAngularDependencies(); const deps: VisualizeKibanaServices = { - ...legacyServices, + angular, ...angularDependencies, addBasePath: contextCore.http.basePath.prepend, core: contextCore as LegacyCoreStart, @@ -123,6 +127,7 @@ export class VisualizePlugin implements Plugin { navigation, savedObjectsClient, savedQueryService: dataStart.search.services.savedQueryService, + share, toastNotifications: contextCore.notifications.toasts, uiSettings: contextCore.uiSettings, visualizeCapabilities: contextCore.application.capabilities.visualize, @@ -135,9 +140,9 @@ export class VisualizePlugin implements Plugin { }, }; - localApplicationService.register({ ...app, id: 'visualize' }); + kibana_legacy.registerLegacyApp({ ...app, id: 'visualize' }); - feature_catalogue.register({ + home.featureCatalogue.register({ id: 'visualize', title: 'Visualize', description: i18n.translate('kbn.visualize.visualizeDescription', { @@ -159,8 +164,9 @@ export class VisualizePlugin implements Plugin { data: dataStart, embeddables, navigation, - visualizations, npData, + share, + visualizations, }: VisualizePluginStartDependencies ) { this.startDependencies = { @@ -169,6 +175,7 @@ export class VisualizePlugin implements Plugin { embeddables, navigation, savedObjectsClient, + share, visualizations, }; From 17b6d9d556d99974dbaf1b859b197b22530f2d54 Mon Sep 17 00:00:00 2001 From: maryia-lapata Date: Tue, 26 Nov 2019 15:58:46 +0300 Subject: [PATCH 111/132] Revert filter bar export --- src/legacy/core_plugins/data/public/index.ts | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/legacy/core_plugins/data/public/index.ts b/src/legacy/core_plugins/data/public/index.ts index 3f0f9a9c5d442..b33aef75e6756 100644 --- a/src/legacy/core_plugins/data/public/index.ts +++ b/src/legacy/core_plugins/data/public/index.ts @@ -58,9 +58,3 @@ export { NoDefaultIndexPattern, NoDefinedIndexPatterns, } from './index_patterns'; - -/** - * These functions can be used to register the angular wrappers for react components - * in a separate module to use them without relying on the uiModules module tree. - * */ -export { createFilterBarHelper, createFilterBarDirective } from './shim/legacy_module'; From 67f27396bdd181270bac57ec21b2313a218fad51 Mon Sep 17 00:00:00 2001 From: maryia-lapata Date: Tue, 26 Nov 2019 17:38:08 +0300 Subject: [PATCH 112/132] Revert ts-ignore --- packages/kbn-i18n/src/core/i18n.ts | 3 --- src/legacy/core_plugins/kibana/public/visualize/application.ts | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/packages/kbn-i18n/src/core/i18n.ts b/packages/kbn-i18n/src/core/i18n.ts index a0219bf06393e..c66555ab015dc 100644 --- a/packages/kbn-i18n/src/core/i18n.ts +++ b/packages/kbn-i18n/src/core/i18n.ts @@ -17,7 +17,6 @@ * under the License. */ -// @ts-ignore import memoizeIntlConstructor from 'intl-format-cache'; import IntlMessageFormat from 'intl-messageformat'; import IntlRelativeFormat from 'intl-relativeformat'; @@ -39,7 +38,6 @@ let currentLocale = EN_LOCALE; let formats = EN_FORMATS; IntlMessageFormat.defaultLocale = defaultLocale; -// @ts-ignore IntlRelativeFormat.defaultLocale = defaultLocale; /** @@ -125,7 +123,6 @@ export function setDefaultLocale(locale: string) { defaultLocale = normalizeLocale(locale); IntlMessageFormat.defaultLocale = defaultLocale; - // @ts-ignore IntlRelativeFormat.defaultLocale = defaultLocale; } diff --git a/src/legacy/core_plugins/kibana/public/visualize/application.ts b/src/legacy/core_plugins/kibana/public/visualize/application.ts index 06d2ed04f79b8..4e1bd0c5d3eeb 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/application.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/application.ts @@ -19,7 +19,7 @@ import { IModule, IAngularStatic } from 'angular'; import { EuiConfirmModal } from '@elastic/eui'; -import { i18nDirective, i18nFilter, I18nProvider } from '@kbn/i18n/src/angular'; +import { i18nDirective, i18nFilter, I18nProvider } from '@kbn/i18n/angular'; import { AppMountContext, LegacyCoreStart } from 'kibana/public'; import { From 1b09e9ca88234d3b6f6804fbffc7226b075e169f Mon Sep 17 00:00:00 2001 From: maryia-lapata Date: Tue, 26 Nov 2019 18:37:31 +0300 Subject: [PATCH 113/132] Clean up --- .../kibana/public/dashboard/plugin.ts | 2 +- .../kibana/public/visualize/application.ts | 50 +++++++++---------- .../kibana/public/visualize/editor/editor.js | 2 +- .../kibana/public/visualize/index.ts | 5 -- .../public/visualize/kibana_services.ts | 15 +++--- .../kibana/public/visualize/legacy_imports.ts | 37 +++++++------- .../kibana/public/visualize/plugin.ts | 16 ++---- .../visualize/wizard/new_vis_modal.test.tsx | 2 +- 8 files changed, 56 insertions(+), 73 deletions(-) diff --git a/src/legacy/core_plugins/kibana/public/dashboard/plugin.ts b/src/legacy/core_plugins/kibana/public/dashboard/plugin.ts index 609bd717f3c48..4758112fb623b 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/plugin.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/plugin.ts @@ -74,7 +74,7 @@ export class DashboardPlugin implements Plugin { public setup( core: CoreSetup, - { __LEGACY: { getAngularDependencies }, home, kibana_legacy }: DashboardPluginSetupDependencies + { home, kibana_legacy, __LEGACY: { getAngularDependencies } }: DashboardPluginSetupDependencies ) { const app: App = { id: '', diff --git a/src/legacy/core_plugins/kibana/public/visualize/application.ts b/src/legacy/core_plugins/kibana/public/visualize/application.ts index 4e1bd0c5d3eeb..1458a349d01dc 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/application.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/application.ts @@ -17,7 +17,7 @@ * under the License. */ -import { IModule, IAngularStatic } from 'angular'; +import angular, { IModule } from 'angular'; import { EuiConfirmModal } from '@elastic/eui'; import { i18nDirective, i18nFilter, I18nProvider } from '@kbn/i18n/angular'; @@ -53,13 +53,13 @@ export const renderApp = async ( deps: VisualizeKibanaServices ) => { if (!angularModuleInstance) { - angularModuleInstance = createLocalAngularModule(deps.core, deps.navigation, deps.angular); + angularModuleInstance = createLocalAngularModule(deps.core, deps.navigation); // global routing stuff configureAppAngularModule(angularModuleInstance, deps.core as LegacyCoreStart, true); // custom routing stuff initVisualizeApp(angularModuleInstance, deps); } - const $injector = mountVisualizeApp(appBasePath, element, deps.angular); + const $injector = mountVisualizeApp(appBasePath, element); return () => { $injector.get('$rootScope').$destroy(); }; @@ -75,7 +75,7 @@ const moduleName = 'app/visualize'; const thirdPartyAngularDependencies = ['ngSanitize', 'ngRoute', 'react']; -function mountVisualizeApp(appBasePath: string, element: HTMLElement, angular: IAngularStatic) { +function mountVisualizeApp(appBasePath: string, element: HTMLElement) { const mountpoint = document.createElement('div'); mountpoint.setAttribute('style', 'height: 100%'); mountpoint.innerHTML = mainTemplate(appBasePath); @@ -88,20 +88,16 @@ function mountVisualizeApp(appBasePath: string, element: HTMLElement, angular: I return $injector; } -function createLocalAngularModule( - core: AppMountContext['core'], - navigation: NavigationStart, - angular: IAngularStatic -) { - createLocalI18nModule(angular); - createLocalPrivateModule(angular); - createLocalPromiseModule(angular); - createLocalConfigModule(core, angular); - createLocalKbnUrlModule(angular); - createLocalStateModule(angular); - createLocalPersistedStateModule(angular); - createLocalTopNavModule(navigation, angular); - createLocalConfirmModalModule(angular); +function createLocalAngularModule(core: AppMountContext['core'], navigation: NavigationStart) { + createLocalI18nModule(); + createLocalPrivateModule(); + createLocalPromiseModule(); + createLocalConfigModule(core); + createLocalKbnUrlModule(); + createLocalStateModule(); + createLocalPersistedStateModule(); + createLocalTopNavModule(navigation); + createLocalConfirmModalModule(); const visualizeAngularModule: IModule = angular.module(moduleName, [ ...thirdPartyAngularDependencies, @@ -116,14 +112,14 @@ function createLocalAngularModule( return visualizeAngularModule; } -function createLocalConfirmModalModule(angular: IAngularStatic) { +function createLocalConfirmModalModule() { angular .module('app/visualize/ConfirmModal', ['react']) .factory('confirmModal', confirmModalFactory) .directive('confirmModal', reactDirective => reactDirective(EuiConfirmModal)); } -function createLocalStateModule(angular: IAngularStatic) { +function createLocalStateModule() { angular .module('app/visualize/State', [ 'app/visualize/Private', @@ -143,7 +139,7 @@ function createLocalStateModule(angular: IAngularStatic) { }); } -function createLocalPersistedStateModule(angular: IAngularStatic) { +function createLocalPersistedStateModule() { angular .module('app/visualize/PersistedState', ['app/visualize/Private', 'app/visualize/Promise']) .factory('PersistedState', (Private: IPrivate) => { @@ -156,14 +152,14 @@ function createLocalPersistedStateModule(angular: IAngularStatic) { }); } -function createLocalKbnUrlModule(angular: IAngularStatic) { +function createLocalKbnUrlModule() { angular .module('app/visualize/KbnUrl', ['app/visualize/Private', 'ngRoute']) .service('kbnUrl', (Private: IPrivate) => Private(KbnUrlProvider)) .service('redirectWhenMissing', (Private: IPrivate) => Private(RedirectWhenMissingProvider)); } -function createLocalConfigModule(core: AppMountContext['core'], angular: IAngularStatic) { +function createLocalConfigModule(core: AppMountContext['core']) { angular .module('app/visualize/Config', ['app/visualize/Private']) .provider('stateManagementConfig', StateManagementConfigProvider) @@ -176,22 +172,22 @@ function createLocalConfigModule(core: AppMountContext['core'], angular: IAngula }); } -function createLocalPromiseModule(angular: IAngularStatic) { +function createLocalPromiseModule() { angular.module('app/visualize/Promise', []).service('Promise', PromiseServiceCreator); } -function createLocalPrivateModule(angular: IAngularStatic) { +function createLocalPrivateModule() { angular.module('app/visualize/Private', []).provider('Private', PrivateProvider); } -function createLocalTopNavModule(navigation: NavigationStart, angular: IAngularStatic) { +function createLocalTopNavModule(navigation: NavigationStart) { angular .module('app/visualize/TopNav', ['react']) .directive('kbnTopNav', createTopNavDirective) .directive('kbnTopNavHelper', createTopNavHelper(navigation.ui)); } -function createLocalI18nModule(angular: IAngularStatic) { +function createLocalI18nModule() { angular .module('app/visualize/I18n', []) .provider('i18n', I18nProvider) diff --git a/src/legacy/core_plugins/kibana/public/visualize/editor/editor.js b/src/legacy/core_plugins/kibana/public/visualize/editor/editor.js index bfd9184f23255..0cdfee4e21014 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/editor/editor.js +++ b/src/legacy/core_plugins/kibana/public/visualize/editor/editor.js @@ -17,6 +17,7 @@ * under the License. */ +import angular from 'angular'; import _ from 'lodash'; import { Subscription } from 'rxjs'; import { i18n } from '@kbn/i18n'; @@ -77,7 +78,6 @@ function VisualizeAppController( globalState, ) { const { - angular, indexPatterns, localStorage, visualizeCapabilities, diff --git a/src/legacy/core_plugins/kibana/public/visualize/index.ts b/src/legacy/core_plugins/kibana/public/visualize/index.ts index dca2d4870f96c..e59f3a4dbe75b 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/index.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/index.ts @@ -17,8 +17,6 @@ * under the License. */ -import angular from 'angular'; -import 'angular-sanitize'; import 'ui/collapsible_sidebar'; // used in default editor import 'ui/vis/editors/default/sidebar'; @@ -60,10 +58,7 @@ async function getAngularDependencies(): Promise string; - angular: IAngularStatic; chrome: ChromeStart; - legacyChrome: any; core: LegacyCoreStart; dataStart: DataStart; editorTypes: any; - npDataStart: NpDataStart; embeddables: IEmbeddableStart; getBasePath: () => string; - indexPatterns: any; + indexPatterns: StaticIndexPattern[]; + legacyChrome: any; localStorage: Storage; navigation: NavigationStart; + npDataStart: NpDataStart; toastNotifications: ToastsStart; savedObjectsClient: SavedObjectsClientContract; savedObjectRegistry: any; - savedQueryService: SavedQueryService; + savedQueryService: DataStart['search']['services']['savedQueryService']; savedVisualizations: SavedVisualizations; share: SharePluginStart; uiSettings: UiSettingsClientContract; visualizeCapabilities: any; - visualizations: any; + visualizations: VisualizationsStart; } let services: VisualizeKibanaServices | null = null; diff --git a/src/legacy/core_plugins/kibana/public/visualize/legacy_imports.ts b/src/legacy/core_plugins/kibana/public/visualize/legacy_imports.ts index 5ffb57e48f577..b7c0a97fdffb0 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/legacy_imports.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/legacy_imports.ts @@ -29,47 +29,48 @@ import chrome from 'ui/chrome'; export const legacyChrome = chrome; export { State } from 'ui/state_management/state'; -export { AppState } from 'ui/state_management/app_state'; // @ts-ignore -export { AppStateProvider } from 'ui/state_management/app_state'; -export { npSetup, npStart } from 'ui/new_platform'; -export { SavedObjectRegistryProvider, SavedObjectsClientProvider } from 'ui/saved_objects'; -export { IPrivate } from 'ui/private'; -export { SavedObjectSaveModal } from 'ui/saved_objects/components/saved_object_save_modal'; -export { subscribeWithScope } from 'ui/utils/subscribe_with_scope'; -export { showSaveModal } from 'ui/saved_objects/show_saved_object_save_modal'; -export { migrateLegacyQuery } from 'ui/utils/migrate_legacy_query'; +export { AppState, AppStateProvider } from 'ui/state_management/app_state'; // @ts-ignore export { GlobalStateProvider } from 'ui/state_management/global_state'; // @ts-ignore export { StateManagementConfigProvider } from 'ui/state_management/config_provider'; +export { stateMonitorFactory } from 'ui/state_management/state_monitor_factory'; +export { unhashUrl } from 'ui/state_management/state_hashing'; +export { PersistedState } from 'ui/persisted_state'; + +export { npSetup, npStart } from 'ui/new_platform'; +export { IPrivate } from 'ui/private'; // @ts-ignore export { PrivateProvider } from 'ui/private/private'; + +export { SavedObjectRegistryProvider } from 'ui/saved_objects'; +export { SavedObjectSaveModal } from 'ui/saved_objects/components/saved_object_save_modal'; +export { SavedObjectFinder } from 'ui/saved_objects/components/saved_object_finder'; +export { showSaveModal } from 'ui/saved_objects/show_saved_object_save_modal'; + +export { subscribeWithScope } from 'ui/utils/subscribe_with_scope'; +export { migrateLegacyQuery } from 'ui/utils/migrate_legacy_query'; // @ts-ignore export { EventsProvider } from 'ui/events'; -export { PersistedState } from 'ui/persisted_state'; // @ts-ignore export { createTopNavDirective, createTopNavHelper } from 'ui/kbn_top_nav/kbn_top_nav'; // @ts-ignore export { PromiseServiceCreator } from 'ui/promises/promises'; // @ts-ignore -export { KbnUrlProvider, RedirectWhenMissingProvider } from 'ui/url'; -// @ts-ignore export { confirmModalFactory } from 'ui/modals/confirm_modal'; -export { configureAppAngularModule } from 'ui/legacy_compat'; - +export { configureAppAngularModule, ensureDefaultIndexPattern } from 'ui/legacy_compat'; export { registerTimefilterWithGlobalStateFactory } from 'ui/timefilter/setup_router'; -export { ensureDefaultIndexPattern } from 'ui/legacy_compat'; // @ts-ignore export { VisEditorTypesRegistryProvider } from 'ui/registry/vis_editor_types'; +// @ts-ignore +export { KbnUrlProvider, RedirectWhenMissingProvider } from 'ui/url'; export { absoluteToParsedUrl } from 'ui/url/absolute_to_parsed_url'; export { KibanaParsedUrl } from 'ui/url/kibana_parsed_url'; -export { stateMonitorFactory } from 'ui/state_management/state_monitor_factory'; + // @ts-ignore export { defaultEditor } from 'ui/vis/editors/default/default'; export { VisType } from 'ui/vis'; -export { SavedObjectFinder } from 'ui/saved_objects/components/saved_object_finder'; export { memoizeLast } from 'ui/utils/memoize'; export { wrapInI18nContext } from 'ui/i18n'; -export { unhashUrl } from 'ui/state_management/state_hashing'; diff --git a/src/legacy/core_plugins/kibana/public/visualize/plugin.ts b/src/legacy/core_plugins/kibana/public/visualize/plugin.ts index e4ca5660c591b..21c82f5d780d5 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/plugin.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/plugin.ts @@ -17,7 +17,6 @@ * under the License. */ -import { IAngularStatic } from 'angular'; import { i18n } from '@kbn/i18n'; import { @@ -28,6 +27,7 @@ import { Plugin, SavedObjectsClientContract, } from 'kibana/public'; + import { Storage } from '../../../../../plugins/kibana_utils/public'; import { DataStart } from '../../../data/public'; import { DataPublicPluginStart as NpDataStart } from '../../../../../plugins/data/public'; @@ -44,7 +44,7 @@ import { FeatureCatalogueCategory, HomePublicPluginSetup, } from '../../../../../plugins/home/public'; -import { defaultEditor } from './legacy_imports'; +import { defaultEditor, VisEditorTypesRegistryProvider } from './legacy_imports'; import { SavedVisualizations } from './types'; export interface LegacyAngularInjectedDependencies { @@ -65,10 +65,8 @@ export interface VisualizePluginStartDependencies { export interface VisualizePluginSetupDependencies { __LEGACY: { - angular: IAngularStatic; getAngularDependencies: () => Promise; }; - VisEditorTypesRegistryProvider: any; home: HomePublicPluginSetup; kibana_legacy: KibanaLegacySetup; } @@ -86,12 +84,7 @@ export class VisualizePlugin implements Plugin { public async setup( core: CoreSetup, - { - home, - kibana_legacy, - VisEditorTypesRegistryProvider, - __LEGACY: { getAngularDependencies, angular }, - }: VisualizePluginSetupDependencies + { home, kibana_legacy, __LEGACY: { getAngularDependencies } }: VisualizePluginSetupDependencies ) { const app: App = { id: '', @@ -113,18 +106,17 @@ export class VisualizePlugin implements Plugin { const angularDependencies = await getAngularDependencies(); const deps: VisualizeKibanaServices = { - angular, ...angularDependencies, addBasePath: contextCore.http.basePath.prepend, core: contextCore as LegacyCoreStart, chrome: contextCore.chrome, dataStart, - npDataStart, embeddables, getBasePath: core.http.basePath.get, indexPatterns: dataStart.indexPatterns.indexPatterns, localStorage: new Storage(localStorage), navigation, + npDataStart, savedObjectsClient, savedQueryService: dataStart.search.services.savedQueryService, share, diff --git a/src/legacy/core_plugins/kibana/public/visualize/wizard/new_vis_modal.test.tsx b/src/legacy/core_plugins/kibana/public/visualize/wizard/new_vis_modal.test.tsx index 1552c0a313abd..929936ebe56ea 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/wizard/new_vis_modal.test.tsx +++ b/src/legacy/core_plugins/kibana/public/visualize/wizard/new_vis_modal.test.tsx @@ -20,7 +20,7 @@ import React from 'react'; import { mountWithIntl } from 'test_utils/enzyme_helpers'; -import { VisType } from 'ui/vis'; +import { VisType } from '../legacy_imports'; import { TypesStart } from '../../../../visualizations/public/np_ready/public/types'; jest.mock('../legacy_imports', () => ({ From e57c785e8570f91bf412de640f17abfcdbd26b02 Mon Sep 17 00:00:00 2001 From: maryia-lapata Date: Tue, 26 Nov 2019 19:13:55 +0300 Subject: [PATCH 114/132] Refactoring --- src/legacy/core_plugins/kibana/public/dashboard/plugin.ts | 2 +- .../core_plugins/kibana/public/visualize/application.ts | 1 - .../core_plugins/kibana/public/visualize/editor/editor.js | 8 -------- .../core_plugins/kibana/public/visualize/visualize_app.ts | 3 ++- .../visualize/wizard/type_selection/new_vis_help.tsx | 2 +- src/legacy/ui/public/vis/vis_filters/vis_filters.js | 1 - 6 files changed, 4 insertions(+), 13 deletions(-) diff --git a/src/legacy/core_plugins/kibana/public/dashboard/plugin.ts b/src/legacy/core_plugins/kibana/public/dashboard/plugin.ts index 4758112fb623b..609bd717f3c48 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/plugin.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/plugin.ts @@ -74,7 +74,7 @@ export class DashboardPlugin implements Plugin { public setup( core: CoreSetup, - { home, kibana_legacy, __LEGACY: { getAngularDependencies } }: DashboardPluginSetupDependencies + { __LEGACY: { getAngularDependencies }, home, kibana_legacy }: DashboardPluginSetupDependencies ) { const app: App = { id: '', diff --git a/src/legacy/core_plugins/kibana/public/visualize/application.ts b/src/legacy/core_plugins/kibana/public/visualize/application.ts index 1458a349d01dc..d3217feedbae5 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/application.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/application.ts @@ -83,7 +83,6 @@ function mountVisualizeApp(appBasePath: string, element: HTMLElement) { // make angular-within-angular possible const $injector = angular.bootstrap(mountpoint, [moduleName]); // initialize global state handler - // $injector.get('globalState'); element.appendChild(mountpoint); return $injector; } diff --git a/src/legacy/core_plugins/kibana/public/visualize/editor/editor.js b/src/legacy/core_plugins/kibana/public/visualize/editor/editor.js index 0cdfee4e21014..5b6a738f9f40d 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/editor/editor.js +++ b/src/legacy/core_plugins/kibana/public/visualize/editor/editor.js @@ -599,12 +599,4 @@ function VisualizeAppController( addHelpMenuToAppChrome(chrome, docLinks); init(); - - const visibleSubscription = chrome.getIsVisible$().subscribe(isVisible => { - $scope.isVisible = isVisible; - }); - - $scope.$on('$destroy', () => { - visibleSubscription.unsubscribe(); - }); } diff --git a/src/legacy/core_plugins/kibana/public/visualize/visualize_app.ts b/src/legacy/core_plugins/kibana/public/visualize/visualize_app.ts index ce44a5051d20d..c64287a0e63b8 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/visualize_app.ts +++ b/src/legacy/core_plugins/kibana/public/visualize/visualize_app.ts @@ -17,6 +17,7 @@ * under the License. */ +import { IModule } from 'angular'; import { VisualizeKibanaServices } from './kibana_services'; // @ts-ignore @@ -24,7 +25,7 @@ import { initEditorDirective } from './editor/editor'; // @ts-ignore import { initListingDirective } from './listing/visualize_listing'; -export function initVisualizeAppDirective(app: any, deps: VisualizeKibanaServices) { +export function initVisualizeAppDirective(app: IModule, deps: VisualizeKibanaServices) { initEditorDirective(app, deps); initListingDirective(app); } diff --git a/src/legacy/core_plugins/kibana/public/visualize/wizard/type_selection/new_vis_help.tsx b/src/legacy/core_plugins/kibana/public/visualize/wizard/type_selection/new_vis_help.tsx index f04a80af8d6f5..107cbc0e754b5 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/wizard/type_selection/new_vis_help.tsx +++ b/src/legacy/core_plugins/kibana/public/visualize/wizard/type_selection/new_vis_help.tsx @@ -42,7 +42,7 @@ export function NewVisHelp(props: Props) { {t.promotion!.description}

1j*;k$iW5JwC1;^#{X`htGeBVM9jI*^!R@G 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 `aTi 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 VIUo$(uEYUISzcYPSjOc2{{xjj8>IjM diff --git a/src/legacy/core_plugins/kibana/public/home/tutorial_resources/logos/cockroachdb.svg b/src/legacy/core_plugins/kibana/public/home/tutorial_resources/logos/cockroachdb.svg deleted file mode 100644 index 72f0958f52824..0000000000000 --- a/src/legacy/core_plugins/kibana/public/home/tutorial_resources/logos/cockroachdb.svg +++ /dev/null @@ -1,666 +0,0 @@ - - - -image/svg+xml - - - - - - - - - - - - - - - - - - - - From e8c59f7c75e10d9430a0b46c48ed49c79e076d8a Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Thu, 31 Oct 2019 10:22:41 +0100 Subject: [PATCH 039/132] fix typing problems --- .../public/dashboard/dashboard_app_controller.tsx | 5 +---- .../kibana/public/dashboard/dashboard_state.test.ts | 1 + .../lib/embeddable_saved_object_converters.test.ts | 8 ++++---- .../lib/embeddable_saved_object_converters.ts | 3 +-- .../public/dashboard/lib/migrate_app_state.test.ts | 10 +++++----- x-pack/legacy/plugins/graph/public/render_app.ts | 3 ++- 6 files changed, 14 insertions(+), 16 deletions(-) diff --git a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx index 8a5cee4ff61c7..8b2ae2854ab2d 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx +++ b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx @@ -179,10 +179,7 @@ export class DashboardAppController { [key: string]: DashboardPanelState; } = {}; dashboardStateManager.getPanels().forEach((panel: SavedDashboardPanel) => { - embeddablesMap[panel.panelIndex] = convertSavedDashboardPanelToPanelState( - panel, - dashboardStateManager.getUseMargins() - ); + embeddablesMap[panel.panelIndex] = convertSavedDashboardPanelToPanelState(panel); }); let expandedPanelId; if (dashboardContainer && !isErrorEmbeddable(dashboardContainer)) { diff --git a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_state.test.ts b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_state.test.ts index a25ce1e607f9a..f5160d8442d79 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_state.test.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_state.test.ts @@ -55,6 +55,7 @@ describe('DashboardState', function() { savedDashboard, AppStateClass: getAppStateMock() as AppStateClass, hideWriteControls: false, + kibanaVersion: '7.0.0', }); } diff --git a/src/legacy/core_plugins/kibana/public/dashboard/lib/embeddable_saved_object_converters.test.ts b/src/legacy/core_plugins/kibana/public/dashboard/lib/embeddable_saved_object_converters.test.ts index 99bb6b115b985..d9b3f0b1dec45 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/lib/embeddable_saved_object_converters.test.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/lib/embeddable_saved_object_converters.test.ts @@ -48,7 +48,7 @@ test('convertSavedDashboardPanelToPanelState', () => { version: '7.0.0', }; - expect(convertSavedDashboardPanelToPanelState(savedDashboardPanel, true)).toEqual({ + expect(convertSavedDashboardPanelToPanelState(savedDashboardPanel)).toEqual({ gridData: { x: 0, y: 0, @@ -82,7 +82,7 @@ test('convertSavedDashboardPanelToPanelState does not include undefined id', () version: '7.0.0', }; - const converted = convertSavedDashboardPanelToPanelState(savedDashboardPanel, false); + const converted = convertSavedDashboardPanelToPanelState(savedDashboardPanel); expect(converted.hasOwnProperty('savedObjectId')).toBe(false); }); @@ -103,7 +103,7 @@ test('convertPanelStateToSavedDashboardPanel', () => { type: 'search', }; - expect(convertPanelStateToSavedDashboardPanel(dashboardPanel)).toEqual({ + expect(convertPanelStateToSavedDashboardPanel(dashboardPanel, '8.0.0')).toEqual({ type: 'search', embeddableConfig: { something: 'hi!', @@ -137,6 +137,6 @@ test('convertPanelStateToSavedDashboardPanel will not add an undefined id when n type: 'search', }; - const converted = convertPanelStateToSavedDashboardPanel(dashboardPanel); + const converted = convertPanelStateToSavedDashboardPanel(dashboardPanel, '8.0.0'); expect(converted.hasOwnProperty('id')).toBe(false); }); diff --git a/src/legacy/core_plugins/kibana/public/dashboard/lib/embeddable_saved_object_converters.ts b/src/legacy/core_plugins/kibana/public/dashboard/lib/embeddable_saved_object_converters.ts index 611f30626f4f8..2d42609e1e25f 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/lib/embeddable_saved_object_converters.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/lib/embeddable_saved_object_converters.ts @@ -21,8 +21,7 @@ import { DashboardPanelState } from 'src/legacy/core_plugins/dashboard_embeddabl import { SavedDashboardPanel } from '../types'; export function convertSavedDashboardPanelToPanelState( - savedDashboardPanel: SavedDashboardPanel, - useMargins: boolean + savedDashboardPanel: SavedDashboardPanel ): DashboardPanelState { return { type: savedDashboardPanel.type, diff --git a/src/legacy/core_plugins/kibana/public/dashboard/lib/migrate_app_state.test.ts b/src/legacy/core_plugins/kibana/public/dashboard/lib/migrate_app_state.test.ts index 10c27226300a5..1d1c844e17420 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/lib/migrate_app_state.test.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/lib/migrate_app_state.test.ts @@ -43,7 +43,7 @@ test('migrate app state from 6.0', async () => { getQueryParamName: () => 'a', save: mockSave, }; - migrateAppState(appState); + migrateAppState(appState, '8.0'); expect(appState.uiState).toBeUndefined(); const newPanel = (appState.panels[0] as unknown) as SavedDashboardPanel; @@ -80,7 +80,7 @@ test('migrate sort from 6.1', async () => { save: mockSave, useMargins: false, }; - migrateAppState(appState); + migrateAppState(appState, '8.0'); expect(appState.uiState).toBeUndefined(); const newPanel = (appState.panels[0] as unknown) as SavedDashboardPanel; @@ -112,7 +112,7 @@ test('migrates 6.0 even when uiState does not exist', async () => { getQueryParamName: () => 'a', save: mockSave, }; - migrateAppState(appState); + migrateAppState(appState, '8.0'); expect((appState as any).uiState).toBeUndefined(); const newPanel = (appState.panels[0] as unknown) as SavedDashboardPanel; @@ -147,7 +147,7 @@ test('6.2 migration adjusts w & h without margins', async () => { save: mockSave, useMargins: false, }; - migrateAppState(appState); + migrateAppState(appState, '8.0'); expect((appState as any).uiState).toBeUndefined(); const newPanel = (appState.panels[0] as unknown) as SavedDashboardPanel; @@ -184,7 +184,7 @@ test('6.2 migration adjusts w & h with margins', async () => { save: mockSave, useMargins: true, }; - migrateAppState(appState); + migrateAppState(appState, '8.0'); expect((appState as any).uiState).toBeUndefined(); const newPanel = (appState.panels[0] as unknown) as SavedDashboardPanel; diff --git a/x-pack/legacy/plugins/graph/public/render_app.ts b/x-pack/legacy/plugins/graph/public/render_app.ts index 8625e20ab9c52..381a3353a99b0 100644 --- a/x-pack/legacy/plugins/graph/public/render_app.ts +++ b/x-pack/legacy/plugins/graph/public/render_app.ts @@ -25,6 +25,7 @@ import { DataStart } from 'src/legacy/core_plugins/data/public'; import { AppMountContext, ChromeStart, + LegacyCoreStart, SavedObjectsClientContract, ToastsStart, UiSettingsClientContract, @@ -80,7 +81,7 @@ export interface LegacyAngularInjectedDependencies { export const renderApp = ({ appBasePath, element, ...deps }: GraphDependencies) => { const graphAngularModule = createLocalAngularModule(deps.coreStart); - configureAppAngularModule(graphAngularModule); + configureAppAngularModule(graphAngularModule, deps.coreStart as LegacyCoreStart); initGraphApp(graphAngularModule, deps); const $injector = mountGraphApp(appBasePath, element); return () => $injector.get('$rootScope').$destroy(); From e238a5dcc2ad62d7a383d2b1831eaae0234c42eb Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Thu, 31 Oct 2019 10:33:12 +0100 Subject: [PATCH 040/132] fix other bugs --- src/legacy/core_plugins/console/np_ready/public/legacy.ts | 2 +- src/legacy/core_plugins/kibana/public/dashboard/render_app.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/legacy/core_plugins/console/np_ready/public/legacy.ts b/src/legacy/core_plugins/console/np_ready/public/legacy.ts index 69d3bd61c7e0c..d32484be3c95c 100644 --- a/src/legacy/core_plugins/console/np_ready/public/legacy.ts +++ b/src/legacy/core_plugins/console/np_ready/public/legacy.ts @@ -30,8 +30,8 @@ import uiRoutes from 'ui/routes'; import { DOC_LINK_VERSION } from 'ui/documentation_links'; import { I18nContext } from 'ui/i18n'; import { ResizeChecker } from 'ui/resize_checker'; -import 'ui/autoload/styles'; /* eslint-enable @kbn/eslint/no-restricted-paths */ + import template from '../../public/quarantined/index.html'; import { App, AppUnmount, NotificationsSetup } from '../../../../../core/public'; diff --git a/src/legacy/core_plugins/kibana/public/dashboard/render_app.ts b/src/legacy/core_plugins/kibana/public/dashboard/render_app.ts index 20c4b9ca23e0b..5d42d11a1a8d1 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/render_app.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/render_app.ts @@ -20,7 +20,7 @@ import { EuiConfirmModal } from '@elastic/eui'; import angular, { IModule } from 'angular'; import { IPrivate } from 'ui/private'; -import { i18nDirective, i18nFilter, I18nProvider } from '@kbn/i18n/src/angular'; +import { i18nDirective, i18nFilter, I18nProvider } from '@kbn/i18n/angular'; // @ts-ignore import { GlobalStateProvider } from 'ui/state_management/global_state'; // @ts-ignore From 3ef62542c610cdfb3a1673c0cb5b57a0d16f21bb Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Thu, 31 Oct 2019 12:28:31 +0100 Subject: [PATCH 041/132] got rid of two other angular dependencies --- .../kibana/public/dashboard/dashboard_app.tsx | 6 ++-- .../dashboard/dashboard_app_controller.tsx | 34 ++++++++++++------- .../kibana/public/dashboard/index.ts | 7 +--- .../kibana/public/dashboard/plugin.ts | 17 +++++++--- .../kibana/public/dashboard/render_app.ts | 4 +-- 5 files changed, 41 insertions(+), 27 deletions(-) diff --git a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app.tsx b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app.tsx index 11c6d13fa0589..6079d88963dc7 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app.tsx +++ b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app.tsx @@ -102,15 +102,15 @@ export function initDashboardAppDirective(app: any, deps: RenderDeps) { $routeParams: { id?: string; }, - getAppState: { - previouslyStored: () => TAppState | undefined; - } + getAppState: any, + globalState: any ) => new DashboardAppController({ $route, $scope, $routeParams, getAppState, + globalState, kbnUrl, AppStateClass: AppState, config, diff --git a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx index 8b2ae2854ab2d..f0e363d488014 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx +++ b/src/legacy/core_plugins/kibana/public/dashboard/dashboard_app_controller.tsx @@ -33,19 +33,23 @@ import { showSaveModal, SaveResult } from 'ui/saved_objects/show_saved_object_sa import { showShareContextMenu } from 'ui/share'; import { migrateLegacyQuery } from 'ui/utils/migrate_legacy_query'; -import { - AppStateClass as TAppStateClass, - AppState as TAppState, -} from 'ui/state_management/app_state'; +import { State } from 'ui/state_management/state'; + +import { AppStateClass as TAppStateClass } from 'ui/state_management/app_state'; import { KbnUrl } from 'ui/url/kbn_url'; import { Filter } from '@kbn/es-query'; import { IndexPattern } from 'ui/index_patterns'; -import { Query, SavedQuery } from 'src/legacy/core_plugins/data/public'; import { SaveOptions } from 'ui/saved_objects/saved_object'; import { Subscription } from 'rxjs'; import { SavedObjectFinder } from 'ui/saved_objects/components/saved_object_finder'; -import { extractTimeFilter, changeTimeFilter } from '../../../data/public'; +import { + extractTimeFilter, + changeTimeFilter, + FilterStateManager, + Query, + SavedQuery, +} from '../../../data/public'; import { DashboardContainer, @@ -80,9 +84,8 @@ export interface DashboardAppControllerDependencies extends RenderDeps { $scope: DashboardAppScope; $route: any; $routeParams: any; - getAppState: { - previouslyStored: () => TAppState | undefined; - }; + getAppState: any; + globalState: State; indexPatterns: { getDefault: () => Promise; }; @@ -104,6 +107,7 @@ export class DashboardAppController { $route, $routeParams, getAppState, + globalState, dashboardConfig, localStorage, kbnUrl, @@ -111,8 +115,6 @@ export class DashboardAppController { indexPatterns, config, confirmModal, - queryFilter, - getUnhashableStates, shareContextMenuExtensions, savedQueryService, embeddables, @@ -121,8 +123,16 @@ export class DashboardAppController { dataStart: { timefilter: { timefilter }, }, - core: { notifications, overlays, chrome, injectedMetadata, docLinks }, + npDataStart, + core: { notifications, overlays, chrome, injectedMetadata }, }: DashboardAppControllerDependencies) { + new FilterStateManager(globalState, getAppState, npDataStart.query.filterManager); + const queryFilter = npDataStart.query.filterManager; + + function getUnhashableStates(): State[] { + return [getAppState(), globalState].filter(Boolean); + } + let lastReloadRequestTime = 0; const dash = ($scope.dash = $route.current.locals.dash); diff --git a/src/legacy/core_plugins/kibana/public/dashboard/index.ts b/src/legacy/core_plugins/kibana/public/dashboard/index.ts index ea9f98016227e..622f047a9b9dc 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/index.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/index.ts @@ -24,8 +24,6 @@ import { docTitle } from 'ui/doc_title/doc_title'; import chrome from 'ui/chrome'; import { IPrivate } from 'ui/private'; import { ShareContextMenuExtensionsRegistryProvider } from 'ui/share'; -import { getUnhashableStatesProvider } from 'ui/state_management/state_hashing/get_unhashable_states_provider'; -import { FilterBarQueryFilterProvider } from 'ui/filter_manager/query_filter'; import { DashboardPlugin, LegacyAngularInjectedDependencies } from './plugin'; import { start as data } from '../../../data/public/legacy'; import { localApplicationService } from '../local_application_service'; @@ -43,14 +41,10 @@ async function getAngularDependencies(): Promise('Private'); - const queryFilter = Private(FilterBarQueryFilterProvider); - const getUnhashableStates = Private(getUnhashableStatesProvider); const shareContextMenuExtensions = Private(ShareContextMenuExtensionsRegistryProvider); const savedObjectRegistry = Private(SavedObjectRegistryProvider); return { - queryFilter, - getUnhashableStates, shareContextMenuExtensions, dashboardConfig: injector.get('dashboardConfig'), savedObjectRegistry, @@ -70,6 +64,7 @@ async function getAngularDependencies(): Promise; navigation: NavigationStart; } @@ -59,6 +59,7 @@ export interface DashboardPluginSetupDependencies { export class DashboardPlugin implements Plugin { private startDependencies: { dataStart: DataStart; + npDataStart: NpDataStart; savedObjectsClient: SavedObjectsClientContract; embeddables: ReturnType; navigation: NavigationStart; @@ -77,7 +78,13 @@ export class DashboardPlugin implements Plugin { if (this.startDependencies === null) { throw new Error('not started yet'); } - const { dataStart, savedObjectsClient, embeddables, navigation } = this.startDependencies; + const { + dataStart, + savedObjectsClient, + embeddables, + navigation, + npDataStart, + } = this.startDependencies; const angularDependencies = await getAngularDependencies(); const deps: RenderDeps = { core: contextCore as LegacyCoreStart, @@ -85,6 +92,7 @@ export class DashboardPlugin implements Plugin { ...angularDependencies, navigation, dataStart, + npDataStart, indexPatterns: dataStart.indexPatterns.indexPatterns, savedObjectsClient, chrome: contextCore.chrome, @@ -105,10 +113,11 @@ export class DashboardPlugin implements Plugin { start( { savedObjects: { client: savedObjectsClient } }: CoreStart, - { data: dataStart, embeddables, navigation }: DashboardPluginStartDependencies + { data: dataStart, embeddables, navigation, npData }: DashboardPluginStartDependencies ) { this.startDependencies = { dataStart, + npDataStart: npData, savedObjectsClient, embeddables, navigation, diff --git a/src/legacy/core_plugins/kibana/public/dashboard/render_app.ts b/src/legacy/core_plugins/kibana/public/dashboard/render_app.ts index 5d42d11a1a8d1..f753849fe13d7 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/render_app.ts +++ b/src/legacy/core_plugins/kibana/public/dashboard/render_app.ts @@ -63,14 +63,14 @@ import { import { SavedQueryService } from '../../../data/public/search/search_bar/lib/saved_query_service'; import { EmbeddablePublicPlugin } from '../../../../../plugins/embeddable/public'; import { NavigationStart } from '../../../navigation/public'; +import { DataPublicPluginStart as NpDataStart } from '../../../../../plugins/data/public'; export interface RenderDeps { core: LegacyCoreStart; indexPatterns: DataStart['indexPatterns']['indexPatterns']; dataStart: DataStart; + npDataStart: NpDataStart; navigation: NavigationStart; - queryFilter: any; - getUnhashableStates: any; shareContextMenuExtensions: any; savedObjectsClient: SavedObjectsClientContract; savedObjectRegistry: any; From ce902cd1566dd28f9015b4f919f65bc48f9d2063 Mon Sep 17 00:00:00 2001 From: Joe Reuter Date: Thu, 31 Oct 2019 14:31:24 +0100 Subject: [PATCH 042/132] fix i18n issue --- .../ui/public/legacy_compat/ensure_default_index_pattern.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/legacy/ui/public/legacy_compat/ensure_default_index_pattern.tsx b/src/legacy/ui/public/legacy_compat/ensure_default_index_pattern.tsx index daedd9f329ed0..06b84c85f0651 100644 --- a/src/legacy/ui/public/legacy_compat/ensure_default_index_pattern.tsx +++ b/src/legacy/ui/public/legacy_compat/ensure_default_index_pattern.tsx @@ -76,7 +76,7 @@ export async function ensureDefaultIndexPattern( Date: Thu, 31 Oct 2019 15:49:06 +0100 Subject: [PATCH 043/132] fix various issues --- .../kibana/public/discover/breadcrumbs.ts | 2 +- src/legacy/ui/public/vis/vis_filters/vis_filters.js | 10 +++------- x-pack/legacy/plugins/graph/public/index.ts | 2 ++ x-pack/legacy/plugins/graph/public/plugin.ts | 8 ++++++-- x-pack/legacy/plugins/graph/public/render_app.ts | 12 +++++++----- 5 files changed, 19 insertions(+), 15 deletions(-) diff --git a/src/legacy/core_plugins/kibana/public/discover/breadcrumbs.ts b/src/legacy/core_plugins/kibana/public/discover/breadcrumbs.ts index 51e0dcba1cad0..6c3856932c96c 100644 --- a/src/legacy/core_plugins/kibana/public/discover/breadcrumbs.ts +++ b/src/legacy/core_plugins/kibana/public/discover/breadcrumbs.ts @@ -34,7 +34,7 @@ export function getSavedSearchBreadcrumbs($route: any) { return [ ...getRootBreadcrumbs(), { - text: $route.current.locals.savedSearch.id, + text: $route.current.locals.savedObjects.savedSearch.id, }, ]; } diff --git a/src/legacy/ui/public/vis/vis_filters/vis_filters.js b/src/legacy/ui/public/vis/vis_filters/vis_filters.js index 9343585fa9508..fa12237808910 100644 --- a/src/legacy/ui/public/vis/vis_filters/vis_filters.js +++ b/src/legacy/ui/public/vis/vis_filters/vis_filters.js @@ -17,8 +17,7 @@ * under the License. */ -import _ from 'lodash'; -import { pushFilterBarFilters } from '../push_filters'; +import { npStart } from 'ui/new_platform'; import { onBrushEvent } from './brush_event'; import { uniqFilters } from '../../../../../plugins/data/public'; import { toggleFilterNegated } from '@kbn/es-query'; @@ -104,14 +103,11 @@ const createFiltersFromEvent = (event) => { return filters; }; -const VisFiltersProvider = (getAppState, $timeout) => { +const VisFiltersProvider = () => { const pushFilters = (filters, simulate) => { - const appState = getAppState(); if (filters.length && !simulate) { - pushFilterBarFilters(appState, uniqFilters(filters)); - // to trigger angular digest cycle, we can get rid of this once we have either new filterManager or actions API - $timeout(_.noop, 0); + npStart.plugins.data.query.filterManager.addFilters(uniqFilters(filters)); } }; diff --git a/x-pack/legacy/plugins/graph/public/index.ts b/x-pack/legacy/plugins/graph/public/index.ts index 5e500367ccdc5..c49fa86b010a7 100644 --- a/x-pack/legacy/plugins/graph/public/index.ts +++ b/x-pack/legacy/plugins/graph/public/index.ts @@ -20,6 +20,7 @@ import { SavedObjectRegistryProvider } from 'ui/saved_objects/saved_object_regis import { npSetup, npStart } from 'ui/new_platform'; import { Storage } from '../../../../../src/plugins/kibana_utils/public'; import { start as data } from '../../../../../src/legacy/core_plugins/data/public/legacy'; +import { start as navigation } from '../../../../../src/legacy/core_plugins/navigation/public/legacy'; import { GraphPlugin } from './plugin'; // @ts-ignore @@ -54,6 +55,7 @@ async function getAngularInjectedDependencies(): Promise; + navigation: NavigationStart; } export interface GraphPluginSetupDependencies { @@ -30,6 +32,7 @@ export interface GraphPluginStartDependencies { export class GraphPlugin implements Plugin { private dataStart: DataStart | null = null; + private navigationStart: NavigationStart | null = null; private npDataStart: ReturnType | null = null; private savedObjectsClient: SavedObjectsClientContract | null = null; private angularDependencies: LegacyAngularInjectedDependencies | null = null; @@ -42,6 +45,7 @@ export class GraphPlugin implements Plugin { const { renderApp } = await import('./render_app'); return renderApp({ ...params, + navigation: this.navigationStart!, npData: this.npDataStart!, savedObjectsClient: this.savedObjectsClient!, xpackInfo, @@ -66,9 +70,9 @@ export class GraphPlugin implements Plugin { start( core: CoreStart, - { data, npData, __LEGACY: { angularDependencies } }: GraphPluginStartDependencies + { data, npData, navigation, __LEGACY: { angularDependencies } }: GraphPluginStartDependencies ) { - // TODO is this really the right way? I though the app context would give us those + this.navigationStart = navigation; this.dataStart = data; this.npDataStart = npData; this.angularDependencies = angularDependencies; diff --git a/x-pack/legacy/plugins/graph/public/render_app.ts b/x-pack/legacy/plugins/graph/public/render_app.ts index 381a3353a99b0..f92490521ba55 100644 --- a/x-pack/legacy/plugins/graph/public/render_app.ts +++ b/x-pack/legacy/plugins/graph/public/render_app.ts @@ -33,6 +33,7 @@ import { // @ts-ignore import { initGraphApp } from './app'; import { Plugin as DataPlugin } from '../../../../../src/plugins/data/public'; +import { NavigationStart } from '../../../../../src/legacy/core_plugins/navigation/public'; /** * These are dependencies of the Graph app besides the base dependencies @@ -45,6 +46,7 @@ export interface GraphDependencies extends LegacyAngularInjectedDependencies { appBasePath: string; capabilities: Record>; coreStart: AppMountContext['core']; + navigation: NavigationStart; chrome: ChromeStart; config: UiSettingsClientContract; toastNotifications: ToastsStart; @@ -80,7 +82,7 @@ export interface LegacyAngularInjectedDependencies { } export const renderApp = ({ appBasePath, element, ...deps }: GraphDependencies) => { - const graphAngularModule = createLocalAngularModule(deps.coreStart); + const graphAngularModule = createLocalAngularModule(deps.navigation); configureAppAngularModule(graphAngularModule, deps.coreStart as LegacyCoreStart); initGraphApp(graphAngularModule, deps); const $injector = mountGraphApp(appBasePath, element); @@ -109,9 +111,9 @@ function mountGraphApp(appBasePath: string, element: HTMLElement) { return $injector; } -function createLocalAngularModule(core: AppMountContext['core']) { +function createLocalAngularModule(navigation: NavigationStart) { createLocalI18nModule(); - createLocalTopNavModule(); + createLocalTopNavModule(navigation); createLocalConfirmModalModule(); const graphAngularModule = angular.module(moduleName, [ @@ -130,11 +132,11 @@ function createLocalConfirmModalModule() { .directive('confirmModal', reactDirective => reactDirective(EuiConfirmModal)); } -function createLocalTopNavModule() { +function createLocalTopNavModule(navigation: NavigationStart) { angular .module('graphTopNav', ['react']) .directive('kbnTopNav', createTopNavDirective) - .directive('kbnTopNavHelper', createTopNavHelper); + .directive('kbnTopNavHelper', createTopNavHelper(navigation.ui)); } function createLocalI18nModule() { From 51d35b6b1aade7421a4b9a92c6c7fef3aa720a9a Mon Sep 17 00:00:00 2001 From: maryia-lapata Date: Thu, 31 Oct 2019 18:31:13 +0300 Subject: [PATCH 044/132] Shim visualize plugin --- .../console/np_ready/public/legacy.ts | 1 - src/legacy/core_plugins/data/public/index.ts | 11 + .../data/public/shim/legacy_module.ts | 185 +-- .../core_plugins/kibana/public/kibana.js | 2 +- .../kibana/public/visualize/app.js | 173 +++ .../public/visualize/editor/editor.html | 10 +- .../kibana/public/visualize/editor/editor.js | 1101 ++++++++--------- .../public/visualize/editor/visualization.js | 12 +- .../visualize/editor/visualization_editor.js | 10 +- .../visualize/embeddable/get_index_pattern.ts | 3 +- .../visualize_embeddable_factory.tsx | 35 +- .../public/visualize/help_menu/help_menu.js | 12 +- .../visualize/help_menu/help_menu_util.js | 4 +- .../kibana/public/visualize/index.ts | 87 ++ .../public/visualize/{index.js => index_o.js} | 0 .../public/visualize/kibana_services.ts | 124 +- .../visualize/listing/visualize_listing.js | 55 +- .../listing/visualize_listing_table.js | 6 +- .../kibana/public/visualize/plugin.ts | 147 +++ .../kibana/public/visualize/render_app.ts | 300 +++++ .../kibana/public/visualize/visualize_app.ts | 95 ++ .../public/visualize/wizard/new_vis_modal.tsx | 10 +- src/legacy/ui/public/chrome/api/angular.js | 4 +- .../ui/public/kbn_top_nav/kbn_top_nav.js | 7 +- .../public/legacy_compat/angular_config.tsx | 46 +- .../ensure_default_index_pattern.tsx | 103 ++ src/legacy/ui/public/legacy_compat/index.ts | 1 + .../public/vis/editors/default/agg_group.js | 116 +- .../ui/public/vis/editors/default/index.ts | 9 + .../ui/public/vis/editors/default/sidebar.js | 7 +- .../public/vis/editors/default/vis_options.js | 168 +-- 31 files changed, 1865 insertions(+), 979 deletions(-) create mode 100644 src/legacy/core_plugins/kibana/public/visualize/app.js create mode 100644 src/legacy/core_plugins/kibana/public/visualize/index.ts rename src/legacy/core_plugins/kibana/public/visualize/{index.js => index_o.js} (100%) create mode 100644 src/legacy/core_plugins/kibana/public/visualize/plugin.ts create mode 100644 src/legacy/core_plugins/kibana/public/visualize/render_app.ts create mode 100644 src/legacy/core_plugins/kibana/public/visualize/visualize_app.ts create mode 100644 src/legacy/ui/public/legacy_compat/ensure_default_index_pattern.tsx diff --git a/src/legacy/core_plugins/console/np_ready/public/legacy.ts b/src/legacy/core_plugins/console/np_ready/public/legacy.ts index 1a2d312823f6f..d32484be3c95c 100644 --- a/src/legacy/core_plugins/console/np_ready/public/legacy.ts +++ b/src/legacy/core_plugins/console/np_ready/public/legacy.ts @@ -30,7 +30,6 @@ import uiRoutes from 'ui/routes'; import { DOC_LINK_VERSION } from 'ui/documentation_links'; import { I18nContext } from 'ui/i18n'; import { ResizeChecker } from 'ui/resize_checker'; -import 'ui/capabilities/route_setup'; /* eslint-enable @kbn/eslint/no-restricted-paths */ import template from '../../public/quarantined/index.html'; diff --git a/src/legacy/core_plugins/data/public/index.ts b/src/legacy/core_plugins/data/public/index.ts index 502ca206e8e12..5dc234904e173 100644 --- a/src/legacy/core_plugins/data/public/index.ts +++ b/src/legacy/core_plugins/data/public/index.ts @@ -62,6 +62,17 @@ export { mockIndexPattern, } from './index_patterns'; +/** + * These functions can be used to register the angular wrappers for react components + * in a separate module to use them without relying on the uiModules module tree. + * */ +export { + createFilterBarHelper, + createFilterBarDirective, + createApplyFiltersPopoverDirective, + createApplyFiltersPopoverHelper, +} from './shim/legacy_module'; + export { TimeHistoryContract, TimefilterContract, diff --git a/src/legacy/core_plugins/data/public/shim/legacy_module.ts b/src/legacy/core_plugins/data/public/shim/legacy_module.ts index b0ed3d43a4c8c..2f981035ee108 100644 --- a/src/legacy/core_plugins/data/public/shim/legacy_module.ts +++ b/src/legacy/core_plugins/data/public/shim/legacy_module.ts @@ -27,97 +27,108 @@ import { npStart } from 'ui/new_platform'; import { FilterBar, ApplyFiltersPopover } from '../filter'; import { IndexPatterns } from '../index_patterns/index_patterns'; +/** @internal */ +export const createFilterBarDirective = () => { + return { + restrict: 'E', + template: '', + compile: (elem: any) => { + const child = document.createElement('filter-bar-helper'); + + // Copy attributes to the child directive + for (const attr of elem[0].attributes) { + child.setAttribute(attr.name, attr.value); + } + + child.setAttribute('ui-settings', 'uiSettings'); + child.setAttribute('doc-links', 'docLinks'); + child.setAttribute('plugin-data-start', 'pluginDataStart'); + + // Append helper directive + elem.append(child); + + const linkFn = ($scope: any) => { + $scope.uiSettings = npStart.core.uiSettings; + $scope.docLinks = npStart.core.docLinks; + $scope.pluginDataStart = npStart.plugins.data; + }; + + return linkFn; + }, + }; +}; + +/** @internal */ +export const createFilterBarHelper = (reactDirective: any) => { + return reactDirective(wrapInI18nContext(FilterBar), [ + ['uiSettings', { watchDepth: 'reference' }], + ['docLinks', { watchDepth: 'reference' }], + ['onFiltersUpdated', { watchDepth: 'reference' }], + ['indexPatterns', { watchDepth: 'collection' }], + ['filters', { watchDepth: 'collection' }], + ['className', { watchDepth: 'reference' }], + ['pluginDataStart', { watchDepth: 'reference' }], + ]); +}; + +/** @internal */ +export const createApplyFiltersPopoverDirective = () => { + return { + restrict: 'E', + template: '', + compile: (elem: any) => { + const child = document.createElement('apply-filters-popover-helper'); + + // Copy attributes to the child directive + for (const attr of elem[0].attributes) { + child.setAttribute(attr.name, attr.value); + } + + // Add a key attribute that will force a full rerender every time that + // a filter changes. + child.setAttribute('key', 'key'); + + // Append helper directive + elem.append(child); + + const linkFn = ($scope: any, _: any, $attr: any) => { + // Watch only for filter changes to update key. + $scope.$watch( + () => { + return $scope.$eval($attr.filters) || []; + }, + (newVal: any) => { + $scope.key = Date.now(); + }, + true + ); + }; + + return linkFn; + }, + }; +}; + +/** @internal */ +export const createApplyFiltersPopoverHelper = (reactDirective: any) => + reactDirective(wrapInI18nContext(ApplyFiltersPopover), [ + ['filters', { watchDepth: 'collection' }], + ['onCancel', { watchDepth: 'reference' }], + ['onSubmit', { watchDepth: 'reference' }], + ['indexPatterns', { watchDepth: 'collection' }], + + // Key is needed to trigger a full rerender of the component + 'key', + ]); + /** @internal */ export const initLegacyModule = once((indexPatterns: IndexPatterns): void => { uiModules .get('app/kibana', ['react']) - .directive('filterBar', () => { - return { - restrict: 'E', - template: '', - compile: (elem: any) => { - const child = document.createElement('filter-bar-helper'); - - // Copy attributes to the child directive - for (const attr of elem[0].attributes) { - child.setAttribute(attr.name, attr.value); - } - - child.setAttribute('ui-settings', 'uiSettings'); - child.setAttribute('doc-links', 'docLinks'); - child.setAttribute('plugin-data-start', 'pluginDataStart'); - - // Append helper directive - elem.append(child); - - const linkFn = ($scope: any) => { - $scope.uiSettings = npStart.core.uiSettings; - $scope.docLinks = npStart.core.docLinks; - $scope.pluginDataStart = npStart.plugins.data; - }; - - return linkFn; - }, - }; - }) - .directive('filterBarHelper', (reactDirective: any) => { - return reactDirective(wrapInI18nContext(FilterBar), [ - ['uiSettings', { watchDepth: 'reference' }], - ['docLinks', { watchDepth: 'reference' }], - ['onFiltersUpdated', { watchDepth: 'reference' }], - ['indexPatterns', { watchDepth: 'collection' }], - ['filters', { watchDepth: 'collection' }], - ['className', { watchDepth: 'reference' }], - ['pluginDataStart', { watchDepth: 'reference' }], - ]); - }) - .directive('applyFiltersPopover', () => { - return { - restrict: 'E', - template: '', - compile: (elem: any) => { - const child = document.createElement('apply-filters-popover-helper'); - - // Copy attributes to the child directive - for (const attr of elem[0].attributes) { - child.setAttribute(attr.name, attr.value); - } - - // Add a key attribute that will force a full rerender every time that - // a filter changes. - child.setAttribute('key', 'key'); - - // Append helper directive - elem.append(child); - - const linkFn = ($scope: any, _: any, $attr: any) => { - // Watch only for filter changes to update key. - $scope.$watch( - () => { - return $scope.$eval($attr.filters) || []; - }, - (newVal: any) => { - $scope.key = Date.now(); - }, - true - ); - }; - - return linkFn; - }, - }; - }) - .directive('applyFiltersPopoverHelper', (reactDirective: any) => - reactDirective(wrapInI18nContext(ApplyFiltersPopover), [ - ['filters', { watchDepth: 'collection' }], - ['onCancel', { watchDepth: 'reference' }], - ['onSubmit', { watchDepth: 'reference' }], - ['indexPatterns', { watchDepth: 'collection' }], - - // Key is needed to trigger a full rerender of the component - 'key', - ]) - ); + .directive('filterBar', createFilterBarDirective) + .directive('filterBarHelper', createFilterBarHelper) + .directive('applyFiltersPopover', createApplyFiltersPopoverDirective) + .directive('applyFiltersPopoverHelper', createApplyFiltersPopoverHelper); uiModules.get('kibana/index_patterns').value('indexPatterns', indexPatterns); }); diff --git a/src/legacy/core_plugins/kibana/public/kibana.js b/src/legacy/core_plugins/kibana/public/kibana.js index af7c9131caf45..eecadd8896184 100644 --- a/src/legacy/core_plugins/kibana/public/kibana.js +++ b/src/legacy/core_plugins/kibana/public/kibana.js @@ -39,7 +39,7 @@ import 'uiExports/managementSections'; import 'uiExports/indexManagement'; import 'uiExports/devTools'; import 'uiExports/docViews'; -import 'uiExports/embeddableFactories'; +//import 'uiExports/embeddableFactories'; import 'uiExports/embeddableActions'; import 'uiExports/inspectorViews'; import 'uiExports/search'; diff --git a/src/legacy/core_plugins/kibana/public/visualize/app.js b/src/legacy/core_plugins/kibana/public/visualize/app.js new file mode 100644 index 0000000000000..62d016727ba97 --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/visualize/app.js @@ -0,0 +1,173 @@ +/* + * 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 { find } from 'lodash'; +import { i18n } from '@kbn/i18n'; + +import editorTemplate from './editor/editor.html'; +import visualizeListingTemplate from './listing/visualize_listing.html'; + +import { initVisualizeAppDirective } from './visualize_app'; +import { VisualizeConstants } from './visualize_constants'; +import { VisualizeListingController } from './listing/visualize_listing'; + +import { + getLandingBreadcrumbs, + getWizardStep1Breadcrumbs, + getCreateBreadcrumbs, + getEditBreadcrumbs +} from './breadcrumbs'; + +import { ensureDefaultIndexPattern, FeatureCatalogueCategory } from './kibana_services'; + +export function initVisualizeApp(app, deps) { + initVisualizeAppDirective(app, deps); + + app.config(function ($routeProvider) { + const defaults = { + requireUICapability: 'visualize.show', + badge: () => { + if (deps.visualizeCapabilities.save) { + return undefined; + } + + return { + text: i18n.translate('kbn.visualize.badge.readOnly.text', { + defaultMessage: 'Read only', + }), + tooltip: i18n.translate('kbn.visualize.badge.readOnly.tooltip', { + defaultMessage: 'Unable to save visualizations', + }), + iconType: 'glasses', + }; + }, + }; + + $routeProvider + .when(VisualizeConstants.LANDING_PAGE_PATH, { + ...defaults, + template: visualizeListingTemplate, + k7Breadcrumbs: getLandingBreadcrumbs, + controller: VisualizeListingController, + controllerAs: 'listingController', + resolve: { + createNewVis: () => false, + hasDefaultIndex: ($rootScope, kbnUrl) => ensureDefaultIndexPattern(deps.core, deps.dataStart, $rootScope, kbnUrl), + }, + }) + .when(VisualizeConstants.WIZARD_STEP_1_PAGE_PATH, { + ...defaults, + template: visualizeListingTemplate, + k7Breadcrumbs: getWizardStep1Breadcrumbs, + controller: VisualizeListingController, + controllerAs: 'listingController', + resolve: { + createNewVis: () => true, + hasDefaultIndex: ($rootScope, kbnUrl) => ensureDefaultIndexPattern(deps.core, deps.dataStart, $rootScope, kbnUrl), + }, + }) + .when(VisualizeConstants.CREATE_PATH, { + ...defaults, + template: editorTemplate, + k7Breadcrumbs: getCreateBreadcrumbs, + resolve: { + savedVis: function (redirectWhenMissing, $route, $rootScope, kbnUrl) { + const { core, dataStart, savedVisualizations } = deps; + const visTypes = deps.visualizations.types.all(); + const visType = find(visTypes, { name: $route.current.params.type }); + const shouldHaveIndex = visType.requiresSearch && visType.options.showIndexSelection; + const hasIndex = $route.current.params.indexPattern || $route.current.params.savedSearchId; + if (shouldHaveIndex && !hasIndex) { + throw new Error( + i18n.translate('kbn.visualize.createVisualization.noIndexPatternOrSavedSearchIdErrorMessage', { + defaultMessage: 'You must provide either an indexPattern or a savedSearchId', + }) + ); + } + + return ensureDefaultIndexPattern(core, dataStart, $rootScope, kbnUrl).then(() => savedVisualizations.get($route.current.params)) + .then(savedVis => { + if (savedVis.vis.type.setup) { + return savedVis.vis.type.setup(savedVis) + .catch(() => savedVis); + } + return savedVis; + }) + .catch(redirectWhenMissing({ + '*': '/visualize' + })); + } + } + }) + .when(`${VisualizeConstants.EDIT_PATH}/:id`, { + ...defaults, + template: editorTemplate, + k7Breadcrumbs: getEditBreadcrumbs, + resolve: { + savedVis: function (redirectWhenMissing, $route, $rootScope, kbnUrl) { + const { core, dataStart, savedVisualizations } = deps; + return ensureDefaultIndexPattern(core, dataStart, $rootScope, kbnUrl) + .then(() => savedVisualizations.get($route.current.params.id)) + .then(savedVis => { + deps.chrome.recentlyAccessed.add( + savedVis.getFullPath(), + savedVis.title, + savedVis.id + ); + return savedVis; + }) + .then(savedVis => { + if (savedVis.vis.type.setup) { + return savedVis.vis.type.setup(savedVis).catch(() => savedVis); + } + return savedVis; + }) + .catch( + redirectWhenMissing({ + 'visualization': '/visualize', + 'search': '/management/kibana/objects/savedVisualizations/' + $route.current.params.id, + 'index-pattern': '/management/kibana/objects/savedVisualizations/' + $route.current.params.id, + 'index-pattern-field': '/management/kibana/objects/savedVisualizations/' + $route.current.params.id + }) + ); + } + } + }) + .when(`visualize/:tail*?`, { + redirectTo: `/${deps.core.injectedMetadata.getInjectedVar('kbnDefaultAppId')}`, + }); + + + }); + + deps.FeatureCatalogueRegistryProvider.register(() => { + return { + id: 'visualize', + title: 'Visualize', + description: i18n.translate('kbn.visualize.visualizeDescription', { + defaultMessage: + 'Create visualizations and aggregate data stores in your Elasticsearch indices.', + }), + icon: 'visualizeApp', + path: `/app/kibana#${VisualizeConstants.LANDING_PAGE_PATH}`, + showOnHomePage: true, + category: FeatureCatalogueCategory.DATA, + }; + }); +} diff --git a/src/legacy/core_plugins/kibana/public/visualize/editor/editor.html b/src/legacy/core_plugins/kibana/public/visualize/editor/editor.html index 0ef3cce832bc7..bdd53f5733d2d 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/editor/editor.html +++ b/src/legacy/core_plugins/kibana/public/visualize/editor/editor.html @@ -1,7 +1,7 @@