Blob Blame History Raw
/* eslint-disable jsx-a11y/label-has-associated-control */

import React from "react";
import { FormattedMessage, defineMessages, injectIntl, intlShape } from "react-intl";
import cockpit from "cockpit"; // eslint-disable-line import/no-unresolved
import PropTypes from "prop-types";
import { Tab, Tabs } from "patternfly-react";
import { Table, TableHeader, TableBody, TableVariant } from "@patternfly/react-table";
import { connect } from "react-redux";
import Link from "../../components/Link/Link";
import Layout from "../../components/Layout/Layout";
import BlueprintContents from "../../components/ListView/BlueprintContents";
import ComponentDetailsView from "../../components/ListView/ComponentDetailsView";
import UserAccount from "../../components/Modal/UserAccount";
import EditDescription from "../../components/Modal/EditDescription";
import CreateImageUpload from "../../components/Wizard/CreateImageUpload";
import ExportBlueprint from "../../components/Modal/ExportBlueprint";
import StopBuild from "../../components/Modal/StopBuild";
import DeleteImage from "../../components/Modal/DeleteImage";
import EmptyState from "../../components/EmptyState/EmptyState";
import BlueprintToolbar from "../../components/Toolbar/BlueprintToolbar";
import ListItemImages from "../../components/ListView/ListItemImages";
import ImagesDataList from "../../components/ListView/ImagesDataList";
import TextInlineEdit from "../../components/Form/TextInlineEdit";
import {
  fetchingBlueprintContents,
  setBlueprintDescription,
  setBlueprintHostname,
  setBlueprintUsers,
  fetchingCompDeps,
} from "../../core/actions/blueprints";
import {
  clearSelectedInput,
  setSelectedInput,
  setSelectedInputDeps,
  setSelectedInputParent,
  fetchingDepDetails,
} from "../../core/actions/inputs";
import { fetchingComposes } from "../../core/actions/composes";
import {
  setModalUserAccountVisible,
  setModalUserAccountData,
  setModalStopBuildVisible,
  setModalStopBuildState,
  setModalDeleteImageVisible,
  setModalDeleteImageState,
} from "../../core/actions/modals";
import {
  setEditDescriptionVisible,
  setEditHostnameVisible,
  setEditHostnameInvalid,
} from "../../core/actions/blueprintPage";
import {
  componentsSortSetKey,
  componentsSortSetValue,
  dependenciesSortSetKey,
  dependenciesSortSetValue,
} from "../../core/actions/sort";
import {
  componentsFilterAddValue,
  componentsFilterRemoveValue,
  componentsFilterClearValues,
} from "../../core/actions/filter";
import {
  makeGetBlueprintById,
  makeGetSortedSelectedComponents,
  makeGetSortedDependencies,
  makeGetFilteredComponents,
  makeGetBlueprintComposes,
  makeGetSelectedDeps,
} from "../../core/selectors";

import "./index.css";

const messages = defineMessages({
  blueprint: {
    defaultMessage: "Blueprint",
  },
  emptyBlueprintTitle: {
    defaultMessage: "Empty Blueprint",
  },
  emptyBlueprintMessage: {
    defaultMessage: "There are no components listed in the blueprint. Edit the blueprint to add components.",
  },
  imagesTitle: {
    defaultMessage: "Images",
  },
  noImagesTitle: {
    defaultMessage: "No Images",
  },
  noImagesMessage: {
    defaultMessage: "No images have been created from this blueprint.",
  },
  customizationsTitle: {
    defaultMessage: "Customizations",
  },
  packagesTitle: {
    defaultMessage: "Packages",
  },
  descriptionButtonLabel: {
    defaultMessage: "Edit description",
  },
  descriptionInputLabel: {
    defaultMessage: "Description",
  },
  hostnameButtonLabel: {
    defaultMessage: "Edit hostname",
  },
  hostnameInputLabel: {
    defaultMessage: "Hostname",
  },
  hostnameHelp: {
    defaultMessage:
      "Valid characters for hostname are letters from a to z, the digits from 0 to 9, and the hyphen (-). A hostname may not start with a hyphen.",
  },
  hostnameHelpEmpty: {
    defaultMessage: "If no hostname is provided, the hostname will be determined by the OS.",
  },
  userEdit: {
    defaultMessage: "Edit User Account",
  },
  userKebab: {
    defaultMessage: "User Account Actions",
  },
  userDelete: {
    defaultMessage: "Delete User Account",
  },
});

