[ADF-2753] New error component ()

* [ADF-1938] Overflowing text in reports section fidex

* [ADF-1938] Long names in report section now fit

* [ADF-1938] Reverted changes in container widget

* [ADF-2753] New error component created

* [ADF-2753] Unit test for Error Content Component

* Deleting unused files

* Deleting unused files

* Deleting unused files

* [ADF-2753] Documentation added

* [ADF-2753] Fixed minor bugs

* [ADF-2753] Authentication not needed to view error

* add error handler

* tslint fix

* router app component

* remove unused import

* fix import modules

* limit to 404

* destroy fixture after any test

* misspelling error
This commit is contained in:
davidcanonieto 2018-05-19 01:20:49 +01:00 committed by Eugenio Romano
parent e2bfcd20a9
commit 70d93805d4
21 changed files with 489 additions and 34 deletions

@ -16,30 +16,40 @@
*/
import { Component, ViewEncapsulation, OnInit } from '@angular/core';
import { SettingsService, PageTitleService, StorageService } from '@alfresco/adf-core';
import { AlfrescoApiService, SettingsService, PageTitleService, StorageService } from '@alfresco/adf-core';
import { Router } from '@angular/router';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss'],
encapsulation: ViewEncapsulation.None
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss'],
encapsulation: ViewEncapsulation.None
})
export class AppComponent implements OnInit {
constructor(private settingsService: SettingsService,
private storage: StorageService,
private pageTitleService: PageTitleService) {
private pageTitleService: PageTitleService,
private alfrescoApiService: AlfrescoApiService,
private router: Router) {
}
ngOnInit() {
this.setProvider();
ngOnInit() {
this.setProvider();
this.pageTitleService.setTitle('title');
}
this.pageTitleService.setTitle('title');
private setProvider() {
if (this.storage.hasItem(`providers`)) {
this.settingsService.setProviders(this.storage.getItem(`providers`));
this.alfrescoApiService.getInstance().on('error', (error) => {
if (error.status === '404') {
this.router.navigate(['/error', error.status]);
}
});
}
private setProvider() {
if (this.storage.hasItem(`providers`)) {
this.settingsService.setProviders(this.storage.getItem(`providers`));
}
}
}
}

@ -17,7 +17,7 @@
import { ModuleWithProviders } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { AuthGuard, AuthGuardBpm, AuthGuardEcm } from '@alfresco/adf-core';
import { AuthGuard, AuthGuardBpm, AuthGuardEcm, ErrorContentComponent } from '@alfresco/adf-core';
import { AppLayoutComponent } from './components/app-layout/app-layout.component';
import { LoginComponent } from './components/login/login.component';
import { SettingsComponent } from './components/settings/settings.component';
@ -51,7 +51,7 @@ import { BlobPreviewComponent } from './components/blob-preview/blob-preview.com
export const appRoutes: Routes = [
{ path: 'login', component: LoginComponent },
{ path: 'settings', component: SettingsComponent },
{ path: 'files/:nodeId/view', component: FileViewComponent, canActivate: [ AuthGuardEcm ], outlet: 'overlay' },
{ path: 'files/:nodeId/view', component: FileViewComponent, canActivate: [AuthGuardEcm], outlet: 'overlay' },
{ path: 'preview/blob', component: BlobPreviewComponent, outlet: 'overlay', pathMatch: 'full' },
{ path: 'preview/s/:id', component: SharedLinkViewComponent },
{
@ -190,7 +190,16 @@ export const appRoutes: Routes = [
{
path: 'datatable-lazy',
loadChildren: 'app/components/lazy-loading/lazy-loading.module#LazyLoadingModule'
},
{
path: 'error/:id',
component: ErrorContentComponent
},
{
path: '**',
redirectTo: 'error/404'
}
]
}
];

@ -277,9 +277,9 @@ export class FilesComponent implements OnInit, OnChanges, OnDestroy {
}
}
onNavigationError(err: any) {
if (err) {
this.errorMessage = err.message || 'Navigation error';
onNavigationError(error: any) {
if (error) {
this.router.navigate(['/error', error.status]);
}
}

@ -0,0 +1,63 @@
# Error Content Component
Displays info about a specific error.
## Basic Usage
Once you have catched the error in your server you will need to redirect to ```/error/errorCode``` to display information about that error.
```ts
this.router.navigate(['/error', errorCode]);
```
```html
<div class="adf-error-content">
<p class="adf-error-content-code">{{ errorCode }}</p>
<div class="adf-error-content-shadow"></div>
<p class="adf-error-content-title">{{ errorTitle | translate }}</p>
<p class="adf-error-content-description">{{ errorDescription | translate }}
<a href="{{errorLinkUrl}}" *ngIf="errorLinkText"
class="adf-error-content-description-link" > {{ errorLinkText | translate }}</a></p>
<button mat-raised-button color="primary" routerLink="/home">{{ homeButton | translate}}</button>
</div>
```
## Properties
### Error Content Component
| Name | Type | Description |
| --- | --- | -- |
| errorCode | string | Error code |
| errorTitle | string | Error title |
| errorDescription | string | Short description about the error |
| errorLink | string | (Optional) This link will be attached at the end of the error description and itt will be highlighted.|
## Details
You can customize your errors by adding them to the tranlate files inside ```lib/core/i18n```.
```json
"ERROR_CONTENT": {
"HOME_BUTTON": "Back to home",
"403": {
"TITLE": "Error 403 forbidden!",
"DESCRIPTION": "Sorry, access to this resource on the server is denied. Either check URL or feel free to",
"LINK": {
"TEXT": "report this issue.",
"URL": ""
}
},
"404": {
"TITLE": "Whoops!",
"DESCRIPTION": "We couldnt find the page you were looking for.",
"LINK": {
"TEXT": "",
"URL": ""
}
}
}
```

@ -20,6 +20,7 @@ import { ButtonsMenuComponent } from './buttons-menu.component';
import { MenuButton } from './menu-button.model';
import { MaterialModule } from '../material.module';
import { CoreTestingModule } from '../testing/core.testing.module';
import { setupTestBed } from '../testing/setupTestBed';
describe('ButtonsMenuComponent', () => {
@ -27,14 +28,12 @@ describe('ButtonsMenuComponent', () => {
let buttonsMenuComponent: ButtonsMenuComponent;
let element: HTMLElement;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
MaterialModule,
CoreTestingModule
]
}).compileComponents();
}));
setupTestBed({
imports: [
CoreTestingModule,
MaterialModule
]
});
beforeEach(() => {
fixture = TestBed.createComponent(ButtonsMenuComponent);

@ -41,6 +41,7 @@ import { FormModule } from './form/form.module';
import { SidenavLayoutModule } from './sidenav-layout/sidenav-layout.module';
import { CommentsModule } from './comments/comments.module';
import { ButtonsMenuModule } from './buttons-menu/buttons-menu.module';
import { TemplatetModule } from './templates/template.module';
import { DirectiveModule } from './directives/directive.module';
import { PipeModule } from './pipes/pipe.module';
@ -80,7 +81,6 @@ import { UploadService } from './services/upload.service';
import { UserPreferencesService } from './services/user-preferences.service';
import { SearchConfigurationService } from './services/search-configuration.service';
import { startupServiceFactory } from './services/startup-service-factory';
import { EmptyContentComponent } from './components/empty-content/empty-content.component';
import { SortingPickerComponent } from './components/sorting-picker/sorting-picker.component';
export function createTranslateLoader(http: HttpClient, logService: LogService) {
@ -155,6 +155,7 @@ export function providers() {
DataColumnModule,
DataTableModule,
ButtonsMenuModule,
TemplatetModule,
TranslateModule.forChild({
loader: {
provide: TranslateLoader,
@ -190,7 +191,8 @@ export function providers() {
DataColumnModule,
DataTableModule,
TranslateModule,
ButtonsMenuModule
ButtonsMenuModule,
TemplatetModule
]
})
export class CoreModuleLazy {
@ -224,6 +226,7 @@ export class CoreModuleLazy {
DataColumnModule,
DataTableModule,
ButtonsMenuModule,
TemplatetModule,
TranslateModule.forRoot({
loader: {
provide: TranslateLoader,
@ -233,7 +236,6 @@ export class CoreModuleLazy {
})
],
declarations: [
EmptyContentComponent,
SortingPickerComponent
],
exports: [
@ -264,7 +266,7 @@ export class CoreModuleLazy {
DataTableModule,
TranslateModule,
ButtonsMenuModule,
EmptyContentComponent,
TemplatetModule,
SortingPickerComponent
],
providers: [

@ -48,7 +48,6 @@ describe('StartFormComponent', () => {
});
beforeEach(() => {
fixture = TestBed.createComponent(StartFormComponent);
component = fixture.componentInstance;
formService = TestBed.get(FormService);
@ -59,6 +58,10 @@ describe('StartFormComponent', () => {
}));
});
afterEach(() => {
fixture.destroy();
});
it('should load start form on change if processDefinitionId defined', () => {
component.processDefinitionId = exampleId1;
component.ngOnChanges({ processDefinitionId: new SimpleChange(exampleId1, exampleId2, true) });

@ -89,6 +89,7 @@
.mat-grid-tile {
overflow: visible;
width: 80%;
}
}

@ -262,5 +262,32 @@
"PLACEHOLDER": "Password",
"ERROR": "Password is wrong"
}
},
"ERROR_CONTENT": {
"HOME_BUTTON": "Back to home",
"UNKNOWN": {
"TITLE": "Oops!",
"DESCRIPTION": "Looks like something went wrong.",
"LINK": {
"TEXT": "",
"URL": ""
}
},
"403": {
"TITLE": "Error 403 forbidden!",
"DESCRIPTION": "Sorry, access to this resource on the server is denied. Either check URL or feel free to",
"LINK": {
"TEXT": "report this issue.",
"URL": ""
}
},
"404": {
"TITLE": "Whoops!",
"DESCRIPTION": "We couldnt find the page you were looking for.",
"LINK": {
"TEXT": "",
"URL": ""
}
}
}
}

@ -35,9 +35,9 @@ export * from './sidenav-layout/index';
export * from './comments/index';
export * from './buttons-menu/index';
export * from './components/empty-content/empty-content.component';
export * from './components/sorting-picker/sorting-picker.component';
export * from './templates/index';
export * from './pipes/index';
export * from './services/index';
export * from './directives/index';

@ -26,7 +26,8 @@
@import '../comments/comment-list.component';
@import '../comments/comments.component';
@import '../sidenav-layout/components/layout-container/layout-container.component';
@import "../components/empty-content/empty-content.component";
@import "../templates/empty-content/empty-content.component";
@import "../templates/error-content/error-content.component";
@mixin adf-core-theme($theme) {
@include adf-colors-theme($theme);
@ -56,6 +57,7 @@
@include adf-task-list-comment-theme($theme);
@include adf-layout-container-theme($theme);
@include adf-empty-content-theme($theme);
@include adf-error-content-theme($theme);
}

@ -0,0 +1,9 @@
<div class="adf-error-content">
<p class="adf-error-content-code">{{ errorCode }}</p>
<div class="adf-error-content-shadow"></div>
<p class="adf-error-content-title">{{ errorTitle | translate }}</p>
<p class="adf-error-content-description">{{ errorDescription | translate }}
<a href="{{errorLinkUrl}}" *ngIf="errorLinkText"
class="adf-error-content-description-link" > {{ errorLinkText | translate }}</a></p>
<a href="" mat-raised-button color="primary" routerLink="/home">{{ homeButton | translate}}</a>
</div>

@ -0,0 +1,72 @@
@mixin adf-error-content-theme($theme) {
$primary: map-get($theme, primary);
$foreground: map-get($theme, foreground);
.adf-error-content {
color: mat-color($foreground, text, 0.54);
display: flex;
flex-direction: column;
align-items: center;
&-code {
font-size: 110px;
font-weight: 300;
margin-top: 200px;
margin-bottom: 45px;
}
&-shadow {
width: 170px;
height: 3px;
opacity: 0.54;
box-shadow: 0 10px 15px 0 rgba(0, 0, 0, 0.39);
}
&-title {
font-size: 46px;
font-weight: normal;
margin-top: 40px;
margin-bottom: 10px;
}
&-description {
font-size: 24px;
font-weight: normal;
text-align: center;
width: 50%;
min-width: 250px;
margin-bottom: 60px;
line-height: 30px;
&-link{
color: mat-color($primary);
text-decoration: none;
}
}
}
@media screen and ($mat-small) {
.adf-error-content {
&-code {
margin-top: 100px;
font-size: 50px;
margin-bottom: 25px;
}
&-shadow {
width: 100px;
}
&-title {
font-size: 24px;
}
&-description {
font-size: 17px;
}
}
}
}

@ -0,0 +1,100 @@
/*!
* @license
* Copyright 2016 Alfresco Software, Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*tslint:disable:ban*/
import { TestBed, async } from '@angular/core/testing';
import { CoreTestingModule } from '../../testing/core.testing.module';
import { ErrorContentComponent } from './error-content.component';
import { TranslationService } from '../../services/translation.service';
import { TranslationMock } from '../../mock/translation.service.mock';
import { setupTestBed } from '../../testing/setupTestBed';
describe('ErrorContentComponent', () => {
let fixture;
let errorContentComponent: ErrorContentComponent;
let element: HTMLElement;
let translateService: TranslationService;
setupTestBed({
imports: [CoreTestingModule],
providers: [
{ provide: TranslationService, useClass: TranslationMock }
]
});
beforeEach(() => {
fixture = TestBed.createComponent(ErrorContentComponent);
element = fixture.nativeElement;
errorContentComponent = <ErrorContentComponent> fixture.debugElement.componentInstance;
translateService = TestBed.get(TranslationService);
});
afterEach(() => {
fixture.destroy();
TestBed.resetTestingModule();
});
it('should create error component', async(() => {
fixture.detectChanges();
expect(errorContentComponent).toBeTruthy();
}));
it('should render error code', async(() => {
fixture.detectChanges();
const errorContentElement = element.querySelector('.adf-error-content-code');
expect(errorContentElement).not.toBeNull();
expect(errorContentElement).toBeDefined();
}));
it('should render error title', async(() => {
fixture.detectChanges();
const errorContentElement = element.querySelector('.adf-error-content-title');
expect(errorContentElement).not.toBeNull();
expect(errorContentElement).toBeDefined();
}));
it('should render error description', async(() => {
fixture.detectChanges();
const errorContentElement = element.querySelector('.adf-error-content-description');
expect(errorContentElement).not.toBeNull();
expect(errorContentElement).toBeDefined();
}));
it('should render report issue links', async(() => {
errorContentComponent.errorLinkUrl = '403 Link';
fixture.detectChanges();
fixture.whenStable().then(() => {
const errorContentElement = element.querySelector('.adf-error-content-description-link');
expect(errorContentElement).not.toBeNull();
expect(errorContentElement).toBeDefined();
});
}));
it('should hide link if this one is empty', async(() => {
spyOn(translateService, 'instant').and.callFake((inputString) => {
return '';
} );
fixture.detectChanges();
fixture.whenStable().then(() => {
const errorContentElement = element.querySelector('.adf-error-content-description-link');
expect(errorContentElement).toBeNull();
});
}));
});

@ -0,0 +1,79 @@
/*!
* @license
* Copyright 2016 Alfresco Software, Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Component, ChangeDetectionStrategy, ViewEncapsulation, OnInit } from '@angular/core';
import { Params, ActivatedRoute } from '@angular/router';
import { TranslationService } from '../../services/translation.service';
@Component({
selector: 'adf-error-content',
templateUrl: './error-content.component.html',
styleUrls: ['./error-content.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
encapsulation: ViewEncapsulation.None,
host: { class: 'adf-error-content' }
})
export class ErrorContentComponent implements OnInit {
errorCode: string;
errorTitle: string;
errorDescription: string;
errorLinkText: string;
errorLinkUrl: string;
homeButton: string;
constructor(private route: ActivatedRoute,
private translateService: TranslationService) {
}
ngOnInit() {
if (this.route) {
this.route.params.forEach((params: Params) => {
if (params['id']) {
this.errorCode = params['id'];
}
});
}
this.getData();
}
getData() {
this.errorTitle = this.translateService.instant(
'ERROR_CONTENT.' + this.errorCode + '.TITLE');
if (this.errorTitle === 'ERROR_CONTENT.' + this.errorCode + '.TITLE') {
this.errorCode = 'UNKNOWN';
this.errorTitle = this.translateService.instant(
'ERROR_CONTENT.' + this.errorCode + '.TITLE');
}
this.errorDescription = this.translateService.instant(
'ERROR_CONTENT.' + this.errorCode + '.DESCRIPTION');
this.errorLinkText = this.translateService.instant(
'ERROR_CONTENT.' + this.errorCode + '.LINK.TEXT');
this.errorLinkUrl = this.translateService.instant(
'ERROR_CONTENT.' + this.errorCode + '.LINK.URL');
this.homeButton = this.translateService.instant(
'ERROR_CONTENT.HOME_BUTTON').toUpperCase();
}
}

@ -0,0 +1,18 @@
/*!
* @license
* Copyright 2016 Alfresco Software, Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export * from './public-api';

@ -0,0 +1,21 @@
/*!
* @license
* Copyright 2016 Alfresco Software, Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export * from './error-content/error-content.component';
export * from './empty-content/empty-content.component';
export * from './template.module';

@ -0,0 +1,40 @@
/*!
* @license
* Copyright 2016 Alfresco Software, Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { TranslateModule } from '@ngx-translate/core';
import { MaterialModule } from '../material.module';
import { ErrorContentComponent } from './error-content/error-content.component';
import { EmptyContentComponent } from './empty-content/empty-content.component';
@NgModule({
imports: [
CommonModule,
MaterialModule,
TranslateModule
],
declarations: [
ErrorContentComponent,
EmptyContentComponent
],
exports: [
ErrorContentComponent,
EmptyContentComponent
]
})
export class TemplatetModule {}