diff --git a/awx/ui/client/features/jobs/jobs.strings.js b/awx/ui/client/features/jobs/jobs.strings.js
index a5b69df13f..6bc6225760 100644
--- a/awx/ui/client/features/jobs/jobs.strings.js
+++ b/awx/ui/client/features/jobs/jobs.strings.js
@@ -4,6 +4,13 @@ function JobsStrings (BaseString) {
const { t } = this;
const ns = this.jobs;
+ ns.sort = {
+ NAME_ASCENDING: t.s('Name (Ascending)'),
+ NAME_DESCENDING: t.s('Name (Descending)'),
+ START_TIME: t.s('Start Time'),
+ FINISH_TIME: t.s('Finish Time')
+ };
+
ns.list = {
PANEL_TITLE: t.s('JOBS'),
ROW_ITEM_LABEL_STARTED: t.s('Started'),
diff --git a/awx/ui/client/features/jobs/jobsList.controller.js b/awx/ui/client/features/jobs/jobsList.controller.js
index 71e12c967f..bfc20c0f47 100644
--- a/awx/ui/client/features/jobs/jobsList.controller.js
+++ b/awx/ui/client/features/jobs/jobsList.controller.js
@@ -28,6 +28,7 @@ function ListJobsController (
// smart-search
const name = 'jobs';
const iterator = 'job';
+ let paginateQuerySet = null;
let launchModalOpen = false;
let refreshAfterLaunchClose = false;
@@ -40,6 +41,63 @@ function ListJobsController (
vm.job_dataset = Dataset.data;
vm.jobs = Dataset.data.results;
+ $scope.$watch('$state.params', () => {
+ setToolbarSort();
+ }, true);
+
+ function setToolbarSort () {
+ const orderByValue = _.get($state.params, 'job_search.order_by');
+ const sortValue = _.find(vm.toolbarSortOptions, (option) => option.value === orderByValue);
+ if (sortValue) {
+ vm.toolbarSortValue = sortValue;
+ } else {
+ vm.toolbarSortValue = toolbarSortDefault;
+ }
+ }
+
+ const toolbarSortDefault = {
+ label: `${strings.get('sort.FINISH_TIME')}`,
+ value: '-finished'
+ };
+
+ vm.toolbarSortOptions = [
+ { label: `${strings.get('sort.NAME_ASCENDING')}`, value: 'name' },
+ { label: `${strings.get('sort.NAME_DESCENDING')}`, value: '-name' },
+ { label: `${strings.get('sort.START_TIME')}`, value: 'finished' },
+ toolbarSortDefault
+ ];
+
+ vm.toolbarSortValue = toolbarSortDefault;
+
+ // Temporary hack to retrieve $scope.querySet from the paginate directive.
+ // Remove this event listener once the page and page_size params
+ // are represented in the url.
+ $scope.$on('updateDataset', (event, dataset, queryset) => {
+ paginateQuerySet = queryset;
+ });
+
+ vm.onToolbarSort = (sort) => {
+ vm.toolbarSortValue = sort;
+
+ const queryParams = Object.assign(
+ {},
+ $state.params.job_search,
+ paginateQuerySet,
+ { order_by: sort.value }
+ );
+
+ // Update URL with params
+ $state.go('.', {
+ job_search: queryParams
+ }, { notify: false, location: 'replace' });
+
+ qs.search(SearchBasePath, queryParams)
+ .then(({ data }) => {
+ vm.jobs = data.results;
+ vm.job_dataset = data;
+ });
+ };
+
$scope.$watch('vm.job_dataset.count', () => {
$scope.$emit('updateCount', vm.job_dataset.count, 'jobs');
});
diff --git a/awx/ui/client/features/jobs/jobsList.view.html b/awx/ui/client/features/jobs/jobsList.view.html
index 812f94d9bf..660221968d 100644
--- a/awx/ui/client/features/jobs/jobsList.view.html
+++ b/awx/ui/client/features/jobs/jobsList.view.html
@@ -14,10 +14,13 @@
+ on-collapse="vm.onCollapse"
+ is-collapsed="vm.isCollapsed"
+ sort-only="false"
+ sort-value="vm.toolbarSortValue"
+ sort-options="vm.toolbarSortOptions"
+ on-sort="vm.onToolbarSort">