console: develop carded table for role based allow list permission

PR-URL: https://github.com/hasura/graphql-engine-mono/pull/5648
Co-authored-by: Daniele Cammareri <5709409+dancamma@users.noreply.github.com>
GitOrigin-RevId: c1cdd6899e5c50032e097496696097874f5abfd5
This commit is contained in:
Varun Choudhary 2022-09-01 13:33:18 +05:30 committed by hasura-bot
parent 00aeb57adf
commit e0c9e2802c
5 changed files with 277 additions and 0 deletions

View File

@ -0,0 +1,25 @@
import React from 'react';
import { Story, Meta } from '@storybook/react';
import { ReactQueryDecorator } from '@/storybook/decorators/react-query';
import {
AllowListPermissions,
AllowListPermissionsTabProps,
} from './AllowListPermissions';
import { handlers } from '../../hooks/AllowListPermissions/mock/handlers.mocks';
export default {
title: 'Features/Allow List Permission/CardedTable',
component: AllowListPermissions,
decorators: [ReactQueryDecorator()],
parameters: {
msw: handlers(),
},
} as Meta;
export const Default: Story<AllowListPermissionsTabProps> = args => {
return <AllowListPermissions {...args} />;
};
Default.args = {
collectionName: 'allowed-queries',
};

View File

