From 939e7c5c73ba9906e2488f71e9ef003d000c3baa Mon Sep 17 00:00:00 2001 From: MichalKinas <113341662+MichalKinas@users.noreply.github.com> Date: Mon, 20 Jan 2025 12:03:27 +0100 Subject: [PATCH] =?UTF-8?q?Revert=20"[ACS-9166]=20Migrate=20Saved=20Search?= =?UTF-8?q?es=20to=20preferences=20API=20from=20config=20file=E2=80=A6"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit 184a38a0ef08e2222820e6654ea356579e2f88d1. --- docs/core/services/saved-searches.service.md | 15 +- .../services/saved-searches.service.spec.ts | 87 ++++++----- .../common/services/saved-searches.service.ts | 137 ++++++++---------- .../content-rest-api/api/preferences.api.ts | 21 --- .../content-rest-api/docs/PreferencesApi.md | 41 +----- 5 files changed, 130 insertions(+), 171 deletions(-) diff --git a/docs/core/services/saved-searches.service.md b/docs/core/services/saved-searches.service.md index a5223cc9594..26ba0a7ffa3 100644 --- a/docs/core/services/saved-searches.service.md +++ b/docs/core/services/saved-searches.service.md @@ -1,7 +1,7 @@ # Saved Searches Service -Manages operations related to saving and retrieving user-defined searches. +Manages operations related to saving and retrieving user-defined searches in the Alfresco Process Services (APS) environment. ## Class members @@ -14,7 +14,7 @@ Manages operations related to saving and retrieving user-defined searches. #### getSavedSearches(): [`Observable`](https://rxjs.dev/api/index/class/Observable)`` -Fetches the file with list of saved searches either from a locally cached node ID or by querying the ACS server. Then it reads the file and maps JSON objects into SavedSearches +Fetches the file with list of saved searches either from a locally cached node ID or by querying the APS server. Then it reads the file and maps JSON objects into SavedSearches - **Returns**: - [`Observable`](https://rxjs.dev/api/index/class/Observable)`` - An observable that emits the list of saved searches. @@ -51,3 +51,14 @@ this.savedSearchService.saveSearch(newSearch).subscribe((response) => { console.log('Saved new search:', response); }); ``` + +#### Creating Saved Searches Node + +When the saved searches file does not exist, it will be created: + +```typescript +this.savedSearchService.createSavedSearchesNode('parent-node-id').subscribe((node) => { + console.log('Created config.json node:', node); +}); +``` + diff --git a/lib/content-services/src/lib/common/services/saved-searches.service.spec.ts b/lib/content-services/src/lib/common/services/saved-searches.service.spec.ts index a683926a11b..7f46da31fa7 100644 --- a/lib/content-services/src/lib/common/services/saved-searches.service.spec.ts +++ b/lib/content-services/src/lib/common/services/saved-searches.service.spec.ts @@ -17,9 +17,9 @@ import { TestBed } from '@angular/core/testing'; import { AlfrescoApiService } from '../../services/alfresco-api.service'; -import { AlfrescoApiServiceMock } from '../../mock'; import { NodeEntry } from '@alfresco/js-api'; import { SavedSearchesService } from './saved-searches.service'; +import { AlfrescoApiServiceMock } from '@alfresco/adf-content-services'; import { HttpClientTestingModule } from '@angular/common/http/testing'; import { AuthenticationService } from '@alfresco/adf-core'; import { Subject } from 'rxjs'; @@ -28,9 +28,10 @@ describe('SavedSearchesService', () => { let service: SavedSearchesService; let authService: AuthenticationService; let testUserName: string; + let getNodeContentSpy: jasmine.Spy; const testNodeId = 'test-node-id'; - const LOCAL_STORAGE_KEY = 'saved-searches-test-user-migrated'; + const SAVED_SEARCHES_NODE_ID = 'saved-searches-node-id__'; const SAVED_SEARCHES_CONTENT = JSON.stringify([ { name: 'Search 1', description: 'Description 1', encodedUrl: 'url1', order: 0 }, { name: 'Search 2', description: 'Description 2', encodedUrl: 'url2', order: 1 } @@ -58,28 +59,23 @@ describe('SavedSearchesService', () => { service = TestBed.inject(SavedSearchesService); authService = TestBed.inject(AuthenticationService); spyOn(service.nodesApi, 'getNode').and.callFake(() => Promise.resolve({ entry: { id: testNodeId } } as NodeEntry)); - spyOn(service.nodesApi, 'getNodeContent').and.callFake(() => createBlob()); - spyOn(service.nodesApi, 'deleteNode').and.callFake(() => Promise.resolve()); - spyOn(service.preferencesApi, 'getPreference').and.callFake(() => - Promise.resolve({ entry: { id: 'saved-searches', value: SAVED_SEARCHES_CONTENT } }) - ); - spyOn(service.preferencesApi, 'updatePreference').and.callFake(() => - Promise.resolve({ entry: { id: 'saved-searches', value: SAVED_SEARCHES_CONTENT } }) - ); + spyOn(service.nodesApi, 'createNode').and.callFake(() => Promise.resolve({ entry: { id: 'new-node-id' } })); + spyOn(service.nodesApi, 'updateNodeContent').and.callFake(() => Promise.resolve({ entry: {} } as NodeEntry)); + getNodeContentSpy = spyOn(service.nodesApi, 'getNodeContent').and.callFake(() => createBlob()); }); afterEach(() => { - localStorage.removeItem(LOCAL_STORAGE_KEY); + localStorage.removeItem(SAVED_SEARCHES_NODE_ID + testUserName); }); - it('should retrieve saved searches from the preferences API', (done) => { + it('should retrieve saved searches from the config.json file', (done) => { spyOn(authService, 'getUsername').and.callFake(() => testUserName); - spyOn(localStorage, 'getItem').and.callFake(() => 'true'); - service.init(); + spyOn(localStorage, 'getItem').and.callFake(() => testNodeId); + service.innit(); service.getSavedSearches().subscribe((searches) => { - expect(localStorage.getItem).toHaveBeenCalledWith(LOCAL_STORAGE_KEY); - expect(service.preferencesApi.getPreference).toHaveBeenCalledWith('-me-', 'saved-searches'); + expect(localStorage.getItem).toHaveBeenCalledWith(SAVED_SEARCHES_NODE_ID + testUserName); + expect(getNodeContentSpy).toHaveBeenCalledWith(testNodeId); expect(searches.length).toBe(2); expect(searches[0].name).toBe('Search 1'); expect(searches[1].name).toBe('Search 2'); @@ -87,43 +83,48 @@ describe('SavedSearchesService', () => { }); }); - it('should automatically migrate saved searches if config.json file exists', (done) => { - spyOn(localStorage, 'setItem'); + it('should create config.json file if it does not exist', (done) => { + const error: Error = { name: 'test', message: '{ "error": { "statusCode": 404 } }' }; spyOn(authService, 'getUsername').and.callFake(() => testUserName); + service.nodesApi.getNode = jasmine.createSpy().and.returnValue(Promise.reject(error)); + getNodeContentSpy.and.callFake(() => Promise.resolve(new Blob(['']))); + service.innit(); service.getSavedSearches().subscribe((searches) => { expect(service.nodesApi.getNode).toHaveBeenCalledWith('-my-', { relativePath: 'config.json' }); - expect(service.nodesApi.getNodeContent).toHaveBeenCalledWith(testNodeId); - expect(localStorage.setItem).toHaveBeenCalledWith(LOCAL_STORAGE_KEY, 'true'); - expect(service.preferencesApi.updatePreference).toHaveBeenCalledWith('-me-', 'saved-searches', SAVED_SEARCHES_CONTENT); - expect(service.nodesApi.deleteNode).toHaveBeenCalledWith(testNodeId, { permanent: true }); - expect(searches.length).toBe(2); + expect(service.nodesApi.createNode).toHaveBeenCalledWith('-my-', jasmine.objectContaining({ name: 'config.json' })); + expect(searches.length).toBe(0); done(); }); }); it('should save a new search', (done) => { spyOn(authService, 'getUsername').and.callFake(() => testUserName); - spyOn(localStorage, 'getItem').and.callFake(() => 'true'); + const nodeId = 'saved-searches-node-id'; + spyOn(localStorage, 'getItem').and.callFake(() => nodeId); const newSearch = { name: 'Search 3', description: 'Description 3', encodedUrl: 'url3' }; - service.init(); + service.innit(); service.saveSearch(newSearch).subscribe(() => { - expect(service.preferencesApi.updatePreference).toHaveBeenCalledWith('-me-', 'saved-searches', jasmine.any(String)); + expect(service.nodesApi.updateNodeContent).toHaveBeenCalledWith(nodeId, jasmine.any(String)); expect(service.savedSearches$).toBeDefined(); - done(); + service.savedSearches$.subscribe((searches) => { + expect(searches.length).toBe(3); + expect(searches[2].name).toBe('Search 2'); + expect(searches[2].order).toBe(2); + done(); + }); }); }); it('should emit initial saved searches on subscription', (done) => { - spyOn(authService, 'getUsername').and.callFake(() => testUserName); - spyOn(localStorage, 'getItem').and.returnValue('true'); - service.init(); + const nodeId = 'saved-searches-node-id'; + spyOn(localStorage, 'getItem').and.returnValue(nodeId); + service.innit(); service.savedSearches$.pipe().subscribe((searches) => { expect(searches.length).toBe(2); expect(searches[0].name).toBe('Search 1'); - expect(service.preferencesApi.getPreference).toHaveBeenCalledWith('-me-', 'saved-searches'); done(); }); @@ -132,18 +133,25 @@ describe('SavedSearchesService', () => { it('should emit updated saved searches after saving a new search', (done) => { spyOn(authService, 'getUsername').and.callFake(() => testUserName); - spyOn(localStorage, 'getItem').and.callFake(() => 'true'); + spyOn(localStorage, 'getItem').and.callFake(() => testNodeId); const newSearch = { name: 'Search 3', description: 'Description 3', encodedUrl: 'url3' }; - service.init(); + service.innit(); - service.saveSearch(newSearch).subscribe(() => { - service.savedSearches$.subscribe((searches) => { + let emissionCount = 0; + + service.savedSearches$.subscribe((searches) => { + emissionCount++; + if (emissionCount === 1) { + expect(searches.length).toBe(2); + } + if (emissionCount === 2) { expect(searches.length).toBe(3); expect(searches[2].name).toBe('Search 2'); - expect(service.preferencesApi.updatePreference).toHaveBeenCalledWith('-me-', 'saved-searches', jasmine.any(String)); done(); - }); + } }); + + service.saveSearch(newSearch).subscribe(); }); it('should edit a search', (done) => { @@ -182,7 +190,8 @@ describe('SavedSearchesService', () => { */ function prepareDefaultMock(): void { spyOn(authService, 'getUsername').and.callFake(() => testUserName); - spyOn(localStorage, 'getItem').and.callFake(() => 'true'); - service.init(); + const nodeId = 'saved-searches-node-id'; + spyOn(localStorage, 'getItem').and.callFake(() => nodeId); + service.innit(); } }); diff --git a/lib/content-services/src/lib/common/services/saved-searches.service.ts b/lib/content-services/src/lib/common/services/saved-searches.service.ts index 3b538a6d9a4..d1f3b88b8ef 100644 --- a/lib/content-services/src/lib/common/services/saved-searches.service.ts +++ b/lib/content-services/src/lib/common/services/saved-searches.service.ts @@ -15,7 +15,7 @@ * limitations under the License. */ -import { NodesApi, NodeEntry, PreferencesApi } from '@alfresco/js-api'; +import { NodesApi, NodeEntry } from '@alfresco/js-api'; import { Injectable } from '@angular/core'; import { Observable, of, from, ReplaySubject, throwError } from 'rxjs'; import { catchError, concatMap, first, map, switchMap, take, tap } from 'rxjs/operators'; @@ -27,25 +27,21 @@ import { AuthenticationService } from '@alfresco/adf-core'; providedIn: 'root' }) export class SavedSearchesService { - private savedSearchFileNodeId: string; private _nodesApi: NodesApi; - private _preferencesApi: PreferencesApi; - get nodesApi(): NodesApi { this._nodesApi = this._nodesApi ?? new NodesApi(this.apiService.getInstance()); return this._nodesApi; } - get preferencesApi(): PreferencesApi { - this._preferencesApi = this._preferencesApi ?? new PreferencesApi(this.apiService.getInstance()); - return this._preferencesApi; - } - readonly savedSearches$ = new ReplaySubject(1); + private savedSearchFileNodeId: string; + private currentUserLocalStorageKey: string; + private createFileAttempt = false; + constructor(private readonly apiService: AlfrescoApiService, private readonly authService: AuthenticationService) {} - init(): void { + innit(): void { this.fetchSavedSearches(); } @@ -55,27 +51,20 @@ export class SavedSearchesService { * @returns SavedSearch list containing user saved searches */ getSavedSearches(): Observable { - const savedSearchesMigrated = localStorage.getItem(this.getLocalStorageKey()) ?? ''; - if (savedSearchesMigrated === 'true') { - return from(this.preferencesApi.getPreference('-me-', 'saved-searches')).pipe( - map((preference) => JSON.parse(preference.entry.value)), - catchError(() => of([])) - ); - } else { - return this.getSavedSearchesNodeId().pipe( - take(1), - concatMap(() => { - if (this.savedSearchFileNodeId !== '') { - return this.migrateSavedSearches(); - } else { - return from(this.preferencesApi.getPreference('-me-', 'saved-searches')).pipe( - map((preference) => JSON.parse(preference.entry.value)), - catchError(() => of([])) - ); - } - }) - ); - } + return this.getSavedSearchesNodeId().pipe( + concatMap(() => + from(this.nodesApi.getNodeContent(this.savedSearchFileNodeId).then((content) => this.mapFileContentToSavedSearches(content))).pipe( + catchError((error) => { + if (!this.createFileAttempt) { + this.createFileAttempt = true; + localStorage.removeItem(this.getLocalStorageKey()); + return this.getSavedSearches(); + } + return throwError(() => error); + }) + ) + ) + ); } /** @@ -105,8 +94,7 @@ export class SavedSearchesService { order: index })); - return from(this.preferencesApi.updatePreference('-me-', 'saved-searches', JSON.stringify(updatedSavedSearches))).pipe( - map((preference) => JSON.parse(preference.entry.value)), + return from(this.nodesApi.updateNodeContent(this.savedSearchFileNodeId, JSON.stringify(updatedSavedSearches))).pipe( tap(() => this.savedSearches$.next(updatedSavedSearches)) ); }), @@ -135,9 +123,7 @@ export class SavedSearchesService { this.savedSearches$.next(updatedSearches); }), switchMap((updatedSearches: SavedSearch[]) => - from(this.preferencesApi.updatePreference('-me-', 'saved-searches', JSON.stringify(updatedSearches))).pipe( - map((preference) => JSON.parse(preference.entry.value)) - ) + from(this.nodesApi.updateNodeContent(this.savedSearchFileNodeId, JSON.stringify(updatedSearches))) ), catchError((error) => { this.savedSearches$.next(previousSavedSearches); @@ -168,9 +154,7 @@ export class SavedSearchesService { this.savedSearches$.next(updatedSearches); }), switchMap((updatedSearches: SavedSearch[]) => - from(this.preferencesApi.updatePreference('-me-', 'saved-searches', JSON.stringify(updatedSearches))).pipe( - map((preference) => JSON.parse(preference.entry.value)) - ) + from(this.nodesApi.updateNodeContent(this.savedSearchFileNodeId, JSON.stringify(updatedSearches))) ), catchError((error) => { this.savedSearches$.next(previousSavedSearchesOrder); @@ -201,9 +185,7 @@ export class SavedSearchesService { }), tap((savedSearches: SavedSearch[]) => this.savedSearches$.next(savedSearches)), switchMap((updatedSearches: SavedSearch[]) => - from(this.preferencesApi.updatePreference('-me-', 'saved-searches', JSON.stringify(updatedSearches))).pipe( - map((preference) => JSON.parse(preference.entry.value)) - ) + from(this.nodesApi.updateNodeContent(this.savedSearchFileNodeId, JSON.stringify(updatedSearches))) ), catchError((error) => { this.savedSearches$.next(previousSavedSearchesOrder); @@ -214,33 +196,52 @@ export class SavedSearchesService { } private getSavedSearchesNodeId(): Observable { - return from(this.nodesApi.getNode('-my-', { relativePath: 'config.json' })).pipe( - first(), - concatMap((configNode) => { - this.savedSearchFileNodeId = configNode.entry.id; - return configNode.entry.id; - }), - catchError((error) => { - const errorStatusCode = JSON.parse(error.message).error.statusCode; - if (errorStatusCode === 404) { - localStorage.setItem(this.getLocalStorageKey(), 'true'); - return ''; - } else { - return throwError(() => error); - } - }) - ); + const localStorageKey = this.getLocalStorageKey(); + if (this.currentUserLocalStorageKey && this.currentUserLocalStorageKey !== localStorageKey) { + this.savedSearches$.next([]); + } + this.currentUserLocalStorageKey = localStorageKey; + let savedSearchesNodeId = localStorage.getItem(this.currentUserLocalStorageKey) ?? ''; + if (savedSearchesNodeId === '') { + return from(this.nodesApi.getNode('-my-', { relativePath: 'config.json' })).pipe( + first(), + concatMap((configNode) => { + savedSearchesNodeId = configNode.entry.id; + localStorage.setItem(this.currentUserLocalStorageKey, savedSearchesNodeId); + this.savedSearchFileNodeId = savedSearchesNodeId; + return savedSearchesNodeId; + }), + catchError((error) => { + const errorStatusCode = JSON.parse(error.message).error.statusCode; + if (errorStatusCode === 404) { + return this.createSavedSearchesNode('-my-').pipe( + first(), + map((node) => { + localStorage.setItem(this.currentUserLocalStorageKey, node.entry.id); + return node.entry.id; + }) + ); + } else { + return throwError(() => error); + } + }) + ); + } else { + this.savedSearchFileNodeId = savedSearchesNodeId; + return of(savedSearchesNodeId); + } + } + + private createSavedSearchesNode(parentNodeId: string): Observable { + return from(this.nodesApi.createNode(parentNodeId, { name: 'config.json', nodeType: 'cm:content' })); } private async mapFileContentToSavedSearches(blob: Blob): Promise> { - return blob - .text() - .then((content) => (content ? JSON.parse(content) : [])) - .catch(() => []); + return blob.text().then((content) => (content ? JSON.parse(content) : [])); } private getLocalStorageKey(): string { - return `saved-searches-${this.authService.getUsername()}-migrated`; + return `saved-searches-node-id__${this.authService.getUsername()}`; } private fetchSavedSearches(): void { @@ -248,14 +249,4 @@ export class SavedSearchesService { .pipe(take(1)) .subscribe((searches) => this.savedSearches$.next(searches)); } - - private migrateSavedSearches(): Observable { - return from(this.nodesApi.getNodeContent(this.savedSearchFileNodeId).then((content) => this.mapFileContentToSavedSearches(content))).pipe( - tap((savedSearches) => { - this.preferencesApi.updatePreference('-me-', 'saved-searches', JSON.stringify(savedSearches)); - localStorage.setItem(this.getLocalStorageKey(), 'true'); - this.nodesApi.deleteNode(this.savedSearchFileNodeId, { permanent: true }); - }) - ); - } } diff --git a/lib/js-api/src/api/content-rest-api/api/preferences.api.ts b/lib/js-api/src/api/content-rest-api/api/preferences.api.ts index 1d22e7e9432..7f9f0be5bb7 100644 --- a/lib/js-api/src/api/content-rest-api/api/preferences.api.ts +++ b/lib/js-api/src/api/content-rest-api/api/preferences.api.ts @@ -84,25 +84,4 @@ export class PreferencesApi extends BaseApi { queryParams }); } - - updatePreference(personId: string, preferenceName: string, preferenceValue: string): Promise { - throwIfNotDefined(personId, 'personId'); - throwIfNotDefined(preferenceName, 'preferenceName'); - throwIfNotDefined(preferenceValue, 'preferenceValue'); - - const pathParams = { - personId, - preferenceName - }; - - const bodyParam = { - value: preferenceValue - }; - - return this.put({ - path: '/people/{personId}/preferences/{preferenceName}', - pathParams, - bodyParam: bodyParam - }); - } } diff --git a/lib/js-api/src/api/content-rest-api/docs/PreferencesApi.md b/lib/js-api/src/api/content-rest-api/docs/PreferencesApi.md index 27a1a7a6373..aca7ff654d6 100644 --- a/lib/js-api/src/api/content-rest-api/docs/PreferencesApi.md +++ b/lib/js-api/src/api/content-rest-api/docs/PreferencesApi.md @@ -2,11 +2,10 @@ All URIs are relative to *https://localhost/alfresco/api/-default-/public/alfresco/versions/1* -| Method | HTTP request | Description | -|---------------------------------------|----------------------------------------------------------|-------------------| -| [getPreference](#getPreference) | **GET** /people/{personId}/preferences/{preferenceName} | Get a preference | -| [listPreferences](#listPreferences) | **GET** /people/{personId}/preferences | List preferences | -| [updatePreference](#updatePreference) | **POST** /people/{personId}/preferences/{preferenceName} | Update preference | +| Method | HTTP request | Description | +|-------------------------------------|---------------------------------------------------------|------------------| +| [getPreference](#getPreference) | **GET** /people/{personId}/preferences/{preferenceName} | Get a preference | +| [listPreferences](#listPreferences) | **GET** /people/{personId}/preferences | List preferences | ## getPreference @@ -72,36 +71,6 @@ preferencesApi.listPreferences(``, opts).then((data) => { }); ``` -## updatePreference - -Update preference - -You can use the `-me-` string in place of `` to specify the currently authenticated user. - -### Parameters - -| Name | Type | Description | -|---------------------|--------|-----------------------------| -| **personId** | string | The identifier of a person. | -| **preferenceName** | string | The name of the preference. | -| **preferenceValue** | string | New preference value. | - -**Return type**: [PreferenceEntry](#PreferenceEntry) - -**Example** - -```javascript -import { AlfrescoApi, PreferencesApi } from '@alfresco/js-api'; - -const alfrescoApi = new AlfrescoApi(/*..*/); -const preferencesApi = new PreferencesApi(alfrescoApi); -const newPreferenceValue = 'test'; - -preferencesApi.updatePreference(``, ``, newPreferenceValue).then((data) => { - console.log('API called successfully. Returned data: ' + data); -}); -``` - # Models ## PreferencePaging @@ -136,4 +105,4 @@ preferencesApi.updatePreference(``, ``, newPreferenceV | Name | Type | Description | |--------|--------|----------------------------------------------------------------------| | **id** | string | The unique id of the preference | -| value | string | The value of the preference. Note that this can be of any JSON type. | +| value | string | The value of the preference. Note that this can be of any JSON type. | \ No newline at end of file