Katello/katello

View on GitHub
webpack/scenes/ContentViews/Details/Filters/CVErrataDateFilterContent.js

Summary

Maintainability
F
4 days
Test Coverage
import React, { useState, useEffect } from 'react';
import PropTypes from 'prop-types';
import { isEqual, sortBy, capitalize } from 'lodash';
import { shallowEqual, useSelector, useDispatch } from 'react-redux';
import { Link, useHistory } from 'react-router-dom';
import {
  Tooltip, Form, ActionGroup, Flex, FlexItem, Select,
  SelectOption, SelectVariant, ChipGroup, Chip,
  Tabs, Tab, TabTitleText, Button, DatePicker, Bullseye,
  Divider, Text,
} from '@patternfly/react-core';
import { OutlinedQuestionCircleIcon } from '@patternfly/react-icons';
import { translate as __ } from 'foremanReact/common/I18n';
import { selectCVFilterDetails } from '../ContentViewDetailSelectors';
import AffectedRepositoryTable from './AffectedRepositories/AffectedRepositoryTable';
import { editCVFilterRule } from '../ContentViewDetailActions';
import { hasPermission } from '../../helpers';
import { CONTENT_VIEW_NEEDS_PUBLISH } from '../../ContentViewsConstants';

export const dateFormat = date => `${(date.getMonth() + 1).toString().padStart(2, '0')}/${date.getDate().toString().padStart(2, '0')}/${date.getFullYear()}`;

export const convertAPIDateToUIFormat = (dateString) => {
  if (!dateString || dateString === '') return '';
  const dateTZ = new Date(dateString);
  dateTZ.setMinutes(dateTZ.getMinutes() + dateTZ.getTimezoneOffset());
  return dateFormat(dateTZ);
};

export const dateParse = (date) => {
  if (!date || date === '') return new Date();
  const split = date.split('/');
  if (split.length !== 3) {
    return undefined;
  }
  const [month, day, year] = split;
  return new Date(`${year}-${month.padStart(2, '0')}-${day.padStart(2, '0')}T12:00:00Z`);
};

export const isValidDate = date => date instanceof Date && !Number.isNaN(date.getTime());

