From 1768001881b4883433eb162d3ee99fb27c824f53 Mon Sep 17 00:00:00 2001 From: Marliana Lara Date: Tue, 13 Feb 2018 13:30:59 -0500 Subject: [PATCH 1/5] Add support for TZID in schedule rrules --- .../factories/r-rule-to-api.factory.js | 10 +++--- .../factories/schedule-post.factory.js | 2 +- .../src/scheduler/schedulerAdd.controller.js | 33 ++++++++++++++++--- .../src/scheduler/schedulerEdit.controller.js | 31 ++++++++++++++--- .../src/scheduler/schedulerForm.partial.html | 16 ++++++--- 5 files changed, 74 insertions(+), 18 deletions(-) diff --git a/awx/ui/client/src/scheduler/factories/r-rule-to-api.factory.js b/awx/ui/client/src/scheduler/factories/r-rule-to-api.factory.js index 1e3b251d8a..7698ed9ea5 100644 --- a/awx/ui/client/src/scheduler/factories/r-rule-to-api.factory.js +++ b/awx/ui/client/src/scheduler/factories/r-rule-to-api.factory.js @@ -1,9 +1,11 @@ export default function RRuleToAPI() { - return function(rrule) { - var response; - response = rrule.replace(/(^.*(?=DTSTART))(DTSTART=.*?;)(.*$)/, function(str, p1, p2, p3) { - return p2.replace(/\;/,'').replace(/=/,':') + ' ' + 'RRULE:' + p1 + p3; + return function(rrule, scope) { + let localTime = scope.schedulerLocalTime; + let timeZone = scope.schedulerTimeZone.name; + + let response = rrule.replace(/(^.*(?=DTSTART))(DTSTART.*?)(=.*?;)(.*$)/, (str, p1, p2, p3, p4) => { + return p2 + ';TZID=' + timeZone + ':' + localTime + ' ' + 'RRULE:' + p4; }); return response; }; diff --git a/awx/ui/client/src/scheduler/factories/schedule-post.factory.js b/awx/ui/client/src/scheduler/factories/schedule-post.factory.js index 3033219275..8911b51265 100644 --- a/awx/ui/client/src/scheduler/factories/schedule-post.factory.js +++ b/awx/ui/client/src/scheduler/factories/schedule-post.factory.js @@ -14,7 +14,7 @@ export default newSchedule = scheduler.getValue(); rrule = scheduler.getRRule(); schedule.name = newSchedule.name; - schedule.rrule = RRuleToAPI(rrule.toString()); + schedule.rrule = RRuleToAPI(rrule.toString(), scope); schedule.description = (/error/.test(rrule.toText())) ? '' : rrule.toText(); if (scope.isFactCleanup) { diff --git a/awx/ui/client/src/scheduler/schedulerAdd.controller.js b/awx/ui/client/src/scheduler/schedulerAdd.controller.js index d7f82c32ca..a0d2d6d8d4 100644 --- a/awx/ui/client/src/scheduler/schedulerAdd.controller.js +++ b/awx/ui/client/src/scheduler/schedulerAdd.controller.js @@ -4,14 +4,14 @@ * All Rights Reserved *************************************************/ -export default ['$filter', '$state', '$stateParams', 'Wait', +export default ['$filter', '$state', '$stateParams', '$http', 'Wait', '$scope', '$rootScope', 'CreateSelect2', 'ParseTypeChange', 'GetBasePath', 'Rest', 'ParentObject', 'JobTemplateModel', '$q', 'Empty', 'SchedulePost', - 'ProcessErrors', 'SchedulerInit', '$location', 'PromptService', - function($filter, $state, $stateParams, Wait, + 'ProcessErrors', 'SchedulerInit', '$location', 'PromptService', 'RRuleToAPI', + function($filter, $state, $stateParams, $http, Wait, $scope, $rootScope, CreateSelect2, ParseTypeChange, GetBasePath, Rest, ParentObject, JobTemplate, $q, Empty, SchedulePost, - ProcessErrors, SchedulerInit, $location, PromptService) { + ProcessErrors, SchedulerInit, $location, PromptService, RRuleToAPI) { var base = $scope.base || $location.path().replace(/^\//, '').split('/')[0], scheduler, @@ -283,6 +283,16 @@ export default ['$filter', '$state', '$stateParams', 'Wait', Wait('start'); $('#form-container').empty(); scheduler = SchedulerInit({ scope: $scope, requireFutureStartTime: false }); + let timeZonesAPI = () => { + return $http.get(`/api/v2/schedules/zoneinfo/`); + }; + // set API timezones to scheduler object + timeZonesAPI().then(({data}) => { + scheduler.scope.timeZones = data; + scheduler.scope.schedulerTimeZone = _.find(data, (zone) => { + return zone.name === scheduler.scope.current_timezone.name; + }); + }); if($scope.schedulerUTCTime) { // The UTC time is already set processSchedulerEndDt(); @@ -306,7 +316,22 @@ export default ['$filter', '$state', '$stateParams', 'Wait', $scope.$on("formUpdated", function() { $rootScope.$broadcast("loadSchedulerDetailPane"); }); + $scope.$on("setPreviewPane", (event) => { + let rrule = event.currentScope.rrule.toString(); + let req = RRuleToAPI(rrule, $scope); + $http.post('/api/v2/schedules/preview/', {'rrule': req}) + .then(({data}) => { + + $scope.preview_list = data; + for (let tz in data) { + $scope.preview_list.isEmpty = data[tz].length === 0; + $scope.preview_list[tz] = data[tz].map(function(date) { + return date.replace(/Z/, ''); + }); + } + }); + }); $scope.$watchGroup(["schedulerName", "schedulerStartDt", "schedulerStartHour", diff --git a/awx/ui/client/src/scheduler/schedulerEdit.controller.js b/awx/ui/client/src/scheduler/schedulerEdit.controller.js index a762b06072..98b92470d4 100644 --- a/awx/ui/client/src/scheduler/schedulerEdit.controller.js +++ b/awx/ui/client/src/scheduler/schedulerEdit.controller.js @@ -1,9 +1,9 @@ export default ['$filter', '$state', '$stateParams', 'Wait', '$scope', -'$rootScope', 'CreateSelect2', 'ParseTypeChange', 'ParentObject', 'ProcessErrors', 'Rest', -'GetBasePath', 'SchedulerInit', 'SchedulePost', 'JobTemplateModel', '$q', 'Empty', 'PromptService', +'$rootScope', '$http', 'CreateSelect2', 'ParseTypeChange', 'ParentObject', 'ProcessErrors', 'Rest', +'GetBasePath', 'SchedulerInit', 'SchedulePost', 'JobTemplateModel', '$q', 'Empty', 'PromptService', 'RRuleToAPI', function($filter, $state, $stateParams, Wait, $scope, - $rootScope, CreateSelect2, ParseTypeChange, ParentObject, ProcessErrors, Rest, - GetBasePath, SchedulerInit, SchedulePost, JobTemplate, $q, Empty, PromptService) { + $rootScope, $http, CreateSelect2, ParseTypeChange, ParentObject, ProcessErrors, Rest, + GetBasePath, SchedulerInit, SchedulePost, JobTemplate, $q, Empty, PromptService, RRuleToAPI) { let schedule, scheduler; @@ -85,6 +85,22 @@ function($filter, $state, $stateParams, Wait, $scope, callSelect2(); }); + $scope.$on("setPreviewPane", (event) => { + let rrule = event.currentScope.rrule.toString(); + let req = RRuleToAPI(rrule, $scope); + + $http.post('/api/v2/schedules/preview/', {'rrule': req}) + .then(({data}) => { + $scope.preview_list = data; + for (let tz in data) { + $scope.preview_list.isEmpty = data[tz].length === 0; + $scope.preview_list[tz] = data[tz].map(function(date) { + return date.replace(/Z/, ''); + }); + } + }); + }); + Wait('start'); // Get the existing record @@ -111,6 +127,13 @@ function($filter, $state, $stateParams, Wait, $scope, $('#form-container').empty(); scheduler = SchedulerInit({ scope: $scope, requireFutureStartTime: false }); + + $http.get('/api/v2/schedules/zoneinfo/').then(({data}) => { + scheduler.scope.timeZones = data; + scheduler.scope.schedulerTimeZone = _.find(data, (zone) => { + return zone.name === scheduler.scope.current_timezone.name; + }); + }); scheduler.inject('form-container', false); scheduler.injectDetail('occurrences', false); diff --git a/awx/ui/client/src/scheduler/schedulerForm.partial.html b/awx/ui/client/src/scheduler/schedulerForm.partial.html index b06b1ea7eb..ad87462d65 100644 --- a/awx/ui/client/src/scheduler/schedulerForm.partial.html +++ b/awx/ui/client/src/scheduler/schedulerForm.partial.html @@ -28,6 +28,12 @@ name="schedulerName" id="schedulerName" ng-model="schedulerName" required + ng-model-options="{ + 'updateOn': 'default blur', + 'debounce': { + 'default': 300, + 'blur': 0 + }}" ng-disabled="!(schedule_obj.summary_fields.user_capabilities.edit || canAdd)" placeholder="Schedule name">
+ ng-show="schedulerIsValid && !preview_list.isEmpty"> @@ -623,15 +629,15 @@ From 73916ade454cf6a8f59215d4aebfd95a47b367bc Mon Sep 17 00:00:00 2001 From: Marliana Lara Date: Thu, 15 Feb 2018 10:09:36 -0500 Subject: [PATCH 2/5] Filter dates with moment.js instead of built-in angular date filter --- awx/ui/client/src/scheduler/schedulerAdd.controller.js | 8 ++++---- awx/ui/client/src/scheduler/schedulerEdit.controller.js | 7 ++++--- awx/ui/client/src/scheduler/schedulerForm.partial.html | 4 ++-- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/awx/ui/client/src/scheduler/schedulerAdd.controller.js b/awx/ui/client/src/scheduler/schedulerAdd.controller.js index a0d2d6d8d4..56a5eff369 100644 --- a/awx/ui/client/src/scheduler/schedulerAdd.controller.js +++ b/awx/ui/client/src/scheduler/schedulerAdd.controller.js @@ -7,11 +7,11 @@ export default ['$filter', '$state', '$stateParams', '$http', 'Wait', '$scope', '$rootScope', 'CreateSelect2', 'ParseTypeChange', 'GetBasePath', 'Rest', 'ParentObject', 'JobTemplateModel', '$q', 'Empty', 'SchedulePost', - 'ProcessErrors', 'SchedulerInit', '$location', 'PromptService', 'RRuleToAPI', + 'ProcessErrors', 'SchedulerInit', '$location', 'PromptService', 'RRuleToAPI', 'moment', function($filter, $state, $stateParams, $http, Wait, $scope, $rootScope, CreateSelect2, ParseTypeChange, GetBasePath, Rest, ParentObject, JobTemplate, $q, Empty, SchedulePost, - ProcessErrors, SchedulerInit, $location, PromptService, RRuleToAPI) { + ProcessErrors, SchedulerInit, $location, PromptService, RRuleToAPI, moment) { var base = $scope.base || $location.path().replace(/^\//, '').split('/')[0], scheduler, @@ -322,12 +322,12 @@ export default ['$filter', '$state', '$stateParams', '$http', 'Wait', $http.post('/api/v2/schedules/preview/', {'rrule': req}) .then(({data}) => { - $scope.preview_list = data; for (let tz in data) { $scope.preview_list.isEmpty = data[tz].length === 0; $scope.preview_list[tz] = data[tz].map(function(date) { - return date.replace(/Z/, ''); + date = date.replace(/Z/, ''); + return moment.parseZone(date).format("MM-DD-YYYY HH:mm:ss"); }); } }); diff --git a/awx/ui/client/src/scheduler/schedulerEdit.controller.js b/awx/ui/client/src/scheduler/schedulerEdit.controller.js index 98b92470d4..0e602e9baf 100644 --- a/awx/ui/client/src/scheduler/schedulerEdit.controller.js +++ b/awx/ui/client/src/scheduler/schedulerEdit.controller.js @@ -1,7 +1,7 @@ -export default ['$filter', '$state', '$stateParams', 'Wait', '$scope', +export default ['$filter', '$state', '$stateParams', 'Wait', '$scope', 'moment', '$rootScope', '$http', 'CreateSelect2', 'ParseTypeChange', 'ParentObject', 'ProcessErrors', 'Rest', 'GetBasePath', 'SchedulerInit', 'SchedulePost', 'JobTemplateModel', '$q', 'Empty', 'PromptService', 'RRuleToAPI', -function($filter, $state, $stateParams, Wait, $scope, +function($filter, $state, $stateParams, Wait, $scope, moment, $rootScope, $http, CreateSelect2, ParseTypeChange, ParentObject, ProcessErrors, Rest, GetBasePath, SchedulerInit, SchedulePost, JobTemplate, $q, Empty, PromptService, RRuleToAPI) { @@ -95,7 +95,8 @@ function($filter, $state, $stateParams, Wait, $scope, for (let tz in data) { $scope.preview_list.isEmpty = data[tz].length === 0; $scope.preview_list[tz] = data[tz].map(function(date) { - return date.replace(/Z/, ''); + date = date.replace(/Z/, ''); + return moment.parseZone(date).format("MM-DD-YYYY HH:mm:ss"); }); } }); diff --git a/awx/ui/client/src/scheduler/schedulerForm.partial.html b/awx/ui/client/src/scheduler/schedulerForm.partial.html index ad87462d65..1d1655400a 100644 --- a/awx/ui/client/src/scheduler/schedulerForm.partial.html +++ b/awx/ui/client/src/scheduler/schedulerForm.partial.html @@ -630,14 +630,14 @@ SchedulerFormDetail-occurrenceList" ng-show="dateChoice == 'utc'">
  • - {{ occurrence | date:'MM-dd-yyyy HH:mm:ss'}} UTC + {{ occurrence }} UTC
    • - {{ occurrence | date:'MM-dd-yyyy HH:mm:ss'}} + {{ occurrence }}
    From e0cfd18aac242e6e2003d9febaaa24951078d8d7 Mon Sep 17 00:00:00 2001 From: Marliana Lara Date: Thu, 15 Feb 2018 15:13:18 -0500 Subject: [PATCH 3/5] Set local timezone dropdown to rrule TZID value --- awx/ui/client/src/scheduler/schedulerEdit.controller.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/awx/ui/client/src/scheduler/schedulerEdit.controller.js b/awx/ui/client/src/scheduler/schedulerEdit.controller.js index 0e602e9baf..468debfb0b 100644 --- a/awx/ui/client/src/scheduler/schedulerEdit.controller.js +++ b/awx/ui/client/src/scheduler/schedulerEdit.controller.js @@ -131,8 +131,9 @@ function($filter, $state, $stateParams, Wait, $scope, moment, $http.get('/api/v2/schedules/zoneinfo/').then(({data}) => { scheduler.scope.timeZones = data; - scheduler.scope.schedulerTimeZone = _.find(data, (zone) => { - return zone.name === scheduler.scope.current_timezone.name; + scheduler.scope.schedulerTimeZone = _.find(data, function(x) { + let tz = $scope.schedule_obj.rrule.match(/TZID=\s*(.*?)\s*:/)[1]; + return x.name === tz; }); }); scheduler.inject('form-container', false); From b9a2f7a87e643630d8709cbe0226150563ead488 Mon Sep 17 00:00:00 2001 From: Marliana Lara Date: Fri, 16 Feb 2018 10:52:02 -0500 Subject: [PATCH 4/5] Add debounce function to preview list to reduce overhead --- .../src/scheduler/schedulerAdd.controller.js | 37 +++++++++++-------- .../src/scheduler/schedulerEdit.controller.js | 11 ++++-- .../src/scheduler/schedulerForm.partial.html | 6 --- 3 files changed, 28 insertions(+), 26 deletions(-) diff --git a/awx/ui/client/src/scheduler/schedulerAdd.controller.js b/awx/ui/client/src/scheduler/schedulerAdd.controller.js index 56a5eff369..a32efb4553 100644 --- a/awx/ui/client/src/scheduler/schedulerAdd.controller.js +++ b/awx/ui/client/src/scheduler/schedulerAdd.controller.js @@ -308,6 +308,27 @@ export default ['$filter', '$state', '$stateParams', '$http', 'Wait', } }); } + + let previewList = _.debounce(function(req) { + $http.post('/api/v2/schedules/preview/', {'rrule': req}) + .then(({data}) => { + $scope.preview_list = data; + for (let tz in data) { + $scope.preview_list.isEmpty = data[tz].length === 0; + $scope.preview_list[tz] = data[tz].map(function(date) { + date = date.replace(/Z/, ''); + return moment.parseZone(date).format("MM-DD-YYYY HH:mm:ss"); + }); + } + }); + }, 300); + + $scope.$on("setPreviewPane", (event) => { + let rrule = event.currentScope.rrule.toString(); + let req = RRuleToAPI(rrule, $scope); + previewList(req); + }); + scheduler.inject('form-container', false); scheduler.injectDetail('occurrences', false); scheduler.clear(); @@ -316,22 +337,6 @@ export default ['$filter', '$state', '$stateParams', '$http', 'Wait', $scope.$on("formUpdated", function() { $rootScope.$broadcast("loadSchedulerDetailPane"); }); - $scope.$on("setPreviewPane", (event) => { - let rrule = event.currentScope.rrule.toString(); - let req = RRuleToAPI(rrule, $scope); - - $http.post('/api/v2/schedules/preview/', {'rrule': req}) - .then(({data}) => { - $scope.preview_list = data; - for (let tz in data) { - $scope.preview_list.isEmpty = data[tz].length === 0; - $scope.preview_list[tz] = data[tz].map(function(date) { - date = date.replace(/Z/, ''); - return moment.parseZone(date).format("MM-DD-YYYY HH:mm:ss"); - }); - } - }); - }); $scope.$watchGroup(["schedulerName", "schedulerStartDt", "schedulerStartHour", diff --git a/awx/ui/client/src/scheduler/schedulerEdit.controller.js b/awx/ui/client/src/scheduler/schedulerEdit.controller.js index 468debfb0b..ff740b61a2 100644 --- a/awx/ui/client/src/scheduler/schedulerEdit.controller.js +++ b/awx/ui/client/src/scheduler/schedulerEdit.controller.js @@ -85,10 +85,7 @@ function($filter, $state, $stateParams, Wait, $scope, moment, callSelect2(); }); - $scope.$on("setPreviewPane", (event) => { - let rrule = event.currentScope.rrule.toString(); - let req = RRuleToAPI(rrule, $scope); - + let previewList = _.debounce(function(req) { $http.post('/api/v2/schedules/preview/', {'rrule': req}) .then(({data}) => { $scope.preview_list = data; @@ -100,6 +97,12 @@ function($filter, $state, $stateParams, Wait, $scope, moment, }); } }); + }, 300); + + $scope.$on("setPreviewPane", (event) => { + let rrule = event.currentScope.rrule.toString(); + let req = RRuleToAPI(rrule, $scope); + previewList(req); }); Wait('start'); diff --git a/awx/ui/client/src/scheduler/schedulerForm.partial.html b/awx/ui/client/src/scheduler/schedulerForm.partial.html index 1d1655400a..bea1d9eade 100644 --- a/awx/ui/client/src/scheduler/schedulerForm.partial.html +++ b/awx/ui/client/src/scheduler/schedulerForm.partial.html @@ -28,12 +28,6 @@ name="schedulerName" id="schedulerName" ng-model="schedulerName" required - ng-model-options="{ - 'updateOn': 'default blur', - 'debounce': { - 'default': 300, - 'blur': 0 - }}" ng-disabled="!(schedule_obj.summary_fields.user_capabilities.edit || canAdd)" placeholder="Schedule name">
    Date: Wed, 21 Feb 2018 11:36:19 -0500 Subject: [PATCH 5/5] Fix jshint confusing semantics error --- awx/ui/client/src/scheduler/schedulerAdd.controller.js | 9 ++++++--- awx/ui/client/src/scheduler/schedulerEdit.controller.js | 9 ++++++--- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/awx/ui/client/src/scheduler/schedulerAdd.controller.js b/awx/ui/client/src/scheduler/schedulerAdd.controller.js index a32efb4553..661a708dd6 100644 --- a/awx/ui/client/src/scheduler/schedulerAdd.controller.js +++ b/awx/ui/client/src/scheduler/schedulerAdd.controller.js @@ -313,12 +313,15 @@ export default ['$filter', '$state', '$stateParams', '$http', 'Wait', $http.post('/api/v2/schedules/preview/', {'rrule': req}) .then(({data}) => { $scope.preview_list = data; - for (let tz in data) { - $scope.preview_list.isEmpty = data[tz].length === 0; - $scope.preview_list[tz] = data[tz].map(function(date) { + let parsePreviewList = (tz) => { + return data[tz].map(function(date) { date = date.replace(/Z/, ''); return moment.parseZone(date).format("MM-DD-YYYY HH:mm:ss"); }); + }; + for (let tz in data) { + $scope.preview_list.isEmpty = data[tz].length === 0; + $scope.preview_list[tz] = parsePreviewList(tz); } }); }, 300); diff --git a/awx/ui/client/src/scheduler/schedulerEdit.controller.js b/awx/ui/client/src/scheduler/schedulerEdit.controller.js index ff740b61a2..b0d8adcb83 100644 --- a/awx/ui/client/src/scheduler/schedulerEdit.controller.js +++ b/awx/ui/client/src/scheduler/schedulerEdit.controller.js @@ -89,12 +89,15 @@ function($filter, $state, $stateParams, Wait, $scope, moment, $http.post('/api/v2/schedules/preview/', {'rrule': req}) .then(({data}) => { $scope.preview_list = data; - for (let tz in data) { - $scope.preview_list.isEmpty = data[tz].length === 0; - $scope.preview_list[tz] = data[tz].map(function(date) { + let parsePreviewList = (tz) => { + return data[tz].map(function(date) { date = date.replace(/Z/, ''); return moment.parseZone(date).format("MM-DD-YYYY HH:mm:ss"); }); + }; + for (let tz in data) { + $scope.preview_list.isEmpty = data[tz].length === 0; + $scope.preview_list[tz] = parsePreviewList(tz); } }); }, 300);