initial splitoff of watchers into separate service

Signed-off-by: Florian Kraft <f.kraft@finn.de>
pull/3434/head
Florian Kraft 9 years ago committed by Jens Ulferts
parent 22015141d3
commit 0e3dc2c14c
  1. 46
      frontend/app/templates/work_packages/tabs/watchers.html
  2. 257
      frontend/app/work_packages/controllers/details-tab-watchers-controller.js
  3. 36
      frontend/app/work_packages/controllers/index.js
  4. 5
      frontend/app/work_packages/services/index.js
  5. 74
      frontend/app/work_packages/services/watchers-service.js

@ -1,37 +1,11 @@
<div id="detail-panel-watchers">
<ul class="watchers-list" ng-if="watchers.length">
<li ng-repeat="watcher in watchers">
<user-field focus="$index == focusElementIndex" user="watcher"></user-field>
<accessible-by-keyboard execute="deleteWatcher(watcher)" ng-if="watcher.links.removeWatcher"
link-class="detail-panel-watchers-delete-watcher-link">
<icon-wrapper icon-name="close"
icon-title="{{I18n.t('js.button_delete_watcher')}}"
class="detail-panel-watchers-delete-watcher-icon"></icon-wrapper>
</accessible-by-keyboard>
</li>
</ul>
<div id="detail-panel-watchers">
<div id="detail-panel-watchers-add-watcher" ng-if="workPackage.link('addWatcher') && availableWatchers.length">
<h3 ng-bind="I18n.t('js.button_add_watcher')"/>
<fieldset>
<legend ng-bind="I18n.t('js.button_add_watcher')" class="hidden-for-sighted"/>
<div id="watchers_selected_list" class="hidden-for-sighted">{{watcherListString()}}</div>
<ui-select
multiple
title="{{ I18n.t('js.button_add_watcher') }}"
aria-labelledby="watchers_selected_list"
ng-model="watcher.selected"
theme="select2">
<ui-select-match
placeholder="{{ I18n.t('js.label_select_watcher') }}">
{{ $item.props.name }}
</ui-select-match>
<ui-select-choices
repeat="item in availableWatchers | filter: $select.search">
<div ng-bind-html="item.props.name | highlight: $select.search"></div>
</ui-select-choices>
</ui-select>
</fieldset>
</div>
</div>
<ul class="watchers-list" ng-if="watchers.length">
<li ng-repeat="watcher in watchers">
<a data-ng-href="{{watcher._links.self.href}}">{{watcher._links.self.title}}</a>
</li>
<li data-ng-show="adding">
</li>
</ul>
<a data-ng-click="adding = true" data-ng-hide="adding">Add watcher</a>
</div>

