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)
+
+
+
+
+
+
+
-
-
-
-
+
+
+
-
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'),