mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[UnifiedSearch] Allow editing ad-hoc data views without permissions (#142723)
* allow editing ad-hoc data views without permissions * [CI] Auto-commit changed files from 'node scripts/build_plugin_list_docs' * fxi tests * fix test * allow field editing from discover table Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Stratoula Kalafateli <efstratia.kalafateli@elastic.co>
This commit is contained in:
parent
41b28155d4
commit
0a01a92368
11 changed files with 118 additions and 145 deletions
|
@ -99,6 +99,8 @@ const IndexPatternEditorFlyoutContentComponent = ({
|
|||
services: { application, http, dataViews, uiSettings, overlays },
|
||||
} = useKibana<DataViewEditorContext>();
|
||||
|
||||
const canSave = dataViews.getCanSaveSync();
|
||||
|
||||
const { form } = useForm<IndexPatternConfig, FormInternal>({
|
||||
// Prefill with data if editData exists
|
||||
defaultValue: {
|
||||
|
@ -447,6 +449,7 @@ const IndexPatternEditorFlyoutContentComponent = ({
|
|||
isEdit={!!editData}
|
||||
isPersisted={Boolean(editData && editData.isPersisted())}
|
||||
allowAdHoc={allowAdHoc}
|
||||
canSave={canSave}
|
||||
/>
|
||||
</FlyoutPanels.Item>
|
||||
<FlyoutPanels.Item>
|
||||
|
|
|
@ -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 = ({
|
|||
</EuiFlexItem>
|
||||
)}
|
||||
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton
|
||||
color="primary"
|
||||
onClick={submitPersisted}
|
||||
data-test-subj="saveIndexPatternButton"
|
||||
fill
|
||||
disabled={submitDisabled}
|
||||
>
|
||||
{isEdit
|
||||
? isPersisted
|
||||
? editButtonLabel
|
||||
: editUnpersistedButtonLabel
|
||||
: saveButtonLabel}
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
{(canSave || (isEdit && !isPersisted)) && (
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton
|
||||
color="primary"
|
||||
onClick={submitPersisted}
|
||||
data-test-subj="saveIndexPatternButton"
|
||||
fill
|
||||
disabled={submitDisabled}
|
||||
>
|
||||
{isEdit
|
||||
? isPersisted
|
||||
? editButtonLabel
|
||||
: editUnpersistedButtonLabel
|
||||
: saveButtonLabel}
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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) => {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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),
|
||||
},
|
||||
|
|
|
@ -190,31 +190,35 @@ export function ChangeDataView({
|
|||
defaultMessage: 'Add a field to this data view',
|
||||
})}
|
||||
</EuiContextMenuItem>,
|
||||
<EuiContextMenuItem
|
||||
key="manage"
|
||||
icon="indexSettings"
|
||||
data-test-subj="indexPattern-manage-field"
|
||||
onClick={async () => {
|
||||
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',
|
||||
})}
|
||||
</EuiContextMenuItem>,
|
||||
onEditDataView || dataViewEditor.userPermissions.editDataView() ? (
|
||||
<EuiContextMenuItem
|
||||
key="manage"
|
||||
icon="indexSettings"
|
||||
data-test-subj="indexPattern-manage-field"
|
||||
onClick={async () => {
|
||||
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',
|
||||
})}
|
||||
</EuiContextMenuItem>
|
||||
) : (
|
||||
<React.Fragment />
|
||||
),
|
||||
<EuiHorizontalRule margin="none" />
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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(() => []),
|
||||
},
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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) => {
|
||||
|
|
|
@ -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) => {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue