From f59abb4228a87d11453265e93e23101fc355efaa Mon Sep 17 00:00:00 2001 From: Urse Daniel Date: Wed, 5 Aug 2020 16:15:20 +0300 Subject: [PATCH] [ACA-100] View a previous version (#1562) * added a feature to view a previous node version * added latest versions * added single selection to match the latest ADF change * hide version list dialog when opening the viewer * pass versionId to adf-viewer * add view versions rendition routes for: shared, recent files, favorites, search. * added functionality to download a previous version of a node * small fix - no need to clear the store * remove unused import * removed changes * Delete package-lock.json * revert * update to last dependencies * ACA-3601: Add e2e tests for Preview of a file's previous version * Address comments * ACA-3601: Make sub-tests execute independently * update dependencies * ACA-3601: Add licenses * Add type * ACA-3601: Refactor execution Co-authored-by: kristian Co-authored-by: Denys Vuika --- e2e/protractor.excludes.json | 17 +- e2e/suites/actions/version-actions.test.ts | 247 ++++++++++++++++++ package-lock.json | 44 ++-- package.json | 14 +- .../src/lib/services/content-api.service.ts | 4 + .../store/src/actions/app.actions.ts | 9 +- .../store/src/actions/viewer.actions.ts | 7 + .../store/src/selectors/app.selectors.ts | 1 + .../aca-shared/store/src/states/app.state.ts | 3 +- .../version-manage/version-manager.ts | 40 +++ .../src/components/viewer/viewer.ts | 14 +- src/app/app.routes.ts | 78 ++++++ .../components/viewer/viewer.component.html | 1 + src/app/components/viewer/viewer.component.ts | 22 +- .../node-versions/node-versions.dialog.html | 7 +- .../node-versions.dialog.spec.ts | 15 +- .../node-versions/node-versions.dialog.ts | 18 +- src/app/services/node-actions.service.ts | 1 + src/app/services/node-template.service.ts | 1 + src/app/store/effects/download.effects.ts | 21 +- src/app/store/effects/viewer.effects.ts | 37 ++- src/app/store/initial-state.ts | 1 + src/app/store/reducers/app.reducer.ts | 12 +- 23 files changed, 560 insertions(+), 54 deletions(-) create mode 100644 e2e/suites/actions/version-actions.test.ts create mode 100644 projects/aca-testing-shared/src/components/version-manage/version-manager.ts diff --git a/e2e/protractor.excludes.json b/e2e/protractor.excludes.json index cef11e22d..fea8b6732 100644 --- a/e2e/protractor.excludes.json +++ b/e2e/protractor.excludes.json @@ -1,4 +1,19 @@ { "C280632": "https://issues.alfresco.com/jira/browse/ACA-3805", - "C297628": "https://issues.alfresco.com/jira/browse/ACA-3805" + "C297628": "https://issues.alfresco.com/jira/browse/ACA-3805", + "C586766" : "Include once ACA starts using ACS 7+, https://issues.alfresco.com/jira/browse/ACA-3601", + "C586767" : "Include once ACA starts using ACS 7+, https://issues.alfresco.com/jira/browse/ACA-3601", + "C586768" : "Include once ACA starts using ACS 7+, https://issues.alfresco.com/jira/browse/ACA-3601", + "C586769" : "Include once ACA starts using ACS 7+, https://issues.alfresco.com/jira/browse/ACA-3601", + "C586770" : "Include once ACA starts using ACS 7+, https://issues.alfresco.com/jira/browse/ACA-3601", + "C586771" : "Include once ACA starts using ACS 7+, https://issues.alfresco.com/jira/browse/ACA-3601", + "C586772" : "Include once ACA starts using ACS 7+, https://issues.alfresco.com/jira/browse/ACA-3601", + "C586773" : "Include once ACA starts using ACS 7+, https://issues.alfresco.com/jira/browse/ACA-3601", + "C586774" : "Include once ACA starts using ACS 7+, https://issues.alfresco.com/jira/browse/ACA-3601", + "C586776" : "Include once ACA starts using ACS 7+, https://issues.alfresco.com/jira/browse/ACA-3601", + "C586777" : "Include once ACA starts using ACS 7+, https://issues.alfresco.com/jira/browse/ACA-3601", + "C586778" : "Include once ACA starts using ACS 7+, https://issues.alfresco.com/jira/browse/ACA-3601", + "C586779" : "Include once ACA starts using ACS 7+, https://issues.alfresco.com/jira/browse/ACA-3601", + "C586780" : "Include once ACA starts using ACS 7+, https://issues.alfresco.com/jira/browse/ACA-3601", + "C586781" : "Include once ACA starts using ACS 7+, https://issues.alfresco.com/jira/browse/ACA-3601" } diff --git a/e2e/suites/actions/version-actions.test.ts b/e2e/suites/actions/version-actions.test.ts new file mode 100644 index 000000000..4ea3b6372 --- /dev/null +++ b/e2e/suites/actions/version-actions.test.ts @@ -0,0 +1,247 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2020 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { LoginPage, BrowsingPage, FILES, RepoClient, Utils, UploadNewVersionDialog } from '@alfresco/aca-testing-shared'; +import { VersionManagePage } from '../../../projects/aca-testing-shared/src/components/version-manage/version-manager'; +import { Viewer } from '../../../projects/aca-testing-shared/src/components'; +import { browser } from 'protractor'; + +describe('Version component actions', () => { + const versionManagePage = new VersionManagePage(); + const viewerPage = new Viewer(); + + const username = `user-${Utils.random()}`; + + let fileId: string; + + const filesToUpload = [FILES.pdfFile, FILES.docxFile, FILES.xlsxFile, FILES.jpgFile, FILES.docxFile2]; + + const apis = { + admin: new RepoClient(), + user: new RepoClient(username, username) + }; + + const loginPage = new LoginPage(); + const page = new BrowsingPage(); + const { dataTable, toolbar } = page; + const uploadNewVersionDialog = new UploadNewVersionDialog(); + const { searchInput } = page.header; + + beforeAll(async (done) => { + await apis.admin.people.createUser({ username }); + fileId = (await apis.user.upload.uploadFile(filesToUpload[0])).entry.id; + await apis.user.shared.shareFilesByIds([fileId]); + await loginPage.loginWith(username); + + for (let i = 0; i < filesToUpload.length - 1; i++) { + await dataTable.selectItem(filesToUpload[i]); + await toolbar.clickMoreActionsUploadNewVersion(); + await Utils.uploadFileNewVersion(filesToUpload[i + 1]); + + await page.waitForDialog(); + + await uploadNewVersionDialog.majorOption.click(); + await uploadNewVersionDialog.enterDescription('new major version description'); + await uploadNewVersionDialog.uploadButton.click(); + await uploadNewVersionDialog.waitForDialogToClose(); + } + done(); + }); + + afterAll(async (done) => { + await apis.user.nodes.deleteNodeById(fileId); + done(); + }); + + describe('on Personal Files', () => { + beforeAll(async (done) => { + await page.clickPersonalFilesAndWait(); + done(); + }); + + beforeEach(async (done) => { + await dataTable.selectItem(filesToUpload[4]); + await toolbar.clickMoreActionsManageVersions(); + await versionManagePage.viewFileVersion('1.0'); + done(); + }); + + afterEach(async (done) => { + await viewerPage.clickCloseButton(); + done(); + }); + + it('[C586766] Should be possible to view a previous document version', async () => { + expect(await browser.getCurrentUrl()).toContain('1.0'); + }); + + it('[C586767] Previous document version title should be the same in Preview mode as the Uploaded File', async () => { + expect(await viewerPage.getFileTitle()).toContain(filesToUpload[0]); + }); + + it('[C586768] Should be possible to download a previous document version', async () => { + await viewerPage.clickDownloadButton(); + + expect(await Utils.fileExistsOnOS(filesToUpload[0])).toBe(true, 'File not found in download location'); + }); + }); + + describe('on Shared Files', () => { + beforeAll(async (done) => { + await page.clickSharedFilesAndWait(); + done(); + }); + + beforeEach(async (done) => { + await dataTable.selectItem(filesToUpload[4]); + await toolbar.clickMoreActionsManageVersions(); + await versionManagePage.viewFileVersion('2.0'); + done(); + }); + + afterEach(async (done) => { + await viewerPage.clickCloseButton(); + done(); + }); + + it('[C586776] Should be possible to view a previous document version', async () => { + expect(await browser.getCurrentUrl()).toContain('2.0'); + }); + + it('[C586777] Previous document version title should be the same in Preview mode as the Uploaded File', async () => { + expect(await viewerPage.getFileTitle()).toContain(filesToUpload[1]); + }); + + it('[C586778] Should be possible to download a previous document version', async () => { + await viewerPage.clickDownloadButton(); + + expect(await Utils.fileExistsOnOS(filesToUpload[1])).toBe(true, 'File not found in download location'); + }); + }); + + describe('on Recent Files', () => { + beforeAll(async (done) => { + await page.clickRecentFilesAndWait(); + done(); + }); + + beforeEach(async (done) => { + await dataTable.selectItem(filesToUpload[4]); + await toolbar.clickMoreActionsManageVersions(); + await versionManagePage.viewFileVersion('3.0'); + done(); + }); + + afterEach(async (done) => { + await viewerPage.clickCloseButton(); + done(); + }); + + it('[C586769] Should be possible to view a previous document version', async () => { + expect(await browser.getCurrentUrl()).toContain('3.0'); + }); + + it('[C586770] Previous document version title should be the same in Preview mode as the Uploaded File', async () => { + expect(await viewerPage.getFileTitle()).toContain(filesToUpload[2]); + }); + + it('[C586771] Should be possible to download a previous document version', async () => { + await viewerPage.clickDownloadButton(); + + expect(await Utils.fileExistsOnOS(filesToUpload[2])).toBe(true, 'File not found in download location'); + }); + }); + + describe('on Favorite Files', () => { + beforeAll(async (done) => { + await apis.user.favorites.addFavoritesByIds('file', [fileId]); + await apis.user.favorites.waitForApi({ expect: 1 }); + await page.clickFavoritesAndWait(); + done(); + }); + + beforeEach(async (done) => { + await dataTable.selectItem(filesToUpload[4]); + await toolbar.clickMoreActionsManageVersions(); + await versionManagePage.viewFileVersion('4.0'); + done(); + }); + + afterEach(async (done) => { + await viewerPage.clickCloseButton(); + done(); + }); + + it('[C586772] Should be possible to view a previous document version', async () => { + expect(await browser.getCurrentUrl()).toContain('4.0'); + }); + + it('[C586773] Previous document version title should be the same in Preview mode as the Uploaded File', async () => { + expect(await viewerPage.getFileTitle()).toContain(filesToUpload[3]); + }); + + it('[C586774] Should be possible to download a previous document version', async () => { + await viewerPage.clickDownloadButton(); + + expect(await Utils.fileExistsOnOS(filesToUpload[3])).toBe(true, 'File not found in download location'); + }); + }); + + describe('on Search Results', () => { + beforeAll(async (done) => { + await searchInput.clickSearchButton(); + await searchInput.checkFilesAndFolders(); + await searchInput.searchFor(filesToUpload[4]); + await dataTable.waitForBody(); + done(); + }); + + beforeEach(async (done) => { + await dataTable.selectItem(filesToUpload[4], 'Personal Files'); + await toolbar.clickMoreActionsManageVersions(); + await versionManagePage.viewFileVersion('5.0'); + done(); + }); + + afterEach(async (done) => { + await viewerPage.clickCloseButton(); + done(); + }); + + it('[C586779] Should be possible to view a previous document version', async () => { + expect(await browser.getCurrentUrl()).toContain('5.0'); + }); + + it('[C586780] Previous document version title should be the same in Preview mode as the Uploaded File', async () => { + expect(await viewerPage.getFileTitle()).toContain(filesToUpload[4]); + }); + + it('[C586781] Should be possible to download a previous document version', async () => { + await viewerPage.clickDownloadButton(); + + expect(await Utils.fileExistsOnOS(filesToUpload[4])).toBe(true, 'File not found in download location'); + }); + }); +}); diff --git a/package-lock.json b/package-lock.json index ce57ec9f7..80e0d10a1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,12 +5,12 @@ "requires": true, "dependencies": { "@alfresco/adf-cli": { - "version": "3.10.0-52579172585e55e74638b199a122c61141647bfe", - "resolved": "https://registry.npmjs.org/@alfresco/adf-cli/-/adf-cli-3.10.0-52579172585e55e74638b199a122c61141647bfe.tgz", - "integrity": "sha512-fHCWd6pSYXmxLcEjiuZyBGqx5crlc/AFSiV0k97S+M3hzHKLDAMSCJIK5CTZhi8H4BXoPdpf5hJWoIcMJ37iRQ==", + "version": "3.10.0-6f5ff737ddd734c02a38aef7efadf13250387fa6", + "resolved": "https://registry.npmjs.org/@alfresco/adf-cli/-/adf-cli-3.10.0-6f5ff737ddd734c02a38aef7efadf13250387fa6.tgz", + "integrity": "sha512-xpAHyi1BnFtNtUxGTHJ+iaBehqn5K+wzbJA4mIwaCigJ0kVwUlZsl0o/RNVrv5MBcEKfXXOqw+DdMwO/zf33dw==", "dev": true, "requires": { - "@alfresco/js-api": "3.10.0-11dab5764e0caeb5387c911e5758e1d9b2c1caf3", + "@alfresco/js-api": "3.10.0-8e063ceb7b7d71ab3001b50fc8d0a79a1a8797c5", "commander": "^4.0.0", "ejs": "^2.6.1", "license-checker": "^25.0.1", @@ -21,9 +21,9 @@ }, "dependencies": { "@alfresco/js-api": { - "version": "3.10.0-11dab5764e0caeb5387c911e5758e1d9b2c1caf3", - "resolved": "https://registry.npmjs.org/@alfresco/js-api/-/js-api-3.10.0-11dab5764e0caeb5387c911e5758e1d9b2c1caf3.tgz", - "integrity": "sha512-ijowA4WVFjC5joRj2KmCd3TbN1ccQ3onFn3vMhag94FNVybV9ZHHFjbMcLkOVNfFfgkjyRRwafJsqDnyzFBIpQ==", + "version": "3.10.0-8e063ceb7b7d71ab3001b50fc8d0a79a1a8797c5", + "resolved": "https://registry.npmjs.org/@alfresco/js-api/-/js-api-3.10.0-8e063ceb7b7d71ab3001b50fc8d0a79a1a8797c5.tgz", + "integrity": "sha512-2ogpnobx5TwWmZy7FO4L5Z1ZE1rVWDxtiRF6Jl1prmHML8GWY3GKV7uTOEQvDX569RRYo/fzsdiwExYipNr1QQ==", "dev": true, "requires": { "event-emitter": "^0.3.5", @@ -40,42 +40,42 @@ } }, "@alfresco/adf-content-services": { - "version": "3.10.0-44c5472fa25d6c22226c8a86a332c3ea5bcfa359", - "resolved": "https://registry.npmjs.org/@alfresco/adf-content-services/-/adf-content-services-3.10.0-44c5472fa25d6c22226c8a86a332c3ea5bcfa359.tgz", - "integrity": "sha512-WAhp5sLrcZ6luS+06jhOowIKGThFaoNauMJZd7dppHzQMLCFY99cjOf6RzMRyl8c7KAgSFQuGMVm8SDdIZS17g==", + "version": "3.10.0-0afbe367878b8a9ae17cad4e839a02a8c6728d8d", + "resolved": "https://registry.npmjs.org/@alfresco/adf-content-services/-/adf-content-services-3.10.0-0afbe367878b8a9ae17cad4e839a02a8c6728d8d.tgz", + "integrity": "sha512-3QSUoV1wya4wTGeIPaMnIvERMm6EmXvD/PVWkU4NZc9H+c4pEWDU2gaQisFoAA/OTWQnliBA7lt602lUWOtWlg==", "requires": { "tslib": "^2.0.0" } }, "@alfresco/adf-core": { - "version": "3.10.0-44c5472fa25d6c22226c8a86a332c3ea5bcfa359", - "resolved": "https://registry.npmjs.org/@alfresco/adf-core/-/adf-core-3.10.0-44c5472fa25d6c22226c8a86a332c3ea5bcfa359.tgz", - "integrity": "sha512-oS18BAV9V2YlH1Lb8UXeb8A1u1qGTtjrcUJmhMgWpKRwntd4nuU25E7JXn8LFH7uBNYCYzIkXDtJ4btqVVQlbA==", + "version": "3.10.0-0afbe367878b8a9ae17cad4e839a02a8c6728d8d", + "resolved": "https://registry.npmjs.org/@alfresco/adf-core/-/adf-core-3.10.0-0afbe367878b8a9ae17cad4e839a02a8c6728d8d.tgz", + "integrity": "sha512-gKq7nwtck2tPywARF/TL/KqJKfJ7+8cwaGDLU7uri5PbbLJ1l7Abew2rhebckMa6kMd+FUpHu4qJDkLELAfEng==", "requires": { "tslib": "^2.0.0" } }, "@alfresco/adf-extensions": { - "version": "3.10.0-52579172585e55e74638b199a122c61141647bfe", - "resolved": "https://registry.npmjs.org/@alfresco/adf-extensions/-/adf-extensions-3.10.0-52579172585e55e74638b199a122c61141647bfe.tgz", - "integrity": "sha512-ZUAcOHwwIWeNVaESGvSUoWjhAfDfBfQP66Y7Ys5Pp1qdr1R6/TaslGm8wby+DxziNb/oNUdJlW8rpig9Rh/iQA==", + "version": "3.10.0-0afbe367878b8a9ae17cad4e839a02a8c6728d8d", + "resolved": "https://registry.npmjs.org/@alfresco/adf-extensions/-/adf-extensions-3.10.0-0afbe367878b8a9ae17cad4e839a02a8c6728d8d.tgz", + "integrity": "sha512-W9W3CTsUcwcuXd0ZgPpOiOYrnMhonqlFb1zxqlgTLs5c0KRovsvtyDVEGN3Uv4Fmllc484HfoJVE/6qvvZvBVw==", "requires": { "tslib": "^2.0.0" } }, "@alfresco/adf-testing": { - "version": "3.10.0-90820a08dfb7de57e3b071f36ce8713bea1178c9", - "resolved": "https://registry.npmjs.org/@alfresco/adf-testing/-/adf-testing-3.10.0-90820a08dfb7de57e3b071f36ce8713bea1178c9.tgz", - "integrity": "sha512-BClCVDn18ml1c7BAcbP0YArAUBfTzxNZLxMO534jPyG0+yuvRtmnn2yoBToScDtDved6H1SfhHaTejrZXKLLRg==", + "version": "3.10.0-6f5ff737ddd734c02a38aef7efadf13250387fa6", + "resolved": "https://registry.npmjs.org/@alfresco/adf-testing/-/adf-testing-3.10.0-6f5ff737ddd734c02a38aef7efadf13250387fa6.tgz", + "integrity": "sha512-uxWkZWReVavmnv3sFtheDIt1/H4Wv3PZkbSLqQMeq+neWxa1qWJ5BuYaI/zVGLc2dqU0mqVlZupNUnGFbR1bVQ==", "dev": true, "requires": { "tslib": "^2.0.0" } }, "@alfresco/js-api": { - "version": "3.10.0-c1ad5d79a257f31a52ffd327022458da7926c211", - "resolved": "https://registry.npmjs.org/@alfresco/js-api/-/js-api-3.10.0-c1ad5d79a257f31a52ffd327022458da7926c211.tgz", - "integrity": "sha512-uGwCzNddEJYv9ZDME4cLqxc7Vam6CqK9ueMSz7pacIT1wHqoc5Y5XnON0zziL5msvRxVq1WMKu76QrF53OmJ3w==", + "version": "3.10.0-42769ecb372a1b7f6841e1971bb63f9f0fc78753", + "resolved": "https://registry.npmjs.org/@alfresco/js-api/-/js-api-3.10.0-42769ecb372a1b7f6841e1971bb63f9f0fc78753.tgz", + "integrity": "sha512-k8xByKGltU3T5othhHjrGQ0kLRQ8zTkiMr+VYb2s2C4aSrq/nMoTbJc47F/f6EU549XmvZuNGCQGTUSQlpo3bA==", "requires": { "event-emitter": "^0.3.5", "minimatch": "3.0.4", diff --git a/package.json b/package.json index 18ce3d0ce..d11a45825 100644 --- a/package.json +++ b/package.json @@ -32,10 +32,10 @@ }, "private": true, "dependencies": { - "@alfresco/adf-content-services": "3.10.0-44c5472fa25d6c22226c8a86a332c3ea5bcfa359", - "@alfresco/adf-core": "3.10.0-44c5472fa25d6c22226c8a86a332c3ea5bcfa359", - "@alfresco/adf-extensions": "3.10.0-52579172585e55e74638b199a122c61141647bfe", - "@alfresco/js-api": "3.10.0-c1ad5d79a257f31a52ffd327022458da7926c211", + "@alfresco/adf-content-services": "3.10.0-0afbe367878b8a9ae17cad4e839a02a8c6728d8d", + "@alfresco/adf-core": "3.10.0-0afbe367878b8a9ae17cad4e839a02a8c6728d8d", + "@alfresco/adf-extensions": "3.10.0-0afbe367878b8a9ae17cad4e839a02a8c6728d8d", + "@alfresco/js-api": "3.10.0-42769ecb372a1b7f6841e1971bb63f9f0fc78753", "@angular-custom-builders/lite-serve": "0.2.2", "@angular/animations": "10.0.4", "@angular/cdk": "^10.0.2", @@ -66,9 +66,9 @@ "zone.js": "~0.10.2" }, "devDependencies": { - "@alfresco/adf-cli": "3.10.0-52579172585e55e74638b199a122c61141647bfe", - "@alfresco/adf-testing": "3.10.0-90820a08dfb7de57e3b071f36ce8713bea1178c9", - "@angular-devkit/build-angular": "~0.1000.5", + "@alfresco/adf-cli": "3.10.0-6f5ff737ddd734c02a38aef7efadf13250387fa6", + "@alfresco/adf-testing": "3.10.0-6f5ff737ddd734c02a38aef7efadf13250387fa6", + "@angular-devkit/build-angular": "~0.1000.4", "@angular-devkit/build-ng-packagr": "~0.1000.4", "@angular/cli": "^10.0.4", "@angular/compiler-cli": "10.0.4", diff --git a/projects/aca-shared/src/lib/services/content-api.service.ts b/projects/aca-shared/src/lib/services/content-api.service.ts index 56459c06f..a94ac108c 100644 --- a/projects/aca-shared/src/lib/services/content-api.service.ts +++ b/projects/aca-shared/src/lib/services/content-api.service.ts @@ -200,6 +200,10 @@ export class ContentApiService { return this.api.contentApi.getContentUrl(nodeId, attachment); } + getVersionContentUrl(nodeId: string, versionId: string, attachment?: boolean): string { + return this.api.contentApi.getVersionContentUrl(nodeId, versionId, attachment); + } + deleteSite(siteId?: string, opts?: { permanent?: boolean }): Observable { return from(this.api.sitesApi.deleteSite(siteId, opts)); } diff --git a/projects/aca-shared/store/src/actions/app.actions.ts b/projects/aca-shared/store/src/actions/app.actions.ts index fb95fdbbd..578904d7a 100644 --- a/projects/aca-shared/store/src/actions/app.actions.ts +++ b/projects/aca-shared/store/src/actions/app.actions.ts @@ -24,7 +24,7 @@ */ import { Action } from '@ngrx/store'; -import { Node, Person, Group, RepositoryInfo } from '@alfresco/js-api'; +import { Node, Person, Group, RepositoryInfo, VersionEntry } from '@alfresco/js-api'; import { AppState } from '../states/app.state'; export enum AppActionTypes { @@ -32,6 +32,7 @@ export enum AppActionTypes { SetInitialState = 'SET_INITIAL_STATE', SetHeaderColor = 'SET_HEADER_COLOR', SetCurrentFolder = 'SET_CURRENT_FOLDER', + SetCurrentVersion = 'SET_CURRENT_VERSION', SetCurrentUrl = 'SET_CURRENT_URL', SetUserProfile = 'SET_USER_PROFILE', SetRepositoryInfo = 'SET_REPOSITORY_INFO', @@ -69,6 +70,12 @@ export class SetCurrentFolderAction implements Action { constructor(public payload: Node) {} } +export class SetCurrentNodeVersionAction implements Action { + readonly type = AppActionTypes.SetCurrentVersion; + + constructor(public payload: VersionEntry) {} +} + export class SetCurrentUrlAction implements Action { readonly type = AppActionTypes.SetCurrentUrl; diff --git a/projects/aca-shared/store/src/actions/viewer.actions.ts b/projects/aca-shared/store/src/actions/viewer.actions.ts index 6557211bd..93b0e8d70 100644 --- a/projects/aca-shared/store/src/actions/viewer.actions.ts +++ b/projects/aca-shared/store/src/actions/viewer.actions.ts @@ -29,6 +29,7 @@ import { MinimalNodeEntity } from '@alfresco/js-api'; export enum ViewerActionTypes { ViewFile = 'VIEW_FILE', ViewNode = 'VIEW_NODE', + ViewNodeVersion = 'VIEW_NODE_VERSION', FullScreen = 'FULLSCREEN_VIEWER', ClosePreview = 'CLOSE_PREVIEW' } @@ -50,6 +51,12 @@ export class ViewNodeAction implements Action { constructor(public nodeId: string, public viewNodeExtras?: ViewNodeExtras) {} } +export class ViewNodeVersionAction implements Action { + readonly type = ViewerActionTypes.ViewNodeVersion; + + constructor(public nodeId: string, public versionId: string, public viewNodeExtras?: ViewNodeExtras) {} +} + export class FullscreenViewerAction implements Action { readonly type = ViewerActionTypes.FullScreen; diff --git a/projects/aca-shared/store/src/selectors/app.selectors.ts b/projects/aca-shared/store/src/selectors/app.selectors.ts index 43e2d1ef8..1ab73c4c0 100644 --- a/projects/aca-shared/store/src/selectors/app.selectors.ts +++ b/projects/aca-shared/store/src/selectors/app.selectors.ts @@ -35,6 +35,7 @@ export const getHeaderImagePath = createSelector(selectApp, (state) => state.hea export const getLanguagePickerState = createSelector(selectApp, (state) => state.languagePicker); export const getUserProfile = createSelector(selectApp, (state) => state.user); export const getCurrentFolder = createSelector(selectApp, (state) => state.navigation.currentFolder); +export const getCurrentVersion = createSelector(selectApp, (state) => state.currentNodeVersion); export const getAppSelection = createSelector(selectApp, (state) => state.selection); export const getSharedUrl = createSelector(selectApp, (state) => state.sharedUrl); export const getNavigationState = createSelector(selectApp, (state) => state.navigation); diff --git a/projects/aca-shared/store/src/states/app.state.ts b/projects/aca-shared/store/src/states/app.state.ts index b90144ee0..2b1711a66 100644 --- a/projects/aca-shared/store/src/states/app.state.ts +++ b/projects/aca-shared/store/src/states/app.state.ts @@ -24,7 +24,7 @@ */ import { SelectionState, ProfileState, NavigationState } from '@alfresco/adf-extensions'; -import { RepositoryInfo } from '@alfresco/js-api'; +import { RepositoryInfo, VersionEntry } from '@alfresco/js-api'; export interface AppState { appName: string; @@ -33,6 +33,7 @@ export interface AppState { headerImagePath: string; languagePicker: boolean; sharedUrl: string; + currentNodeVersion: VersionEntry; selection: SelectionState; user: ProfileState; navigation: NavigationState; diff --git a/projects/aca-testing-shared/src/components/version-manage/version-manager.ts b/projects/aca-testing-shared/src/components/version-manage/version-manager.ts new file mode 100644 index 000000000..9347146e5 --- /dev/null +++ b/projects/aca-testing-shared/src/components/version-manage/version-manager.ts @@ -0,0 +1,40 @@ +/*! + * @license + * Alfresco Example Content Application + * + * Copyright (C) 2005 - 2020 Alfresco Software Limited + * + * This file is part of the Alfresco Example Content Application. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * The Alfresco Example Content Application is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * The Alfresco Example Content Application is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ + +import { BrowserActions, BrowserVisibility } from '@alfresco/adf-testing'; +import { by, element, ElementFinder } from 'protractor'; + +export class VersionManagePage { + async clickActionButton(version: string): Promise { + await BrowserActions.click(element(by.id(`adf-version-list-action-menu-button-${version}`))); + await BrowserVisibility.waitUntilElementIsVisible(element(by.css('.cdk-overlay-container .mat-menu-content'))); + } + + async viewFileVersion(version: string): Promise { + await this.clickActionButton(version); + const viewButton: ElementFinder = element(by.id(`adf-version-list-action-view-${version}`)); + await BrowserActions.click(viewButton); + } +} diff --git a/projects/aca-testing-shared/src/components/viewer/viewer.ts b/projects/aca-testing-shared/src/components/viewer/viewer.ts index 9e1f6e27e..e8ad3766a 100755 --- a/projects/aca-testing-shared/src/components/viewer/viewer.ts +++ b/projects/aca-testing-shared/src/components/viewer/viewer.ts @@ -23,8 +23,8 @@ * along with Alfresco. If not, see . */ -import { browser } from 'protractor'; -import { Logger } from '@alfresco/adf-testing'; +import { browser, by, element, ElementFinder } from 'protractor'; +import { BrowserActions, Logger } from '@alfresco/adf-testing'; import { Component } from '../component'; import { Toolbar } from '../toolbar/toolbar'; import { waitForPresence } from '../../utilities/utils'; @@ -93,4 +93,14 @@ export class Viewer extends Component { const count = await this.pdfViewerContentPages.count(); return count > 0; } + + async clickDownloadButton(): Promise { + const downloadButton: ElementFinder = element(by.id(`app.viewer.download`)); + await BrowserActions.click(downloadButton); + } + + async clickCloseButton(): Promise { + const closeButton: ElementFinder = element(by.css('button[data-automation-id="adf-toolbar-back"]')); + await BrowserActions.click(closeButton); + } } diff --git a/src/app/app.routes.ts b/src/app/app.routes.ts index 66d4c8ebf..2c2c66f41 100644 --- a/src/app/app.routes.ts +++ b/src/app/app.routes.ts @@ -111,6 +111,19 @@ export const APP_ROUTES: Routes = [ loadChildren: () => import('./components/viewer/viewer.module').then((m) => m.AppViewerModule) } ] + }, + { + path: 'view/:nodeId/:versionId', + outlet: 'viewer', + children: [ + { + path: '', + data: { + navigateSource: 'personal-files' + }, + loadChildren: () => import('./components/viewer/viewer.module').then((m) => m.AppViewerModule) + } + ] } ] }, @@ -125,6 +138,19 @@ export const APP_ROUTES: Routes = [ sortingPreferenceKey: 'personal-files' } }, + { + path: 'view/:nodeId/:versionId', + outlet: 'viewer', + children: [ + { + path: '', + data: { + navigateSource: 'personal-files' + }, + loadChildren: () => import('./components/viewer/viewer.module').then((m) => m.AppViewerModule) + } + ] + }, // deprecated, backwards compatibility with ACA 1.8 { path: 'preview/:nodeId', @@ -283,6 +309,19 @@ export const APP_ROUTES: Routes = [ loadChildren: () => import('./components/viewer/viewer.module').then((m) => m.AppViewerModule) } ] + }, + { + path: 'view/:nodeId/:versionId', + outlet: 'viewer', + children: [ + { + path: '', + data: { + navigateSource: 'favorites' + }, + loadChildren: () => import('./components/viewer/viewer.module').then((m) => m.AppViewerModule) + } + ] } ] }, @@ -321,6 +360,19 @@ export const APP_ROUTES: Routes = [ loadChildren: () => import('./components/viewer/viewer.module').then((m) => m.AppViewerModule) } ] + }, + { + path: 'view/:nodeId/:versionId', + outlet: 'viewer', + children: [ + { + path: '', + data: { + navigateSource: 'recent-files' + }, + loadChildren: () => import('./components/viewer/viewer.module').then((m) => m.AppViewerModule) + } + ] } ] }, @@ -357,6 +409,19 @@ export const APP_ROUTES: Routes = [ loadChildren: () => import('./components/viewer/viewer.module').then((m) => m.AppViewerModule) } ] + }, + { + path: 'view/:nodeId/:versionId', + outlet: 'viewer', + children: [ + { + path: '', + data: { + navigateSource: 'shared' + }, + loadChildren: () => import('./components/viewer/viewer.module').then((m) => m.AppViewerModule) + } + ] } ], canActivateChild: [AppSharedRuleGuard], @@ -400,6 +465,19 @@ export const APP_ROUTES: Routes = [ loadChildren: () => import('./components/viewer/viewer.module').then((m) => m.AppViewerModule) } ] + }, + { + path: 'view/:nodeId/:versionId', + outlet: 'viewer', + children: [ + { + path: '', + data: { + navigateSource: 'search' + }, + loadChildren: () => import('./components/viewer/viewer.module').then((m) => m.AppViewerModule) + } + ] } ] }, diff --git a/src/app/components/viewer/viewer.component.html b/src/app/components/viewer/viewer.component.html index f725363bb..f62a0a2ff 100644 --- a/src/app/components/viewer/viewer.component.html +++ b/src/app/components/viewer/viewer.component.html @@ -6,6 +6,7 @@ [fileName]="fileName" [maxRetries]="'viewer.maxRetries' | adfAppConfig" [nodeId]="nodeId" + [versionId]="versionId" [allowNavigate]="navigateMultiple" [allowRightSidebar]="true" [allowPrint]="false" diff --git a/src/app/components/viewer/viewer.component.ts b/src/app/components/viewer/viewer.component.ts index eab553658..de51eafa5 100644 --- a/src/app/components/viewer/viewer.component.ts +++ b/src/app/components/viewer/viewer.component.ts @@ -32,10 +32,11 @@ import { ClosePreviewAction, ViewerActionTypes, ViewNodeAction, - ReloadDocumentListAction + ReloadDocumentListAction, + SetCurrentNodeVersionAction } from '@alfresco/aca-shared/store'; import { ContentActionRef, SelectionState } from '@alfresco/adf-extensions'; -import { MinimalNodeEntryEntity, SearchRequest } from '@alfresco/js-api'; +import { MinimalNodeEntryEntity, SearchRequest, VersionEntry } from '@alfresco/js-api'; import { Component, HostListener, OnDestroy, OnInit, ViewEncapsulation } from '@angular/core'; import { ActivatedRoute, Router, PRIMARY_OUTLET } from '@angular/router'; import { UserPreferencesService, ObjectUtils, UploadService, AlfrescoApiService } from '@alfresco/adf-core'; @@ -58,6 +59,7 @@ export class AppViewerComponent implements OnInit, OnDestroy { fileName: string; folderId: string = null; nodeId: string = null; + versionId: string = null; node: MinimalNodeEntryEntity; selection: SelectionState; infoDrawerOpened$: Observable; @@ -135,6 +137,14 @@ export class AppViewerComponent implements OnInit, OnDestroy { this.route.params.subscribe((params) => { this.folderId = params.folderId; const { nodeId } = params; + this.versionId = params.versionId; + if (this.versionId) { + this.apiService.versionsApi.getVersion(nodeId, this.versionId).then((version: VersionEntry) => { + if (version) { + this.store.dispatch(new SetCurrentNodeVersionAction(version)); + } + }); + } if (nodeId) { this.displayNode(nodeId); } @@ -151,9 +161,10 @@ export class AppViewerComponent implements OnInit, OnDestroy { } } - this.actions$ - .pipe(ofType(ViewerActionTypes.ClosePreview), takeUntil(this.onDestroy$)) - .subscribe(() => this.navigateToFileLocation()); + this.actions$.pipe(ofType(ViewerActionTypes.ClosePreview), takeUntil(this.onDestroy$)).subscribe(() => { + this.store.dispatch(new SetCurrentNodeVersionAction(null)); + this.navigateToFileLocation(); + }); this.content.nodesDeleted.pipe(takeUntil(this.onDestroy$)).subscribe(() => this.navigateToFileLocation()); @@ -173,6 +184,7 @@ export class AppViewerComponent implements OnInit, OnDestroy { } ngOnDestroy() { + this.store.dispatch(new SetCurrentNodeVersionAction(null)); this.onDestroy$.next(true); this.onDestroy$.complete(); } diff --git a/src/app/dialogs/node-versions/node-versions.dialog.html b/src/app/dialogs/node-versions/node-versions.dialog.html index 52a4823b8..21ed24791 100644 --- a/src/app/dialogs/node-versions/node-versions.dialog.html +++ b/src/app/dialogs/node-versions/node-versions.dialog.html @@ -5,11 +5,7 @@ {{ 'VERSION.DIALOG.TITLE' | translate }}
- + diff --git a/src/app/dialogs/node-versions/node-versions.dialog.spec.ts b/src/app/dialogs/node-versions/node-versions.dialog.spec.ts index 96063eafa..e423af005 100644 --- a/src/app/dialogs/node-versions/node-versions.dialog.spec.ts +++ b/src/app/dialogs/node-versions/node-versions.dialog.spec.ts @@ -37,19 +37,20 @@ import { VersionUploadComponent } from '@alfresco/adf-content-services'; import { TranslateFakeLoader, TranslateLoader, TranslateModule } from '@ngx-translate/core'; -import { AppStore, UnlockWriteAction } from '@alfresco/aca-shared/store'; +import { AppStore, UnlockWriteAction, ViewNodeExtras, ViewNodeVersionAction } from '@alfresco/aca-shared/store'; +import { RouterTestingModule } from '@angular/router/testing'; describe('NodeVersionsDialogComponent', () => { let fixture: ComponentFixture; let component: NodeVersionsDialogComponent; let store: Store; - beforeEach(() => { TestBed.configureTestingModule({ imports: [ CoreModule.forRoot(), AppTestingModule, MatDialogModule, + RouterTestingModule.withRoutes([]), TranslateModule.forRoot({ loader: { provide: TranslateLoader, useClass: TranslateFakeLoader } }) @@ -153,4 +154,14 @@ describe('NodeVersionsDialogComponent', () => { component.handleUpload(nodeEvent); expect(store.dispatch).toHaveBeenCalledWith(new UnlockWriteAction(nodeEvent.value)); }); + + it('should view a previous version of a node', () => { + component.isTypeList = false; + const versionId = '1.0'; + const location: ViewNodeExtras = { + location: '/' + }; + component.onViewingVersion(versionId); + expect(store.dispatch).toHaveBeenCalledWith(new ViewNodeVersionAction(component.node.id, versionId, location)); + }); }); diff --git a/src/app/dialogs/node-versions/node-versions.dialog.ts b/src/app/dialogs/node-versions/node-versions.dialog.ts index c161cdb48..9aaef1f63 100644 --- a/src/app/dialogs/node-versions/node-versions.dialog.ts +++ b/src/app/dialogs/node-versions/node-versions.dialog.ts @@ -23,12 +23,13 @@ * along with Alfresco. If not, see . */ -import { AppStore, SnackbarErrorAction, UnlockWriteAction } from '@alfresco/aca-shared/store'; +import { AppStore, SnackbarErrorAction, UnlockWriteAction, ViewNodeVersionAction } from '@alfresco/aca-shared/store'; import { MinimalNodeEntryEntity, Node } from '@alfresco/js-api'; import { Component, EventEmitter, Inject, Output, ViewEncapsulation } from '@angular/core'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { Store } from '@ngrx/store'; import { NodeEntityEvent } from '@alfresco/adf-content-services'; +import { Router } from '@angular/router'; @Component({ templateUrl: './node-versions.dialog.html', @@ -44,7 +45,12 @@ export class NodeVersionsDialogComponent { @Output() refreshEvent: EventEmitter = new EventEmitter(); - constructor(@Inject(MAT_DIALOG_DATA) data: any, private store: Store, private dialogRef: MatDialogRef) { + constructor( + @Inject(MAT_DIALOG_DATA) data: any, + private store: Store, + private dialogRef: MatDialogRef, + private router: Router + ) { this.node = data.node; this.file = data.file; this.isTypeList = data.isTypeList !== undefined ? data.isTypeList : true; @@ -68,4 +74,12 @@ export class NodeVersionsDialogComponent { refresh(node: Node) { this.refreshEvent.emit(node); } + + onViewingVersion(versionId: string) { + this.store.dispatch( + new ViewNodeVersionAction(this.node.id, versionId, { + location: this.router.url + }) + ); + } } diff --git a/src/app/services/node-actions.service.ts b/src/app/services/node-actions.service.ts index d3fbb2b78..c091c3b37 100644 --- a/src/app/services/node-actions.service.ts +++ b/src/app/services/node-actions.service.ts @@ -186,6 +186,7 @@ export class NodeActionsService { this.isSitesDestinationAvailable = false; const data: ContentNodeSelectorComponentData = { + selectionMode: 'single', title: title, currentFolderId: currentParentFolderId, actionName: action, diff --git a/src/app/services/node-template.service.ts b/src/app/services/node-template.service.ts index 36f3cea99..3b1c9354d 100644 --- a/src/app/services/node-template.service.ts +++ b/src/app/services/node-template.service.ts @@ -62,6 +62,7 @@ export class NodeTemplateService { }); const data: ContentNodeSelectorComponentData = { + selectionMode: 'single', title: this.title(config.selectionType), actionName: 'NEXT', dropdownHideMyFiles: true, diff --git a/src/app/store/effects/download.effects.ts b/src/app/store/effects/download.effects.ts index 7e9025289..9027cd07d 100644 --- a/src/app/store/effects/download.effects.ts +++ b/src/app/store/effects/download.effects.ts @@ -23,9 +23,9 @@ * along with Alfresco. If not, see . */ -import { AppStore, DownloadNodesAction, NodeActionTypes, NodeInfo, getAppSelection } from '@alfresco/aca-shared/store'; +import { AppStore, DownloadNodesAction, NodeActionTypes, NodeInfo, getAppSelection, getCurrentVersion } from '@alfresco/aca-shared/store'; import { DownloadZipDialogComponent } from '@alfresco/adf-core'; -import { MinimalNodeEntity } from '@alfresco/js-api'; +import { MinimalNodeEntity, Version } from '@alfresco/js-api'; import { Injectable } from '@angular/core'; import { MatDialog } from '@angular/material/dialog'; import { Actions, Effect, ofType } from '@ngrx/effects'; @@ -49,7 +49,16 @@ export class DownloadEffects { .pipe(take(1)) .subscribe((selection) => { if (selection && !selection.isEmpty) { - this.downloadNodes(selection.nodes); + this.store + .select(getCurrentVersion) + .pipe(take(1)) + .subscribe((version) => { + if (version) { + this.downloadFileVersion(selection.nodes[0].entry, version.entry); + } else { + this.downloadNodes(selection.nodes); + } + }); } }); } @@ -99,6 +108,12 @@ export class DownloadEffects { } } + private downloadFileVersion(node: NodeInfo, version: Version) { + if (node && version) { + this.download(this.contentApi.getVersionContentUrl(node.id, version.id, true), version.name); + } + } + private downloadZip(nodes: Array) { if (nodes && nodes.length > 0) { const nodeIds = nodes.map((node) => node.id); diff --git a/src/app/store/effects/viewer.effects.ts b/src/app/store/effects/viewer.effects.ts index 354581fcc..917d48a28 100644 --- a/src/app/store/effects/viewer.effects.ts +++ b/src/app/store/effects/viewer.effects.ts @@ -33,11 +33,13 @@ import { ViewNodeAction, getCurrentFolder, getAppSelection, - FullscreenViewerAction + FullscreenViewerAction, + ViewNodeVersionAction } from '@alfresco/aca-shared/store'; import { Router, UrlTree, UrlSegmentGroup, PRIMARY_OUTLET, UrlSegment } from '@angular/router'; import { Store, createSelector } from '@ngrx/store'; import { AppExtensionService } from '@alfresco/aca-shared'; +import { MatDialog } from '@angular/material/dialog'; export const fileToPreview = createSelector(getAppSelection, getCurrentFolder, (selection, folder) => { return { @@ -48,7 +50,13 @@ export const fileToPreview = createSelector(getAppSelection, getCurrentFolder, ( @Injectable() export class ViewerEffects { - constructor(private store: Store, private actions$: Actions, private router: Router, private extensions: AppExtensionService) {} + constructor( + private store: Store, + private actions$: Actions, + private router: Router, + private extensions: AppExtensionService, + private dialog: MatDialog + ) {} @Effect({ dispatch: false }) fullscreenViewer$ = this.actions$.pipe( @@ -112,6 +120,31 @@ export class ViewerEffects { }) ); + @Effect({ dispatch: false }) + viewNodeVersion$ = this.actions$.pipe( + ofType(ViewerActionTypes.ViewNodeVersion), + map((action) => { + this.dialog.closeAll(); + if (action.viewNodeExtras) { + const { location, path } = action.viewNodeExtras; + if (location) { + const navigation = this.getNavigationCommands(location); + this.router.navigate([...navigation, { outlets: { viewer: ['view', action.nodeId, action.versionId] } }], { + queryParams: { location } + }); + } + + if (path) { + this.router.navigate(['view', { outlets: { viewer: [action.nodeId, action.versionId] } }], { + queryParams: { path } + }); + } + } else { + this.router.navigate(['view', { outlets: { viewer: [action.nodeId, action.versionId] } }]); + } + }) + ); + private displayPreview(nodeId: string, parentId: string) { if (!nodeId) { return; diff --git a/src/app/store/initial-state.ts b/src/app/store/initial-state.ts index 337034b19..da89b2fd9 100644 --- a/src/app/store/initial-state.ts +++ b/src/app/store/initial-state.ts @@ -47,6 +47,7 @@ export const INITIAL_APP_STATE: AppState = { navigation: { currentFolder: null }, + currentNodeVersion: null, infoDrawerOpened: false, infoDrawerMetadataAspect: '', showFacetFilter: true, diff --git a/src/app/store/reducers/app.reducer.ts b/src/app/store/reducers/app.reducer.ts index 8508cf898..d42ef7d46 100644 --- a/src/app/store/reducers/app.reducer.ts +++ b/src/app/store/reducers/app.reducer.ts @@ -38,7 +38,8 @@ import { SetInfoDrawerStateAction, SetInfoDrawerMetadataAspectAction, SetSettingsParameterAction, - SetHeaderColorAction + SetHeaderColorAction, + SetCurrentNodeVersionAction } from '@alfresco/aca-shared/store'; import { INITIAL_APP_STATE } from '../initial-state'; @@ -67,6 +68,9 @@ export function appReducer(state: AppState = INITIAL_APP_STATE, action: Action): case AppActionTypes.SetCurrentFolder: newState = updateCurrentFolder(state, action as SetCurrentFolderAction); break; + case AppActionTypes.SetCurrentVersion: + newState = updateCurrentNodeVersion(state, action as SetCurrentNodeVersionAction); + break; case AppActionTypes.SetCurrentUrl: newState = updateCurrentUrl(state, action as SetCurrentUrlAction); break; @@ -155,6 +159,12 @@ function updateCurrentFolder(state: AppState, action: SetCurrentFolderAction) { return newState; } +function updateCurrentNodeVersion(state: AppState, action: SetCurrentNodeVersionAction) { + const newState = { ...state }; + newState.currentNodeVersion = action.payload; + return newState; +} + function updateCurrentUrl(state: AppState, action: SetCurrentUrlAction) { const newState = { ...state }; newState.navigation.url = action.payload;