Katello/katello

View on GitHub
webpack/components/extensions/Hosts/BulkActions/BulkChangeHostCVModal/BulkChangeHostCVModal.js

Summary

Maintainability
C
1 day
Test Coverage
import React, { useState } from 'react';
import PropTypes from 'prop-types';
import { useDispatch, useSelector } from 'react-redux';
import { FormattedMessage } from 'react-intl';
import { Modal, Button, Alert, TextContent, Text, TextVariants } from '@patternfly/react-core';
import { translate as __ } from 'foremanReact/common/I18n';
import { STATUS } from 'foremanReact/constants';
import { useAPI } from 'foremanReact/common/hooks/API/APIHooks';
import { selectAPIStatus } from 'foremanReact/redux/API/APISelectors';
import { ENVIRONMENT_PATHS_KEY } from '../../../../../scenes/ContentViews/components/EnvironmentPaths/EnvironmentPathConstants';
import EnvironmentPaths from '../../../../../scenes/ContentViews/components/EnvironmentPaths/EnvironmentPaths';
import ContentViewSelect from '../../../../../scenes/ContentViews/components/ContentViewSelect/ContentViewSelect';
import ContentViewSelectOption from '../../../../../scenes/ContentViews/components/ContentViewSelect/ContentViewSelectOption';
import api from '../../../../../services/api';
import getContentViews from '../../../../../scenes/ContentViews/ContentViewsActions';
import { selectContentViews, selectContentViewStatus } from '../../../../../scenes/ContentViews/ContentViewSelectors';
import { bulkUpdateHostContentViewAndEnvironment } from './actions';
import { getCVPlaceholderText } from '../../../../../scenes/ContentViews/components/ContentViewSelect/helpers';
import HOST_CV_AND_ENV_KEY from '../../../HostDetails/Cards/ContentViewDetailsCard/HostContentViewConstants';

const ENV_PATH_OPTIONS = { key: ENVIRONMENT_PATHS_KEY };