class BlueprintPage extends React.Component {
  constructor() {
    super();
    this.handleComponentDetails = this.handleComponentDetails.bind(this);
    this.handleComponentListItem = this.handleComponentListItem.bind(this);
    this.handleDepListItem = this.handleDepListItem.bind(this);
    this.handleShowModalUserAccount = this.handleShowModalUserAccount.bind(this);
    this.handleShowModalEditUser = this.handleShowModalEditUser.bind(this);
    this.handleHideModalStop = this.handleHideModalStop.bind(this);
    this.handleHideModalDeleteImage = this.handleHideModalDeleteImage.bind(this);
    this.handleEditDescription = this.handleEditDescription.bind(this);
    this.handleEditHostname = this.handleEditHostname.bind(this);
    this.handleEditHostnameValue = this.handleEditHostnameValue.bind(this);
    this.handlePostUser = this.handlePostUser.bind(this);
    this.handleDeleteUser = this.handleDeleteUser.bind(this);
    this.downloadUrl = this.downloadUrl.bind(this);
  }

  componentDidMount() {
    const { formatMessage } = this.props.intl;
    document.title = formatMessage(messages.blueprint);

    if (this.props.blueprint.components === undefined) {
      this.props.fetchingBlueprintContents(this.props.route.params.blueprint.replace(/\s/g, "-"));
    }
    if (this.props.composesLoading === true) {
      this.props.fetchingComposes();
    }
    this.props.setEditDescriptionVisible(false);
    this.props.setEditHostnameVisible(false);
  }

  componentWillUnmount() {
    this.props.clearSelectedInput();
  }

  handleComponentDetails(event, component) {
    // the user selected a component to view more details
    this.props.setSelectedInput(component);
    this.props.setSelectedInputParent([]);
    event.preventDefault();
    event.stopPropagation();
  }

  handleComponentListItem(component) {
    this.props.fetchingCompDeps(component, this.props.blueprint.id);
  }

  handleDepListItem(component) {
    this.props.fetchingDepDetails(component, this.props.blueprint.id);
  }

  handleEditDescription(value) {
    this.props.setBlueprintDescription(this.props.blueprint, value);
  }

  handleEditHostname(action, value) {
    const state = !this.props.blueprintPage.editHostnameVisible;
    this.props.setEditHostnameVisible(state);
    if (!state && action === "commit") {
      this.props.setBlueprintHostname(this.props.blueprint, value);
    }
  }

  handleEditHostnameValue(value) {
    const validCharacters = value.length === 0 || /^(\d|\w|-|\.){0,252}$/.test(value);
    const validElements = value.split(".").every((element) => element.length < 63);
    const invalid = !!(!validCharacters || !validElements || value.startsWith("-") || value.endsWith("."));
    this.props.setEditHostnameInvalid(invalid);
  }

  handlePostUser(password) {
    const user = {
      name: this.props.userAccount.name,
      ...(this.props.userAccount.description && { description: this.props.userAccount.description }),
      ...(this.props.userAccount.key && { key: this.props.userAccount.key.trim() }),
      ...(this.props.userAccount.groups.includes("wheel") && { groups: ["wheel"] }),
    };
    if (password) {
      password = password.trim();
      user.password = password;
    }
    let users = [];
    if (this.props.blueprint.customizations !== undefined && this.props.blueprint.customizations.user !== undefined) {
      users = this.props.blueprint.customizations.user;
    }
    if (this.props.userAccount.editUser !== "") {
      const userIndex = users.findIndex((user) => user.name === this.props.userAccount.editUser);
      users = users
        .slice(0, userIndex)
        .concat([user])
        .concat(users.slice(userIndex + 1));
    } else {
      users = users.concat(user);
    }
    this.props.setBlueprintUsers(this.props.blueprint.id, users);
    $("#cmpsr-modal-user-account").modal("hide");
  }

