[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 <tomasz.nastaly@hyland.com>
This commit is contained in:
Darya Blavanovich
2024-08-09 13:20:07 +02:00
parent 2890e4856c
commit ddd508ba60
22 changed files with 388 additions and 140 deletions

View File

@@ -18,11 +18,13 @@ Manages holds for nodes.
- _filePlanId_: `string` - The identifier of a file plan. You can also use the -filePlan- alias - _filePlanId_: `string` - The identifier of a file plan. You can also use the -filePlan- alias
- _options_: `ContentPagingQuery` - Optional parameters supported by JS-API - _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 <br/> - **Returns** [`Observable`](http://reactivex.io/documentation/observable.html)`<`[`Hold`](../../../lib/js-api/src/api/gs-core-rest-api/docs/Hold.md)`[]>` - List of holds <br/>
- **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)`>`<br/> - **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)`>`<br/>
Create new hold in File Plan. Create new hold in File Plan.
- _filePlanId_: `string` - The identifier of a file plan. You can also use the -filePlan- alias - _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 - _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<br/> - **Returns** [`Observable`](http://reactivex.io/documentation/observable.html)`<`[`HoldEntry`](../../../lib/js-api/src/api/gs-core-rest-api/docs/HoldEntry.md)`>` - Hold entry<br/>
- **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)`[]>`<br/> - **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)`[]>`<br/>
Create new holds in File Plan. Create new holds in File Plan.
- _filePlanId_: `string` - The identifier of a file plan. You can also use the -filePlan- alias - _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. Assign a node to a hold.
- _nodeId_: `string` - The Id of the node which will be assigned 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 - _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 <br/>
- **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)`>`<br/> - **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)`>`<br/>
Assign multiple nodes to a hold. Assign multiple nodes to a hold.
@@ -47,6 +49,20 @@ Manages holds for nodes.
- _nodeId_: `string` - The Id of the node which is unassigned - _nodeId_: `string` - The Id of the node which is unassigned
- **Returns** [`Observable`](http://reactivex.io/documentation/observable.html)`<void>` - **Returns** [`Observable`](http://reactivex.io/documentation/observable.html)`<void>`
- **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)`>`<br/>
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 <br/>
- **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)`>`<br/>
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 <br/>
## Details ## Details

View File

