@patternfly/react-core#Pagination JavaScript Examples

The following examples show how to use @patternfly/react-core#Pagination. 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: ToolbarFooter.js    From edge-frontend with Apache License 2.0 6 votes vote down vote up
ToolbarFooter = ({
  isLoading,
  count,
  perPage,
  setPerPage,
  page,
  setPage,
}) => {
  return (
    <Toolbar style={{ padding: '16px' }} id="toolbar-footer">
      <ToolbarContent>
        <ToolbarItem variant="pagination" align={{ default: 'alignRight' }}>
          {isLoading ? (
            <Skeleton width="400px" />
          ) : count > 0 ? (
            <Pagination
              data-testid="pagination-footer-test-id"
              itemCount={count}
              perPage={perPage}
              page={page}
              onSetPage={(_e, pageNumber) => setPage(pageNumber)}
              widgetId="pagination-options-menu-top"
              onPerPageSelect={(_e, perPage) => setPerPage(perPage)}
            />
          ) : null}
        </ToolbarItem>
      </ToolbarContent>
    </Toolbar>
  );
}
Example #2
Source File: TasksTables.js    From tasks-frontend with Apache License 2.0 6 votes vote down vote up
TasksTables = ({
  label,
  ouiaId,
  items = [],
  columns = [],
  filters = [],
  options = {},
  //toolbarProps: toolbarPropsProp,
  ...tablePropsRest
}) => {
  const { toolbarProps, tableProps } = useTableTools(items, columns, {
    filters,
    tableProps: tablePropsRest,
    ...options,
  });

  return (
    <React.Fragment>
      <PrimaryToolbar {...toolbarProps} />

      <Table aria-label={label} ouiaId={ouiaId} {...tableProps}>
        <TableHeader />
        <TableBody />
      </Table>

      {/* The -1 are to combat a bug currently in the TableToolbar component */}
      <TableToolbar isFooter results={-1} selected={-1}>
        <Pagination
          variant={PaginationVariant.bottom}
          /*{...toolbarProps.pagination}*/
        />
      </TableToolbar>

      {/*ColumnManager && <ColumnManager />*/}
    </React.Fragment>
  );
}
Example #3
Source File: ToolbarHeader.js    From edge-frontend with Apache License 2.0 5 votes vote down vote up
ToolbarHeader = ({
  toolbarButtons,
  filters,
  setFilterValues,
  filterValues,
  chipsArray,
  setChipsArray,
  isLoading,
  count,
  perPage,
  setPerPage,
  page,
  setPage,
  toggleButton,
  toggleAction,
  toggleState,
  children,
  kebabItems,
}) => {
  return (
    <Toolbar
      style={{ padding: '16px' }}
      id="toolbar-header"
      data-testid="toolbar-header-testid"
    >
      <ToolbarContent>
        <FilterControls
          filters={filters}
          filterValues={filterValues}
          setFilterValues={setFilterValues}
        >
          {children}
        </FilterControls>
        {toolbarButtons && <ToolbarButtons buttons={toolbarButtons} />}
        {toggleButton && (
          <ToggleGroup>
            {toggleButton.map((btn) => (
              <ToggleGroupItem
                key={btn.key}
                text={btn.title}
                isSelected={toggleState === btn.key}
                onChange={() => toggleAction(btn.key)}
              />
            ))}
          </ToggleGroup>
        )}
        {kebabItems && <ToolbarKebab kebabItems={kebabItems} />}
        <ToolbarItem variant="pagination" align={{ default: 'alignRight' }}>
          {isLoading ? (
            <Skeleton width="200px" />
          ) : count > 0 ? (
            <Pagination
              data-testid="pagination-header-test-id"
              itemCount={count}
              perPage={perPage}
              page={page}
              onSetPage={(_e, pageNumber) => setPage(pageNumber)}
              widgetId="pagination-options-menu-top"
              onPerPageSelect={(_e, perPage) => setPerPage(perPage)}
              isCompact
            />
          ) : null}
        </ToolbarItem>
      </ToolbarContent>
      <ToolbarContent>
        <ToolbarItem variant="chip-group" spacer={{ default: 'spacerNone' }}>
          <FilterChip
            filterValues={filterValues}
            setFilterValues={setFilterValues}
            chipsArray={chipsArray}
            setChipsArray={setChipsArray}
            setPage={setPage}
          />
        </ToolbarItem>
      </ToolbarContent>
    </Toolbar>
  );
}
Example #4
Source File: LogsTable.js    From sed-frontend with Apache License 2.0 5 votes vote down vote up
LogsTable = () => {
  const [opened, setOpened] = useState([]);
  const dispatch = useDispatch();
  const logLoaded = useSelector(
    ({ logReducer }) => logReducer?.loaded || false
  );
  const rows = useSelector(({ logReducer }) => logReducer?.results || []);
  const pagination = useSelector(
    ({ logReducer }) => ({
      itemCount: logReducer?.total,
      perPage: logReducer?.limit,
      page:
        Math.floor((logReducer?.offset || 0) / (logReducer?.limit || 0)) + 1,
    }),
    shallowEqual
  );
  useEffect(() => {
    dispatch(fetchLog());
  }, []);
  const onCollapse = (_e, _key, isOpen, { id }) => {
    setOpened(() =>
      isOpen ? [...opened, id] : opened.filter((openId) => openId !== id)
    );
  };

  const setPage = useCallback(
    (_e, pageNumber) =>
      dispatch(fetchLog({ page: pageNumber, perPage: pagination.perPage })),
    [dispatch, pagination.perPage]
  );

  const setPerPage = useCallback(
    (_e, perPage) => dispatch(fetchLog({ page: 1, perPage })),
    [dispatch]
  );

  return (
    <Fragment>
      <PrimaryToolbar
        pagination={
          logLoaded ? (
            {
              ...pagination,
              onSetPage: setPage,
              onPerPageSelect: setPerPage,
            }
          ) : (
            <Skeleton width="33%" />
          )
        }
      />
      {logLoaded ? (
        <Table
          aria-label="Logs table"
          variant={TableVariant.compact}
          rows={rowsMapper(rows, opened)}
          cells={columns}
          onCollapse={onCollapse}
        >
          <TableHeader />
          <TableBody />
        </Table>
      ) : (
        <SkeletonTable colSize={3} rowSize={10} />
      )}
      <TableToolbar isFooter>
        {logLoaded ? (
          <Pagination
            {...pagination}
            variant={PaginationVariant.bottom}
            onSetPage={setPage}
            onPerPageSelect={setPerPage}
          />
        ) : (
          <Skeleton width="33%" />
        )}
      </TableToolbar>
    </Fragment>
  );
}
Example #5
Source File: AccessRequestsTable.js    From access-requests-frontend with Apache License 2.0 4 votes vote down vote up
AccessRequestsTable = ({ isInternal }) => {
  const columns = isInternal
    ? [
        'Request ID',
        'Account number',
        'Start date',
        'End date',
        'Created',
        'Status',
      ]
    : [
        'Request ID',
        'First name',
        'Last name',
        'Start date',
        'End date',
        'Created',
        'Decision',
      ];

  // Sorting
  const [activeSortIndex, setActiveSortIndex] = React.useState(
    isInternal ? 4 : 5
  );
  const [activeSortDirection, setActiveSortDirection] = React.useState('desc');
  const onSort = (_ev, index, direction) => {
    setActiveSortIndex(index);
    setActiveSortDirection(direction);
  };

  // Pagination
  const [page, setPage] = React.useState(1);
  const [perPage, setPerPage] = React.useState(20);
  const AccessRequestsPagination = ({ id }) => (
    <Pagination
      itemCount={numRows}
      perPage={perPage}
      page={page}
      onSetPage={(_ev, pageNumber) => setPage(pageNumber)}
      id={'access-requests-table-pagination-' + id}
      variant={id}
      perPageOptions={[5, 10, 20, 50].map((n) => ({ title: n, value: n }))}
      onPerPageSelect={(_ev, perPage) => {
        setPage(1);
        setPerPage(perPage);
      }}
      isCompact={id === 'top'}
    />
  );

  AccessRequestsPagination.propTypes = {
    id: PropTypes.string,
  };

  // Filtering
  const [isDropdownOpen, setIsDropdownOpen] = React.useState(false);
  const [filterColumn, setFilterColumn] = React.useState(
    columns[isInternal ? 1 : 6]
  );
  const [isSelectOpen, setIsSelectOpen] = React.useState(false);
  const [statusSelections, setStatusSelections] = React.useState([]);

  // Harder than it needs to be to match rest of RBAC which doesn't wait
  // for user to click a button or press enter.
  const [accountFilter, setAccountFilter] = React.useState('');
  const [filtersDirty, setFiltersDirty] = React.useState(false);
  const hasFilters = statusSelections.length > 0 || accountFilter;

  // Row loading
  const [isLoading, setIsLoading] = React.useState(true);
  const [numRows, setNumRows] = React.useState(0);
  const [rows, setRows] = React.useState([]);
  const dispatch = useDispatch();
  const fetchAccessRequests = () => {
    setIsLoading(true);
    const listUrl = new URL(
      `${window.location.origin}${API_BASE}/cross-account-requests/`
    );

    isInternal
      ? listUrl.searchParams.append('query_by', 'user_id')
      : listUrl.searchParams.append('query_by', 'target_account');

    listUrl.searchParams.append('offset', (page - 1) * perPage);
    listUrl.searchParams.append('limit', perPage);
    // https://github.com/RedHatInsights/insights-rbac/blob/master/rbac/api/cross_access/view.py
    if (accountFilter) {
      listUrl.searchParams.append('account', accountFilter);
    }
    if (statusSelections.length > 0) {
      listUrl.searchParams.append('status', statusSelections.join(','));
    }
    const orderBy = `${activeSortDirection === 'desc' ? '-' : ''}${columns[
      activeSortIndex
    ]
      .toLowerCase()
      .replace(' ', '_')}`;
    listUrl.searchParams.append('order_by', orderBy);

    apiInstance
      .get(listUrl.href, { headers: { Accept: 'application/json' } })
      .then((res) => {
        setNumRows(res.meta.count);
        setRows(
          res.data.map((d) =>
            isInternal
              ? [
                  d.request_id,
                  d.target_account,
                  d.start_date,
                  d.end_date,
                  d.created,
                  d.status,
                ]
              : [
                  d.request_id,
                  d.first_name,
                  d.last_name,
                  d.start_date,
                  d.end_date,
                  d.created,
                  d.status,
                ]
          )
        );
        setIsLoading(false);
      })
      .catch((err) => {
        setIsLoading(false);
        dispatch(
          addNotification({
            variant: 'danger',
            title: 'Could not list access requests',
            description: err.message,
          })
        );
      });
  };
  const debouncedAccountFilter = useDebounce(accountFilter, 400);
  React.useEffect(() => {
    fetchAccessRequests();
  }, [
    debouncedAccountFilter,
    statusSelections,
    activeSortIndex,
    activeSortDirection,
    perPage,
    page,
  ]);

  // Modal actions
  const [openModal, setOpenModal] = React.useState({ type: null });
  const onModalClose = (isChanged) => {
    setOpenModal({ type: null });
    if (isChanged) {
      fetchAccessRequests();
    }
  };
  const modals = (
    <React.Fragment>
      {openModal.type === 'cancel' && (
        <CancelRequestModal
          requestId={openModal.requestId}
          onClose={onModalClose}
        />
      )}
      {['edit', 'create'].includes(openModal.type) && (
        <EditRequestModal
          variant={openModal.type}
          requestId={openModal.requestId}
          onClose={onModalClose}
        />
      )}
    </React.Fragment>
  );

  // Rendering
  const createButton = isInternal && (
    <Button variant="primary" onClick={() => setOpenModal({ type: 'create' })}>
      Create request
    </Button>
  );
  if (rows.length === 0 && !isLoading && !filtersDirty) {
    return (
      <Bullseye style={{ height: 'auto' }} className="pf-u-mt-lg">
        <EmptyState variant="large">
          <EmptyStateIcon icon={PlusCircleIcon} />
          <Title headingLevel="h3" size="lg">
            {isInternal ? 'No access requests' : 'You have no access requests'}
          </Title>
          <EmptyStateBody>
            {isInternal
              ? 'Click the button below to create an access request.'
              : 'You have no pending Red Hat access requests.'}
          </EmptyStateBody>
          {createButton}
        </EmptyState>
        {modals}
      </Bullseye>
    );
  }

  const selectLabelId = 'filter-status';
  const selectPlaceholder = `Filter by ${uncapitalize(
    columns[columns.length - 1]
  )}`;
  const clearFiltersButton = (
    <Button
      variant="link"
      onClick={() => {
        setStatusSelections([]);
        setAccountFilter('');
        setPage(1);
      }}
    >
      Clear filters
    </Button>
  );
  const toolbar = (
    <Toolbar id="access-requests-table-toolbar">
      <ToolbarContent>
        <ToolbarItem>
          <InputGroup>
            <Dropdown
              isOpen={isDropdownOpen}
              onSelect={(ev) => {
                setIsDropdownOpen(false);
                setFilterColumn(ev.target.value);
                setIsSelectOpen(false);
                setFiltersDirty(true);
              }}
              toggle={
                <DropdownToggle
                  onToggle={(isOpen) => setIsDropdownOpen(isOpen)}
                >
                  <FilterIcon /> {filterColumn}
                </DropdownToggle>
              }
              // https://marvelapp.com/prototype/257je526/screen/74764732
              dropdownItems={(isInternal ? [1, 5] : [6])
                .map((i) => columns[i])
                .map((colName) => (
                  // Filterable columns are RequestID, AccountID, and Status
                  <DropdownItem
                    key={colName}
                    value={colName}
                    component="button"
                  >
                    {capitalize(colName)}
                  </DropdownItem>
                ))}
            />
            {['Status', 'Decision'].includes(filterColumn) && (
              <React.Fragment>
                <span id={selectLabelId} hidden>
                  {selectPlaceholder}
                </span>
                <Select
                  aria-labelledby={selectLabelId}
                  variant="checkbox"
                  aria-label="Select statuses"
                  onToggle={(isOpen) => setIsSelectOpen(isOpen)}
                  onSelect={(_ev, selection) => {
                    setFiltersDirty(true);
                    if (statusSelections.includes(selection)) {
                      setStatusSelections(
                        statusSelections.filter((s) => s !== selection)
                      );
                    } else {
                      setStatusSelections([...statusSelections, selection]);
                    }
                    setPage(1);
                  }}
                  isOpen={isSelectOpen}
                  selections={Array.from(statusSelections)}
                  isCheckboxSelectionBadgeHidden
                  placeholderText={selectPlaceholder}
                >
                  {statuses.map((status) => (
                    <SelectOption key={status} value={status}>
                      {capitalize(status)}
                    </SelectOption>
                  ))}
                </Select>
              </React.Fragment>
            )}
            {filterColumn === 'Account number' && (
              <form
                style={{ display: 'flex' }}
                onSubmit={(ev) => ev.preventDefault()}
              >
                <TextInput
                  name={`${filterColumn}-filter`}
                  id={`${filterColumn}-filter`}
                  type="search"
                  iconVariant="search"
                  placeholder={`Filter by ${uncapitalize(filterColumn)}`}
                  aria-label={`${filterColumn} search input`}
                  value={accountFilter}
                  onChange={(val) => {
                    setAccountFilter(val), setFiltersDirty(true), setPage(1);
                  }}
                />
              </form>
            )}
          </InputGroup>
        </ToolbarItem>
        <ToolbarItem>{createButton}</ToolbarItem>
        <ToolbarItem variant="pagination" align={{ default: 'alignRight' }}>
          <AccessRequestsPagination id="top" />
        </ToolbarItem>
      </ToolbarContent>
      <ToolbarContent>
        <ChipGroup categoryName="Status">
          {statusSelections.map((status) => (
            <Chip
              key={status}
              onClick={() => {
                setStatusSelections(
                  statusSelections.filter((s) => s !== status)
                );
                setPage(1);
              }}
            >
              {status}
            </Chip>
          ))}
        </ChipGroup>
        {accountFilter && (
          <ChipGroup categoryName="Account number">
            <Chip
              onClick={() => {
                setAccountFilter(''), setPage(1);
              }}
            >
              {accountFilter}
            </Chip>
          </ChipGroup>
        )}
        {hasFilters && clearFiltersButton}
      </ToolbarContent>
    </Toolbar>
  );
  function getColumnWidth(columnIndex) {
    if (isInternal) {
      return columnIndex === 0 ? 30 : 15;
    }

    return [0, 6].includes(columnIndex) ? 20 : 10;
  }
  const { url } = useRouteMatch();
  const table = (
    <TableComposable aria-label="Access requests table" variant="compact">
      <Thead>
        <Tr>
          {columns.map((column, columnIndex) => (
            <Th
              key={columnIndex}
              {...(!column.includes('name') &&
                column !== 'Decision' && {
                  sort: {
                    sortBy: {
                      index: activeSortIndex,
                      direction: activeSortDirection,
                    },
                    onSort,
                    columnIndex,
                  },
                })}
              width={getColumnWidth(columnIndex)}
            >
              {column}
            </Th>
          ))}
          {isInternal && <Th />}
        </Tr>
      </Thead>
      <Tbody>
        {isLoading
          ? [...Array(rows.length || perPage).keys()].map((i) => (
              <Tr key={i}>
                {columns.map((name, j) => (
                  <Td key={j} dataLabel={name}>
                    <div
                      style={{ height: '30px' }}
                      className="ins-c-skeleton ins-c-skeleton__md"
                    >
                      {' '}
                    </div>
                  </Td>
                ))}
              </Tr>
            ))
          : rows.map((row, rowIndex) => (
              <Tr key={rowIndex}>
                <Td dataLabel={columns[0]}>
                  <Link to={`${url}${url.endsWith('/') ? '' : '/'}${row[0]}`}>
                    {row[0]}
                  </Link>
                </Td>
                <Td dataLabel={columns[1]}>{row[1]}</Td>
                <Td dataLabel={columns[2]}>{row[2]}</Td>
                <Td dataLabel={columns[3]}>{row[3]}</Td>
                <Td dataLabel={columns[4]}>{row[4]}</Td>
                {isInternal ? (
                  <Td dataLabel={columns[5]}>
                    <StatusLabel
                      requestId={row[0]}
                      status={row[5]}
                      onLabelClick={() => {
                        setStatusSelections([
                          ...statusSelections.filter((s) => s !== status),
                          status,
                        ]);
                        setPage(1);
                      }}
                      hideActions
                    />
                  </Td>
                ) : (
                  <Td dataLabel={columns[5]}>{row[5]}</Td>
                )}
                {isInternal ? (
                  // Different actions based on status
                  <Td
                    actions={getInternalActions(row[5], row[0], setOpenModal)}
                  />
                ) : (
                  <Td dataLabel={columns[6]}>
                    <StatusLabel requestId={row[0]} status={row[6]} />
                  </Td>
                )}
              </Tr>
            ))}
        {rows.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>
        )}
      </Tbody>
    </TableComposable>
  );

  return (
    <React.Fragment>
      {toolbar}
      {table}
      <AccessRequestsPagination id="bottom" />
      {modals}
    </React.Fragment>
  );
}
Example #6
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 #7
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 #8
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 #9
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 #10
Source File: filtertable.js    From ibutsu-server with MIT License 4 votes vote down vote up
render() {
    const {
      isEmpty,
      isError,
      onCollapse,
      onRowSelect,
      onApplyFilter,
      onRemoveFilter,
      onClearFilters,
      onApplyReport,
      onSetPage,
      onSetPageSize,
      variant
    } = this.props;
    let columns = this.props.columns || [];
    let rows = this.props.rows || [];
    let actions = this.props.actions || [];
    let filters = this.props.filters || [];
    let hideFilters = this.props.hideFilters || [];
    let activeFilters = this.props.activeFilters || {};
    let pagination = this.props.pagination || {page: 0, pageSize: 0, totalItems: 0};
    let canSelectAll = this.props.canSelectAll || false;
    return (
      <React.Fragment>
        <Flex>
          {(filters || onApplyFilter) &&
          <Flex spaceItems={{default: 'spaceItemsXs'}} grow={{default: 'grow'}}>
            {filters && filters.map((filter, index) => {
              return (
                <FlexItem key={index}>{filter}</FlexItem>
              );
            })}
            {onApplyFilter &&
            <FlexItem>
              <Button onClick={onApplyFilter}>Apply Filter</Button>
            </FlexItem>
            }
          </Flex>
          }
          <Flex align={{default: 'alignRight'}}>
            <FlexItem>
              <Pagination
                perPage={pagination.pageSize}
                page={pagination.page}
                variant={PaginationVariant.top}
                itemCount={pagination.totalItems}
                onSetPage={onSetPage}
                onPerPageSelect={onSetPageSize}
                isCompact
              />
            </FlexItem>
          </Flex>
        </Flex>
        {Object.keys(activeFilters).length > 0 &&
        <Flex style={{marginTop: "1rem"}}>
          <Flex>
            <FlexItem>
              Active filters
            </FlexItem>
          </Flex>
          <Flex grow={{default: 'grow'}}>
            {Object.keys(activeFilters).map(key => (
            <FlexItem spacer={{ default: 'spacerXs'}} key={key}>
              {!hideFilters.includes(key) &&
              <ChipGroup categoryName={key}>
                <Chip onClick={() => onRemoveFilter(key)}>
                  {(typeof activeFilters[key] === 'object') &&
                    <React.Fragment>
                      <Badge isRead={true}>{activeFilters[key]['op']}</Badge>
                      {activeFilters[key]['val']}
                    </React.Fragment>
                  }
                  {(typeof activeFilters[key] !== 'object') && activeFilters[key]}
                </Chip>
              </ChipGroup>
              }
            </FlexItem>
            ))}
          </Flex>
          {onApplyReport &&
          <Flex>
            <FlexItem style={{marginLeft: "0.75em"}}>
              <Button onClick={onApplyReport} variant="secondary">Use Active Filters in Report</Button>
            </FlexItem>
          </Flex>
          }
        </Flex>
        }
        <Table
          cells={columns}
          rows={rows}
          actions={actions}
          aria-label="List"
          canSelectAll={canSelectAll}
          onCollapse={onCollapse}
          onSelect={onRowSelect}
          variant={variant}
        >
          <TableHeader />
          <TableBody />
        </Table>
        {isEmpty && <TableEmptyState onClearFilters={onClearFilters} />}
        {isError && <TableErrorState onClearFilters={onClearFilters} />}
        <Pagination
          widgetId="pagination-options-menu-bottom"
          perPage={pagination.pageSize}
          page={pagination.page}
          variant={PaginationVariant.bottom}
          itemCount={pagination.totalItems}
          dropDirection="up"
          onSetPage={onSetPage}
          onPerPageSelect={onSetPageSize}
          style={{marginTop: "1rem"}}
        />
      </React.Fragment>
    );
  }