  handleDeleteUser(userName, e) {
    const users = this.props.blueprint.customizations.user.filter((user) => user.name !== userName);
    this.props.setBlueprintUsers(this.props.blueprint.id, users);
    e.preventDefault();
    e.stopPropagation();
  }

  // handle show/hide of modal dialogs
  handleShowModalUserAccount(e) {
    this.props.setModalUserAccountVisible(true);
    e.preventDefault();
    e.stopPropagation();
  }

  handleShowModalEditUser(e, user) {
    const userInfo = { ...user };
    userInfo.editUser = user.name;
    userInfo.disabledSubmit = false;
    userInfo.dynamicName = false;
    // the previously encrypted password is stored in component state
    // this property is for storing a new password that needs to be encrypted
    userInfo.password = "";
    this.props.setModalUserAccountData(userInfo);
    this.props.setModalUserAccountVisible(true);
    e.preventDefault();
    e.stopPropagation();
  }

  handleHideModalStop() {
    this.props.setModalStopBuildVisible(false);
    this.props.setModalStopBuildState("", "");
  }

  handleHideModalDeleteImage() {
    this.props.setModalDeleteImageVisible(false);
    this.props.setModalDeleteImageState("", "");
  }

  downloadUrl(compose) {
    const query = window.btoa(
      JSON.stringify({
        payload: "http-stream2",
        unix: "/run/weldr/api.socket",
        method: "GET",
        path: `/api/v0/compose/image/${compose.id}`,
        superuser: "try",
      })
    );

    return `/cockpit/channel/${cockpit.transport.csrf_token}?${query}`;
  }

