ManageIQ/manageiq-ui-classic

View on GitHub
app/javascript/components/generic-objects-form/index.jsx

Summary

Maintainability
C
1 day
Test Coverage
/* eslint-disable camelcase */
import React, { useState, useMemo, useEffect } from 'react';
import PropTypes from 'prop-types';

import MiqFormRenderer from '@@ddf';
import get from 'lodash/get';
import componentMapper from '../../forms/mappers/componentMapper';
import validatorMapper from '../../forms/mappers/validatorMapper';
import toBase64 from '../../helpers/toBase64';
import createSchema from './generic-objects-form.schema';
import { FileEditComponent, uniqueNameValidator } from './helper';
import miqRedirectBack from '../../helpers/miq-redirect-back';

const GenericObjectForm = ({ recordId }) => {
  const [{ initialValues, isLoading, classOptions }, setState] = useState({ isLoading: !!recordId });
  const promise = useMemo(() => API.options('/api/generic_object_definitions/'), []);
  const submitLabel = !!recordId ? __('Save') : __('Add');

  // custom component mapper that includes the file edit component created for the generic objects form
  const mapper = {
    ...componentMapper,
    'file-edit': FileEditComponent,
  };

  const getClassOptions = (allowed_association_types) => Object.keys(allowed_association_types).map((key) => ({
    value: `${key}`,
    label: __(allowed_association_types[key]),
  }));

  useEffect(() => {
    if (recordId) {
      miqSparkleOn();
      API.get(`/api/generic_object_definitions/${recordId}?attributes=picture.image_href`).then((initialValues) => {
        // eslint-disable-next-line camelcase
        promise.then(({ data: { allowed_association_types } }) => {
          initialValues.attributes = [];
          initialValues.associations = [];
          initialValues.methods = [];
          // modifies the attributes/associations/methods data from the API to match what the form is expecting
          Object.entries(initialValues.properties.attributes).forEach((attr) => {
            initialValues.attributes.push({ attributes_name: attr[0], type: attr[1] });
          });
          Object.entries(initialValues.properties.associations).forEach((attr) => {
            initialValues.associations.push({ associations_name: attr[0], class: attr[1] });
          });
          Object.entries(initialValues.properties.methods).forEach((attr) => {
            initialValues.methods.push({ methods_name: attr[1] });
          });

          // check to display file upload/edit component depending on whether the generic object being edited already has a custom image or not
          initialValues.image_update = !initialValues.picture;
          delete initialValues.properties;
          setState({
            initialValues,
            isLoading: false,
            classOptions: getClassOptions(allowed_association_types),
          });
        });
      });
      miqSparkleOff();
    } else {
      promise.then(({ data: { allowed_association_types } }) => {
        setState({
          classOptions: getClassOptions(allowed_association_types),
        });
      });
    }
  }, [recordId]);

  const onSubmit = (values, formApi) => {
    promise.then(async({ data: { allowed_association_types } }) => {
      // check to determine whether to delete or replace existing custom image
      if (values.file_upload) {
        const fileList = get(values, formApi.fileInputs[0]).inputFiles;
        const base64Encoded = await toBase64(fileList[0]);
        values.picture = { extension: fileList[0].type.split('/').pop(), content: base64Encoded.split(',').pop() };
      } else if (values.image_update) values.picture = {};

      // modifies the attributes/methods data from the form to match what the API is expecting
      values.properties = { attributes: {}, associations: {}, methods: [] };
      if (values.attributes) {
        values.attributes.forEach((attr) => {
          values.properties.attributes[attr.attributes_name] = attr.type;
        });
        delete values.attributes;
      }

      if (values.methods) {
        values.methods.forEach((method) => {
          values.properties.methods.push(method.methods_name);
        });
        delete values.methods;
      }

      delete values.image_update;
      delete values.file_upload;

      // data in values.associations is handled differently when editing a generic object
      if (recordId) {
        API.get(`/api/generic_object_definitions/${recordId}?attributes=picture.image_href`).then((initialValues) => {
          if (values.associations) {
            values.associations.forEach((association) => {
              switch (typeof association.class) {
                case 'string':
                  values.properties.associations[association.associations_name] = association.class;
                  break;
                case 'undefined':
                // eslint-disable-next-line max-len
                  values.properties.associations[association.associations_name] = initialValues.properties.associations[association.associations_name];
                  break;
                default:
                  values.properties.associations[association.associations_name] = Object.keys(allowed_association_types).find((key) =>
                    allowed_association_types[key] === association.class.replace(/»/, '').replace(/«/, ''));
              }
            });
            delete values.associations;
          }

          miqSparkleOn();
          const request = API.patch(`/api/generic_object_definitions/${recordId}`, values);
          request.then(() => {
            const message = sprintf(__('Generic Object Definition "%s" was saved.'), values.name);
            miqRedirectBack(message, undefined, '/generic_object_definition/show_list');
          }).catch(miqSparkleOff);
        });
      } else {
        if (values.associations) {
          values.associations.forEach((association) => {
            values.properties.associations[association.associations_name] = association.class;
          });

          delete values.associations;
        }

        miqSparkleOn();
        const request = API.post('/api/generic_object_definitions', values);
        request.then(() => {
          const message = sprintf(__('Generic Object Definition "%s" was added.'), values.name);
          miqRedirectBack(message, undefined, '/generic_object_definition/show_list');
        }).catch(miqSparkleOff);
      }
    });
  };

  const onCancel = () => {
    const message = sprintf(
      recordId
        ? __('Edit of Generic Object Definition "%s" was cancelled by the user.')
        : __('Add of new Generic Object Definition was cancelled by the user.'),
      initialValues && initialValues.name,
    );
    miqRedirectBack(message, 'warning', '/generic_object_definition/show_list');
  };

  return !isLoading && (
    <MiqFormRenderer
      schema={createSchema(initialValues, !!recordId, promise, classOptions)}
      validate={uniqueNameValidator}
      componentMapper={mapper}
      validatorMapper={validatorMapper}
      initialValues={initialValues}
      canReset={!!recordId}
      onSubmit={onSubmit}
      onCancel={onCancel}
      buttonsLabels={{ submitLabel }}
    />
  );
};

GenericObjectForm.propTypes = {
  recordId: PropTypes.string,
};
GenericObjectForm.defaultProps = {
  recordId: undefined,
};

export default GenericObjectForm;