const BulkChangeHostCVModal = ({
  isOpen,
  closeModal,
  selectedCount,
  orgId,
  fetchBulkParams,
}) => {
  const [selectedLifecycleEnv, setSelectedLifecycleEnv]
    = useState([]);

  const [selectedContentView, setSelectedContentView] = useState(null);
  const [cvSelectOpen, setCVSelectOpen] = useState(false);
  const dispatch = useDispatch();
  const contentViewsInEnvResponse = useSelector(state => selectContentViews(state, '_FOR_DEFAULT_ENV'));
  const { results } = contentViewsInEnvResponse;
  const contentViewsInEnvStatus = useSelector(state => selectContentViewStatus(state, '_FOR_DEFAULT_ENV'));
  const hostUpdateStatus = useSelector(state => selectAPIStatus(state, HOST_CV_AND_ENV_KEY));
  const pathsUrl = `/organizations/${orgId}/environments/paths?permission_type=promotable`;
  useAPI( // No TableWrapper here, so we can useAPI from Foreman
    'get',
    api.getApiUrl(pathsUrl),
    ENV_PATH_OPTIONS,
  );
  const selectedContentViewId = results?.find(cv => cv.name === selectedContentView)?.id;

  const handleModalClose = () => {
    setCVSelectOpen(false);
    setSelectedContentView(null);
    setSelectedLifecycleEnv([]);
    closeModal();
  };

  const selectedEnv = selectedLifecycleEnv?.[0];
  const selectedEnvId = selectedEnv?.id;

  const handleCVSelect = (event, selection) => {
    setSelectedContentView(selection);
    setCVSelectOpen(false);
  };

  const handleEnvSelect = (selection) => {
    dispatch(getContentViews({
      environment_id: selection[0].id,
      include_default: true,
      full_result: true,
      order: 'default DESC', // show Default Organization View first
    }, '_FOR_DEFAULT_ENV'));
    setSelectedContentView(null);
    setSelectedLifecycleEnv(selection);
  };
  const { results: contentViewsInEnv = [] } = contentViewsInEnvResponse;
  const canSave = !!(selectedContentView && selectedLifecycleEnv.length);

  const handleSave = () => {
    const requestBody = {
      content_view_id: selectedContentViewId,
      environment_id: selectedEnvId,
      organization_id: orgId,
      included: {
        search: fetchBulkParams(),
      },
    };
    dispatch(bulkUpdateHostContentViewAndEnvironment(
      requestBody, fetchBulkParams(),
      handleModalClose, handleModalClose,
    ));
  };

  const cvPlaceholderText = getCVPlaceholderText({
    environments: selectedLifecycleEnv,
    cvSelectOptions: contentViewsInEnv,
    contentViewsStatus: contentViewsInEnvStatus,
  });

  const stillLoading =
    (contentViewsInEnvStatus === STATUS.PENDING || hostUpdateStatus === STATUS.PENDING);
  const noContentViewsAvailable =
    (contentViewsInEnv.length === 0 || selectedLifecycleEnv.length === 0);

  const modalActions = ([
    <Button
      key="add"
      ouiaId="bulk-change-host-cv-modal-add-button"
      variant="primary"
      onClick={handleSave}
      isDisabled={!canSave || hostUpdateStatus === STATUS.PENDING}
      isLoading={hostUpdateStatus === STATUS.PENDING}
    >
      {__('Save')}
    </Button>,
    <Button key="cancel" ouiaId="change-host-cv-modal-cancel-button" variant="link" onClick={handleModalClose}>
      Cancel
    </Button>,
  ]);
  return (
    <Modal
      isOpen={isOpen}
      onClose={handleModalClose}
      onEscapePress={handleModalClose}
      title={__('Edit content view environments')}
      width="50%"
      position="top"
      actions={modalActions}
      id="bulk-change-host-cv-modal"
      key="bulk-change-host-cv-modal"
      ouiaId="bulk-change-host-cv-modal"
    >
      <TextContent>
        <Text
          ouiaId="bulk-change-cv-options-description"
        >
          <FormattedMessage
            defaultMessage={__('This will update the content view environments for {hosts}.')}
            values={{
              hosts: (
                <strong>
                  <FormattedMessage
                    defaultMessage="{count, plural, one {# {singular}} other {# {plural}}}"
                    values={{
                      count: selectedCount,
                      singular: __('selected host'),
                      plural: __('selected hosts'),
                    }}
                    id="ccs-options-i18n"
                  />
                </strong>
              ),
            }}
            id="bulk-change-cv-options-description-i18n"
          />
        </Text>
      </TextContent>
      {contentViewsInEnvStatus === STATUS.RESOLVED &&
        !!selectedLifecycleEnv.length && contentViewsInEnv.length === 0 &&
        <Alert
          ouiaId="no-cv-alert"
          variant="warning"
          isInline
          title={__('No content views available for the selected environment')}
          style={{ marginBottom: '1rem' }}
        >
          <a href="/content_views">{__('View the Content Views page')}</a>
          {__(' to manage and promote content views, or select a different environment.')}
        </Alert>
      }
      <EnvironmentPaths
        userCheckedItems={selectedLifecycleEnv}
        setUserCheckedItems={handleEnvSelect}
        publishing={false}
        multiSelect={false}
        headerText={__('Select environment')}
        isDisabled={hostUpdateStatus === STATUS.PENDING}
      />
      <ContentViewSelect
        selections={selectedContentView}
        onClear={() => setSelectedContentView(null)}
        onSelect={handleCVSelect}
        isOpen={cvSelectOpen}
        isDisabled={stillLoading || noContentViewsAvailable}
        onToggle={isExpanded => setCVSelectOpen(isExpanded)}
        placeholderText={cvPlaceholderText}
      >
        {(contentViewsInEnv.length !== 0 && selectedLifecycleEnv.length !== 0) &&
            contentViewsInEnv?.map(cv => (
              <ContentViewSelectOption
                key={cv.id}
                value={cv.name}
                cv={cv}
                env={selectedLifecycleEnv[0]}
              />
            ))}
      </ContentViewSelect>
      <hr />
      <TextContent>
        <Text component={TextVariants.small} ouiaId="profile-upload-reminder-text">
          {__('Errata and package information will be updated at the next host check-in or package action.')}
        </Text>
      </TextContent>
      <hr />
    </Modal>
  );
};

BulkChangeHostCVModal.propTypes = {
  isOpen: PropTypes.bool,
  closeModal: PropTypes.func,
  selectedCount: PropTypes.number.isRequired,
  orgId: PropTypes.number.isRequired,
  fetchBulkParams: PropTypes.func.isRequired,
};

BulkChangeHostCVModal.defaultProps = {
  isOpen: false,
  closeModal: () => {},
};


export default BulkChangeHostCVModal;