Repository: zeppelin Updated Branches: refs/heads/master c90a3c2e5 -> 3183a5967
[ZEPPELIN-2501] Better Job Navigation Experience ### What is this PR for? Improve UX for Job Navigation. Please refer the *TODOs* section and screenshots attached below for detail. FYI, **pagination** is added to improve page loading speed. Currently, it is going to be slow when there are 50+ jobs. ### What type of PR is it? [Improvement | Feature] ### Todos * [x] - improve UI * [x] - add pagination to improve page loading speed * [x] - interactive job searching without *enter key* * [x] - add date sorter * [x] - display total job count ### What is the Jira issue? [ZEPPELIN-2501](https://issues.apache.org/jira/browse/ZEPPELIN-2501) ### How should this be tested? 1. build: `mvn clean package -DskipTests; ./bin/zeppelin-daemon.sh restart` 2. open the job page: `http://localhost:8080/#/jobmanager` 3. try to search, filter, sort. ### Screenshots (if appropriate) #### Before  #### After: Larger UI components  #### After: Interactive search without enter-key  #### After Newly added timestamp sorter  #### After: Newly added pagination  #### Total Job Count  ### Questions: * Does the licenses files need update? - NO * Is there breaking changes for older versions? - NO * Does this needs documentation? - NO Author: 1ambda <[email protected]> Closes #2391 from 1ambda/ZEPPELIN-2501/pagination-for-job-page and squashes the following commits: 53769661e [1ambda] feat: Add total job count c5ff1f5a2 [1ambda] fix: Add license notation to job-status 3dd508155 [1ambda] feat: Add date sorter e20d1dbe1 [1ambda] fix: Apply interactive search 081b9bb4e [1ambda] feat: re-style search tools d52008e1f [1ambda] fix: Remove all styles in search tools d7017f41c [1ambda] fix: Better layout for jobmanager header 084407a1e [1ambda] feat: Add pagination for JOB page 018048af1 [1ambda] fix: Simplify job names f8fab3f10 [1ambda] refactor: Remove duplicated switch DOMs 0ae31a01b [1ambda] style: Reindent jobmanager.html Project: http://git-wip-us.apache.org/repos/asf/zeppelin/repo Commit: http://git-wip-us.apache.org/repos/asf/zeppelin/commit/3183a596 Tree: http://git-wip-us.apache.org/repos/asf/zeppelin/tree/3183a596 Diff: http://git-wip-us.apache.org/repos/asf/zeppelin/diff/3183a596 Branch: refs/heads/master Commit: 3183a596750ee81440ce785d73d9b9d5b0b8873a Parents: c90a3c2 Author: 1ambda <[email protected]> Authored: Sat Jun 3 08:04:14 2017 +0900 Committer: Khalid Huseynov <[email protected]> Committed: Thu Jun 22 10:56:30 2017 +0900 ---------------------------------------------------------------------- zeppelin-web/src/app/app.js | 2 +- .../src/app/jobmanager/jobmanager.controller.js | 128 +++++++----- zeppelin-web/src/app/jobmanager/jobmanager.css | 88 +++++++- .../src/app/jobmanager/jobmanager.filter.js | 20 +- zeppelin-web/src/app/jobmanager/jobmanager.html | 199 +++++++++---------- .../src/app/jobmanager/jobs/job-status.js | 22 ++ zeppelin-web/src/app/jobmanager/jobs/job.html | 83 +------- 7 files changed, 293 insertions(+), 249 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/zeppelin/blob/3183a596/zeppelin-web/src/app/app.js ---------------------------------------------------------------------- diff --git a/zeppelin-web/src/app/app.js b/zeppelin-web/src/app/app.js index c6674f1..dad04f0 100644 --- a/zeppelin-web/src/app/app.js +++ b/zeppelin-web/src/app/app.js @@ -105,7 +105,7 @@ let zeppelinWebApp = angular.module('zeppelinWebApp', requiredModules) }) .when('/jobmanager', { templateUrl: 'app/jobmanager/jobmanager.html', - controller: 'JobmanagerCtrl' + controller: 'JobManagerCtrl' }) .when('/interpreter', { templateUrl: 'app/interpreter/interpreter.html', http://git-wip-us.apache.org/repos/asf/zeppelin/blob/3183a596/zeppelin-web/src/app/jobmanager/jobmanager.controller.js ---------------------------------------------------------------------- diff --git a/zeppelin-web/src/app/jobmanager/jobmanager.controller.js b/zeppelin-web/src/app/jobmanager/jobmanager.controller.js index ec6f1ab..16d47ba 100644 --- a/zeppelin-web/src/app/jobmanager/jobmanager.controller.js +++ b/zeppelin-web/src/app/jobmanager/jobmanager.controller.js @@ -12,12 +12,40 @@ * limitations under the License. */ +import { JobStatus, } from './jobs/job-status' + angular.module('zeppelinWebApp') - .controller('JobmanagerCtrl', JobmanagerCtrl) + .controller('JobManagerCtrl', JobManagerCtrl) + +const JobDateSorter = { + RECENTLY_UPDATED: 'Recently Update', + OLDEST_UPDATED: 'Oldest Updated', +} -function JobmanagerCtrl ($scope, websocketMsgSrv, $interval, ngToast, $q, $timeout, jobManagerFilter) { +function JobManagerCtrl ($scope, websocketMsgSrv, $interval, ngToast, $q, $timeout, jobManagerFilter) { 'ngInject' + $scope.pagination = { + currentPage: 1, + itemsPerPage: 10, + maxPageCount: 5, + } + + $scope.sorter = { + AvailableDateSorter: Object.keys(JobDateSorter).map(key => { return JobDateSorter[key] }), + currentDateSorter: JobDateSorter.RECENTLY_UPDATED, + } + + $scope.setJobDateSorter = function(dateSorter) { + $scope.sorter.currentDateSorter = dateSorter + } + + $scope.getJobsInCurrentPage = function(jobs) { + const cp = $scope.pagination.currentPage + const itp = $scope.pagination.itemsPerPage + return jobs.slice((cp - 1) * itp, (cp * itp)) + } + ngToast.dismiss() let asyncNotebookJobFilter = function (jobInfomations, filterConfig) { return $q(function (resolve, reject) { @@ -26,15 +54,52 @@ function JobmanagerCtrl ($scope, websocketMsgSrv, $interval, ngToast, $q, $timeo }) } + $scope.$watch('sorter.currentDateSorter', function() { + $scope.filterConfig.isSortByAsc = + $scope.sorter.currentDateSorter === JobDateSorter.OLDEST_UPDATED + asyncNotebookJobFilter($scope.jobInfomations, $scope.filterConfig) + }) + + $scope.getJobIconByStatus = function(jobStatus) { + if (jobStatus === JobStatus.READY) { + return 'fa fa-circle-o' + } else if (jobStatus === JobStatus.FINISHED) { + return 'fa fa-circle' + } else if (jobStatus === JobStatus.ABORT) { + return 'fa fa-circle' + } else if (jobStatus === JobStatus.ERROR) { + return 'fa fa-circle' + } else if (jobStatus === JobStatus.PENDING) { + return 'fa fa-circle' + } else if (jobStatus === JobStatus.RUNNING) { + return 'fa fa-spinner' + } + } + + $scope.getJobColorByStatus = function(jobStatus) { + if (jobStatus === JobStatus.READY) { + return 'green' + } else if (jobStatus === JobStatus.FINISHED) { + return 'green' + } else if (jobStatus === JobStatus.ABORT) { + return 'orange' + } else if (jobStatus === JobStatus.ERROR) { + return 'red' + } else if (jobStatus === JobStatus.PENDING) { + return 'gray' + } else if (jobStatus === JobStatus.RUNNING) { + return 'blue' + } + } + $scope.doFiltering = function (jobInfomations, filterConfig) { - asyncNotebookJobFilter(jobInfomations, filterConfig).then( - function () { - // success - $scope.isLoadingFilter = false - }, - function () { - // failed - }) + asyncNotebookJobFilter(jobInfomations, filterConfig) + .then( + () => { $scope.isLoadingFilter = false }, + (error) => { + console.error('Failed to search jobs from server', error) + } + ) } $scope.filterValueToName = function (filterValue, maxStringLength) { @@ -48,7 +113,7 @@ function JobmanagerCtrl ($scope, websocketMsgSrv, $interval, ngToast, $q, $timeo } return $scope.activeInterpreters[index].name } else { - return 'Interpreter is not set' + return 'NONE' } } @@ -57,37 +122,6 @@ function JobmanagerCtrl ($scope, websocketMsgSrv, $interval, ngToast, $q, $timeo $scope.doFiltering($scope.jobInfomations, $scope.filterConfig) } - $scope.onChangeRunJobToAlwaysTopToggle = function () { - $scope.filterConfig.isRunningAlwaysTop = !$scope.filterConfig.isRunningAlwaysTop - $scope.doFiltering($scope.jobInfomations, $scope.filterConfig) - } - - $scope.onChangeSortAsc = function () { - $scope.filterConfig.isSortByAsc = !$scope.filterConfig.isSortByAsc - $scope.doFiltering($scope.jobInfomations, $scope.filterConfig) - } - - $scope.doFilterInputTyping = function (keyEvent, jobInfomations, filterConfig) { - let RETURN_KEY_CODE = 13 - $timeout.cancel($scope.dofilterTimeoutObject) - $scope.isActiveSearchTimer = true - $scope.dofilterTimeoutObject = $timeout(function () { - $scope.doFiltering(jobInfomations, filterConfig) - $scope.isActiveSearchTimer = false - }, 10000) - if (keyEvent.which === RETURN_KEY_CODE) { - $timeout.cancel($scope.dofilterTimeoutObject) - $scope.doFiltering(jobInfomations, filterConfig) - $scope.isActiveSearchTimer = false - } - } - - $scope.doForceFilterInputTyping = function (keyEvent, jobInfomations, filterConfig) { - $timeout.cancel($scope.dofilterTimeoutObject) - $scope.doFiltering(jobInfomations, filterConfig) - $scope.isActiveSearchTimer = false - } - $scope.init = function () { $scope.isLoadingFilter = true $scope.jobInfomations = [] @@ -96,21 +130,13 @@ function JobmanagerCtrl ($scope, websocketMsgSrv, $interval, ngToast, $q, $timeo isRunningAlwaysTop: true, filterValueNotebookName: '', filterValueInterpreter: '*', - isSortByAsc: true + isSortByAsc: $scope.sorter.currentDateSorter === JobDateSorter.OLDEST_UPDATED, } $scope.sortTooltipMsg = 'Switch to sort by desc' $scope.jobTypeFilter = jobManagerFilter websocketMsgSrv.getNoteJobsList() - $scope.$watch('filterConfig.isSortByAsc', function (value) { - if (value) { - $scope.sortTooltipMsg = 'Switch to sort by desc' - } else { - $scope.sortTooltipMsg = 'Switch to sort by asc' - } - }) - $scope.$on('$destroy', function () { websocketMsgSrv.unsubscribeJobManager() }) http://git-wip-us.apache.org/repos/asf/zeppelin/blob/3183a596/zeppelin-web/src/app/jobmanager/jobmanager.css ---------------------------------------------------------------------- diff --git a/zeppelin-web/src/app/jobmanager/jobmanager.css b/zeppelin-web/src/app/jobmanager/jobmanager.css index 5f7ffb7..7b5dc04 100644 --- a/zeppelin-web/src/app/jobmanager/jobmanager.css +++ b/zeppelin-web/src/app/jobmanager/jobmanager.css @@ -22,7 +22,7 @@ min-height: 32px; } -.jobManagerHead { +.job-manager-header { margin: -10px -10px 20px; padding: 10px 15px 15px 15px; background: white; @@ -30,18 +30,88 @@ border-bottom: 1px solid #E5E5E5; } -.jobManagerHead .header { +.job-manager-header .header { font-family: 'Roboto', sans-serif; } -.job-note-name-query { - padding: 6px; - height: 25px; - width: 200px; +.job-search-tool { + display: inline-block; + margin-left: 15px; } -.job-note-name-font-family { - font: inherit; +#job-manager-header .job-search-tool .search-input { + margin-right: 7px; + min-width: 215px; +} + +#job-manager-header .job-search-tool .search-input > input { + font-family: 'FontAwesome', 'Helvetica Neue', Helvetica, Arial, sans-serif; + border-radius: 4px; font-size: 14px; - font-weight: normal; + padding-left: 10px; +} + +#job-manager-header .job-search-tool .dropdown-toggle { + border-radius: 3px; + float: none; + min-width: 150px; + text-align: left; + margin-right: 5px; +} + + +#job-manager-header .job-search-tool .date-sort-button { + min-width: 180px; +} + +.job-search-tool .dropdown-text-desc { + color: gray; + font-weight: 400; + font-family: "Roboto", "Helvetica Neue", Helvetica, Arial, sans-serif; +} + +.job-search-tool .dropdown-text-value { + margin-left: 2px; + font-family: "Roboto", "Helvetica Neue", Helvetica, Arial, sans-serif; +} + +.search-tool-dropdown-content > li > a { + border-bottom: 1px solid #eee; +} + +.search-tool-dropdown-content > li:last-child > a { + border-bottom: none; +} + +.job-icon-desc-container { + display: inline-block; + margin-top: 10px; + margin-right: 30px; + float: right; +} + +.job-desc-icon { + margin-right: 3px; +} + +.job-pagination-container { + text-align: center; + margin-top: 50px; + padding-bottom: 100px; +} + +.job-counter { + float: right; + clear: both; + margin-left: 8px; + margin-top: 6px; +} + +.job-counter .job-counter-label { + color: gray; +} + +.job-counter .job-counter-value { + font-size: 15px; + font-weight: bold; } http://git-wip-us.apache.org/repos/asf/zeppelin/blob/3183a596/zeppelin-web/src/app/jobmanager/jobmanager.filter.js ---------------------------------------------------------------------- diff --git a/zeppelin-web/src/app/jobmanager/jobmanager.filter.js b/zeppelin-web/src/app/jobmanager/jobmanager.filter.js index 9211498..2b724a4 100644 --- a/zeppelin-web/src/app/jobmanager/jobmanager.filter.js +++ b/zeppelin-web/src/app/jobmanager/jobmanager.filter.js @@ -22,26 +22,34 @@ function jobManagerFilter () { let filterItems = jobItems if (filterValueInterpreter === undefined) { - filterItems = _.filter(filterItems, function (jobItem) { - return jobItem.interpreter === undefined ? true : false + filterItems = filterItems.filter((jobItem) => { + return jobItem.interpreter === undefined }) } else if (filterValueInterpreter !== '*') { filterItems = _.where(filterItems, {interpreter: filterValueInterpreter}) } if (filterValueNotebookName !== '') { - filterItems = _.filter(filterItems, function (jobItem) { + filterItems = filterItems.filter((jobItem) => { let lowerFilterValue = filterValueNotebookName.toLocaleLowerCase() let lowerNotebookName = jobItem.noteName.toLocaleLowerCase() return lowerNotebookName.match(new RegExp('.*' + lowerFilterValue + '.*')) }) } - filterItems = _.sortBy(filterItems, function (sortItem) { - return sortItem.noteName.toLowerCase() + filterItems = filterItems.sort((jobItem) => { + return jobItem.noteName.toLowerCase() }) - return isSortByAsc ? filterItems : filterItems.reverse() + filterItems = filterItems.sort((x, y) => { + if (isSortByAsc) { + return x.unixTimeLastRun - y.unixTimeLastRun + } else { + return y.unixTimeLastRun - x.unixTimeLastRun + } + }) + + return filterItems } return filterContext } http://git-wip-us.apache.org/repos/asf/zeppelin/blob/3183a596/zeppelin-web/src/app/jobmanager/jobmanager.html ---------------------------------------------------------------------- diff --git a/zeppelin-web/src/app/jobmanager/jobmanager.html b/zeppelin-web/src/app/jobmanager/jobmanager.html index 49ab2ee..e5c030a 100644 --- a/zeppelin-web/src/app/jobmanager/jobmanager.html +++ b/zeppelin-web/src/app/jobmanager/jobmanager.html @@ -11,8 +11,8 @@ 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. --> -<!-- Here the controller <JobmanagerCtrl> is not needed because explicitly set in the app.js (route) --> -<div class="jobManagerHead" data-ng-init="init()"> +<!-- Here the controller <JobManagerCtrl> is not needed because explicitly set in the app.js (route) --> +<div id="job-manager-header" class="job-manager-header" data-ng-init="init()"> <div class="header"> <div class="row"> <div class="col-md-12"> @@ -27,138 +27,117 @@ limitations under the License. </div> </div> </div> - <div style="margin: 0px"> - <hr style="margin-top: 10px; margin-bottom: 10px;" /> - </div> + + <hr style="margin-top: 15px; margin-bottom: 15px;" /> <div class="row"> - <div class="col-md-6 text-left"> + <!-- search tools (input, drop-down, sorting) --> + <div class="job-search-tool"> <div class="form-inline"> - <span class="labelBtn btn-group"> - <button type="button" - class="btn btn-default" - style="width: 25px; height: 25px; margin-right: 0px; padding: 1px 0px 3px 3px" - ng-click="onChangeSortAsc()" - tooltip-placement="right" uib-tooltip="{{sortTooltipMsg}}"> - <i class="fa" ng-class="{true: 'fa-sort-amount-asc', false : 'fa-sort-amount-desc'}[filterConfig.isSortByAsc]"></i> - </button> - </span> - <span class="labelBtn btn-group" style="margin-left: 0px; padding-left: 3px;"> - <button type="button" class="btn btn-default btn-xs dropdown-toggle" - data-toggle="dropdown" - style="min-width: 100px; text-align: right !important;"> - <span class="text-right job-note-name-font-family"> - {{filterValueToName(filterConfig.filterValueInterpreter)}}<span class="caret" style="margin-left: 10px"></span> - </span> - </button> - <ul class="dropdown-menu pull-left" role="menu"> + <span class="btn-group"> + <!-- search tool: input --> + <div class="input-group search-input"> + <input class="form-control btn-xs" + placeholder=" Search jobs..." + type="text" + ng-model="filterConfig.filterValueNotebookName" + ng-model-options="{ updateOn: 'default blur', debounce: { 'default': 300, 'blur': 0 } }" + ng-change="doFiltering(jobInfomations, filterConfig)" /> + </div> + + <!-- search tool: default interpreter dropdown --> + <div class="btn btn-default dropdown-toggle" + data-toggle="dropdown"> + <span> + <span class="dropdown-text-desc">Interpreter: </span> + <span class="dropdown-text-value">{{filterValueToName(filterConfig.filterValueInterpreter)}}</span> + <span class="caret" style="margin-top: 8px; float: right;"></span> + <span style="clear: both;"></span> + </span> + </div> + <ul class="dropdown-menu dropdown-menu-right search-tool-dropdown-content" role="menu"> <li ng-repeat="interpreterOption in activeInterpreters"> - <a class="job-note-name-font-family" style="cursor:pointer" - ng-click="setFilterValue(interpreterOption.value)"> + <a ng-click="setFilterValue(interpreterOption.value)" + ng-style="(filterValueToName(interpreterOption.value) === 'ALL' || filterValueToName(interpreterOption.value) === 'NONE') ? { 'font-weight': 500 } : {}" + class="dropdown-list-value"> {{filterValueToName(interpreterOption.value)}} </a> </li> </ul> - <div class="input-group"> - <input - class="job-note-name-query job-note-name-font-family form-control btn-xs" - style="margin-left: 5px;" - placeholder="Search for Job" - type="text" ng-model="filterConfig.filterValueNotebookName" - ng-keyup="doFilterInputTyping($event, jobInfomations, filterConfig, isLoadingFilter)"/> - <span - class="input-group-addon text-right" ng-class="{true : 'btn-primary active', false: ''}[isActiveSearchTimer]" - style="height: 5px; padding: 0px 6px 0px 9px !important;" - ng-click="doForceFilterInputTyping($event, jobInfomations, filterConfig, isLoadingFilter)"> - <i class="fa fa-search fa-sm"></i></span> - </div> - </span> - </div> - </div> - <div - class="col-md-6 text-right" - style="padding-top: 6px;"> - <span - ng-repeat="jobStatus in ['READY', 'FINISHED', 'ABORT', 'ERROR','PENDING','RUNNING']" - ng-switch="jobStatus"> - <span - ng-switch-when="FINISHED"> - <i style="color: green; margin-right: 3px;" class="fa fa-circle" - ng-click=""> - </i> - {{jobStatus}} - </span> - <span - ng-switch-when="RUNNING" - style="margin-right: 3px;"> - <i style="color: blue" class="fa fa-spinner" - ng-click=""> - </i> - {{jobStatus}} - </span> - <span - ng-switch-when="READY" - style="margin-right: 3px;"> - <i style="color: green" class="fa fa-circle-o" - ng-click=""> - </i> - {{jobStatus}} + </span> - <span - ng-switch-when="PENDING" - style="margin-right: 3px;"> - <i style="color: gray" class="fa fa-circle" - ng-click=""> - </i> - {{jobStatus}} + + <span class="btn-group"> + <!-- search tool: date dropdown --> + <div class="date-sort-button btn btn-default dropdown-toggle" data-toggle="dropdown"> + <span> + <span class="dropdown-text-desc">Sort: </span> + <span class="dropdown-text-value">{{sorter.currentDateSorter}}</span> + <span class="caret" style="margin-top: 8px; float: right;"></span> + <span style="clear: both;"></span> + </span> + </div> + <ul class="dropdown-menu dropdown-menu-right search-tool-dropdown-content" role="menu"> + <li ng-repeat="dateSorter in sorter.AvailableDateSorter"> + <a ng-click="setJobDateSorter(dateSorter)" class="dropdown-list-value"> + {{dateSorter}} + </a> + </li> + </ul> </span> - <span - ng-switch-when="ABORT" - style="margin-right: 3px;"> - <i style="color: orange" class="fa fa-circle" - ng-click=""> - </i> - {{jobStatus}} + <span class="job-counter"> + <span class="job-counter-label">Total: </span> + <span class="job-counter-value">{{JobInfomationsByFilter.length}}</span> </span> - <span - ng-switch-when="ERROR" - style="margin-right: 3px;"> - <i style="color: red" class="fa fa-circle" - ng-click=""> - </i> - {{jobStatus}} + </div> + </div> + + <!-- job icon descriptions --> + <div class="job-icon-desc-container hidden-xs hidden-sm hidden-md"> + <span ng-repeat="jobStatus in ['READY', 'FINISHED', 'ABORT', 'ERROR','PENDING','RUNNING']"> + <span style="margin-right: 2px;"> + <i class="job-desc-icon" + ng-style="{'color': getJobColorByStatus(jobStatus)}" + ng-class="getJobIconByStatus(jobStatus)" ></i>{{jobStatus}} </span> </span> </div> + + <div style="clear: both;"></div> + </div> </div> + <div> <div class="note-jump"></div> - <div - ng-if="isLoadingFilter === true" - class="paragraph-col"> - <div - class="job-space box job-margin text-center"> - <i style="color: blue" class="fa fa-spinner spinAnimation"> - </i> Loading... + <div ng-if="isLoadingFilter === true" class="paragraph-col"> + <div class="job-space box job-margin text-center"> + <i style="color: blue" class="fa fa-spinner spinAnimation"></i>Loading... </div> </div> - <div - ng-if="JobInfomationsByFilter.length > 0" - ng-repeat="notebookJob in JobInfomationsByFilter track by $index" - class="paragraph-col"> + <div ng-if="JobInfomationsByFilter.length > 0" + ng-repeat="notebookJob in getJobsInCurrentPage(JobInfomationsByFilter)" + class="paragraph-col"> <div ng-include src="'app/jobmanager/jobs/job.html'" class="job-space box job-margin" ng-controller="JobCtrl"> </div> </div> - <div - ng-if="isLoadingFilter === false && JobInfomationsByFilter.length <= 0" - class="paragraph-col"> - <div - class="job-space box job-margin text-center"> - No Job found - </div> + <div ng-if="isLoadingFilter === false && JobInfomationsByFilter.length <= 0" + class="paragraph-col"> + <div class="job-space box job-margin text-center">No Job found</div> </div> - <div style="clear:both;height:10px"></div> + + <!-- pagination --> + <div class="job-pagination-container"> + <ul uib-pagination class="pagination-sm" + total-items="JobInfomationsByFilter.length" + ng-model="pagination.currentPage" + items-per-page="pagination.itemsPerPage" + boundary-links="true" rotate="false" + max-size="pagination.maxPageCount" + previous-text="‹" next-text="›" + first-text="«" last-text="»"></ul> + </div> + </div> http://git-wip-us.apache.org/repos/asf/zeppelin/blob/3183a596/zeppelin-web/src/app/jobmanager/jobs/job-status.js ---------------------------------------------------------------------- diff --git a/zeppelin-web/src/app/jobmanager/jobs/job-status.js b/zeppelin-web/src/app/jobmanager/jobs/job-status.js new file mode 100644 index 0000000..fa14637 --- /dev/null +++ b/zeppelin-web/src/app/jobmanager/jobs/job-status.js @@ -0,0 +1,22 @@ +/* + * 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. + */ + +export const JobStatus = { + READY: 'READY', + FINISHED: 'FINISHED', + ABORT: 'ABORT', + ERROR: 'ERROR', + PENDING: 'PENDING', + RUNNING: 'RUNNING', +} http://git-wip-us.apache.org/repos/asf/zeppelin/blob/3183a596/zeppelin-web/src/app/jobmanager/jobs/job.html ---------------------------------------------------------------------- diff --git a/zeppelin-web/src/app/jobmanager/jobs/job.html b/zeppelin-web/src/app/jobmanager/jobs/job.html index b4becee..e555771 100644 --- a/zeppelin-web/src/app/jobmanager/jobs/job.html +++ b/zeppelin-web/src/app/jobmanager/jobs/job.html @@ -15,89 +15,28 @@ limitations under the License. <div class="job" data-ng-init="init(notebookJob)"> <div> <div ng-include src="'app/jobmanager/jobs/job-control.html'"></div> - <span - class="job-types" - ng-switch="notebookJob.noteType"> + <span class="job-types" + ng-switch="notebookJob.noteType"> <i ng-switch-when="normal" class="icon-doc"></i> <i ng-switch-when="cron" class="icon-clock"></i> <i ng-switch-default class="icon-question"></i> </span> - <a style="text-decoration: none !important;" ng-href="#/notebook/{{notebookJob.noteId}}"> - <span> - {{notebookJob.noteName}} - </span> - <span> - - - </span> - <span> - <span ng-if="notebookJob.interpreter === undefined" style="color: orange"> - Interpreter is not set - </span> - <span ng-if="notebookJob.interpreter !== undefined" style="color: gray"> - {{notebookJob.interpreter}} - </span> - </span> + <span>{{notebookJob.noteName}} - </span> + <span ng-if="notebookJob.interpreter === undefined" style="color: gray;"> + interpreter is not set</span> + <span ng-if="notebookJob.interpreter !== undefined" style="color: black;"> + {{notebookJob.interpreter}}</span> </a> <div ng-include src="'app/jobmanager/jobs/job-progressBar.html'"></div> </div> <div> - <span - ng-repeat="paragraphJob in notebookJob.paragraphs" - ng-switch="paragraphJob.status"> - <a ng-switch-when="READY" - style="text-decoration: none !important;" + <span ng-repeat="paragraphJob in notebookJob.paragraphs"> + <a style="text-decoration: none !important;" ng-href="#/notebook/{{notebookJob.noteId}}?paragraph={{paragraphJob.id}}"> - <i style="color: green" class="fa fa-circle-o" - tooltip-placement="top-left" - uib-tooltip="{{paragraphJob.name}} is READY"> - </i> - </a> - <a ng-switch-when="FINISHED" - style="text-decoration: none !important;" - ng-href="#/notebook/{{notebookJob.noteId}}?paragraph={{paragraphJob.id}}"> - <i style="color: green" class="fa fa-circle" - tooltip-placement="top-left" - uib-tooltip="{{paragraphJob.name}} is FINISHED"> - </i> - </a> - <a ng-switch-when="ABORT" - style="text-decoration: none !important;" - ng-href="#/notebook/{{notebookJob.noteId}}?paragraph={{paragraphJob.id}}"> - <i style="color: orange" class="fa fa-circle" - tooltip-placement="top-left" - uib-tooltip="{{paragraphJob.name}} is ABORT"> - </i> - </a> - <a ng-switch-when="ERROR" - style="text-decoration: none !important;" - ng-href="#/notebook/{{notebookJob.noteId}}?paragraph={{paragraphJob.id}}"> - <i style="color: red" class="fa fa-circle" - tooltip-placement="top-left" - uib-tooltip="{{paragraphJob.name}} is ERROR"> - </i> - </a> - <a ng-switch-when="PENDING" - style="text-decoration: none !important;" - ng-href="#/notebook/{{notebookJob.noteId}}?paragraph={{paragraphJob.id}}"> - <i style="color: gray" class="fa fa-circle" - tooltip-placement="top-left" - uib-tooltip="{{paragraphJob.name}} is PENDING"> - </i> - </a> - <a ng-switch-when="RUNNING" - style="text-decoration: none !important;" - ng-href="#/notebook/{{notebookJob.noteId}}?paragraph={{paragraphJob.id}}"> - <i style="color: blue" class="fa fa-spinner spinAnimation" - tooltip-placement="top-left" - uib-tooltip="{{paragraphJob.name}} is RUNNING"> - </i> - </a> - <a ng-switch-default class="icon-question" - style="text-decoration: none !important;" - ng-href="#/notebook/{{notebookJob.noteId}}?paragraph={{paragraphJob.id}}"> - <i class="icon-question" + <i ng-style="{'color': $parent.getJobColorByStatus(paragraphJob.status)}" + ng-class="$parent.getJobIconByStatus(paragraphJob.status)" tooltip-placement="top-left" uib-tooltip="{{paragraphJob.name}} is {{paragraphJob.status}}"> </i>
