Commit 54b4aa34 authored by François Agneray's avatar François Agneray
Browse files

#52 => done

parent 959a83ae
......@@ -14,6 +14,7 @@ import { CoreModule } from './core/core.module';
import { StaticModule } from './static/static.module';
import { LoginModule } from './login/login.module';
import { SearchModule } from './search/search.module';
import { DetailModule } from './detail/detail.module';
import { AppRoutingModule } from './app.routing';
import { AppComponent } from './core/containers/app.component';
import { environment } from '../environments/environment';
......@@ -31,6 +32,7 @@ import { environment } from '../environments/environment';
StaticModule,
LoginModule,
SearchModule,
DetailModule,
AppRoutingModule
],
providers: [ { provide: RouterStateSerializer, useClass: CustomRouterStateSerializer } ],
......
import { ObjectDisplayComponent } from './object-display.component';
export const dummiesComponents = [
ObjectDisplayComponent
];
\ No newline at end of file
<div class="row text-center mb-5">
<div class="col">
<h1>Object detail</h1>
</div>
</div>
<div class="row">
<table class="table table-striped table-bordered">
<tr *ngFor="let attribute of getAttributesVisible()">
<th>{{ attribute.form_label }}</th>
<td>{{ object[attribute.label] }}</td>
</tr>
</table>
</div>
\ No newline at end of file
import { Component, Input, ChangeDetectionStrategy } from '@angular/core';
import { Attribute } from '../../metamodel/model';
@Component({
selector: 'app-object-display',
templateUrl: 'object-display.component.html',
styleUrls: ['object-display.component.css'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class ObjectDisplayComponent {
@Input() attributeList: Attribute[];
@Input() object: any;
getAttributesVisible() {
return this.attributeList
.filter(a => a.detail)
.sort((a, b) => a.display_detail - b.display_detail);;
}
}
<div *ngIf="objectIsLoading | async" class="row justify-content-center mt-5">
<i class="fas fa-circle-notch fa-spin fa-3x"></i>
<span class="sr-only">Loading...</span>
</div>
<div *ngIf="objectIsLoaded | async">
<app-object-display [attributeList]="attributeList | async" [object]="object | async"></app-object-display>
</div>
<div *ngIf="!(pristine | async)" class="row mt-5 justify-content-between">
<div class="col">
<button (click)="goBackToResult()" class="btn btn-outline-secondary">
<i class="fa fa-backward"></i> Back to search results
</button>
</div>
</div>
\ No newline at end of file
import { Component, OnInit } from '@angular/core';
import { Location } from '@angular/common';
import { Observable } from 'rxjs';
import { Store } from '@ngrx/store';
import * as detailActions from '../store/detail.action';
import * as fromDetail from '../store/detail.reducer';
import * as detailSelector from '../store/detail.selector';
import * as searchSelector from '../../search/store/search.selector';
import { Attribute } from '../../metamodel/model';
interface StoreState {
detail: fromDetail.State;
}
@Component({
selector: 'app-detail-page',
templateUrl: 'detail.component.html',
styleUrls: ['detail.component.css']
})
export class DetailComponent implements OnInit {
public pristine: Observable<boolean>;
public attributeList: Observable<Attribute[]>;
public objectIsLoading: Observable<boolean>;
public objectIsLoaded: Observable<boolean>;
public object: Observable<any>;
constructor(private location: Location, private store: Store<StoreState>) {
this.pristine = this.store.select(searchSelector.getPristine);
this.attributeList = this.store.select(detailSelector.getAttributeList);
this.objectIsLoading = this.store.select(detailSelector.getObjectIsLoading);
this.objectIsLoaded = this.store.select(detailSelector.getObjectIsLoaded);
this.object = this.store.select(detailSelector.getObject);
}
ngOnInit() {
// Create a micro task that is processed after the current synchronous code
// This micro task prevent the expression has changed after view init error
Promise.resolve(null).then(() => this.store.dispatch(new detailActions.InitDetailAction()));
}
goBackToResult() {
this.location.back();
}
}
import { NgModule } from '@angular/core';
import { StoreModule } from '@ngrx/store';
import { EffectsModule } from '@ngrx/effects';
import { SharedModule } from '../shared/shared.module';
import { MetamodelModule } from '../metamodel/metamodel.module';
import { DetailEffects } from './store/detail.effects';
import { DetailService } from './store/detail.service';
import { DetailRoutingModule, routedComponents } from './detail.routing';
import { dummiesComponents } from './components';
import { reducer } from './store/detail.reducer';
@NgModule({
imports: [
SharedModule,
MetamodelModule,
DetailRoutingModule,
StoreModule.forFeature('detail', reducer),
EffectsModule.forFeature([DetailEffects])
],
declarations: [
routedComponents,
dummiesComponents
],
providers: [
DetailService
]
})
export class DetailModule { }
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { DetailComponent } from './containers/detail.component';
const routes: Routes = [
{ path: 'detail/:dname/:objectSelected', component: DetailComponent }
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class DetailRoutingModule { }
export const routedComponents = [
DetailComponent
];
import { Action } from '@ngrx/store';
import { Attribute } from '../../metamodel/model'
export const INIT_DETAIL = '[Detail] Init Detail';
export const LOAD_ATTRIBUTE_LIST = '[Detail] Load Attribute List';
export const LOAD_ATTRIBUTE_LIST_SUCCESS = '[Detail] Load Attribute List Success';
export const LOAD_ATTRIBUTE_LIST_FAIL = '[Detail] Load Attribute List Fail';
export const COPY_ATTRIBUTE_LIST = '[Detail] Copy Attribute List';
export const RETRIEVE_OBJECT = '[Detail] Retrieve Object';
export const RETRIEVE_OBJECT_SUCCESS = '[Detail] Retrieve Object Success';
export const RETRIEVE_OBJECT_FAIL = '[Detail] Retrieve Object Fail';
export class InitDetailAction implements Action {
type = INIT_DETAIL;
constructor(public payload: {} = null) { }
}
export class LoadAttributeListAction implements Action {
type = LOAD_ATTRIBUTE_LIST;
constructor(public payload: {} = null) { }
}
export class LoadAttributeListSuccessAction implements Action {
type = LOAD_ATTRIBUTE_LIST_SUCCESS;
constructor(public payload: Attribute[]) { }
}
export class LoadAttributeListFailAction implements Action {
type = LOAD_ATTRIBUTE_LIST_FAIL;
constructor(public payload: {} = null) { }
}
export class CopyAttributeListAction implements Action {
type = COPY_ATTRIBUTE_LIST;
constructor(public payload: {} = null) { }
}
export class RetrieveObjectAction implements Action {
type = RETRIEVE_OBJECT;
constructor(public payload: {datasetName: string, idAttribute: number, id: string} = null) { }
}
export class RetrieveObjectSuccessAction implements Action {
type = RETRIEVE_OBJECT_SUCCESS;
constructor(public payload: any) { }
}
export class RetrieveObjectFailAction implements Action {
type = RETRIEVE_OBJECT_FAIL;
constructor(public payload: {} = null) { }
}
export type Actions
= InitDetailAction
| LoadAttributeListAction
| LoadAttributeListSuccessAction
| LoadAttributeListFailAction
| CopyAttributeListAction
| RetrieveObjectAction
| RetrieveObjectSuccessAction
| RetrieveObjectFailAction;
\ No newline at end of file
import { Injectable } from '@angular/core';
import { ToastrService } from 'ngx-toastr';
import { Effect, Actions, ofType } from '@ngrx/effects';
import { Store } from '@ngrx/store';
import { of } from 'rxjs';
import { switchMap, map, catchError, tap, withLatestFrom } from 'rxjs/operators';
import * as detailActions from './detail.action';
import * as fromRouter from '@ngrx/router-store';
import * as fromMetamodel from '../../metamodel/reducers';
import * as fromSearch from '../../search/store/search.reducer';
import * as fromDetail from './detail.reducer';
import * as utils from '../../shared/utils';
import { Attribute } from '../../metamodel/model';
import { DetailService } from './detail.service';
import { AttributeService } from 'src/app/metamodel/services/attribute.service';
interface State {
router: fromRouter.RouterReducerState<utils.RouterStateUrl>,
metamodel: fromMetamodel.State,
search: fromSearch.State,
detail: fromDetail.State
}
@Injectable()
export class DetailEffects {
constructor(
private actions$: Actions,
private detailService: DetailService,
private attributeService: AttributeService,
private toastr: ToastrService,
private store$: Store<State>
) { }
@Effect()
initDetailAction$ = this.actions$.pipe(
ofType(detailActions.INIT_DETAIL),
withLatestFrom(this.store$),
map(([action, state]) => {
if (state.search.pristine) {
return new detailActions.LoadAttributeListAction();
} else {
return new detailActions.CopyAttributeListAction();
}
})
);
@Effect()
loadAttributeAction$ = this.actions$.pipe(
ofType(detailActions.LOAD_ATTRIBUTE_LIST),
withLatestFrom(this.store$),
switchMap(([action, state]) => {
const datasetName = state.router.state.params.dname;
return this.attributeService.retrieveAttributeList(datasetName).pipe(
map((attributeList: Attribute[]) => new detailActions.LoadAttributeListSuccessAction(attributeList)),
catchError(() => of(new detailActions.LoadAttributeListFailAction()))
);
})
);
@Effect()
loadAttributeSuccessAction$ = this.actions$.pipe(
ofType(detailActions.LOAD_ATTRIBUTE_LIST_SUCCESS),
switchMap(_ => of(new detailActions.RetrieveObjectAction()))
);
@Effect({ dispatch: false })
loadAttributeFailAction$ = this.actions$.pipe(
ofType(detailActions.LOAD_ATTRIBUTE_LIST_FAIL),
tap(_ => this.toastr.error('Loading Failed!', 'The attribute list loading failed'))
);
@Effect()
copyAttributeAction$ = this.actions$.pipe(
ofType(detailActions.COPY_ATTRIBUTE_LIST),
withLatestFrom(this.store$),
switchMap(([action, state]) => {
const attributeList = state.metamodel.attribute.datasetAttributeList;
return of(new detailActions.LoadAttributeListSuccessAction(attributeList));
})
);
@Effect()
retrieveObjectAction$ = this.actions$.pipe(
ofType(detailActions.RETRIEVE_OBJECT),
withLatestFrom(this.store$),
switchMap(([action, state]) => {
const datasetName = state.router.state.params.dname;
const objectSelected = state.router.state.params.objectSelected;
const attributes = state.detail.attributeList;
const attributeCriterionId = attributes.find(a => a.search_flag === 'ID');
const attributesOutputList = attributes.filter(a => a.detail).map(a => a.id);
return this.detailService.retrieveObject(datasetName, attributeCriterionId.id, objectSelected, attributesOutputList).pipe(
map((object: any[]) => new detailActions.RetrieveObjectSuccessAction(object[0])),
catchError(() => of(new detailActions.RetrieveObjectFailAction()))
);
})
);
@Effect({ dispatch: false })
retrieveObjectFailAction$ = this.actions$.pipe(
ofType(detailActions.RETRIEVE_OBJECT_FAIL),
tap(_ => this.toastr.error('Loading Failed!', 'The object loading failed'))
);
}
\ No newline at end of file
import * as actions from './detail.action';
import { Attribute } from '../../metamodel/model';
export interface State {
attributeList: Attribute[];
objectIsLoading: boolean;
objectIsLoaded: boolean;
object: any;
}
const initialState: State = {
attributeList: null,
objectIsLoading: false,
objectIsLoaded: false,
object: null
};
export function reducer(state: State = initialState, action: actions.Actions): State {
switch (action.type) {
case actions.INIT_DETAIL:
return {
...state,
objectIsLoading: true
};
case actions.LOAD_ATTRIBUTE_LIST_SUCCESS:
const attributeList = action.payload as Attribute[];
return  {
...state,
attributeList
}
case actions.RETRIEVE_OBJECT_SUCCESS:
const object = action.payload as any;
return {
...state,
objectIsLoading: false,
objectIsLoaded: true,
object
};
case actions.LOAD_ATTRIBUTE_LIST_FAIL:
case actions.RETRIEVE_OBJECT_FAIL:
return {
...state,
objectIsLoading: false,
objectIsLoaded: false
};
default:
return state;
}
}
export const getAttributeList = (state: State) => state.attributeList;
export const getObjectIsLoading = (state: State) => state.objectIsLoading;
export const getObjectIsLoaded = (state: State) => state.objectIsLoaded;
export const getObject = (state: State) => state.object;
\ No newline at end of file
import { createSelector, createFeatureSelector } from '@ngrx/store';
import * as detail from './detail.reducer';
export const getDetailState = createFeatureSelector<detail.State>('detail');
export const getAttributeList = createSelector(
getDetailState,
detail.getAttributeList
);
export const getObjectIsLoading = createSelector(
getDetailState,
detail.getObjectIsLoading
);
export const getObjectIsLoaded = createSelector(
getDetailState,
detail.getObjectIsLoaded
);
export const getObject = createSelector(
getDetailState,
detail.getObject
);
\ No newline at end of file
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { environment } from '../../../environments/environment';
@Injectable()
export class DetailService {
private API_PATH: string = environment.apiUrl + '/search';
constructor(private http: HttpClient) { }
retrieveObject(datasetName: string, attributeCriterionId: number, objectSelected: string, attributesOutputList: number[]) {
const query = datasetName + '?c=' + attributeCriterionId + "::eq::" + objectSelected + "&a=" + attributesOutputList.join(';');
return this.http.get<any[]>(this.API_PATH + '/data/' + query);
}
}
......@@ -2,13 +2,11 @@ import { Injectable } from '@angular/core';
import { ToastrService } from 'ngx-toastr';
import { Effect, Actions, ofType } from '@ngrx/effects';
import { Store } from '@ngrx/store';
import { of } from 'rxjs';
import { withLatestFrom, switchMap, map, catchError, tap } from 'rxjs/operators';
import { switchMap, map, catchError, tap } from 'rxjs/operators';
import { Attribute } from '../model';
import * as attributeActions from '../action/attribute.action';
import * as fromMetamodel from '../reducers';
import { AttributeService } from '../services/attribute.service';
@Injectable()
......@@ -16,8 +14,7 @@ export class AttributeEffects {
constructor(
private actions$: Actions,
private attributeService: AttributeService,
private toastr: ToastrService,
private store$: Store<{ metamodel: fromMetamodel.State }>
private toastr: ToastrService
) { }
@Effect()
......
......@@ -9,7 +9,6 @@ import { SummaryComponent } from './summary.component';
import { criteriaComponents } from './criteria/search-type';
import { UrlDisplayComponent } from './result/url-display.component';
import { DatatableComponent } from './result/datatable.component';
import { DetailComponent } from './result/detail.component';
import { ModalComponent } from './result/modal.component';
export const dummiesComponents = [
......@@ -24,6 +23,5 @@ export const dummiesComponents = [
SummaryComponent,
UrlDisplayComponent,
DatatableComponent,
DetailComponent,
ModalComponent
];
......@@ -41,10 +41,7 @@
{{ datum[attribute.name] }}</a>
</div>
<div *ngSwitchCase="'int-link'">
<!-- <a [routerLink]="getAttributeUriAction(attribute.name, datum[attribute.label])">
{{ datum[attribute.name] }}</a> -->
<a routerLink="/search/result/{{ datasetName }}/{{ datum[attribute.label] }}"
[queryParams]="queryParams">
<a routerLink="/detail/{{ datasetName }}/{{ datum[attribute.label] }}">
{{ datum[attribute.label] }}
</a>
</div>
......@@ -54,8 +51,8 @@
{{ datum[attribute.label] }}</a>
</div>
<div *ngSwitchCase="'int-btn'">
<a routerLink="/search/result/{{ datasetName }}/{{ datum[attribute.label] }}"
[queryParams]="queryParams" class="btn btn-outline-primary btn-sm">
<a routerLink="/detail/{{ datasetName }}/{{ datum[attribute.label] }}"
class="btn btn-outline-primary btn-sm">
{{ datum[attribute.label] }}
</a>
</div>
......
......@@ -38,6 +38,11 @@ export class DatatableComponent {
this.getSearchData.emit(1);
}
getAttributeId(attributeName: string): number {
const attribute = this.datasetAttributeList.find(a => a.name === attributeName);
return attribute.id;
}
getAttributeRenderer(attributeName: string): string {
const attribute = this.datasetAttributeList.find(a => a.name === attributeName);
return attribute.renderer;
......
<p>dataset: {{ datasetName }}</p>
<p *ngFor="let attribute of searchMeta.attributes_selected">
{{ attribute.label }}: {{ getData(attribute.name) }}
</p>
\ No newline at end of file
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment