diff --git a/x-pack/plugins/lens/public/app_plugin/csv_download_provider/csv_download_provider.tsx b/x-pack/plugins/lens/public/app_plugin/csv_download_provider/csv_download_provider.tsx index 0ab3834909da88..880a8bed3f4052 100644 --- a/x-pack/plugins/lens/public/app_plugin/csv_download_provider/csv_download_provider.tsx +++ b/x-pack/plugins/lens/public/app_plugin/csv_download_provider/csv_download_provider.tsx @@ -100,7 +100,7 @@ export const downloadCsvShareProvider = ({ formatFactoryFn, }: DownloadPanelShareOpts): ShareMenuProvider => { const getShareMenuItems = ({ objectType, sharingData, onClose }: ShareContext) => { - if ('lens_visualization' !== objectType) { + if ('lens' !== objectType) { return []; } diff --git a/x-pack/plugins/lens/public/app_plugin/get_application_user_messages.test.tsx b/x-pack/plugins/lens/public/app_plugin/get_application_user_messages.test.tsx index c49ef7f349f181..fa0ed4e21a6682 100644 --- a/x-pack/plugins/lens/public/app_plugin/get_application_user_messages.test.tsx +++ b/x-pack/plugins/lens/public/app_plugin/get_application_user_messages.test.tsx @@ -149,7 +149,7 @@ describe('application-level user messages', () => { activeDatasource: { checkIntegrity: jest.fn(() => ['missing_pattern']), } as unknown as Datasource, - activeDatasourceState: { state: {} }, + activeDatasourceState: { isLoading: false, state: {} }, core: createCoreStartWithPermissions(), ...irrelevantProps, }) @@ -166,7 +166,7 @@ describe('application-level user messages', () => { activeDatasource: { checkIntegrity: jest.fn(() => ['missing_pattern']), } as unknown as Datasource, - activeDatasourceState: { state: {} }, + activeDatasourceState: { isLoading: false, state: {} }, // user can go to management, but indexPatterns management is not accessible core: createCoreStartWithPermissions({ navLinks: { management: true }, @@ -188,7 +188,7 @@ describe('application-level user messages', () => { activeDatasource: { checkIntegrity: jest.fn(() => ['missing_pattern']), } as unknown as Datasource, - activeDatasourceState: { state: {} }, + activeDatasourceState: { isLoading: false, state: {} }, // user can't go to management at all core: createCoreStartWithPermissions({ navLinks: { management: false }, diff --git a/x-pack/plugins/lens/public/app_plugin/get_application_user_messages.tsx b/x-pack/plugins/lens/public/app_plugin/get_application_user_messages.tsx index 4a323a4d654b8b..b5a2e0fe24def3 100644 --- a/x-pack/plugins/lens/public/app_plugin/get_application_user_messages.tsx +++ b/x-pack/plugins/lens/public/app_plugin/get_application_user_messages.tsx @@ -36,7 +36,7 @@ export const getApplicationUserMessages = ({ visualization: VisualizationState | undefined; visualizationMap: VisualizationMap; activeDatasource: Datasource | null | undefined; - activeDatasourceState: { state: unknown } | null; + activeDatasourceState: { isLoading: boolean; state: unknown } | null; dataViews: DataViewsState; core: CoreStart; }): UserMessage[] => { diff --git a/x-pack/plugins/lens/public/app_plugin/lens_top_nav.tsx b/x-pack/plugins/lens/public/app_plugin/lens_top_nav.tsx index 2052fa5d68fe83..9715b8054a7f2e 100644 --- a/x-pack/plugins/lens/public/app_plugin/lens_top_nav.tsx +++ b/x-pack/plugins/lens/public/app_plugin/lens_top_nav.tsx @@ -15,6 +15,8 @@ import { getEsQueryConfig } from '@kbn/data-plugin/public'; import type { DataView } from '@kbn/data-views-plugin/public'; import { useKibana } from '@kbn/kibana-react-plugin/public'; import { DataViewPickerProps } from '@kbn/unified-search-plugin/public'; +import moment from 'moment'; +import { LENS_APP_LOCATOR } from '../../common/locator/locator'; import { ENABLE_SQL } from '../../common/constants'; import { LensAppServices, LensTopNavActions, LensTopNavMenuProps } from './types'; import { toggleSettingsMenuOpen } from './settings_menu'; @@ -457,8 +459,9 @@ export const LensTopNavMenu = ({ isSaveable && application.capabilities.dashboard?.showWriteControls ); - const unsavedTitle = i18n.translate('xpack.lens.app.unsavedFilename', { - defaultMessage: 'unsaved', + const defaultLensTitle = i18n.translate('xpack.lens.app.share.defaultDashboardTitle', { + defaultMessage: 'Lens Visualization [{date}]', + values: { date: moment().toISOString(true) }, }); const additionalMenuEntries = useMemo(() => { if (!visualization.activeId) return undefined; @@ -574,13 +577,12 @@ export const LensTopNavMenu = ({ if (!share) { return; } - const sharingData = { - activeData, - csvEnabled, - title: title || unsavedTitle, - }; - const { shareableUrl, savedObjectURL } = await getShareURL( + const { + shareableUrl, + savedObjectURL, + reportingLocatorParams: locatorParams, + } = await getShareURL( shortUrlService, { application, data }, { @@ -593,8 +595,20 @@ export const LensTopNavMenu = ({ visualization, currentDoc, adHocDataViews: adHocDataViews.map((dataView) => dataView.toSpec()), - } + }, + shareUrlEnabled, + isCurrentStateDirty ); + const sharingData = { + activeData, + csvEnabled, + reportingDisabled: !csvEnabled, + title: title || defaultLensTitle, + locatorParams: { + id: LENS_APP_LOCATOR, + params: locatorParams, + }, + }; share.toggleShareContextMenu({ anchorElement, @@ -603,7 +617,7 @@ export const LensTopNavMenu = ({ shareableUrl: shareableUrl || '', shareableUrlForSavedObject: savedObjectURL.href, objectId: currentDoc?.savedObjectId, - objectType: 'lens_visualization', + objectType: 'lens', objectTypeTitle: i18n.translate('xpack.lens.app.share.panelTitle', { defaultMessage: 'visualization', }), @@ -735,7 +749,6 @@ export const LensTopNavMenu = ({ initialContextIsEmbedded, activeData, isSaveable, - shortUrlService, application, getIsByValueMode, savingToLibraryPermitted, @@ -746,7 +759,7 @@ export const LensTopNavMenu = ({ lensInspector, title, share, - unsavedTitle, + shortUrlService, data, filters, query, @@ -756,6 +769,8 @@ export const LensTopNavMenu = ({ visualizationMap, visualization, currentDoc, + adHocDataViews, + defaultLensTitle, isCurrentStateDirty, onAppLeave, runSave, @@ -770,7 +785,6 @@ export const LensTopNavMenu = ({ isOnTextBasedMode, lensStore, theme$, - adHocDataViews, ]); const onQuerySubmitWrapped = useCallback( @@ -1072,6 +1086,7 @@ export const LensTopNavMenu = ({ screenTitle={'lens'} appName={'lens'} displayStyle="detached" + className="hide-for-sharing" /> ); }; diff --git a/x-pack/plugins/lens/public/app_plugin/share_action.ts b/x-pack/plugins/lens/public/app_plugin/share_action.ts index 02b4e8cc298984..55e4b978f015dc 100644 --- a/x-pack/plugins/lens/public/app_plugin/share_action.ts +++ b/x-pack/plugins/lens/public/app_plugin/share_action.ts @@ -45,8 +45,7 @@ function getShareURLForSavedObject( ); } -function getShortShareableURL( - shortUrlService: (params: LensAppLocatorParams) => Promise, +export function getLocatorParams( data: LensAppServices['data'], { filters, @@ -57,7 +56,9 @@ function getShortShareableURL( visualizationMap, visualization, adHocDataViews, - }: ShareableConfiguration + currentDoc, + }: ShareableConfiguration, + isDirty: boolean ) { const references = extractReferencesFromState({ activeDatasources: Object.keys(datasourceStates).reduce( @@ -80,7 +81,7 @@ function getShortShareableURL( const serializableDatasourceStates = datasourceStates as LensAppState['datasourceStates'] & SerializableRecord; - return shortUrlService({ + const snapshotParams = { filters, query, resolvedDateRange: getResolvedDateRange(data.query.timefilter.timefilter), @@ -90,16 +91,38 @@ function getShortShareableURL( searchSessionId: data.search.session.getSessionId(), references, dataViewSpecs: adHocDataViews, - }); + }; + + return { + shareURL: snapshotParams, + // for reporting use the shorten version when available + reporting: + currentDoc?.savedObjectId && !isDirty + ? { + filters, + query, + resolvedDateRange: getResolvedDateRange(data.query.timefilter.timefilter), + savedObjectId: currentDoc?.savedObjectId, + } + : snapshotParams, + }; } export async function getShareURL( shortUrlService: (params: LensAppLocatorParams) => Promise, services: Pick, - configuration: ShareableConfiguration + configuration: ShareableConfiguration, + shareUrlEnabled: boolean, + isDirty: boolean ) { + const { shareURL: locatorParams, reporting: reportingLocatorParams } = getLocatorParams( + services.data, + configuration, + isDirty + ); return { - shareableUrl: await getShortShareableURL(shortUrlService, services.data, configuration), + shareableUrl: await (shareUrlEnabled ? shortUrlService(locatorParams) : undefined), savedObjectURL: getShareURLForSavedObject(services, configuration.currentDoc), + reportingLocatorParams, }; } diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/frame_layout.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/frame_layout.tsx index a0e068ba9f37fd..23fc53ababe9f3 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/frame_layout.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/frame_layout.tsx @@ -53,7 +53,7 @@ export function FrameLayout(props: FrameLayoutProps) { aria-labelledby="lns_ChartTitle" >
@@ -79,12 +79,18 @@ export function FrameLayout(props: FrameLayoutProps) { {props.workspacePanel} -
{props.suggestionsPanel}
+
+ {props.suggestionsPanel} +
diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/state_helpers.ts b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/state_helpers.ts index cea4597be577a6..9a31b98328b534 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/state_helpers.ts +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/state_helpers.ts @@ -404,10 +404,14 @@ export async function persistedStateToExpression( export function getMissingIndexPattern( currentDatasource: Datasource | null | undefined, - currentDatasourceState: { state: unknown } | null, + currentDatasourceState: { isLoading: boolean; state: unknown } | null, indexPatterns: IndexPatternMap ) { - if (currentDatasourceState?.state == null || currentDatasource == null) { + if ( + currentDatasourceState?.isLoading || + currentDatasourceState?.state == null || + currentDatasource == null + ) { return []; } const missingIds = currentDatasource.checkIntegrity(currentDatasourceState.state, indexPatterns); diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.test.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.test.tsx index 1d118a6f869c79..910e3970e5c15a 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.test.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.test.tsx @@ -72,8 +72,8 @@ const defaultProps = { getSuggestionForField: () => undefined, lensInspector: getLensInspectorService(inspectorPluginMock.createStartContract()), toggleFullscreen: jest.fn(), - getUserMessages: () => [], - addUserMessages: () => () => {}, + getUserMessages: jest.fn(() => []), + addUserMessages: jest.fn(() => () => {}), }; const toExpr = ( @@ -728,10 +728,10 @@ describe('workspace_panel', () => { const mounted = await mountWithProvider( ); instance = mounted.instance; @@ -752,11 +752,12 @@ describe('workspace_panel', () => { // but not yet applied their changes let userMessages = [] as UserMessage[]; + const getUserMessageFn = jest.fn(() => userMessages); const mounted = await mountWithProvider( userMessages} + getUserMessages={getUserMessageFn} datasourceMap={{ testDatasource: mockDatasource, }} diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.tsx index a0769fc18350fc..f4571b952abf67 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.tsx @@ -624,6 +624,26 @@ export const InnerWorkspacePanel = React.memo(function InnerWorkspacePanel({ ); }); +function useReportingState(errors: UserMessage[]): { + isRenderComplete: boolean; + hasDynamicError: boolean; + setIsRenderComplete: (state: boolean) => void; + setDynamicError: (state: boolean) => void; + nodeRef: React.RefObject; +} { + const [isRenderComplete, setIsRenderComplete] = useState(Boolean(errors?.length)); + const [hasDynamicError, setDynamicError] = useState(false); + const nodeRef = useRef(null); + + useEffect(() => { + if (isRenderComplete && nodeRef.current) { + nodeRef.current.dispatchEvent(new CustomEvent('renderComplete', { bubbles: true })); + } + }, [isRenderComplete, errors]); + + return { isRenderComplete, setIsRenderComplete, hasDynamicError, setDynamicError, nodeRef }; +} + export const VisualizationWrapper = ({ expression, framePublicAPI, @@ -654,6 +674,9 @@ export const VisualizationWrapper = ({ onData$: (data: unknown, adapters?: Partial) => void; }) => { const context = useLensSelector(selectExecutionContext); + // Used for reporting + const { isRenderComplete, hasDynamicError, setIsRenderComplete, setDynamicError, nodeRef } = + useReportingState(errors); const searchContext: ExecutionContextSearch = useMemo( () => ({ query: context.query, @@ -668,7 +691,7 @@ export const VisualizationWrapper = ({ ); const searchSessionId = useLensSelector(selectSearchSessionId); - if (errors?.length) { + if (errors.length) { const showExtraErrorsAction = !localState.expandError && errors.length > 1 ? ( + +
{ + setIsRenderComplete(true); + onRender$(); + }} inspectorAdapters={lensInspector.adapters} executionContext={executionContext} renderMode="edit" @@ -741,6 +787,10 @@ export const VisualizationWrapper = ({ ? [errorMessage] : []; + if (!hasDynamicError) { + setDynamicError(true); + } + return ( diff --git a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel_wrapper.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel_wrapper.tsx index 8e8a914a15bffa..6b61e4dd374c50 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel_wrapper.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel_wrapper.tsx @@ -91,7 +91,11 @@ export function WorkspacePanelWrapper({ mainProps={{ component: 'div' } as unknown as {}} > {!(isFullscreen && (autoApplyEnabled || userMessages?.length)) && ( - + { - const currentSessionId = - initialStateFromLocator?.searchSessionId || data.search.session.getSessionId(); - store.dispatch( - setState({ - isSaveable: true, - filters: initialStateFromLocator.filters || data.query.filterManager.getFilters(), - query: initialStateFromLocator.query || emptyState.query, - searchSessionId: currentSessionId, - activeDatasourceId: emptyState.activeDatasourceId, - visualization: { - activeId: emptyState.visualization.activeId, - state: visualizationState, - }, - dataViews: getInitialDataViewsObject(indexPatterns, indexPatternRefs), - datasourceStates: Object.entries(datasourceStates).reduce( - (state, [datasourceId, datasourceState]) => ({ - ...state, - [datasourceId]: { - ...datasourceState, - isLoading: false, - }, - }), - {} - ), - isLoading: false, - }) - ); - - if (autoApplyDisabled) { - store.dispatch(disableAutoApply()); + return initializeSources( + { + datasourceMap, + visualizationMap, + visualizationState: emptyState.visualization, + datasourceStates: emptyState.datasourceStates, + initialContext, + adHocDataViews: + lens.persistedDoc?.state.adHocDataViews || initialStateFromLocator.dataViewSpecs, + references: locatorReferences, + ...loaderSharedArgs, + }, + { + isFullEditor: true, } - }) - .catch((e: { message: string }) => { - notifications.toasts.addDanger({ - title: e.message, + ) + .then(({ datasourceStates, visualizationState, indexPatterns, indexPatternRefs }) => { + const currentSessionId = + initialStateFromLocator?.searchSessionId || data.search.session.getSessionId(); + store.dispatch( + setState({ + isSaveable: true, + filters: initialStateFromLocator.filters || data.query.filterManager.getFilters(), + query: initialStateFromLocator.query || emptyState.query, + searchSessionId: currentSessionId, + activeDatasourceId: emptyState.activeDatasourceId, + visualization: { + activeId: emptyState.visualization.activeId, + state: visualizationState, + }, + dataViews: getInitialDataViewsObject(indexPatterns, indexPatternRefs), + datasourceStates: Object.entries(datasourceStates).reduce( + (state, [datasourceId, datasourceState]) => ({ + ...state, + [datasourceId]: { + ...datasourceState, + isLoading: false, + }, + }), + {} + ), + isLoading: false, + }) + ); + + if (autoApplyDisabled) { + store.dispatch(disableAutoApply()); + } + }) + .catch((e: { message: string }) => { + notifications.toasts.addDanger({ + title: e.message, + }); }); - }); + } } if ( diff --git a/x-pack/plugins/lens/public/state_management/lens_slice.ts b/x-pack/plugins/lens/public/state_management/lens_slice.ts index 5f00eed48005f0..3663daef8fcae3 100644 --- a/x-pack/plugins/lens/public/state_management/lens_slice.ts +++ b/x-pack/plugins/lens/public/state_management/lens_slice.ts @@ -62,13 +62,21 @@ export const getPreloadedState = ({ }: LensStoreDeps) => { const initialDatasourceId = getInitialDatasourceId(datasourceMap); const datasourceStates: LensAppState['datasourceStates'] = {}; + // Initialize an empty datasourceStates for each datasource + if (initialDatasourceId) { + Object.keys(datasourceMap).forEach((datasourceId) => { + datasourceStates[datasourceId] = { + state: null, + isLoading: true, + }; + }); + } if (initialStateFromLocator) { + // if anything is passed via locator then populate the empty state if ('datasourceStates' in initialStateFromLocator) { Object.keys(datasourceMap).forEach((datasourceId) => { - datasourceStates[datasourceId] = { - state: initialStateFromLocator.datasourceStates[datasourceId], - isLoading: true, - }; + datasourceStates[datasourceId].state = + initialStateFromLocator.datasourceStates[datasourceId]; }); } return { @@ -82,14 +90,6 @@ export const getPreloadedState = ({ datasourceStates, }; } - if (initialDatasourceId) { - Object.keys(datasourceMap).forEach((datasourceId) => { - datasourceStates[datasourceId] = { - state: null, - isLoading: true, - }; - }); - } const state = { ...initialState, diff --git a/x-pack/plugins/reporting/public/management/utils.ts b/x-pack/plugins/reporting/public/management/utils.ts index 1b3ed519b89aec..87e49c2054f923 100644 --- a/x-pack/plugins/reporting/public/management/utils.ts +++ b/x-pack/plugins/reporting/public/management/utils.ts @@ -25,6 +25,8 @@ export const guessAppIconTypeFromObjectType = (type: string): IconType => { return 'visualizeApp'; case 'canvas workpad': return 'canvasApp'; + case 'lens': + return 'lensApp'; default: return 'apps'; } diff --git a/x-pack/plugins/reporting/public/share_context_menu/__snapshots__/screen_capture_panel_content.test.tsx.snap b/x-pack/plugins/reporting/public/share_context_menu/__snapshots__/screen_capture_panel_content.test.tsx.snap index fe178b03de95df..14d73e5df7877b 100644 --- a/x-pack/plugins/reporting/public/share_context_menu/__snapshots__/screen_capture_panel_content.test.tsx.snap +++ b/x-pack/plugins/reporting/public/share_context_menu/__snapshots__/screen_capture_panel_content.test.tsx.snap @@ -95,6 +95,7 @@ exports[`ScreenCapturePanelContent properly renders a view with "canvas" layout />

{ const { enableLinks, showLinks, message } = checkLicense(license.check('reporting', 'gold')); @@ -95,8 +96,9 @@ export const reportingScreenshotShareProvider = ({ if (!licenseHasScreenshotReporting) { return []; } + const isSupportedType = ['dashboard', 'visualization', 'lens'].includes(objectType); - if (!['dashboard', 'visualization'].includes(objectType)) { + if (!isSupportedType) { return []; } @@ -104,7 +106,7 @@ export const reportingScreenshotShareProvider = ({ return []; } - if (objectType === 'visualize' && !capabilityHasVisualizeScreenshotReporting) { + if (isSupportedType && !capabilityHasVisualizeScreenshotReporting) { return []; } @@ -116,7 +118,7 @@ export const reportingScreenshotShareProvider = ({ }); const jobProviderOptions: JobParamsProviderOptions = { - shareableUrl, + shareableUrl: isDirty ? shareableUrl : shareableUrlForSavedObject ?? shareableUrl, objectType, sharingData, }; @@ -131,7 +133,7 @@ export const reportingScreenshotShareProvider = ({ name: pngPanelTitle, icon: 'document', toolTipContent: licenseToolTipContent, - disabled: licenseDisabled, + disabled: licenseDisabled || sharingData.reportingDisabled, ['data-test-subj']: 'PNGReports', sortOrder: 10, }, @@ -166,7 +168,7 @@ export const reportingScreenshotShareProvider = ({ name: pdfPanelTitle, icon: 'document', toolTipContent: licenseToolTipContent, - disabled: licenseDisabled, + disabled: licenseDisabled || sharingData.reportingDisabled, ['data-test-subj']: 'PDFReports', sortOrder: 10, }, diff --git a/x-pack/plugins/reporting/public/share_context_menu/reporting_panel_content/components/error_unsaved_work_panel.tsx b/x-pack/plugins/reporting/public/share_context_menu/reporting_panel_content/components/error_unsaved_work_panel.tsx index b9fa0c5ac38183..75a6a8ba35626f 100644 --- a/x-pack/plugins/reporting/public/share_context_menu/reporting_panel_content/components/error_unsaved_work_panel.tsx +++ b/x-pack/plugins/reporting/public/share_context_menu/reporting_panel_content/components/error_unsaved_work_panel.tsx @@ -18,7 +18,13 @@ const i18nTexts = { export const ErrorUnsavedWorkPanel: FunctionComponent = () => { return ( - +

{ return ( {(copy) => ( - + { defaultMessage: 'Advanced options', })} paddingSize="none" + data-test-subj="shareReportingAdvancedOptionsButton" > diff --git a/x-pack/plugins/screenshotting/server/layouts/preserve_layout.css b/x-pack/plugins/screenshotting/server/layouts/preserve_layout.css index 7b692881d5bdee..1e88c4efad9535 100644 --- a/x-pack/plugins/screenshotting/server/layouts/preserve_layout.css +++ b/x-pack/plugins/screenshotting/server/layouts/preserve_layout.css @@ -7,6 +7,11 @@ display: none !important; } +/* some elements needs to be stretched to be shared */ +.stretch-for-sharing { + margin: 0px; +} + /** * Global overrides */ diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index 751d0f97a69815..db7ce93d8c4b7e 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -19256,7 +19256,6 @@ "xpack.lens.app.showUnderlyingDataMultipleLayers": "Impossible d’afficher les données sous-jacentes pour les visualisations avec plusieurs calques.", "xpack.lens.app.showUnderlyingDataNoData": "La visualisation ne comprend aucune donnée disponible à afficher.", "xpack.lens.app.showUnderlyingDataTimeShifts": "Impossible d’afficher les données sous-jacentes lorsqu’un décalage temporel est configuré.", - "xpack.lens.app.unsavedFilename": "non enregistré", "xpack.lens.app.unsavedWorkConfirmBtn": "Abandonner les modifications", "xpack.lens.app.unsavedWorkMessage": "Quitter Lens avec un travail non enregistré ?", "xpack.lens.app.unsavedWorkTitle": "Modifications non enregistrées", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index b395853501d980..4db2cc219d4cf6 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -19256,7 +19256,6 @@ "xpack.lens.app.showUnderlyingDataMultipleLayers": "複数レイヤーのビジュアライゼーションでは、基本データを表示できません", "xpack.lens.app.showUnderlyingDataNoData": "ビジュアライゼーションには表示するデータがありません", "xpack.lens.app.showUnderlyingDataTimeShifts": "時間シフトが構成されているときには基本データを表示できません", - "xpack.lens.app.unsavedFilename": "未保存", "xpack.lens.app.unsavedWorkConfirmBtn": "変更を破棄", "xpack.lens.app.unsavedWorkMessage": "作業内容を保存せずに、Lens から移動しますか?", "xpack.lens.app.unsavedWorkTitle": "保存されていない変更", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 4d688bf73539c3..aae60fa33d6d83 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -19257,7 +19257,6 @@ "xpack.lens.app.showUnderlyingDataMultipleLayers": "无法显示具有多个图层的可视化的底层数据", "xpack.lens.app.showUnderlyingDataNoData": "可视化没有可显示的可用数据", "xpack.lens.app.showUnderlyingDataTimeShifts": "配置了时间偏移时无法显示底层数据", - "xpack.lens.app.unsavedFilename": "未保存", "xpack.lens.app.unsavedWorkConfirmBtn": "放弃更改", "xpack.lens.app.unsavedWorkMessage": "离开有未保存工作的 Lens?", "xpack.lens.app.unsavedWorkTitle": "未保存的更改", diff --git a/x-pack/test/functional/apps/lens/group3/lens_reporting.ts b/x-pack/test/functional/apps/lens/group3/lens_reporting.ts index dd54475efff32f..853173698ad3ed 100644 --- a/x-pack/test/functional/apps/lens/group3/lens_reporting.ts +++ b/x-pack/test/functional/apps/lens/group3/lens_reporting.ts @@ -9,11 +9,20 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../../ftr_provider_context'; export default function ({ getService, getPageObjects }: FtrProviderContext) { - const PageObjects = getPageObjects(['common', 'dashboard', 'reporting', 'timePicker']); + const PageObjects = getPageObjects([ + 'common', + 'dashboard', + 'lens', + 'reporting', + 'timePicker', + 'visualize', + ]); const es = getService('es'); + const testSubjects = getService('testSubjects'); const kibanaServer = getService('kibanaServer'); const listingTable = getService('listingTable'); const security = getService('security'); + const browser = getService('browser'); describe('lens reporting', () => { before(async () => { @@ -25,6 +34,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { [ 'test_logstash_reader', 'global_dashboard_read', + 'global_visualize_all', 'reporting_user', // NOTE: the built-in role granting full reporting access is deprecated. See xpack.reporting.roles.enabled ], { skipBrowserRefresh: true } @@ -50,5 +60,89 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const url = await PageObjects.reporting.getReportURL(60000); expect(url).to.be.ok(); }); + + for (const type of ['PNG', 'PDF'] as const) { + describe(`${type} report`, () => { + it(`should not allow to download reports for incomplete visualization`, async () => { + await PageObjects.visualize.gotoVisualizationLandingPage(); + await PageObjects.visualize.navigateToNewVisualization(); + await PageObjects.visualize.clickVisType('lens'); + await PageObjects.lens.goToTimeRange(); + + await PageObjects.lens.configureDimension({ + dimension: 'lnsXY_xDimensionPanel > lns-empty-dimension', + operation: 'date_histogram', + field: '@timestamp', + }); + await PageObjects.lens.configureDimension({ + dimension: 'lnsXY_yDimensionPanel > lns-empty-dimension', + operation: 'average', + field: 'bytes', + }); + + // now remove a dimension to make it incomplete + await PageObjects.lens.removeDimension('lnsXY_yDimensionPanel'); + // open the share menu and check that reporting is disabled + await PageObjects.lens.clickShareMenu(); + + expect(await PageObjects.lens.isShareActionEnabled(`${type}Reports`)); + }); + + it(`should be able to download report of the current visualization`, async () => { + // make the configuration complete + await PageObjects.lens.configureDimension({ + dimension: 'lnsXY_yDimensionPanel > lns-empty-dimension', + operation: 'average', + field: 'bytes', + }); + + await PageObjects.lens.openReportingShare(type); + await PageObjects.reporting.clickGenerateReportButton(); + const url = await PageObjects.reporting.getReportURL(60000); + expect(url).to.be.ok(); + }); + + it(`should show a warning message for curl reporting of unsaved visualizations`, async () => { + await PageObjects.lens.openReportingShare(type); + await testSubjects.click('shareReportingAdvancedOptionsButton'); + await testSubjects.existOrFail('shareReportingUnsavedState'); + expect(await testSubjects.getVisibleText('shareReportingUnsavedState')).to.eql( + 'Unsaved work\nSave your work before copying this URL.' + ); + }); + + it(`should enable curl reporting if the visualization is saved`, async () => { + await PageObjects.lens.save(`ASavedVisualizationToShareIn${type}`); + + await PageObjects.lens.openReportingShare(type); + await testSubjects.click('shareReportingAdvancedOptionsButton'); + await testSubjects.existOrFail('shareReportingCopyURL'); + expect(await testSubjects.getVisibleText('shareReportingCopyURL')).to.eql( + 'Copy POST URL' + ); + }); + + it(`should produce a valid URL for reporting`, async () => { + await PageObjects.reporting.clickGenerateReportButton(); + await PageObjects.reporting.getReportURL(60000); + // navigate to the reporting page + await PageObjects.common.navigateToUrl('management', '/insightsAndAlerting'); + await testSubjects.click('reporting'); + // find the latest Lens report + await testSubjects.click('reportJobRow > euiCollapsedItemActionsButton'); + // click on Open in Kibana and check that all is ok + await testSubjects.click('reportOpenInKibanaApp'); + + const [reportingWindowHandler, lensWindowHandle] = await browser.getAllWindowHandles(); + await browser.switchToWindow(lensWindowHandle); + // verify some configuration + expect(await PageObjects.lens.getDimensionTriggerText('lnsXY_yDimensionPanel')).to.eql( + 'Average of bytes' + ); + await browser.closeCurrentWindow(); + await browser.switchToWindow(reportingWindowHandler); + }); + }); + } }); } diff --git a/x-pack/test/functional/page_objects/lens_page.ts b/x-pack/test/functional/page_objects/lens_page.ts index 43bbaf418dc17b..a246ef54b8dd60 100644 --- a/x-pack/test/functional/page_objects/lens_page.ts +++ b/x-pack/test/functional/page_objects/lens_page.ts @@ -1677,16 +1677,20 @@ export function LensPageProvider({ getService, getPageObjects }: FtrProviderCont return await testSubjects.isEnabled('lnsApp_shareButton'); }, - async isShareActionEnabled(action: 'csvDownload' | 'permalinks') { + async isShareActionEnabled(action: 'csvDownload' | 'permalinks' | 'PNGReports' | 'PDFReports') { switch (action) { case 'csvDownload': return await testSubjects.isEnabled('sharePanel-CSVDownload'); case 'permalinks': return await testSubjects.isEnabled('sharePanel-Permalinks'); + default: + return await testSubjects.isEnabled(`sharePanel-${action}`); } }, - async ensureShareMenuIsOpen(action: 'csvDownload' | 'permalinks') { + async ensureShareMenuIsOpen( + action: 'csvDownload' | 'permalinks' | 'PNGReports' | 'PDFReports' + ) { await this.clickShareMenu(); if (!(await testSubjects.exists('shareContextMenu'))) { @@ -1739,6 +1743,11 @@ export function LensPageProvider({ getService, getPageObjects }: FtrProviderCont }, value); }, + async openReportingShare(type: 'PNG' | 'PDF') { + await this.ensureShareMenuIsOpen(`${type}Reports`); + await testSubjects.click(`sharePanel-${type}Reports`); + }, + async getCSVContent() { await testSubjects.click('lnsApp_downloadCSVButton'); return await browser.execute<