diff --git a/src/plugins/dashboard/public/dashboard_app/_dashboard_app_strings.ts b/src/plugins/dashboard/public/dashboard_app/_dashboard_app_strings.ts
index 9c6526ce3403e..0a2e825a2b7f7 100644
--- a/src/plugins/dashboard/public/dashboard_app/_dashboard_app_strings.ts
+++ b/src/plugins/dashboard/public/dashboard_app/_dashboard_app_strings.ts
@@ -139,6 +139,22 @@ export const shareModalStrings = {
defaultMessage:
'One or more panels on this dashboard have changed. Before you generate a snapshot, save the dashboard.',
}),
+ getDraftSharePanelChangesWarning: () =>
+ i18n.translate('dashboard.snapshotShare.panelChangesWarning', {
+ defaultMessage:
+ 'You are about to share a dashboard with unsaved changes, and the link may not work properly. Save the dashboard first to create a permanent link.',
+ }),
+ getEmbedSharePanelChangesWarning: () =>
+ i18n.translate('dashboard.embedShare.draftWarning', {
+ defaultMessage:
+ 'You are about to create an embedded dashboard with unsaved changes, and the embed code may not work properly. Save the dashboard first to create a permanent embedded dashboard.',
+ }),
+ getDraftShareWarning: (shareType: 'embed' | 'link') =>
+ i18n.translate('dashboard.snapshotShare.draftWarning', {
+ defaultMessage:
+ 'This dashboard has unsaved changes. Consider saving your dashboard before generating the {shareType}.',
+ values: { shareType: shareType === 'embed' ? 'embed code' : 'link' },
+ }),
};
/*
diff --git a/src/plugins/dashboard/public/dashboard_app/top_nav/share/show_share_modal.tsx b/src/plugins/dashboard/public/dashboard_app/top_nav/share/show_share_modal.tsx
index 2e3690e40d4ee..41a290844328a 100644
--- a/src/plugins/dashboard/public/dashboard_app/top_nav/share/show_share_modal.tsx
+++ b/src/plugins/dashboard/public/dashboard_app/top_nav/share/show_share_modal.tsx
@@ -11,7 +11,7 @@ import { omit } from 'lodash';
import moment from 'moment';
import React, { ReactElement, useState } from 'react';
-import { EuiCheckboxGroup } from '@elastic/eui';
+import { EuiCallOut, EuiCheckboxGroup } from '@elastic/eui';
import type { Capabilities } from '@kbn/core/public';
import { QueryState } from '@kbn/data-plugin/common';
import { DASHBOARD_APP_LOCATOR } from '@kbn/deeplinks-analytics';
@@ -19,6 +19,7 @@ import { ViewMode } from '@kbn/embeddable-plugin/public';
import { i18n } from '@kbn/i18n';
import { getStateFromKbnUrl, setStateToKbnUrl, unhashUrl } from '@kbn/kibana-utils-plugin/public';
+import { FormattedMessage } from '@kbn/i18n-react';
import { convertPanelMapToPanelsArray, DashboardPanelMap } from '../../../../common';
import { DashboardLocatorParams } from '../../../dashboard_container';
import {
@@ -122,7 +123,7 @@ export function ShowShareModal({
const allUnsavedPanels = (() => {
if (
Object.keys(unsavedDashboardState?.panels ?? {}).length === 0 &&
- Object.keys(panelModifications ?? {}).length === 0
+ Object.keys(omit(panelModifications ?? {}, PANELS_CONTROL_GROUP_KEY)).length === 0
) {
// if this dashboard has no modifications or unsaved panels return early. No overrides needed.
return;
@@ -195,11 +196,13 @@ export function ShowShareModal({
unhashUrl(baseUrl)
);
+ const allowShortUrl = getDashboardCapabilities().createShortUrl;
+
shareService.toggleShareContextMenu({
isDirty,
anchorElement,
allowEmbed: true,
- allowShortUrl: getDashboardCapabilities().createShortUrl,
+ allowShortUrl,
shareableUrl,
objectId: savedObjectId,
objectType: 'dashboard',
@@ -207,6 +210,44 @@ export function ShowShareModal({
title: i18n.translate('dashboard.share.shareModal.title', {
defaultMessage: 'Share this dashboard',
}),
+ config: {
+ link: {
+ draftModeCallOut: (
+
+ }
+ >
+ {Boolean(unsavedDashboardState?.panels)
+ ? shareModalStrings.getDraftSharePanelChangesWarning()
+ : shareModalStrings.getDraftShareWarning('link')}
+
+ ),
+ },
+ embed: {
+ draftModeCallOut: (
+
+ }
+ >
+ {Boolean(unsavedDashboardState?.panels)
+ ? shareModalStrings.getEmbedSharePanelChangesWarning()
+ : shareModalStrings.getDraftShareWarning('embed')}
+
+ ),
+ },
+ },
},
sharingData: {
title:
diff --git a/src/plugins/share/public/components/context/index.tsx b/src/plugins/share/public/components/context/index.tsx
index 13d6138e42a60..0f18a46c8e399 100644
--- a/src/plugins/share/public/components/context/index.tsx
+++ b/src/plugins/share/public/components/context/index.tsx
@@ -19,7 +19,7 @@ import type {
ShareContext,
} from '../../types';
-export type { ShareMenuItemV2 } from '../../types';
+export type { ShareMenuItemV2, ShareContextObjectTypeConfig } from '../../types';
export interface IShareContext extends ShareContext {
allowEmbed: boolean;
diff --git a/src/plugins/share/public/components/tabs/embed/embed_content.test.tsx b/src/plugins/share/public/components/tabs/embed/embed_content.test.tsx
index be3d8c941b8ea..ee9fdf01588df 100644
--- a/src/plugins/share/public/components/tabs/embed/embed_content.test.tsx
+++ b/src/plugins/share/public/components/tabs/embed/embed_content.test.tsx
@@ -19,6 +19,7 @@ describe('Share modal embed content tab', () => {
beforeEach(() => {
component = mountWithIntl(
jest.fn()}
shareableUrl="/home#/"
diff --git a/src/plugins/share/public/components/tabs/embed/embed_content.tsx b/src/plugins/share/public/components/tabs/embed/embed_content.tsx
index 557a499a38021..5a9163097c8dc 100644
--- a/src/plugins/share/public/components/tabs/embed/embed_content.tsx
+++ b/src/plugins/share/public/components/tabs/embed/embed_content.tsx
@@ -23,7 +23,7 @@ import useMountedState from 'react-use/lib/useMountedState';
import { format as formatUrl, parse as parseUrl } from 'url';
import { AnonymousAccessState } from '../../../../common';
-import { type IShareContext } from '../../context';
+import type { IShareContext, ShareContextObjectTypeConfig } from '../../context';
type EmbedProps = Pick<
IShareContext,
@@ -32,8 +32,10 @@ type EmbedProps = Pick<
| 'shareableUrl'
| 'embedUrlParamExtensions'
| 'objectType'
+ | 'isDirty'
> & {
setIsNotSaved: () => void;
+ objectConfig?: ShareContextObjectTypeConfig;
};
interface UrlParams {
@@ -52,7 +54,9 @@ export const EmbedContent = ({
shareableUrlForSavedObject,
shareableUrl,
objectType,
+ objectConfig = {},
setIsNotSaved,
+ isDirty,
}: EmbedProps) => {
const isMounted = useMountedState();
const [urlParams, setUrlParams] = useState(undefined);
@@ -252,12 +256,20 @@ export const EmbedContent = ({
/>
);
+ const { draftModeCallOut: DraftModeCallout } = objectConfig;
+
return (
<>
{helpText}
{renderUrlParamExtensions()}
+ {isDirty && DraftModeCallout && (
+ <>
+
+ {DraftModeCallout}
+ >
+ )}
diff --git a/src/plugins/share/public/components/tabs/embed/index.tsx b/src/plugins/share/public/components/tabs/embed/index.tsx
index 3c66b48f21c8c..44d61833268cd 100644
--- a/src/plugins/share/public/components/tabs/embed/index.tsx
+++ b/src/plugins/share/public/components/tabs/embed/index.tsx
@@ -38,8 +38,14 @@ const embedTabReducer: IEmbedTab['reducer'] = (state = { url: '', isNotSaved: fa
};
const EmbedTabContent: NonNullable = ({ state, dispatch }) => {
- const { embedUrlParamExtensions, shareableUrlForSavedObject, shareableUrl, objectType, isDirty } =
- useShareTabsContext()!;
+ const {
+ embedUrlParamExtensions,
+ shareableUrlForSavedObject,
+ shareableUrl,
+ objectType,
+ objectTypeMeta,
+ isDirty,
+ } = useShareTabsContext()!;
const setIsNotSaved = useCallback(() => {
dispatch({
@@ -55,8 +61,10 @@ const EmbedTabContent: NonNullable = ({ state, dispatch })
shareableUrlForSavedObject,
shareableUrl,
objectType,
+ objectConfig: objectTypeMeta?.config?.embed,
isNotSaved: state?.isNotSaved,
setIsNotSaved,
+ isDirty,
}}
/>
);
diff --git a/src/plugins/share/public/components/tabs/link/index.tsx b/src/plugins/share/public/components/tabs/link/index.tsx
index c59ae74a62f35..3b602d43c1dfd 100644
--- a/src/plugins/share/public/components/tabs/link/index.tsx
+++ b/src/plugins/share/public/components/tabs/link/index.tsx
@@ -51,6 +51,7 @@ const linkTabReducer: ILinkTab['reducer'] = (
const LinkTabContent: ILinkTab['content'] = ({ state, dispatch }) => {
const {
objectType,
+ objectTypeMeta,
objectId,
isDirty,
shareableUrl,
@@ -86,6 +87,7 @@ const LinkTabContent: ILinkTab['content'] = ({ state, dispatch }) => {
;
+> & { objectConfig?: ShareContextObjectTypeConfig };
interface UrlParams {
[extensionName: string]: {
@@ -44,6 +43,7 @@ interface UrlParams {
export const LinkContent = ({
isDirty,
objectType,
+ objectConfig = {},
shareableUrl,
urlService,
shareableUrlLocatorParams,
@@ -116,6 +116,8 @@ export const LinkContent = ({
setIsLoading(false);
}, [snapshotUrl, delegatedShareUrlHandler, allowShortUrl, createShortUrl]);
+ const { draftModeCallOut: DraftModeCallout } = objectConfig;
+
return (
<>
@@ -126,21 +128,10 @@ export const LinkContent = ({
values={{ objectType }}
/>
- {isDirty && objectType === 'lens' && (
+ {isDirty && DraftModeCallout && (
<>
-
- }
- >
-
-
+ {DraftModeCallout}
>
)}
diff --git a/src/plugins/share/public/types.ts b/src/plugins/share/public/types.ts
index 930b1b1d5f127..c9cb28e9660c4 100644
--- a/src/plugins/share/public/types.ts
+++ b/src/plugins/share/public/types.ts
@@ -7,7 +7,7 @@
* License v3.0 only", or the "Server Side Public License, v 1".
*/
-import type { ComponentType, ReactElement } from 'react';
+import type { ComponentType, ReactElement, ReactNode } from 'react';
import type { InjectedIntl } from '@kbn/i18n-react';
import { EuiContextMenuPanelDescriptor } from '@elastic/eui';
import { EuiContextMenuPanelItemDescriptorEntry } from '@elastic/eui/src/components/context_menu/context_menu';
@@ -21,6 +21,10 @@ export type BrowserUrlService = UrlService<
BrowserShortUrlClient
>;
+export interface ShareContextObjectTypeConfig {
+ draftModeCallOut?: ReactNode;
+}
+
/**
* @public
* Properties of the current object to share. Registered share
@@ -37,6 +41,7 @@ export interface ShareContext {
*/
objectTypeMeta: {
title: string;
+ config?: Partial>;
};
objectId?: string;
/**
diff --git a/x-pack/platform/plugins/private/translations/translations/fr-FR.json b/x-pack/platform/plugins/private/translations/translations/fr-FR.json
index 81298783701e2..b030a284db1c0 100644
--- a/x-pack/platform/plugins/private/translations/translations/fr-FR.json
+++ b/x-pack/platform/plugins/private/translations/translations/fr-FR.json
@@ -7621,8 +7621,6 @@
"share.link.copyEmbedCodeButton": "Copier le code intégré",
"share.link.copyLinkButton": "Copier le lien",
"share.link.helpText": "Partager un lien direct vers ce {objectType}.",
- "share.link.warning.lens": "Copiez le lien afin d’obtenir un lien temporaire. Enregistrez la visualisation Lens pour créer un lien permanent.",
- "share.link.warning.title": "Modifications non enregistrées",
"share.modalContent.copyUrlButtonLabel": "Copier l'URL Post",
"share.postURLWatcherMessage": "Copiez cette URL POST pour appeler la génération depuis l'extérieur de Kibana ou à partir de Watcher.",
"share.postURLWatcherMessage.unsavedChanges": "L'URL peut changer si vous mettez Kibana à niveau.",
@@ -26161,6 +26159,8 @@
"xpack.lens.app.share.defaultDashboardTitle": "Visualisation Lens [{date}]",
"xpack.lens.app.shareButtonDisabledWarning": "La visualisation ne comprend aucune donnée à partager.",
"xpack.lens.app.shareModal.title": "Partager cette visualisation Lens",
+ "xpack.lens.app.shareModal.draftModeCallout.link.warning": "Copiez le lien afin d’obtenir un lien temporaire. Enregistrez la visualisation Lens pour créer un lien permanent.",
+ "xpack.lens.app.shareModal.draftModeCallout.title": "Modifications non enregistrées",
"xpack.lens.app.shareTitle": "Partager",
"xpack.lens.app.shareTitleAria": "Partager la visualisation",
"xpack.lens.app.showUnderlyingDataMultipleLayers": "Impossible d’afficher les données sous-jacentes pour les visualisations avec plusieurs calques.",
diff --git a/x-pack/platform/plugins/private/translations/translations/ja-JP.json b/x-pack/platform/plugins/private/translations/translations/ja-JP.json
index ca80aaa127e32..e73f1f23a10e2 100644
--- a/x-pack/platform/plugins/private/translations/translations/ja-JP.json
+++ b/x-pack/platform/plugins/private/translations/translations/ja-JP.json
@@ -7498,8 +7498,6 @@
"share.link.copyEmbedCodeButton": "埋め込みコードをコピー",
"share.link.copyLinkButton": "リンクをコピー",
"share.link.helpText": "この{objectType}への直接リンクを共有します。",
- "share.link.warning.lens": "リンクをコピーして、一時リンクを取得します。Lensビジュアライゼーションを保存して、永続リンクを作成します。",
- "share.link.warning.title": "保存されていない変更",
"share.modalContent.copyUrlButtonLabel": "POST URLをコピー",
"share.postURLWatcherMessage": "POST URLをコピーしてKibana外または旧Watcherから生成を呼び出します。",
"share.postURLWatcherMessage.unsavedChanges": "Kibanaをアップグレードした場合、URLが変更されることがあります。",
@@ -26020,6 +26018,8 @@
"xpack.lens.app.share.defaultDashboardTitle": "Lensビジュアライゼーション[{date}]",
"xpack.lens.app.shareButtonDisabledWarning": "ビジュアライゼーションには共有するデータがありません。",
"xpack.lens.app.shareModal.title": "このLensビジュアライゼーションを共有",
+ "xpack.lens.app.shareModal.draftModeCallout.link.warning": "リンクをコピーして、一時リンクを取得します。Lensビジュアライゼーションを保存して、永続リンクを作成します。",
+ "xpack.lens.app.shareModal.draftModeCallout.title": "保存されていない変更",
"xpack.lens.app.shareTitle": "共有",
"xpack.lens.app.shareTitleAria": "ビジュアライゼーションを共有",
"xpack.lens.app.showUnderlyingDataMultipleLayers": "複数レイヤーのビジュアライゼーションでは、基本データを表示できません",
diff --git a/x-pack/platform/plugins/private/translations/translations/zh-CN.json b/x-pack/platform/plugins/private/translations/translations/zh-CN.json
index 31eb102ba6780..6ac177493572b 100644
--- a/x-pack/platform/plugins/private/translations/translations/zh-CN.json
+++ b/x-pack/platform/plugins/private/translations/translations/zh-CN.json
@@ -7381,8 +7381,6 @@
"share.link.copyEmbedCodeButton": "复制嵌入代码",
"share.link.copyLinkButton": "复制链接",
"share.link.helpText": "共享指向此 {objectType} 的直接链接。",
- "share.link.warning.lens": "复制链接以获取临时链接。保存 Lens 可视化以创建永久链接。",
- "share.link.warning.title": "未保存的更改",
"share.modalContent.copyUrlButtonLabel": "复制 Post URL",
"share.postURLWatcherMessage": "复制此 POST URL 以从 Kibana 外部或从 Watcher 调用生成。",
"share.postURLWatcherMessage.unsavedChanges": "如果升级 Kibana,URL 可能会发生更改。",
@@ -25582,6 +25580,8 @@
"xpack.lens.app.share.defaultDashboardTitle": "Lens 可视化 [{date}]",
"xpack.lens.app.shareButtonDisabledWarning": "此可视化没有可共享的数据。",
"xpack.lens.app.shareModal.title": "共享此 Lens 可视化",
+ "xpack.lens.app.shareModal.draftModeCallout.link.warning": "复制链接以获取临时链接。保存 Lens 可视化以创建永久链接。",
+ "xpack.lens.app.shareModal.draftModeCallout.title": "未保存的更改",
"xpack.lens.app.shareTitle": "共享",
"xpack.lens.app.shareTitleAria": "共享可视化",
"xpack.lens.app.showUnderlyingDataMultipleLayers": "无法显示具有多个图层的可视化的底层数据",
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 cf76df44eefc0..c673c48db6c82 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
@@ -17,6 +17,8 @@ import { useKibana } from '@kbn/kibana-react-plugin/public';
import { DataViewPickerProps } from '@kbn/unified-search-plugin/public';
import { getManagedContentBadge } from '@kbn/managed-content-badge';
import moment from 'moment';
+import { EuiCallOut } from '@elastic/eui';
+import { FormattedMessage } from '@kbn/i18n-react';
import { LENS_APP_LOCATOR } from '../../common/locator/locator';
import { LENS_APP_NAME } from '../../common/constants';
import { LensAppServices, LensTopNavActions, LensTopNavMenuProps } from './types';
@@ -641,6 +643,26 @@ export const LensTopNavMenu = ({
title: i18n.translate('xpack.lens.app.shareModal.title', {
defaultMessage: 'Share this Lens visualization',
}),
+ config: {
+ link: {
+ draftModeCallOut: (
+
+ }
+ >
+
+
+ ),
+ },
+ },
},
sharingData,
// only want to know about changes when savedObjectURL.href