Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Enterprise Search] Migrate shared role mapping components #91723

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
c6356a2
Initial copy/paste of components
scottybollinger Feb 17, 2021
f62615f
Add types
scottybollinger Feb 17, 2021
c2d6c4f
Add constants
scottybollinger Feb 17, 2021
9596b05
Replace EUI toSentenceCase with lodash startCase
scottybollinger Feb 17, 2021
1dab689
Update paths
scottybollinger Feb 17, 2021
1b19482
Fix test
scottybollinger Feb 17, 2021
891abf3
Fix TypeScript issues
scottybollinger Feb 17, 2021
820a184
Remove ability check for non-federated users
scottybollinger Feb 17, 2021
e1d4fde
Use Kibana React Router helpers
scottybollinger Feb 17, 2021
b7ccfb9
Update comments
scottybollinger Feb 17, 2021
27c3591
Fix failing test
scottybollinger Feb 17, 2021
3e23a4f
Add i18n
scottybollinger Feb 17, 2021
4f755d1
Add tests for smaller components
scottybollinger Feb 17, 2021
9ebde24
Fix fallbacks
scottybollinger Feb 17, 2021
fba159d
Add tests for AttributeSelector
scottybollinger Feb 17, 2021
235b368
Add mocks and testSubj attrs
scottybollinger Feb 18, 2021
6c6b757
Add tests for RoleMappingsTable
scottybollinger Feb 18, 2021
fc839e6
Fix types
scottybollinger Feb 18, 2021
0167313
Refactor for better typing
scottybollinger Feb 18, 2021
6880c20
Remove return type
scottybollinger Feb 19, 2021
96e293e
Rename interface
scottybollinger Feb 19, 2021
852ecc2
Rename more interfaces
scottybollinger Feb 19, 2021
3f27d15
PR feedback
scottybollinger Feb 19, 2021
f09e9fe
Merge branch 'master' into scottybollinger/shared-role-mapping
kibanamachine Feb 19, 2021
6131f2f
Add test for radio checked state
scottybollinger Feb 19, 2021
36f3caa
Add false radio assertion to
scottybollinger Feb 19, 2021
4f9bbd5
Update className
scottybollinger Feb 19, 2021
83a7e45
Merge branch 'master' into scottybollinger/shared-role-mapping
kibanamachine Feb 22, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,5 @@
*/

export * from '../../../common/types/app_search';
export { Role, RoleTypes, AbilityTypes } from './utils/role';
export { Role, RoleTypes, AbilityTypes, ASRoleMapping } from './utils/role';
export { Engine } from './components/engine/types';
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
* 2.0.
*/

import { RoleMapping } from '../../../shared/types';
import { Engine } from '../../components/engine/types';
import { Account } from '../../types';

export type RoleTypes = 'owner' | 'admin' | 'dev' | 'editor' | 'analyst';
Expand Down Expand Up @@ -103,3 +105,11 @@ export const getRoleAbilities = (role: Account['role']): Role => {

return Object.assign(myRole, topLevelProps, abilities);
};

