From 76b5ebc9bccb4eab7f8cd8a38c34d2cd506b3ad6 Mon Sep 17 00:00:00 2001 From: dej611 Date: Wed, 22 Mar 2023 11:22:28 +0100 Subject: [PATCH 01/21] :sparkles: Enable reporting sharing for Lens --- .../lens/public/app_plugin/lens_top_nav.tsx | 19 ++++++++++----- .../lens/public/app_plugin/share_action.ts | 11 +++++---- .../editor_frame/frame_layout.tsx | 16 +++++++++---- .../workspace_panel/workspace_panel.tsx | 23 +++++++++++++++++-- .../workspace_panel_wrapper.tsx | 8 +++++-- 5 files changed, 57 insertions(+), 20 deletions(-) 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 14eaaab75ba73..f6f8c9abb02a0 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,7 @@ 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 { LENS_APP_LOCATOR } from '../../common/locator/locator'; import { ENABLE_SQL } from '../../common'; import { LensAppServices, LensTopNavActions, LensTopNavMenuProps } from './types'; import { toggleSettingsMenuOpen } from './settings_menu'; @@ -574,13 +575,8 @@ export const LensTopNavMenu = ({ if (!share) { return; } - const sharingData = { - activeData, - csvEnabled, - title: title || unsavedTitle, - }; - const { shareableUrl, savedObjectURL } = await getShareURL( + const { shareableUrl, savedObjectURL, locatorParams } = await getShareURL( shortUrlService, { application, data }, { @@ -595,6 +591,16 @@ export const LensTopNavMenu = ({ adHocDataViews: adHocDataViews.map((dataView) => dataView.toSpec()), } ); + const sharingData = { + activeData, + csvEnabled, + reportingDisabled: !csvEnabled, + title: title || unsavedTitle, + locatorParams: { + id: LENS_APP_LOCATOR, + params: locatorParams, + }, + }; share.toggleShareContextMenu({ anchorElement, @@ -1072,6 +1078,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 13ff9d53f25f1..cf6f54de4190f 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, @@ -80,7 +79,7 @@ function getShortShareableURL( const serializableDatasourceStates = datasourceStates as LensAppState['datasourceStates'] & SerializableRecord; - return shortUrlService({ + return { filters, query, resolvedDateRange: getResolvedDateRange(data.query.timefilter.timefilter), @@ -90,7 +89,7 @@ function getShortShareableURL( searchSessionId: data.search.session.getSessionId(), references, dataViewSpecs: adHocDataViews, - }); + }; } export async function getShareURL( @@ -98,8 +97,10 @@ export async function getShareURL( services: Pick, configuration: ShareableConfiguration ) { + const locatorParams = getLocatorParams(services.data, configuration); return { - shareableUrl: await getShortShareableURL(shortUrlService, services.data, configuration), + shareableUrl: await shortUrlService(locatorParams), savedObjectURL: getShareURLForSavedObject(services, configuration.currentDoc), + locatorParams, }; } 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 a0e068ba9f37f..23fc53ababe9f 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/workspace_panel/workspace_panel.tsx b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.tsx index 0e4bbca409990..5735f00ee11d4 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,12 @@ export const InnerWorkspacePanel = React.memo(function InnerWorkspacePanel({ ); }); +function dispatchRenderComplete(node: HTMLDivElement | null) { + if (node) { + node.dispatchEvent(new CustomEvent('renderComplete', { bubbles: true })); + } +} + export const VisualizationWrapper = ({ expression, framePublicAPI, @@ -654,6 +660,9 @@ export const VisualizationWrapper = ({ onData$: (data: unknown, adapters?: Partial) => void; }) => { const context = useLensSelector(selectExecutionContext); + // Used for reporting + const [isLoading, setIsLoading] = useState(true); + const nodeRef = useRef(null); const searchContext: ExecutionContextSearch = useMemo( () => ({ query: context.query, @@ -719,7 +728,13 @@ export const VisualizationWrapper = ({ } return ( -
+
{ + setIsLoading(false); + onRender$(); + dispatchRenderComplete(nodeRef.current); + }} inspectorAdapters={lensInspector.adapters} executionContext={executionContext} renderMode="edit" 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 8e8a914a15bff..6b61e4dd374c5 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)) && ( - + Date: Wed, 22 Mar 2023 11:22:48 +0100 Subject: [PATCH 02/21] :sparkles: Extends reporting to support lens_visualization --- .../reporting/public/share_context_menu/index.ts | 1 + .../register_pdf_png_reporting.tsx | 14 +++++++++----- .../server/layouts/preserve_layout.css | 5 +++++ 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/x-pack/plugins/reporting/public/share_context_menu/index.ts b/x-pack/plugins/reporting/public/share_context_menu/index.ts index 1d3760edd2625..b9fad615f6a39 100644 --- a/x-pack/plugins/reporting/public/share_context_menu/index.ts +++ b/x-pack/plugins/reporting/public/share_context_menu/index.ts @@ -28,6 +28,7 @@ export interface ExportPanelShareOpts { export interface ReportingSharingData { title: string; layout: LayoutParams; + reportingDisabled?: boolean; [key: string]: unknown; } diff --git a/x-pack/plugins/reporting/public/share_context_menu/register_pdf_png_reporting.tsx b/x-pack/plugins/reporting/public/share_context_menu/register_pdf_png_reporting.tsx index 167ff3311f50a..15a6403b9c9f6 100644 --- a/x-pack/plugins/reporting/public/share_context_menu/register_pdf_png_reporting.tsx +++ b/x-pack/plugins/reporting/public/share_context_menu/register_pdf_png_reporting.tsx @@ -71,6 +71,7 @@ export const reportingScreenshotShareProvider = ({ isDirty, onClose, shareableUrl, + shareableUrlForSavedObject, ...shareOpts }: ShareContext) => { const { enableLinks, showLinks, message } = checkLicense(license.check('reporting', 'gold')); @@ -96,7 +97,7 @@ export const reportingScreenshotShareProvider = ({ return []; } - if (!['dashboard', 'visualization'].includes(objectType)) { + if (!['dashboard', 'visualization', 'lens_visualization'].includes(objectType)) { return []; } @@ -104,7 +105,10 @@ export const reportingScreenshotShareProvider = ({ return []; } - if (objectType === 'visualize' && !capabilityHasVisualizeScreenshotReporting) { + if ( + ['visualize', 'visualization', 'lens_visualization'].includes(objectType) && + !capabilityHasVisualizeScreenshotReporting + ) { return []; } @@ -116,7 +120,7 @@ export const reportingScreenshotShareProvider = ({ }); const jobProviderOptions: JobParamsProviderOptions = { - shareableUrl, + shareableUrl: shareableUrlForSavedObject ?? shareableUrl, objectType, sharingData, }; @@ -131,7 +135,7 @@ export const reportingScreenshotShareProvider = ({ name: pngPanelTitle, icon: 'document', toolTipContent: licenseToolTipContent, - disabled: licenseDisabled, + disabled: licenseDisabled || sharingData.reportingDisabled, ['data-test-subj']: 'PNGReports', sortOrder: 10, }, @@ -166,7 +170,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/screenshotting/server/layouts/preserve_layout.css b/x-pack/plugins/screenshotting/server/layouts/preserve_layout.css index 7b692881d5bde..1e88c4efad953 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 */ From 7b49555b71824f49fe4fbdb54ab077c0d7557fa9 Mon Sep 17 00:00:00 2001 From: dej611 Date: Wed, 22 Mar 2023 15:13:15 +0100 Subject: [PATCH 03/21] :recycle: Migrate objectType to "lens" --- .../csv_download_provider/csv_download_provider.tsx | 2 +- x-pack/plugins/lens/public/app_plugin/lens_top_nav.tsx | 2 +- x-pack/plugins/reporting/public/management/utils.ts | 2 ++ .../public/share_context_menu/register_pdf_png_reporting.tsx | 4 ++-- 4 files changed, 6 insertions(+), 4 deletions(-) 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 bdcb5e5e74edd..499a5d87425c8 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/lens_top_nav.tsx b/x-pack/plugins/lens/public/app_plugin/lens_top_nav.tsx index f6f8c9abb02a0..e6873974eeb96 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 @@ -609,7 +609,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', }), diff --git a/x-pack/plugins/reporting/public/management/utils.ts b/x-pack/plugins/reporting/public/management/utils.ts index 1b3ed519b89ae..87e49c2054f92 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/register_pdf_png_reporting.tsx b/x-pack/plugins/reporting/public/share_context_menu/register_pdf_png_reporting.tsx index 15a6403b9c9f6..fa6781dcf976d 100644 --- a/x-pack/plugins/reporting/public/share_context_menu/register_pdf_png_reporting.tsx +++ b/x-pack/plugins/reporting/public/share_context_menu/register_pdf_png_reporting.tsx @@ -97,7 +97,7 @@ export const reportingScreenshotShareProvider = ({ return []; } - if (!['dashboard', 'visualization', 'lens_visualization'].includes(objectType)) { + if (!['dashboard', 'visualization', 'lens'].includes(objectType)) { return []; } @@ -106,7 +106,7 @@ export const reportingScreenshotShareProvider = ({ } if ( - ['visualize', 'visualization', 'lens_visualization'].includes(objectType) && + ['visualize', 'visualization', 'lens'].includes(objectType) && !capabilityHasVisualizeScreenshotReporting ) { return []; From dc40345c5ecb0c9d13f25b1ceb92d6ed6babe866 Mon Sep 17 00:00:00 2001 From: dej611 Date: Wed, 22 Mar 2023 15:19:39 +0100 Subject: [PATCH 04/21] :recycle: Minor refactors --- .../share_context_menu/register_pdf_png_reporting.tsx | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/x-pack/plugins/reporting/public/share_context_menu/register_pdf_png_reporting.tsx b/x-pack/plugins/reporting/public/share_context_menu/register_pdf_png_reporting.tsx index fa6781dcf976d..b6dc20a9e6b57 100644 --- a/x-pack/plugins/reporting/public/share_context_menu/register_pdf_png_reporting.tsx +++ b/x-pack/plugins/reporting/public/share_context_menu/register_pdf_png_reporting.tsx @@ -96,8 +96,9 @@ export const reportingScreenshotShareProvider = ({ if (!licenseHasScreenshotReporting) { return []; } + const isSupportedType = ['dashboard', 'visualization', 'lens'].includes(objectType); - if (!['dashboard', 'visualization', 'lens'].includes(objectType)) { + if (!isSupportedType) { return []; } @@ -105,10 +106,7 @@ export const reportingScreenshotShareProvider = ({ return []; } - if ( - ['visualize', 'visualization', 'lens'].includes(objectType) && - !capabilityHasVisualizeScreenshotReporting - ) { + if (isSupportedType && !capabilityHasVisualizeScreenshotReporting) { return []; } From f408271df771aa460dc31284f4e072003ebd278a Mon Sep 17 00:00:00 2001 From: dej611 Date: Thu, 23 Mar 2023 12:11:31 +0100 Subject: [PATCH 05/21] :bug: Fix bug when shareURL priviliges are not granted --- x-pack/plugins/lens/public/app_plugin/lens_top_nav.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 e6873974eeb96..51943f846eda1 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 @@ -589,7 +589,8 @@ export const LensTopNavMenu = ({ visualization, currentDoc, adHocDataViews: adHocDataViews.map((dataView) => dataView.toSpec()), - } + }, + shareUrlEnabled ); const sharingData = { activeData, From ef9246e5ba15d22ae1c761f952b125f2654ad364 Mon Sep 17 00:00:00 2001 From: dej611 Date: Thu, 23 Mar 2023 12:11:47 +0100 Subject: [PATCH 06/21] :recycle: Add priviledges check --- x-pack/plugins/lens/public/app_plugin/share_action.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) 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 cf6f54de4190f..cf6c14b0f9f23 100644 --- a/x-pack/plugins/lens/public/app_plugin/share_action.ts +++ b/x-pack/plugins/lens/public/app_plugin/share_action.ts @@ -95,11 +95,12 @@ export function getLocatorParams( export async function getShareURL( shortUrlService: (params: LensAppLocatorParams) => Promise, services: Pick, - configuration: ShareableConfiguration + configuration: ShareableConfiguration, + shareUrlEnabled: boolean ) { const locatorParams = getLocatorParams(services.data, configuration); return { - shareableUrl: await shortUrlService(locatorParams), + shareableUrl: await (shareUrlEnabled ? shortUrlService(locatorParams) : undefined), savedObjectURL: getShareURLForSavedObject(services, configuration.currentDoc), locatorParams, }; From 9c215843b4eca9b13797a4cc9558a7afb48d4c9e Mon Sep 17 00:00:00 2001 From: dej611 Date: Thu, 23 Mar 2023 12:12:07 +0100 Subject: [PATCH 07/21] :white_check_mark: Add functional tests --- .../components/error_unsaved_work_panel.tsx | 8 ++- .../reporting_panel_content.tsx | 9 ++- .../apps/lens/group3/lens_reporting.ts | 70 ++++++++++++++++++- .../test/functional/page_objects/lens_page.ts | 13 +++- 4 files changed, 95 insertions(+), 5 deletions(-) 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 b9fa0c5ac3818..75a6a8ba35626 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/test/functional/apps/lens/group3/lens_reporting.ts b/x-pack/test/functional/apps/lens/group3/lens_reporting.ts index dd54475efff32..effd3f8665756 100644 --- a/x-pack/test/functional/apps/lens/group3/lens_reporting.ts +++ b/x-pack/test/functional/apps/lens/group3/lens_reporting.ts @@ -9,8 +9,16 @@ 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'); @@ -25,6 +33,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 +59,64 @@ 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) { + it('should not allow to download PNG 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 PNG 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'); + }); + } }); } diff --git a/x-pack/test/functional/page_objects/lens_page.ts b/x-pack/test/functional/page_objects/lens_page.ts index 0742e3cba9bb4..ae2180458f545 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< From b9f06e765e44f7c00b62ad9fb2e857b4ee4d8b2d Mon Sep 17 00:00:00 2001 From: dej611 Date: Thu, 23 Mar 2023 17:29:39 +0100 Subject: [PATCH 08/21] :camera_flash: Update snapshots --- .../screen_capture_panel_content.test.tsx.snap | 6 ++++++ 1 file changed, 6 insertions(+) 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 fe178b03de95d..14d73e5df7877 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 />

Date: Fri, 24 Mar 2023 15:20:57 +0100 Subject: [PATCH 09/21] :recycle: Move from unsaved to timestamped title --- .../lens/public/app_plugin/lens_top_nav.tsx | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) 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 51943f846eda1..15427920c6213 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,7 @@ 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'; import { LensAppServices, LensTopNavActions, LensTopNavMenuProps } from './types'; @@ -458,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('dashboard.share.defaultDashboardTitle', { + defaultMessage: 'Lens Visualization [{date}]', + values: { date: moment().toISOString(true) }, }); const additionalMenuEntries = useMemo(() => { if (!visualization.activeId) return undefined; @@ -596,7 +598,7 @@ export const LensTopNavMenu = ({ activeData, csvEnabled, reportingDisabled: !csvEnabled, - title: title || unsavedTitle, + title: title || defaultLensTitle, locatorParams: { id: LENS_APP_LOCATOR, params: locatorParams, @@ -742,7 +744,6 @@ export const LensTopNavMenu = ({ initialContextIsEmbedded, activeData, isSaveable, - shortUrlService, application, getIsByValueMode, savingToLibraryPermitted, @@ -753,7 +754,7 @@ export const LensTopNavMenu = ({ lensInspector, title, share, - unsavedTitle, + shortUrlService, data, filters, query, @@ -763,6 +764,8 @@ export const LensTopNavMenu = ({ visualizationMap, visualization, currentDoc, + adHocDataViews, + defaultLensTitle, isCurrentStateDirty, onAppLeave, runSave, @@ -777,7 +780,6 @@ export const LensTopNavMenu = ({ isOnTextBasedMode, lensStore, theme$, - adHocDataViews, ]); const onQuerySubmitWrapped = useCallback( From a359e4b6a97295bdeb1665bf4e92a1dce1b7ab91 Mon Sep 17 00:00:00 2001 From: dej611 Date: Fri, 24 Mar 2023 16:33:16 +0100 Subject: [PATCH 10/21] :bug: Fix i18n id --- x-pack/plugins/lens/public/app_plugin/lens_top_nav.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 15427920c6213..8e44c806ffb11 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 @@ -459,7 +459,7 @@ export const LensTopNavMenu = ({ isSaveable && application.capabilities.dashboard?.showWriteControls ); - const defaultLensTitle = i18n.translate('dashboard.share.defaultDashboardTitle', { + const defaultLensTitle = i18n.translate('xpack.lens.app.share.defaultDashboardTitle', { defaultMessage: 'Lens Visualization [{date}]', values: { date: moment().toISOString(true) }, }); From e556fe18bfc968c2fe348dab8c6b447bac0b5acb Mon Sep 17 00:00:00 2001 From: dej611 Date: Fri, 24 Mar 2023 16:55:55 +0100 Subject: [PATCH 11/21] :fire: Remove unused translations --- x-pack/plugins/translations/translations/fr-FR.json | 1 - x-pack/plugins/translations/translations/ja-JP.json | 1 - x-pack/plugins/translations/translations/zh-CN.json | 1 - 3 files changed, 3 deletions(-) diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index c0635ec59ccf5..229ec71fe02fc 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -19362,7 +19362,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 93a6762ecb76b..926255d2921e1 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -19362,7 +19362,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 f4e2a62b5dd3c..c1719e78d38ad 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -19363,7 +19363,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": "未保存的更改", From daad4c7ba89fbe66a2e47f2f949f0f3d1896c597 Mon Sep 17 00:00:00 2001 From: dej611 Date: Wed, 29 Mar 2023 14:16:13 +0200 Subject: [PATCH 12/21] :bug: Fix issue with partial state and SO id provided --- .../init_middleware/load_initial.ts | 114 +++++++++--------- .../public/state_management/lens_slice.ts | 24 ++-- 2 files changed, 70 insertions(+), 68 deletions(-) diff --git a/x-pack/plugins/lens/public/state_management/init_middleware/load_initial.ts b/x-pack/plugins/lens/public/state_management/init_middleware/load_initial.ts index 97c08a1ad3258..f979b90d3c5da 100644 --- a/x-pack/plugins/lens/public/state_management/init_middleware/load_initial.ts +++ b/x-pack/plugins/lens/public/state_management/init_middleware/load_initial.ts @@ -121,11 +121,7 @@ export function loadInitial( if (initialContext && 'query' in initialContext) { activeDatasourceId = 'textBased'; } - if (initialStateFromLocator) { - const locatorReferences = - 'references' in initialStateFromLocator ? initialStateFromLocator.references : undefined; - const newFilters = initialStateFromLocator.filters ? cloneDeep(initialStateFromLocator.filters) : undefined; @@ -141,61 +137,67 @@ export function loadInitial( }; data.query.timefilter.timefilter.setTime(newTimeRange); } + // URL Reporting is using the locator params but also passing the savedObjectId + // so be sure to not go here as there's no full snapshot URL + if (!initialInput) { + const locatorReferences = + 'references' in initialStateFromLocator ? initialStateFromLocator.references : undefined; - return initializeSources( - { - datasourceMap, - visualizationMap, - visualizationState: emptyState.visualization, - datasourceStates: emptyState.datasourceStates, - initialContext, - adHocDataViews: - lens.persistedDoc?.state.adHocDataViews || initialStateFromLocator.dataViewSpecs, - references: locatorReferences, - ...loaderSharedArgs, - }, - { - isFullEditor: true, - } - ) - .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()); + 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 5f00eed48005f..3663daef8fcae 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, From fa61289b5b65c1b5bcfd5e3bca8ccba92671f016 Mon Sep 17 00:00:00 2001 From: dej611 Date: Wed, 29 Mar 2023 14:16:38 +0200 Subject: [PATCH 13/21] :ok_hand: Improved reporting URL for saved visualizations --- .../lens/public/app_plugin/lens_top_nav.tsx | 6 ++++- .../lens/public/app_plugin/share_action.ts | 23 ++++++++++++++++--- 2 files changed, 25 insertions(+), 4 deletions(-) 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 8e44c806ffb11..f70147d0c87b5 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 @@ -578,7 +578,11 @@ export const LensTopNavMenu = ({ return; } - const { shareableUrl, savedObjectURL, locatorParams } = await getShareURL( + const { + shareableUrl, + savedObjectURL, + reportingLocatorParams: locatorParams, + } = await getShareURL( shortUrlService, { application, data }, { 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 cf6c14b0f9f23..6a526ee4e7763 100644 --- a/x-pack/plugins/lens/public/app_plugin/share_action.ts +++ b/x-pack/plugins/lens/public/app_plugin/share_action.ts @@ -56,6 +56,7 @@ export function getLocatorParams( visualizationMap, visualization, adHocDataViews, + currentDoc, }: ShareableConfiguration ) { const references = extractReferencesFromState({ @@ -79,7 +80,7 @@ export function getLocatorParams( const serializableDatasourceStates = datasourceStates as LensAppState['datasourceStates'] & SerializableRecord; - return { + const snapshotParams = { filters, query, resolvedDateRange: getResolvedDateRange(data.query.timefilter.timefilter), @@ -90,6 +91,19 @@ export function getLocatorParams( references, dataViewSpecs: adHocDataViews, }; + + return { + shareURL: snapshotParams, + // for reporting use the shorten version when available + reporting: currentDoc?.savedObjectId + ? { + filters, + query, + resolvedDateRange: getResolvedDateRange(data.query.timefilter.timefilter), + savedObjectId: currentDoc?.savedObjectId, + } + : snapshotParams, + }; } export async function getShareURL( @@ -98,10 +112,13 @@ export async function getShareURL( configuration: ShareableConfiguration, shareUrlEnabled: boolean ) { - const locatorParams = getLocatorParams(services.data, configuration); + const { shareURL: locatorParams, reporting: reportingLocatorParams } = getLocatorParams( + services.data, + configuration + ); return { shareableUrl: await (shareUrlEnabled ? shortUrlService(locatorParams) : undefined), savedObjectURL: getShareURLForSavedObject(services, configuration.currentDoc), - locatorParams, + reportingLocatorParams, }; } From 2b3d9e90550d20704a640917bc42eb1f2cfa87c6 Mon Sep 17 00:00:00 2001 From: dej611 Date: Wed, 29 Mar 2023 18:00:20 +0200 Subject: [PATCH 14/21] :sparkles: Add error handling for reporting --- .../workspace_panel/workspace_panel.tsx | 29 +++++++++++++++++-- 1 file changed, 26 insertions(+), 3 deletions(-) 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 39e92d23b8f50..8128cf231edd7 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 @@ -661,7 +661,8 @@ export const VisualizationWrapper = ({ }) => { const context = useLensSelector(selectExecutionContext); // Used for reporting - const [isLoading, setIsLoading] = useState(true); + const [isLoading, setIsLoading] = useState(!errors?.length); + const [hasDynamicError, setHasDynamicError] = useState(false); const nodeRef = useRef(null); const searchContext: ExecutionContextSearch = useMemo( () => ({ @@ -677,6 +678,12 @@ export const VisualizationWrapper = ({ ); const searchSessionId = useLensSelector(selectSearchSessionId); + useEffect(() => { + if (!isLoading) { + dispatchRenderComplete(nodeRef.current); + } + }, [isLoading, errors]); + if (errors?.length) { const showExtraErrorsAction = !localState.expandError && errors.length > 1 ? ( @@ -699,7 +706,15 @@ export const VisualizationWrapper = ({ const [firstMessage, ...rest] = errors; return ( - + { setIsLoading(false); onRender$(); - dispatchRenderComplete(nodeRef.current); }} inspectorAdapters={lensInspector.adapters} executionContext={executionContext} @@ -760,6 +781,8 @@ export const VisualizationWrapper = ({ ? [errorMessage] : []; + setHasDynamicError(true); + return ( From d73df1ef76e3990857f4ce1543a8fc8349441461 Mon Sep 17 00:00:00 2001 From: dej611 Date: Wed, 29 Mar 2023 18:00:40 +0200 Subject: [PATCH 15/21] :white_check_mark: Add functional test for reporting URL --- .../apps/lens/group3/lens_reporting.ts | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) 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 effd3f8665756..2851a8122e4e1 100644 --- a/x-pack/test/functional/apps/lens/group3/lens_reporting.ts +++ b/x-pack/test/functional/apps/lens/group3/lens_reporting.ts @@ -22,6 +22,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const kibanaServer = getService('kibanaServer'); const listingTable = getService('listingTable'); const security = getService('security'); + const browser = getService('browser'); describe('lens reporting', () => { before(async () => { @@ -117,6 +118,27 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { 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); + }); } }); } From a1b3006b056b7439b9295b36524cb8ee7c3874d7 Mon Sep 17 00:00:00 2001 From: Marco Liberati Date: Wed, 29 Mar 2023 21:08:58 +0200 Subject: [PATCH 16/21] Update x-pack/plugins/lens/public/editor_frame_service/editor_frame/workspace_panel/workspace_panel.tsx --- .../editor_frame/workspace_panel/workspace_panel.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 8128cf231edd7..c8c4122a1dc5d 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 @@ -751,7 +751,7 @@ export const VisualizationWrapper = ({ data-render-error={ hasDynamicError ? i18n.translate('xpack.lens.editorFrame.dataFailure', { - defaultMessage: ` An error occurred when loading data.`, + defaultMessage: `An error occurred when loading data.`, }) : undefined } From 82c18f137f39a207be77542af5cd6dea9512412e Mon Sep 17 00:00:00 2001 From: dej611 Date: Thu, 30 Mar 2023 14:52:36 +0200 Subject: [PATCH 17/21] :white_check_mark: Fix unit test --- .../workspace_panel/workspace_panel.test.tsx | 9 ++-- .../workspace_panel/workspace_panel.tsx | 46 +++++++++++-------- 2 files changed, 32 insertions(+), 23 deletions(-) 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 1d118a6f869c7..910e3970e5c15 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 c8c4122a1dc5d..f4571b952abf6 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,10 +624,24 @@ export const InnerWorkspacePanel = React.memo(function InnerWorkspacePanel({ ); }); -function dispatchRenderComplete(node: HTMLDivElement | null) { - if (node) { - node.dispatchEvent(new CustomEvent('renderComplete', { bubbles: true })); - } +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 = ({ @@ -661,9 +675,8 @@ export const VisualizationWrapper = ({ }) => { const context = useLensSelector(selectExecutionContext); // Used for reporting - const [isLoading, setIsLoading] = useState(!errors?.length); - const [hasDynamicError, setHasDynamicError] = useState(false); - const nodeRef = useRef(null); + const { isRenderComplete, hasDynamicError, setIsRenderComplete, setDynamicError, nodeRef } = + useReportingState(errors); const searchContext: ExecutionContextSearch = useMemo( () => ({ query: context.query, @@ -678,13 +691,7 @@ export const VisualizationWrapper = ({ ); const searchSessionId = useLensSelector(selectSearchSessionId); - useEffect(() => { - if (!isLoading) { - dispatchRenderComplete(nodeRef.current); - } - }, [isLoading, errors]); - - if (errors?.length) { + if (errors.length) { const showExtraErrorsAction = !localState.expandError && errors.length > 1 ? ( @@ -746,7 +752,7 @@ export const VisualizationWrapper = ({

{ - setIsLoading(false); + setIsRenderComplete(true); onRender$(); }} inspectorAdapters={lensInspector.adapters} @@ -781,7 +787,9 @@ export const VisualizationWrapper = ({ ? [errorMessage] : []; - setHasDynamicError(true); + if (!hasDynamicError) { + setDynamicError(true); + } return ( From 39ca589fda2487d42a73e1194ed34ba2565892c5 Mon Sep 17 00:00:00 2001 From: dej611 Date: Fri, 31 Mar 2023 10:43:09 +0200 Subject: [PATCH 18/21] :white_check_mark: Fix test titles --- .../apps/lens/group3/lens_reporting.ts | 144 +++++++++--------- 1 file changed, 74 insertions(+), 70 deletions(-) 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 2851a8122e4e1..05f245f14ffc2 100644 --- a/x-pack/test/functional/apps/lens/group3/lens_reporting.ts +++ b/x-pack/test/functional/apps/lens/group3/lens_reporting.ts @@ -5,7 +5,7 @@ * 2.0. */ -import expect from '@kbn/expect'; +import expect from '@kbn/expect/expect'; import { FtrProviderContext } from '../../../ftr_provider_context'; export default function ({ getService, getPageObjects }: FtrProviderContext) { @@ -62,82 +62,86 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); for (const type of ['PNG', 'PDF'] as const) { - it('should not allow to download PNG 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', + 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`)); }); - 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 PNG report of the current visualization', async () => { - // make the configuration complete - await PageObjects.lens.configureDimension({ - dimension: 'lnsXY_yDimensionPanel > lns-empty-dimension', - operation: 'average', - field: 'bytes', + 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(); }); - 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 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}`); + 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'); - }); + 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); + 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); + }); }); } }); From f7b89434f21477fe449a1f8417d9365909d08e2e Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Fri, 31 Mar 2023 08:47:19 +0000 Subject: [PATCH 19/21] [CI] Auto-commit changed files from 'node scripts/precommit_hook.js --ref HEAD~1..HEAD --fix' --- x-pack/test/functional/apps/lens/group3/lens_reporting.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 05f245f14ffc2..853173698ad3e 100644 --- a/x-pack/test/functional/apps/lens/group3/lens_reporting.ts +++ b/x-pack/test/functional/apps/lens/group3/lens_reporting.ts @@ -5,7 +5,7 @@ * 2.0. */ -import expect from '@kbn/expect/expect'; +import expect from '@kbn/expect'; import { FtrProviderContext } from '../../../ftr_provider_context'; export default function ({ getService, getPageObjects }: FtrProviderContext) { From e8c4e5fd22d2e4e8d47ae7f5a0aec03e4f4905a3 Mon Sep 17 00:00:00 2001 From: dej611 Date: Fri, 31 Mar 2023 12:54:09 +0200 Subject: [PATCH 20/21] :bug: Fix first rendering ghost issue --- .../app_plugin/get_application_user_messages.test.tsx | 6 +++--- .../public/app_plugin/get_application_user_messages.tsx | 2 +- .../editor_frame_service/editor_frame/state_helpers.ts | 8 ++++++-- x-pack/plugins/lens/public/embeddable/embeddable.tsx | 5 ++++- 4 files changed, 14 insertions(+), 7 deletions(-) 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 c49ef7f349f18..fa0ed4e21a668 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 b56736eabe7ac..da3caeca2d60a 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/editor_frame_service/editor_frame/state_helpers.ts b/x-pack/plugins/lens/public/editor_frame_service/editor_frame/state_helpers.ts index cea4597be577a..9a31b98328b53 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/embeddable/embeddable.tsx b/x-pack/plugins/lens/public/embeddable/embeddable.tsx index 21aae0b8a4d44..3a70c583d0253 100644 --- a/x-pack/plugins/lens/public/embeddable/embeddable.tsx +++ b/x-pack/plugins/lens/public/embeddable/embeddable.tsx @@ -569,7 +569,10 @@ export class Embeddable }, visualizationMap: this.deps.visualizationMap, activeDatasource: this.activeDatasource, - activeDatasourceState: { state: this.activeDatasourceState }, + activeDatasourceState: { + isLoading: Boolean(this.activeDatasourceState), + state: this.activeDatasourceState, + }, dataViews: { indexPatterns: this.indexPatterns, indexPatternRefs: this.indexPatternRefs, // TODO - are these actually used? From fe0d60635ea6e3d74253fc4dd8e0b03323bdfee4 Mon Sep 17 00:00:00 2001 From: dej611 Date: Tue, 4 Apr 2023 10:28:31 +0200 Subject: [PATCH 21/21] :bug: Fix reporting for dirty state --- .../lens/public/app_plugin/lens_top_nav.tsx | 3 ++- .../lens/public/app_plugin/share_action.ts | 26 +++++++++++-------- .../register_pdf_png_reporting.tsx | 2 +- 3 files changed, 18 insertions(+), 13 deletions(-) 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 2c870dd3def5d..9715b8054a7f2 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 @@ -596,7 +596,8 @@ export const LensTopNavMenu = ({ currentDoc, adHocDataViews: adHocDataViews.map((dataView) => dataView.toSpec()), }, - shareUrlEnabled + shareUrlEnabled, + isCurrentStateDirty ); const sharingData = { activeData, 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 98b96a0253fe8..55e4b978f015d 100644 --- a/x-pack/plugins/lens/public/app_plugin/share_action.ts +++ b/x-pack/plugins/lens/public/app_plugin/share_action.ts @@ -57,7 +57,8 @@ export function getLocatorParams( visualization, adHocDataViews, currentDoc, - }: ShareableConfiguration + }: ShareableConfiguration, + isDirty: boolean ) { const references = extractReferencesFromState({ activeDatasources: Object.keys(datasourceStates).reduce( @@ -95,14 +96,15 @@ export function getLocatorParams( return { shareURL: snapshotParams, // for reporting use the shorten version when available - reporting: currentDoc?.savedObjectId - ? { - filters, - query, - resolvedDateRange: getResolvedDateRange(data.query.timefilter.timefilter), - savedObjectId: currentDoc?.savedObjectId, - } - : snapshotParams, + reporting: + currentDoc?.savedObjectId && !isDirty + ? { + filters, + query, + resolvedDateRange: getResolvedDateRange(data.query.timefilter.timefilter), + savedObjectId: currentDoc?.savedObjectId, + } + : snapshotParams, }; } @@ -110,11 +112,13 @@ export async function getShareURL( shortUrlService: (params: LensAppLocatorParams) => Promise, services: Pick, configuration: ShareableConfiguration, - shareUrlEnabled: boolean + shareUrlEnabled: boolean, + isDirty: boolean ) { const { shareURL: locatorParams, reporting: reportingLocatorParams } = getLocatorParams( services.data, - configuration + configuration, + isDirty ); return { shareableUrl: await (shareUrlEnabled ? shortUrlService(locatorParams) : undefined), diff --git a/x-pack/plugins/reporting/public/share_context_menu/register_pdf_png_reporting.tsx b/x-pack/plugins/reporting/public/share_context_menu/register_pdf_png_reporting.tsx index b6dc20a9e6b57..7efea3195b76a 100644 --- a/x-pack/plugins/reporting/public/share_context_menu/register_pdf_png_reporting.tsx +++ b/x-pack/plugins/reporting/public/share_context_menu/register_pdf_png_reporting.tsx @@ -118,7 +118,7 @@ export const reportingScreenshotShareProvider = ({ }); const jobProviderOptions: JobParamsProviderOptions = { - shareableUrl: shareableUrlForSavedObject ?? shareableUrl, + shareableUrl: isDirty ? shareableUrl : shareableUrlForSavedObject ?? shareableUrl, objectType, sharingData, };