diff --git a/docs/content-services/components/document-list.component.md b/docs/content-services/components/document-list.component.md index c15407bb24..90012990cb 100644 --- a/docs/content-services/components/document-list.component.md +++ b/docs/content-services/components/document-list.component.md @@ -69,6 +69,7 @@ Displays the documents from a repository. | displayCheckboxesOnHover | `boolean` | false | Enables checkboxes in datatable rows being displayed on hover only. | | displayDragAndDropHint | `boolean` | true | Display drag and drop hint. | | emptyFolderImageUrl | `string` | | Custom image for empty folder. Default value: './assets/images/empty_doc_lib.svg' | +| filters | `string[]` | | Specifies additional filters to apply (joined with **AND**). Applied for recent files only | | filterValue | `any` | | Initial value for filter. | | headerFilters | `boolean` | false | Toggles the header filters mode. | | imageResolver | `any \| null` | null | Custom function to choose image file paths to show. See the [Image Resolver Model](image-resolver.model.md) page for more information. | diff --git a/docs/content-services/services/custom-resources.service.md b/docs/content-services/services/custom-resources.service.md index b50894cac2..fdb9955fdc 100644 --- a/docs/content-services/services/custom-resources.service.md +++ b/docs/content-services/services/custom-resources.service.md @@ -47,12 +47,13 @@ Manages Document List information that is specific to a user. - _includeFields:_ `string[]` - List of data field names to include in the results - _where:_ `string` - (Optional) A string to restrict the returned objects by using a predicate - **Returns** [`Observable`](http://reactivex.io/documentation/observable.html)`<`[`NodePaging`](https://github.com/Alfresco/alfresco-js-api/blob/develop/src/api/content-rest-api/docs/NodePaging.md)`>` - List of favorite files -- **loadFolderByNodeId**(nodeId: `string`, pagination: [`PaginationModel`](../../../lib/core/src/lib/models/pagination.model.ts), includeFields: `string[]` = `[]`, where?: `string`): `any`
+- **loadFolderByNodeId**(nodeId: `string`, pagination: [`PaginationModel`](../../../lib/core/src/lib/models/pagination.model.ts), includeFields: `string[]` = `[]`, where?: `string`, filters?: `string[]`): `any`
Gets a folder's contents. - _nodeId:_ `string` - ID of the target folder node - _pagination:_ [`PaginationModel`](../../../lib/core/src/lib/models/pagination.model.ts) - Specifies how to paginate the results - _includeFields:_ `string[]` - List of data field names to include in the results - _where:_ `string` - (Optional) Filters the Node list using the _where_ condition of the REST API (for example, isFolder=true). See the REST API documentation for more information. + - _filters:_ `string[]` - Specifies additional filters to apply (joined with **AND**). Applied for '-recent-' only. - **Returns** `any` - List of items contained in the folder - **loadMemberSites**(pagination: [`PaginationModel`](../../../lib/core/src/lib/models/pagination.model.ts), where?: `string`): [`Observable`](http://reactivex.io/documentation/observable.html)`<`[`SiteMemberPaging`](https://github.com/Alfresco/alfresco-js-api/blob/develop/src/api/content-rest-api/docs/SiteMemberPaging.md)`>`
Gets sites that the current user is a member of. diff --git a/docs/content-services/services/document-list.service.md b/docs/content-services/services/document-list.service.md index 75ec50f9f7..5539991b69 100644 --- a/docs/content-services/services/document-list.service.md +++ b/docs/content-services/services/document-list.service.md @@ -62,13 +62,14 @@ import { DocumentListService } from '@alfresco/adf-core'; - _nodeId:_ `any` - - **Returns** `boolean` - -- **loadFolderByNodeId**(nodeId: `string`, pagination: `PaginationModel`, includeFields: `string[]`, where?: `string`, orderBy?: `string[]`): `Observable`
+- **loadFolderByNodeId**(nodeId: `string`, pagination: `PaginationModel`, includeFields: `string[]`, where?: `string`, orderBy?: `string[]`, filters?: `string[]`): `Observable`
Load a folder by Node Id. - _nodeId:_ `string` - ID of the folder node - _pagination:_ `PaginationModel` - pagination model - _includeFields:_ `string[]` - List of data field names to include in the results - _where:_ `string` - (Optional) Optionally filter the list - _orderBy:_ `string[]` - (Optional) order by node property + - _filters:_ `string[]` - (Optional) Specifies additional filters to apply (joined with **AND**). Applied for recent files only - **Returns** `Observable` - Details of the folder - **moveNode**(nodeId: `string`, targetParentId: `string`): `Observable`
Moves a node to destination node. diff --git a/lib/content-services/src/lib/breadcrumb/breadcrumb.component.spec.ts b/lib/content-services/src/lib/breadcrumb/breadcrumb.component.spec.ts index 90e54d801d..e98ccf45bb 100644 --- a/lib/content-services/src/lib/breadcrumb/breadcrumb.component.spec.ts +++ b/lib/content-services/src/lib/breadcrumb/breadcrumb.component.spec.ts @@ -116,7 +116,25 @@ describe('Breadcrumb', () => { documentListComponent.DEFAULT_PAGINATION, documentListComponent.includeFields, documentListComponent.where, - documentListComponent.orderBy + documentListComponent.orderBy, + undefined + ); + }); + + it('should update document list with filters param for recent files when filters are provided', () => { + const node = { id: '-id-', name: 'name' }; + component.target = documentListComponent; + documentListComponent.filters = ['filter1', 'filter2']; + + component.onRoutePathClick(node, null); + + expect(documentListService.loadFolderByNodeId).toHaveBeenCalledWith( + node.id, + documentListComponent.DEFAULT_PAGINATION, + documentListComponent.includeFields, + documentListComponent.where, + documentListComponent.orderBy, + documentListComponent.filters ); }); diff --git a/lib/content-services/src/lib/breadcrumb/dropdown-breadcrumb.component.spec.ts b/lib/content-services/src/lib/breadcrumb/dropdown-breadcrumb.component.spec.ts index 59162f1f9b..886b707c65 100644 --- a/lib/content-services/src/lib/breadcrumb/dropdown-breadcrumb.component.spec.ts +++ b/lib/content-services/src/lib/breadcrumb/dropdown-breadcrumb.component.spec.ts @@ -113,7 +113,14 @@ describe('DropdownBreadcrumb', () => { await fixture.whenStable(); clickOnTheFirstOption(); - expect(documentListService.loadFolderByNodeId).toHaveBeenCalledWith('1', documentList.DEFAULT_PAGINATION, undefined, undefined, null); + expect(documentListService.loadFolderByNodeId).toHaveBeenCalledWith( + '1', + documentList.DEFAULT_PAGINATION, + undefined, + undefined, + null, + undefined + ); }); it('should open the selectBox when clicking on the folder icon', (done) => { diff --git a/lib/content-services/src/lib/document-list/components/document-list.component.spec.ts b/lib/content-services/src/lib/document-list/components/document-list.component.spec.ts index 859a4d68ea..3ebc174333 100644 --- a/lib/content-services/src/lib/document-list/components/document-list.component.spec.ts +++ b/lib/content-services/src/lib/document-list/components/document-list.component.spec.ts @@ -1732,6 +1732,23 @@ describe('DocumentList', () => { expect(getEmptyFolderDragDropSubtitle()).toBeUndefined(); }); + it('should call loadFolderByNodeId with filters when they are provided', () => { + spyOn(documentListService, 'loadFolderByNodeId').and.callFake(() => of(new DocumentLoaderNode(null, { list: { pagination: {} } }))); + documentList.filters = ['filter1', 'filter2']; + documentList.currentFolderId = '-recent-'; + documentList.loadFolder(); + + fixture.detectChanges(); + expect(documentListService.loadFolderByNodeId).toHaveBeenCalledWith( + documentList.currentFolderId, + documentList.DEFAULT_PAGINATION, + documentList.includeFields, + documentList.where, + documentList.orderBy, + documentList.filters + ); + }); + describe('Preselect nodes', () => { beforeEach(() => { spyOn(thumbnailService, 'getMimeTypeIcon').and.returnValue(`assets/images/ft_ic_created.svg`); diff --git a/lib/content-services/src/lib/document-list/components/document-list.component.ts b/lib/content-services/src/lib/document-list/components/document-list.component.ts index 8901fb6d23..f2d867d4e4 100644 --- a/lib/content-services/src/lib/document-list/components/document-list.component.ts +++ b/lib/content-services/src/lib/document-list/components/document-list.component.ts @@ -154,6 +154,12 @@ export class DocumentListComponent extends DataTableSchema implements OnInit, On @Input() where: string; + /** + * Specifies additional filters to apply (joined with **AND**). Applied for recent files only. + */ + @Input() + filters: string[]; + /** * Define a set of CSS styles to apply depending on the permission * of the user on that node. See the Permission Style model @@ -789,18 +795,20 @@ export class DocumentListComponent extends DataTableSchema implements OnInit, On this.updateCustomSourceData(this.currentFolderId); } - this.documentListService.loadFolderByNodeId(this.currentFolderId, this._pagination, this.includeFields, this.where, this.orderBy).subscribe( - (documentNode: DocumentLoaderNode) => { - if (documentNode.currentNode) { - this.folderNode = documentNode.currentNode.entry; - this.$folderNode.next(documentNode.currentNode.entry); + this.documentListService + .loadFolderByNodeId(this.currentFolderId, this._pagination, this.includeFields, this.where, this.orderBy, this.filters) + .subscribe({ + next: (documentNode: DocumentLoaderNode) => { + if (documentNode.currentNode) { + this.folderNode = documentNode.currentNode.entry; + this.$folderNode.next(documentNode.currentNode.entry); + } + this.onPageLoaded(documentNode.children); + }, + error: (err) => { + this.handleError(err); } - this.onPageLoaded(documentNode.children); - }, - (err) => { - this.handleError(err); - } - ); + }); } resetSelection() { diff --git a/lib/content-services/src/lib/document-list/interfaces/document-list-loader.interface.ts b/lib/content-services/src/lib/document-list/interfaces/document-list-loader.interface.ts index 7e9cf23e96..9440c6ece9 100644 --- a/lib/content-services/src/lib/document-list/interfaces/document-list-loader.interface.ts +++ b/lib/content-services/src/lib/document-list/interfaces/document-list-loader.interface.ts @@ -20,6 +20,11 @@ import { Observable } from 'rxjs'; import { DocumentLoaderNode } from '../models/document-folder.model'; export interface DocumentListLoader { - - loadFolderByNodeId(nodeId: string, pagination: PaginationModel, includeFields: string[], where?: string): Observable ; + loadFolderByNodeId( + nodeId: string, + pagination: PaginationModel, + includeFields: string[], + where?: string, + filters?: string[] + ): Observable; } diff --git a/lib/content-services/src/lib/document-list/services/custom-resources.service.spec.ts b/lib/content-services/src/lib/document-list/services/custom-resources.service.spec.ts index 0738f746d5..f4e75311f9 100644 --- a/lib/content-services/src/lib/document-list/services/custom-resources.service.spec.ts +++ b/lib/content-services/src/lib/document-list/services/custom-resources.service.spec.ts @@ -19,7 +19,22 @@ import { CustomResourcesService } from './custom-resources.service'; import { PaginationModel } from '@alfresco/adf-core'; import { TestBed } from '@angular/core/testing'; import { ContentTestingModule } from '../../testing/content.testing.module'; -import { Favorite, FavoritePaging, FavoritePagingList } from '@alfresco/js-api'; +import { + Favorite, + FavoritePaging, + FavoritePagingList, + NodeEntry, + NodePaging, + Site, + SiteEntry, + SiteMember, + SitePaging, + SiteRoleEntry, + SiteRolePaging +} from '@alfresco/js-api'; +import { of } from 'rxjs'; + +type Extra = T & K; describe('CustomResourcesService', () => { let customResourcesService: CustomResourcesService; @@ -131,4 +146,376 @@ describe('CustomResourcesService', () => { }); }); }); + + describe('loadFolderByNodeId', () => { + const pagination: PaginationModel = { + maxItems: 100, + skipCount: 0 + }; + + it('should return null observable when nodeId is not from custom source', (done) => { + customResourcesService.loadFolderByNodeId('unsupported', pagination).subscribe((result) => { + expect(result).toBeNull(); + done(); + }); + }); + + it('should call loadTrashcan when nodeId is -trashcan-', () => { + spyOn(customResourcesService, 'loadTrashcan').and.stub(); + + customResourcesService.loadFolderByNodeId('-trashcan-', pagination); + expect(customResourcesService.loadTrashcan).toHaveBeenCalledWith(pagination, []); + }); + + it('should call loadSharedLinks when nodeId is -sharedlinks-', () => { + spyOn(customResourcesService, 'loadSharedLinks').and.stub(); + + customResourcesService.loadFolderByNodeId('-sharedlinks-', pagination, ['include'], 'where'); + expect(customResourcesService.loadSharedLinks).toHaveBeenCalledWith(pagination, ['include'], 'where'); + }); + + it('should call loadSites when nodeId is -sites-', () => { + spyOn(customResourcesService, 'loadSites').and.stub(); + + customResourcesService.loadFolderByNodeId('-sites-', pagination, ['include'], 'where'); + expect(customResourcesService.loadSites).toHaveBeenCalledWith(pagination, 'where'); + }); + + it('should call loadMemberSites when nodeId is -mysites-', () => { + spyOn(customResourcesService, 'loadMemberSites').and.stub(); + + customResourcesService.loadFolderByNodeId('-mysites-', pagination, ['include'], 'where'); + expect(customResourcesService.loadMemberSites).toHaveBeenCalledWith(pagination, 'where'); + }); + + it('should call loadFavorites when nodeId is -favorites-', () => { + spyOn(customResourcesService, 'loadFavorites').and.stub(); + + customResourcesService.loadFolderByNodeId('-favorites-', pagination, ['include'], 'where'); + expect(customResourcesService.loadFavorites).toHaveBeenCalledWith(pagination, ['include'], 'where'); + }); + + it('should call getRecentFiles when nodeId is -recent-', () => { + spyOn(customResourcesService, 'getRecentFiles').and.stub(); + + customResourcesService.loadFolderByNodeId('-recent-', pagination, ['include'], 'where', ['filters']); + expect(customResourcesService.getRecentFiles).toHaveBeenCalledWith('-me-', pagination, ['filters']); + }); + }); + + describe('hasCorrespondingNodeIds', () => { + it('should return true when nodeId comes from custom source', () => { + spyOn(customResourcesService, 'isCustomSource').and.returnValue(true); + + const result = customResourcesService.hasCorrespondingNodeIds('-trashcan-'); + expect(result).toBeTrue(); + }); + + it('should return true when nodeId comes from supported source', () => { + spyOn(customResourcesService, 'isCustomSource').and.returnValue(false); + spyOn(customResourcesService, 'isSupportedSource').and.returnValue(true); + + const result = customResourcesService.hasCorrespondingNodeIds('-trashcan-'); + expect(result).toBeTrue(); + }); + + it('should return false when nodeId comes from neither custom nor supported source', () => { + spyOn(customResourcesService, 'isCustomSource').and.returnValue(false); + spyOn(customResourcesService, 'isSupportedSource').and.returnValue(false); + + const result = customResourcesService.hasCorrespondingNodeIds('-trashcan-'); + expect(result).toBeFalse(); + }); + }); + + describe('getIdFromEntry', () => { + const fakeNode: any = { + entry: { + id: 'some-id', + nodeId: 'some-node-id', + guid: 'guid', + targetGuid: 'target-guid' + } + }; + + it('should return nodeId for shared links', () => { + expect(customResourcesService.getIdFromEntry(fakeNode, '-sharedlinks-')).toBe('some-node-id'); + }); + + it('should return guid for sites and my sites', () => { + expect(customResourcesService.getIdFromEntry(fakeNode, '-sites-')).toBe('guid'); + expect(customResourcesService.getIdFromEntry(fakeNode, '-mysites-')).toBe('guid'); + }); + + it('should return targetGuid for favorites', () => { + expect(customResourcesService.getIdFromEntry(fakeNode, '-favorites-')).toBe('target-guid'); + }); + + it('should return id for other custom sources', () => { + expect(customResourcesService.getIdFromEntry(fakeNode, '-trashcan-')).toBe('some-id'); + }); + }); + + describe('getCorrespondingNodeIds', () => { + const fakeNode: NodeEntry = { + entry: { + id: 'fake-id', + name: 'fake-name', + nodeType: 'cm:folder', + isFolder: true, + isFile: false, + modifiedAt: new Date(), + createdAt: new Date(), + createdByUser: { id: 'fake-user', displayName: 'Fake User' }, + modifiedByUser: { id: 'fake-user', displayName: 'Fake User' }, + parentId: 'fake-parent-id' + } + }; + + const fakePaging: NodePaging = { + list: { + pagination: { count: 1, hasMoreItems: false, totalItems: 1, skipCount: 0, maxItems: 20 }, + entries: [fakeNode] + } + }; + + it('should return empty array when source is not custom and nodeId is not defined', (done) => { + spyOn(customResourcesService, 'isCustomSource').and.returnValue(false); + + customResourcesService.getCorrespondingNodeIds(undefined).subscribe((result) => { + expect(result).toEqual([]); + done(); + }); + }); + + it('should return node id when source is not custom and nodeId is defined', (done) => { + spyOn(customResourcesService, 'isCustomSource').and.returnValue(false); + spyOn(customResourcesService['nodesApi'], 'getNode').and.returnValue(Promise.resolve(fakeNode)); + + customResourcesService.getCorrespondingNodeIds('nodeId').subscribe((result) => { + expect(result).toEqual(['fake-id']); + done(); + }); + }); + + it('should return node id when source is custom', (done) => { + spyOn(customResourcesService, 'isCustomSource').and.returnValue(true); + spyOn(customResourcesService, 'loadFolderByNodeId').and.returnValue(of(fakePaging)); + spyOn(customResourcesService, 'getIdFromEntry').and.callThrough(); + + customResourcesService.getCorrespondingNodeIds('nodeId').subscribe((result) => { + expect(result).toEqual(['fake-id']); + expect(customResourcesService.getIdFromEntry).toHaveBeenCalledWith(fakeNode, 'nodeId'); + done(); + }); + }); + }); + + describe('isSupportedSource', () => { + it('should return true for supported sources', () => { + expect(customResourcesService.isSupportedSource('-my-')).toBeTrue(); + expect(customResourcesService.isSupportedSource('-root-')).toBeTrue(); + expect(customResourcesService.isSupportedSource('-shared-')).toBeTrue(); + }); + + it('should return false for unsupported sources', () => { + expect(customResourcesService.isSupportedSource('-unsupported-')).toBeFalse(); + expect(customResourcesService.isSupportedSource('regular-node-id')).toBeFalse(); + }); + }); + + describe('isCustomSource', () => { + it('should return true for custom sources', () => { + expect(customResourcesService.isCustomSource('-trashcan-')).toBeTrue(); + expect(customResourcesService.isCustomSource('-sharedlinks-')).toBeTrue(); + expect(customResourcesService.isCustomSource('-sites-')).toBeTrue(); + expect(customResourcesService.isCustomSource('-mysites-')).toBeTrue(); + expect(customResourcesService.isCustomSource('-favorites-')).toBeTrue(); + expect(customResourcesService.isCustomSource('-recent-')).toBeTrue(); + }); + + it('should return false for other sources', () => { + expect(customResourcesService.isCustomSource('-unsupported-')).toBeFalse(); + expect(customResourcesService.isCustomSource('regular-node-id')).toBeFalse(); + }); + }); + + describe('loadSharedLinks', () => { + it('should call sharedLinksApi.listSharedLinks', (done) => { + spyOn(customResourcesService.sharedLinksApi, 'listSharedLinks').and.callFake(() => Promise.resolve(null)); + + customResourcesService.loadSharedLinks({ maxItems: 10, skipCount: 0 }).subscribe(() => { + expect(customResourcesService.sharedLinksApi.listSharedLinks).toHaveBeenCalledWith({ + include: ['path', 'properties', 'allowableOperations', 'permissions', 'aspectNames'], + maxItems: 10, + skipCount: 0, + where: undefined + }); + done(); + }); + }); + + it('should call sharedLinksApi.listSharedLinks with custom params when provided', (done) => { + spyOn(customResourcesService.sharedLinksApi, 'listSharedLinks').and.callFake(() => Promise.resolve(null)); + + customResourcesService.loadSharedLinks({ maxItems: 10, skipCount: 0 }, ['custom'], 'customWhere').subscribe(() => { + expect(customResourcesService.sharedLinksApi.listSharedLinks).toHaveBeenCalledWith({ + include: ['path', 'properties', 'allowableOperations', 'permissions', 'aspectNames', 'custom'], + maxItems: 10, + skipCount: 0, + where: 'customWhere' + }); + done(); + }); + }); + }); + + describe('loadTrashcan', () => { + it('should call trashcanApi.listDeletedNodes', (done) => { + spyOn(customResourcesService.trashcanApi, 'listDeletedNodes').and.callFake(() => Promise.resolve(null)); + + customResourcesService.loadTrashcan({ maxItems: 10, skipCount: 0 }).subscribe(() => { + expect(customResourcesService.trashcanApi.listDeletedNodes).toHaveBeenCalledWith({ + include: ['path', 'properties', 'allowableOperations', 'permissions', 'aspectNames'], + maxItems: 10, + skipCount: 0 + }); + done(); + }); + }); + + it('should call trashcanApi.listDeletedNodes with custom params when provided', (done) => { + spyOn(customResourcesService.trashcanApi, 'listDeletedNodes').and.callFake(() => Promise.resolve(null)); + + customResourcesService.loadTrashcan({ maxItems: 10, skipCount: 0 }, ['custom']).subscribe(() => { + expect(customResourcesService.trashcanApi.listDeletedNodes).toHaveBeenCalledWith({ + include: ['path', 'properties', 'allowableOperations', 'permissions', 'aspectNames', 'custom'], + maxItems: 10, + skipCount: 0 + }); + done(); + }); + }); + }); + + describe('loadSites', () => { + const fakeSite: SiteEntry = { + entry: { + id: 'fake-site-id', + guid: 'fake-site-guid', + title: 'Fake Site', + description: 'This is a fake site for testing purposes', + visibility: 'PUBLIC' + } + }; + + const fakeSitePaging: SitePaging = { + list: { + entries: [fakeSite], + pagination: { + count: 1, + hasMoreItems: false, + totalItems: 1, + skipCount: 0, + maxItems: 25 + } + } + }; + + it('should call sitesApi.listSites', (done) => { + spyOn(customResourcesService.sitesApi, 'listSites').and.callFake(() => Promise.resolve(fakeSitePaging)); + + customResourcesService.loadSites({ maxItems: 10, skipCount: 0 }, 'where').subscribe((result) => { + expect(customResourcesService.sitesApi.listSites).toHaveBeenCalledWith({ + include: ['properties', 'aspectNames'], + maxItems: 10, + skipCount: 0, + where: 'where' + }); + expect(result).toEqual(fakeSitePaging); + expect((result.list.entries[0].entry as Extra).name).toBe(fakeSite.entry.title); + done(); + }); + }); + + it('should return error when sitesApi.listSites fails', (done) => { + spyOn(customResourcesService.sitesApi, 'listSites').and.rejectWith(new Error('Error')); + customResourcesService.loadSites({ maxItems: 10, skipCount: 0 }, 'where').subscribe({ + next: () => { + fail('Expected to throw an error'); + done(); + }, + error: (error) => { + expect(error).toEqual(new Error('Error')); + done(); + } + }); + }); + }); + + describe('loadMemberSites', () => { + const fakeSite: SiteEntry = { + entry: { + id: 'fake-site-id', + guid: 'fake-site-guid', + title: 'Fake Site', + description: 'This is a fake site for testing purposes', + visibility: 'PUBLIC' + } + }; + + const fakeRole: SiteRoleEntry = { + entry: { + id: 'fake-site-id', + guid: 'fake-site-guid', + role: 'Fake Role', + site: fakeSite.entry + } + }; + + const fakeRolePaging: SiteRolePaging = { + list: { + entries: [fakeRole], + pagination: { + count: 1, + hasMoreItems: false, + totalItems: 1, + skipCount: 0, + maxItems: 25 + } + } + }; + + it('should call sitesApi.listSiteMembershipsForPerson', (done) => { + spyOn(customResourcesService.sitesApi, 'listSiteMembershipsForPerson').and.callFake(() => Promise.resolve(fakeRolePaging)); + + customResourcesService.loadMemberSites({ maxItems: 10, skipCount: 0 }, 'where').subscribe((result) => { + expect(customResourcesService.sitesApi.listSiteMembershipsForPerson).toHaveBeenCalledWith('-me-', { + include: ['properties'], + maxItems: 10, + skipCount: 0, + where: 'where' + }); + expect((result.list.entries[0].entry as Extra).name).toBe(fakeSite.entry.title); + expect((result.list.entries[0].entry as Extra).allowableOperations).toEqual([ + 'create' + ]); + done(); + }); + }); + + it('should return error when sitesApi.listSiteMembershipsForPerson fails', (done) => { + spyOn(customResourcesService.sitesApi, 'listSiteMembershipsForPerson').and.rejectWith(new Error('Error')); + customResourcesService.loadMemberSites({ maxItems: 10, skipCount: 0 }, 'where').subscribe({ + next: () => { + fail('Expected to throw an error'); + done(); + }, + error: (error) => { + expect(error).toEqual(new Error('Error')); + done(); + } + }); + }); + }); }); diff --git a/lib/content-services/src/lib/document-list/services/custom-resources.service.ts b/lib/content-services/src/lib/document-list/services/custom-resources.service.ts index eba75b71f0..2869f3cece 100644 --- a/lib/content-services/src/lib/document-list/services/custom-resources.service.ts +++ b/lib/content-services/src/lib/document-list/services/custom-resources.service.ts @@ -385,9 +385,10 @@ export class CustomResourcesService { * @param pagination Specifies how to paginate the results * @param includeFields List of data field names to include in the results * @param where Filters the Node list using the *where* condition of the REST API (for example, isFolder=true). See the REST API documentation for more information. + * @param filters Specifies additional filters to apply (joined with **AND**). Applied for '-recent-' only. * @returns List of items contained in the folder */ - loadFolderByNodeId(nodeId: string, pagination: PaginationModel, includeFields: string[] = [], where?: string): any { + loadFolderByNodeId(nodeId: string, pagination: PaginationModel, includeFields: string[] = [], where?: string, filters?: string[]): any { if (nodeId === '-trashcan-') { return this.loadTrashcan(pagination, includeFields); } else if (nodeId === '-sharedlinks-') { @@ -399,7 +400,9 @@ export class CustomResourcesService { } else if (nodeId === '-favorites-') { return this.loadFavorites(pagination, includeFields, where); } else if (nodeId === '-recent-') { - return this.getRecentFiles('-me-', pagination); + return this.getRecentFiles('-me-', pagination, filters); + } else { + return of(null); } } diff --git a/lib/content-services/src/lib/document-list/services/document-list.service.spec.ts b/lib/content-services/src/lib/document-list/services/document-list.service.spec.ts index 977152330e..da0b41644a 100644 --- a/lib/content-services/src/lib/document-list/services/document-list.service.spec.ts +++ b/lib/content-services/src/lib/document-list/services/document-list.service.spec.ts @@ -18,11 +18,17 @@ import { DocumentListService } from './document-list.service'; import { fakeAsync, TestBed } from '@angular/core/testing'; import { ContentTestingModule } from '../../testing/content.testing.module'; +import { of } from 'rxjs'; +import { NodeEntry, NodePaging } from '@alfresco/js-api'; +import { CustomResourcesService } from './custom-resources.service'; +import { NodesApiService } from '../../common'; declare let jasmine: any; describe('DocumentListService', () => { let service: DocumentListService; + let customResourcesService: CustomResourcesService; + let nodesApiService: NodesApiService; const fakeFolder = { list: { @@ -71,6 +77,8 @@ describe('DocumentListService', () => { imports: [ContentTestingModule] }); service = TestBed.inject(DocumentListService); + customResourcesService = TestBed.inject(CustomResourcesService); + nodesApiService = TestBed.inject(NodesApiService); jasmine.Ajax.install(); }); @@ -109,8 +117,38 @@ describe('DocumentListService', () => { }); })); + it('should use rootFolderId provided in options', () => { + const spyGetNodeInfo = spyOn(service.nodes, 'listNodeChildren').and.callThrough(); + + service.getFolder('/fake-root/fake-name', { rootFolderId: 'testRoot' }, ['isLocked']); + + expect(spyGetNodeInfo).toHaveBeenCalledWith('testRoot', { + includeSource: true, + include: ['path', 'properties', 'allowableOperations', 'permissions', 'aspectNames', 'isLocked'], + relativePath: '/fake-root/fake-name' + }); + }); + + it('should use provided other values passed in options', () => { + const spyGetNodeInfo = spyOn(service.nodes, 'listNodeChildren').and.callThrough(); + + service.getFolder('/fake-root/fake-name', { rootFolderId: 'testRoot', maxItems: 10, skipCount: 5, where: 'where', orderBy: ['order'] }, [ + 'isLocked' + ]); + + expect(spyGetNodeInfo).toHaveBeenCalledWith('testRoot', { + includeSource: true, + include: ['path', 'properties', 'allowableOperations', 'permissions', 'aspectNames', 'isLocked'], + relativePath: '/fake-root/fake-name', + maxItems: 10, + skipCount: 5, + where: 'where', + orderBy: ['order'] + }); + }); + it('should add the includeTypes in the request Node Children if required', () => { - const spyGetNodeInfo = spyOn(service['nodes'], 'listNodeChildren').and.callThrough(); + const spyGetNodeInfo = spyOn(service.nodes, 'listNodeChildren').and.callThrough(); service.getFolder('/fake-root/fake-name', {}, ['isLocked']); @@ -122,7 +160,7 @@ describe('DocumentListService', () => { }); it('should not add the includeTypes in the request Node Children if is duplicated', () => { - const spyGetNodeInfo = spyOn(service['nodes'], 'listNodeChildren').and.callThrough(); + const spyGetNodeInfo = spyOn(service.nodes, 'listNodeChildren').and.callThrough(); service.getFolder('/fake-root/fake-name', {}, ['allowableOperations']); @@ -134,7 +172,7 @@ describe('DocumentListService', () => { }); it('should add the includeTypes in the request getFolderNode if required', () => { - const spyGetNodeInfo = spyOn(service['nodes'], 'getNode').and.callThrough(); + const spyGetNodeInfo = spyOn(service.nodes, 'getNode').and.callThrough(); service.getFolderNode('test-id', ['isLocked']); @@ -145,7 +183,7 @@ describe('DocumentListService', () => { }); it('should not add the includeTypes in the request getFolderNode if is duplicated', () => { - const spyGetNodeInfo = spyOn(service['nodes'], 'getNode').and.callThrough(); + const spyGetNodeInfo = spyOn(service.nodes, 'getNode').and.callThrough(); service.getFolderNode('test-id', ['allowableOperations']); @@ -155,6 +193,17 @@ describe('DocumentListService', () => { }); }); + it('should add default includeTypes in the request getFolderNode if none is provided', () => { + const spyGetNodeInfo = spyOn(service.nodes, 'getNode').and.callThrough(); + + service.getFolderNode('test-id'); + + expect(spyGetNodeInfo).toHaveBeenCalledWith('test-id', { + includeSource: true, + include: ['path', 'properties', 'allowableOperations', 'permissions', 'aspectNames'] + }); + }); + it('should delete the folder', fakeAsync(() => { service.deleteNode('fake-id').subscribe((res) => { expect(res).toBe(''); @@ -185,4 +234,95 @@ describe('DocumentListService', () => { jasmine.Ajax.requests.mostRecent().respondWith({ status: 200, contentType: 'json' }); }); + + it('should call isCustomSource from customResourcesService when isCustomSourceService is called', () => { + spyOn(customResourcesService, 'isCustomSource').and.returnValue(true); + + const isCustom = service.isCustomSourceService('fake-source-id'); + expect(isCustom).toBeTrue(); + expect(customResourcesService.isCustomSource).toHaveBeenCalledWith('fake-source-id'); + }); + + it('should call getNode from nodes service with proper params when getNode is called', () => { + spyOn(nodesApiService, 'getNode').and.returnValue(of(null)); + + service.getNode('fake-node-id'); + + expect(nodesApiService.getNode).toHaveBeenCalledWith('fake-node-id', { + includeSource: true, + include: ['path', 'properties', 'allowableOperations', 'permissions', 'definition'] + }); + }); + + it('should removed duplicated include values for getNode', () => { + spyOn(nodesApiService, 'getNode').and.returnValue(of(null)); + + service.getNode('fake-node-id', ['aspectNames', 'path', 'properties', 'custom1', 'custom2']); + + expect(nodesApiService.getNode).toHaveBeenCalledWith('fake-node-id', { + includeSource: true, + include: ['path', 'properties', 'allowableOperations', 'permissions', 'definition', 'aspectNames', 'custom1', 'custom2'] + }); + }); + + describe('loadFolderByNodeId', () => { + const fakeFolderLocal: NodeEntry = { + entry: { + id: 'fake-id', + name: 'fake-name', + nodeType: 'cm:folder', + isFolder: true, + isFile: false, + modifiedAt: new Date(), + createdAt: new Date(), + createdByUser: { id: 'fake-user', displayName: 'Fake User' }, + modifiedByUser: { id: 'fake-user', displayName: 'Fake User' }, + parentId: 'fake-parent-id' + } + }; + + const fakeFolderPaging: NodePaging = { + list: { + pagination: { count: 1, hasMoreItems: false, totalItems: 1, skipCount: 0, maxItems: 20 }, + entries: [fakeFolderLocal] + } + }; + it('should call getFolder and getFolderNode when source is not custom', (done) => { + spyOn(service, 'getFolder').and.returnValue(of(fakeFolderPaging)); + spyOn(service, 'getFolderNode').and.returnValue(of(fakeFolderLocal)); + spyOn(customResourcesService, 'isCustomSource').and.returnValue(false); + + service.loadFolderByNodeId('fake-id', { maxItems: 10, skipCount: 0 }, ['includeMe'], 'where', ['order']).subscribe((result) => { + expect(service.getFolder).toHaveBeenCalledWith( + null, + { maxItems: 10, skipCount: 0, orderBy: ['order'], rootFolderId: 'fake-id', where: 'where' }, + ['includeMe'] + ); + expect(service.getFolderNode).toHaveBeenCalledWith('fake-id', ['includeMe']); + expect(result.currentNode).toEqual(fakeFolderLocal); + expect(result.children).toEqual(fakeFolderPaging); + done(); + }); + }); + + it('should call customResourcesService.loadFolderByNodeId when source is custom', (done) => { + spyOn(customResourcesService, 'isCustomSource').and.returnValue(true); + spyOn(customResourcesService, 'loadFolderByNodeId').and.returnValue(of(fakeFolderPaging)); + + service + .loadFolderByNodeId('fake-id', { maxItems: 10, skipCount: 0 }, ['includeMe'], 'where', ['order'], ['filter']) + .subscribe((result) => { + expect(customResourcesService.loadFolderByNodeId).toHaveBeenCalledWith( + 'fake-id', + { maxItems: 10, skipCount: 0 }, + ['includeMe'], + 'where', + ['filter'] + ); + expect(result.currentNode).toBeNull(); + expect(result.children).toEqual(fakeFolderPaging); + done(); + }); + }); + }); }); diff --git a/lib/content-services/src/lib/document-list/services/document-list.service.ts b/lib/content-services/src/lib/document-list/services/document-list.service.ts index 97e542129c..8f6c98e314 100644 --- a/lib/content-services/src/lib/document-list/services/document-list.service.ts +++ b/lib/content-services/src/lib/document-list/services/document-list.service.ts @@ -190,6 +190,7 @@ export class DocumentListService implements DocumentListLoader { * @param includeFields List of data field names to include in the results * @param where Optionally filter the list * @param orderBy order by node property + * @param filters Specifies additional filters to apply (joined with **AND**). Applied for recent files only. * @returns Details of the folder */ loadFolderByNodeId( @@ -197,11 +198,12 @@ export class DocumentListService implements DocumentListLoader { pagination: PaginationModel, includeFields: string[], where?: string, - orderBy?: string[] + orderBy?: string[], + filters?: string[] ): Observable { if (this.customResourcesService.isCustomSource(nodeId)) { return this.customResourcesService - .loadFolderByNodeId(nodeId, pagination, includeFields, where) + .loadFolderByNodeId(nodeId, pagination, includeFields, where, filters) .pipe(map((result: any) => new DocumentLoaderNode(null, result))); } else { return this.retrieveDocumentNode(nodeId, pagination, includeFields, where, orderBy);