Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
23 changes: 18 additions & 5 deletions public/apps/configuration/panels/role-edit/role-edit.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ import {
} from './tenant-panel';
import { RoleIndexPermissionStateClass, RoleTenantPermissionStateClass } from './types';
import { buildHashUrl, buildUrl } from '../../utils/url-builder';
import { ComboBoxOptions, ResourceType, Action } from '../../types';
import { ComboBoxOptions, ResourceType, Action, ActionGroupItem } from '../../types';
import {
useToastState,
createUnknownErrorToast,
Expand Down Expand Up @@ -106,12 +106,12 @@ export function RoleEdit(props: RoleEditDeps) {
}
}, [addToast, props.action, props.coreStart.http, props.sourceRoleName]);

const [actionGroups, setActionGroups] = useState<string[]>([]);
const [actionGroups, setActionGroups] = useState<Array<[string, ActionGroupItem]>>([]);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Just for my understanding, why are we needing this ActionGroupItem, or can we use the original Action here?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

If i'm understanding your question correctly I need the whole item here in order to tell whether it is a cluster/index one later on in order to filter them accordingly

React.useEffect(() => {
const fetchActionGroupNames = async () => {
try {
const actionGroupsObject = await fetchActionGroups(props.coreStart.http);
setActionGroups(Object.keys(actionGroupsObject));
setActionGroups(Object.entries(actionGroupsObject));
} catch (e) {
addToast(createUnknownErrorToast('actionGroup', 'load data'));
console.error(e);
Expand Down Expand Up @@ -167,12 +167,25 @@ export function RoleEdit(props: RoleEditDeps) {
const clusterWisePermissionOptions = [
{
label: 'Permission groups',
options: actionGroups.map(stringToComboBoxOption),
options: actionGroups
.filter((actionGroup) => actionGroup[1].type === 'cluster')
.map((actionGroup) => actionGroup[0])
.map(stringToComboBoxOption),
},
{
label: 'Cluster permissions',
options: CLUSTER_PERMISSIONS.map(stringToComboBoxOption),
},
];

const indexWisePermissionOptions = [
{
label: 'Permission groups',
options: actionGroups
.filter((actionGroup) => actionGroup[1].type === 'index')
.map((actionGroup) => actionGroup[0])
.map(stringToComboBoxOption),
},
{
label: 'Index permissions',
options: INDEX_PERMISSIONS.map(stringToComboBoxOption),
Expand Down Expand Up @@ -219,7 +232,7 @@ export function RoleEdit(props: RoleEditDeps) {
<IndexPermissionPanel
state={roleIndexPermission}
setState={setRoleIndexPermission}
optionUniverse={clusterWisePermissionOptions}
optionUniverse={indexWisePermissionOptions}
/>
<EuiSpacer size="m" />
<TenantPanel
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
/*
* Copyright OpenSearch Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* or in the "license" file accompanying this file. This file is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/

import React from 'react';
import { ClusterPermissionPanel } from '../cluster-permission-panel';
import { RoleEdit } from '../role-edit';
import { ActionGroupItem } from '../../../types';
import { fetchActionGroups } from '../../../utils/action-groups-utils';

import { render, waitFor } from '@testing-library/react';

import { act } from 'react-dom/test-utils';
import { IndexPermissionPanel } from '../index-permission-panel';
import { CLUSTER_PERMISSIONS, INDEX_PERMISSIONS } from '../../../constants';

jest.mock('../../../utils/role-detail-utils', () => ({
getRoleDetail: jest.fn().mockReturnValue({
cluster_permissions: [],
index_permissions: [],
tenant_permissions: [],
reserved: false,
}),
updateRole: jest.fn(),
}));
jest.mock('../../../utils/action-groups-utils');

jest.mock('../cluster-permission-panel', () => ({
ClusterPermissionPanel: jest.fn(() => null) as jest.Mock,
}));

jest.mock('../index-permission-panel', () => ({
IndexPermissionPanel: jest.fn(() => null) as jest.Mock,
}));

describe('Role edit filtering', () => {
const sampleSourceRole = 'role';
const mockCoreStart = {
http: 1,
};

(fetchActionGroups as jest.Mock).mockResolvedValue({
data_access: {
reserved: true,
hidden: false,
allowed_actions: ['indices:data/*', 'crud'],
type: 'index',
description: 'Allow all read/write operations on data',
static: true,
},
cluster_manage_pipelines: {
reserved: true,
hidden: false,
allowed_actions: ['cluster:admin/ingest/pipeline/*'],
type: 'cluster',
description: 'Manage pipelines',
static: true,
},
});

it('basic cluster permission panel rendering', async () => {
const action = 'create';
const buildBreadcrumbs = jest.fn();

render(
<RoleEdit
action={action}
sourceRoleName={sampleSourceRole}
buildBreadcrumbs={buildBreadcrumbs}
coreStart={mockCoreStart as any}
navigation={{} as any}
params={{} as any}
config={{} as any}
/>
);

await act(async () => {
await waitFor(() => {
expect(ClusterPermissionPanel).toHaveBeenCalled();
});
});

const lastCallArgs =
ClusterPermissionPanel.mock.calls[ClusterPermissionPanel.mock.calls.length - 1];
const [props] = lastCallArgs;

// Cluster Permission Panel props is filtered to action groups with type cluster, and only the cluster permission constants
expect(props.optionUniverse).toEqual([
{
label: 'Permission groups',
options: [
{
label: 'cluster_manage_pipelines',
},
],
},
{
label: 'Cluster permissions',
options: CLUSTER_PERMISSIONS.map((x) => {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Much more robust and less lines of code :)

return { label: x };
}),
},
]);
});

it('basic index permission panel rendering', async () => {
const action = 'create';
const buildBreadcrumbs = jest.fn();

render(
<RoleEdit
action={action}
sourceRoleName={sampleSourceRole}
buildBreadcrumbs={buildBreadcrumbs}
coreStart={mockCoreStart as any}
navigation={{} as any}
params={{} as any}
config={{} as any}
/>
);

await act(async () => {
await waitFor(() => {
expect(IndexPermissionPanel).toHaveBeenCalled();
});
});

const lastCallArgs =
IndexPermissionPanel.mock.calls[IndexPermissionPanel.mock.calls.length - 1];
const [props] = lastCallArgs;

// Index Permission Panel props is filtered to action groups with type index, and only the index permission constants
expect(props.optionUniverse).toEqual([
{
label: 'Permission groups',
options: [
{
label: 'data_access',
},
],
},
{
label: 'Index permissions',
options: INDEX_PERMISSIONS.map((x) => {
return { label: x };
}),
},
]);
});
});