Repository: aurora Updated Branches: refs/heads/master 22f9cbb7e -> 9b8868fb7
Add a new UI page to show all tasks (active and completed) for a specific instance id. Reviewed at https://reviews.apache.org/r/37365/ Project: http://git-wip-us.apache.org/repos/asf/aurora/repo Commit: http://git-wip-us.apache.org/repos/asf/aurora/commit/9b8868fb Tree: http://git-wip-us.apache.org/repos/asf/aurora/tree/9b8868fb Diff: http://git-wip-us.apache.org/repos/asf/aurora/diff/9b8868fb Branch: refs/heads/master Commit: 9b8868fb7a6e5bf131926e59b5023497e9b7db4f Parents: 22f9cbb Author: Joshua Cohen <[email protected]> Authored: Tue Aug 18 16:23:42 2015 -0500 Committer: Joshua Cohen <[email protected]> Committed: Tue Aug 18 16:23:42 2015 -0500 ---------------------------------------------------------------------- src/main/python/apache/aurora/client/base.py | 2 +- .../resources/scheduler/assets/breadcrumb.html | 5 +- src/main/resources/scheduler/assets/css/app.css | 4 +- .../resources/scheduler/assets/instance.html | 106 ++++++ src/main/resources/scheduler/assets/job.html | 4 +- src/main/resources/scheduler/assets/js/app.js | 5 +- .../scheduler/assets/js/controllers.js | 346 +++++++------------ .../resources/scheduler/assets/js/services.js | 183 +++++++++- .../scheduler/assets/taskInstance.html | 14 + .../apache/aurora/client/cli/test_supdate.py | 6 +- .../python/apache/aurora/client/test_base.py | 22 ++ 11 files changed, 472 insertions(+), 225 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/aurora/blob/9b8868fb/src/main/python/apache/aurora/client/base.py ---------------------------------------------------------------------- diff --git a/src/main/python/apache/aurora/client/base.py b/src/main/python/apache/aurora/client/base.py index dee4c28..487f8e7 100644 --- a/src/main/python/apache/aurora/client/base.py +++ b/src/main/python/apache/aurora/client/base.py @@ -191,7 +191,7 @@ def synthesize_url(scheduler_url, role=None, env=None, job=None, update_id=None) if job: scheduler_url += '/' + job if update_id: - scheduler_url += '/' + update_id + scheduler_url += '/update/' + update_id return scheduler_url http://git-wip-us.apache.org/repos/asf/aurora/blob/9b8868fb/src/main/resources/scheduler/assets/breadcrumb.html ---------------------------------------------------------------------- diff --git a/src/main/resources/scheduler/assets/breadcrumb.html b/src/main/resources/scheduler/assets/breadcrumb.html index 1314793..9265277 100644 --- a/src/main/resources/scheduler/assets/breadcrumb.html +++ b/src/main/resources/scheduler/assets/breadcrumb.html @@ -24,10 +24,11 @@ <a href='/scheduler/{{role}}/{{environment}}'>Environment: {{environment}}</a> </li> - <li ng-if='job && !update' class='active'>Job: {{job}}</li> + <li ng-if='job && (!update && !(instance >= 0))' class='active'>Job: {{job}}</li> - <li ng-if='job && update'><a href='/scheduler/{{role}}/{{environment}}/{{job}}'>Job: {{job}}</a></li> + <li ng-if='job && (update || instance >= 0)'><a href='/scheduler/{{role}}/{{environment}}/{{job}}'>Job: {{job}}</a></li> + <li ng-if='instance >= 0' class='active'>Instance: {{instance}}</li> <li ng-if='update' class='active'>Update: {{update.update.summary.key.id}}</li> </ul> </div> http://git-wip-us.apache.org/repos/asf/aurora/blob/9b8868fb/src/main/resources/scheduler/assets/css/app.css ---------------------------------------------------------------------- diff --git a/src/main/resources/scheduler/assets/css/app.css b/src/main/resources/scheduler/assets/css/app.css index a4735ef..9437c53 100644 --- a/src/main/resources/scheduler/assets/css/app.css +++ b/src/main/resources/scheduler/assets/css/app.css @@ -441,6 +441,7 @@ li.instance-rolled-back { -webkit-transform: rotate(360deg); } } + @keyframes spin { 0% { transform: rotate(0deg); @@ -454,7 +455,8 @@ li.instance-rolled-back { text-align: center; } -.loading span { +.loading span, +span.loading { -webkit-animation: spin 1.1s infinite linear; animation: spin 1.1s infinite linear; } \ No newline at end of file http://git-wip-us.apache.org/repos/asf/aurora/blob/9b8868fb/src/main/resources/scheduler/assets/instance.html ---------------------------------------------------------------------- diff --git a/src/main/resources/scheduler/assets/instance.html b/src/main/resources/scheduler/assets/instance.html new file mode 100644 index 0000000..317e2ce --- /dev/null +++ b/src/main/resources/scheduler/assets/instance.html @@ -0,0 +1,106 @@ +<div class='container-fluid'> + <!-- + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + --> + <div ng-show='error'> + <error></error> + </div> + + <div ng-hide='error'> + <breadcrumb></breadcrumb> + + <div class='row'> + <div class='col-md-12'> + <div class='page-header'> + <h2 class='text-center'> + Instance <em>{{instance}}</em> of job <em>{{job}}</em> in role <em>{{role}}</em> and + environment <em>{{environment}}</em> + </h2> + </div> + </div> + </div> + + <div ng-if="!tasksReady"> + <div class="row"> + <div class="col-md-12"> + Loading instance information. + <span class="glyphicon glyphicon-refresh loading" aria-hidden="true"></span> + </div> + </div> + </div> + + <div ng-if="tasksReady"> + <div ng-if="activeTasks.length === 0"> + <h3>No Active Tasks</h3> + </div> + <div ng-if="activeTasks.length > 0"> + <h3>Active Task</h3> + <div class="row"> + <div class="col-md-6"> + <h4>Task Details</h4> + <table class="table table-bordered table-striped"> + <tbody> + <tr> + <td><strong>Current Status</strong></td> + <td>{{activeTasks[0].status}}</td> + </tr> + <tr> + <td><strong>Task ID</strong></td> + <td><a ng-href="/structdump/task/{{activeTasks[0].taskId}}">{{activeTasks[0].taskId}}</a></td> + </tr> + <tr> + <td><strong>Host</strong></td> + <td><a ng-href="http://{{activeTasks[0].host}}:1338/task/{{activeTasks[0].taskId}}">{{activeTasks[0].host}}</a></td> + </tr> + </tbody> + </table> + </div> + + <div class="col-md-6"> + <h4>Status History</h4> + <table class="table table-bordered table-striped"> + <thead> + <tr> + <th>Status</th> + <th>Timestamp</th> + <th>Message</th> + </tr> + </thead> + <tbody> + <tr ng-repeat="e in activeTasks[0].taskEvents"> + <td>{{e.status}}</td> + <td>{{e.timestamp | toUtcTime}}</td> + <td>{{e.message}}</td> + </tr> + </tbody> + </table> + </div> + </div> + </div> + + <div ng-if="completedTasks.length === 0"> + <h3>No Completed Tasks</h3> + </div> + <div ng-if="completedTasks.length > 0"> + <h3>Completed Tasks</h3> + <div class='container-fluid'> + <smart-table config='completedTasksTableConfig' + columns='completedTasksTableColumns' + rows='completedTasks' + class='table table-striped table-hover table-bordered table-condensed'> + </smart-table> + </div> + </div> + </div> + </div> +</div> http://git-wip-us.apache.org/repos/asf/aurora/blob/9b8868fb/src/main/resources/scheduler/assets/job.html ---------------------------------------------------------------------- diff --git a/src/main/resources/scheduler/assets/job.html b/src/main/resources/scheduler/assets/job.html index 942635a..bfe51ab 100644 --- a/src/main/resources/scheduler/assets/job.html +++ b/src/main/resources/scheduler/assets/job.html @@ -39,7 +39,7 @@ <div ng-if="updateInProgress" class="content-box in-progress-alert"> <div class="row"> <div class="col-md-4"> - <a href="/scheduler/{{role}}/{{environment}}/{{job}}/{{updateInProgress.update.summary.key.id}}">Update In Progress</a> + <a href="/scheduler/{{role}}/{{environment}}/{{job}}/update/{{updateInProgress.update.summary.key.id}}">Update In Progress</a> </div> <div class="col-md-4"> <progressbar class="progress" max="updateStats.totalInstancesToBeUpdated" value="updateStats.instancesUpdatedSoFar" type="success"><i>{{updateStats.instancesUpdatedSoFar}} of {{updateStats.totalInstancesToBeUpdated}}</i></progressbar> @@ -146,7 +146,7 @@ </tr> <tr ng-repeat="update in updates"> <td> - <a href="/scheduler/{{role}}/{{environment}}/{{job}}/{{update.key.id}}"> + <a href="/scheduler/{{role}}/{{environment}}/{{job}}/update/{{update.key.id}}"> {{update.key.id}} </a> </td> http://git-wip-us.apache.org/repos/asf/aurora/blob/9b8868fb/src/main/resources/scheduler/assets/js/app.js ---------------------------------------------------------------------- diff --git a/src/main/resources/scheduler/assets/js/app.js b/src/main/resources/scheduler/assets/js/app.js index b66409f..310aa35 100644 --- a/src/main/resources/scheduler/assets/js/app.js +++ b/src/main/resources/scheduler/assets/js/app.js @@ -32,7 +32,10 @@ var auroraUI; $routeProvider.when('/scheduler/:role/:environment/:job', {templateUrl: '/assets/job.html', controller: 'JobController'}); - $routeProvider.when('/scheduler/:role/:environment/:job/:update', + $routeProvider.when('/scheduler/:role/:environment/:job/:instance', + {templateUrl: '/assets/instance.html', controller: 'JobInstanceController'}); + + $routeProvider.when('/scheduler/:role/:environment/:job/update/:update', {templateUrl: '/assets/update.html', controller: 'UpdateController'}); $routeProvider.when('/updates', http://git-wip-us.apache.org/repos/asf/aurora/blob/9b8868fb/src/main/resources/scheduler/assets/js/controllers.js ---------------------------------------------------------------------- diff --git a/src/main/resources/scheduler/assets/js/controllers.js b/src/main/resources/scheduler/assets/js/controllers.js index 04ea1cb..9892019 100644 --- a/src/main/resources/scheduler/assets/js/controllers.js +++ b/src/main/resources/scheduler/assets/js/controllers.js @@ -12,7 +12,7 @@ * limitations under the License. */ (function () { - /* global ScheduleStatus:false, JobUpdateKey:false, JobUpdateQuery:false, JobKey:false */ + /* global JobUpdateKey:false, JobUpdateQuery:false, JobKey:false */ 'use strict'; /* Controllers */ @@ -330,239 +330,159 @@ } ); - auroraUIControllers.controller('JobController', - function ($scope, $routeParams, $timeout, $q, auroraClient, taskUtil, updateUtil) { - $scope.error = ''; - - $scope.role = $routeParams.role; - $scope.environment = $routeParams.environment; - $scope.job = $routeParams.job; - - var taskTableConfig = { - isGlobalSearchActivated: false, - isPaginationEnabled: true, - itemsByPage: 50, - maxSize: 8, - selectionMode: 'single' - }; - - $scope.activeTasksTableConfig = taskTableConfig; - $scope.completedTasksTableConfig = taskTableConfig; - - var taskColumns = [ - {label: 'Instance', map: 'instanceId'}, - {label: 'Status', map: 'status', cellTemplateUrl: '/assets/taskStatus.html'}, - {label: 'Host', map: 'host', cellTemplateUrl: '/assets/taskSandbox.html'} - ]; - - var completedTaskColumns = addColumn(2, - taskColumns, - {label: 'Running duration', - map: 'duration', - formatFunction: function (duration) { - return moment.duration(duration).humanize(); - } - }); - - var taskIdColumn = { - label: 'Task ID', - map: 'taskId', - cellTemplateUrl: '/assets/taskLink.html' - }; - - $scope.activeTasksTableColumns = taskColumns; - - $scope.completedTasksTableColumns = completedTaskColumns; - - function addColumn(idxPosition, currentColumns, newColumn) { - return _.union( - _.first(currentColumns, idxPosition), - [newColumn], - _.last(currentColumns, currentColumns.length - idxPosition)); + function initializeJobController($scope, $routeParams) { + $scope.error = ''; + + $scope.role = $routeParams.role; + $scope.environment = $routeParams.environment; + $scope.job = $routeParams.job; + $scope.instance = $routeParams.instance; + + // These two task arrays need to be initialized due to a quirk in SmartTable's behavior. + $scope.activeTasks = []; + $scope.completedTasks = []; + $scope.tasksReady = false; + } + + function JobController( + $scope, + $routeParams, + $timeout, + $q, + auroraClient, + taskUtil, + updateUtil, + jobTasksService) { + + initializeJobController($scope, $routeParams); + + $scope.showTaskInfoLink = false; + $scope.jobDashboardUrl = ''; + + $scope.toggleTaskInfoLinkVisibility = function () { + $scope.showTaskInfoLink = !$scope.showTaskInfoLink; + + $scope.activeTasksTableColumns = $scope.showTaskInfoLink ? + jobTasksService.addColumn( + 'Status', + jobTasksService.taskColumns, + jobTasksService.taskIdColumn) : + jobTasksService.taskColumns; + + $scope.completedTasksTableColumns = $scope.showTaskInfoLink ? + jobTasksService.addColumn( + 'Running duration', + jobTasksService.completedTaskColumns, + jobTasksService.taskIdColumn) : + jobTasksService.completedTaskColumns; + }; + + jobTasksService.getTasksForJob($scope); + + function buildGroupSummary(response) { + if (response.error) { + $scope.error = 'Error fetching configuration summary: ' + response.error; + return []; } - $scope.showTaskInfoLink = false; - - $scope.toggleTaskInfoLinkVisibility = function () { - $scope.showTaskInfoLink = !$scope.showTaskInfoLink; - - $scope.activeTasksTableColumns = $scope.showTaskInfoLink ? - addColumn(2, taskColumns, taskIdColumn) : - taskColumns; - - $scope.completedTasksTableColumns = $scope.showTaskInfoLink ? - addColumn(3, completedTaskColumns, taskIdColumn) : - completedTaskColumns; - }; - - $scope.jobDashboardUrl = ''; - // These two task arrays need to be initialized due to a quirk in SmartTable's behavior. - $scope.activeTasks = []; - $scope.completedTasks = []; - $scope.tasksReady = false; - - function buildGroupSummary(response) { - - if (response.error) { - $scope.error = 'Error fetching configuration summary: ' + response.error; - return []; - } - - var colors = [ - 'steelblue', - 'darkseagreen', - 'sandybrown', - 'plum', - 'khaki' - ]; - - var total = _.reduce(response.groups, function (m, n) { - return m + n.instanceIds.length; - }, 0); + var colors = [ + 'steelblue', + 'darkseagreen', + 'sandybrown', + 'plum', + 'khaki' + ]; - $scope.groupSummary = response.groups.map(function (group, i) { - var count = group.instanceIds.length; - var percentage = (count / total) * 100; + var total = _.reduce(response.groups, function (m, n) { + return m + n.instanceIds.length; + }, 0); - var ranges = taskUtil.toRanges(group.instanceIds).map(function (r) { - return (r.start === r.end) ? r.start : r.start + '-' + r.end; - }); + $scope.groupSummary = response.groups.map(function (group, i) { + var count = group.instanceIds.length; + var percentage = (count / total) * 100; - return { - label: ranges.join(', '), - value: count, - percentage: percentage, - summary: { schedulingDetail: taskUtil.configToDetails(group.config)}, - color: colors[i % colors.length] - }; + var ranges = taskUtil.toRanges(group.instanceIds).map(function (r) { + return (r.start === r.end) ? r.start : r.start + '-' + r.end; }); - } - - auroraClient.getTasksWithoutConfigs($scope.role, $scope.environment, $scope.job) - .then(getTasksForJob); - auroraClient.getConfigSummary($scope.role, $scope.environment, $scope.job) - .then(buildGroupSummary); - - var query = new JobUpdateQuery(); - var jobKey = new JobKey(); - jobKey.role = $scope.role; - jobKey.environment = $scope.environment; - jobKey.name = $scope.job; - query.jobKey = jobKey; - - auroraClient.getJobUpdateSummaries(query).then(getUpdatesForJob); - - - function getUpdatesForJob(response) { - $scope.updates = response.summaries; + return { + label: ranges.join(', '), + value: count, + percentage: percentage, + summary: { schedulingDetail: taskUtil.configToDetails(group.config)}, + color: colors[i % colors.length] + }; + }); + } - function getUpdateInProgress() { - auroraClient.getJobUpdateDetails($scope.updates[0].key).then(function (response) { - $scope.updateInProgress = response.details; + auroraClient.getConfigSummary($scope.role, $scope.environment, $scope.job) + .then(buildGroupSummary); - $scope.updateStats = updateUtil.getUpdateStats($scope.updateInProgress); + var query = new JobUpdateQuery(); + query.jobKey = new JobKey({ + role: $scope.role, + environment: $scope.environment, + name: $scope.job + }); - if (updateUtil.isInProgress($scope.updateInProgress.update.summary.state.status)) { - // Poll for updates as long as this update is in progress. - $timeout(function () { - getUpdateInProgress(); - }, REFRESH_RATES.IN_PROGRESS_UPDATE_MS); - } - }); - } + auroraClient.getJobUpdateSummaries(query).then(getUpdatesForJob); - if ($scope.updates.length > 0 && updateUtil.isInProgress($scope.updates[0].state.status)) { - getUpdateInProgress(); - } - } + function getUpdatesForJob(response) { + $scope.updates = response.summaries; - function getTasksForJob(response) { - if (response.error) { - $scope.error = 'Error fetching tasks: ' + response.error; - return []; - } + function getUpdateInProgress() { + auroraClient.getJobUpdateDetails($scope.updates[0].key).then(function (response) { + $scope.updateInProgress = response.details; - $scope.jobDashboardUrl = getJobDashboardUrl(response.statsUrlPrefix); + $scope.updateStats = updateUtil.getUpdateStats($scope.updateInProgress); - var tasks = _.map(response.tasks, function (task) { - return summarizeTask(task); + if (updateUtil.isInProgress($scope.updateInProgress.update.summary.state.status)) { + // Poll for updates as long as this update is in progress. + $timeout(function () { + getUpdateInProgress(); + }, REFRESH_RATES.IN_PROGRESS_UPDATE_MS); + } }); - - var activeTaskPredicate = function (task) { - return task.isActive; - }; - - $scope.activeTasks = _.chain(tasks) - .filter(activeTaskPredicate) - .sortBy('instanceId') - .value(); - - $scope.completedTasks = _.chain(tasks) - .reject(activeTaskPredicate) - .sortBy(function (task) { - return -task.latestActivity; //sort in descending order - }) - .value(); - - $scope.tasksReady = true; } - function summarizeTask(task) { - var isActive = taskUtil.isActiveTask(task); - var sortedTaskEvents = _.sortBy(task.taskEvents, function (taskEvent) { - return taskEvent.timestamp; - }); - - var latestTaskEvent = _.last(sortedTaskEvents); - - return { - instanceId: task.assignedTask.instanceId, - status: _.invert(ScheduleStatus)[latestTaskEvent.status], - statusMessage: latestTaskEvent.message, - host: task.assignedTask.slaveHost || '', - latestActivity: _.isEmpty(sortedTaskEvents) ? 0 : latestTaskEvent.timestamp, - duration: getDuration(sortedTaskEvents), - isActive: isActive, - taskId: task.assignedTask.taskId, - taskEvents: summarizeTaskEvents(sortedTaskEvents), - showDetails: false, - // TODO(maxim): Revisit this approach when the UI fix in AURORA-715 is finalized. - sandboxExists: true - }; + if ($scope.updates.length > 0 && updateUtil.isInProgress($scope.updates[0].state.status)) { + getUpdateInProgress(); } + } - function getDuration(sortedTaskEvents) { - var runningTaskEvent = _.find(sortedTaskEvents, function (taskEvent) { - return taskEvent.status === ScheduleStatus.RUNNING; - }); - - if (runningTaskEvent) { - var nextEvent = sortedTaskEvents[_.indexOf(sortedTaskEvents, runningTaskEvent) + 1]; + } + + auroraUIControllers.controller('JobController', JobController); + + var guidPattern = new RegExp( + /^[A-Za-z0-9]{8}-[A-Za-z0-9]{4}-[A-Za-z0-9]{4}-[A-Za-z0-9]{4}-[A-Za-z0-9]{12}$/); + + function JobInstanceController($scope, $routeParams, $location, jobTasksService) { + if (guidPattern.test($routeParams.instance)) { + $location.path( + [ + 'scheduler', + $routeParams.role, + $routeParams.environment, + $routeParams.job, + 'update', + $routeParams.instance + ].join('/')); + return; + } - return nextEvent ? - nextEvent.timestamp - runningTaskEvent.timestamp : - moment().valueOf() - runningTaskEvent.timestamp; - } + initializeJobController($scope, $routeParams); + jobTasksService.getTasksForJob($scope); - return 0; - } + $scope.completedTasksTableColumns = $scope.completedTasksTableColumns.filter(function (column) { + return column.label !== 'Instance'; + }); - function summarizeTaskEvents(taskEvents) { - return _.map(taskEvents, function (taskEvent) { - return { - timestamp: taskEvent.timestamp, - status: _.invert(ScheduleStatus)[taskEvent.status], - message: taskEvent.message - }; - }); - } + $scope.completedTasksTableColumns = jobTasksService.addColumn( + 'Status', + $scope.completedTasksTableColumns, + jobTasksService.taskIdColumn); + } - function getJobDashboardUrl(statsUrlPrefix) { - return _.isEmpty(statsUrlPrefix) ? - '' : - statsUrlPrefix + $scope.role + '.' + $scope.environment + '.' + $scope.job; - } - } - ); + auroraUIControllers.controller('JobInstanceController', JobInstanceController); })(); http://git-wip-us.apache.org/repos/asf/aurora/blob/9b8868fb/src/main/resources/scheduler/assets/js/services.js ---------------------------------------------------------------------- diff --git a/src/main/resources/scheduler/assets/js/services.js b/src/main/resources/scheduler/assets/js/services.js index a514fa7..085a3d9 100644 --- a/src/main/resources/scheduler/assets/js/services.js +++ b/src/main/resources/scheduler/assets/js/services.js @@ -28,13 +28,18 @@ */ 'use strict'; - function makeJobTaskQuery(role, environment, jobName) { + function makeJobTaskQuery(role, environment, jobName, instance) { var id = new Identity(); id.role = role; var taskQuery = new TaskQuery(); taskQuery.owner = id; taskQuery.environment = environment; taskQuery.jobName = jobName; + + if (instance) { + taskQuery.instanceIds = [ instance ]; + } + return taskQuery; } @@ -99,8 +104,8 @@ }); }, - getTasksWithoutConfigs: function (role, environment, jobName) { - var query = makeJobTaskQuery(role, environment, jobName); + getTasksWithoutConfigs: function (role, environment, jobName, instance) { + var query = makeJobTaskQuery(role, environment, jobName, instance); return async(function (deferred) { var tasksPromise = async(function (d1) { @@ -608,4 +613,176 @@ }; return cronJobSmrySvc; }]); + + auroraUI.factory( + 'jobTasksService', + ['auroraClient', 'taskUtil', function jobTasksServiceFactory(auroraClient, taskUtil) { + var baseTableConfig = { + isGlobalSearchActivated: false, + isPaginationEnabled: true, + itemsByPage: 50, + maxSize: 8, + selectionMode: 'single' + }; + + function addColumn(afterLabel, currentColumns, newColumn) { + var idxPosition = -1; + currentColumns.some(function (column, index) { + if (column.label === afterLabel) { + idxPosition = index + 1; + return true; + } + + return false; + }); + + if (idxPosition === -1) { + return; + } + + return _.union( + _.first(currentColumns, idxPosition), + [newColumn], + _.last(currentColumns, currentColumns.length - idxPosition)); + } + + var baseColumns = [ + {label: 'Instance', map: 'instanceId', cellTemplateUrl: '/assets/taskInstance.html'}, + {label: 'Status', map: 'status', cellTemplateUrl: '/assets/taskStatus.html'}, + {label: 'Host', map: 'host', cellTemplateUrl: '/assets/taskSandbox.html'} + ]; + + var completedTaskColumns = addColumn( + 'Status', + baseColumns, + { + label: 'Running duration', + map: 'duration', + formatFunction: function (duration) { + return moment.duration(duration).humanize(); + } + }); + + function summarizeTask(task) { + var isActive = taskUtil.isActiveTask(task); + var sortedTaskEvents = _.sortBy(task.taskEvents, function (taskEvent) { + return taskEvent.timestamp; + }); + + var latestTaskEvent = _.last(sortedTaskEvents); + + return { + instanceId: task.assignedTask.instanceId, + jobKey: task.assignedTask.task.job, + status: _.invert(ScheduleStatus)[latestTaskEvent.status], + statusMessage: latestTaskEvent.message, + host: task.assignedTask.slaveHost || '', + latestActivity: _.isEmpty(sortedTaskEvents) ? 0 : latestTaskEvent.timestamp, + duration: getDuration(sortedTaskEvents), + isActive: isActive, + taskId: task.assignedTask.taskId, + taskEvents: summarizeTaskEvents(sortedTaskEvents), + showDetails: false, + // TODO(maxim): Revisit this approach when the UI fix in AURORA-715 is finalized. + sandboxExists: true + }; + } + + function getDuration(sortedTaskEvents) { + var runningTaskEvent = _.find(sortedTaskEvents, function (taskEvent) { + return taskEvent.status === ScheduleStatus.RUNNING; + }); + + if (runningTaskEvent) { + var nextEvent = sortedTaskEvents[_.indexOf(sortedTaskEvents, runningTaskEvent) + 1]; + + return nextEvent ? + nextEvent.timestamp - runningTaskEvent.timestamp : + moment().valueOf() - runningTaskEvent.timestamp; + } + + return 0; + } + + function summarizeTaskEvents(taskEvents) { + return _.map(taskEvents, function (taskEvent) { + return { + timestamp: taskEvent.timestamp, + status: _.invert(ScheduleStatus)[taskEvent.status], + message: taskEvent.message + }; + }); + } + + function getJobDashboardUrl(statsUrlPrefix, role, environment, job) { + return _.isEmpty(statsUrlPrefix) ? + '' : + statsUrlPrefix + role + '.' + environment + '.' + job; + } + + return { + taskIdColumn: { + label: 'Task ID', + map: 'taskId', + cellTemplateUrl: '/assets/taskLink.html' + }, + + taskColumns: baseColumns, + completedTaskColumns: completedTaskColumns, + addColumn: addColumn, + + getTasksForJob: function getTasksForJob($scope) { + $scope.activeTasksTableColumns = baseColumns; + $scope.completedTasksTableColumns = completedTaskColumns; + + $scope.activeTasksTableConfig = baseTableConfig; + $scope.completedTasksTableConfig = baseTableConfig; + + auroraClient.getTasksWithoutConfigs( + $scope.role, + $scope.environment, + $scope.job, + $scope.instance) + .then(function (response) { + if (response.error) { + $scope.error = 'Error fetching tasks: ' + response.error; + return []; + } + + $scope.jobDashboardUrl = getJobDashboardUrl( + response.statsUrlPrefix, + $scope.role, + $scope.environment, + $scope.job); + + var tasks = _.map(response.tasks, function (task) { + return summarizeTask(task); + }); + + var activeTaskPredicate = function (task) { + return task.isActive; + }; + + $scope.activeTasks = _.chain(tasks) + .filter(activeTaskPredicate) + .sortBy('instanceId') + .value(); + + $scope.completedTasks = _.chain(tasks) + .reject(activeTaskPredicate) + .sortBy(function (task) { + return -task.latestActivity; //sort in descending order + }) + .value(); + + $scope.activeTasksTableConfig.isPaginationEnabled = + $scope.activeTasks.length > $scope.activeTasksTableConfig.itemsByPage; + $scope.completedTasksTableConfig.isPaginationEnabled = + $scope.completedTasks.length > $scope.completedTasksTableConfig.itemsByPage; + + $scope.tasksReady = true; + }); + } + }; + }]); })(); http://git-wip-us.apache.org/repos/asf/aurora/blob/9b8868fb/src/main/resources/scheduler/assets/taskInstance.html ---------------------------------------------------------------------- diff --git a/src/main/resources/scheduler/assets/taskInstance.html b/src/main/resources/scheduler/assets/taskInstance.html new file mode 100644 index 0000000..0ab63ee --- /dev/null +++ b/src/main/resources/scheduler/assets/taskInstance.html @@ -0,0 +1,14 @@ +<!-- + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + --> +<a ng-href="/scheduler/{{dataRow.jobKey.role}}/{{dataRow.jobKey.environment}}/{{dataRow.jobKey.name}}/{{dataRow.instanceId}}">{{dataRow.instanceId}}</a> \ No newline at end of file http://git-wip-us.apache.org/repos/asf/aurora/blob/9b8868fb/src/test/python/apache/aurora/client/cli/test_supdate.py ---------------------------------------------------------------------- diff --git a/src/test/python/apache/aurora/client/cli/test_supdate.py b/src/test/python/apache/aurora/client/cli/test_supdate.py index 21bea70..2135ca9 100644 --- a/src/test/python/apache/aurora/client/cli/test_supdate.py +++ b/src/test/python/apache/aurora/client/cli/test_supdate.py @@ -158,7 +158,8 @@ class TestStartUpdate(AuroraClientCommandTest): call(ANY, 'hello', None) ] assert self._fake_context.get_out() == [ - StartUpdate.UPDATE_MSG_TEMPLATE % ('http://something_or_other/scheduler/role/env/name/id') + StartUpdate.UPDATE_MSG_TEMPLATE % + ('http://something_or_other/scheduler/role/env/name/update/id') ] assert self._fake_context.get_err() == [] @@ -183,7 +184,8 @@ class TestStartUpdate(AuroraClientCommandTest): ] assert self._fake_context.get_out() == [ - StartUpdate.UPDATE_MSG_TEMPLATE % ('http://something_or_other/scheduler/role/env/name/id'), + StartUpdate.UPDATE_MSG_TEMPLATE % + ('http://something_or_other/scheduler/role/env/name/update/id'), 'Current state ROLLED_FORWARD' ] assert self._fake_context.get_err() == [] http://git-wip-us.apache.org/repos/asf/aurora/blob/9b8868fb/src/test/python/apache/aurora/client/test_base.py ---------------------------------------------------------------------- diff --git a/src/test/python/apache/aurora/client/test_base.py b/src/test/python/apache/aurora/client/test_base.py index 1a56008..fa5eb07 100644 --- a/src/test/python/apache/aurora/client/test_base.py +++ b/src/test/python/apache/aurora/client/test_base.py @@ -56,3 +56,25 @@ class TestBase(unittest.TestCase): resp = Response(responseCode=ResponseCode.OK, result=Result(populateJobResult=PopulateJobResult( taskConfig=config))) assert config == resp.result.populateJobResult.taskConfig + + def test_synthesize_url(self): + base_url = 'http://example.com' + role = 'some-role' + environment = 'some-environment' + job = 'some-job' + update_id = 'some-update-id' + + assert (('%s/scheduler/%s/%s/%s/update/%s' % (base_url, role, environment, job, update_id)) == + base.synthesize_url(base_url, role, environment, job, update_id=update_id)) + + assert (('%s/scheduler/%s/%s/%s' % (base_url, role, environment, job)) == + base.synthesize_url(base_url, role, environment, job)) + + assert (('%s/scheduler/%s/%s' % (base_url, role, environment)) == + base.synthesize_url(base_url, role, environment)) + + assert (('%s/scheduler/%s' % (base_url, role)) == + base.synthesize_url(base_url, role)) + + assert (('%s/scheduler/%s' % (base_url, role)) == + base.synthesize_url(base_url, role))
