RedHatInsights/insights-rbac-ui

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

Summary

Maintainability
F
3 days
Test Coverage
import React, { Fragment, useEffect, useState } from 'react';
import useChrome from '@redhat-cloud-services/frontend-components/useChrome';
import { useLocation, useParams, Outlet, useNavigationType } from 'react-router-dom';
import { shallowEqual, useDispatch, useSelector } from 'react-redux';
import { FormattedMessage, useIntl } from 'react-intl';
import { OutlinedQuestionCircleIcon } from '@patternfly/react-icons';
import { Alert, AlertActionCloseButton, Popover, PopoverPosition, Split, SplitItem, Button } from '@patternfly/react-core';
import { DropdownItem, Dropdown, KebabToggle } from '@patternfly/react-core/deprecated';
import WarningModal from '@patternfly/react-component-groups/dist/dynamic/WarningModal';
import SkeletonTable from '@patternfly/react-component-groups/dist/esm/SkeletonTable';
import AppTabs from '../app-tabs/app-tabs';
import useAppNavigate from '../../hooks/useAppNavigate';
import { TopToolbar, TopToolbarTitle } from '../../presentational-components/shared/top-toolbar';
import { fetchGroup, fetchSystemGroup, removeGroups } from '../../redux/actions/group-actions';
import AppLink, { mergeToBasename } from '../../presentational-components/shared/AppLink';
import EmptyWithAction from '../../presentational-components/shared/empty-state';
import RbacBreadcrumbs from '../../presentational-components/shared/breadcrumbs';
import { BAD_UUID, getBackRoute } from '../../helpers/shared/helpers';
import { DEFAULT_ACCESS_GROUP_ID } from '../../utilities/constants';
import messages from '../../Messages';
import pathnames from '../../utilities/pathnames';
import { useFlag } from '@unleash/proxy-client-react';
import './group.scss';