@@ -25,7 +25,8 @@ import {
ResultNode, ResultNode,
ResultSetPaging, ResultSetPaging,
ResultSetPagingList, ResultSetPagingList,
ResultSetRowEntry ResultSetRowEntry,
SEARCH_LANGUAGE
} from '@alfresco/js-api'; } from '@alfresco/js-api';
import { fakeAsync, TestBed } from '@angular/core/testing'; import { fakeAsync, TestBed } from '@angular/core/testing';
import { CategoryService } from './category.service'; import { CategoryService } from './category.service';
@@ -122,7 +123,7 @@ describe('CategoryService', () => {
categoryService.searchCategories(name, skipCount, maxItems); categoryService.searchCategories(name, skipCount, maxItems);
expect(categoryService.searchApi.search).toHaveBeenCalledWith({ expect(categoryService.searchApi.search).toHaveBeenCalledWith({
query: { query: {
language: 'afts', language: SEARCH_LANGUAGE.AFTS,
query: `cm:name:"*${name}*" AND TYPE:'cm:category' AND PATH:"/cm:categoryRoot/cm:generalclassifiable//*"` query: `cm:name:"*${name}*" AND TYPE:'cm:category' AND PATH:"/cm:categoryRoot/cm:generalclassifiable//*"`
}, },
paging: { paging: {
@@ -139,7 +140,7 @@ describe('CategoryService', () => {
categoryService.searchCategories(name); categoryService.searchCategories(name);
expect(categoryService.searchApi.search).toHaveBeenCalledWith({ expect(categoryService.searchApi.search).toHaveBeenCalledWith({
query: { query: {
language: 'afts', language: SEARCH_LANGUAGE.AFTS,
query: `cm:name:"*${name}*" AND TYPE:'cm:category' AND PATH:"/cm:categoryRoot/cm:generalclassifiable//*"` query: `cm:name:"*${name}*" AND TYPE:'cm:category' AND PATH:"/cm:categoryRoot/cm:generalclassifiable//*"`
}, },
paging: { paging: {

View File

@@ -17,7 +17,16 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { AlfrescoApiService, AppConfigService, UserPreferencesService } from '@alfresco/adf-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'; import { from, Observable } from 'rxjs';
@Injectable({ providedIn: 'root' }) @Injectable({ providedIn: 'root' })
@@ -113,7 +122,7 @@ export class CategoryService {
return from( return from(
this.searchApi.search({ this.searchApi.search({
query: { query: {
language: 'afts', language: SEARCH_LANGUAGE.AFTS,
query: `cm:name:"*${name}*" AND TYPE:'cm:category' AND PATH:"/cm:categoryRoot/cm:generalclassifiable//*"` query: `cm:name:"*${name}*" AND TYPE:'cm:category' AND PATH:"/cm:categoryRoot/cm:generalclassifiable//*"`
}, },
paging: { paging: {
@@ -163,6 +172,6 @@ export class CategoryService {
* @returns boolean true if categories plugin is enabled, false otherwise. * @returns boolean true if categories plugin is enabled, false otherwise.
*/ */
areCategoriesEnabled(): boolean { areCategoriesEnabled(): boolean {
return this.appConfigService.get('plugins.categoriesEnabled', true); return this.appConfigService.get('plugins.categoriesEnabled', true);
} }
} }

View File

@@ -43,7 +43,10 @@ export class NodesApiService {
return this._nodesApi; return this._nodesApi;
} }
constructor(private apiService: AlfrescoApiService, private preferences: UserPreferencesService) {} constructor(
private apiService: AlfrescoApiService,
private preferences: UserPreferencesService
) {}
private getEntryFromEntity(entity: NodeEntry): Node { private getEntryFromEntity(entity: NodeEntry): Node {
return entity.entry; return entity.entry;
@@ -164,82 +167,10 @@ export class NodesApiService {
* @param nodeId ID of the target node * @param nodeId ID of the target node
* @returns Node metadata * @returns Node metadata
*/ */
public getNodeMetadata(nodeId: string): Observable<NodeMetadata> { getNodeMetadata(nodeId: string): Observable<NodeMetadata> {
return from(this.nodesApi.getNode(nodeId)).pipe(map(this.cleanMetadataFromSemicolon)); 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<NodeEntry> {
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<any> {
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<NodeEntry> {
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. * 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<any> {
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<NodeEntry> {
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<NodeEntry> {
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);
}
} }

View File

@@ -31,7 +31,8 @@ import {
TrashcanApi, TrashcanApi,
NodesApi, NodesApi,
SitePaging, SitePaging,
ResultSetPaging ResultSetPaging,
SEARCH_LANGUAGE
} from '@alfresco/js-api'; } from '@alfresco/js-api';
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { Observable, from, of } from 'rxjs'; import { Observable, from, of } from 'rxjs';
@@ -136,7 +137,7 @@ export class CustomResourcesService {
const query: SearchRequest = { const query: SearchRequest = {
query: { query: {
query: '*', query: '*',
language: 'afts' language: SEARCH_LANGUAGE.AFTS
}, },
filterQueries, filterQueries,
include: ['path', 'properties', 'allowableOperations', 'aspectNames'], include: ['path', 'properties', 'allowableOperations', 'aspectNames'],

View File

@@ -16,7 +16,7 @@
*/ */
import { AlfrescoApiService } from '@alfresco/adf-core'; 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 { Injectable } from '@angular/core';
import { Observable, from } from 'rxjs'; import { Observable, from } from 'rxjs';
import { map } from 'rxjs/operators'; import { map } from 'rxjs/operators';
@@ -105,4 +105,32 @@ export class LegalHoldService {
createHolds(filePlanId: string, holds: HoldBody[]): Observable<HoldPaging> { createHolds(filePlanId: string, holds: HoldBody[]): Observable<HoldPaging> {
return from(this.legalHoldApi.createHolds(filePlanId, holds)); 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<BulkAssignHoldResponse>
*/
bulkAssignHold(holdId: string, query: RequestQuery): Observable<BulkAssignHoldResponse> {
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<BulkAssignHoldResponse>
*/
bulkAssignHoldToFolder(holdId: string, folderId: string, language: string): Observable<BulkAssignHoldResponse> {
const query: RequestQuery = {
query: `ANCESTOR:'workspace://SpacesStore/${folderId}' and TYPE:content`,
language
};
return from(this.legalHoldApi.bulkAssignHold(holdId, query));
}
} }

View File

@@ -18,14 +18,17 @@
import { TestBed } from '@angular/core/testing'; import { TestBed } from '@angular/core/testing';
import { LegalHoldService } from './legal-hold.service'; import { LegalHoldService } from './legal-hold.service';
import { ContentTestingModule } from '../../testing/content.testing.module'; 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', () => { describe('LegalHoldsService', () => {
let service: LegalHoldService; let service: LegalHoldService;
let legalHolds: HoldPaging; let legalHolds: HoldPaging;
let legalHoldEntry: HoldEntry; let legalHoldEntry: HoldEntry;
let returnedHolds: Hold[]; let returnedHolds: Hold[];
const mockId = 'mockId'; const filePlanId = 'mockId';
const nodeId = 'mockNodeId';
const holdId = 'holdId';
const mockBulkResponse: BulkAssignHoldResponse = { totalItems: 3, bulkStatusId: 'bulkStatus' };
beforeEach(() => { beforeEach(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
@@ -41,7 +44,7 @@ describe('LegalHoldsService', () => {
legalHoldEntry = { legalHoldEntry = {
entry: { entry: {
id: mockId, id: holdId,
name: 'some name', name: 'some name',
reason: 'some reason', reason: 'some reason',
description: 'some description' description: 'some description'
@@ -50,10 +53,12 @@ describe('LegalHoldsService', () => {
returnedHolds = [ returnedHolds = [
{ {
id: mockId, id: holdId,
name: 'some name' name: 'some name'
} }
]; ];
spyOn(service.legalHoldApi, 'bulkAssignHold').and.returnValue(Promise.resolve(mockBulkResponse));
}); });
it('should be created', () => { it('should be created', () => {
@@ -64,9 +69,9 @@ describe('LegalHoldsService', () => {
it('should return array of Hold interface', (done) => { it('should return array of Hold interface', (done) => {
spyOn(service.legalHoldApi, 'getHolds').and.returnValue(Promise.resolve(legalHolds)); spyOn(service.legalHoldApi, 'getHolds').and.returnValue(Promise.resolve(legalHolds));
service.getHolds(mockId).subscribe((holds) => { service.getHolds(filePlanId).subscribe((holds) => {
expect(holds).toEqual(returnedHolds); expect(holds).toEqual(returnedHolds);
expect(service.legalHoldApi.getHolds).toHaveBeenCalledWith(mockId, undefined); expect(service.legalHoldApi.getHolds).toHaveBeenCalledWith(filePlanId, undefined);
done(); done();
}); });
}); });
@@ -74,8 +79,6 @@ describe('LegalHoldsService', () => {
describe('assignHold', () => { describe('assignHold', () => {
it('should assign node to existing hold', (done) => { it('should assign node to existing hold', (done) => {
const nodeId = 'qwe';
const holdId = 'foo';
const mockResponse = { entry: { id: holdId } }; const mockResponse = { entry: { id: holdId } };
spyOn(service.legalHoldApi, 'assignHold').and.returnValue(Promise.resolve(mockResponse)); spyOn(service.legalHoldApi, 'assignHold').and.returnValue(Promise.resolve(mockResponse));
@@ -90,7 +93,6 @@ describe('LegalHoldsService', () => {
describe('assignHolds', () => { describe('assignHolds', () => {
it('should assign nodes to existing hold', (done) => { it('should assign nodes to existing hold', (done) => {
const nodeIds = [{ id: 'qwe' }, { id: 'abc' }]; const nodeIds = [{ id: 'qwe' }, { id: 'abc' }];
const holdId = 'foo';
spyOn(service.legalHoldApi, 'assignHolds').and.returnValue(Promise.resolve(legalHolds)); spyOn(service.legalHoldApi, 'assignHolds').and.returnValue(Promise.resolve(legalHolds));
service.assignHolds(nodeIds, holdId).subscribe((holds) => { service.assignHolds(nodeIds, holdId).subscribe((holds) => {
@@ -103,9 +105,6 @@ describe('LegalHoldsService', () => {
describe('unassignHold', () => { describe('unassignHold', () => {
it('should unassign node from existing hold', (done) => { it('should unassign node from existing hold', (done) => {
const nodeId = 'qwe';
const holdId = 'foo';
spyOn(service.legalHoldApi, 'unassignHold').and.returnValue(Promise.resolve(undefined)); spyOn(service.legalHoldApi, 'unassignHold').and.returnValue(Promise.resolve(undefined));
service.unassignHold(holdId, nodeId).subscribe(() => { service.unassignHold(holdId, nodeId).subscribe(() => {
@@ -123,9 +122,9 @@ describe('LegalHoldsService', () => {
}; };
spyOn(service.legalHoldApi, 'createHold').and.returnValue(Promise.resolve(legalHoldEntry)); 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(hold).toEqual(legalHoldEntry);
expect(service.legalHoldApi.createHold).toHaveBeenCalledWith(mockId, mockHold); expect(service.legalHoldApi.createHold).toHaveBeenCalledWith(filePlanId, mockHold);
done(); done();
}); });
}); });
@@ -145,9 +144,40 @@ describe('LegalHoldsService', () => {
]; ];
spyOn(service.legalHoldApi, 'createHolds').and.returnValue(Promise.resolve(legalHolds)); 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(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(); done();
}); });
}); });

View File

@@ -15,12 +15,12 @@
* limitations under the License. * limitations under the License.
*/ */
import { SearchRequest } from '@alfresco/js-api'; import { SEARCH_LANGUAGE, SearchRequest } from '@alfresco/js-api';
export const mockSearchRequest = { export const mockSearchRequest = {
query: { query: {
query: '(search-term*)', query: '(search-term*)',
language: 'afts' language: SEARCH_LANGUAGE.AFTS
}, },
include: ['path', 'allowableOperations'], include: ['path', 'allowableOperations'],
paging: { paging: {

View File

@@ -24,7 +24,8 @@ import {
ResultSetPaging, ResultSetPaging,
RequestHighlight, RequestHighlight,
RequestScope, RequestScope,
SearchApi SearchApi,
SEARCH_LANGUAGE
} from '@alfresco/js-api'; } from '@alfresco/js-api';
import { SearchCategory } from '../models/search-category.interface'; import { SearchCategory } from '../models/search-category.interface';
import { FilterQuery } from '../models/filter-query.interface'; import { FilterQuery } from '../models/filter-query.interface';
@@ -86,7 +87,10 @@ export abstract class BaseQueryBuilderService {
// TODO: to be supported in future iterations // TODO: to be supported in future iterations
ranges: { [id: string]: SearchRange } = {}; ranges: { [id: string]: SearchRange } = {};
protected constructor(protected appConfig: AppConfigService, protected alfrescoApiService: AlfrescoApiService) { protected constructor(
protected appConfig: AppConfigService,
protected alfrescoApiService: AlfrescoApiService
) {
this.resetToDefaults(); this.resetToDefaults();
} }
@@ -342,7 +346,7 @@ export abstract class BaseQueryBuilderService {
const result: SearchRequest = { const result: SearchRequest = {
query: { query: {
query, query,
language: 'afts' language: SEARCH_LANGUAGE.AFTS
}, },
include, include,
paging: this.paging, paging: this.paging,
@@ -456,9 +460,9 @@ export abstract class BaseQueryBuilderService {
end: set.end, end: set.end,
startInclusive: set.startInclusive, startInclusive: set.startInclusive,
endInclusive: set.endInclusive endInclusive: set.endInclusive
} as any) }) as any
) )
} as any) }) as any
) )
}; };
} }
@@ -517,7 +521,7 @@ export abstract class BaseQueryBuilderService {
limit: facet.limit, limit: facet.limit,
offset: facet.offset, offset: facet.offset,
prefix: facet.prefix prefix: facet.prefix
} as any) }) as any
) )
}; };
} }

View File

@@ -35,6 +35,7 @@ $mat-progress-bar: '.mat-mdc-progress-bar';
$mat-progress-spinner: '.mat-mdc-progress-spinner'; $mat-progress-spinner: '.mat-mdc-progress-spinner';
$mat-form-field: '.mat-mdc-form-field'; $mat-form-field: '.mat-mdc-form-field';
$mat-form-field-flex: '.mat-mdc-form-field-flex'; $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-form-field-wrapper: '.mat-mdc-text-field-wrapper';
$mat-line-ripple: '.mdc-line-ripple'; $mat-line-ripple: '.mdc-line-ripple';
$mat-form-field-subscript-wrapper: '.mat-mdc-form-field-subscript-wrapper'; $mat-form-field-subscript-wrapper: '.mat-mdc-form-field-subscript-wrapper';

View File

@@ -30,6 +30,7 @@ export interface ContentActionRef extends ExtensionElement {
type: ContentActionType; type: ContentActionType;
title?: string; title?: string;
tooltip?: string;
description?: string; description?: string;
icon?: string; icon?: string;
children?: Array<ContentActionRef>; children?: Array<ContentActionRef>;

View File

@@ -42,3 +42,4 @@ export * from './src/to-deprecate/alfresco-api-type';
export * from './src/api-clients/api-client'; export * from './src/api-clients/api-client';
export * from './src/api-clients/http-client.interface'; export * from './src/api-clients/http-client.interface';
export * from './src/utils'; export * from './src/utils';
export * from './src/constants';

View File

@@ -19,6 +19,8 @@ import { BaseApi } from './base.api';
import { throwIfNotDefined } from '../../../assert'; import { throwIfNotDefined } from '../../../assert';
import { ContentPagingQuery } from '../../content-rest-api'; import { ContentPagingQuery } from '../../content-rest-api';
import { HoldBody, HoldEntry, HoldPaging } from './../model'; import { HoldBody, HoldEntry, HoldPaging } from './../model';
import { BulkAssignHoldResponse } from '../model/bulkAssignHoldResponse';
import { RequestQuery } from '../../search-rest-api';
/** /**
* Legal Holds service. * Legal Holds service.
@@ -153,4 +155,25 @@ export class LegalHoldApi extends BaseApi {
returnType: HoldPaging 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<BulkAssignHoldResponse>
*/
bulkAssignHold(holdId: string, query: RequestQuery): Promise<BulkAssignHoldResponse> {
throwIfNotDefined(holdId, 'holdId');
throwIfNotDefined(query, 'query');
return this.post({
path: `/holds/{holdId}/bulk`,
pathParams: { holdId },
bodyParam: {
query,
op: 'ADD'
}
});
}
} }

View File

@@ -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 |

View File

@@ -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 | | [**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 | | [**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 | | [**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 | [**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 | [**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 |
<a name="getHolds"></a> <a name="getHolds"></a>
@@ -133,7 +134,7 @@ legalHoldApi.assignHolds([{ id: 'foo' }, { id: 'bar' }], 'holdId').then(
### Parameters ### Parameters
| Name | Type | Default value | Description | | Name | Type | Default value | Description |
| ----------- |----------------------| ------------- | ---------------------------------------------------- | | ----------- | -------------------- | ------------- | ---------------------------------------------------- |
| **nodeIds** | **{ id: string }[]** | | The list with id of nodes to assign to existing hold | | **nodeIds** | **{ id: string }[]** | | The list with id of nodes to assign to existing hold |
| **holdId** | **string** | | The identifier of a hold. | | **holdId** | **string** | | The identifier of a hold. |
@@ -184,7 +185,9 @@ legalHoldApi.unassignHold('holdId', 'nodeId').then(
**void** **void**
<a name="createHold"></a> <a name="createHold"></a>
# **createHold** # **createHold**
> HoldEntry createHold(filePlanId, holds) > HoldEntry createHold(filePlanId, holds)
Create legal hold. Create legal hold.
@@ -202,33 +205,35 @@ this.alfrescoApi.setConfig({
const legalHoldApi = new LegalHoldApi(this.alfrescoApi); const legalHoldApi = new LegalHoldApi(this.alfrescoApi);
const hold = { const hold = {
name: 'Hold 1', name: 'Hold 1',
reason: 'Reason 1' reason: 'Reason 1'
}; };
legalHoldApi.createHold('-filePlan-', hold).then((data) => { legalHoldApi.createHold('-filePlan-', hold).then(
console.log('API called successfully. Returned data: ' + data); (data) => {
}, function(error) { console.log('API called successfully. Returned data: ' + data);
console.error(error); },
}); function (error) {
console.error(error);
}
);
``` ```
### Parameters ### Parameters
Name | Type | Default value | Description | Name | Type | Default value | Description |
------------- | ------------- | ------------- | ------------- | -------------- | ---------- | ------------- | ---------------- |
**filePlanId** | **string** | | The site details | **filePlanId** | **string** | | The site details |
**hold** | **Hold**| | Hold to create. | **hold** | **Hold** | | Hold to create. |
### Return type ### Return type
[**HoldEntry**](./HoldEntry.md) [**HoldEntry**](./HoldEntry.md)
<a name="createHolds"></a> <a name="createHolds"></a>
# **createHolds** # **createHolds**
> HoldPaging createHolds(filePlanId, holds)
Create legal holds list. Create legal holds list.
@@ -245,8 +250,8 @@ this.alfrescoApi.setConfig({
const legalHoldApi = new LegalHoldApi(this.alfrescoApi); const legalHoldApi = new LegalHoldApi(this.alfrescoApi);
let opts = [ let holds = [
{ {
name: 'Hold 1', name: 'Hold 1',
reason: 'Reason 1' reason: 'Reason 1'
}, },
@@ -257,21 +262,65 @@ let opts = [
} }
]; ];
legalHoldApi.createHolds('-filePlan-', holds).then((data) => { legalHoldApi.createHolds('-filePlan-', holds).then(
console.log('API called successfully. Returned data: ' + data); (data) => {
}, function(error) { console.log('API called successfully. Returned data: ' + data);
console.error(error); },
}); function (error) {
console.error(error);
}
);
``` ```
### Parameters ### Parameters
Name | Type | Default value | Description | Name | Type | Default value | Description |
------------- | ------------- | ------------- | ------------- | -------------- | ---------- | ------------- | ------------------- |
**filePlanId** | **string** | | The site details | **filePlanId** | **string** | | The site details |
**holds** | **Hold[]**| | Array of new holds. | **holds** | **Hold[]** | | Array of new holds. |
### Return type ### Return type
[**HoldPaging**](./HoldPaging.md) [**HoldPaging**](./HoldPaging.md)
<a name="bulkAssignHold"></a>
# **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)

View File

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

View File

@@ -79,3 +79,4 @@ export * from './holdBody';
export * from './holdEntry'; export * from './holdEntry';
export * from './holdPaging'; export * from './holdPaging';
export * from './holdPagingList'; export * from './holdPagingList';
export * from './bulkAssignHoldResponse';

View File

@@ -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';

View File

@@ -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'
};

View File

@@ -42,3 +42,4 @@ export * from './to-deprecate/alfresco-api-type';
export * from './api-clients/api-client'; export * from './api-clients/api-client';
export * from './api-clients/http-client.interface'; export * from './api-clients/http-client.interface';
export * from './utils'; export * from './utils';
export * from './constants';

View File

@@ -17,6 +17,7 @@
import nock from 'nock'; import nock from 'nock';
import { BaseMock } from '../base.mock'; import { BaseMock } from '../base.mock';
import { SEARCH_LANGUAGE } from '@alfresco/js-api';
export class SearchMock extends BaseMock { export class SearchMock extends BaseMock {
get200Response(): void { get200Response(): void {
@@ -24,7 +25,7 @@ export class SearchMock extends BaseMock {
.post('/alfresco/api/-default-/public/search/versions/1/search', { .post('/alfresco/api/-default-/public/search/versions/1/search', {
query: { query: {
query: 'select * from cmis:folder', query: 'select * from cmis:folder',
language: 'cmis' language: SEARCH_LANGUAGE.CMIS
} }
}) })
.reply(200, { .reply(200, {

View File

@@ -16,7 +16,7 @@
*/ */
import assert from 'assert'; import assert from 'assert';
import { AlfrescoApi, SearchApi } from '../src'; import { AlfrescoApi, SEARCH_LANGUAGE, SearchApi } from '../src';
import { EcmAuthMock, SearchMock } from './mockObjects'; import { EcmAuthMock, SearchMock } from './mockObjects';
describe('Search', () => { describe('Search', () => {
@@ -50,7 +50,7 @@ describe('Search', () => {
.search({ .search({
query: { query: {
query: 'select * from cmis:folder', query: 'select * from cmis:folder',
language: 'cmis' language: SEARCH_LANGUAGE.CMIS
} }
}) })
.then((data) => { .then((data) => {