Merge branch 'dev' into feature/refactor-work-package-index-styling-flexboxes

Signed-off-by: Alex Coles <alex@alexbcoles.com>

Conflicts:
	public/templates/work_packages.list.html
pull/1768/head
Alex Coles 10 years ago
commit 8039c89c4f
  1. 2
      app/assets/javascripts/angular/config/work-packages-config.js
  2. 2
      app/assets/javascripts/angular/controllers/dialogs/save.js
  3. 5
      app/assets/javascripts/angular/directives/components/table-pagination-directive.js
  4. 82
      app/assets/javascripts/angular/helpers/url-params-helper.js
  5. 63
      app/assets/javascripts/angular/models/query.js
  6. 40
      app/assets/javascripts/angular/services/query-service.js
  7. 25
      app/assets/javascripts/angular/services/work-package-service.js
  8. 109
      app/assets/javascripts/angular/work_packages/controllers/work-packages-list-controller.js
  9. 8
      app/assets/javascripts/angular/work_packages/directives/filter-clear-directive.js
  10. 3
      app/assets/javascripts/angular/work_packages/directives/options-dropdown-directive.js
  11. 2
      app/assets/javascripts/angular/work_packages/directives/query-columns-directive.js
  12. 5
      app/assets/javascripts/angular/work_packages/directives/query-filter-directive.js
  13. 1
      app/assets/javascripts/angular/work_packages/directives/query-filters-directive.js
  14. 30
      app/assets/javascripts/angular/work_packages/directives/query-form-directive.js
  15. 3
      app/assets/javascripts/angular/work_packages/directives/work-package-group-sums-directive.js
  16. 2
      app/assets/javascripts/angular/work_packages/directives/work-package-total-sums-directive.js
  17. 4
      app/assets/javascripts/angular/work_packages/directives/work-packages-table-directive.js
  18. 2
      app/assets/javascripts/angular/work_packages/tabs/work-package-relations-directive.js
  19. 2
      config/locales/js-de.yml
  20. 2
      config/locales/js-en.yml
  21. 98
      karma/tests/controllers/work-packages-list-controller-test.js
  22. 26
      karma/tests/directives/components/table_pagination-test.js
  23. 2
      karma/tests/directives/work_packages/sort-header-directive-test.js
  24. 62
      karma/tests/helpers/url-params-helper-test.js
  25. 5
      karma/tests/models/query_test.js
  26. 2
      public/templates/components/selectable_title.html
  27. 11
      public/templates/work_packages.list.html
  28. 15
      public/templates/work_packages/query_filters.html
  29. 3
      public/templates/work_packages/work_packages_table.html

