RedHatInsights/insights-rbac-ui

View on GitHub
src/smart-components/myUserAccess/MUARolesTable.js

Summary

Maintainability
A
0 mins
Test Coverage
import React, { Fragment, useCallback, useEffect, useState, lazy, Suspense } from 'react';
import PropTypes from 'prop-types';
import debounce from 'lodash/debounce';
import SkeletonTable from '@patternfly/react-component-groups/dist/dynamic/SkeletonTable';
import { TableToolbarView } from '../../presentational-components/shared/table-toolbar-view';
import { shallowEqual, useDispatch, useSelector } from 'react-redux';
import { useIntl } from 'react-intl';
import { fetchRoles, fetchRoleForPrincipal } from '../../redux/actions/role-actions';
import messages from '../../Messages';

const ResourceDefinitionsModal = lazy(() => import('./ResourceDefinitionsModal'));

import { TableVariant, compoundExpand, cellWidth, sortable } from '@patternfly/react-table';
import { Table, TableHeader, TableBody } from '@patternfly/react-table/deprecated';
import ResourceDefinitionsLink from '../../presentational-components/myUserAccess/ResourceDefinitionsLink';

const MUARolesTable = ({ filters, setFilters, apps, showResourceDefinitions }) => {
  const intl = useIntl();
  const [expanded, setExpanded] = useState({});
  const [{ rdOpen, rdPermission, resourceDefinitions }, setRdConfig] = useState({ rdOpen: false });

  const columns = [
    {
      title: intl.formatMessage(messages.roles),
      key: 'display_name',
      transforms: [sortable],
    },
    { title: intl.formatMessage(messages.description) },
    {
      title: intl.formatMessage(messages.permissions),
      cellTransforms: [compoundExpand, cellWidth(20)],
    },
  ];

  const compoundPermissionsCells = [
    intl.formatMessage(messages.application),
    intl.formatMessage(messages.resourceType),
    intl.formatMessage(messages.operation),
    ...(showResourceDefinitions ? [intl.formatMessage(messages.resourceDefinitions)] : []),
  ];

  const { roles, isLoading, rolesWithAccess } = useSelector(
    ({ roleReducer: { roles, isLoading, rolesWithAccess } }) => ({
      roles,
      isLoading,
      rolesWithAccess,
    }),
    shallowEqual
  );

  const [sortByState, setSortByState] = useState({ index: 0, direction: 'asc' });
  const orderBy = `${sortByState?.direction === 'desc' ? '-' : ''}${columns[sortByState?.index].key}`;
  const dispatch = useDispatch();

  useEffect(() => {
    dispatch(fetchRoles({ limit: 20, offset: 0, orderBy, scope: 'principal', application: apps.join(',') }));
  }, []);

  const createRows = (data) => {
    return data?.reduce(
      (acc, { uuid, display_name, name, description, accessCount }, i) => [
        ...acc,
        {
          uuid,
          cells: [
            { title: display_name || name, props: { component: 'th', isOpen: false } },
            { title: description, props: { isOpen: false } },
            { title: accessCount, props: { isOpen: expanded[uuid] === 2 } },
          ],
        },
        {
          uuid: `${uuid}-groups`,
          parent: 2 * i,
          compoundParent: 2,
          cells: [
            {
              props: { colSpan: 4, className: 'pf-m-no-padding' },
              title: rolesWithAccess?.[uuid] ? (
                <Table
                  ouiaId="permissions-in-role-nested-table"
                  aria-label="Simple Table"
                  borders={false}
                  variant={TableVariant.compact}
                  cells={compoundPermissionsCells}
                  rows={rolesWithAccess[uuid].access.map((access) => ({
                    cells: [
                      ...access.permission.split(':'),
                      ...(showResourceDefinitions
                        ? [
                            <Fragment key="rd">
                              <ResourceDefinitionsLink
                                onClick={() =>
                                  setRdConfig({
                                    rdOpen: true,
                                    rdPermission: access.permission,
                                    resourceDefinitions: access.resourceDefinitions,
                                  })
                                }
                                access={access}
                              />
                            </Fragment>,
                          ]
                        : []),
                    ],
                  }))}
                >
                  <TableHeader />
                  <TableBody />
                </Table>
              ) : (
                <SkeletonTable columns={compoundPermissionsCells} rows={accessCount} variant={TableVariant.compact} />
              ),
            },
          ],
        },
      ],
      []
    );
  };

  let debouncedFetch = useCallback(
    debounce((limit, offset, name, application, permission, orderBy) => {
      const applicationParam = application?.length > 0 ? application : apps;
      return dispatch(fetchRoles({ limit, offset, scope: 'principal', orderBy, name, application: applicationParam.join(','), permission }));
    }, 800),
    []
  );

  const onExpand = (_event, _rowIndex, colIndex, isOpen, rowData) => {
    if (!isOpen) {
      setExpanded((expanded) => ({ ...expanded, [rowData.uuid]: colIndex }));
      // Permissions
      if (colIndex === 2) {
        dispatch(fetchRoleForPrincipal(rowData.uuid));
      }
    } else {
      setExpanded((expanded) => ({ ...expanded, [rowData.uuid]: -1 }));
    }
  };

  return (
    <Fragment>
      <TableToolbarView
        filters={filters}
        columns={columns}
        rows={createRows(roles.data)}
        data={roles.data}
        isCompact={false}
        isExpandable={true}
        onExpand={onExpand}
        ouiaId="roles-table"
        fetchData={({ limit, offset, name, application, permission }) => {
          debouncedFetch(limit, offset, name, application, permission, orderBy);
        }}
        sortBy={sortByState}
        onSort={(e, index, direction) => {
          const orderBy = `${direction === 'desc' ? '-' : ''}${columns[index].key}`;
          setSortByState({ index, direction });
          dispatch(
            fetchRoles({
              offset: 0,
              orderBy,
              ...(filters?.length > 0
                ? {
                    ...filters.reduce(
                      (acc, curr) => ({
                        ...acc,
                        [curr.key]: curr.value,
                      }),
                      {}
                    ),
                  }
                : { name: '', application: [] }),
            })
          );
        }}
        emptyFilters={{ name: '', application: [] }}
        setFilterValue={setFilters}
        isLoading={isLoading}
        pagination={roles.meta}
        filterPlaceholder="role name"
        titlePlural="roles"
        titleSingular="role"
        tableId="mua-roles"
      />
      <Suspense fallback={<Fragment />}>
        {rdOpen && (
          <ResourceDefinitionsModal
            resourceDefinitions={resourceDefinitions}
            isOpen={rdOpen}
            handleClose={() => setRdConfig({ rdOpen: false })}
            permission={rdPermission}
          />
        )}
      </Suspense>
    </Fragment>
  );
};

MUARolesTable.propTypes = {
  fetchRoles: PropTypes.func,
  fetchRoleForPrincipal: PropTypes.func,
  fetchUsers: PropTypes.func,
  roles: PropTypes.object,
  isLoading: PropTypes.bool,
  rolesWithAccess: PropTypes.object,
  filters: PropTypes.arrayOf(PropTypes.object).isRequired,
  setFilters: PropTypes.func.isRequired,
  apps: PropTypes.arrayOf(PropTypes.string).isRequired,
  showResourceDefinitions: PropTypes.bool,
};

export default MUARolesTable;