Skip to content
Snippets Groups Projects
Commit e425c15d authored by François Agneray's avatar François Agneray
Browse files

Merge branch '3-ajouter-une-table-pour-l-affichage-des-donnees' into 'develop'

#3 in progress

Closes #3

See merge request !5
parents b8e90dfc 4213c7db
No related branches found
No related tags found
2 merge requests!68Develop,!5#3 in progress
Pipeline #1133 passed
Showing
with 244 additions and 14 deletions
......@@ -52,7 +52,6 @@ export class LoginService {
}
changePassword(email: string, changePassword: ChangePassword) {
console.log({...changePassword, email});
return this.http.post(this.API_PATH + 'change-password', {...changePassword, email});
}
}
<p>
<button (click)="click()">click</button>
</p>
<accordion>
<accordion-group #ag [panelClass]="'custom-accordion'" class="my-2">
<button class="btn btn-link btn-block clearfix" accordion-heading>
<div class="pull-left float-left">
Datatable
&nbsp;
<span *ngIf="ag.isOpen"><i class="fas fa-chevron-up"></i></span>
<span *ngIf="!ag.isOpen"><i class="fas fa-chevron-down"></i></span>
</div>
</button>
<div *ngIf="searchMeta">
<div class="table-responsive-sm">
<table class="table table-striped">
<thead>
<tr>
<th *ngFor="let attribute of searchMeta.attributes_selected" scope="col">{{ attribute.label }}</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let datum of searchData">
<td *ngFor="let attribute of searchMeta.attributes_selected" [innerHTML]="datum[attribute.name]"></td>
</tr>
</tbody>
</table>
</div>
<pagination [totalItems]="searchMeta.total_items" (pageChanged)="getSearchData.emit($event.page)"></pagination>
</div>
</accordion-group>
</accordion>
import { Component, Input, ChangeDetectionStrategy, Output, EventEmitter } from '@angular/core';
import { SearchMeta } from '../store/model';
@Component({
selector: 'app-datatable',
templateUrl: 'datatable.component.html',
styleUrls: [ 'datatable.component.css' ],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class DatatableComponent {
@Input() searchMeta: SearchMeta;
@Input() searchData: any[];
@Output() initSearchMeta: EventEmitter<{}> = new EventEmitter();
@Output() getSearchData: EventEmitter<number> = new EventEmitter();
click() {
this.initSearchMeta.emit();
this.getSearchData.emit(1);
}
}
\ No newline at end of file
......@@ -6,6 +6,7 @@ import { OutputTabsComponent } from './output-tabs.component';
import { SummaryComponent } from './summary.component';
import { criteriaComponents } from './criteria';
import { UrlDisplayComponent } from './url-display.component';
import { DatatableComponent } from './datatable.component';
export const dummiesComponents = [
ProgressComponent,
......@@ -15,5 +16,6 @@ export const dummiesComponents = [
OutputTabsComponent,
criteriaComponents,
SummaryComponent,
UrlDisplayComponent
UrlDisplayComponent,
DatatableComponent
];
<p>
<a target="_blank" [href]="getUrl()">{{ getUrl() }}</a>
Direct link to the result (JSON format): <a target="_blank" [href]="getUrl()">{{ getUrl() }}</a>
</p>
\ No newline at end of file
......@@ -6,7 +6,12 @@
[criteriaList]="criteriaList | async"
[outputList]="outputList | async">
</app-url-display>
Datatable ?
<app-datatable
[searchMeta]="searchMeta | async"
[searchData]="searchData | async"
(initSearchMeta)="getSearchMeta()"
(getSearchData)="getSearchData($event)">
</app-datatable>
</div>
<div class="col-12 col-md-4 pt-2">
<app-summary
......
......@@ -5,7 +5,7 @@ import { Observable } from 'rxjs';
import { Store } from '@ngrx/store';
import { environment } from '../../../environments/environment';
import { Criterion } from '../store/model';
import { Criterion, SearchMeta } from '../store/model';
import { Dataset, DatasetOutputFamily, OutputFamily, Category, Attribute } from '../../metamodel/model';
import * as searchActions from '../store/search.action';
import * as datasetActions from '../../metamodel/action/dataset.action';
......@@ -37,6 +37,8 @@ export class ResultComponent implements OnInit {
public categoryList: Observable<Category[]>;
public criteriaList: Observable<Criterion[]>;
public outputList: Observable<number[]>;
public searchMeta: Observable<SearchMeta>;
public searchData: Observable<any[]>;
constructor(private route: ActivatedRoute, private store: Store<StoreState>) {
this.datasetName = store.select(searchSelector.getDatasetName);
......@@ -48,6 +50,8 @@ export class ResultComponent implements OnInit {
this.categoryList = this.store.select(metamodelSelector.getCategoryList);
this.criteriaList = this.store.select(searchSelector.getCriteriaList);
this.outputList = this.store.select(searchSelector.getOutputList);
this.searchMeta = this.store.select(searchSelector.getSearchMeta);
this.searchData = this.store.select(searchSelector.getSearchData);
}
ngOnInit() {
......@@ -64,4 +68,12 @@ export class ResultComponent implements OnInit {
this.store.dispatch(new outputActions.LoadOutputFamilyListAction());
this.store.dispatch(new outputActions.LoadCategoryListAction());
}
getSearchMeta(): void {
this.store.dispatch(new searchActions.RetrieveMetaAction());
}
getSearchData(page: number): void {
this.store.dispatch(new searchActions.RetrieveDataAction(page));
}
}
......@@ -6,6 +6,7 @@ import { EffectsModule } from '@ngrx/effects';
import { SharedModule } from '../shared/shared.module';
import { MetamodelModule } from '../metamodel/metamodel.module';
import { SearchEffects } from './store/search.effects';
import { SearchService } from './store/search.service';
import { SearchRoutingModule, routedComponents } from './search.routing';
import { dummiesComponents } from './components';
import { reducer } from './store/search.reducer';
......@@ -23,6 +24,7 @@ import { reducer } from './store/search.reducer';
dummiesComponents
],
providers: [
SearchService
]
})
export class SearchModule { }
......@@ -21,6 +21,6 @@ export class BetweenCriterion extends Criterion {
}
getCriterionStr() {
return this.id + '-bw-' + this.min + ':' + this.max;
return this.id + ':bw:' + this.min + '|' + this.max;
}
}
......@@ -4,4 +4,5 @@ export * from './field-criterion.model';
export * from './select-criterion';
export * from './checkbox-criterion.model';
export * from './radio-criterion.model';
export * from './select-multiple-criterion.model';
\ No newline at end of file
export * from './select-multiple-criterion.model';
export * from './search-meta.model';
\ No newline at end of file
export interface SearchMeta {
dataset_selected: string;
attributes_selected: {name: string, label: string}[];
total_items: number;
}
import { Action } from '@ngrx/store';
import { Criterion } from './model';
import { Criterion, SearchMeta } from './model';
export const CHANGE_STEP = 'Change Search Step';
export const SELECT_DATASET = 'Select Dataset';
......@@ -10,6 +10,12 @@ export const ADD_CRITERION = 'Add Criterion';
export const DELETE_CRITERION = 'Delete Criterion';
export const UPDATE_DEFAULT_OUTPUT_LIST = 'Update Default Output List';
export const UPDATE_OUTPUT_LIST = 'Update Output List';
export const RETRIEVE_META = 'Retrieve Meta';
export const RETRIEVE_META_SUCCESS = 'Retrieve Meta Success';
export const RETRIEVE_META_FAILED = 'Retrieve Meta Failed';
export const RETRIEVE_DATA = 'Retrieve Data';
export const RETRIEVE_DATA_SUCCESS = 'Retrieve Data Success';
export const RETRIEVE_DATA_FAILED = 'Retrieve Data Failed';
export class ChangeStepAction implements Action {
type = CHANGE_STEP;
......@@ -59,6 +65,42 @@ export class UpdateOutputListAction implements Action {
constructor(public payload: number[]) { }
}
export class RetrieveMetaAction implements Action {
type = RETRIEVE_META;
constructor(public payload: {} = null) { }
}
export class RetrieveMetaSuccessAction implements Action {
type = RETRIEVE_META_SUCCESS;
constructor(public payload: SearchMeta) { }
}
export class RetrieveMetaFailedAction implements Action {
type = RETRIEVE_META_FAILED;
constructor(public payload: {} = null) { }
}
export class RetrieveDataAction implements Action {
type = RETRIEVE_DATA;
constructor(public payload: number) { }
}
export class RetrieveDataSuccessAction implements Action {
type = RETRIEVE_DATA_SUCCESS;
constructor(public payload: any[]) { }
}
export class RetrieveDataFailedAction implements Action {
type = RETRIEVE_DATA_FAILED;
constructor(public payload: {} = null) { }
}
export type Actions
= ChangeStepAction
| SelectDatasetAction
......@@ -67,4 +109,10 @@ export type Actions
| AddCriterionAction
| DeleteCriterionAction
| UpdateDefaultOutputListAction
| UpdateOutputListAction;
| UpdateOutputListAction
| RetrieveMetaAction
| RetrieveMetaSuccessAction
| RetrieveMetaFailedAction
| RetrieveDataAction
| RetrieveDataSuccessAction
| RetrieveDataFailedAction;
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, withLatestFrom } from 'rxjs/operators';
import { map, switchMap, withLatestFrom, catchError } from 'rxjs/operators';
import * as criteriaActions from '../../metamodel/action/criteria.action';
import * as searchActions from './search.action';
import * as fromSearch from './search.reducer';
import { BetweenCriterion } from './model';
import { SearchService } from './search.service';
import { BetweenCriterion, SearchMeta } from './model';
@Injectable()
export class SearchEffects {
constructor(
private actions$: Actions,
private searchService: SearchService,
private toastr: ToastrService,
private store$: Store<{search: fromSearch.State}>
) { }
......@@ -67,4 +71,48 @@ export class SearchEffects {
}
})
);
@Effect()
retrieveMetaAction$ = this.actions$.pipe(
ofType(searchActions.RETRIEVE_META),
withLatestFrom(this.store$),
switchMap(([action, state]) => {
const query = state.search.datasetName + '?a=' +
state.search.outputList.join(';') + '&c=' +
state.search.criteriaList.map(criterion => criterion.getCriterionStr()).join(';');
return this.searchService.retrieveMeta(query).pipe(
map((searchMeta: SearchMeta) => new searchActions.RetrieveMetaSuccessAction(searchMeta)),
catchError(() => of(new searchActions.RetrieveMetaFailedAction()))
)
})
)
@Effect({dispatch: false})
retrieveMetaFailedAction$ = this.actions$.pipe(
ofType(searchActions.RETRIEVE_META_FAILED),
map(_ => this.toastr.error('Loading Failed!', 'The search meta data loading failed'))
);
@Effect()
retrieveDataAction$ = this.actions$.pipe(
ofType(searchActions.RETRIEVE_DATA),
withLatestFrom(this.store$),
switchMap(([action, state]) => {
const retrieveDataAction = action as searchActions.RetrieveDataAction;
const query = state.search.datasetName + '?a=' +
state.search.outputList.join(';') + '&c=' +
state.search.criteriaList.map(criterion => criterion.getCriterionStr()).join(';') +
'&p=10:' + retrieveDataAction.payload;
return this.searchService.retrieveData(query).pipe(
map((searchData: any[]) => new searchActions.RetrieveDataSuccessAction(searchData)),
catchError(() => of(new searchActions.RetrieveDataFailedAction()))
)
})
)
@Effect({dispatch: false})
retrieveDataFailedAction$ = this.actions$.pipe(
ofType(searchActions.RETRIEVE_DATA_FAILED),
map(_ => this.toastr.error('Loading Failed!', 'The search data loading failed'))
);
}
import * as actions from './search.action';
import { Criterion } from './model';
import { Criterion, SearchMeta } from './model';
export interface State {
currentStep: string;
datasetName: string;
criteriaList: Criterion[];
outputList: number[];
searchMeta: SearchMeta;
searchData: any[];
}
const initialState: State = {
currentStep: null,
datasetName: null,
criteriaList: [],
outputList: []
outputList: [],
searchMeta: null,
searchData: null
};
export function reducer(state: State = initialState, action: actions.Actions): State {
......@@ -66,6 +70,22 @@ export function reducer(state: State = initialState, action: actions.Actions): S
outputList
};
case actions.RETRIEVE_META_SUCCESS:
const searchMeta = action.payload as SearchMeta;
return {
...state,
searchMeta
}
case actions.RETRIEVE_DATA_SUCCESS:
const searchData = action.payload as any[];
return {
...state,
searchData
}
default:
return state;
}
......@@ -75,3 +95,5 @@ export const getCurrentStep = (state: State) => state.currentStep;
export const getDatasetName = (state: State) => state.datasetName;
export const getCriteriaList = (state: State) => state.criteriaList;
export const getOutputList = (state: State) => state.outputList;
export const getSearchMeta = (state: State) => state.searchMeta;
export const getSearchData = (state: State) => state.searchData;
......@@ -23,3 +23,13 @@ export const getOutputList = createSelector(
getSearchState,
search.getOutputList
);
export const getSearchMeta = createSelector(
getSearchState,
search.getSearchMeta
)
export const getSearchData = createSelector(
getSearchState,
search.getSearchData
)
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { SearchMeta } from './model';
import { environment } from '../../../environments/environment';
@Injectable()
export class SearchService {
private API_PATH: string = environment.apiUrl + '/search';
constructor(private http: HttpClient) { }
retrieveMeta(query: string) {
return this.http.get<SearchMeta>(this.API_PATH + '/meta/' + query);
}
retrieveData(query: string) {
return this.http.get<any[]>(this.API_PATH + '/data/' + query);
}
}
......@@ -4,7 +4,7 @@ import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { RouterModule } from '@angular/router';
import { ToastrModule } from 'ngx-toastr';
import { ModalModule, TabsModule, AccordionModule, CollapseModule, PopoverModule } from 'ngx-bootstrap';
import { ModalModule, TabsModule, AccordionModule, CollapseModule, PopoverModule, PaginationModule } from 'ngx-bootstrap';
import { BsModalService } from 'ngx-bootstrap/modal';
import { NgSelectModule } from '@ng-select/ng-select';
......@@ -19,6 +19,7 @@ import { NgSelectModule } from '@ng-select/ng-select';
AccordionModule.forRoot(),
CollapseModule.forRoot(),
PopoverModule.forRoot(),
PaginationModule.forRoot(),
NgSelectModule,
RouterModule
],
......@@ -32,6 +33,7 @@ import { NgSelectModule } from '@ng-select/ng-select';
AccordionModule,
CollapseModule,
PopoverModule,
PaginationModule,
NgSelectModule
],
providers: [ BsModalService ]
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment