From dd2c210db2d4230bfacd102dce7d9adf573527a2 Mon Sep 17 00:00:00 2001 From: Drew Tate Date: Wed, 31 Jan 2024 07:32:55 -0700 Subject: [PATCH] [Visualize] Prevent overwriting managed content (#175274) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Close https://github.com/elastic/kibana/issues/172380 I marked this a breaking change since it is preventing users from doing something they have been able to do before. They can no longer save changes to managed visualizations. Instead, they have to save changes to a new visualization. This is how the UI should look for the managed visualization. Screenshot 2024-01-24 at 12 57 53 PM ### Checklist Delete any items that are not applicable to this PR. - [x] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md) - [ ] [Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html) was added for features that require explanation or tutorials — going to happen in https://github.com/elastic/kibana/issues/175150 - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios - [x] [Flaky Test Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was used on any tests changed — https://buildkite.com/elastic/kibana-flaky-test-suite-runner/builds/4967 - [x] Any UI touched in this PR is usable by keyboard only (learn more about [keyboard accessibility](https://webaim.org/techniques/keyboard/)) --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Stratoula Kalafateli --- .../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_top_nav_config.tsx | 8 ++++++ .../utils/get_visualization_instance.ts | 4 +-- src/plugins/visualizations/tsconfig.json | 1 + .../kbn_archiver/managed_content.json | 4 +++ .../apps/managed_content/managed_content.ts | 27 ++++++++++++++++++- 9 files changed, 63 insertions(+), 4 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_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 + } /> ); } 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( diff --git a/src/plugins/visualizations/tsconfig.json b/src/plugins/visualizations/tsconfig.json index 5b1a718d551c8..adeaf7ae280c2 100644 --- a/src/plugins/visualizations/tsconfig.json +++ b/src/plugins/visualizations/tsconfig.json @@ -68,6 +68,7 @@ "@kbn/content-management-table-list-view-common", "@kbn/chart-expressions-common", "@kbn/shared-ux-utility", + "@kbn/managed-content-badge", "@kbn/presentation-publishing" ], "exclude": [ diff --git a/test/functional/fixtures/kbn_archiver/managed_content.json b/test/functional/fixtures/kbn_archiver/managed_content.json index 3bd33217c4e58..9e46c515d6b8b 100644 --- a/test/functional/fixtures/kbn_archiver/managed_content.json +++ b/test/functional/fixtures/kbn_archiver/managed_content.json @@ -8,6 +8,10 @@ {"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"} + {"attributes":{"description":"","layerListJSON":"[{\"locale\":\"autoselect\",\"sourceDescriptor\":{\"type\":\"EMS_TMS\",\"isAutoSelect\":true,\"lightModeDefault\":\"road_map_desaturated\"},\"id\":\"5ff9c98e-e0d3-4aff-ac98-b33c191496b4\",\"label\":null,\"minZoom\":0,\"maxZoom\":24,\"alpha\":1,\"visible\":true,\"style\":{\"type\":\"EMS_VECTOR_TILE\",\"color\":\"\"},\"includeInFitToBounds\":true,\"type\":\"EMS_VECTOR_TILE\"}]","mapStateJSON":"{\"adHocDataViews\":[],\"zoom\":1.4,\"center\":{\"lon\":0,\"lat\":19.94277},\"timeFilters\":{\"from\":\"now-15m\",\"to\":\"now\"},\"refreshConfig\":{\"isPaused\":true,\"interval\":60000},\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filters\":[],\"settings\":{\"autoFitToDataBounds\":false,\"backgroundColor\":\"#ffffff\",\"customIcons\":[],\"disableInteractive\":false,\"disableTooltipControl\":false,\"hideToolbarOverlay\":false,\"hideLayerControl\":false,\"hideViewControl\":false,\"initialLocation\":\"LAST_SAVED_LOCATION\",\"fixedLocation\":{\"lat\":0,\"lon\":0,\"zoom\":2},\"browserLocation\":{\"zoom\":2},\"keydownScrollZoom\":false,\"maxZoom\":24,\"minZoom\":0,\"showScaleControl\":false,\"showSpatialFilters\":true,\"showTimesliderToggleButton\":true,\"spatialFiltersAlpa\":0.3,\"spatialFiltersFillColor\":\"#DA8B45\",\"spatialFiltersLineColor\":\"#DA8B45\"}}","title":"Map","uiStateJSON":"{\"isLayerTOCOpen\":true,\"openTOCDetails\":[]}"},"coreMigrationVersion":"8.8.0","created_at":"2024-01-24T18:22:40.360Z","id":"managed-d7ab-46eb-a807-8fed28ed8566","managed":true,"references":[],"type":"map","typeMigrationVersion":"8.4.0","updated_at":"2024-01-24T18:23:07.090Z","version":"WzEyLDFd"} {"attributes":{"description":"","layerListJSON":"[{\"locale\":\"autoselect\",\"sourceDescriptor\":{\"type\":\"EMS_TMS\",\"isAutoSelect\":true,\"lightModeDefault\":\"road_map_desaturated\"},\"id\":\"5ff9c98e-e0d3-4aff-ac98-b33c191496b4\",\"label\":null,\"minZoom\":0,\"maxZoom\":24,\"alpha\":1,\"visible\":true,\"style\":{\"type\":\"EMS_VECTOR_TILE\",\"color\":\"\"},\"includeInFitToBounds\":true,\"type\":\"EMS_VECTOR_TILE\"}]","mapStateJSON":"{\"adHocDataViews\":[],\"zoom\":1.4,\"center\":{\"lon\":0,\"lat\":19.94277},\"timeFilters\":{\"from\":\"now-15m\",\"to\":\"now\"},\"refreshConfig\":{\"isPaused\":true,\"interval\":60000},\"query\":{\"query\":\"\",\"language\":\"kuery\"},\"filters\":[],\"settings\":{\"autoFitToDataBounds\":false,\"backgroundColor\":\"#ffffff\",\"customIcons\":[],\"disableInteractive\":false,\"disableTooltipControl\":false,\"hideToolbarOverlay\":false,\"hideLayerControl\":false,\"hideViewControl\":false,\"initialLocation\":\"LAST_SAVED_LOCATION\",\"fixedLocation\":{\"lat\":0,\"lon\":0,\"zoom\":2},\"browserLocation\":{\"zoom\":2},\"keydownScrollZoom\":false,\"maxZoom\":24,\"minZoom\":0,\"showScaleControl\":false,\"showSpatialFilters\":true,\"showTimesliderToggleButton\":true,\"spatialFiltersAlpa\":0.3,\"spatialFiltersFillColor\":\"#DA8B45\",\"spatialFiltersLineColor\":\"#DA8B45\"}}","title":"Map","uiStateJSON":"{\"isLayerTOCOpen\":true,\"openTOCDetails\":[]}"},"coreMigrationVersion":"8.8.0","created_at":"2024-01-24T18:22:40.360Z","id":"unmanaged-d7ab-46eb-a807-8fed28ed8566","managed":false,"references":[],"type":"map","typeMigrationVersion":"8.4.0","updated_at":"2024-01-24T18:23:07.090Z","version":"WzEyLDFd"} 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 05d30c248f0ea..a42d5b5a0c94c 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,14 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ getPageObjects, getService }: FtrProviderContext) { - const PageObjects = getPageObjects(['timePicker', 'lens', 'common', 'discover', 'maps']); + const PageObjects = getPageObjects([ + 'visChart', + 'timePicker', + 'lens', + 'common', + 'discover', + 'maps', + ]); const kibanaServer = getService('kibanaServer'); const esArchiver = getService('esArchiver'); const testSubjects = getService('testSubjects'); @@ -77,6 +84,24 @@ 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'); + }); + it('maps', async () => { await PageObjects.common.navigateToActualUrl( 'maps',