From 3d4f06a8f9c8b9d8b5f23c2762e3e1b9de8aae8c Mon Sep 17 00:00:00 2001 From: harunurhan Date: Wed, 24 Jan 2018 14:18:47 +0100 Subject: [PATCH 1/6] add error.module and routes --- src/app/app.module.ts | 2 +- src/app/app.router.ts | 5 +- src/app/error/error.module.ts | 47 +++++++++++++++++++ src/app/error/error.router.ts | 25 ++++++++++ .../error/forbidden/forbidden.component.html | 1 + .../error/forbidden/forbidden.component.scss | 0 .../error/forbidden/forbidden.component.ts | 30 ++++++++++++ src/app/error/forbidden/index.ts | 1 + src/app/error/internal-server-error/index.ts | 1 + .../internal-server-error.component.html | 1 + .../internal-server-error.component.scss | 0 .../internal-server-error.component.ts | 30 ++++++++++++ src/app/error/locked/index.ts | 1 + src/app/error/locked/locked.component.html | 1 + src/app/error/locked/locked.component.scss | 0 src/app/error/locked/locked.component.ts | 30 ++++++++++++ src/app/error/not-found/index.ts | 1 + .../error/not-found/not-found.component.html | 1 + .../error/not-found/not-found.component.scss | 0 .../error/not-found/not-found.component.ts | 30 ++++++++++++ 20 files changed, 205 insertions(+), 2 deletions(-) create mode 100644 src/app/error/error.module.ts create mode 100644 src/app/error/error.router.ts create mode 100644 src/app/error/forbidden/forbidden.component.html create mode 100644 src/app/error/forbidden/forbidden.component.scss create mode 100644 src/app/error/forbidden/forbidden.component.ts create mode 100644 src/app/error/forbidden/index.ts create mode 100644 src/app/error/internal-server-error/index.ts create mode 100644 src/app/error/internal-server-error/internal-server-error.component.html create mode 100644 src/app/error/internal-server-error/internal-server-error.component.scss create mode 100644 src/app/error/internal-server-error/internal-server-error.component.ts create mode 100644 src/app/error/locked/index.ts create mode 100644 src/app/error/locked/locked.component.html create mode 100644 src/app/error/locked/locked.component.scss create mode 100644 src/app/error/locked/locked.component.ts create mode 100644 src/app/error/not-found/index.ts create mode 100644 src/app/error/not-found/not-found.component.html create mode 100644 src/app/error/not-found/not-found.component.scss create mode 100644 src/app/error/not-found/not-found.component.ts diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 91b4cfa..324c578 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -40,7 +40,7 @@ export class RavenErrorHandler implements ErrorHandler { BrowserAnimationsModule, // needed for ToastrModule HttpModule, AppRouter, - // feature-modules + CoreModule, // all core services AccordionModule.forRoot(), // ngx-toastr diff --git a/src/app/app.router.ts b/src/app/app.router.ts index 91d788f..e33c2f3 100644 --- a/src/app/app.router.ts +++ b/src/app/app.router.ts @@ -4,7 +4,10 @@ import { NgModule } from '@angular/core'; const appRoutes: Routes = [ { path: 'holdingpen', loadChildren: './holdingpen-editor/holdingpen-editor.module#HoldingpenEditorModule' }, { path: 'record', loadChildren: './record-editor/record-editor.module#RecordEditorModule' }, - { path: 'multieditor', loadChildren: './multi-editor/multi-editor.module#MultiEditorModule' } + { path: 'multieditor', loadChildren: './multi-editor/multi-editor.module#MultiEditorModule' }, + { path: 'error', loadChildren: './error/error.module#ErrorModule' }, + { path: '**', pathMatch: 'full', redirectTo: 'error' } + ]; @NgModule({ diff --git a/src/app/error/error.module.ts b/src/app/error/error.module.ts new file mode 100644 index 0000000..88e576f --- /dev/null +++ b/src/app/error/error.module.ts @@ -0,0 +1,47 @@ +/* + * This file is part of record-editor. + * Copyright (C) 2018 CERN. + * + * record-editor is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * record-editor 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with record-editor; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. + * In applying this license, CERN does not + * waive the privileges and immunities granted to it by virtue of its status + * as an Intergovernmental Organization or submit itself to any jurisdiction. + */ + +import { NgModule } from '@angular/core'; + +import { SharedModule } from '../shared'; + +import { ErrorRouter } from './error.router'; +import { NotFoundComponent } from './not-found'; +import { ForbiddenComponent } from './forbidden'; +import { LockedComponent } from './locked'; +import { InternalServerErrorComponent } from './internal-server-error'; + + + +@NgModule({ + imports: [ + SharedModule, + ErrorRouter + ], + declarations: [ + NotFoundComponent, + ForbiddenComponent, + LockedComponent, + InternalServerErrorComponent + ] +}) +export class ErrorModule { } diff --git a/src/app/error/error.router.ts b/src/app/error/error.router.ts new file mode 100644 index 0000000..1d3661d --- /dev/null +++ b/src/app/error/error.router.ts @@ -0,0 +1,25 @@ +import { Routes, RouterModule } from '@angular/router'; +import { NgModule } from '@angular/core'; + +import { NotFoundComponent } from './not-found'; +import { ForbiddenComponent } from './forbidden'; +import { LockedComponent } from './locked'; +import { InternalServerErrorComponent } from './internal-server-error'; + +const errorRoutes: Routes = [ + { path: '', redirectTo: '404' }, + { path: '403', component: ForbiddenComponent }, + { path: '404', component: NotFoundComponent }, + { path: '423', component: LockedComponent }, + { path: '500', component: InternalServerErrorComponent } +]; + +@NgModule({ + imports: [ + RouterModule.forChild(errorRoutes) + ], + exports: [ + RouterModule, + ] +}) +export class ErrorRouter { } diff --git a/src/app/error/forbidden/forbidden.component.html b/src/app/error/forbidden/forbidden.component.html new file mode 100644 index 0000000..04e0e17 --- /dev/null +++ b/src/app/error/forbidden/forbidden.component.html @@ -0,0 +1 @@ +

Forbidden

\ No newline at end of file diff --git a/src/app/error/forbidden/forbidden.component.scss b/src/app/error/forbidden/forbidden.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/src/app/error/forbidden/forbidden.component.ts b/src/app/error/forbidden/forbidden.component.ts new file mode 100644 index 0000000..a63f524 --- /dev/null +++ b/src/app/error/forbidden/forbidden.component.ts @@ -0,0 +1,30 @@ +/* + * This file is part of record-editor. + * Copyright (C) 2018 CERN. + * + * record-editor is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * record-editor 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with record-editor; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. + * In applying this license, CERN does not + * waive the privileges and immunities granted to it by virtue of its status + * as an Intergovernmental Organization or submit itself to any jurisdiction. + */ + +import { Component } from '@angular/core'; + +@Component({ + selector: 're-forbidden', + templateUrl: './forbidden.component.html', + styleUrls: ['./forbidden.component.scss'] +}) +export class ForbiddenComponent { } diff --git a/src/app/error/forbidden/index.ts b/src/app/error/forbidden/index.ts new file mode 100644 index 0000000..f088d0e --- /dev/null +++ b/src/app/error/forbidden/index.ts @@ -0,0 +1 @@ +export { ForbiddenComponent } from './forbidden.component'; diff --git a/src/app/error/internal-server-error/index.ts b/src/app/error/internal-server-error/index.ts new file mode 100644 index 0000000..e0d7f38 --- /dev/null +++ b/src/app/error/internal-server-error/index.ts @@ -0,0 +1 @@ +export { InternalServerErrorComponent } from './internal-server-error.component'; diff --git a/src/app/error/internal-server-error/internal-server-error.component.html b/src/app/error/internal-server-error/internal-server-error.component.html new file mode 100644 index 0000000..c678e37 --- /dev/null +++ b/src/app/error/internal-server-error/internal-server-error.component.html @@ -0,0 +1 @@ +

Not found

\ No newline at end of file diff --git a/src/app/error/internal-server-error/internal-server-error.component.scss b/src/app/error/internal-server-error/internal-server-error.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/src/app/error/internal-server-error/internal-server-error.component.ts b/src/app/error/internal-server-error/internal-server-error.component.ts new file mode 100644 index 0000000..bfc4bcf --- /dev/null +++ b/src/app/error/internal-server-error/internal-server-error.component.ts @@ -0,0 +1,30 @@ +/* + * This file is part of record-editor. + * Copyright (C) 2018 CERN. + * + * record-editor is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * record-editor 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with record-editor; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. + * In applying this license, CERN does not + * waive the privileges and immunities granted to it by virtue of its status + * as an Intergovernmental Organization or submit itself to any jurisdiction. + */ + +import { Component } from '@angular/core'; + +@Component({ + selector: 're-internal-server-error', + templateUrl: './internal-server-error.component.html', + styleUrls: ['./internal-server-error.component.scss'] +}) +export class InternalServerErrorComponent { } diff --git a/src/app/error/locked/index.ts b/src/app/error/locked/index.ts new file mode 100644 index 0000000..270664e --- /dev/null +++ b/src/app/error/locked/index.ts @@ -0,0 +1 @@ +export { LockedComponent } from './locked.component'; diff --git a/src/app/error/locked/locked.component.html b/src/app/error/locked/locked.component.html new file mode 100644 index 0000000..d3da4a5 --- /dev/null +++ b/src/app/error/locked/locked.component.html @@ -0,0 +1 @@ +

Locked

\ No newline at end of file diff --git a/src/app/error/locked/locked.component.scss b/src/app/error/locked/locked.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/src/app/error/locked/locked.component.ts b/src/app/error/locked/locked.component.ts new file mode 100644 index 0000000..af6b62e --- /dev/null +++ b/src/app/error/locked/locked.component.ts @@ -0,0 +1,30 @@ +/* + * This file is part of record-editor. + * Copyright (C) 2018 CERN. + * + * record-editor is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * record-editor 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with record-editor; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. + * In applying this license, CERN does not + * waive the privileges and immunities granted to it by virtue of its status + * as an Intergovernmental Organization or submit itself to any jurisdiction. + */ + +import { Component } from '@angular/core'; + +@Component({ + selector: 're-locked', + templateUrl: './locked.component.html', + styleUrls: ['./locked.component.scss'] +}) +export class LockedComponent { } diff --git a/src/app/error/not-found/index.ts b/src/app/error/not-found/index.ts new file mode 100644 index 0000000..ed3aaa0 --- /dev/null +++ b/src/app/error/not-found/index.ts @@ -0,0 +1 @@ +export { NotFoundComponent } from './not-found.component'; diff --git a/src/app/error/not-found/not-found.component.html b/src/app/error/not-found/not-found.component.html new file mode 100644 index 0000000..c678e37 --- /dev/null +++ b/src/app/error/not-found/not-found.component.html @@ -0,0 +1 @@ +

Not found

\ No newline at end of file diff --git a/src/app/error/not-found/not-found.component.scss b/src/app/error/not-found/not-found.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/src/app/error/not-found/not-found.component.ts b/src/app/error/not-found/not-found.component.ts new file mode 100644 index 0000000..6bcb7ee --- /dev/null +++ b/src/app/error/not-found/not-found.component.ts @@ -0,0 +1,30 @@ +/* + * This file is part of record-editor. + * Copyright (C) 2018 CERN. + * + * record-editor is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * record-editor 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with record-editor; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. + * In applying this license, CERN does not + * waive the privileges and immunities granted to it by virtue of its status + * as an Intergovernmental Organization or submit itself to any jurisdiction. + */ + +import { Component } from '@angular/core'; + +@Component({ + selector: 're-not-found', + templateUrl: './not-found.component.html', + styleUrls: ['./not-found.component.scss'] +}) +export class NotFoundComponent { } From f58c4454fb5ef0e082e0afed80c014d9ab9977ff Mon Sep 17 00:00:00 2001 From: harunurhan Date: Fri, 26 Jan 2018 09:30:47 +0100 Subject: [PATCH 2/6] use record resouce and use route resolvers * Referencing #307 --- src/app/core/services/record-api.service.ts | 19 +++-- .../json-editor-wrapper.component.ts | 80 +++++++------------ src/app/record-editor/record-editor.router.ts | 21 ++++- .../record-resources.resolver.ts | 46 +++++++++++ .../record-editor/record-search.resolver.ts | 50 ++++++++++++ .../record-search/record-search.component.ts | 22 +++-- .../search-bar/search-bar.component.ts | 4 +- src/app/shared/interfaces/index.ts | 1 + src/app/shared/interfaces/record-resources.ts | 6 ++ src/app/shared/interfaces/search-params.ts | 1 + src/polyfills.ts | 1 + 11 files changed, 177 insertions(+), 74 deletions(-) create mode 100644 src/app/record-editor/record-resources.resolver.ts create mode 100644 src/app/record-editor/record-search.resolver.ts create mode 100644 src/app/shared/interfaces/record-resources.ts diff --git a/src/app/core/services/record-api.service.ts b/src/app/core/services/record-api.service.ts index f375ac3..df2566c 100644 --- a/src/app/core/services/record-api.service.ts +++ b/src/app/core/services/record-api.service.ts @@ -29,14 +29,14 @@ import { Observable } from 'rxjs/Observable'; import { environment } from '../../../environments/environment'; import { AppConfigService } from './app-config.service'; import { CommonApiService } from './common-api.service'; -import { Ticket, RecordRevision } from '../../shared/interfaces'; +import { Ticket, RecordRevision, RecordResources } from '../../shared/interfaces'; import { ApiError } from '../../shared/classes'; import { editorApiUrl, apiUrl } from '../../shared/config'; @Injectable() export class RecordApiService extends CommonApiService { - private currentRecordApiUrl: string; + private currentRecordSaveApiUrl: string; private currentRecordEditorApiUrl: string; private currentRecordId: string; @@ -55,17 +55,21 @@ export class RecordApiService extends CommonApiService { .toPromise(); } - fetchRecord(pidType: string, pidValue: string): Promise { + fetchRecordResources(pidType: string, pidValue: string): Observable { this.currentRecordId = pidValue; - this.currentRecordApiUrl = `${apiUrl}/${pidType}/${pidValue}/db`; + this.currentRecordSaveApiUrl = `${apiUrl}/${pidType}/${pidValue}/db`; this.currentRecordEditorApiUrl = `${editorApiUrl}/${pidType}/${pidValue}`; this.newRecordFetched$.next(null); - return this.fetchUrl(this.currentRecordApiUrl); + return this.http + .get(this.currentRecordEditorApiUrl) + .map(res => res.json()) + .catch(error => Observable.throw(new ApiError(error))); + } saveRecord(record: object): Observable { return this.http - .put(this.currentRecordApiUrl, record) + .put(this.currentRecordSaveApiUrl, record) .catch(error => Observable.throw(new ApiError(error))); } @@ -125,7 +129,8 @@ export class RecordApiService extends CommonApiService { return this.http .get(`${apiUrl}/${recordType}/?q=${query}&size=200`, { headers: this.returnOnlyIdsHeaders }) .map(res => res.json()) - .map(json => json.hits.recids); + .map(json => json.hits.recids) + .catch(error => Observable.throw(new ApiError(error))); } preview(record: object): Promise { diff --git a/src/app/record-editor/json-editor-wrapper/json-editor-wrapper.component.ts b/src/app/record-editor/json-editor-wrapper/json-editor-wrapper.component.ts index 5b437b0..365f8e4 100644 --- a/src/app/record-editor/json-editor-wrapper/json-editor-wrapper.component.ts +++ b/src/app/record-editor/json-editor-wrapper/json-editor-wrapper.component.ts @@ -21,12 +21,13 @@ */ import { Component, Input, OnInit, OnChanges, SimpleChanges, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core'; -import { ActivatedRoute } from '@angular/router'; +import { ActivatedRoute, Router } from '@angular/router'; import { SchemaValidationProblems } from 'ng2-json-editor'; import { ToastrService } from 'ngx-toastr'; import { RecordApiService, AppConfigService, DomUtilsService, GlobalAppStateService } from '../../core/services'; -import { SubscriberComponent } from '../../shared/classes'; +import { RecordResources } from '../../shared/interfaces'; +import { SubscriberComponent, ApiError } from '../../shared/classes'; @Component({ selector: 're-json-editor-wrapper', @@ -47,6 +48,7 @@ export class JsonEditorWrapperComponent extends SubscriberComponent implements O revision: object | undefined; constructor(private changeDetectorRef: ChangeDetectorRef, + private router: Router, private route: ActivatedRoute, private apiService: RecordApiService, private appConfigService: AppConfigService, @@ -58,9 +60,19 @@ export class JsonEditorWrapperComponent extends SubscriberComponent implements O ngOnChanges(changes: SimpleChanges) { if ((changes['recordId'] || changes['recordType']) && this.recordId && this.recordType) { - // component loaded and being used by record-search + // component loaded and being used by record-search, not router this.record = undefined; // don't display old record while new is loading - this.fetch(this.recordType, this.recordId); + this.apiService + .fetchRecordResources(this.recordType, this.recordId) + .subscribe(resources => { + this.assignResourcesToProperties(resources); + }, (error: ApiError) => { + if (error.status === 403) { + this.toastrService.error(`Logged in user can not access to the record: ${this.recordType}/${this.recordId}`, 'Forbidden'); + } else { + this.toastrService.error('Could not load the record!', 'Error'); + } + }); } } @@ -69,13 +81,12 @@ export class JsonEditorWrapperComponent extends SubscriberComponent implements O this.domUtilsService.fitEditorHeightFullPageOnResize(); this.domUtilsService.fitEditorHeightFullPage(); - if (!this.recordId || !this.recordType) { - // component loaded via router, @Input() aren't passed - this.route.params - .filter(params => params['recid']) + if (!this.recordId && !this.recordType) { + // component loaded via router, if @Input() aren't passed + this.route.data .takeUntil(this.isDestroyed) - .subscribe(params => { - this.fetch(params['type'], params['recid']); + .subscribe((data: { resources: RecordResources }) => { + this.assignResourcesToProperties(data.resources); }); } @@ -87,6 +98,15 @@ export class JsonEditorWrapperComponent extends SubscriberComponent implements O }); } + private assignResourcesToProperties(resources: RecordResources) { + this.record = resources.record; + this.globalAppStateService.jsonBeingEdited$.next(this.record); + this.globalAppStateService.isJsonUpdated$.next(false); + this.config = this.appConfigService.getConfigForRecord(this.record); + this.schema = resources.schema; + this.changeDetectorRef.markForCheck(); + } + onRecordChange(record: object) { // update record if the edited one is not revision. if (!this.revision) { @@ -115,44 +135,4 @@ export class JsonEditorWrapperComponent extends SubscriberComponent implements O this.globalAppStateService .validationProblems$.next(problems); } - - /** - * Performs api calls for a single record to be loaded - * and __assigns__ fetched data to class properties - * - * - checks permission - * - fetches record - * - fetches schema - * - * - shows toast message when any call fails - */ - private fetch(recordType: string, recordId: string) { - let loadingToastId; - this.apiService.checkEditorPermission(recordType, recordId) - .then(() => { - // TODO: move toast call out of then after https://github.com/angular/angular/pull/18352 - loadingToastId = this.toastrService.info( - `Loading ${recordType}/${recordId}`, 'Wait').toastId; - return this.apiService.fetchRecord(recordType, recordId); - }).then(json => { - this.record = json['metadata']; - this.globalAppStateService - .jsonBeingEdited$.next(this.record); - this.globalAppStateService - .isJsonUpdated$.next(false); - this.config = this.appConfigService.getConfigForRecord(this.record); - return this.apiService.fetchUrl(this.record['$schema']); - }).then(schema => { - this.toastrService.clear(loadingToastId); - this.schema = schema; - this.changeDetectorRef.markForCheck(); - }).catch(error => { - this.toastrService.clear(loadingToastId); - if (error.status === 403) { - this.toastrService.error(`Logged in user can not access to the record: ${recordType}/${recordId}`, 'Forbidden'); - } else { - this.toastrService.error('Could not load the record!', 'Error'); - } - }); - } } diff --git a/src/app/record-editor/record-editor.router.ts b/src/app/record-editor/record-editor.router.ts index c32c20c..00d6625 100644 --- a/src/app/record-editor/record-editor.router.ts +++ b/src/app/record-editor/record-editor.router.ts @@ -3,11 +3,22 @@ import { NgModule } from '@angular/core'; import { RecordSearchComponent } from './record-search'; import { JsonEditorWrapperComponent } from './json-editor-wrapper'; +import { RecordResourcesResolver } from './record-resources.resolver'; +import { RecordSearchResolver } from './record-search.resolver'; const recordEditorRoutes: Routes = [ - { path: ':type/search', component: RecordSearchComponent }, - { path: ':type/:recid', component: JsonEditorWrapperComponent } + { + path: ':type/search', + component: RecordSearchComponent, + resolve: { recids: RecordSearchResolver }, + runGuardsAndResolvers: 'paramsOrQueryParamsChange' + }, + { + path: ':type/:recid', + component: JsonEditorWrapperComponent, + resolve: { resources: RecordResourcesResolver } + } ]; @NgModule({ @@ -15,7 +26,11 @@ const recordEditorRoutes: Routes = [ RouterModule.forChild(recordEditorRoutes) ], exports: [ - RouterModule, + RouterModule + ], + providers: [ + RecordResourcesResolver, + RecordSearchResolver ] }) export class RecordEditorRouter { } diff --git a/src/app/record-editor/record-resources.resolver.ts b/src/app/record-editor/record-resources.resolver.ts new file mode 100644 index 0000000..cd7dfd3 --- /dev/null +++ b/src/app/record-editor/record-resources.resolver.ts @@ -0,0 +1,46 @@ +/* + * This file is part of record-editor. + * Copyright (C) 2018 CERN. + * + * record-editor is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * record-editor 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with record-editor; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. + * In applying this license, CERN does not + * waive the privileges and immunities granted to it by virtue of its status + * as an Intergovernmental Organization or submit itself to any jurisdiction. + */ + +import { Resolve, ActivatedRouteSnapshot, Router } from '@angular/router'; +import { Injectable } from '@angular/core'; +import { Observable } from 'rxjs/Observable'; + +import { RecordApiService } from '../core/services'; +import { RecordResources } from '../shared/interfaces'; +import { ApiError } from '../shared/classes'; + +@Injectable() +export class RecordResourcesResolver implements Resolve { + constructor(private router: Router, + private apiService: RecordApiService) { } + + resolve(route: ActivatedRouteSnapshot): Observable { + const params = route.params; + return this.apiService + .fetchRecordResources(params.type, params.recid) + .take(1) + .catch((error: ApiError) => { + this.router.navigate(['error', error.status]); + return null; + }); + } +} diff --git a/src/app/record-editor/record-search.resolver.ts b/src/app/record-editor/record-search.resolver.ts new file mode 100644 index 0000000..7a93461 --- /dev/null +++ b/src/app/record-editor/record-search.resolver.ts @@ -0,0 +1,50 @@ +/* + * This file is part of record-editor. + * Copyright (C) 2018 CERN. + * + * record-editor is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * record-editor 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with record-editor; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. + * In applying this license, CERN does not + * waive the privileges and immunities granted to it by virtue of its status + * as an Intergovernmental Organization or submit itself to any jurisdiction. + */ + +import { Resolve, ActivatedRouteSnapshot, Router } from '@angular/router'; +import { Injectable } from '@angular/core'; +import { Observable } from 'rxjs/Observable'; + +import { RecordResourcesResolver } from './record-resources.resolver'; +import { RecordSearchService } from '../core/services'; +import { RecordResources } from '../shared/interfaces'; +import { ApiError } from '../shared/classes'; + +@Injectable() +export class RecordSearchResolver implements Resolve> { + constructor(private router: Router, + private recordSearchService: RecordSearchService) { } + + resolve(route: ActivatedRouteSnapshot): Observable> { + if (!route.queryParams.query) { + return Observable.of([]); + } + + return this.recordSearchService + .search(route.params.type, route.queryParams.query) + .take(1) + .catch((error: ApiError) => { + this.router.navigate(['error', error.status]); + return null; + }); + } +} diff --git a/src/app/record-editor/record-search/record-search.component.ts b/src/app/record-editor/record-search/record-search.component.ts index 8490899..062eec0 100644 --- a/src/app/record-editor/record-search/record-search.component.ts +++ b/src/app/record-editor/record-search/record-search.component.ts @@ -43,7 +43,7 @@ interface RouteType { export class RecordSearchComponent extends SubscriberComponent implements OnInit { recordType: string; - recordCursor: number; + recordCursor = 0; foundRecordIds: Array; constructor(private route: ActivatedRoute, @@ -60,18 +60,16 @@ export class RecordSearchComponent extends SubscriberComponent implements OnInit this.changeDetectorRef.markForCheck(); }); - const searchSub = Observable.combineLatest( - this.route.params, - this.route.queryParams, - (params, queryParams) => { - return { params, queryParams }; - }).do((route: RouteType) => { - this.recordType = route.params.type; - }).filter((route: RouteType) => Boolean(route.queryParams.query)) - .switchMap((route: RouteType) => this.recordSearchService.search(route.params.type, route.queryParams.query)) + this.route.params .takeUntil(this.isDestroyed) - .subscribe(recordIds => { - this.foundRecordIds = recordIds; + .subscribe(params => { + this.recordType = params.type; + }); + + this.route.data + .takeUntil(this.isDestroyed) + .subscribe((data: { recids: Array }) => { + this.foundRecordIds = data.recids; this.changeDetectorRef.markForCheck(); }); } diff --git a/src/app/record-editor/search-bar/search-bar.component.ts b/src/app/record-editor/search-bar/search-bar.component.ts index 1ebab1d..e3d4ec9 100644 --- a/src/app/record-editor/search-bar/search-bar.component.ts +++ b/src/app/record-editor/search-bar/search-bar.component.ts @@ -113,9 +113,9 @@ export class SearchBarComponent extends SubscriberComponent implements OnInit { const query = this.query; const isQueryNumber = !isNaN(+query); if (isQueryNumber) { - this.router.navigate([`${this.recordType}/${query}`]); + this.router.navigate(['record', this.recordType, query]); } else { - this.router.navigate([`${this.recordType}/search`], { queryParams: { query } }); + this.router.navigate(['record', this.recordType, 'search'], { queryParams: { query } }); } } diff --git a/src/app/shared/interfaces/index.ts b/src/app/shared/interfaces/index.ts index 7d03ca9..c46770c 100644 --- a/src/app/shared/interfaces/index.ts +++ b/src/app/shared/interfaces/index.ts @@ -5,3 +5,4 @@ export { RecordRevision } from './record-revision'; export { SearchParams } from './search-params'; export { SavePreviewModalOptions } from './save-preview-modal-options'; export { AuthorExtractResult } from './author-extract-result'; +export { RecordResources } from './record-resources'; diff --git a/src/app/shared/interfaces/record-resources.ts b/src/app/shared/interfaces/record-resources.ts new file mode 100644 index 0000000..f29428a --- /dev/null +++ b/src/app/shared/interfaces/record-resources.ts @@ -0,0 +1,6 @@ +import { JSONSchema } from 'ng2-json-editor'; + +export interface RecordResources { + schema: JSONSchema; + record: object; +} diff --git a/src/app/shared/interfaces/search-params.ts b/src/app/shared/interfaces/search-params.ts index 1102ca1..8e9a2b9 100644 --- a/src/app/shared/interfaces/search-params.ts +++ b/src/app/shared/interfaces/search-params.ts @@ -1,3 +1,4 @@ export interface SearchParams { query: string; + cursor?: number; } diff --git a/src/polyfills.ts b/src/polyfills.ts index 96389d6..59db4ed 100644 --- a/src/polyfills.ts +++ b/src/polyfills.ts @@ -29,5 +29,6 @@ import 'rxjs/add/operator/do'; import 'rxjs/add/operator/first'; import 'rxjs/add/operator/map'; import 'rxjs/add/operator/switchMap'; +import 'rxjs/add/operator/take'; import 'rxjs/add/operator/takeUntil'; import 'rxjs/add/operator/toPromise'; From 32b2c02f6bea472a79449eff13e206e10bd3c89a Mon Sep 17 00:00:00 2001 From: harunurhan Date: Fri, 26 Jan 2018 17:03:44 +0100 Subject: [PATCH 3/6] add lock/unlock manually * Refreshes the lock at the end of each 10 minutes if the user was active within this 10 minutes. * Unlocks when user switch to another record. --- package.json | 1 + src/app/core/services/record-api.service.ts | 19 +++++--- .../json-editor-wrapper.component.ts | 43 ++++++++++++++++--- 3 files changed, 51 insertions(+), 12 deletions(-) diff --git a/package.json b/package.json index ae4602b..03c1963 100644 --- a/package.json +++ b/package.json @@ -38,6 +38,7 @@ "@angular/router": "^4.0.0", "core-js": "^2.4.1", "font-awesome": "^4.7.0", + "idlejs": "^2.0.0", "immutable": "^3.8.1", "lodash": "4.17.2", "ng2-json-editor": "^0.24.0", diff --git a/src/app/core/services/record-api.service.ts b/src/app/core/services/record-api.service.ts index df2566c..ac442b4 100644 --- a/src/app/core/services/record-api.service.ts +++ b/src/app/core/services/record-api.service.ts @@ -48,13 +48,6 @@ export class RecordApiService extends CommonApiService { super(http); } - checkEditorPermission(pidType: string, pidValue: string): Promise { - this.currentRecordEditorApiUrl = `${editorApiUrl}/${pidType}/${pidValue}`; - return this.http - .get(`${this.currentRecordEditorApiUrl}/permission`) - .toPromise(); - } - fetchRecordResources(pidType: string, pidValue: string): Observable { this.currentRecordId = pidValue; this.currentRecordSaveApiUrl = `${apiUrl}/${pidType}/${pidValue}/db`; @@ -73,6 +66,18 @@ export class RecordApiService extends CommonApiService { .catch(error => Observable.throw(new ApiError(error))); } + lockRecord() { + this.http + .post(`${this.currentRecordEditorApiUrl}/lock/lock`, null) + .subscribe(); + } + + unlockRecord() { + this.http + .post(`${this.currentRecordEditorApiUrl}/lock/unlock`, null) + .subscribe(); + } + fetchRecordTickets(): Promise> { return this.fetchUrl(`${this.currentRecordEditorApiUrl}/rt/tickets`); } diff --git a/src/app/record-editor/json-editor-wrapper/json-editor-wrapper.component.ts b/src/app/record-editor/json-editor-wrapper/json-editor-wrapper.component.ts index 365f8e4..5af44cb 100644 --- a/src/app/record-editor/json-editor-wrapper/json-editor-wrapper.component.ts +++ b/src/app/record-editor/json-editor-wrapper/json-editor-wrapper.component.ts @@ -20,11 +20,22 @@ * as an Intergovernmental Organization or submit itself to any jurisdiction. */ -import { Component, Input, OnInit, OnChanges, SimpleChanges, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core'; +import { + Component, + Input, + OnInit, + OnDestroy, + OnChanges, + SimpleChanges, + ChangeDetectionStrategy, + ChangeDetectorRef +} from '@angular/core'; import { ActivatedRoute, Router } from '@angular/router'; import { SchemaValidationProblems } from 'ng2-json-editor'; import { ToastrService } from 'ngx-toastr'; +import { NotIdle } from 'idlejs/dist'; + import { RecordApiService, AppConfigService, DomUtilsService, GlobalAppStateService } from '../../core/services'; import { RecordResources } from '../../shared/interfaces'; import { SubscriberComponent, ApiError } from '../../shared/classes'; @@ -37,10 +48,17 @@ import { SubscriberComponent, ApiError } from '../../shared/classes'; ], changeDetection: ChangeDetectionStrategy.OnPush }) -export class JsonEditorWrapperComponent extends SubscriberComponent implements OnInit, OnChanges { +export class JsonEditorWrapperComponent extends SubscriberComponent implements OnInit, OnDestroy, OnChanges { @Input() recordId?: string; @Input() recordType?: string; + notIdle = new NotIdle() + .whenInteractive() + .within(10) + .do(() => { + this.apiService.lockRecord(); + }); + record: object; schema: object; config: object; @@ -62,10 +80,16 @@ export class JsonEditorWrapperComponent extends SubscriberComponent implements O if ((changes['recordId'] || changes['recordType']) && this.recordId && this.recordType) { // component loaded and being used by record-search, not router this.record = undefined; // don't display old record while new is loading + // unlock old record before fetching new one + if (changes['recordId'].previousValue) { + this.apiService.unlockRecord(); + this.notIdle.stop(); + } + this.apiService .fetchRecordResources(this.recordType, this.recordId) .subscribe(resources => { - this.assignResourcesToProperties(resources); + this.assignResourcesToPropertiesWithSideEffects(resources); }, (error: ApiError) => { if (error.status === 403) { this.toastrService.error(`Logged in user can not access to the record: ${this.recordType}/${this.recordId}`, 'Forbidden'); @@ -81,12 +105,13 @@ export class JsonEditorWrapperComponent extends SubscriberComponent implements O this.domUtilsService.fitEditorHeightFullPageOnResize(); this.domUtilsService.fitEditorHeightFullPage(); + if (!this.recordId && !this.recordType) { // component loaded via router, if @Input() aren't passed this.route.data .takeUntil(this.isDestroyed) .subscribe((data: { resources: RecordResources }) => { - this.assignResourcesToProperties(data.resources); + this.assignResourcesToPropertiesWithSideEffects(data.resources); }); } @@ -98,13 +123,21 @@ export class JsonEditorWrapperComponent extends SubscriberComponent implements O }); } - private assignResourcesToProperties(resources: RecordResources) { + private assignResourcesToPropertiesWithSideEffects(resources: RecordResources) { this.record = resources.record; this.globalAppStateService.jsonBeingEdited$.next(this.record); this.globalAppStateService.isJsonUpdated$.next(false); this.config = this.appConfigService.getConfigForRecord(this.record); this.schema = resources.schema; this.changeDetectorRef.markForCheck(); + + this.notIdle.start(); + } + + ngOnDestroy() { + super.ngOnDestroy(); + this.apiService.unlockRecord(); + this.notIdle.stop(); } onRecordChange(record: object) { From dd9a7d7470785199d7f78ec444a210eaaeadd38f Mon Sep 17 00:00:00 2001 From: harunurhan Date: Wed, 31 Jan 2018 09:40:30 +0100 Subject: [PATCH 4/6] fix bugs and refactor routing * Adds more resolvers * Refactors search route to remove record-search component and branching in json-editor-wrapper component. --- .../core/services/global-app-state.service.ts | 5 +- src/app/core/services/record-api.service.ts | 12 +-- .../core/services/record-search.service.ts | 24 ++++-- .../internal-server-error.component.html | 2 +- .../json-editor-wrapper.component.html | 2 +- .../json-editor-wrapper.component.ts | 50 ++---------- src/app/record-editor/record-editor.module.ts | 2 - src/app/record-editor/record-editor.router.ts | 27 +++++-- .../record-history.component.ts | 2 +- .../record-resources.resolver.ts | 5 +- .../record-editor/record-search.resolver.ts | 25 +++--- src/app/record-editor/record-search/index.ts | 1 - .../record-search.component.html | 3 - .../record-search.component.scss | 0 .../record-search/record-search.component.ts | 77 ------------------- .../record-editor/record-tickets.resolver.ts | 42 ++++++++++ .../search-bar/search-bar.component.html | 4 +- .../search-bar/search-bar.component.ts | 21 ++--- .../tickets/tickets.component.ts | 29 ++----- src/app/shared/classes/api-error.ts | 11 ++- 20 files changed, 147 insertions(+), 197 deletions(-) delete mode 100644 src/app/record-editor/record-search/index.ts delete mode 100644 src/app/record-editor/record-search/record-search.component.html delete mode 100644 src/app/record-editor/record-search/record-search.component.scss delete mode 100644 src/app/record-editor/record-search/record-search.component.ts create mode 100644 src/app/record-editor/record-tickets.resolver.ts diff --git a/src/app/core/services/global-app-state.service.ts b/src/app/core/services/global-app-state.service.ts index f07414c..019a0ce 100644 --- a/src/app/core/services/global-app-state.service.ts +++ b/src/app/core/services/global-app-state.service.ts @@ -22,6 +22,7 @@ import { Injectable } from '@angular/core'; import { Subject } from 'rxjs/Subject'; +import { ReplaySubject } from 'rxjs/ReplaySubject'; import { SchemaValidationProblems } from 'ng2-json-editor'; import { editorConfigs } from '../../shared/config'; @@ -29,9 +30,9 @@ import { onDocumentTypeChange } from '../../shared/config/hep'; @Injectable() export class GlobalAppStateService { - readonly jsonBeingEdited$ = new Subject(); + readonly jsonBeingEdited$ = new ReplaySubject(1); - readonly isJsonUpdated$ = new Subject(); + readonly isJsonUpdated$ = new ReplaySubject(1); readonly validationProblems$ = new Subject(); readonly hasAnyValidationProblem$ = this.validationProblems$ diff --git a/src/app/core/services/record-api.service.ts b/src/app/core/services/record-api.service.ts index ac442b4..4d25f35 100644 --- a/src/app/core/services/record-api.service.ts +++ b/src/app/core/services/record-api.service.ts @@ -52,6 +52,7 @@ export class RecordApiService extends CommonApiService { this.currentRecordId = pidValue; this.currentRecordSaveApiUrl = `${apiUrl}/${pidType}/${pidValue}/db`; this.currentRecordEditorApiUrl = `${editorApiUrl}/${pidType}/${pidValue}`; + // TODO: remove this side effect when every action done in resolvers this.newRecordFetched$.next(null); return this.http .get(this.currentRecordEditorApiUrl) @@ -78,8 +79,10 @@ export class RecordApiService extends CommonApiService { .subscribe(); } - fetchRecordTickets(): Promise> { - return this.fetchUrl(`${this.currentRecordEditorApiUrl}/rt/tickets`); + fetchRecordTickets(): Observable> { + return this.http + .get(`${this.currentRecordEditorApiUrl}/rt/tickets`) + .map(res => res.json()); } createRecordTicket(ticket: Ticket): Promise<{ id: string, link: string }> { @@ -109,11 +112,10 @@ export class RecordApiService extends CommonApiService { .map((queues: Array<{ name: string }>) => queues.map(queue => queue.name)); } - fetchRevisions(): Promise> { + fetchRevisions(): Observable> { return this.http .get(`${this.currentRecordEditorApiUrl}/revisions`) - .map(res => res.json()) - .toPromise(); + .map(res => res.json()); } fetchRevisionData(transactionId: number, recUUID: string): Promise { diff --git a/src/app/core/services/record-search.service.ts b/src/app/core/services/record-search.service.ts index 1572f12..c607efe 100644 --- a/src/app/core/services/record-search.service.ts +++ b/src/app/core/services/record-search.service.ts @@ -30,19 +30,27 @@ import { RecordApiService } from './record-api.service'; @Injectable() export class RecordSearchService { readonly resultCount$ = new ReplaySubject(1); - readonly cursor$ = new ReplaySubject(1); + + private lastSearchResult: Array; + private lastSearchQuery: string; constructor(private apiService: RecordApiService) { } + /** + * Performs search with side effect and simple caching + */ search(recordType: string, query: string): Observable> { - return this.apiService.searchRecord(recordType, query) - .do(results => { - this.resultCount$.next(results.length); - this.cursor$.next(0); + if (query === this.lastSearchQuery) { + return Observable.of(this.lastSearchResult); + } + + return this.apiService + .searchRecord(recordType, query) + .do((foundIds) => { + this.lastSearchResult = foundIds; + this.lastSearchQuery = query; + this.resultCount$.next(foundIds.length); }); } - setCursor(cursor: number) { - this.cursor$.next(cursor); - } } diff --git a/src/app/error/internal-server-error/internal-server-error.component.html b/src/app/error/internal-server-error/internal-server-error.component.html index c678e37..f2e5626 100644 --- a/src/app/error/internal-server-error/internal-server-error.component.html +++ b/src/app/error/internal-server-error/internal-server-error.component.html @@ -1 +1 @@ -

Not found

\ No newline at end of file +

Internal Server Error

\ No newline at end of file diff --git a/src/app/record-editor/json-editor-wrapper/json-editor-wrapper.component.html b/src/app/record-editor/json-editor-wrapper/json-editor-wrapper.component.html index 832ecc6..3d9cd19 100644 --- a/src/app/record-editor/json-editor-wrapper/json-editor-wrapper.component.html +++ b/src/app/record-editor/json-editor-wrapper/json-editor-wrapper.component.html @@ -18,7 +18,7 @@ - +
{ - this.assignResourcesToPropertiesWithSideEffects(resources); - }, (error: ApiError) => { - if (error.status === 403) { - this.toastrService.error(`Logged in user can not access to the record: ${this.recordType}/${this.recordId}`, 'Forbidden'); - } else { - this.toastrService.error('Could not load the record!', 'Error'); - } - }); - } - } - ngOnInit() { this.domUtilsService.registerBeforeUnloadPrompt(); this.domUtilsService.fitEditorHeightFullPageOnResize(); this.domUtilsService.fitEditorHeightFullPage(); - - if (!this.recordId && !this.recordType) { - // component loaded via router, if @Input() aren't passed - this.route.data - .takeUntil(this.isDestroyed) - .subscribe((data: { resources: RecordResources }) => { - this.assignResourcesToPropertiesWithSideEffects(data.resources); - }); - } + this.route.data + .takeUntil(this.isDestroyed) + .subscribe((data: { resources: RecordResources }) => { + this.assignResourcesToPropertiesWithSideEffects(data.resources); + }); this.appConfigService.onConfigChange .takeUntil(this.isDestroyed) @@ -153,12 +121,6 @@ export class JsonEditorWrapperComponent extends SubscriberComponent implements O } } - onRevisionRevert() { - this.record = this.revision; - this.revision = undefined; - this.changeDetectorRef.markForCheck(); - } - onRevisionChange(revision: Object) { this.revision = revision; this.changeDetectorRef.markForCheck(); diff --git a/src/app/record-editor/record-editor.module.ts b/src/app/record-editor/record-editor.module.ts index 457e01e..abe334a 100644 --- a/src/app/record-editor/record-editor.module.ts +++ b/src/app/record-editor/record-editor.module.ts @@ -31,7 +31,6 @@ import { RecordToolbarComponent } from './record-toolbar'; import { RecordHistoryComponent } from './record-history'; import { SearchBarComponent } from './search-bar'; import { TicketsComponent, NewTicketModalComponent, TicketComponent } from './tickets'; -import { RecordSearchComponent } from './record-search'; import { SavePreviewModalComponent } from './save-preview-modal'; import { ManualMergeModalComponent } from './manual-merge-modal'; @@ -48,7 +47,6 @@ import { ManualMergeModalComponent } from './manual-merge-modal'; TicketsComponent, NewTicketModalComponent, TicketComponent, - RecordSearchComponent, SavePreviewModalComponent, ManualMergeModalComponent ] diff --git a/src/app/record-editor/record-editor.router.ts b/src/app/record-editor/record-editor.router.ts index 00d6625..7c57af4 100644 --- a/src/app/record-editor/record-editor.router.ts +++ b/src/app/record-editor/record-editor.router.ts @@ -1,23 +1,35 @@ import { Routes, RouterModule } from '@angular/router'; import { NgModule } from '@angular/core'; -import { RecordSearchComponent } from './record-search'; import { JsonEditorWrapperComponent } from './json-editor-wrapper'; import { RecordResourcesResolver } from './record-resources.resolver'; import { RecordSearchResolver } from './record-search.resolver'; - +import { RecordTicketsResolver } from './record-tickets.resolver'; const recordEditorRoutes: Routes = [ { path: ':type/search', - component: RecordSearchComponent, - resolve: { recids: RecordSearchResolver }, - runGuardsAndResolvers: 'paramsOrQueryParamsChange' + resolve: { foundRecordId: RecordSearchResolver }, + runGuardsAndResolvers: 'paramsOrQueryParamsChange', + children: [ + { + path: '', + component: JsonEditorWrapperComponent, + resolve: { + resources: RecordResourcesResolver, + tickets: RecordTicketsResolver + }, + runGuardsAndResolvers: 'paramsOrQueryParamsChange' + } + ] }, { path: ':type/:recid', component: JsonEditorWrapperComponent, - resolve: { resources: RecordResourcesResolver } + resolve: { + resources: RecordResourcesResolver, + tickets: RecordTicketsResolver + } } ]; @@ -30,7 +42,8 @@ const recordEditorRoutes: Routes = [ ], providers: [ RecordResourcesResolver, - RecordSearchResolver + RecordSearchResolver, + RecordTicketsResolver ] }) export class RecordEditorRouter { } diff --git a/src/app/record-editor/record-history/record-history.component.ts b/src/app/record-editor/record-history/record-history.component.ts index 6baf375..a762b4d 100644 --- a/src/app/record-editor/record-history/record-history.component.ts +++ b/src/app/record-editor/record-history/record-history.component.ts @@ -78,7 +78,7 @@ export class RecordHistoryComponent extends SubscriberComponent implements OnIni private fetchRevisions() { this.apiService .fetchRevisions() - .then(revisions => { + .subscribe(revisions => { this.revisions = revisions; this.changeDetectorRef.markForCheck(); }); diff --git a/src/app/record-editor/record-resources.resolver.ts b/src/app/record-editor/record-resources.resolver.ts index cd7dfd3..a17a5e2 100644 --- a/src/app/record-editor/record-resources.resolver.ts +++ b/src/app/record-editor/record-resources.resolver.ts @@ -34,9 +34,10 @@ export class RecordResourcesResolver implements Resolve { private apiService: RecordApiService) { } resolve(route: ActivatedRouteSnapshot): Observable { - const params = route.params; + const recordType = route.params.type; + const recordId = route.params.recid || route.parent.data.foundRecordId; return this.apiService - .fetchRecordResources(params.type, params.recid) + .fetchRecordResources(recordType, recordId) .take(1) .catch((error: ApiError) => { this.router.navigate(['error', error.status]); diff --git a/src/app/record-editor/record-search.resolver.ts b/src/app/record-editor/record-search.resolver.ts index 7a93461..7c84bfd 100644 --- a/src/app/record-editor/record-search.resolver.ts +++ b/src/app/record-editor/record-search.resolver.ts @@ -24,24 +24,31 @@ import { Resolve, ActivatedRouteSnapshot, Router } from '@angular/router'; import { Injectable } from '@angular/core'; import { Observable } from 'rxjs/Observable'; -import { RecordResourcesResolver } from './record-resources.resolver'; -import { RecordSearchService } from '../core/services'; +import { RecordApiService, RecordSearchService } from '../core/services'; import { RecordResources } from '../shared/interfaces'; import { ApiError } from '../shared/classes'; @Injectable() -export class RecordSearchResolver implements Resolve> { +export class RecordSearchResolver implements Resolve { + constructor(private router: Router, + private apiService: RecordApiService, private recordSearchService: RecordSearchService) { } - resolve(route: ActivatedRouteSnapshot): Observable> { - if (!route.queryParams.query) { - return Observable.of([]); - } + resolve(route: ActivatedRouteSnapshot): Observable { + const recordType = route.params.type; + const query = route.queryParams.query; + let cursor = Number(route.queryParams.cursor); return this.recordSearchService - .search(route.params.type, route.queryParams.query) - .take(1) + .search(recordType, query) + .filter(foundIds => foundIds.length > 0) + .do(foundIds => { + if (!cursor || cursor >= foundIds.length || cursor < 0) { + cursor = 0; + } + }) + .map(foundIds => String(foundIds[cursor])) .catch((error: ApiError) => { this.router.navigate(['error', error.status]); return null; diff --git a/src/app/record-editor/record-search/index.ts b/src/app/record-editor/record-search/index.ts deleted file mode 100644 index e0f6847..0000000 --- a/src/app/record-editor/record-search/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { RecordSearchComponent } from './record-search.component'; diff --git a/src/app/record-editor/record-search/record-search.component.html b/src/app/record-editor/record-search/record-search.component.html deleted file mode 100644 index d9c9064..0000000 --- a/src/app/record-editor/record-search/record-search.component.html +++ /dev/null @@ -1,3 +0,0 @@ - - diff --git a/src/app/record-editor/record-search/record-search.component.scss b/src/app/record-editor/record-search/record-search.component.scss deleted file mode 100644 index e69de29..0000000 diff --git a/src/app/record-editor/record-search/record-search.component.ts b/src/app/record-editor/record-search/record-search.component.ts deleted file mode 100644 index 062eec0..0000000 --- a/src/app/record-editor/record-search/record-search.component.ts +++ /dev/null @@ -1,77 +0,0 @@ -/* - * This file is part of record-editor. - * Copyright (C) 2017 CERN. - * - * record-editor is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation; either version 2 of the - * License, or (at your option) any later version. - * - * record-editor 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 - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with record-editor; if not, write to the Free Software Foundation, Inc., - * 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. - * In applying this license, CERN does not - * waive the privileges and immunities granted to it by virtue of its status - * as an Intergovernmental Organization or submit itself to any jurisdiction. - */ - -import { Component, OnInit, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core'; -import { ActivatedRoute } from '@angular/router'; - -import { Observable } from 'rxjs/Observable'; - -import { RecordSearchService } from '../../core/services'; -import { SearchParams } from '../../shared/interfaces'; -import { SubscriberComponent } from '../../shared/classes'; - -interface RouteType { - params: { type: string }; - queryParams: SearchParams; -} - -@Component({ - selector: 're-record-search', - templateUrl: './record-search.component.html', - styleUrls: ['./record-search.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class RecordSearchComponent extends SubscriberComponent implements OnInit { - - recordType: string; - recordCursor = 0; - foundRecordIds: Array; - - constructor(private route: ActivatedRoute, - private changeDetectorRef: ChangeDetectorRef, - private recordSearchService: RecordSearchService) { - super(); - } - - ngOnInit() { - this.recordSearchService.cursor$ - .takeUntil(this.isDestroyed) - .subscribe(cursor => { - this.recordCursor = cursor; - this.changeDetectorRef.markForCheck(); - }); - - this.route.params - .takeUntil(this.isDestroyed) - .subscribe(params => { - this.recordType = params.type; - }); - - this.route.data - .takeUntil(this.isDestroyed) - .subscribe((data: { recids: Array }) => { - this.foundRecordIds = data.recids; - this.changeDetectorRef.markForCheck(); - }); - } - -} diff --git a/src/app/record-editor/record-tickets.resolver.ts b/src/app/record-editor/record-tickets.resolver.ts new file mode 100644 index 0000000..be60ffc --- /dev/null +++ b/src/app/record-editor/record-tickets.resolver.ts @@ -0,0 +1,42 @@ +/* + * This file is part of record-editor. + * Copyright (C) 2018 CERN. + * + * record-editor is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * record-editor 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with record-editor; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. + * In applying this license, CERN does not + * waive the privileges and immunities granted to it by virtue of its status + * as an Intergovernmental Organization or submit itself to any jurisdiction. + */ + +import { Resolve, ActivatedRouteSnapshot } from '@angular/router'; +import { Injectable } from '@angular/core'; +import { Observable } from 'rxjs/Observable'; + +import { RecordApiService } from '../core/services'; +import { Ticket } from '../shared/interfaces'; +import { ApiError } from '../shared/classes'; + +@Injectable() +export class RecordTicketsResolver implements Resolve> { + + constructor(private apiService: RecordApiService) { } + + resolve(route: ActivatedRouteSnapshot): Observable> { + return this.apiService + .fetchRecordTickets() + // TODO: `Observable.throw(error)` when https://github.com/angular/angular/issues/13873 is resolved + .catch((error: ApiError) => Observable.of(null)); + } +} diff --git a/src/app/record-editor/search-bar/search-bar.component.html b/src/app/record-editor/search-bar/search-bar.component.html index cdf0d41..78ccca7 100644 --- a/src/app/record-editor/search-bar/search-bar.component.html +++ b/src/app/record-editor/search-bar/search-bar.component.html @@ -13,9 +13,9 @@ - + {{cursor + 1}} / {{resultCount}} - +
\ No newline at end of file diff --git a/src/app/record-editor/search-bar/search-bar.component.ts b/src/app/record-editor/search-bar/search-bar.component.ts index e3d4ec9..507b618 100644 --- a/src/app/record-editor/search-bar/search-bar.component.ts +++ b/src/app/record-editor/search-bar/search-bar.component.ts @@ -55,13 +55,6 @@ export class SearchBarComponent extends SubscriberComponent implements OnInit { } ngOnInit() { - this.recordSearchService.cursor$ - .takeUntil(this.isDestroyed) - .subscribe(cursor => { - this.cursor = cursor; - this.changeDetectorRef.markForCheck(); - }); - this.recordSearchService.resultCount$ .takeUntil(this.isDestroyed) .subscribe(resultCount => { @@ -73,6 +66,7 @@ export class SearchBarComponent extends SubscriberComponent implements OnInit { .takeUntil(this.isDestroyed) .subscribe((params: SearchParams) => { this.query = params.query; + this.cursor = Number(params.cursor) || 0; this.changeDetectorRef.markForCheck(); }); @@ -145,12 +139,21 @@ export class SearchBarComponent extends SubscriberComponent implements OnInit { private next() { this.cursor++; - this.recordSearchService.setCursor(this.cursor); + this.navigateToCursor(); } private previous() { this.cursor--; - this.recordSearchService.setCursor(this.cursor); + this.navigateToCursor(); + } + + private navigateToCursor() { + this.router.navigate(['record', this.recordType, 'search'], { + queryParams: { + query: this.query, + cursor: this.cursor + } + }); } } diff --git a/src/app/record-editor/tickets/tickets.component.ts b/src/app/record-editor/tickets/tickets.component.ts index 5d07a7f..1b53330 100644 --- a/src/app/record-editor/tickets/tickets.component.ts +++ b/src/app/record-editor/tickets/tickets.component.ts @@ -21,12 +21,10 @@ */ import { Component, OnInit, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core'; - -import { ToastrService } from 'ngx-toastr'; +import { ActivatedRoute } from '@angular/router'; import { Observable } from 'rxjs/Observable'; -import { RecordApiService } from '../../core/services'; import { Ticket } from '../../shared/interfaces'; import { SubscriberComponent } from '../../shared/classes'; @@ -44,16 +42,19 @@ export class TicketsComponent extends SubscriberComponent implements OnInit { displayLimit = 1; tickets: Array; - constructor(private apiService: RecordApiService, - private toastrService: ToastrService, + constructor(private route: ActivatedRoute, private changeDetectorRef: ChangeDetectorRef) { super(); } ngOnInit() { - this.apiService.newRecordFetched$ + // TODO: handle error after https://github.com/angular/angular/issues/13873 is resolved + this.route.data .takeUntil(this.isDestroyed) - .subscribe(() => this.fetchTickets()); + .subscribe((data: { tickets: Array}) => { + this.tickets = data.tickets; + this.changeDetectorRef.markForCheck(); + }); } onTicketResolve(ticketIndex: number) { @@ -63,18 +64,4 @@ export class TicketsComponent extends SubscriberComponent implements OnInit { onTicketCreate(ticket: Ticket) { this.tickets.push(ticket); } - - private fetchTickets() { - this.apiService.fetchRecordTickets() - .then(tickets => { - this.tickets = tickets; - this.changeDetectorRef.markForCheck(); - }).catch(error => { - if (error.status === 403) { - this.toastrService.error('Logged in user can not access to tickets', 'Forbidden'); - } else { - this.toastrService.error('Could not load the tickets!', 'Error'); - } - }); - } } diff --git a/src/app/shared/classes/api-error.ts b/src/app/shared/classes/api-error.ts index dab7547..dbeac41 100644 --- a/src/app/shared/classes/api-error.ts +++ b/src/app/shared/classes/api-error.ts @@ -31,8 +31,15 @@ export class ApiError { constructor(error: Response, parseBody = true) { this.status = error.status; - this.body = parseBody ? - error.json() : Object.create(null); + if (parseBody) { + try { + this.body = error.json(); + } catch (_) { + this.body = Object.create(null); + } + } else { + this.body = Object.create(null); + } } get message(): string | undefined { From 5e2c99bfe815788ff89a3f3991713db52ffb56a2 Mon Sep 17 00:00:00 2001 From: harunurhan Date: Wed, 31 Jan 2018 15:43:10 +0100 Subject: [PATCH 5/6] fix lock and only search route --- src/app/core/services/record-api.service.ts | 3 +++ .../json-editor-wrapper/json-editor-wrapper.component.ts | 2 +- src/app/record-editor/record-resources.resolver.ts | 5 +++++ src/app/record-editor/record-search.resolver.ts | 5 +++++ 4 files changed, 14 insertions(+), 1 deletion(-) diff --git a/src/app/core/services/record-api.service.ts b/src/app/core/services/record-api.service.ts index 4d25f35..d2bc7a7 100644 --- a/src/app/core/services/record-api.service.ts +++ b/src/app/core/services/record-api.service.ts @@ -49,6 +49,9 @@ export class RecordApiService extends CommonApiService { } fetchRecordResources(pidType: string, pidValue: string): Observable { + if (this.currentRecordEditorApiUrl) { + this.unlockRecord(); + } this.currentRecordId = pidValue; this.currentRecordSaveApiUrl = `${apiUrl}/${pidType}/${pidValue}/db`; this.currentRecordEditorApiUrl = `${editorApiUrl}/${pidType}/${pidValue}`; diff --git a/src/app/record-editor/json-editor-wrapper/json-editor-wrapper.component.ts b/src/app/record-editor/json-editor-wrapper/json-editor-wrapper.component.ts index 8467ab4..2e9c483 100644 --- a/src/app/record-editor/json-editor-wrapper/json-editor-wrapper.component.ts +++ b/src/app/record-editor/json-editor-wrapper/json-editor-wrapper.component.ts @@ -79,6 +79,7 @@ export class JsonEditorWrapperComponent extends SubscriberComponent implements O this.route.data .takeUntil(this.isDestroyed) + .filter((data: { resources: RecordResources }) => data.resources != null) .subscribe((data: { resources: RecordResources }) => { this.assignResourcesToPropertiesWithSideEffects(data.resources); }); @@ -104,7 +105,6 @@ export class JsonEditorWrapperComponent extends SubscriberComponent implements O ngOnDestroy() { super.ngOnDestroy(); - this.apiService.unlockRecord(); this.notIdle.stop(); } diff --git a/src/app/record-editor/record-resources.resolver.ts b/src/app/record-editor/record-resources.resolver.ts index a17a5e2..d97ac8f 100644 --- a/src/app/record-editor/record-resources.resolver.ts +++ b/src/app/record-editor/record-resources.resolver.ts @@ -36,6 +36,11 @@ export class RecordResourcesResolver implements Resolve { resolve(route: ActivatedRouteSnapshot): Observable { const recordType = route.params.type; const recordId = route.params.recid || route.parent.data.foundRecordId; + + if (!recordId) { + return Observable.of(null); + } + return this.apiService .fetchRecordResources(recordType, recordId) .take(1) diff --git a/src/app/record-editor/record-search.resolver.ts b/src/app/record-editor/record-search.resolver.ts index 7c84bfd..9aaaddf 100644 --- a/src/app/record-editor/record-search.resolver.ts +++ b/src/app/record-editor/record-search.resolver.ts @@ -38,6 +38,11 @@ export class RecordSearchResolver implements Resolve { resolve(route: ActivatedRouteSnapshot): Observable { const recordType = route.params.type; const query = route.queryParams.query; + + if (!query) { + return Observable.of(null); + } + let cursor = Number(route.queryParams.cursor); return this.recordSearchService From a4750f52cad8ac531a36f65335acd3826b6104f4 Mon Sep 17 00:00:00 2001 From: harunurhan Date: Wed, 31 Jan 2018 16:06:25 +0100 Subject: [PATCH 6/6] fix observable error on resolver --- src/app/record-editor/record-resources.resolver.ts | 2 +- src/app/record-editor/record-search.resolver.ts | 2 +- src/polyfills.ts | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/app/record-editor/record-resources.resolver.ts b/src/app/record-editor/record-resources.resolver.ts index d97ac8f..6fcf502 100644 --- a/src/app/record-editor/record-resources.resolver.ts +++ b/src/app/record-editor/record-resources.resolver.ts @@ -46,7 +46,7 @@ export class RecordResourcesResolver implements Resolve { .take(1) .catch((error: ApiError) => { this.router.navigate(['error', error.status]); - return null; + return Observable.empty(); }); } } diff --git a/src/app/record-editor/record-search.resolver.ts b/src/app/record-editor/record-search.resolver.ts index 9aaaddf..131f425 100644 --- a/src/app/record-editor/record-search.resolver.ts +++ b/src/app/record-editor/record-search.resolver.ts @@ -56,7 +56,7 @@ export class RecordSearchResolver implements Resolve { .map(foundIds => String(foundIds[cursor])) .catch((error: ApiError) => { this.router.navigate(['error', error.status]); - return null; + return Observable.empty(); }); } } diff --git a/src/polyfills.ts b/src/polyfills.ts index 59db4ed..c46e12b 100644 --- a/src/polyfills.ts +++ b/src/polyfills.ts @@ -21,6 +21,7 @@ import 'zone.js/dist/zone'; // common rxjs imports import 'rxjs/add/observable/combineLatest'; +import 'rxjs/add/observable/empty'; import 'rxjs/add/observable/from'; import 'rxjs/add/observable/throw';