-
Notifications
You must be signed in to change notification settings - Fork 277
[ACS-10421] Add support for legacy config.json based approach for saved-searches #11301
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
Closed
Changes from all commits
Commits
Show all changes
11 commits
Select commit
Hold shift + click to select a range
dc2e73e
[ACS-9166]: introduces strategy interface
rmnvch 53bb04a
[ACS-9166]: restores config-based saved searches approach
rmnvch 6fc92b2
[ACS-9166]: updates existing modern service
rmnvch a68de54
[ACS-9166]: introduces saved search base class
rmnvch af66fec
[ACS-9166]: migrates both saved searches to inheritance model
rmnvch 65b18bb
[ACS-9166]: tests clean up
rmnvch 504615d
[ACS-9166]: imports fixes
rmnvch 93998d5
[ACS-9166]: types and naming fixes
rmnvch 53d3076
[ACS-9166]: redundant types
rmnvch 34cb40c
[ACS-9166]: introduces variable for search save mode threshold
rmnvch 1859683
[ACS-9166]: minor fix
rmnvch File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
71 changes: 71 additions & 0 deletions
71
lib/content-services/src/lib/common/interfaces/saved-searches-strategy.interface.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,71 @@ | ||
| /*! | ||
| * @license | ||
| * Copyright © 2005-2025 Hyland Software, Inc. and its affiliates. All rights reserved. | ||
| * | ||
| * 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 { Observable } from 'rxjs'; | ||
| import { NodeEntry } from '@alfresco/js-api'; | ||
| import { SavedSearch } from './saved-search.interface'; | ||
|
|
||
| /** | ||
| * Contract that describes the public API for saved searches strategy. | ||
| * Implemented by both the new and legacy SavedSearches services so callers | ||
| * can depend on the same shape. | ||
| */ | ||
| export interface SavedSearchStrategy { | ||
| savedSearches$: Observable<SavedSearch[]>; | ||
|
|
||
| init(): void; | ||
|
|
||
| /** | ||
| * Gets a list of saved searches by user. | ||
| * | ||
| * @returns SavedSearch list containing user saved searches | ||
| */ | ||
| getSavedSearches(): Observable<SavedSearch[]>; | ||
|
|
||
| /** | ||
| * Saves a new search into state and updates state. If there are less than 5 searches, | ||
| * it will be pushed on first place, if more it will be pushed to 6th place. | ||
| * | ||
| * @param newSaveSearch object { name: string, description: string, encodedUrl: string } | ||
| * @returns NodeEntry | ||
| */ | ||
| saveSearch(newSaveSearch: Pick<SavedSearch, 'name' | 'description' | 'encodedUrl'>): Observable<NodeEntry>; | ||
|
|
||
| /** | ||
| * Replace Save Search with new one and also updates the state. | ||
| * | ||
| * @param updatedSavedSearch - updated Save Search | ||
| * @returns NodeEntry | ||
| */ | ||
| editSavedSearch(updatedSavedSearch: SavedSearch): Observable<NodeEntry>; | ||
|
|
||
| /** | ||
| * Deletes Save Search and update state. | ||
| * | ||
| * @param deletedSavedSearch - Save Search to delete | ||
| * @returns NodeEntry | ||
| */ | ||
| deleteSavedSearch(deletedSavedSearch: SavedSearch): Observable<NodeEntry>; | ||
|
|
||
| /** | ||
| * Reorders saved search place | ||
| * | ||
| * @param previousIndex - previous index of saved search | ||
| * @param currentIndex - new index of saved search | ||
| */ | ||
| changeOrder(previousIndex: number, currentIndex: number): void; | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
139 changes: 139 additions & 0 deletions
139
lib/content-services/src/lib/common/services/saved-searches-base.service.spec.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,139 @@ | ||
| /*! | ||
| * @license | ||
| * Copyright © 2005-2025 Hyland Software, Inc. and its affiliates. All rights reserved. | ||
| * | ||
| * 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 { AlfrescoApiService } from '../../services'; | ||
| import { AlfrescoApiServiceMock } from '../../mock'; | ||
| import { MockSavedSearchesService } from '../../mock/saved-searches-derived.mock'; | ||
| import { SavedSearch } from '../interfaces/saved-search.interface'; | ||
| import { Subject } from 'rxjs'; | ||
| import { AuthenticationService } from '@alfresco/adf-core'; | ||
|
|
||
| describe('SavedSearchesBaseService', () => { | ||
| let service: MockSavedSearchesService; | ||
|
|
||
| const SAVED_SEARCHES_CONTENT: SavedSearch[] = [ | ||
| { name: 'Search 1', description: 'Description 1', encodedUrl: 'url1', order: 0 }, | ||
| { name: 'Search 2', description: 'Description 2', encodedUrl: 'url2', order: 1 } | ||
| ]; | ||
|
|
||
| beforeEach(() => { | ||
| TestBed.configureTestingModule({ | ||
| providers: [ | ||
| { provide: AlfrescoApiService, useClass: AlfrescoApiServiceMock }, | ||
| { provide: AuthenticationService, useValue: { getUsername: () => {}, onLogin: new Subject() } }, | ||
| MockSavedSearchesService | ||
| ] | ||
| }); | ||
| service = TestBed.inject(MockSavedSearchesService); | ||
| }); | ||
|
|
||
| it('should emit loaded data in savedSearches$ on init', (done) => { | ||
| service.savedSearches$.subscribe((value) => { | ||
| expect(value).toEqual(SAVED_SEARCHES_CONTENT); | ||
| done(); | ||
| }); | ||
| service.mockFetch(SAVED_SEARCHES_CONTENT); | ||
| service.init(); | ||
| }); | ||
|
|
||
| it('should emit updated searches with correct order if total of saved searches is less than limit (5)', (done) => { | ||
| service.mockFetch(SAVED_SEARCHES_CONTENT); | ||
| const newSearch = { name: 'new-search' } as SavedSearch; | ||
| service.saveSearch(newSearch).subscribe(() => { | ||
| const args = (service.updateSpy as jasmine.Spy).calls.mostRecent().args[0]; | ||
|
|
||
| expect(args.length).toBe(1 + SAVED_SEARCHES_CONTENT.length); | ||
|
|
||
| expect(args[0]).toEqual({ ...newSearch, order: 0 }); | ||
|
|
||
| service.savedSearches$.subscribe((savedSearches) => { | ||
| expect(savedSearches).toEqual(args); | ||
| done(); | ||
| }); | ||
| }); | ||
| }); | ||
|
|
||
| it('should emit updated searches with correct order if total of saved searches is more than limit (5)', (done) => { | ||
| const moreSavedSearches = [...SAVED_SEARCHES_CONTENT, ...SAVED_SEARCHES_CONTENT, ...SAVED_SEARCHES_CONTENT]; | ||
| service.mockFetch(moreSavedSearches); | ||
| const newSearch = { name: 'new-search' } as SavedSearch; | ||
| service.saveSearch(newSearch).subscribe(() => { | ||
| const args = (service.updateSpy as jasmine.Spy).calls.mostRecent().args[0]; | ||
|
|
||
| expect(args.length).toBe(1 + moreSavedSearches.length); | ||
|
|
||
| expect(args[5]).toEqual({ ...newSearch, order: 5 }); | ||
|
|
||
| service.savedSearches$.subscribe((savedSearches) => { | ||
| expect(savedSearches).toEqual(args); | ||
| done(); | ||
| }); | ||
| }); | ||
| }); | ||
|
|
||
| it('should edit a search and emit updated saved searches', (done) => { | ||
| service.mockFetch(SAVED_SEARCHES_CONTENT); | ||
| service.init(); | ||
| const updatedSearch = { name: 'updated-search', order: 0 } as SavedSearch; | ||
| service.editSavedSearch(updatedSearch).subscribe(() => { | ||
| const args = (service.updateSpy as jasmine.Spy).calls.mostRecent().args[0]; | ||
| expect(args[0]).toEqual(updatedSearch); | ||
|
|
||
| service.savedSearches$.subscribe((savedSearches) => { | ||
| expect(savedSearches[0]).toEqual(updatedSearch); | ||
| done(); | ||
| }); | ||
| }); | ||
| }); | ||
|
|
||
| it('should delete a search and emit updated saved searches', (done) => { | ||
| service.mockFetch(SAVED_SEARCHES_CONTENT); | ||
| service.init(); | ||
| const searchToDelete = { name: 'Search 1', order: 0 } as SavedSearch; | ||
| service.deleteSavedSearch(searchToDelete).subscribe(() => { | ||
| const args = (service.updateSpy as jasmine.Spy).calls.mostRecent().args[0]; | ||
| expect(args.find((savedSearch: SavedSearch) => savedSearch.name === 'Search 1')).toBeUndefined(); | ||
|
|
||
| service.savedSearches$.subscribe((savedSearches) => { | ||
| expect(savedSearches.length).toBe(1); | ||
| expect(savedSearches[0].name).toBe('Search 2'); | ||
| expect(savedSearches[0].order).toBe(0); | ||
| done(); | ||
| }); | ||
| }); | ||
| }); | ||
|
|
||
| it('should change order of saved searches and emit updated saved searches', (done) => { | ||
| const updatedOrder: SavedSearch[] = [ | ||
| { ...SAVED_SEARCHES_CONTENT[1], order: 0 }, | ||
| { ...SAVED_SEARCHES_CONTENT[0], order: 1 } | ||
| ]; | ||
| service.mockFetch(SAVED_SEARCHES_CONTENT); | ||
| service.init(); | ||
| service.changeOrder(1, 0); | ||
|
|
||
| service.savedSearches$.subscribe((savedSearches) => { | ||
| expect(service.updateSpy).toHaveBeenCalledWith(updatedOrder); | ||
|
|
||
| expect(savedSearches.length).toBe(SAVED_SEARCHES_CONTENT.length); | ||
| expect(savedSearches[0]).toEqual(updatedOrder[0]); | ||
| expect(savedSearches[1]).toEqual(updatedOrder[1]); | ||
| done(); | ||
| }); | ||
| }); | ||
| }); |
151 changes: 151 additions & 0 deletions
151
lib/content-services/src/lib/common/services/saved-searches-base.service.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,151 @@ | ||
| /*! | ||
| * @license | ||
| * Copyright © 2005-2025 Hyland Software, Inc. and its affiliates. All rights reserved. | ||
| * | ||
| * 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 { Injectable } from '@angular/core'; | ||
| import { SavedSearchStrategy } from '../interfaces/saved-searches-strategy.interface'; | ||
| import { AuthenticationService } from '@alfresco/adf-core'; | ||
| import { ReplaySubject, Observable, catchError, switchMap, take, tap, throwError, map } from 'rxjs'; | ||
| import { NodeEntry, NodesApi } from '@alfresco/js-api'; | ||
| import { SavedSearch } from '../interfaces/saved-search.interface'; | ||
| import { AlfrescoApiService } from '../../services'; | ||
|
|
||
| @Injectable() | ||
| export abstract class SavedSearchesBaseService implements SavedSearchStrategy { | ||
| private _nodesApi: NodesApi; | ||
|
|
||
| private static readonly SAVE_MODE_THRESHOLD = 5; | ||
|
|
||
| protected readonly _savedSearches$ = new ReplaySubject<SavedSearch[]>(1); | ||
| readonly savedSearches$: Observable<SavedSearch[]> = this._savedSearches$.asObservable(); | ||
|
|
||
| get nodesApi(): NodesApi { | ||
| this._nodesApi = this._nodesApi ?? new NodesApi(this.apiService.getInstance()); | ||
| return this._nodesApi; | ||
| } | ||
|
|
||
| protected constructor( | ||
| protected readonly apiService: AlfrescoApiService, | ||
| protected readonly authService: AuthenticationService | ||
| ) {} | ||
|
|
||
| protected abstract fetchAllSavedSearches(): Observable<SavedSearch[]>; | ||
| protected abstract updateSavedSearches(searches: SavedSearch[]): Observable<NodeEntry>; | ||
|
|
||
| init(): void { | ||
| this.fetchSavedSearches(); | ||
| } | ||
|
|
||
| getSavedSearches(): Observable<SavedSearch[]> { | ||
| return this.fetchAllSavedSearches(); | ||
| } | ||
|
|
||
| saveSearch(newSaveSearch: Pick<SavedSearch, 'name' | 'description' | 'encodedUrl'>): Observable<NodeEntry> { | ||
| const limit = SavedSearchesBaseService.SAVE_MODE_THRESHOLD; | ||
| return this.fetchAllSavedSearches().pipe( | ||
| take(1), | ||
| switchMap((savedSearches) => { | ||
| let updatedSavedSearches: SavedSearch[] = []; | ||
|
|
||
| if (savedSearches.length < limit) { | ||
| updatedSavedSearches = [{ ...newSaveSearch, order: 0 }, ...savedSearches]; | ||
| } else { | ||
| const upToLimitSearches = savedSearches.slice(0, limit); | ||
| const restSearches = savedSearches.slice(limit); | ||
| updatedSavedSearches = [...upToLimitSearches, { ...newSaveSearch, order: limit }, ...restSearches]; | ||
| } | ||
|
|
||
| updatedSavedSearches = updatedSavedSearches.map((search, index) => ({ ...search, order: index })); | ||
|
|
||
| return this.updateSavedSearches(updatedSavedSearches).pipe(tap(() => this._savedSearches$.next(updatedSavedSearches))); | ||
| }), | ||
| catchError((error) => { | ||
| console.error('Error saving new search:', error); | ||
| return throwError(() => error); | ||
| }) | ||
| ); | ||
| } | ||
|
|
||
| editSavedSearch(updatedSavedSearch: SavedSearch): Observable<NodeEntry> { | ||
| let previousSavedSearches: SavedSearch[]; | ||
| return this.savedSearches$.pipe( | ||
| take(1), | ||
| map((savedSearches) => { | ||
| previousSavedSearches = [...savedSearches]; | ||
| return savedSearches.map((search) => (search.order === updatedSavedSearch.order ? updatedSavedSearch : search)); | ||
| }), | ||
| tap((updatedSearches) => { | ||
| this._savedSearches$.next(updatedSearches); | ||
| }), | ||
| switchMap((updatedSearches) => this.updateSavedSearches(updatedSearches)), | ||
| catchError((error) => { | ||
| this._savedSearches$.next(previousSavedSearches); | ||
| return throwError(() => error); | ||
| }) | ||
| ); | ||
| } | ||
|
|
||
| deleteSavedSearch(deletedSavedSearch: SavedSearch): Observable<NodeEntry> { | ||
| let previousSavedSearchesOrder: SavedSearch[]; | ||
| return this.savedSearches$.pipe( | ||
| take(1), | ||
| map((savedSearches) => { | ||
| previousSavedSearchesOrder = [...savedSearches]; | ||
| const updatedSearches = savedSearches.filter((search) => search.order !== deletedSavedSearch.order); | ||
| return updatedSearches.map((search, index) => ({ ...search, order: index })); | ||
| }), | ||
| tap((updatedSearches: SavedSearch[]) => { | ||
| this._savedSearches$.next(updatedSearches); | ||
| }), | ||
| switchMap((updatedSearches: SavedSearch[]) => this.updateSavedSearches(updatedSearches)), | ||
rmnvch marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| catchError((error) => { | ||
| this._savedSearches$.next(previousSavedSearchesOrder); | ||
| return throwError(() => error); | ||
| }) | ||
| ); | ||
| } | ||
|
|
||
| changeOrder(previousIndex: number, currentIndex: number): void { | ||
| let previousSavedSearchesOrder: SavedSearch[]; | ||
| this.savedSearches$ | ||
| .pipe( | ||
| take(1), | ||
| map((savedSearches) => { | ||
| previousSavedSearchesOrder = [...savedSearches]; | ||
| const [movedSearch] = savedSearches.splice(previousIndex, 1); | ||
| savedSearches.splice(currentIndex, 0, movedSearch); | ||
| return savedSearches.map((search, index) => ({ ...search, order: index })); | ||
| }), | ||
| tap((savedSearches: SavedSearch[]) => this._savedSearches$.next(savedSearches)), | ||
rmnvch marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| switchMap((updatedSearches: SavedSearch[]) => this.updateSavedSearches(updatedSearches)), | ||
rmnvch marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| catchError((error) => { | ||
| this._savedSearches$.next(previousSavedSearchesOrder); | ||
| return throwError(() => error); | ||
| }) | ||
| ) | ||
| .subscribe(); | ||
| } | ||
|
|
||
| protected resetSavedSearchesStream(): void { | ||
| this._savedSearches$.next([]); | ||
| } | ||
|
|
||
| private fetchSavedSearches(): void { | ||
| this.getSavedSearches() | ||
| .pipe(take(1)) | ||
| .subscribe((searches) => this._savedSearches$.next(searches)); | ||
| } | ||
| } | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.