[ADF-4926] fix attachments downloading (#5134)

* fix attachments downloading

* update code and tests
This commit is contained in:
Denys Vuika
2019-10-10 12:06:37 +01:00
committed by Eugenio Romano
parent 2def8d0557
commit b1d0c50e88
7 changed files with 110 additions and 85 deletions

View File

@@ -18,7 +18,7 @@
import { Injectable, Output, EventEmitter } from '@angular/core'; import { Injectable, Output, EventEmitter } from '@angular/core';
import { Node, NodeEntry } from '@alfresco/js-api'; import { Node, NodeEntry } from '@alfresco/js-api';
import { Subject } from 'rxjs'; import { Subject } from 'rxjs';
import { AlfrescoApiService, ContentService, NodeDownloadDirective } from '@alfresco/adf-core'; import { AlfrescoApiService, ContentService, NodeDownloadDirective, DownloadService } from '@alfresco/adf-core';
import { MatDialog } from '@angular/material'; import { MatDialog } from '@angular/material';
import { DocumentListService } from './document-list.service'; import { DocumentListService } from './document-list.service';
@@ -30,17 +30,18 @@ import { ContentNodeDialogService } from '../../content-node-selector/content-no
export class NodeActionsService { export class NodeActionsService {
@Output() @Output()
error: EventEmitter<any> = new EventEmitter<any>(); error = new EventEmitter<any>();
constructor(private contentDialogService: ContentNodeDialogService, constructor(private contentDialogService: ContentNodeDialogService,
public dialogRef: MatDialog, public dialogRef: MatDialog,
public content: ContentService, public content: ContentService,
private documentListService?: DocumentListService, private documentListService?: DocumentListService,
private apiService?: AlfrescoApiService, private apiService?: AlfrescoApiService,
private dialog?: MatDialog) {} private dialog?: MatDialog,
private downloadService?: DownloadService) {}
downloadNode(node: NodeEntry) { downloadNode(node: NodeEntry) {
new NodeDownloadDirective(this.apiService, this.dialog) new NodeDownloadDirective(this.apiService, this.downloadService, this.dialog)
.downloadNode(node); .downloadNode(node);
} }
@@ -50,7 +51,7 @@ export class NodeActionsService {
* @param contentEntry node to copy * @param contentEntry node to copy
* @param permission permission which is needed to apply the action * @param permission permission which is needed to apply the action
*/ */
public copyContent(contentEntry: Node, permission?: string): Subject<string> { copyContent(contentEntry: Node, permission?: string): Subject<string> {
return this.doFileOperation('copy', 'content', contentEntry, permission); return this.doFileOperation('copy', 'content', contentEntry, permission);
} }
@@ -60,7 +61,7 @@ export class NodeActionsService {
* @param contentEntry node to copy * @param contentEntry node to copy
* @param permission permission which is needed to apply the action * @param permission permission which is needed to apply the action
*/ */
public copyFolder(contentEntry: Node, permission?: string): Subject<string> { copyFolder(contentEntry: Node, permission?: string): Subject<string> {
return this.doFileOperation('copy', 'folder', contentEntry, permission); return this.doFileOperation('copy', 'folder', contentEntry, permission);
} }
@@ -70,7 +71,7 @@ export class NodeActionsService {
* @param contentEntry node to move * @param contentEntry node to move
* @param permission permission which is needed to apply the action * @param permission permission which is needed to apply the action
*/ */
public moveContent(contentEntry: Node, permission?: string): Subject<string> { moveContent(contentEntry: Node, permission?: string): Subject<string> {
return this.doFileOperation('move', 'content', contentEntry, permission); return this.doFileOperation('move', 'content', contentEntry, permission);
} }
@@ -80,7 +81,7 @@ export class NodeActionsService {
* @param contentEntry node to move * @param contentEntry node to move
* @param permission permission which is needed to apply the action * @param permission permission which is needed to apply the action
*/ */
public moveFolder(contentEntry: Node, permission?: string): Subject<string> { moveFolder(contentEntry: Node, permission?: string): Subject<string> {
return this.doFileOperation('move', 'folder', contentEntry, permission); return this.doFileOperation('move', 'folder', contentEntry, permission);
} }
@@ -93,7 +94,7 @@ export class NodeActionsService {
* @param permission permission which is needed to apply the action * @param permission permission which is needed to apply the action
*/ */
private doFileOperation(action: string, type: string, contentEntry: Node, permission?: string): Subject<string> { private doFileOperation(action: string, type: string, contentEntry: Node, permission?: string): Subject<string> {
const observable: Subject<string> = new Subject<string>(); const observable = new Subject<string>();
this.contentDialogService this.contentDialogService
.openCopyMoveDialog(action, contentEntry, permission) .openCopyMoveDialog(action, contentEntry, permission)

View File

@@ -20,17 +20,18 @@ import { MatDialog } from '@angular/material';
import { AlfrescoApiService } from '../services/alfresco-api.service'; import { AlfrescoApiService } from '../services/alfresco-api.service';
import { DownloadZipDialogComponent } from '../dialogs/download-zip/download-zip.dialog'; import { DownloadZipDialogComponent } from '../dialogs/download-zip/download-zip.dialog';
import { NodeEntry } from '@alfresco/js-api'; import { NodeEntry } from '@alfresco/js-api';
import { DownloadService } from '../services/download.service';
/** /**
* Directive selectors without adf- prefix will be deprecated on 3.0.0 * Directive selectors without adf- prefix will be deprecated on 3.0.0
*/ */
@Directive({ @Directive({
selector: '[adf-node-download], [adfNodeDownload]' // tslint:disable-next-line: directive-selector
selector: '[adfNodeDownload]'
}) })
export class NodeDownloadDirective { export class NodeDownloadDirective {
/** Nodes to download. */ /** Nodes to download. */
// tslint:disable-next-line:no-input-rename
@Input('adfNodeDownload') @Input('adfNodeDownload')
nodes: NodeEntry | NodeEntry[]; nodes: NodeEntry | NodeEntry[];
@@ -41,6 +42,7 @@ export class NodeDownloadDirective {
constructor( constructor(
private apiService: AlfrescoApiService, private apiService: AlfrescoApiService,
private downloadService: DownloadService,
private dialog: MatDialog) { private dialog: MatDialog) {
} }
@@ -102,7 +104,7 @@ export class NodeDownloadDirective {
const url = contentApi.getContentUrl(id, true); const url = contentApi.getContentUrl(id, true);
const fileName = node.entry.name; const fileName = node.entry.name;
this.download(url, fileName); this.downloadService.downloadUrl(url, fileName);
} }
} }
@@ -120,18 +122,4 @@ export class NodeDownloadDirective {
}); });
} }
} }
private download(url: string, fileName: string) {
if (url && fileName) {
const link = document.createElement('a');
link.style.display = 'none';
link.download = fileName;
link.href = url;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}
}
} }

View File

@@ -86,4 +86,23 @@ export class DownloadService {
downloadJSON(json: any, fileName: string): void { downloadJSON(json: any, fileName: string): void {
this.saveData(json, 'json', fileName); this.saveData(json, 'json', fileName);
} }
/**
* Invokes the download of the file by its URL address.
* @param url Url address pointing to the file.
* @param fileName Name of the file download.
*/
downloadUrl(url: string, fileName: string): void {
if (url && fileName) {
const link = document.createElement('a');
link.style.display = 'none';
link.download = fileName;
link.href = url;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}
}
} }

