mirror of
https://github.com/Alfresco/alfresco-ng2-components.git
synced 2026-09-02 17:53:30 +00:00
[ACS-12332] Add checkout and cancel-checkout node support (#12133)
* [ACS-12332] Add checkout and cancel-checkout node support * [ACS-12332] cr fix
This commit is contained in:
@@ -18,6 +18,7 @@ Accesses and manipulates ACS document nodes using their node IDs.
|
||||
- [Getting folder node contents](#getting-folder-node-contents)
|
||||
- [Creating and updating nodes](#creating-and-updating-nodes)
|
||||
- [Deleting and restoring nodes](#deleting-and-restoring-nodes)
|
||||
- [Checking out nodes](#checking-out-nodes)
|
||||
- [See also](#see-also)
|
||||
|
||||
## Class members
|
||||
@@ -103,6 +104,16 @@ Accesses and manipulates ACS document nodes using their node IDs.
|
||||
- _nodeId:_ `string` - ID of the target node
|
||||
- _opts:_ `{ where?: string; includeSource?: boolean;} & NodesIncludeQuery & ContentPagingQuery` - additional options that API can take
|
||||
- **Returns** [`Observable`](http://reactivex.io/documentation/observable.html)`<`[`NodeAssociationPaging`](../../../lib/js-api/src/api/content-rest-api/docs/NodesApi.md#NodeAssociationPaging)`>` - List of node's parents.
|
||||
- **checkoutNode**(nodeId: `string`, opts?: `NodesIncludeQuery`): [`Observable`](http://reactivex.io/documentation/observable.html)`<`[`NodeEntry`](https://github.com/Alfresco/alfresco-ng2-components/blob/develop/lib/js-api/src/api/content-rest-api/docs/NodeEntry.md)`>`<br/>
|
||||
Checks out a file node for offline editing. Creates a private working copy and locks the original.
|
||||
- _nodeId:_ `string` - ID of the file node to check out
|
||||
- _opts:_ `NodesIncludeQuery` - (Optional) Additional query parameters (`include`, `fields`)
|
||||
- **Returns** [`Observable`](http://reactivex.io/documentation/observable.html)`<`[`NodeEntry`](https://github.com/Alfresco/alfresco-ng2-components/blob/develop/lib/js-api/src/api/content-rest-api/docs/NodeEntry.md)`>` - The working copy node
|
||||
- **cancelCheckoutNode**(nodeId: `string`, opts?: `NodesIncludeQuery`): [`Observable`](http://reactivex.io/documentation/observable.html)`<`[`NodeEntry`](https://github.com/Alfresco/alfresco-ng2-components/blob/develop/lib/js-api/src/api/content-rest-api/docs/NodeEntry.md)`>`<br/>
|
||||
Cancels a checkout. Accepts either the working copy or the original checked-out node. Deletes the working copy and unlocks the original.
|
||||
- _nodeId:_ `string` - ID of the working copy or original checked-out node
|
||||
- _opts:_ `NodesIncludeQuery` - (Optional) Additional query parameters (`include`, `fields`)
|
||||
- **Returns** [`Observable`](http://reactivex.io/documentation/observable.html)`<`[`NodeEntry`](https://github.com/Alfresco/alfresco-ng2-components/blob/develop/lib/js-api/src/api/content-rest-api/docs/NodeEntry.md)`>` - The original (unlocked) node
|
||||
|
||||
## Details
|
||||
|
||||
@@ -160,6 +171,12 @@ and
|
||||
pages in the Alfresco JS API for further details and options. Note that you can also use the
|
||||
[Deleted Nodes Api service](deleted-nodes-api.service.md) get a list of all items currently in the trashcan.
|
||||
|
||||
### Checking out nodes
|
||||
|
||||
Use `checkoutNode` to lock a file node for offline editing. This creates a private working copy and locks the original node so other users cannot modify it. On success, the Observable emits the working copy `NodeEntry`.
|
||||
|
||||
Use `cancelCheckoutNode` to discard the working copy and unlock the original. You can pass either the working copy node ID or the original node ID. On success, the Observable emits the original (unlocked) `NodeEntry`.
|
||||
|
||||
## See also
|
||||
|
||||
- [Deleted nodes api service](deleted-nodes-api.service.md)
|
||||
|
||||
@@ -18,11 +18,19 @@
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
import { RedirectAuthService } from '@alfresco/adf-core';
|
||||
import { EMPTY, firstValueFrom, of } from 'rxjs';
|
||||
import { JobIdBodyEntry, SizeDetails, SizeDetailsEntry } from '@alfresco/js-api';
|
||||
import { JobIdBodyEntry, NodeEntry, SizeDetails, SizeDetailsEntry } from '@alfresco/js-api';
|
||||
import { NodesApiService } from './nodes-api.service';
|
||||
import { AlfrescoApiService } from '../../services/alfresco-api.service';
|
||||
import { AlfrescoApiServiceMock } from '../../mock/alfresco-api.service.mock';
|
||||
|
||||
const fakeNodeEntry = {
|
||||
entry: {
|
||||
id: 'fake-node-id',
|
||||
name: 'fake-file.txt',
|
||||
nodeType: 'cm:content'
|
||||
}
|
||||
} as NodeEntry;
|
||||
|
||||
const fakeInitiateFolderSizeResponse: JobIdBodyEntry = {
|
||||
entry: {
|
||||
jobId: 'fake-job-id'
|
||||
@@ -75,4 +83,20 @@ describe('NodesApiService', () => {
|
||||
|
||||
expect(nodesApiService.nodesApi.listParents).toHaveBeenCalledWith('fake-node-id', { include: ['path'], where: 'isPrimary=true' });
|
||||
});
|
||||
|
||||
it('should call nodesApi.checkoutNode with the node ID and optional query params', async () => {
|
||||
const opts = { include: ['path', 'allowableOperations'], fields: ['id', 'name'] };
|
||||
spyOn(nodesApiService.nodesApi, 'checkoutNode').and.returnValue(Promise.resolve(fakeNodeEntry));
|
||||
await firstValueFrom(nodesApiService.checkoutNode(fakeNodeEntry.entry.id, opts));
|
||||
|
||||
expect(nodesApiService.nodesApi.checkoutNode).toHaveBeenCalledWith(fakeNodeEntry.entry.id, opts);
|
||||
});
|
||||
|
||||
it('should call nodesApi.cancelCheckoutNode with the node ID and optional query params', async () => {
|
||||
const opts = { include: ['path'], fields: ['id'] };
|
||||
spyOn(nodesApiService.nodesApi, 'cancelCheckoutNode').and.returnValue(Promise.resolve(fakeNodeEntry));
|
||||
await firstValueFrom(nodesApiService.cancelCheckoutNode(fakeNodeEntry.entry.id, opts));
|
||||
|
||||
expect(nodesApiService.nodesApi.cancelCheckoutNode).toHaveBeenCalledWith(fakeNodeEntry.entry.id, opts);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -293,6 +293,30 @@ export class NodesApiService {
|
||||
return from(this.nodesApi.listParents(nodeId, opts));
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks out a file node for offline editing.
|
||||
* Creates a private working copy and locks the original.
|
||||
*
|
||||
* @param nodeId ID of the node to check out
|
||||
* @param opts Optional query parameters (`include`, `fields`)
|
||||
* @returns Observable emitting the working copy node
|
||||
*/
|
||||
checkoutNode(nodeId: string, opts?: NodesIncludeQuery): Observable<NodeEntry> {
|
||||
return from(this.nodesApi.checkoutNode(nodeId, opts));
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancels a checkout. Accepts either the working copy or the original node.
|
||||
* Deletes the working copy and unlocks the original.
|
||||
*
|
||||
* @param nodeId ID of the working copy or the original checked-out node
|
||||
* @param opts Optional query parameters (`include`, `fields`)
|
||||
* @returns Observable emitting the original (unlocked) node
|
||||
*/
|
||||
cancelCheckoutNode(nodeId: string, opts?: NodesIncludeQuery): Observable<NodeEntry> {
|
||||
return from(this.nodesApi.cancelCheckoutNode(nodeId, opts));
|
||||
}
|
||||
|
||||
private randomNodeName(): string {
|
||||
return `node_${Date.now()}`;
|
||||
}
|
||||
|
||||
@@ -1033,4 +1033,63 @@ export class NodesApi extends BaseApi {
|
||||
returnType: SizeDetailsEntry
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Checkout a node
|
||||
*
|
||||
* Checks out a file node for offline editing. Creates a private working copy and locks the original.
|
||||
*
|
||||
* @param nodeId The identifier of a file node to check out.
|
||||
* @param opts Optional parameters
|
||||
* @returns Promise<NodeEntry> - the working copy node
|
||||
*/
|
||||
checkoutNode(nodeId: string, opts?: NodesIncludeQuery): Promise<NodeEntry> {
|
||||
throwIfNotDefined(nodeId, 'nodeId');
|
||||
|
||||
const pathParams = {
|
||||
nodeId
|
||||
};
|
||||
|
||||
const queryParams = {
|
||||
include: buildCollectionParam(opts?.include, 'csv'),
|
||||
fields: buildCollectionParam(opts?.fields, 'csv')
|
||||
};
|
||||
|
||||
return this.post({
|
||||
path: '/nodes/{nodeId}/checkout',
|
||||
pathParams,
|
||||
queryParams,
|
||||
returnType: NodeEntry
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel checkout of a node
|
||||
*
|
||||
* Cancels a checkout. Accepts either the working copy or the original checked-out node.
|
||||
* Deletes the working copy, unlocks the original, and returns the original node.
|
||||
*
|
||||
* @param nodeId The identifier of the working copy or the original checked-out node.
|
||||
* @param opts Optional parameters
|
||||
* @returns Promise<NodeEntry> - the original (unlocked) node
|
||||
*/
|
||||
cancelCheckoutNode(nodeId: string, opts?: NodesIncludeQuery): Promise<NodeEntry> {
|
||||
throwIfNotDefined(nodeId, 'nodeId');
|
||||
|
||||
const pathParams = {
|
||||
nodeId
|
||||
};
|
||||
|
||||
const queryParams = {
|
||||
include: buildCollectionParam(opts?.include, 'csv'),
|
||||
fields: buildCollectionParam(opts?.fields, 'csv')
|
||||
};
|
||||
|
||||
return this.post({
|
||||
path: '/nodes/{nodeId}/cancel-checkout',
|
||||
pathParams,
|
||||
queryParams,
|
||||
returnType: NodeEntry
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,8 +25,10 @@ All URIs are relative to *https://localhost/alfresco/api/-default-/public/alfres
|
||||
| [unlockNode](#unlockNode) | **POST** /nodes/{nodeId}/unlock | Unlock a node |
|
||||
| [updateNode](#updateNode) | **PUT** /nodes/{nodeId} | Update a node |
|
||||
| [updateNodeContent](#updateNodeContent) | **PUT** /nodes/{nodeId}/content | Update node content |
|
||||
| [initiateFolderSizeCalculation](#initiateFolderSizeCalculation) | **POST** /nodes/{nodeId}/size-details | Initiate a new request to calculate folder size |
|
||||
| [initiateFolderSizeCalculation](#initiateFolderSizeCalculation) | **POST** /nodes/{nodeId}/size-details | Initiate a new request to calculate folder size |
|
||||
| [getFolderSizeInfo](#getFolderSizeInfo) | **GET** /nodes/{nodeId}/size-details/{jobId} | Gets the details of a folder |
|
||||
| [cancelCheckoutNode](#cancelCheckoutNode) | **POST** /nodes/{nodeId}/cancel-checkout | Cancel checkout of a node |
|
||||
| [checkoutNode](#checkoutNode) | **POST** /nodes/{nodeId}/checkout | Checkout a node |
|
||||
|
||||
## copyNode
|
||||
|
||||
@@ -1260,6 +1262,66 @@ nodesApi.getFolderSizeInfo(`<nodeId>`, `<jobId>`).then((data) => {
|
||||
});
|
||||
```
|
||||
|
||||
## checkoutNode
|
||||
|
||||
Checkout a node
|
||||
|
||||
Checks out a file node for offline editing. Creates a private working copy and locks the original.
|
||||
|
||||
**Parameters**
|
||||
|
||||
| Name | Type | Description |
|
||||
|--------------|----------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| **nodeId** | string | The identifier of a file node to check out. |
|
||||
| opts.include | string[] | Returns additional information about the node. The following optional fields can be requested: `allowableOperations`, `association`, `isLink`, `isFavorite`, `isLocked`, `path`, `permissions`, `definition` |
|
||||
| opts.fields | string[] | A list of field names. You can use this parameter to restrict the fields returned within a response if, for example, you want to save on overall bandwidth. The list applies to a returned individual entity or entries within a collection. If the API method also supports the **include** parameter, then the fields specified in the **include** parameter are returned in addition to those specified in the **fields** parameter. |
|
||||
|
||||
**Return type**: [NodeEntry](NodeEntry.md)
|
||||
|
||||
**Example**
|
||||
|
||||
```javascript
|
||||
import { AlfrescoApi, NodesApi } from '@alfresco/js-api';
|
||||
|
||||
const alfrescoApi = new AlfrescoApi(/*..*/);
|
||||
const nodesApi = new NodesApi(alfrescoApi);
|
||||
const opts = {};
|
||||
|
||||
nodesApi.checkoutNode(`<nodeId>`, opts).then((data) => {
|
||||
console.log('API called successfully. Returned data: ' + data);
|
||||
});
|
||||
```
|
||||
|
||||
## cancelCheckoutNode
|
||||
|
||||
Cancel checkout of a node
|
||||
|
||||
Cancels a checkout. Accepts either the working copy or the original checked-out node. Deletes the working copy, unlocks the original, and returns the original node.
|
||||
|
||||
**Parameters**
|
||||
|
||||
| Name | Type | Description |
|
||||
|--------------|----------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| **nodeId** | string | The identifier of the working copy or original checked-out node. |
|
||||
| opts.include | string[] | Returns additional information about the node. The following optional fields can be requested: `allowableOperations`, `association`, `isLink`, `isFavorite`, `isLocked`, `path`, `permissions`, `definition` |
|
||||
| opts.fields | string[] | A list of field names. You can use this parameter to restrict the fields returned within a response if, for example, you want to save on overall bandwidth. The list applies to a returned individual entity or entries within a collection. If the API method also supports the **include** parameter, then the fields specified in the **include** parameter are returned in addition to those specified in the **fields** parameter. |
|
||||
|
||||
**Return type**: [NodeEntry](NodeEntry.md)
|
||||
|
||||
**Example**
|
||||
|
||||
```javascript
|
||||
import { AlfrescoApi, NodesApi } from '@alfresco/js-api';
|
||||
|
||||
const alfrescoApi = new AlfrescoApi(/*..*/);
|
||||
const nodesApi = new NodesApi(alfrescoApi);
|
||||
const opts = {};
|
||||
|
||||
nodesApi.cancelCheckoutNode(`<nodeId>`, opts).then((data) => {
|
||||
console.log('API called successfully. Returned data: ' + data);
|
||||
});
|
||||
```
|
||||
|
||||
# Models
|
||||
|
||||
## NodeBodyUpdate
|
||||
|
||||
@@ -141,6 +141,30 @@ describe('Node', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('Checkout', () => {
|
||||
it('should POST to the checkout endpoint', async () => {
|
||||
nodeMock.post200CheckoutNode('fake-node-id');
|
||||
const result = await nodesApi.checkoutNode('fake-node-id');
|
||||
assert.ok(result.entry, 'cancelCheckoutNode should return a NodeEntry');
|
||||
});
|
||||
|
||||
it('should throw if nodeId is not defined', () => {
|
||||
assert.throws(() => nodesApi.checkoutNode(undefined));
|
||||
});
|
||||
});
|
||||
|
||||
describe('Cancel Checkout', () => {
|
||||
it('should POST to the cancel-checkout endpoint', async () => {
|
||||
nodeMock.post200CancelCheckoutNode('fake-node-id');
|
||||
const result = await nodesApi.cancelCheckoutNode('fake-node-id');
|
||||
assert.ok(result.entry, 'checkoutNode should return a NodeEntry');
|
||||
});
|
||||
|
||||
it('should throw if nodeId is not defined', () => {
|
||||
assert.throws(() => nodesApi.cancelCheckoutNode(undefined));
|
||||
});
|
||||
});
|
||||
|
||||
describe('FolderInformation', () => {
|
||||
it('should return jobId on initiateFolderSizeCalculation API call if everything is ok', async () => {
|
||||
nodeMock.post200ResponseInitiateFolderSizeCalculation();
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
*/
|
||||
|
||||
import { BaseMock } from '../base.mock';
|
||||
import { NodeEntry } from '../../../src';
|
||||
|
||||
export class NodeMock extends BaseMock {
|
||||
get200ResponseChildren(): void {
|
||||
@@ -284,4 +285,20 @@ export class NodeMock extends BaseMock {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
post200CheckoutNode(nodeId: string): void {
|
||||
this.mock()
|
||||
.post(`/alfresco/api/-default-/public/alfresco/versions/1/nodes/${nodeId}/checkout`)
|
||||
.reply(200, {
|
||||
entry: { id: 'test-node-id', name: 'Test Node' }
|
||||
} as NodeEntry);
|
||||
}
|
||||
|
||||
post200CancelCheckoutNode(nodeId: string): void {
|
||||
this.mock()
|
||||
.post(`/alfresco/api/-default-/public/alfresco/versions/1/nodes/${nodeId}/cancel-checkout`)
|
||||
.reply(200, {
|
||||
entry: { id: 'test-node-id', name: 'Test Node' }
|
||||
} as NodeEntry);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user