RedHatInsights/insights-rbac-ui

View on GitHub
src/smart-components/role/roles.js

Summary

Maintainability
F
3 days
Test Coverage
import React, { useState, useEffect, useContext, useRef, Suspense } from 'react';
import { useIntl } from 'react-intl';
import { shallowEqual, useSelector, useDispatch } from 'react-redux';
import { Outlet, useLocation, useNavigate } from 'react-router-dom';
import { cellWidth, compoundExpand, nowrap, sortable } from '@patternfly/react-table';
import { Button, Stack, StackItem } from '@patternfly/react-core';
import { useScreenSize, isSmallScreen } from '@redhat-cloud-services/frontend-components/useScreenSize';
import { useChrome } from '@redhat-cloud-services/frontend-components/useChrome';
import Section from '@redhat-cloud-services/frontend-components/Section';
import { createRows } from './role-table-helpers';
import { getBackRoute, mappedProps, removeQueryParams } from '../../helpers/shared/helpers';
import { fetchRolesWithPolicies } from '../../redux/actions/role-actions';
import { TopToolbar, TopToolbarTitle } from '../../presentational-components/shared/top-toolbar';
import { TableToolbarView } from '../../presentational-components/shared/table-toolbar-view';
import PermissionsContext from '../../utilities/permissions-context';
import {
  syncDefaultPaginationWithUrl,
  applyPaginationToUrl,
  isPaginationPresentInUrl,
  defaultAdminSettings,
  defaultSettings,
} from '../../helpers/shared/pagination';
import { syncDefaultFiltersWithUrl, applyFiltersToUrl, areFiltersPresentInUrl } from '../../helpers/shared/filters';
import RoleRowWrapper from './role-row-wrapper';
import AppLink, { mergeToBasename } from '../../presentational-components/shared/AppLink';
import messages from '../../Messages';
import paths from '../../utilities/pathnames';
import './roles.scss';
import pathnames from '../../utilities/pathnames';
import { addRolesToGroup, fetchAdminGroup } from '../../redux/actions/group-actions';

