diff --git a/SAS/ResourceAssignment/ResourceAssignmentDatabase/radbpglistener.py b/SAS/ResourceAssignment/ResourceAssignmentDatabase/radbpglistener.py index 4da8de503b025c64cc51765fc04ff4a97098aa97..d6fbb82c38b7274234c3b8a568822db16ac4987d 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentDatabase/radbpglistener.py +++ b/SAS/ResourceAssignment/ResourceAssignmentDatabase/radbpglistener.py @@ -79,7 +79,17 @@ class RADBPGListener(PostgresListener): self.subscribe('resource_capacity_update', self.onResourceCapacityUpdated) def onTaskUpdated(self, payload = None): - self._sendNotification('TaskUpdated', self.rarpc.getTask(payload)) + # Send notification for the given updated task + task_id = payload + task = self.rarpc.getTask(task_id) + self._sendNotification('TaskUpdated', task) + + # The "blocked_by_ids" property of the given task's successors might have been updated due to the given task + # status being updated. Therefore also send a notification for these successors - lazily ignoring that they + # might not have changed. + suc_sched_tasks = self.rarpc.getTasks(task_ids=task['successor_ids'], task_status='scheduled') + for suc_sched_task in suc_sched_tasks: + self._sendNotification('TaskUpdated', suc_sched_task) def onTaskInserted(self, payload = None): self._sendNotification('TaskInserted', self.rarpc.getTask(payload)) diff --git a/SAS/ResourceAssignment/ResourceAssignmentDatabase/sql/create_database.sql b/SAS/ResourceAssignment/ResourceAssignmentDatabase/sql/create_database.sql index 0dbaed1f40b5a458ca5f791b959592733cf31314..643de3fa1e0008be8e00fb1503dfa528dd50f067 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentDatabase/sql/create_database.sql +++ b/SAS/ResourceAssignment/ResourceAssignmentDatabase/sql/create_database.sql @@ -307,7 +307,12 @@ CREATE OR REPLACE VIEW resource_allocation.task_view AS SELECT t.id, t.mom_id, t.otdb_id, t.status_id, t.type_id, t.specification_id, ts.name AS status, tt.name AS type, s.starttime, s.endtime, extract(epoch from age(s.endtime, s.starttime)) as duration, s.cluster, (SELECT array_agg(tp.predecessor_id) FROM resource_allocation.task_predecessor tp where tp.task_id=t.id) as predecessor_ids, - (SELECT array_agg(tp.task_id) FROM resource_allocation.task_predecessor tp where tp.predecessor_id=t.id) as successor_ids + (SELECT array_agg(tp.task_id) FROM resource_allocation.task_predecessor tp where tp.predecessor_id=t.id) as successor_ids, + (SELECT DISTINCT ARRAY ( + SELECT _tp.predecessor_id + FROM resource_allocation.task_predecessor _tp, resource_allocation.task _t + WHERE t.status_id = 400 AND _tp.task_id = t.id AND _t.id = _tp.predecessor_id AND (_t.status_id = 1100 OR _t.status_id = 1150) ) as array_agg + ) as blocked_by_ids FROM resource_allocation.task t JOIN resource_allocation.task_status ts ON ts.id = t.status_id JOIN resource_allocation.task_type tt ON tt.id = t.type_id diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js index 0ebfd32d4f27e02ce441f9142ba7a05c1e212f63..0a4346b3783bab093faa64a47f29b8c067d7604c 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/datacontroller.js @@ -355,7 +355,7 @@ angular.module('raeApp').factory("dataService", ['$http', '$q', function($http, id = parseInt(id); } - var foundTask = self.tasks.find(function(t) { return t['id_name'] == id; }); + var foundTask = id_name == 'id' ? self.taskDict[id] : self.tasks.find(function(t) { return t[id_name] == id; }); if(foundTask) { defer.resolve(foundTask); @@ -892,6 +892,19 @@ dataControllerMod.controller('DataController', }; $scope.jumpToNow(); + + $scope.loadTasksSelectAndJumpIntoView = function(task_ids) { + var list_of_promises = task_ids.map(function(t_id) { return $scope.dataService.getTask(t_id); }); + var defer = $q.defer(); + $q.all(list_of_promises).then(function(in_tasks) { + var loaded_tasks = in_tasks.filter(function(t) { return t != undefined; }); + var loaded_tasks_ids = loaded_tasks.map(function(t) { return t.id; }); + $scope.dataService.setSelectedTaskIds(loaded_tasks_ids); + $scope.jumpToSelectedTasks(); + defer.resolve(loaded_tasks); + }); + return defer.promise; + }; $scope.loadTaskByOTDBIdSelectAndJumpIntoView = function(otdb_id) { var defer = $q.defer(); diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/ganttprojectcontroller.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/ganttprojectcontroller.js index ecc42fb4bda06a18d02ff8fa9de2fc74ea62e609..071293a45363a0d5243066b6c252863f30f8a021 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/ganttprojectcontroller.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/ganttprojectcontroller.js @@ -261,6 +261,15 @@ ganttProjectControllerMod.controller('GanttProjectController', ['$scope', 'dataS ganntProjectTypeRows.push(availableRow); ganntRows.push(availableRow); } + + // Scheduled tasks that are blocked tasks are shown differently and use a tooltip + var css_class = "task-status-"; + if (task.blocked_by_ids.length > 0) { + css_class += "blocked"; + } + else { + css_class += task.status; + } var rowTask = { id: task.id.toString(), @@ -268,8 +277,11 @@ ganttProjectControllerMod.controller('GanttProjectController', ['$scope', 'dataS from: task.starttime, to: task.endtime, raTask: task, - color: self.taskStatusColors[task.status], - classes: 'task-status-' + task.status, + + // Leave color property undefined; it is now defined by CSS + //color: self.taskStatusColors[task.status], + + classes: css_class, movable: $.inArray(task.status_id, editableTaskStatusIds) > -1 }; diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js index 6cfa82daf48d31161bd5577ce737619e4b16677f..10991e728fe0da5d07176d1d95d111e5bcfe8910 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/controllers/gridcontroller.js @@ -63,8 +63,15 @@ $scope.columns = [ editableCellTemplate: 'ui-grid/dropdownEditor', editDropdownOptionsArray: [], cellClass: function(grid, row, col, rowRenderIndex, colRenderIndex) { - var value = grid.getCellValue(row,col); - return "grid-status-" + value; + // Scheduled tasks that are blocked tasks use a different make-up style + var css_class = "grid-status-"; + if (row.entity.blocked_by_ids.length > 0) { + css_class += "blocked"; + } + else { + css_class += grid.getCellValue(row,col); + } + return css_class; } }, { field: 'type', @@ -261,7 +268,8 @@ $scope.columns = [ mom_object_group_id: task.mom_object_group_id, mom_object_group_name: task.mom_object_group_name, mom_object_group_mom2object_id: task.mom_object_group_mom2object_id, - cluster: task.cluster + cluster: task.cluster, + blocked_by_ids: task.blocked_by_ids, }; gridTasks.push(gridTask); } @@ -588,20 +596,39 @@ gridControllerMod.directive('contextMenu', ['$document', '$window', function($do }); } - var finished_selected_cep4_pipelines = selected_cep4_tasks.filter(function(t) { return (t.status == 'finished' || t.status == 'aborted' || t.status == 'error') && t.type == 'pipeline'; }); + var aborted_selected_cep4_pipelines = selected_cep4_tasks.filter(function(t) { return (t.status == 'aborted' || t.status == 'error') && t.type == 'pipeline'; }); - if(finished_selected_cep4_pipelines.length > 0) { - var liContent = '<li><a href="#">Reschedule finished/aborted CEP4 pipelines</a></li>' + if(aborted_selected_cep4_pipelines.length > 0) { + var liContent = '<li><a href="#">Reschedule aborted/error CEP4 pipelines</a></li>' var liElement = angular.element(liContent); ulElement.append(liElement); liElement.on('click', function() { closeContextMenu(); - for(var pl of finished_selected_cep4_pipelines) { + for(var pl of aborted_selected_cep4_pipelines) { var newTask = { id: pl.id, status: 'prescheduled' }; dataService.putTask(newTask); } }); } + + var blocked_selected_cep4_tasks = selected_cep4_tasks.filter(function(t) { return (t.blocked_by_ids.length > 0); }); + + if(blocked_selected_cep4_tasks.length > 0) { + var liContent = '<li><a href="#">Select blocking predecessors</a></li>' + var liElement = angular.element(liContent); + ulElement.append(liElement); + liElement.on('click', function() { + closeContextMenu(); + + var blocking_predecessors = [] + for(var task of blocked_selected_cep4_tasks) { + blocking_predecessors = blocking_predecessors.concat(task.blocked_by_ids); + } + + var dataCtrlScope = $scope.$parent.$parent.$parent.$parent.$parent.$parent.$parent.$parent.$parent.$parent; + dataCtrlScope.loadTasksSelectAndJumpIntoView(blocking_predecessors); + }); + } var closeContextMenu = function(cme) { contextmenuElement.remove(); diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/gantt-plugins/angular-gantt-contextmenu-plugin.js b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/gantt-plugins/angular-gantt-contextmenu-plugin.js index 1e05df07db08861df57260dc4659d80531c329b6..ead27ced7b04a2391430ab7f9d6b558989a19cd6 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/gantt-plugins/angular-gantt-contextmenu-plugin.js +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/app/gantt-plugins/angular-gantt-contextmenu-plugin.js @@ -162,21 +162,40 @@ }); } - var finished_selected_cep4_pipelines = selected_cep4_tasks.filter(function(t) { return (t.status == 'finished' || t.status == 'aborted' || t.status == 'error') && t.type == 'pipeline'; }); + var aborted_selected_cep4_pipelines = selected_cep4_tasks.filter(function(t) { return (t.status == 'aborted' || t.status == 'error') && t.type == 'pipeline'; }); - if(finished_selected_cep4_pipelines.length > 0) { - var liContent = '<li><a href="#">Reschedule finished/aborted CEP4 pipelines</a></li>' + if(aborted_selected_cep4_pipelines.length > 0) { + var liContent = '<li><a href="#">Reschedule aborted/error CEP4 pipelines</a></li>' var liElement = angular.element(liContent); ulElement.append(liElement); liElement.on('click', function() { closeContextMenu(); - for(var pl of finished_selected_cep4_pipelines) { + for(var pl of aborted_selected_cep4_pipelines) { var newTask = { id: pl.id, status: 'prescheduled' }; dataService.putTask(newTask); } }); } + var blocked_selected_cep4_tasks = selected_cep4_tasks.filter(function(t) { return (t.blocked_by_ids.length > 0); }); + + if(blocked_selected_cep4_tasks.length > 0) { + var liContent = '<li><a href="#">Select blocking predecessors</a></li>' + var liElement = angular.element(liContent); + ulElement.append(liElement); + liElement.on('click', function() { + closeContextMenu(); + + var blocking_predecessors = [] + for(var task of blocked_selected_cep4_tasks) { + blocking_predecessors = blocking_predecessors.concat(task.blocked_by_ids); + } + + dataCtrlScope.loadTasksSelectAndJumpIntoView(blocking_predecessors); + }); + } + + var closeContextMenu = function() { contextmenuElement.remove(); diff --git a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/css/main.css b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/css/main.css index 04e51917753190b41778446cf93ebb134eb50b00..9ef3d56d52777c119abc32d653afc6fe871849f9 100644 --- a/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/css/main.css +++ b/SAS/ResourceAssignment/ResourceAssignmentEditor/lib/static/css/main.css @@ -82,7 +82,8 @@ table.uib-timepicker td.uib-time { } .gantt-task-content { - margin-top: 2.2px; +/* margin-top: 2.2px; */ + padding-top: 2.2px; } .gantt-task.task-selected-task { @@ -104,132 +105,115 @@ div.gantt-task span { padding: 0px 10px; } -div.gantt-task.task-status-on_hold span, div.gantt-task.task-status-prescheduled span, div.gantt-task.task-status-scheduled span, div.gantt-task.task-status-aborted span, div.gantt-task.task-status-error span { - color: #ffffff; -} - -div.gantt-task.claim-task-status-on_hold span, div.gantt-task.claim-task-status-prescheduled span, div.gantt-task.claim-task-status-scheduled span, div.gantt-task.claim-task-status-aborted span, div.gantt-task.claim-task-status-error span { - color: #ffffff; -} +/* + The same status coloring is used for both the grid items and the gantt-chart items (a.k.a. tasks) +*/ +.grid-status-prepared, +div.gantt-task.task-status-prepared div, div.gantt-task.claim-task-status-prepared span { - background: #cccccc; -} - -div.gantt-task.claim-task-status-approved span { - background: #8cb3d9; -} - -div.gantt-task.claim-task-status-on_hold span { - background: #b34700; -} - -div.gantt-task.claim-task-status-conflict span { - background: #ff0000; -} - -div.gantt-task.claim-task-status-prescheduled span { - background: #6666ff; -} - -div.gantt-task.claim-task-status-scheduled span { - background: #0000ff; -} - -div.gantt-task.claim-task-status-queued span { - background: #ccffff; -} - -div.gantt-task.claim-task-status-active span { - background: #ffff00; -} - -div.gantt-task.claim-task-status-completing span { - background: #ffdd99; -} - -div.gantt-task.claim-task-status-finished span { - background: #00ff00; -} - -div.gantt-task.claim-task-status-aborted span { - background: #cc0000; -} - -div.gantt-task.claim-task-status-error span { - background: #990033; -} - -div.gantt-task.claim-task-status-opened span { - background: #d9e5f2; -} - -div.gantt-task.claim-task-status-suspended span { - background: #996666; -} - -.grid-status-prepared { background-color: #cccccc !important; } -.grid-status-opened { +.grid-status-opened, +div.gantt-task.task-status-opened div, +div.gantt-task.claim-task-status-opened span { background-color: #aaaaaa !important; } -.grid-status-suspended { +.grid-status-suspended, +div.gantt-task.task-status-suspended div, +div.gantt-task.claim-task-status-suspended span { background-color: #776666 !important; } -.grid-status-approved { +.grid-status-approved, +div.gantt-task.task-status-approved div, +div.gantt-task.claim-task-status-approved span { background-color: #8cb3d9 !important; } -.grid-status-on_hold { +.grid-status-on_hold, +div.gantt-task.task-status-on_hold div, +div.gantt-task.claim-task-status-on_hold span { background-color: #b34700 !important; color: #ffffff; } -.grid-status-conflict { +.grid-status-conflict, +div.gantt-task.task-status-conflict div, +div.gantt-task.claim-task-status-conflict span { background-color: #ff0000 !important; } -.grid-status-prescheduled { +.grid-status-prescheduled, +div.gantt-task.task-status-prescheduled div, +div.gantt-task.claim-task-status-prescheduled span { background-color: #6666ff !important; color: #ffffff; } -.grid-status-scheduled { +.grid-status-scheduled, .grid-status-blocked, +div.gantt-task.task-status-scheduled div, +div.gantt-task.task-status-blocked div, +div.gantt-task.claim-task-status-scheduled span { background-color: #0000ff !important; color: #ffffff; } -.grid-status-queued { +.grid-status-queued, +div.gantt-task.task-status-queued div, +div.gantt-task.claim-task-status-queued span { background-color: #ccffff !important; } -.grid-status-active { +.grid-status-active, +div.gantt-task.task-status-active div, +div.gantt-task.claim-task-status-active span { background-color: #ffff00 !important; } -.grid-status-completing { +.grid-status-completing, +div.gantt-task.task-status-completing div, +div.gantt-task.claim-task-status-completing span { background-color: #ffdd99 !important; } -.grid-status-finished { +.grid-status-finished, +div.gantt-task.task-status-finished div, +div.gantt-task.claim-task-status-finished span { background-color: #00ff00 !important; } -.grid-status-aborted { +.grid-status-aborted, +div.gantt-task.task-status-aborted div, +div.gantt-task.claim-task-status-aborted span { background-color: #cc0000 !important; color: #ffffff; } -.grid-status-error { +.grid-status-error, +div.gantt-task.task-status-error div, +div.gantt-task.claim-task-status-error span { background-color: #990033 !important; color: #ffffff; } +.grid-status-blocked, div.gantt-task.task-status-blocked div { + background-image: url(/static/icons/warning.png); + background-repeat: no-repeat; +} + +.grid-status-blocked { + background-position: right 50%; + padding-right: 18px; +} + +div.gantt-task.task-status-blocked div{ + background-position: left 50%; +} + .grid-cluster-CEP2 { background-color: #ccffff !important;