Katello/katello

View on GitHub
webpack/scenes/Subscriptions/components/SubscriptionsTable/SubscriptionsTable.js

Summary

Maintainability
A
2 hrs
Test Coverage
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { cloneDeep, findIndex, isEqual } from 'lodash';
import { translate as __ } from 'foremanReact/common/I18n';
import { LoadingState } from '../../../../components/LoadingState';
import { recordsValid } from '../../SubscriptionValidations';
import { buildTableRows, groupSubscriptionsByProductId } from './SubscriptionsTableHelpers';
import Table from './components/Table';
import Dialogs from './components/Dialogs';

class SubscriptionsTable extends Component {
  constructor(props) {
    super(props);

    this.state = {
      rows: undefined,
      subscriptions: undefined,
      groupedSubscriptions: undefined,
      updatedQuantity: {},
      editing: false,
      showUpdateConfirmDialog: false,
      showCancelConfirmDialog: false,
      showErrorDialog: false,
    };
  }

  static getDerivedStateFromProps(nextProps, prevState) {
    if (
      nextProps.subscriptions !== undefined &&
      !isEqual(nextProps.subscriptions, prevState.subscriptions)
    ) {
      const groupedSubscriptions = groupSubscriptionsByProductId(
        nextProps.subscriptions,
        prevState.groupedSubscriptions,
      );
      const rows = buildTableRows(
        groupedSubscriptions,
        nextProps.subscriptions.availableQuantities,
        prevState.updatedQuantity,
      );

      return { rows, groupedSubscriptions, subscriptions: nextProps.subscriptions };
    }

    return null;
  }

  getInlineEditController = () => ({
    isEditing: ({ rowData }) =>
      (this.state.editing && rowData.available >= 0 && rowData.upstream_pool_id),
    hasChanged: ({ rowData }) => {
      const editedValue = this.state.updatedQuantity[rowData.id];
      return this.hasQuantityChanged(rowData, editedValue);
    },
    onActivate: () => this.enableEditing(true),
    onConfirm: () => {
      if (recordsValid(this.state.rows)) {
        this.showUpdateConfirm(true);
      } else {
        this.showErrorDialog(true);
      }
    },
    onCancel: () => {
      this.showCancelConfirm(true);
    },
    onChange: (value, { rowData }) => {
      const updatedQuantity = cloneDeep(this.state.updatedQuantity);

      if (this.hasQuantityChanged(rowData, value)) {
        updatedQuantity[rowData.id] = value;
      } else {
        delete updatedQuantity[rowData.id];
      }

      this.updateRows(updatedQuantity);
    },
  });

  getSelectionController = () => {
    const allSubscriptionResults = this.props.subscriptions.results;

    const checkAllRowsSelected = () =>
      allSubscriptionResults.length === this.props.selectedRows.length;

    return ({
      allRowsSelected: () => checkAllRowsSelected(),
      selectAllRows: () => {
        if (checkAllRowsSelected()) {
          this.props.onSelectedRowsChange([]);
          this.props.toggleDeleteButton(false);
        } else {
          this.props.onSelectedRowsChange(allSubscriptionResults.map(row => row.id));
          this.props.toggleDeleteButton(true);
        }
      },
      selectRow: ({ rowData }) => {
        let { selectedRows } = this.props;
        if (selectedRows.includes(rowData.id)) {
          selectedRows = selectedRows.filter(e => e !== rowData.id);
        } else {
          selectedRows = selectedRows.concat(rowData.id);
        }
        this.props.onSelectedRowsChange(selectedRows);
        this.props.toggleDeleteButton(selectedRows.length > 0);
      },
      isSelected: ({ rowData }) => this.props.selectedRows.includes(rowData.id),
    });
  };

  getTableProps = () => {
    const {
      subscriptions,
      emptyState,
      tableColumns,
      loadSubscriptions,
      selectionEnabled,
    } = this.props;
    const { groupedSubscriptions, rows, editing } = this.state;

    return {
      emptyState,
      editing,
      groupedSubscriptions,
      loadSubscriptions,
      rows,
      subscriptions,
      selectionEnabled,
      tableColumns,
      toggleSubscriptionGroup: this.toggleSubscriptionGroup,
      inlineEditController: this.getInlineEditController(),
      selectionController: this.getSelectionController(),
    };
  };

  getUpdateDialogProps = () => {
    const { showUpdateConfirmDialog: show, updatedQuantity } = this.state;
    const {
      updateQuantity,
    } = this.props;
    return {
      show,
      updatedQuantity,
      updateQuantity,
      enableEditing: this.enableEditing,
      showUpdateConfirm: this.showUpdateConfirm,
    };
  };