  render() {
    if (this.props.blueprint.components === undefined) {
      return <div />;
    }
    const {
      blueprint,
      userAccount,
      stopBuild,
      deleteImage,
      selectedComponents,
      dependencies,
      componentsFilters,
      composeList,
      selectedInput,
      selectedInputDeps,
      setSelectedInput,
      setSelectedInputParent,
      clearSelectedInput,
    } = this.props;
    const { editHostnameVisible, editHostnameInvalid } = this.props.blueprintPage;
    const { formatMessage } = this.props.intl;
    let hostname = "";
    if (blueprint.customizations !== undefined && blueprint.customizations.hostname !== undefined) {
      hostname = blueprint.customizations.hostname;
    }
    let users = [];
    if (blueprint.customizations !== undefined && blueprint.customizations.user !== undefined) {
      users = blueprint.customizations.user;
    }
    const pathSuffix = cockpit.location.path[cockpit.location.path.length - 1];
    const activeKey = ["customizations", "packages", "images"].includes(pathSuffix) ? pathSuffix : undefined;

    const rows = users.map((user) => ({
      props: { "data-tr": user.name },
      cells: [
        user.description,
        user.name,
        { title: user.groups !== undefined && user.groups.includes("wheel") && <span className="fa fa-check" /> },
        { title: user.password && <span className="fa fa-check" /> },
        {
          title: user.key !== undefined && (
            <span>
              <span className="fa fa-check" />
              {` `}
              {user.key.split(" ")[2] || ""}
            </span>
          ),
        },
        {
          title: (
            <div>
              <button
                className="btn btn-default"
                type="button"
                aria-label={`${formatMessage(messages.userEdit)} ${user.name}`}
                onClick={(e) => this.handleShowModalEditUser(e, user)}
                data-btn="edit"
              >
                <span className="pficon pficon-edit" />
              </button>

              <div className="dropdown btn-group dropdown-kebab-pf">
                <button
                  aria-label={`${formatMessage(messages.userKebab)} ${user.name}`}
                  className="btn btn-link dropdown-toggle"
                  type="button"
                  data-btn="more"
                  id="dropdownKebab"
                  data-toggle="dropdown"
                  aria-haspopup="true"
                  aria-expanded="false"
                >
                  <span className="fa fa-ellipsis-v" />
                </button>
                <ul className="dropdown-menu dropdown-menu-right" aria-labelledby="dropdownKebab">
                  <li>
                    <a href="#" onClick={(e) => this.handleDeleteUser(user.name, e)}>
                      {formatMessage(messages.userDelete)}
                    </a>
                  </li>
                </ul>
              </div>
            </div>
          ),
        },
      ],
    }));

    return (
      <Layout className="container-fluid" ref={(c) => (this.layout = c)}>
        <header className="cmpsr-header">
          <ol className="breadcrumb">
            <li>
              <Link to="/blueprints">
                <FormattedMessage defaultMessage="Back to Blueprints" />
              </Link>
            </li>
            <li className="active">
              <strong>{this.props.route.params.blueprint}</strong>
            </li>
          </ol>
          <div className="cmpsr-header__actions">
            <ul className="list-inline">
              <li>
                <Link to={`/edit/${this.props.route.params.blueprint}`} className="btn btn-default">
                  <FormattedMessage defaultMessage="Edit Packages" />
                </Link>
              </li>
              <li>
                <CreateImageUpload blueprint={blueprint} layout={this.layout} />
              </li>
              <li>
                <div className="dropdown dropdown-kebab-pf">
                  <button
                    className="btn btn-link dropdown-toggle"
                    type="button"
                    id="dropdownKebab"
                    data-toggle="dropdown"
                    aria-haspopup="true"
                    aria-expanded="false"
                  >
                    <span className="fa fa-ellipsis-v" />
                  </button>
                  <ul className="dropdown-menu dropdown-menu-right" aria-labelledby="dropdownKebab">
                    <li>
                      <EditDescription
                        description={blueprint.description}
                        handleEditDescription={this.handleEditDescription}
                      />
                    </li>
                    <li>
                      <ExportBlueprint blueprint={blueprint} />
                    </li>
                  </ul>
                </div>
              </li>
            </ul>
          </div>
          <div className="cmpsr-title">
            <h1 className="cmpsr-title__item">{this.props.route.params.blueprint}</h1>
            <p className="cmpsr-title__item">
              {blueprint.description && (
                <EditDescription
                  descriptionAsLink
                  description={blueprint.description}
                  handleEditDescription={this.handleEditDescription}
                />
              )}
            </p>
          </div>
        </header>
        <Tabs
          activeKey={activeKey}
          onSelect={(eventId) => cockpit.location.go(["blueprint", blueprint.name, eventId])}
          id="blueprint-tabs"
        >
          <Tab eventKey="customizations" title={formatMessage(messages.customizationsTitle)}>
            <div className="tab-container row">
              <div className="col-sm-12">
                <div className="form-horizontal">
                  <form
                    className={`form-group ${editHostnameInvalid ? "has-error" : ""}`}
                    data-form="hostname"
                    onSubmit={() => this.handleEditHostname("commit")}
                  >
                    <label className="col-sm-2 control-label">{formatMessage(messages.hostnameInputLabel)}</label>
                    <TextInlineEdit
                      className="col-sm-10"
                      editVisible={editHostnameVisible}
                      handleEdit={this.handleEditHostname}
                      validateValue={this.handleEditHostnameValue}
                      buttonLabel={formatMessage(messages.hostnameButtonLabel)}
                      inputLabel={formatMessage(messages.hostnameInputLabel)}
                      value={hostname}
                      invalid={editHostnameInvalid}
                      helpblock={formatMessage(messages.hostnameHelp)}
                      helpblockNoValue={formatMessage(messages.hostnameHelpEmpty)}
                    />
                  </form>
                  <div className="form-group user-list">
                    <label className="col-sm-2 control-label">
                      <FormattedMessage defaultMessage="Users" />
                    </label>
                    <div className="col-sm-10">
                      {users.length > 0 && (
                        <div>
                          <Table
                            variant={TableVariant.compact}
                            aria-label="Users List"
                            cells={["Full name", "User name", "Server administrator", "Password", "SSH key", "Actions"]}
                            rows={rows}
                          >
                            <TableHeader />
                            <TableBody />
                          </Table>
                        </div>
                      )}
                      <button className="btn btn-default" type="button" onClick={this.handleShowModalUserAccount}>
                        <FormattedMessage defaultMessage="Create User Account" />
                      </button>
                    </div>
                  </div>
                </div>
              </div>
            </div>
          </Tab>
          <Tab eventKey="packages" title={formatMessage(messages.packagesTitle)}>
            <div className="row">
              {(selectedInput.set === false && (
                <div className="col-sm-12">
                  <BlueprintToolbar
                    emptyState={
                      (selectedComponents === undefined || selectedComponents.length === 0) &&
                      componentsFilters.filterValues.length === 0
                    }
                    filters={componentsFilters}
                    filterRemoveValue={this.props.componentsFilterRemoveValue}
                    filterClearValues={this.props.componentsFilterClearValues}
                    filterAddValue={this.props.componentsFilterAddValue}
                    componentsSortKey={this.props.componentsSortKey}
                    componentsSortValue={this.props.componentsSortValue}
                    componentsSortSetValue={this.props.componentsSortSetValue}
                    dependenciesSortSetValue={this.props.dependenciesSortSetValue}
                  />
                  <BlueprintContents
                    components={selectedComponents}
                    dependencies={dependencies}
                    noEditComponent
                    handleComponentDetails={this.handleComponentDetails}
                    filterClearValues={this.props.componentsFilterClearValues}
                    filterValues={componentsFilters.filterValues}
                    errorState={this.props.blueprintContentsError}
                    fetchingState={this.props.blueprintContentsFetching}
                    fetchDetails={this.handleComponentListItem}
                  >
                    <EmptyState
                      title={formatMessage(messages.emptyBlueprintTitle)}
                      message={formatMessage(messages.emptyBlueprintMessage)}
                    >
                      <Link to={`/edit/${this.props.route.params.blueprint}`}>
                        <button className="btn btn-default btn-primary" type="button">
                          <FormattedMessage defaultMessage="Edit Packages" />
                        </button>
                      </Link>
                    </EmptyState>
                  </BlueprintContents>
                </div>
              )) || (
                <div className="col-sm-12 cmpsr-component-details--view">
                  <h3 className="cmpsr-panel__title cmpsr-panel__title--main">
                    <FormattedMessage defaultMessage="Component Details" />
                  </h3>
                  <ComponentDetailsView
                    blueprint={this.props.route.params.blueprint}
                    component={selectedInput.component}
                    dependencies={selectedInputDeps}
                    componentParent={selectedInput.parent}
                    setSelectedInput={setSelectedInput}
                    setSelectedInputParent={setSelectedInputParent}
                    clearSelectedInput={clearSelectedInput}
                    handleComponentDetails={this.handleComponentDetails}
                    handleDepListItem={this.handleDepListItem}
                  />
                </div>
              )}
            </div>
          </Tab>
          <Tab eventKey="images" title={formatMessage(messages.imagesTitle)}>
            <div className="tab-container">
              {(composeList.length === 0 && (
                <EmptyState
                  title={formatMessage(messages.noImagesTitle)}
                  message={formatMessage(messages.noImagesMessage)}
                >
                  <CreateImageUpload blueprint={blueprint} layout={this.layout} />
                </EmptyState>
              )) || (
                <ImagesDataList ariaLabel={formatMessage(messages.imagesTitle)}>
                  {composeList.map((compose) => (
                    <ListItemImages
                      blueprint={this.props.route.params.blueprint}
                      listItem={compose}
                      downloadUrl={this.downloadUrl(compose)}
                      key={compose.id}
                    />
                  ))}
                </ImagesDataList>
              )}
            </div>
          </Tab>
        </Tabs>
        {userAccount.visible ? <UserAccount handlePostUser={this.handlePostUser} users={users} /> : null}
        {stopBuild.visible ? (
          <StopBuild
            composeId={stopBuild.composeId}
            blueprintName={stopBuild.blueprintName}
            handleHideModal={this.handleHideModalStop}
          />
        ) : null}
        {deleteImage.visible ? (
          <DeleteImage
            composeId={deleteImage.composeId}
            blueprintName={deleteImage.blueprintName}
            handleHideModal={this.handleHideModalDeleteImage}
          />
        ) : null}
      </Layout>
    );
  }
}

BlueprintPage.propTypes = {
  route: PropTypes.shape({
    keys: PropTypes.arrayOf(PropTypes.object),
    load: PropTypes.func,
    page: PropTypes.string,
    params: PropTypes.object,
    path: PropTypes.string,
    pattern: PropTypes.object,
  }),
  fetchingBlueprintContents: PropTypes.func,
  fetchingCompDeps: PropTypes.func,
  blueprint: PropTypes.shape({
    components: PropTypes.arrayOf(PropTypes.object),
    description: PropTypes.string,
    groups: PropTypes.array,
    id: PropTypes.string,
    localPendingChanges: PropTypes.arrayOf(PropTypes.object),
    modules: PropTypes.array,
    name: PropTypes.string,
    packages: PropTypes.arrayOf(PropTypes.object),
    version: PropTypes.string,
    workspacePendingChanges: PropTypes.arrayOf(PropTypes.object),
    customizations: PropTypes.shape({
      hostname: PropTypes.string,
      user: PropTypes.arrayOf(PropTypes.object),
    }),
  }),
  fetchingComposes: PropTypes.func,
  composesLoading: PropTypes.bool,
  composeList: PropTypes.arrayOf(PropTypes.object),
  setEditDescriptionVisible: PropTypes.func,
  setEditHostnameVisible: PropTypes.func,
  setEditHostnameInvalid: PropTypes.func,
  setModalUserAccountVisible: PropTypes.func,
  setModalUserAccountData: PropTypes.func,
  setBlueprintUsers: PropTypes.func,
  blueprintPage: PropTypes.shape({
    editDescriptionVisible: PropTypes.bool,
    editHostnameVisible: PropTypes.bool,
    editHostnameInvalid: PropTypes.bool,
  }),
  setBlueprintDescription: PropTypes.func,
  setBlueprintHostname: PropTypes.func,
  selectedInput: PropTypes.shape({
    set: PropTypes.bool,
    component: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
    parent: PropTypes.arrayOf(PropTypes.object),
  }),
  selectedInputDeps: PropTypes.arrayOf(PropTypes.object),
  setSelectedInput: PropTypes.func,
  clearSelectedInput: PropTypes.func,
  setSelectedInputParent: PropTypes.func,
  fetchingDepDetails: PropTypes.func,
  stopBuild: PropTypes.shape({
    blueprintName: PropTypes.string,
    composeId: PropTypes.string,
    visible: PropTypes.bool,
  }),
  deleteImage: PropTypes.shape({
    blueprintName: PropTypes.string,
    composeId: PropTypes.string,
    visible: PropTypes.bool,
  }),
  CreateImageUpload: PropTypes.shape({
    blueprint: PropTypes.object,
    visible: PropTypes.bool,
  }),
  userAccount: PropTypes.shape({
    name: PropTypes.string,
    description: PropTypes.string,
    password: PropTypes.string,
    key: PropTypes.string,
    groups: PropTypes.arrayOf(PropTypes.string),
    visible: PropTypes.bool,
    editUser: PropTypes.string,
  }),
  dependenciesSortSetValue: PropTypes.func,
  componentsSortSetValue: PropTypes.func,
  componentsFilters: PropTypes.shape({
    defaultFilterType: PropTypes.string,
    filterTypes: PropTypes.arrayOf(PropTypes.object),
    filterValues: PropTypes.arrayOf(PropTypes.object),
  }),
  componentsFilterAddValue: PropTypes.func,
  componentsFilterRemoveValue: PropTypes.func,
  componentsFilterClearValues: PropTypes.func,
  selectedComponents: PropTypes.arrayOf(PropTypes.object),
  dependencies: PropTypes.arrayOf(PropTypes.object),
  componentsSortKey: PropTypes.string,
  componentsSortValue: PropTypes.string,
  setModalStopBuildVisible: PropTypes.func,
  setModalStopBuildState: PropTypes.func,
  setModalDeleteImageVisible: PropTypes.func,
  setModalDeleteImageState: PropTypes.func,
  blueprintContentsError: PropTypes.shape({
    message: PropTypes.string,
    options: PropTypes.object,
    problem: PropTypes.string,
    url: PropTypes.string,
  }),
  blueprintContentsFetching: PropTypes.bool,
  intl: intlShape.isRequired,
};

BlueprintPage.defaultProps = {
  route: {},
  fetchingBlueprintContents() {},
  blueprint: {},
  fetchingComposes() {},
  composesLoading: false,
  composeList: [],
  setEditDescriptionVisible() {},
  setEditHostnameVisible() {},
  setEditHostnameInvalid() {},
  setModalUserAccountVisible() {},
  setModalUserAccountData() {},
  setBlueprintUsers() {},
  blueprintPage: {},
  setBlueprintDescription() {},
  setBlueprintHostname() {},
  stopBuild: {},
  deleteImage: {},
  CreateImageUpload: {},
  userAccount: {},
  dependenciesSortSetValue() {},
  componentsSortSetValue() {},
  componentsFilters: {},
  componentsFilterAddValue() {},
  componentsFilterRemoveValue() {},
  componentsFilterClearValues() {},
  selectedComponents: [],
  dependencies: [],
  componentsSortKey: "",
  componentsSortValue: "",
  fetchingCompDeps() {},
  selectedInput: {},
  selectedInputDeps: undefined,
  setSelectedInput() {},
  clearSelectedInput() {},
  setSelectedInputParent() {},
  fetchingDepDetails() {},
  setModalStopBuildVisible() {},
  setModalStopBuildState() {},
  setModalDeleteImageVisible() {},
  setModalDeleteImageState() {},
  blueprintContentsError: {},
  blueprintContentsFetching: false,
};

const makeMapStateToProps = () => {
  const getBlueprintById = makeGetBlueprintById();
  const getSortedSelectedComponents = makeGetSortedSelectedComponents();
  const getSortedDependencies = makeGetSortedDependencies();
  const getFilteredComponents = makeGetFilteredComponents();
  const getSelectedDeps = makeGetSelectedDeps();
  const getBlueprintComposes = makeGetBlueprintComposes();
  const mapStateToProps = (state, props) => {
    if (getBlueprintById(state, props.route.params.blueprint.replace(/\s/g, "-")) !== undefined) {
      const fetchedBlueprint = getBlueprintById(state, props.route.params.blueprint.replace(/\s/g, "-"));
      return {
        blueprint: fetchedBlueprint.present,
        selectedComponents: getFilteredComponents(state, getSortedSelectedComponents(state, fetchedBlueprint.present)),
        dependencies: getFilteredComponents(state, getSortedDependencies(state, fetchedBlueprint.present)),
        composeList: getBlueprintComposes(state, fetchedBlueprint.present),
        composesLoading: state.composes.fetchingComposes,
        blueprintPage: state.blueprintPage,
        selectedInput: state.inputs.selectedInput,
        selectedInputDeps: getSelectedDeps(
          state,
          state.inputs.selectedInput.component.dependencies,
          fetchedBlueprint.present.components
        ),
        userAccount: state.modals.userAccount,
        stopBuild: state.modals.stopBuild,
        deleteImage: state.modals.deleteImage,
        componentsSortKey: state.sort.components.key,
        componentsSortValue: state.sort.components.value,
        componentsFilters: state.filter.components,
        blueprintContentsError: fetchedBlueprint.errorState,
        blueprintContentsFetching: !!(
          fetchedBlueprint.present.components === undefined && fetchedBlueprint.errorState === undefined
        ),
      };
    }
    return {
      blueprint: {},
      selectedComponents: [],
      dependencies: [],
      composeList: [],
      composesLoading: state.composes.fetchingComposes,
      blueprintPage: state.blueprintPage,
      userAccount: state.modals.userAccount,
      stopBuild: state.modals.stopBuild,
      deleteImage: state.modals.deleteImage,
      componentsSortKey: state.sort.components.key,
      componentsSortValue: state.sort.components.value,
      componentsFilters: state.filter.components,
      blueprintContentsError: {},
    };
  };
  return mapStateToProps;
};

const mapDispatchToProps = (dispatch) => ({
  fetchingBlueprintContents: (blueprintId) => {
    dispatch(fetchingBlueprintContents(blueprintId));
  },
  fetchingComposes: () => {
    dispatch(fetchingComposes());
  },
  setBlueprintDescription: (blueprint, description) => {
    dispatch(setBlueprintDescription(blueprint, description));
  },
  setEditDescriptionVisible: (visible) => {
    dispatch(setEditDescriptionVisible(visible));
  },
  setBlueprintHostname: (blueprint, hostname) => {
    dispatch(setBlueprintHostname(blueprint, hostname));
  },
  setEditHostnameVisible: (visible) => {
    dispatch(setEditHostnameVisible(visible));
  },
  setEditHostnameInvalid: (invalid) => {
    dispatch(setEditHostnameInvalid(invalid));
  },
  setBlueprintUsers: (blueprintId, users) => {
    dispatch(setBlueprintUsers(blueprintId, users));
  },
  setSelectedInput: (selectedInput) => {
    dispatch(setSelectedInput(selectedInput));
  },
  setSelectedInputDeps: (dependencies) => {
    dispatch(setSelectedInputDeps(dependencies));
  },
  setSelectedInputParent: (selectedInputParent) => {
    dispatch(setSelectedInputParent(selectedInputParent));
  },
  clearSelectedInput: () => {
    dispatch(clearSelectedInput());
  },
  setModalUserAccountVisible: (visible) => {
    dispatch(setModalUserAccountVisible(visible));
  },
  setModalUserAccountData: (data) => {
    dispatch(setModalUserAccountData(data));
  },
  setModalStopBuildState: (composeId, blueprintName) => {
    dispatch(setModalStopBuildState(composeId, blueprintName));
  },
  setModalStopBuildVisible: (visible) => {
    dispatch(setModalStopBuildVisible(visible));
  },
  setModalDeleteImageState: (composeId, blueprintName) => {
    dispatch(setModalDeleteImageState(composeId, blueprintName));
  },
  setModalDeleteImageVisible: (visible) => {
    dispatch(setModalDeleteImageVisible(visible));
  },
  componentsSortSetKey: (key) => {
    dispatch(componentsSortSetKey(key));
  },
  componentsSortSetValue: (value) => {
    dispatch(componentsSortSetValue(value));
  },
  dependenciesSortSetKey: (key) => {
    dispatch(dependenciesSortSetKey(key));
  },
  dependenciesSortSetValue: (value) => {
    dispatch(dependenciesSortSetValue(value));
  },
  componentsFilterAddValue: (value) => {
    dispatch(componentsFilterAddValue(value));
  },
  componentsFilterRemoveValue: (value) => {
    dispatch(componentsFilterRemoveValue(value));
  },
  componentsFilterClearValues: (value) => {
    dispatch(componentsFilterClearValues(value));
  },
  fetchingCompDeps: (component, blueprintId) => {
    dispatch(fetchingCompDeps(component, blueprintId));
  },
  fetchingDepDetails: (component, blueprintId) => {
    dispatch(fetchingDepDetails(component, blueprintId));
  },
});

export default connect(makeMapStateToProps, mapDispatchToProps)(injectIntl(BlueprintPage));