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 14 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,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 IAddRoleMappingButtonProps {
scottybollinger marked this conversation as resolved.
Show resolved Hide resolved
path: string;
}

export const AddRoleMappingButton: React.FC<IAddRoleMappingButtonProps> = ({ 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,95 @@
/*
* 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 { AttributeSelector } from './attribute_selector';

const baseProps = {
attributeName: 'An Attribute',
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"]')).toBeDefined();
scottybollinger marked this conversation as resolved.
Show resolved Hide resolved
});

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(wrapper.find('[data-test-subj="authProviderSelect"]')).toHaveLength(0);
scottybollinger marked this conversation as resolved.
Show resolved Hide resolved
});

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', 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 handleAuthProviderChangeMock = jest.fn();
const wrapper = shallow(
<AttributeSelector {...baseProps} handleAuthProviderChange={handleAuthProviderChangeMock} />
);

const select = findAuthProvidersSelect(wrapper);
select.simulate('change', [{ label: 'kbn_saml', value: 'kbn_saml' }]);

expect(handleAuthProviderChangeMock).toHaveBeenCalledWith(['kbn_saml']);
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
/*
* 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 {
EuiComboBox,
EuiComboBoxOptionOption,
EuiFieldText,
EuiFlexGroup,
EuiFlexItem,
EuiFormRow,
EuiPanel,
EuiSelect,
EuiSpacer,
EuiTitle,
} from '@elastic/eui';

import {
ANY_AUTH_PROVIDER,
ANY_AUTH_PROVIDER_OPTION_LABEL,
AUTH_ANY_PROVIDER_LABEL,
AUTH_INDIVIDUAL_PROVIDER_LABEL,
ATTRIBUTE_SELECTOR_TITLE,
AUTH_PROVIDER_LABEL,
EXTERNAL_ATTRIBUTE_LABEL,
ATTRIBUTE_VALUE_LABEL,
} from './constants';

interface IAttributeSelectorProps {
scottybollinger marked this conversation as resolved.
Show resolved Hide resolved
attributeName: string;
attributeValue?: string;
attributes: string[];
selectedAuthProviders?: string[];
availableAuthProviders?: string[];
elasticsearchRoles: string[];
disabled: boolean;
multipleAuthProvidersConfig: boolean;
handleAttributeSelectorChange(value: string, elasticsearchRole: string): void;
handleAttributeValueChange(value: string): void;
handleAuthProviderChange?(value: string[]): void;
}

interface AttributeExamples {
username: string;
email: string;
metadata: string;
}

const attributeValueExamples = {
username: 'elastic,*_system',
email: 'user@example.com,*@example.org',
metadata: '{"_reserved": true}',
};

const getAuthProviderOptions = (
availableAuthProviders: string[]
): Array<EuiComboBoxOptionOption<string>> => {
return [
{
label: AUTH_ANY_PROVIDER_LABEL,
options: [{ value: ANY_AUTH_PROVIDER, label: ANY_AUTH_PROVIDER_OPTION_LABEL }],
},
{
label: AUTH_INDIVIDUAL_PROVIDER_LABEL,
options: availableAuthProviders.map((authProvider) => ({
value: authProvider,
label: authProvider,
})),
},
];
};

const getSelectedOptions = (
selectedAuthProviders: string[],
availableAuthProviders: string[]
): Array<EuiComboBoxOptionOption<string>> => {
const groupedOptions: Array<EuiComboBoxOptionOption<string>> = getAuthProviderOptions(
availableAuthProviders
);
const options: Array<EuiComboBoxOptionOption<string>> = groupedOptions.reduce(
(acc: Array<EuiComboBoxOptionOption<string>>, n: EuiComboBoxOptionOption<string>) => [
...acc,
...(n.options as Array<EuiComboBoxOptionOption<string>>),
scottybollinger marked this conversation as resolved.
Show resolved Hide resolved
],
[]
);
return options.filter((o) => o.value && selectedAuthProviders.includes(o.value));
};

export const AttributeSelector: React.FC<IAttributeSelectorProps> = ({
attributeName,
attributeValue = '',
attributes,
availableAuthProviders,
selectedAuthProviders = [ANY_AUTH_PROVIDER],
elasticsearchRoles,
disabled,
multipleAuthProvidersConfig,
handleAttributeSelectorChange,
handleAttributeValueChange,
handleAuthProviderChange = () => null,
}) => {
return (
<EuiPanel
data-test-subj="attributeSelector"
scottybollinger marked this conversation as resolved.
Show resolved Hide resolved
paddingSize="l"
className={disabled ? 'euiPanel--disabled' : ''}
>
<EuiTitle size="s">
<h3>{ATTRIBUTE_SELECTOR_TITLE}</h3>
scottybollinger marked this conversation as resolved.
Show resolved Hide resolved
</EuiTitle>
<EuiSpacer />
{availableAuthProviders && multipleAuthProvidersConfig && (
<EuiFlexGroup alignItems="stretch">
<EuiFlexItem>
<EuiFormRow label={AUTH_PROVIDER_LABEL} fullWidth>
<EuiComboBox
data-test-subj="authProviderSelect"
selectedOptions={getSelectedOptions(selectedAuthProviders, availableAuthProviders)}
options={getAuthProviderOptions(availableAuthProviders)}
onChange={(options) => {
handleAuthProviderChange(options.map((o) => o.value as string));
}}
fullWidth
isDisabled={disabled}
/>
</EuiFormRow>
</EuiFlexItem>
<EuiFlexItem />
</EuiFlexGroup>
)}
<EuiFlexGroup alignItems="stretch">
<EuiFlexItem>
<EuiFormRow label={EXTERNAL_ATTRIBUTE_LABEL} fullWidth>
<EuiSelect
name="external-attribute"
value={attributeName}
required
options={attributes.map((attribute) => ({ value: attribute, text: attribute }))}
onChange={(e) => {
handleAttributeSelectorChange(e.target.value, elasticsearchRoles[0]);
}}
fullWidth
disabled={disabled}
/>
</EuiFormRow>
</EuiFlexItem>
<EuiFlexItem>
<EuiFormRow label={ATTRIBUTE_VALUE_LABEL} fullWidth>
{attributeName === 'role' ? (
<EuiSelect
value={attributeValue}
name="elasticsearch-role"
required
options={elasticsearchRoles.map((elasticsearchRole) => ({
value: elasticsearchRole,
text: elasticsearchRole,
}))}
onChange={(e) => {
handleAttributeValueChange(e.target.value);
}}
fullWidth
disabled={disabled}
/>
) : (
<EuiFieldText
value={attributeValue}
name="attribute-value"
placeholder={attributeValueExamples[attributeName as keyof AttributeExamples]}
scottybollinger marked this conversation as resolved.
Show resolved Hide resolved
onChange={(e) => {
handleAttributeValueChange(e.target.value);
}}
fullWidth
disabled={disabled}
/>
)}
</EuiFormRow>
</EuiFlexItem>
</EuiFlexGroup>
</EuiPanel>
);
};
Loading