  getUnsavedChangesDialogProps = () => {
    const { showCancelConfirmDialog: show } = this.state;
    return {
      show,
      cancelEdit: this.cancelEdit,
      showCancelConfirm: this.showCancelConfirm,
    };
  };

  getInputsErrorsDialogProps = () => {
    const { showErrorDialog: show } = this.state;
    return {
      show,
      showErrorDialog: this.showErrorDialog,
    };
  };

  getDeleteDialogProps = () => {
    const {
      subscriptionDeleteModalOpen: show,
      onDeleteSubscriptions,
      onSubscriptionDeleteModalClose,
    } = this.props;
    const { selectedRows } = this.props;
    return {
      show,
      selectedRows,
      onSubscriptionDeleteModalClose,
      onDeleteSubscriptions,
    };
  };

  getLoadingStateProps = () => {
    const { subscriptions: { loading } } = this.props;
    return {
      loading,
      loadingText: __('Loading'),
    };
  };

  getDialogsProps = () => ({
    updateDialog: this.getUpdateDialogProps(),
    unsavedChangesDialog: this.getUnsavedChangesDialogProps(),
    inputsErrorsDialog: this.getInputsErrorsDialogProps(),
    deleteDialog: this.getDeleteDialogProps(),
  });


  toggleSubscriptionGroup = (groupId) => {
    this.setState((prevState) => {
      const { subscriptions } = this.props;
      const { groupedSubscriptions, updatedQuantity } = prevState;
      const { open } = groupedSubscriptions[groupId];

      groupedSubscriptions[groupId].open = !open;

      const rows = buildTableRows(
        groupedSubscriptions,
        subscriptions.availableQuantities,
        updatedQuantity,
      );
      return { rows, groupedSubscriptions };
    });
  };

  enableEditing = (editingState) => {
    this.setState({
      updatedQuantity: {},
      editing: editingState,
    });
  };

  updateRows = (updatedQuantity) => {
    this.setState((prevState) => {
      const { groupedSubscriptions } = prevState;
      const { subscriptions } = this.props;

      const rows = buildTableRows(
        groupedSubscriptions,
        subscriptions.availableQuantities,
        updatedQuantity,
      );
      return { rows, updatedQuantity };
    });
  };

  showUpdateConfirm = (show) => {
    this.setState({
      showUpdateConfirmDialog: show,
    });
  };

  showCancelConfirm = (show) => {
    this.setState({
      showCancelConfirmDialog: show,
    });
  };

  showErrorDialog = (show) => {
    this.setState({
      showErrorDialog: show,
    });
  };

  cancelEdit = () => {
    this.showCancelConfirm(false);
    this.enableEditing(false);
    this.updateRows({});
  };

  hasQuantityChanged = (rowData, editedValue) => {
    if (editedValue !== undefined) {
      const originalRows = this.props.subscriptions.results;
      const index = findIndex(originalRows, row => (row.id === rowData.id));
      const currentValue = originalRows[index].quantity;

      return (`${editedValue}` !== `${currentValue}`);
    }
    return false;
  };

  render() {
    return (
      <LoadingState {...this.getLoadingStateProps()}>
        <Table ouiaId="subscriptions-table" {...this.getTableProps()} />
        <Dialogs {...this.getDialogsProps()} />
      </LoadingState>
    );
  }
}

SubscriptionsTable.propTypes = {
  tableColumns: PropTypes.arrayOf(PropTypes.string).isRequired,
  loadSubscriptions: PropTypes.func.isRequired,
  updateQuantity: PropTypes.func.isRequired,
  emptyState: PropTypes.shape({}).isRequired,
  subscriptions: PropTypes.shape({
    loading: PropTypes.bool,
    availableQuantities: PropTypes.shape({}),
    // Disabling rule as existing code failed due to an eslint-plugin-react update
    // eslint-disable-next-line react/forbid-prop-types
    results: PropTypes.array,
  }).isRequired,
  subscriptionDeleteModalOpen: PropTypes.bool.isRequired,
  onDeleteSubscriptions: PropTypes.func.isRequired,
  onSubscriptionDeleteModalClose: PropTypes.func.isRequired,
  toggleDeleteButton: PropTypes.func.isRequired,
  selectedRows: PropTypes.instanceOf(Array).isRequired,
  onSelectedRowsChange: PropTypes.func.isRequired,
  selectionEnabled: PropTypes.bool,
};

SubscriptionsTable.defaultProps = {
  selectionEnabled: false,
};

export default SubscriptionsTable;