export interface ASRoleMapping extends RoleMapping {
accessAllEngines: boolean;
engines: Engine[];
toolTip?: {
content: string;
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/*
* 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 const asRoleMapping = {
id: null,
attributeName: 'role',
attributeValue: ['superuser'],
authProvider: ['*'],
roleType: 'owner',
rules: {
role: 'superuser',
},
accessAllEngines: true,
engines: [],
toolTip: {
content: 'Elasticsearch superusers will always be able to log in as the owner',
},
};

export const wsRoleMapping = {
id: '602d4ba85foobarbaz123',
attributeName: 'username',
attributeValue: 'user',
authProvider: ['*', 'other_auth'],
roleType: 'admin',
rules: {
username: 'user',
},
allGroups: true,
groups: [
{
id: '602c3b475foobarbaz123',
name: 'Default',
createdAt: '2021-02-16T21:38:15Z',
updatedAt: '2021-02-16T21:40:32Z',
contentSources: [
{
id: '602c3bcf5foobarbaz123',
name: 'National Parks',
serviceType: 'custom',
},
],
users: [
{
id: '602c3b485foobarbaz123',
name: 'you_know_for_search',
email: 'foo@example.com',
initials: 'E',
pictureUrl: null,
color: '#ffcc13',
},
{
id: '602c3bf85foobarbaz123',
name: 'elastic',
email: null,
initials: 'E',
pictureUrl: null,
color: '#7968ff',
},
],
usersCount: 2,
},
],
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*
* 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 React from 'react';

import { shallow } from 'enzyme';

import { EuiButtonTo } from '../react_router_helpers';

import { AddRoleMappingButton } from './add_role_mapping_button';

describe('AddRoleMappingButton', () => {
it('renders', () => {
const wrapper = shallow(<AddRoleMappingButton path="/foo" />);

expect(wrapper.find(EuiButtonTo)).toHaveLength(1);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*
* 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 React from 'react';

import { EuiButtonTo } from '../react_router_helpers';

import { ADD_ROLE_MAPPING_BUTTON } from './constants';

interface Props {
path: string;
}

export const AddRoleMappingButton: React.FC<Props> = ({ path }) => (
<EuiButtonTo to={path} fill color="secondary">
{ADD_ROLE_MAPPING_BUTTON}
</EuiButtonTo>
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
/*
* 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 React from 'react';

import { shallow, ShallowWrapper } from 'enzyme';

import { EuiComboBox, EuiFieldText } from '@elastic/eui';

import { AttributeSelector, AttributeName } from './attribute_selector';
import { ANY_AUTH_PROVIDER, ANY_AUTH_PROVIDER_OPTION_LABEL } from './constants';

const handleAttributeSelectorChange = jest.fn();
const handleAttributeValueChange = jest.fn();
const handleAuthProviderChange = jest.fn();

const baseProps = {
attributeName: 'username' as AttributeName,
attributeValue: 'Something',
attributes: ['a', 'b', 'c'],
availableAuthProviders: ['ees_saml', 'kbn_saml'],
selectedAuthProviders: ['ees_saml'],
elasticsearchRoles: ['whatever'],
multipleAuthProvidersConfig: true,
disabled: false,
handleAttributeSelectorChange,
handleAttributeValueChange,
handleAuthProviderChange,
};

describe('AttributeSelector', () => {
it('renders', () => {
const wrapper = shallow(<AttributeSelector {...baseProps} />);

expect(wrapper.find('[data-test-subj="AttributeSelector"]').exists()).toBe(true);
});

it('renders disabled panel with className', () => {
const wrapper = shallow(<AttributeSelector {...baseProps} disabled />);

expect(wrapper.find('[data-test-subj="AttributeSelector"]').prop('className')).toEqual(
'euiPanel--disabled'
);
});

describe('Auth Providers', () => {
const findAuthProvidersSelect = (wrapper: ShallowWrapper) =>
wrapper.find('[data-test-subj="AuthProviderSelect"]');

it('will not render if "availableAuthProviders" prop has not been provided', () => {
const wrapper = shallow(
<AttributeSelector {...baseProps} availableAuthProviders={undefined} />
);

expect(findAuthProvidersSelect(wrapper)).toHaveLength(0);
});

it('handles fallback props', () => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FWIW, I don't really understand what this means... does it mean like .... if attributeValue and selectedAuthProviders aren't provided then it populates a different set of select options?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I didn't know of a better way to name this. I added this for test coverage for the lines that have fallback props. Perhaps "default" props would be a better name but am open to suggestions:

export const AttributeSelector: React.FC<Props> = ({
  attributeName,
  attributeValue = '', // this
  attributes,
  availableAuthProviders,
  selectedAuthProviders = [ANY_AUTH_PROVIDER], // and this
  ...

const wrapper = shallow(
<AttributeSelector
{...baseProps}
attributeValue={undefined}
selectedAuthProviders={undefined}
/>
);

const select: ShallowWrapper = findAuthProvidersSelect(wrapper);

expect(select.prop('selectedOptions')).toEqual([
{
label: ANY_AUTH_PROVIDER_OPTION_LABEL,
scottybollinger marked this conversation as resolved.
Show resolved Hide resolved
value: ANY_AUTH_PROVIDER,
},
]);
});

it('renders a list of auth providers from the "availableAuthProviders" prop including an "Any" option', () => {
const wrapper = shallow(
<AttributeSelector {...baseProps} availableAuthProviders={['ees_saml', 'kbn_saml']} />
);
const select = findAuthProvidersSelect(wrapper) as any;

expect(select.props().options).toEqual([
{
label: expect.any(String),
options: [{ label: ANY_AUTH_PROVIDER_OPTION_LABEL, value: '*' }],
},
{
label: expect.any(String),
options: [
{ label: 'ees_saml', value: 'ees_saml' },
{ label: 'kbn_saml', value: 'kbn_saml' },
],
},
]);
});

it('the "selectedAuthProviders" prop should be used as the selected value', () => {
const wrapper = shallow(
<AttributeSelector
{...baseProps}
availableAuthProviders={['ees_saml', 'kbn_saml']}
selectedAuthProviders={['kbn_saml']}
/>
);
const select = findAuthProvidersSelect(wrapper) as any;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const select = findAuthProvidersSelect(wrapper) as any;
const select: ShallowWrapper = findAuthProvidersSelect(wrapper);

Constance said there are metrics within Kibana on any usage, so we should avoid it when possible, even in tests.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You actually may not even need to provide a type here.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see you resolved this but didn't make the change. It's fine if you're choosing not to make this change, but could you just update the comments in the future so I know?


expect(select.props().selectedOptions).toEqual([{ label: 'kbn_saml', value: 'kbn_saml' }]);
});

it('should call the "handleAuthProviderChange" prop when a value is selected', () => {
const wrapper = shallow(<AttributeSelector {...baseProps} />);
const select = findAuthProvidersSelect(wrapper);
select.simulate('change', [{ label: 'kbn_saml', value: 'kbn_saml' }]);

expect(handleAuthProviderChange).toHaveBeenCalledWith(['kbn_saml']);
});

it('should call the "handleAttributeSelectorChange" prop when a value is selected', () => {
const wrapper = shallow(<AttributeSelector {...baseProps} />);
const select = wrapper.find('[data-test-subj="ExternalAttributeSelect"]');
const event = { target: { value: 'kbn_saml' } };
select.simulate('change', event);

expect(handleAttributeSelectorChange).toHaveBeenCalledWith(
'kbn_saml',
baseProps.elasticsearchRoles[0]
);
});

it('handles fallback when no "handleAuthProviderChange" provided', () => {
const wrapper = shallow(
<AttributeSelector {...baseProps} handleAuthProviderChange={undefined} />
);

expect(wrapper.find(EuiComboBox).prop('onChange')!([])).toEqual(undefined);
});

it('should call the "handleAttributeSelectorChange" prop when field text value is changed', () => {
const wrapper = shallow(<AttributeSelector {...baseProps} />);
const input = wrapper.find(EuiFieldText);
const event = { target: { value: 'kbn_saml' } };
input.simulate('change', event);

expect(handleAttributeSelectorChange).toHaveBeenCalledWith(
'kbn_saml',
baseProps.elasticsearchRoles[0]
);
});

it('should call the "handleAttributeSelectorChange" prop when attribute value is selected', () => {
const wrapper = shallow(<AttributeSelector {...baseProps} attributeName="role" />);
const select = wrapper.find('[data-test-subj="ElasticsearchRoleSelect"]');
const event = { target: { value: 'kbn_saml' } };
select.simulate('change', event);

expect(handleAttributeSelectorChange).toHaveBeenCalledWith(
'kbn_saml',
baseProps.elasticsearchRoles[0]
);
});
});
});
Loading