const Roles = () => {
  const { orgAdmin, userAccessAdministrator } = useContext(PermissionsContext);
  const [selectedRows, setSelectedRows] = useState([]);
  const intl = useIntl();
  const dispatch = useDispatch();
  const textFilterRef = useRef(null);
  const navigate = useNavigate();
  const location = useLocation();
  const screenSize = useScreenSize();
  const chrome = useChrome();

  const { roles, filters, pagination, isLoading, adminGroup } = useSelector(
    ({
      roleReducer: {
        roles: { data, filters, pagination },
        isLoading,
      },
      groupReducer: { adminGroup },
    }) => ({
      adminGroup,
      roles: data,
      filters,
      pagination: {
        limit: pagination.limit ?? (orgAdmin ? defaultAdminSettings : defaultSettings).limit,
        offset: pagination.offset ?? (orgAdmin ? defaultAdminSettings : defaultSettings).offset,
        count: pagination.count,
        redirected: pagination.redirected,
      },
      isLoading,
    }),
    shallowEqual
  );

  const columns = [
    { title: intl.formatMessage(messages.name), key: 'display_name', transforms: [cellWidth(20), sortable] },
    { title: intl.formatMessage(messages.description) },
    { title: intl.formatMessage(messages.groups), cellTransforms: [compoundExpand], transforms: [nowrap] },
    { title: intl.formatMessage(messages.permissions), cellTransforms: [compoundExpand], transforms: [nowrap] },
    { title: intl.formatMessage(messages.lastModified), key: 'modified', transforms: [nowrap, sortable] },
  ];
  const fetchData = (options) => dispatch(fetchRolesWithPolicies({ ...options, usesMetaInURL: true, chrome }));

  const isSelectable = orgAdmin || userAccessAdministrator;
  const [filterValue, setFilterValue] = useState(filters.display_name || '');
  const [sortByState, setSortByState] = useState({ index: Number(isSelectable), direction: 'asc' });
  const [expanded, setExpanded] = useState({});
  const [removeRolesList, setRemoveRolesList] = useState([]);
  const [selectedAddRoles, setSelectedAddRoles] = useState([]);

  const orderBy = `${sortByState?.direction === 'desc' ? '-' : ''}${columns[sortByState?.index - Number(isSelectable)].key}`;

  useEffect(() => {
    applyPaginationToUrl(location, navigate, pagination.limit, pagination.offset);
  }, [pagination.offset, pagination.limit, pagination.count, pagination.redirected]);

  useEffect(() => {
    applyFiltersToUrl(location, navigate, { display_name: filterValue });
  }, [filterValue]);

  useEffect(() => {
    const { limit, offset } = syncDefaultPaginationWithUrl(location, navigate, pagination);
    const { display_name } = syncDefaultFiltersWithUrl(location, navigate, ['display_name'], { display_name: filterValue });
    setFilterValue(display_name);
    chrome.appNavClick({ id: 'roles', secondaryNav: true });
    dispatch(fetchAdminGroup({ chrome }));
    fetchData({ limit, offset, orderBy, filters: { display_name } });
  }, []);

  useEffect(() => {
    if (!location.pathname.includes('detail')) {
      isPaginationPresentInUrl(location) || applyPaginationToUrl(location, navigate, pagination.limit, pagination.offset);
      filterValue?.length > 0 &&
        !areFiltersPresentInUrl(location, ['display_name']) &&
        syncDefaultFiltersWithUrl(location, navigate, ['display_name'], { display_name: filterValue });
    } else {
      removeQueryParams(location, navigate);
    }
  }, [location.pathname]);

  const actionResolver = (row) =>
    row.compoundParent
      ? []
      : [
          {
            title: intl.formatMessage(messages.edit),
            onClick: (_event, _rowId, role) => navigate(mergeToBasename(paths['edit-role'].link.replace(':roleId', role.uuid))),
          },
          {
            title: intl.formatMessage(messages.delete),
            onClick: (_event, _rowId, role) => {
              setRemoveRolesList([role]);
              navigate(mergeToBasename(paths['remove-role'].link.replace(':roleId', role.uuid)));
            },
          },
        ];

  const toolbarButtons = () =>
    orgAdmin || userAccessAdministrator
      ? [
          <AppLink to={paths['add-role'].link} key="add-role" className="rbac-m-hide-on-sm">
            <Button ouiaId="create-role-button" variant="primary" aria-label="Create role">
              {intl.formatMessage(messages.createRole)}
            </Button>
          </AppLink>,
          ...(isSmallScreen(screenSize)
            ? [
                {
                  label: intl.formatMessage(messages.createRole),
                  onClick: () => navigate(mergeToBasename(paths['add-role'].link)),
                },
              ]
            : []),
          {
            label: intl.formatMessage(messages.edit),
            props: {
              isDisabled: !(selectedRows.length === 1),
            },
            onClick: () => navigate(mergeToBasename(paths['edit-role'].link.replace(':roleId', selectedRows[0].uuid))),
          },
          {
            label: intl.formatMessage(messages.delete),
            props: {
              isDisabled: !selectedRows.length > 0,
            },
            onClick: () => {
              setRemoveRolesList(selectedRows);
              navigate(
                mergeToBasename(
                  paths['remove-role'].link.replace(
                    ':roleId',
                    selectedRows.map(({ uuid }) => uuid)
                  )
                )
              );
            },
          },
        ]
      : [];

  const setCheckedItems = (newSelection) => {
    setSelectedRows((rows) =>
      newSelection(rows)
        .filter(({ platform_default: isPlatformDefault, admin_default: isAdminDefault, system }) => !(isPlatformDefault || isAdminDefault || system))
        .map(({ uuid, name }) => ({ uuid, label: name }))
    );
  };

  const onExpand = (_event, _rowIndex, colIndex, isOpen, rowData) =>
    setExpanded({ ...expanded, [rowData.uuid]: isOpen ? -1 : colIndex + Number(!isSelectable) });

  const rows = createRows(roles, selectedRows, intl, expanded, adminGroup);
  // used for (not) reseting the filters after submit
  const removingAllRows = pagination.count === removeRolesList.length;

  return (
    <Stack className="rbac-c-roles">
      <StackItem>
        <TopToolbar>
          <TopToolbarTitle title={intl.formatMessage(messages.roles)} />
        </TopToolbar>
      </StackItem>
      <StackItem>
        <Section type="content" id="tab-roles">
          <TableToolbarView
            isSelectable={isSelectable}
            isRowSelectable={(row) => !(row.platform_default || row.admin_default || row.system)}
            checkedRows={selectedRows}
            textFilterRef={textFilterRef}
            setCheckedItems={setCheckedItems}
            actionResolver={actionResolver}
            columns={columns}
            areActionsDisabled={({ system }) => !!system}
            rowWrapper={RoleRowWrapper}
            rows={rows}
            data={roles}
            filterValue={filterValue}
            fetchData={({ name, limit, offset }) => {
              applyFiltersToUrl(location, navigate, { display_name: name });
              return fetchData(mappedProps({ limit, offset, orderBy, filters: { display_name: name } }));
            }}
            setFilterValue={({ name = '' }) => setFilterValue(name)}
            isExpandable
            onExpand={onExpand}
            isLoading={!isLoading && roles?.length === 0 && filterValue?.length === 0 ? true : isLoading}
            pagination={pagination}
            ouiaId="roles-table"
            titlePlural={intl.formatMessage(messages.roles).toLowerCase()}
            titleSingular={intl.formatMessage(messages.role).toLowerCase()}
            toolbarButtons={toolbarButtons}
            filterPlaceholder={intl.formatMessage(messages.name).toLowerCase()}
            tableId="roles"
            sortBy={sortByState}
            onSort={(e, index, direction) => {
              const orderBy = `${direction === 'desc' ? '-' : ''}${columns[index - Number(isSelectable)].key}`;
              setSortByState({ index, direction });
              fetchData(
                mappedProps({
                  limit: pagination.limit,
                  offset: 0,
                  orderBy,
                  filters: { display_name: filterValue },
                })
              );
            }}
          />
          <Suspense>
            <Outlet
              context={{
                [pathnames['add-role'].path]: {
                  pagination,
                  filters: { display_name: filterValue },
                },
                [pathnames['remove-role'].path]: {
                  isLoading,
                  cancelRoute: getBackRoute(paths.roles.link, pagination, filters),
                  submitRoute: getBackRoute(paths.roles.link, { ...pagination, offset: 0 }, removingAllRows ? {} : filters),
                  afterSubmit: () => {
                    fetchData({ ...pagination, filters: removingAllRows ? {} : { display_name: filterValue }, offset: 0 }, true);
                    removingAllRows && setFilterValue('');
                    setSelectedRows([]);
                  },
                  setFilterValue,
                },
                [pathnames['edit-role'].path]: {
                  isLoading,
                  cancelRoute: getBackRoute(paths.roles.link, pagination, filters),
                  afterSubmit: () => {
                    fetchData({ ...pagination, offset: 0, filters: { display_name: filterValue } }, true);
                    setSelectedRows([]);
                  },
                },
                [pathnames['roles-add-group-roles'].path]: {
                  selectedRoles: selectedAddRoles,
                  setSelectedRoles: setSelectedAddRoles,
                  closeUrl: getBackRoute(paths.roles.link, pagination, filters),
                  addRolesToGroup: (groupId, roles) => dispatch(addRolesToGroup(groupId, roles)),
                  afterSubmit: () => {
                    fetchData({ ...pagination, offset: 0, filters: { display_name: filterValue } }, true);
                    setSelectedAddRoles([]);
                  },
                },
              }}
            />
          </Suspense>
        </Section>
      </StackItem>
    </Stack>
  );
};

export default Roles;