Revert "[ACS-9166] Migrate Saved Searches to preferences API from config file…" (#10566)

This reverts commit 184a38a0ef08e2222820e6654ea356579e2f88d1.
This commit is contained in:
MichalKinas 2025-01-20 12:38:27 +01:00 committed by GitHub
parent c4b3a53d56
commit fcd6e25dc6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 130 additions and 171 deletions

View File

@ -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)`<SavedSearch[]>`
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)`<SavedSearch[]>` - 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);
});
```

View File

@ -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();
}
});

View File

@ -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<SavedSearch[]>(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<SavedSearch[]> {
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<string> {
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<NodeEntry> {
return from(this.nodesApi.createNode(parentNodeId, { name: 'config.json', nodeType: 'cm:content' }));
}
private async mapFileContentToSavedSearches(blob: Blob): Promise<Array<SavedSearch>> {
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<SavedSearch[]> {
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 });
})
);
}
}

View File

@ -84,25 +84,4 @@ export class PreferencesApi extends BaseApi {
queryParams
});
}
updatePreference(personId: string, preferenceName: string, preferenceValue: string): Promise<PreferenceEntry> {
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
});
}
}

View File

@ -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(`<personId>`, opts).then((data) => {
});
```
## updatePreference
Update preference
You can use the `-me-` string in place of `<personId>` 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(`<personId>`, `<preferenceName>`, newPreferenceValue).then((data) => {
console.log('API called successfully. Returned data: ' + data);
});
```
# Models
## PreferencePaging
@ -136,4 +105,4 @@ preferencesApi.updatePreference(`<personId>`, `<preferenceName>`, 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. |