View File

@@ -26,7 +26,8 @@ import {
FormModel, FormModel,
FormFieldTypes, FormFieldTypes,
FormFieldMetadata, FormFieldMetadata,
FormService FormService,
DownloadService
} from '@alfresco/adf-core'; } from '@alfresco/adf-core';
import { ProcessServiceCloudTestingModule } from '../../../testing/process-service-cloud.testing.module'; import { ProcessServiceCloudTestingModule } from '../../../testing/process-service-cloud.testing.module';
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
@@ -43,6 +44,7 @@ describe('AttachFileCloudWidgetComponent', () => {
let contentCloudNodeSelectorService: ContentCloudNodeSelectorService; let contentCloudNodeSelectorService: ContentCloudNodeSelectorService;
let processCloudContentService: ProcessCloudContentService; let processCloudContentService: ProcessCloudContentService;
let formService: FormService; let formService: FormService;
let downloadService: DownloadService;
const fakePngAnswer = { const fakePngAnswer = {
id: 1155, id: 1155,
@@ -116,6 +118,7 @@ describe('AttachFileCloudWidgetComponent', () => {
}); });
beforeEach(async(() => { beforeEach(async(() => {
downloadService = TestBed.get(DownloadService);
fixture = TestBed.createComponent(AttachFileCloudWidgetComponent); fixture = TestBed.createComponent(AttachFileCloudWidgetComponent);
widget = fixture.componentInstance; widget = fixture.componentInstance;
element = fixture.nativeElement; element = fixture.nativeElement;
@@ -278,22 +281,29 @@ describe('AttachFileCloudWidgetComponent', () => {
it('should download file when download is clicked', (done) => { it('should download file when download is clicked', (done) => {
spyOn(processCloudContentService, 'getRawContentNode').and.returnValue(of(new Blob())); spyOn(processCloudContentService, 'getRawContentNode').and.returnValue(of(new Blob()));
spyOn(processCloudContentService, 'downloadNodeContent').and.stub(); spyOn(processCloudContentService, 'getAuthTicket').and.returnValue(Promise.resolve('ticket'));
spyOn(downloadService, 'downloadUrl').and.stub();
fixture.detectChanges(); fixture.detectChanges();
const menuButton: HTMLButtonElement = <HTMLButtonElement> ( const menuButton: HTMLButtonElement = <HTMLButtonElement> (
fixture.debugElement.query(By.css('#file-1155-option-menu')) fixture.debugElement.query(By.css('#file-1155-option-menu'))
.nativeElement .nativeElement
); );
menuButton.click(); menuButton.click();
fixture.detectChanges(); fixture.detectChanges();
const downloadOption: HTMLButtonElement = <HTMLButtonElement> ( const downloadOption: HTMLButtonElement = <HTMLButtonElement> (
fixture.debugElement.query(By.css('#file-1155-download-file')) fixture.debugElement.query(By.css('#file-1155-download-file'))
.nativeElement .nativeElement
); );
downloadOption.click(); downloadOption.click();
fixture.detectChanges(); fixture.detectChanges();
fixture.whenStable().then(() => { fixture.whenStable().then(() => {
expect(processCloudContentService.downloadNodeContent).toHaveBeenCalled(); expect(downloadService.downloadUrl).toHaveBeenCalled();
done(); done();
}); });
}); });

View File

@@ -23,7 +23,8 @@ import {
LogService, LogService,
ThumbnailService, ThumbnailService,
ContentLinkModel, ContentLinkModel,
NotificationService NotificationService,
baseHost
} from '@alfresco/adf-core'; } from '@alfresco/adf-core';
import { Node, RelatedContentRepresentation } from '@alfresco/js-api'; import { Node, RelatedContentRepresentation } from '@alfresco/js-api';
import { ContentCloudNodeSelectorService } from '../../services/content-cloud-node-selector.service'; import { ContentCloudNodeSelectorService } from '../../services/content-cloud-node-selector.service';
@@ -34,17 +35,7 @@ import { UploadCloudWidgetComponent } from '../upload-cloud.widget';
selector: 'adf-cloud-attach-file-cloud-widget', selector: 'adf-cloud-attach-file-cloud-widget',
templateUrl: './attach-file-cloud-widget.component.html', templateUrl: './attach-file-cloud-widget.component.html',
styleUrls: ['./attach-file-cloud-widget.component.scss'], styleUrls: ['./attach-file-cloud-widget.component.scss'],
host: { host: baseHost,
'(click)': 'event($event)',
'(blur)': 'event($event)',
'(change)': 'event($event)',
'(focus)': 'event($event)',
'(focusin)': 'event($event)',
'(focusout)': 'event($event)',
'(input)': 'event($event)',
'(invalid)': 'event($event)',
'(select)': 'event($event)'
},
encapsulation: ViewEncapsulation.None encapsulation: ViewEncapsulation.None
}) })
export class AttachFileCloudWidgetComponent extends UploadCloudWidgetComponent export class AttachFileCloudWidgetComponent extends UploadCloudWidgetComponent
@@ -52,12 +43,12 @@ export class AttachFileCloudWidgetComponent extends UploadCloudWidgetComponent
static ACS_SERVICE = 'alfresco-content'; static ACS_SERVICE = 'alfresco-content';
constructor( constructor(
public formService: FormService, formService: FormService,
public logger: LogService, logger: LogService,
public thumbnails: ThumbnailService, thumbnails: ThumbnailService,
public processCloudContentService: ProcessCloudContentService, processCloudContentService: ProcessCloudContentService,
public contentNodeSelectorService: ContentCloudNodeSelectorService, notificationService: NotificationService,
notificationService: NotificationService private contentNodeSelectorService: ContentCloudNodeSelectorService
) { ) {
super(formService, thumbnails, processCloudContentService, notificationService, logger); super(formService, thumbnails, processCloudContentService, notificationService, logger);
} }
@@ -127,20 +118,9 @@ export class AttachFileCloudWidgetComponent extends UploadCloudWidgetComponent
} }
downloadContent(file: Node): void { downloadContent(file: Node): void {
this.processCloudContentService this.processCloudContentService.downloadFile(
.getRawContentNode(file.id, this.field.form.contentHost) file.id,
.subscribe( this.field.form.contentHost
(blob: Blob) => {
this.processCloudContentService.downloadNodeContent(
blob,
file.name
);
},
() => {
this.logger.error(
'Impossible retrieve content for download'
);
}
); );
} }
@@ -148,9 +128,10 @@ export class AttachFileCloudWidgetComponent extends UploadCloudWidgetComponent
this.processCloudContentService this.processCloudContentService
.getRawContentNode(file.nodeId, this.field.form.contentHost) .getRawContentNode(file.nodeId, this.field.form.contentHost)
.subscribe( .subscribe(
(blob: Blob) => { blob => {
file.contentBlob = blob; file.contentBlob = blob;
this.fileClicked(file); this.fileClicked(file);
}); }
);
} }
} }

