mirror of
https://github.com/Alfresco/alfresco-ng2-components.git
synced 2025-07-24 17:32:15 +00:00
[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:
@@ -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: {
|
||||
|
@@ -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);
|
||||
}
|
||||
}
|
||||
|
@@ -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<NodeMetadata> {
|
||||
getNodeMetadata(nodeId: string): Observable<NodeMetadata> {
|
||||
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.
|
||||
*
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
@@ -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'],
|
||||
|
@@ -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<HoldPaging> {
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
@@ -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();
|
||||
});
|
||||
});
|
||||
|
@@ -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: {
|
||||
|
@@ -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
|
||||
)
|
||||
};
|
||||
}
|
||||
|
Reference in New Issue
Block a user