diff --git a/.eslintrc b/.eslintrc index 42325f5879a..89c9eb71cf9 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,6 +1,7 @@ { "ecmaFeatures": { "jsx": false, + "modules": true, }, "extends": "eslint:recommended", "rules": { diff --git a/app/scripts/modules/core/pipeline/config/stages/executionWindows/ExecutionWindowDayPickerComponent.spec.js b/app/scripts/modules/core/pipeline/config/stages/executionWindows/ExecutionWindowDayPickerComponent.spec.js new file mode 100644 index 00000000000..d97e1e3456e --- /dev/null +++ b/app/scripts/modules/core/pipeline/config/stages/executionWindows/ExecutionWindowDayPickerComponent.spec.js @@ -0,0 +1,90 @@ +import { DAYS_OF_WEEK} from './daysOfWeek'; + +describe('Component: Execution Window Day Picker', () => { + + let $componentController; + beforeEach(window.module(require('./executionWindowDayPicker.component.js'))); + beforeEach(window.inject((_$componentController_) => $componentController = _$componentController_)); + + function constructController(bindings) { + return $componentController('executionWindowDayPicker', null, bindings); + } + + it('should not have any days selected when the days property is not defined', () => { + const ctrl = constructController({days: undefined}); + expect(ctrl.days).toBeUndefined(); + }); + + it('should not have any days selected when the days property is null', () => { + const ctrl = constructController({days: null}); + expect(ctrl.days).toBe(null); + }); + + it('should not have any days selected when the days property is an empty array', () => { + const ctrl = constructController({days: []}); + expect(ctrl.days.length).toBe(0); + }); + + DAYS_OF_WEEK.forEach((day) => { + it(`should have ${day.key} selected when the days property contains ${day.ordinal}`, () => { + const ctrl = constructController({days: [day.ordinal]}); + expect(ctrl.days.length).toBe(1); + expect(ctrl.days[0]).toBe(day.ordinal); + }); + }); + + it('should select all the days when the all button is clicked', () => { + const ctrl = constructController({}); + ctrl.all(); + expect(ctrl.days.length).toBe(7); + }); + + it('should select none of the days when the none button is clicked', () => { + const ctrl = constructController({}); + ctrl.all(); + expect(ctrl.days.length).toBe(7); + ctrl.none(); + expect(ctrl.days.length).toBe(0); + }); + + it('should select just the weekdays when the weekday button is clicked', () => { + const ctrl = constructController({}); + ctrl.weekdays(); + expect(ctrl.days.length).toBe(5); + expect(ctrl.days).toEqual([2,3,4,5,6]); + }); + + it('should select just the weekend when the weekend button is clicked', () => { + const ctrl = constructController({}); + ctrl.weekend(); + expect(ctrl.days.length).toBe(2); + expect(ctrl.days[0]).toBe(1); // sunday + expect(ctrl.days[1]).toBe(7); // saturday + }); + + it('should specify whether or not a day is selected', () => { + const ctrl = constructController({ + days: [1] + }); + expect(ctrl.daySelected(1)).toBe(true); + expect(ctrl.daySelected(2)).toBe(false); + + ctrl.days = [1, 2]; + expect(ctrl.daySelected(2)).toBe(true); + expect(ctrl.daySelected(3)).toBe(false); + }); + + it('should add a day to the model when selected', () => { + const ctrl = constructController({}); + ctrl.updateModel(DAYS_OF_WEEK[0]); + expect(ctrl.daySelected(1)).toBe(true); + }); + + it('should remove a day from the model when unselected', () => { + const ctrl = constructController({ + days: [1] // pre-select sunday + }); + ctrl.updateModel(DAYS_OF_WEEK[0]); + expect(ctrl.daySelected(1)).toBe(false); + }); +}); diff --git a/app/scripts/modules/core/pipeline/config/stages/executionWindows/daysOfWeek.js b/app/scripts/modules/core/pipeline/config/stages/executionWindows/daysOfWeek.js new file mode 100644 index 00000000000..5af0fb93e0b --- /dev/null +++ b/app/scripts/modules/core/pipeline/config/stages/executionWindows/daysOfWeek.js @@ -0,0 +1,31 @@ +export const DAYS_OF_WEEK = [ + { + key: 'sunday', + label: 'Sun', + ordinal: 1 + }, { + key: 'monday', + label: 'Mon', + ordinal: 2 + }, { + key: 'tuesday', + label: 'Tue', + ordinal: 3 + }, { + key: 'wednesday', + label: 'Wed', + ordinal: 4 + }, { + key: 'thursday', + label: 'Thu', + ordinal: 5 + }, { + key: 'friday', + label: 'Fri', + ordinal: 6 + }, { + key: 'saturday', + label: 'Sat', + ordinal: 7 + } +]; diff --git a/app/scripts/modules/core/pipeline/config/stages/executionWindows/executionWindowDayPicker.component.html b/app/scripts/modules/core/pipeline/config/stages/executionWindows/executionWindowDayPicker.component.html new file mode 100644 index 00000000000..0da87513ee6 --- /dev/null +++ b/app/scripts/modules/core/pipeline/config/stages/executionWindows/executionWindowDayPicker.component.html @@ -0,0 +1,13 @@ +
+ +
+
+ All + None + Weekdays + Weekend +
diff --git a/app/scripts/modules/core/pipeline/config/stages/executionWindows/executionWindowDayPicker.component.js b/app/scripts/modules/core/pipeline/config/stages/executionWindows/executionWindowDayPicker.component.js new file mode 100644 index 00000000000..c6dc1bf9fe6 --- /dev/null +++ b/app/scripts/modules/core/pipeline/config/stages/executionWindows/executionWindowDayPicker.component.js @@ -0,0 +1,55 @@ +const angular = require('angular'); + +import { DAYS_OF_WEEK } from './daysOfWeek'; + +class ExecutionWindowDayPickerController { + + constructor() { + this.DAYS_OF_WEEK = DAYS_OF_WEEK; + } + + daySelected(ordinal) { + const days = new Set(this.days); + return days.has(ordinal); + } + + all() { + this.days = [1, 2, 3, 4, 5, 6, 7]; + } + + none() { + this.days = []; + } + + weekdays() { + this.days = [2, 3, 4, 5, 6]; + } + + weekend() { + this.days = [1, 7]; + } + + updateModel(day) { + + if (!this.days) { + this.days = []; // for pre-existing pipelines, the 'days' property will not exist + } + + const days = new Set(this.days); + if (days.has(day.ordinal)) { + this.days = this.days.filter((_day) => _day !== day.ordinal); + } + else { + this.days.push(day.ordinal); + } + } +} + +module.exports = angular.module('spinnaker.core.pipeline.stage.executionWindows.dayPicker', []) + .component('executionWindowDayPicker', { + bindings: { + days: '=' + }, + controller: ExecutionWindowDayPickerController, + templateUrl: require('./executionWindowDayPicker.component.html') + }); diff --git a/app/scripts/modules/core/pipeline/config/stages/executionWindows/executionWindows.html b/app/scripts/modules/core/pipeline/config/stages/executionWindows/executionWindows.html index 244b23bf4ae..164ebeeb17b 100644 --- a/app/scripts/modules/core/pipeline/config/stages/executionWindows/executionWindows.html +++ b/app/scripts/modules/core/pipeline/config/stages/executionWindows/executionWindows.html @@ -9,6 +9,24 @@
+
+
+
+ Days of the Week + (No days selected implies the stage will execute on any day if triggered) +
+

+ +

+
+
+
+
+

+ Time of Day +

+
+
@@ -75,13 +93,11 @@
-
-
- -
+
+
+
-
diff --git a/app/scripts/modules/core/pipeline/config/stages/executionWindows/executionWindows.less b/app/scripts/modules/core/pipeline/config/stages/executionWindows/executionWindows.less index 7b3c31db656..fdae22dfa1b 100644 --- a/app/scripts/modules/core/pipeline/config/stages/executionWindows/executionWindows.less +++ b/app/scripts/modules/core/pipeline/config/stages/executionWindows/executionWindows.less @@ -7,23 +7,23 @@ execution-windows { height: @graph_height; border: 1px solid @mid_lighter_grey; position: relative; - margin: 20px 0; + margin: 0 0 0 10px; @label-height: 20px; @divider-height: @graph_height - @label-height; .execution-window { - height: @graph_height + 10; + height: @graph_height - 1; background-color: @healthy_green; position: absolute; bottom: 0; z-index: 3; &:after { content: ''; - width: calc(~"100% + 2px"); + width: calc(~"100% + 1px"); border: 1px solid darken(@mid_lighter_grey, 20%); border-bottom-width: 0; - height: @graph_height - @label-height + 10; + height: @graph_height - @label-height - 1; position: absolute; top: 0; left: -1px; @@ -92,3 +92,11 @@ execution-windows { margin-left: 20px; } } + +p.execution-window-days-of-week { + margin-left: 10px; + + div.button-controls { + display: inline-block; + } +} diff --git a/app/scripts/modules/core/pipeline/config/stages/executionWindows/executionWindowsDetails.controller.js b/app/scripts/modules/core/pipeline/config/stages/executionWindows/executionWindowsDetails.controller.js index 031b60c7ee6..b72159d2a41 100644 --- a/app/scripts/modules/core/pipeline/config/stages/executionWindows/executionWindowsDetails.controller.js +++ b/app/scripts/modules/core/pipeline/config/stages/executionWindows/executionWindowsDetails.controller.js @@ -1,5 +1,7 @@ 'use strict'; +import { DAYS_OF_WEEK } from './daysOfWeek'; + let angular = require('angular'); module.exports = angular.module('spinnaker.core.pipeline.stage.executionWindows.details.controller', [ @@ -13,9 +15,28 @@ module.exports = angular.module('spinnaker.core.pipeline.stage.executionWindows. $scope.configSections = ['windowConfig', 'taskStatus']; + // yes, this is ugly - when we replace the execution window w/ an ng2 component, this will go away. i promise + function replaceDays(days) { + const daySet = new Set(days); + return DAYS_OF_WEEK.filter(day => daySet.has(day.ordinal)).map(day => day.label); + } + + // ditto + function getDayText() { + let dayText = 'Everyday'; + const days = $scope.stage.context.restrictedExecutionWindow.days; + if (days && (days.length > 0)) { + const daysAsText = replaceDays(days); + dayText = daysAsText.join(', '); + } + + return dayText; + } + function initialize() { executionDetailsSectionService.synchronizeSection($scope.configSections); $scope.detailsSection = $stateParams.details; + $scope.dayText = getDayText(); } initialize(); @@ -27,7 +48,7 @@ module.exports = angular.module('spinnaker.core.pipeline.stage.executionWindows. return match.status !== 'RUNNING'; }; - let data = { skipRemainingWait: true }; + let data = {skipRemainingWait: true}; confirmationModalService.confirm({ header: 'Really skip execution window?', buttonText: 'Skip', diff --git a/app/scripts/modules/core/pipeline/config/stages/executionWindows/executionWindowsDetails.html b/app/scripts/modules/core/pipeline/config/stages/executionWindows/executionWindowsDetails.html index 2f38ef4f955..da6723f069d 100644 --- a/app/scripts/modules/core/pipeline/config/stages/executionWindows/executionWindowsDetails.html +++ b/app/scripts/modules/core/pipeline/config/stages/executionWindows/executionWindowsDetails.html @@ -15,6 +15,8 @@
Execution Windows Configuration
 
+
On
+
{{ ::dayText }}
diff --git a/app/scripts/modules/core/pipeline/config/stages/executionWindows/executionWindowsStage.module.js b/app/scripts/modules/core/pipeline/config/stages/executionWindows/executionWindowsStage.module.js index d1417b69a44..4dc5b3e06ca 100644 --- a/app/scripts/modules/core/pipeline/config/stages/executionWindows/executionWindowsStage.module.js +++ b/app/scripts/modules/core/pipeline/config/stages/executionWindows/executionWindowsStage.module.js @@ -8,6 +8,7 @@ module.exports = angular.module('spinnaker.core.pipeline.stage.executionWindows' require('./executionWindowsStage.js'), require('./executionWindows.transformer.js'), require('./executionWindows.directive.js'), + require('./executionWindowDayPicker.component'), require('./executionWindowsDetails.controller'), require('../stage.module.js'), require('../core/stage.core.module.js'),