[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 <kristian.dimitrov@alfresco.com>
Co-authored-by: Denys Vuika <denys.vuika@alfresco.com>
This commit is contained in:
Urse Daniel
2020-08-05 16:15:20 +03:00
committed by GitHub
parent 3c5522821e
commit f59abb4228
23 changed files with 560 additions and 54 deletions

View File

@@ -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)
}
]
}
]
},

View File

@@ -6,6 +6,7 @@
[fileName]="fileName"
[maxRetries]="'viewer.maxRetries' | adfAppConfig"
[nodeId]="nodeId"
[versionId]="versionId"
[allowNavigate]="navigateMultiple"
[allowRightSidebar]="true"
[allowPrint]="false"

View File

@@ -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<boolean>;
@@ -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<ClosePreviewAction>(ViewerActionTypes.ClosePreview), takeUntil(this.onDestroy$))
.subscribe(() => this.navigateToFileLocation());
this.actions$.pipe(ofType<ClosePreviewAction>(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();
}

View File

@@ -5,11 +5,7 @@
{{ 'VERSION.DIALOG.TITLE' | translate }}
</header>
<section mat-dialog-content *ngIf="!isTypeList">
<adf-version-comparison
id="adf-version-comparison"
[newFileVersion]="file"
[node]="node"
></adf-version-comparison>
<adf-version-comparison id="adf-version-comparison" [newFileVersion]="file" [node]="node"></adf-version-comparison>
<adf-version-upload
id="adf-version-upload-button"
[node]="node"
@@ -30,6 +26,7 @@
[allowDownload]="'adf-version-manager.allowDownload' | adfAppConfig: true"
(deleted)="refresh($event)"
(restored)="refresh($event)"
(viewVersion)="onViewingVersion($event)"
></adf-version-list>
</div>
</div>

View File

@@ -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<NodeVersionsDialogComponent>;
let component: NodeVersionsDialogComponent;
let store: Store<AppStore>;
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));
});
});

View File

@@ -23,12 +23,13 @@
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
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<Node> = new EventEmitter<Node>();
constructor(@Inject(MAT_DIALOG_DATA) data: any, private store: Store<AppStore>, private dialogRef: MatDialogRef<NodeVersionsDialogComponent>) {
constructor(
@Inject(MAT_DIALOG_DATA) data: any,
private store: Store<AppStore>,
private dialogRef: MatDialogRef<NodeVersionsDialogComponent>,
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
})
);
}
}

View File

@@ -186,6 +186,7 @@ export class NodeActionsService {
this.isSitesDestinationAvailable = false;
const data: ContentNodeSelectorComponentData = {
selectionMode: 'single',
title: title,
currentFolderId: currentParentFolderId,
actionName: action,

View File

@@ -62,6 +62,7 @@ export class NodeTemplateService {
});
const data: ContentNodeSelectorComponentData = {
selectionMode: 'single',
title: this.title(config.selectionType),
actionName: 'NEXT',
dropdownHideMyFiles: true,

View File

@@ -23,9 +23,9 @@
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
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<NodeInfo>) {
if (nodes && nodes.length > 0) {
const nodeIds = nodes.map((node) => node.id);

View File

@@ -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<AppStore>, private actions$: Actions, private router: Router, private extensions: AppExtensionService) {}
constructor(
private store: Store<AppStore>,
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<ViewNodeVersionAction>(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;

View File

@@ -47,6 +47,7 @@ export const INITIAL_APP_STATE: AppState = {
navigation: {
currentFolder: null
},
currentNodeVersion: null,
infoDrawerOpened: false,
infoDrawerMetadataAspect: '',
showFacetFilter: true,

View File

@@ -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;