RedHatInsights/insights-rbac-ui

View on GitHub
src/smart-components/group/member/group-members.js

Summary

Maintainability
D
1 day
Test Coverage
import { nowrap } from '@patternfly/react-table';
import React, { Fragment, useState, useEffect, useContext, useRef, Suspense } from 'react';
import { FormattedMessage, useIntl } from 'react-intl';
import { useDispatch, useSelector, shallowEqual } from 'react-redux';
import { Outlet, useParams } from 'react-router-dom';
import { Button, Card, CardBody, Text, TextVariants, Bullseye, TextContent, ButtonVariant } from '@patternfly/react-core';
import { useChrome } from '@redhat-cloud-services/frontend-components/useChrome';
import Section from '@redhat-cloud-services/frontend-components/Section';
import { TableToolbarView } from '../../../presentational-components/shared/table-toolbar-view';
import WarningModal from '@patternfly/react-component-groups/dist/dynamic/WarningModal';
import { createRows } from './member-table-helpers';
import { fetchMembersForGroup, removeMembersFromGroup, fetchGroups, fetchGroup } from '../../../redux/actions/group-actions';
import { getBackRoute } from '../../../helpers/shared/helpers';
import UsersRow from '../../../presentational-components/shared/UsersRow';
import PermissionsContext from '../../../utilities/permissions-context';
import AppLink from '../../../presentational-components/shared/AppLink';
import useAppNavigate from '../../../hooks/useAppNavigate';
import messages from '../../../Messages';
import pathnames from '../../../utilities/pathnames';

const selector = ({ groupReducer: { selectedGroup, groups } }) => ({
  members: selectedGroup.members.data,
  pagination: selectedGroup.members.meta,
  groupsPagination: groups.pagination || groups.meta,
  groupsFilters: groups.filters,
  group: selectedGroup,
  adminDefault: selectedGroup.admin_default,
  platformDefault: selectedGroup.platform_default,
  isLoading: selectedGroup.members.isLoading,
});

