diff --git a/app/scripts/modules/core/src/pipeline/config/stages/overrideTimeout/OverrideTimeout.tsx b/app/scripts/modules/core/src/pipeline/config/stages/overrideTimeout/OverrideTimeout.tsx new file mode 100644 index 00000000000..d6d4859e5a8 --- /dev/null +++ b/app/scripts/modules/core/src/pipeline/config/stages/overrideTimeout/OverrideTimeout.tsx @@ -0,0 +1,138 @@ +import * as React from 'react'; + +import { IStage } from 'core/domain'; +import { CheckboxInput, NumberInput } from 'core/presentation'; +import { HelpContentsRegistry, HelpField } from 'core/help'; + +const { useEffect, useState } = React; + +export interface IOverrideTimeoutConfigProps { + stageConfig: IStageConfig; + stageTimeoutMs: number; + updateStageField: (changes: Partial) => void; +} + +interface IStageConfig { + defaultTimeoutMs: number; +} + +export const OverrideTimeout = (props: IOverrideTimeoutConfigProps) => { + const [hours, setHours] = useState(0); + const [minutes, setMinutes] = useState(0); + const [overrideTimeout, setOverrideTimeout] = useState(false); + const [configurable, setConfigurable] = useState(false); + const [defaults, setDefaults] = useState({ hours: 0, minutes: 0 }); + const helpContent = HelpContentsRegistry.getHelpField('pipeline.config.timeout'); + + useEffect(() => { + setOverrideValues(overrideTimeout); + }, [props.stageConfig]); + + const setOverrideValues = (newOverrideTimeout: boolean) => { + const stageDefaults = props.stageConfig ? props.stageConfig.defaultTimeoutMs : null; + const originalOverrideTimeout = newOverrideTimeout === true; + const shouldRemoveOverride = originalOverrideTimeout === false; + + setConfigurable(!!stageDefaults); + setDefaults(toHoursAndMinutes(stageDefaults)); + + if (shouldRemoveOverride) { + props.updateStageField({ stageTimeoutMs: undefined }); + } else if (originalOverrideTimeout || props.stageTimeoutMs !== undefined) { + // Either vm.overrideTimeout was originally true, or forcing to true because stageTimeoutMs is defined + setOverrideTimeout(true); + props.updateStageField({ + stageTimeoutMs: props.stageTimeoutMs || stageDefaults, + }); + setHours(toHoursAndMinutes(props.stageTimeoutMs).hours); + setMinutes(toHoursAndMinutes(props.stageTimeoutMs).minutes); + } + }; + + const synchronizeTimeout = (h: number, m: number) => { + let timeout = 0; + timeout += 60 * 60 * 1000 * h; + timeout += 60 * 1000 * m; + props.updateStageField({ stageTimeoutMs: timeout }); + }; + + function toHoursAndMinutes(ms: number) { + if (!ms) { + return { hours: 0, minutes: 0 }; + } else { + const seconds = ms / 1000; + return { + hours: Math.floor(seconds / 3600), + minutes: Math.floor(seconds / 60) % 60, + }; + } + } + + if (configurable) { + return ( + <> +
+
+
+ + Override default timeout ( + {defaults.hours > 0 && ( + + {defaults.hours} {defaults.hours > 1 ? 'hours' : 'hour'}{' '} + + )} + {defaults.minutes > 0 && ( + + {defaults.minutes} {defaults.minutes > 1 ? 'minutes' : 'minute'} + + )} + ) + + } + value={overrideTimeout} + onChange={() => { + const newOverrideTimeout = !overrideTimeout; + setOverrideTimeout(newOverrideTimeout); + setOverrideValues(newOverrideTimeout); + }} + /> +
+
+
+ {overrideTimeout && ( +
+
+
+ Fail this stage if it takes longer than + ) => { + setHours(e.target.value); + synchronizeTimeout(e.target.value, minutes); + }} + value={hours} + /> + hours + ) => { + setMinutes(e.target.value); + synchronizeTimeout(hours, e.target.value); + }} + value={minutes} + /> + minutes to complete +
+
+
+ )} + + ); + } else { + return <>; + } +}; diff --git a/app/scripts/modules/core/src/pipeline/config/stages/overrideTimeout/overrideTimeout.directive.html b/app/scripts/modules/core/src/pipeline/config/stages/overrideTimeout/overrideTimeout.directive.html deleted file mode 100644 index 12fb8f61746..00000000000 --- a/app/scripts/modules/core/src/pipeline/config/stages/overrideTimeout/overrideTimeout.directive.html +++ /dev/null @@ -1,46 +0,0 @@ -
-
-
-
- -
-
-
-
-
-
- Fail this stage if it takes longer than - - hours - - minutes to complete -
-
-
-
diff --git a/app/scripts/modules/core/src/pipeline/config/stages/overrideTimeout/overrideTimeout.directive.js b/app/scripts/modules/core/src/pipeline/config/stages/overrideTimeout/overrideTimeout.directive.js deleted file mode 100644 index f264f35ec8a..00000000000 --- a/app/scripts/modules/core/src/pipeline/config/stages/overrideTimeout/overrideTimeout.directive.js +++ /dev/null @@ -1,79 +0,0 @@ -'use strict'; - -const angular = require('angular'); - -import { Registry } from 'core/registry'; -import { HelpContentsRegistry } from 'core/help'; - -module.exports = angular - .module('spinnaker.core.pipeline.stage.overrideTimeout', []) - .directive('overrideTimeout', function() { - return { - restrict: 'E', - scope: { - stage: '=', - }, - templateUrl: require('./overrideTimeout.directive.html'), - controller: 'OverrideTimeoutCtrl', - controllerAs: 'overrideTimeoutCtrl', - }; - }) - .controller('OverrideTimeoutCtrl', [ - '$scope', - function($scope) { - function toHoursAndMinutes(ms) { - if (!ms) { - return { hours: 0, minutes: 0 }; - } else { - var seconds = ms / 1000; - return { - hours: Math.floor(seconds / 3600), - minutes: Math.floor(seconds / 60) % 60, - }; - } - } - - this.setOverrideValues = function() { - const stage = $scope.stage, - stageConfig = Registry.pipeline.getStageConfig(stage), - stageDefaults = stageConfig ? stageConfig.defaultTimeoutMs : null; - - // Capturing this so that we can restore it after $scope.vm is clobbered - const originalOverrideTimeout = $scope.vm && $scope.vm.overrideTimeout; - // vm.overrideTimeout being false is only a transition state to indicate the override got unchecked - const shouldRemoveOverride = originalOverrideTimeout === false; - - $scope.vm = { - configurable: !!stageDefaults, - }; - - $scope.vm.helpContent = HelpContentsRegistry.getHelpField('pipeline.config.timeout'); - $scope.vm.defaults = toHoursAndMinutes(stageDefaults); - - if (shouldRemoveOverride) { - delete stage.stageTimeoutMs; - } else if (originalOverrideTimeout || stage.stageTimeoutMs !== undefined) { - // Either vm.overrideTimeout was originally true, or forcing to true because stageTimeoutMs is defined - $scope.vm.overrideTimeout = true; - - stage.stageTimeoutMs = stage.stageTimeoutMs || stageDefaults; - $scope.vm.hours = toHoursAndMinutes(stage.stageTimeoutMs).hours; - $scope.vm.minutes = toHoursAndMinutes(stage.stageTimeoutMs).minutes; - } - }; - - this.synchronizeTimeout = function() { - var timeout = 0, - vm = $scope.vm; - if (!isNaN(vm.minutes)) { - timeout += 60 * 1000 * parseInt(vm.minutes); - } - if (!isNaN(vm.hours)) { - timeout += 60 * 60 * 1000 * parseInt(vm.hours); - } - $scope.stage.stageTimeoutMs = timeout; - }; - - $scope.$watch('stage', this.setOverrideValues, true); - }, - ]); diff --git a/app/scripts/modules/core/src/pipeline/config/stages/overrideTimeout/overrideTimeout.directive.spec.js b/app/scripts/modules/core/src/pipeline/config/stages/overrideTimeout/overrideTimeout.directive.spec.js deleted file mode 100644 index 64893cc2425..00000000000 --- a/app/scripts/modules/core/src/pipeline/config/stages/overrideTimeout/overrideTimeout.directive.spec.js +++ /dev/null @@ -1,101 +0,0 @@ -'use strict'; - -import { Registry } from 'core/registry'; - -require('./overrideTimeout.directive.html'); - -describe('Directives: overrideTimeout', function() { - let stageConfig; - - beforeEach(window.module(require('./overrideTimeout.directive').name)); - - beforeEach(function() { - window.inject(function($rootScope, $compile, $controller) { - this.scope = $rootScope.$new(); - this.scope.stage = {}; - this.compile = $compile; - this.$controller = $controller; - - stageConfig = { defaultTimeoutMs: 90 * 60 * 1000 }; - Registry.reinitialize(); - }); - }); - - describe('checkbox toggle control', function() { - it('displays nothing when stage is not supported', function() { - stageConfig = {}; - spyOn(Registry.pipeline, 'getStageConfig').and.returnValue(stageConfig); - var domNode = this.compile('')(this.scope); - this.scope.$digest(); - - expect(domNode.find('div').length).toBe(0); - }); - - it('shows the default value when stage is supported', function() { - spyOn(Registry.pipeline, 'getStageConfig').and.returnValue(stageConfig); - var domNode = this.compile('')(this.scope); - this.scope.$digest(); - expect( - domNode - .find('.default-timeout') - .text() - .indexOf('1 hour'), - ).not.toBe(-1); - expect( - domNode - .find('.default-timeout') - .text() - .indexOf('30 minutes'), - ).not.toBe(-1); - }); - - it('shows the contents when stageTimeoutMs is set', function() { - spyOn(Registry.pipeline, 'getStageConfig').and.returnValue(stageConfig); - this.scope.stage.stageTimeoutMs = 30000; - var domNode = this.compile('')(this.scope); - this.scope.$digest(); - expect(domNode.find('input[type="number"]').length).toBe(2); - }); - - it('unsets timeout, removes contents when we click to uncheck', function() { - spyOn(Registry.pipeline, 'getStageConfig').and.returnValue(stageConfig); - this.scope.stage.stageTimeoutMs = 30000; - var domNode = this.compile('')(this.scope); - this.scope.$digest(); - expect(domNode.find('input[type="number"]').length).toBe(2); - - domNode.find('input[type="checkbox"]').click(); - this.scope.$digest(); - expect(domNode.find('input[type="number"]').length).toBe(0); - expect(this.scope.stage.stageTimeoutMs).toBeUndefined(); - }); - }); - - describe('time conversion', function() { - it('rounds down', function() { - this.scope.stage.stageTimeoutMs = 30 * 60 * 1000 + 499; - this.$controller('OverrideTimeoutCtrl', { - $scope: this.scope, - }); - this.scope.$digest(); - expect(this.scope.vm.minutes).toBe(30); - }); - - it('rolls minutes over to hours', function() { - this.scope.stage.stageTimeoutMs = 95 * 60 * 1000; - var ctrl = this.$controller('OverrideTimeoutCtrl', { - $scope: this.scope, - }); - this.scope.$digest(); - expect(this.scope.vm.minutes).toBe(35); - expect(this.scope.vm.hours).toBe(1); - - this.scope.vm.hours = 0; - this.scope.vm.minutes = 99; - ctrl.synchronizeTimeout(); - this.scope.$digest(); - expect(this.scope.vm.hours).toBe(1); - expect(this.scope.vm.minutes).toBe(39); - }); - }); -}); diff --git a/app/scripts/modules/core/src/pipeline/config/stages/overrideTimeout/overrideTimeout.module.ts b/app/scripts/modules/core/src/pipeline/config/stages/overrideTimeout/overrideTimeout.module.ts new file mode 100644 index 00000000000..157efb1a915 --- /dev/null +++ b/app/scripts/modules/core/src/pipeline/config/stages/overrideTimeout/overrideTimeout.module.ts @@ -0,0 +1,10 @@ +import { module } from 'angular'; +import { react2angular } from 'react2angular'; + +import { OverrideTimeout } from './OverrideTimeout'; + +export const OVERRIDE_TIMEOUT_COMPONENT = 'spinnaker.core.pipeline.stage.overrideTimeout'; +module(OVERRIDE_TIMEOUT_COMPONENT, []).component( + 'overrideTimeout', + react2angular(OverrideTimeout, ['stageConfig', 'stageTimeoutMs', 'updateStageField']), +); diff --git a/app/scripts/modules/core/src/pipeline/config/stages/stage.html b/app/scripts/modules/core/src/pipeline/config/stages/stage.html index f73042a96e7..83363be785b 100644 --- a/app/scripts/modules/core/src/pipeline/config/stages/stage.html +++ b/app/scripts/modules/core/src/pipeline/config/stages/stage.html @@ -97,7 +97,11 @@

skip-window-text="stage.skipWindowText" update-stage-field="stageConfigCtrl.updateStageField" > - + diff --git a/app/scripts/modules/core/src/pipeline/config/stages/stage.module.js b/app/scripts/modules/core/src/pipeline/config/stages/stage.module.js index 2fbbeecb7db..0ef92d48e3a 100644 --- a/app/scripts/modules/core/src/pipeline/config/stages/stage.module.js +++ b/app/scripts/modules/core/src/pipeline/config/stages/stage.module.js @@ -18,13 +18,14 @@ import { EditStageJsonModal } from './common/EditStageJsonModal'; import { ReactModal } from 'core/presentation'; import { PRODUCES_ARTIFACTS_REACT } from './producesArtifacts/ProducesArtifacts'; import { OVERRRIDE_FAILURE } from './overrideFailure/overrideFailure.module'; +import { OVERRIDE_TIMEOUT_COMPONENT } from './overrideTimeout/overrideTimeout.module'; module.exports = angular .module('spinnaker.core.pipeline.config.stage', [ PRODUCES_ARTIFACTS_REACT, BASE_EXECUTION_DETAILS_CTRL, STAGE_NAME, - require('./overrideTimeout/overrideTimeout.directive').name, + OVERRIDE_TIMEOUT_COMPONENT, OVERRRIDE_FAILURE, require('./optionalStage/optionalStage.directive').name, require('./failOnFailedExpressions/failOnFailedExpressions.directive').name, @@ -250,6 +251,8 @@ module.exports = angular $scope.description = null; $scope.extendedDescription = null; } + + updateStageConfig($scope.stage); }; function applyConfigController(config, stageScope) { @@ -282,6 +285,12 @@ module.exports = angular } } + function updateStageConfig(stage) { + $scope.$applyAsync(() => { + $scope.stageConfig = getConfig(stage); + }); + } + $scope.$on('pipeline-reverted', this.selectStage); $scope.$on('pipeline-json-edited', this.selectStage); $scope.$watch('stage.type', this.selectStage);