View File

@@ -42,11 +42,11 @@ export class UploadCloudWidgetComponent extends WidgetComponent implements OnIni
fileInput: ElementRef; fileInput: ElementRef;
constructor( constructor(
public formService: FormService, formService: FormService,
private thumbnailService: ThumbnailService, private thumbnailService: ThumbnailService,
public processCloudContentService: ProcessCloudContentService, protected processCloudContentService: ProcessCloudContentService,
private notificationService: NotificationService, protected notificationService: NotificationService,
private logService: LogService) { protected logService: LogService) {
super(formService); super(formService);
} }

View File

@@ -21,7 +21,8 @@ import { catchError, map } from 'rxjs/operators';
import { import {
AlfrescoApiService, AlfrescoApiService,
LogService, LogService,
ContentService ContentService,
DownloadService
} from '@alfresco/adf-core'; } from '@alfresco/adf-core';
import { Node } from '@alfresco/js-api'; import { Node } from '@alfresco/js-api';
@@ -32,7 +33,8 @@ export class ProcessCloudContentService {
constructor( constructor(
private apiService: AlfrescoApiService, private apiService: AlfrescoApiService,
private logService: LogService, private logService: LogService,
public contentService: ContentService public contentService: ContentService,
private downloadService: DownloadService
) {} ) {}
createTemporaryRawRelatedContent( createTemporaryRawRelatedContent(
@@ -40,12 +42,7 @@ export class ProcessCloudContentService {
nodeId: string, nodeId: string,
contentHost: string contentHost: string
): Observable<Node> { ): Observable<Node> {
const changedConfig = this.apiService.lastConfig; this.updateConfig(contentHost);
changedConfig.provider = 'ALL';
changedConfig.hostEcm = contentHost.replace('/alfresco', '');
this.apiService.getInstance().setConfig(changedConfig);
return from( return from(
this.apiService this.apiService
@@ -62,11 +59,8 @@ export class ProcessCloudContentService {
); );
} }
getRawContentNode(nodeId: string, contentHost: string): Observable<any> { getRawContentNode(nodeId: string, contentHost: string): Observable<Blob> {
const changedConfig = this.apiService.lastConfig; this.updateConfig(contentHost);
changedConfig.provider = 'ALL';
changedConfig.hostEcm = contentHost.replace('/alfresco', '');
this.apiService.getInstance().setConfig(changedConfig);
return this.contentService.getNodeContent(nodeId); return this.contentService.getNodeContent(nodeId);
} }
@@ -74,6 +68,38 @@ export class ProcessCloudContentService {
this.contentService.downloadBlob(blob, fileName); this.contentService.downloadBlob(blob, fileName);
} }
async downloadFile(nodeId: string, contentHost: string) {
this.updateConfig(contentHost);
const ticket = await this.getAuthTicket();
const url = this.contentService.getContentUrl(nodeId, true, ticket);
this.downloadService.downloadUrl(url, nodeId);
}
async getAuthTicket(): Promise<string> {
const { auth } = this.apiService.getInstance();
const ticket = await auth.authenticationApi.getTicket();
if (ticket && ticket.entry) {
return ticket.entry.id || '';
}
return '';
}
private updateConfig(contentHost: string) {
const changedConfig = this.apiService.lastConfig;
changedConfig.provider = 'ALL';
if (contentHost) {
changedConfig.hostEcm = contentHost.replace('/alfresco', '');
}
this.apiService.getInstance().setConfig(changedConfig);
}
private handleError(error: any) { private handleError(error: any) {
this.logService.error(error); this.logService.error(error);
return throwError(error || 'Server error'); return throwError(error || 'Server error');