Skip to content

Commit

Permalink
[Multiple datasource] Adds datasource picker to Permissions tab (open…
Browse files Browse the repository at this point in the history
…search-project#1857)

* Adds datasource picker to Permissions tab

Signed-off-by: Darshit Chanpura <[email protected]>

* Finalizes changes for Permissions tab dataSource picker

Signed-off-by: Darshit Chanpura <[email protected]>

* Fixes Unit tests

Signed-off-by: Darshit Chanpura <[email protected]>

* Adds integration tests

Signed-off-by: Darshit Chanpura <[email protected]>

* Adds cypress test for Permission tab with remote data source

Signed-off-by: Darshit Chanpura <[email protected]>

* Completes cypress test for Permissions tab, fixes cypress test for Users tab and fixes a spelling error

Signed-off-by: Darshit Chanpura <[email protected]>

* Updates unit tests and reverts a change in cypress test

Signed-off-by: Darshit Chanpura <[email protected]>

* Addresses nits to make tests more robust

Signed-off-by: Darshit Chanpura <[email protected]>

* Fixes unit tests

Signed-off-by: Darshit Chanpura <[email protected]>

---------

Signed-off-by: Darshit Chanpura <[email protected]>
  • Loading branch information
DarshitChanpura authored Mar 29, 2024
1 parent 9bceddf commit be3e939
Show file tree
Hide file tree
Showing 9 changed files with 296 additions and 57 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,14 @@ import {
Query,
} from '@elastic/eui';
import { difference } from 'lodash';
import React, { Dispatch, ReactNode, SetStateAction, useCallback, useState } from 'react';
import React, {
Dispatch,
ReactNode,
SetStateAction,
useCallback,
useState,
useContext,
} from 'react';
import { AppDependencies } from '../../../types';
import { Action, DataObject, ActionGroupItem, ExpandedRowMapInterface } from '../../types';
import {
Expand All @@ -54,6 +61,9 @@ import { useDeleteConfirmState } from '../../utils/delete-confirm-modal-utils';
import { useContextMenuState } from '../../utils/context-menu';
import { generateResourceName } from '../../utils/resource-utils';
import { DocLinks } from '../../constants';
import { SecurityPluginTopNavMenu } from '../../top-nav-menu';
import { DataSourceContext } from '../../app-router';
import { createDataSourceQuery } from '../../../../utils/datasource-utils';

export function renderBooleanToCheckMark(value: boolean): React.ReactNode {
return value ? <EuiIcon type="check" /> : '';
Expand All @@ -77,7 +87,7 @@ export function toggleRowDetails(
});
}

export function renderRowExpanstionArrow(
export function renderRowExpansionArrow(
itemIdToExpandedRowMap: ExpandedRowMapInterface,
actionGroupDict: DataObject<ActionGroupItem>,
setItemIdToExpandedRowMap: Dispatch<SetStateAction<ExpandedRowMapInterface>>
Expand Down Expand Up @@ -129,7 +139,7 @@ function getColumns(
align: RIGHT_ALIGNMENT,
width: '40px',
isExpander: true,
render: renderRowExpanstionArrow(
render: renderRowExpansionArrow(
itemIdToExpandedRowMap,
actionGroupDict,
setItemIdToExpandedRowMap
Expand Down Expand Up @@ -182,6 +192,8 @@ export function PermissionList(props: AppDependencies) {
const [selection, setSelection] = React.useState<PermissionListingItem[]>([]);
const [itemIdToExpandedRowMap, setItemIdToExpandedRowMap] = useState<ExpandedRowMapInterface>({});

const { dataSource, setDataSource } = useContext(DataSourceContext)!;

// Modal state
const [editModal, setEditModal] = useState<ReactNode>(null);

Expand All @@ -194,7 +206,10 @@ export function PermissionList(props: AppDependencies) {
const fetchData = useCallback(async () => {
try {
setLoading(true);
const actionGroups = await fetchActionGroups(props.coreStart.http);
const actionGroups = await fetchActionGroups(
props.coreStart.http,
createDataSourceQuery(dataSource.id)
);
setActionGroupDict(actionGroups);
setPermissionList(await mergeAllPermissions(actionGroups));
} catch (e) {
Expand All @@ -203,16 +218,20 @@ export function PermissionList(props: AppDependencies) {
} finally {
setLoading(false);
}
}, [props.coreStart.http]);
}, [props.coreStart.http, dataSource.id]);

React.useEffect(() => {
fetchData();
}, [props.coreStart.http, fetchData]);
}, [props.coreStart.http, fetchData, dataSource.id]);

const handleDelete = async () => {
const groupsToDelete: string[] = selection.map((r) => r.name);
try {
await requestDeleteActionGroups(props.coreStart.http, groupsToDelete);
await requestDeleteActionGroups(
props.coreStart.http,
groupsToDelete,
createDataSourceQuery(dataSource.id)
);
setPermissionList(difference(permissionList, selection));
setSelection([]);
} catch (e) {
Expand Down Expand Up @@ -276,9 +295,12 @@ export function PermissionList(props: AppDependencies) {
handleClose={() => setEditModal(null)}
handleSave={async (groupName, allowedAction) => {
try {
await updateActionGroup(props.coreStart.http, groupName, {
allowed_actions: allowedAction,
});
await updateActionGroup(
props.coreStart.http,
groupName,
{ allowed_actions: allowedAction },
createDataSourceQuery(dataSource.id)
);
setEditModal(null);
fetchData();
addToast({
Expand All @@ -301,7 +323,11 @@ export function PermissionList(props: AppDependencies) {
};

const createActionGroupMenuItems = [
<EuiButtonEmpty key="create-from-blank" onClick={() => showEditModal('', Action.create, [])}>
<EuiButtonEmpty
key="create-from-blank"
id="create-from-blank"
onClick={() => showEditModal('', Action.create, [])}
>
Create from blank
</EuiButtonEmpty>,
<EuiButtonEmpty
Expand All @@ -328,6 +354,12 @@ export function PermissionList(props: AppDependencies) {

return (
<>
<SecurityPluginTopNavMenu
{...props}
dataSourcePickerReadOnly={false}
setDataSource={setDataSource}
selectedDataSource={dataSource}
/>
<EuiPageHeader>
<EuiTitle size="l">
<h1>Permissions</h1>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import {
PermissionList,
renderBooleanToCheckMark,
toggleRowDetails,
renderRowExpanstionArrow,
renderRowExpansionArrow,
} from '../permission-list';
import { EuiInMemoryTable, EuiButtonIcon } from '@elastic/eui';
import {
Expand Down Expand Up @@ -49,6 +49,11 @@ jest.mock('../../../utils/toast-utils', () => ({
useToastState: jest.fn().mockReturnValue([[], jest.fn(), jest.fn()]),
}));

jest.mock('react', () => ({
...jest.requireActual('react'),
useContext: jest.fn().mockReturnValue({ dataSource: { id: 'test' }, setDataSource: jest.fn() }), // Mock the useContext hook to return dummy datasource and setdatasource function
}));

describe('Permission list page ', () => {
const sampleActionGroup: PermissionListingItem = {
name: 'group',
Expand All @@ -73,15 +78,15 @@ describe('Permission list page ', () => {

describe('renderRowExpanstionArrow', () => {
it('should render down arrow when collapsed', () => {
const renderFunc = renderRowExpanstionArrow({}, {}, jest.fn());
const renderFunc = renderRowExpansionArrow({}, {}, jest.fn());
const Wrapper = () => <>{renderFunc(sampleActionGroup)}</>;
const component = shallow(<Wrapper />);

expect(component.find(EuiButtonIcon).prop('iconType')).toBe('arrowDown');
});

it('should render up arrow when expanded', () => {
const renderFunc = renderRowExpanstionArrow(
const renderFunc = renderRowExpansionArrow(
{ [sampleActionGroup.name]: sampleActionGroup },
{},
jest.fn()
Expand All @@ -94,6 +99,12 @@ describe('Permission list page ', () => {
});

describe('PermissionList', () => {
const mockCoreStart = {
http: 1,
};
const dataSourceQuery = {
dataSourceId: 'test',
};
it('render empty', () => {
const component = shallow(
<PermissionList
Expand All @@ -111,14 +122,14 @@ describe('Permission list page ', () => {
jest.spyOn(React, 'useEffect').mockImplementationOnce((f) => f());
shallow(
<PermissionList
coreStart={{} as any}
coreStart={mockCoreStart as any}
navigation={{} as any}
params={{} as any}
config={{} as any}
/>
);

expect(fetchActionGroups).toBeCalled();
expect(fetchActionGroups).toBeCalledWith(mockCoreStart.http, dataSourceQuery);
});

it('fetch data error', () => {
Expand Down Expand Up @@ -147,7 +158,7 @@ describe('Permission list page ', () => {
it('submit change', () => {
const component = shallow(
<PermissionList
coreStart={{} as any}
coreStart={mockCoreStart as any}
navigation={{} as any}
params={{} as any}
config={{} as any}
Expand All @@ -157,7 +168,12 @@ describe('Permission list page ', () => {
const submitFunc = component.find(PermissionEditModal).prop('handleSave');
submitFunc('group1', []);

expect(updateActionGroup).toBeCalled();
expect(updateActionGroup).toBeCalledWith(
mockCoreStart.http,
'group1',
{ allowed_actions: [] },
dataSourceQuery
);
});

it('submit change error', () => {
Expand Down Expand Up @@ -186,7 +202,7 @@ describe('Permission list page ', () => {
it('delete action group', (done) => {
shallow(
<PermissionList
coreStart={{} as any}
coreStart={mockCoreStart as any}
navigation={{} as any}
params={{} as any}
config={{} as any}
Expand All @@ -197,7 +213,7 @@ describe('Permission list page ', () => {
deleteFunc();

process.nextTick(() => {
expect(requestDeleteActionGroups).toBeCalled();
expect(requestDeleteActionGroups).toBeCalledWith(mockCoreStart.http, [], dataSourceQuery);
done();
});
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ exports[`Role list Render columns render Customization column 1`] = `
>
<EuiText
className="table-items"
data-test-subj="filter-reserved-action-groups"
>
Reserved
</EuiText>
Expand Down
34 changes: 25 additions & 9 deletions public/apps/configuration/utils/action-groups-utils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
* permissions and limitations under the License.
*/

import { HttpStart } from 'opensearch-dashboards/public';
import { HttpStart, HttpFetchQuery } from 'opensearch-dashboards/public';
import { map } from 'lodash';
import { API_ENDPOINT_ACTIONGROUPS, CLUSTER_PERMISSIONS, INDEX_PERMISSIONS } from '../constants';
import { DataObject, ActionGroupItem, ActionGroupUpdate, ObjectsMessage } from '../types';
Expand All @@ -29,10 +29,14 @@ export interface PermissionListingItem {
hasIndexPermission: boolean;
}

export async function fetchActionGroups(http: HttpStart): Promise<DataObject<ActionGroupItem>> {
export async function fetchActionGroups(
http: HttpStart,
query: HttpFetchQuery
): Promise<DataObject<ActionGroupItem>> {
const actiongroups = await httpGet<ObjectsMessage<ActionGroupItem>>({
http,
url: API_ENDPOINT_ACTIONGROUPS,
query,
});
return actiongroups.data;
}
Expand All @@ -50,8 +54,11 @@ export function transformActionGroupsToListingFormat(
}));
}

export async function fetchActionGroupListing(http: HttpStart): Promise<PermissionListingItem[]> {
return transformActionGroupsToListingFormat(await fetchActionGroups(http));
export async function fetchActionGroupListing(
http: HttpStart,
query: HttpFetchQuery
): Promise<PermissionListingItem[]> {
return transformActionGroupsToListingFormat(await fetchActionGroups(http, query));
}

function getClusterSinglePermissions(): PermissionListingItem[] {
Expand All @@ -76,8 +83,11 @@ function getIndexSinglePermissions(): PermissionListingItem[] {
}));
}

export async function getAllPermissionsListing(http: HttpStart): Promise<PermissionListingItem[]> {
return mergeAllPermissions(await fetchActionGroups(http));
export async function getAllPermissionsListing(
http: HttpStart,
query: HttpFetchQuery
): Promise<PermissionListingItem[]> {
return mergeAllPermissions(await fetchActionGroups(http, query));
}

export async function mergeAllPermissions(
Expand All @@ -91,17 +101,23 @@ export async function mergeAllPermissions(
export async function updateActionGroup(
http: HttpStart,
groupName: string,
updateObject: ActionGroupUpdate
updateObject: ActionGroupUpdate,
query: HttpFetchQuery
): Promise<ActionGroupUpdate> {
return await httpPost({
http,
url: getResourceUrl(API_ENDPOINT_ACTIONGROUPS, groupName),
body: updateObject,
query,
});
}

export async function requestDeleteActionGroups(http: HttpStart, groups: string[]) {
export async function requestDeleteActionGroups(
http: HttpStart,
groups: string[],
query: HttpFetchQuery
) {
for (const group of groups) {
await httpDelete({ http, url: getResourceUrl(API_ENDPOINT_ACTIONGROUPS, group) });
await httpDelete({ http, url: getResourceUrl(API_ENDPOINT_ACTIONGROUPS, group), query });
}
}
9 changes: 8 additions & 1 deletion public/apps/configuration/utils/display-utils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,14 @@ export function renderCustomization(reserved: boolean, props: UIProps) {
<EuiIcon type={reserved ? 'lock' : 'pencil'} />
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiText className={props.cssClassName}>{reserved ? 'Reserved' : 'Custom'}</EuiText>
<EuiText
className={props.cssClassName}
data-test-subj={
reserved ? 'filter-reserved-action-groups' : 'filter-custom-action-groups'
}
>
{reserved ? 'Reserved' : 'Custom'}
</EuiText>
</EuiFlexItem>
</EuiFlexGroup>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ exports[`Display utils Render Customization column when reserved = False 1`] = `
>
<EuiText
className="table-items"
data-test-subj="filter-custom-action-groups"
>
Custom
</EuiText>
Expand All @@ -39,6 +40,7 @@ exports[`Display utils Render Customization column when reserved = True 1`] = `
>
<EuiText
className="table-items"
data-test-subj="filter-reserved-action-groups"
>
Reserved
</EuiText>
Expand Down
Loading

0 comments on commit be3e939

Please sign in to comment.