From c6a3da2a7493419e4ab796b020194e6a57629b95 Mon Sep 17 00:00:00 2001 From: dhrn <14145706+dhrn@users.noreply.github.com> Date: Mon, 26 Oct 2020 23:15:37 +0530 Subject: [PATCH] [ADF-5170] [upload] not able to upload file/folder from external content service specfic path (#6256) * [ADF-5170] [upload] not able to upload file/folder from external content service specfic path * * minor changes --- ...-file-widget-dialog-component.interface.ts | 3 + ...ttach-file-widget-dialog.component.spec.ts | 44 +++++++++- .../attach-file-widget-dialog.component.ts | 24 +++++- .../attach-file-widget-dialog.service.spec.ts | 2 +- .../attach-file-widget-dialog.service.ts | 68 +++++++++++---- .../attach-file-widget.component.spec.ts | 43 +++++++++- .../attach-file-widget.component.ts | 83 ++++++++++++------- 7 files changed, 214 insertions(+), 53 deletions(-) diff --git a/lib/process-services/src/lib/content-widget/attach-file-widget-dialog-component.interface.ts b/lib/process-services/src/lib/content-widget/attach-file-widget-dialog-component.interface.ts index e193301b12..beeda4bb04 100644 --- a/lib/process-services/src/lib/content-widget/attach-file-widget-dialog-component.interface.ts +++ b/lib/process-services/src/lib/content-widget/attach-file-widget-dialog-component.interface.ts @@ -27,4 +27,7 @@ export interface AttachFileWidgetDialogComponentData { context?: string; isSelectionValid?: (entry: Node) => boolean; showFilesInResult?: boolean; + loginOnly?: boolean; + accountIdentifier?: string; + registerExternalHost?: Function; } diff --git a/lib/process-services/src/lib/content-widget/attach-file-widget-dialog.component.spec.ts b/lib/process-services/src/lib/content-widget/attach-file-widget-dialog.component.spec.ts index 5ee8151c7a..879fa3bc03 100644 --- a/lib/process-services/src/lib/content-widget/attach-file-widget-dialog.component.spec.ts +++ b/lib/process-services/src/lib/content-widget/attach-file-widget-dialog.component.spec.ts @@ -16,7 +16,7 @@ */ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { MAT_DIALOG_DATA } from '@angular/material/dialog'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { ContentModule, ContentNodeSelectorPanelComponent, DocumentListService } from '@alfresco/adf-content-services'; import { EventEmitter, NO_ERRORS_SCHEMA } from '@angular/core'; import { ProcessTestingModule } from '../testing/process.testing.module'; @@ -44,6 +44,8 @@ describe('AttachFileWidgetDialogComponent', () => { let siteService: SitesService; let nodeService: NodesApiService; let documentListService: DocumentListService; + let apiService: AlfrescoApiService; + let matDialogRef: MatDialogRef; let isLogged = false; const fakeSite = new SiteEntry({ entry: { id: 'fake-site', guid: 'fake-site', title: 'fake-site', visibility: 'visible' } }); @@ -56,7 +58,8 @@ describe('AttachFileWidgetDialogComponent', () => { ], providers: [ { provide: MAT_DIALOG_DATA, useValue: data }, - { provide: AlfrescoApiService, useClass: AlfrescoApiServiceMock } + { provide: AlfrescoApiService, useClass: AlfrescoApiServiceMock }, + { provide: MatDialogRef, useValue: { close: () => of() } } ], schemas: [NO_ERRORS_SCHEMA] }); @@ -69,6 +72,8 @@ describe('AttachFileWidgetDialogComponent', () => { siteService = fixture.debugElement.injector.get(SitesService); nodeService = fixture.debugElement.injector.get(NodesApiService); documentListService = fixture.debugElement.injector.get(DocumentListService); + matDialogRef = fixture.debugElement.injector.get(MatDialogRef); + apiService = fixture.debugElement.injector.get(AlfrescoApiService); spyOn(documentListService, 'getFolderNode').and.returnValue(of( { entry: { path: { elements: [] } } })); spyOn(documentListService, 'getFolder').and.returnValue(throwError('No results for test')); @@ -169,4 +174,39 @@ describe('AttachFileWidgetDialogComponent', () => { expect(titleElement.nativeElement.innerText).toBe('ATTACH-FILE.ACTIONS.CHOOSE_ITEM'); }); }); + + describe('login only', () => { + beforeEach(async(() => { + spyOn(authService, 'login').and.returnValue(of({ type: 'type', ticket: 'ticket'})); + spyOn(matDialogRef, 'close').and.callThrough(); + fixture.detectChanges(); + widget.data.loginOnly = true; + widget.data.registerExternalHost = () => {}; + isLogged = false; + })); + + it('should close the dialog once user loggedIn', () => { + fixture.detectChanges(); + isLogged = true; + const loginButton: HTMLButtonElement = element.querySelector('button[data-automation-id="attach-file-dialog-actions-login"]'); + const usernameInput: HTMLInputElement = element.querySelector('#username'); + const passwordInput: HTMLInputElement = element.querySelector('#password'); + usernameInput.value = 'fake-user'; + passwordInput.value = 'fake-user'; + usernameInput.dispatchEvent(new Event('input')); + passwordInput.dispatchEvent(new Event('input')); + loginButton.click(); + authService.onLogin.next('logged In'); + fixture.detectChanges(); + expect(matDialogRef.close).toHaveBeenCalled(); + }); + + it('should close the dialog immediately if user already loggedIn', () => { + isLogged = true; + fixture.detectChanges(); + spyOn(apiService, 'getInstance').and.returnValue({ isLoggedIn: () => true }); + widget.updateExternalHost(); + expect(matDialogRef.close).toHaveBeenCalled(); + }); + }); }); diff --git a/lib/process-services/src/lib/content-widget/attach-file-widget-dialog.component.ts b/lib/process-services/src/lib/content-widget/attach-file-widget-dialog.component.ts index 5b42560a73..c58b1bc9aa 100644 --- a/lib/process-services/src/lib/content-widget/attach-file-widget-dialog.component.ts +++ b/lib/process-services/src/lib/content-widget/attach-file-widget-dialog.component.ts @@ -16,7 +16,7 @@ */ import { Component, Inject, ViewEncapsulation, ViewChild } from '@angular/core'; -import { MAT_DIALOG_DATA } from '@angular/material/dialog'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { ExternalAlfrescoApiService, AlfrescoApiService, LoginDialogPanelComponent, SearchService, TranslationService, AuthenticationService, SitesService } from '@alfresco/adf-core'; import { AttachFileWidgetDialogComponentData } from './attach-file-widget-dialog-component.interface'; import { DocumentListService } from '@alfresco/adf-content-services'; @@ -32,7 +32,7 @@ import { Node } from '@alfresco/js-api'; DocumentListService, SitesService, SearchService, - { provide: AlfrescoApiService, useClass: ExternalAlfrescoApiService} ] + { provide: AlfrescoApiService, useClass: ExternalAlfrescoApiService } ] }) export class AttachFileWidgetDialogComponent { @@ -46,11 +46,29 @@ export class AttachFileWidgetDialogComponent { constructor(private translation: TranslationService, @Inject(MAT_DIALOG_DATA) public data: AttachFileWidgetDialogComponentData, - private externalApiService: AlfrescoApiService) { + private externalApiService: AlfrescoApiService, + private authenticationService: AuthenticationService, + private matDialogRef: MatDialogRef) { ( externalApiService).init(data.ecmHost, data.context); this.action = data.actionName ? data.actionName.toUpperCase() : 'CHOOSE'; this.buttonActionName = `ATTACH-FILE.ACTIONS.${this.action}`; this.updateTitle('DROPDOWN.MY_FILES_OPTION'); + this.updateExternalHost(); + } + + updateExternalHost() { + this.authenticationService.onLogin.subscribe(() => this.registerAndClose()); + if (this.externalApiService.getInstance().isLoggedIn()) { + this.registerAndClose(); + } + } + + private registerAndClose() { + this.data.registerExternalHost(this.data.accountIdentifier, this.externalApiService); + if (this.data.loginOnly) { + this.data.selected.complete(); + this.matDialogRef.close(); + } } isLoggedIn() { diff --git a/lib/process-services/src/lib/content-widget/attach-file-widget-dialog.service.spec.ts b/lib/process-services/src/lib/content-widget/attach-file-widget-dialog.service.spec.ts index 4ae70c3b20..9fdeaac2d1 100644 --- a/lib/process-services/src/lib/content-widget/attach-file-widget-dialog.service.spec.ts +++ b/lib/process-services/src/lib/content-widget/attach-file-widget-dialog.service.spec.ts @@ -49,7 +49,7 @@ describe('AttachFileWidgetDialogService', () => { }); it('should be able to open the dialog when node has permission', () => { - service.openLogin('fake-title', 'fake-action'); + service.openLogin({ id: 1, name: 'fake-title', repositoryUrl: 'http://fakeurl.com/alfresco' }, 'fake-action'); expect(spyOnDialogOpen).toHaveBeenCalled(); }); diff --git a/lib/process-services/src/lib/content-widget/attach-file-widget-dialog.service.ts b/lib/process-services/src/lib/content-widget/attach-file-widget-dialog.service.ts index d640fc33dd..d95858208d 100644 --- a/lib/process-services/src/lib/content-widget/attach-file-widget-dialog.service.ts +++ b/lib/process-services/src/lib/content-widget/attach-file-widget-dialog.service.ts @@ -17,17 +17,19 @@ import { MatDialog } from '@angular/material/dialog'; import { EventEmitter, Injectable, Output } from '@angular/core'; -import { TranslationService } from '@alfresco/adf-core'; -import { Subject, Observable } from 'rxjs'; +import { AlfrescoApiService, TranslationService } from '@alfresco/adf-core'; +import { Observable, of, Subject } from 'rxjs'; import { AttachFileWidgetDialogComponentData } from './attach-file-widget-dialog-component.interface'; -import { Node } from '@alfresco/js-api'; +import { AlfrescoEndpointRepresentation, Node } from '@alfresco/js-api'; import { AttachFileWidgetDialogComponent } from './attach-file-widget-dialog.component'; +import { switchMap } from 'rxjs/operators'; @Injectable({ providedIn: 'root' }) // tslint:disable-next-line: directive-class-suffix export class AttachFileWidgetDialogService { + private externalApis: { [key: string]: AlfrescoApiService } = {}; /** Emitted when an error occurs. */ @Output() @@ -39,25 +41,20 @@ export class AttachFileWidgetDialogService { /** * Opens a dialog to choose a file to upload. - * @param action Name of the action to show in the title - * @param contentEntry Item to upload + * @param repository Alfresco endpoint that represents the content service + * @param currentFolderId Upload file from specific folder * @returns Information about the chosen file(s) */ - openLogin(ecmHost: string, actionName?: string, context?: string): Observable { - const selected = new Subject(); - selected.subscribe({ - complete: this.close.bind(this) - }); - + openLogin(repository: AlfrescoEndpointRepresentation, currentFolderId = '-my-'): Observable { + const { title, ecmHost, selected, registerExternalHost } = this.constructPayload(repository); const data: AttachFileWidgetDialogComponentData = { - title : this.getLoginTitleTranslation(ecmHost), - actionName, + title, selected, ecmHost, - currentFolderId: '-my-', - context, + currentFolderId, isSelectionValid: (entry: Node) => entry.isFile, - showFilesInResult: true + showFilesInResult: true, + registerExternalHost }; this.openLoginDialog(data, 'adf-attach-file-widget-dialog', '630px'); @@ -68,6 +65,45 @@ export class AttachFileWidgetDialogService { this.dialog.open(AttachFileWidgetDialogComponent, { data, panelClass: currentPanelClass, width: chosenWidth }); } + private showExternalHostLoginDialog(repository: AlfrescoEndpointRepresentation): Observable { + const data = { + ...this.constructPayload(repository), + loginOnly: true + }; + return this.dialog.open(AttachFileWidgetDialogComponent, { data, panelClass: 'adf-attach-file-widget-dialog', width: '630px' }) + .afterClosed(); + } + + downloadURL(repository: AlfrescoEndpointRepresentation, sourceId: string): Observable { + const { accountIdentifier } = this.constructPayload(repository); + + if (this.externalApis[accountIdentifier]?.getInstance()?.isLoggedIn()) { + return of(this.externalApis[accountIdentifier].contentApi.getContentUrl(sourceId)); + } + + return this.showExternalHostLoginDialog(repository).pipe( + switchMap(() => of(this.externalApis[accountIdentifier].getInstance().content.getContentUrl(sourceId))) + ); + } + + private constructPayload(repository: AlfrescoEndpointRepresentation) { + const accountIdentifier = 'alfresco-' + repository.id + '-' + repository.name; + const ecmHost = repository.repositoryUrl.replace('/alfresco', ''); + const selected = new Subject(); + selected.subscribe({ + complete: this.close.bind(this) + }); + const title = this.getLoginTitleTranslation(ecmHost); + const registerExternalHost = this.addService.bind(this); + return { ecmHost, accountIdentifier, selected, title, registerExternalHost }; + } + + addService(accountIdentifier: string, apiService: AlfrescoApiService) { + if (!this.externalApis[accountIdentifier]) { + this.externalApis[accountIdentifier] = apiService; + } + } + /** Closes the currently open dialog. */ close() { this.dialog.closeAll(); diff --git a/lib/process-services/src/lib/content-widget/attach-file-widget.component.spec.ts b/lib/process-services/src/lib/content-widget/attach-file-widget.component.spec.ts index fc119680e7..1c8864f1ac 100644 --- a/lib/process-services/src/lib/content-widget/attach-file-widget.component.spec.ts +++ b/lib/process-services/src/lib/content-widget/attach-file-widget.component.spec.ts @@ -34,6 +34,7 @@ import { of } from 'rxjs'; import { Node } from '@alfresco/js-api'; import { ProcessTestingModule } from '../testing/process.testing.module'; import { TranslateModule } from '@ngx-translate/core'; +import { AttachFileWidgetDialogService } from './attach-file-widget-dialog.service'; const fakeRepositoryListAnswer = [ { @@ -50,7 +51,16 @@ const fakeRepositoryListAnswer = [ 'metaDataAllowed': true, 'name': 'GOKUSHARE', 'repositoryUrl': 'http://localhost:0000/GOKUSHARE' - }]; + }, + { + 'authorized': true, + 'serviceId': 'alfresco-2000-external', + 'metaDataAllowed': true, + 'name': 'external', + 'repositoryUrl': 'http://externalhost.com/alfresco', + 'id': 2000 + } +]; const onlyLocalParams = { fileSource: { @@ -82,6 +92,16 @@ const definedSourceParams = { } }; +const externalDefinedSourceParams = { + fileSource: { + serviceId: 'external-sources', + name: 'external', + selectedFolder: { + accountId: 'external-account-id' + } + } +}; + const fakeMinimalNode: Node = { id: 'fake', name: 'fake-name', @@ -130,6 +150,7 @@ describe('AttachFileWidgetComponent', () => { let processContentService: ProcessContentService; let downloadService: DownloadService; let formService: FormService; + let attachFileWidgetDialogService: AttachFileWidgetDialogService; setupTestBed({ imports: [ @@ -148,6 +169,7 @@ describe('AttachFileWidgetComponent', () => { processContentService = TestBed.inject(ProcessContentService); downloadService = TestBed.inject(DownloadService); formService = TestBed.inject(FormService); + attachFileWidgetDialogService = TestBed.inject(AttachFileWidgetDialogService); })); afterEach(() => { @@ -532,4 +554,23 @@ describe('AttachFileWidgetComponent', () => { expect(showOption.disabled).toBeTruthy(); }); }); + + it('should be able to upload files when a defined folder from external content service', async(() => { + widget.field = new FormFieldModel(new FormModel(), { type: FormFieldTypes.UPLOAD, value: [] }); + widget.field.id = 'attach-external-file-attach'; + widget.field.params = externalDefinedSourceParams; + spyOn(activitiContentService, 'getAlfrescoRepositories').and.returnValue(of(fakeRepositoryListAnswer)); + spyOn(activitiContentService, 'applyAlfrescoNode').and.returnValue(of(fakePngAnswer)); + spyOn(attachFileWidgetDialogService, 'openLogin').and.returnValue(of([fakeMinimalNode])); + fixture.detectChanges(); + fixture.whenStable().then(() => { + fixture.detectChanges(); + const attachButton: HTMLButtonElement = element.querySelector('#attach-external-file-attach'); + attachButton.click(); + fixture.detectChanges(); + fixture.debugElement.query(By.css('#attach-external')).nativeElement.click(); + fixture.detectChanges(); + expect(element.querySelector('#file-1155-icon')).not.toBeNull(); + }); + })); }); diff --git a/lib/process-services/src/lib/content-widget/attach-file-widget.component.ts b/lib/process-services/src/lib/content-widget/attach-file-widget.component.ts index 2c060f22fe..4671a489c2 100644 --- a/lib/process-services/src/lib/content-widget/attach-file-widget.component.ts +++ b/lib/process-services/src/lib/content-widget/attach-file-widget.component.ts @@ -17,22 +17,27 @@ /* tslint:disable:component-selector */ -import { Component, ViewEncapsulation, OnInit, OnDestroy } from '@angular/core'; +import { Component, OnDestroy, OnInit, ViewEncapsulation } from '@angular/core'; import { - UploadWidgetComponent, + ActivitiContentService, + AppConfigService, + AppConfigValues, + ContentService, + DownloadService, FormService, LogService, - ThumbnailService, ProcessContentService, - ActivitiContentService, - AppConfigValues, - AppConfigService, - DownloadService, - ContentService + ThumbnailService, + UploadWidgetComponent } from '@alfresco/adf-core'; import { ContentNodeDialogService } from '@alfresco/adf-content-services'; -import { Node, RelatedContentRepresentation, NodeChildAssociation } from '@alfresco/js-api'; -import { from, zip, of, Subject } from 'rxjs'; +import { + AlfrescoEndpointRepresentation, + Node, + NodeChildAssociation, + RelatedContentRepresentation +} from '@alfresco/js-api'; +import { from, of, Subject, zip } from 'rxjs'; import { mergeMap, takeUntil } from 'rxjs/operators'; import { AttachFileWidgetDialogService } from './attach-file-widget-dialog.service'; @@ -56,7 +61,7 @@ import { AttachFileWidgetDialogService } from './attach-file-widget-dialog.servi export class AttachFileWidgetComponent extends UploadWidgetComponent implements OnInit, OnDestroy { typeId = 'AttachFileWidgetComponent'; - repositoryList = []; + repositoryList: AlfrescoEndpointRepresentation[] = []; private tempFilesList = []; private onDestroy$ = new Subject(); @@ -126,9 +131,7 @@ export class AttachFileWidgetComponent extends UploadWidgetComponent implements } isDefinedSourceFolder(): boolean { - return !!this.field.params && - !!this.field.params.fileSource && - !!this.field.params.fileSource.selectedFolder; + return !!this.field.params?.fileSource?.selectedFolder; } isTemporaryFile(file): boolean { @@ -141,7 +144,10 @@ export class AttachFileWidgetComponent extends UploadWidgetComponent implements openSelectDialogFromFileSource() { const params = this.field.params; - if (this.isDefinedSourceFolder()) { + const repository = this.repositoryList.find((repo) => repo.name === params?.fileSource?.name); + if (repository && this.isExternalHost(repository)) { + this.uploadFileFromExternalCS(repository, params?.fileSource?.selectedFolder?.pathId); + } else { this.contentDialog.openFileBrowseDialogByFolderId(params.fileSource.selectedFolder.pathId).subscribe( (selections: Node[]) => { this.tempFilesList.push(...selections); @@ -188,8 +194,15 @@ export class AttachFileWidgetComponent extends UploadWidgetComponent implements } } if (file.sourceId) { - const nodeUrl = this.contentService.getContentUrl(file.sourceId); - this.downloadService.downloadUrl(nodeUrl, file.name); + const sourceHost = this.findSource(file.source); + if (sourceHost && this.isExternalHost(sourceHost)) { + this.attachDialogService.downloadURL(sourceHost, file.sourceId).subscribe((nodeUrl) => { + this.downloadService.downloadUrl(nodeUrl, file.name); + }); + } else { + const nodeUrl = this.contentService.getContentUrl(file.sourceId); + this.downloadService.downloadUrl(nodeUrl, file.name); + } } else { this.processContentService.getFileRawContent(( file).id).subscribe( (blob: Blob) => { @@ -202,27 +215,37 @@ export class AttachFileWidgetComponent extends UploadWidgetComponent implements } } - openSelectDialog(repository) { - const accountIdentifier = 'alfresco-' + repository.id + '-' + repository.name; - const currentECMHost = this.getDomainHost(this.appConfigService.get(AppConfigValues.ECMHOST)); - const chosenRepositoryHost = this.getDomainHost(repository.repositoryUrl); - if (chosenRepositoryHost !== currentECMHost) { - const formattedRepositoryHost = repository.repositoryUrl.replace('/alfresco', ''); - this.attachDialogService.openLogin(formattedRepositoryHost).subscribe( - (selections: any[]) => { - selections.forEach((node) => node.isExternal = true); - this.tempFilesList.push(...selections); - this.uploadFileFromCS(selections, accountIdentifier); - }); + openSelectDialog(repository: AlfrescoEndpointRepresentation) { + if (this.isExternalHost(repository)) { + this.uploadFileFromExternalCS(repository); } else { this.contentDialog.openFileBrowseDialogByDefaultLocation().subscribe( (selections: Node[]) => { this.tempFilesList.push(...selections); - this.uploadFileFromCS(selections, accountIdentifier); + this.uploadFileFromCS(selections, `alfresco-${repository.id}-${repository.name}`); }); } } + private isExternalHost(repository: AlfrescoEndpointRepresentation): boolean { + const currentECMHost = this.getDomainHost(this.appConfigService.get(AppConfigValues.ECMHOST)); + const chosenRepositoryHost = this.getDomainHost(repository.repositoryUrl); + return chosenRepositoryHost !== currentECMHost; + } + + private findSource(sourceIdentifier: string): AlfrescoEndpointRepresentation { + return this.repositoryList.find(repository => sourceIdentifier === `alfresco-${repository.id}-${repository.name}`); + } + + private uploadFileFromExternalCS(repository: AlfrescoEndpointRepresentation, currentFolderId?: string) { + this.attachDialogService.openLogin(repository, currentFolderId).subscribe( + (selections: any[]) => { + selections.forEach((node) => node.isExternal = true); + this.tempFilesList.push(...selections); + this.uploadFileFromCS(selections, `alfresco-${repository.id}-${repository.name}`); + }); + } + private uploadFileFromCS(fileNodeList: any[], accountId: string, siteId?: string) { const filesSaved = [];