diff --git a/src/plugins/data_view_editor/public/components/data_view_editor_flyout_content.tsx b/src/plugins/data_view_editor/public/components/data_view_editor_flyout_content.tsx index 37a5b2b1efca9..510719d6c266e 100644 --- a/src/plugins/data_view_editor/public/components/data_view_editor_flyout_content.tsx +++ b/src/plugins/data_view_editor/public/components/data_view_editor_flyout_content.tsx @@ -99,6 +99,8 @@ const IndexPatternEditorFlyoutContentComponent = ({ services: { application, http, dataViews, uiSettings, overlays }, } = useKibana(); + const canSave = dataViews.getCanSaveSync(); + const { form } = useForm({ // Prefill with data if editData exists defaultValue: { @@ -447,6 +449,7 @@ const IndexPatternEditorFlyoutContentComponent = ({ isEdit={!!editData} isPersisted={Boolean(editData && editData.isPersisted())} allowAdHoc={allowAdHoc} + canSave={canSave} /> diff --git a/src/plugins/data_view_editor/public/components/footer/footer.tsx b/src/plugins/data_view_editor/public/components/footer/footer.tsx index f832173b7156e..024885e91d548 100644 --- a/src/plugins/data_view_editor/public/components/footer/footer.tsx +++ b/src/plugins/data_view_editor/public/components/footer/footer.tsx @@ -24,6 +24,7 @@ interface FooterProps { isEdit: boolean; isPersisted: boolean; allowAdHoc: boolean; + canSave: boolean; } const closeButtonLabel = i18n.translate('indexPatternEditor.editor.flyoutCloseButtonLabel', { @@ -56,6 +57,7 @@ export const Footer = ({ isEdit, allowAdHoc, isPersisted, + canSave, }: FooterProps) => { const submitPersisted = () => { onSubmit(false); @@ -96,21 +98,23 @@ export const Footer = ({ )} - - - {isEdit - ? isPersisted - ? editButtonLabel - : editUnpersistedButtonLabel - : saveButtonLabel} - - + {(canSave || (isEdit && !isPersisted)) && ( + + + {isEdit + ? isPersisted + ? editButtonLabel + : editUnpersistedButtonLabel + : saveButtonLabel} + + + )} diff --git a/src/plugins/discover/public/application/main/components/sidebar/discover_sidebar_responsive.tsx b/src/plugins/discover/public/application/main/components/sidebar/discover_sidebar_responsive.tsx index de3b30810859f..274cb85cc3535 100644 --- a/src/plugins/discover/public/application/main/components/sidebar/discover_sidebar_responsive.tsx +++ b/src/plugins/discover/public/application/main/components/sidebar/discover_sidebar_responsive.tsx @@ -182,7 +182,8 @@ export function DiscoverSidebarResponsive(props: DiscoverSidebarResponsiveProps) const { dataViewFieldEditor, dataViewEditor } = services; const { availableFields$ } = props; - const canEditDataView = Boolean(dataViewEditor?.userPermissions.editDataView()); + const canEditDataView = + Boolean(dataViewEditor?.userPermissions.editDataView()) || !selectedDataView?.isPersisted(); useEffect( () => { @@ -241,25 +242,19 @@ export function DiscoverSidebarResponsive(props: DiscoverSidebarResponsiveProps) ] ); - const createNewDataView = useMemo( - () => - canEditDataView - ? () => { - const ref = dataViewEditor.openEditor({ - onSave: async (dataView) => { - onDataViewCreated(dataView); - }, - }); - if (setDataViewEditorRef) { - setDataViewEditorRef(ref); - } - if (closeFlyout) { - closeFlyout(); - } - } - : undefined, - [canEditDataView, dataViewEditor, setDataViewEditorRef, closeFlyout, onDataViewCreated] - ); + const createNewDataView = useCallback(() => { + const ref = dataViewEditor.openEditor({ + onSave: async (dataView) => { + onDataViewCreated(dataView); + }, + }); + if (setDataViewEditorRef) { + setDataViewEditorRef(ref); + } + if (closeFlyout) { + closeFlyout(); + } + }, [dataViewEditor, setDataViewEditorRef, closeFlyout, onDataViewCreated]); if (!selectedDataView) { return null; diff --git a/src/plugins/discover/public/application/main/components/top_nav/discover_topnav.tsx b/src/plugins/discover/public/application/main/components/top_nav/discover_topnav.tsx index 82f3b1aadadbf..66f06169256ab 100644 --- a/src/plugins/discover/public/application/main/components/top_nav/discover_topnav.tsx +++ b/src/plugins/discover/public/application/main/components/top_nav/discover_topnav.tsx @@ -68,7 +68,8 @@ export const DiscoverTopNav = ({ const services = useDiscoverServices(); const { dataViewEditor, navigation, dataViewFieldEditor, data, uiSettings, dataViews } = services; - const canEditDataView = Boolean(dataViewEditor?.userPermissions.editDataView()); + const canEditDataView = + Boolean(dataViewEditor?.userPermissions.editDataView()) || !dataView.isPersisted(); const closeFieldEditor = useRef<() => void | undefined>(); const closeDataViewEditor = useRef<() => void | undefined>(); @@ -124,22 +125,16 @@ export const DiscoverTopNav = ({ [editField, canEditDataView] ); - const createNewDataView = useMemo( - () => - canEditDataView - ? () => { - closeDataViewEditor.current = dataViewEditor.openEditor({ - onSave: async (dataViewToSave) => { - if (dataViewToSave.id) { - onChangeDataView(dataViewToSave.id); - } - }, - allowAdHocDataView: true, - }); - } - : undefined, - [canEditDataView, dataViewEditor, onChangeDataView] - ); + const createNewDataView = useCallback(() => { + closeDataViewEditor.current = dataViewEditor.openEditor({ + onSave: async (dataViewToSave) => { + if (dataViewToSave.id) { + onChangeDataView(dataViewToSave.id); + } + }, + allowAdHocDataView: true, + }); + }, [dataViewEditor, onChangeDataView]); const onCreateDefaultAdHocDataView = useCallback( async (pattern: string) => { diff --git a/src/plugins/discover/public/components/discover_grid/build_edit_field_button.tsx b/src/plugins/discover/public/components/discover_grid/build_edit_field_button.tsx index 3d7ee2c52e37c..1d9eab7e1d8e4 100644 --- a/src/plugins/discover/public/components/discover_grid/build_edit_field_button.tsx +++ b/src/plugins/discover/public/components/discover_grid/build_edit_field_button.tsx @@ -29,7 +29,8 @@ export const buildEditFieldButton = ({ } const { canEdit: canEditField } = getFieldCapabilities(dataView, field); - const canEditDataView = Boolean(services.dataViewEditor?.userPermissions?.editDataView()); + const canEditDataView = + Boolean(services.dataViewEditor?.userPermissions?.editDataView()) || !dataView.isPersisted(); if (!canEditField || !canEditDataView) { return null; diff --git a/src/plugins/unified_search/public/dataview_picker/change_dataview.test.tsx b/src/plugins/unified_search/public/dataview_picker/change_dataview.test.tsx index 8497b599650b2..362ff4a209164 100644 --- a/src/plugins/unified_search/public/dataview_picker/change_dataview.test.tsx +++ b/src/plugins/unified_search/public/dataview_picker/change_dataview.test.tsx @@ -13,6 +13,7 @@ import { mountWithIntl as mount } from '@kbn/test-jest-helpers'; import { findTestSubject } from '@elastic/eui/lib/test'; import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; import { dataPluginMock } from '@kbn/data-plugin/public/mocks'; +import { indexPatternEditorPluginMock as dataViewEditorPluginMock } from '@kbn/data-view-editor-plugin/public/mocks'; import { ChangeDataView } from './change_dataview'; import { DataViewPickerPropsExtended, TextBasedLanguages } from '.'; @@ -44,6 +45,8 @@ describe('DataView component', () => { storageValue: boolean, uiSettingValue: boolean = false ) { + const dataViewEditorMock = dataViewEditorPluginMock.createStartContract(); + (dataViewEditorMock.userPermissions.editDataView as jest.Mock).mockReturnValue(true); let dataMock = dataPluginMock.createStartContract(); dataMock = { ...dataMock, @@ -56,6 +59,7 @@ describe('DataView component', () => { const services = { data: dataMock, storage: getStorage(storageValue), + dataViewEditor: dataViewEditorMock, uiSettings: { get: jest.fn(() => uiSettingValue), }, diff --git a/src/plugins/unified_search/public/dataview_picker/change_dataview.tsx b/src/plugins/unified_search/public/dataview_picker/change_dataview.tsx index c4be51c5c80ee..1e71da3a0b20a 100644 --- a/src/plugins/unified_search/public/dataview_picker/change_dataview.tsx +++ b/src/plugins/unified_search/public/dataview_picker/change_dataview.tsx @@ -190,31 +190,35 @@ export function ChangeDataView({ defaultMessage: 'Add a field to this data view', })} , - { - if (onEditDataView) { - const dataView = await dataViews.get(currentDataViewId!); - dataViewEditor.openEditor({ - editData: dataView, - onSave: (updatedDataView) => { - onEditDataView(updatedDataView); - }, - }); - } else { - application.navigateToApp('management', { - path: `/kibana/indexPatterns/patterns/${currentDataViewId}`, - }); - } - setPopoverIsOpen(false); - }} - > - {i18n.translate('unifiedSearch.query.queryBar.indexPattern.manageFieldButton', { - defaultMessage: 'Manage this data view', - })} - , + onEditDataView || dataViewEditor.userPermissions.editDataView() ? ( + { + if (onEditDataView) { + const dataView = await dataViews.get(currentDataViewId!); + dataViewEditor.openEditor({ + editData: dataView, + onSave: (updatedDataView) => { + onEditDataView(updatedDataView); + }, + }); + } else { + application.navigateToApp('management', { + path: `/kibana/indexPatterns/patterns/${currentDataViewId}`, + }); + } + setPopoverIsOpen(false); + }} + > + {i18n.translate('unifiedSearch.query.queryBar.indexPattern.manageFieldButton', { + defaultMessage: 'Manage this data view', + })} + + ) : ( + + ), ); } diff --git a/src/plugins/unified_search/public/search_bar/search_bar.test.tsx b/src/plugins/unified_search/public/search_bar/search_bar.test.tsx index b47b2d81b780c..518f9d1f16e16 100644 --- a/src/plugins/unified_search/public/search_bar/search_bar.test.tsx +++ b/src/plugins/unified_search/public/search_bar/search_bar.test.tsx @@ -10,6 +10,7 @@ import React from 'react'; import SearchBar from './search_bar'; import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; +import { indexPatternEditorPluginMock as dataViewEditorPluginMock } from '@kbn/data-view-editor-plugin/public/mocks'; import { I18nProvider } from '@kbn/i18n-react'; import { coreMock } from '@kbn/core/public/mocks'; @@ -83,6 +84,9 @@ function wrapSearchBarInContext(testProps: any) { intl: null as any, }; + const dataViewEditorMock = dataViewEditorPluginMock.createStartContract(); + (dataViewEditorMock.userPermissions.editDataView as jest.Mock).mockReturnValue(true); + const services = { uiSettings: startMock.uiSettings, savedObjects: startMock.savedObjects, @@ -111,6 +115,7 @@ function wrapSearchBarInContext(testProps: any) { }), }, }, + dataViewEditor: dataViewEditorMock, dataViews: { getIdsWithTitle: jest.fn(() => []), }, diff --git a/x-pack/plugins/lens/public/app_plugin/app.test.tsx b/x-pack/plugins/lens/public/app_plugin/app.test.tsx index 58e65b3b79bbe..648fd61203943 100644 --- a/x-pack/plugins/lens/public/app_plugin/app.test.tsx +++ b/x-pack/plugins/lens/public/app_plugin/app.test.tsx @@ -405,39 +405,6 @@ describe('Lens App', () => { }); describe('TopNavMenu#dataViewPickerProps', () => { - it('calls the nav component with the correct dataview picker props if no permissions are given', async () => { - const { instance, lensStore } = await mountWith({ preloadedState: {} }); - const document = { - savedObjectId: defaultSavedObjectId, - state: { - query: 'fake query', - filters: [{ query: { match_phrase: { src: 'test' } } }], - }, - references: [{ type: 'index-pattern', id: '1', name: 'index-pattern-0' }], - } as unknown as Document; - - act(() => { - lensStore.dispatch( - setState({ - query: 'fake query' as unknown as Query, - persistedDoc: document, - }) - ); - }); - instance.update(); - const props = instance - .find('[data-test-subj="lnsApp_topNav"]') - .prop('dataViewPickerComponentProps') as TopNavMenuData[]; - expect(props).toEqual( - expect.objectContaining({ - currentDataViewId: 'mockip', - onChangeDataView: expect.any(Function), - onDataViewCreated: undefined, - onAddField: undefined, - }) - ); - }); - it('calls the nav component with the correct dataview picker props if permissions are given', async () => { const { instance, lensStore, services } = await mountWith({ preloadedState: {} }); services.dataViewEditor.userPermissions.editDataView = () => true; 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 80c1384a1020c..4176e5f1e51a7 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 @@ -288,7 +288,8 @@ export const LensTopNavMenu = ({ ] ); - const canEditDataView = Boolean(dataViewEditor?.userPermissions.editDataView()); + const canEditDataView = + Boolean(dataViewEditor?.userPermissions.editDataView()) || !currentIndexPattern?.isPersisted(); const closeFieldEditor = useRef<() => void | undefined>(); const closeDataViewEditor = useRef<() => void | undefined>(); @@ -756,39 +757,32 @@ export const LensTopNavMenu = ({ [editField, canEditDataView] ); - const createNewDataView = useMemo( - () => - canEditDataView - ? () => { - closeDataViewEditor.current = dataViewEditor.openEditor({ - onSave: async (dataView) => { - if (dataView.id) { - if (isOnTextBasedMode) { - dispatch( - switchAndCleanDatasource({ - newDatasourceId: 'indexpattern', - visualizationId: visualization?.activeId, - currentIndexPatternId: dataView?.id, - }) - ); - } - dispatchChangeIndexPattern(dataView); - setCurrentIndexPattern(dataView); - } - }, - allowAdHocDataView: true, - }); + const createNewDataView = useCallback(() => { + closeDataViewEditor.current = dataViewEditor.openEditor({ + onSave: async (dataView) => { + if (dataView.id) { + if (isOnTextBasedMode) { + dispatch( + switchAndCleanDatasource({ + newDatasourceId: 'indexpattern', + visualizationId: visualization?.activeId, + currentIndexPatternId: dataView?.id, + }) + ); } - : undefined, - [ - canEditDataView, - dataViewEditor, - dispatch, - dispatchChangeIndexPattern, - isOnTextBasedMode, - visualization?.activeId, - ] - ); + dispatchChangeIndexPattern(dataView); + setCurrentIndexPattern(dataView); + } + }, + allowAdHocDataView: true, + }); + }, [ + dataViewEditor, + dispatch, + dispatchChangeIndexPattern, + isOnTextBasedMode, + visualization?.activeId, + ]); const onCreateDefaultAdHocDataView = useCallback( async (pattern: string) => { diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/datapanel.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/datapanel.tsx index 1ce184e623e7a..4107cd0c14fa5 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/datapanel.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/datapanel.tsx @@ -329,7 +329,8 @@ export const InnerIndexPatternDataPanel = function InnerIndexPatternDataPanel({ const fieldInfoUnavailable = existenceFetchFailed || existenceFetchTimeout || currentIndexPattern?.hasRestrictions; - const editPermission = indexPatternFieldEditor.userPermissions.editIndexPattern(); + const editPermission = + indexPatternFieldEditor.userPermissions.editIndexPattern() || !currentIndexPattern.isPersisted; const unfilteredFieldGroups: FieldGroups = useMemo(() => { const containsData = (field: IndexPatternField) => {