const CVErrataDateFilterContent = ({
  cvId, filterId, inclusion, showAffectedRepos, setShowAffectedRepos, details,
}) => {
  const { push } = useHistory();
  const filterDetails = useSelector(state =>
    selectCVFilterDetails(state, cvId, filterId), shallowEqual);
  const { repositories = [], rules } = filterDetails;
  const [{
    id, types, start_date: ruleStartDate, end_date: ruleEndDate, date_type: ruleDateType,
  } = {}] = rules;
  const { permissions } = details;
  const [startDate, setStartDate] = useState(convertAPIDateToUIFormat(ruleStartDate));
  const [endDate, setEndDate] = useState(convertAPIDateToUIFormat(ruleEndDate));
  const [dateType, setDateType] = useState(ruleDateType);
  const [dateTypeSelectOpen, setDateTypeSelectOpen] = useState(false);
  const [typeSelectOpen, setTypeSelectOpen] = useState(false);
  const [selectedTypes, setSelectedTypes] = useState(types);
  const dispatch = useDispatch();
  const [activeTabKey, setActiveTabKey] = useState(0);
  const [startEntry, setStartEntry] = useState(false);
  const [endEntry, setEndEntry] = useState(false);

  const onSave = () => {
    dispatch(editCVFilterRule(
      filterId,
      {
        id,
        content_view_filter_id: filterId,
        start_date: startDate && startDate !== '' ? dateParse(startDate) : null,
        end_date: endDate && endDate !== '' ? dateParse(endDate) : null,
        types: selectedTypes,
        date_type: dateType,
      },
      () => {
        dispatch({ type: CONTENT_VIEW_NEEDS_PUBLISH });
        push('/filters');
      },
    ));
  };

  const resetFilters = () => {
    setStartDate(convertAPIDateToUIFormat(ruleStartDate));
    setEndDate(convertAPIDateToUIFormat(ruleEndDate));
    setSelectedTypes(types);
    setDateType(ruleDateType);
  };

  const onTypeSelect = (selection) => {
    if (selectedTypes.includes(selection)) {
      if (selectedTypes.length === 1) return;
      setSelectedTypes(selectedTypes.filter(e => e !== selection));
    } else setSelectedTypes([...selectedTypes, selection]);
  };

  const singleSelection = selection => (selectedTypes.length === 1
    && selectedTypes.includes(selection));

  const saveDisabled = !isValidDate(dateParse(startDate)) || !isValidDate(dateParse(endDate)) ||
    (
      isEqual(convertAPIDateToUIFormat(ruleStartDate), startDate) &&
      isEqual(convertAPIDateToUIFormat(ruleEndDate), endDate) &&
      isEqual(sortBy(types), sortBy(selectedTypes)) &&
      isEqual(ruleDateType, dateType)
    );

  useEffect(() => {
    if (!repositories.length && showAffectedRepos) {
      setActiveTabKey(1);
    } else {
      setActiveTabKey(0);
    }
  }, [showAffectedRepos, repositories.length]);

  const tabTitle = inclusion ? __('Included errata') : __('Excluded errata');
  const invalidDateFormat = __('Enter a valid date: MM/DD/YYYY');

  return (
    <Tabs
      className="margin-0-24"
      ouiaId="errata-date-filter-tabs"
      activeKey={activeTabKey}
      onSelect={(_event, eventKey) => setActiveTabKey(eventKey)}
    >
      <Tab
        ouiaId="errata-date-filter-form-tab"
        eventKey={0}
        title={<TabTitleText>{tabTitle}</TabTitleText>}
      >
        <div className="margin-24">
          <Form onSubmit={(e) => {
            e.preventDefault();
            onSave();
          }}
          >
            <Flex flex={{ default: 'inlineFlex' }}>
              <FlexItem span={2}>
                <Select
                  variant={SelectVariant.checkbox}
                  onToggle={setTypeSelectOpen}
                  onSelect={(_event, selection) => onTypeSelect(selection)}
                  selections={selectedTypes}
                  isOpen={typeSelectOpen}
                  ouiaId="errata-type-selector"
                  placeholderText={__('Errata type')}
                  isCheckboxSelectionBadgeHidden
                >
                  <SelectOption
                    isDisabled={singleSelection('security') || !hasPermission(permissions, 'edit_content_views')}
                    key="security"
                    value="security"
                  >
                    <p style={{ marginTop: '4px' }}>
                      {__('Security')}
                    </p>
                  </SelectOption>
                  <SelectOption
                    isDisabled={singleSelection('enhancement') || !hasPermission(permissions, 'edit_content_views')}
                    key="enhancement"
                    value="enhancement"
                  >
                    <p style={{ marginTop: '4px' }}>
                      {__('Enhancement')}
                    </p>
                  </SelectOption>
                  <SelectOption
                    isDisabled={singleSelection('bugfix') || !hasPermission(permissions, 'edit_content_views')}
                    key="bugfix"
                    value="bugfix"
                  >
                    <p style={{ marginTop: '4px' }}>
                      {__('Bugfix')}
                    </p>
                  </SelectOption>
                </Select>
              </FlexItem>
              <FlexItem span={1} spacer={{ default: 'spacerNone' }}>
                {(selectedTypes.length === 1) &&
                  <Tooltip
                    position="top"
                    content={
                      __('Atleast one errata type needs to be selected.')
                    }
                  >
                    <OutlinedQuestionCircleIcon />
                  </Tooltip>
                }
              </FlexItem>
              <Divider isVertical />
              <FlexItem span={2} spacer={{ default: 'spacerNone' }}>
                <Select
                  selections={dateType}
                  onSelect={(_event, selection) => {
                    setDateType(selection);
                    setDateTypeSelectOpen(false);
                  }}
                  isOpen={dateTypeSelectOpen}
                  onToggle={setDateTypeSelectOpen}
                  id="date_type_selector"
                  ouiaId="date_type_selector"
                  name="date_type_selector"
                  aria-label="date_type_selector"
                  isDisabled={!hasPermission(permissions, 'edit_content_views')}
                >
                  <SelectOption key="issued" value="issued">{__('Issued from')}</SelectOption>
                  <SelectOption key="updated" value="updated">{__('Updated from')}</SelectOption>
                </Select>
              </FlexItem>
              <FlexItem span={2} spacer={{ default: 'spacerNone' }}>
                <Bullseye
                  onFocus={() => setStartEntry(true)}
                  onBlur={() => setStartEntry(false)}
                >
                  <DatePicker
                    aria-label="start_date_input"
                    value={startDate}
                    invalidFormatText={invalidDateFormat}
                    dateFormat={dateFormat}
                    onChange={(e, val) => setStartDate(val)}
                    dateParse={dateParse}
                    placeholder={startEntry ? 'MM/DD/YYYY' : __('Start date')}
                    isDisabled={!hasPermission(permissions, 'edit_content_views')}
                  />
                </Bullseye>
              </FlexItem>
              <FlexItem spacer={{ default: 'spacerNone' }}>
                <Bullseye style={{ padding: '0 5px' }}>
                  <Text ouiaId="to-text">{__('to')}</Text>
                </Bullseye>
              </FlexItem>
              <FlexItem span={2}>
                <Bullseye
                  onFocus={() => setEndEntry(true)}
                  onBlur={() => setEndEntry(false)}
                >
                  <DatePicker
                    aria-label="end_date_input"
                    value={endDate}
                    invalidFormatText={invalidDateFormat}
                    dateFormat={dateFormat}
                    onChange={(e, val) => setEndDate(val)}
                    dateParse={dateParse}
                    placeholder={endEntry ? 'MM/DD/YYYY' : __('End date')}
                    isDisabled={!hasPermission(permissions, 'edit_content_views')}
                  />
                </Bullseye>
              </FlexItem>
            </Flex>
            <Flex>
              <FlexItem>
                <ChipGroup ouiaId="chipgroup-type" categoryName={__('Type')}>
                  {selectedTypes.map(type => (
                    <Chip
                      ouiaId={type}
                      key={type}
                      onClick={() => onTypeSelect(type)}
                      isReadOnly={singleSelection(type) || !hasPermission(permissions, 'edit_content_views')}
                    >
                      {capitalize(type)}
                    </Chip>
                  ))}
                </ChipGroup>
              </FlexItem>
              <FlexItem>
                <ChipGroup
                  ouiaId="chip-issued"
                  categoryName={dateType === 'issued' ? __('Issued from') : __('Updated from')}
                >
                  <Chip
                    ouiaId="startDate"
                    key="startDate"
                    onClick={() => setStartDate('')}
                    isReadOnly={!startDate || !hasPermission(permissions, 'edit_content_views')}
                  >
                    {startDate || __('ANY')}
                  </Chip>
                  {__('to')}
                  <Chip ouiaId="startDate" key="endDate" onClick={() => setEndDate('')} isReadOnly={!endDate || !hasPermission(permissions, 'edit_content_views')}>
                    {endDate || __('ANY')}
                  </Chip>
                </ChipGroup>
              </FlexItem>
              {hasPermission(permissions, 'edit_content_views') &&
                <FlexItem>
                  <Button
                    ouiaId="errata-date-reset-filters-button"
                    isDisabled={saveDisabled}
                    variant="link"
                    onClick={resetFilters}
                    isInline
                  >
                    {__('Reset filters')}
                  </Button>
                </FlexItem>
              }
            </Flex>
            {hasPermission(permissions, 'edit_content_views') &&
              <ActionGroup>
                <Button
                  ouiaId="save-filter-rule-button"
                  aria-label="save_filter_rule"
                  variant="primary"
                  isDisabled={saveDisabled}
                  type="submit"
                >
                  {__('Save')}
                </Button>
                <Link to={`/content_views/${cvId}#/filters`}>
                  <Button ouiaId="cancel-save-filter-rule-button" variant="link">
                    {__('Cancel')}
                  </Button>
                </Link>
              </ActionGroup>
            }
          </Form>
        </div>
      </Tab>
      {(repositories.length || showAffectedRepos) &&
        <Tab
          ouiaId="affected-repositories-tab"
          eventKey={1}
          title={<TabTitleText>{__('Affected repositories')}</TabTitleText>}
        >
          <div className="margin-24-0">
            <AffectedRepositoryTable cvId={cvId} filterId={filterId} repoType="yum" setShowAffectedRepos={setShowAffectedRepos} details={details} />
          </div>
        </Tab>}
    </Tabs>
  );
};

CVErrataDateFilterContent.propTypes = {
  cvId: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
  filterId: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
  inclusion: PropTypes.bool,
  showAffectedRepos: PropTypes.bool.isRequired,
  setShowAffectedRepos: PropTypes.func.isRequired,
  details: PropTypes.shape({
    permissions: PropTypes.shape({}),
  }).isRequired,
};

CVErrataDateFilterContent.defaultProps = {
  cvId: '',
  filterId: '',
  inclusion: false,
};
export default CVErrataDateFilterContent;