From e1ec9d9b817084bb3310746a665f7944e696616a Mon Sep 17 00:00:00 2001 From: Drew Tate Date: Wed, 17 Jan 2024 11:41:06 -0700 Subject: [PATCH 01/28] detecting managed Lens visualizations --- packages/kbn-content-management-utils/src/types.ts | 2 +- .../plugins/lens/public/app_plugin/lens_top_nav.tsx | 8 ++++++++ x-pack/plugins/lens/public/embeddable/embeddable.tsx | 1 + x-pack/plugins/lens/public/lens_attribute_service.ts | 1 + .../state_management/init_middleware/load_initial.ts | 12 ++++++++++-- .../lens/public/state_management/lens_slice.ts | 1 + .../lens/public/state_management/selectors.ts | 1 + x-pack/plugins/lens/public/state_management/types.ts | 3 +++ 8 files changed, 26 insertions(+), 3 deletions(-) diff --git a/packages/kbn-content-management-utils/src/types.ts b/packages/kbn-content-management-utils/src/types.ts index f350da4f82cf9..4285587611357 100644 --- a/packages/kbn-content-management-utils/src/types.ts +++ b/packages/kbn-content-management-utils/src/types.ts @@ -200,7 +200,7 @@ export interface SOWithMetadata { statusCode: number; metadata?: Record; }; - managed?: boolean; + managed: boolean; attributes: Attributes; references: Reference[]; namespaces?: string[]; 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 7f501d408a02a..a202e35c304c8 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 @@ -26,6 +26,7 @@ import { useLensDispatch, LensAppState, switchAndCleanDatasource, + selectIsManaged, } from '../state_management'; import { getIndexPatternsObjects, @@ -1050,6 +1051,8 @@ export const LensTopNavMenu = ({ severity: 'error', }).map(({ shortMessage }) => new Error(shortMessage)); + const managed = useLensSelector(selectIsManaged); + return ( export interface LensUnwrapMetaInfo { sharingSavedObjectProps?: SharingSavedObjectProps; + managed: boolean; } export interface LensUnwrapResult { diff --git a/x-pack/plugins/lens/public/lens_attribute_service.ts b/x-pack/plugins/lens/public/lens_attribute_service.ts index 045fee2394138..c63d9c1dd5c3d 100644 --- a/x-pack/plugins/lens/public/lens_attribute_service.ts +++ b/x-pack/plugins/lens/public/lens_attribute_service.ts @@ -71,6 +71,7 @@ export function getLensAttributeService( }, metaInfo: { sharingSavedObjectProps, + managed: savedObject.managed, }, }; }, 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 418fcc2308e7e..b98c0f9447dda 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 @@ -28,7 +28,12 @@ export const getPersisted = async ({ lensServices: LensAppServices; history?: History; }): Promise< - { doc: Document; sharingSavedObjectProps: Omit } | undefined + | { + doc: Document; + sharingSavedObjectProps: Omit; + managed: boolean; + } + | undefined > => { const { notifications, spaces, attributeService } = lensServices; let doc: Document; @@ -44,6 +49,7 @@ export const getPersisted = async ({ sharingSavedObjectProps: { outcome: 'exactMatch', }, + managed: false, }; } const { metaInfo, attributes } = result; @@ -74,6 +80,7 @@ export const getPersisted = async ({ aliasTargetId: sharingSavedObjectProps?.aliasTargetId, outcome: sharingSavedObjectProps?.outcome, }, + managed: Boolean(metaInfo?.managed), }; } catch (e) { notifications.toasts.addDanger( @@ -273,7 +280,7 @@ export function loadInitial( .then( (persisted) => { if (persisted) { - const { doc, sharingSavedObjectProps } = persisted; + const { doc, sharingSavedObjectProps, managed } = persisted; if (attributeService.inputIsRefType(initialInput)) { lensServices.chrome.recentlyAccessed.add( getFullPath(initialInput.savedObjectId), @@ -361,6 +368,7 @@ export function loadInitial( ), isLoading: false, annotationGroups, + managed, }) ); 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 df3564958111b..309efeb8ea827 100644 --- a/x-pack/plugins/lens/public/state_management/lens_slice.ts +++ b/x-pack/plugins/lens/public/state_management/lens_slice.ts @@ -68,6 +68,7 @@ export const initialState: LensAppState = { indexPatterns: {}, }, annotationGroups: {}, + managed: false, }; export const getPreloadedState = ({ diff --git a/x-pack/plugins/lens/public/state_management/selectors.ts b/x-pack/plugins/lens/public/state_management/selectors.ts index 44121c4d064c7..58c7d981dcb41 100644 --- a/x-pack/plugins/lens/public/state_management/selectors.ts +++ b/x-pack/plugins/lens/public/state_management/selectors.ts @@ -36,6 +36,7 @@ export const selectVisualizationState = (state: LensState) => state.lens.visuali export const selectActiveDatasourceId = (state: LensState) => state.lens.activeDatasourceId; export const selectActiveData = (state: LensState) => state.lens.activeData; export const selectDataViews = (state: LensState) => state.lens.dataViews; +export const selectIsManaged = (state: LensState) => state.lens.managed; export const selectIsFullscreenDatasource = (state: LensState) => Boolean(state.lens.isFullscreenDatasource); diff --git a/x-pack/plugins/lens/public/state_management/types.ts b/x-pack/plugins/lens/public/state_management/types.ts index 85e7dae2f92ce..1d683b655b58d 100644 --- a/x-pack/plugins/lens/public/state_management/types.ts +++ b/x-pack/plugins/lens/public/state_management/types.ts @@ -70,6 +70,9 @@ export interface LensAppState extends EditorFrameState { // Dataview/Indexpattern management has moved in here from datasource dataViews: DataViewsState; annotationGroups: AnnotationGroups; + + // Whether the current visualization is managed by the system + managed: boolean; } export interface LensState { From 7eb00bd3de664f1723b73e1e05b40425df4585cd Mon Sep 17 00:00:00 2001 From: Drew Tate Date: Wed, 17 Jan 2024 13:50:28 -0700 Subject: [PATCH 02/28] add managed badge to lens --- .i18nrc.json | 3 ++- package.json | 1 + packages/kbn-managed-content-badge/README.md | 3 +++ packages/kbn-managed-content-badge/index.ts | 26 +++++++++++++++++++ .../kbn-managed-content-badge/jest.config.js | 13 ++++++++++ .../kbn-managed-content-badge/kibana.jsonc | 5 ++++ .../kbn-managed-content-badge/package.json | 6 +++++ .../kbn-managed-content-badge/tsconfig.json | 17 ++++++++++++ .../dashboard_app/_dashboard_app_strings.ts | 4 --- .../internal_dashboard_top_nav.tsx | 13 ++-------- tsconfig.base.json | 2 ++ .../lens/public/app_plugin/lens_top_nav.tsx | 18 +++++++++---- yarn.lock | 4 +++ 13 files changed, 94 insertions(+), 21 deletions(-) create mode 100644 packages/kbn-managed-content-badge/README.md create mode 100644 packages/kbn-managed-content-badge/index.ts create mode 100644 packages/kbn-managed-content-badge/jest.config.js create mode 100644 packages/kbn-managed-content-badge/kibana.jsonc create mode 100644 packages/kbn-managed-content-badge/package.json create mode 100644 packages/kbn-managed-content-badge/tsconfig.json diff --git a/.i18nrc.json b/.i18nrc.json index 119c072688489..fefda3688ab78 100644 --- a/.i18nrc.json +++ b/.i18nrc.json @@ -140,7 +140,8 @@ "unifiedFieldList": "packages/kbn-unified-field-list", "unifiedHistogram": "src/plugins/unified_histogram", "unifiedDataTable": "packages/kbn-unified-data-table", - "unsavedChangesBadge": "packages/kbn-unsaved-changes-badge" + "unsavedChangesBadge": "packages/kbn-unsaved-changes-badge", + "managedContentBadge": "packages/kbn-managed-content-badge" }, "translations": [] } diff --git a/package.json b/package.json index c73e28f842001..cee3a225f4d29 100644 --- a/package.json +++ b/package.json @@ -522,6 +522,7 @@ "@kbn/logging-mocks": "link:packages/kbn-logging-mocks", "@kbn/logs-shared-plugin": "link:x-pack/plugins/logs_shared", "@kbn/logstash-plugin": "link:x-pack/plugins/logstash", + "@kbn/managed-content-badge": "link:packages/kbn-managed-content-badge", "@kbn/management-cards-navigation": "link:packages/kbn-management/cards_navigation", "@kbn/management-plugin": "link:src/plugins/management", "@kbn/management-settings-application": "link:packages/kbn-management/settings/application", diff --git a/packages/kbn-managed-content-badge/README.md b/packages/kbn-managed-content-badge/README.md new file mode 100644 index 0000000000000..fca7826d80899 --- /dev/null +++ b/packages/kbn-managed-content-badge/README.md @@ -0,0 +1,3 @@ +# @kbn/managed-content-badge + +Empty package generated by @kbn/generate diff --git a/packages/kbn-managed-content-badge/index.ts b/packages/kbn-managed-content-badge/index.ts new file mode 100644 index 0000000000000..8cb62b6e3d938 --- /dev/null +++ b/packages/kbn-managed-content-badge/index.ts @@ -0,0 +1,26 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ +import { i18n } from '@kbn/i18n'; +import type { EuiToolTipProps } from '@elastic/eui'; +import type { TopNavMenuBadgeProps } from '@kbn/navigation-plugin/public'; + +export const getManagedContentBadge: (tooltipText: string) => TopNavMenuBadgeProps = ( + tooltipText +) => ({ + 'data-test-subj': 'managedContentBadge', + badgeText: i18n.translate('managedContentBadge.text', { + defaultMessage: 'Managed', + }), + title: '', // TODO + color: 'primary', + iconType: 'glasses', + toolTipProps: { + content: tooltipText, + position: 'bottom', + } as EuiToolTipProps, +}); diff --git a/packages/kbn-managed-content-badge/jest.config.js b/packages/kbn-managed-content-badge/jest.config.js new file mode 100644 index 0000000000000..2e409a7e6b5ad --- /dev/null +++ b/packages/kbn-managed-content-badge/jest.config.js @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +module.exports = { + preset: '@kbn/test/jest_node', + rootDir: '../..', + roots: ['/packages/kbn-managed-content-badge'], +}; diff --git a/packages/kbn-managed-content-badge/kibana.jsonc b/packages/kbn-managed-content-badge/kibana.jsonc new file mode 100644 index 0000000000000..e679a5e83d36f --- /dev/null +++ b/packages/kbn-managed-content-badge/kibana.jsonc @@ -0,0 +1,5 @@ +{ + "type": "shared-browser", + "id": "@kbn/managed-content-badge", + "owner": "@elastic/kibana-visualizations" +} diff --git a/packages/kbn-managed-content-badge/package.json b/packages/kbn-managed-content-badge/package.json new file mode 100644 index 0000000000000..41b11fa33fcac --- /dev/null +++ b/packages/kbn-managed-content-badge/package.json @@ -0,0 +1,6 @@ +{ + "name": "@kbn/managed-content-badge", + "private": true, + "version": "1.0.0", + "license": "SSPL-1.0 OR Elastic License 2.0" +} \ No newline at end of file diff --git a/packages/kbn-managed-content-badge/tsconfig.json b/packages/kbn-managed-content-badge/tsconfig.json new file mode 100644 index 0000000000000..2f9ddddbeea23 --- /dev/null +++ b/packages/kbn-managed-content-badge/tsconfig.json @@ -0,0 +1,17 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "target/types", + "types": [ + "jest", + "node" + ] + }, + "include": [ + "**/*.ts", + ], + "exclude": [ + "target/**/*" + ], + "kbn_references": [] +} 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 8e2f7b07d195a..df343c3cf9d9c 100644 --- a/src/plugins/dashboard/public/dashboard_app/_dashboard_app_strings.ts +++ b/src/plugins/dashboard/public/dashboard_app/_dashboard_app_strings.ts @@ -26,10 +26,6 @@ export const dashboardReadonlyBadge = { }; export const dashboardManagedBadge = { - getText: () => - i18n.translate('dashboard.badge.managed.text', { - defaultMessage: 'Managed', - }), getTooltip: () => i18n.translate('dashboard.badge.managed.tooltip', { defaultMessage: 'This dashboard is system managed. Clone this dashboard to make changes.', diff --git a/src/plugins/dashboard/public/dashboard_top_nav/internal_dashboard_top_nav.tsx b/src/plugins/dashboard/public/dashboard_top_nav/internal_dashboard_top_nav.tsx index c2e0e273a572e..f184c3c23eb3c 100644 --- a/src/plugins/dashboard/public/dashboard_top_nav/internal_dashboard_top_nav.tsx +++ b/src/plugins/dashboard/public/dashboard_top_nav/internal_dashboard_top_nav.tsx @@ -14,6 +14,7 @@ import { LazyLabsFlyout, getContextProvider as getPresentationUtilContextProvider, } from '@kbn/presentation-util-plugin/public'; +import { getManagedContentBadge } from '@kbn/managed-content-badge'; import { ViewMode } from '@kbn/embeddable-plugin/public'; import type { DataView } from '@kbn/data-views-plugin/public'; import { TopNavMenuProps } from '@kbn/navigation-plugin/public'; @@ -305,17 +306,7 @@ export function InternalDashboardTopNav({ }); } if (showWriteControls && managed) { - allBadges.push({ - 'data-test-subj': 'dashboardSaveRecommendedBadge', - badgeText: dashboardManagedBadge.getText(), - title: '', - color: 'primary', - iconType: 'glasses', - toolTipProps: { - content: dashboardManagedBadge.getTooltip(), - position: 'bottom', - } as EuiToolTipProps, - }); + allBadges.push(getManagedContentBadge(dashboardManagedBadge.getTooltip())); } return allBadges; }, [hasUnsavedChanges, viewMode, hasRunMigrations, showWriteControls, managed]); diff --git a/tsconfig.base.json b/tsconfig.base.json index a4f658e7acb62..2fe1cf28effc1 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -998,6 +998,8 @@ "@kbn/logs-shared-plugin/*": ["x-pack/plugins/logs_shared/*"], "@kbn/logstash-plugin": ["x-pack/plugins/logstash"], "@kbn/logstash-plugin/*": ["x-pack/plugins/logstash/*"], + "@kbn/managed-content-badge": ["packages/kbn-managed-content-badge"], + "@kbn/managed-content-badge/*": ["packages/kbn-managed-content-badge/*"], "@kbn/managed-vscode-config": ["packages/kbn-managed-vscode-config"], "@kbn/managed-vscode-config/*": ["packages/kbn-managed-vscode-config/*"], "@kbn/managed-vscode-config-cli": ["packages/kbn-managed-vscode-config-cli"], 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 a202e35c304c8..5fcaefbcbe748 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, DataViewSpec } from '@kbn/data-views-plugin/public'; 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 { LENS_APP_LOCATOR } from '../../common/locator/locator'; import { LENS_APP_NAME } from '../../common/constants'; @@ -1062,11 +1063,18 @@ export const LensTopNavMenu = ({ ? 'allowed_by_app_privilege' : 'globally_managed' } - badges={[ - { - badgeText: managed ? 'Managed' : 'not managed', - }, - ]} + badges={ + managed + ? [ + getManagedContentBadge( + i18n.translate('x-pack.lens.managedBadgeTooltip', { + defaultMessage: + 'This visualization is managed by Elastic. Changes made here must be saved in a new visualization.', + }) + ), + ] + : undefined + } savedQuery={savedQuery} onQuerySubmit={onQuerySubmitWrapped} onSaved={onSavedWrapped} diff --git a/yarn.lock b/yarn.lock index 538392d3595f0..bbb7491646ad5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5056,6 +5056,10 @@ version "0.0.0" uid "" +"@kbn/managed-content-badge@link:packages/kbn-managed-content-badge": + version "0.0.0" + uid "" + "@kbn/managed-vscode-config-cli@link:packages/kbn-managed-vscode-config-cli": version "0.0.0" uid "" From 8f2588d60adf303e578136a93a1e07e514a1926c Mon Sep 17 00:00:00 2001 From: Drew Tate Date: Wed, 17 Jan 2024 14:11:51 -0700 Subject: [PATCH 03/28] force copy-on-save for managed content --- .../saved_object_save_modal_dashboard.tsx | 1 + .../presentation_util/public/components/types.ts | 2 ++ .../save_modal/saved_object_save_modal.tsx | 16 ++++++++++++++-- x-pack/plugins/lens/public/app_plugin/app.tsx | 4 ++++ .../lens/public/app_plugin/save_modal.tsx | 7 +++++++ .../public/app_plugin/save_modal_container.tsx | 4 ++++ 6 files changed, 32 insertions(+), 2 deletions(-) diff --git a/src/plugins/presentation_util/public/components/saved_object_save_modal_dashboard.tsx b/src/plugins/presentation_util/public/components/saved_object_save_modal_dashboard.tsx index 8425454d35968..6beab9973ba05 100644 --- a/src/plugins/presentation_util/public/components/saved_object_save_modal_dashboard.tsx +++ b/src/plugins/presentation_util/public/components/saved_object_save_modal_dashboard.tsx @@ -117,6 +117,7 @@ function SavedObjectSaveModalDashboard(props: SaveModalDashboardProps) { options={isAddToLibrarySelected ? tagOptions : undefined} // Show tags when not adding to dashboard description={documentInfo.description} showDescription={true} + mustCopyOnSaveMessage={props.mustCopyOnSaveMessage} {...{ confirmButtonLabel, initialCopyOnSave, diff --git a/src/plugins/presentation_util/public/components/types.ts b/src/plugins/presentation_util/public/components/types.ts index 4b996b3297180..f241cd9da2022 100644 --- a/src/plugins/presentation_util/public/components/types.ts +++ b/src/plugins/presentation_util/public/components/types.ts @@ -27,6 +27,8 @@ export interface SaveModalDashboardProps { onClose: () => void; onSave: (props: OnSaveProps & { dashboardId: string | null; addToLibrary: boolean }) => void; tagOptions?: React.ReactNode | ((state: SaveModalState) => React.ReactNode); + // include a message if the user has to copy on save + mustCopyOnSaveMessage?: string; } /** diff --git a/src/plugins/saved_objects/public/save_modal/saved_object_save_modal.tsx b/src/plugins/saved_objects/public/save_modal/saved_object_save_modal.tsx index b9bd5e5d7fe36..09876df3c8285 100644 --- a/src/plugins/saved_objects/public/save_modal/saved_object_save_modal.tsx +++ b/src/plugins/saved_objects/public/save_modal/saved_object_save_modal.tsx @@ -25,6 +25,7 @@ import { EuiSwitch, EuiSwitchEvent, EuiTextArea, + EuiToolTip, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; import React from 'react'; @@ -44,6 +45,7 @@ interface Props { onClose: () => void; title: string; showCopyOnSave: boolean; + mustCopyOnSaveMessage?: string; onCopyOnSaveChange?: (copyOnChange: boolean) => void; initialCopyOnSave?: boolean; objectType: string; @@ -173,7 +175,16 @@ export class SavedObjectSaveModal extends React.Component - {this.props.showCopyOnSave && {this.renderCopyOnSave()}} + {this.props.showCopyOnSave && ( + <> + + +
{this.renderCopyOnSave()}
+
+
+ + + )} return ( void; onSave: (props: SaveProps, options: { saveToLibrary: boolean }) => void; + + managed: boolean; } export const SaveModal = (props: Props) => { @@ -57,6 +59,7 @@ export const SaveModal = (props: Props) => { onClose, onSave, returnToOrigin, + managed, } = props; // Use the modal with return-to-origin features if we're in an app's edit flow or if by-value embeddables are disabled @@ -104,6 +107,10 @@ export const SaveModal = (props: Props) => { })} data-test-subj="lnsApp_saveModalDashboard" getOriginatingPath={getOriginatingPath} + mustCopyOnSaveMessage={i18n.translate('xpack.lens.app.mustCopyOnSave', { + defaultMessage: + 'This visualization is managed by Elastic. Changes here must be saved to a new visualization.', + })} /> ); }; diff --git a/x-pack/plugins/lens/public/app_plugin/save_modal_container.tsx b/x-pack/plugins/lens/public/app_plugin/save_modal_container.tsx index 3bebdb2e7c214..f34baffdc295f 100644 --- a/x-pack/plugins/lens/public/app_plugin/save_modal_container.tsx +++ b/x-pack/plugins/lens/public/app_plugin/save_modal_container.tsx @@ -38,6 +38,8 @@ export type SaveModalContainerProps = { getAppNameFromId?: () => string | undefined; lensServices: LensAppServices; initialContext?: VisualizeFieldContext | VisualizeEditorContext; + // is this visualization managed by the system? + managed: boolean; } & ExtraProps; export function SaveModalContainer({ @@ -56,6 +58,7 @@ export function SaveModalContainer({ lastKnownDoc: initLastKnownDoc, lensServices, initialContext, + managed, }: SaveModalContainerProps) { let title = ''; let description; @@ -168,6 +171,7 @@ export function SaveModalContainer({ savedObjectId={savedObjectId} returnToOriginSwitchLabel={returnToOriginSwitchLabel} returnToOrigin={redirectToOrigin != null} + managed={managed} /> ); } From ce339fc806fc5508a86b6a16c27901608d94bf16 Mon Sep 17 00:00:00 2001 From: Drew Tate Date: Wed, 17 Jan 2024 14:14:24 -0700 Subject: [PATCH 04/28] Add title --- packages/kbn-managed-content-badge/index.ts | 4 ++- .../lens/public/app_plugin/save_modal.tsx | 12 ++++++--- .../app_plugin/save_modal_container.tsx | 26 +++++++++++++++++-- .../init_middleware/load_initial.ts | 2 +- 4 files changed, 36 insertions(+), 8 deletions(-) diff --git a/packages/kbn-managed-content-badge/index.ts b/packages/kbn-managed-content-badge/index.ts index 8cb62b6e3d938..a548520c01237 100644 --- a/packages/kbn-managed-content-badge/index.ts +++ b/packages/kbn-managed-content-badge/index.ts @@ -16,7 +16,9 @@ export const getManagedContentBadge: (tooltipText: string) => TopNavMenuBadgePro badgeText: i18n.translate('managedContentBadge.text', { defaultMessage: 'Managed', }), - title: '', // TODO + title: i18n.translate('managedContentBadge.text', { + defaultMessage: 'Managed', + }), color: 'primary', iconType: 'glasses', toolTipProps: { diff --git a/x-pack/plugins/lens/public/app_plugin/save_modal.tsx b/x-pack/plugins/lens/public/app_plugin/save_modal.tsx index c5aaf0a5e8792..48dce06f08b4c 100644 --- a/x-pack/plugins/lens/public/app_plugin/save_modal.tsx +++ b/x-pack/plugins/lens/public/app_plugin/save_modal.tsx @@ -107,10 +107,14 @@ export const SaveModal = (props: Props) => { })} data-test-subj="lnsApp_saveModalDashboard" getOriginatingPath={getOriginatingPath} - mustCopyOnSaveMessage={i18n.translate('xpack.lens.app.mustCopyOnSave', { - defaultMessage: - 'This visualization is managed by Elastic. Changes here must be saved to a new visualization.', - })} + mustCopyOnSaveMessage={ + managed + ? i18n.translate('xpack.lens.app.mustCopyOnSave', { + defaultMessage: + 'This visualization is managed by Elastic. Changes here must be saved to a new visualization.', + }) + : undefined + } /> ); }; diff --git a/x-pack/plugins/lens/public/app_plugin/save_modal_container.tsx b/x-pack/plugins/lens/public/app_plugin/save_modal_container.tsx index f34baffdc295f..f28750907a4fc 100644 --- a/x-pack/plugins/lens/public/app_plugin/save_modal_container.tsx +++ b/x-pack/plugins/lens/public/app_plugin/save_modal_container.tsx @@ -36,7 +36,19 @@ export type SaveModalContainerProps = { runSave?: (saveProps: SaveProps, options: { saveToLibrary: boolean }) => void; isSaveable?: boolean; getAppNameFromId?: () => string | undefined; - lensServices: LensAppServices; + lensServices: Pick< + LensAppServices, + | 'attributeService' + | 'savedObjectsTagging' + | 'application' + | 'dashboardFeatureFlag' + | 'notifications' + | 'http' + | 'chrome' + | 'overlays' + | 'stateTransfer' + | 'savedObjectStore' + >; initialContext?: VisualizeFieldContext | VisualizeEditorContext; // is this visualization managed by the system? managed: boolean; @@ -208,7 +220,17 @@ export const runSaveLensVisualization = async ( switchDatasource?: () => void; savedObjectStore: SavedObjectIndexStore; } & ExtraProps & - LensAppServices, + Pick< + LensAppServices, + | 'application' + | 'chrome' + | 'overlays' + | 'notifications' + | 'stateTransfer' + | 'dashboardFeatureFlag' + | 'attributeService' + | 'savedObjectsTagging' + >, saveProps: SaveProps, options: { saveToLibrary: boolean } ): Promise | undefined> => { 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 b98c0f9447dda..2431676b8dadd 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 @@ -25,7 +25,7 @@ export const getPersisted = async ({ history, }: { initialInput: LensEmbeddableInput; - lensServices: LensAppServices; + lensServices: Pick; history?: History; }): Promise< | { From 817742a3b0525e3533dc32077d7e29c3046298a3 Mon Sep 17 00:00:00 2001 From: Drew Tate Date: Thu, 18 Jan 2024 11:39:33 -0700 Subject: [PATCH 05/28] Add functional test --- .buildkite/ftr_configs.yml | 3 +- .../services/test_subjects.ts | 9 ++- .../kbn_archiver/managed_content.json | 5 ++ .../functional/apps/managed_content/config.ts | 17 ++++++ .../functional/apps/managed_content/index.js | 12 ++++ .../apps/managed_content/managed_content.ts | 58 +++++++++++++++++++ 6 files changed, 101 insertions(+), 3 deletions(-) create mode 100644 test/functional/fixtures/kbn_archiver/managed_content.json create mode 100644 x-pack/test/functional/apps/managed_content/config.ts create mode 100644 x-pack/test/functional/apps/managed_content/index.js create mode 100644 x-pack/test/functional/apps/managed_content/managed_content.ts diff --git a/.buildkite/ftr_configs.yml b/.buildkite/ftr_configs.yml index ac929afea575b..ff6296f84584d 100644 --- a/.buildkite/ftr_configs.yml +++ b/.buildkite/ftr_configs.yml @@ -291,6 +291,7 @@ enabled: - x-pack/test/functional/apps/lens/open_in_lens/dashboard/config.ts - x-pack/test/functional/apps/license_management/config.ts - x-pack/test/functional/apps/logstash/config.ts + - x-pack/test/functional/apps/managed_content/config.ts - x-pack/test/functional/apps/management/config.ts - x-pack/test/functional/apps/maps/group1/config.ts - x-pack/test/functional/apps/maps/group2/config.ts @@ -516,4 +517,4 @@ enabled: - x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_read/configs/serverless.config.ts - x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_read/configs/ess.config.ts - x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_bulk_actions/configs/ess.config.ts - - x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_bulk_actions/configs/serverless.config.ts \ No newline at end of file + - x-pack/test/security_solution_api_integration/test_suites/detections_response/default_license/rule_bulk_actions/configs/serverless.config.ts diff --git a/packages/kbn-ftr-common-functional-ui-services/services/test_subjects.ts b/packages/kbn-ftr-common-functional-ui-services/services/test_subjects.ts index ec9960290a5dc..730b7a692aabe 100644 --- a/packages/kbn-ftr-common-functional-ui-services/services/test_subjects.ts +++ b/packages/kbn-ftr-common-functional-ui-services/services/test_subjects.ts @@ -402,8 +402,13 @@ export class TestSubjects extends FtrService { } } - public async isEuiSwitchChecked(selector: string) { - const euiSwitch = await this.find(selector); + public async isEuiSwitchChecked(selector: string | WebElementWrapper) { + let euiSwitch: WebElementWrapper; + if (typeof selector === 'string') { + euiSwitch = await this.find(selector); + } else { + euiSwitch = selector; + } const isChecked = await euiSwitch.getAttribute('aria-checked'); return isChecked === 'true'; } diff --git a/test/functional/fixtures/kbn_archiver/managed_content.json b/test/functional/fixtures/kbn_archiver/managed_content.json new file mode 100644 index 0000000000000..2bbc6e50a9e40 --- /dev/null +++ b/test/functional/fixtures/kbn_archiver/managed_content.json @@ -0,0 +1,5 @@ +{"attributes":{"allowHidden":false,"fieldAttrs":"{}","fieldFormatMap":"{}","fields":"[]","name":"logstash-*","runtimeFieldMap":"{}","sourceFilters":"[]","timeFieldName":"@timestamp","title":"logstash-*"},"coreMigrationVersion":"8.8.0","created_at":"2024-01-18T17:35:58.606Z","id":"5f863f70-4728-4e8d-b441-db08f8c33b28","managed":false,"references":[],"type":"index-pattern","typeMigrationVersion":"8.0.0","updated_at":"2024-01-18T17:35:58.606Z","version":"WzI4LDFd"} + +{"attributes":{"description":"","state":{"adHocDataViews":{},"datasourceStates":{"formBased":{"layers":{"e633b1af-3ab4-4bf5-8faa-fefde06c4a4a":{"columnOrder":["f2555a1a-6f93-43fd-bc63-acdfadd47729","d229daf9-9658-4579-99af-01d8adb2f25f"],"columns":{"d229daf9-9658-4579-99af-01d8adb2f25f":{"dataType":"number","isBucketed":false,"label":"Median of bytes","operationType":"median","params":{"emptyAsNull":true},"scale":"ratio","sourceField":"bytes"},"f2555a1a-6f93-43fd-bc63-acdfadd47729":{"dataType":"date","isBucketed":true,"label":"@timestamp","operationType":"date_histogram","params":{"dropPartials":false,"includeEmptyRows":true,"interval":"auto"},"scale":"interval","sourceField":"@timestamp"}},"incompleteColumns":{},"sampling":1}}},"indexpattern":{"layers":{}},"textBased":{"layers":{}}},"filters":[],"internalReferences":[],"query":{"language":"kuery","query":""},"visualization":{"axisTitlesVisibilitySettings":{"x":true,"yLeft":true,"yRight":true},"fittingFunction":"None","gridlinesVisibilitySettings":{"x":true,"yLeft":true,"yRight":true},"labelsOrientation":{"x":0,"yLeft":0,"yRight":0},"layers":[{"accessors":["d229daf9-9658-4579-99af-01d8adb2f25f"],"layerId":"e633b1af-3ab4-4bf5-8faa-fefde06c4a4a","layerType":"data","position":"top","seriesType":"bar_stacked","showGridlines":false,"xAccessor":"f2555a1a-6f93-43fd-bc63-acdfadd47729"}],"legend":{"isVisible":true,"position":"right"},"preferredSeriesType":"bar_stacked","tickLabelsVisibilitySettings":{"x":true,"yLeft":true,"yRight":true},"valueLabels":"hide"}},"title":"Lens vis (managed)","visualizationType":"lnsXY"},"coreMigrationVersion":"8.8.0","created_at":"2024-01-18T17:42:12.920Z","id":"managed-36db-4a3b-a4ba-7a64ab8f130b","managed":true,"references":[{"id":"5f863f70-4728-4e8d-b441-db08f8c33b28","name":"indexpattern-datasource-layer-e633b1af-3ab4-4bf5-8faa-fefde06c4a4a","type":"index-pattern"}],"type":"lens","typeMigrationVersion":"8.9.0","updated_at":"2024-01-18T17:42:12.920Z","version":"WzQ1LDFd"} + +{"attributes":{"description":"","state":{"adHocDataViews":{},"datasourceStates":{"formBased":{"layers":{"e633b1af-3ab4-4bf5-8faa-fefde06c4a4a":{"columnOrder":["f2555a1a-6f93-43fd-bc63-acdfadd47729","d229daf9-9658-4579-99af-01d8adb2f25f"],"columns":{"d229daf9-9658-4579-99af-01d8adb2f25f":{"dataType":"number","isBucketed":false,"label":"Median of bytes","operationType":"median","params":{"emptyAsNull":true},"scale":"ratio","sourceField":"bytes"},"f2555a1a-6f93-43fd-bc63-acdfadd47729":{"dataType":"date","isBucketed":true,"label":"@timestamp","operationType":"date_histogram","params":{"dropPartials":false,"includeEmptyRows":true,"interval":"auto"},"scale":"interval","sourceField":"@timestamp"}},"incompleteColumns":{},"sampling":1}}},"indexpattern":{"layers":{}},"textBased":{"layers":{}}},"filters":[],"internalReferences":[],"query":{"language":"kuery","query":""},"visualization":{"axisTitlesVisibilitySettings":{"x":true,"yLeft":true,"yRight":true},"fittingFunction":"None","gridlinesVisibilitySettings":{"x":true,"yLeft":true,"yRight":true},"labelsOrientation":{"x":0,"yLeft":0,"yRight":0},"layers":[{"accessors":["d229daf9-9658-4579-99af-01d8adb2f25f"],"layerId":"e633b1af-3ab4-4bf5-8faa-fefde06c4a4a","layerType":"data","position":"top","seriesType":"bar_stacked","showGridlines":false,"xAccessor":"f2555a1a-6f93-43fd-bc63-acdfadd47729"}],"legend":{"isVisible":true,"position":"right"},"preferredSeriesType":"bar_stacked","tickLabelsVisibilitySettings":{"x":true,"yLeft":true,"yRight":true},"valueLabels":"hide"}},"title":"Lens vis (unmanaged)","visualizationType":"lnsXY"},"coreMigrationVersion":"8.8.0","created_at":"2024-01-18T17:42:12.920Z","id":"unmanaged-36db-4a3b-a4ba-7a64ab8f130b","managed":false,"references":[{"id":"5f863f70-4728-4e8d-b441-db08f8c33b28","name":"indexpattern-datasource-layer-e633b1af-3ab4-4bf5-8faa-fefde06c4a4a","type":"index-pattern"}],"type":"lens","typeMigrationVersion":"8.9.0","updated_at":"2024-01-18T17:42:12.920Z","version":"WzQ1LDFd"} diff --git a/x-pack/test/functional/apps/managed_content/config.ts b/x-pack/test/functional/apps/managed_content/config.ts new file mode 100644 index 0000000000000..d0d07ff200281 --- /dev/null +++ b/x-pack/test/functional/apps/managed_content/config.ts @@ -0,0 +1,17 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { FtrConfigProviderContext } from '@kbn/test'; + +export default async function ({ readConfigFile }: FtrConfigProviderContext) { + const functionalConfig = await readConfigFile(require.resolve('../../config.base.js')); + + return { + ...functionalConfig.getAll(), + testFiles: [require.resolve('.')], + }; +} diff --git a/x-pack/test/functional/apps/managed_content/index.js b/x-pack/test/functional/apps/managed_content/index.js new file mode 100644 index 0000000000000..ecd0776ac5ef4 --- /dev/null +++ b/x-pack/test/functional/apps/managed_content/index.js @@ -0,0 +1,12 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export default function ({ loadTestFile }) { + describe('managed content', function () { + loadTestFile(require.resolve('./managed_content')); + }); +} diff --git a/x-pack/test/functional/apps/managed_content/managed_content.ts b/x-pack/test/functional/apps/managed_content/managed_content.ts new file mode 100644 index 0000000000000..e7dbbdd25d95c --- /dev/null +++ b/x-pack/test/functional/apps/managed_content/managed_content.ts @@ -0,0 +1,58 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../ftr_provider_context'; + +export default function ({ getPageObjects, getService }: FtrProviderContext) { + const PageObjects = getPageObjects(['timePicker', 'lens', 'common']); + const kibanaServer = getService('kibanaServer'); + const esArchiver = getService('esArchiver'); + const testSubjects = getService('testSubjects'); + + describe('Managed Content', () => { + before(async () => { + esArchiver.load('x-pack/test/functional/es_archives/logstash_functional'); + kibanaServer.importExport.load('test/functional/fixtures/kbn_archiver/managed_content'); + }); + + after(async () => { + esArchiver.unload('x-pack/test/functional/es_archives/logstash_functional'); + kibanaServer.importExport.unload('test/functional/fixtures/kbn_archiver/managed_content'); + }); + + describe('preventing the user from overwriting managed content', () => { + it('lens', async () => { + await PageObjects.common.navigateToActualUrl( + 'lens', + 'edit/managed-36db-4a3b-a4ba-7a64ab8f130b' + ); + + await PageObjects.lens.waitForVisualization('xyVisChart'); + + await testSubjects.existOrFail('managedContentBadge'); + await testSubjects.click('lnsApp_saveButton'); + const saveAsNewCheckbox = await testSubjects.find('saveAsNewCheckbox'); + expect(await testSubjects.isEuiSwitchChecked(saveAsNewCheckbox)).to.be(true); + expect(await saveAsNewCheckbox.getAttribute('disabled')).to.be('true'); + + await PageObjects.common.navigateToActualUrl( + 'lens', + 'edit/unmanaged-36db-4a3b-a4ba-7a64ab8f130b' + ); + + await PageObjects.lens.waitForVisualization('xyVisChart'); + + await testSubjects.missingOrFail('managedContentBadge'); + await testSubjects.click('lnsApp_saveButton'); + await testSubjects.existOrFail('saveAsNewCheckbox'); + expect(await testSubjects.isEuiSwitchChecked('saveAsNewCheckbox')).to.be(false); + expect(await saveAsNewCheckbox.getAttribute('disabled')).to.be(null); + }); + }); + }); +} From 77d71e21fb1615496eaa64d3742cecccb41b1f27 Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Thu, 18 Jan 2024 18:49:09 +0000 Subject: [PATCH 06/28] [CI] Auto-commit changed files from 'node scripts/lint_ts_projects --fix' --- packages/kbn-managed-content-badge/tsconfig.json | 5 ++++- src/plugins/dashboard/tsconfig.json | 3 ++- x-pack/plugins/lens/tsconfig.json | 3 ++- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/packages/kbn-managed-content-badge/tsconfig.json b/packages/kbn-managed-content-badge/tsconfig.json index 2f9ddddbeea23..249cf05d36c96 100644 --- a/packages/kbn-managed-content-badge/tsconfig.json +++ b/packages/kbn-managed-content-badge/tsconfig.json @@ -13,5 +13,8 @@ "exclude": [ "target/**/*" ], - "kbn_references": [] + "kbn_references": [ + "@kbn/i18n", + "@kbn/navigation-plugin", + ] } diff --git a/src/plugins/dashboard/tsconfig.json b/src/plugins/dashboard/tsconfig.json index 1f37320d775f2..bf8d10d693b8f 100644 --- a/src/plugins/dashboard/tsconfig.json +++ b/src/plugins/dashboard/tsconfig.json @@ -70,7 +70,8 @@ "@kbn/core-lifecycle-browser", "@kbn/logging", "@kbn/content-management-table-list-view-common", - "@kbn/shared-ux-utility" + "@kbn/shared-ux-utility", + "@kbn/managed-content-badge" ], "exclude": ["target/**/*"] } diff --git a/x-pack/plugins/lens/tsconfig.json b/x-pack/plugins/lens/tsconfig.json index ddffc5c8114c8..f60f36471fb1a 100644 --- a/x-pack/plugins/lens/tsconfig.json +++ b/x-pack/plugins/lens/tsconfig.json @@ -104,7 +104,8 @@ "@kbn/visualization-utils", "@kbn/test-eui-helpers", "@kbn/shared-ux-utility", - "@kbn/text-based-editor" + "@kbn/text-based-editor", + "@kbn/managed-content-badge" ], "exclude": [ "target/**/*" From f7441eda3dc25d19a5d4299cfcbeef6a4809674a Mon Sep 17 00:00:00 2001 From: Drew Tate Date: Thu, 18 Jan 2024 12:03:01 -0700 Subject: [PATCH 07/28] move must copy on save message to keyboard accessible element --- ...d_object_save_modal_dashboard_selector.tsx | 2 +- .../save_modal/saved_object_save_modal.tsx | 48 ++++++++++--------- 2 files changed, 26 insertions(+), 24 deletions(-) diff --git a/src/plugins/presentation_util/public/components/saved_object_save_modal_dashboard_selector.tsx b/src/plugins/presentation_util/public/components/saved_object_save_modal_dashboard_selector.tsx index 3a96fc148aff7..8aca5a7965065 100644 --- a/src/plugins/presentation_util/public/components/saved_object_save_modal_dashboard_selector.tsx +++ b/src/plugins/presentation_util/public/components/saved_object_save_modal_dashboard_selector.tsx @@ -151,7 +151,7 @@ export function SaveModalDashboardSelector(props: SaveModalDashboardSelectorProp content={ } /> diff --git a/src/plugins/saved_objects/public/save_modal/saved_object_save_modal.tsx b/src/plugins/saved_objects/public/save_modal/saved_object_save_modal.tsx index 09876df3c8285..b5dbd342f1b9c 100644 --- a/src/plugins/saved_objects/public/save_modal/saved_object_save_modal.tsx +++ b/src/plugins/saved_objects/public/save_modal/saved_object_save_modal.tsx @@ -25,12 +25,13 @@ import { EuiSwitch, EuiSwitchEvent, EuiTextArea, - EuiToolTip, + EuiIconTip, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; import React from 'react'; import { EuiText } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; +import { euiThemeVars } from '@kbn/ui-theme'; export interface OnSaveProps { newTitle: string; @@ -175,16 +176,7 @@ export class SavedObjectSaveModal extends React.Component - {this.props.showCopyOnSave && ( - <> - - -
{this.renderCopyOnSave()}
-
-
- - - )} + {this.props.showCopyOnSave && this.renderCopyOnSave()} private renderCopyOnSave = () => { return ( - + + + } /> - } - /> + + {this.props.mustCopyOnSaveMessage && ( + + + + )} + + ); }; } From cb61a5772d445a65e3b191db8029aa8a340ac05d Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Thu, 18 Jan 2024 19:11:06 +0000 Subject: [PATCH 08/28] [CI] Auto-commit changed files from 'node scripts/lint_ts_projects --fix' --- src/plugins/saved_objects/tsconfig.json | 1 + 1 file changed, 1 insertion(+) diff --git a/src/plugins/saved_objects/tsconfig.json b/src/plugins/saved_objects/tsconfig.json index 18332952255c8..41227a95d1927 100644 --- a/src/plugins/saved_objects/tsconfig.json +++ b/src/plugins/saved_objects/tsconfig.json @@ -14,6 +14,7 @@ "@kbn/i18n-react", "@kbn/test-jest-helpers", "@kbn/utility-types", + "@kbn/ui-theme", ], "exclude": [ "target/**/*", From 08495b8b50cc464d9b0fd568a0fc44674cf69ae3 Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Thu, 18 Jan 2024 19:52:15 +0000 Subject: [PATCH 09/28] [CI] Auto-commit changed files from 'node scripts/generate codeowners' --- .github/CODEOWNERS | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index d9449dd6b4105..e4401fa34b9c6 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -502,6 +502,7 @@ packages/kbn-logging @elastic/kibana-core packages/kbn-logging-mocks @elastic/kibana-core x-pack/plugins/logs_shared @elastic/obs-ux-logs-team x-pack/plugins/logstash @elastic/logstash +packages/kbn-managed-content-badge @elastic/kibana-visualizations packages/kbn-managed-vscode-config @elastic/kibana-operations packages/kbn-managed-vscode-config-cli @elastic/kibana-operations packages/kbn-management/cards_navigation @elastic/platform-deployment-management From 0978bc00e2b0a8aeace067d2f2cd19cec172c520 Mon Sep 17 00:00:00 2001 From: Drew Tate Date: Thu, 18 Jan 2024 14:00:30 -0700 Subject: [PATCH 10/28] undo managed type change --- packages/kbn-content-management-utils/src/types.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/kbn-content-management-utils/src/types.ts b/packages/kbn-content-management-utils/src/types.ts index 4285587611357..f350da4f82cf9 100644 --- a/packages/kbn-content-management-utils/src/types.ts +++ b/packages/kbn-content-management-utils/src/types.ts @@ -200,7 +200,7 @@ export interface SOWithMetadata { statusCode: number; metadata?: Record; }; - managed: boolean; + managed?: boolean; attributes: Attributes; references: Reference[]; namespaces?: string[]; From 7c15f12e7c9e5f0d04707407cc11303d6bc86140 Mon Sep 17 00:00:00 2001 From: Drew Tate Date: Thu, 18 Jan 2024 14:17:49 -0700 Subject: [PATCH 11/28] make managed prop optional --- .../plugins/lens/public/app_plugin/save_modal_container.tsx | 4 ++-- x-pack/plugins/lens/public/embeddable/embeddable.tsx | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/x-pack/plugins/lens/public/app_plugin/save_modal_container.tsx b/x-pack/plugins/lens/public/app_plugin/save_modal_container.tsx index f28750907a4fc..75fbf4eb7ead3 100644 --- a/x-pack/plugins/lens/public/app_plugin/save_modal_container.tsx +++ b/x-pack/plugins/lens/public/app_plugin/save_modal_container.tsx @@ -51,7 +51,7 @@ export type SaveModalContainerProps = { >; initialContext?: VisualizeFieldContext | VisualizeEditorContext; // is this visualization managed by the system? - managed: boolean; + managed?: boolean; } & ExtraProps; export function SaveModalContainer({ @@ -183,7 +183,7 @@ export function SaveModalContainer({ savedObjectId={savedObjectId} returnToOriginSwitchLabel={returnToOriginSwitchLabel} returnToOrigin={redirectToOrigin != null} - managed={managed} + managed={Boolean(managed)} /> ); } diff --git a/x-pack/plugins/lens/public/embeddable/embeddable.tsx b/x-pack/plugins/lens/public/embeddable/embeddable.tsx index 25fe9fb917e94..eaef54b88b9e6 100644 --- a/x-pack/plugins/lens/public/embeddable/embeddable.tsx +++ b/x-pack/plugins/lens/public/embeddable/embeddable.tsx @@ -142,7 +142,7 @@ export type LensSavedObjectAttributes = Omit export interface LensUnwrapMetaInfo { sharingSavedObjectProps?: SharingSavedObjectProps; - managed: boolean; + managed?: boolean; } export interface LensUnwrapResult { From 7e24a6b17d7b4cd170c785c0c182015d5595d77c Mon Sep 17 00:00:00 2001 From: Drew Tate Date: Thu, 18 Jan 2024 14:30:04 -0700 Subject: [PATCH 12/28] update snap --- .../state_management/__snapshots__/load_initial.test.tsx.snap | 1 + 1 file changed, 1 insertion(+) diff --git a/x-pack/plugins/lens/public/state_management/__snapshots__/load_initial.test.tsx.snap b/x-pack/plugins/lens/public/state_management/__snapshots__/load_initial.test.tsx.snap index b93e1dafa225c..4c09cefdd6de9 100644 --- a/x-pack/plugins/lens/public/state_management/__snapshots__/load_initial.test.tsx.snap +++ b/x-pack/plugins/lens/public/state_management/__snapshots__/load_initial.test.tsx.snap @@ -24,6 +24,7 @@ Object { "isLinkedToOriginatingApp": false, "isLoading": false, "isSaveable": true, + "managed": false, "persistedDoc": Object { "exactMatchDoc": Object { "attributes": Object { From 394db6d5262ff292278ae7d0c2954a14dd877b18 Mon Sep 17 00:00:00 2001 From: Drew Tate Date: Thu, 18 Jan 2024 16:16:04 -0700 Subject: [PATCH 13/28] fix translation string --- 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 5fcaefbcbe748..ee96f2d89c7e4 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 @@ -1067,7 +1067,7 @@ export const LensTopNavMenu = ({ managed ? [ getManagedContentBadge( - i18n.translate('x-pack.lens.managedBadgeTooltip', { + i18n.translate('xpack.lens.managedBadgeTooltip', { defaultMessage: 'This visualization is managed by Elastic. Changes made here must be saved in a new visualization.', }) From ee5e0b8808a1d60f68da3858941afd460690e58d Mon Sep 17 00:00:00 2001 From: Drew Tate Date: Sat, 20 Jan 2024 21:23:28 -0700 Subject: [PATCH 14/28] remove unused translation --- 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 2598405f9e40f..34b1c0c19ebcc 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -1162,7 +1162,6 @@ "dashboard.appLeaveConfirmModal.cancelButtonLabel": "Annuler", "dashboard.appLeaveConfirmModal.unsavedChangesSubtitle": "Quitter le tableau de bord sans enregistrer ?", "dashboard.appLeaveConfirmModal.unsavedChangesTitle": "Modifications non enregistrées", - "dashboard.badge.managed.text": "Géré", "dashboard.badge.managed.tooltip": "Ce tableau de bord est géré par le système. Cloner ce tableau de bord pour effectuer des modifications.", "dashboard.badge.readOnly.text": "Lecture seule", "dashboard.badge.readOnly.tooltip": "Impossible d'enregistrer les tableaux de bord", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 2243248e76da1..b1fecf1ac3072 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -1176,7 +1176,6 @@ "dashboard.appLeaveConfirmModal.cancelButtonLabel": "キャンセル", "dashboard.appLeaveConfirmModal.unsavedChangesSubtitle": "作業を保存せずにダッシュボードから移動しますか?", "dashboard.appLeaveConfirmModal.unsavedChangesTitle": "保存されていない変更", - "dashboard.badge.managed.text": "管理中", "dashboard.badge.managed.tooltip": "このダッシュボードはシステムで管理されています。変更するには、このダッシュボードを複製してください。", "dashboard.badge.readOnly.text": "読み取り専用", "dashboard.badge.readOnly.tooltip": "ダッシュボードを保存できません", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index a0f4b527e0671..735bca8ee82b8 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -1176,7 +1176,6 @@ "dashboard.appLeaveConfirmModal.cancelButtonLabel": "取消", "dashboard.appLeaveConfirmModal.unsavedChangesSubtitle": "离开有未保存工作的仪表板?", "dashboard.appLeaveConfirmModal.unsavedChangesTitle": "未保存的更改", - "dashboard.badge.managed.text": "托管", "dashboard.badge.managed.tooltip": "此仪表板由系统管理。克隆此仪表板以做出更改。", "dashboard.badge.readOnly.text": "只读", "dashboard.badge.readOnly.tooltip": "无法保存仪表板", From 367dc86b3d63f12bc31ef7299daa91c717920d22 Mon Sep 17 00:00:00 2001 From: Drew Tate Date: Mon, 22 Jan 2024 08:53:36 -0700 Subject: [PATCH 15/28] show managed badge for saved searches --- .../main/components/top_nav/get_top_nav_badges.tsx | 14 ++++++++++++++ .../saved_search/common/saved_searches_utils.ts | 4 +++- .../common/service/get_saved_searches.ts | 7 ++++++- .../common/service/saved_searches_utils.ts | 5 +++-- src/plugins/saved_search/common/types.ts | 2 ++ 5 files changed, 28 insertions(+), 4 deletions(-) diff --git a/src/plugins/discover/public/application/main/components/top_nav/get_top_nav_badges.tsx b/src/plugins/discover/public/application/main/components/top_nav/get_top_nav_badges.tsx index 74547544848d9..c48af2a22e0b2 100644 --- a/src/plugins/discover/public/application/main/components/top_nav/get_top_nav_badges.tsx +++ b/src/plugins/discover/public/application/main/components/top_nav/get_top_nav_badges.tsx @@ -8,6 +8,8 @@ import type { TopNavMenuBadgeProps } from '@kbn/navigation-plugin/public'; import { getTopNavUnsavedChangesBadge } from '@kbn/unsaved-changes-badge'; +import { getManagedContentBadge } from '@kbn/managed-content-badge'; +import { i18n } from '@kbn/i18n'; import { DiscoverStateContainer } from '../../services/discover_state'; import type { TopNavCustomization } from '../../../../customizations'; import { onSaveSearch } from './on_save_search'; @@ -53,5 +55,17 @@ export const getTopNavBadges = ({ }); } + if (stateContainer.savedSearchState.getState().managed) { + entries.push({ + data: getManagedContentBadge( + i18n.translate('discover.topNav.managedContentLabel', { + defaultMessage: + 'This saved search is managed by Elastic. Changes here must be saved to a new saved search.', + }) + ), + order: -100, + }); + } + return entries.sort((a, b) => a.order - b.order).map((entry) => entry.data); }; diff --git a/src/plugins/saved_search/common/saved_searches_utils.ts b/src/plugins/saved_search/common/saved_searches_utils.ts index d2a179e36817b..2d3b0fc3e7b12 100644 --- a/src/plugins/saved_search/common/saved_searches_utils.ts +++ b/src/plugins/saved_search/common/saved_searches_utils.ts @@ -12,7 +12,8 @@ export const fromSavedSearchAttributes = ( id: string | undefined, attributes: SavedSearchAttributes, tags: string[] | undefined, - searchSource: SavedSearch['searchSource'] + searchSource: SavedSearch['searchSource'], + managed: boolean ): SavedSearch => ({ id, searchSource, @@ -34,4 +35,5 @@ export const fromSavedSearchAttributes = ( rowsPerPage: attributes.rowsPerPage, sampleSize: attributes.sampleSize, breakdownField: attributes.breakdownField, + managed, }); diff --git a/src/plugins/saved_search/common/service/get_saved_searches.ts b/src/plugins/saved_search/common/service/get_saved_searches.ts index 653403c9f0b47..d81830e76d519 100644 --- a/src/plugins/saved_search/common/service/get_saved_searches.ts +++ b/src/plugins/saved_search/common/service/get_saved_searches.ts @@ -64,11 +64,13 @@ export const convertToSavedSearch = async ( attributes, references, sharingSavedObjectProps, + managed, }: { savedSearchId: string | undefined; attributes: SavedSearchAttributes; references: Reference[]; sharingSavedObjectProps: SavedSearch['sharingSavedObjectProps']; + managed: boolean | undefined; }, { searchSourceCreate, savedObjectsTagging }: GetSavedSearchDependencies ) => { @@ -92,7 +94,8 @@ export const convertToSavedSearch = async ( tags, references, await searchSourceCreate(searchSourceValues), - sharingSavedObjectProps + sharingSavedObjectProps, + Boolean(managed) ); return returnVal; @@ -106,6 +109,7 @@ export const getSavedSearch = async (savedSearchId: string, deps: GetSavedSearch attributes: so.item.attributes, references: so.item.references, sharingSavedObjectProps: so.meta, + managed: so.item.managed, }, deps ); @@ -124,4 +128,5 @@ export const getNewSavedSearch = ({ searchSource: ISearchStartSearchSource; }): SavedSearch => ({ searchSource: searchSource.createEmpty(), + managed: false, }); diff --git a/src/plugins/saved_search/common/service/saved_searches_utils.ts b/src/plugins/saved_search/common/service/saved_searches_utils.ts index ab4720b7802f8..33bb9621835e6 100644 --- a/src/plugins/saved_search/common/service/saved_searches_utils.ts +++ b/src/plugins/saved_search/common/service/saved_searches_utils.ts @@ -19,9 +19,10 @@ export const fromSavedSearchAttributes = ( tags: string[] | undefined, references: SavedObjectReference[] | undefined, searchSource: SavedSearch['searchSource'], - sharingSavedObjectProps: SavedSearch['sharingSavedObjectProps'] + sharingSavedObjectProps: SavedSearch['sharingSavedObjectProps'], + managed: boolean ): SavedSearch => ({ - ...fromSavedSearchAttributesCommon(id, attributes, tags, searchSource), + ...fromSavedSearchAttributesCommon(id, attributes, tags, searchSource, managed), sharingSavedObjectProps, references, }); diff --git a/src/plugins/saved_search/common/types.ts b/src/plugins/saved_search/common/types.ts index acb98d26a0d14..d4ccb46d69cd5 100644 --- a/src/plugins/saved_search/common/types.ts +++ b/src/plugins/saved_search/common/types.ts @@ -74,6 +74,8 @@ export interface SavedSearch { rowsPerPage?: number; sampleSize?: number; breakdownField?: string; + // Whether or not this saved search is managed by the system + managed: boolean; references?: SavedObjectReference[]; sharingSavedObjectProps?: { outcome?: SavedObjectsResolveResponse['outcome']; From c401eb631b7744625fb1ad07460c336e6b8984a1 Mon Sep 17 00:00:00 2001 From: Drew Tate Date: Mon, 22 Jan 2024 10:50:02 -0700 Subject: [PATCH 16/28] update unsaved changes badge for managed content --- .../top_nav/get_top_nav_badges.test.ts | 38 +++++++++++++++++++ .../components/top_nav/get_top_nav_badges.tsx | 12 ++++-- 2 files changed, 46 insertions(+), 4 deletions(-) diff --git a/src/plugins/discover/public/application/main/components/top_nav/get_top_nav_badges.test.ts b/src/plugins/discover/public/application/main/components/top_nav/get_top_nav_badges.test.ts index 165c48c8d23c4..6f3ec1c3bd4b6 100644 --- a/src/plugins/discover/public/application/main/components/top_nav/get_top_nav_badges.test.ts +++ b/src/plugins/discover/public/application/main/components/top_nav/get_top_nav_badges.test.ts @@ -9,6 +9,9 @@ import { getTopNavBadges } from './get_top_nav_badges'; import { discoverServiceMock } from '../../../../__mocks__/services'; import { getDiscoverStateMock } from '../../../../__mocks__/discover_state.mock'; +import { savedSearchMock } from '../../../../__mocks__/saved_search'; +import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; const stateContainer = getDiscoverStateMock({ isTimeBased: true }); @@ -40,6 +43,41 @@ describe('getTopNavBadges()', function () { `); }); + describe('managed saved search', () => { + const stateContainerWithManagedSavedSearch = getDiscoverStateMock({ + savedSearch: { ...savedSearchMock, managed: true }, + }); + + test('should return the managed badge when managed saved search', () => { + const topNavBadges = getTopNavBadges({ + hasUnsavedChanges: false, + services: discoverServiceMock, + stateContainer: stateContainerWithManagedSavedSearch, + topNavCustomization: undefined, + }); + + expect(topNavBadges).toHaveLength(1); + expect(topNavBadges[0].badgeText).toEqual('Managed'); + }); + + test('should not show save in unsaved changed badge', () => { + const topNavBadges = getTopNavBadges({ + hasUnsavedChanges: true, + services: discoverServiceMock, + stateContainer: stateContainerWithManagedSavedSearch, + topNavCustomization: undefined, + }); + + expect(topNavBadges).toHaveLength(2); + const unsavedChangesBadge = topNavBadges[1]; + expect(unsavedChangesBadge.badgeText).toEqual('Unsaved changes'); + + render(unsavedChangesBadge.renderCustomBadge!({ badgeText: 'Unsaved changes' })); + userEvent.click(screen.getByRole('button')); // open menu + expect(screen.queryByText('Save')).toBeNull(); + }); + }); + test('should not return the unsaved changes badge when disabled in customization', () => { const topNavBadges = getTopNavBadges({ hasUnsavedChanges: true, diff --git a/src/plugins/discover/public/application/main/components/top_nav/get_top_nav_badges.tsx b/src/plugins/discover/public/application/main/components/top_nav/get_top_nav_badges.tsx index c48af2a22e0b2..351a273d568f0 100644 --- a/src/plugins/discover/public/application/main/components/top_nav/get_top_nav_badges.tsx +++ b/src/plugins/discover/public/application/main/components/top_nav/get_top_nav_badges.tsx @@ -40,13 +40,17 @@ export const getTopNavBadges = ({ const defaultBadges = topNavCustomization?.defaultBadges; const entries = [...(topNavCustomization?.getBadges?.() ?? [])]; + const isManaged = stateContainer.savedSearchState.getState().managed; + if (hasUnsavedChanges && !defaultBadges?.unsavedChangesBadge?.disabled) { entries.push({ data: getTopNavUnsavedChangesBadge({ onRevert: stateContainer.actions.undoSavedSearchChanges, - onSave: async () => { - await saveSearch(); - }, + onSave: !isManaged + ? async () => { + await saveSearch(); + } + : undefined, onSaveAs: async () => { await saveSearch(true); }, @@ -55,7 +59,7 @@ export const getTopNavBadges = ({ }); } - if (stateContainer.savedSearchState.getState().managed) { + if (isManaged) { entries.push({ data: getManagedContentBadge( i18n.translate('discover.topNav.managedContentLabel', { From 8fa69cc39fb8b67f5d0b4e028359dd51140ec442 Mon Sep 17 00:00:00 2001 From: Drew Tate Date: Mon, 22 Jan 2024 10:57:54 -0700 Subject: [PATCH 17/28] disable overwriting in save modal --- .../main/components/top_nav/on_save_search.tsx | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/plugins/discover/public/application/main/components/top_nav/on_save_search.tsx b/src/plugins/discover/public/application/main/components/top_nav/on_save_search.tsx index 30d73a6280072..443a528e26e6e 100644 --- a/src/plugins/discover/public/application/main/components/top_nav/on_save_search.tsx +++ b/src/plugins/discover/public/application/main/components/top_nav/on_save_search.tsx @@ -179,6 +179,7 @@ export async function onSaveSearch({ description={savedSearch.description} timeRestore={savedSearch.timeRestore} tags={savedSearch.tags ?? []} + managed={savedSearch.managed} onSave={onSave} onClose={onClose ?? (() => {})} /> @@ -197,6 +198,7 @@ const SaveSearchObjectModal: React.FC<{ tags: string[]; onSave: (props: OnSaveProps & { newTimeRestore: boolean; newTags: string[] }) => void; onClose: () => void; + managed: boolean; }> = ({ isTimeBased, services, @@ -208,6 +210,7 @@ const SaveSearchObjectModal: React.FC<{ timeRestore: savedTimeRestore, onSave, onClose, + managed, }) => { const { savedObjectsTagging } = services; const [timeRestore, setTimeRestore] = useState( @@ -277,6 +280,14 @@ const SaveSearchObjectModal: React.FC<{ options={options} onSave={onModalSave} onClose={onClose} + mustCopyOnSaveMessage={ + managed + ? i18n.translate('discover.localMenu.mustCopyOnSave', { + defaultMessage: + 'This saved search is managed by Elastic. Changes here must be saved to a new saved search.', + }) + : undefined + } /> ); }; From caaedecb4915c04e00400f94e0357111bdd8f0db Mon Sep 17 00:00:00 2001 From: Drew Tate Date: Mon, 22 Jan 2024 11:30:27 -0700 Subject: [PATCH 18/28] add FT --- .../kbn_archiver/managed_content.json | 4 ++ .../apps/managed_content/managed_content.ts | 44 ++++++++++++++----- 2 files changed, 37 insertions(+), 11 deletions(-) diff --git a/test/functional/fixtures/kbn_archiver/managed_content.json b/test/functional/fixtures/kbn_archiver/managed_content.json index 2bbc6e50a9e40..3f94bf396b1ee 100644 --- a/test/functional/fixtures/kbn_archiver/managed_content.json +++ b/test/functional/fixtures/kbn_archiver/managed_content.json @@ -3,3 +3,7 @@ {"attributes":{"description":"","state":{"adHocDataViews":{},"datasourceStates":{"formBased":{"layers":{"e633b1af-3ab4-4bf5-8faa-fefde06c4a4a":{"columnOrder":["f2555a1a-6f93-43fd-bc63-acdfadd47729","d229daf9-9658-4579-99af-01d8adb2f25f"],"columns":{"d229daf9-9658-4579-99af-01d8adb2f25f":{"dataType":"number","isBucketed":false,"label":"Median of bytes","operationType":"median","params":{"emptyAsNull":true},"scale":"ratio","sourceField":"bytes"},"f2555a1a-6f93-43fd-bc63-acdfadd47729":{"dataType":"date","isBucketed":true,"label":"@timestamp","operationType":"date_histogram","params":{"dropPartials":false,"includeEmptyRows":true,"interval":"auto"},"scale":"interval","sourceField":"@timestamp"}},"incompleteColumns":{},"sampling":1}}},"indexpattern":{"layers":{}},"textBased":{"layers":{}}},"filters":[],"internalReferences":[],"query":{"language":"kuery","query":""},"visualization":{"axisTitlesVisibilitySettings":{"x":true,"yLeft":true,"yRight":true},"fittingFunction":"None","gridlinesVisibilitySettings":{"x":true,"yLeft":true,"yRight":true},"labelsOrientation":{"x":0,"yLeft":0,"yRight":0},"layers":[{"accessors":["d229daf9-9658-4579-99af-01d8adb2f25f"],"layerId":"e633b1af-3ab4-4bf5-8faa-fefde06c4a4a","layerType":"data","position":"top","seriesType":"bar_stacked","showGridlines":false,"xAccessor":"f2555a1a-6f93-43fd-bc63-acdfadd47729"}],"legend":{"isVisible":true,"position":"right"},"preferredSeriesType":"bar_stacked","tickLabelsVisibilitySettings":{"x":true,"yLeft":true,"yRight":true},"valueLabels":"hide"}},"title":"Lens vis (managed)","visualizationType":"lnsXY"},"coreMigrationVersion":"8.8.0","created_at":"2024-01-18T17:42:12.920Z","id":"managed-36db-4a3b-a4ba-7a64ab8f130b","managed":true,"references":[{"id":"5f863f70-4728-4e8d-b441-db08f8c33b28","name":"indexpattern-datasource-layer-e633b1af-3ab4-4bf5-8faa-fefde06c4a4a","type":"index-pattern"}],"type":"lens","typeMigrationVersion":"8.9.0","updated_at":"2024-01-18T17:42:12.920Z","version":"WzQ1LDFd"} {"attributes":{"description":"","state":{"adHocDataViews":{},"datasourceStates":{"formBased":{"layers":{"e633b1af-3ab4-4bf5-8faa-fefde06c4a4a":{"columnOrder":["f2555a1a-6f93-43fd-bc63-acdfadd47729","d229daf9-9658-4579-99af-01d8adb2f25f"],"columns":{"d229daf9-9658-4579-99af-01d8adb2f25f":{"dataType":"number","isBucketed":false,"label":"Median of bytes","operationType":"median","params":{"emptyAsNull":true},"scale":"ratio","sourceField":"bytes"},"f2555a1a-6f93-43fd-bc63-acdfadd47729":{"dataType":"date","isBucketed":true,"label":"@timestamp","operationType":"date_histogram","params":{"dropPartials":false,"includeEmptyRows":true,"interval":"auto"},"scale":"interval","sourceField":"@timestamp"}},"incompleteColumns":{},"sampling":1}}},"indexpattern":{"layers":{}},"textBased":{"layers":{}}},"filters":[],"internalReferences":[],"query":{"language":"kuery","query":""},"visualization":{"axisTitlesVisibilitySettings":{"x":true,"yLeft":true,"yRight":true},"fittingFunction":"None","gridlinesVisibilitySettings":{"x":true,"yLeft":true,"yRight":true},"labelsOrientation":{"x":0,"yLeft":0,"yRight":0},"layers":[{"accessors":["d229daf9-9658-4579-99af-01d8adb2f25f"],"layerId":"e633b1af-3ab4-4bf5-8faa-fefde06c4a4a","layerType":"data","position":"top","seriesType":"bar_stacked","showGridlines":false,"xAccessor":"f2555a1a-6f93-43fd-bc63-acdfadd47729"}],"legend":{"isVisible":true,"position":"right"},"preferredSeriesType":"bar_stacked","tickLabelsVisibilitySettings":{"x":true,"yLeft":true,"yRight":true},"valueLabels":"hide"}},"title":"Lens vis (unmanaged)","visualizationType":"lnsXY"},"coreMigrationVersion":"8.8.0","created_at":"2024-01-18T17:42:12.920Z","id":"unmanaged-36db-4a3b-a4ba-7a64ab8f130b","managed":false,"references":[{"id":"5f863f70-4728-4e8d-b441-db08f8c33b28","name":"indexpattern-datasource-layer-e633b1af-3ab4-4bf5-8faa-fefde06c4a4a","type":"index-pattern"}],"type":"lens","typeMigrationVersion":"8.9.0","updated_at":"2024-01-18T17:42:12.920Z","version":"WzQ1LDFd"} + +{"attributes":{"columns":["@tags","clientip"],"description":"","grid":{},"hideChart":false,"isTextBasedQuery":false,"kibanaSavedObjectMeta":{"searchSourceJSON":"{\"query\":{\"query\":\"agent.raw:\\\"Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.1.4322)\\\" \",\"language\":\"kuery\"},\"filter\":[],\"indexRefName\":\"kibanaSavedObjectMeta.searchSourceJSON.index\"}"},"sort":[["@timestamp","desc"]],"timeRestore":false,"title":"Saved search","usesAdHocDataView":false},"coreMigrationVersion":"8.8.0","created_at":"2024-01-22T18:11:05.016Z","id":"managed-3d62-4113-ac7c-de2e20a68fbc","managed":true,"references":[{"id":"5f863f70-4728-4e8d-b441-db08f8c33b28","name":"kibanaSavedObjectMeta.searchSourceJSON.index","type":"index-pattern"}],"type":"search","typeMigrationVersion":"8.0.0","updated_at":"2024-01-22T18:11:05.016Z","version":"WzIzLDFd"} + +{"attributes":{"columns":["@tags","clientip"],"description":"","grid":{},"hideChart":false,"isTextBasedQuery":false,"kibanaSavedObjectMeta":{"searchSourceJSON":"{\"query\":{\"query\":\"agent.raw:\\\"Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.1.4322)\\\" \",\"language\":\"kuery\"},\"filter\":[],\"indexRefName\":\"kibanaSavedObjectMeta.searchSourceJSON.index\"}"},"sort":[["@timestamp","desc"]],"timeRestore":false,"title":"Saved search","usesAdHocDataView":false},"coreMigrationVersion":"8.8.0","created_at":"2024-01-22T18:11:05.016Z","id":"unmanaged-3d62-4113-ac7c-de2e20a68fbc","managed":false,"references":[{"id":"5f863f70-4728-4e8d-b441-db08f8c33b28","name":"kibanaSavedObjectMeta.searchSourceJSON.index","type":"index-pattern"}],"type":"search","typeMigrationVersion":"8.0.0","updated_at":"2024-01-22T18:11:05.016Z","version":"WzIzLDFd"} \ No newline at end of file diff --git a/x-pack/test/functional/apps/managed_content/managed_content.ts b/x-pack/test/functional/apps/managed_content/managed_content.ts index e7dbbdd25d95c..2ba7d1ce0b4ed 100644 --- a/x-pack/test/functional/apps/managed_content/managed_content.ts +++ b/x-pack/test/functional/apps/managed_content/managed_content.ts @@ -9,7 +9,7 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ getPageObjects, getService }: FtrProviderContext) { - const PageObjects = getPageObjects(['timePicker', 'lens', 'common']); + const PageObjects = getPageObjects(['timePicker', 'lens', 'common', 'discover']); const kibanaServer = getService('kibanaServer'); const esArchiver = getService('esArchiver'); const testSubjects = getService('testSubjects'); @@ -25,6 +25,18 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { kibanaServer.importExport.unload('test/functional/fixtures/kbn_archiver/managed_content'); }); + const expectManagedContentSignifiers = async ( + expected: boolean, + saveButtonTestSubject: string + ) => { + await testSubjects[expected ? 'existOrFail' : 'missingOrFail']('managedContentBadge'); + await testSubjects.click(saveButtonTestSubject); + + const saveAsNewCheckbox = await testSubjects.find('saveAsNewCheckbox'); + expect(await testSubjects.isEuiSwitchChecked(saveAsNewCheckbox)).to.be(expected); + expect(await saveAsNewCheckbox.getAttribute('disabled')).to.be(expected ? 'true' : null); + }; + describe('preventing the user from overwriting managed content', () => { it('lens', async () => { await PageObjects.common.navigateToActualUrl( @@ -34,11 +46,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { await PageObjects.lens.waitForVisualization('xyVisChart'); - await testSubjects.existOrFail('managedContentBadge'); - await testSubjects.click('lnsApp_saveButton'); - const saveAsNewCheckbox = await testSubjects.find('saveAsNewCheckbox'); - expect(await testSubjects.isEuiSwitchChecked(saveAsNewCheckbox)).to.be(true); - expect(await saveAsNewCheckbox.getAttribute('disabled')).to.be('true'); + await expectManagedContentSignifiers(true, 'lnsApp_saveButton'); await PageObjects.common.navigateToActualUrl( 'lens', @@ -47,12 +55,26 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { await PageObjects.lens.waitForVisualization('xyVisChart'); - await testSubjects.missingOrFail('managedContentBadge'); - await testSubjects.click('lnsApp_saveButton'); - await testSubjects.existOrFail('saveAsNewCheckbox'); - expect(await testSubjects.isEuiSwitchChecked('saveAsNewCheckbox')).to.be(false); - expect(await saveAsNewCheckbox.getAttribute('disabled')).to.be(null); + await expectManagedContentSignifiers(false, 'lnsApp_saveButton'); }); }); + + it('discover', async () => { + await PageObjects.common.navigateToActualUrl( + 'discover', + 'view/managed-3d62-4113-ac7c-de2e20a68fbc' + ); + await PageObjects.discover.waitForDiscoverAppOnScreen(); + + await expectManagedContentSignifiers(true, 'discoverSaveButton'); + + await PageObjects.common.navigateToActualUrl( + 'discover', + 'view/unmanaged-3d62-4113-ac7c-de2e20a68fbc' + ); + await PageObjects.discover.waitForDiscoverAppOnScreen(); + + await expectManagedContentSignifiers(false, 'discoverSaveButton'); + }); }); } From fa167c96405d5240f43709e8673b87a9767fb550 Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Mon, 22 Jan 2024 18:42:23 +0000 Subject: [PATCH 19/28] [CI] Auto-commit changed files from 'node scripts/lint_ts_projects --fix' --- src/plugins/discover/tsconfig.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/plugins/discover/tsconfig.json b/src/plugins/discover/tsconfig.json index b75f27c9266f8..def0cb5c85c02 100644 --- a/src/plugins/discover/tsconfig.json +++ b/src/plugins/discover/tsconfig.json @@ -80,7 +80,8 @@ "@kbn/core-plugins-server", "@kbn/shared-ux-button-toolbar", "@kbn/serverless", - "@kbn/deeplinks-observability" + "@kbn/deeplinks-observability", + "@kbn/managed-content-badge" ], "exclude": ["target/**/*"] } From 6ae44e428cefe497e95ccfe9c7547467de5c113a Mon Sep 17 00:00:00 2001 From: Drew Tate Date: Mon, 22 Jan 2024 12:15:55 -0700 Subject: [PATCH 20/28] fix saved search mocks --- .../main/services/discover_app_state_container.test.ts | 2 ++ .../public/embeddable/saved_search_embeddable.test.ts | 1 + .../saved_search/common/expressions/kibana_context.test.ts | 3 ++- .../saved_search/common/service/saved_searches_utils.test.ts | 4 +++- src/plugins/saved_search/public/mocks.ts | 1 + .../services/saved_searches/save_saved_searches.test.ts | 1 + .../services/saved_searches/saved_search_attribute_service.ts | 1 + .../server/services/saved_searches/get_saved_searches.ts | 3 ++- .../application/jobs/new_job/utils/new_job_utils.test.ts | 3 ++- .../components/timeline/esql_tab_content/utils/index.test.ts | 1 + .../security_solution/public/timelines/containers/api.test.ts | 1 + 11 files changed, 17 insertions(+), 4 deletions(-) diff --git a/src/plugins/discover/public/application/main/services/discover_app_state_container.test.ts b/src/plugins/discover/public/application/main/services/discover_app_state_container.test.ts index f93a9fd759da9..a215635e7cde8 100644 --- a/src/plugins/discover/public/application/main/services/discover_app_state_container.test.ts +++ b/src/plugins/discover/public/application/main/services/discover_app_state_container.test.ts @@ -99,6 +99,7 @@ describe('Test discover app state container', () => { hideChart: true, rowsPerPage: 250, hideAggregatedPreview: true, + managed: false, } as SavedSearch; test('should return correct output', () => { @@ -135,6 +136,7 @@ describe('Test discover app state container', () => { filter: [customFilter], query: undefined, }), + managed: false, }; const appState = state.getAppStateFromSavedSearch(newSavedSearchMock); expect(appState).toMatchObject( diff --git a/src/plugins/discover/public/embeddable/saved_search_embeddable.test.ts b/src/plugins/discover/public/embeddable/saved_search_embeddable.test.ts index eaa7680137fe3..47c6a26ba7647 100644 --- a/src/plugins/discover/public/embeddable/saved_search_embeddable.test.ts +++ b/src/plugins/discover/public/embeddable/saved_search_embeddable.test.ts @@ -101,6 +101,7 @@ describe('saved search embeddable', () => { sort: [['message', 'asc']] as Array<[string, string]>, searchSource, viewMode: viewModeMockValue, + managed: false, }; executeTriggerActions = jest.fn(); jest diff --git a/src/plugins/saved_search/common/expressions/kibana_context.test.ts b/src/plugins/saved_search/common/expressions/kibana_context.test.ts index dfb5ccfbf73eb..0d60de71892ee 100644 --- a/src/plugins/saved_search/common/expressions/kibana_context.test.ts +++ b/src/plugins/saved_search/common/expressions/kibana_context.test.ts @@ -89,7 +89,8 @@ describe('kibanaContextFn', () => { filter: [], }), } as unknown as SavedSearch['searchSource'], - {} as SavedSearch['sharingSavedObjectProps'] + {} as SavedSearch['sharingSavedObjectProps'], + false ) ); const args = { diff --git a/src/plugins/saved_search/common/service/saved_searches_utils.test.ts b/src/plugins/saved_search/common/service/saved_searches_utils.test.ts index b118799858348..b5d8baaeb75b0 100644 --- a/src/plugins/saved_search/common/service/saved_searches_utils.test.ts +++ b/src/plugins/saved_search/common/service/saved_searches_utils.test.ts @@ -37,7 +37,8 @@ describe('saved_searches_utils', () => { ['tags-1', 'tags-2'], [], createSearchSourceMock(), - {} + {}, + false ) ).toMatchInlineSnapshot(` Object { @@ -106,6 +107,7 @@ describe('saved_searches_utils', () => { hideChart: true, isTextBasedQuery: true, usesAdHocDataView: false, + managed: false, }; expect(toSavedSearchAttributes(savedSearch, '{}')).toMatchInlineSnapshot(` diff --git a/src/plugins/saved_search/public/mocks.ts b/src/plugins/saved_search/public/mocks.ts index 3e0e20bd6e7a7..15c4bdd556f38 100644 --- a/src/plugins/saved_search/public/mocks.ts +++ b/src/plugins/saved_search/public/mocks.ts @@ -57,6 +57,7 @@ const savedSearchStartMock = (): SavedSearchPublicPluginStart => ({ id, title: result.attributes.title, searchSource: createEmptySearchSource(), + managed: false, }) ), }, diff --git a/src/plugins/saved_search/public/services/saved_searches/save_saved_searches.test.ts b/src/plugins/saved_search/public/services/saved_searches/save_saved_searches.test.ts index a04f0af45eb29..344087cdf6686 100644 --- a/src/plugins/saved_search/public/services/saved_searches/save_saved_searches.test.ts +++ b/src/plugins/saved_search/public/services/saved_searches/save_saved_searches.test.ts @@ -34,6 +34,7 @@ describe('saveSavedSearch', () => { sharingSavedObjectProps: { outcome: 'aliasMatch', }, + managed: false, } as SavedSearch; }); diff --git a/src/plugins/saved_search/public/services/saved_searches/saved_search_attribute_service.ts b/src/plugins/saved_search/public/services/saved_searches/saved_search_attribute_service.ts index f79b010bd62d2..bb056b1046f10 100644 --- a/src/plugins/saved_search/public/services/saved_searches/saved_search_attribute_service.ts +++ b/src/plugins/saved_search/public/services/saved_searches/saved_search_attribute_service.ts @@ -98,6 +98,7 @@ export const toSavedSearch = async ( ...splitReferences(result.attributes), savedSearchId: id, sharingSavedObjectProps, + managed: false, }, createGetSavedSearchDeps(services) ); diff --git a/src/plugins/saved_search/server/services/saved_searches/get_saved_searches.ts b/src/plugins/saved_search/server/services/saved_searches/get_saved_searches.ts index 6a097481b67b3..7ac1755ca04bb 100644 --- a/src/plugins/saved_search/server/services/saved_searches/get_saved_searches.ts +++ b/src/plugins/saved_search/server/services/saved_searches/get_saved_searches.ts @@ -38,6 +38,7 @@ export const getSavedSearch = async (savedSearchId: string, deps: GetSavedSearch savedSearchId, savedSearch.attributes, undefined, - await deps.searchSourceStart.create(searchSourceValues) + await deps.searchSourceStart.create(searchSourceValues), + false ); }; diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/utils/new_job_utils.test.ts b/x-pack/plugins/ml/public/application/jobs/new_job/utils/new_job_utils.test.ts index bdec35bfa6054..73410aa70f022 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/utils/new_job_utils.test.ts +++ b/x-pack/plugins/ml/public/application/jobs/new_job/utils/new_job_utils.test.ts @@ -44,7 +44,8 @@ describe('createSearchItems', () => { [], { getField: getFieldMock(searchSource), - } as unknown as ISearchSource + } as unknown as ISearchSource, + false ); test('should match data view', () => { diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/esql_tab_content/utils/index.test.ts b/x-pack/plugins/security_solution/public/timelines/components/timeline/esql_tab_content/utils/index.test.ts index bc7cb3e2f8a0a..df4508a935317 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/esql_tab_content/utils/index.test.ts +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/esql_tab_content/utils/index.test.ts @@ -28,6 +28,7 @@ describe('savedSearchComparator', () => { index: dataViewMock, query: customQuery, }), + managed: false, }; it('should result true when saved search is same', () => { diff --git a/x-pack/plugins/security_solution/public/timelines/containers/api.test.ts b/x-pack/plugins/security_solution/public/timelines/containers/api.test.ts index a055b028036e3..e6238d462e3c3 100644 --- a/x-pack/plugins/security_solution/public/timelines/containers/api.test.ts +++ b/x-pack/plugins/security_solution/public/timelines/containers/api.test.ts @@ -488,6 +488,7 @@ describe('copyTimeline', () => { index: dataViewMock, query: customQuery, }), + managed: false, }; beforeAll(() => { From 3c70448674a50f7f87bb8687ffbbc727080bea1d Mon Sep 17 00:00:00 2001 From: Drew Tate Date: Mon, 22 Jan 2024 14:41:53 -0700 Subject: [PATCH 21/28] show managed badge --- .../common/content_management/v1/types.ts | 1 + src/plugins/visualizations/public/types.ts | 1 + .../public/utils/saved_visualize_utils.ts | 2 ++ .../components/visualize_top_nav.tsx | 19 ++++++++++++++++++- .../utils/get_visualization_instance.ts | 4 ++-- 5 files changed, 24 insertions(+), 3 deletions(-) diff --git a/src/plugins/visualizations/common/content_management/v1/types.ts b/src/plugins/visualizations/common/content_management/v1/types.ts index a1e5bd6a1aba5..80159165d97d9 100644 --- a/src/plugins/visualizations/common/content_management/v1/types.ts +++ b/src/plugins/visualizations/common/content_management/v1/types.ts @@ -57,6 +57,7 @@ export interface VisualizationSavedObject { statusCode: number; metadata?: Record; }; + managed?: boolean; } export type PartialVisualizationSavedObject = Omit< diff --git a/src/plugins/visualizations/public/types.ts b/src/plugins/visualizations/public/types.ts index c23f40dc85bae..f00777c5ef957 100644 --- a/src/plugins/visualizations/public/types.ts +++ b/src/plugins/visualizations/public/types.ts @@ -55,6 +55,7 @@ export interface VisSavedObject extends ISavedVis { searchSource?: ISearchSource; version?: string; tags?: string[]; + managed: boolean; } export interface SaveVisOptions { diff --git a/src/plugins/visualizations/public/utils/saved_visualize_utils.ts b/src/plugins/visualizations/public/utils/saved_visualize_utils.ts index 85932c09729c3..7ef70fb8fc9b6 100644 --- a/src/plugins/visualizations/public/utils/saved_visualize_utils.ts +++ b/src/plugins/visualizations/public/utils/saved_visualize_utils.ts @@ -227,6 +227,7 @@ export async function getSavedVisualization( getEsType: () => SAVED_VIS_TYPE, getDisplayName: () => SAVED_VIS_TYPE, searchSource: opts.searchSource ? services.search.searchSource.createEmpty() : undefined, + managed: false, } as VisSavedObject; const defaultsProps = getDefaults(opts); @@ -255,6 +256,7 @@ export async function getSavedVisualization( Object.assign(savedObject, attributes); savedObject.lastSavedTitle = savedObject.title; + savedObject.managed = Boolean(resp.managed); savedObject.sharingSavedObjectProps = { aliasTargetId, diff --git a/src/plugins/visualizations/public/visualize_app/components/visualize_top_nav.tsx b/src/plugins/visualizations/public/visualize_app/components/visualize_top_nav.tsx index 4fc81d760d31b..17600cfd23146 100644 --- a/src/plugins/visualizations/public/visualize_app/components/visualize_top_nav.tsx +++ b/src/plugins/visualizations/public/visualize_app/components/visualize_top_nav.tsx @@ -14,6 +14,7 @@ import useLocalStorage from 'react-use/lib/useLocalStorage'; import { useKibana } from '@kbn/kibana-react-plugin/public'; import type { DataView } from '@kbn/data-views-plugin/public'; import { switchMap } from 'rxjs'; +import { getManagedContentBadge } from '@kbn/managed-content-badge'; import type { VisualizeServices, VisualizeAppState, @@ -63,7 +64,11 @@ const TopNav = ({ const { services } = useKibana(); const { TopNavMenu } = services.navigation.ui; const { setHeaderActionMenu, visualizeCapabilities } = services; - const { embeddableHandler, vis } = visInstance; + const { + embeddableHandler, + vis, + savedVis: { managed }, + } = visInstance; const [inspectorSession, setInspectorSession] = useState(); const [navigateToLens, setNavigateToLens] = useState(false); const [displayEditInLensItem, setDisplayEditInLensItem] = useState(false); @@ -315,6 +320,18 @@ const TopNav = ({ showDatePicker={showDatePicker()} showFilterBar={showFilterBar} showQueryInput={showQueryInput} + badges={ + managed + ? [ + getManagedContentBadge( + i18n.translate('visualizations.managedBadgeTooltip', { + defaultMessage: + 'This visualization is managed by Elastic. Changes made here must be saved to a new visualization.', + }) + ), + ] + : undefined + } saveQueryMenuVisibility={ services.visualizeCapabilities.saveQuery ? 'allowed_by_app_privilege' : 'globally_managed' } diff --git a/src/plugins/visualizations/public/visualize_app/utils/get_visualization_instance.ts b/src/plugins/visualizations/public/visualize_app/utils/get_visualization_instance.ts index 5ad857c13c4ea..443d83541640a 100644 --- a/src/plugins/visualizations/public/visualize_app/utils/get_visualization_instance.ts +++ b/src/plugins/visualizations/public/visualize_app/utils/get_visualization_instance.ts @@ -19,7 +19,7 @@ import { VisualizeEmbeddableContract, VisualizeInput, } from '../..'; -import type { VisualizeServices } from '../types'; +import type { VisInstance, VisualizeServices } from '../types'; function isErrorRelatedToRuntimeFields(error: ExpressionValueError['error']) { const originalError = error.original || error; @@ -120,7 +120,7 @@ export const getVisualizationInstance = async ( * Both come from url search query */ opts?: Record | string -) => { +): Promise => { const { data, spaces, savedObjectsTagging } = visualizeServices; const savedVis: VisSavedObject = await getSavedVisualization( From e0ce6ad7b1773387784c6e4120489669172e2587 Mon Sep 17 00:00:00 2001 From: Drew Tate Date: Mon, 22 Jan 2024 14:50:45 -0700 Subject: [PATCH 22/28] Enforce copy-on-save --- .../public/visualize_app/utils/get_top_nav_config.tsx | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/plugins/visualizations/public/visualize_app/utils/get_top_nav_config.tsx b/src/plugins/visualizations/public/visualize_app/utils/get_top_nav_config.tsx index ce12d23ad0c28..955c3f6111762 100644 --- a/src/plugins/visualizations/public/visualize_app/utils/get_top_nav_config.tsx +++ b/src/plugins/visualizations/public/visualize_app/utils/get_top_nav_config.tsx @@ -605,6 +605,14 @@ export const getTopNavConfig = ( } )} onClose={() => {}} + mustCopyOnSaveMessage={ + savedVis.managed + ? i18n.translate('visualizations.topNavMenu.mustCopyOnSave', { + defaultMessage: + 'This visualization is managed by Elastic. Changes here must be saved to a new visualization.', + }) + : undefined + } /> ); } From d33a521760ed4393494e4ba312d956f90a50c59d Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Mon, 22 Jan 2024 21:58:05 +0000 Subject: [PATCH 23/28] [CI] Auto-commit changed files from 'node scripts/lint_ts_projects --fix' --- src/plugins/visualizations/tsconfig.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/plugins/visualizations/tsconfig.json b/src/plugins/visualizations/tsconfig.json index 296367543271a..ce65dfa5d5e3e 100644 --- a/src/plugins/visualizations/tsconfig.json +++ b/src/plugins/visualizations/tsconfig.json @@ -67,7 +67,8 @@ "@kbn/logging", "@kbn/content-management-table-list-view-common", "@kbn/chart-expressions-common", - "@kbn/shared-ux-utility" + "@kbn/shared-ux-utility", + "@kbn/managed-content-badge" ], "exclude": [ "target/**/*", From 5ecd977488c631fdf896d28a249dad622dae5666 Mon Sep 17 00:00:00 2001 From: Drew Tate Date: Mon, 22 Jan 2024 15:00:40 -0700 Subject: [PATCH 24/28] update snapshots --- .../saved_search/common/service/get_saved_searches.test.ts | 2 ++ .../saved_search/common/service/saved_searches_utils.test.ts | 1 + 2 files changed, 3 insertions(+) diff --git a/src/plugins/saved_search/common/service/get_saved_searches.test.ts b/src/plugins/saved_search/common/service/get_saved_searches.test.ts index 2b26b82eafece..792a46b904de8 100644 --- a/src/plugins/saved_search/common/service/get_saved_searches.test.ts +++ b/src/plugins/saved_search/common/service/get_saved_searches.test.ts @@ -94,6 +94,7 @@ describe('getSavedSearch', () => { "hideChart": false, "id": "ccf1af80-2297-11ec-86e0-1155ffb9c7a7", "isTextBasedQuery": undefined, + "managed": false, "references": Array [ Object { "id": "ff959d40-b880-11e8-a6d9-e546fe2bba5f", @@ -200,6 +201,7 @@ describe('getSavedSearch', () => { "hideChart": true, "id": "ccf1af80-2297-11ec-86e0-1155ffb9c7a7", "isTextBasedQuery": true, + "managed": false, "references": Array [ Object { "id": "ff959d40-b880-11e8-a6d9-e546fe2bba5f", diff --git a/src/plugins/saved_search/common/service/saved_searches_utils.test.ts b/src/plugins/saved_search/common/service/saved_searches_utils.test.ts index b5d8baaeb75b0..950823e0018a0 100644 --- a/src/plugins/saved_search/common/service/saved_searches_utils.test.ts +++ b/src/plugins/saved_search/common/service/saved_searches_utils.test.ts @@ -53,6 +53,7 @@ describe('saved_searches_utils', () => { "hideChart": true, "id": "id", "isTextBasedQuery": false, + "managed": false, "references": Array [], "refreshInterval": undefined, "rowHeight": undefined, From 19eda22e2baa5467f7bee8936ecd1658f525a03c Mon Sep 17 00:00:00 2001 From: Drew Tate Date: Mon, 22 Jan 2024 15:35:42 -0700 Subject: [PATCH 25/28] actually save a new copy --- .../saved_object_save_modal.test.tsx | 38 ++++++++++++++++--- .../save_modal/saved_object_save_modal.tsx | 2 +- 2 files changed, 34 insertions(+), 6 deletions(-) diff --git a/src/plugins/saved_objects/public/save_modal/saved_object_save_modal.test.tsx b/src/plugins/saved_objects/public/save_modal/saved_object_save_modal.test.tsx index 8e939ec58a792..237de1e835c53 100644 --- a/src/plugins/saved_objects/public/save_modal/saved_object_save_modal.test.tsx +++ b/src/plugins/saved_objects/public/save_modal/saved_object_save_modal.test.tsx @@ -10,7 +10,8 @@ import { shallow } from 'enzyme'; import React from 'react'; import { SavedObjectSaveModal } from './saved_object_save_modal'; -import { mountWithIntl } from '@kbn/test-jest-helpers'; +import { render, screen, waitFor } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; describe('SavedObjectSaveModal', () => { it('should render matching snapshot', () => { @@ -72,7 +73,9 @@ describe('SavedObjectSaveModal', () => { }); it('allows specifying custom save button label', () => { - const wrapper = mountWithIntl( + const confirmButtonLabel = 'Save and done'; + + render( void 0} onClose={() => void 0} @@ -80,11 +83,36 @@ describe('SavedObjectSaveModal', () => { showCopyOnSave={false} objectType="visualization" showDescription={true} - confirmButtonLabel="Save and done" + confirmButtonLabel={confirmButtonLabel} /> ); - expect(wrapper.find('button[data-test-subj="confirmSaveSavedObjectButton"]').text()).toBe( - 'Save and done' + + expect(screen.queryByText(confirmButtonLabel)).toBeInTheDocument(); + }); + + it('enforces copy on save', async () => { + const onSave = jest.fn(); + + render( + void 0} + title={'Saved Object title'} + objectType="visualization" + showDescription={true} + showCopyOnSave={true} + mustCopyOnSaveMessage="You must save a copy of the object." + /> ); + + expect(onSave).not.toHaveBeenCalled(); + + expect(screen.getByTestId('saveAsNewCheckbox')).toBeDisabled(); + userEvent.click(screen.getByRole('button', { name: 'Save' })); + + await waitFor(() => { + expect(onSave).toHaveBeenCalled(); + expect(onSave.mock.calls[0][0].newCopyOnSave).toBe(true); + }); }); }); diff --git a/src/plugins/saved_objects/public/save_modal/saved_object_save_modal.tsx b/src/plugins/saved_objects/public/save_modal/saved_object_save_modal.tsx index b5dbd342f1b9c..ae65ee40cee88 100644 --- a/src/plugins/saved_objects/public/save_modal/saved_object_save_modal.tsx +++ b/src/plugins/saved_objects/public/save_modal/saved_object_save_modal.tsx @@ -252,7 +252,7 @@ export class SavedObjectSaveModal extends React.Component await this.props.onSave({ newTitle: this.state.title, - newCopyOnSave: this.state.copyOnSave, + newCopyOnSave: Boolean(this.props.mustCopyOnSaveMessage) || this.state.copyOnSave, isTitleDuplicateConfirmed: this.state.isTitleDuplicateConfirmed, onTitleDuplicate: this.onTitleDuplicate, newDescription: this.state.visualizationDescription, From 1b864fb8be77be0d7fb6bbe7d977cdc548eee2be Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Mon, 22 Jan 2024 22:42:06 +0000 Subject: [PATCH 26/28] [CI] Auto-commit changed files from 'node scripts/lint_ts_projects --fix' --- src/plugins/saved_objects/tsconfig.json | 1 - 1 file changed, 1 deletion(-) diff --git a/src/plugins/saved_objects/tsconfig.json b/src/plugins/saved_objects/tsconfig.json index 41227a95d1927..ffbf2b88e543b 100644 --- a/src/plugins/saved_objects/tsconfig.json +++ b/src/plugins/saved_objects/tsconfig.json @@ -12,7 +12,6 @@ "@kbn/i18n", "@kbn/data-views-plugin", "@kbn/i18n-react", - "@kbn/test-jest-helpers", "@kbn/utility-types", "@kbn/ui-theme", ], From 8928cd9aac4941e27907a8bd419b6667937ed56f Mon Sep 17 00:00:00 2001 From: Drew Tate Date: Tue, 23 Jan 2024 08:17:01 -0700 Subject: [PATCH 27/28] update snapshot --- .../saved_searches/saved_search_attribute_service.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/plugins/saved_search/public/services/saved_searches/saved_search_attribute_service.test.ts b/src/plugins/saved_search/public/services/saved_searches/saved_search_attribute_service.test.ts index 35c35e669bff8..f2dcef1104520 100644 --- a/src/plugins/saved_search/public/services/saved_searches/saved_search_attribute_service.test.ts +++ b/src/plugins/saved_search/public/services/saved_searches/saved_search_attribute_service.test.ts @@ -190,6 +190,7 @@ describe('getSavedSearchAttributeService', () => { "hideChart": false, "id": "saved-object-id", "isTextBasedQuery": false, + "managed": false, "references": Array [ Object { "id": "1", From ef4e2a0b44495ae4eba0e601e52cfdeb340c9f03 Mon Sep 17 00:00:00 2001 From: Drew Tate Date: Wed, 24 Jan 2024 12:50:21 -0700 Subject: [PATCH 28/28] add FT --- .../kbn_archiver/managed_content.json | 4 ++++ .../apps/managed_content/managed_content.ts | 20 ++++++++++++++++++- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/test/functional/fixtures/kbn_archiver/managed_content.json b/test/functional/fixtures/kbn_archiver/managed_content.json index b530dfd3f2352..9cea153812a52 100644 --- a/test/functional/fixtures/kbn_archiver/managed_content.json +++ b/test/functional/fixtures/kbn_archiver/managed_content.json @@ -7,3 +7,7 @@ {"attributes":{"columns":["@tags","clientip"],"description":"","grid":{},"hideChart":false,"isTextBasedQuery":false,"kibanaSavedObjectMeta":{"searchSourceJSON":"{\"query\":{\"query\":\"agent.raw:\\\"Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.1.4322)\\\" \",\"language\":\"kuery\"},\"filter\":[],\"indexRefName\":\"kibanaSavedObjectMeta.searchSourceJSON.index\"}"},"sort":[["@timestamp","desc"]],"timeRestore":false,"title":"Saved search","usesAdHocDataView":false},"coreMigrationVersion":"8.8.0","created_at":"2024-01-22T18:11:05.016Z","id":"managed-3d62-4113-ac7c-de2e20a68fbc","managed":true,"references":[{"id":"5f863f70-4728-4e8d-b441-db08f8c33b28","name":"kibanaSavedObjectMeta.searchSourceJSON.index","type":"index-pattern"}],"type":"search","typeMigrationVersion":"8.0.0","updated_at":"2024-01-22T18:11:05.016Z","version":"WzIzLDFd"} {"attributes":{"columns":["@tags","clientip"],"description":"","grid":{},"hideChart":false,"isTextBasedQuery":false,"kibanaSavedObjectMeta":{"searchSourceJSON":"{\"query\":{\"query\":\"agent.raw:\\\"Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.1.4322)\\\" \",\"language\":\"kuery\"},\"filter\":[],\"indexRefName\":\"kibanaSavedObjectMeta.searchSourceJSON.index\"}"},"sort":[["@timestamp","desc"]],"timeRestore":false,"title":"Saved search","usesAdHocDataView":false},"coreMigrationVersion":"8.8.0","created_at":"2024-01-22T18:11:05.016Z","id":"unmanaged-3d62-4113-ac7c-de2e20a68fbc","managed":false,"references":[{"id":"5f863f70-4728-4e8d-b441-db08f8c33b28","name":"kibanaSavedObjectMeta.searchSourceJSON.index","type":"index-pattern"}],"type":"search","typeMigrationVersion":"8.0.0","updated_at":"2024-01-22T18:11:05.016Z","version":"WzIzLDFd"} + +{"attributes":{"description":"","kibanaSavedObjectMeta":{"searchSourceJSON":"{\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filter\":[]}"},"title":"Legacy visualization","uiStateJSON":"{}","version":1,"visState":"{\"title\":\"Legacy visualization\",\"type\":\"metrics\",\"aggs\":[],\"params\":{\"id\":\"1a14d0ad-0d74-4470-a189-8f040cddc1a1\",\"type\":\"timeseries\",\"series\":[{\"id\":\"daa8bbf7-86cc-4394-b249-be48da9f7351\",\"color\":\"#68BC00\",\"split_mode\":\"everything\",\"palette\":{\"type\":\"palette\",\"name\":\"default\"},\"metrics\":[{\"id\":\"795375d9-1aa6-454d-9b23-687e69f3382c\",\"type\":\"count\"}],\"separate_axis\":0,\"axis_position\":\"right\",\"formatter\":\"default\",\"chart_type\":\"line\",\"line_width\":1,\"point_size\":1,\"fill\":0.5,\"stacked\":\"none\",\"override_index_pattern\":0,\"series_drop_last_bucket\":0}],\"time_field\":\"\",\"use_kibana_indexes\":true,\"interval\":\"\",\"axis_position\":\"left\",\"axis_formatter\":\"number\",\"axis_scale\":\"normal\",\"show_legend\":1,\"truncate_legend\":1,\"max_lines_legend\":1,\"show_grid\":1,\"tooltip_mode\":\"show_all\",\"drop_last_bucket\":0,\"index_pattern_ref_name\":\"metrics_0_index_pattern\"}}"},"coreMigrationVersion":"8.8.0","created_at":"2024-01-24T18:53:06.818Z","id":"managed-feb9-4ba6-9538-1b8f67fb4f57","managed":true,"references":[{"id":"5f863f70-4728-4e8d-b441-db08f8c33b28","name":"metrics_0_index_pattern","type":"index-pattern"}],"type":"visualization","typeMigrationVersion":"8.5.0","updated_at":"2024-01-24T18:53:06.818Z","version":"WzEzLDFd"} + +{"attributes":{"description":"","kibanaSavedObjectMeta":{"searchSourceJSON":"{\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filter\":[]}"},"title":"Legacy visualization","uiStateJSON":"{}","version":1,"visState":"{\"title\":\"Legacy visualization\",\"type\":\"metrics\",\"aggs\":[],\"params\":{\"id\":\"1a14d0ad-0d74-4470-a189-8f040cddc1a1\",\"type\":\"timeseries\",\"series\":[{\"id\":\"daa8bbf7-86cc-4394-b249-be48da9f7351\",\"color\":\"#68BC00\",\"split_mode\":\"everything\",\"palette\":{\"type\":\"palette\",\"name\":\"default\"},\"metrics\":[{\"id\":\"795375d9-1aa6-454d-9b23-687e69f3382c\",\"type\":\"count\"}],\"separate_axis\":0,\"axis_position\":\"right\",\"formatter\":\"default\",\"chart_type\":\"line\",\"line_width\":1,\"point_size\":1,\"fill\":0.5,\"stacked\":\"none\",\"override_index_pattern\":0,\"series_drop_last_bucket\":0}],\"time_field\":\"\",\"use_kibana_indexes\":true,\"interval\":\"\",\"axis_position\":\"left\",\"axis_formatter\":\"number\",\"axis_scale\":\"normal\",\"show_legend\":1,\"truncate_legend\":1,\"max_lines_legend\":1,\"show_grid\":1,\"tooltip_mode\":\"show_all\",\"drop_last_bucket\":0,\"index_pattern_ref_name\":\"metrics_0_index_pattern\"}}"},"coreMigrationVersion":"8.8.0","created_at":"2024-01-24T18:53:06.818Z","id":"unmanaged-feb9-4ba6-9538-1b8f67fb4f57","managed":false,"references":[{"id":"5f863f70-4728-4e8d-b441-db08f8c33b28","name":"metrics_0_index_pattern","type":"index-pattern"}],"type":"visualization","typeMigrationVersion":"8.5.0","updated_at":"2024-01-24T18:53:06.818Z","version":"WzEzLDFd"} \ No newline at end of file diff --git a/x-pack/test/functional/apps/managed_content/managed_content.ts b/x-pack/test/functional/apps/managed_content/managed_content.ts index 2ba7d1ce0b4ed..5d5364f9bad97 100644 --- a/x-pack/test/functional/apps/managed_content/managed_content.ts +++ b/x-pack/test/functional/apps/managed_content/managed_content.ts @@ -9,7 +9,7 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ getPageObjects, getService }: FtrProviderContext) { - const PageObjects = getPageObjects(['timePicker', 'lens', 'common', 'discover']); + const PageObjects = getPageObjects(['visChart', 'timePicker', 'lens', 'common', 'discover']); const kibanaServer = getService('kibanaServer'); const esArchiver = getService('esArchiver'); const testSubjects = getService('testSubjects'); @@ -76,5 +76,23 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { await expectManagedContentSignifiers(false, 'discoverSaveButton'); }); + + it('visualize', async () => { + await PageObjects.common.navigateToActualUrl( + 'visualize', + 'edit/managed-feb9-4ba6-9538-1b8f67fb4f57' + ); + await PageObjects.visChart.waitForVisualization(); + + await expectManagedContentSignifiers(true, 'visualizeSaveButton'); + + await PageObjects.common.navigateToActualUrl( + 'visualize', + 'edit/unmanaged-feb9-4ba6-9538-1b8f67fb4f57' + ); + await PageObjects.visChart.waitForVisualization(); + + await expectManagedContentSignifiers(false, 'visualizeSaveButton'); + }); }); }