diff --git a/projects/aca-content/assets/i18n/en.json b/projects/aca-content/assets/i18n/en.json
index 288db43ea..7bc908ee2 100644
--- a/projects/aca-content/assets/i18n/en.json
+++ b/projects/aca-content/assets/i18n/en.json
@@ -150,6 +150,11 @@
"TOOLTIP": "Access my favorite libraries"
}
}
+ },
+ "ERRORS": {
+ "LIBRARY_NOT_FOUND": "This library no longer exists.",
+ "LIBRARY_NO_PERMISSIONS": "You do not have permission to view this library.",
+ "LIBRARY_LOADING_ERROR": "There was some problem during loading this library."
}
},
"SHARED": {
diff --git a/projects/aca-content/src/lib/components/files/files.component.html b/projects/aca-content/src/lib/components/files/files.component.html
index 5fef7b66b..28d71d37d 100644
--- a/projects/aca-content/src/lib/components/files/files.component.html
+++ b/projects/aca-content/src/lib/components/files/files.component.html
@@ -16,7 +16,7 @@
@@ -47,7 +47,7 @@
(name-click)="handleNodeClick($event)"
(selectedItemsCountChanged)="onSelectedItemsCountChanged($event)"
(filterSelection)="onFilterSelected($event)"
- (error)="onError()"
+ (error)="onError($event)"
>
diff --git a/projects/aca-content/src/lib/components/files/files.component.spec.ts b/projects/aca-content/src/lib/components/files/files.component.spec.ts
index d7f16e5a1..d4b509e07 100644
--- a/projects/aca-content/src/lib/components/files/files.component.spec.ts
+++ b/projects/aca-content/src/lib/components/files/files.component.spec.ts
@@ -29,7 +29,7 @@ import { DocumentListService, FilterSearch, UploadService } from '@alfresco/adf-
import { NodeActionsService } from '../../services/node-actions.service';
import { FilesComponent } from './files.component';
import { AppTestingModule } from '../../testing/app-testing.module';
-import { AppExtensionService, ContentApiService, DocumentBasePageService, initialState } from '@alfresco/aca-shared';
+import { AppExtensionService, ContentApiService, DocumentBasePageService, GenericErrorComponent, initialState } from '@alfresco/aca-shared';
import { of, Subject, throwError } from 'rxjs';
import { By } from '@angular/platform-browser';
import { NodeEntry, NodePaging, Node, PathElement } from '@alfresco/js-api';
@@ -38,6 +38,8 @@ import { MatSnackBarModule } from '@angular/material/snack-bar';
import { testHeader } from '../../testing/document-base-page-utils';
import { MockStore, provideMockStore } from '@ngrx/store/testing';
import { getCurrentFolder } from '@alfresco/aca-shared/store';
+import { UnitTestingUtils } from '@alfresco/adf-core';
+import { HttpErrorResponse } from '@angular/common/http';
describe('FilesComponent', () => {
let node;
@@ -57,6 +59,7 @@ describe('FilesComponent', () => {
let spyContent = null;
let loadFolderByNodeIdSpy: jasmine.Spy;
+ let unitTestingUtils: UnitTestingUtils;
function verifyEmptyFilterTemplate() {
const template = fixture.debugElement.query(By.css('.empty-search__block')).nativeElement as HTMLElement;
@@ -108,6 +111,7 @@ describe('FilesComponent', () => {
extensions = TestBed.inject(AppExtensionService);
store = TestBed.inject(MockStore);
spyContent = spyOn(contentApi, 'getNode');
+ unitTestingUtils = new UnitTestingUtils(fixture.debugElement);
});
beforeEach(() => {
@@ -122,7 +126,7 @@ describe('FilesComponent', () => {
});
it('should be a valid current page', fakeAsync(() => {
- spyContent.and.returnValue(throwError(null));
+ spyContent.and.returnValue(throwError(() => new HttpErrorResponse({ status: 404 })));
component.ngOnInit();
fixture.detectChanges();
@@ -521,5 +525,177 @@ describe('FilesComponent', () => {
});
});
+ describe('Generic error', () => {
+ const getGenericErrorText = () => unitTestingUtils.getByDirective(GenericErrorComponent).componentInstance.text;
+
+ beforeEach(() => {
+ router.url = '/libraries';
+ });
+
+ describe('error returned by getNode on contentApi', () => {
+ it('should have set text to library no permission error if user does not have permission and actual url is libraries', () => {
+ spyContent.and.returnValue(
+ throwError(
+ () =>
+ new HttpErrorResponse({
+ status: 403
+ })
+ )
+ );
+ fixture.detectChanges();
+
+ expect(getGenericErrorText()).toBe('APP.BROWSE.LIBRARIES.ERRORS.LIBRARY_NO_PERMISSIONS');
+ });
+
+ it('should have set text to library not found error if library does not exist and actual url is libraries', () => {
+ spyContent.and.returnValue(
+ throwError(
+ () =>
+ new HttpErrorResponse({
+ status: 404
+ })
+ )
+ );
+ fixture.detectChanges();
+
+ expect(getGenericErrorText()).toBe('APP.BROWSE.LIBRARIES.ERRORS.LIBRARY_NOT_FOUND');
+ });
+
+ it('should have set text to generic library loading error if there is different problem with loading of library and actual url is libraries', () => {
+ spyContent.and.returnValue(
+ throwError(
+ () =>
+ new HttpErrorResponse({
+ status: 500
+ })
+ )
+ );
+ fixture.detectChanges();
+
+ expect(getGenericErrorText()).toBe('APP.BROWSE.LIBRARIES.ERRORS.LIBRARY_LOADING_ERROR');
+ });
+
+ it('should have set text to generic error if user does not have permission and actual url is not libraries', () => {
+ router.url = '/personal-files';
+ spyContent.and.returnValue(
+ throwError(
+ () =>
+ new HttpErrorResponse({
+ status: 403
+ })
+ )
+ );
+ fixture.detectChanges();
+
+ expect(getGenericErrorText()).toBe('APP.MESSAGES.ERRORS.MISSING_CONTENT');
+ });
+
+ it('should have set text to generic error if library does not exist and actual url is not libraries', () => {
+ router.url = '/personal-files';
+ spyContent.and.returnValue(
+ throwError(
+ () =>
+ new HttpErrorResponse({
+ status: 404
+ })
+ )
+ );
+ fixture.detectChanges();
+
+ expect(getGenericErrorText()).toBe('APP.MESSAGES.ERRORS.MISSING_CONTENT');
+ });
+
+ it('should have set text to generic error if there is different problem with loading of node and actual url is not libraries', () => {
+ router.url = '/personal-files';
+ spyContent.and.returnValue(
+ throwError(
+ () =>
+ new HttpErrorResponse({
+ status: 500
+ })
+ )
+ );
+ fixture.detectChanges();
+
+ expect(getGenericErrorText()).toBe('APP.MESSAGES.ERRORS.MISSING_CONTENT');
+ });
+ });
+
+ describe('error emitted by error event', () => {
+ beforeEach(() => {
+ fixture.detectChanges();
+ });
+
+ it('should have set text to library no permission error if user does not have permission issue and actual url is libraries', () => {
+ component.documentList.error.emit(
+ new HttpErrorResponse({
+ status: 403
+ })
+ );
+ fixture.detectChanges();
+
+ expect(getGenericErrorText()).toBe('APP.BROWSE.LIBRARIES.ERRORS.LIBRARY_NO_PERMISSIONS');
+ });
+
+ it('should have set text to library not found error if library does not exist issue and actual url is libraries', () => {
+ component.documentList.error.emit(
+ new HttpErrorResponse({
+ status: 404
+ })
+ );
+ fixture.detectChanges();
+
+ expect(getGenericErrorText()).toBe('APP.BROWSE.LIBRARIES.ERRORS.LIBRARY_NOT_FOUND');
+ });
+
+ it('should have set text to generic library loading error if there is different problem with loading of library and actual url is libraries', () => {
+ component.documentList.error.emit(
+ new HttpErrorResponse({
+ status: 500
+ })
+ );
+ fixture.detectChanges();
+
+ expect(getGenericErrorText()).toBe('APP.BROWSE.LIBRARIES.ERRORS.LIBRARY_LOADING_ERROR');
+ });
+
+ it('should have set text to generic error if user does not have permission issue and actual url is not libraries', () => {
+ router.url = '/personal-files';
+ component.documentList.error.emit(
+ new HttpErrorResponse({
+ status: 403
+ })
+ );
+ fixture.detectChanges();
+
+ expect(getGenericErrorText()).toBe('APP.MESSAGES.ERRORS.MISSING_CONTENT');
+ });
+
+ it('should have set text to generic error if library does not exist issue and actual url is not libraries', () => {
+ router.url = '/personal-files';
+ component.documentList.error.emit(
+ new HttpErrorResponse({
+ status: 404
+ })
+ );
+ fixture.detectChanges();
+
+ expect(getGenericErrorText()).toBe('APP.MESSAGES.ERRORS.MISSING_CONTENT');
+ });
+
+ it('should have set text to generic error if there is different problem with loading of node and actual url is not libraries', () => {
+ router.url = '/personal-files';
+ component.documentList.error.emit(
+ new HttpErrorResponse({
+ status: 500
+ })
+ );
+ fixture.detectChanges();
+
+ expect(getGenericErrorText()).toBe('APP.MESSAGES.ERRORS.MISSING_CONTENT');
+ });
+ });
+ });
+
testHeader(FilesComponent);
});
diff --git a/projects/aca-content/src/lib/components/files/files.component.ts b/projects/aca-content/src/lib/components/files/files.component.ts
index af635f12c..6a2ca23b5 100644
--- a/projects/aca-content/src/lib/components/files/files.component.ts
+++ b/projects/aca-content/src/lib/components/files/files.component.ts
@@ -60,6 +60,7 @@ import { DocumentListDirective } from '../../directives/document-list.directive'
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
import { SearchAiInputContainerComponent } from '../knowledge-retrieval/search-ai/search-ai-input-container/search-ai-input-container.component';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
+import { HttpErrorResponse } from '@angular/common/http';
@Component({
standalone: true,
@@ -95,11 +96,16 @@ export class FilesComponent extends PageComponent implements OnInit, OnDestroy {
selectedNode: NodeEntry;
queryParams = null;
showLoader$ = this.store.select(showLoaderSelector);
- private nodePath: PathElement[];
-
columns: DocumentListPresetRef[] = [];
isFilterHeaderActive = false;
+ private nodePath: PathElement[];
+ private _errorTranslationKey = 'APP.MESSAGES.ERRORS.MISSING_CONTENT';
+
+ get errorTranslationKey(): string {
+ return this._errorTranslationKey;
+ }
+
constructor(private contentApi: ContentApiService, private nodeActionsService: NodeActionsService, private route: ActivatedRoute) {
super();
}
@@ -132,7 +138,7 @@ export class FilesComponent extends PageComponent implements OnInit, OnDestroy {
});
}
},
- () => (this.isValidPath = false)
+ (error: HttpErrorResponse) => this.onError(error)
);
});
@@ -394,7 +400,19 @@ export class FilesComponent extends PageComponent implements OnInit, OnDestroy {
void this.router.navigate([], { relativeTo: this.route, queryParams: objectFromMap });
}
- onError() {
+ onError(error: HttpErrorResponse) {
this.isValidPath = false;
+ if (this.router.url.includes('libraries')) {
+ switch (error.status) {
+ case 403:
+ this._errorTranslationKey = 'APP.BROWSE.LIBRARIES.ERRORS.LIBRARY_NO_PERMISSIONS';
+ break;
+ case 404:
+ this._errorTranslationKey = 'APP.BROWSE.LIBRARIES.ERRORS.LIBRARY_NOT_FOUND';
+ break;
+ default:
+ this._errorTranslationKey = 'APP.BROWSE.LIBRARIES.ERRORS.LIBRARY_LOADING_ERROR';
+ }
+ }
}
}
diff --git a/projects/aca-content/src/lib/store/effects/library.effects.spec.ts b/projects/aca-content/src/lib/store/effects/library.effects.spec.ts
new file mode 100644
index 000000000..d53651bfb
--- /dev/null
+++ b/projects/aca-content/src/lib/store/effects/library.effects.spec.ts
@@ -0,0 +1,76 @@
+/*!
+ * Copyright © 2005-2025 Hyland Software, Inc. and its affiliates. All rights reserved.
+ *
+ * Alfresco Example Content Application
+ *
+ * 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
+ * from Hyland Software. If not, see .
+ */
+
+import { AppStore, NavigateLibraryAction } from '@alfresco/aca-shared/store';
+import { Store } from '@ngrx/store';
+import { TestBed } from '@angular/core/testing';
+import { ContentApiService } from '@alfresco/aca-shared';
+import { Subject } from 'rxjs';
+import { HttpErrorResponse } from '@angular/common/http';
+import { NotificationService } from '@alfresco/adf-core';
+import { EffectsModule } from '@ngrx/effects';
+import { LibraryEffects } from './library.effects';
+import { AppTestingModule } from '../../testing/app-testing.module';
+import { NodeEntry } from '@alfresco/js-api';
+
+describe('LibraryEffects', () => {
+ let store: Store;
+
+ beforeEach(() => {
+ TestBed.configureTestingModule({
+ imports: [AppTestingModule, EffectsModule.forRoot([LibraryEffects])]
+ });
+ store = TestBed.inject(Store);
+ });
+
+ describe('navigateLibrary$', () => {
+ let notificationService: NotificationService;
+ let node$: Subject;
+
+ beforeEach(() => {
+ node$ = new Subject();
+ spyOn(TestBed.inject(ContentApiService), 'getNode').and.returnValue(node$);
+ notificationService = TestBed.inject(NotificationService);
+ spyOn(notificationService, 'showError');
+ });
+
+ it('should display library no permission error if user does not have permission', () => {
+ store.dispatch(new NavigateLibraryAction('libraryId'));
+ node$.error(new HttpErrorResponse({ status: 403 }));
+ expect(notificationService.showError).toHaveBeenCalledWith('APP.BROWSE.LIBRARIES.ERRORS.LIBRARY_NO_PERMISSIONS');
+ });
+
+ it('should display library not found error if library does not exist', () => {
+ store.dispatch(new NavigateLibraryAction('libraryId'));
+ node$.error(new HttpErrorResponse({ status: 404 }));
+ expect(notificationService.showError).toHaveBeenCalledWith('APP.BROWSE.LIBRARIES.ERRORS.LIBRARY_NOT_FOUND');
+ });
+
+ it('should display generic library loading error if there is different problem than missing permissions or absence of library', () => {
+ store.dispatch(new NavigateLibraryAction('libraryId'));
+ node$.error(new HttpErrorResponse({ status: 500 }));
+ expect(notificationService.showError).toHaveBeenCalledWith('APP.BROWSE.LIBRARIES.ERRORS.LIBRARY_LOADING_ERROR');
+ });
+ });
+});
diff --git a/projects/aca-content/src/lib/store/effects/library.effects.ts b/projects/aca-content/src/lib/store/effects/library.effects.ts
index cd81c0c27..9b768dcda 100644
--- a/projects/aca-content/src/lib/store/effects/library.effects.ts
+++ b/projects/aca-content/src/lib/store/effects/library.effects.ts
@@ -40,6 +40,7 @@ import { map, mergeMap, take } from 'rxjs/operators';
import { ContentApiService } from '@alfresco/aca-shared';
import { ContentManagementService } from '../../services/content-management.service';
import { NotificationService } from '@alfresco/adf-core';
+import { HttpErrorResponse } from '@angular/common/http';
@Injectable()
export class LibraryEffects {
@@ -118,8 +119,19 @@ export class LibraryEffects {
const route = action.route ? action.route : 'libraries';
this.store.dispatch(new NavigateRouteAction([route, id]));
},
- () => {
- this.notificationService.showError('APP.MESSAGES.ERRORS.MISSING_CONTENT');
+ (error: HttpErrorResponse) => {
+ let errorTranslationKey: string;
+ switch (error.status) {
+ case 403:
+ errorTranslationKey = 'APP.BROWSE.LIBRARIES.ERRORS.LIBRARY_NO_PERMISSIONS';
+ break;
+ case 404:
+ errorTranslationKey = 'APP.BROWSE.LIBRARIES.ERRORS.LIBRARY_NOT_FOUND';
+ break;
+ default:
+ errorTranslationKey = 'APP.BROWSE.LIBRARIES.ERRORS.LIBRARY_LOADING_ERROR';
+ }
+ this.notificationService.showError(errorTranslationKey);
}
);
}