{
await BrowserVisibility.waitUntilElementIsVisible(element(by.id(`adf-version-list-action-download-${version}`)));
await BrowserVisibility.waitUntilElementIsVisible(element(by.id(`adf-version-list-action-delete-${version}`)));
diff --git a/e2e/core/viewer/viewer-custom-toolbar-info-drawer.e2e.ts b/e2e/core/viewer/viewer-custom-toolbar-info-drawer.e2e.ts
index 8b3ff66d05..04d508f374 100644
--- a/e2e/core/viewer/viewer-custom-toolbar-info-drawer.e2e.ts
+++ b/e2e/core/viewer/viewer-custom-toolbar-info-drawer.e2e.ts
@@ -16,10 +16,11 @@
*/
import { browser } from 'protractor';
-import { ApiService, LoginPage, UploadActions, UserModel, UsersActions, ViewerPage } from '@alfresco/adf-testing';
+import { ApiService, BrowserActions, FileBrowserUtil, LoginPage, UploadActions, UserModel, UsersActions, ViewerPage } from '@alfresco/adf-testing';
import { ContentServicesPage } from '../../core/pages/content-services.page';
import { FileModel } from '../../models/ACS/file.model';
import { NavigationBarPage } from '../../core/pages/navigation-bar.page';
+import { VersionManagePage } from '../pages/version-manager.page';
describe('Viewer', () => {
@@ -32,6 +33,7 @@ describe('Viewer', () => {
const uploadActions = new UploadActions(apiService);
const usersActions = new UsersActions(apiService);
+ const versionManagePage = new VersionManagePage();
const acsUser = new UserModel();
let txtFileUploaded;
@@ -40,6 +42,11 @@ describe('Viewer', () => {
'location': browser.params.resources.Files.ADF_DOCUMENTS.TXT.file_path
});
+ const fileModelVersionTwo = new FileModel({
+ 'name': browser.params.resources.Files.ADF_DOCUMENTS.TXT.file_name,
+ 'location': browser.params.resources.Files.ADF_DOCUMENTS.TXT.file_location
+ });
+
beforeAll(async () => {
await apiService.getInstance().login(browser.params.testConfig.admin.email, browser.params.testConfig.admin.password);
await usersActions.createUser(acsUser);
@@ -84,4 +91,22 @@ describe('Viewer', () => {
await viewerPage.clickOnTab('Versions');
await viewerPage.checkTabIsActive('Versions');
});
+
+ it('[C362242] Should the Viewer be able to view a previous version of a file', async () => {
+ await viewerPage.clickCloseButton();
+ await contentServicesPage.versionManagerContent(txtFileInfo.name);
+ await BrowserActions.click(versionManagePage.showNewVersionButton);
+ await versionManagePage.uploadNewVersionFile(fileModelVersionTwo.location);
+ await versionManagePage.closeVersionDialog();
+ await contentServicesPage.doubleClickRow(txtFileUploaded.entry.name);
+ await viewerPage.clickInfoButton();
+ await viewerPage.clickOnTab('Versions');
+ await versionManagePage.viewFileVersion('1.0');
+ await viewerPage.expectUrlToContain('1.0');
+ });
+
+ it('[C362265] Should the Viewer be able to download a previous version of a file', async () => {
+ await viewerPage.clickDownloadButton();
+ await FileBrowserUtil.isFileDownloaded(txtFileInfo.name);
+ });
});
diff --git a/lib/content-services/src/lib/i18n/en.json b/lib/content-services/src/lib/i18n/en.json
index 27022215bc..a441716bc6 100644
--- a/lib/content-services/src/lib/i18n/en.json
+++ b/lib/content-services/src/lib/i18n/en.json
@@ -4,6 +4,7 @@
"RESTORE": "Restore",
"DELETE": "Delete",
"DOWNLOAD": "Download",
+ "VIEW": "View",
"UPLOAD": {
"TITLE": "Upload new version",
"TOOLTIP": "Restriction: you must upload a file with the same name to create a new version of it",
diff --git a/lib/content-services/src/lib/version-manager/version-list.component.html b/lib/content-services/src/lib/version-manager/version-list.component.html
index 0f6ac19452..d3a908b4ef 100644
--- a/lib/content-services/src/lib/version-manager/version-list.component.html
+++ b/lib/content-services/src/lib/version-manager/version-list.component.html
@@ -12,6 +12,14 @@
+
+
+
diff --git a/lib/content-services/src/lib/version-manager/version-manager.component.spec.ts b/lib/content-services/src/lib/version-manager/version-manager.component.spec.ts
index 718ac0a735..3423413842 100644
--- a/lib/content-services/src/lib/version-manager/version-manager.component.spec.ts
+++ b/lib/content-services/src/lib/version-manager/version-manager.component.spec.ts
@@ -73,6 +73,13 @@ describe('VersionManagerComponent', () => {
expect(component.uploadState).toBe('open');
});
+ it('should be able to view a version', () => {
+ spyOn(component.viewVersion, 'emit');
+ component.onViewVersion('1.0');
+ fixture.detectChanges();
+ expect(component.viewVersion.emit).toHaveBeenCalledWith('1.0');
+ });
+
it('should display comments for versions when not configured otherwise', async(() => {
fixture.detectChanges();
fixture.whenStable().then(() => {
diff --git a/lib/content-services/src/lib/version-manager/version-manager.component.ts b/lib/content-services/src/lib/version-manager/version-manager.component.ts
index 845394ed8f..d4d572fcdf 100644
--- a/lib/content-services/src/lib/version-manager/version-manager.component.ts
+++ b/lib/content-services/src/lib/version-manager/version-manager.component.ts
@@ -75,6 +75,10 @@ export class VersionManagerComponent implements OnInit {
@Output()
uploadCancel: EventEmitter = new EventEmitter();
+ /** Emitted when viewing a version. */
+ @Output()
+ viewVersion: EventEmitter = new EventEmitter();
+
@ViewChild('versionList', { static: true })
versionListComponent: VersionListComponent;
@@ -117,6 +121,10 @@ export class VersionManagerComponent implements OnInit {
this.uploadCancel.emit(true);
}
+ onViewVersion(versionId: string) {
+ this.viewVersion.emit(versionId);
+ }
+
toggleNewVersion() {
this.uploadState = this.uploadState === 'open' ? 'close' : 'open';
}
diff --git a/lib/core/directives/node-download.directive.spec.ts b/lib/core/directives/node-download.directive.spec.ts
index e41255763e..68800aa8a1 100755
--- a/lib/core/directives/node-download.directive.spec.ts
+++ b/lib/core/directives/node-download.directive.spec.ts
@@ -26,10 +26,11 @@ import { CoreTestingModule } from '../testing/core.testing.module';
import { TranslateModule } from '@ngx-translate/core';
@Component({
- template: ''
+ template: ''
})
class TestComponent {
selection;
+ version;
}
describe('NodeDownloadDirective', () => {
@@ -91,6 +92,22 @@ describe('NodeDownloadDirective', () => {
expect(contentService.getContentUrl).toHaveBeenCalledWith(node.entry.id, true);
});
+ it('should download selected node version as file', () => {
+ component.version = {
+ entry: {
+ id: '1.0'
+ }
+ };
+ spyOn(contentService, 'getVersionContentUrl');
+ const node = {entry: {id: 'node-id', isFile: true}};
+ component.selection = [node];
+
+ fixture.detectChanges();
+ element.triggerEventHandler('click', null);
+
+ expect(contentService.getVersionContentUrl).toHaveBeenCalledWith(node.entry.id, '1.0', true);
+ });
+
it('should download selected shared node as file', () => {
spyOn(contentService, 'getContentUrl');
const node = { entry: { nodeId: 'shared-node-id', isFile: true } };
diff --git a/lib/core/directives/node-download.directive.ts b/lib/core/directives/node-download.directive.ts
index 6ec248bdb2..47387c122a 100755
--- a/lib/core/directives/node-download.directive.ts
+++ b/lib/core/directives/node-download.directive.ts
@@ -19,7 +19,7 @@ import { Directive, Input, HostListener } from '@angular/core';
import { MatDialog } from '@angular/material/dialog';
import { AlfrescoApiService } from '../services/alfresco-api.service';
import { DownloadZipDialogComponent } from '../dialogs/download-zip/download-zip.dialog';
-import { NodeEntry } from '@alfresco/js-api';
+import { NodeEntry, VersionEntry } from '@alfresco/js-api';
import { DownloadService } from '../services/download.service';
/**
@@ -35,6 +35,10 @@ export class NodeDownloadDirective {
@Input('adfNodeDownload')
nodes: NodeEntry | NodeEntry[];
+ /** Node's version to download. */
+ @Input()
+ version: VersionEntry;
+
@HostListener('click')
onClick() {
this.downloadNodes(this.nodes);
@@ -101,8 +105,14 @@ export class NodeDownloadDirective {
// nodeId for Shared node
const id = ( node.entry).nodeId || node.entry.id;
- const url = contentApi.getContentUrl(id, true);
- const fileName = node.entry.name;
+ let url, fileName;
+ if (this.version) {
+ url = contentApi.getVersionContentUrl(id, this.version.entry.id, true);
+ fileName = this.version.entry.name;
+ } else {
+ url = contentApi.getContentUrl(id, true);
+ fileName = node.entry.name;
+ }
this.downloadService.downloadUrl(url, fileName);
}
diff --git a/lib/core/viewer/components/viewer.component.html b/lib/core/viewer/components/viewer.component.html
index e8ac50229b..106d635655 100644
--- a/lib/core/viewer/components/viewer.component.html
+++ b/lib/core/viewer/components/viewer.component.html
@@ -82,7 +82,8 @@
[attr.aria-label]="'ADF_VIEWER.ACTIONS.DOWNLOAD' | translate"
title="{{ 'ADF_VIEWER.ACTIONS.DOWNLOAD' | translate }}"
data-automation-id="adf-toolbar-download"
- [adfNodeDownload]="nodeEntry">
+ [adfNodeDownload]="nodeEntry"
+ [version]="versionEntry">
file_download
diff --git a/lib/core/viewer/components/viewer.component.ts b/lib/core/viewer/components/viewer.component.ts
index 158fd424b1..44ddc3858b 100644
--- a/lib/core/viewer/components/viewer.component.ts
+++ b/lib/core/viewer/components/viewer.component.ts
@@ -20,7 +20,7 @@ import {
Input, OnChanges, Output, TemplateRef,
ViewEncapsulation, OnInit, OnDestroy
} from '@angular/core';
-import { RenditionPaging, SharedLinkEntry, Node, RenditionEntry, NodeEntry } from '@alfresco/js-api';
+import { SharedLinkEntry, Node, Version, RenditionEntry, NodeEntry, VersionEntry } from '@alfresco/js-api';
import { BaseEvent } from '../../events';
import { AlfrescoApiService } from '../../services/alfresco-api.service';
import { LogService } from '../../services/log.service';
@@ -74,6 +74,10 @@ export class ViewerComponent implements OnChanges, OnInit, OnDestroy {
@Input()
nodeId: string = null;
+ /** Version Id of the file to load. */
+ @Input()
+ versionId: string = null;
+
/** Shared link id (to display shared file). */
@Input()
sharedLinkId: string = null;
@@ -206,6 +210,7 @@ export class ViewerComponent implements OnChanges, OnInit, OnDestroy {
viewerType = 'unknown';
isLoading = false;
nodeEntry: NodeEntry;
+ versionEntry: VersionEntry;
extensionTemplates: { template: TemplateRef, isVisible: boolean }[] = [];
externalExtensions: string[] = [];
@@ -255,6 +260,17 @@ export class ViewerComponent implements OnChanges, OnInit, OnDestroy {
).subscribe((node) => this.onNodeUpdated(node))
);
+ this.subscriptions.push(
+ this.viewUtils.viewerTypeChange.subscribe((type: string) => {
+ this.viewerType = type;
+ })
+ );
+ this.subscriptions.push(
+ this.viewUtils.urlFileContentChange.subscribe((content: string) => {
+ this.urlFileContent = content;
+ })
+ );
+
this.loadExtensions();
}
@@ -298,9 +314,20 @@ export class ViewerComponent implements OnChanges, OnInit, OnDestroy {
this.apiService.nodesApi.getNode(this.nodeId, { include: ['allowableOperations'] }).then(
(node: NodeEntry) => {
this.nodeEntry = node;
- this.setUpNodeFile(node.entry).then(() => {
- this.isLoading = false;
- });
+ if (this.versionId) {
+ this.apiService.versionsApi.getVersion(this.nodeId, this.versionId).then(
+ (version: VersionEntry) => {
+ this.versionEntry = version;
+ this.setUpNodeFile(node.entry, version.entry).then(() => {
+ this.isLoading = false;
+ });
+ }
+ );
+ } else {
+ this.setUpNodeFile(node.entry).then(() => {
+ this.isLoading = false;
+ });
+ }
},
() => {
this.isLoading = false;
@@ -353,21 +380,23 @@ export class ViewerComponent implements OnChanges, OnInit, OnDestroy {
this.scrollTop();
}
- private async setUpNodeFile(data: Node) {
+ private async setUpNodeFile(nodeData: Node, versionData?: Version) {
let setupNode;
- if (data.content) {
- this.mimeType = data.content.mimeType;
+ if (versionData && versionData.content) {
+ this.mimeType = versionData.content.mimeType;
+ } else if (nodeData.content) {
+ this.mimeType = nodeData.content.mimeType;
}
- this.fileTitle = this.getDisplayName(data.name);
+ this.fileTitle = this.getDisplayName(nodeData.name);
- this.urlFileContent = this.apiService.contentApi.getContentUrl(data.id);
+ this.urlFileContent = versionData ? this.apiService.contentApi.getVersionContentUrl(this.nodeId, versionData.id) :
this.urlFileContent = this.cacheBusterNumber ? this.urlFileContent + '&' + this.cacheBusterNumber : this.urlFileContent;
- this.extension = this.getFileExtension(data.name);
+ this.extension = this.getFileExtension(versionData ? versionData.name : nodeData.name);
- this.fileName = data.name;
+ this.fileName = versionData ? versionData.name : nodeData.name;
this.viewerType = this.getViewerTypeByExtension(this.extension);
if (this.viewerType === 'unknown') {
@@ -375,12 +404,12 @@ export class ViewerComponent implements OnChanges, OnInit, OnDestroy {
}
if (this.viewerType === 'unknown') {
- setupNode = this.displayNodeRendition(data.id);
+ setupNode = this.viewUtils.displayNodeRendition(nodeData.id, versionData ? versionData.id : undefined);
}
this.extensionChange.emit(this.extension);
- this.sidebarRightTemplateContext.node = data;
- this.sidebarLeftTemplateContext.node = data;
+ this.sidebarRightTemplateContext.node = nodeData;
+ this.sidebarLeftTemplateContext.node = nodeData;
this.scrollTop();
return setupNode;
@@ -603,25 +632,6 @@ export class ViewerComponent implements OnChanges, OnInit, OnDestroy {
}
}
- private async displayNodeRendition(nodeId: string) {
- try {
- const rendition = await this.resolveRendition(nodeId, 'pdf');
- if (rendition) {
- const renditionId = rendition.entry.id;
-
- if (renditionId === 'pdf') {
- this.viewerType = 'pdf';
- } else if (renditionId === 'imgpreview') {
- this.viewerType = 'image';
- }
-
- this.urlFileContent = this.apiService.contentApi.getRenditionUrl(nodeId, renditionId);
- }
- } catch (err) {
- this.logService.error(err);
- }
- }
-
private async displaySharedLinkRendition(sharedId: string) {
try {
const rendition: RenditionEntry = await this.apiService.renditionsApi.getSharedLinkRendition(sharedId, 'pdf');
@@ -643,69 +653,6 @@ export class ViewerComponent implements OnChanges, OnInit, OnDestroy {
}
}
- private async resolveRendition(nodeId: string, renditionId: string): Promise {
- renditionId = renditionId.toLowerCase();
-
- const supportedRendition: RenditionPaging = await this.apiService.renditionsApi.getRenditions(nodeId);
-
- let rendition: RenditionEntry = supportedRendition.list.entries.find((renditionEntry: RenditionEntry) => renditionEntry.entry.id.toLowerCase() === renditionId);
- if (!rendition) {
- renditionId = 'imgpreview';
- rendition = supportedRendition.list.entries.find((renditionEntry: RenditionEntry) => renditionEntry.entry.id.toLowerCase() === renditionId);
- }
-
- if (rendition) {
- const status: string = rendition.entry.status.toString();
-
- if (status === 'NOT_CREATED') {
- try {
- await this.apiService.renditionsApi.createRendition(nodeId, { id: renditionId }).then(() => {
- this.viewerType = 'in_creation';
- });
- rendition = await this.waitRendition(nodeId, renditionId);
- } catch (err) {
- this.logService.error(err);
- }
- }
- }
-
- return rendition;
- }
-
- private async waitRendition(nodeId: string, renditionId: string): Promise {
- let currentRetry: number = 0;
- return new Promise((resolve, reject) => {
- const intervalId = setInterval(() => {
- currentRetry++;
- if (this.maxRetries >= currentRetry) {
- this.apiService.renditionsApi.getRendition(nodeId, renditionId).then((rendition: RenditionEntry) => {
- const status: string = rendition.entry.status.toString();
- if (status === 'CREATED') {
-
- if (renditionId === 'pdf') {
- this.viewerType = 'pdf';
- } else if (renditionId === 'imgpreview') {
- this.viewerType = 'image';
- }
-
- this.urlFileContent = this.apiService.contentApi.getRenditionUrl(nodeId, renditionId);
-
- clearInterval(intervalId);
- return resolve(rendition);
- }
- }, () => {
- this.viewerType = 'error_in_creation';
- return reject();
- });
- } else {
- this.isLoading = false;
- this.viewerType = 'error_in_creation';
- clearInterval(intervalId);
- }
- }, this.TRY_TIMEOUT);
- });
- }
-
checkExtensions(extensionAllowed) {
if (typeof extensionAllowed === 'string') {
return this.extension.toLowerCase() === extensionAllowed.toLowerCase();
diff --git a/lib/core/viewer/services/view-util.service.ts b/lib/core/viewer/services/view-util.service.ts
index 0b8a1dbb41..ff5c73cdb4 100644
--- a/lib/core/viewer/services/view-util.service.ts
+++ b/lib/core/viewer/services/view-util.service.ts
@@ -19,6 +19,7 @@ import { Injectable } from '@angular/core';
import { RenditionEntry, RenditionPaging } from '@alfresco/js-api';
import { AlfrescoApiService } from '../../services/alfresco-api.service';
import { LogService } from '../../services/log.service';
+import { Subject } from 'rxjs';
@Injectable({
providedIn: 'root'
@@ -54,6 +55,17 @@ export class ViewUtilService {
media: ['video/mp4', 'video/webm', 'video/ogg', 'audio/mpeg', 'audio/ogg', 'audio/wav']
};
+ /**
+ * Timeout used for setInterval.
+ */
+ TRY_TIMEOUT: number = 10000;
+
+ /**
+ * Subscribers needed for ViewerComponent to update the viewerType and urlFileContent.
+ */
+ viewerTypeChange: Subject = new Subject();
+ urlFileContentChange: Subject = new Subject();
+
constructor(private apiService: AlfrescoApiService,
private logService: LogService) {
}
@@ -167,4 +179,110 @@ export class ViewUtilService {
return new Promise((resolve) => resolve(rendition));
}
+ async displayNodeRendition(nodeId: string, versionId?: string) {
+ try {
+ const rendition = await this.resolveNodeRendition(nodeId, 'pdf', versionId);
+ if (rendition) {
+ const renditionId = rendition.entry.id;
+
+ if (renditionId === 'pdf') {
+ this.viewerTypeChange.next('pdf');
+ } else if (renditionId === 'imgpreview') {
+ this.viewerTypeChange.next('image');
+ }
+
+ const urlFileContent = versionId ? this.apiService.contentApi.getVersionRenditionUrl(nodeId, versionId, renditionId) :
+ this.apiService.contentApi.getRenditionUrl(nodeId, renditionId);
+ this.urlFileContentChange.next(urlFileContent);
+ }
+ } catch (err) {
+ this.logService.error(err);
+ }
+ }
+
+ private async resolveNodeRendition(nodeId: string, renditionId: string, versionId?: string): Promise {
+ renditionId = renditionId.toLowerCase();
+
+ const supportedRendition: RenditionPaging = versionId ? await this.apiService.versionsApi.listVersionRenditions(nodeId, versionId) :
+ await this.apiService.renditionsApi.getRenditions(nodeId);
+
+ let rendition: RenditionEntry = supportedRendition.list.entries.find((renditionEntry: RenditionEntry) => renditionEntry.entry.id.toLowerCase() === renditionId);
+ if (!rendition) {
+ renditionId = 'imgpreview';
+ rendition = supportedRendition.list.entries.find((renditionEntry: RenditionEntry) => renditionEntry.entry.id.toLowerCase() === renditionId);
+ }
+
+ if (rendition) {
+ const status: string = rendition.entry.status.toString();
+
+ if (status === 'NOT_CREATED') {
+ try {
+ if (versionId) {
+ await this.apiService.versionsApi.createVersionRendition(nodeId, versionId, {id: renditionId}).then(() => {
+ this.viewerTypeChange.next('in_creation');
+ });
+ } else {
+ await this.apiService.renditionsApi.createRendition(nodeId, {id: renditionId}).then(() => {
+ this.viewerTypeChange.next('in_creation');
+ });
+ }
+ rendition = await this.waitNodeRendition(nodeId, renditionId, versionId);
+ } catch (err) {
+ this.logService.error(err);
+ }
+ }
+ }
+
+ return rendition;
+ }
+
+ private async waitNodeRendition(nodeId: string, renditionId: string, versionId?: string): Promise {
+ let currentRetry: number = 0;
+ return new Promise((resolve, reject) => {
+ const intervalId = setInterval(() => {
+ currentRetry++;
+ if (this.maxRetries >= currentRetry) {
+ if (!versionId) {
+ this.apiService.versionsApi.getVersionRendition(nodeId, versionId, renditionId).then((rendition: RenditionEntry) => {
+ this.handleNodeRendition(rendition, nodeId, renditionId, versionId);
+ clearInterval(intervalId);
+ return resolve(rendition);
+ }, () => {
+ this.viewerTypeChange.next('error_in_creation');
+ return reject();
+ });
+ } else {
+ this.apiService.renditionsApi.getRendition(nodeId, renditionId).then((rendition: RenditionEntry) => {
+ this.handleNodeRendition(rendition, nodeId, renditionId);
+ clearInterval(intervalId);
+ return resolve(rendition);
+ }, () => {
+ this.viewerTypeChange.next('error_in_creation');
+ return reject();
+ });
+ }
+ } else {
+ this.viewerTypeChange.next('error_in_creation');
+ clearInterval(intervalId);
+ }
+ }, this.TRY_TIMEOUT);
+ });
+ }
+
+ private async handleNodeRendition(rendition: RenditionEntry, nodeId: string, renditionId: string, versionId?: string) {
+ const status: string = rendition.entry.status.toString();
+ if (status === 'CREATED') {
+
+ if (renditionId === 'pdf') {
+ this.viewerTypeChange.next('pdf');
+ } else if (renditionId === 'imgpreview') {
+ this.viewerTypeChange.next('image');
+ }
+
+ const urlFileContent = versionId ? this.apiService.contentApi.getVersionRenditionUrl(nodeId, versionId, renditionId) :
+ this.apiService.contentApi.getRenditionUrl(nodeId, renditionId);
+ this.urlFileContentChange.next(urlFileContent);
+ }
+ }
+
}
diff --git a/lib/testing/src/lib/core/pages/viewer.page.ts b/lib/testing/src/lib/core/pages/viewer.page.ts
index b9147287c6..105d1c0d4d 100644
--- a/lib/testing/src/lib/core/pages/viewer.page.ts
+++ b/lib/testing/src/lib/core/pages/viewer.page.ts
@@ -637,4 +637,8 @@ export class ViewerPage {
const unknownFormatLabel = this.unknownFormat.element(by.css(`.adf-viewer__unknown-label`));
return BrowserActions.getText(unknownFormatLabel);
}
+
+ async expectUrlToContain(text: string): Promise {
+ await expect(browser.getCurrentUrl()).toContain(text);
+ }
}