From ddd508ba60faf812fa598aabb3c92fdc13e03730 Mon Sep 17 00:00:00 2001 From: Darya Blavanovich <166367848+DaryaBalvanovich@users.noreply.github.com> Date: Fri, 9 Aug 2024 13:20:07 +0200 Subject: [PATCH] [ACS-8036] [FE] Legal Hold in ADW. Part 2 (Bulk) (#10062) * ACS-8329 Manage bulks endpoint (#9972) * ACS-8325 extend ContentActionRef with tooltip (#9998) * [ACS-8425] Add functionality to select folder and assign it to a hold (#10011) * ACS-8425 add bulkFolderHold method * ACS-8425 refactor names and add more specific query to folder * ACS-8425 refactor names * ACS-8425 fix description in md file * ACS-8425 update interfaces and add constant * ACS-8425 fix readme file and refactor tests * ACS-8425 fix readme * ACS-8425 fix readme * ACS-8036 fix import path --------- Co-authored-by: Tomasz Nastaly --- .../services/legal-hold.service.md | 18 ++- .../services/category.service.spec.ts | 7 +- .../lib/category/services/category.service.ts | 15 +- .../lib/common/services/nodes-api.service.ts | 151 +++++++++--------- .../services/custom-resources.service.ts | 5 +- .../legal-hold/services/legal-hold.service.ts | 30 +++- .../services/legal-holds.service.spec.ts | 62 +++++-- .../src/lib/mock/search-query.mock.ts | 4 +- .../services/base-query-builder.service.ts | 16 +- lib/core/src/lib/styles/_mat-selectors.scss | 1 + .../src/lib/config/action.extensions.ts | 1 + lib/js-api/index.ts | 1 + .../gs-core-rest-api/api/legal-hold.api.ts | 23 +++ .../docs/BulkAssignHoldResponse.md | 17 ++ .../api/gs-core-rest-api/docs/LegalHoldApi.md | 107 +++++++++---- .../model/bulkAssignHoldResponse.ts | 21 +++ .../src/api/gs-core-rest-api/model/index.ts | 1 + lib/js-api/src/constants/index.ts | 18 +++ .../src/constants/language.constants.ts | 22 +++ lib/js-api/src/index.ts | 1 + .../content-services/search.mock.ts | 3 +- lib/js-api/test/searchApi.spec.spec.ts | 4 +- 22 files changed, 388 insertions(+), 140 deletions(-) create mode 100644 lib/js-api/src/api/gs-core-rest-api/docs/BulkAssignHoldResponse.md create mode 100644 lib/js-api/src/api/gs-core-rest-api/model/bulkAssignHoldResponse.ts create mode 100644 lib/js-api/src/constants/index.ts create mode 100644 lib/js-api/src/constants/language.constants.ts diff --git a/docs/content-services/services/legal-hold.service.md b/docs/content-services/services/legal-hold.service.md index 6ddb4408d4..ecd5905ad1 100644 --- a/docs/content-services/services/legal-hold.service.md +++ b/docs/content-services/services/legal-hold.service.md @@ -18,11 +18,13 @@ Manages holds for nodes. - _filePlanId_: `string` - The identifier of a file plan. You can also use the -filePlan- alias - _options_: `ContentPagingQuery` - Optional parameters supported by JS-API - **Returns** [`Observable`](http://reactivex.io/documentation/observable.html)`<`[`Hold`](../../../lib/js-api/src/api/gs-core-rest-api/docs/Hold.md)`[]>` - List of holds
+ - **createHold**(filePlanId: `string`, hold: [`HoldBody`](../../../lib/js-api/src/api/gs-core-rest-api/docs/HoldBody.md)): [`Observable`](http://reactivex.io/documentation/observable.html)`<`[`HoldEntry`](../../../lib/js-api/src/api/gs-core-rest-api/docs/HoldEntry.md)`>`
Create new hold in File Plan. - _filePlanId_: `string` - The identifier of a file plan. You can also use the -filePlan- alias - _hold_: [`HoldBody`](../../../lib/js-api/src/api/gs-core-rest-api/docs/HoldBody.md) - Hold that should be created - **Returns** [`Observable`](http://reactivex.io/documentation/observable.html)`<`[`HoldEntry`](../../../lib/js-api/src/api/gs-core-rest-api/docs/HoldEntry.md)`>` - Hold entry
+ - **createHolds**(filePlanId: `string`, holds: [`HoldBody`](../../../lib/js-api/src/api/gs-core-rest-api/docs/HoldBody.md)`[]`): [`Observable`](http://reactivex.io/documentation/observable.html)`<`[`Hold`](../../../lib/js-api/src/api/gs-core-rest-api/docs/Hold.md)`[]>`
Create new holds in File Plan. - _filePlanId_: `string` - The identifier of a file plan. You can also use the -filePlan- alias @@ -33,7 +35,7 @@ Manages holds for nodes. Assign a node to a hold. - _nodeId_: `string` - The Id of the node which will be assigned to a hold - _holdId_: `string` - The Id of the hold to which nodes will be assigned - - **Returns** [`Observable`](http://reactivex.io/documentation/observable.html)`<`[`HoldEntry`](../../../lib/js-api/src/api/gs-core-rest-api/docs/HoldEntry.md)`>` - Entry with the hold + - **Returns** [`Observable`](http://reactivex.io/documentation/observable.html)`<`[`HoldEntry`](../../../lib/js-api/src/api/gs-core-rest-api/docs/HoldEntry.md)`>` - Entry with the hold
- **assignHolds**(nodeIds: `<{id: string}[]>`, holdId: `string`): [`Observable`](http://reactivex.io/documentation/observable.html)`<`[`HoldPaging`](../../../lib/js-api/src/api/gs-core-rest-api/docs/HoldPaging.md)`>`
Assign multiple nodes to a hold. @@ -47,6 +49,20 @@ Manages holds for nodes. - _nodeId_: `string` - The Id of the node which is unassigned - **Returns** [`Observable`](http://reactivex.io/documentation/observable.html)`` +- **bulkAssignHold**(holdId: `string`, query: [`RequestQuery`](../../../lib/js-api/src/api/search-rest-api/docs/RequestQuery.md)): [`Observable`](http://reactivex.io/documentation/observable.html)`<`[`BulkAssignHoldResponse`](../../../lib/js-api/src/api/gs-core-rest-api/docs/BulkAssignHoldResponse.md)`>`
+ Assign multiple files to a hold. + - _holdId_: `string` - The hold id + - _query_: [`RequestQuery`](../../../lib/js-api/src/api/search-rest-api/docs/RequestQuery.md) - Search query + - **Returns** [`Observable`](http://reactivex.io/documentation/observable.html)`<`[`BulkAssignHoldResponse`](../../../lib/js-api/src/api/gs-core-rest-api/docs/BulkAssignHoldResponse.md)`>` - Bulk status
+ +- **bulkAssignHoldToFolder**(holdId: `string`, folderId: `string`, language: `string`): [`Observable`](http://reactivex.io/documentation/observable.html)`<`[`BulkAssignHoldResponse`](../../../lib/js-api/src/api/gs-core-rest-api/docs/BulkAssignHoldResponse.md)`>`
+ Assign a folder to a hold. + - _holdId_: `string` - The hold id + - _folderId_: `string` - The folder id + - _language_: `string` - Language code. `afts` can be used for search query + + - **Returns** [`Observable`](http://reactivex.io/documentation/observable.html)`<`[`BulkAssignHoldResponse`](../../../lib/js-api/src/api/gs-core-rest-api/docs/BulkAssignHoldResponse.md)`>` - Bulk status
+ ## Details diff --git a/lib/content-services/src/lib/category/services/category.service.spec.ts b/lib/content-services/src/lib/category/services/category.service.spec.ts index 21bce87407..4e62690fad 100644 --- a/lib/content-services/src/lib/category/services/category.service.spec.ts +++ b/lib/content-services/src/lib/category/services/category.service.spec.ts @@ -25,7 +25,8 @@ import { ResultNode, ResultSetPaging, ResultSetPagingList, - ResultSetRowEntry + ResultSetRowEntry, + SEARCH_LANGUAGE } from '@alfresco/js-api'; import { fakeAsync, TestBed } from '@angular/core/testing'; import { CategoryService } from './category.service'; @@ -122,7 +123,7 @@ describe('CategoryService', () => { categoryService.searchCategories(name, skipCount, maxItems); expect(categoryService.searchApi.search).toHaveBeenCalledWith({ query: { - language: 'afts', + language: SEARCH_LANGUAGE.AFTS, query: `cm:name:"*${name}*" AND TYPE:'cm:category' AND PATH:"/cm:categoryRoot/cm:generalclassifiable//*"` }, paging: { @@ -139,7 +140,7 @@ describe('CategoryService', () => { categoryService.searchCategories(name); expect(categoryService.searchApi.search).toHaveBeenCalledWith({ query: { - language: 'afts', + language: SEARCH_LANGUAGE.AFTS, query: `cm:name:"*${name}*" AND TYPE:'cm:category' AND PATH:"/cm:categoryRoot/cm:generalclassifiable//*"` }, paging: { diff --git a/lib/content-services/src/lib/category/services/category.service.ts b/lib/content-services/src/lib/category/services/category.service.ts index ba9a8b8fff..f2ce9861ac 100644 --- a/lib/content-services/src/lib/category/services/category.service.ts +++ b/lib/content-services/src/lib/category/services/category.service.ts @@ -17,7 +17,16 @@ import { Injectable } from '@angular/core'; import { AlfrescoApiService, AppConfigService, UserPreferencesService } from '@alfresco/adf-core'; -import { CategoriesApi, CategoryBody, CategoryEntry, CategoryLinkBody, CategoryPaging, ResultSetPaging, SearchApi } from '@alfresco/js-api'; +import { + CategoriesApi, + CategoryBody, + CategoryEntry, + CategoryLinkBody, + CategoryPaging, + ResultSetPaging, + SearchApi, + SEARCH_LANGUAGE +} from '@alfresco/js-api'; import { from, Observable } from 'rxjs'; @Injectable({ providedIn: 'root' }) @@ -113,7 +122,7 @@ export class CategoryService { return from( this.searchApi.search({ query: { - language: 'afts', + language: SEARCH_LANGUAGE.AFTS, query: `cm:name:"*${name}*" AND TYPE:'cm:category' AND PATH:"/cm:categoryRoot/cm:generalclassifiable//*"` }, paging: { @@ -163,6 +172,6 @@ export class CategoryService { * @returns boolean true if categories plugin is enabled, false otherwise. */ areCategoriesEnabled(): boolean { - return this.appConfigService.get('plugins.categoriesEnabled', true); + return this.appConfigService.get('plugins.categoriesEnabled', true); } } diff --git a/lib/content-services/src/lib/common/services/nodes-api.service.ts b/lib/content-services/src/lib/common/services/nodes-api.service.ts index e1bdc890e1..3ca09d12c2 100644 --- a/lib/content-services/src/lib/common/services/nodes-api.service.ts +++ b/lib/content-services/src/lib/common/services/nodes-api.service.ts @@ -43,7 +43,10 @@ export class NodesApiService { return this._nodesApi; } - constructor(private apiService: AlfrescoApiService, private preferences: UserPreferencesService) {} + constructor( + private apiService: AlfrescoApiService, + private preferences: UserPreferencesService + ) {} private getEntryFromEntity(entity: NodeEntry): Node { return entity.entry; @@ -164,82 +167,10 @@ export class NodesApiService { * @param nodeId ID of the target node * @returns Node metadata */ - public getNodeMetadata(nodeId: string): Observable { + getNodeMetadata(nodeId: string): Observable { return from(this.nodesApi.getNode(nodeId)).pipe(map(this.cleanMetadataFromSemicolon)); } - /** - * Create a new Node from form metadata. - * - * @param nodeType Node type - * @param nameSpace Namespace for properties - * @param data Property data to store in the node under namespace - * @param path Path to the node - * @param name Node name - * @returns The created node - */ - public createNodeMetadata(nodeType: string, nameSpace: any, data: any, path: string, name?: string): Observable { - const properties = {}; - for (const key in data) { - if (data[key]) { - properties[nameSpace + ':' + key] = data[key]; - } - } - - return this.createNodeInsideRoot(name || this.randomNodeName(), nodeType, properties, path); - } - - private randomNodeName(): string { - return `node_${Date.now()}`; - } - - /** - * Gets content for the given node. - * - * @param nodeId ID of the target node - * @returns Content data - */ - getNodeContent(nodeId: string): Observable { - return from(this.nodesApi.getNodeContent(nodeId)).pipe(catchError((err) => throwError(err))); - } - - /** - * Create a new Node inside `-root-` folder - * - * @param name Node name - * @param nodeType Node type - * @param properties Node body properties - * @param path Path to the node - * @returns The created node - */ - public createNodeInsideRoot(name: string, nodeType: string, properties: any, path: string): Observable { - const body = { - name, - nodeType, - properties, - relativePath: path - }; - return from(this.nodesApi.createNode('-root-', body, {})); - } - - private cleanMetadataFromSemicolon(nodeEntry: NodeEntry): NodeMetadata { - const metadata = {}; - - if (nodeEntry?.entry.properties) { - for (const key in nodeEntry.entry.properties) { - if (key) { - if (key.indexOf(':') !== -1) { - metadata[key.split(':')[1]] = nodeEntry.entry.properties[key]; - } else { - metadata[key] = nodeEntry.entry.properties[key]; - } - } - } - } - - return new NodeMetadata(metadata, nodeEntry.entry.nodeType); - } - /** * Gets the list of holds assigned to the node. * @@ -266,4 +197,76 @@ export class NodesApiService { ) ); } + + /** + * Gets content for the given node. + * + * @param nodeId ID of the target node + * @returns Content data + */ + getNodeContent(nodeId: string): Observable { + return from(this.nodesApi.getNodeContent(nodeId)).pipe(catchError((err) => throwError(err))); + } + + /** + * Create a new Node inside `-root-` folder + * + * @param name Node name + * @param nodeType Node type + * @param properties Node body properties + * @param path Path to the node + * @returns The created node + */ + createNodeInsideRoot(name: string, nodeType: string, properties: any, path: string): Observable { + const body = { + name, + nodeType, + properties, + relativePath: path + }; + return from(this.nodesApi.createNode('-root-', body, {})); + } + + /** + * Create a new Node from form metadata. + * + * @param nodeType Node type + * @param nameSpace Namespace for properties + * @param data Property data to store in the node under namespace + * @param path Path to the node + * @param name Node name + * @returns The created node + */ + createNodeMetadata(nodeType: string, nameSpace: any, data: any, path: string, name?: string): Observable { + const properties = {}; + for (const key in data) { + if (data[key]) { + properties[nameSpace + ':' + key] = data[key]; + } + } + + return this.createNodeInsideRoot(name || this.randomNodeName(), nodeType, properties, path); + } + + private randomNodeName(): string { + return `node_${Date.now()}`; + } + + private cleanMetadataFromSemicolon(nodeEntry: NodeEntry): NodeMetadata { + const metadata = {}; + + if (nodeEntry?.entry.properties) { + for (const key in nodeEntry.entry.properties) { + if (key) { + if (key.indexOf(':') !== -1) { + metadata[key.split(':')[1]] = nodeEntry.entry.properties[key]; + } else { + metadata[key] = nodeEntry.entry.properties[key]; + } + } + } + } + + return new NodeMetadata(metadata, nodeEntry.entry.nodeType); + } } 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 0cd4c9778e..61b0281bd8 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 @@ -31,7 +31,8 @@ import { TrashcanApi, NodesApi, SitePaging, - ResultSetPaging + ResultSetPaging, + SEARCH_LANGUAGE } from '@alfresco/js-api'; import { Injectable } from '@angular/core'; import { Observable, from, of } from 'rxjs'; @@ -136,7 +137,7 @@ export class CustomResourcesService { const query: SearchRequest = { query: { query: '*', - language: 'afts' + language: SEARCH_LANGUAGE.AFTS }, filterQueries, include: ['path', 'properties', 'allowableOperations', 'aspectNames'], diff --git a/lib/content-services/src/lib/legal-hold/services/legal-hold.service.ts b/lib/content-services/src/lib/legal-hold/services/legal-hold.service.ts index 94b87cf43f..d08cd13ec2 100644 --- a/lib/content-services/src/lib/legal-hold/services/legal-hold.service.ts +++ b/lib/content-services/src/lib/legal-hold/services/legal-hold.service.ts @@ -16,7 +16,7 @@ */ import { AlfrescoApiService } from '@alfresco/adf-core'; -import { ContentPagingQuery, Hold, HoldBody, HoldEntry, HoldPaging, LegalHoldApi } from '@alfresco/js-api'; +import { BulkAssignHoldResponse, ContentPagingQuery, Hold, HoldBody, HoldEntry, HoldPaging, LegalHoldApi, RequestQuery } from '@alfresco/js-api'; import { Injectable } from '@angular/core'; import { Observable, from } from 'rxjs'; import { map } from 'rxjs/operators'; @@ -105,4 +105,32 @@ export class LegalHoldService { createHolds(filePlanId: string, holds: HoldBody[]): Observable { return from(this.legalHoldApi.createHolds(filePlanId, holds)); } + + /** + * Start the asynchronous bulk process for a hold with id holdId based on search query results. + * + * @param holdId The identifier of a hold + * @param query Search query + * @returns Observable + */ + bulkAssignHold(holdId: string, query: RequestQuery): Observable { + return from(this.legalHoldApi.bulkAssignHold(holdId, query)); + } + + /** + * Assign a folder to a hold. + * + * @param holdId The identifier of a hold + * @param folderId The identifier of a folder + * @param language Language code + * @returns Observable + */ + bulkAssignHoldToFolder(holdId: string, folderId: string, language: string): Observable { + const query: RequestQuery = { + query: `ANCESTOR:'workspace://SpacesStore/${folderId}' and TYPE:content`, + language + }; + + return from(this.legalHoldApi.bulkAssignHold(holdId, query)); + } } diff --git a/lib/content-services/src/lib/legal-hold/services/legal-holds.service.spec.ts b/lib/content-services/src/lib/legal-hold/services/legal-holds.service.spec.ts index c9c149c790..1e5a64a04b 100644 --- a/lib/content-services/src/lib/legal-hold/services/legal-holds.service.spec.ts +++ b/lib/content-services/src/lib/legal-hold/services/legal-holds.service.spec.ts @@ -18,14 +18,17 @@ import { TestBed } from '@angular/core/testing'; import { LegalHoldService } from './legal-hold.service'; import { ContentTestingModule } from '../../testing/content.testing.module'; -import { Hold, HoldEntry, HoldPaging } from '@alfresco/js-api'; +import { BulkAssignHoldResponse, Hold, HoldEntry, HoldPaging, RequestQuery, SEARCH_LANGUAGE } from '@alfresco/js-api'; describe('LegalHoldsService', () => { let service: LegalHoldService; let legalHolds: HoldPaging; let legalHoldEntry: HoldEntry; let returnedHolds: Hold[]; - const mockId = 'mockId'; + const filePlanId = 'mockId'; + const nodeId = 'mockNodeId'; + const holdId = 'holdId'; + const mockBulkResponse: BulkAssignHoldResponse = { totalItems: 3, bulkStatusId: 'bulkStatus' }; beforeEach(() => { TestBed.configureTestingModule({ @@ -41,7 +44,7 @@ describe('LegalHoldsService', () => { legalHoldEntry = { entry: { - id: mockId, + id: holdId, name: 'some name', reason: 'some reason', description: 'some description' @@ -50,10 +53,12 @@ describe('LegalHoldsService', () => { returnedHolds = [ { - id: mockId, + id: holdId, name: 'some name' } ]; + + spyOn(service.legalHoldApi, 'bulkAssignHold').and.returnValue(Promise.resolve(mockBulkResponse)); }); it('should be created', () => { @@ -64,9 +69,9 @@ describe('LegalHoldsService', () => { it('should return array of Hold interface', (done) => { spyOn(service.legalHoldApi, 'getHolds').and.returnValue(Promise.resolve(legalHolds)); - service.getHolds(mockId).subscribe((holds) => { + service.getHolds(filePlanId).subscribe((holds) => { expect(holds).toEqual(returnedHolds); - expect(service.legalHoldApi.getHolds).toHaveBeenCalledWith(mockId, undefined); + expect(service.legalHoldApi.getHolds).toHaveBeenCalledWith(filePlanId, undefined); done(); }); }); @@ -74,8 +79,6 @@ describe('LegalHoldsService', () => { describe('assignHold', () => { it('should assign node to existing hold', (done) => { - const nodeId = 'qwe'; - const holdId = 'foo'; const mockResponse = { entry: { id: holdId } }; spyOn(service.legalHoldApi, 'assignHold').and.returnValue(Promise.resolve(mockResponse)); @@ -90,7 +93,6 @@ describe('LegalHoldsService', () => { describe('assignHolds', () => { it('should assign nodes to existing hold', (done) => { const nodeIds = [{ id: 'qwe' }, { id: 'abc' }]; - const holdId = 'foo'; spyOn(service.legalHoldApi, 'assignHolds').and.returnValue(Promise.resolve(legalHolds)); service.assignHolds(nodeIds, holdId).subscribe((holds) => { @@ -103,9 +105,6 @@ describe('LegalHoldsService', () => { describe('unassignHold', () => { it('should unassign node from existing hold', (done) => { - const nodeId = 'qwe'; - const holdId = 'foo'; - spyOn(service.legalHoldApi, 'unassignHold').and.returnValue(Promise.resolve(undefined)); service.unassignHold(holdId, nodeId).subscribe(() => { @@ -123,9 +122,9 @@ describe('LegalHoldsService', () => { }; spyOn(service.legalHoldApi, 'createHold').and.returnValue(Promise.resolve(legalHoldEntry)); - service.createHold(mockId, mockHold).subscribe((hold) => { + service.createHold(filePlanId, mockHold).subscribe((hold) => { expect(hold).toEqual(legalHoldEntry); - expect(service.legalHoldApi.createHold).toHaveBeenCalledWith(mockId, mockHold); + expect(service.legalHoldApi.createHold).toHaveBeenCalledWith(filePlanId, mockHold); done(); }); }); @@ -145,9 +144,40 @@ describe('LegalHoldsService', () => { ]; spyOn(service.legalHoldApi, 'createHolds').and.returnValue(Promise.resolve(legalHolds)); - service.createHolds(mockId, mockHolds).subscribe((holds) => { + service.createHolds(filePlanId, mockHolds).subscribe((holds) => { expect(holds).toEqual(legalHolds); - expect(service.legalHoldApi.createHolds).toHaveBeenCalledWith(mockId, mockHolds); + expect(service.legalHoldApi.createHolds).toHaveBeenCalledWith(filePlanId, mockHolds); + done(); + }); + }); + }); + + describe('bulkAssignHold', () => { + it('should add nodes to hold based on search query results', (done) => { + const query: RequestQuery = { + query: 'mockQuery', + language: SEARCH_LANGUAGE.AFTS + }; + + service.bulkAssignHold(nodeId, query).subscribe((response) => { + expect(response).toEqual(mockBulkResponse); + expect(service.legalHoldApi.bulkAssignHold).toHaveBeenCalledWith(nodeId, query); + done(); + }); + }); + }); + + describe('bulkAssignHoldToFolder', () => { + it('should add nodes to hold based on search query results', (done) => { + const folderId = 'mockFolderId'; + const query: RequestQuery = { + query: `ANCESTOR:'workspace://SpacesStore/${folderId}' and TYPE:content`, + language: SEARCH_LANGUAGE.AFTS + }; + + service.bulkAssignHoldToFolder(nodeId, folderId, SEARCH_LANGUAGE.AFTS).subscribe((response) => { + expect(response).toEqual(mockBulkResponse); + expect(service.legalHoldApi.bulkAssignHold).toHaveBeenCalledWith(nodeId, query); done(); }); }); diff --git a/lib/content-services/src/lib/mock/search-query.mock.ts b/lib/content-services/src/lib/mock/search-query.mock.ts index 5d1943a21b..7b5fb008b7 100644 --- a/lib/content-services/src/lib/mock/search-query.mock.ts +++ b/lib/content-services/src/lib/mock/search-query.mock.ts @@ -15,12 +15,12 @@ * limitations under the License. */ -import { SearchRequest } from '@alfresco/js-api'; +import { SEARCH_LANGUAGE, SearchRequest } from '@alfresco/js-api'; export const mockSearchRequest = { query: { query: '(search-term*)', - language: 'afts' + language: SEARCH_LANGUAGE.AFTS }, include: ['path', 'allowableOperations'], paging: { diff --git a/lib/content-services/src/lib/search/services/base-query-builder.service.ts b/lib/content-services/src/lib/search/services/base-query-builder.service.ts index c312fe7b3c..ab3773780e 100644 --- a/lib/content-services/src/lib/search/services/base-query-builder.service.ts +++ b/lib/content-services/src/lib/search/services/base-query-builder.service.ts @@ -24,7 +24,8 @@ import { ResultSetPaging, RequestHighlight, RequestScope, - SearchApi + SearchApi, + SEARCH_LANGUAGE } from '@alfresco/js-api'; import { SearchCategory } from '../models/search-category.interface'; import { FilterQuery } from '../models/filter-query.interface'; @@ -86,7 +87,10 @@ export abstract class BaseQueryBuilderService { // TODO: to be supported in future iterations ranges: { [id: string]: SearchRange } = {}; - protected constructor(protected appConfig: AppConfigService, protected alfrescoApiService: AlfrescoApiService) { + protected constructor( + protected appConfig: AppConfigService, + protected alfrescoApiService: AlfrescoApiService + ) { this.resetToDefaults(); } @@ -342,7 +346,7 @@ export abstract class BaseQueryBuilderService { const result: SearchRequest = { query: { query, - language: 'afts' + language: SEARCH_LANGUAGE.AFTS }, include, paging: this.paging, @@ -456,9 +460,9 @@ export abstract class BaseQueryBuilderService { end: set.end, startInclusive: set.startInclusive, endInclusive: set.endInclusive - } as any) + }) as any ) - } as any) + }) as any ) }; } @@ -517,7 +521,7 @@ export abstract class BaseQueryBuilderService { limit: facet.limit, offset: facet.offset, prefix: facet.prefix - } as any) + }) as any ) }; } diff --git a/lib/core/src/lib/styles/_mat-selectors.scss b/lib/core/src/lib/styles/_mat-selectors.scss index 0900e160ac..ab97021fcc 100644 --- a/lib/core/src/lib/styles/_mat-selectors.scss +++ b/lib/core/src/lib/styles/_mat-selectors.scss @@ -35,6 +35,7 @@ $mat-progress-bar: '.mat-mdc-progress-bar'; $mat-progress-spinner: '.mat-mdc-progress-spinner'; $mat-form-field: '.mat-mdc-form-field'; $mat-form-field-flex: '.mat-mdc-form-field-flex'; +$mat-form-field-outline: '.mat-mdc-form-field-outline'; $mat-form-field-wrapper: '.mat-mdc-text-field-wrapper'; $mat-line-ripple: '.mdc-line-ripple'; $mat-form-field-subscript-wrapper: '.mat-mdc-form-field-subscript-wrapper'; diff --git a/lib/extensions/src/lib/config/action.extensions.ts b/lib/extensions/src/lib/config/action.extensions.ts index 3a7d3c31a7..4585def128 100644 --- a/lib/extensions/src/lib/config/action.extensions.ts +++ b/lib/extensions/src/lib/config/action.extensions.ts @@ -30,6 +30,7 @@ export interface ContentActionRef extends ExtensionElement { type: ContentActionType; title?: string; + tooltip?: string; description?: string; icon?: string; children?: Array; diff --git a/lib/js-api/index.ts b/lib/js-api/index.ts index c447218ebe..4615a3ca45 100644 --- a/lib/js-api/index.ts +++ b/lib/js-api/index.ts @@ -42,3 +42,4 @@ export * from './src/to-deprecate/alfresco-api-type'; export * from './src/api-clients/api-client'; export * from './src/api-clients/http-client.interface'; export * from './src/utils'; +export * from './src/constants'; diff --git a/lib/js-api/src/api/gs-core-rest-api/api/legal-hold.api.ts b/lib/js-api/src/api/gs-core-rest-api/api/legal-hold.api.ts index b1f28b461f..c29af3f9e3 100644 --- a/lib/js-api/src/api/gs-core-rest-api/api/legal-hold.api.ts +++ b/lib/js-api/src/api/gs-core-rest-api/api/legal-hold.api.ts @@ -19,6 +19,8 @@ import { BaseApi } from './base.api'; import { throwIfNotDefined } from '../../../assert'; import { ContentPagingQuery } from '../../content-rest-api'; import { HoldBody, HoldEntry, HoldPaging } from './../model'; +import { BulkAssignHoldResponse } from '../model/bulkAssignHoldResponse'; +import { RequestQuery } from '../../search-rest-api'; /** * Legal Holds service. @@ -153,4 +155,25 @@ export class LegalHoldApi extends BaseApi { returnType: HoldPaging }); } + + /** + * Start the asynchronous bulk process for a hold with id holdId based on search query results. + * + * @param holdId The identifier of a hold + * @param query Search query + * @returns Promise + */ + bulkAssignHold(holdId: string, query: RequestQuery): Promise { + throwIfNotDefined(holdId, 'holdId'); + throwIfNotDefined(query, 'query'); + + return this.post({ + path: `/holds/{holdId}/bulk`, + pathParams: { holdId }, + bodyParam: { + query, + op: 'ADD' + } + }); + } } diff --git a/lib/js-api/src/api/gs-core-rest-api/docs/BulkAssignHoldResponse.md b/lib/js-api/src/api/gs-core-rest-api/docs/BulkAssignHoldResponse.md new file mode 100644 index 0000000000..fbf584c21e --- /dev/null +++ b/lib/js-api/src/api/gs-core-rest-api/docs/BulkAssignHoldResponse.md @@ -0,0 +1,17 @@ +# BulkAssignHoldResponse + +## Basic usage + +```ts +export interface BulkAssignHoldResponse { + bulkStatusId: string; + totalItems: number; +} +``` + +## Properties + +| Name | Type | Default value | Description | +| ---------------- | ---------- | ------------- | ---------------------------------------- | +| **bulkStatusId** | **string** | | Indentifier of Bulk Status | +| **totalItems** | **number** | **0** | Amount of total nodes assigned to a hold | diff --git a/lib/js-api/src/api/gs-core-rest-api/docs/LegalHoldApi.md b/lib/js-api/src/api/gs-core-rest-api/docs/LegalHoldApi.md index dd4b3e497a..0e6bf6832e 100644 --- a/lib/js-api/src/api/gs-core-rest-api/docs/LegalHoldApi.md +++ b/lib/js-api/src/api/gs-core-rest-api/docs/LegalHoldApi.md @@ -8,8 +8,9 @@ All URIs are relative to _https://localhost/alfresco/api/-default-/public/gs/ver | [**assignHold**](LegalHoldApi.md#assignHold) | **POST** /holds/{holdId}/children | Assign node to legal hold | | [**assignHolds**](LegalHoldApi.md#assignHolds) | **POST** /holds/{holdId}/children | Assign nodes to legal hold | | [**unassignHold**](LegalHoldApi.md#unassignHold) | **DELETE** /holds/{holdId}/children/{nodeId} | Unassign node from legal hold | -[**createHold**](LegalHoldApi.md#createHold) | **POST** /file-plans/{filePlanId}/holds | Create one hold -[**createHolds**](LegalHoldApi.md#createHolds) | **POST** /file-plans/{filePlanId}/holds | Create list of holds +| [**createHold**](LegalHoldApi.md#createHold) | **POST** /file-plans/{filePlanId}/holds | Create one hold | +| [**createHolds**](LegalHoldApi.md#createHolds) | **POST** /file-plans/{filePlanId}/holds | Create list of holds | +| [**bulkAssignHold**](LegalHoldApi.md#bulkAssignHold) | **POST** /holds/{holdId}/bulk | Bulk add of nodes to the hold | @@ -133,7 +134,7 @@ legalHoldApi.assignHolds([{ id: 'foo' }, { id: 'bar' }], 'holdId').then( ### Parameters | Name | Type | Default value | Description | -| ----------- |----------------------| ------------- | ---------------------------------------------------- | +| ----------- | -------------------- | ------------- | ---------------------------------------------------- | | **nodeIds** | **{ id: string }[]** | | The list with id of nodes to assign to existing hold | | **holdId** | **string** | | The identifier of a hold. | @@ -184,7 +185,9 @@ legalHoldApi.unassignHold('holdId', 'nodeId').then( **void** + # **createHold** + > HoldEntry createHold(filePlanId, holds) Create legal hold. @@ -202,33 +205,35 @@ this.alfrescoApi.setConfig({ const legalHoldApi = new LegalHoldApi(this.alfrescoApi); -const hold = { - name: 'Hold 1', - reason: 'Reason 1' +const hold = { + name: 'Hold 1', + reason: 'Reason 1' }; -legalHoldApi.createHold('-filePlan-', hold).then((data) => { - console.log('API called successfully. Returned data: ' + data); -}, function(error) { - console.error(error); -}); - +legalHoldApi.createHold('-filePlan-', hold).then( + (data) => { + console.log('API called successfully. Returned data: ' + data); + }, + function (error) { + console.error(error); + } +); ``` ### Parameters -Name | Type | Default value | Description -------------- | ------------- | ------------- | ------------- - **filePlanId** | **string** | | The site details - **hold** | **Hold**| | Hold to create. +| Name | Type | Default value | Description | +| -------------- | ---------- | ------------- | ---------------- | +| **filePlanId** | **string** | | The site details | +| **hold** | **Hold** | | Hold to create. | ### Return type [**HoldEntry**](./HoldEntry.md) + # **createHolds** -> HoldPaging createHolds(filePlanId, holds) Create legal holds list. @@ -245,8 +250,8 @@ this.alfrescoApi.setConfig({ const legalHoldApi = new LegalHoldApi(this.alfrescoApi); -let opts = [ - { +let holds = [ + { name: 'Hold 1', reason: 'Reason 1' }, @@ -257,21 +262,65 @@ let opts = [ } ]; -legalHoldApi.createHolds('-filePlan-', holds).then((data) => { - console.log('API called successfully. Returned data: ' + data); -}, function(error) { - console.error(error); -}); - +legalHoldApi.createHolds('-filePlan-', holds).then( + (data) => { + console.log('API called successfully. Returned data: ' + data); + }, + function (error) { + console.error(error); + } +); ``` ### Parameters -Name | Type | Default value | Description -------------- | ------------- | ------------- | ------------- - **filePlanId** | **string** | | The site details - **holds** | **Hold[]**| | Array of new holds. +| Name | Type | Default value | Description | +| -------------- | ---------- | ------------- | ------------------- | +| **filePlanId** | **string** | | The site details | +| **holds** | **Hold[]** | | Array of new holds. | ### Return type [**HoldPaging**](./HoldPaging.md) + + + +# **bulkAssignHold** + +> BulkAssignHoldResponse bulkAssignHold(holdId, query) + +Start the asynchronous bulk process for a hold with id `holdId` based on search query results. + +### Example + +```javascript +import LegalHoldApi from 'LegalHoldApi'; +import { AlfrescoApi } from '@alfresco/js-api'; + +this.alfrescoApi = new AlfrescoApi(); +this.alfrescoApi.setConfig({ + hostEcm: 'http://127.0.0.1:8080' +}); + +const legalHoldApi = new LegalHoldApi(this.alfrescoApi); + +legalHoldApi.bulkAssignHold('holdId', { query: 'SITE:swsdp and TYPE:content', language: 'afts' }).then( + (data) => { + console.log('API called successfully. Returned data: ' + data); + }, + function (error) { + console.error(error); + } +); +``` + +### Parameters + +| Name | Type | Default value | Description | +| ------------ | ---------- | ------------- | ------------------------ | +| **holdId** | **string** | | The identifier of a hold | +| **query** | **RequestQuery** | | Search query. | + +### Return type + +[**BulkAssignHoldResponse**](./BulkAssignHoldResponse.md) diff --git a/lib/js-api/src/api/gs-core-rest-api/model/bulkAssignHoldResponse.ts b/lib/js-api/src/api/gs-core-rest-api/model/bulkAssignHoldResponse.ts new file mode 100644 index 0000000000..578e2eac93 --- /dev/null +++ b/lib/js-api/src/api/gs-core-rest-api/model/bulkAssignHoldResponse.ts @@ -0,0 +1,21 @@ +/*! + * @license + * Copyright © 2005-2024 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. + */ + +export interface BulkAssignHoldResponse { + bulkStatusId: string; + totalItems: number; +} diff --git a/lib/js-api/src/api/gs-core-rest-api/model/index.ts b/lib/js-api/src/api/gs-core-rest-api/model/index.ts index c184d0cc74..bd8c7fff0c 100644 --- a/lib/js-api/src/api/gs-core-rest-api/model/index.ts +++ b/lib/js-api/src/api/gs-core-rest-api/model/index.ts @@ -79,3 +79,4 @@ export * from './holdBody'; export * from './holdEntry'; export * from './holdPaging'; export * from './holdPagingList'; +export * from './bulkAssignHoldResponse'; diff --git a/lib/js-api/src/constants/index.ts b/lib/js-api/src/constants/index.ts new file mode 100644 index 0000000000..3364aff71b --- /dev/null +++ b/lib/js-api/src/constants/index.ts @@ -0,0 +1,18 @@ +/*! + * @license + * Copyright © 2005-2024 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. + */ + +export * from './language.constants'; diff --git a/lib/js-api/src/constants/language.constants.ts b/lib/js-api/src/constants/language.constants.ts new file mode 100644 index 0000000000..e7e14408aa --- /dev/null +++ b/lib/js-api/src/constants/language.constants.ts @@ -0,0 +1,22 @@ +/*! + * @license + * Copyright © 2005-2024 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. + */ + +export const SEARCH_LANGUAGE = { + AFTS: 'afts', + LUCENE: 'lucene', + CMIS: 'cmis' +}; diff --git a/lib/js-api/src/index.ts b/lib/js-api/src/index.ts index 568ba99356..9498d241cc 100644 --- a/lib/js-api/src/index.ts +++ b/lib/js-api/src/index.ts @@ -42,3 +42,4 @@ export * from './to-deprecate/alfresco-api-type'; export * from './api-clients/api-client'; export * from './api-clients/http-client.interface'; export * from './utils'; +export * from './constants'; diff --git a/lib/js-api/test/mockObjects/content-services/search.mock.ts b/lib/js-api/test/mockObjects/content-services/search.mock.ts index 16e83e277b..d2d9127d48 100644 --- a/lib/js-api/test/mockObjects/content-services/search.mock.ts +++ b/lib/js-api/test/mockObjects/content-services/search.mock.ts @@ -17,6 +17,7 @@ import nock from 'nock'; import { BaseMock } from '../base.mock'; +import { SEARCH_LANGUAGE } from '@alfresco/js-api'; export class SearchMock extends BaseMock { get200Response(): void { @@ -24,7 +25,7 @@ export class SearchMock extends BaseMock { .post('/alfresco/api/-default-/public/search/versions/1/search', { query: { query: 'select * from cmis:folder', - language: 'cmis' + language: SEARCH_LANGUAGE.CMIS } }) .reply(200, { diff --git a/lib/js-api/test/searchApi.spec.spec.ts b/lib/js-api/test/searchApi.spec.spec.ts index 077b834558..233556cf9f 100644 --- a/lib/js-api/test/searchApi.spec.spec.ts +++ b/lib/js-api/test/searchApi.spec.spec.ts @@ -16,7 +16,7 @@ */ import assert from 'assert'; -import { AlfrescoApi, SearchApi } from '../src'; +import { AlfrescoApi, SEARCH_LANGUAGE, SearchApi } from '../src'; import { EcmAuthMock, SearchMock } from './mockObjects'; describe('Search', () => { @@ -50,7 +50,7 @@ describe('Search', () => { .search({ query: { query: 'select * from cmis:folder', - language: 'cmis' + language: SEARCH_LANGUAGE.CMIS } }) .then((data) => {