const removeModalText = (name, group, plural) => (
  <FormattedMessage
    {...(plural ? messages.removeMembersText : messages.removeMemberText)}
    values={{
      b: (text) => <b>{text}</b>,
      name,
      group,
    }}
  />
);
const GroupMembers = () => {
  const intl = useIntl();
  const chrome = useChrome();
  const [filterValue, setFilterValue] = useState('');
  const [selectedMembers, setSelectedMembers] = useState([]);
  const [showRemoveModal, setShowRemoveModal] = useState(false);
  const [confirmDelete, setConfirmDelete] = useState(() => null);
  const [deleteInfo, setDeleteInfo] = useState({});

  const { groupId } = useParams();
  const { members, pagination, groupsPagination, groupsFilters, isLoading, adminDefault, platformDefault, group } = useSelector(
    selector,
    shallowEqual
  );
  const { userAccessAdministrator, orgAdmin } = useContext(PermissionsContext);
  const hasPermissions = useRef(orgAdmin || userAccessAdministrator);

  const columns = [
    { title: intl.formatMessage(messages.status), transforms: [nowrap] },
    { title: intl.formatMessage(messages.username) },
    { title: intl.formatMessage(messages.email) },
    { title: intl.formatMessage(messages.lastName) },
    { title: intl.formatMessage(messages.firstName) },
  ];

  const dispatch = useDispatch();

  const fetchData = (usernames, options = pagination) => {
    dispatch(fetchMembersForGroup(groupId, usernames, options));
  };

  useEffect(() => {
    fetchData();
  }, []);

  useEffect(() => {
    hasPermissions.current = orgAdmin || userAccessAdministrator;
  }, [orgAdmin, userAccessAdministrator]);

  const setCheckedMembers = (newSelection) => {
    setSelectedMembers((members) => newSelection(members));
  };

  const removeMembers = (userNames) => {
    return dispatch(removeMembersFromGroup(groupId, userNames)).then(() => {
      setSelectedMembers([]);
      fetchData(undefined, { ...pagination, offset: 0 });
      dispatch(fetchGroups({ usesMetaInURL: true, chrome }));
    });
  };

  const actionResolver = () =>
    !hasPermissions.current
      ? null
      : [
          {
            title: intl.formatMessage(messages.remove),
            onClick: (_event, _rowId, member) => {
              setConfirmDelete(() => () => removeMembers([member.username.title]));
              setDeleteInfo({
                title: intl.formatMessage(messages.removeMemberQuestion),
                text: removeModalText(member.username.title, group.name, false),
                confirmButtonLabel: intl.formatMessage(messages.removeMember),
              });
              setShowRemoveModal(true);
            },
          },
        ];

  const navigate = useAppNavigate();

  const toolbarButtons = () => [
    ...(hasPermissions.current
      ? [
          <AppLink to={pathnames['group-add-members'].link.replace(':groupId', groupId)} key="remove-from-group" className="rbac-m-hide-on-sm">
            <Button variant="primary" aria-label="Add member">
              {intl.formatMessage(messages.addMember)}
            </Button>
          </AppLink>,
          {
            label: intl.formatMessage(messages.addMember),
            props: {
              className: 'rbac-m-hide-on-md',
            },
            onClick: () => {
              () => navigate(pathnames['group-add-members'].link.replace(':groupId', groupId));
            },
          },
          {
            label: intl.formatMessage(messages.remove),
            props: {
              isDisabled: !selectedMembers || !selectedMembers.length > 0,
              variant: 'danger',
            },
            onClick: () => {
              const multipleMembersSelected = selectedMembers.length > 1;
              const removeText = intl.formatMessage(multipleMembersSelected ? messages.removeMembersQuestion : messages.removeMemberQuestion);
              setConfirmDelete(() => () => removeMembers(selectedMembers.map((user) => user.uuid)));
              setDeleteInfo({
                title: removeText,
                confirmButtonLabel: removeText,
                text: removeModalText(
                  multipleMembersSelected ? selectedMembers.length : selectedMembers[0].uuid,
                  group.name,
                  multipleMembersSelected
                ),
              });
              setShowRemoveModal(true);
            },
          },
        ]
      : []),
  ];
  const data = (members || []).map((user) => ({ ...user, uuid: user.username }));
  const rows = createRows(data, selectedMembers);

  return (
    <Fragment>
      <WarningModal
        title={deleteInfo.title}
        confirmButtonLabel={deleteInfo.confirmButtonLabel}
        confirmButtonVariant={ButtonVariant.danger}
        isOpen={showRemoveModal}
        onClose={() => setShowRemoveModal(false)}
        onConfirm={() => {
          setShowRemoveModal(false);
          confirmDelete();
        }}
      >
        {deleteInfo.text}
      </WarningModal>
      <Section type="content" id="tab-principals">
        {platformDefault || adminDefault ? (
          <Card>
            <CardBody>
              <Bullseye>
                <TextContent>
                  <Text component={TextVariants.h1}>
                    {intl.formatMessage(adminDefault ? messages.allOrgAdminsAreMembers : messages.allUsersAreMembers)}
                  </Text>
                </TextContent>
              </Bullseye>
            </CardBody>
          </Card>
        ) : (
          <TableToolbarView
            data={data}
            isSelectable={hasPermissions.current}
            rows={rows}
            columns={columns}
            actionResolver={actionResolver}
            filterPlaceholder={intl.formatMessage(messages.username).toLowerCase()}
            titlePlural={intl.formatMessage(messages.members).toLowerCase()}
            titleSingular={intl.formatMessage(messages.member)}
            ouiaId="members-table"
            pagination={pagination}
            filterValue={filterValue}
            fetchData={({ limit, offset, name }) => fetchData(name, { limit, offset })}
            setFilterValue={({ name }) => setFilterValue(name)}
            checkedRows={selectedMembers}
            isLoading={isLoading}
            rowWrapper={UsersRow}
            setCheckedItems={setCheckedMembers}
            toolbarButtons={toolbarButtons}
            emptyProps={{ title: intl.formatMessage(messages.noGroupMembers), description: [intl.formatMessage(messages.addUserToConfigure), ''] }}
            tableId="group-members"
          />
        )}
      </Section>
      <Suspense>
        <Outlet
          context={{
            [pathnames['group-members-edit-group'].path]: {
              group,
              cancelRoute: pathnames['group-detail-members'].link.replace(':groupId', groupId),
              postMethod: () => dispatch(fetchGroup(group.uuid)),
            },
            [pathnames['group-members-remove-group'].path]: {
              postMethod: () => dispatch(fetchGroups({ ...groupsPagination, offset: 0, filters: groupsFilters, usesMetaInURL: true, chrome })),
              cancelRoute: pathnames['group-detail-members'].link.replace(':groupId', groupId),
              submitRoute: getBackRoute(pathnames.groups.link, { ...groupsPagination, offset: 0 }, groupsFilters),
              groupsUuid: [group],
            },
            [pathnames['group-add-members'].path]: {
              fetchData,
              cancelRoute: pathnames['group-detail-members'].link.replace(':groupId', groupId),
            },
          }}
        />
      </Suspense>
    </Fragment>
  );
};

export default GroupMembers;