[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
This commit is contained in:
dhrn 2020-10-26 23:15:37 +05:30 committed by GitHub
parent 1f68bfebb2
commit c6a3da2a74
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 214 additions and 53 deletions

View File

@ -27,4 +27,7 @@ export interface AttachFileWidgetDialogComponentData {
context?: string;
isSelectionValid?: (entry: Node) => boolean;
showFilesInResult?: boolean;
loginOnly?: boolean;
accountIdentifier?: string;
registerExternalHost?: Function;
}

View File

@ -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<AttachFileWidgetDialogComponent>;
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(<NodeEntry> { 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();
});
});
});

View File

@ -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<AttachFileWidgetDialogComponent>) {
(<any> 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() {

View File

@ -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();
});

View File

@ -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<Node[]> {
const selected = new Subject<Node[]>();
selected.subscribe({
complete: this.close.bind(this)
});
openLogin(repository: AlfrescoEndpointRepresentation, currentFolderId = '-my-'): Observable<Node[]> {
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<AlfrescoApiService> {
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<string> {
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<Node[]>();
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();

View File

@ -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 = <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 = <FormFieldMetadata> 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();
});
}));
});

View File

@ -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<boolean>();
@ -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((<any> 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 = [];