Collapse/expand job_events list using the api parent/child relationship

This commit is contained in:
chouseknecht
2013-06-18 17:20:25 -04:00
parent c9da8294df
commit 2abd33cf22
9 changed files with 215 additions and 35 deletions

View File

@@ -436,4 +436,17 @@
.list-header .icon-sort-down, .list-header .icon-sort-down,
.list-header .icon-sort-up { .list-header .icon-sort-up {
color: #36454F; color: #36454F;
}
/* job_events syles */
tr td i {
float: none;
margin-right: 10px;
padding-top: 3px;
padding-left: 0;
margin-left: 0;
}
#event_display-header {
min-width: 250px;
} }

View File

@@ -52,7 +52,8 @@ angular.module('ansible', [
'JobHostDefinition', 'JobHostDefinition',
'GroupsHelper', 'GroupsHelper',
'HostsHelper', 'HostsHelper',
'ParseHelper' 'ParseHelper',
'ChildrenHelper'
]) ])
.config(['$routeProvider', function($routeProvider) { .config(['$routeProvider', function($routeProvider) {
$routeProvider. $routeProvider.

View File

@@ -12,21 +12,50 @@
function JobEventsList ($scope, $rootScope, $location, $log, $routeParams, Rest, Alert, JobEventList, function JobEventsList ($scope, $rootScope, $location, $log, $routeParams, Rest, Alert, JobEventList,
GenerateList, LoadBreadCrumbs, Prompt, SearchInit, PaginateInit, ReturnToCaller, GenerateList, LoadBreadCrumbs, Prompt, SearchInit, PaginateInit, ReturnToCaller,
ClearScope, ProcessErrors, GetBasePath, LookUpInit) ClearScope, ProcessErrors, GetBasePath, LookUpInit, ToggleChildren)
{ {
ClearScope('htmlTemplate'); ClearScope('htmlTemplate');
var list = JobEventList; var list = JobEventList;
list.base = $location.path(); list.base = $location.path();
var defaultUrl = GetBasePath('jobs') + $routeParams.id + '/job_events/';
var defaultUrl = GetBasePath('jobs') + $routeParams.id + '/job_events/?parent__isnull=1';
var view = GenerateList; var view = GenerateList;
var base = $location.path().replace(/^\//,'').split('/')[0]; var base = $location.path().replace(/^\//,'').split('/')[0];
var scope = view.inject(list, { mode: 'edit' }); var scope = view.inject(list, { mode: 'edit' });
scope.selected = []; scope.selected = [];
if (scope.RemovePostRefresh) {
scope.RemovePostRefresh();
}
scope.RemovePostRefresh = scope.$on('PostRefresh', function() {
// Initialize the parent levels
var set = scope[list.name];
for (var i=0; i < set.length; i++) {
set[i].event_display = set[i].event_display.replace(/^\u00a0*/g,'');
if (set[i].parent == null && set[i]['ngclick'] === undefined && set[i]['ngicon'] == undefined) {
set[i].parent = 0;
set[i]['ngclick'] = "toggleChildren(" + set[i].id + ", \"" + set[i].related.children + "\")";
set[i]['ngicon'] = 'icon-expand-alt';
set[i]['level'] = 0;
set[i]['spaces'] = 0;
}
}
});
SearchInit({ scope: scope, set: 'jobevents', list: list, url: defaultUrl }); SearchInit({ scope: scope, set: 'jobevents', list: list, url: defaultUrl });
PaginateInit({ scope: scope, list: list, url: defaultUrl }); PaginateInit({ scope: scope, list: list, url: defaultUrl });
scope.search(list.iterator); scope.search(list.iterator);
scope.toggleChildren = function(id, children) {
ToggleChildren({
scope: scope,
list: list,
id: id,
children: children
});
}
LoadBreadCrumbs(); LoadBreadCrumbs();
if (scope.PostRefreshRemove) { if (scope.PostRefreshRemove) {
@@ -76,7 +105,7 @@ function JobEventsList ($scope, $rootScope, $location, $log, $routeParams, Rest,
JobEventsList.$inject = [ '$scope', '$rootScope', '$location', '$log', '$routeParams', 'Rest', 'Alert', 'JobEventList', JobEventsList.$inject = [ '$scope', '$rootScope', '$location', '$log', '$routeParams', 'Rest', 'Alert', 'JobEventList',
'GenerateList', 'LoadBreadCrumbs', 'Prompt', 'SearchInit', 'PaginateInit', 'ReturnToCaller', 'ClearScope', 'GenerateList', 'LoadBreadCrumbs', 'Prompt', 'SearchInit', 'PaginateInit', 'ReturnToCaller', 'ClearScope',
'ProcessErrors','GetBasePath', 'LookUpInit' 'ProcessErrors','GetBasePath', 'LookUpInit', 'ToggleChildren'
]; ];
function JobEventsEdit ($scope, $rootScope, $compile, $location, $log, $routeParams, JobEventForm, function JobEventsEdit ($scope, $rootScope, $compile, $location, $log, $routeParams, JobEventForm,

View File

@@ -0,0 +1,112 @@
/*********************************************
* Copyright (c) 2013 AnsibleWorks, Inc.
*
* ChildrenHelper
*
* Used in job_events to expand/collapse children
*
*/
angular.module('ChildrenHelper', ['RestServices', 'Utilities'])
.factory('ToggleChildren', ['Alert', 'Rest', 'GetBasePath','ProcessErrors',
function(Alert, Rest, GetBasePath, ProcessErrors) {
return function(params) {
var scope = params.scope;
var list = params.list;
var id = params.id;
var children = params.children;
var set = scope[list.name]; // set is now a pointer to scope[list.name]
function calcSpaces(lvl) {
return lvl * 24;
}
// Scan the array list and find the clicked element
var clicked;
var found = false;
for (var i = 0; i < set.length && found == false; i++){
if (set[i].id == id) {
clicked = i;
found = true;
}
}
// Expand or collapse children based on clicked element's icon
if (set[clicked]['ngicon'] == 'icon-expand-alt') {
// Expand: lookup and display children
Rest.setUrl(children);
Rest.get()
.success( function(data, status, headers, config) {
var found = false;
var level = (set[clicked].level !== undefined) ? set[clicked].level + 1 : 1;
var spaces = calcSpaces(level);
set[clicked]['ngicon'] = 'icon-collapse-alt';
for (var j=0; j < data.results.length; j++) {
data.results[j].level = level;
data.results[j].spaces = spaces;
//if (data.results[j].related.children === undefined) {
// data.results[j].spaces += 12
//}
data.results[j].event_display = data.results[j].event_display.replace(/^\u00a0*/g,'');
if (data.results[j].related.children) {
data.results[j]['ngclick'] = "toggleChildren(" + data.results[j].id + ", \"" + data.results[j].related.children + "\")";
data.results[j]['ngicon'] = 'icon-expand-alt';
}
if (clicked == (set.length - 1)) {
set.push(data.results[j]);
}
else {
set.splice(clicked + 1, 0, data.results[j]);
}
clicked++;
}
})
.error( function(data, status, headers, config) {
ProcessErrors(scope, data, status, null,
{ hdr: 'Error!', msg: 'Call to ' + children + ' failed. GET returned status: ' + status });
});
}
else {
// Collapse: find and remove children
var parents = [];
function findChildren(parent, idx) {
// recursive look through the tree finding all
// parents including and related the clicked element
for (var i=idx; i < set.length; i++) {
if (set[i].parent == parent) {
parents.push(parent);
findChildren(set[i].id, i + 1);
}
}
}
findChildren(id, clicked + 1);
// Remove all the children of the clicked element
var count;
for (var i=0; i < parents.length; i++) {
count = 0;
for (var j=clicked + 1; j< set.length; j++) {
if (set[j].parent == parents[i]) {
set.splice(j,1);
j=clicked; // start back a the top of the list
}
}
}
set[clicked]['ngicon'] = 'icon-expand-alt';
}
}
}]);

View File

@@ -102,15 +102,15 @@ angular.module('SearchHelper', ['RestServices', 'Utilities', 'RefreshHelper'])
(list.fields[scope[iterator + 'SearchField']].searchType && list.fields[scope[iterator + 'SearchField']].searchType == 'gtzero') ) { (list.fields[scope[iterator + 'SearchField']].searchType && list.fields[scope[iterator + 'SearchField']].searchType == 'gtzero') ) {
if (list.fields[scope[iterator + 'SearchField']].searchField) { if (list.fields[scope[iterator + 'SearchField']].searchField) {
scope[iterator + 'SearchParams'] = '?' + list.fields[scope[iterator + 'SearchField']].searchField + '__'; scope[iterator + 'SearchParams'] = list.fields[scope[iterator + 'SearchField']].searchField + '__';
} }
else if (list.fields[scope[iterator + 'SearchField']].sourceModel) { else if (list.fields[scope[iterator + 'SearchField']].sourceModel) {
// handle fields whose source is a related model e.g. inventories.organization // handle fields whose source is a related model e.g. inventories.organization
scope[iterator + 'SearchParams'] = '?' + list.fields[scope[iterator + 'SearchField']].sourceModel + '__' + scope[iterator + 'SearchParams'] = list.fields[scope[iterator + 'SearchField']].sourceModel + '__' +
list.fields[scope[iterator + 'SearchField']].sourceField + '__'; list.fields[scope[iterator + 'SearchField']].sourceField + '__';
} }
else { else {
scope[iterator + 'SearchParams'] = '?' + scope[iterator + 'SearchField'] + '__'; scope[iterator + 'SearchParams'] = scope[iterator + 'SearchField'] + '__';
} }
if ( list.fields[scope[iterator + 'SearchField']].searchType && if ( list.fields[scope[iterator + 'SearchField']].searchType &&
@@ -138,11 +138,16 @@ angular.module('SearchHelper', ['RestServices', 'Utilities', 'RefreshHelper'])
scope[iterator + 'SearchParams'] += (sort_order) ? '&order_by=' + escape(sort_order) : ''; scope[iterator + 'SearchParams'] += (sort_order) ? '&order_by=' + escape(sort_order) : '';
} }
else { else {
scope[iterator + 'SearchParams'] = ''; scope[iterator + 'SearchParams'] = (sort_order) ? 'order_by=' + escape(sort_order) : "";
scope[iterator + 'SearchParams'] += (sort_order) ? '?order_by=' + escape(sort_order) : '';
} }
scope[iterator + 'Page'] = 0; scope[iterator + 'Page'] = 0;
url += scope[iterator + 'SearchParams']; if (/\/$/.test(url)) {
url += '?' + scope[iterator + 'SearchParams'];
}
else {
url += '&' + scope[iterator + 'SearchParams'];
}
url = url.replace(/\&\&/,'&');
url += (scope[iterator + 'PageSize']) ? '&page_size=' + scope[iterator + 'PageSize'] : ""; url += (scope[iterator + 'PageSize']) ? '&page_size=' + scope[iterator + 'PageSize'] : "";
Refresh({ scope: scope, set: set, iterator: iterator, url: url }); Refresh({ scope: scope, set: set, iterator: iterator, url: url });
} }

View File

@@ -15,20 +15,19 @@ angular.module('JobEventsListDefinition', [])
editTitle: 'Job Events', editTitle: 'Job Events',
index: false, index: false,
hover: true, hover: true,
hasChildren: true,
fields: { fields: {
id: { created: {
label: 'Event ID', label: 'Creation Date',
key: true, key: true,
desc: true, nosort: true
searchType: 'int'
}, },
event_display: { event_display: {
label: 'Event', label: 'Event',
link: true hasChildren: true,
}, link: true,
created: { nosort: true
label: 'Creation Date'
}, },
host: { host: {
label: 'Host', label: 'Host',
@@ -36,7 +35,8 @@ angular.module('JobEventsListDefinition', [])
ngBind: 'jobevent.host_name', ngBind: 'jobevent.host_name',
sourceModel: 'host', sourceModel: 'host',
sourceField: 'name', sourceField: 'name',
searchField: 'hosts__name' searchField: 'hosts__name',
nosort: true
}, },
status: { status: {
label: 'Status', label: 'Status',
@@ -44,7 +44,8 @@ angular.module('JobEventsListDefinition', [])
"class": 'job-\{\{ jobevent.status \}\}', "class": 'job-\{\{ jobevent.status \}\}',
searchField: 'failed', searchField: 'failed',
searchType: 'boolean', searchType: 'boolean',
searchOptions: [{ name: "success", value: 0 }, { name: "error", value: 1 }] searchOptions: [{ name: "success", value: 0 }, { name: "error", value: 1 }],
nosort: true
} }
}, },

View File

@@ -84,7 +84,7 @@ angular.module('GeneratorHelpers', ['GeneratorHelpers'])
var html = ''; var html = '';
html += "<td "; html += "<td ";
html += "<td class=\"" + fld + "-column"; html += "<td class=\"" + fld + "-column";
html += (field['class']) ? " " + field['class'] : ""; html += (field['class']) ? " " + field['class'] : "";
html += "\" "; html += "\" ";
html += (field.ngClass) ? this.attr(field, 'ngClass') : ""; html += (field.ngClass) ? this.attr(field, 'ngClass') : "";
@@ -92,6 +92,13 @@ angular.module('GeneratorHelpers', ['GeneratorHelpers'])
// Add ngShow // Add ngShow
html += (field.ngShow) ? "<span " + Attr(field,'ngShow') + ">" : ""; html += (field.ngShow) ? "<span " + Attr(field,'ngShow') + ">" : "";
// Add collapse/expand icon --used on job_events page
if (list['hasChildren'] && field.hasChildren) {
html += "<span style=\"padding-left: \{\{ " + list.iterator + ".spaces \}\}px\"><a href=\"\" ng-click=\"\{\{ " + list.iterator + ".ngclick \}\}\"> " +
"<i class=\"\{\{ " + list.iterator + ".ngicon \}\}\" ng-show=\"'\{\{ " +
list.iterator + ".related.children \}\}' !== ''\" ></i></a> ";
}
// Start the Link // Start the Link
if ((field.key || field.link || field.linkTo || field.ngClick ) && options['mode'] != 'lookup' && options['mode'] != 'select') { if ((field.key || field.link || field.linkTo || field.ngClick ) && options['mode'] != 'lookup' && options['mode'] != 'select') {
@@ -111,7 +118,9 @@ angular.module('GeneratorHelpers', ['GeneratorHelpers'])
html += "<i ng-show=\"" + field.ngShowIcon + "\" class=\"" + field.icon + "\"></i> "; html += "<i ng-show=\"" + field.ngShowIcon + "\" class=\"" + field.icon + "\"></i> ";
} }
else { else {
html += Icon(field.icon) + " "; if (field.icon) {
html += Icon(field.icon) + " ";
}
} }
// Add data binds // Add data binds
@@ -120,15 +129,19 @@ angular.module('GeneratorHelpers', ['GeneratorHelpers'])
html += "{{ " + field.ngBind + " }}"; html += "{{ " + field.ngBind + " }}";
} }
else { else {
html += "{{" + list.iterator + "." + fld + "}}"; html += "{{" + list.iterator + "." + fld + "}}";
} }
} }
// Add additional text: // Add additional text:
if (field.text) { if (field.text) {
html += field.text; html += field.text;
} }
if (list['hasChildren'] && field.hasChildren) {
html += "</span>";
}
// close the link // close the link
if ((field.key || field.link || field.linkTo || field.ngClick ) if ((field.key || field.link || field.linkTo || field.ngClick )
&& options.mode != 'lookup' && options.mode != 'select') { && options.mode != 'lookup' && options.mode != 'select') {

View File

@@ -153,20 +153,25 @@ angular.module('ListGenerator', ['GeneratorHelpers'])
html += "<th>#</th>\n"; html += "<th>#</th>\n";
} }
for (var fld in list.fields) { for (var fld in list.fields) {
html += "<th class=\"list-header\" id=\"" + fld + "-header\" ng-click=\"sort('" + fld + "')\">" + list.fields[fld].label; html += "<th class=\"list-header\" id=\"" + fld + "-header\" ";
html += " <i class=\""; html += (list.fields[fld].nosort === undefined || list.fields[fld].nosort !== true) ? "ng-click=\"sort('" + fld + "')\"" : "";
if (list.fields[fld].key) { html += ">" + list.fields[fld].label;
if (list.fields[fld].desc) { if (list.fields[fld].nosort === undefined || list.fields[fld].nosort !== true) {
html += "icon-sort-down"; html += " <i class=\"";
if (list.fields[fld].key) {
if (list.fields[fld].desc) {
html += "icon-sort-down";
}
else {
html += "icon-sort-up";
}
} }
else { else {
html += "icon-sort-up"; html += "icon-sort";
} }
html += "\"></i></a>";
} }
else { html += "</th>\n";
html += "icon-sort";
}
html += "\"></i></a></th>\n";
} }
if (options.mode == 'select') { if (options.mode == 'select') {
html += "<th>Select</th>"; html += "<th>Select</th>";

View File

@@ -74,6 +74,7 @@
<script src="{{ STATIC_URL }}js/helpers/Groups.js"></script> <script src="{{ STATIC_URL }}js/helpers/Groups.js"></script>
<script src="{{ STATIC_URL }}js/helpers/Hosts.js"></script> <script src="{{ STATIC_URL }}js/helpers/Hosts.js"></script>
<script src="{{ STATIC_URL }}js/helpers/Parse.js"></script> <script src="{{ STATIC_URL }}js/helpers/Parse.js"></script>
<script src="{{ STATIC_URL }}js/helpers/Children.js"></script>
<script src="{{ STATIC_URL }}lib/ansible/directives.js"></script> <script src="{{ STATIC_URL }}lib/ansible/directives.js"></script>
<script src="{{ STATIC_URL }}lib/ansible/filters.js"></script> <script src="{{ STATIC_URL }}lib/ansible/filters.js"></script>
<script src="{{ STATIC_URL }}lib/ansible/api-loader.js"></script> <script src="{{ STATIC_URL }}lib/ansible/api-loader.js"></script>