@patternfly/react-core#Tooltip JavaScript Examples

The following examples show how to use @patternfly/react-core#Tooltip. You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: widget-components.js    From ibutsu-server with MIT License 6 votes vote down vote up
render() {
    const { isOpen } = this.state;
    const { showDescription } = this.state;
    let dropdownItems = [];
    this.props.dropdownItems.forEach( (item) => {
      dropdownItems.push(<DropdownItem onClick={this.onSelect} key={item}> {item} </DropdownItem>)
    });
    return (
      <div data-id='widget-param-dropdown'>
        {showDescription &&
        <Text component='h3'>{this.props.description || this.props.tooltip || ""}</Text>
        }
        <Tooltip content={this.props.tooltip}>
          <Dropdown
            direction={this.direction}
            isOpen={isOpen}
            dropdownItems={dropdownItems}
            toggle={
              <DropdownToggle id="toggle-dropdown" onToggle={this.onToggle}>
                {this.state.value}
              </DropdownToggle>
            }
          />
        </Tooltip>
      </div>
    );
  }
Example #2
Source File: ImageSetsTable.js    From edge-frontend with Apache License 2.0 6 votes vote down vote up
TooltipSelectorRef = ({ index }) => (
  <div>
    <Tooltip
      content={<div>More options</div>}
      reference={() =>
        document.getElementById(`pf-dropdown-toggle-id-${index}`)
      }
    />
  </div>
)
Example #3
Source File: Status.js    From edge-frontend with Apache License 2.0 6 votes vote down vote up
Status = ({
  type,
  isLabel = false,
  toolTipContent = '',
  className = '',
}) => {
  const { text, Icon, color, labelColor } =
    Object.prototype.hasOwnProperty.call(statusMapper, type)
      ? statusMapper[type]
      : statusMapper['default'];

  return (
    <>
      {isLabel ? (
        <Label color={labelColor} icon={<Icon />} className={className}>
          {text}
        </Label>
      ) : (
        <Split style={{ color }} className={className}>
          <SplitItem className="pf-u-mr-sm">
            {toolTipContent ? (
              <Tooltip content="blargh">
                <Icon />
              </Tooltip>
            ) : (
              <Icon />
            )}
          </SplitItem>
          <SplitItem>
            <p>{text}</p>
          </SplitItem>
        </Split>
      )}
    </>
  );
}
Example #4
Source File: RegisterWithActivationKey.js    From sed-frontend with Apache License 2.0 6 votes vote down vote up
RegisterWithActivationKey = () => (
  <FormGroup
    label={
      <span>
        Register with an activation key&nbsp;
        <Tooltip
          content={
            <div>
              Organization administrators can view, create, and edit activation
              keys on the &quot;Activation keys&quot; section of
              console.redhat.com. The organization ID is a Candlepin-specific
              identifier, which can be accessed from the activation keys page.
            </div>
          }
        >
          <OutlinedQuestionCircleIcon />
        </Tooltip>
      </span>
    }
    helperText={<CopyHelperText />}
  >
    <ClipboardCopy>
      rhc connect -a &#60;activation-key&#62; -o&nbsp; &#60;organization-id&#62;
    </ClipboardCopy>
  </FormGroup>
)
Example #5
Source File: RuleLabels.js    From ocp-advisor-frontend with Apache License 2.0 6 votes vote down vote up
RuleLabels = ({ rule }) => {
  const intl = useIntl();
  return (
    <React.Fragment>
      {rule.disabled && (
        <Tooltip
          content={intl.formatMessage(messages.ruleIsDisabledTooltip)}
          position={TooltipPosition.right}
        >
          <Label color="gray" isCompact>
            {intl.formatMessage(messages.disabled)}
          </Label>
        </Tooltip>
      )}
    </React.Fragment>
  );
}
Example #6
Source File: ImageVersionsTab.js    From edge-frontend with Apache License 2.0 5 votes vote down vote up
createRows = (data, imageSetId, latestImageVersion) => {
  return data?.map(({ image }) => ({
    rowInfo: {
      id: image?.ID,
      imageStatus: image?.Status,
      isoURL: image?.Installer?.ImageBuildISOURL,
      latestImageVersion,
      currentImageVersion: image.Version,
    },
    noApiSortFilter: [
      image?.Version,
      imageTypeMapper[image?.ImageType],
      image?.CreatedAt,
      image?.Status,
    ],
    cells: [
      {
        title: (
          <Link
            to={`${paths['manage-images']}/${imageSetId}/versions/${image.ID}/details`}
          >
            {image?.Version}
          </Link>
        ),
      },
      {
        title: imageTypeMapper[image?.ImageType],
      },
      {
        title: image?.Commit?.OSTreeCommit ? (
          <Tooltip content={<div>{image?.Commit?.OSTreeCommit}</div>}>
            <span>{truncateString(image?.Commit?.OSTreeCommit, 5, 5)}</span>
          </Tooltip>
        ) : (
          <Text>Unavailable</Text>
        ),
      },
      {
        title: <DateFormat date={image?.CreatedAt} />,
      },
      {
        title: <Status type={image?.Status.toLowerCase()} />,
      },
    ],
  }));
}
Example #7
Source File: StatusIcon.js    From edge-frontend with Apache License 2.0 5 votes vote down vote up
StatusIcon = ({ status, ...props }) => {
  const Icon = statusToIcon?.[status]?.icon || UnknownIcon;
  return (
    <Tooltip content={statusToIcon?.[status]?.title}>
      <Icon {...props} color={statusToIcon?.[status]?.color} />
    </Tooltip>
  );
}
Example #8
Source File: AffectedClustersTable.js    From ocp-advisor-frontend with Apache License 2.0 4 votes vote down vote up
AffectedClustersTable = ({ query, rule, afterDisableFn }) => {
  const intl = useIntl();
  const dispatch = useDispatch();

  const [filteredRows, setFilteredRows] = useState([]);
  const [displayedRows, setDisplayedRows] = useState([]);
  const [disableRuleModalOpen, setDisableRuleModalOpen] = useState(false);
  const [selected, setSelected] = useState([]);
  const [host, setHost] = useState(undefined);

  const {
    isError,
    isUninitialized,
    isFetching,
    isSuccess,
    /* the response contains two lists: `disabled` has clusters
      for which the rec is disabled (acked), and `enable` contains
       clusters that are affected by the rec */
    data = { disabled: [], enabled: [] },
  } = query;
  const rows = data?.enabled || [];
  const filters = useSelector(({ filters }) => filters.affectedClustersState);
  const perPage = filters.limit;
  const page = filters.offset / filters.limit + 1;
  const allSelected =
    filteredRows.length !== 0 && selected.length === filteredRows.length;
  const loadingState = isUninitialized || isFetching;
  const errorState = isError;
  const successState = isSuccess;
  const noInput = successState && rows.length === 0;
  const noMatch = rows.length > 0 && filteredRows.length === 0;

  const updateFilters = (filters) =>
    dispatch(updateAffectedClustersFilters(filters));

  const removeFilterParam = (param) =>
    _removeFilterParam(filters, updateFilters, param);

  const addFilterParam = (param, values) =>
    _addFilterParam(filters, updateFilters, param, values);

  const filterConfig = {
    items: [
      {
        label: intl.formatMessage(messages.name),
        placeholder: intl.formatMessage(messages.filterByName),
        type: conditionalFilterType.text,
        filterValues: {
          id: 'name-filter',
          key: 'name-filter',
          onChange: (event, value) => addFilterParam('text', value),
          value: filters.text,
        },
      },
      {
        label: intl.formatMessage(messages.version),
        placeholder: intl.formatMessage(messages.filterByVersion),
        type: conditionalFilterType.checkbox,
        filterValues: {
          id: 'version-filter',
          key: 'version-filter',
          onChange: (event, value) => addFilterParam('version', value),
          value: filters.version,
          items: uniqBy(
            rows
              .filter((r) => r.meta.cluster_version !== '')
              .map((r) => ({
                value: r.meta.cluster_version,
              }))
              .sort((a, b) => compareSemVer(a.value, b.value, 1))
              .reverse(), // should start from the latest version
            'value'
          ),
        },
      },
    ],
    isDisabled: isError || (rows && rows.length === 0),
  };

  const onSort = (_e, index, direction) => {
    updateFilters({ ...filters, sortIndex: index, sortDirection: direction });
  };

  const onSetPage = (_e, pageNumber) => {
    const newOffset = pageNumber * filters.limit - filters.limit;
    updateFilters({ ...filters, offset: newOffset });
  };

  const onSetPerPage = (_e, perPage) => {
    updateFilters({ ...filters, limit: perPage });
  };

  // constructs array of rows (from the initial data) checking currently applied filters
  const buildFilteredRows = (allRows, filters) => {
    const rows = allRows.map((r) => {
      if (r.meta.cluster_version !== '' && !valid(r.meta.cluster_version)) {
        console.error(
          `Cluster version ${r.meta.cluster_version} has invalid format!`
        );
      }

      return {
        id: r.cluster,
        cells: [
          '',
          r.cluster_name || r.cluster,
          r.meta.cluster_version,
          r.last_checked_at,
          r.impacted,
        ],
      };
    });
    return rows
      .filter((row) => {
        return (
          row?.cells[AFFECTED_CLUSTERS_NAME_CELL].toLowerCase().includes(
            filters.text.toLowerCase()
          ) &&
          (filters.version.length === 0 ||
            filters.version.includes(row.cells[AFFECTED_CLUSTERS_VERSION_CELL]))
        );
      })
      .sort((a, b) => {
        let fst, snd;
        const d = filters.sortDirection === 'asc' ? 1 : -1;
        switch (filters.sortIndex) {
          case AFFECTED_CLUSTERS_NAME_CELL:
            return (
              d *
              a?.cells[AFFECTED_CLUSTERS_NAME_CELL].localeCompare(
                b?.cells[AFFECTED_CLUSTERS_NAME_CELL]
              )
            );
          case AFFECTED_CLUSTERS_VERSION_CELL:
            return compareSemVer(
              a.cells[AFFECTED_CLUSTERS_VERSION_CELL] || '0.0.0',
              b.cells[AFFECTED_CLUSTERS_VERSION_CELL] || '0.0.0',
              d
            );
          case AFFECTED_CLUSTERS_LAST_SEEN_CELL:
            fst = new Date(a.cells[AFFECTED_CLUSTERS_LAST_SEEN_CELL] || 0);
            snd = new Date(b.cells[AFFECTED_CLUSTERS_LAST_SEEN_CELL] || 0);
            return fst > snd ? d : snd > fst ? -d : 0;
          case AFFECTED_CLUSTERS_IMPACTED_CELL:
            fst = new Date(a.cells[AFFECTED_CLUSTERS_IMPACTED_CELL] || 0);
            snd = new Date(b.cells[AFFECTED_CLUSTERS_IMPACTED_CELL] || 0);
            return fst > snd ? d : snd > fst ? -d : 0;
        }
      });
  };

  const buildDisplayedRows = (rows) => {
    return rows
      .slice(perPage * (page - 1), perPage * (page - 1) + perPage)
      .map((r) => ({
        ...r,
        cells: [
          <span key={r.id}>
            <Link to={`/clusters/${r.id}?first=${rule.rule_id}`}>
              {r.cells[AFFECTED_CLUSTERS_NAME_CELL]}
            </Link>
          </span>,
          <span key={r.id}>
            {r.cells[AFFECTED_CLUSTERS_VERSION_CELL] ||
              intl.formatMessage(messages.notAvailable)}
          </span>,
          <span key={r.id}>
            {r.cells[AFFECTED_CLUSTERS_LAST_SEEN_CELL] ? (
              <DateFormat
                extraTitle={`${intl.formatMessage(messages.lastSeen)}: `}
                date={r.cells[AFFECTED_CLUSTERS_LAST_SEEN_CELL]}
                variant="relative"
              />
            ) : (
              <Tooltip
                key={r.id}
                content={
                  <span>
                    {intl.formatMessage(messages.lastSeen) + ': '}
                    {intl.formatMessage(messages.nA)}
                  </span>
                }
              >
                <span>{intl.formatMessage(messages.nA)}</span>
              </Tooltip>
            )}
          </span>,
          <span key={r.id}>
            {r.cells[AFFECTED_CLUSTERS_IMPACTED_CELL] ? (
              <DateFormat
                extraTitle={`${intl.formatMessage(messages.impacted)}: `}
                date={r.cells[AFFECTED_CLUSTERS_IMPACTED_CELL]}
                variant="relative"
              />
            ) : (
              <Tooltip
                key={r.id}
                content={
                  <span>
                    {intl.formatMessage(messages.impacted) + ': '}
                    {intl.formatMessage(messages.nA)}
                  </span>
                }
              >
                <span>{intl.formatMessage(messages.nA)}</span>
              </Tooltip>
            )}
          </span>,
        ],
      }));
  };

  // if rowId === -1, then select all rows
  const onSelect = (event, isSelected, rowId) => {
    let rows;
    rowId === -1
      ? (rows = filteredRows.map((r) => ({ ...r, selected: isSelected })))
      : (rows = filteredRows.map((r, i) => ({
          ...r,
          selected: i === rowId ? isSelected : r.selected,
        })));
    setSelected(rows.filter((r) => r.selected));
    setFilteredRows(rows);
    setDisplayedRows(buildDisplayedRows(rows));
  };

  useEffect(() => {
    const newFilteredRows = buildFilteredRows(rows, filters);
    const newDisplayedRows = buildDisplayedRows(newFilteredRows);
    setFilteredRows(newFilteredRows);
    setDisplayedRows(newDisplayedRows);
  }, [query, filters]);

  const handleModalToggle = (disableRuleModalOpen, host = undefined) => {
    setDisableRuleModalOpen(disableRuleModalOpen);
    setHost(host);
  };

  return (
    <div id="affected-list-table">
      {disableRuleModalOpen && (
        <DisableRule
          handleModalToggle={handleModalToggle}
          isModalOpen={disableRuleModalOpen}
          rule={rule}
          afterFn={afterDisableFn}
          hosts={host !== undefined ? [] : selected}
          host={host}
        />
      )}
      <PrimaryToolbar
        filterConfig={filterConfig}
        pagination={{
          itemCount: filteredRows.length,
          page,
          perPage,
          onSetPage: onSetPage,
          onPerPageSelect: onSetPerPage,
          ouiaId: 'pager',
        }}
        activeFiltersConfig={
          isError || (rows && rows.length === 0)
            ? undefined
            : {
                filters: buildFilterChips(filters, FILTER_CATEGORIES),
                deleteTitle: intl.formatMessage(messages.resetFilters),
                onDelete: (event, itemsToRemove, isAll) => {
                  if (isAll) {
                    updateFilters(AFFECTED_CLUSTERS_INITIAL_STATE);
                  } else {
                    itemsToRemove.map((item) => {
                      const newFilter = {
                        [item.urlParam]: Array.isArray(filters[item.urlParam])
                          ? filters[item.urlParam].filter(
                              (value) =>
                                String(value) !== String(item.chips[0].value)
                            )
                          : '',
                      };
                      newFilter[item.urlParam].length > 0
                        ? updateFilters({ ...filters, ...newFilter })
                        : removeFilterParam(item.urlParam);
                    });
                  }
                },
              }
        }
        bulkSelect={{
          count: selected.length,
          items: [
            {
              title: intl.formatMessage(messages.selectNone),
              onClick: (event) => onSelect(event, false, -1),
            },
            {
              title: intl.formatMessage(messages.selectAll, {
                items: filteredRows?.length || 0,
              }),
              onClick: (event) => onSelect(event, true, -1),
            },
          ],
          checked: allSelected,
          onSelect: (event) =>
            allSelected
              ? onSelect(event, false, -1)
              : onSelect(event, true, -1),
          ouiaId: 'clusters-selector',
        }}
        actionsConfig={{
          actions: [
            '',
            {
              label: intl.formatMessage(messages.disableRuleForClusters),
              props: { isDisabled: selected.length === 0 },
              onClick: () => handleModalToggle(true),
            },
          ],
        }}
      />
      <Table
        aria-label="Table of affected clusters"
        ouiaId="clusters"
        variant="compact"
        cells={AFFECTED_CLUSTERS_COLUMNS}
        rows={
          errorState || loadingState || noMatch || noInput ? (
            [
              {
                fullWidth: true,
                cells: [
                  {
                    props: {
                      colSpan: AFFECTED_CLUSTERS_COLUMNS.length + 1,
                    },
                    title: errorState ? (
                      <ErrorState />
                    ) : loadingState ? (
                      <Loading />
                    ) : noInput ? (
                      <NoAffectedClusters />
                    ) : (
                      <NoMatchingClusters />
                    ),
                  },
                ],
              },
            ]
          ) : successState ? (
            displayedRows
          ) : (
            <ErrorState />
          )
        }
        sortBy={{
          index: filters.sortIndex,
          direction: filters.sortDirection,
        }}
        onSort={onSort}
        canSelectAll={false}
        onSelect={displayedRows?.length > 0 ? onSelect : undefined}
        actions={[
          {
            title: 'Disable recommendation for cluster',
            onClick: (event, rowIndex) =>
              handleModalToggle(true, filteredRows[rowIndex].id),
          },
        ]}
      >
        <TableHeader />
        <TableBody />
      </Table>
      <TableToolbar isFooter className="ins-c-inventory__table--toolbar">
        <Pagination
          variant={PaginationVariant.bottom}
          itemCount={filteredRows.length}
          page={page}
          perPage={perPage}
          onSetPage={onSetPage}
          onPerPageSelect={onSetPerPage}
          onPageInput={onSetPage}
          ouiaId="pager"
        />
      </TableToolbar>
    </div>
  );
}
Example #9
Source File: InterfaceActions.js    From cockpit-wicked with GNU General Public License v2.0 4 votes vote down vote up
InterfaceActions = ({ iface, connection }) => {
    const [showDeleteConfirmation, setShowDeleteConfirmation] = useState(false);
    const dispatch = useNetworkDispatch();

    const DeleteIcon = connection.virtual ? TrashIcon : ResetIcon;
    const deleteTooltip = connection.virtual ? _("Delete") : _("Reset");
    const changeStatusLabel = _("Enabled");
    const changeStatusLabelOff = _("Disabled");
    const changeStatusAction = iface.link ? _("Disable") : _("Enable");
    const changeStatusTooltip = cockpit.format(
        _("Click to $action it"),
        { action: changeStatusAction.toLowerCase() }
    );
    const openDeleteConfirmation = () => setShowDeleteConfirmation(true);
    const closeDeleteConfirmation = () => setShowDeleteConfirmation(false);
    const onDeleteConfirmation = () => {
        deleteConnection(dispatch, connection);
        closeDeleteConfirmation();
    };

    const renderDeleteConfirmation = () => {
        if (!showDeleteConfirmation) return null;

        const confirmationTitle = cockpit.format(
            _("$action '$iface' configuration"),
            { action: deleteTooltip, iface: connection?.name }
        );
        const confirmationMessage = cockpit.format(
            _("Please, confirm that you really want to $action the interface configuration."),
            { action:  deleteTooltip.toLowerCase() }
        );

        return (
            <ModalConfirm
                title={confirmationTitle}
                isOpen={showDeleteConfirmation}
                onCancel={closeDeleteConfirmation}
                onConfirm={onDeleteConfirmation}
            >
                {confirmationMessage}
            </ModalConfirm>
        );
    };

    const renderDeleteAction = () => {
        if (!connection.exists) return null;

        return (
            <>
                <Tooltip content={deleteTooltip}>
                    <Button
                        variant="link"
                        aria-label={`${deleteTooltip} ${iface.name}`}
                        className="delete-action"
                        onClick={openDeleteConfirmation}
                    >
                        <DeleteIcon />
                    </Button>
                </Tooltip>
                { renderDeleteConfirmation() }
            </>
        );
    };

    return (
        <>
            <Tooltip content={changeStatusTooltip}>
                <Switch
                    id={`${iface.name}-status-switcher}`}
                    aria-label={`${changeStatusAction} ${iface.name}`}
                    label={changeStatusLabel}
                    labelOff={changeStatusLabelOff}
                    className="interface-status-switcher"
                    isChecked={iface.link}
                    onChange={() => changeConnectionState(dispatch, connection, !iface.link)}
                />
            </Tooltip>
            { renderDeleteAction() }
        </>
    );
}
Example #10
Source File: certificateList.jsx    From cockpit-certificates with GNU Lesser General Public License v2.1 4 votes vote down vote up
generalDetails = ({ idPrefix, cas, cert, certPath, onAutorenewChanged }) => {
    const caName = getCAName(cas, cert);

    return (<Flex justifyContent={{ default: "justifyContentCenter" }}>
        <Flex direction={{ default:"column" }} flex={{ default: 'flex_1' }}>
            <DescriptionList isHorizontal>
                {cert.status && cert.status.v && <DescriptionListGroup>
                    <DescriptionListTerm>{_("Status")}</DescriptionListTerm>
                    <DescriptionListDescription id={`${idPrefix}-general-status`}>
                        {cert.stuck.v && (
                            <Flex alignItems={{ default: 'alignItemsCenter' }} spaceItems={{ default: 'spaceItemsSm' }}>
                                <ExclamationTriangleIcon className="ct-icon-exclamation-triangle" />
                                <span id={`${idPrefix}-general-stuck`}>{_("Stuck: ")}</span>
                            </Flex>
                        )}
                        <Flex alignItems={{ default: 'alignItemsCenter' }} spaceItems={{ default: 'spaceItemsSm' }}>
                            <FlexItem>
                                {cert.status.v.includes('_')
                                    ? cert.status.v
                                    : cert.status.v.charAt(0) + cert.status.v.slice(1).toLowerCase()}
                            </FlexItem>
                            <Tooltip position={TooltipPosition.top}
                                entryDelay={0}
                                content={certificateStates[cert.status.v]}>
                                <span className="info-circle">
                                    <InfoAltIcon />
                                </span>
                            </Tooltip>
                        </Flex>
                    </DescriptionListDescription>
                </DescriptionListGroup>}

                {cert.ca && cert.ca.v && <DescriptionListGroup>
                    <DescriptionListTerm>{_("Certificate authority")}</DescriptionListTerm>
                    <DescriptionListDescription id={`${idPrefix}-general-ca`}>{caName == "SelfSign" ? _("Self-signed") : caName}</DescriptionListDescription>
                </DescriptionListGroup>}

                {cert["not-valid-after"] && cert["not-valid-after"].v !== 0 && <DescriptionListGroup>
                    <DescriptionListTerm>
                        {_("Valid")}
                    </DescriptionListTerm>
                    <DescriptionListDescription id={`${idPrefix}-general-validity`}>
                        {prettyTime(cert["not-valid-before"].v) +
                        _(" to ") + prettyTime(cert["not-valid-after"].v)}
                    </DescriptionListDescription>
                </DescriptionListGroup>}

                {cert.autorenew && <DescriptionListGroup>
                    <DescriptionListTerm>
                        {_("Auto-renewal")}
                    </DescriptionListTerm>
                    <DescriptionListDescription>
                        <Checkbox id={`${idPrefix}-general-autorenewal`}
                                  isChecked={cert.autorenew.v}
                                  label={_("Renew before expiration")}
                                  onChange={() => onAutorenewChanged(cert, certPath)} />
                    </DescriptionListDescription>
                </DescriptionListGroup>}
            </DescriptionList>
        </Flex>
        <Flex direction={{ default:"column" }} flex={{ default: 'flex_1' }}>
            <DescriptionList isHorizontal>
                {cert.subject && cert.subject.v && <DescriptionListGroup>
                    <DescriptionListTerm>
                        {_("Subject name")}
                    </DescriptionListTerm>
                    <DescriptionListDescription>
                        <span id={`${idPrefix}-general-subject`}>{cert.subject.v}</span>
                    </DescriptionListDescription>
                </DescriptionListGroup>}

                {cert.principal && cert.principal.v.length > 0 && <DescriptionListGroup>
                    <DescriptionListTerm>
                        {_("Principal name")}
                    </DescriptionListTerm>
                    <DescriptionListDescription>
                        <span id={`${idPrefix}-general-principal`}>{cert.principal.v.join(", ")}</span>
                    </DescriptionListDescription>
                </DescriptionListGroup>}

                {cert.hostname && cert.hostname.v.length > 0 && <DescriptionListGroup>
                    <DescriptionListTerm>
                        {_("DNS name")}
                    </DescriptionListTerm>
                    <DescriptionListDescription>
                        <span id={`${idPrefix}-general-dns`}>{cert.hostname.v.join(", ")}</span>
                    </DescriptionListDescription>
                </DescriptionListGroup>}
            </DescriptionList>
        </Flex>
    </Flex>);
}
Example #11
Source File: Services.js    From sed-frontend with Apache License 2.0 4 votes vote down vote up
Services = ({
  defaults,
  setConfirmChangesOpen,
  onChange,
  isEditing,
  setIsEditing,
}) => {
  const initState = {
    enableCloudConnector: {
      value: defaults.enableCloudConnector,
      isDisabled: false,
    },
    useOpenSCAP: { value: defaults.useOpenSCAP, isDisabled: false },
  };
  const [formState, setFormState] = useState(initState);
  const [madeChanges, setMadeChanges] = useState(false);

  const { hasAccess, isLoading } = usePermissions(
    '',
    [
      'config-manager:activation_keys:*',
      'config-manager:state:read',
      'config-manager:state:write',
      'config-manager:state-changes:read',
      'inventory:*:read',
      'playbook-dispatcher:run:read',
    ],
    false,
    true
  );

  const cancelEditing = () => {
    setFormState(initState);
    setIsEditing(false);
  };

  useEffect(() => {
    setMadeChanges(
      formState.useOpenSCAP.value !== defaults.useOpenSCAP ||
        formState.enableCloudConnector.value != defaults.enableCloudConnector
    );
    onChange({
      useOpenSCAP: formState.useOpenSCAP.value,
      enableCloudConnector: formState.enableCloudConnector.value,
    });
  }, [formState]);

  const getStatusIcon = (row) => {
    if (formState[row.id].value) {
      return (
        <Flex style={{ color: 'var(--pf-global--success-color--200)' }}>
          <FlexItem spacer={{ default: 'spacerXs' }}>
            <CheckCircleIcon />
          </FlexItem>
          <FlexItem className="status">
            <b>Enabled</b>
          </FlexItem>
        </Flex>
      );
    }
    return (
      <Flex style={{ color: 'var(--pf-global--default-color--300)' }}>
        <FlexItem spacer={{ default: 'spacerXs' }}>
          <BanIcon />
        </FlexItem>
        <FlexItem className="status">
          <b>Disabled</b>
        </FlexItem>
      </Flex>
    );
  };

  return (
    <Stack hasGutter className="pf-u-p-md">
      <StackItem>
        <Toolbar id="toolbar-items">
          <ToolbarContent>
            {!isEditing && (
              <ToolbarItem>
                {!hasAccess ? (
                  <Tooltip
                    content={
                      <div>
                        To perform this action, you must be granted the
                        &quot;System Administrator&quot; role by your
                        Organization Administrator in your Setting&apos;s User
                        Access area.
                      </div>
                    }
                  >
                    {changeSettingsButton(isLoading, hasAccess, setIsEditing)}
                  </Tooltip>
                ) : (
                  changeSettingsButton(isLoading, hasAccess, setIsEditing)
                )}
              </ToolbarItem>
            )}
            {isEditing && (
              <>
                <ToolbarItem>
                  <Button
                    ouiaId="primary-save-button"
                    onClick={() => setConfirmChangesOpen(true)}
                    isDisabled={!madeChanges}
                  >
                    Save changes
                  </Button>
                </ToolbarItem>
                <ToolbarItem>
                  <Button
                    ouiaId="secondary-cancel-button"
                    onClick={() => cancelEditing()}
                    variant="secondary"
                  >
                    Cancel
                  </Button>
                </ToolbarItem>
                <ToolbarItem>
                  <Alert
                    variant="info"
                    isInline
                    isPlain
                    title="Changes will affect all systems connected with Red Hat connector"
                  />
                </ToolbarItem>
              </>
            )}
          </ToolbarContent>
        </Toolbar>
      </StackItem>
      <StackItem>
        <TableComposable aria-label="Settings table">
          <Thead>
            <Tr>
              <Th>Permission</Th>
              <Th>Status</Th>
            </Tr>
          </Thead>
          <Tbody>
            {permissions.map((row) => (
              <Tr key={row.name}>
                <Td
                  dataLabel="Permission"
                  width={80}
                  style={row.secondary && { paddingLeft: 70, fontSize: 14 }}
                >
                  <Stack>
                    <StackItem>
                      <Flex>
                        <FlexItem>
                          <b>{row.name}</b>
                        </FlexItem>
                        {row.additionalInfo && (
                          <FlexItem
                            style={{ color: 'var(--pf-global--Color--100)' }}
                          >
                            <i>{row.additionalInfo}</i>
                          </FlexItem>
                        )}
                      </Flex>
                    </StackItem>
                    <StackItem style={{ fontSize: 14 }}>
                      {row.description}
                    </StackItem>
                    {row.links && (
                      <StackItem className="stack-item">
                        <Flex>
                          {row.links.map((link) => (
                            <FlexItem key={link.name}>
                              <a
                                href={link.link}
                                target="_blank"
                                rel="noopener noreferrer"
                              >
                                {link.name}
                                <ExternalLinkAltIcon className="pf-u-ml-sm" />
                              </a>
                            </FlexItem>
                          ))}
                        </Flex>
                      </StackItem>
                    )}
                  </Stack>
                </Td>
                {!isEditing && <Td dataLabel="Status">{getStatusIcon(row)}</Td>}
                {isEditing && (
                  <Td dataLabel="Status">
                    <ToggleGroup aria-label="Default with single selectable">
                      <ToggleGroupItem
                        text="Enabled"
                        isSelected={formState[row.id].value}
                        onChange={() =>
                          setFormState({
                            ...formState,
                            [row.id]: { ...formState[row.id], value: true },
                          })
                        }
                        isDisabled={formState[row.id].isDisabled}
                      />
                      <ToggleGroupItem
                        text="Disabled"
                        isSelected={!formState[row.id].value}
                        onChange={() =>
                          setFormState({
                            ...formState,
                            [row.id]: { ...formState[row.id], value: false },
                          })
                        }
                        isDisabled={formState[row.id].isDisabled}
                      />
                    </ToggleGroup>
                  </Td>
                )}
              </Tr>
            ))}
          </Tbody>
        </TableComposable>
      </StackItem>
    </Stack>
  );
}
Example #12
Source File: RecsListTable.js    From ocp-advisor-frontend with Apache License 2.0 4 votes vote down vote up
RecsListTable = ({ query }) => {
  const intl = useIntl();
  const dispatch = useDispatch();
  const filters = useSelector(({ filters }) => filters.recsListState);
  const { isError, isUninitialized, isFetching, isSuccess, data, refetch } =
    query;
  const recs = data?.recommendations || [];
  const page = filters.offset / filters.limit + 1;
  const [filteredRows, setFilteredRows] = useState([]);
  const [displayedRows, setDisplayedRows] = useState([]);
  const [disableRuleOpen, setDisableRuleOpen] = useState(false);
  const [selectedRule, setSelectedRule] = useState({});
  const [isAllExpanded, setIsAllExpanded] = useState(false);
  const notify = (data) => dispatch(addNotification(data));
  const { search } = useLocation();
  const [filterBuilding, setFilterBuilding] = useState(true);
  // helps to distinguish the state when the API data received but not yet filtered
  const [rowsFiltered, setRowsFiltered] = useState(false);
  // helps to distinguish if the component safe to test
  const testSafe = rowsFiltered && !(isFetching || isUninitialized);
  const updateFilters = (filters) => dispatch(updateRecsListFilters(filters));
  const searchText = filters?.text || '';
  const loadingState = isUninitialized || isFetching || !rowsFiltered;
  const errorState = isError || (isSuccess && recs.length === 0);
  const successState = isSuccess && recs.length > 0;
  const noMatch = recs.length > 0 && filteredRows.length === 0;

  const removeFilterParam = (param) =>
    _removeFilterParam(filters, updateFilters, param);

  const addFilterParam = (param, values) =>
    _addFilterParam(filters, updateFilters, param, values);

  useEffect(() => {
    setDisplayedRows(
      buildDisplayedRows(filteredRows, filters.sortIndex, filters.sortDirection)
    );
    if (isSuccess && !rowsFiltered) {
      setRowsFiltered(true);
    }
  }, [
    filteredRows,
    filters.limit,
    filters.offset,
    filters.sortIndex,
    filters.sortDirection,
  ]);

  useEffect(() => {
    setFilteredRows(buildFilteredRows(recs, filters));
  }, [data, filters]);

  useEffect(() => {
    if (search && filterBuilding) {
      const paramsObject = paramParser(search);

      if (paramsObject.sort) {
        const sortObj = translateSortParams(paramsObject.sort);
        paramsObject.sortIndex = RECS_LIST_COLUMNS_KEYS.indexOf(sortObj.name);
        paramsObject.sortDirection = sortObj.direction;
      }
      paramsObject.offset &&
        (paramsObject.offset = Number(paramsObject.offset[0]));
      paramsObject.limit &&
        (paramsObject.limit = Number(paramsObject.limit[0]));
      paramsObject.impacting &&
        !Array.isArray(paramsObject.impacting) &&
        (paramsObject.impacting = [`${paramsObject.impacting}`]);
      updateFilters({ ...filters, ...paramsObject });
    }
    setFilterBuilding(false);
  }, []);

  useEffect(() => {
    if (!filterBuilding) {
      updateSearchParams(filters, RECS_LIST_COLUMNS_KEYS);
    }
  }, [filters, filterBuilding]);

  // constructs array of rows (from the initial data) checking currently applied filters
  const buildFilteredRows = (allRows, filters) => {
    setRowsFiltered(false);
    return allRows
      .filter((rule) => passFilters(rule, filters))
      .map((value, key) => [
        {
          isOpen: isAllExpanded,
          rule: value,
          cells: [
            {
              title: (
                <span key={key}>
                  <Link key={key} to={`/recommendations/${value.rule_id}`}>
                    {' '}
                    {value?.description || value?.rule_id}{' '}
                  </Link>
                  <RuleLabels rule={value} />
                </span>
              ),
            },
            {
              title: value?.publish_date ? (
                <DateFormat
                  key={key}
                  date={value.publish_date}
                  variant="relative"
                />
              ) : (
                intl.formatMessage(messages.nA)
              ),
            },
            {
              title: <CategoryLabel key={key} tags={value.tags} />,
            },
            {
              title: (
                <div key={key}>
                  <Tooltip
                    key={key}
                    position={TooltipPosition.bottom}
                    content={intl.formatMessage(
                      messages.rulesDetailsTotalRiskBody,
                      {
                        risk:
                          TOTAL_RISK_LABEL_LOWER[value.total_risk] ||
                          intl.formatMessage(messages.undefined),
                        strong,
                      }
                    )}
                  >
                    {value?.total_risk ? (
                      <InsightsLabel
                        value={value.total_risk}
                        rest={{ isCompact: true }}
                      />
                    ) : (
                      intl.formatMessage(messages.nA)
                    )}
                  </Tooltip>
                </div>
              ),
            },
            {
              title: (
                <div key={key}>{`${
                  value?.impacted_clusters_count !== undefined
                    ? value.impacted_clusters_count.toLocaleString()
                    : intl.formatMessage(messages.nA)
                }`}</div>
              ),
            },
          ],
        },
        {
          fullWidth: true,
          cells: [
            {
              title: (
                <section className="pf-m-light pf-l-page__main-section pf-c-page__main-section">
                  <Stack hasGutter>
                    <RuleDetails
                      messages={formatMessages(
                        intl,
                        RuleDetailsMessagesKeys,
                        mapContentToValues(intl, adjustOCPRule(value))
                      )}
                      product={AdvisorProduct.ocp}
                      rule={adjustOCPRule(value)}
                      isDetailsPage={false}
                      showViewAffected
                      linkComponent={Link}
                    />
                  </Stack>
                </section>
              ),
            },
          ],
        },
      ]);
  };
  /* the category sorting compares only the first element of the array.
   Could be refactored later when we assign a priority numbers to each of the category
   and sort them in the array based on the priority.
*/
  const buildDisplayedRows = (rows, index, direction) => {
    const sortingRows = [...rows].sort((firstItem, secondItem) => {
      const d = direction === SortByDirection.asc ? 1 : -1;
      const fst = firstItem[0].rule[RECS_LIST_COLUMNS_KEYS[index]];
      const snd = secondItem[0].rule[RECS_LIST_COLUMNS_KEYS[index]];
      if (index === 3) {
        return (
          d * extractCategories(fst)[0].localeCompare(extractCategories(snd)[0])
        );
      }
      return fst > snd ? d : snd > fst ? -d : 0;
    });
    return sortingRows
      .slice(
        filters.limit * (page - 1),
        filters.limit * (page - 1) + filters.limit
      )
      .flatMap((row, index) => {
        const updatedRow = [...row];
        row[1].parent = index * 2;
        return updatedRow;
      });
  };

  const toggleRulesDisabled = (rule_status) =>
    updateFilters({
      ...filters,
      rule_status,
      offset: 0,
    });

  const filterConfigItems = [
    {
      label: intl.formatMessage(messages.name).toLowerCase(),
      filterValues: {
        key: 'text-filter',
        onChange: (_event, value) =>
          updateFilters({ ...filters, offset: 0, text: value }),
        value: searchText,
        placeholder: intl.formatMessage(messages.filterByName),
      },
    },
    {
      label: FILTER_CATEGORIES.total_risk.title,
      type: FILTER_CATEGORIES.total_risk.type,
      id: FILTER_CATEGORIES.total_risk.urlParam,
      value: `checkbox-${FILTER_CATEGORIES.total_risk.urlParam}`,
      filterValues: {
        key: `${FILTER_CATEGORIES.total_risk.urlParam}-filter`,
        onChange: (_event, values) =>
          addFilterParam(FILTER_CATEGORIES.total_risk.urlParam, values),
        value: filters.total_risk,
        items: FILTER_CATEGORIES.total_risk.values,
      },
    },
    {
      label: FILTER_CATEGORIES.impact.title,
      type: FILTER_CATEGORIES.impact.type,
      id: FILTER_CATEGORIES.impact.urlParam,
      value: `checkbox-${FILTER_CATEGORIES.impact.urlParam}`,
      filterValues: {
        key: `${FILTER_CATEGORIES.impact.urlParam}-filter`,
        onChange: (_event, values) =>
          addFilterParam(FILTER_CATEGORIES.impact.urlParam, values),
        value: filters.impact,
        items: FILTER_CATEGORIES.impact.values,
      },
    },
    {
      label: FILTER_CATEGORIES.likelihood.title,
      type: FILTER_CATEGORIES.likelihood.type,
      id: FILTER_CATEGORIES.likelihood.urlParam,
      value: `checkbox-${FILTER_CATEGORIES.likelihood.urlParam}`,
      filterValues: {
        key: `${FILTER_CATEGORIES.likelihood.urlParam}-filter`,
        onChange: (_event, values) =>
          addFilterParam(FILTER_CATEGORIES.likelihood.urlParam, values),
        value: filters.likelihood,
        items: FILTER_CATEGORIES.likelihood.values,
      },
    },
    {
      label: FILTER_CATEGORIES.category.title,
      type: FILTER_CATEGORIES.category.type,
      id: FILTER_CATEGORIES.category.urlParam,
      value: `checkbox-${FILTER_CATEGORIES.category.urlParam}`,
      filterValues: {
        key: `${FILTER_CATEGORIES.category.urlParam}-filter`,
        onChange: (_event, values) =>
          addFilterParam(FILTER_CATEGORIES.category.urlParam, values),
        value: filters.category,
        items: FILTER_CATEGORIES.category.values,
      },
    },
    {
      label: FILTER_CATEGORIES.rule_status.title,
      type: FILTER_CATEGORIES.rule_status.type,
      id: FILTER_CATEGORIES.rule_status.urlParam,
      value: `radio-${FILTER_CATEGORIES.rule_status.urlParam}`,
      filterValues: {
        key: `${FILTER_CATEGORIES.rule_status.urlParam}-filter`,
        onChange: (_event, value) => toggleRulesDisabled(value),
        value: `${filters.rule_status}`,
        items: FILTER_CATEGORIES.rule_status.values,
      },
    },
    {
      label: FILTER_CATEGORIES.impacting.title,
      type: FILTER_CATEGORIES.impacting.type,
      id: FILTER_CATEGORIES.impacting.urlParam,
      value: `checkbox-${FILTER_CATEGORIES.impacting.urlParam}`,
      filterValues: {
        key: `${FILTER_CATEGORIES.impacting.urlParam}-filter`,
        onChange: (e, values) =>
          addFilterParam(FILTER_CATEGORIES.impacting.urlParam, values),
        value: filters.impacting,
        items: FILTER_CATEGORIES.impacting.values,
      },
    },
  ];

  const onSort = (_e, index, direction) => {
    setRowsFiltered(false);
    return updateFilters({
      ...filters,
      sortIndex: index,
      sortDirection: direction,
    });
  };

  const pruneFilters = (localFilters, filterCategories) => {
    const prunedFilters = Object.entries(localFilters);
    return prunedFilters.length > 0
      ? prunedFilters.reduce((arr, item) => {
          if (filterCategories[item[0]]) {
            const category = filterCategories[item[0]];
            const chips = Array.isArray(item[1])
              ? item[1].map((value) => {
                  const selectedCategoryValue = category.values.find(
                    (values) => values.value === String(value)
                  );
                  return selectedCategoryValue
                    ? {
                        name:
                          selectedCategoryValue.text ||
                          selectedCategoryValue.label,
                        value,
                      }
                    : { name: value, value };
                })
              : [
                  {
                    name: category.values.find(
                      (values) => values.value === String(item[1])
                    ).label,
                    value: item[1],
                  },
                ];
            return [
              ...arr,
              {
                category: capitalize(category.title),
                chips,
                urlParam: category.urlParam,
              },
            ];
          } else if (item[0] === 'text') {
            return [
              ...arr,
              ...(item[1].length > 0
                ? [
                    {
                      category: 'Name',
                      chips: [{ name: item[1], value: item[1] }],
                      urlParam: item[0],
                    },
                  ]
                : []),
            ];
          } else {
            return arr;
          }
        }, [])
      : [];
  };

  // TODO: use the function from Common/Tables.js
  const buildFilterChips = () => {
    const localFilters = { ...filters };
    delete localFilters.sortIndex;
    delete localFilters.sortDirection;
    delete localFilters.offset;
    delete localFilters.limit;
    return pruneFilters(localFilters, FILTER_CATEGORIES);
  };

  const activeFiltersConfig = {
    showDeleteButton: true,
    deleteTitle: intl.formatMessage(messages.resetFilters),
    filters: buildFilterChips(),
    onDelete: (_event, itemsToRemove, isAll) => {
      if (isAll) {
        if (isEqual(filters, RECS_LIST_INITIAL_STATE)) {
          refetch();
        } else {
          updateFilters(RECS_LIST_INITIAL_STATE);
        }
      } else {
        itemsToRemove.map((item) => {
          const newFilter = {
            [item.urlParam]: Array.isArray(filters[item.urlParam])
              ? filters[item.urlParam].filter(
                  (value) => String(value) !== String(item.chips[0].value)
                )
              : '',
          };
          newFilter[item.urlParam].length > 0
            ? updateFilters({ ...filters, ...newFilter })
            : removeFilterParam(item.urlParam);
        });
      }
    },
  };

  const handleOnCollapse = (_e, rowId, isOpen) => {
    if (rowId === undefined) {
      // if undefined, all rows are affected
      setIsAllExpanded(isOpen);
      setDisplayedRows(
        displayedRows.map((row) => ({
          ...row,
          isOpen: isOpen,
        }))
      );
    } else {
      setDisplayedRows(
        displayedRows.map((row, index) =>
          index === rowId ? { ...row, isOpen } : row
        )
      );
    }
  };

  const ackRule = async (rowId) => {
    const rule = displayedRows[rowId].rule;

    try {
      if (!rule.disabled) {
        // show disable rule modal
        setSelectedRule(rule);
        setDisableRuleOpen(true);
      } else {
        try {
          await Delete(`${BASE_URL}/v2/ack/${rule.rule_id}/`);
          notify({
            variant: 'success',
            timeout: true,
            dismissable: true,
            title: intl.formatMessage(messages.recSuccessfullyEnabled),
          });
          refetch();
        } catch (error) {
          notify({
            variant: 'danger',
            dismissable: true,
            title: intl.formatMessage(messages.error),
            description: `${error}`,
          });
        }
      }
    } catch (error) {
      notify({
        variant: 'danger',
        dismissable: true,
        title: rule.disabled
          ? intl.formatMessage(messages.rulesTableErrorEnabled)
          : intl.formatMessage(messages.rulesTableErrorDisabled),
        description: `${error}`,
      });
    }
  };

  const actionResolver = (rowData, { rowIndex }) => {
    const rule = displayedRows?.[rowIndex]?.rule
      ? displayedRows[rowIndex].rule
      : null;
    if (rowIndex % 2 !== 0 || !rule) {
      return null;
    }

    return rule && !rule.disabled
      ? [
          {
            title: intl.formatMessage(messages.disableRule),
            onClick: (_event, rowId) => ackRule(rowId),
          },
        ]
      : [
          {
            title: intl.formatMessage(messages.enableRule),
            onClick: (_event, rowId) => ackRule(rowId),
          },
        ];
  };

  return (
    <div id="recs-list-table" data-ouia-safe={testSafe}>
      {disableRuleOpen && (
        <DisableRule
          handleModalToggle={setDisableRuleOpen}
          isModalOpen={disableRuleOpen}
          rule={selectedRule}
          afterFn={refetch}
        />
      )}
      <PrimaryToolbar
        pagination={{
          itemCount: filteredRows.length,
          page: filters.offset / filters.limit + 1,
          perPage: Number(filters.limit),
          onSetPage(_event, page) {
            setRowsFiltered(false);
            updateFilters({
              ...filters,
              offset: filters.limit * (page - 1),
            });
          },
          onPerPageSelect(_event, perPage) {
            setRowsFiltered(false);
            updateFilters({ ...filters, limit: perPage, offset: 0 });
          },
          isCompact: true,
          ouiaId: 'pager',
        }}
        filterConfig={{
          items: filterConfigItems,
          isDisabled: loadingState || errorState,
        }}
        activeFiltersConfig={errorState ? undefined : activeFiltersConfig}
      />
      <Table
        aria-label="Table of recommendations"
        ouiaId="recommendations"
        variant={TableVariant.compact}
        cells={RECS_LIST_COLUMNS}
        rows={
          errorState || loadingState || noMatch ? (
            [
              {
                fullWidth: true,
                cells: [
                  {
                    props: {
                      colSpan: RECS_LIST_COLUMNS.length + 1,
                    },
                    title: errorState ? (
                      <ErrorState />
                    ) : loadingState ? (
                      <Loading />
                    ) : (
                      <NoMatchingRecs />
                    ),
                  },
                ],
              },
            ]
          ) : successState ? (
            displayedRows
          ) : (
            <ErrorState />
          )
        }
        onCollapse={handleOnCollapse} // TODO: set undefined when there is an empty state
        sortBy={{
          index: filters.sortIndex,
          direction: filters.sortDirection,
        }}
        onSort={onSort}
        actionResolver={actionResolver}
        isStickyHeader
        ouiaSafe={testSafe}
        canCollapseAll
      >
        <TableHeader />
        <TableBody />
      </Table>
      <Pagination
        ouiaId="pager"
        itemCount={filteredRows.length}
        page={filters.offset / filters.limit + 1}
        perPage={Number(filters.limit)}
        onSetPage={(_e, page) =>
          updateFilters({
            ...filters,
            offset: filters.limit * (page - 1),
          })
        }
        onPerPageSelect={(_e, perPage) =>
          updateFilters({ ...filters, limit: perPage, offset: 0 })
        }
        widgetId={`pagination-options-menu-bottom`}
        variant={PaginationVariant.bottom}
      />
    </div>
  );
}
Example #13
Source File: ClustersListTable.js    From ocp-advisor-frontend with Apache License 2.0 4 votes vote down vote up
ClustersListTable = ({
  query: { isError, isUninitialized, isFetching, isSuccess, data, refetch },
}) => {
  const intl = useIntl();
  const dispatch = useDispatch();
  const updateFilters = (payload) =>
    dispatch(updateClustersListFilters(payload));
  const filters = useSelector(({ filters }) => filters.clustersListState);

  const clusters = data?.data || [];
  const page = filters.offset / filters.limit + 1;

  const [filteredRows, setFilteredRows] = useState([]);
  const [displayedRows, setDisplayedRows] = useState([]);
  // helps to distinguish the state when the API data received but not yet filtered
  const [rowsFiltered, setRowsFiltered] = useState(false);
  const [filterBuilding, setFilterBuilding] = useState(true);
  const { search } = useLocation();
  const loadingState = isUninitialized || isFetching || !rowsFiltered;
  const errorState = isError;
  const noMatch = clusters.length > 0 && filteredRows.length === 0;
  const successState = isSuccess;

  const removeFilterParam = (param) =>
    _removeFilterParam(filters, updateFilters, param);

  const addFilterParam = (param, values) =>
    _addFilterParam(filters, updateFilters, param, values);

  useEffect(() => {
    setDisplayedRows(buildDisplayedRows(filteredRows));
  }, [filteredRows, filters.limit, filters.offset]);

  useEffect(() => {
    setFilteredRows(buildFilteredRows(clusters));
    if (isSuccess && !rowsFiltered) {
      setRowsFiltered(true);
    }
  }, [data, filters]);

  useEffect(() => {
    if (search && filterBuilding) {
      const paramsObject = paramParser(search);

      if (paramsObject.sort) {
        const sortObj = translateSortParams(paramsObject.sort);
        paramsObject.sortIndex = CLUSTERS_LIST_COLUMNS_KEYS.indexOf(
          sortObj.name
        );
        paramsObject.sortDirection = sortObj.direction;
      }
      paramsObject.offset &&
        (paramsObject.offset = Number(paramsObject.offset[0]));
      paramsObject.limit &&
        (paramsObject.limit = Number(paramsObject.limit[0]));
      paramsObject.impacting &&
        !Array.isArray(paramsObject.impacting) &&
        (paramsObject.impacting = [`${paramsObject.impacting}`]);
      updateFilters({ ...filters, ...paramsObject });
    }
    setFilterBuilding(false);
  }, []);

  useEffect(() => {
    if (!filterBuilding) {
      updateSearchParams(filters, CLUSTERS_LIST_COLUMNS_KEYS);
    }
  }, [filters, filterBuilding]);

  const buildFilteredRows = (items) => {
    const filtered = items.filter((it) => {
      return passFiltersCluster(it, filters);
    });
    const mapped = filtered.map((it, index) => {
      if (
        it.cluster_version !== undefined &&
        it.cluster_version !== '' &&
        !valid(coerce(it.cluster_version))
      ) {
        console.error(
          `Cluster version ${it.cluster_version} has invalid format!`
        );
      }
      const ver = toValidSemVer(it.cluster_version);

      return {
        entity: it,
        cells: [
          <span key={index}>
            <Link to={`clusters/${it.cluster_id}`}>
              {it.cluster_name || it.cluster_id}
            </Link>
          </span>,
          ver === '0.0.0' ? intl.formatMessage(messages.notAvailable) : ver,
          it.total_hit_count,
          it.hits_by_total_risk?.[4] || 0,
          it.hits_by_total_risk?.[3] || 0,
          it.hits_by_total_risk?.[2] || 0,
          it.hits_by_total_risk?.[1] || 0,
          <span key={index}>
            {it.last_checked_at ? (
              <DateFormat
                extraTitle={`${intl.formatMessage(messages.lastSeen)}: `}
                date={it.last_checked_at}
                variant="relative"
              />
            ) : (
              <Tooltip
                key={index}
                content={
                  <span>
                    {intl.formatMessage(messages.lastSeen) + ': '}
                    {intl.formatMessage(messages.nA)}
                  </span>
                }
              >
                <span>{intl.formatMessage(messages.nA)}</span>
              </Tooltip>
            )}
          </span>,
        ],
      };
    });
    const sorted =
      filters.sortIndex === -1
        ? mapped
        : mapped.sort((a, b) => {
            let fst, snd;
            const d = filters.sortDirection === SortByDirection.asc ? 1 : -1;
            switch (filters.sortIndex) {
              case CLUSTERS_TABLE_CELL_NAME:
                fst = a.entity.cluster_name || a.entity.cluster_id;
                snd = b.entity.cluster_name || b.entity.cluster_id;
                return fst.localeCompare(snd) ? fst.localeCompare(snd) * d : 0;
              case CLUSTERS_TABLE_CELL_VERSION:
                return compareSemVer(
                  toValidSemVer(a.entity.cluster_version),
                  toValidSemVer(b.entity.cluster_version),
                  d
                );
              case CLUSTERS_TABLE_CELL_LAST_SEEN:
                fst = new Date(a.entity.last_checked_at || 0);
                snd = new Date(b.entity.last_checked_at || 0);
                return fst > snd ? d : snd > fst ? -d : 0;
              default:
                fst = a.cells[filters.sortIndex];
                snd = b.cells[filters.sortIndex];
                return fst > snd ? d : snd > fst ? -d : 0;
            }
          });
    return sorted;
  };

  const buildDisplayedRows = (items) =>
    items.slice(
      filters.limit * (page - 1),
      filters.limit * (page - 1) + filters.limit
    );

  const filterConfigItems = [
    {
      label: intl.formatMessage(messages.name).toLowerCase(),
      filterValues: {
        key: 'text-filter',
        onChange: (_event, value) => updateFilters({ ...filters, text: value }),
        value: filters.text,
        placeholder: intl.formatMessage(messages.filterByName),
      },
    },
    {
      label: intl.formatMessage(messages.version),
      placeholder: intl.formatMessage(messages.filterByVersion),
      type: conditionalFilterType.checkbox,
      filterValues: {
        id: 'version-filter',
        key: 'version-filter',
        onChange: (event, value) => addFilterParam('version', value),
        value: filters.version,
        items: uniqBy(
          clusters
            .filter(
              (c) => c.cluster_version !== undefined && c.cluster_version !== ''
            )
            .map((c) => ({
              value: toValidSemVer(c.cluster_version),
            }))
            .sort((a, b) =>
              compareSemVer(
                toValidSemVer(a.cluster_version),
                toValidSemVer(b.cluster_version),
                1
              )
            )
            .reverse(), // should start from the latest version
          'value'
        ),
      },
    },
    {
      label: CLUSTER_FILTER_CATEGORIES.hits.title,
      type: CLUSTER_FILTER_CATEGORIES.hits.type,
      id: CLUSTER_FILTER_CATEGORIES.hits.urlParam,
      value: `checkbox-${CLUSTER_FILTER_CATEGORIES.hits.urlParam}`,
      filterValues: {
        key: `${CLUSTER_FILTER_CATEGORIES.hits.urlParam}-filter`,
        onChange: (_event, values) =>
          addFilterParam(CLUSTER_FILTER_CATEGORIES.hits.urlParam, values),
        value: filters.hits,
        items: CLUSTER_FILTER_CATEGORIES.hits.values,
      },
    },
  ];

  const activeFiltersConfig = {
    showDeleteButton: true,
    deleteTitle: intl.formatMessage(messages.resetFilters),
    filters: buildFilterChips(filters, CLUSTER_FILTER_CATEGORIES),
    onDelete: (_event, itemsToRemove, isAll) => {
      if (isAll) {
        if (isEqual(filters, CLUSTERS_LIST_INITIAL_STATE)) {
          refetch();
        } else {
          updateFilters(CLUSTERS_LIST_INITIAL_STATE);
        }
      } else {
        itemsToRemove.map((item) => {
          const newFilter = {
            [item.urlParam]: Array.isArray(filters[item.urlParam])
              ? filters[item.urlParam].filter(
                  (value) => String(value) !== String(item.chips[0].value)
                )
              : '',
          };
          newFilter[item.urlParam].length > 0
            ? updateFilters({ ...filters, ...newFilter })
            : removeFilterParam(item.urlParam);
        });
      }
    },
  };

  const onSort = (_e, index, direction) => {
    updateFilters({ ...filters, sortIndex: index, sortDirection: direction });
  };

  return (
    <>
      {isSuccess && clusters.length === 0 ? (
        <NoRecsForClusters /> // TODO: do not mix this logic in the table component
      ) : (
        <div id="clusters-list-table">
          <PrimaryToolbar
            pagination={{
              itemCount: filteredRows.length,
              page,
              perPage: filters.limit,
              onSetPage: (_event, page) =>
                updateFilters({
                  ...filters,
                  offset: filters.limit * (page - 1),
                }),
              onPerPageSelect: (_event, perPage) =>
                updateFilters({ ...filters, limit: perPage, offset: 0 }),
              isCompact: true,
              ouiaId: 'pager',
            }}
            filterConfig={{ items: filterConfigItems }}
            activeFiltersConfig={activeFiltersConfig}
          />
          <Table
            aria-label="Table of clusters"
            ouiaId="clusters"
            variant={TableVariant.compact}
            cells={CLUSTERS_LIST_COLUMNS}
            rows={
              errorState || loadingState || noMatch ? (
                [
                  {
                    fullWidth: true,
                    cells: [
                      {
                        props: {
                          colSpan: CLUSTERS_LIST_COLUMNS.length + 1,
                        },
                        title: errorState ? (
                          <ErrorState />
                        ) : loadingState ? (
                          <Loading />
                        ) : (
                          <NoMatchingClusters />
                        ),
                      },
                    ],
                  },
                ]
              ) : successState ? (
                displayedRows
              ) : (
                <ErrorState />
              )
            }
            sortBy={{
              index: filters.sortIndex,
              direction: filters.sortDirection,
            }}
            onSort={onSort}
            isStickyHeader
          >
            <TableHeader />
            <TableBody />
          </Table>
          <Pagination
            ouiaId="pager"
            itemCount={filteredRows.length}
            page={filters.offset / filters.limit + 1}
            perPage={Number(filters.limit)}
            onSetPage={(_e, page) =>
              updateFilters({
                ...filters,
                offset: filters.limit * (page - 1),
              })
            }
            onPerPageSelect={(_e, perPage) =>
              updateFilters({ ...filters, limit: perPage, offset: 0 })
            }
            widgetId={`pagination-options-menu-bottom`}
            variant={PaginationVariant.bottom}
          />
        </div>
      )}
    </>
  );
}
Example #14
Source File: ClusterRules.js    From ocp-advisor-frontend with Apache License 2.0 4 votes vote down vote up
ClusterRules = ({ cluster }) => {
  const intl = useIntl();
  const dispatch = useDispatch();
  const updateFilters = (filters) =>
    dispatch(updateClusterRulesFilters(filters));
  const filters = useSelector(({ filters }) => filters.clusterRulesState);
  const { isError, isUninitialized, isFetching, isSuccess, data, error } =
    cluster;
  const reports = data?.report?.data || [];
  const [filteredRows, setFilteredRows] = useState([]);
  const [displayedRows, setDisplayedRows] = useState([]);
  const [isAllExpanded, setIsAllExpanded] = useState(false);
  const [expandFirst, setExpandFirst] = useState(true);
  const [firstRule, setFirstRule] = useState(''); // show a particular rule first
  const results = filteredRows.length;
  const { search } = useLocation();
  const [rowsUpdating, setRowsUpdating] = useState(true);
  const [rowsFiltered, setRowsFiltered] = useState(false);
  const loadingState = isUninitialized || isFetching || rowsUpdating;
  const errorState = isError;
  const successState = isSuccess;
  const noInput = successState && reports.length === 0;
  const noMatch = reports.length > 0 && filteredRows.length === 0;

  const removeFilterParam = (param) =>
    _removeFilterParam(filters, updateFilters, param);

  const addFilterParam = (param, values) => {
    setExpandFirst(false);
    setFirstRule('');
    return _addFilterParam(filters, updateFilters, param, values);
  };

  useEffect(() => {
    if (search) {
      const paramsObject = paramParser(search);
      if (paramsObject.sort) {
        const sortObj = translateSortParams(paramsObject.sort);
        paramsObject.sortIndex = CLUSTER_RULES_COLUMNS_KEYS.indexOf(
          sortObj.name
        );
        paramsObject.sortDirection = sortObj.direction;
      }
      if (paramsObject.first) {
        setFirstRule(paramsObject.first);
        delete paramsObject.first;
      }
      updateFilters({ ...filters, ...paramsObject });
    }
  }, []);

  useEffect(() => {
    setFilteredRows(buildFilteredRows(reports, filters));
  }, [data, filters]);

  useEffect(() => {
    setDisplayedRows(
      buildDisplayedRows(filteredRows, filters.sortIndex, filters.sortDirection)
    );
    setRowsFiltered(true);
  }, [
    filteredRows,
    filters.limit,
    filters.offset,
    filters.sortIndex,
    filters.sortDirection,
  ]);

  useEffect(() => {
    if (rowsFiltered) {
      setRowsUpdating(false);
    }
  }, [rowsFiltered]);

  const handleOnCollapse = (_e, rowId, isOpen) => {
    if (rowId === undefined) {
      // if undefined, all rows are affected
      setIsAllExpanded(isOpen);
      setDisplayedRows(
        displayedRows.map((row) => ({
          ...row,
          isOpen: isOpen,
        }))
      );
    } else {
      setDisplayedRows(
        displayedRows.map((row, index) =>
          index === rowId ? { ...row, isOpen } : row
        )
      );
    }
  };

  const buildFilteredRows = (allRows, filters) => {
    const expandedRowsSet = new Set(
      displayedRows
        .filter((ruleExpanded) => ruleExpanded?.isOpen)
        .map((object) => object?.rule?.rule_id)
    );

    return allRows
      .filter((rule) => passFilters(rule, filters))
      .map((value, key) => [
        {
          rule: value,
          isOpen: isAllExpanded || expandedRowsSet?.has(value?.rule_id),
          cells: [
            {
              title: (
                <div>
                  {value?.description || value?.rule_id}{' '}
                  <RuleLabels rule={value} />
                </div>
              ),
            },
            {
              title: (
                <div key={key}>
                  <DateFormat
                    date={value.created_at}
                    type="relative"
                    tooltipProps={{ position: TooltipPosition.bottom }}
                  />
                </div>
              ),
            },
            {
              title: (
                <div key={key} style={{ verticalAlign: 'top' }}>
                  {value?.likelihood && value?.impact ? (
                    <Tooltip
                      key={key}
                      position={TooltipPosition.bottom}
                      content={
                        // TODO: refine fields lookup
                        <span>
                          The <strong>likelihood</strong> that this will be a
                          problem is{' '}
                          {value.likelihood
                            ? LIKELIHOOD_LABEL[value.likelihood]
                            : 'unknown'}
                          .The <strong>impact</strong> of the problem would be{' '}
                          {value.impact
                            ? IMPACT_LABEL[value.impact]
                            : 'unknown'}{' '}
                          if it occurred.
                        </span>
                      }
                    >
                      <InsightsLabel
                        value={value.total_risk}
                        rest={{ isCompact: true }}
                      />
                    </Tooltip>
                  ) : (
                    <InsightsLabel
                      value={value.total_risk}
                      rest={{ isCompact: true }}
                    />
                  )}
                </div>
              ),
            },
          ],
        },
        {
          fullWidth: true,
          cells: [
            {
              title: (
                <ReportDetails
                  key={`child-${key}`}
                  report={{
                    rule: value,
                    resolution: value.resolution,
                    details: value.extra_data,
                  }}
                />
              ),
            },
          ],
        },
      ]);
  };

  const buildDisplayedRows = (rows, index, direction) => {
    let sortingRows = [...rows];
    if (index >= 0 && !firstRule) {
      const d = direction === SortByDirection.asc ? 1 : -1;
      sortingRows = [...rows].sort((firstItem, secondItem) => {
        const fst = firstItem[0].rule[CLUSTER_RULES_COLUMNS_KEYS[index]];
        const snd = secondItem[0].rule[CLUSTER_RULES_COLUMNS_KEYS[index]];
        return fst > snd ? d : snd > fst ? -d : 0;
      });
    } else if (firstRule) {
      const i = rows.findIndex((row) => {
        const rule = row[0].rule;
        /* rule_id is given with the plugin name only,
           thus we need to look at extra_data for the error key */
        return (
          rule.rule_id.split('.report')[0] === getPluginName(firstRule) &&
          rule.extra_data.error_key === getErrorKey(firstRule)
        );
      });
      i !== -1 && sortingRows.unshift(sortingRows.splice(i, 1)[0]);
    }
    return sortingRows.flatMap((row, index) => {
      const updatedRow = [...row];
      if (expandFirst && index === 0) {
        row[0].isOpen = true;
      }
      row[1].parent = index * 2;
      return updatedRow;
    });
  };

  const onSort = (_e, index, direction) => {
    setExpandFirst(false);
    setFirstRule('');
    return updateFilters({
      ...filters,
      sortIndex: index,
      sortDirection: direction,
    });
  };

  const filterConfigItems = [
    {
      label: 'description',
      filterValues: {
        key: 'text-filter',
        onChange: (_e, value) => addFilterParam('text', value),
        value: filters.text,
      },
    },
    {
      label: FC.total_risk.title,
      type: FC.total_risk.type,
      id: FC.total_risk.urlParam,
      value: `checkbox-${FC.total_risk.urlParam}`,
      filterValues: {
        key: `${FC.total_risk.urlParam}-filter`,
        onChange: (_e, values) =>
          addFilterParam(FILTER_CATEGORIES.total_risk.urlParam, values),
        value: filters.total_risk,
        items: FC.total_risk.values,
      },
    },
    {
      label: FC.category.title,
      type: FC.category.type,
      id: FC.category.urlParam,
      value: `checkbox-${FC.category.urlParam}`,
      filterValues: {
        key: `${FC.category.urlParam}-filter`,
        onChange: (_e, values) =>
          addFilterParam(FILTER_CATEGORIES.category.urlParam, values),
        value: filters.category,
        items: FC.category.values,
      },
    },
  ];

  const pruneFilters = (localFilters, filterCategories) => {
    const prunedFilters = Object.entries(localFilters);
    return prunedFilters.length > 0
      ? prunedFilters.reduce((arr, item) => {
          if (filterCategories[item[0]]) {
            const category = filterCategories[item[0]];
            const chips = Array.isArray(item[1])
              ? item[1].map((value) => {
                  const selectedCategoryValue = category.values.find(
                    (values) => values.value === String(value)
                  );
                  return selectedCategoryValue
                    ? {
                        name:
                          selectedCategoryValue.text ||
                          selectedCategoryValue.label,
                        value,
                      }
                    : { name: value, value };
                })
              : [
                  {
                    name: category.values.find(
                      (values) => values.value === String(item[1])
                    ).label,
                    value: item[1],
                  },
                ];
            return [
              ...arr,
              {
                category: capitalize(category.title),
                chips,
                urlParam: category.urlParam,
              },
            ];
          } else if (item[0] === 'text') {
            return [
              ...arr,
              ...(item[1].length > 0
                ? [
                    {
                      category: intl.formatMessage(messages.description),
                      chips: [{ name: item[1], value: item[1] }],
                      urlParam: item[0],
                    },
                  ]
                : []),
            ];
          } else {
            return arr;
          }
        }, [])
      : [];
  };

  const buildFilterChips = () => {
    const localFilters = { ...filters };
    delete localFilters.sortIndex;
    delete localFilters.sortDirection;
    delete localFilters.offset;
    delete localFilters.limit;
    return pruneFilters(localFilters, FILTER_CATEGORIES);
  };

  const activeFiltersConfig = {
    deleteTitle: intl.formatMessage(messages.resetFilters),
    filters: buildFilterChips(),
    onDelete: (_event, itemsToRemove, isAll) => {
      if (isAll) {
        updateFilters(CLUSTER_RULES_INITIAL_STATE);
      } else {
        itemsToRemove.map((item) => {
          const newFilter = {
            [item.urlParam]: Array.isArray(filters[item.urlParam])
              ? filters[item.urlParam].filter(
                  (value) => String(value) !== String(item.chips[0].value)
                )
              : '',
          };
          newFilter[item.urlParam].length > 0
            ? updateFilters({ ...filters, ...newFilter })
            : removeFilterParam(item.urlParam);
        });
      }
    },
  };

  return (
    <div id="cluster-recs-list-table">
      <PrimaryToolbar
        filterConfig={{
          items: filterConfigItems,
          isDisabled: loadingState || errorState || reports.length === 0,
        }}
        pagination={
          <React.Fragment>
            {results === 1
              ? `${results} ${intl.formatMessage(messages.recommendation)}`
              : `${results} ${intl.formatMessage(messages.recommendations)}`}
          </React.Fragment>
        }
        activeFiltersConfig={
          loadingState || errorState || reports.length === 0
            ? undefined
            : activeFiltersConfig
        }
      />
      <Table
        aria-label={'Cluster recommendations table'}
        ouiaId="recommendations"
        onCollapse={handleOnCollapse} // TODO: set undefined when there is an empty state
        rows={
          errorState || loadingState || noMatch || noInput ? (
            [
              {
                fullWidth: true,
                cells: [
                  {
                    props: {
                      colSpan: CLUSTER_RULES_COLUMNS.length + 1,
                    },
                    title: errorState ? (
                      error?.status === 404 ? (
                        <NoInsightsResults /> // no Insights results received yet
                      ) : (
                        <NoRecsError /> // any other problem
                      )
                    ) : loadingState ? (
                      <Loading />
                    ) : noInput ? (
                      <NoRecsAffecting />
                    ) : (
                      <NoMatchingRecs />
                    ),
                  },
                ],
              },
            ]
          ) : successState ? (
            displayedRows
          ) : (
            <ErrorState />
          )
        }
        cells={CLUSTER_RULES_COLUMNS}
        sortBy={{
          index: filters.sortIndex,
          direction: filters.sortDirection,
        }}
        onSort={onSort}
        variant={TableVariant.compact}
        isStickyHeader
        canCollapseAll
      >
        <TableHeader />
        <TableBody />
      </Table>
    </div>
  );
}
Example #15
Source File: MUARolesTable.js    From access-requests-frontend with Apache License 2.0 4 votes vote down vote up
MUARolesTable = ({
  roles: selectedRoles,
  setRoles: setSelectedRoles,
}) => {
  const isReadOnly = setSelectedRoles === undefined;
  const columns = ['Role name', 'Role description', 'Permissions'];
  const [rows, setRows] = React.useState(Array.from(rolesCache));
  const [applications, setApplications] = React.useState(applicationsCache);
  React.useEffect(() => {
    if (rolesCache.length === 0 || applicationsCache.length === 0) {
      apiInstance
        .get(
          `${API_BASE}/roles/?limit=9999&order_by=display_name&add_fields=groups_in_count`,
          { headers: { Accept: 'application/json' } }
        )
        .then(({ data }) => {
          data.forEach((role) => {
            role.isExpanded = false;
            role.permissions = role.accessCount;
          });
          rolesCache = data.map((role) => Object.assign({}, role));
          setRows(data);

          // Build application filter from data
          const apps = Array.from(
            data
              .map((role) => role.applications)
              .flat()
              .reduce((acc, cur) => {
                acc.add(cur);
                return acc;
              }, new Set())
          ).sort();
          applicationsCache = apps;
          setApplications(apps);
        })
        .catch((err) =>
          dispatch(
            addNotification({
              variant: 'danger',
              title: 'Could not fetch roles list',
              description: err.message,
            })
          )
        );
    }
  }, []);

  // Sorting
  const [activeSortIndex, setActiveSortIndex] = React.useState('name');
  const [activeSortDirection, setActiveSortDirection] = React.useState('asc');
  const onSort = (_ev, index, direction) => {
    setActiveSortIndex(index);
    setActiveSortDirection(direction);
  };

  // Filtering
  const [isDropdownOpen, setIsDropdownOpen] = React.useState(false);
  const [filterColumn, setFilterColumn] = React.useState(columns[0]);
  const [isSelectOpen, setIsSelectOpen] = React.useState(false);
  const [appSelections, setAppSelections] = React.useState([]);
  const [nameFilter, setNameFilter] = React.useState('');
  const hasFilters = appSelections.length > 0 || nameFilter;
  const selectLabelId = 'filter-application';
  const selectPlaceholder = 'Filter by application';

  const selectedNames = selectedRoles.map((role) => role.display_name);
  const filteredRows = rows
    .filter((row) =>
      appSelections.length > 0
        ? row.applications.find((app) => appSelections.includes(app))
        : true
    )
    .filter((row) => row.name.toLowerCase().includes(nameFilter))
    .filter((row) =>
      isReadOnly ? selectedNames.includes(row.display_name) : true
    );

  // Pagination
  const [page, setPage] = React.useState(1);
  const [perPage, setPerPage] = React.useState(10);
  const AccessRequestsPagination = ({ id }) => (
    <Pagination
      itemCount={filteredRows.length}
      perPage={perPage}
      page={page}
      onSetPage={(_ev, pageNumber) => setPage(pageNumber)}
      id={'access-requests-roles-table-pagination-' + id}
      variant={id}
      onPerPageSelect={(_ev, perPage) => {
        setPage(1);
        setPerPage(perPage);
      }}
      isCompact={id === 'top'}
    />
  );
  AccessRequestsPagination.propTypes = {
    id: PropTypes.string,
  };
  const pagedRows = filteredRows
    .sort((a, b) => {
      if (typeof a[activeSortIndex] === 'number') {
        // numeric sort
        if (activeSortDirection === 'asc') {
          return a[activeSortIndex] - b[activeSortIndex];
        }

        return b[activeSortIndex] - a[activeSortIndex];
      } else {
        // string sort
        if (activeSortDirection === 'asc') {
          return (a[activeSortIndex] + '').localeCompare(b[activeSortIndex]);
        }

        return (b[activeSortIndex] + '').localeCompare(a[activeSortIndex]);
      }
    })
    .slice((page - 1) * perPage, page * perPage);

  // Selecting
  const [isBulkSelectOpen, setIsBulkSelectOpen] = React.useState(false);
  const anySelected = selectedRoles.length > 0;
  const someChecked = anySelected ? null : false;
  const isChecked =
    selectedRoles.length === filteredRows.length && selectedRoles.length > 0
      ? true
      : someChecked;
  const onSelect = (_ev, isSelected, rowId) => {
    const changed = pagedRows[rowId].display_name;
    if (isSelected) {
      setSelectedRoles(selectedRoles.concat(changed));
    } else {
      setSelectedRoles(selectedRoles.filter((role) => role !== changed));
    }
  };

  const onSelectAll = (_ev, isSelected) => {
    if (isSelected) {
      setSelectedRoles(filteredRows.map((row) => row.display_name));
    } else {
      setSelectedRoles([]);
    }
  };

  const clearFiltersButton = (
    <Button
      variant="link"
      onClick={() => {
        setAppSelections([]);
        setNameFilter('');
      }}
    >
      Clear filters
    </Button>
  );
  const roleToolbar = isReadOnly ? null : (
    <Toolbar id="access-requests-roles-table-toolbar">
      <ToolbarContent>
        <ToolbarItem>
          <Dropdown
            onSelect={() => setIsBulkSelectOpen(!isBulkSelectOpen)}
            position="left"
            toggle={
              <DropdownToggle
                splitButtonItems={[
                  <DropdownToggleCheckbox
                    key="a"
                    id="example-checkbox-2"
                    aria-label={anySelected ? 'Deselect all' : 'Select all'}
                    isChecked={isChecked}
                    onClick={() => onSelectAll(null, !anySelected)}
                  />,
                ]}
                onToggle={(isOpen) => setIsBulkSelectOpen(isOpen)}
                isDisabled={rows.length === 0}
              >
                {selectedRoles.length !== 0 && (
                  <React.Fragment>
                    {selectedRoles.length} selected
                  </React.Fragment>
                )}
              </DropdownToggle>
            }
            isOpen={isBulkSelectOpen}
            dropdownItems={[
              <DropdownItem key="0" onClick={() => onSelectAll(null, false)}>
                Select none (0 items)
              </DropdownItem>,
              <DropdownItem
                key="1"
                onClick={() =>
                  setSelectedRoles(
                    selectedRoles.concat(pagedRows.map((r) => r.display_name))
                  )
                }
              >
                Select page ({Math.min(pagedRows.length, perPage)} items)
              </DropdownItem>,
              <DropdownItem key="2" onClick={() => onSelectAll(null, true)}>
                Select all ({filteredRows.length} items)
              </DropdownItem>,
            ]}
          />
        </ToolbarItem>
        <ToolbarItem>
          <InputGroup>
            <Dropdown
              isOpen={isDropdownOpen}
              onSelect={(ev) => {
                setIsDropdownOpen(false);
                setFilterColumn(ev.target.value);
                setIsSelectOpen(false);
              }}
              toggle={
                <DropdownToggle
                  onToggle={(isOpen) => setIsDropdownOpen(isOpen)}
                >
                  <FilterIcon /> {filterColumn}
                </DropdownToggle>
              }
              dropdownItems={['Role name', 'Application'].map((colName) => (
                // Filterable columns are RequestID, AccountID, and Status
                <DropdownItem key={colName} value={colName} component="button">
                  {capitalize(colName)}
                </DropdownItem>
              ))}
            />
            {filterColumn === 'Application' ? (
              <React.Fragment>
                <span id={selectLabelId} hidden>
                  {selectPlaceholder}
                </span>
                <Select
                  aria-labelledby={selectLabelId}
                  variant="checkbox"
                  aria-label="Select applications"
                  onToggle={(isOpen) => setIsSelectOpen(isOpen)}
                  onSelect={(_ev, selection) => {
                    if (appSelections.includes(selection)) {
                      setAppSelections(
                        appSelections.filter((s) => s !== selection)
                      );
                    } else {
                      setAppSelections([...appSelections, selection]);
                    }
                  }}
                  isOpen={isSelectOpen}
                  selections={appSelections}
                  isCheckboxSelectionBadgeHidden
                  placeholderText={selectPlaceholder}
                  style={{ maxHeight: '400px', overflowY: 'auto' }}
                >
                  {applications.map((app) => (
                    <SelectOption key={app} value={app}>
                      {capitalize(app.replace(/-/g, ' '))}
                    </SelectOption>
                  ))}
                </Select>
              </React.Fragment>
            ) : (
              <TextInput
                name="rolesSearch"
                id="rolesSearch"
                type="search"
                iconVariant="search"
                aria-label="Search input"
                placeholder="Filter by role name"
                value={nameFilter}
                onChange={(val) => setNameFilter(val)}
              />
            )}
          </InputGroup>
        </ToolbarItem>
        <ToolbarItem variant="pagination" align={{ default: 'alignRight' }}>
          <AccessRequestsPagination id="top" />
        </ToolbarItem>
      </ToolbarContent>
      {hasFilters && (
        <ToolbarContent>
          {nameFilter && (
            <ChipGroup categoryName="Role name">
              <Chip onClick={() => setNameFilter('')}>{nameFilter}</Chip>
            </ChipGroup>
          )}
          {appSelections.length > 0 && (
            <ChipGroup categoryName="Status">
              {appSelections.map((status) => (
                <Chip
                  key={status}
                  onClick={() =>
                    setAppSelections(appSelections.filter((s) => s !== status))
                  }
                >
                  {status}
                </Chip>
              ))}
            </ChipGroup>
          )}
          {clearFiltersButton}
        </ToolbarContent>
      )}
    </Toolbar>
  );

  const expandedColumns = ['Application', 'Resource type', 'Operation'];
  const dispatch = useDispatch();
  const onExpand = (row) => {
    row.isExpanded = !row.isExpanded;
    setRows([...rows]);
    if (!row.access) {
      apiInstance
        .get(`${API_BASE}/roles/${row.uuid}/`, {
          headers: { Accept: 'application/json' },
        })
        .then((res) => {
          row.access = res.access.map((a) => a.permission.split(':'));
          setRows([...rows]);
        })
        .catch((err) =>
          dispatch(
            addNotification({
              variant: 'danger',
              title: `Could not fetch permission list for ${row.name}.`,
              description: err.message,
            })
          )
        );
    }
  };
  const roleTable = (
    <TableComposable aria-label="My user access roles" variant="compact">
      <Thead>
        <Tr>
          {!isReadOnly && <Th />}
          <Th
            width={30}
            sort={{
              sortBy: {
                index: activeSortIndex,
                direction: activeSortDirection,
              },
              onSort,
              columnIndex: 'name',
            }}
          >
            {columns[0]}
          </Th>
          <Th
            width={50}
            sort={{
              sortBy: {
                index: activeSortIndex,
                direction: activeSortDirection,
              },
              onSort,
              columnIndex: 'description',
            }}
          >
            {columns[1]}
          </Th>
          <Th
            width={10}
            sort={{
              sortBy: {
                index: activeSortIndex,
                direction: activeSortDirection,
              },
              onSort,
              columnIndex: 'permissions',
            }}
            modifier="nowrap"
          >
            {columns[2]}
          </Th>
        </Tr>
      </Thead>
      {rows.length === 0 &&
        [...Array(perPage).keys()].map((i) => (
          <Tbody key={i}>
            <Tr>
              {!isReadOnly && <Td />}
              {columns.map((col, key) => (
                <Td dataLabel={col} key={key}>
                  <div
                    style={{ height: '22px' }}
                    className="ins-c-skeleton ins-c-skeleton__md"
                  >
                    {' '}
                  </div>
                </Td>
              ))}
            </Tr>
          </Tbody>
        ))}
      {pagedRows.map((row, rowIndex) => (
        <Tbody key={rowIndex}>
          <Tr>
            {!isReadOnly && (
              <Td
                select={{
                  rowIndex,
                  onSelect,
                  isSelected: selectedRoles.find((r) => r === row.display_name),
                }}
              />
            )}
            <Td dataLabel={columns[0]}>{row.display_name}</Td>
            <Td dataLabel={columns[1]} className="pf-m-truncate">
              <Tooltip entryDelay={1000} content={row.description}>
                <span className="pf-m-truncate pf-c-table__text">
                  {row.description}
                </span>
              </Tooltip>
            </Td>
            <Td
              dataLabel={columns[2]}
              className={css(
                'pf-c-table__compound-expansion-toggle',
                row.isExpanded && 'pf-m-expanded'
              )}
            >
              <button
                type="button"
                className="pf-c-table__button"
                onClick={() => onExpand(row)}
              >
                {row.permissions}
              </button>
            </Td>
          </Tr>
          <Tr isExpanded={row.isExpanded} borders={false}>
            {!isReadOnly && <Td />}
            <Td className="pf-u-p-0" colSpan={3}>
              <TableComposable isCompact className="pf-m-no-border-rows">
                <Thead>
                  <Tr>
                    {expandedColumns.map((col) => (
                      <Th key={col}>{col}</Th>
                    ))}
                  </Tr>
                </Thead>
                <Tbody>
                  {Array.isArray(row.access)
                    ? row.access.map((permissions) => (
                        <Tr key={permissions.join(':')}>
                          <Td dataLabel={expandedColumns[0]}>
                            {permissions[0]}
                          </Td>
                          <Td dataLabel={expandedColumns[1]}>
                            {permissions[1]}
                          </Td>
                          <Td dataLabel={expandedColumns[2]}>
                            {permissions[2]}
                          </Td>
                        </Tr>
                      ))
                    : [...Array(row.permissions).keys()].map((i) => (
                        <Tr key={i}>
                          {expandedColumns.map((val) => (
                            <Td key={val} dataLabel={val}>
                              <div
                                style={{ height: '22px' }}
                                className="ins-c-skeleton ins-c-skeleton__sm"
                              >
                                {' '}
                              </div>
                            </Td>
                          ))}
                        </Tr>
                      ))}
                </Tbody>
              </TableComposable>
            </Td>
          </Tr>
        </Tbody>
      ))}
      {pagedRows.length === 0 && hasFilters && (
        <Tr>
          <Td colSpan={columns.length}>
            <EmptyState variant="small">
              <EmptyStateIcon icon={SearchIcon} />
              <Title headingLevel="h2" size="lg">
                No matching requests found
              </Title>
              <EmptyStateBody>
                No results match the filter criteria. Remove all filters or
                clear all filters to show results.
              </EmptyStateBody>
              {clearFiltersButton}
            </EmptyState>
          </Td>
        </Tr>
      )}
    </TableComposable>
  );

  return (
    <React.Fragment>
      {!isReadOnly && (
        <React.Fragment>
          <Title headingLevel="h2">Select roles</Title>
          <p>Select the roles you would like access to.</p>
        </React.Fragment>
      )}
      {roleToolbar}
      {roleTable}
      {isReadOnly && <AccessRequestsPagination id="bottom" />}
    </React.Fragment>
  );
}
Example #16
Source File: DeviceDetail.js    From edge-frontend with Apache License 2.0 4 votes vote down vote up
GeneralInformationTab = () => {
  const writePermissions = useSelector(
    ({ permissionsReducer }) => permissionsReducer?.writePermissions
  );

  const { greenbootStatus, rhcHealth } = useSelector(
    ({ systemProfileStore }) => ({
      greenbootStatus: systemProfileStore?.systemProfile?.greenboot_status,
      rhcHealth: null,
    })
  );

  return (
    <Suspense fallback="">
      <GeneralInformation
        store={useStore()}
        writePermissions={writePermissions}
        SystemCardWrapper={(props) => (
          <Suspense fallback="">
            <SystemCard
              {...props}
              hasCPUs={false}
              hasSockets={false}
              hasCores={false}
              hasCPUFlags={false}
              hasRAM={false}
              hasSAP={false}
              extra={[
                {
                  title: (
                    <TitleWithPopover
                      title="GreenBoot Status"
                      content="This is a description about greenboot status"
                    />
                  ),
                  value: <GreenbootStatus status={greenbootStatus} />,
                },
              ]}
            />
          </Suspense>
        )}
        OperatingSystemCardWrapper={(props) => (
          <Suspense fallback="">
            {' '}
            <InfrastructureCard {...props} />
          </Suspense>
        )}
        BiosCardWrapper={(props) => (
          <Suspense fallback="">
            {' '}
            <ImageInformationCard {...props} />
          </Suspense>
        )}
        InfrastructureCardWrapper={(props) => (
          <Suspense fallback="">
            <BiosCard {...props} />
          </Suspense>
        )}
        ConfigurationCardWrapper={(props) => (
          <Suspense fallback="">
            <OperatingSystemCard {...props} hasKernelModules={true} />
          </Suspense>
        )}
        CollectionCardWrapper={(props) => (
          <Suspense fallback="">
            <CollectionCard
              {...props}
              extra={[
                {
                  title: 'RHC Health (broker functioning)',
                  value: statusHelper[rhcHealth?.toUpperCase()] || (
                    <Tooltip content="Unknown service status">
                      <OutlinedQuestionCircleIcon className="ins-c-inventory__detail--unknown" />
                    </Tooltip>
                  ),
                },
              ]}
            />
          </Suspense>
        )}
      />
    </Suspense>
  );
}
Example #17
Source File: RepositoryTable.js    From edge-frontend with Apache License 2.0 4 votes vote down vote up
RepositoryTable = ({
  data,
  count,
  isLoading,
  hasError,
  fetchRepos,
  openModal,
}) => {
  const actionResolver = (rowData) => {
    if (!rowData.rowInfo) {
      return [];
    }
    const { id, repoName, repoBaseURL } = rowData.rowInfo;
    return [
      {
        title: 'Edit',
        onClick: () =>
          openModal({
            type: 'edit',
            id: id,
            name: repoName,
            baseURL: repoBaseURL,
          }),
      },
      {
        title: 'Remove',
        onClick: () =>
          openModal({
            type: 'remove',
            id: id,
            name: repoName,
            baseURL: repoBaseURL,
          }),
      },
    ];
  };

  const buildRows = data.map(({ ID, Name, URL }) => {
    return {
      rowInfo: {
        id: ID,
        repoName: Name,
        repoBaseURL: URL,
      },
      cells: [
        {
          title: (
            <>
              <Text className="pf-u-mb-xs" component={TextVariants.p}>
                <Tooltip content={<div>{Name}</div>}>
                  <span>{truncateString(Name, 20)}</span>
                </Tooltip>
              </Text>
              <Text
                component={TextVariants.a}
                href={URL}
                target="_blank"
                rel="noopener noreferrer"
              >
                {URL} <ExternalLinkAltIcon className="pf-u-ml-sm" />
              </Text>
            </>
          ),
        },
      ],
    };
  });

  return (
    <>
      <GeneralTable
        apiFilterSort={true}
        isUseApi={true}
        loadTableData={fetchRepos}
        filters={filters}
        tableData={{
          count,
          data,
          isLoading,
          hasError,
        }}
        columnNames={[{ title: 'Name', type: 'name', sort: true }]}
        rows={buildRows}
        actionResolver={actionResolver}
        areActionsDisabled={() => false}
        defaultSort={{ index: 0, direction: 'asc' }}
        toolbarButtons={[
          {
            title: 'Add repository',
            click: () => openModal({ type: 'add' }),
          },
        ]}
      />
    </>
  );
}
Example #18
Source File: GroupTable.js    From edge-frontend with Apache License 2.0 4 votes vote down vote up
GroupTable = ({
  data,
  count,
  isLoading,
  hasError,
  handleCreateModal,
  handleRenameModal,
  handleDeleteModal,
  fetchGroups,
}) => {
  const [updateModal, setUpdateModal] = useState({
    isOpen: false,
    deviceData: null,
    imageData: null,
  });

  const actionResolver = (rowData) => {
    if (!rowData?.rowInfo) return [];
    const { id, title, devices, devicesImageInfo } = rowData?.rowInfo;
    const hasUpdate = devicesImageInfo?.some((image) => image.UpdateAvailable);

    return (
      id && [
        {
          title: 'Rename',
          onClick: () => handleRenameModal(id, title),
        },
        {
          title: 'Delete',
          onClick: () => handleDeleteModal(id, title),
        },
        {
          title: 'Update',
          onClick: () =>
            setUpdateModal((prevState) => ({
              ...prevState,
              deviceData: devices.map((device) => ({
                id: device.UUID,
                display_name: device.Name,
              })),
              imageId: devices.find((device) => device?.ImageID).ImageID,
              isOpen: true,
            })),
          isDisabled:
            devices.length > 0
              ? !(rowData?.rowInfo?.hasValidUpdate && hasUpdate)
              : true,
        },
      ]
    );
  };

  const buildRows = data?.map((rowData) => {
    const { ID, Name, Devices } = rowData?.DeviceGroup;
    let { DevicesImageInfo } = rowData;
    if (!DevicesImageInfo) {
      DevicesImageInfo = [];
    }
    const systems = Devices ?? [];
    const image = (
      <div>
        <Tooltip
          content={
            <div>
              {DevicesImageInfo.map((device, index) => (
                <p key={index}>{device.Name}</p>
              ))}
            </div>
          }
        >
          <span>Multiple images</span>
        </Tooltip>
      </div>
    );

    return {
      rowInfo: {
        id: ID,
        title: Name,
        image:
          DevicesImageInfo.length === 0
            ? '-'
            : DevicesImageInfo.length > 1
            ? 'Multiple images'
            : DevicesImageInfo[0]?.Name,
        devicesImageInfo: rowData.DevicesImageInfo,
        devices: Devices,
        hasValidUpdate: rowData?.DeviceGroup?.ValidUpdate,
      },
      cells: [
        {
          title: <Link to={`${paths['fleet-management']}/${ID}`}>{Name}</Link>,
        },
        {
          title: systems.length,
        },
        {
          title:
            DevicesImageInfo.length === 0
              ? '-'
              : DevicesImageInfo.length > 1
              ? image
              : DevicesImageInfo[0]?.Name,
        },
      ],
    };
  });

  return (
    <>
      <GeneralTable
        apiFilterSort={true}
        isUseApi={true}
        loadTableData={fetchGroups}
        filters={filters}
        tableData={{
          count,
          data,
          isLoading,
          hasError,
        }}
        columnNames={columns}
        rows={buildRows}
        actionResolver={actionResolver}
        areActionsDisabled={() => false}
        defaultSort={{ index: 0, direction: 'asc' }}
        emptyFilterState={{
          title: 'No matching groups found',
          body: 'To continue, edit your filter settings and try again',
        }}
        toolbarButtons={[
          {
            title: 'Create group',
            click: handleCreateModal,
          },
        ]}
      />
      {updateModal.isOpen && (
        <Suspense
          fallback={
            <Bullseye>
              <Spinner />
            </Bullseye>
          }
        >
          <UpdateDeviceModal
            navigateBack={() => {
              history.push({ pathname: history.location.pathname });
              setUpdateModal((prevState) => {
                return {
                  ...prevState,
                  isOpen: false,
                };
              });
            }}
            setUpdateModal={setUpdateModal}
            updateModal={updateModal}
            refreshTable={fetchGroups}
          />
        </Suspense>
      )}
    </>
  );
}
Example #19
Source File: DeviceTable.js    From edge-frontend with Apache License 2.0 4 votes vote down vote up
createRows = (devices, hasLinks) => {
  return devices?.map((device) => {
    let { DeviceName, DeviceGroups } = device;

    const {
      DeviceID,
      DeviceUUID,
      UpdateAvailable,
      LastSeen,
      ImageName,
      ImageSetID,
      // ImageID,
      Status,
    } = device;

    if (DeviceName === '') {
      // needs to be fixed with proper name in sync with inv
      DeviceName = 'localhost';
    }

    if (DeviceGroups === null) {
      DeviceGroups = [];
    }

    const deviceGroupTooltip = (
      <div>
        <Tooltip
          content={
            <div>
              {DeviceGroups.map((group, index) => (
                <p key={index}>{group.Name}</p>
              ))}
            </div>
          }
        >
          <span>Multiple groups</span>
        </Tooltip>
      </div>
    );

    return {
      rowInfo: {
        deviceID: DeviceID,
        id: DeviceUUID,
        display_name: DeviceName,
        updateImageData: UpdateAvailable,
        deviceStatus: getDeviceStatus(Status, UpdateAvailable),
        imageSetId: ImageSetID,
        imageName: ImageName,
        deviceGroups: DeviceGroups,
      },
      noApiSortFilter: [
        DeviceName || '',
        ImageName || '',
        '',
        LastSeen || '',
        getDeviceStatus(Status, UpdateAvailable),
      ],
      cells: [
        {
          title: hasLinks ? (
            <Link to={`${paths['inventory']}/${DeviceUUID}`}>{DeviceName}</Link>
          ) : (
            DeviceName
          ),
        },
        {
          title: ImageName ? (
            hasLinks ? (
              <Link to={`${paths['manage-images']}/${ImageSetID}/`}>
                {ImageName}
              </Link>
            ) : (
              ImageName
            )
          ) : (
            'unavailable'
          ),
        },
        {
          title:
            DeviceGroups.length === 0
              ? '-'
              : DeviceGroups.length === 1
              ? DeviceGroups[0].Name
              : deviceGroupTooltip,
        },
        {
          title: LastSeen ? <DateFormat date={LastSeen} /> : 'Unknown',
        },
        {
          title: (
            <DeviceStatus type={getDeviceStatus(Status, UpdateAvailable)} />
          ),
        },
      ],
    };
  });
}