diff --git a/docs/content-services/services/new-version-uploader.dialog.service.md b/docs/content-services/services/new-version-uploader.dialog.service.md new file mode 100644 index 0000000000..724e11caac --- /dev/null +++ b/docs/content-services/services/new-version-uploader.dialog.service.md @@ -0,0 +1,89 @@ +--- +Title: New Version Uploader service +Added: v1.0.0 +Status: Active +Last reviewed: 2022-05-26 +--- + +# [New Version Uploader service](../../../lib/content-services/src/lib/new-version-uploader/new-version-uploader.service.ts "Defined in new-version-uploader.service.ts") + +Display a dialog that allows to upload new file version or to manage the current node versions. + +## Class members + +### Methods + +- **openUploadNewVersionDialog**(data: [NewVersionUploaderDialogData](../../../lib/content-services/src/lib/new-version-uploader/models/new-version-uploader.model.ts), config: `MatDialogConfig`): `Observable`
+ Opens a dialog to upload new file version or to manage current node versions + - _data:_ [NewVersionUploaderDialogData](../../../lib/content-services/src/lib/new-version-uploader/models/new-version-uploader.model.ts) - The data to pass to the dialog + - _config:_ `MatDialogConfig` - A configuration object that allows to override default dialog configuration + - **Returns** `Observable` - [`Observable`](http://reactivex.io/documentation/observable.html) which you can subscribe in order to get information about the dialog actions or error notification in case of error condition. +## Details + +You can open dialog in two different ways: + +- [Upload new file version](#upload-new-version) +- [Manage node versions](#manage-versions) + +### Upload New Version + +The dialog shows + +- a side by side comparison between the current target node (type, name, icon) and the new file that should update it's version +- the new version's minor/major changes +- the optional comment +- a button to upload a new file version + ![Upload new version image](../../docassets/images/adf-new-version-uploader_upload.png) + +Usage example: +```ts +import { NewVersionUploaderService } from '@alfresco/adf-content-services' + +constructor(private newVersionUploaderService: NewVersionUploaderService){} + +yourFunctionToOpenDialog(){ + const newVersionUploaderDialogData: NewVersionUploaderDialogData = { + file, + node + }; + this.newVersionUploaderService.openUploadNewVersionDialog(newVersionUploaderDialogData).subscribe( + (data: NewVersionUploaderData) => { + // place your action here on operation success! + }, + (error) => { + // place your action here on operation error! + }) +} +``` + +--- + +### Manage Versions + +Setting `showVersionsOnly` to `true` the dialog displays the version history of a node, with the ability to restore, delete and view version of the current node +![Manage versions image](../../docassets/images/adf-new-version-uploader_manage-versions.png) + +Usage example: +```ts +import { NewVersionUploaderService } from '@alfresco/adf-content-services' + +constructor(private newVersionUploaderService: NewVersionUploaderService){} + +yourFunctionToOpenDialog(){ + const newVersionUploaderDialogData: NewVersionUploaderDialogData = { + file, + node, + showVersionsOnly: true + }; + this.newVersionUploaderService.openUploadNewVersionDialog(newVersionUploaderDialogData).subscribe( + (data: NewVersionUploaderData) => { + // place your action here on operation success! + }) +} +``` + +## See Also + +- [Version list component](../components/docs/content-services/components/version-list.component.md) +- [Version Comparison Component](../components/docs/content-services/components/version-comparison.component.md) +- [Version Upload Component](../components/docs/content-services/components/version-upload.component.md) diff --git a/docs/docassets/images/adf-new-version-uploader_manage-versions.png b/docs/docassets/images/adf-new-version-uploader_manage-versions.png new file mode 100644 index 0000000000..43acaefbd8 Binary files /dev/null and b/docs/docassets/images/adf-new-version-uploader_manage-versions.png differ diff --git a/docs/docassets/images/adf-new-version-uploader_upload.png b/docs/docassets/images/adf-new-version-uploader_upload.png new file mode 100644 index 0000000000..1ae650541a Binary files /dev/null and b/docs/docassets/images/adf-new-version-uploader_upload.png differ diff --git a/lib/content-services/src/lib/i18n/en.json b/lib/content-services/src/lib/i18n/en.json index 12c1ed7a8d..f64214f76d 100644 --- a/lib/content-services/src/lib/i18n/en.json +++ b/lib/content-services/src/lib/i18n/en.json @@ -22,6 +22,17 @@ "NO_LABEL": "No" } }, + "ADF-NEW-VERSION-UPLOADER": { + "DIALOG_LIST": { + "TITLE": "Manage Versions", + "CLOSE": "Close" + }, + "DIALOG_UPLOAD": { + "TITLE": "Upload New Version", + "CANCEL": "Cancel", + "UPLOAD": "Upload" + } + }, "ADF_VERSION_COMPARISON": { "CURRENT_VERSION": "Current", "NEW_VERSION": "New", diff --git a/lib/content-services/src/lib/mock/new-version-uploader.service.mock.ts b/lib/content-services/src/lib/mock/new-version-uploader.service.mock.ts new file mode 100644 index 0000000000..7bce9cf8a9 --- /dev/null +++ b/lib/content-services/src/lib/mock/new-version-uploader.service.mock.ts @@ -0,0 +1,182 @@ +/*! + * @license + * Copyright 2019 Alfresco Software, Ltd. + * + * 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 mockNode: any = ({ + isFile: true, + createdByUser: { id: 'admin', displayName: 'Administrator' }, + modifiedAt: '2017-05-24T15:08:55.640Z', + nodeType: 'cm:content', + content: { + mimeType: 'application/rtf', + mimeTypeName: 'Rich Text Format', + sizeInBytes: 14530, + encoding: 'UTF-8' + }, + parentId: 'd124de26-6ba0-4f40-8d98-4907da2d337a', + createdAt: '2017-05-24T15:08:55.640Z', + path: { + name: '/Company Home/Guest Home', + isComplete: true, + elements: [{ + id: '94acfc73-7014-4475-9bd9-93a2162f0f8c', + name: 'Company Home' + }, { id: 'd124de26-6ba0-4f40-8d98-4907da2d337a', name: 'Guest Home' }] + }, + isFolder: false, + modifiedByUser: { id: 'admin', displayName: 'Administrator' }, + name: 'b_txt_file.rtf', + id: '70e1cc6a-6918-468a-b84a-1048093b06fd', + properties: { 'cm:versionLabel': '1.0', 'cm:versionType': 'MAJOR' }, + allowableOperations: ['delete', 'update'] +}); + +export const mockFile = new File(['fakefake'], 'file-fake.png', { type: 'image/png' }); + +export const mockNewVersionUploaderData: any = { + action: 'upload', + newVersion: { + value: { + entry: { + isFile: true, + createdByUser: { + id: 'hruser', + displayName: 'hruser' + }, + modifiedAt: '2022-05-24T10:19:43.544Z', + nodeType: 'cm:content', + content: { + mimeType: + 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + mimeTypeName: 'Microsoft Word 2007', + sizeInBytes: 11887, + encoding: 'UTF-8' + }, + parentId: '422538ca-ea4b-4086-83f9-b36e4521ec7f', + aspectNames: [ + 'rn:renditioned', + 'cm:versionable', + 'cm:titled', + 'cm:auditable', + 'cm:author', + 'cm:thumbnailModification' + ], + createdAt: '2022-05-24T07:26:44.429Z', + isFolder: false, + modifiedByUser: { + id: 'hruser', + displayName: 'hruser' + }, + name: 'Test3.docx', + id: '42ddb84d-fc96-4b45-aa3c-f24ca997d602', + properties: { + 'cm:versionType': 'MINOR', + 'cm:versionLabel': '1.1', + 'cm:author': 'Amedeo Lepore', + 'cm:lastThumbnailModification': ['doclib:1653377205499'] + }, + allowableOperations: ['delete', 'update', 'updatePermissions'] + } + } + }, + currentVersion: { + isFile: true, + createdByUser: { + id: 'hruser', + displayName: 'hruser' + }, + modifiedAt: '2022-05-24T07:26:45.337Z', + nodeType: 'cm:content', + content: { + mimeType: + 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + mimeTypeName: 'Microsoft Word 2007', + sizeInBytes: 11949, + encoding: 'UTF-8' + }, + parentId: '422538ca-ea4b-4086-83f9-b36e4521ec7f', + aspectNames: [ + 'rn:renditioned', + 'cm:versionable', + 'cm:titled', + 'cm:auditable', + 'cm:author', + 'cm:thumbnailModification' + ], + createdAt: '2022-05-24T07:26:44.429Z', + path: { + name: '/Company Home/User Homes/hruser', + isComplete: true, + elements: [ + { + id: '4e2284fd-9457-4914-a612-ea844e87f53f', + name: 'Company Home', + nodeType: 'cm:folder', + aspectNames: ['cm:titled', 'cm:auditable', 'app:uifacets'] + }, + { + id: '75a5d2d2-6edb-40b6-822e-499f5e8beffb', + name: 'User Homes', + nodeType: 'cm:folder', + aspectNames: ['cm:titled', 'cm:auditable', 'app:uifacets'] + }, + { + id: '422538ca-ea4b-4086-83f9-b36e4521ec7f', + name: 'hruser', + nodeType: 'cm:folder', + aspectNames: ['cm:ownable', 'cm:auditable'] + } + ] + }, + isFolder: false, + permissions: { + inherited: [ + { + authorityId: 'ROLE_OWNER', + name: 'All', + accessStatus: 'ALLOWED' + }, + { + authorityId: 'hruser', + name: 'All', + accessStatus: 'ALLOWED' + } + ], + settable: [ + 'Contributor', + 'Collaborator', + 'Coordinator', + 'Editor', + 'Consumer' + ], + isInheritanceEnabled: true + }, + modifiedByUser: { + id: 'hruser', + displayName: 'hruser' + }, + name: 'Test2.docx', + id: '42ddb84d-fc96-4b45-aa3c-f24ca997d602', + properties: { + 'cm:versionType': 'MAJOR', + 'cm:versionLabel': '1.0', + 'cm:author': 'Amedeo Lepore', + 'cm:lastThumbnailModification': ['doclib:1653377205499'] + }, + allowableOperations: ['delete', 'update', 'updatePermissions'], + isExternal: true + } +}; diff --git a/lib/content-services/src/lib/mock/public-api.ts b/lib/content-services/src/lib/mock/public-api.ts index ac5c588d4f..93938de2ec 100644 --- a/lib/content-services/src/lib/mock/public-api.ts +++ b/lib/content-services/src/lib/mock/public-api.ts @@ -22,3 +22,4 @@ export * from './search.service.mock'; export * from './search-filter-mock'; export * from './sites-dropdown.component.mock'; export * from './search-query.mock'; +export * from './new-version-uploader.service.mock'; diff --git a/lib/content-services/src/lib/new-version-uploader/index.ts b/lib/content-services/src/lib/new-version-uploader/index.ts new file mode 100644 index 0000000000..a7e30cc675 --- /dev/null +++ b/lib/content-services/src/lib/new-version-uploader/index.ts @@ -0,0 +1,18 @@ +/*! + * @license + * Copyright 2019 Alfresco Software, Ltd. + * + * 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 './public-api'; diff --git a/lib/content-services/src/lib/new-version-uploader/models/index.ts b/lib/content-services/src/lib/new-version-uploader/models/index.ts new file mode 100644 index 0000000000..cbf55165cc --- /dev/null +++ b/lib/content-services/src/lib/new-version-uploader/models/index.ts @@ -0,0 +1,17 @@ +/*! + * @license + * Copyright 2019 Alfresco Software, Ltd. + * + * 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 './new-version-uploader.model'; diff --git a/lib/content-services/src/lib/new-version-uploader/models/new-version-uploader.model.ts b/lib/content-services/src/lib/new-version-uploader/models/new-version-uploader.model.ts new file mode 100644 index 0000000000..bbea817f34 --- /dev/null +++ b/lib/content-services/src/lib/new-version-uploader/models/new-version-uploader.model.ts @@ -0,0 +1,55 @@ +/*! + * @license + * Copyright 2019 Alfresco Software, Ltd. + * + * 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. + */ +import { MinimalNodeEntryEntity, Version, NodeChildAssociation, Node } from '@alfresco/js-api'; +import { NodeEntityEvent } from '../../document-list'; + +export interface NewVersionUploaderDialogData { + title?: string; + node: MinimalNodeEntryEntity; + file?: File; + currentVersion?: Version; + showVersionsOnly?: boolean; +} + +export type NewVersionUploaderData = VersionManagerUploadData | ViewVersion | RefreshData; + +// eslint-disable-next-line no-shadow +export enum NewVersionUploaderDataAction { + refresh = 'refresh', + upload = 'upload', + view = 'view' +} + +interface BaseData { + action: NewVersionUploaderDataAction; +} + +export interface VersionManagerUploadData extends BaseData { + action: NewVersionUploaderDataAction.upload; + newVersion: NodeEntityEvent; + currentVersion: NodeChildAssociation; +} + +export interface ViewVersion extends BaseData { + action: NewVersionUploaderDataAction.view; + versionId: string; +} + +export interface RefreshData extends BaseData { + action: NewVersionUploaderDataAction.refresh; + node: Node; +} diff --git a/lib/content-services/src/lib/new-version-uploader/new-version-uploader.dialog.html b/lib/content-services/src/lib/new-version-uploader/new-version-uploader.dialog.html new file mode 100644 index 0000000000..17eada95a3 --- /dev/null +++ b/lib/content-services/src/lib/new-version-uploader/new-version-uploader.dialog.html @@ -0,0 +1,33 @@ +
{{ title | translate }}
+
+ + + +
+ +
+
+
+ +
+
+
+ +
diff --git a/lib/content-services/src/lib/new-version-uploader/new-version-uploader.dialog.scss b/lib/content-services/src/lib/new-version-uploader/new-version-uploader.dialog.scss new file mode 100644 index 0000000000..ffc32eec5d --- /dev/null +++ b/lib/content-services/src/lib/new-version-uploader/new-version-uploader.dialog.scss @@ -0,0 +1,79 @@ +/*! + * @license + * Copyright 2019 Alfresco Software, Ltd. + * + * 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. + */ + +.adf-new-version-uploader-dialog { + &-list { + height: 400px; + } + + &-upload { + height: 500px; + } + + .mat-dialog { + &-title { + flex: 0 0 auto; + font-size: 20px; + font-weight: 600; + font-style: normal; + font-stretch: normal; + line-height: 1.6; + margin: 0; + letter-spacing: -0.5px; + color: var(--theme-text-bold-color); + } + + &-content { + flex: 1 1 auto; + position: relative; + overflow-y: auto; + max-height: 100vh; + overflow: hidden; + padding: 2px 26px; + } + + &-actions { + /* stylelint-disable-next-line shorthand-property-no-redundant-values */ + padding: 8px 8px 24px 8px; + display: flex; + justify-content: flex-end; + color: var(--theme-text-color); + + button { + text-transform: uppercase; + font-weight: normal; + } + } + } + + .mat-list-item-content { + padding: 0; + margin: 0 16px; + } + + .adf-version-list-container { + .adf-version-list { + height: 250px; + overflow: hidden; + padding: 0; + } + + .mat-list.adf-version-list { + overflow: auto; + } + } +} diff --git a/lib/content-services/src/lib/new-version-uploader/new-version-uploader.dialog.spec.ts b/lib/content-services/src/lib/new-version-uploader/new-version-uploader.dialog.spec.ts new file mode 100644 index 0000000000..2c5b1b8ea3 --- /dev/null +++ b/lib/content-services/src/lib/new-version-uploader/new-version-uploader.dialog.spec.ts @@ -0,0 +1,209 @@ +/*! + * @license + * Copyright 2019 Alfresco Software, Ltd. + * + * 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. + */ + +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog'; +import { TranslateModule } from '@ngx-translate/core'; +import { setupTestBed } from 'core'; +import { mockFile, mockNode } from '../mock'; +import { ContentTestingModule } from '../testing/content.testing.module'; +import { UploadVersionButtonComponent } from '../upload'; +import { VersionComparisonComponent, VersionListComponent, VersionUploadComponent } from '../version-manager'; +import { NewVersionUploaderDataAction } from './models'; +import { NewVersionUploaderDialogComponent } from './new-version-uploader.dialog'; + +describe('NewVersionUploaderDialog', () => { + let component: NewVersionUploaderDialogComponent; + let fixture: ComponentFixture; + let nativeElement; + + const cssSelectors = { + adfVersionUploadButton: '#adf-version-upload-button', + adfVersionComparison: '#adf-version-comparison', + adfVersionList: '.adf-version-list', + matDialogTitle: '.mat-dialog-title' + }; + + const mockDialogRef = { + close: jasmine.createSpy('close'), + open: jasmine.createSpy('open') + }; + const showVersionsOnly = true; + + setupTestBed({ + imports: [ + TranslateModule.forRoot(), + ContentTestingModule + ], + declarations: [ + NewVersionUploaderDialogComponent, + VersionListComponent, + VersionUploadComponent, + UploadVersionButtonComponent, + VersionComparisonComponent + ], + providers: [ + { provide: MAT_DIALOG_DATA, useValue: { node: mockNode, showVersionsOnly, file: mockFile } }, + { + provide: MatDialogRef, useValue: mockDialogRef + } + ] + }); + + beforeEach(() => { + fixture = TestBed.createComponent(NewVersionUploaderDialogComponent); + component = fixture.componentInstance; + nativeElement = fixture.debugElement.nativeElement; + }); + + afterEach(() => { + fixture.destroy(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + describe('Upload New Version', () => { + + const expectedUploadNewVersionTitle = 'ADF-NEW-VERSION-UPLOADER.DIALOG_UPLOAD.TITLE'; + + it('should display adf version upload button if showVersionsOnly is passed as false from parent component', () => { + component.data.showVersionsOnly = false; + fixture.detectChanges(); + const adfVersionComponent = nativeElement.querySelector(cssSelectors.adfVersionUploadButton); + expect(adfVersionComponent).not.toEqual(null); + }); + + it('should display adf version comparison if showVersionsOnly is passed as false from parent component', () => { + component.data.showVersionsOnly = false; + fixture.detectChanges(); + const adfVersionComparisonComponent = nativeElement.querySelector(cssSelectors.adfVersionComparison); + expect(adfVersionComparisonComponent).not.toEqual(null); + }); + + it('should not display adf version list if showVersionsOnly is passed as false from parent component', () => { + component.data.showVersionsOnly = false; + fixture.detectChanges(); + const adfVersionComparisonComponent = nativeElement.querySelector(cssSelectors.adfVersionList); + expect(adfVersionComparisonComponent).toEqual(null); + }); + + it('should show default title if title is not provided from parent component', () => { + component.data.showVersionsOnly = false; + fixture.detectChanges(); + const matDialogTitle = nativeElement.querySelector(cssSelectors.matDialogTitle); + expect(matDialogTitle.innerHTML).toEqual(expectedUploadNewVersionTitle); + }); + + it('should show default title if title is provided as empty from parent component', () => { + component.data.showVersionsOnly = false; + component.data.title = ''; + fixture.detectChanges(); + const matDialogTitle = nativeElement.querySelector(cssSelectors.matDialogTitle); + expect(matDialogTitle.innerHTML).toEqual(expectedUploadNewVersionTitle); + }); + + it('should not show Upload New Version default title if title is provided from parent component', () => { + component.data.showVersionsOnly = false; + component.data.title = 'TEST_TITLE'; + fixture.detectChanges(); + const matDialogTitle = nativeElement.querySelector(cssSelectors.matDialogTitle); + expect(matDialogTitle.innerHTML).toEqual('TEST_TITLE'); + }); + + it('should emit dialog action when upload a new file', () => { + const spyOnDialogAction = spyOn(component.dialogAction, 'emit'); + component.data.showVersionsOnly = false; + fixture.detectChanges(); + component.handleUpload(mockNode); + const expectedEmittedValue = { + action: NewVersionUploaderDataAction.upload, + currentVersion: component.data.node, + newVersion: mockNode + }; + expect(spyOnDialogAction).toHaveBeenCalledWith(expectedEmittedValue); + }); + + it('should close dialog after file is uploaded', () => { + component.data.showVersionsOnly = false; + fixture.detectChanges(); + component.handleUpload(mockFile); + expect(mockDialogRef.close).toHaveBeenCalled(); + }); + + it('should close dialog after click on dialog cancel', () => { + component.data.showVersionsOnly = false; + fixture.detectChanges(); + component.handleCancel(); + expect(mockDialogRef.close).toHaveBeenCalled(); + }); + + }); + + describe('Manage Versions', () => { + + const expectedManageVersionsTitle = 'ADF-NEW-VERSION-UPLOADER.DIALOG_LIST.TITLE'; + + it('should display adf version list if showVersionsOnly is passed as true from parent component', () => { + component.data.showVersionsOnly = true; + fixture.detectChanges(); + const adfVersionListComponent = document.querySelector(cssSelectors.adfVersionList); + expect(adfVersionListComponent).not.toEqual(null); + }); + + it('should not display adf version upload button if showVersionsOnly is passed as true from parent component', () => { + component.data.showVersionsOnly = true; + fixture.detectChanges(); + const adfVersionComponent = nativeElement.querySelector(cssSelectors.adfVersionUploadButton); + expect(adfVersionComponent).toEqual(null); + }); + + it('should not display adf version comparison if showVersionsOnly is passed as true from parent component', () => { + component.data.showVersionsOnly = true; + fixture.detectChanges(); + const adfVersionComponent = nativeElement.querySelector(cssSelectors.adfVersionComparison); + expect(adfVersionComponent).toEqual(null); + }); + + it('should show Manage Versions default title if title is not provided from parent component', () => { + component.data.showVersionsOnly = true; + component.data.title = undefined; + fixture.detectChanges(); + const matDialogTitle = nativeElement.querySelector(cssSelectors.matDialogTitle); + expect(matDialogTitle.innerHTML).toEqual(expectedManageVersionsTitle); + }); + + it('should show Manage Versions default title if title is provided as empty from parent component', () => { + component.data.showVersionsOnly = true; + component.data.title = ''; + fixture.detectChanges(); + const matDialogTitle = nativeElement.querySelector(cssSelectors.matDialogTitle); + expect(matDialogTitle.innerHTML).toEqual(expectedManageVersionsTitle); + }); + + it('should not show Manage Versions default title if title is provided from parent component', () => { + component.data.showVersionsOnly = true; + component.data.title = 'TEST_TITLE'; + fixture.detectChanges(); + const matDialogTitle = nativeElement.querySelector(cssSelectors.matDialogTitle); + expect(matDialogTitle.innerHTML).toEqual('TEST_TITLE'); + }); + + }); + +}); diff --git a/lib/content-services/src/lib/new-version-uploader/new-version-uploader.dialog.ts b/lib/content-services/src/lib/new-version-uploader/new-version-uploader.dialog.ts new file mode 100644 index 0000000000..e03c667f69 --- /dev/null +++ b/lib/content-services/src/lib/new-version-uploader/new-version-uploader.dialog.ts @@ -0,0 +1,83 @@ +/*! + * @license + * Copyright 2019 Alfresco Software, Ltd. + * + * 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. + */ + +import { Node } from '@alfresco/js-api'; +import { Component, EventEmitter, Inject, OnInit, Output, ViewEncapsulation } from '@angular/core'; +import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog'; +import { NewVersionUploaderDialogData, NewVersionUploaderData, NewVersionUploaderDataAction } from './models'; + +@Component({ + selector: 'adf-new-version-uploader-dialog', + templateUrl: './new-version-uploader.dialog.html', + styleUrls: ['./new-version-uploader.dialog.scss'], + encapsulation: ViewEncapsulation.None +}) +export class NewVersionUploaderDialogComponent implements OnInit { + + /** + * Dialog title to show into the header. + * If data.title is not provided, a default title is set + * */ + title: string; + + /** Emitted when an action is done. */ + @Output() + dialogAction = new EventEmitter(); + + /** Emitted when an error occurs. */ + @Output() + uploadError = new EventEmitter(); + + constructor( + @Inject(MAT_DIALOG_DATA) public data: NewVersionUploaderDialogData, + private dialogRef: MatDialogRef + ) { } + + ngOnInit(): void { + this.setDialogTitle(); + } + + private setDialogTitle() { + if (!this.data.title) { + this.title = this.data.showVersionsOnly ? 'ADF-NEW-VERSION-UPLOADER.DIALOG_LIST.TITLE' : 'ADF-NEW-VERSION-UPLOADER.DIALOG_UPLOAD.TITLE'; + } else { + this.title = this.data.title; + } + } + + handleUpload(newFileVersion) { + this.dialogAction.emit({ action: NewVersionUploaderDataAction.upload, newVersion: newFileVersion, currentVersion: this.data.node }); + this.dialogRef.close(); + } + + handleCancel() { + this.dialogRef.close(); + } + + onUploadError(error) { + this.uploadError.emit(error); + } + + onViewingVersion(versionId: string) { + this.dialogAction.emit({ action: NewVersionUploaderDataAction.view, versionId }); + } + + refresh(node: Node) { + this.dialogAction.emit({ action: NewVersionUploaderDataAction.refresh, node }); + } + +} diff --git a/lib/content-services/src/lib/new-version-uploader/new-version-uploader.module.ts b/lib/content-services/src/lib/new-version-uploader/new-version-uploader.module.ts new file mode 100644 index 0000000000..b1360aa736 --- /dev/null +++ b/lib/content-services/src/lib/new-version-uploader/new-version-uploader.module.ts @@ -0,0 +1,46 @@ +/*! + * @license + * Copyright 2019 Alfresco Software, Ltd. + * + * 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. + */ + +import { CommonModule } from '@angular/common'; +import { FormsModule } from '@angular/forms'; +import { NgModule } from '@angular/core'; +import { MaterialModule } from '../material.module'; + +import { UploadModule } from '../upload/upload.module'; +import { CoreModule } from '@alfresco/adf-core'; +import { VersionManagerModule } from '../version-manager'; +import { NewVersionUploaderDialogComponent } from './new-version-uploader.dialog'; + + +@NgModule({ + imports: [ + CommonModule, + MaterialModule, + CoreModule, + UploadModule, + FormsModule, + VersionManagerModule + ], + declarations: [ + NewVersionUploaderDialogComponent + ], + exports: [ + NewVersionUploaderDialogComponent, + FormsModule + ] +}) +export class NewVersionUploaderModule { } diff --git a/lib/content-services/src/lib/new-version-uploader/new-version-uploader.service.spec.ts b/lib/content-services/src/lib/new-version-uploader/new-version-uploader.service.spec.ts new file mode 100644 index 0000000000..8a0be24b00 --- /dev/null +++ b/lib/content-services/src/lib/new-version-uploader/new-version-uploader.service.spec.ts @@ -0,0 +1,242 @@ +/*! + * @license + * Copyright 2019 Alfresco Software, Ltd. + * + * 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. + */ + +import { ContentService } from '@alfresco/adf-core'; +import { Component, EventEmitter, Output } from '@angular/core'; +import { ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing'; +import { MatDialog, MatDialogConfig } from '@angular/material/dialog'; +import { TranslateModule } from '@ngx-translate/core'; +import { BehaviorSubject, of, Subject } from 'rxjs'; +import { mockFile, mockNewVersionUploaderData, mockNode } from '../mock'; +import { ContentTestingModule } from '../testing/content.testing.module'; +import { NewVersionUploaderData, NewVersionUploaderDataAction, NewVersionUploaderDialogData, RefreshData, VersionManagerUploadData, ViewVersion } from './models'; +import { NewVersionUploaderDialogComponent } from './new-version-uploader.dialog'; +import { NewVersionUploaderService } from './new-version-uploader.service'; + +@Component({ + template: '' +}) +class TestDialogComponent { + @Output() + dialogAction = new EventEmitter(); + + @Output() + uploadError = new EventEmitter(); + + afterClosed = () => of({ action: 'refresh', node: mockNode }); + +} + +describe('NewVersionUploaderService', () => { + let fixture: ComponentFixture; + let service: NewVersionUploaderService; + let contentService: ContentService; + let dialog: MatDialog; + let spyOnDialogOpen: jasmine.Spy; + let dialogRefSpyObj; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [ + TranslateModule.forRoot(), + ContentTestingModule + ], + declarations: [TestDialogComponent] + }); + }); + + beforeEach(() => { + service = TestBed.inject(NewVersionUploaderService); + contentService = TestBed.inject(ContentService); + dialog = TestBed.inject(MatDialog); + fixture = TestBed.createComponent(TestDialogComponent); + + dialogRefSpyObj = jasmine.createSpyObj({ afterClosed: null }); + dialogRefSpyObj.componentInstance = fixture.componentInstance; + dialogRefSpyObj.afterClosed = fixture.componentInstance.afterClosed; + spyOnDialogOpen = spyOn(dialog, 'open').and.returnValue(dialogRefSpyObj); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); + + describe('openUploadNewVersionDialog', () => { + it('Should not open dialog if update operation is not allowed', () => { + spyOn(contentService, 'hasAllowableOperations').and.returnValue(false); + expect(spyOnDialogOpen).not.toHaveBeenCalled(); + }); + + it('Should return error if update operation is not allowed', async () => { + spyOn(contentService, 'hasAllowableOperations').and.returnValue(false); + const mockNewVersionUploaderDialogData: NewVersionUploaderDialogData = { + node: mockNode, + file: mockFile + }; + try { + await service.openUploadNewVersionDialog(mockNewVersionUploaderDialogData).toPromise(); + fail('An error should have been thrown'); + } catch (error) { + expect(error).toEqual({ value: 'OPERATION.ERROR.PERMISSION' }); + } + }); + + describe('Mat Dialog configuration', () => { + let mockNewVersionUploaderDialogData: NewVersionUploaderDialogData; + beforeEach(() => { + spyOn(contentService, 'hasAllowableOperations').and.returnValue(true); + spyOn(service.versionsApi, 'listVersionHistory').and.returnValue(Promise.resolve({ + list: { entries: [{ entry: '2' }] } + })); + mockNewVersionUploaderDialogData = { + node: mockNode, + file: mockFile + }; + }); + + it('Should open dialog with default configuration', fakeAsync(() => { + service.openUploadNewVersionDialog(mockNewVersionUploaderDialogData).toPromise(); + tick(); + expect(spyOnDialogOpen).toHaveBeenCalledWith(NewVersionUploaderDialogComponent, { + data: { file: mockFile, node: mockNode, currentVersion: '2', showComments: true, allowDownload: true, showVersionsOnly: undefined }, + panelClass: ['adf-new-version-uploader-dialog', 'adf-new-version-uploader-dialog-upload'], + width: '630px' + }); + })); + + it('Should override default dialog panelClass', fakeAsync(() => { + const mockDialogConfiguration: MatDialogConfig = { + panelClass: 'adf-custom-class', + width: '500px' + }; + service.openUploadNewVersionDialog(mockNewVersionUploaderDialogData, mockDialogConfiguration).toPromise(); + tick(); + expect(spyOnDialogOpen).toHaveBeenCalledWith(NewVersionUploaderDialogComponent, { + data: { file: mockFile, node: mockNode, currentVersion: '2', showComments: true, allowDownload: true, showVersionsOnly: undefined }, + panelClass: 'adf-custom-class', + width: '500px' + }); + })); + + it('Should set dialog height', fakeAsync(() => { + const mockDialogConfiguration: MatDialogConfig = { + height: '600px' + }; + service.openUploadNewVersionDialog(mockNewVersionUploaderDialogData, mockDialogConfiguration).toPromise(); + tick(); + expect(spyOnDialogOpen).toHaveBeenCalledWith(NewVersionUploaderDialogComponent, { + data: { file: mockFile, node: mockNode, currentVersion: '2', showComments: true, allowDownload: true, showVersionsOnly: undefined }, + panelClass: ['adf-new-version-uploader-dialog', 'adf-new-version-uploader-dialog-upload'], + width: '630px', + height: '600px' + }); + })); + + it('Should not override dialog configuration, if dialog configuration is empty', fakeAsync(() => { + const mockDialogConfiguration: MatDialogConfig = {}; + service.openUploadNewVersionDialog(mockNewVersionUploaderDialogData, mockDialogConfiguration).toPromise(); + tick(); + expect(spyOnDialogOpen).toHaveBeenCalledWith(NewVersionUploaderDialogComponent, { + data: { file: mockFile, node: mockNode, currentVersion: '2', showComments: true, allowDownload: true, showVersionsOnly: undefined }, + panelClass: ['adf-new-version-uploader-dialog', 'adf-new-version-uploader-dialog-upload'], + width: '630px' + }); + })); + + it('Should dialog add list css class if showVersionsOnly is true', fakeAsync(() => { + const mockNewVersionUploaderDialogDataWithVersionsOnly = { + node: mockNode, + file: mockFile, + showVersionsOnly: true + }; + service.openUploadNewVersionDialog(mockNewVersionUploaderDialogDataWithVersionsOnly).toPromise(); + tick(); + expect(spyOnDialogOpen).toHaveBeenCalledWith(NewVersionUploaderDialogComponent, { + data: { file: mockFile, node: mockNode, currentVersion: '2', showComments: true, allowDownload: true, showVersionsOnly: true }, + panelClass: ['adf-new-version-uploader-dialog', 'adf-new-version-uploader-dialog-list'], + width: '630px' + }); + })); + + }); + + describe('Subscribe events from Dialog', () => { + let mockNewVersionUploaderDialogData: NewVersionUploaderDialogData; + + beforeEach(() => { + spyOn(contentService, 'hasAllowableOperations').and.returnValue(true); + spyOn(service.versionsApi, 'listVersionHistory').and.returnValue(Promise.resolve({ + list: { entries: [{ entry: '2' }] } + })); + mockNewVersionUploaderDialogData = { + node: mockNode, + file: mockFile + }; + }); + + it('Should return Refresh action', (done) => { + dialogRefSpyObj.componentInstance = { + dialogAction: new BehaviorSubject({ action: NewVersionUploaderDataAction.refresh, node: mockNode }), + uploadError: new Subject() + }; + service.openUploadNewVersionDialog(mockNewVersionUploaderDialogData).subscribe((res) => { + expect(res).toEqual({ action: NewVersionUploaderDataAction.refresh, node: mockNode }); + done(); + }); + }); + + it('Should return Upload action', (done) => { + dialogRefSpyObj.componentInstance = { + dialogAction: new BehaviorSubject(mockNewVersionUploaderData), + uploadError: new Subject() + }; + service.openUploadNewVersionDialog(mockNewVersionUploaderDialogData).subscribe((res) => { + expect(res).toEqual(mockNewVersionUploaderData); + done(); + }); + }); + + it('Should return View Version action', (done) => { + dialogRefSpyObj.componentInstance = { + dialogAction: new BehaviorSubject({ action: NewVersionUploaderDataAction.view, versionId: '2' }), + uploadError: new Subject() + }; + service.openUploadNewVersionDialog(mockNewVersionUploaderDialogData).subscribe((res) => { + expect(res).toEqual({ action: NewVersionUploaderDataAction.view, versionId: '2' }); + done(); + }); + }); + + it('Should return upload error', (done) => { + dialogRefSpyObj.componentInstance = { + dialogAction: new Subject(), + uploadError: new BehaviorSubject({ value: 'Upload error' }) + }; + spyOnDialogOpen.and.returnValue(dialogRefSpyObj); + service.openUploadNewVersionDialog(mockNewVersionUploaderDialogData).subscribe(() => { + fail('An error should have been thrown'); + }, + error => { + expect(error).toEqual({ value: 'Upload error' }); + done(); + }); + }); + + }); + + }); +}); diff --git a/lib/content-services/src/lib/new-version-uploader/new-version-uploader.service.ts b/lib/content-services/src/lib/new-version-uploader/new-version-uploader.service.ts new file mode 100644 index 0000000000..620290061f --- /dev/null +++ b/lib/content-services/src/lib/new-version-uploader/new-version-uploader.service.ts @@ -0,0 +1,86 @@ +/*! + * @license + * Copyright 2019 Alfresco Software, Ltd. + * + * 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. + */ + +import { Injectable } from '@angular/core'; +import { MatDialog, MatDialogConfig } from '@angular/material/dialog'; +import { AlfrescoApiService, ContentService } from '@alfresco/adf-core'; + +import { NewVersionUploaderDialogComponent } from './new-version-uploader.dialog'; +import { VersionPaging, VersionsApi } from '@alfresco/js-api'; +import { NewVersionUploaderData, NewVersionUploaderDialogData } from './models'; +import { Observable } from 'rxjs'; + +@Injectable({ + providedIn: 'root' +}) +export class NewVersionUploaderService { + + _versionsApi: VersionsApi; + get versionsApi(): VersionsApi { + this._versionsApi = this._versionsApi ?? new VersionsApi(this.apiService.getInstance()); + return this._versionsApi; + } + + constructor( + private contentService: ContentService, + private apiService: AlfrescoApiService, + private dialog: MatDialog + ) { } + + /** + * Open a dialog NewVersionUploaderDialogComponent to display: + * - a side by side comparison between the current target node (type, name, icon) and the new file that should update it's version + * - the new version's minor/major changes and the optional comment of a node and the ability to upload a new file version + * - if data.showVersionsOnly is set to true, displays the version history of a node, with the ability to restore, delete and view version of the current node + * @param data data to pass to MatDialog + * @param config allow to override default MatDialogConfig + * @returns an Observable represents the triggered dialog action or an error in case of an error condition + */ + openUploadNewVersionDialog(data: NewVersionUploaderDialogData, config?: MatDialogConfig) { + const { file, node, showVersionsOnly } = data; + const showComments = true; + const allowDownload = true; + + return new Observable((observer) => { + if (this.contentService.hasAllowableOperations(node, 'update')) { + this.versionsApi.listVersionHistory(node.id).then((versionPaging: VersionPaging) => { + const dialogRef = this.dialog.open(NewVersionUploaderDialogComponent, { + data: { file, node, currentVersion: versionPaging.list.entries[0].entry, showComments, allowDownload, showVersionsOnly }, + panelClass: this.composePanelClass(showVersionsOnly), + width: '630px', + ...(config && Object.keys(config).length > 0 && config) + }); + dialogRef.componentInstance.dialogAction.asObservable() + .subscribe((newVersionUploaderData: NewVersionUploaderData) => { + observer.next(newVersionUploaderData); + }); + dialogRef.componentInstance.uploadError.asObservable().subscribe(error => { + observer.error(error); + }); + }); + } else { + observer.error({ value: 'OPERATION.ERROR.PERMISSION' }); + } + }); + + } + + private composePanelClass(showVersionsOnly: boolean): string | string[] { + const dialogCssClass = 'adf-new-version-uploader-dialog'; + return [dialogCssClass, `${dialogCssClass}-${showVersionsOnly ? 'list' : 'upload'}`]; + } +} diff --git a/lib/content-services/src/lib/new-version-uploader/public-api.ts b/lib/content-services/src/lib/new-version-uploader/public-api.ts new file mode 100644 index 0000000000..6ba0d0007f --- /dev/null +++ b/lib/content-services/src/lib/new-version-uploader/public-api.ts @@ -0,0 +1,21 @@ +/*! + * @license + * Copyright 2019 Alfresco Software, Ltd. + * + * 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 './new-version-uploader.dialog'; +export * from './new-version-uploader.module'; +export * from './new-version-uploader.service'; +export * from './models'; diff --git a/lib/content-services/src/public-api.ts b/lib/content-services/src/public-api.ts index 04291a44c1..a0ddc7abe5 100644 --- a/lib/content-services/src/public-api.ts +++ b/lib/content-services/src/public-api.ts @@ -35,5 +35,6 @@ export * from './lib/tree-view/index'; export * from './lib/group/index'; export * from './lib/aspect-list/index'; export * from './lib/content-type/index'; +export * from './lib/new-version-uploader'; export * from './lib/content.module'; diff --git a/lib/process-services-cloud/src/lib/form/components/widgets/attach-file/attach-file-cloud-widget.component.html b/lib/process-services-cloud/src/lib/form/components/widgets/attach-file/attach-file-cloud-widget.component.html index 00c714492f..c95f6db234 100644 --- a/lib/process-services-cloud/src/lib/form/components/widgets/attach-file/attach-file-cloud-widget.component.html +++ b/lib/process-services-cloud/src/lib/form/components/widgets/attach-file/attach-file-cloud-widget.component.html @@ -26,6 +26,7 @@ (rowClick)="onRowClicked($event)" (attachFileClick)="onAttachFileClicked($event)" (downloadFile)="downloadContent($event)" + (uploadNewFileVersion)="onUploadNewFileVersion($event)" (contentModelFileHandler)="contentModelFormFileHandler($event)" (removeAttachFile)="onRemoveAttachFile($event)" > diff --git a/lib/process-services-cloud/src/lib/form/components/widgets/attach-file/attach-file-cloud-widget.component.spec.ts b/lib/process-services-cloud/src/lib/form/components/widgets/attach-file/attach-file-cloud-widget.component.spec.ts index e2bc6d734c..5c94784cab 100644 --- a/lib/process-services-cloud/src/lib/form/components/widgets/attach-file/attach-file-cloud-widget.component.spec.ts +++ b/lib/process-services-cloud/src/lib/form/components/widgets/attach-file/attach-file-cloud-widget.component.spec.ts @@ -29,7 +29,8 @@ import { DownloadService, AppConfigService, UploadWidgetContentLinkModel, - LocalizedDatePipe + LocalizedDatePipe, + NotificationService } from '@alfresco/adf-core'; import { allSourceParams, @@ -60,11 +61,12 @@ import { } from '../../../mocks/attach-file-cloud-widget.mock'; import { ProcessServiceCloudTestingModule } from '../../../../testing/process-service-cloud.testing.module'; import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; -import { ContentModule, ContentNodeSelectorPanelService } from '@alfresco/adf-content-services'; +import { ContentModule, ContentNodeSelectorPanelService, NewVersionUploaderDataAction, NewVersionUploaderService } from '@alfresco/adf-content-services'; import { By } from '@angular/platform-browser'; -import { of } from 'rxjs'; +import { of, throwError } from 'rxjs'; import { FormCloudModule } from '../../../form-cloud.module'; import { TranslateModule } from '@ngx-translate/core'; +import { mockNode } from 'content-services/src/lib/mock'; describe('AttachFileCloudWidgetComponent', () => { let widget: AttachFileCloudWidgetComponent; @@ -82,6 +84,8 @@ describe('AttachFileCloudWidgetComponent', () => { let contentClickedSpy: jasmine.Spy; let openUploadFileDialogSpy: jasmine.Spy; let localizedDataPipe: LocalizedDatePipe; + let newVersionUploaderService: NewVersionUploaderService; + let notificationService: NotificationService; const createUploadWidgetField = (form: FormModel, fieldId: string, value?: any, params?: any, multiple?: boolean, name?: string, readOnly?: boolean) => { widget.field = new FormFieldModel(form, { @@ -204,7 +208,7 @@ describe('AttachFileCloudWidgetComponent', () => { describe('when is required', () => { it('should be able to display label with asterisk', async () => { - widget.field = new FormFieldModel( new FormModel({ taskId: '' }), { + widget.field = new FormFieldModel(new FormModel({ taskId: '' }), { type: FormFieldTypes.UPLOAD, required: true }); @@ -298,7 +302,7 @@ describe('AttachFileCloudWidgetComponent', () => { it('should be able to use mapped string variable value if the destinationFolderPath set to string type variable', async () => { const getNodeIdFromPathSpy = spyOn(contentCloudNodeSelectorService, 'getNodeIdFromPath').and.returnValue(mockNodeIdBasedOnStringVariableValue); - const form = new FormModel({ formVariables, processVariables}); + const form = new FormModel({ formVariables, processVariables }); createUploadWidgetField(form, 'attach-file-alfresco', [], mockAllFileSourceWithStringVariablePathType); fixture.detectChanges(); await fixture.whenStable(); @@ -383,9 +387,9 @@ describe('AttachFileCloudWidgetComponent', () => { appConfigService.config = Object.assign(appConfigService.config, { 'alfresco-deployed-apps': [ { - name: 'fakeapp' + name: 'fakeapp' } - ] + ] }); expect(widget.replaceAppNameAliasWithValue('/myfiles/-appname-/folder')).toBe('/myfiles/fakeapp/folder'); @@ -504,7 +508,7 @@ describe('AttachFileCloudWidgetComponent', () => { describe('when a file is uploaded', () => { beforeEach(async () => { - apiServiceSpy = spyOn(widget['nodesApi'], 'getNode').and.returnValue(new Promise(resolve => resolve({entry: fakeNodeWithProperties}))); + apiServiceSpy = spyOn(widget['nodesApi'], 'getNode').and.returnValue(new Promise(resolve => resolve({ entry: fakeNodeWithProperties }))); spyOn(contentCloudNodeSelectorService, 'getNodeIdFromPath').and.returnValue(new Promise(resolve => resolve('fake-properties'))); openUploadFileDialogSpy.and.returnValue(of([fakeNodeWithProperties])); widget.field = new FormFieldModel(new FormModel(), { @@ -846,4 +850,48 @@ describe('AttachFileCloudWidgetComponent', () => { expect(getProcessVariableValueSpy).not.toHaveBeenCalled(); }); }); + + describe('onUploadNewFileVersion', () => { + let spyOnOpenUploadNewVersionDialog: jasmine.Spy; + let spyOnReplaceOldFileVersionWithNew: jasmine.Spy; + let spyOnShowError: jasmine.Spy; + + beforeEach(() => { + notificationService = TestBed.inject(NotificationService); + newVersionUploaderService = TestBed.inject(NewVersionUploaderService); + spyOnOpenUploadNewVersionDialog = spyOn(newVersionUploaderService, 'openUploadNewVersionDialog') + .and.returnValue(of({ action: NewVersionUploaderDataAction.refresh })); + spyOnReplaceOldFileVersionWithNew = spyOn(widget, 'replaceOldFileVersionWithNew'); + spyOnShowError = spyOn(notificationService, 'showError'); + }); + + it('Should open new version uploader dialog', async () => { + await fixture.whenStable(); + widget.onUploadNewFileVersion(mockNode); + expect(spyOnOpenUploadNewVersionDialog).toHaveBeenCalledWith(mockNode); + }); + + it('Should not replace old file version with the new one if dialog returned action is not upload', async () => { + await fixture.whenStable(); + widget.onUploadNewFileVersion(mockNode); + expect(spyOnReplaceOldFileVersionWithNew).not.toHaveBeenCalled(); + }); + + it('Should replace old file version with the new one if dialog returned action is upload', async () => { + spyOnOpenUploadNewVersionDialog.and.returnValue(of({ action: NewVersionUploaderDataAction.upload })); + await fixture.whenStable(); + widget.onUploadNewFileVersion(mockNode); + expect(spyOnReplaceOldFileVersionWithNew).toHaveBeenCalledTimes(1); + }); + + it('Should show notification error if new version uploader dialog return error', async () => { + const mockError = {value: 'Upload error'}; + spyOnOpenUploadNewVersionDialog.and.returnValue(throwError(mockError)); + await fixture.whenStable(); + widget.onUploadNewFileVersion(mockNode); + expect(spyOnReplaceOldFileVersionWithNew).not.toHaveBeenCalled(); + expect(spyOnShowError).toHaveBeenCalledWith(mockError.value); + }); + + }); }); diff --git a/lib/process-services-cloud/src/lib/form/components/widgets/attach-file/attach-file-cloud-widget.component.ts b/lib/process-services-cloud/src/lib/form/components/widgets/attach-file/attach-file-cloud-widget.component.ts index 6f32e4f4ef..323e2da8e7 100644 --- a/lib/process-services-cloud/src/lib/form/components/widgets/attach-file/attach-file-cloud-widget.component.ts +++ b/lib/process-services-cloud/src/lib/form/components/widgets/attach-file/attach-file-cloud-widget.component.ts @@ -35,7 +35,7 @@ import { ContentCloudNodeSelectorService } from '../../../services/content-cloud import { ProcessCloudContentService } from '../../../services/process-cloud-content.service'; import { UploadCloudWidgetComponent } from './upload-cloud.widget'; import { DestinationFolderPathModel, DestinationFolderPathType } from '../../../models/form-cloud-representation.model'; -import { ContentNodeSelectorPanelService } from '@alfresco/adf-content-services'; +import { ContentNodeSelectorPanelService, NewVersionUploaderData, NewVersionUploaderDataAction, NewVersionUploaderDialogData, NewVersionUploaderService, VersionManagerUploadData } from '@alfresco/adf-content-services'; export const RETRIEVE_METADATA_OPTION = 'retrieveMetadata'; export const ALIAS_ROOT_FOLDER = '-root-'; @@ -81,7 +81,8 @@ export class AttachFileCloudWidgetComponent extends UploadCloudWidgetComponent i private contentNodeSelectorService: ContentCloudNodeSelectorService, private appConfigService: AppConfigService, private apiService: AlfrescoApiService, - private contentNodeSelectorPanelService: ContentNodeSelectorPanelService + private contentNodeSelectorPanelService: ContentNodeSelectorPanelService, + private newVersionUploaderService: NewVersionUploaderService ) { super(formService, thumbnails, processCloudContentService, notificationService, logger); } @@ -211,6 +212,16 @@ export class AttachFileCloudWidgetComponent extends UploadCloudWidgetComponent i this.processCloudContentService.downloadFile(file.id); } + onUploadNewFileVersion(node: NewVersionUploaderDialogData): void { + this.newVersionUploaderService.openUploadNewVersionDialog(node).subscribe((newVersionUploaderData: NewVersionUploaderData) => { + if (newVersionUploaderData.action === NewVersionUploaderDataAction.upload) { + this.replaceOldFileVersionWithNew(newVersionUploaderData as VersionManagerUploadData); + } + }, + error => this.notificationService.showError(error.value) + ); + } + onAttachFileClicked(nodeSelector: any) { nodeSelector.nodeId = nodeSelector.id; this.fileClicked(new ContentLinkModel(nodeSelector)); diff --git a/lib/process-services-cloud/src/lib/form/components/widgets/attach-file/file-properties-table-cloud.component.html b/lib/process-services-cloud/src/lib/form/components/widgets/attach-file/file-properties-table-cloud.component.html index 7d0514ebee..dadb7faace 100644 --- a/lib/process-services-cloud/src/lib/form/components/widgets/attach-file/file-properties-table-cloud.component.html +++ b/lib/process-services-cloud/src/lib/form/components/widgets/attach-file/file-properties-table-cloud.component.html @@ -14,7 +14,7 @@ [alt]="mimeTypeIcon" role="button" tabindex="0" /> - + {{ 'FORM.FIELD.FILE_NAME' | translate }} @@ -22,7 +22,7 @@ (click)="onRowClicked(element)">{{element.name}} - + {{ columnName.title ? columnName.title : columnName.name | titlecase }} @@ -31,7 +31,7 @@ (click)="onRowClicked(row)">{{ getColumnValue(row, columnName) }} - + @@ -62,11 +62,19 @@ highlight_off {{ 'FORM.FIELD.REMOVE_FILE' | translate }} +
+ +
- + diff --git a/lib/process-services-cloud/src/lib/form/components/widgets/attach-file/file-properties-table-cloud.component.ts b/lib/process-services-cloud/src/lib/form/components/widgets/attach-file/file-properties-table-cloud.component.ts index 4fba9f160f..f0a64deb11 100644 --- a/lib/process-services-cloud/src/lib/form/components/widgets/attach-file/file-properties-table-cloud.component.ts +++ b/lib/process-services-cloud/src/lib/form/components/widgets/attach-file/file-properties-table-cloud.component.ts @@ -20,6 +20,7 @@ import { Component, EventEmitter, Input, Output } from '@angular/core'; import { LocalizedDatePipe, ThumbnailService } from '@alfresco/adf-core'; import { Node } from '@alfresco/js-api'; +import { NewVersionUploaderDialogData } from '@alfresco/adf-content-services'; export const RETRIEVE_METADATA_OPTION = 'retrieveMetadata'; @@ -56,6 +57,9 @@ export class FilePropertiesTableCloudComponent { @Output() downloadFile: EventEmitter = new EventEmitter(); + @Output() + uploadNewFileVersion: EventEmitter = new EventEmitter(); + @Output() contentModelFileHandler: EventEmitter = new EventEmitter(); @@ -76,6 +80,14 @@ export class FilePropertiesTableCloudComponent { this.downloadFile.emit(file); } + onUploadNewFileVersion(customEvent: any, node: Node){ + const newVersionUploaderDialogData: NewVersionUploaderDialogData = { + file: customEvent.detail.files[0].file, + node + }; + this.uploadNewFileVersion.emit(newVersionUploaderDialogData); + } + contentModelFormFileHandler(file?: any) { this.contentModelFileHandler.emit(file); } diff --git a/lib/process-services-cloud/src/lib/form/components/widgets/attach-file/upload-cloud.widget.ts b/lib/process-services-cloud/src/lib/form/components/widgets/attach-file/upload-cloud.widget.ts index d1de313e60..6bfb5ba066 100644 --- a/lib/process-services-cloud/src/lib/form/components/widgets/attach-file/upload-cloud.widget.ts +++ b/lib/process-services-cloud/src/lib/form/components/widgets/attach-file/upload-cloud.widget.ts @@ -24,6 +24,7 @@ import { mergeMap } from 'rxjs/operators'; import { WidgetComponent, LogService, FormService, ThumbnailService, NotificationService } from '@alfresco/adf-core'; import { ProcessCloudContentService } from '../../../services/process-cloud-content.service'; import { FileSourceTypes, DestinationFolderPathType } from '../../../models/form-cloud-representation.model'; +import { VersionManagerUploadData } from '@alfresco/adf-content-services'; @Component({ selector: 'upload-cloud-widget', @@ -78,6 +79,13 @@ export class UploadCloudWidgetComponent extends WidgetComponent implements OnIni } } + replaceOldFileVersionWithNew(versionManagerData: VersionManagerUploadData) { + const currentUploadedFileIndex = this.uploadedFiles.findIndex(file => file.name === versionManagerData.currentVersion.name); + this.uploadedFiles[currentUploadedFileIndex] = { ...versionManagerData.newVersion.value.entry}; + this.field.value = [...this.uploadedFiles]; + this.field.form.values[this.field.id] = [...this.uploadedFiles]; + } + onFileChanged(event: any) { const files: File[] = []; const filesSaved: Node[] = []; diff --git a/lib/process-services-cloud/src/lib/form/form-cloud.module.ts b/lib/process-services-cloud/src/lib/form/form-cloud.module.ts index 6dfc1bed6f..69f7248cfe 100644 --- a/lib/process-services-cloud/src/lib/form/form-cloud.module.ts +++ b/lib/process-services-cloud/src/lib/form/form-cloud.module.ts @@ -24,7 +24,7 @@ import { MaterialModule } from '../material.module'; import { FormCloudComponent } from './components/form-cloud.component'; import { FormDefinitionSelectorCloudComponent } from './components/form-definition-selector-cloud.component'; import { FormCustomOutcomesComponent } from './components/form-cloud-custom-outcomes.component'; -import { ContentMetadataModule, ContentNodeSelectorModule } from '@alfresco/adf-content-services'; +import { ContentMetadataModule, ContentNodeSelectorModule, UploadModule } from '@alfresco/adf-content-services'; import { DateCloudWidgetComponent } from './components/widgets/date/date-cloud.widget'; import { DropdownCloudWidgetComponent } from './components/widgets/dropdown/dropdown-cloud.widget'; @@ -51,7 +51,8 @@ import { FilePropertiesTableCloudComponent } from './components/widgets/attach-f ContentNodeSelectorModule, PeopleCloudModule, GroupCloudModule, - ContentMetadataModule + ContentMetadataModule, + UploadModule ], declarations: [ FormCloudComponent,