[ADF-3887] Different local storages for different ADF apps (#4539)

* [ADF-3887] Different local storages for different ADF apps

* [ADF-3887] Add documentation

* [ADF-3887] Add unit tests and improve code

* [ADF-3887] Add unit tests

* [ADF-3887] Fix e2e tests

* fix test

* fix test

* Update storage.service.md
This commit is contained in:
davidcanonieto 2019-04-08 15:23:46 +01:00 committed by Eugenio Romano
parent f89bf507f5
commit dee63e3f3b
22 changed files with 181 additions and 43 deletions

View File

@ -23,6 +23,7 @@
"redirectUriLogout": "/logout"
},
"application": {
"storagePrefix": "ADF",
"name": "Alfresco ADF Application",
"copyright": "© 2016 - 2018 Alfresco Software, Inc. All Rights Reserved."
},

View File

@ -44,6 +44,23 @@ more widely supported by browsers and can be set to expire after a certain date.
If local storage is not available then non-persistent memory storage within the app is
used instead.
## Storage specific to an ADF app
If you are using multiple ADF apps, you might want to set the following configuration so that the apps have specific storages and are independent of others when setting and getting data from the local storage.
In order to achieve this, you will only need to set your app identifier under the `storagePrefix` property of the app in your `app.config.json` file.
```json
"application": {
"storagePrefix": "ADF_Identifier",
"name": "Your app name",
"copyright": "Your copyright message"
}
```
**Important note**
This identifier must be unique to the app to guarantee that it has its own storage.
## See also
- [Cookie service](cookie.service.md)

View File

