Experiment with angular-cache

One prominent example where we can employ frontend-side caching
is the fetching of associated users in activities.

For a large number of comments of the same user, that user is retrieved
once per activity, which causes a drastic overhead.

This commit introduces a simple wrapper around angular-cache to store
values in the sessionStorage.
pull/3553/head
Oliver Günther 9 years ago
parent 5fde63dfff
commit 64d5b399c5
  1. 4
      frontend/app/openproject-app.js
  2. 77
      frontend/app/services/cache-service.js
  3. 8
      frontend/app/services/index.js
  4. 14
      frontend/app/services/user-service.js
  5. 1
      frontend/app/work_packages/activities/index.js
  6. 3
      frontend/app/work_packages/activities/user-activity-directive.js
  7. 1
      frontend/bower.json
  8. 2
      frontend/tests/unit/tests/work_packages/activities/user-activity-directive-test.js

@ -59,6 +59,7 @@ require('angular-busy/dist/angular-busy.css');
require('angular-context-menu'); require('angular-context-menu');
require('angular-elastic'); require('angular-elastic');
require('angular-cache');
require('mousetrap'); require('mousetrap');
require('ngFileUpload'); require('ngFileUpload');
@ -190,7 +191,8 @@ var openprojectApp = angular.module('openproject', [
'cgBusy', 'cgBusy',
'openproject.api', 'openproject.api',
'openproject.templates', 'openproject.templates',
'monospaced.elastic' 'monospaced.elastic',
'angular-cache'
]); ]);
window.appBasePath = jQuery('meta[name=app_base_path]').attr('content') || window.appBasePath = jQuery('meta[name=app_base_path]').attr('content') ||

@ -0,0 +1,77 @@
//-- 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(
HALAPIResource,
$http,
$q,
CacheFactory) {
var cacheName = 'openproject-cache';
var _cache = CacheFactory(cacheName, {
maxAge: 30 * 60 * 1000, // 30 mins
storageMode: 'sessionStorage'
});
var CacheService = {
cache: function(key, value) {
_cache.put(key, value);
},
get: function(key) {
return _cache.get(key);
},
loadResource: function(resource, force) {
var deferred = $q.defer(),
key = resource.props.href,
cached = CacheService.get(key);
// Return an existing promise if it exists
// Avoids intermittent requests while a first
// is already underway.
if (cached && !force) {
return cached;
}
var promise = deferred.promise;
CacheService.cache(key, promise);
resource.fetch().then(function(data) {
deferred.resolve(data);
}, function() {
deferred.reject();
});
return promise;
},
};
return CacheService;
};

@ -35,6 +35,13 @@ angular.module('openproject.services')
require('./activity-service') require('./activity-service')
]) ])
.service('AuthorisationService', require('./authorisation-service')) .service('AuthorisationService', require('./authorisation-service'))
.service('CacheService', [
'HALAPIResource',
'$http',
'$q',
'CacheFactory',
require('./cache-service')
])
.service('GroupService', ['$http', 'PathHelper', require('./group-service')]) .service('GroupService', ['$http', 'PathHelper', require('./group-service')])
.service('HookService', require('./hook-service')) .service('HookService', require('./hook-service'))
.service('KeyboardShortcutService', [ .service('KeyboardShortcutService', [
@ -83,6 +90,7 @@ angular.module('openproject.services')
'HALAPIResource', 'HALAPIResource',
'$http', '$http',
'PathHelper', 'PathHelper',
'CacheService',
require('./user-service') require('./user-service')
]) ])
.service('VersionService', ['$http', 'PathHelper', require( .service('VersionService', ['$http', 'PathHelper', require(

@ -26,16 +26,22 @@
// See doc/COPYRIGHT.rdoc for more details. // See doc/COPYRIGHT.rdoc for more details.
//++ //++
module.exports = function(HALAPIResource, $http, PathHelper) { module.exports = function(
HALAPIResource,
var registeredUserIds = [], cachedUsers = {}; $http,
PathHelper,
CacheService) {
var registeredUserIds = [];
var UserService = { var UserService = {
getUser: function(id) { getUser: function(id) {
var path = PathHelper.apiV3UserPath(id), var path = PathHelper.apiV3UserPath(id),
resource = HALAPIResource.setup(path); resource = HALAPIResource.setup(path);
return resource.fetch(); return getUserByResource(resource);
getUserByResource: function(user, force) {
return CacheService.loadResource(user, force);
}, },
getUsers: function(projectIdentifier) { getUsers: function(projectIdentifier) {

@ -39,6 +39,7 @@ angular.module('openproject.workPackages.activities')
'PathHelper', 'PathHelper',
'ActivityService', 'ActivityService',
'UsersHelper', 'UsersHelper',
'UserService',
'ConfigurationService', 'ConfigurationService',
'AutoCompleteHelper', 'AutoCompleteHelper',
'EditableFieldsState', 'EditableFieldsState',

@ -34,6 +34,7 @@ module.exports = function($uiViewScroll,
PathHelper, PathHelper,
ActivityService, ActivityService,
UsersHelper, UsersHelper,
UserService,
ConfigurationService, ConfigurationService,
AutoCompleteHelper, AutoCompleteHelper,
EditableFieldsState, EditableFieldsState,
@ -74,7 +75,7 @@ module.exports = function($uiViewScroll,
scope.userCanQuote = !!scope.workPackage.links.addComment; scope.userCanQuote = !!scope.workPackage.links.addComment;
scope.accessibilityModeEnabled = ConfigurationService.accessibilityModeEnabled(); scope.accessibilityModeEnabled = ConfigurationService.accessibilityModeEnabled();
scope.activity.links.user.fetch().then(function(user) { UserService.getUserByResource(scope.activity.links.user).then(function(user) {
scope.userId = user.props.id; scope.userId = user.props.id;
scope.userName = user.props.name; scope.userName = user.props.name;
scope.userAvatar = user.props.avatar; scope.userAvatar = user.props.avatar;

@ -18,6 +18,7 @@
"angular-truncate": "sparkalow/angular-truncate#fdf60fda265042d12e9414b5354b2cc52f1419de", "angular-truncate": "sparkalow/angular-truncate#fdf60fda265042d12e9414b5354b2cc52f1419de",
"angular-feature-flags": "mjt01/angular-feature-flags", "angular-feature-flags": "mjt01/angular-feature-flags",
"angular-elastic": "2.5.0", "angular-elastic": "2.5.0",
"angular-cache": "~4.3.2",
"jquery-migrate": "~1.2.1", "jquery-migrate": "~1.2.1",
"moment": "~2.10.6", "moment": "~2.10.6",
"moment-timezone": "0.4.x", "moment-timezone": "0.4.x",

@ -63,7 +63,7 @@ describe('userActivity Directive', function() {
describe('element', function() { describe('element', function() {
describe('with a valid user', function(){ describe('with a valid user', function(){
beforeEach(function() { beforeEach(inject(function($q) {
scope.workPackage = { scope.workPackage = {
links: { links: {
addComment: true addComment: true

Loading…
Cancel
Save