@ -28,7 +28,7 @@
angular.module('openproject.workPackages.config')
.constant('INITIALLY_SELECTED_COLUMNS', ['id', 'project', 'type', 'status', 'priority', 'subject', 'assigned_to_id', 'updated_at'])
.constant('INITIALLY_SELECTED_COLUMNS', [{ name: 'id' }, { name: 'project' }, { name: 'type' }, { name: 'status' }, { name: 'priority' }, { name: 'subject' }, { name: 'assigned_to_id' }, { name: 'updated_at' }])
.constant('OPERATORS_AND_LABELS_BY_FILTER_TYPE', {
list: [['=', 'label_equals'], ['!', 'label_not_equals']],

@ -52,7 +52,7 @@ angular.module('openproject.workPackages.controllers')
.then(function(data){
// push query id to URL without reinitializing work-packages-list-controller
if (data.query) {
$state.go('work-packages.list', { query_id: data.query.id }, { notify: false });
$state.go('work-packages.list', { query_id: data.query.id, query: null }, { notify: false });
AuthorisationService.initModelAuth("query", data.query._links);
}

@ -33,8 +33,7 @@ angular.module('openproject.uiComponents')
restrict: 'EA',
templateUrl: '/templates/components/table_pagination.html',
scope: {
totalEntries: '=',
updateResults: '&'
totalEntries: '='
},
link: function(scope, element, attributes) {
scope.I18n = I18n;
@ -53,7 +52,7 @@ angular.module('openproject.uiComponents')
updateCurrentRangeLabel();
updatePageNumbers();
scope.updateResults(); // update table
scope.$emit('workPackagesRefreshRequired');
};
/**

@ -51,6 +51,88 @@ angular.module('openproject.helpers')
return parts.join('&');
},
encodeQueryJsonParams: function(query) {
var paramsData = {
c: query.columns.map(function(column) { return column.name; })
};
if(!!query.displaySums) {
paramsData.s = query.displaySums;
}
if(!!query.projectId) {
paramsData.p = query.projectId;
}
if(!!query.groupBy) {
paramsData.g = query.groupBy;
}
if(!!query.getSortation()) {
paramsData.t = query.getSortation().encode()
}
if(query.filters && query.filters.length) {
paramsData.f = query.filters.filter(function(filter) {
return !filter.deactivated;
})
.map(function(filter) {
var filterData = {
n: filter.name,
m: filter.modelName,
o: encodeURIComponent(filter.operator),
t: filter.type
};
if(filter.values) {
angular.extend(filterData, { v: filter.values })
}
return filterData
});
}
return JSON.stringify(paramsData);
},
decodeQueryFromJsonParams: function(queryId, updateJson) {
var queryData = {};
if(queryId) {
queryData.id = queryId;
}
if(updateJson) {
var properties = JSON.parse(updateJson);
if(!!properties.c) {
queryData.columns = properties.c.map(function(column) { return { name: column }; });
}
if(!!properties.s) {
queryData.displaySums = properties.s;
}
if(!!properties.p) {
queryData.projectId = properties.p;
}
if(!!properties.g) {
queryData.groupBy = properties.g;
}
if(!!properties.f) {
queryData.filters = properties.f.map(function(urlFilter) {
var filterData = {
name: urlFilter.n,
modelName: urlFilter.m,
operator: decodeURIComponent(urlFilter.o),
type: urlFilter.t
};
if(urlFilter.v) {
var vs = Array.isArray(urlFilter.v) ? urlFilter.v : [urlFilter.v];
angular.extend(filterData, { values: vs });
}
return filterData;
});
}
if(!!properties.t) {
queryData.sortCriteria = properties.t;
}
}
return queryData;
},
buildQueryExportOptions: function(query){
var relativeUrl = "/work_packages";
if (query.project_id){

@ -31,7 +31,8 @@ angular.module('openproject.models')
.factory('Query', ['Filter',
'Sortation',
'UrlParamsHelper',
function(Filter, Sortation, UrlParamsHelper) {
'INITIALLY_SELECTED_COLUMNS',
function(Filter, Sortation, UrlParamsHelper, INITIALLY_SELECTED_COLUMNS) {
Query = function (queryData, options) {
angular.extend(this, queryData, options);
@ -39,7 +40,19 @@ angular.module('openproject.models')
this.filters = [];
this.groupBy = this.groupBy || '';
if(queryData) this.setFilters(queryData.filters);
if(queryData.filters){
if(options && options.rawFilters) {
this.setRawFilters(queryData.filters);
} else {
this.setFilters(queryData.filters);
}
}
if(queryData.sortCriteria) this.setSortation(queryData.sortCriteria);
if(!this.columns) {
this.setColumns(INITIALLY_SELECTED_COLUMNS);
}
};
Query.prototype = {
@ -56,7 +69,7 @@ angular.module('openproject.models')
'f[]': this.getFilterNames(this.getActiveConfiguredFilters()),
'c[]': this.getParamColumns(),
'group_by': this.groupBy,
'sort': this.sortation.encode(),
'sort': this.getEncodedSortation(),
'display_sums': this.displaySums,
'name': this.name,
'is_public': this.isPublic
@ -74,7 +87,7 @@ angular.module('openproject.models')
'f[]': this.getFilterNames(this.getActiveConfiguredFilters()),
'c[]': this.getParamColumns(),
'group_by': this.groupBy,
'sort': this.sortation.encode(),
'sort': this.getEncodedSortation(),
'display_sums': this.displaySums,
'name': this.name,
'is_public': this.isPublic
@ -87,6 +100,7 @@ angular.module('openproject.models')
save: function(data){
// Note: query has already been updated, only the id needs to be set
this.id = data.id;
this.dirty = false;
return this;
},
@ -98,6 +112,19 @@ angular.module('openproject.models')
this.starred = false;
},
update: function(queryData) {
angular.extend(this, queryData);
if(queryData.filters){
this.filters = [];
this.setRawFilters(queryData.filters);
}
if(queryData.sortCriteria) this.setSortation(queryData.sortCriteria);
this.dirty = true;
return this;
},
getQueryString: function(){
return UrlParamsHelper.buildQueryString(this.toParams());
},
@ -106,8 +133,8 @@ angular.module('openproject.models')
return this.sortation;
},
setSortation: function(sortation){
this.sortation = sortation;
setSortation: function(sortCriteria){
this.sortation = new Sortation(sortCriteria);
},
setGroupBy: function(groupBy) {
@ -164,6 +191,20 @@ angular.module('openproject.models')
}
},
setRawFilters: function(filters) {
if (filters){
var self = this;
this.filters = filters.map(function(filterData){
return new Filter(filterData);
});
}
},
setColumns: function(columns) {
this.columns = columns;
},
/**
* @name isDefault
* @function
@ -224,6 +265,10 @@ angular.module('openproject.models')
return selectedColumns;
},
getEncodedSortation: function() {
return !!this.sortation ? this.sortation.encode() : null;
},
getColumnNames: function() {
return this.columns.map(function(column) {
return column.name;
@ -294,10 +339,14 @@ angular.module('openproject.models')
});
},
isNew: function(){
isNew: function() {
return !this.id;
},
isDirty: function() {
return this.isNew() || this.dirty;
},
hasName: function() {
return !!this.name && !this.isDefault();
},

@ -74,14 +74,15 @@ angular.module('openproject.services')
starred: queryData.starred,
links: queryData._links
});
query.setSortation(new Sortation(queryData.sort_criteria));
query.setSortation(queryData.sort_criteria);
QueryService.getAvailableFilters(query.project_id)
.then(function(availableFilters) {
query.setAvailableWorkPackageFilters(availableFilters);
if (query.isDefault()) {
query.setDefaultFilter();
} else {
}
if(queryData.filters && queryData.filters.length) {
query.setFilters(queryData.filters);
}
@ -92,10 +93,45 @@ angular.module('openproject.services')
return query;
},
updateQuery: function(values, afterUpdate) {
var queryData = {
};
if(!!values.display_sums) {
queryData.displaySums = values.display_sums;
}
if(!!values.columns) {
queryData.columns = values.columns;
}
if(!!values.group_by) {
queryData.groupBy = values.group_by;
}
if(!!values.sort_criteria) {
queryData.sortCriteria = values.sort_criteria;
}
query.update(queryData);
QueryService.getAvailableFilters(query.project_id)
.then(function(availableFilters) {
query.setAvailableWorkPackageFilters(availableFilters);
if(queryData.filters && queryData.filters.length) {
query.setFilters(queryData.filters);
}
return query;
})
.then(afterUpdate);
return query;
},
getQuery: function() {
return query;
},
clearQuery: function() {
query = null;
},
getQueryName: function() {
if (query && query.hasName()) {
return query.name;

@ -36,7 +36,8 @@ angular.module('openproject.services')
'WorkPackagesHelper',
'HALAPIResource',
'DEFAULT_FILTER_PARAMS',
function($http, PathHelper, WorkPackagesHelper, HALAPIResource, DEFAULT_FILTER_PARAMS) {
'DEFAULT_PAGINATION_OPTIONS',
function($http, PathHelper, WorkPackagesHelper, HALAPIResource, DEFAULT_FILTER_PARAMS, DEFAULT_PAGINATION_OPTIONS) {
var workPackage;
var WorkPackageService = {
@ -56,20 +57,22 @@ angular.module('openproject.services')
return WorkPackageService.doQuery(url, params);
},
getWorkPackagesFromUrlQueryParams: function(projectIdentifier, location) {
getWorkPackages: function(projectIdentifier, query, paginationOptions) {
var url = projectIdentifier ? PathHelper.apiProjectWorkPackagesPath(projectIdentifier) : PathHelper.apiWorkPackagesPath();
var params = {};
angular.extend(params, location.search());
return WorkPackageService.doQuery(url, params);
},
if(query) {
angular.extend(params, query.toUpdateParams());
}
getWorkPackages: function(projectIdentifier, query, paginationOptions) {
var url = projectIdentifier ? PathHelper.apiProjectWorkPackagesPath(projectIdentifier) : PathHelper.apiWorkPackagesPath();
var params = angular.extend(query.toUpdateParams(), {
page: paginationOptions.page,
per_page: paginationOptions.perPage
});
if(paginationOptions) {
angular.extend(params, {
page: paginationOptions.page,
per_page: paginationOptions.perPage
});
} else {
angular.extend(params, DEFAULT_PAGINATION_OPTIONS);
}
return WorkPackageService.doQuery(url, params);
},

@ -31,10 +31,8 @@ angular.module('openproject.workPackages.controllers')
.controller('WorkPackagesListController', [
'$scope',
'$rootScope',
'$q',
'$location',
'$stateParams',
'$state',
'$location',
'latestTab',
'I18n',
'WorkPackagesTableService',
@ -43,14 +41,12 @@ angular.module('openproject.workPackages.controllers')
'QueryService',
'PaginationService',
'AuthorisationService',
'WorkPackageLoadingHelper',
'HALAPIResource',
'INITIALLY_SELECTED_COLUMNS',
'UrlParamsHelper',
'OPERATORS_AND_LABELS_BY_FILTER_TYPE',
function($scope, $rootScope, $q, $location, $stateParams, $state, latestTab,
function($scope, $rootScope, $state, $location, latestTab,
I18n, WorkPackagesTableService,
WorkPackageService, ProjectService, QueryService, PaginationService,
AuthorisationService, WorkPackageLoadingHelper, HALAPIResource, INITIALLY_SELECTED_COLUMNS,
AuthorisationService, UrlParamsHelper,
OPERATORS_AND_LABELS_BY_FILTER_TYPE) {
@ -59,12 +55,19 @@ angular.module('openproject.workPackages.controllers')
$scope.operatorsAndLabelsByFilterType = OPERATORS_AND_LABELS_BY_FILTER_TYPE;
$scope.disableFilters = false;
$scope.disableNewWorkPackage = true;
var queryParams = $location.search().query_props;
var fetchWorkPackages;
if($scope.query_id){
fetchWorkPackages = WorkPackageService.getWorkPackagesByQueryId($scope.projectIdentifier, $scope.query_id);
if(queryParams) {
// Attempt to build up query from URL params
fetchWorkPackages = fetchWorkPackagesFromUrlParams(queryParams);
} else if($state.params.query_id) {
// Load the query by id if present
fetchWorkPackages = WorkPackageService.getWorkPackagesByQueryId($scope.projectIdentifier, $state.params.query_id);
} else {
fetchWorkPackages = WorkPackageService.getWorkPackagesFromUrlQueryParams($scope.projectIdentifier, $location);
// Clear the cached query and load the default
QueryService.clearQuery();
fetchWorkPackages = WorkPackageService.getWorkPackages($scope.projectIdentifier);
}
$scope.settingUpPage = fetchWorkPackages // put promise in scope for cg-busy
@ -76,6 +79,28 @@ angular.module('openproject.workPackages.controllers')
});
}
function fetchWorkPackagesFromUrlParams(queryParams) {
try {
var queryData = UrlParamsHelper.decodeQueryFromJsonParams($state.params.query_id, queryParams);
var queryFromParams = new Query(queryData, { rawFilters: true });
return WorkPackageService.getWorkPackages($scope.projectIdentifier, queryFromParams);
} catch(e) {
$scope.$emit('flashMessage', {
isError: true,
text: I18n.t('js.work_packages.query.errors.unretrievable_query')
});
clearUrlQueryParams();
return WorkPackageService.getWorkPackages($scope.projectIdentifier);
}
}
function clearUrlQueryParams() {
$location.search('query_props', null);
$location.search('query_id', null);
}
function fetchProjectTypesAndQueries() {
if ($scope.projectIdentifier) {
ProjectService.getProject($scope.projectIdentifier)
@ -98,21 +123,24 @@ angular.module('openproject.workPackages.controllers')
}
function initQuery(metaData) {
var storedQuery = QueryService.getQuery();
var queryData = metaData.query,
columnData = metaData.columns;
if (storedQuery && $stateParams.query_id !== null && storedQuery.id === $scope.query_id) {
$scope.query = storedQuery;
} else {
var queryData = metaData.query,
columnData = metaData.columns;
var cachedQuery = QueryService.getQuery();
var urlQueryId = $state.params.query_id;
$scope.query = QueryService.initQuery($scope.query_id, queryData, columnData, metaData.export_formats, afterQuerySetupCallback);
if (cachedQuery && urlQueryId && cachedQuery.id == urlQueryId) {
// Augment current unsaved query with url param data
var updateData = angular.extend(queryData, { columns: columnData })
$scope.query = QueryService.updateQuery(updateData, afterQuerySetupCallback);
} else {
// Set up fresh query from retrieved query meta data
$scope.query = QueryService.initQuery($state.params.query_id, queryData, columnData, metaData.export_formats, afterQuerySetupCallback);
}
}
function afterQuerySetupCallback(query) {
$scope.showFiltersOptions = query.filters.length > 0;
$scope.updateBackUrl();
}
function setupWorkPackagesTable(json) {
@ -145,9 +173,6 @@ angular.module('openproject.workPackages.controllers')
$scope.workPackageCountByGroup = meta.work_package_count_by_group;
$scope.totalEntries = QueryService.getTotalEntries();
// back url
$scope.updateBackUrl();
// Authorisation
AuthorisationService.initModelAuth("work_package", meta._links);
AuthorisationService.initModelAuth("query", meta.query._links);
@ -162,21 +187,30 @@ angular.module('openproject.workPackages.controllers')
// Updates
$scope.updateBackUrl = function(){
// Easier than trying to extract it from $location
$scope.maintainUrlQueryState = function(){
var relativeUrl = "/work_packages";
if ($scope.projectIdentifier){
relativeUrl = "/projects/" + $scope.projectIdentifier + relativeUrl;
}
if($scope.query){
relativeUrl = relativeUrl + "#?" + $scope.query.getQueryString();
if($scope.query) {
var queryString = UrlParamsHelper.encodeQueryJsonParams($scope.query);
$location.search('query_props', queryString);
relativeUrl = relativeUrl + "?query_props=" + queryString;
}
$scope.backUrl = relativeUrl;
};
$scope.updateResults = function() {
$scope.loadQuery = function(queryId) {
// Clear unsaved changes to current query
clearUrlQueryParams();
// Load new query
$state.go('work-packages.list', { query_id: queryId });
};
function updateResults() {
$scope.$broadcast('openproject.workPackages.updateResults');
$scope.refreshWorkPackages = WorkPackageService.getWorkPackages($scope.projectIdentifier, $scope.query, PaginationService.getPaginationOptions())
@ -185,10 +219,6 @@ angular.module('openproject.workPackages.controllers')
return $scope.refreshWorkPackages;
};
$scope.setQueryState = function(query_id) {
$state.go('work-packages.list', { query_id: query_id });
};
// More
function serviceErrorHandler(data) {
@ -208,6 +238,23 @@ angular.module('openproject.workPackages.controllers')
$scope.selectedTitle = queryName || I18n.t('js.toolbar.unselected_title');
});
$rootScope.$on('queryStateChange', function(event, message) {
$scope.maintainUrlQueryState();
});
$rootScope.$on('workPackagesRefreshRequired', function(event, message) {
updateResults();
});
$rootScope.$on('queryClearRequired', function(event, message) {
$location.search('query_props', null);
if($location.search().query_id) {
$location.search('query_id', null);
} else {
initialSetup();
}
});
$scope.openLatestTab = function() {
$state.go(latestTab.getStateName(), { workPackageId: $scope.preselectedWorkPackageId });
};

@ -29,18 +29,18 @@
angular.module('openproject.workPackages.directives')
.directive('filterClear', [
'$state',
'$location',
'I18n',
'QueryService',
function($state, I18n, QueryService){
function($location, I18n, QueryService){
return {
restrict: 'E',
templateUrl: '/templates/work_packages/filter_clear.html',
scope: true,
link: function(scope, element, attributes) {
scope.I18n = I18n;
scope.clearQuery = function(){
$state.go('work-packages.list', { query_id: null }, { reload: true });
scope.clearQuery = function() {
scope.$emit('queryClearRequired')
};
}
};

@ -61,6 +61,7 @@ angular.module('openproject.workPackages.directives')
QueryService.saveQuery()
.then(function(data){
scope.$emit('flashMessage', data.status);
$state.go('work-packages.list', { query_id: scope.query.id, query: null }, { notify: false });
});
}
}
@ -72,7 +73,7 @@ angular.module('openproject.workPackages.directives')
.then(function(data){
settingsModal.deactivate();
scope.$emit('flashMessage', data.status);
$state.go('work-packages.list', { query_id: null }, { reload: true });
$state.go('work-packages.list', { query_id: null, query: null }, { reload: true });
});
}
};

@ -76,7 +76,7 @@ angular.module('openproject.workPackages.directives')
var newColumns = WorkPackagesTableHelper.selectColumnsByName(scope.columns, columnNames);
WorkPackageService.augmentWorkPackagesWithColumnsData(workPackages, newColumns, groupBy)
.then(scope.updateBackUrl);
.then(function(){ scope.$emit('queryStateChange') });
}
}
};

@ -64,9 +64,8 @@ angular.module('openproject.workPackages.directives')
if (filter !== oldFilter) {
if (filter.isConfigured() && (filterChanged(filter, oldFilter) || valueReset(filter, oldFilter))) {
PaginationService.resetPage();
applyFilters();
scope.updateBackUrl();
scope.$emit('queryStateChange');
scope.$emit('workPackagesRefreshRequired');
}
}
}, true);

@ -43,7 +43,6 @@ angular.module('openproject.workPackages.directives')
if (filterName) {
scope.query.addFilter(filterName);
scope.filterToBeAdded = undefined;
scope.updateBackUrl();
}
});

@ -55,6 +55,15 @@ angular.module('openproject.workPackages.directives')
return groupByChanged || sortElementsChanged;
}
function passiveQueryPropertiesChanged(currentProperties, formerProperties) {
if (formerProperties === undefined) return false;
var columnsChanged = JSON.stringify(currentProperties.columns) !== JSON.stringify(formerProperties.columns);
var displaySumsChanged = currentProperties.displaySums !== formerProperties.displaySums;
return columnsChanged || displaySumsChanged;
}
function observedQueryProperties() {
var query = scope.query;
@ -69,14 +78,31 @@ angular.module('openproject.workPackages.directives')
}
}
function passiveQueryProperties() {
var query = scope.query;
if (query !== undefined) {
return {
columns: query.columns,
displaySums: query.displaySums
};
}
}
scope.$watch(observedQueryProperties, function(newProperties, oldProperties) {
if (!querySwitched(newProperties, oldProperties)) {
if (queryPropertiesChanged(newProperties, oldProperties)) {
scope.updateResults();
scope.updateBackUrl();
scope.$emit('queryStateChange');
scope.$emit('workPackagesRefreshRequired');
}
}
}, true);
scope.$watch(passiveQueryProperties, function(newProperties, oldProperties) {
if (passiveQueryPropertiesChanged(newProperties, oldProperties)) {
scope.$emit('queryStateChange');
}
}, true);
}
};
}

@ -48,7 +48,8 @@ angular.module('openproject.workPackages.directives')
scope.$watch('groupSums.length', function() {
// map columns to sums if the column data is a number
setSums();
scope.updateBackUrl();
scope.$emit('queryStateChange');
scope.$emit('workPackagesRefreshRequired');
});
}

@ -64,7 +64,7 @@ angular.module('openproject.workPackages.directives')
scope.$watch(columnNames, function(columnNames, formerNames) {
if (!angular.equals(columnNames, formerNames) && !totalSumsFetched()) {
fetchTotalSums();
scope.updateBackUrl();
scope.$emit('queryStateChange');
}
}, true);
}

@ -50,9 +50,7 @@ angular.module('openproject.workPackages.directives')
groupByColumn: '=',
displaySums: '=',
totalSums: '=',
groupSums: '=',
updateResults: '&',
updateBackUrl: '='
groupSums: '='
},
link: function(scope, element, attributes) {
var activeSelectionBorderIndex;

@ -72,7 +72,7 @@ angular.module('openproject.workPackages.tabs')
});
scope.$watch('expand', function(newVal, oldVal) {
scope.stateClass = WorkPackagesHelper.collapseStateIcon(!newVal);
scope.stateClass = WorkPackagesHelper.collapseStateIcon(newVal);
});
scope.toggleExpand = function() {

@ -301,6 +301,8 @@ de:
insert_columns: "Spalten hinzufügen ..."
filters: "Filter"
display_sums: "Summen anzeigen"
errors:
unretrievable_query: "Die URL enthält keine benutzerdefinierte Abfrage"
tabs:
overview: "Übersicht"
activity: "Aktivität"

@ -304,6 +304,8 @@ en:
insert_columns: "Insert columns ..."
filters: "Filters"
display_sums: "Display Sums"
errors:
unretrievable_query: "Unable to retrieve query from URL"
tabs:
overview: Overview
activity: Activity

@ -31,6 +31,7 @@
describe('WorkPackagesListController', function() {
var scope, ctrl, win, testParams,
testProjectService, testWorkPackageService, testQueryService, testPaginationService;
var testQueries;
var buildController;
beforeEach(module('openproject.api', 'openproject.workPackages.controllers', 'openproject.workPackages.services', 'ng-context-menu', 'btford.modal', 'openproject.services'));
@ -47,9 +48,24 @@ describe('WorkPackagesListController', function() {
location: { pathname: "" }
};
var workPackageData = {
var defaultWorkPackagesData = {
meta: {
}
query: {
_links: []
},
sums: [null]
},
work_packages: []
};
var workPackagesDataByQueryId = {
meta: {
query: {
props: { id: 1 },
_links: []
},
sums: [null]
},
work_packages: []
};
var columnData = {
};
@ -58,6 +74,16 @@ describe('WorkPackagesListController', function() {
var projectData = { embedded: { types: [] } };
var projectsData = [ projectData ];
testQueries = {
'1': {
id: 1,
columns: ['type']
},
'2': {
id: 2,
columns: ['type']
},
};
testProjectService = {
getProject: function(identifier) {
@ -74,15 +100,18 @@ describe('WorkPackagesListController', function() {
testWorkPackageService = {
getWorkPackages: function () {
return $timeout(function () {
return defaultWorkPackagesData;
}, 10);
},
getWorkPackagesByQueryId: function (params) {
return $timeout(function () {
return workPackageData;
return workPackagesDataByQueryId;
}, 10);
},
getWorkPackagesFromUrlQueryParams: function () {
return $timeout(function () {
return workPackageData;
return defaultWorkPackagesData;
}, 10);
}
};
@ -93,8 +122,11 @@ describe('WorkPackagesListController', function() {
}
};
},
initQuery: function () {
initQuery: function (id) {
var queryId = id || 1;
return testQueries[queryId];
},
clearQuery: function() {},
getAvailableOptions: function() {
return {};
},
@ -130,11 +162,12 @@ describe('WorkPackagesListController', function() {
setPage: function () {
}
};
testAuthorisationService = {
initModelAuth: function(model, links) {
}
}
testParams = {};
testState = {};
buildController = function() {
buildController = function(params, state, location) {
scope.projectIdentifier = 'test';
ctrl = $controller("WorkPackagesListController", {
@ -142,19 +175,58 @@ describe('WorkPackagesListController', function() {
$window: win,
QueryService: testQueryService,
PaginationService: testPaginationService,
ProjectService: testProjectService,
WorkPackageService: testWorkPackageService,
$stateParams: testParams,
$state: testState,
$stateParams: params,
$state: state,
$location: location,
latestTab: {}
});
$timeout.flush();
};
}));
describe('initialisation', function() {
describe('initialisation of default query', function() {
beforeEach(function(){
testParams = {};
testState = { params: {} };
testLocation = {
search: function() {
return {};
}
}
buildController(testParams, testState, testLocation);
});
it('should initialise', function() {
buildController();
expect(scope.settingUpPage).to.be.defined;
expect(scope.operatorsAndLabelsByFilterType).to.be.defined;
expect(scope.disableFilters).to.eq(false);
expect(scope.disableNewWorkPackage).to.eq(true);
expect(scope.query.id).to.eq(testQueries['1'].id);
});
});
describe('initialisation of query by id', function() {
beforeEach(function(){
testParams = { };
testState = { params: {
query_id: testQueries['2'].id
} };
testLocation = {
search: function() {
return {};
}
}
buildController(testParams, testState, testLocation);
});
it('should initialise', function() {
expect(scope.query.id).to.eq(testQueries['2'].id);
});
});
});

@ -34,7 +34,7 @@ describe('tablePagination Directive', function () {
beforeEach(inject(function ($rootScope, $compile, _I18n_) {
var html, I18n, t;;
html = '<table-pagination total-entries="tableEntries" icon-name="totalResults" update-results="showUserSomething()"></table-pagination>';
html = '<table-pagination total-entries="tableEntries" icon-name="totalResults"></table-pagination>';
element = angular.element(html);
rootScope = $rootScope;
@ -48,7 +48,7 @@ describe('tablePagination Directive', function () {
}));
describe('page ranges and links', function () {
beforeEach(function() {
beforeEach(function() {
compile();
});
@ -88,7 +88,7 @@ describe('tablePagination Directive', function () {
});
describe('perPage options', function () {
beforeEach(function() {
beforeEach(function() {
t = sinon.stub(I18n, 't');
t.withArgs('js.label_per_page').returns('Per page:');
compile();
@ -104,24 +104,4 @@ describe('tablePagination Directive', function () {
expect(perPageOptions.text()).to.include('Per page:');
});
});
describe('callback function', function() {
var updateResultsFn;
beforeEach(function () {
updateResultsFn = sinon.spy();
scope.showUserSomething = updateResultsFn;
compile();
});
it('should call the updateResults function on navigating page', function () {
var nextPage = element.find('.next_page');
var previousPage = element.find('.previous_page');
nextPage.click();
previousPage.click();
expect(updateResultsFn).to.have.been.calledTwice;
});
});
});

@ -72,7 +72,7 @@ describe('sortHeader Directive', function() {
beforeEach(function(){
query = new Query({
});
query.setSortation(new Sortation([]));
query.setSortation('parent:desc');
scope.query = query;
compile();

@ -55,4 +55,66 @@ describe('UrlParamsHelper', function() {
expect(queryString).not.to.include('@');
});
});
describe('encodeQueryJsonParams', function(){
var query;
beforeEach(function() {
var filter = {
modelName: 'soße',
name: 'soße_id',
type: 'list model',
operator: '=',
values: ['knoblauch']
};
query = new Query({
id: 1,
name: 'knoblauch soße',
projectId: 2,
displaySums: true,
columns: [{ name: 'type' }, { name: 'status' }, { name: 'soße' }],
groupBy: 'status',
sortCriteria: 'type:desc',
filters: [filter]
}, { rawFilters: true });
});
it('should encode query to params JSON', function() {
var encodedJSON = UrlParamsHelper.encodeQueryJsonParams(query);
var expectedJSON = "{\"c\":[\"type\",\"status\",\"soße\"],\"s\":true,\"p\":2,\"g\":\"status\",\"t\":\"type:desc\",\"f\":[{\"n\":\"soße_id\",\"m\":\"soße\",\"o\":\"%3D\",\"t\":\"list model\",\"v\":[\"knoblauch\"]}]}";
expect(encodedJSON).to.eq(expectedJSON);
})
});
describe('decodeQueryFromJsonParams', function() {
var params;
var queryId;
beforeEach(function() {
params = "{\"c\":[\"type\",\"status\",\"soße\"],\"s\":true,\"p\":2,\"g\":\"status\",\"t\":\"type:desc\",\"f\":[{\"n\":\"soße_id\",\"m\":\"soße\",\"o\":\"%3D\",\"t\":\"list model\",\"v\":[\"knoblauch\"]}]}";
queryId = 2;
});
it('should decode query params to object', function() {
var decodedQueryParams = UrlParamsHelper.decodeQueryFromJsonParams(queryId, params);
var expected = {
id: queryId,
projectId: 2,
displaySums: true,
columns: [{ name: 'type' }, { name: 'status' }, { name: 'soße' }],
groupBy: 'status',
sortCriteria: 'type:desc',
filters: [{
modelName: 'soße',
name: 'soße_id',
type: 'list model',
operator: '=',
values: ['knoblauch']
}]
};
expect(angular.equals(decodedQueryParams, expected)).to.be.true;
});
})
});

@ -42,8 +42,9 @@ describe('Query', function() {
});
it('should be a constructor function', function() {
expect(new Query()).to.exist;
expect(new Query()).to.be.an('object');
var queryData = { id: 1 };
expect(new Query(queryData)).to.exist;
expect(new Query(queryData)).to.be.an('object');
});
describe('adding filters', function(){

@ -23,7 +23,7 @@
<div class="title-group-header">{{ group.name }}</div>
<li ng-repeat="model in group.models" ng-class="{'first': model.highlighted }">
<a href
ui-sref="work-packages.list({ query_id: model.id })"
ui-sref="work-packages.list({ query_id: model.id, query: undefined })"
title="{{ model.label }}"
ng-bind-html="model.labelHtml"></a>
</li>

@ -2,7 +2,7 @@
<div toolbar id="toolbar">
<selectable-title selected-title="selectedTitle"
groups="groups"
transition-method="setQueryState">
transition-method="loadQuery">
</selectable-title>
<ul id="toolbar-items">
@ -65,7 +65,7 @@
<li><a href ng-click="toggleDisplaySums()"><i ng-if="query.displaySums" class="icon-action-menu icon-yes"></i><i ng-if="!query.displaySums" class="icon-action-menu no-icon"></i>{{ I18n.t('js.toolbar.settings.display_sums') }}</a></li>
<li class="dropdown-divider"></li>
<li><a href ng-click="saveQuery($event)"
ng-class="{'inactive': (!query.isNew() && cannot('query', 'update')) || (query.isNew() && cannot('query', 'create'))}">
ng-class="{'inactive': (!query.isDirty() && cannot('query', 'update')) || (query.isNew() && cannot('query', 'create'))}">
<i class="icon-action-menu icon-save1"></i>{{ I18n.t('js.toolbar.settings.save') }}</a>
</li>
<li><a href ng-click="showSaveAsModal($event)" ng-class="{'inactive': query.isNew() || cannot('query', 'create')}">
@ -117,14 +117,11 @@
count-by-group="workPackageCountByGroup"
display-sums="query.displaySums"
total-sums="totalSums"
group-sums="groupSums"
update-results="updateResults()"
update-back-url="updateBackUrl">
group-sums="groupSums">
</work-packages-table>
</div>
<div class="work-packages--list-pagination-area">
<table-pagination total-entries="totalEntries"
update-results="updateResults()">
<table-pagination total-entries="totalEntries">
</table-pagination>
</div>
<modal-loading>

@ -58,8 +58,7 @@
size="30"
type="text"
value=""
ng-disabled="isLoading"
execute-on-enter="updateResults()"/>
ng-disabled="isLoading"/>
<label ng-switch-when="string"
for="values_{{name}}"
class="hidden-for-sighted">
@ -76,8 +75,7 @@
size="30"
type="text"
value=""
ng-disabled="isLoading"
execute-on-enter="updateResults()"/>
ng-disabled="isLoading"/>
<label ng-switch-when="'text'"for="values_{{name}}" class="hidden-for-sighted">
{{ I18n.t('js.work_packages.description_enter_text') }}
</label>
@ -91,8 +89,7 @@
min="0"
type="number"
value=""
ng-disabled="isLoading"
execute-on-enter="updateResults()"/>
ng-disabled="isLoading"/>
<label ng-switch-when="'integer'" for="values_{{name}}" class="hidden-for-sighted">
{{ I18n.t('js.work_packages.description_enter_text') }}
</label>
@ -105,8 +102,7 @@
name="v[{{filter.name}}]"
size="3"
type="text"
ng-disabled="isLoading"
execute-on-enter="updateResults()"/>
ng-disabled="isLoading"/>
<label ng-switch-when="date" for="values_{{name}}" class="hidden-for-sighted">
{{ I18n.t('js.work_packages.time_relative.days') }}
</label>
@ -120,8 +116,7 @@
name="v[{{filter.name}}]"
size="3"
type="text"
ng-disabled="isLoading"
execute-on-enter="updateResults()"/>
ng-disabled="isLoading"/>
<label ng-switch-when="date_past" for="values_{{name}}" class="hidden-for-sighted">
{{ I18n.t('js.work_packages.time_relative.days') }}
</label>

@ -24,8 +24,7 @@
target="ColumnContextMenu"
locals="columns, column"
sortable="true"
query="query"
update-results="updateResults()"/>
query="query"/>
<th sort-header ng-repeat="column in columns"
has-dropdown-menu

Loading…
Cancel
Save