Fixed icon display on role create/edit screen where custom feature privileges have been selected (#134857)

* Adding new logic for Info Icon on custom feature privileges

* Incorporating feedback from PR review

* changing the order of the conditions so the less expensive term will be evaluated first
This commit is contained in:
Kurt 2022-06-22 12:19:00 -04:00 committed by GitHub
parent 1455107797
commit 206de80d2c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 368 additions and 8 deletions

View file

@ -5,7 +5,7 @@
* 2.0.
*/
import { EuiAccordion } from '@elastic/eui';
import { EuiAccordion, EuiIconTip } from '@elastic/eui';
import React from 'react';
import type { KibanaFeature, SubFeatureConfig } from '@kbn/features-plugin/public';
@ -33,11 +33,14 @@ interface TestConfig {
calculateDisplayedPrivileges: boolean;
canCustomizeSubFeaturePrivileges: boolean;
}
const setup = (config: TestConfig) => {
const kibanaPrivileges = createKibanaPrivileges(config.features, {
allowSubFeaturePrivileges: config.canCustomizeSubFeaturePrivileges,
});
const calculator = new PrivilegeFormCalculator(kibanaPrivileges, config.role);
const onChange = jest.fn();
const onChangeAll = jest.fn();
const wrapper = mountWithIntl(
@ -120,6 +123,7 @@ describe('FeatureTable', () => {
feature: {},
},
]);
const { displayedPrivileges } = setup({
role,
features: kibanaFeatures,
@ -127,6 +131,7 @@ describe('FeatureTable', () => {
calculateDisplayedPrivileges: true,
canCustomizeSubFeaturePrivileges,
});
expect(displayedPrivileges).toEqual({
excluded_from_base: {
primaryFeaturePrivilege: 'none',
@ -271,6 +276,7 @@ describe('FeatureTable', () => {
},
},
]);
const { displayedPrivileges } = setup({
role,
features: kibanaFeatures,
@ -312,6 +318,7 @@ describe('FeatureTable', () => {
feature: {},
},
]);
const { wrapper } = setup({
role,
features: kibanaFeatures,
@ -325,6 +332,7 @@ describe('FeatureTable', () => {
.find(EuiAccordion)
.filter(`#featurePrivilegeControls_${feature.id}`)
.props();
if (!feature.subFeatures || feature.subFeatures.length === 0) {
expect(arrowDisplay).toEqual('none');
} else {
@ -941,4 +949,334 @@ describe('FeatureTable', () => {
`"2 / 2 features granted"`
);
});
describe('Info Icon Tooltip for Customized Subfeature privileges', () => {
it('should render if there are custom privileges and the accordion is toggled open then toggled closed', () => {
const role = createRole([
{
spaces: ['foo'],
base: [],
feature: {
unit_test: ['minimal_read', 'sub-toggle-1', 'sub-toggle-2'],
},
},
]);
const feature = createFeature({
id: 'unit_test',
name: 'Unit Test Feature',
subFeatures: [
{
name: 'Some Sub Feature',
privilegeGroups: [
{
groupType: 'independent',
privileges: [
{
id: 'sub-toggle-1',
name: 'Sub Toggle 1',
includeIn: 'all',
savedObject: { all: [], read: [] },
ui: ['sub-toggle-1'],
},
{
id: 'sub-toggle-2',
name: 'Sub Toggle 2',
includeIn: 'all',
savedObject: { all: [], read: [] },
ui: ['sub-toggle-2'],
},
{
id: 'sub-toggle-3',
name: 'Sub Toggle 3',
includeIn: 'all',
savedObject: { all: [], read: [] },
ui: ['sub-toggle-3'],
},
],
},
],
},
] as SubFeatureConfig[],
});
const { wrapper } = setup({
role,
features: [feature],
privilegeIndex: 0,
calculateDisplayedPrivileges: false,
canCustomizeSubFeaturePrivileges: true,
});
const categoryExpander = findTestSubject(wrapper, 'featureCategoryButton_foo');
categoryExpander.simulate('click');
const featureExpander = findTestSubject(wrapper, 'featureTableCell');
featureExpander.simulate('click').simulate('click');
const { type } = wrapper.find(EuiIconTip).props();
expect(type).toBe('iInCircle');
});
it('should render if there are custom privileges and the accordion has not been toggled (i.e. on load)', () => {
const role = createRole([
{
spaces: ['foo'],
base: [],
feature: {
unit_test: ['minimal_read', 'sub-toggle-1', 'sub-toggle-2'],
},
},
]);
const feature = createFeature({
id: 'unit_test',
name: 'Unit Test Feature',
subFeatures: [
{
name: 'Some Sub Feature',
privilegeGroups: [
{
groupType: 'independent',
privileges: [
{
id: 'sub-toggle-1',
name: 'Sub Toggle 1',
includeIn: 'all',
savedObject: { all: [], read: [] },
ui: ['sub-toggle-1'],
},
{
id: 'sub-toggle-2',
name: 'Sub Toggle 2',
includeIn: 'all',
savedObject: { all: [], read: [] },
ui: ['sub-toggle-2'],
},
{
id: 'sub-toggle-3',
name: 'Sub Toggle 3',
includeIn: 'all',
savedObject: { all: [], read: [] },
ui: ['sub-toggle-3'],
},
],
},
],
},
] as SubFeatureConfig[],
});
const { wrapper } = setup({
role,
features: [feature],
privilegeIndex: 0,
calculateDisplayedPrivileges: false,
canCustomizeSubFeaturePrivileges: true,
});
const { type } = wrapper.find(EuiIconTip).props();
expect(type).toBe('iInCircle');
});
it('should not render if there are custom privileges and the accordion is open', () => {
const role = createRole([
{
spaces: ['foo'],
base: [],
feature: {
unit_test: ['minimal_read', 'sub-toggle-1', 'sub-toggle-2'],
},
},
]);
const feature = createFeature({
id: 'unit_test',
name: 'Unit Test Feature',
subFeatures: [
{
name: 'Some Sub Feature',
privilegeGroups: [
{
groupType: 'independent',
privileges: [
{
id: 'sub-toggle-1',
name: 'Sub Toggle 1',
includeIn: 'all',
savedObject: { all: [], read: [] },
ui: ['sub-toggle-1'],
},
{
id: 'sub-toggle-2',
name: 'Sub Toggle 2',
includeIn: 'all',
savedObject: { all: [], read: [] },
ui: ['sub-toggle-2'],
},
{
id: 'sub-toggle-3',
name: 'Sub Toggle 3',
includeIn: 'all',
savedObject: { all: [], read: [] },
ui: ['sub-toggle-3'],
},
],
},
],
},
] as SubFeatureConfig[],
});
const { wrapper } = setup({
role,
features: [feature],
privilegeIndex: 0,
calculateDisplayedPrivileges: false,
canCustomizeSubFeaturePrivileges: true,
});
const categoryExpander = findTestSubject(wrapper, 'featureCategoryButton_foo');
categoryExpander.simulate('click');
const featureExpander = findTestSubject(wrapper, 'featureTableCell');
featureExpander.simulate('click');
const { type } = wrapper.find(EuiIconTip).props();
expect(type).toBe('empty');
});
it('should not render if there are NOT custom privileges and the accordion has not been toggled (i.e on load)', () => {
const role = createRole([
{
spaces: ['foo'],
base: [],
feature: {
unit_test: ['all', 'sub-toggle-1', 'sub-toggle-2', 'sub-toggle-3'],
},
},
]);
const feature = createFeature({
id: 'unit_test',
name: 'Unit Test Feature',
subFeatures: [
{
name: 'Some Sub Feature',
privilegeGroups: [
{
groupType: 'independent',
privileges: [
{
id: 'sub-toggle-1',
name: 'Sub Toggle 1',
includeIn: 'all',
savedObject: { all: [], read: [] },
ui: ['sub-toggle-1'],
},
{
id: 'sub-toggle-2',
name: 'Sub Toggle 2',
includeIn: 'all',
savedObject: { all: [], read: [] },
ui: ['sub-toggle-2'],
},
{
id: 'sub-toggle-3',
name: 'Sub Toggle 3',
includeIn: 'all',
savedObject: { all: [], read: [] },
ui: ['sub-toggle-3'],
},
],
},
],
},
] as SubFeatureConfig[],
});
const { wrapper } = setup({
role,
features: [feature],
privilegeIndex: 0,
calculateDisplayedPrivileges: false,
canCustomizeSubFeaturePrivileges: true,
});
const { type } = wrapper.find(EuiIconTip).props();
expect(type).toBe('empty');
});
it('should not render if there are NOT custom privileges and the accordion has been toggled open then toggled closed', () => {
const role = createRole([
{
spaces: ['foo'],
base: [],
feature: {
unit_test: ['all', 'sub-toggle-1', 'sub-toggle-2', 'sub-toggle-3'],
},
},
]);
const feature = createFeature({
id: 'unit_test',
name: 'Unit Test Feature',
subFeatures: [
{
name: 'Some Sub Feature',
privilegeGroups: [
{
groupType: 'independent',
privileges: [
{
id: 'sub-toggle-1',
name: 'Sub Toggle 1',
includeIn: 'all',
savedObject: { all: [], read: [] },
ui: ['sub-toggle-1'],
},
{
id: 'sub-toggle-2',
name: 'Sub Toggle 2',
includeIn: 'all',
savedObject: { all: [], read: [] },
ui: ['sub-toggle-2'],
},
{
id: 'sub-toggle-3',
name: 'Sub Toggle 3',
includeIn: 'all',
savedObject: { all: [], read: [] },
ui: ['sub-toggle-3'],
},
],
},
],
},
] as SubFeatureConfig[],
});
const { wrapper } = setup({
role,
features: [feature],
privilegeIndex: 0,
calculateDisplayedPrivileges: false,
canCustomizeSubFeaturePrivileges: true,
});
const categoryExpander = findTestSubject(wrapper, 'featureCategoryButton_foo');
categoryExpander.simulate('click');
const featureExpander = findTestSubject(wrapper, 'featureTableCell');
featureExpander.simulate('click').simulate('click');
const { type } = wrapper.find(EuiIconTip).props();
expect(type).toBe('empty');
});
});
});

View file

@ -48,7 +48,11 @@ interface Props {
disabled?: boolean;
}
export class FeatureTable extends Component<Props, {}> {
interface State {
expandedPrivilegeControls: Set<string>;
}
export class FeatureTable extends Component<Props, State> {
public static defaultProps = {
privilegeIndex: -1,
showLocks: true,
@ -67,8 +71,11 @@ export class FeatureTable extends Component<Props, {}> {
if (!this.featureCategories.has(feature.category.id)) {
this.featureCategories.set(feature.category.id, []);
}
this.featureCategories.get(feature.category.id)!.push(feature);
});
this.state = { expandedPrivilegeControls: new Set() };
}
public render() {
@ -207,14 +214,14 @@ export class FeatureTable extends Component<Props, {}> {
const renderFeatureMarkup = (
buttonContent: EuiAccordionProps['buttonContent'],
extraAction: EuiAccordionProps['extraAction'],
warningIcon: JSX.Element
infoIcon: JSX.Element
) => {
const { canCustomizeSubFeaturePrivileges } = this.props;
const hasSubFeaturePrivileges = feature.getSubFeaturePrivileges().length > 0;
return (
<EuiFlexGroup gutterSize="s" alignItems="center">
<EuiFlexItem grow={false}>{warningIcon}</EuiFlexItem>
<EuiFlexItem grow={false}>{infoIcon}</EuiFlexItem>
<EuiFlexItem>
<EuiAccordion
id={`featurePrivilegeControls_${feature.id}`}
@ -227,6 +234,17 @@ export class FeatureTable extends Component<Props, {}> {
arrowDisplay={
canCustomizeSubFeaturePrivileges && hasSubFeaturePrivileges ? 'left' : 'none'
}
onToggle={(isOpen: boolean) => {
if (isOpen) {
this.state.expandedPrivilegeControls.add(feature.id);
} else {
this.state.expandedPrivilegeControls.delete(feature.id);
}
this.setState({
expandedPrivilegeControls: this.state.expandedPrivilegeControls,
});
}}
>
<div className="subFeaturePrivilegeExpandedRegion">
<FeatureTableExpandedRow
@ -292,17 +310,21 @@ export class FeatureTable extends Component<Props, {}> {
isDisabled: this.props.disabled ?? false,
});
let warningIcon = <EuiIconTip type="empty" content={null} />;
let infoIcon = <EuiIconTip type="empty" content={null} />;
const arePrivilegeControlsCollapsed = !this.state.expandedPrivilegeControls.has(feature.id);
if (
arePrivilegeControlsCollapsed &&
this.props.privilegeCalculator.hasCustomizedSubFeaturePrivileges(
feature.id,
this.props.privilegeIndex,
this.props.allSpacesSelected
)
) {
warningIcon = (
infoIcon = (
<EuiIconTip
type="alert"
type="iInCircle"
content={
<FormattedMessage
id="xpack.security.management.editRole.featureTable.privilegeCustomizationTooltip"
@ -345,7 +367,7 @@ export class FeatureTable extends Component<Props, {}> {
/>
);
return renderFeatureMarkup(buttonContent, extraAction, warningIcon);
return renderFeatureMarkup(buttonContent, extraAction, infoIcon);
};
private onChange = (featureId: string) => (featurePrivilegeId: string) => {