diff --git a/demo-shell/src/app/app.component.ts b/demo-shell/src/app/app.component.ts index dae3d46971..a99aa77a79 100644 --- a/demo-shell/src/app/app.component.ts +++ b/demo-shell/src/app/app.component.ts @@ -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`)); + } } - } } diff --git a/demo-shell/src/app/app.routes.ts b/demo-shell/src/app/app.routes.ts index f7ccfd73d5..3c945bc23b 100644 --- a/demo-shell/src/app/app.routes.ts +++ b/demo-shell/src/app/app.routes.ts @@ -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' } + ] } ]; diff --git a/demo-shell/src/app/components/files/files.component.ts b/demo-shell/src/app/components/files/files.component.ts index 70bb53982d..a4490e678b 100644 --- a/demo-shell/src/app/components/files/files.component.ts +++ b/demo-shell/src/app/components/files/files.component.ts @@ -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]); } } diff --git a/docs/core/error-content.component.md b/docs/core/error-content.component.md new file mode 100644 index 0000000000..5dbcd2645c --- /dev/null +++ b/docs/core/error-content.component.md @@ -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 +
+

{{ errorCode }}

+
+

{{ errorTitle | translate }}

+

{{ errorDescription | translate }} + {{ errorLinkText | translate }}

+ +
+``` + +## 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 couldn’t find the page you were looking for.", + "LINK": { + "TEXT": "", + "URL": "" + } + } + } +``` + + + diff --git a/lib/core/buttons-menu/buttons-menu.component.spec.ts b/lib/core/buttons-menu/buttons-menu.component.spec.ts index 878f80e921..28b9e00c24 100644 --- a/lib/core/buttons-menu/buttons-menu.component.spec.ts +++ b/lib/core/buttons-menu/buttons-menu.component.spec.ts @@ -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); diff --git a/lib/core/core.module.ts b/lib/core/core.module.ts index cc28912336..99b73364ce 100644 --- a/lib/core/core.module.ts +++ b/lib/core/core.module.ts @@ -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: [ diff --git a/lib/core/form/components/start-form.component.spec.ts b/lib/core/form/components/start-form.component.spec.ts index 29f5ae72e9..7bf75ff22e 100644 --- a/lib/core/form/components/start-form.component.spec.ts +++ b/lib/core/form/components/start-form.component.spec.ts @@ -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) }); diff --git a/lib/core/form/components/widgets/container/container.widget.scss b/lib/core/form/components/widgets/container/container.widget.scss index d23cbb7e09..4a7cd091b1 100644 --- a/lib/core/form/components/widgets/container/container.widget.scss +++ b/lib/core/form/components/widgets/container/container.widget.scss @@ -89,6 +89,7 @@ .mat-grid-tile { overflow: visible; + width: 80%; } } diff --git a/lib/core/i18n/en.json b/lib/core/i18n/en.json index 9271d64cad..efbbf1983e 100644 --- a/lib/core/i18n/en.json +++ b/lib/core/i18n/en.json @@ -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 couldn’t find the page you were looking for.", + "LINK": { + "TEXT": "", + "URL": "" + } + } } } diff --git a/lib/core/index.ts b/lib/core/index.ts index b262848dcd..8c6179a0b5 100644 --- a/lib/core/index.ts +++ b/lib/core/index.ts @@ -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'; diff --git a/lib/core/styles/_index.scss b/lib/core/styles/_index.scss index 0fd157337c..48b2d2c9b5 100644 --- a/lib/core/styles/_index.scss +++ b/lib/core/styles/_index.scss @@ -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); } diff --git a/lib/core/components/empty-content/empty-content.component.html b/lib/core/templates/empty-content/empty-content.component.html similarity index 100% rename from lib/core/components/empty-content/empty-content.component.html rename to lib/core/templates/empty-content/empty-content.component.html diff --git a/lib/core/components/empty-content/empty-content.component.scss b/lib/core/templates/empty-content/empty-content.component.scss similarity index 100% rename from lib/core/components/empty-content/empty-content.component.scss rename to lib/core/templates/empty-content/empty-content.component.scss diff --git a/lib/core/components/empty-content/empty-content.component.ts b/lib/core/templates/empty-content/empty-content.component.ts similarity index 100% rename from lib/core/components/empty-content/empty-content.component.ts rename to lib/core/templates/empty-content/empty-content.component.ts diff --git a/lib/core/templates/error-content/error-content.component.html b/lib/core/templates/error-content/error-content.component.html new file mode 100644 index 0000000000..935ba2c2a0 --- /dev/null +++ b/lib/core/templates/error-content/error-content.component.html @@ -0,0 +1,9 @@ +
+

{{ errorCode }}

+
+

{{ errorTitle | translate }}

+

{{ errorDescription | translate }} + {{ errorLinkText | translate }}

+ {{ homeButton | translate}} +
diff --git a/lib/core/templates/error-content/error-content.component.scss b/lib/core/templates/error-content/error-content.component.scss new file mode 100644 index 0000000000..ae88b499a0 --- /dev/null +++ b/lib/core/templates/error-content/error-content.component.scss @@ -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; + } + } + } +} diff --git a/lib/core/templates/error-content/error-content.component.spec.ts b/lib/core/templates/error-content/error-content.component.spec.ts new file mode 100644 index 0000000000..53514cc7d5 --- /dev/null +++ b/lib/core/templates/error-content/error-content.component.spec.ts @@ -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 = 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(); + }); + })); + +}); diff --git a/lib/core/templates/error-content/error-content.component.ts b/lib/core/templates/error-content/error-content.component.ts new file mode 100644 index 0000000000..3eb3d6b268 --- /dev/null +++ b/lib/core/templates/error-content/error-content.component.ts @@ -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(); + + } +} diff --git a/lib/core/templates/index.ts b/lib/core/templates/index.ts new file mode 100644 index 0000000000..4c6ac1d58f --- /dev/null +++ b/lib/core/templates/index.ts @@ -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'; diff --git a/lib/core/templates/public-api.ts b/lib/core/templates/public-api.ts new file mode 100644 index 0000000000..182c2242ab --- /dev/null +++ b/lib/core/templates/public-api.ts @@ -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'; diff --git a/lib/core/templates/template.module.ts b/lib/core/templates/template.module.ts new file mode 100644 index 0000000000..e357ac560c --- /dev/null +++ b/lib/core/templates/template.module.ts @@ -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 {}