@ -26,132 +26,147 @@
// See doc/COPYRIGHT.rdoc for more details.
//++
module.exports = function($scope, $filter, $timeout, I18n, ADD_WATCHER_SELECT_INDEX) {
$scope.I18n = I18n;
$scope.focusElementIndex;
$scope.watcher = { selected: null };
$scope.watcher.selected = $scope.watchers;
fetchAvailableWatchers();
$scope.watcherListString = function() {
return _.map($scope.watcher.selected, function(item) {
return item.props.name;
}).join(', ');
};
/**
* @name getResourceIdentifier
* @function
*
* @description
* Returns the resource identifier of an API resource retrieved via hyperagent
*
* @param {Object} resource The resource object
*
* @returns {String} identifier
*/
function getResourceIdentifier(resource) {
// TODO move to helper
return resource.links.self.href;
}
/**
* @name getFilteredCollection
* @function
*
* @description
* Filters collection of HAL resources by entries listed in resourcesToBeFilteredOut
*
* @param {Array} collection Array of resources retrieved via hyperagent
* @param {Array} resourcesToBeFilteredOut Entries to be filtered out
*
* @returns {Array} filtered collection
*/
function getFilteredCollection(collection, resourcesToBeFilteredOut) {
return collection.filter(function(resource) {
return resourcesToBeFilteredOut.map(getResourceIdentifier).indexOf(getResourceIdentifier(resource)) === -1;
module.exports = function($scope, I18n, WatchersService, ADD_WATCHER_SELECT_INDEX) {
'use strict';
var fetchWatchers = function(loading) {
$scope.error = false;
$scope.loading = angular.isUndefined(loading) ? true : false;
WatchersService.all($scope.workPackage).then(function(users) {
$scope.watchers = users.watching;
$scope.available = users.available;
}, function() {
$scope.watchers = [];
$scope.available = [];
$scope.error = true;
}).finally(function() {
$scope.loading = false;
});
}
function fetchAvailableWatchers() {
if ($scope.workPackage.links.availableWatchers === undefined) {
$scope.availableWatchers = [];
return;
}
$scope.workPackage.links.availableWatchers
.fetch()
.then(function(data) {
// Temporarily filter out watchers already assigned to the work package on the client-side
$scope.availableWatchers = getFilteredCollection(data.embedded.elements, $scope.watchers);
// TODO do filtering on the API side and replace the update of the
// available watchers with the code provided in the following line
// $scope.availableWatchers = data.embedded.elements;
});
}
function addWatcher(newValue, oldValue) {
if (newValue && newValue !== oldValue) {
var user = newValue[newValue.length - 1],
href = user ? user.links.self.href : null;
if (href) {
var data = JSON.stringify({ user: { href: href } });
$scope.workPackage.link('addWatcher', {})
.fetch({ajax: {
method: 'POST',
contentType: 'application/json; charset=utf-8',
data: data
}})
.then(addWatcherSuccess, $scope.outputError);
}
}
}
function addWatcherSuccess() {
$scope.outputMessage(I18n.t("js.label_watcher_added_successfully"));
$scope.refreshWorkPackage();
$scope.watcher.selected = null;
$scope.focusElementIndex = ADD_WATCHER_SELECT_INDEX;
}
fetchWatchers();
$scope.deleteWatcher = function(watcher) {
watcher.links.removeWatcher
.fetch({ ajax: watcher.links.removeWatcher.props })
.then(deleteWatcherSuccess(watcher), $scope.outputError);
};
function deleteWatcherSuccess(watcher) {
$scope.outputMessage(I18n.t("js.label_watcher_deleted_successfully"));
removeWatcherFromList(watcher);
}
function removeWatcherFromList(watcher) {
var index = $scope.watchers.indexOf(watcher);
if (index >= 0) {
$scope.watchers.splice(index, 1);
updateWatcherFocus(index);
$scope.$emit('workPackageRefreshRequired');
}
}
function updateWatcherFocus(index) {
if ($scope.watchers.length == 0) {
$scope.focusElementIndex = ADD_WATCHER_SELECT_INDEX;
} else {
$scope.focusElementIndex = (index < $scope.watchers.length) ? index : $scope.watchers.length - 1;
}
$scope.I18n = I18n;
$timeout(function() {
$scope.$broadcast('updateFocus');
});
$scope.add = function() {
$scope.adding = true;
}
$scope.$watch('watchers.length', fetchAvailableWatchers);
$scope.$watch('watcher.selected', addWatcher);
// $scope.watcherListString = function() {
// return _.map($scope.watcher.selected, function(item) {
// return item.props.name;
// }).join(', ');
// };
// /**
// * @name getResourceIdentifier
// * @function
// *
// * @description
// * Returns the resource identifier of an API resource retrieved via hyperagent
// *
// * @param {Object} resource The resource object
// *
// * @returns {String} identifier
// */
// function getResourceIdentifier(resource) {
// // TODO move to helper
// return resource.links.self.href;
// }
// /**
// * @name getFilteredCollection
// * @function
// *
// * @description
// * Filters collection of HAL resources by entries listed in resourcesToBeFilteredOut
// *
// * @param {Array} collection Array of resources retrieved via hyperagent
// * @param {Array} resourcesToBeFilteredOut Entries to be filtered out
// *
// * @returns {Array} filtered collection
// */
// function getFilteredCollection(collection, resourcesToBeFilteredOut) {
// return collection.filter(function(resource) {
// return resourcesToBeFilteredOut.map(getResourceIdentifier).indexOf(getResourceIdentifier(resource)) === -1;
// });
// }
// function fetchAvailableWatchers() {
// if ($scope.workPackage.links.availableWatchers === undefined) {
// $scope.availableWatchers = [];
// return;
// }
// $scope.workPackage.links.availableWatchers
// .fetch()
// .then(function(data) {
// // Temporarily filter out watchers already assigned to the work package on the client-side
// $scope.availableWatchers = getFilteredCollection(data.embedded.elements, $scope.watchers);
// // TODO do filtering on the API side and replace the update of the
// // available watchers with the code provided in the following line
// // $scope.availableWatchers = data.embedded.elements;
// });
// }
// function addWatcher(newValue, oldValue) {
// if (newValue && newValue !== oldValue) {
// var user = newValue[newValue.length - 1],
// href = user ? user.links.self.href : null;
// if (href) {
// var data = JSON.stringify({ user: { href: href } });
// $scope.workPackage.link('addWatcher', {})
// .fetch({ajax: {
// method: 'POST',
// contentType: 'application/json; charset=utf-8',
// data: data
// }})
// .then(addWatcherSuccess, $scope.outputError);
// }
// }
// }
// function addWatcherSuccess() {
// $scope.outputMessage(I18n.t("js.label_watcher_added_successfully"));
// $scope.refreshWorkPackage();
// $scope.watcher.selected = null;
// $scope.focusElementIndex = ADD_WATCHER_SELECT_INDEX;
// }
// $scope.deleteWatcher = function(watcher) {
// watcher.links.removeWatcher
// .fetch({ ajax: watcher.links.removeWatcher.props })
// .then(deleteWatcherSuccess(watcher), $scope.outputError);
// };
// function deleteWatcherSuccess(watcher) {
// $scope.outputMessage(I18n.t("js.label_watcher_deleted_successfully"));
// removeWatcherFromList(watcher);
// }
// function removeWatcherFromList(watcher) {
// var index = $scope.watchers.indexOf(watcher);
// if (index >= 0) {
// $scope.watchers.splice(index, 1);
// updateWatcherFocus(index);
// $scope.$emit('workPackageRefreshRequired');
// }
// }
// function updateWatcherFocus(index) {
// if ($scope.watchers.length == 0) {
// $scope.focusElementIndex = ADD_WATCHER_SELECT_INDEX;
// } else {
// $scope.focusElementIndex = (index < $scope.watchers.length) ? index : $scope.watchers.length - 1;
// }
// $timeout(function() {
// $scope.$broadcast('updateFocus');
// });
// }
};

@ -47,30 +47,30 @@ angular.module('openproject.workPackages.controllers')
require('./details-tab-overview-controller')
])
.constant('ADD_WATCHER_SELECT_INDEX', -1)
.controller('DetailsTabWatchersController', ['$scope',
'$filter',
'$timeout',
.controller('DetailsTabWatchersController', [
'$scope',
'I18n',
'WatchersService',
'ADD_WATCHER_SELECT_INDEX', require('./details-tab-watchers-controller')
])
.constant('RELATION_TYPES', {
relatedTo: "Relation::Relates",
duplicates: "Relation::Duplicates",
duplicated: "Relation::Duplicated",
blocks: "Relation::Blocks",
blocked: "Relation::Blocked",
precedes: "Relation::Precedes",
follows: "Relation::Follows"
relatedTo: 'Relation::Relates',
duplicates: 'Relation::Duplicates',
duplicated: 'Relation::Duplicated',
blocks: 'Relation::Blocks',
blocked: 'Relation::Blocked',
precedes: 'Relation::Precedes',
follows: 'Relation::Follows'
})
.constant('RELATION_IDENTIFIERS', {
parent: "parent",
relatedTo: "relates",
duplicates: "duplicates",
duplicated: "duplicated",
blocks: "blocks",
blocked: "blocked",
precedes: "precedes",
follows: "follows"
parent: 'parent',
relatedTo: 'relates',
duplicates: 'duplicates',
duplicated: 'duplicated',
blocks: 'blocks',
blocked: 'blocked',
precedes: 'precedes',
follows: 'follows'
})
.controller('WorkPackageDetailsController', [
'$scope',

@ -82,4 +82,9 @@ angular.module('openproject.workPackages.services')
'$timeout',
'$http',
require('./work-package-attachments-service')
])
.service('WatchersService', [
'$http',
'$q',
require('./watchers-service.js')
]);

@ -0,0 +1,74 @@
//-- copyright
// OpenProject is a project management system.
// Copyright (C) 2012-2015 the OpenProject Foundation (OPF)
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License version 3.
//
// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
// Copyright (C) 2006-2013 Jean-Philippe Lang
// Copyright (C) 2010-2013 the ChiliProject Team
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// See doc/COPYRIGHT.rdoc for more details.
//++
module.exports = function($http, $q) {
'use strict';
var getWatchers = function(path) {
return function() {
var watchers = $q.defer();
$http.get(path).success(function(data) {
watchers.resolve(data._embedded.elements);
}).error(function(err) {
watchers.reject(err);
});
return watchers.promise;
};
};
var load = function(workPackage) {
var path = workPackage.links.watchers.url();
return getWatchers(path)();
},
available = function(workPackage) {
var path = workPackage.links.availableWatchers.url();
return getWatchers(path)();
},
all = function(workPackage) {
var watchers = $q.defer();
$q.all([load(workPackage), available(workPackage)]).then(function(allWatchers) {
var watching = allWatchers[0],
available = _.filter(allWatchers[1], function(user) {
return _.findIndex(allWatchers[0], function(matchedUser) {
return matchedUser.id !== user.id;
}) > -1;
})
watchers.resolve({ watching: watching, available: available });
}, function(err) {
watchers.reject(err);
});
return watchers.promise;
};
return {
load: load,
available: available,
all: all
};
};
Loading…
Cancel
Save