const Group = () => {
  const intl = useIntl();
  const dispatch = useDispatch();
  const navigate = useAppNavigate();
  const location = useLocation();
  const navigationType = useNavigationType();
  const chrome = useChrome();
  const { groupId } = useParams();
  const isPlatformDefault = groupId === DEFAULT_ACCESS_GROUP_ID;
  const enableServiceAccounts =
    (chrome.isBeta() && useFlag('platform.rbac.group-service-accounts')) ||
    (!chrome.isBeta() && useFlag('platform.rbac.group-service-accounts.stable'));

  const tabItems = [
    { eventKey: 0, title: 'Roles', name: pathnames['group-detail-roles'].link.replace(':groupId', groupId), to: 'roles' },
    { eventKey: 1, title: 'Members', name: pathnames['group-detail-members'].link.replace(':groupId', groupId), to: 'members' },
    ...(enableServiceAccounts
      ? [
          {
            eventKey: 2,
            title: 'Service accounts',
            name: pathnames['group-detail-service-accounts'].link.replace(':groupId', groupId),
            to: 'service-accounts',
          },
        ]
      : []),
  ];

  const { pagination, filters, groupExists, systemGroupUuid } = useSelector(
    ({ groupReducer: { groups, error, systemGroup } }) => ({
      pagination: groups.pagination || groups.meta,
      filters: groups.filters,
      groupExists: error !== BAD_UUID,
      systemGroupUuid: systemGroup?.uuid,
    }),
    shallowEqual
  );

  const { group, isGroupLoading } = useSelector(
    ({ groupReducer: { selectedGroup, isRecordLoading } }) => ({
      group: selectedGroup,
      isGroupLoading: isRecordLoading,
    }),
    shallowEqual
  );

  const [isResetWarningVisible, setResetWarningVisible] = useState(false);
  const [isDropdownOpen, setDropdownOpen] = useState(false);
  const [showDefaultGroupChangedInfo, setShowDefaultGroupChangedInfo] = useState(false);

  useEffect(() => {
    dispatch(fetchSystemGroup({ chrome }));
    const currId = !isPlatformDefault ? groupId : systemGroupUuid;
    if (currId) {
      dispatch(fetchGroup(currId));
      chrome.appObjectId(currId);
    }
    return () => chrome.appObjectId(undefined);
  }, [groupId, systemGroupUuid]);

  const breadcrumbsList = () => [
    {
      title: intl.formatMessage(messages.groups),
      to: getBackRoute(mergeToBasename(pathnames.groups.link), pagination, filters),
    },
    groupExists
      ? { title: isGroupLoading ? undefined : group.name, isActive: true }
      : { title: intl.formatMessage(messages.invalidGroup), isActive: true },
  ];

  const defaultGroupChangedIcon = (name) => (
    <div style={{ display: 'inline-flex' }}>
      {name}
      <div className="pf-v5-u-ml-sm">
        <Popover
          aria-label="default-group-icon"
          bodyContent={
            <FormattedMessage
              {...messages.defaultAccessGroupNameChanged}
              values={{
                b: (text) => <b>{text}</b>,
              }}
            />
          }
        >
          <OutlinedQuestionCircleIcon className="rbac-default-group-info-icon" />
        </Popover>
      </div>
    </div>
  );

  const defaultGroupRestore = () => (
    <div className="rbac-default-group-reset-btn">
      <Button variant="link" onClick={() => setResetWarningVisible(true)}>
        {intl.formatMessage(messages.restoreToDefault)}
      </Button>
      <Popover
        aria-label="default-group-icon"
        position={PopoverPosition.bottomEnd}
        bodyContent={
          <FormattedMessage
            {...messages.restoreDefaultAccessInfo}
            values={{
              b: (text) => <b>{text}</b>,
            }}
          />
        }
      >
        <OutlinedQuestionCircleIcon className="rbac-default-group-info-icon pf-v5-u-mt-sm" />
      </Popover>
    </div>
  );

  const dropdownItems = [
    <DropdownItem
      component={
        <AppLink
          onClick={() => setDropdownOpen(false)}
          to={(location.pathname.includes('members') ? pathnames['group-members-edit-group'] : pathnames['group-roles-edit-group']).link.replace(
            ':groupId',
            isPlatformDefault ? DEFAULT_ACCESS_GROUP_ID : groupId
          )}
        >
          {intl.formatMessage(messages.edit)}
        </AppLink>
      }
      key="edit-group"
    />,
    <DropdownItem
      component={
        <AppLink
          to={(location.pathname.includes('members') ? pathnames['group-members-remove-group'] : pathnames['group-roles-remove-group']).link.replace(
            ':groupId',
            groupId
          )}
        >
          {intl.formatMessage(messages.delete)}
        </AppLink>
      }
      className="rbac-c-group__action"
      key="delete-group"
    />,
  ];

  return (
    <Fragment>
      {isResetWarningVisible && (
        <WarningModal
          isOpen={isResetWarningVisible}
          title={intl.formatMessage(messages.restoreDefaultAccessQuestion)}
          confirmButtonLabel={intl.formatMessage(messages.continue)}
          onClose={() => setResetWarningVisible(false)}
          onConfirm={() => {
            dispatch(removeGroups([systemGroupUuid])).then(() =>
              dispatch(fetchSystemGroup({ chrome })).then(() => {
                setShowDefaultGroupChangedInfo(false);
              })
            );
            setResetWarningVisible(false);
            navigate(pathnames['group-detail-roles'].link.replace(':groupId', DEFAULT_ACCESS_GROUP_ID));
          }}
        >
          <FormattedMessage
            {...messages.restoreDefaultAccessDescription}
            values={{
              b: (text) => <b>{text}</b>,
            }}
          />
        </WarningModal>
      )}
      {groupExists ? (
        <Fragment>
          <TopToolbar breadcrumbs={breadcrumbsList()}>
            <Split hasGutter>
              <SplitItem isFilled>
                <TopToolbarTitle
                  title={
                    !isGroupLoading && group ? (
                      <Fragment>{group.platform_default && !group.system ? defaultGroupChangedIcon(group.name) : group.name}</Fragment>
                    ) : undefined
                  }
                  description={(!isGroupLoading && group?.description) || undefined}
                />
              </SplitItem>
              {group.platform_default && !group.system ? <SplitItem>{defaultGroupRestore()}</SplitItem> : null}
              <SplitItem>
                {group.platform_default || group.admin_default ? null : (
                  <Dropdown
                    ouiaId="group-title-actions-dropdown"
                    toggle={<KebabToggle onToggle={(_event, isOpen) => setDropdownOpen(isOpen)} id="group-actions-dropdown" />}
                    isOpen={isDropdownOpen}
                    isPlain
                    position="right"
                    dropdownItems={dropdownItems}
                  />
                )}
              </SplitItem>
            </Split>
            {showDefaultGroupChangedInfo ? (
              <Alert
                variant="info"
                isInline
                title={intl.formatMessage(messages.defaultAccessGroupChanged)}
                action={<AlertActionCloseButton onClose={() => setShowDefaultGroupChangedInfo(false)} />}
                className="pf-v5-u-mb-lg pf-v5-u-mt-sm"
              >
                <FormattedMessage
                  {...messages.defaultAccessGroupNameChanged}
                  values={{
                    b: (text) => <b>{text}</b>,
                  }}
                />
              </Alert>
            ) : null}
          </TopToolbar>
          <AppTabs isHeader tabItems={tabItems} />
          <Outlet
            context={{
              [pathnames['group-detail-roles'].path]: {
                onDefaultGroupChanged: setShowDefaultGroupChangedInfo,
              },
              groupId, // used for redirect from /:groupId to /:groupId/roles
              systemGroupUuid,
            }}
          />
          {!group && <SkeletonTable numberOfColumns={1} />}
        </Fragment>
      ) : (
        <Fragment>
          <section className="pf-v5-c-page__main-breadcrumb pf-v5-u-pb-md">
            <RbacBreadcrumbs {...breadcrumbsList()} />
          </section>
          <EmptyWithAction
            title={intl.formatMessage(messages.groupNotFound)}
            description={[intl.formatMessage(messages.groupDoesNotExist, { id: groupId })]}
            actions={[
              <Button
                key="back-button"
                className="pf-v5-u-mt-xl"
                ouiaId="back-button"
                variant="primary"
                aria-label="Back to previous page"
                onClick={() => navigate(navigationType !== 'POP' ? -1 : pathnames.groups.link)}
              >
                {intl.formatMessage(messages.backToPreviousPage)}
              </Button>,
            ]}
          />
        </Fragment>
      )}
    </Fragment>
  );
};

export default Group;