mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
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:
parent
1455107797
commit
206de80d2c
2 changed files with 368 additions and 8 deletions
|
@ -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');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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) => {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue