client/src/components/pipelines/configuration/DetachedConfiguration.js (1,057 lines of code) (raw):

/* * Copyright 2017-2020 EPAM Systems, Inc. (https://www.epam.com/) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import React from 'react'; import {inject, observer} from 'mobx-react'; import classNames from 'classnames'; import {computed, observable} from 'mobx'; import {Row, Col, Modal, Button, Alert, Icon, Tabs, message} from 'antd'; import LaunchPipelineForm from '../launch/form/LaunchPipelineForm'; import pipelines from '../../../models/pipelines/Pipelines'; import pipelinesLibrary from '../../../models/folders/FolderLoadTree'; import folders from '../../../models/folders/Folders'; import AllowedInstanceTypes from '../../../models/utils/AllowedInstanceTypes'; import configurations from '../../../models/configuration/Configurations'; import PipelineConfigurations from '../../../models/pipelines/PipelineConfigurations'; import ConfigurationUpdate from '../../../models/configuration/ConfigurationUpdate'; import ConfigurationDelete from '../../../models/configuration/ConfigurationDelete'; import preferences from '../../../models/preferences/PreferencesLoad'; import ConfigurationRun from '../../../models/configuration/ConfigurationRun'; import CreateConfigurationForm from '../../pipelines/version/configuration/forms/CreateConfigurationForm'; import EditDetachedConfigurationForm from './forms/EditDetachedConfigurationForm'; import LoadingView from '../../special/LoadingView'; import SessionStorageWrapper from '../../special/SessionStorageWrapper'; import Breadcrumbs from '../../special/Breadcrumbs'; import roleModel from '../../../utils/roleModel'; import localization from '../../../utils/localization'; import connect from '../../../utils/connect'; import styles from './DetachedConfiguration.css'; import Schedule from './schedule'; import browserStyles from '../browser/Browser.css'; import {ItemTypes} from '../model/treeStructureFunctions'; import HiddenObjects from '../../../utils/hidden-objects'; import getPathParameters from '../browser/metadata-controls/get-path-parameters'; import {applyCustomCapabilitiesParameters} from '../launch/form/utilities/run-capabilities'; const DTS_ENVIRONMENT = 'DTS'; @connect({ configurations, pipelines, folders, pipelinesLibrary, preferences }) @localization.localizedComponent @HiddenObjects.checkConfigurations(props => props?.params?.id) @inject('projects') @inject(( {configurations, projects, folders, pipelinesLibrary, preferences, history, routing}, {onReloadTree, params}) => { return { history, routing, onReloadTree, configurations: configurations.getConfiguration(params.id), project: projects.getProjectFor(params.id, 'CONFIGURATION'), pipelines, folders, pipelinesLibrary, configurationId: params.id, currentConfiguration: params.name, configurationsCache: configurations, preferences }; }) @observer export default class DetachedConfiguration extends localization.LocalizedReactComponent { @observable allowedInstanceTypes; @observable configurationModified; navigationBlockedListener; navigationBlocker; allowedNavigation; state = { configurationsListCollapsed: false, createConfigurationForm: false, editConfigurationFormVisible: false, overriddenConfiguration: null, emptyPipeline: false, selectedPipelineParameters: {}, selectedPipelineParametersIsLoading: true }; @computed get selectedConfiguration () { if (this.props.configurations.loaded && this.props.configurations.value.entries) { const [configuration] = this.props.configurations.value.entries.filter(c => c.name === this.selectedConfigurationName); return configuration; } return null; } @computed get selectedConfigurationName () { if (this.props.currentConfiguration) { return this.props.currentConfiguration; } if (this.props.configurations.loaded && this.props.configurations.value.entries.length > 0) { const [configuration] = this.props.configurations.value.entries.filter(c => c.default); if (configuration) { return configuration.name; } else { return this.props.configurations.value.entries[0].name; } } return null; } @computed get selectedConfigurationIsDefault () { if (!this.props.currentConfiguration) { return true; } if (this.props.configurations.loaded && this.props.configurations.value.entries.length > 0) { const [configuration] = this.props.configurations.value.entries .filter(c => c.name.toLowerCase() === this.props.currentConfiguration.toLowerCase()); if (configuration) { return configuration.default; } } return false; } @computed get defaultConfigurationName () { if (this.props.configurations.loaded && this.props.configurations.value.entries.length > 0) { const [configuration] = this.props.configurations.value.entries .filter(c => c.default); if (configuration) { return configuration.name; } } return undefined; } get canModifySources () { if (!this.props.configurations.loaded) { return false; } return roleModel.writeAllowed(this.props.configurations.value); }; get canExecute () { if (!this.props.configurations.loaded) { return false; } return roleModel.executeAllowed(this.props.configurations.value); }; onSelectConfiguration = (key) => { if (key !== this.props.currentConfiguration) { this.setState({ overriddenConfiguration: null, emptyPipeline: false }, () => { this.props.router.push(`/configuration/${this.props.configurationId}/${key}`); }); } }; onRemoveConfigurationClicked = (configuration) => (e) => { if (e) { e.stopPropagation(); } const removeConfiguration = async () => { const entries = this.props.configurations.value.entries.map(e => e); const [removableConfiguration] = entries .filter(c => c.name.toLowerCase() === configuration.name.toLowerCase()); const index = entries.indexOf(removableConfiguration); if (index >= 0) { entries.splice(index, 1); const hide = message.loading(`Removing '${configuration.name}' configuration ...`, 0); const request = new ConfigurationUpdate(); const payload = { id: this.props.configurations.value.id, name: this.props.configurations.value.name, description: this.props.configurations.value.description, parentId: this.props.configurations.value.parent ? this.props.configurations.value.parent.id : undefined, entries }; await request.send(payload); if (request.error) { hide(); message.error(request.error, 5); } else { await this.props.configurations.fetch(); this.props.configurationsCache.invalidateConfigurationCache(this.props.configurationId); hide(); this.props.router.push(`/configuration/${this.props.configurationId}/${entries[0].name}`); } } }; Modal.confirm({ title: `Are you sure you want to remove configuration '${configuration.name}'?`, style: { wordWrap: 'break-word' }, async onOk () { await removeConfiguration(); }, okText: 'Yes', cancelText: 'No' }); }; openCreateConfigurationFormDialog = () => { this.setState({createConfigurationForm: true}); }; closeCreateConfigurationFormDialog = () => { this.setState({createConfigurationForm: false}); }; createConfigurationForm = async ({name, description, template}) => { const entries = this.props.configurations.value.entries; if (entries.filter(c => c.name === name).length > 0) { message.error(`Configuration '${name}' already exists`, 5); return; } let newConfiguration; const [configuration] = entries .filter(c => template && c.name.toLowerCase() === template.toLowerCase()); if (configuration) { newConfiguration = Object.assign({}, configuration); } else { newConfiguration = { configuration: { cmd_template: 'sleep 100' } }; } newConfiguration.name = name; newConfiguration.description = description; newConfiguration.default = false; entries.push(newConfiguration); const hide = message.loading(`Creating '${name}' configuration ...`, 0); const request = new ConfigurationUpdate(); const payload = { id: this.props.configurations.value.id, name: this.props.configurations.value.name, description: this.props.configurations.value.description, parentId: this.props.configurations.value.parent ? this.props.configurations.value.parent.id : undefined, entries }; await request.send(payload); if (request.error) { hide(); message.error(request.error, 5); } else { this.closeCreateConfigurationFormDialog(); await this.props.configurations.fetch(); this.props.configurationsCache.invalidateConfigurationCache(this.props.configurationId); hide(); this.props.router.push(`/configuration/${this.props.configurationId}/${name}`); } }; onSetAsDefault = async () => { if (this.selectedConfigurationName && this.props.configurations.loaded && this.props.configurations.value.entries.length > 0) { const entries = this.props.configurations.value.entries; const [configuration] = entries .filter(c => c.name.toLowerCase() === this.selectedConfigurationName.toLowerCase()); if (configuration) { entries.forEach(c => { c.default = false; }); configuration.default = true; const hide = message.loading(`Updating '${this.selectedConfigurationName}' configuration ...`, 0); const request = new ConfigurationUpdate(); const payload = { id: this.props.configurations.value.id, name: this.props.configurations.value.name, description: this.props.configurations.value.description, parentId: this.props.configurations.value.parent ? this.props.configurations.value.parent.id : undefined, entries }; await request.send(payload); if (request.error) { hide(); message.error(request.error, 5); } else { await this.props.configurations.fetch(); this.props.configurationsCache.invalidateConfigurationCache(this.props.configurationId); hide(); } } } }; onConfigurationModified = (modified) => { this.configurationModified = modified; }; onSaveConfiguration = async (opts) => { if (this.selectedConfigurationName && this.props.configurations.loaded && this.props.configurations.value.entries.length > 0) { const entries = this.props.configurations.value.entries; if (entries .filter(c => c.name.toLowerCase() !== this.selectedConfigurationName.toLowerCase() && c.name === opts.configuration.name).length > 0) { message.error(`Configuration ${opts.configuration.name} already exists`, 5); return false; } const [configuration] = entries .filter(c => c.name.toLowerCase() === this.selectedConfigurationName.toLowerCase()); if (configuration) { const hide = message.loading(`Updating '${this.selectedConfigurationName}' configuration ...`, 0); if (this.selectedConfigurationName !== opts.configuration.name) { configuration.name = opts.configuration.name; } if (opts.pipelineId && opts.pipelineVersion) { configuration.pipelineId = opts.pipelineId; configuration.pipelineVersion = opts.pipelineVersion; configuration.configName = opts.configName; configuration.methodName = null; configuration.methodSnapshot = null; configuration.methodConfigurationName = null; configuration.methodConfigurationSnapshot = null; configuration.methodInputs = null; configuration.methodOutputs = null; } else if (opts.methodName && opts.methodSnapshot) { configuration.methodName = opts.methodName; configuration.methodSnapshot = opts.methodSnapshot; configuration.methodConfigurationName = opts.methodConfigurationName; configuration.methodConfigurationSnapshot = opts.methodConfigurationSnapshot; configuration.methodInputs = opts.methodInputs; configuration.methodOutputs = opts.methodOutputs; configuration.pipelineId = null; configuration.pipelineVersion = null; configuration.configName = null; } else { configuration.pipelineId = null; configuration.pipelineVersion = null; configuration.configName = null; configuration.methodName = null; configuration.methodSnapshot = null; configuration.methodConfigurationName = null; configuration.methodConfigurationSnapshot = null; configuration.methodInputs = null; configuration.methodOutputs = null; } configuration.executionEnvironment = opts.executionEnvironment; configuration.rootEntityId = opts.rootEntityId; configuration.endpointName = opts.endpointName; configuration.stopAfter = opts.stopAfter; opts.pipelineId = undefined; opts.pipelineVersion = undefined; opts.configName = undefined; opts.configuration = undefined; opts.rootEntityId = undefined; opts.methodName = undefined; opts.methodSnapshot = undefined; opts.methodConfigurationName = undefined; opts.methodConfigurationSnapshot = undefined; opts.methodInputs = undefined; opts.methodOutputs = undefined; opts.endpointName = undefined; opts.stopAfter = undefined; if (opts.executionEnvironment) { opts.executionEnvironment = undefined; } if (configuration.executionEnvironment === DTS_ENVIRONMENT) { for (const key in opts) { if (opts.hasOwnProperty(key) && opts[key] !== undefined) { configuration[key] = opts[key]; } } } else { configuration.configuration = { ...configuration.configuration, ...opts }; } const request = new ConfigurationUpdate(); const payload = { id: this.props.configurations.value.id, name: this.props.configurations.value.name, description: this.props.configurations.value.description, parentId: this.props.configurations.value.parent ? this.props.configurations.value.parent.id : undefined, entries }; await request.send(payload); if (request.error) { hide(); message.error(request.error, 5); return false; } else { await this.props.configurations.fetch(); this.props.configurationsCache.invalidateConfigurationCache(this.props.configurationId); hide(); this.setState({ overriddenConfiguration: null }, () => { if (this.selectedConfigurationName !== configuration.name) { this.allowedNavigation = `/configuration/${this.props.configurationId}/${configuration.name}`; this.props.router.push(this.allowedNavigation); } }); return true; } } } return false; }; @computed get selectedFireCloudMethod () { if (this.selectedConfiguration) { const configuration = this.selectedConfiguration; if (configuration && configuration.methodName && configuration.methodSnapshot) { const nameParts = configuration.methodName.split('/'); const configurationNameParts = configuration.methodConfigurationName ? configuration.methodConfigurationName.split('/') : []; return { namespace: nameParts[0], name: nameParts[1], snapshot: configuration.methodSnapshot, configuration: configurationNameParts.pop(), configurationSnapshot: configuration.methodConfigurationSnapshot, methodInputs: (configuration.methodInputs || []).map(i => i), methodOutputs: (configuration.methodOutputs || []).map(o => o) }; } } return undefined; } getPipelines = () => { if (!this.props.pipelines.loaded || this.props.pipelines.error || !this.props.pipelines.value) { return []; } return this.props.pipelines.value.map(p => p); }; getConfigurations = () => { if (!!this.props.configurations && this.props.configurations.loaded) { return (this.props.configurations.value.entries || []).map(c => c); } return []; }; getParameters = () => { const extractEndpointNameAndStopAfter = (c) => ({ endpointName: c.endpointName, stopAfter: c.stopAfter }); if (this.state.overriddenConfiguration) { const parameters = this.state.overriddenConfiguration.configuration ? this.state.overriddenConfiguration.configuration.parameters : this.state.overriddenConfiguration.parameters; for (let parameterName in parameters) { const currentParam = parameters[parameterName]; currentParam.readOnly = !!currentParam.value; } return { ...extractEndpointNameAndStopAfter(this.state.overriddenConfiguration), ...this.state.overriddenConfiguration.configuration || this.state.overriddenConfiguration, parameters }; } if (!this.props.configurations || !this.props.configurations.loaded) { return undefined; } let configuration; if (this.selectedConfigurationName) { [configuration] = (this.props.configurations.value.entries || []).filter(c => { return c.name.toLowerCase() === this.selectedConfigurationName.toLowerCase(); }); } if (!configuration) { [configuration] = (this.props.configurations.value.entries || []).filter(c => c.default); } if (!configuration) { return {parameters: {}}; } const parameters = configuration.configuration ? configuration.configuration.parameters : configuration.parameters; for (let parameter in parameters) { if (this.state.selectedPipelineParameters && this.state.selectedPipelineParameters.hasOwnProperty(parameter)) { parameters[parameter].readOnly = !!this.state.selectedPipelineParameters[parameter].value; } else { parameters[parameter].readOnly = false; } } return { ...extractEndpointNameAndStopAfter(configuration), ...configuration.configuration || configuration, parameters }; }; getSelectedPipeline = () => { if (!this.props.configurations || !this.props.configurations.loaded) { return undefined; } if (this.state.emptyPipeline) { return undefined; } let configuration; if (this.selectedConfigurationName) { [configuration] = (this.props.configurations.value.entries || []).filter(c => { return c.name.toLowerCase() === this.selectedConfigurationName.toLowerCase(); }); } if (!configuration) { [configuration] = (this.props.configurations.value.entries || []).filter(c => c.default); } if (configuration && configuration.pipelineId && configuration.pipelineVersion) { const pipeline = this.getPipelines() .find(p => `${p.id}` === `${configuration.pipelineId}`); if (pipeline) { return pipeline; } else { return { id: configuration.pipelineId, name: `${this.localizedString('Pipeline')} #${configuration.pipelineId}`, unknown: true }; } } return undefined; }; getSelectedPipelineVersion = () => { const pipeline = this.getSelectedPipeline(); if (!pipeline) { return undefined; } let configuration; if (this.selectedConfigurationName) { [configuration] = (this.props.configurations.value.entries || []).filter(c => { return c.name.toLowerCase() === this.selectedConfigurationName.toLowerCase(); }); } if (!configuration) { [configuration] = (this.props.configurations.value.entries || []).filter(c => c.default); } if (configuration && configuration.pipelineVersion) { return configuration.pipelineVersion; } return undefined; }; getSelectedPipelineVersionConfiguration = () => { const pipeline = this.getSelectedPipeline(); if (!pipeline) { return undefined; } let configuration; if (this.selectedConfigurationName) { [configuration] = (this.props.configurations.value.entries || []).filter(c => { return c.name.toLowerCase() === this.selectedConfigurationName.toLowerCase(); }); } if (!configuration) { [configuration] = (this.props.configurations.value.entries || []).filter(c => c.default); } if (configuration && configuration.configName) { return configuration.configName; } return undefined; }; loadSelectedPipelineParameters = async () => { this.setState({selectedPipelineParametersIsLoading: true}); await this.props.configurations.fetch(); const selectedPipline = this.getSelectedPipeline(); const selectedPiplineVersion = this.getSelectedPipelineVersion(); const selectedPiplineVersionConfiguration = this.getSelectedPipelineVersionConfiguration(); let selectedPipelineParameters = {}; if (selectedPipline) { const request = new PipelineConfigurations(selectedPipline.id, selectedPiplineVersion); await request.fetch(); if (!request.error) { const [config] = selectedPiplineVersionConfiguration ? request.value.map(c => c).filter(c => c.name === selectedPiplineVersionConfiguration) : request.value.map(c => c).filter(c => c.default); selectedPipelineParameters = config.configuration.parameters; } } this.setState({ selectedPipelineParameters: selectedPipelineParameters, selectedPipelineParametersIsLoading: false }); }; runSelected = (opts, entitiesIds, metadataClass, expansionExpression, folderId) => { const launchFn = async () => { await this.props.project.fetchIfNeededOrWait(); await this.props.preferences.fetchIfNeededOrWait(); let parameters = {}; if (this.props.project.loaded && this.props.project.value) { parameters = await getPathParameters( this.props.pipelinesLibrary, this.props.project.value.id ); } const entries = this.props.configurations.value.entries.map(e => ({...e})); const [configuration] = entries .filter(c => c.name.toLowerCase() === this.selectedConfigurationName.toLowerCase()); if (configuration) { configuration.configName = opts.configName; if (opts.pipelineId && opts.pipelineVersion) { configuration.pipelineId = opts.pipelineId; configuration.pipelineVersion = opts.pipelineVersion; configuration.configName = opts.configName; configuration.methodName = null; configuration.methodSnapshot = null; configuration.methodConfigurationName = null; configuration.methodConfigurationSnapshot = null; configuration.methodInputs = null; configuration.methodOutputs = null; } else if (opts.methodName && opts.methodSnapshot) { configuration.methodName = opts.methodName; configuration.methodSnapshot = opts.methodSnapshot; configuration.methodConfigurationName = opts.methodConfigurationName; configuration.methodConfigurationSnapshot = opts.methodConfigurationSnapshot; configuration.methodInputs = opts.methodInputs; configuration.methodOutputs = opts.methodOutputs; configuration.pipelineId = null; configuration.pipelineVersion = null; configuration.configName = null; } else { configuration.pipelineId = null; configuration.pipelineVersion = null; configuration.configName = null; configuration.methodName = null; configuration.methodSnapshot = null; configuration.methodConfigurationName = null; configuration.methodConfigurationSnapshot = null; configuration.methodInputs = null; configuration.methodOutputs = null; } configuration.executionEnvironment = opts.executionEnvironment; configuration.rootEntityId = opts.rootEntityId; configuration.endpointName = opts.endpointName; configuration.stopAfter = opts.stopAfter; opts.pipelineId = undefined; opts.pipelineVersion = undefined; opts.configName = undefined; opts.configuration = undefined; opts.rootEntityId = undefined; opts.methodName = undefined; opts.methodSnapshot = undefined; opts.methodConfigurationName = undefined; opts.methodConfigurationSnapshot = undefined; opts.methodInputs = undefined; opts.methodOutputs = undefined; opts.executionEnvironment = undefined; opts.endpointName = undefined; opts.stopAfter = undefined; if (configuration.executionEnvironment === DTS_ENVIRONMENT) { for (const key in opts) { if (opts.hasOwnProperty(key) && opts[key] !== undefined) { configuration[key] = opts[key]; } } } else { configuration.configuration = { ...configuration.configuration, ...opts }; } if (!configuration.configuration) { configuration.configuration = {}; } configuration.configuration.parameters = { ...parameters, ...(configuration.configuration.parameters || {}) }; configuration.configuration.parameters = applyCustomCapabilitiesParameters( configuration.configuration.parameters, this.props.preferences ); } const hide = message.loading('Launching...', 0); const request = new ConfigurationRun(expansionExpression); await request.send({ id: this.props.configurationId, entries: [configuration], entitiesIds: entitiesIds, metadataClass: metadataClass, folderId: folderId }); hide(); if (request.error) { message.error(request.error); } else { SessionStorageWrapper.navigateToActiveRuns(this.props.router); } }; let title = `Launch ${this.selectedConfigurationName} configuration?`; if (this.configurationModified) { title = `You have unsaved changes. ${title}`; } Modal.confirm({ title: title, style: { wordWrap: 'break-word' }, onOk: () => { launchFn(); } }); }; runCluster = (opts, entitiesIds, metadataClass, expansionExpression, folderId) => { const launchFn = async () => { await this.props.project.fetchIfNeededOrWait(); let parameters = {}; if (this.props.project.loaded && this.props.project.value) { parameters = await getPathParameters( this.props.pipelinesLibrary, this.props.project.value.id ); } const entries = this.props.configurations.value.entries.map(e => { if (e.name.toLowerCase() === this.selectedConfigurationName.toLowerCase()) { return {...e}; } return { name: e.name }; }); const [configuration] = entries .filter(c => c.name.toLowerCase() === this.selectedConfigurationName.toLowerCase()); if (configuration) { configuration.configName = opts.configName; if (opts.pipelineId && opts.pipelineVersion) { configuration.pipelineId = opts.pipelineId; configuration.pipelineVersion = opts.pipelineVersion; configuration.configName = opts.configName; configuration.methodName = null; configuration.methodSnapshot = null; configuration.methodConfigurationName = null; configuration.methodConfigurationSnapshot = null; configuration.methodInputs = null; configuration.methodOutputs = null; } else if (opts.methodName && opts.methodSnapshot) { configuration.methodName = opts.methodName; configuration.methodSnapshot = opts.methodSnapshot; configuration.methodConfigurationName = opts.methodConfigurationName; configuration.methodConfigurationSnapshot = opts.methodConfigurationSnapshot; configuration.methodInputs = opts.methodInputs; configuration.methodOutputs = opts.methodOutputs; configuration.pipelineId = null; configuration.pipelineVersion = null; configuration.configName = null; } else { configuration.pipelineId = null; configuration.pipelineVersion = null; configuration.configName = null; configuration.methodName = null; configuration.methodSnapshot = null; configuration.methodConfigurationName = null; configuration.methodConfigurationSnapshot = null; configuration.methodInputs = null; configuration.methodOutputs = null; } configuration.executionEnvironment = opts.executionEnvironment; configuration.rootEntityId = this.selectedConfigurationIsDefault ? opts.rootEntityId : undefined; opts.pipelineId = undefined; opts.pipelineVersion = undefined; opts.configName = undefined; opts.configuration = undefined; opts.rootEntityId = undefined; opts.methodName = undefined; opts.methodSnapshot = undefined; opts.methodConfigurationName = undefined; opts.methodConfigurationSnapshot = undefined; opts.methodInputs = undefined; opts.methodOutputs = undefined; opts.executionEnvironment = undefined; if (configuration.executionEnvironment === DTS_ENVIRONMENT) { for (const key in opts) { if (opts.hasOwnProperty(key) && opts[key] !== undefined) { configuration[key] = opts[key]; } } } else { configuration.configuration = { ...configuration.configuration, ...opts }; } if (!configuration.configuration) { configuration.configuration = {}; } configuration.configuration.parameters = { ...parameters, ...(configuration.configuration.parameters || {}) }; configuration.configuration.parameters = applyCustomCapabilitiesParameters( configuration.configuration.parameters, this.props.preferences ); } const hide = message.loading('Launching...', 0); const request = new ConfigurationRun(expansionExpression); await request.send({ id: this.props.configurationId, entries, entitiesIds: entitiesIds, metadataClass: metadataClass, folderId: folderId }); hide(); if (request.error) { message.error(request.error); } else { SessionStorageWrapper.navigateToActiveRuns(this.props.router); } }; let title = 'Launch cluster?'; if (this.configurationModified) { title = `You have unsaved changes. ${title}`; } Modal.confirm({ title: title, onOk: () => { launchFn(); } }); }; openEditConfigurationForm = () => { this.setState({editConfigurationFormVisible: true}); }; closeEditConfigurationForm = () => { this.setState({editConfigurationFormVisible: false}, () => { this.props.configurations.fetch(); }); }; editConfiguration = async (opts) => { const hide = message.loading(`Updating configuration '${this.props.configurations.value.name}'...`, 0); const request = new ConfigurationUpdate(); const payload = { id: this.props.configurations.value.id, name: opts.name, description: opts.description, parentId: this.props.configurations.value.parent ? this.props.configurations.value.parent.id : undefined, entries: this.props.configurations.value.entries.map(e => e) }; await request.send(payload); if (request.error) { hide(); message.error(request.error, 5); } else { await this.props.configurations.fetch(); this.props.configurationsCache.invalidateConfigurationCache(this.props.configurationId); hide(); this.closeEditConfigurationForm(); this.props.router.push(`/configuration/${this.props.configurationId}/${this.selectedConfigurationName}`); } }; renameConfiguration = async (name) => { const hide = message.loading(`Renaming configuration '${this.props.configurations.value.name}'...`, 0); const request = new ConfigurationUpdate(); const payload = { id: this.props.configurations.value.id, name: name, description: this.props.configurations.value.description, parentId: this.props.configurations.value.parent ? this.props.configurations.value.parent.id : undefined, entries: this.props.configurations.value.entries.map(e => e) }; await request.send(payload); if (request.error) { hide(); message.error(request.error, 5); return false; } else { await this.props.configurations.fetch(); this.props.configurationsCache.invalidateConfigurationCache(this.props.configurationId); hide(); this.closeEditConfigurationForm(); if (this.props.onReloadTree) { this.props.onReloadTree(!this.props.configurations.value.parent); } return true; } }; deleteConfiguration = async () => { const hide = message.loading(`Deleting configuration '${this.props.configurations.value.name}'...`, 0); const request = new ConfigurationDelete(this.props.configurationId); await request.fetch(); if (request.error) { hide(); message.error(request.error, 5); } else { hide(); this.closeEditConfigurationForm(); this.props.configurationsCache.invalidateConfigurationCache(this.props.configurationId); const parentFolderId = this.props.configurations.value.parent ? this.props.configurations.value.parent.id : undefined; if (parentFolderId) { this.props.folders.invalidateFolder(parentFolderId); } else { this.props.pipelinesLibrary.invalidateCache(); } if (this.props.onReloadTree) { this.props.onReloadTree(!parentFolderId); } if (parentFolderId) { this.props.router.push(`/folder/${parentFolderId}`); } else { this.props.router.push('/library'); } } }; onConfigurationSelectPipeline = async (opts, callback) => { if (opts && !opts.isFireCloud) { const {pipeline, version, configuration} = opts; const request = new PipelineConfigurations(pipeline.id, version); await request.fetch(); if (!request.error) { const [config] = configuration ? request.value.map(c => c).filter(c => c.name === configuration) : request.value.map(c => c).filter(c => c.default); if (config) { this.setState({ overriddenConfiguration: config, emptyPipeline: false }, () => callback()); } } } else { this.setState({ overriddenConfiguration: null, emptyPipeline: true }, () => callback()); } }; renderTabs = () => { let addButton; if (this.canModifySources) { addButton = ( <Button id="add-configuration-button" size="small" onClick={this.openCreateConfigurationFormDialog}> <Icon type="plus" style={{lineHeight: 'inherit'}} /> ADD </Button> ); } return ( <Row> <Tabs className={classNames(styles.tabs, 'cp-tabs-no-content')} hideAdd onChange={this.onSelectConfiguration} activeKey={this.selectedConfigurationName} tabBarExtraContent={addButton} type="editable-card"> { (this.props.configurations.value.entries || []) .map(c => { return ( <Tabs.TabPane closable={false} tab={c.default ? <i>{c.name}</i> : c.name} key={c.name} /> ); }) } </Tabs> </Row> ); }; render () { if ( (!this.props.configurations.loaded && this.props.configurations.pending) || !this.allowedInstanceTypes || (this.props.pipelines.pending && !this.props.pipelines.loaded) ) { return <LoadingView />; } if (this.props.configurations.error) { return <Alert type="error" message={this.props.configurations.error} />; } let defaultPriceTypeIsSpot = false; if (this.props.preferences.loaded) { defaultPriceTypeIsSpot = this.props.preferences.useSpot; } const configurationTitleClassName = this.props.configurations.value.locked ? browserStyles.readonly : undefined; return ( <div className={styles.fullHeightContainer}> <Row type="flex" justify="space-between" align="middle" style={{marginBottom: 10, minHeight: 41}}> <Col className={browserStyles.itemHeader}> <Breadcrumbs id={parseInt(this.props.configurationId)} type={ItemTypes.configuration} textEditableField={this.props.configurations.value.name} onSaveEditableField={this.renameConfiguration} editStyleEditableField={{flex: 1}} readOnlyEditableField={!this.canModifySources} icon="setting" iconClassName={`${browserStyles.editableControl} ${configurationTitleClassName}`} lock={this.props.configurations.value.locked} lockClassName={`${browserStyles.editableControl} ${configurationTitleClassName}`} subject={this.props.configurations.value} /> </Col> <Col className={styles.actionButtons}> <Schedule configurationId={this.props.configurationId} /> <Button onClick={this.openEditConfigurationForm} size="small"> <Icon type="setting" style={{lineHeight: 'inherit', verticalAlign: 'middle'}} /> </Button> </Col> </Row> <Row style={{position: 'relative', display: 'flex', flexDirection: 'column', flex: 1}}> {this.renderTabs()} <Row className="cp-tabs-content" style={{position: 'relative', flex: 1, overflowY: 'auto'}} > <LaunchPipelineForm defaultPriceTypeIsLoading={this.props.preferences.pending} defaultPriceTypeIsSpot={defaultPriceTypeIsSpot} ref={form => { this.form = form; }} readOnly={!this.canModifySources} canExecute={this.canExecute} canRunCluster={false} canRemove={ this.canModifySources && this.props.configurations.value.entries && this.props.configurations.value.entries.length > 1 } onRemoveConfiguration={this.onRemoveConfigurationClicked(this.selectedConfiguration)} detached editConfigurationMode currentConfigurationName={this.selectedConfigurationName} currentConfigurationIsDefault={this.selectedConfigurationIsDefault} onSetConfigurationAsDefault={this.onSetAsDefault} runConfigurationCluster={this.runCluster} runConfiguration={this.runSelected} pipeline={this.getSelectedPipeline()} version={this.getSelectedPipelineVersion()} pipelineConfiguration={this.getSelectedPipelineVersionConfiguration()} pipelines={this.getPipelines()} allowedInstanceTypes={this.allowedInstanceTypes} parameters={this.getParameters()} configurations={this.getConfigurations()} onLaunch={this.onSaveConfiguration} onSelectPipeline={this.onConfigurationSelectPipeline} isDetachedConfiguration configurationId={this.props.configurationId} selectedPipelineParametersIsLoading={this.state.selectedPipelineParametersIsLoading} fireCloudMethod={this.selectedFireCloudMethod} onModified={this.onConfigurationModified} /> </Row> <CreateConfigurationForm pending={false} configurations={(this.props.configurations.value.entries || []).map(c => c)} visible={this.state.createConfigurationForm} defaultTemplate={this.defaultConfigurationName} onSubmit={this.createConfigurationForm} onCancel={this.closeCreateConfigurationFormDialog} /> <EditDetachedConfigurationForm configuration={this.props.configurations.value} visible={this.state.editConfigurationFormVisible} onCancel={this.closeEditConfigurationForm} pending={false} onSubmit={this.editConfiguration} onDelete={this.deleteConfiguration} /> </Row> </div> ); } componentDidMount () { this.loadSelectedPipelineParameters(); this.navigationBlockedListener = this.props.history.listenBefore((location, callback) => { const locationBefore = this.props.routing.location.pathname; if (location.pathname === locationBefore) { callback(); return; } const clearBlocker = () => { setTimeout(() => { this.navigationBlocker = null; }, 0); }; if (this.configurationModified && !this.navigationBlocker && location.pathname !== this.allowedNavigation) { const cancel = () => { if (this.props.history.getCurrentLocation().pathname !== locationBefore) { this.props.history.replace(locationBefore); } clearBlocker(); }; this.navigationBlocker = Modal.confirm({ title: 'You have unsaved changes. Continue?', style: { wordWrap: 'break-word' }, onOk () { callback(); clearBlocker(); }, onCancel () { cancel(); }, okText: 'Yes', cancelText: 'No' }); } else { callback(); } }); } componentWillUnmount () { if (this.navigationBlockedListener) { this.navigationBlockedListener(); } } componentDidUpdate (prevProps) { if (prevProps.currentConfiguration !== this.props.currentConfiguration || prevProps.configurationId !== this.props.configurationId) { this.loadSelectedPipelineParameters(); this.setState({overriddenConfiguration: null}); } const parameters = this.getParameters(); if (!this.allowedInstanceTypes) { this.allowedInstanceTypes = new AllowedInstanceTypes(); } if (this.allowedInstanceTypes && parameters) { this.allowedInstanceTypes.setParameters({ isSpot: parameters.is_spot, regionId: parameters.cloudRegionId }); } } }