[ACS-437] Update Download UI to use direct access when enabled (#2321)

* [ACS-437] Update Download UI to use direct access when enabled

* Refactor

* Add unit tests

* Refactor by adding separate service

* Moved unit tests to ContentUrlService instead of DownloadEffects

* Move import in app.module.ts

* Fixed review comments

* npm install

* Fix lint error
This commit is contained in:
Thomas Hunter 2021-10-22 14:50:52 +01:00 committed by Denys Vuika
parent 26f1cd84ba
commit 882f9de392
5 changed files with 266 additions and 7 deletions

View File

@ -51,7 +51,9 @@ import {
SitesApi, SitesApi,
SearchApi, SearchApi,
PeopleApi, PeopleApi,
VersionsApi VersionsApi,
DirectAccessUrlEntry,
VersionPaging
} from '@alfresco/js-api'; } from '@alfresco/js-api';
import { map } from 'rxjs/operators'; import { map } from 'rxjs/operators';
@ -344,11 +346,19 @@ export class ContentApiService {
); );
} }
unlockNode(nodeId: string, opts?: any) { unlockNode(nodeId: string, opts?: any): Promise<MinimalNodeEntity> {
return this.nodesApi.unlockNode(nodeId, opts); return this.nodesApi.unlockNode(nodeId, opts);
} }
getNodeVersions(nodeId: string, opts?: any) { getNodeVersions(nodeId: string, opts?: any): Observable<VersionPaging> {
return from(this.versionsApi.listVersionHistory(nodeId, opts)); return from(this.versionsApi.listVersionHistory(nodeId, opts));
} }
requestNodeDirectAccessUrl(nodeId: string): Observable<DirectAccessUrlEntry> {
return from(this.nodesApi.requestDirectAccessUrl(nodeId));
}
requestVersionDirectAccessUrl(nodeId: string, versionId: string): Observable<DirectAccessUrlEntry> {
return from(this.versionsApi.requestDirectAccessUrl(nodeId, versionId));
}
} }

View File

@ -29,7 +29,7 @@ import { RouterModule } from '@angular/router';
import { BrowserAnimationsModule, NoopAnimationsModule } from '@angular/platform-browser/animations'; import { BrowserAnimationsModule, NoopAnimationsModule } from '@angular/platform-browser/animations';
import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { TRANSLATION_PROVIDER, CoreModule, AppConfigService, DebugAppConfigService } from '@alfresco/adf-core'; import { TRANSLATION_PROVIDER, CoreModule, AppConfigService, DebugAppConfigService } from '@alfresco/adf-core';
import { ContentModule } from '@alfresco/adf-content-services'; import { ContentModule, ContentVersionService } from '@alfresco/adf-content-services';
import { SharedModule } from '@alfresco/aca-shared'; import { SharedModule } from '@alfresco/aca-shared';
import { AppComponent } from './app.component'; import { AppComponent } from './app.component';
@ -65,6 +65,7 @@ import { SharedFilesComponent } from './components/shared-files/shared-files.com
import { CreateFromTemplateDialogComponent } from './dialogs/node-template/create-from-template.dialog'; import { CreateFromTemplateDialogComponent } from './dialogs/node-template/create-from-template.dialog';
import { environment } from '../environments/environment'; import { environment } from '../environments/environment';
import { DetailsComponent } from './components/details/details.component'; import { DetailsComponent } from './components/details/details.component';
import { ContentUrlService } from './services/content-url.service';
import { registerLocaleData } from '@angular/common'; import { registerLocaleData } from '@angular/common';
import localeFr from '@angular/common/locales/fr'; import localeFr from '@angular/common/locales/fr';
@ -149,6 +150,7 @@ registerLocaleData(localeSv);
], ],
providers: [ providers: [
{ provide: AppConfigService, useClass: DebugAppConfigService }, { provide: AppConfigService, useClass: DebugAppConfigService },
{ provide: ContentVersionService, useClass: ContentUrlService },
{ {
provide: TRANSLATION_PROVIDER, provide: TRANSLATION_PROVIDER,
multi: true, multi: true,

View File

@ -0,0 +1,158 @@
/*!
* @license
* Alfresco Example Content Application
*
* Copyright (C) 2005 - 2020 Alfresco Software Limited
*
* This file is part of the Alfresco Example Content Application.
* If the software was purchased under a paid Alfresco license, the terms of
* the paid license agreement will prevail. Otherwise, the software is
* provided under the following open source license terms:
*
* The Alfresco Example Content Application is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* The Alfresco Example Content Application is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
import { TestBed } from '@angular/core/testing';
import { AppTestingModule } from '../testing/app-testing.module';
import { MockStore, provideMockStore } from '@ngrx/store/testing';
import { ContentUrlService } from './content-url.service';
import { getRepositoryStatus } from '@alfresco/aca-shared/store';
import { ContentApiService } from '@alfresco/aca-shared';
import { of, throwError } from 'rxjs';
describe('ContentUrlService', () => {
let contentUrlService: ContentUrlService;
let contentApiService: ContentApiService;
let store: MockStore;
const fakeNodeId = 'fake-node-id';
const fakeVersionId = 'fake-version-id';
const fakeNodeContentUrl = 'https://api.example.com/fake-node-content-url';
const fakeNodeDirectAccessUrl = 'https://remote.example.com/fake-node-content-url';
const requestDauMock = {
entry: {
contentUrl: fakeNodeDirectAccessUrl,
attachment: true,
expiryTime: new Date()
}
};
const requestDauErrorMock = { error: 'fakeError' };
const overrideSelectorWithEnabledDAU = () => {
store.overrideSelector(getRepositoryStatus, {
status: {
isDirectAccessUrlEnabled: true
}
} as any);
};
beforeEach(() => {
TestBed.configureTestingModule({
imports: [AppTestingModule],
providers: [provideMockStore()]
});
contentUrlService = TestBed.inject(ContentUrlService);
contentApiService = TestBed.inject(ContentApiService);
store = TestBed.inject(MockStore);
store.overrideSelector(getRepositoryStatus, {
status: {
isDirectAccessUrlEnabled: false
}
} as any);
});
describe('Nodes', () => {
let getContentUrlSpy: jasmine.Spy;
let requestNodeDirectAccessUrlSpy: jasmine.Spy;
beforeEach(() => {
getContentUrlSpy = spyOn(contentApiService, 'getContentUrl').and.returnValue(fakeNodeContentUrl);
});
it('should get the node URL from the content API if DAU is disabled', async () => {
requestNodeDirectAccessUrlSpy = spyOn(contentApiService, 'requestNodeDirectAccessUrl').and.returnValue(of(requestDauMock));
const url = await contentUrlService.getNodeContentUrl(fakeNodeId).toPromise();
expect(url).toBe(fakeNodeContentUrl);
expect(getContentUrlSpy).toHaveBeenCalledWith(fakeNodeId, true);
expect(requestNodeDirectAccessUrlSpy).not.toHaveBeenCalled();
});
it('should get the node URL from remote if DAU is enabled', async () => {
overrideSelectorWithEnabledDAU();
requestNodeDirectAccessUrlSpy = spyOn(contentApiService, 'requestNodeDirectAccessUrl').and.returnValue(of(requestDauMock));
const url = await contentUrlService.getNodeContentUrl(fakeNodeId).toPromise();
expect(url).toBe(fakeNodeDirectAccessUrl);
expect(getContentUrlSpy).not.toHaveBeenCalled();
expect(requestNodeDirectAccessUrlSpy).toHaveBeenCalledWith(fakeNodeId);
});
it('should fallback to getting the node URL from the content API if an error occurs requesting a DAU', async () => {
overrideSelectorWithEnabledDAU();
requestNodeDirectAccessUrlSpy = spyOn(contentApiService, 'requestNodeDirectAccessUrl').and.returnValue(throwError(requestDauErrorMock));
const url = await contentUrlService.getNodeContentUrl(fakeNodeId).toPromise();
expect(url).toBe(fakeNodeContentUrl);
expect(getContentUrlSpy).toHaveBeenCalledWith(fakeNodeId, true);
expect(requestNodeDirectAccessUrlSpy).toHaveBeenCalledWith(fakeNodeId);
});
});
describe('Versions', () => {
let getVersionContentUrlSpy: jasmine.Spy;
let requestVersionDirectAccessUrlSpy: jasmine.Spy;
beforeEach(() => {
getVersionContentUrlSpy = spyOn(contentApiService, 'getVersionContentUrl').and.returnValue(fakeNodeContentUrl);
});
it('should get the version URL from the content API if DAU is disabled', async () => {
requestVersionDirectAccessUrlSpy = spyOn(contentApiService, 'requestVersionDirectAccessUrl').and.returnValue(of(requestDauMock));
const url = await contentUrlService.getVersionContentUrl(fakeNodeId, fakeVersionId).toPromise();
expect(url).toBe(fakeNodeContentUrl);
expect(getVersionContentUrlSpy).toHaveBeenCalledWith(fakeNodeId, fakeVersionId, true);
expect(requestVersionDirectAccessUrlSpy).not.toHaveBeenCalled();
});
it('should get the version URL from remote if DAU is enabled', async () => {
overrideSelectorWithEnabledDAU();
requestVersionDirectAccessUrlSpy = spyOn(contentApiService, 'requestVersionDirectAccessUrl').and.returnValue(of(requestDauMock));
const url = await contentUrlService.getVersionContentUrl(fakeNodeId, fakeVersionId).toPromise();
expect(url).toBe(fakeNodeDirectAccessUrl);
expect(getVersionContentUrlSpy).not.toHaveBeenCalled();
expect(requestVersionDirectAccessUrlSpy).toHaveBeenCalledWith(fakeNodeId, fakeVersionId);
});
it('should fallback to getting the version URL from the content API if an error occurs requesting a DAU', async () => {
overrideSelectorWithEnabledDAU();
requestVersionDirectAccessUrlSpy = spyOn(contentApiService, 'requestVersionDirectAccessUrl').and.returnValue(throwError(requestDauErrorMock));
const url = await contentUrlService.getVersionContentUrl(fakeNodeId, fakeVersionId).toPromise();
expect(url).toBe(fakeNodeContentUrl);
expect(getVersionContentUrlSpy).toHaveBeenCalledWith(fakeNodeId, fakeVersionId, true);
expect(requestVersionDirectAccessUrlSpy).toHaveBeenCalledWith(fakeNodeId, fakeVersionId);
});
});
});

View File

@ -0,0 +1,78 @@
/*!
* @license
* Alfresco Example Content Application
*
* Copyright (C) 2005 - 2020 Alfresco Software Limited
*
* This file is part of the Alfresco Example Content Application.
* If the software was purchased under a paid Alfresco license, the terms of
* the paid license agreement will prevail. Otherwise, the software is
* provided under the following open source license terms:
*
* The Alfresco Example Content Application is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* The Alfresco Example Content Application is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
*/
import { Injectable } from '@angular/core';
import { Observable, of } from 'rxjs';
import { AppStore, getRepositoryStatus } from '@alfresco/aca-shared/store';
import { take, map, catchError, mergeMap } from 'rxjs/operators';
import { Store } from '@ngrx/store';
import { ContentApiService } from '@alfresco/aca-shared';
import { DirectAccessUrlEntry } from '@alfresco/js-api';
import { ContentVersionService } from '@alfresco/adf-content-services';
import { AlfrescoApiService } from '@alfresco/adf-core';
@Injectable({ providedIn: 'root' })
export class ContentUrlService extends ContentVersionService {
constructor(private store: Store<AppStore>, private contentApiService: ContentApiService, alfrescoApiService: AlfrescoApiService) {
super(alfrescoApiService);
}
getNodeContentUrl(nodeId: string, attachment = true): Observable<string> {
return this.isDirectAccessUrlEnabled().pipe(
mergeMap((dauEnabled) => {
if (dauEnabled) {
return this.contentApiService.requestNodeDirectAccessUrl(nodeId).pipe(
map((dauObj: DirectAccessUrlEntry) => dauObj.entry.contentUrl),
catchError(() => of(this.contentApiService.getContentUrl(nodeId, true)))
);
} else {
return of(this.contentApiService.getContentUrl(nodeId, attachment));
}
})
);
}
getVersionContentUrl(nodeId: string, versionId: string, attachment = true): Observable<string> {
return this.isDirectAccessUrlEnabled().pipe(
mergeMap((dauEnabled) => {
if (dauEnabled) {
return this.contentApiService.requestVersionDirectAccessUrl(nodeId, versionId).pipe(
map((dauObj: DirectAccessUrlEntry) => dauObj.entry.contentUrl),
catchError(() => of(this.contentApiService.getVersionContentUrl(nodeId, versionId, true)))
);
} else {
return of(this.contentApiService.getVersionContentUrl(nodeId, versionId, attachment));
}
})
);
}
private isDirectAccessUrlEnabled(): Observable<boolean> {
return this.store.select(getRepositoryStatus).pipe(
take(1),
map((repository) => !!repository?.status?.isDirectAccessUrlEnabled)
);
}
}

View File

@ -32,10 +32,17 @@ import { Actions, Effect, ofType } from '@ngrx/effects';
import { Store } from '@ngrx/store'; import { Store } from '@ngrx/store';
import { map, take } from 'rxjs/operators'; import { map, take } from 'rxjs/operators';
import { ContentApiService } from '@alfresco/aca-shared'; import { ContentApiService } from '@alfresco/aca-shared';
import { ContentUrlService } from '../../services/content-url.service';
@Injectable() @Injectable()
export class DownloadEffects { export class DownloadEffects {
constructor(private store: Store<AppStore>, private actions$: Actions, private contentApi: ContentApiService, private dialog: MatDialog) {} constructor(
private store: Store<AppStore>,
private actions$: Actions,
private contentApi: ContentApiService,
private dialog: MatDialog,
private contentUrlService: ContentUrlService
) {}
@Effect({ dispatch: false }) @Effect({ dispatch: false })
downloadNode$ = this.actions$.pipe( downloadNode$ = this.actions$.pipe(
@ -100,7 +107,9 @@ export class DownloadEffects {
private downloadFile(node: NodeInfo) { private downloadFile(node: NodeInfo) {
if (node && !this.isSharedLinkPreview) { if (node && !this.isSharedLinkPreview) {
this.download(this.contentApi.getContentUrl(node.id, true), node.name); this.contentUrlService.getNodeContentUrl(node.id, true).subscribe((contentUrl) => {
this.download(contentUrl, node.name);
});
} }
if (node && this.isSharedLinkPreview) { if (node && this.isSharedLinkPreview) {
@ -110,7 +119,9 @@ export class DownloadEffects {
private downloadFileVersion(node: NodeInfo, version: Version) { private downloadFileVersion(node: NodeInfo, version: Version) {
if (node && version) { if (node && version) {
this.download(this.contentApi.getVersionContentUrl(node.id, version.id, true), version.name); this.contentUrlService.getVersionContentUrl(node.id, version.id, true).subscribe((contentUrl) => {
this.download(contentUrl, node.name);
});
} }
} }