@ -0,0 +1,134 @@
import React from 'react';
import { Switch } from '@/new-components/Switch';
import { FaCheck } from 'react-icons/fa';
import { useSetRoleToAllowListPermission } from '../../hooks/AllowListPermissions/useSetRoleToAllowListPermissions';
import { useEnabledRolesFromAllowListState } from '../../hooks/AllowListPermissions/useEnabledRolesFromAllowListState';
export interface AllowListPermissionsTabProps {
collectionName: string;
}
export const AllowListPermissions: React.FC<AllowListPermissionsTabProps> = ({
collectionName,
}) => {
const {
allAvailableRoles,
newRoles,
setNewRoles,
enabledRoles,
setEnabledRoles,
} = useEnabledRolesFromAllowListState(collectionName);
const { setRoleToAllowListPermission } =
useSetRoleToAllowListPermission(collectionName);
const handleToggle = (roleName: string, index: number) => {
let newEnabledRoles = [];
// add roleName to enabledRoles, remove duplicates
if (enabledRoles.includes(roleName)) {
newEnabledRoles = Array.from(
new Set(enabledRoles.filter(role => role !== roleName))
);
setEnabledRoles(newEnabledRoles);
} else {
newEnabledRoles = Array.from(new Set([...enabledRoles, roleName]));
// remove enabled role from newRoles
setNewRoles(
newRoles.length > 1
? newRoles.filter(role => role !== newRoles[index])
: newRoles
);
setEnabledRoles(newEnabledRoles);
}
setRoleToAllowListPermission(newEnabledRoles);
};
const handleNewRole = (value: string, index: number) => {
const newAddedRoles = [...newRoles];
newAddedRoles[index] = value;
// if last item is not an empty string, add empty string as last index
if (newAddedRoles[newAddedRoles.length - 1] !== '') {
newAddedRoles.push('');
}
// drop last one
if (newAddedRoles[newAddedRoles.length - 2] === '') {
newAddedRoles.pop();
}
setNewRoles(newAddedRoles);
};
return (
<>
<div className="p-md">
<div className="overflow-x-auto border border-gray-300 rounded mb-md">
<table className="min-w-full divide-y divide-gray-300 text-left">
<thead>
<tr className="divide-x divide-gray-300">
<th className="w-0 bg-gray-50 px-sm py-xs text-sm font-semibold text-muted uppercase tracking-wider">
Role
</th>
<th className="text-center bg-gray-50 px-sm py-xs text-sm font-semibold text-muted uppercase tracking-wider">
Access
</th>
</tr>
</thead>
<tbody className="bg-white divide-y divide-gray-300">
<tr className="group divide-x divide-gray-300">
<td className="w-0 bg-gray-50 p-sm font-semibold text-muted">
<div className="flex items-center">
<label className="flex items-center">admin</label>
</div>
</td>
<td className="text-center p-sm whitespace-nowrap cursor-not-allowed">
<FaCheck className="text-green-600" />
</td>
</tr>
{allAvailableRoles.map((roleName, index) => (
<tr className="divide-x divide-gray-300">
<td className="w-0 bg-gray-50 p-sm font-semibold text-muted">
<div className="flex items-center">
<label>{roleName}</label>
</div>
</td>
<td className="group relative text-center p-sm whitespace-nowrap cursor-pointer">
<Switch
checked={enabledRoles?.includes(roleName)}
onCheckedChange={() => handleToggle(roleName, index)}
/>
</td>
</tr>
))}
{newRoles.map((newRole, index) => (
<tr className="divide-x divide-gray-300">
<td className="w-0 bg-gray-50 p-2 font-semibold text-muted">
<input
x-model="newRole"
type="text"
className="block w-full h-input min-w-max shadow-sm rounded border border-gray-300 hover:border-gray-400 focus:outline-0 focus:ring-2 focus:ring-yellow-200 focus:border-yellow-400"
placeholder="Create New Role..."
value={newRole}
onChange={e => handleNewRole(e.target.value, index)}
/>
</td>
<td className="group relative text-center p-sm whitespace-nowrap cursor-pointer">
<Switch
checked={enabledRoles.includes(newRole)}
onCheckedChange={
newRole !== ''
? () => handleToggle(newRole, index)
: () => {}
}
/>
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
</>
);
};

View File

@ -0,0 +1,24 @@
import React, { useEffect } from 'react';
import { useRoles } from '@/features/MetadataAPI';
import { useEnabledRolesFromAllowList } from './useEnabledRolesFromAllowList';
export const useEnabledRolesFromAllowListState = (collectionName: string) => {
const { data: allAvailableRoles } = useRoles();
const { data: allowListRoles } = useEnabledRolesFromAllowList(collectionName);
const [enabledRoles, setEnabledRoles] = React.useState<string[]>([]);
const [newRoles, setNewRoles] = React.useState<string[]>(['']);
useEffect(() => {
if (allowListRoles && allowListRoles !== enabledRoles) {
setEnabledRoles(allowListRoles);
}
}, [allowListRoles]);
return {
allAvailableRoles,
newRoles,
setNewRoles,
enabledRoles,
setEnabledRoles,
};
};

View File

@ -0,0 +1,92 @@
import {
REQUEST_SUCCESS,
updateSchemaInfo,
} from '@/components/Services/Data/DataActions';
import {
allowedMetadataTypes,
useMetadata,
useMetadataMigration,
} from '@/features/MetadataAPI';
import { useDispatch } from 'react-redux';
import { AnyAction } from 'redux';
import { ReduxState } from '@/types';
import { ThunkDispatch } from 'redux-thunk';
import { useFireNotification } from '@/new-components/Notifications';
export const useSetRoleToAllowListPermission = (collectionName: string) => {
const { fireNotification } = useFireNotification();
const { data: metadata } = useMetadata();
const dispatch: ThunkDispatch<ReduxState, unknown, AnyAction> = useDispatch();
// to check if collection is present in allowList
const isAllowList = metadata?.metadata.allowlist?.find(
qs => qs.collection === collectionName
);
const mutation = useMetadataMigration({
onSuccess: () => {
dispatch({ type: REQUEST_SUCCESS });
dispatch(updateSchemaInfo()).then(() => {
fireNotification({
title: 'Success!',
message: 'Permission added',
type: 'success',
});
});
},
onError: (error: Error) => {
fireNotification({
title: 'Error',
message: error?.message ?? 'Error while adding permission',
type: 'error',
});
},
});
const setRoleToAllowListPermission = (roles: string[]): void => {
// drop collection from allow list if we are disabling the last role
if (roles.length === 0) {
const type: allowedMetadataTypes = 'drop_collection_from_allowlist';
mutation.mutate({
query: {
type,
args: {
collection: collectionName,
},
},
});
} else if (!isAllowList) {
// add collection to allow list and add roles if we are enabling the 1st role
const type: allowedMetadataTypes = 'add_collection_to_allowlist';
mutation.mutate({
query: {
type,
args: {
collection: collectionName,
scope: {
global: false,
roles,
},
},
},
});
} else {
const type: allowedMetadataTypes =
'update_scope_of_collection_in_allowlist';
mutation.mutate({
query: {
type,
args: {
collection: collectionName,
scope: {
global: false,
roles,
},
},
},
});
}
};
return { setRoleToAllowListPermission };
};

View File

@ -40,6 +40,8 @@ export const allowedMetadataTypesArr = [
'delete_remote_schema_remote_relationship', 'delete_remote_schema_remote_relationship',
'add_remote_schema', 'add_remote_schema',
'update_scope_of_collection_in_allowlist', 'update_scope_of_collection_in_allowlist',
'drop_collection_from_allowlist',
'add_collection_from_allowlist',
'bulk', 'bulk',
] as const; ] as const;