mirror of
https://github.com/Alfresco/alfresco-ng2-components.git
synced 2025-07-24 17:32:15 +00:00
[ACS-8634] "Manage Searches" - a full page list of saved searches (#10307)
* [ACS-8634] "Manage Searches" - a full page list of saved searches * Changes after CR * [ACS-8634] Removed extra selector, drag&drop on hover * [ACS-8634] Fix discovered bugs * [ACS-8634] Unit test fixes * [ACS-8634] Cleanup failing test case * [ACS-8634] Unit test fixes * [ACS-8634] Remove unused import * [ACS-8634] Final cleanup * [ACS-8634] Remove unused imports --------- Co-authored-by: MichalKinas <michal.kinas@hyland.com>
This commit is contained in:
committed by
GitHub
parent
ba52074bb5
commit
a7911338c3
@@ -28,6 +28,7 @@ describe('SavedSearchesService', () => {
|
||||
let service: SavedSearchesService;
|
||||
let authService: AuthenticationService;
|
||||
let testUserName: string;
|
||||
let getNodeContentSpy: jasmine.Spy;
|
||||
|
||||
const testNodeId = 'test-node-id';
|
||||
const SAVED_SEARCHES_NODE_ID = 'saved-searches-node-id__';
|
||||
@@ -59,6 +60,9 @@ describe('SavedSearchesService', () => {
|
||||
authService = TestBed.inject(AuthenticationService);
|
||||
spyOn(service.nodesApi, 'getNode').and.callFake(() => Promise.resolve({ entry: { id: testNodeId } } as NodeEntry));
|
||||
spyOn(service.searchApi, 'search').and.callFake(() => Promise.resolve({ list: { entries: [] } }));
|
||||
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(() => {
|
||||
@@ -68,12 +72,11 @@ describe('SavedSearchesService', () => {
|
||||
it('should retrieve saved searches from the saved-searches.json file', (done) => {
|
||||
spyOn(authService, 'getUsername').and.callFake(() => testUserName);
|
||||
spyOn(localStorage, 'getItem').and.callFake(() => testNodeId);
|
||||
spyOn(service.nodesApi, 'getNodeContent').and.callFake(() => createBlob());
|
||||
service.innit();
|
||||
|
||||
service.getSavedSearches().subscribe((searches) => {
|
||||
expect(localStorage.getItem).toHaveBeenCalledWith(SAVED_SEARCHES_NODE_ID + testUserName);
|
||||
expect(service.nodesApi.getNodeContent).toHaveBeenCalledWith(testNodeId);
|
||||
expect(getNodeContentSpy).toHaveBeenCalledWith(testNodeId);
|
||||
expect(searches.length).toBe(2);
|
||||
expect(searches[0].name).toBe('Search 1');
|
||||
expect(searches[1].name).toBe('Search 2');
|
||||
@@ -83,8 +86,7 @@ describe('SavedSearchesService', () => {
|
||||
|
||||
it('should create saved-searches.json file if it does not exist', (done) => {
|
||||
spyOn(authService, 'getUsername').and.callFake(() => testUserName);
|
||||
spyOn(service.nodesApi, 'createNode').and.callFake(() => Promise.resolve({ entry: { id: 'new-node-id' } }));
|
||||
spyOn(service.nodesApi, 'getNodeContent').and.callFake(() => Promise.resolve(new Blob([''])));
|
||||
getNodeContentSpy.and.callFake(() => Promise.resolve(new Blob([''])));
|
||||
service.innit();
|
||||
|
||||
service.getSavedSearches().subscribe((searches) => {
|
||||
@@ -100,9 +102,7 @@ describe('SavedSearchesService', () => {
|
||||
spyOn(authService, 'getUsername').and.callFake(() => testUserName);
|
||||
const nodeId = 'saved-searches-node-id';
|
||||
spyOn(localStorage, 'getItem').and.callFake(() => nodeId);
|
||||
spyOn(service.nodesApi, 'getNodeContent').and.callFake(() => createBlob());
|
||||
const newSearch = { name: 'Search 3', description: 'Description 3', encodedUrl: 'url3' };
|
||||
spyOn(service.nodesApi, 'updateNodeContent').and.callFake(() => Promise.resolve({ entry: {} } as NodeEntry));
|
||||
service.innit();
|
||||
|
||||
service.saveSearch(newSearch).subscribe(() => {
|
||||
@@ -110,7 +110,7 @@ describe('SavedSearchesService', () => {
|
||||
expect(service.savedSearches$).toBeDefined();
|
||||
service.savedSearches$.subscribe((searches) => {
|
||||
expect(searches.length).toBe(3);
|
||||
expect(searches[2].name).toBe('Search 3');
|
||||
expect(searches[2].name).toBe('Search 2');
|
||||
expect(searches[2].order).toBe(2);
|
||||
done();
|
||||
});
|
||||
@@ -120,7 +120,6 @@ describe('SavedSearchesService', () => {
|
||||
it('should emit initial saved searches on subscription', (done) => {
|
||||
const nodeId = 'saved-searches-node-id';
|
||||
spyOn(localStorage, 'getItem').and.returnValue(nodeId);
|
||||
spyOn(service.nodesApi, 'getNodeContent').and.returnValue(createBlob());
|
||||
service.innit();
|
||||
|
||||
service.savedSearches$.pipe().subscribe((searches) => {
|
||||
@@ -135,9 +134,7 @@ 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(() => testNodeId);
|
||||
spyOn(service.nodesApi, 'getNodeContent').and.callFake(() => createBlob());
|
||||
const newSearch = { name: 'Search 3', description: 'Description 3', encodedUrl: 'url3' };
|
||||
spyOn(service.nodesApi, 'updateNodeContent').and.callFake(() => Promise.resolve({ entry: {} } as NodeEntry));
|
||||
service.innit();
|
||||
|
||||
let emissionCount = 0;
|
||||
@@ -149,11 +146,52 @@ describe('SavedSearchesService', () => {
|
||||
}
|
||||
if (emissionCount === 2) {
|
||||
expect(searches.length).toBe(3);
|
||||
expect(searches[2].name).toBe('Search 3');
|
||||
expect(searches[2].name).toBe('Search 2');
|
||||
done();
|
||||
}
|
||||
});
|
||||
|
||||
service.saveSearch(newSearch).subscribe();
|
||||
});
|
||||
|
||||
it('should edit a search', (done) => {
|
||||
const updatedSearch = { name: 'Search 3', description: 'Description 3', encodedUrl: 'url3', order: 0 };
|
||||
prepareDefaultMock();
|
||||
|
||||
service.editSavedSearch(updatedSearch).subscribe(() => {
|
||||
service.savedSearches$.subscribe((searches) => {
|
||||
expect(searches.length).toBe(2);
|
||||
expect(searches[0].name).toBe('Search 3');
|
||||
expect(searches[0].order).toBe(0);
|
||||
|
||||
expect(searches[1].name).toBe('Search 2');
|
||||
expect(searches[1].order).toBe(1);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should delete a search', (done) => {
|
||||
const searchToDelete = { name: 'Search 1', description: 'Description 1', encodedUrl: 'url1', order: 0 };
|
||||
prepareDefaultMock();
|
||||
|
||||
service.deleteSavedSearch(searchToDelete).subscribe(() => {
|
||||
service.savedSearches$.subscribe((searches) => {
|
||||
expect(searches.length).toBe(1);
|
||||
expect(searches[0].name).toBe('Search 2');
|
||||
expect(searches[0].order).toBe(0);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Prepares default mocks for service
|
||||
*/
|
||||
function prepareDefaultMock(): void {
|
||||
spyOn(authService, 'getUsername').and.callFake(() => testUserName);
|
||||
const nodeId = 'saved-searches-node-id';
|
||||
spyOn(localStorage, 'getItem').and.callFake(() => nodeId);
|
||||
service.innit();
|
||||
}
|
||||
});
|
||||
|
@@ -76,23 +76,133 @@ export class SavedSearchesService {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a list of saved searches by user.
|
||||
* 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 Adds and saves search also updating current saved search state
|
||||
* @returns NodeEntry
|
||||
*/
|
||||
saveSearch(newSaveSearch: Pick<SavedSearch, 'name' | 'description' | 'encodedUrl'>): Observable<NodeEntry> {
|
||||
return this.getSavedSearches().pipe(
|
||||
take(1),
|
||||
switchMap((savedSearches: Array<SavedSearch>) => {
|
||||
const updatedSavedSearches = [...savedSearches, { ...newSaveSearch, order: savedSearches.length }];
|
||||
switchMap((savedSearches: SavedSearch[]) => {
|
||||
let updatedSavedSearches: SavedSearch[] = [];
|
||||
|
||||
if (savedSearches.length < 5) {
|
||||
updatedSavedSearches = [{ ...newSaveSearch, order: 0 }, ...savedSearches];
|
||||
} else {
|
||||
const firstFiveSearches = savedSearches.slice(0, 5);
|
||||
const restOfSearches = savedSearches.slice(5);
|
||||
|
||||
updatedSavedSearches = [...firstFiveSearches, { ...newSaveSearch, order: 5 }, ...restOfSearches];
|
||||
}
|
||||
|
||||
updatedSavedSearches = updatedSavedSearches.map((search, index) => ({
|
||||
...search,
|
||||
order: index
|
||||
}));
|
||||
|
||||
return from(this.nodesApi.updateNodeContent(this.savedSearchFileNodeId, JSON.stringify(updatedSavedSearches))).pipe(
|
||||
tap(() => this.savedSearches$.next(updatedSavedSearches))
|
||||
);
|
||||
}),
|
||||
catchError((error) => {
|
||||
console.error('Error saving new search:', error);
|
||||
return throwError(() => error);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace Save Search with new one and also updates the state.
|
||||
*
|
||||
* @param updatedSavedSearch - updated Save Search
|
||||
* @returns NodeEntry
|
||||
*/
|
||||
editSavedSearch(updatedSavedSearch: SavedSearch): Observable<NodeEntry> {
|
||||
let previousSavedSearches: SavedSearch[];
|
||||
return this.savedSearches$.pipe(
|
||||
take(1),
|
||||
map((savedSearches: SavedSearch[]) => {
|
||||
previousSavedSearches = [...savedSearches];
|
||||
return savedSearches.map((search) => (search.order === updatedSavedSearch.order ? updatedSavedSearch : search));
|
||||
}),
|
||||
tap((updatedSearches: SavedSearch[]) => {
|
||||
this.savedSearches$.next(updatedSearches);
|
||||
}),
|
||||
switchMap((updatedSearches: SavedSearch[]) => {
|
||||
return from(this.nodesApi.updateNodeContent(this.savedSearchFileNodeId, JSON.stringify(updatedSearches)));
|
||||
}),
|
||||
catchError((error) => {
|
||||
this.savedSearches$.next(previousSavedSearches);
|
||||
return throwError(() => error);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes Save Search and update state.
|
||||
*
|
||||
* @param deletedSavedSearch - Save Search to delete
|
||||
* @returns NodeEntry
|
||||
*/
|
||||
deleteSavedSearch(deletedSavedSearch: SavedSearch): Observable<NodeEntry> {
|
||||
let previousSavedSearchesOrder: SavedSearch[];
|
||||
return this.savedSearches$.pipe(
|
||||
take(1),
|
||||
map((savedSearches: SavedSearch[]) => {
|
||||
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[]) => {
|
||||
return from(this.nodesApi.updateNodeContent(this.savedSearchFileNodeId, JSON.stringify(updatedSearches)));
|
||||
}),
|
||||
catchError((error) => {
|
||||
this.savedSearches$.next(previousSavedSearchesOrder);
|
||||
return throwError(() => error);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reorders saved search place
|
||||
*
|
||||
* @param previousIndex - previous index of saved search
|
||||
* @param currentIndex - new index of saved search
|
||||
*/
|
||||
changeOrder(previousIndex: number, currentIndex: number): void {
|
||||
let previousSavedSearchesOrder: SavedSearch[];
|
||||
this.savedSearches$
|
||||
.pipe(
|
||||
take(1),
|
||||
map((savedSearches: SavedSearch[]) => {
|
||||
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)),
|
||||
switchMap((updatedSearches: SavedSearch[]) => {
|
||||
return from(this.nodesApi.updateNodeContent(this.savedSearchFileNodeId, JSON.stringify(updatedSearches)));
|
||||
}),
|
||||
catchError((error) => {
|
||||
this.savedSearches$.next(previousSavedSearchesOrder);
|
||||
return throwError(() => error);
|
||||
})
|
||||
)
|
||||
.subscribe();
|
||||
}
|
||||
|
||||
private getSavedSearchesNodeId(): Observable<string> {
|
||||
const localStorageKey = this.getLocalStorageKey();
|
||||
if (this.currentUserLocalStorageKey && this.currentUserLocalStorageKey !== localStorageKey) {
|
||||
|
@@ -596,6 +596,7 @@ export abstract class BaseQueryBuilderService {
|
||||
* @param searchUrl search url to navigate to
|
||||
*/
|
||||
async navigateToSearch(query: string, searchUrl: string) {
|
||||
this.update();
|
||||
this.userQuery = query;
|
||||
await this.execute();
|
||||
await this.router.navigate([searchUrl], {
|
||||
|
Reference in New Issue
Block a user