"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.App = App; require("./app.scss"); var _react = _interopRequireWildcard(require("react")); var _i18n = require("@kbn/i18n"); var _eui = require("@elastic/eui"); var _public = require("@kbn/kibana-react-plugin/public"); var _lens_top_nav = require("./lens_top_nav"); var _state_management = require("../state_management"); var _save_modal_container = require("./save_modal_container"); var _constants = require("../../common/constants"); var _lens_document_equality = require("./lens_document_equality"); var _service = require("../data_views_service/service"); var _lens_slice = require("../state_management/lens_slice"); var _get_application_user_messages = require("./get_application_user_messages"); function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); } function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; } /* * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one * or more contributor license agreements. Licensed under the Elastic License * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ function App({ history, onAppLeave, redirectTo, editorFrame, initialInput, incomingState, redirectToOrigin, setHeaderActionMenu, datasourceMap, visualizationMap, contextOriginatingApp, topNavMenuEntryGenerators, initialContext, theme$, coreStart, savedObjectStore }) { var _incomingState$origin; const lensAppServices = (0, _public.useKibana)().services; const { data, dataViews, uiActions, uiSettings, chrome, inspector: lensInspector, application, savedObjectsTagging, getOriginatingAppName, spaces, http, notifications, executionContext, // Temporarily required until the 'by value' paradigm is default. dashboardFeatureFlag, locator, share, serverless } = lensAppServices; const saveAndExit = (0, _react.useRef)(); const dispatch = (0, _state_management.useLensDispatch)(); const dispatchSetState = (0, _react.useCallback)(state => dispatch((0, _state_management.setState)(state)), [dispatch]); const { persistedDoc, sharingSavedObjectProps, isLinkedToOriginatingApp, searchSessionId, datasourceStates, isLoading, isSaveable, visualization, annotationGroups } = (0, _state_management.useLensSelector)(state => state.lens); const selectorDependencies = (0, _react.useMemo)(() => ({ datasourceMap, visualizationMap, extractFilterReferences: data.query.filterManager.extract.bind(data.query.filterManager) }), [datasourceMap, visualizationMap, data.query.filterManager]); const currentDoc = (0, _state_management.useLensSelector)(state => (0, _state_management.selectSavedObjectFormat)(state, selectorDependencies)); const shortUrls = (0, _react.useMemo)(() => share === null || share === void 0 ? void 0 : share.url.shortUrls.get(null), [share]); // Used to show a popover that guides the user towards changing the date range when no data is available. const [indicateNoData, setIndicateNoData] = (0, _react.useState)(false); const [isSaveModalVisible, setIsSaveModalVisible] = (0, _react.useState)(false); const [lastKnownDoc, setLastKnownDoc] = (0, _react.useState)(undefined); const [initialDocFromContext, setInitialDocFromContext] = (0, _react.useState)(undefined); const [isGoBackToVizEditorModalVisible, setIsGoBackToVizEditorModalVisible] = (0, _react.useState)(false); const [shouldCloseAndSaveTextBasedQuery, setShouldCloseAndSaveTextBasedQuery] = (0, _react.useState)(false); const savedObjectId = initialInput === null || initialInput === void 0 ? void 0 : initialInput.savedObjectId; (0, _react.useEffect)(() => { if (currentDoc) { setLastKnownDoc(currentDoc); } }, [currentDoc]); const showNoDataPopover = (0, _react.useCallback)(() => { setIndicateNoData(true); }, [setIndicateNoData]); (0, _public.useExecutionContext)(executionContext, { type: 'application', id: savedObjectId || 'new', page: 'editor' }); (0, _react.useEffect)(() => { if (indicateNoData) { setIndicateNoData(false); } }, [setIndicateNoData, indicateNoData, searchSessionId]); const getIsByValueMode = (0, _react.useCallback)(() => Boolean( // Temporarily required until the 'by value' paradigm is default. dashboardFeatureFlag.allowByValueEmbeddables && isLinkedToOriginatingApp && !savedObjectId), [dashboardFeatureFlag.allowByValueEmbeddables, isLinkedToOriginatingApp, savedObjectId]); (0, _react.useEffect)(() => { onAppLeave(actions => { if (application.capabilities.visualize.save && !(0, _lens_document_equality.isLensEqual)(persistedDoc, lastKnownDoc, data.query.filterManager.inject.bind(data.query.filterManager), datasourceMap, visualizationMap, annotationGroups) && (isSaveable || persistedDoc)) { return actions.confirm(_i18n.i18n.translate('xpack.lens.app.unsavedWorkMessage', { defaultMessage: 'Leave with unsaved changes?' }), _i18n.i18n.translate('xpack.lens.app.unsavedWorkTitle', { defaultMessage: 'Unsaved changes' }), undefined, _i18n.i18n.translate('xpack.lens.app.unsavedWorkConfirmBtn', { defaultMessage: 'Discard changes' }), 'danger'); } else { return actions.default(); } }); }, [onAppLeave, lastKnownDoc, isSaveable, persistedDoc, application.capabilities.visualize.save, data.query.filterManager, datasourceMap, visualizationMap, annotationGroups]); const getLegacyUrlConflictCallout = (0, _react.useCallback)(() => { // This function returns a callout component *if* we have encountered a "legacy URL conflict" scenario if (spaces && (sharingSavedObjectProps === null || sharingSavedObjectProps === void 0 ? void 0 : sharingSavedObjectProps.outcome) === 'conflict' && persistedDoc !== null && persistedDoc !== void 0 && persistedDoc.savedObjectId) { // We have resolved to one object, but another object has a legacy URL alias associated with this ID/page. We should display a // callout with a warning for the user, and provide a way for them to navigate to the other object. const currentObjectId = persistedDoc.savedObjectId; const otherObjectId = sharingSavedObjectProps === null || sharingSavedObjectProps === void 0 ? void 0 : sharingSavedObjectProps.aliasTargetId; // This is always defined if outcome === 'conflict' const otherObjectPath = http.basePath.prepend(`${(0, _constants.getEditPath)(otherObjectId)}${history.location.search}`); return spaces.ui.components.getLegacyUrlConflict({ objectNoun: _i18n.i18n.translate('xpack.lens.appName', { defaultMessage: 'Lens visualization' }), currentObjectId, otherObjectId, otherObjectPath }); } return null; }, [persistedDoc, sharingSavedObjectProps, spaces, http, history]); // Sync Kibana breadcrumbs any time the saved document's title changes (0, _react.useEffect)(() => { const isByValueMode = getIsByValueMode(); const comesFromVizEditorDashboard = initialContext && 'originatingApp' in initialContext && initialContext.originatingApp; const breadcrumbs = []; if ((isLinkedToOriginatingApp || comesFromVizEditorDashboard) && getOriginatingAppName() && redirectToOrigin) { breadcrumbs.push({ onClick: () => { redirectToOrigin(); }, text: getOriginatingAppName() }); } if (!isByValueMode) { breadcrumbs.push({ href: application.getUrlForApp('visualize'), onClick: e => { application.navigateToApp('visualize', { path: '/' }); e.preventDefault(); }, text: _i18n.i18n.translate('xpack.lens.breadcrumbsTitle', { defaultMessage: 'Visualize Library' }) }); } let currentDocTitle = _i18n.i18n.translate('xpack.lens.breadcrumbsCreate', { defaultMessage: 'Create' }); if (persistedDoc) { currentDocTitle = isByValueMode ? _i18n.i18n.translate('xpack.lens.breadcrumbsByValue', { defaultMessage: 'Edit visualization' }) : persistedDoc.title; } if (!(persistedDoc !== null && persistedDoc !== void 0 && persistedDoc.title) && initialContext && 'isEmbeddable' in initialContext && initialContext.isEmbeddable) { currentDocTitle = _i18n.i18n.translate('xpack.lens.breadcrumbsEditInLensFromDashboard', { defaultMessage: 'Converting {title} visualization', values: { title: initialContext.title ? `"${initialContext.title}"` : initialContext.visTypeTitle } }); } const currentDocBreadcrumb = { text: currentDocTitle }; breadcrumbs.push(currentDocBreadcrumb); if (serverless !== null && serverless !== void 0 && serverless.setBreadcrumbs) { // TODO: https://github.com/elastic/kibana/issues/163488 // for now, serverless breadcrumbs only set the title, // the rest of the breadcrumbs are handled by the serverless navigation // the serverless navigation is not yet aware of the byValue/originatingApp context serverless.setBreadcrumbs(currentDocBreadcrumb); } else { chrome.setBreadcrumbs(breadcrumbs); } }, [dashboardFeatureFlag.allowByValueEmbeddables, getOriginatingAppName, redirectToOrigin, getIsByValueMode, application, chrome, isLinkedToOriginatingApp, persistedDoc, initialContext, serverless]); const switchDatasource = (0, _react.useCallback)(() => { if (saveAndExit && saveAndExit.current) { saveAndExit.current(); } }, []); const runSave = (0, _react.useCallback)((saveProps, options) => { dispatch((0, _state_management.applyChanges)()); return (0, _save_modal_container.runSaveLensVisualization)({ lastKnownDoc, getIsByValueMode, savedObjectsTagging, initialInput, redirectToOrigin, persistedDoc, onAppLeave, redirectTo, switchDatasource, originatingApp: incomingState === null || incomingState === void 0 ? void 0 : incomingState.originatingApp, textBasedLanguageSave: shouldCloseAndSaveTextBasedQuery, ...lensAppServices }, saveProps, options).then(newState => { if (newState) { dispatchSetState(newState); setIsSaveModalVisible(false); setShouldCloseAndSaveTextBasedQuery(false); } }, () => { // error is handled inside the modal // so ignoring it here }); }, [dispatch, lastKnownDoc, getIsByValueMode, savedObjectsTagging, initialInput, redirectToOrigin, persistedDoc, onAppLeave, redirectTo, switchDatasource, incomingState === null || incomingState === void 0 ? void 0 : incomingState.originatingApp, shouldCloseAndSaveTextBasedQuery, lensAppServices, dispatchSetState]); // keeping the initial doc state created by the context (0, _react.useEffect)(() => { if (lastKnownDoc && !initialDocFromContext) { setInitialDocFromContext(lastKnownDoc); } }, [lastKnownDoc, initialDocFromContext]); // if users comes to Lens from the Viz editor, they should have the option to navigate back const goBackToOriginatingApp = (0, _react.useCallback)(() => { if (initialContext && 'vizEditorOriginatingAppUrl' in initialContext && initialContext.vizEditorOriginatingAppUrl) { const [initialDocFromContextUnchanged, currentDocHasBeenSavedInLens] = [initialDocFromContext, persistedDoc].map(refDoc => (0, _lens_document_equality.isLensEqual)(refDoc, lastKnownDoc, data.query.filterManager.inject, datasourceMap, visualizationMap, annotationGroups)); if (initialDocFromContextUnchanged || currentDocHasBeenSavedInLens) { onAppLeave(actions => { return actions.default(); }); application.navigateToApp('visualize', { path: initialContext.vizEditorOriginatingAppUrl }); } else { setIsGoBackToVizEditorModalVisible(true); } } }, [annotationGroups, application, data.query.filterManager.inject, datasourceMap, initialContext, initialDocFromContext, lastKnownDoc, onAppLeave, persistedDoc, visualizationMap]); const navigateToVizEditor = (0, _react.useCallback)(() => { setIsGoBackToVizEditorModalVisible(false); if (initialContext && 'vizEditorOriginatingAppUrl' in initialContext && initialContext.vizEditorOriginatingAppUrl) { onAppLeave(actions => { return actions.default(); }); application.navigateToApp('visualize', { path: initialContext.vizEditorOriginatingAppUrl }); } }, [application, initialContext, onAppLeave]); const initialContextIsEmbedded = (0, _react.useMemo)(() => { return Boolean(initialContext && 'originatingApp' in initialContext && initialContext.originatingApp); }, [initialContext]); const indexPatternService = (0, _react.useMemo)(() => (0, _service.createIndexPatternService)({ dataViews, uiActions, core: { http, notifications, uiSettings }, contextDataViewSpec: initialContext === null || initialContext === void 0 ? void 0 : initialContext.dataViewSpec, updateIndexPatterns: (newIndexPatternsState, options) => { dispatch((0, _state_management.updateIndexPatterns)(newIndexPatternsState)); if (options !== null && options !== void 0 && options.applyImmediately) { dispatch((0, _state_management.applyChanges)()); } }, replaceIndexPattern: (newIndexPattern, oldId, options) => { dispatch((0, _lens_slice.replaceIndexpattern)({ newIndexPattern, oldId })); if (options !== null && options !== void 0 && options.applyImmediately) { dispatch((0, _state_management.applyChanges)()); } } }), [dataViews, uiActions, http, notifications, uiSettings, initialContext, dispatch]); const onTextBasedSavedAndExit = (0, _react.useCallback)(async ({ onSave, onCancel }) => { setIsSaveModalVisible(true); setShouldCloseAndSaveTextBasedQuery(true); saveAndExit.current = () => { onSave(); }; }, []); // remember latest URL based on the configuration // url_panel_content has a similar logic const shareURLCache = (0, _react.useRef)({ params: '', url: '' }); const shortUrlService = (0, _react.useCallback)(async params => { const cacheKey = JSON.stringify(params); if (shareURLCache.current.params === cacheKey) { return shareURLCache.current.url; } if (locator && shortUrls) { // This is a stripped down version of what the share URL plugin is doing const shortUrl = await shortUrls.createWithLocator({ locator, params }); const absoluteShortUrl = await shortUrl.locator.getUrl(shortUrl.params, { absolute: true }); shareURLCache.current = { params: cacheKey, url: absoluteShortUrl }; return absoluteShortUrl; } return ''; }, [locator, shortUrls]); const returnToOriginSwitchLabelForContext = initialContext && 'isEmbeddable' in initialContext && initialContext.isEmbeddable && !persistedDoc ? _i18n.i18n.translate('xpack.lens.app.replacePanel', { defaultMessage: 'Replace panel on {originatingApp}', values: { originatingApp: initialContext === null || initialContext === void 0 ? void 0 : initialContext.originatingApp } }) : undefined; const activeDatasourceId = (0, _state_management.useLensSelector)(_state_management.selectActiveDatasourceId); const frameDatasourceAPI = (0, _state_management.useLensSelector)(state => (0, _state_management.selectFrameDatasourceAPI)(state, datasourceMap)); const [userMessages, setUserMessages] = (0, _react.useState)([]); (0, _react.useEffect)(() => { var _visualizationMap$vis, _visualizationMap$vis2, _visualizationMap$vis3; setUserMessages([...(activeDatasourceId ? datasourceMap[activeDatasourceId].getUserMessages(datasourceStates[activeDatasourceId].state, { frame: frameDatasourceAPI, setState: newStateOrUpdater => { dispatch((0, _state_management.updateDatasourceState)({ newDatasourceState: typeof newStateOrUpdater === 'function' ? newStateOrUpdater(datasourceStates[activeDatasourceId].state) : newStateOrUpdater, datasourceId: activeDatasourceId })); } }) : []), ...(visualization.activeId && visualization.state ? (_visualizationMap$vis = (_visualizationMap$vis2 = visualizationMap[visualization.activeId]) === null || _visualizationMap$vis2 === void 0 ? void 0 : (_visualizationMap$vis3 = _visualizationMap$vis2.getUserMessages) === null || _visualizationMap$vis3 === void 0 ? void 0 : _visualizationMap$vis3.call(_visualizationMap$vis2, visualization.state, { frame: frameDatasourceAPI })) !== null && _visualizationMap$vis !== void 0 ? _visualizationMap$vis : [] : []), ...(0, _get_application_user_messages.getApplicationUserMessages)({ visualizationType: persistedDoc === null || persistedDoc === void 0 ? void 0 : persistedDoc.visualizationType, visualizationMap, visualization, activeDatasource: activeDatasourceId ? datasourceMap[activeDatasourceId] : null, activeDatasourceState: activeDatasourceId ? datasourceStates[activeDatasourceId] : null, core: coreStart, dataViews: frameDatasourceAPI.dataViews })]); }, [activeDatasourceId, coreStart, datasourceMap, datasourceStates, dispatch, frameDatasourceAPI, persistedDoc === null || persistedDoc === void 0 ? void 0 : persistedDoc.visualizationType, visualization, visualizationMap]); // these are messages managed from other parts of Lens const [additionalUserMessages, setAdditionalUserMessages] = (0, _react.useState)({}); const getUserMessages = (locationId, filterArgs) => (0, _get_application_user_messages.filterAndSortUserMessages)([...userMessages, ...Object.values(additionalUserMessages)], locationId, filterArgs !== null && filterArgs !== void 0 ? filterArgs : {}); const addUserMessages = messages => { const newMessageMap = { ...additionalUserMessages }; const addedMessageIds = []; messages.forEach(message => { if (!newMessageMap[message.uniqueId]) { addedMessageIds.push(message.uniqueId); newMessageMap[message.uniqueId] = message; } }); if (addedMessageIds.length) { setAdditionalUserMessages(newMessageMap); } return () => { const withMessagesRemoved = { ...additionalUserMessages }; addedMessageIds.forEach(id => delete withMessagesRemoved[id]); setAdditionalUserMessages(withMessagesRemoved); }; }; return /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, /*#__PURE__*/_react.default.createElement("div", { className: "lnsApp", "data-test-subj": "lnsApp", role: "main" }, /*#__PURE__*/_react.default.createElement(_lens_top_nav.LensTopNavMenu, { initialInput: initialInput, redirectToOrigin: redirectToOrigin, getIsByValueMode: getIsByValueMode, onAppLeave: onAppLeave, runSave: runSave, setIsSaveModalVisible: setIsSaveModalVisible, setHeaderActionMenu: setHeaderActionMenu, indicateNoData: indicateNoData, datasourceMap: datasourceMap, visualizationMap: visualizationMap, title: persistedDoc === null || persistedDoc === void 0 ? void 0 : persistedDoc.title, lensInspector: lensInspector, currentDoc: currentDoc, isCurrentStateDirty: !(0, _lens_document_equality.isLensEqual)(persistedDoc, lastKnownDoc, data.query.filterManager.inject.bind(data.query.filterManager), datasourceMap, visualizationMap, annotationGroups), goBackToOriginatingApp: goBackToOriginatingApp, contextOriginatingApp: contextOriginatingApp, initialContextIsEmbedded: initialContextIsEmbedded, topNavMenuEntryGenerators: topNavMenuEntryGenerators, initialContext: initialContext, theme$: theme$, indexPatternService: indexPatternService, onTextBasedSavedAndExit: onTextBasedSavedAndExit, getUserMessages: getUserMessages, shortUrlService: shortUrlService }), getLegacyUrlConflictCallout(), (!isLoading || persistedDoc) && /*#__PURE__*/_react.default.createElement(MemoizedEditorFrameWrapper, { editorFrame: editorFrame, showNoDataPopover: showNoDataPopover, lensInspector: lensInspector, indexPatternService: indexPatternService, getUserMessages: getUserMessages, addUserMessages: addUserMessages })), isSaveModalVisible && /*#__PURE__*/_react.default.createElement(_save_modal_container.SaveModalContainer, { lensServices: lensAppServices, originatingApp: isLinkedToOriginatingApp ? (_incomingState$origin = incomingState === null || incomingState === void 0 ? void 0 : incomingState.originatingApp) !== null && _incomingState$origin !== void 0 ? _incomingState$origin : initialContext === null || initialContext === void 0 ? void 0 : initialContext.originatingApp : undefined, isSaveable: isSaveable, runSave: runSave, onClose: () => { setIsSaveModalVisible(false); }, getAppNameFromId: () => getOriginatingAppName(), lastKnownDoc: lastKnownDoc, onAppLeave: onAppLeave, persistedDoc: persistedDoc, initialInput: initialInput, redirectTo: redirectTo, redirectToOrigin: redirectToOrigin, initialContext: initialContext, returnToOriginSwitchLabel: returnToOriginSwitchLabelForContext !== null && returnToOriginSwitchLabelForContext !== void 0 ? returnToOriginSwitchLabelForContext : getIsByValueMode() && initialInput ? _i18n.i18n.translate('xpack.lens.app.updatePanel', { defaultMessage: 'Update panel on {originatingAppName}', values: { originatingAppName: getOriginatingAppName() } }) : undefined }), isGoBackToVizEditorModalVisible && /*#__PURE__*/_react.default.createElement(_eui.EuiConfirmModal, { maxWidth: 600, title: _i18n.i18n.translate('xpack.lens.app.unsavedWorkTitle', { defaultMessage: 'Unsaved changes' }), onCancel: () => setIsGoBackToVizEditorModalVisible(false), onConfirm: navigateToVizEditor, cancelButtonText: _i18n.i18n.translate('xpack.lens.app.goBackModalCancelBtn', { defaultMessage: 'Cancel' }), confirmButtonText: _i18n.i18n.translate('xpack.lens.app.unsavedWorkConfirmBtn', { defaultMessage: 'Discard changes' }), buttonColor: "danger", defaultFocusedButton: "confirm", "data-test-subj": "lnsApp_discardChangesModalOrigin" }, _i18n.i18n.translate('xpack.lens.app.goBackModalMessage', { defaultMessage: 'Your changes here won’t work with your original {contextOriginatingApp} visualization. Leave with unsaved changes and return to {contextOriginatingApp}?', values: { contextOriginatingApp } }))); } const MemoizedEditorFrameWrapper = /*#__PURE__*/_react.default.memo(function EditorFrameWrapper({ editorFrame, showNoDataPopover, getUserMessages, addUserMessages, lensInspector, indexPatternService }) { const { EditorFrameContainer } = editorFrame; return /*#__PURE__*/_react.default.createElement(EditorFrameContainer, { showNoDataPopover: showNoDataPopover, getUserMessages: getUserMessages, addUserMessages: addUserMessages, lensInspector: lensInspector, indexPatternService: indexPatternService }); });