[data view management] Fix set default data view permissions check (#124897)

* fix set default data view permissions

* fix fields table

* fix scripted field add button

* fix jest tests

* lint fixes

* fix test

* updte snapshot
This commit is contained in:
Matthew Kime 2022-02-09 10:32:20 -06:00 committed by GitHub
parent 67cd496daa
commit ded0478ed7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 72 additions and 57 deletions

View file

@ -70,7 +70,11 @@ export const CreateEditField = withRouter(
if (spec) { if (spec) {
return ( return (
<> <>
<IndexHeader indexPattern={indexPattern} defaultIndex={uiSettings.get('defaultIndex')} /> <IndexHeader
indexPattern={indexPattern}
defaultIndex={uiSettings.get('defaultIndex')}
canSave={dataViews.getCanSaveSync()}
/>
<EuiSpacer size={'l'} /> <EuiSpacer size={'l'} />
<FieldEditor <FieldEditor
indexPattern={indexPattern} indexPattern={indexPattern}

View file

@ -65,7 +65,7 @@ const securitySolution = 'security-solution';
export const EditIndexPattern = withRouter( export const EditIndexPattern = withRouter(
({ indexPattern, history, location }: EditIndexPatternProps) => { ({ indexPattern, history, location }: EditIndexPatternProps) => {
const { application, uiSettings, overlays, chrome, dataViews } = const { uiSettings, overlays, chrome, dataViews } =
useKibana<IndexPatternManagmentContext>().services; useKibana<IndexPatternManagmentContext>().services;
const [fields, setFields] = useState<DataViewField[]>(indexPattern.getNonScriptedFields()); const [fields, setFields] = useState<DataViewField[]>(indexPattern.getNonScriptedFields());
const [conflictedFields, setConflictedFields] = useState<DataViewField[]>( const [conflictedFields, setConflictedFields] = useState<DataViewField[]>(
@ -143,15 +143,16 @@ export const EditIndexPattern = withRouter(
const showTagsSection = Boolean(indexPattern.timeFieldName || (tags && tags.length > 0)); const showTagsSection = Boolean(indexPattern.timeFieldName || (tags && tags.length > 0));
const kibana = useKibana(); const kibana = useKibana();
const docsUrl = kibana.services.docLinks!.links.elasticsearch.mapping; const docsUrl = kibana.services.docLinks!.links.elasticsearch.mapping;
const userEditPermission = !!application?.capabilities?.indexPatterns?.save; const userEditPermission = dataViews.getCanSaveSync();
return ( return (
<div data-test-subj="editIndexPattern" role="region" aria-label={headingAriaLabel}> <div data-test-subj="editIndexPattern" role="region" aria-label={headingAriaLabel}>
<IndexHeader <IndexHeader
indexPattern={indexPattern} indexPattern={indexPattern}
setDefault={setDefaultPattern} setDefault={setDefaultPattern}
{...(userEditPermission ? { deleteIndexPatternClick: removePattern } : {})} deleteIndexPatternClick={removePattern}
defaultIndex={defaultIndex} defaultIndex={defaultIndex}
canSave={userEditPermission}
> >
{showTagsSection && ( {showTagsSection && (
<EuiFlexGroup wrap gutterSize="s"> <EuiFlexGroup wrap gutterSize="s">

View file

@ -16,6 +16,7 @@ interface IndexHeaderProps {
defaultIndex?: string; defaultIndex?: string;
setDefault?: () => void; setDefault?: () => void;
deleteIndexPatternClick?: () => void; deleteIndexPatternClick?: () => void;
canSave: boolean;
} }
const setDefaultAriaLabel = i18n.translate('indexPatternManagement.editDataView.setDefaultAria', { const setDefaultAriaLabel = i18n.translate('indexPatternManagement.editDataView.setDefaultAria', {
@ -40,12 +41,13 @@ export const IndexHeader: React.FC<IndexHeaderProps> = ({
setDefault, setDefault,
deleteIndexPatternClick, deleteIndexPatternClick,
children, children,
canSave,
}) => { }) => {
return ( return (
<EuiPageHeader <EuiPageHeader
pageTitle={<span data-test-subj="indexPatternTitle">{indexPattern.title}</span>} pageTitle={<span data-test-subj="indexPatternTitle">{indexPattern.title}</span>}
rightSideItems={[ rightSideItems={[
defaultIndex !== indexPattern.id && setDefault && ( defaultIndex !== indexPattern.id && setDefault && canSave && (
<EuiToolTip content={setDefaultTooltip}> <EuiToolTip content={setDefaultTooltip}>
<EuiButtonIcon <EuiButtonIcon
color="text" color="text"
@ -56,7 +58,7 @@ export const IndexHeader: React.FC<IndexHeaderProps> = ({
/> />
</EuiToolTip> </EuiToolTip>
), ),
deleteIndexPatternClick && ( canSave && (
<EuiToolTip content={removeTooltip}> <EuiToolTip content={removeTooltip}>
<EuiButtonIcon <EuiButtonIcon
color="danger" color="danger"

View file

@ -161,6 +161,8 @@ exports[`IndexedFieldsTable IndexedFieldsTable with rollup index pattern should
}, },
] ]
} }
openModal={[Function]}
theme={Object {}}
/> />
</div> </div>
`; `;
@ -196,6 +198,8 @@ exports[`IndexedFieldsTable should filter based on the query bar 1`] = `
}, },
] ]
} }
openModal={[Function]}
theme={Object {}}
/> />
</div> </div>
`; `;
@ -233,6 +237,8 @@ exports[`IndexedFieldsTable should filter based on the schema filter 1`] = `
}, },
] ]
} }
openModal={[Function]}
theme={Object {}}
/> />
</div> </div>
`; `;
@ -267,6 +273,8 @@ exports[`IndexedFieldsTable should filter based on the type filter 1`] = `
}, },
] ]
} }
openModal={[Function]}
theme={Object {}}
/> />
</div> </div>
`; `;
@ -366,6 +374,8 @@ exports[`IndexedFieldsTable should render normally 1`] = `
}, },
] ]
} }
openModal={[Function]}
theme={Object {}}
/> />
</div> </div>
`; `;

View file

@ -97,6 +97,12 @@ const fields = [
}, },
].map(mockFieldToIndexPatternField); ].map(mockFieldToIndexPatternField);
const mockedServices = {
userEditPermission: false,
openModal: () => ({ onClose: new Promise<void>(() => {}), close: async () => {} }),
theme: {} as any,
};
describe('IndexedFieldsTable', () => { describe('IndexedFieldsTable', () => {
test('should render normally', async () => { test('should render normally', async () => {
const component: ShallowWrapper<any, Readonly<{}>, React.Component<{}, {}, any>> = shallow( const component: ShallowWrapper<any, Readonly<{}>, React.Component<{}, {}, any>> = shallow(
@ -110,8 +116,9 @@ describe('IndexedFieldsTable', () => {
indexedFieldTypeFilter={[]} indexedFieldTypeFilter={[]}
schemaFieldTypeFilter={[]} schemaFieldTypeFilter={[]}
fieldFilter="" fieldFilter=""
{...mockedServices}
/> />
).dive(); );
await new Promise((resolve) => process.nextTick(resolve)); await new Promise((resolve) => process.nextTick(resolve));
component.update(); component.update();
@ -131,8 +138,9 @@ describe('IndexedFieldsTable', () => {
indexedFieldTypeFilter={[]} indexedFieldTypeFilter={[]}
schemaFieldTypeFilter={[]} schemaFieldTypeFilter={[]}
fieldFilter="" fieldFilter=""
{...mockedServices}
/> />
).dive(); );
await new Promise((resolve) => process.nextTick(resolve)); await new Promise((resolve) => process.nextTick(resolve));
component.setProps({ fieldFilter: 'Elast' }); component.setProps({ fieldFilter: 'Elast' });
@ -153,8 +161,9 @@ describe('IndexedFieldsTable', () => {
indexedFieldTypeFilter={[]} indexedFieldTypeFilter={[]}
schemaFieldTypeFilter={[]} schemaFieldTypeFilter={[]}
fieldFilter="" fieldFilter=""
{...mockedServices}
/> />
).dive(); );
await new Promise((resolve) => process.nextTick(resolve)); await new Promise((resolve) => process.nextTick(resolve));
component.setProps({ indexedFieldTypeFilter: ['date'] }); component.setProps({ indexedFieldTypeFilter: ['date'] });
@ -175,8 +184,9 @@ describe('IndexedFieldsTable', () => {
indexedFieldTypeFilter={[]} indexedFieldTypeFilter={[]}
schemaFieldTypeFilter={[]} schemaFieldTypeFilter={[]}
fieldFilter="" fieldFilter=""
{...mockedServices}
/> />
).dive(); );
await new Promise((resolve) => process.nextTick(resolve)); await new Promise((resolve) => process.nextTick(resolve));
component.setProps({ schemaFieldTypeFilter: ['runtime'] }); component.setProps({ schemaFieldTypeFilter: ['runtime'] });
@ -198,8 +208,9 @@ describe('IndexedFieldsTable', () => {
indexedFieldTypeFilter={[]} indexedFieldTypeFilter={[]}
schemaFieldTypeFilter={[]} schemaFieldTypeFilter={[]}
fieldFilter="" fieldFilter=""
{...mockedServices}
/> />
).dive(); );
await new Promise((resolve) => process.nextTick(resolve)); await new Promise((resolve) => process.nextTick(resolve));
component.update(); component.update();

View file

@ -10,10 +10,8 @@ import React, { Component } from 'react';
import { createSelector } from 'reselect'; import { createSelector } from 'reselect';
import { OverlayStart, ThemeServiceStart } from 'src/core/public'; import { OverlayStart, ThemeServiceStart } from 'src/core/public';
import { DataViewField, DataView } from '../../../../../../plugins/data_views/public'; import { DataViewField, DataView } from '../../../../../../plugins/data_views/public';
import { useKibana } from '../../../../../../plugins/kibana_react/public';
import { Table } from './components/table'; import { Table } from './components/table';
import { IndexedFieldItem } from './types'; import { IndexedFieldItem } from './types';
import { IndexPatternManagmentContext } from '../../../types';
interface IndexedFieldsTableProps { interface IndexedFieldsTableProps {
fields: DataViewField[]; fields: DataViewField[];
@ -36,16 +34,10 @@ interface IndexedFieldsTableState {
fields: IndexedFieldItem[]; fields: IndexedFieldItem[];
} }
const withHooks = (Comp: typeof Component) => { export class IndexedFieldsTable extends Component<
return (props: any) => { IndexedFieldsTableProps,
const { application } = useKibana<IndexPatternManagmentContext>().services; IndexedFieldsTableState
const userEditPermission = !!application?.capabilities?.indexPatterns?.save; > {
return <Comp userEditPermission={userEditPermission} {...props} />;
};
};
class IndexedFields extends Component<IndexedFieldsTableProps, IndexedFieldsTableState> {
constructor(props: IndexedFieldsTableProps) { constructor(props: IndexedFieldsTableProps) {
super(props); super(props);
@ -158,5 +150,3 @@ class IndexedFields extends Component<IndexedFieldsTableProps, IndexedFieldsTabl
); );
} }
} }
export const IndexedFieldsTable = withHooks(IndexedFields);

View file

@ -22,9 +22,9 @@ interface HeaderProps extends RouteComponentProps {
} }
export const Header = withRouter(({ indexPatternId, history }: HeaderProps) => { export const Header = withRouter(({ indexPatternId, history }: HeaderProps) => {
const { application, docLinks } = useKibana<IndexPatternManagmentContext>().services; const { dataViews, docLinks } = useKibana<IndexPatternManagmentContext>().services;
const links = docLinks?.links; const links = docLinks?.links;
const userEditPermission = !!application?.capabilities?.indexPatterns?.save; const userEditPermission = dataViews.getCanSaveSync();
return ( return (
<EuiFlexGroup alignItems="center"> <EuiFlexGroup alignItems="center">
<EuiFlexItem> <EuiFlexItem>

View file

@ -68,9 +68,10 @@ describe('ScriptedFieldsTable', () => {
helpers={helpers} helpers={helpers}
painlessDocLink={'painlessDoc'} painlessDocLink={'painlessDoc'}
saveIndexPattern={async () => {}} saveIndexPattern={async () => {}}
userEditPermission={false}
scriptedFieldLanguageFilter={[]} scriptedFieldLanguageFilter={[]}
/> />
).dive(); );
// Allow the componentWillMount code to execute // Allow the componentWillMount code to execute
// https://github.com/airbnb/enzyme/issues/450 // https://github.com/airbnb/enzyme/issues/450
@ -87,9 +88,10 @@ describe('ScriptedFieldsTable', () => {
helpers={helpers} helpers={helpers}
painlessDocLink={'painlessDoc'} painlessDocLink={'painlessDoc'}
saveIndexPattern={async () => {}} saveIndexPattern={async () => {}}
userEditPermission={false}
scriptedFieldLanguageFilter={[]} scriptedFieldLanguageFilter={[]}
/> />
).dive(); );
// Allow the componentWillMount code to execute // Allow the componentWillMount code to execute
// https://github.com/airbnb/enzyme/issues/450 // https://github.com/airbnb/enzyme/issues/450
@ -119,9 +121,10 @@ describe('ScriptedFieldsTable', () => {
painlessDocLink={'painlessDoc'} painlessDocLink={'painlessDoc'}
helpers={helpers} helpers={helpers}
saveIndexPattern={async () => {}} saveIndexPattern={async () => {}}
userEditPermission={false}
scriptedFieldLanguageFilter={[]} scriptedFieldLanguageFilter={[]}
/> />
).dive(); );
// Allow the componentWillMount code to execute // Allow the componentWillMount code to execute
// https://github.com/airbnb/enzyme/issues/450 // https://github.com/airbnb/enzyme/issues/450
@ -145,9 +148,10 @@ describe('ScriptedFieldsTable', () => {
painlessDocLink={'painlessDoc'} painlessDocLink={'painlessDoc'}
helpers={helpers} helpers={helpers}
saveIndexPattern={async () => {}} saveIndexPattern={async () => {}}
userEditPermission={false}
scriptedFieldLanguageFilter={[]} scriptedFieldLanguageFilter={[]}
/> />
).dive(); );
// Allow the componentWillMount code to execute // Allow the componentWillMount code to execute
// https://github.com/airbnb/enzyme/issues/450 // https://github.com/airbnb/enzyme/issues/450
@ -166,9 +170,10 @@ describe('ScriptedFieldsTable', () => {
helpers={helpers} helpers={helpers}
painlessDocLink={'painlessDoc'} painlessDocLink={'painlessDoc'}
saveIndexPattern={async () => {}} saveIndexPattern={async () => {}}
userEditPermission={false}
scriptedFieldLanguageFilter={[]} scriptedFieldLanguageFilter={[]}
/> />
).dive(); );
await component.update(); // Fire `componentWillMount()` await component.update(); // Fire `componentWillMount()`
// @ts-expect-error lang is not valid // @ts-expect-error lang is not valid
@ -194,9 +199,10 @@ describe('ScriptedFieldsTable', () => {
helpers={helpers} helpers={helpers}
painlessDocLink={'painlessDoc'} painlessDocLink={'painlessDoc'}
saveIndexPattern={async () => {}} saveIndexPattern={async () => {}}
userEditPermission={false}
scriptedFieldLanguageFilter={[]} scriptedFieldLanguageFilter={[]}
/> />
).dive(); );
await component.update(); // Fire `componentWillMount()` await component.update(); // Fire `componentWillMount()`
// @ts-expect-error // @ts-expect-error

View file

@ -15,10 +15,8 @@ import {
import { Table, Header, CallOuts, DeleteScritpedFieldConfirmationModal } from './components'; import { Table, Header, CallOuts, DeleteScritpedFieldConfirmationModal } from './components';
import { ScriptedFieldItem } from './types'; import { ScriptedFieldItem } from './types';
import { IndexPatternManagmentContext } from '../../../types';
import { DataView, DataViewsPublicPluginStart } from '../../../../../../plugins/data_views/public'; import { DataView, DataViewsPublicPluginStart } from '../../../../../../plugins/data_views/public';
import { useKibana } from '../../../../../../plugins/kibana_react/public';
interface ScriptedFieldsTableProps { interface ScriptedFieldsTableProps {
indexPattern: DataView; indexPattern: DataView;
@ -41,16 +39,10 @@ interface ScriptedFieldsTableState {
fields: ScriptedFieldItem[]; fields: ScriptedFieldItem[];
} }
const withHooks = (Comp: typeof Component) => { export class ScriptedFieldsTable extends Component<
return (props: any) => { ScriptedFieldsTableProps,
const { application } = useKibana<IndexPatternManagmentContext>().services; ScriptedFieldsTableState
const userEditPermission = !!application?.capabilities?.indexPatterns?.save; > {
return <Comp userEditPermission={userEditPermission} {...props} />;
};
};
class ScriptedFields extends Component<ScriptedFieldsTableProps, ScriptedFieldsTableState> {
constructor(props: ScriptedFieldsTableProps) { constructor(props: ScriptedFieldsTableProps) {
super(props); super(props);
@ -168,5 +160,3 @@ class ScriptedFields extends Component<ScriptedFieldsTableProps, ScriptedFieldsT
); );
} }
} }
export const ScriptedFieldsTable = withHooks(ScriptedFields);

View file

@ -132,7 +132,7 @@ export function Tabs({
location, location,
refreshFields, refreshFields,
}: TabsProps) { }: TabsProps) {
const { application, uiSettings, docLinks, dataViewFieldEditor, overlays, theme } = const { uiSettings, docLinks, dataViewFieldEditor, overlays, theme, dataViews } =
useKibana<IndexPatternManagmentContext>().services; useKibana<IndexPatternManagmentContext>().services;
const [fieldFilter, setFieldFilter] = useState<string>(''); const [fieldFilter, setFieldFilter] = useState<string>('');
const [syncingStateFunc, setSyncingStateFunc] = useState<any>({ const [syncingStateFunc, setSyncingStateFunc] = useState<any>({
@ -241,7 +241,7 @@ export function Tabs({
[uiSettings] [uiSettings]
); );
const userEditPermission = !!application?.capabilities?.indexPatterns?.save; const userEditPermission = dataViews.getCanSaveSync();
const getFilterSection = useCallback( const getFilterSection = useCallback(
(type: string) => { (type: string) => {
return ( return (
@ -448,7 +448,8 @@ export function Tabs({
getFieldInfo, getFieldInfo,
}} }}
openModal={overlays.openModal} openModal={overlays.openModal}
theme={theme} theme={theme!}
userEditPermission={dataViews.getCanSaveSync()}
/> />
)} )}
</DeleteRuntimeFieldProvider> </DeleteRuntimeFieldProvider>
@ -472,6 +473,7 @@ export function Tabs({
}} }}
onRemoveField={refreshFilters} onRemoveField={refreshFilters}
painlessDocLink={docLinks.links.scriptedFields.painless} painlessDocLink={docLinks.links.scriptedFields.painless}
userEditPermission={dataViews.getCanSaveSync()}
/> />
</Fragment> </Fragment>
); );
@ -510,6 +512,7 @@ export function Tabs({
refreshFields, refreshFields,
overlays, overlays,
theme, theme,
dataViews,
] ]
); );

View file

@ -39,7 +39,7 @@ export async function mountManagementSection(
params: ManagementAppMountParams params: ManagementAppMountParams
) { ) {
const [ const [
{ chrome, application, uiSettings, notifications, overlays, http, docLinks, theme }, { chrome, uiSettings, notifications, overlays, http, docLinks, theme },
{ data, dataViewFieldEditor, dataViewEditor, dataViews, fieldFormats }, { data, dataViewFieldEditor, dataViewEditor, dataViews, fieldFormats },
indexPatternManagementStart, indexPatternManagementStart,
] = await getStartServices(); ] = await getStartServices();
@ -51,7 +51,6 @@ export async function mountManagementSection(
const deps: IndexPatternManagmentContext = { const deps: IndexPatternManagmentContext = {
chrome, chrome,
application,
uiSettings, uiSettings,
notifications, notifications,
overlays, overlays,

View file

@ -13,6 +13,7 @@ import { urlForwardingPluginMock } from '../../url_forwarding/public/mocks';
import { dataPluginMock } from '../../data/public/mocks'; import { dataPluginMock } from '../../data/public/mocks';
import { indexPatternFieldEditorPluginMock } from '../../data_view_field_editor/public/mocks'; import { indexPatternFieldEditorPluginMock } from '../../data_view_field_editor/public/mocks';
import { indexPatternEditorPluginMock } from '../../data_view_editor/public/mocks'; import { indexPatternEditorPluginMock } from '../../data_view_editor/public/mocks';
import { dataViewPluginMocks } from '../../data_views/public/mocks';
import { import {
IndexPatternManagementSetup, IndexPatternManagementSetup,
IndexPatternManagementStart, IndexPatternManagementStart,
@ -54,15 +55,14 @@ const docLinks = {
const createIndexPatternManagmentContext = (): { const createIndexPatternManagmentContext = (): {
[key in keyof IndexPatternManagmentContext]: any; [key in keyof IndexPatternManagmentContext]: any;
} => { } => {
const { chrome, application, uiSettings, notifications, overlays } = coreMock.createStart(); const { chrome, uiSettings, notifications, overlays } = coreMock.createStart();
const { http } = coreMock.createSetup(); const { http } = coreMock.createSetup();
const data = dataPluginMock.createStartContract(); const data = dataPluginMock.createStartContract();
const dataViewFieldEditor = indexPatternFieldEditorPluginMock.createStartContract(); const dataViewFieldEditor = indexPatternFieldEditorPluginMock.createStartContract();
const dataViews = data.indexPatterns; const dataViews = dataViewPluginMocks.createStartContract();
return { return {
chrome, chrome,
application,
uiSettings, uiSettings,
notifications, notifications,
overlays, overlays,

View file

@ -8,7 +8,6 @@
import { import {
ChromeStart, ChromeStart,
ApplicationStart,
IUiSettingsClient, IUiSettingsClient,
OverlayStart, OverlayStart,
NotificationsStart, NotificationsStart,
@ -26,7 +25,6 @@ import { FieldFormatsStart } from '../../field_formats/public';
export interface IndexPatternManagmentContext { export interface IndexPatternManagmentContext {
chrome: ChromeStart; chrome: ChromeStart;
application: ApplicationStart;
uiSettings: IUiSettingsClient; uiSettings: IUiSettingsClient;
notifications: NotificationsStart; notifications: NotificationsStart;
overlays: OverlayStart; overlays: OverlayStart;

View file

@ -27,6 +27,7 @@ const createStartContract = (): Start => {
}), }),
get: jest.fn().mockReturnValue(Promise.resolve({})), get: jest.fn().mockReturnValue(Promise.resolve({})),
clearCache: jest.fn(), clearCache: jest.fn(),
getCanSaveSync: jest.fn(),
} as unknown as jest.Mocked<DataViewsContract>; } as unknown as jest.Mocked<DataViewsContract>;
}; };