@ -16,7 +16,7 @@
*/
import { AlfrescoApiServiceMock, AppConfigService, ContentService,
StorageService, setupTestBed, CoreModule, TranslationMock
setupTestBed, CoreModule, TranslationMock
} from '@alfresco/adf-core';
import { FileNode, FolderNode } from '../../mock';
import { ContentActionHandler } from '../models/content-action.model';
@ -37,7 +37,7 @@ describe('DocumentActionsService', () => {
beforeEach(() => {
const contentService = new ContentService(null, null, null, null);
const alfrescoApiService = new AlfrescoApiServiceMock(new AppConfigService(null), new StorageService());
const alfrescoApiService = new AlfrescoApiServiceMock(new AppConfigService(null));
documentListService = new DocumentListService(contentService, alfrescoApiService, null, null);
service = new DocumentActionsService(null, null, new TranslationMock(), documentListService, contentService);

View File

@ -16,7 +16,7 @@
*/
import { AlfrescoApiServiceMock, AlfrescoApiService,
AppConfigService, StorageService, ContentService, setupTestBed, CoreModule, LogService, AppConfigServiceMock } from '@alfresco/adf-core';
AppConfigService, ContentService, setupTestBed, CoreModule, LogService, AppConfigServiceMock } from '@alfresco/adf-core';
import { DocumentListService } from './document-list.service';
import { CustomResourcesService } from './custom-resources.service';
@ -70,7 +70,7 @@ describe('DocumentListService', () => {
beforeEach(() => {
const logService = new LogService(new AppConfigServiceMock(null));
const contentService = new ContentService(null, null, null, null);
alfrescoApiService = new AlfrescoApiServiceMock(new AppConfigService(null), new StorageService());
alfrescoApiService = new AlfrescoApiServiceMock(new AppConfigService(null));
const customActionService = new CustomResourcesService(alfrescoApiService, logService);
service = new DocumentListService(contentService, alfrescoApiService, logService, customActionService);
jasmine.Ajax.install();

View File

@ -16,7 +16,7 @@
*/
import { TestBed } from '@angular/core/testing';
import { AlfrescoApiServiceMock, AppConfigService, StorageService, ContentService, setupTestBed, CoreModule, TranslationMock } from '@alfresco/adf-core';
import { AlfrescoApiServiceMock, AppConfigService, ContentService, setupTestBed, CoreModule, TranslationMock } from '@alfresco/adf-core';
import { Observable } from 'rxjs';
import { FileNode, FolderNode } from '../../mock';
import { ContentActionHandler } from '../models/content-action.model';
@ -39,7 +39,7 @@ describe('FolderActionsService', () => {
appConfig.config.ecmHost = 'http://localhost:9876/ecm';
const contentService = new ContentService(null, null, null, null);
const alfrescoApiService = new AlfrescoApiServiceMock(new AppConfigService(null), new StorageService());
const alfrescoApiService = new AlfrescoApiServiceMock(new AppConfigService(null));
documentListService = new DocumentListService(contentService, alfrescoApiService, null, null);
service = new FolderActionsService(null, documentListService, contentService, new TranslationMock());
});

View File

@ -37,7 +37,8 @@ export enum AppConfigValues {
LOG_LEVEL = 'logLevel',
LOGIN_ROUTE = 'loginRoute',
DISABLECSRF = 'disableCSRF',
AUTH_WITH_CREDENTIALS = 'auth.withCredentials'
AUTH_WITH_CREDENTIALS = 'auth.withCredentials',
APPLICATION = 'application'
}
export enum Status {

View File

@ -17,21 +17,26 @@
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { StorageService } from '../services/storage.service';
import { AppConfigService, AppConfigValues } from '../app-config/app-config.service';
@Injectable()
export class DebugAppConfigService extends AppConfigService {
constructor(private storage: StorageService, http: HttpClient) {
constructor(http: HttpClient) {
super(http);
}
/** @override */
get<T>(key: string, defaultValue?: T): T {
if (key === AppConfigValues.OAUTHCONFIG) {
return <T> (JSON.parse(this.storage.getItem(key)) || super.get<T>(key, defaultValue));
return <T> (JSON.parse(this.getItem(key)) || super.get<T>(key, defaultValue));
} else if (key === AppConfigValues.APPLICATION) {
return undefined;
} else {
return <T> (<any> this.storage.getItem(key) || super.get<T>(key, defaultValue));
return <T> (<any> this.getItem(key) || super.get<T>(key, defaultValue));
}
}
getItem(key: string): string | null {
return localStorage.getItem(key);
}
}

View File

@ -20,7 +20,6 @@ import { fakeAsync, tick } from '@angular/core/testing';
import { NodeFavoriteDirective } from './node-favorite.directive';
import { AlfrescoApiServiceMock } from '../mock/alfresco-api.service.mock';
import { AppConfigService } from '../app-config/app-config.service';
import { StorageService } from '../services/storage.service';
import { setupTestBed } from '../testing/setupTestBed';
import { CoreTestingModule } from '../testing/core.testing.module';
@ -34,7 +33,7 @@ describe('NodeFavoriteDirective', () => {
});
beforeEach(() => {
alfrescoApiService = new AlfrescoApiServiceMock(new AppConfigService(null), new StorageService());
alfrescoApiService = new AlfrescoApiServiceMock(new AppConfigService(null));
directive = new NodeFavoriteDirective( alfrescoApiService);
});

View File

@ -17,16 +17,14 @@
import { Injectable } from '@angular/core';
import { AppConfigService } from '../app-config/app-config.service';
import { StorageService } from '../services/storage.service';
import { AlfrescoApiService } from '../services/alfresco-api.service';
/* tslint:disable:adf-file-name */
@Injectable()
export class AlfrescoApiServiceMock extends AlfrescoApiService {
constructor(protected appConfig: AppConfigService,
protected storage: StorageService) {
super(appConfig, storage);
constructor(protected appConfig: AppConfigService) {
super(appConfig);
if (!this.alfrescoApi) {
this.initAlfrescoApi();
}

View File

@ -25,7 +25,6 @@ import {
} from '@alfresco/js-api';
import { AlfrescoApiCompatibility, AlfrescoApiConfig } from '@alfresco/js-api';
import { AppConfigService, AppConfigValues } from '../app-config/app-config.service';
import { StorageService } from './storage.service';
import { Subject } from 'rxjs';
import { OauthConfigModel } from '../models/oauth-config.model';
@ -96,8 +95,7 @@ export class AlfrescoApiService {
return this.getInstance().core.groupsApi;
}
constructor(protected appConfig: AppConfigService,
protected storage: StorageService) {
constructor(protected appConfig: AppConfigService) {
}
async load() {

View File

@ -0,0 +1,100 @@
/*!
* @license
* Copyright 2019 Alfresco Software, Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { TestBed } from '@angular/core/testing';
import { AppConfigService } from '../app-config/app-config.service';
import { StorageService } from './storage.service';
import { setupTestBed } from '../testing/setupTestBed';
import { CoreTestingModule } from '../testing/core.testing.module';
import { AppConfigServiceMock } from '../mock/app-config.service.mock';
describe('StorageService', () => {
let storage: StorageService;
let appConfig: AppConfigServiceMock;
const key = 'test_key';
const value = 'test_value';
setupTestBed({
imports: [CoreTestingModule],
providers: [
{ provide: AppConfigService, useClass: AppConfigServiceMock }
]
});
beforeEach(() => {
appConfig = TestBed.get(AppConfigService);
appConfig.config = {
application: {
storagePrefix: 'ADF_APP'
}
};
storage = TestBed.get(StorageService);
});
it('should get the prefix for the storage from app config', (done) => {
appConfig.load().then(() => {
expect(storage.storagePrefix).toBe('ADF_APP_');
done();
});
});
it('should set an empty prefix when the it is not defined in the app config', (done) => {
appConfig.config.application.storagePrefix = '';
appConfig.load().then(() => {
expect(storage.storagePrefix).toBe('');
done();
});
});
it('should set a property with the prefix in the local storage', (done) => {
storage.clear();
appConfig.load().then(() => {
storage.setItem(key, value);
const storageKey = localStorage.key(0);
expect(storageKey).toBe('ADF_APP_' + key);
expect(localStorage.getItem(storageKey)).toBe(value);
done();
});
});
it('should set a property without a prefix in the local storage', (done) => {
storage.clear();
appConfig.config.application.storagePrefix = '';
appConfig.load().then(() => {
storage.setItem(key, value);
const storageKey = localStorage.key(0);
expect(storageKey).toBe(key);
expect(localStorage.getItem(storageKey)).toBe(value);
done();
});
});
it('should be able to get a property from the local storage', (done) => {
storage.clear();
appConfig.load().then(() => {
storage.setItem(key, value);
expect(storage.getItem(key)).toBe(value);
done();
});
});
});

View File

@ -16,6 +16,7 @@
*/
import { Injectable } from '@angular/core';
import { AppConfigService } from '../app-config/app-config.service';
@Injectable({
providedIn: 'root'
@ -24,9 +25,11 @@ export class StorageService {
private memoryStore: { [key: string]: any } = {};
private useLocalStorage: boolean = false;
storagePrefix: string;
constructor() {
constructor(private appConfigService: AppConfigService) {
this.useLocalStorage = this.storageAvailable('localStorage');
this.appConfigService.onLoad.subscribe(this.getAppPrefix.bind(this));
}
/**
@ -36,9 +39,9 @@ export class StorageService {
*/
getItem(key: string): string | null {
if (this.useLocalStorage) {
return localStorage.getItem(key);
return localStorage.getItem(this.storagePrefix + key);
} else {
return this.memoryStore.hasOwnProperty(key) ? this.memoryStore[key] : null;
return this.memoryStore.hasOwnProperty(this.storagePrefix + key) ? this.memoryStore[this.storagePrefix + key] : null;
}
}
@ -49,9 +52,9 @@ export class StorageService {
*/
setItem(key: string, data: string) {
if (this.useLocalStorage) {
localStorage.setItem(key, data);
localStorage.setItem(this.storagePrefix + key, data);
} else {
this.memoryStore[key] = data.toString();
this.memoryStore[this.storagePrefix + key] = data.toString();
}
}
@ -70,9 +73,9 @@ export class StorageService {
*/
removeItem(key: string) {
if (this.useLocalStorage) {
localStorage.removeItem(key);
localStorage.removeItem(this.storagePrefix + key);
} else {
delete this.memoryStore[key];
delete this.memoryStore[this.storagePrefix + key];
}
}
@ -83,7 +86,7 @@ export class StorageService {
*/
hasItem(key: string): boolean {
if (this.useLocalStorage) {
return localStorage.getItem(key) ? true : false;
return localStorage.getItem(this.storagePrefix + key) ? true : false;
} else {
return this.memoryStore.hasOwnProperty(key);
}
@ -101,4 +104,18 @@ export class StorageService {
}
}
/**
* Sets the prefix that is used for the local storage of the app
* It assigns the string that is defined i the app config,
* empty prefix otherwise.
*/
getAppPrefix() {
const appConfiguration = this.appConfigService.get<any>('application');
if (appConfiguration && appConfiguration.storagePrefix) {
this.storagePrefix = appConfiguration.storagePrefix + '_';
} else {
this.storagePrefix = '';
}
}
}

View File

@ -153,6 +153,7 @@ export class UserPreferencesService {
*/
setStoragePrefix(value: string) {
this.storage.setItem('USER_PROFILE', value || 'GUEST');
this.initUserPreferenceStatus();
}
/**

View File

@ -17,7 +17,7 @@
import { async } from '@angular/core/testing';
import { setupTestBed } from '@alfresco/adf-core';
import { fakeProcessCloudList } from '../mock/process-list-service.mock';
import { AlfrescoApiServiceMock, LogService, AppConfigService, StorageService, CoreModule } from '@alfresco/adf-core';
import { AlfrescoApiServiceMock, LogService, AppConfigService, CoreModule } from '@alfresco/adf-core';
import { ProcessListCloudService } from './process-list-cloud.service';
import { ProcessQueryCloudRequestModel } from '../models/process-cloud-query-request.model';
@ -62,7 +62,7 @@ describe('Activiti ProcessList Cloud Service', () => {
});
beforeEach(async(() => {
alfrescoApiMock = new AlfrescoApiServiceMock(new AppConfigService(null), new StorageService());
alfrescoApiMock = new AlfrescoApiServiceMock(new AppConfigService(null));
service = new ProcessListCloudService(alfrescoApiMock,
new AppConfigService(null),
new LogService(new AppConfigService(null)));

View File

@ -17,7 +17,7 @@
import { async, TestBed } from '@angular/core/testing';
import { setupTestBed, IdentityUserService } from '@alfresco/adf-core';
import { AlfrescoApiServiceMock, LogService, AppConfigService, StorageService, CoreModule } from '@alfresco/adf-core';
import { AlfrescoApiServiceMock, LogService, AppConfigService, CoreModule } from '@alfresco/adf-core';
import { TaskCloudService } from './task-cloud.service';
import { taskCompleteCloudMock } from '../task-header/mocks/fake-complete-task.mock';
import { taskDetailsCloudMock } from '../task-header/mocks/task-details-cloud.mock';
@ -68,7 +68,7 @@ describe('Task Cloud Service', () => {
});
beforeEach(async(() => {
alfrescoApiMock = new AlfrescoApiServiceMock(new AppConfigService(null), new StorageService() );
alfrescoApiMock = new AlfrescoApiServiceMock(new AppConfigService(null));
identityUserService = TestBed.get(IdentityUserService);
spyOn(identityUserService, 'getCurrentUserInfo').and.returnValue(cloudMockUser);
service = new TaskCloudService(alfrescoApiMock,

View File

@ -18,7 +18,7 @@
import { async } from '@angular/core/testing';
import { setupTestBed } from '@alfresco/adf-core';
import { fakeTaskCloudList } from '../mock/fakeTaskResponseMock';
import { AlfrescoApiServiceMock, LogService, AppConfigService, StorageService, CoreModule } from '@alfresco/adf-core';
import { AlfrescoApiServiceMock, LogService, AppConfigService, CoreModule } from '@alfresco/adf-core';
import { TaskListCloudService } from './task-list-cloud.service';
import { TaskQueryCloudRequestModel } from '../models/filter-cloud-model';
@ -64,7 +64,7 @@ describe('Activiti TaskList Cloud Service', () => {
});
beforeEach(async(() => {
alfrescoApiMock = new AlfrescoApiServiceMock(new AppConfigService(null), new StorageService() );
alfrescoApiMock = new AlfrescoApiServiceMock(new AppConfigService(null));
service = new TaskListCloudService(alfrescoApiMock,
new AppConfigService(null),
new LogService(new AppConfigService(null)));

View File

@ -43,6 +43,7 @@ module.exports = function (config) {
'/assets/': '/base/lib/process-services/assets/',
'/base/assets/': '/base/lib/process-services/assets/',
'/assets/adf-core/i18n/en.json': '/base/lib/core/i18n/en.json',
'/assets/adf-core/i18n/en.json': '/base/lib/core/i18n/en.json',
'/assets/adf-content-services/i18n/en.json': '/base/lib/content-services/i18n/en.json',
'/assets/adf-process-services/i18n/en-GB.json': '/base/lib/process-services/i18n/en.json',
'/app.config.json': '/base/lib/config/app.config.json'

View File

@ -19,7 +19,7 @@ import { async } from '@angular/core/testing';
import { mockError, fakeProcessFilters } from '../../mock';
import { FilterProcessRepresentationModel } from '../models/filter-process.model';
import { ProcessFilterService } from './process-filter.service';
import { AlfrescoApiServiceMock, AlfrescoApiService, AppConfigService, StorageService, setupTestBed, CoreModule } from '@alfresco/adf-core';
import { AlfrescoApiServiceMock, AlfrescoApiService, AppConfigService, setupTestBed, CoreModule } from '@alfresco/adf-core';
declare let jasmine: any;
@ -36,7 +36,7 @@ describe('Process filter', () => {
});
beforeEach(() => {
apiService = new AlfrescoApiServiceMock(new AppConfigService(null), new StorageService() );
apiService = new AlfrescoApiServiceMock(new AppConfigService(null));
service = new ProcessFilterService(apiService);
alfrescoApi = apiService.getInstance();
});

View File

@ -21,7 +21,7 @@ import { mockError, fakeProcessDef, fakeTasksList } from '../../mock';
import { ProcessFilterParamRepresentationModel } from '../models/filter-process.model';
import { ProcessInstanceVariable } from '../models/process-instance-variable.model';
import { ProcessService } from './process.service';
import { AlfrescoApiService, AlfrescoApiServiceMock, AppConfigService, StorageService, setupTestBed, CoreModule } from '@alfresco/adf-core';
import { AlfrescoApiService, AlfrescoApiServiceMock, AppConfigService, setupTestBed, CoreModule } from '@alfresco/adf-core';
declare let moment: any;
@ -38,7 +38,7 @@ describe('ProcessService', () => {
});
beforeEach(() => {
apiService = new AlfrescoApiServiceMock(new AppConfigService(null), new StorageService() );
apiService = new AlfrescoApiServiceMock(new AppConfigService(null));
service = new ProcessService(apiService);
alfrescoApi = apiService.getInstance();
});

View File

@ -19,7 +19,7 @@ import { async } from '@angular/core/testing';
import { fakeAppFilter, fakeAppPromise, fakeFilters } from '../../mock';
import { FilterRepresentationModel } from '../models/filter.model';
import { TaskFilterService } from './task-filter.service';
import { AlfrescoApiServiceMock, LogService, AppConfigService, StorageService, setupTestBed, CoreModule } from '@alfresco/adf-core';
import { AlfrescoApiServiceMock, LogService, AppConfigService, setupTestBed, CoreModule } from '@alfresco/adf-core';
declare let jasmine: any;
@ -34,7 +34,7 @@ describe('Activiti Task filter Service', () => {
});
beforeEach(async(() => {
service = new TaskFilterService(new AlfrescoApiServiceMock(new AppConfigService(null), new StorageService()), new LogService(new AppConfigService(null)));
service = new TaskFilterService(new AlfrescoApiServiceMock(new AppConfigService(null)), new LogService(new AppConfigService(null)));
jasmine.Ajax.install();
}));

View File

@ -35,7 +35,7 @@ import {
import { FilterRepresentationModel, TaskQueryRequestRepresentationModel } from '../models/filter.model';
import { TaskDetailsModel } from '../models/task-details.model';
import { TaskListService } from './tasklist.service';
import { AlfrescoApiServiceMock, LogService, AppConfigService, StorageService } from '@alfresco/adf-core';
import { AlfrescoApiServiceMock, LogService, AppConfigService } from '@alfresco/adf-core';
declare let jasmine: any;
@ -50,7 +50,7 @@ describe('Activiti TaskList Service', () => {
});
beforeEach(async(() => {
service = new TaskListService(new AlfrescoApiServiceMock(new AppConfigService(null), new StorageService() ), new LogService(new AppConfigService(null)));
service = new TaskListService(new AlfrescoApiServiceMock(new AppConfigService(null)), new LogService(new AppConfigService(null)));
jasmine.Ajax.install();
}));

View File

@ -26,7 +26,7 @@ export class TestingAlfrescoApiService extends AlfrescoApiService {
};
constructor(public appConfig: AppConfigService) {
super(null, null);
super(null);
const oauth = Object.assign({}, this.appConfig.get<any>(AppConfigValues.OAUTHCONFIG, null));
this.config = new AlfrescoApiConfig({
provider: this.appConfig.get<string>(AppConfigValues.PROVIDERS),