diff --git a/src/app/search-multiple/containers/result.component.html b/src/app/search-multiple/containers/result.component.html new file mode 100644 index 0000000000000000000000000000000000000000..95c32c87c658a06e90d72124970e962bdf7fed3c --- /dev/null +++ b/src/app/search-multiple/containers/result.component.html @@ -0,0 +1,61 @@ +<div *ngIf="(datasetSearchMetaIsLoading | async) || (attributeSearchMetaIsLoading | async)" + class="row justify-content-center mt-5"> + <span class="fas fa-circle-notch fa-spin fa-3x"></span> + <span class="sr-only">Loading...</span> +</div> +<div *ngIf="(datasetSearchMetaIsLoaded | async) && (attributeSearchMetaIsLoaded | async)" class="row mt-4"> + <div class="col-12"> + <app-result-download + [datasetName]="datasetName | async" + [datasetList]="datasetList | async" + [dataLength]="dataLength | async" + [isConeSearchAdded]="isConeSearchAdded | async" + [coneSearch]="coneSearch | async" + [criteriaList]="criteriaList | async" + [outputList]="outputList | async"> + </app-result-download> + <app-reminder + [datasetAttributeList]="datasetAttributeList | async" + [isConeSearchAdded]="isConeSearchAdded | async" + [coneSearch]="coneSearch | async" + [criteriaFamilyList]="criteriaFamilyList | async" + [criteriaList]="criteriaList | async" + [outputFamilyList]="outputFamilyList | async" + [categoryList]="categoryList | async" + [outputList]="outputList | async"> + </app-reminder> + <app-result-url-display + [datasetList]="datasetList | async" + [datasetName]="datasetName | async" + [isConeSearchAdded]="isConeSearchAdded | async" + [coneSearch]="coneSearch | async" + [criteriaList]="criteriaList | async" + [outputList]="outputList | async"> + </app-result-url-display> + <app-result-datatable + [datasetName]="datasetName | async" + [datasetList]="datasetList | async" + [queryParams]="queryParams | async" + [datasetAttributeList]="datasetAttributeList | async" + [outputList]="outputList | async" + [searchData]="searchData | async" + [dataLength]="dataLength | async" + [selectedData]="selectedData | async" + (getSearchData)="getSearchData($event)" + (addSelectedData)="addSearchData($event)" + (deleteSelectedData)="deleteSearchData($event)" + [processWip]="processWip | async" + [processDone]="processDone | async" + [processId]="processId | async" + (executeProcess)="executeProcess($event)"> + </app-result-datatable> + </div> +</div> +<div class="row mt-5 justify-content-between"> + <div class="col"> + <a routerLink="/search/output/{{ datasetName | async }}" [queryParams]="queryParams | async" + class="btn btn-outline-secondary"> + <span class="fas fa-arrow-left"></span> Previous + </a> + </div> +</div> \ No newline at end of file diff --git a/src/app/search-multiple/containers/result.component.spec.ts b/src/app/search-multiple/containers/result.component.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..dc458148cdbefe510476ffe411ba567c6c0926e3 --- /dev/null +++ b/src/app/search-multiple/containers/result.component.spec.ts @@ -0,0 +1,161 @@ +import { ComponentFixture, TestBed, async } from '@angular/core/testing'; +import { provideMockStore, MockStore } from '@ngrx/store/testing'; +import { Component, Input } from '@angular/core'; + +import { ResultComponent } from './result.component'; +import * as fromMetamodel from '../../metamodel/reducers'; +import * as fromSearch from '../store/search.reducer'; +import * as fromConeSearch from '../../shared/cone-search/store/cone-search.reducer'; +import * as datasetActions from '../../metamodel/action/dataset.action'; +import * as searchActions from '../store/search.action'; +import { Dataset, Attribute, Family, Category } from 'src/app/metamodel/model'; +import { Criterion, SearchQueryParams } from '../store/model'; +import { ScrollTopService } from '../../shared/service/sroll-top.service'; +import { RouterLinkDirectiveStub } from '../../../settings/test-data/router-link-directive-stub'; +import { ConeSearch } from "../../shared/cone-search/store/model"; + +describe('[Search] Container: ResultComponent', () => { + @Component({ selector: 'app-result-download', template: '' }) + class DownloadSectionStubComponent { + @Input() datasetName: string; + @Input() datasetList: Dataset[]; + @Input() dataLength: number; + @Input() isConeSearchAdded: boolean; + @Input() coneSearch: ConeSearch; + @Input() criteriaList: Criterion[]; + @Input() outputList: number[]; + } + + @Component({ selector: 'app-reminder', template: '' }) + class ReminderStubComponent { + @Input() datasetAttributeList: Attribute[]; + @Input() isConeSearchAdded: boolean; + @Input() coneSearch: ConeSearch; + @Input() criteriaList: Criterion[]; + @Input() criteriaFamilyList: Family[]; + @Input() outputFamilyList: Family[]; + @Input() categoryList: Category[]; + @Input() outputList: number[]; + } + + @Component({ selector: 'app-result-url-display', template: '' }) + class UrlDisplaySectionStubComponent { + @Input() datasetList: Dataset[]; + @Input() datasetName: string; + @Input() isConeSearchAdded: boolean; + @Input() coneSearch: ConeSearch; + @Input() criteriaList: Criterion[]; + @Input() outputList: number[]; + } + + @Component({ selector: 'app-result-datatable', template: '' }) + class DatatableSectionStubComponent { + @Input() datasetName: string; + @Input() datasetList: Dataset[]; + @Input() queryParams: SearchQueryParams; + @Input() datasetAttributeList: Attribute[]; + @Input() outputList: number[]; + @Input() searchData: any[]; + @Input() dataLength: number; + @Input() selectedData: any[]; + @Input() processWip: boolean; + @Input() processDone: boolean; + @Input() processId: string; + } + + let scrollTopServiceStub: Partial<ScrollTopService> = { + setScrollTop() {} + }; + + let component: ResultComponent; + let fixture: ComponentFixture<ResultComponent>; + let store: MockStore; + const initialState = { + search: { ...fromSearch.initialState }, + metamodel: { ...fromMetamodel }, + coneSearch: { ...fromConeSearch } + }; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ + ResultComponent, + DownloadSectionStubComponent, + ReminderStubComponent, + UrlDisplaySectionStubComponent, + DatatableSectionStubComponent, + RouterLinkDirectiveStub + ], + providers: [ + provideMockStore({ initialState }), + { provide: ScrollTopService, useValue: scrollTopServiceStub } + ] + }); + fixture = TestBed.createComponent(ResultComponent); + component = fixture.componentInstance; + store = TestBed.inject(MockStore); + })); + + it('should create the component', () => { + expect(component).toBeTruthy(); + }); + + it('should execute ngOnInit lifecycle', (done) => { + const loadDatasetSearchMetaAction = new datasetActions.LoadDatasetSearchMetaAction(); + const changeStepAction = new searchActions.ChangeStepAction('result'); + const resultChecked = new searchActions.ResultChecked(); + const initSearchByUrl = new searchActions.InitSearchByUrl(); + const spy = spyOn(store, 'dispatch'); + component.ngOnInit(); + Promise.resolve(null).then(function() { + expect(spy).toHaveBeenCalledTimes(4); + expect(spy).toHaveBeenCalledWith(loadDatasetSearchMetaAction); + expect(spy).toHaveBeenCalledWith(changeStepAction); + expect(spy).toHaveBeenCalledWith(resultChecked); + expect(spy).toHaveBeenCalledWith(initSearchByUrl); + done(); + }); + }); + + it('#getSearchData() should dispatch RetrieveDataAction and GetDataLengthAction', () => { + const retrieveDataAction = new searchActions.RetrieveDataAction([1, 2, 3, 'four']); + const getDataLengthAction = new searchActions.GetDataLengthAction(); + const spy = spyOn(store, 'dispatch'); + component.getSearchData([1, 2, 3, 'four']); + expect(spy).toHaveBeenCalledTimes(2); + expect(spy).toHaveBeenCalledWith(retrieveDataAction); + expect(spy).toHaveBeenCalledWith(getDataLengthAction); + }); + + it('#addSearchData() should dispatch AddSelectedDataAction', () => { + const addSelectedDataAction = new searchActions.AddSelectedDataAction('toto'); + const spy = spyOn(store, 'dispatch'); + component.addSearchData('toto'); + expect(spy).toHaveBeenCalledTimes(1); + expect(spy).toHaveBeenCalledWith(addSelectedDataAction); + }); + + it('#deleteSearchData() should dispatch DeleteSelectedDataAction', () => { + const deleteSelectedDataAction = new searchActions.DeleteSelectedDataAction('toto'); + const spy = spyOn(store, 'dispatch'); + component.deleteSearchData('toto'); + expect(spy).toHaveBeenCalledTimes(1); + expect(spy).toHaveBeenCalledWith(deleteSelectedDataAction); + }); + + it('#executeProcess() should dispatch ExecuteProcessAction', () => { + const executeProcessAction = new searchActions.ExecuteProcessAction('toto'); + const spy = spyOn(store, 'dispatch'); + component.executeProcess('toto'); + expect(spy).toHaveBeenCalledTimes(1); + expect(spy).toHaveBeenCalledWith(executeProcessAction); + }); + + it('#ngOnDestroy() should dispatch DestroyResultsAction', () => { + const destroyResultsAction = new searchActions.DestroyResultsAction(); + const spy = spyOn(store, 'dispatch'); + component.ngOnDestroy(); + expect(spy).toHaveBeenCalledTimes(1); + expect(spy).toHaveBeenCalledWith(destroyResultsAction); + }); +}); diff --git a/src/app/search-multiple/containers/result.component.ts b/src/app/search-multiple/containers/result.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..385811068eb368fba81c3e0c33f3b3475c51154d --- /dev/null +++ b/src/app/search-multiple/containers/result.component.ts @@ -0,0 +1,43 @@ +import { Component, OnInit, OnDestroy } from '@angular/core'; + +import { Observable } from 'rxjs'; +import { Store } from '@ngrx/store'; + +import { Criterion, SearchQueryParams } from '../store/model'; +import { Dataset, Attribute, Family, Category } from '../../metamodel/model'; +import { ConeSearch } from "../../shared/cone-search/store/model"; +import * as searchActions from '../store/search.action'; +import * as datasetActions from '../../metamodel/action/dataset.action'; +import * as fromSearch from '../store/search.reducer'; +import * as fromMetamodel from '../../metamodel/reducers'; +import * as searchSelector from '../store/search.selector'; +import * as metamodelSelector from '../../metamodel/selectors'; +import * as coneSearchSelector from '../../shared/cone-search/store/cone-search.selector'; +import { ScrollTopService } from '../../shared/service/sroll-top.service'; + +interface StoreState { + searchMultiple: fromSearchMultiple.State; + metamodel: fromMetamodel.State; +} + +@Component({ + selector: 'app-result-multiple', + templateUrl: 'result.component.html' +}) +export class ResultMultipleComponent implements OnInit { + // public datasetSearchMetaIsLoading: Observable<boolean>; + + constructor(private store: Store<StoreState>, private scrollTopService: ScrollTopService) { + // this.datasetSearchMetaIsLoading = store.select(metamodelSelector.getDatasetSearchMetaIsLoading); + } + + 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 searchActions.ChangeStepAction('result'))); + // Promise.resolve(null).then(() => this.store.dispatch(new searchActions.ResultChecked())); + // Promise.resolve(null).then(() => this.store.dispatch(new searchActions.InitSearchByUrl())); + // this.store.dispatch(new datasetActions.LoadDatasetSearchMetaAction()); + this.scrollTopService.setScrollTop(); + } +} diff --git a/src/app/search-multiple/search-multiple-routing.module.ts b/src/app/search-multiple/search-multiple-routing.module.ts new file mode 100644 index 0000000000000000000000000000000000000000..949d486f317bf1a2c09efe245434d1dc3e55d9a2 --- /dev/null +++ b/src/app/search-multiple/search-multiple-routing.module.ts @@ -0,0 +1,31 @@ +import { NgModule } from '@angular/core'; +import { Routes, RouterModule } from '@angular/router'; + +import { SearchMultipleComponent } from './containers/search.component'; +import { ResultMultipleComponent } from './containers/result.component'; + +const routes: Routes = [ + { + path: '', component: SearchComponent, children: [ + { path: '', redirectTo: 'dataset', pathMatch: 'full' }, + { path: 'dataset', component: DatasetComponent }, + { path: 'criteria/:dname', component: CriteriaComponent }, + { path: 'output/:dname', component: OutputComponent }, + { path: 'result/:dname', component: ResultComponent } + ] + } +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule] +}) +export class SearchMultipleRoutingModule { } + +export const routedComponents = [ + SearchComponent, + DatasetComponent, + CriteriaComponent, + OutputComponent, + ResultComponent +]; diff --git a/src/app/search-multiple/search-multiple.module.ts b/src/app/search-multiple/search-multiple.module.ts new file mode 100644 index 0000000000000000000000000000000000000000..19006919b6e658cda420f46ecbb3b32f170b5737 --- /dev/null +++ b/src/app/search-multiple/search-multiple.module.ts @@ -0,0 +1,28 @@ +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 { SearchMultipleEffects } from './store/search-multiple.effects'; +import { SearchMultipleService } from './store/search-multiple.service'; +import { SearchMultipleRoutingModule, routedComponents } from './search-multiple-routing.module'; +import { dummiesComponents } from './components'; +import { reducer } from './store/search-multiple.reducer'; + +@NgModule({ + imports: [ + SharedModule, + MetamodelModule, + SearchMultipleRoutingModule, + StoreModule.forFeature('search-multiple', reducer), + EffectsModule.forFeature([SearchMultipleEffects]) + ], + declarations: [ + routedComponents, + dummiesComponents + ], + providers: [SearchMultipleService] +}) +export class SearchMultipleModule { } diff --git a/src/app/search-multiple/store/search-multiple.action.spec.ts b/src/app/search-multiple/store/search-multiple.action.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..fbee347b2e3a23518e22c1ab880d411754ded196 --- /dev/null +++ b/src/app/search-multiple/store/search-multiple.action.spec.ts @@ -0,0 +1,154 @@ +import * as searchActions from '../store/search.action'; +import { Criterion } from './model'; +import { CRITERIA_LIST } from 'src/settings/test-data'; + +describe('[Search] Action', () => { + it('should create InitSearchByUrl action', () => { + const action = new searchActions.InitSearchByUrl(); + expect(action.type).toEqual(searchActions.INIT_SEARCH_BY_URL); + }); + + it('should create ChangeStepAction', () => { + const action = new searchActions.ChangeStepAction('toto'); + expect(action.type).toEqual(searchActions.CHANGE_STEP); + expect(action.payload).toEqual('toto'); + }); + + it('should create SelectDatasetAction', () => { + const action = new searchActions.SelectDatasetAction('toto'); + expect(action.type).toEqual(searchActions.SELECT_DATASET); + expect(action.payload).toEqual('toto'); + }); + + it('should create NewSearchAction', () => { + const action = new searchActions.NewSearchAction('toto'); + expect(action.type).toEqual(searchActions.NEW_SEARCH); + expect(action.payload).toEqual('toto'); + }); + + it('should create CriteriaChecked action', () => { + const action = new searchActions.CriteriaChecked(); + expect(action.type).toEqual(searchActions.CRITERIA_CHECKED); + }); + + it('should create OutputChecked action', () => { + const action = new searchActions.OutputChecked(); + expect(action.type).toEqual(searchActions.OUTPUT_CHECKED); + }); + + it('should create ResultChecked action', () => { + const action = new searchActions.ResultChecked(); + expect(action.type).toEqual(searchActions.RESULT_CHECKED); + }); + + it('should create IsConeSearchAddedAction', () => { + const action = new searchActions.IsConeSearchAddedAction(true); + expect(action.type).toEqual(searchActions.IS_CONE_SEARCH_ADDED); + expect(action.payload).toBeTruthy(); + }); + + it('should create UpdateCriteriaListAction', () => { + const criteriaList: Criterion[] = CRITERIA_LIST; + const action = new searchActions.UpdateCriteriaListAction(criteriaList); + expect(action.type).toEqual(searchActions.UPDATE_CRITERIA_LIST); + expect(action.payload).toEqual(criteriaList); + }); + + it('should create AddCriterionAction', () => { + const criterion: Criterion = CRITERIA_LIST[0]; + const action = new searchActions.AddCriterionAction(criterion); + expect(action.type).toEqual(searchActions.ADD_CRITERION); + expect(action.payload).toEqual(criterion); + }); + + it('should create DeleteCriterionAction', () => { + const action = new searchActions.DeleteCriterionAction(1); + expect(action.type).toEqual(searchActions.DELETE_CRITERION); + expect(action.payload).toEqual(1); + }); + + it('should create UpdateOutputListAction', () => { + const action = new searchActions.UpdateOutputListAction([1, 2]); + expect(action.type).toEqual(searchActions.UPDATE_OUTPUT_LIST); + expect(action.payload).toEqual([1, 2]); + }); + + it('should create OutputListEmptyAction', () => { + const action = new searchActions.OutputListEmptyAction(true); + expect(action.type).toEqual(searchActions.OUTPUT_LIST_EMPTY); + expect(action.payload).toBeTruthy(); + }); + + it('should create RetrieveDataAction', () => { + const action = new searchActions.RetrieveDataAction([1, 2, 3, 'four']); + expect(action.type).toEqual(searchActions.RETRIEVE_DATA); + expect(action.payload).toEqual([1, 2, 3, 'four']); + }); + + it('should create RetrieveDataSuccessAction', () => { + const action = new searchActions.RetrieveDataSuccessAction(['toto']); + expect(action.type).toEqual(searchActions.RETRIEVE_DATA_SUCCESS); + expect(action.payload).toEqual(['toto']); + }); + + it('should create RetrieveDataFailAction', () => { + const action = new searchActions.RetrieveDataFailAction(); + expect(action.type).toEqual(searchActions.RETRIEVE_DATA_FAIL); + }); + + it('should create GetDataLengthAction', () => { + const action = new searchActions.GetDataLengthAction(); + expect(action.type).toEqual(searchActions.GET_DATA_LENGTH); + }); + + it('should create GetDataLengthSuccessAction', () => { + const action = new searchActions.GetDataLengthSuccessAction(1); + expect(action.type).toEqual(searchActions.GET_DATA_LENGTH_SUCCESS); + expect(action.payload).toEqual(1); + }); + + it('should create GetDataLengthFailAction', () => { + const action = new searchActions.GetDataLengthFailAction(); + expect(action.type).toEqual(searchActions.GET_DATA_LENGTH_FAIL); + }); + + it('should create AddSelectedDataAction', () => { + const action = new searchActions.AddSelectedDataAction(1); + expect(action.type).toEqual(searchActions.ADD_SELECTED_DATA); + expect(action.payload).toEqual(1); + }); + + it('should create DeleteSelectedDataAction', () => { + const action = new searchActions.DeleteSelectedDataAction(1); + expect(action.type).toEqual(searchActions.DELETE_SELECTED_DATA); + expect(action.payload).toEqual(1); + }); + + it('should create ExecuteProcessAction', () => { + const action = new searchActions.ExecuteProcessAction('toto'); + expect(action.type).toEqual(searchActions.EXECUTE_PROCESS); + expect(action.payload).toEqual('toto'); + }); + + it('should create ExecuteProcessWipAction', () => { + const action = new searchActions.ExecuteProcessWipAction('toto'); + expect(action.type).toEqual(searchActions.EXECUTE_PROCESS_WIP); + expect(action.payload).toEqual('toto'); + }); + + it('should create ExecuteProcessSuccessAction', () => { + const action = new searchActions.ExecuteProcessSuccessAction('toto'); + expect(action.type).toEqual(searchActions.EXECUTE_PROCESS_SUCCESS); + expect(action.payload).toEqual('toto'); + }); + + it('should create ExecuteProcessFailAction', () => { + const action = new searchActions.ExecuteProcessFailAction(); + expect(action.type).toEqual(searchActions.EXECUTE_PROCESS_FAIL); + }); + + it('should create DestroyResultsAction', () => { + const action = new searchActions.DestroyResultsAction(); + expect(action.type).toEqual(searchActions.DESTROY_RESULTS); + }); +}); diff --git a/src/app/search-multiple/store/search-multiple.action.ts b/src/app/search-multiple/store/search-multiple.action.ts new file mode 100644 index 0000000000000000000000000000000000000000..179c52af6d87754748bc155a5f1f6d63393197cd --- /dev/null +++ b/src/app/search-multiple/store/search-multiple.action.ts @@ -0,0 +1,216 @@ +import { Action } from '@ngrx/store'; + +import { Criterion } from './model'; + +export const INIT_SEARCH_BY_URL = '[Search] Init Search By Url'; +export const CHANGE_STEP = '[Search] Change Search Step'; +export const SELECT_DATASET = '[Search] Select Dataset'; +export const NEW_SEARCH = '[Search] New Search'; +export const CRITERIA_CHECKED = '[Search] Criteria Checked'; +export const OUTPUT_CHECKED = '[Search] Output Checked'; +export const RESULT_CHECKED = '[Search] Result Checked'; +export const IS_CONE_SEARCH_ADDED = '[Search] Is Cone Search Added'; +export const UPDATE_CRITERIA_LIST = '[Search] Update Criteria List'; +export const ADD_CRITERION = '[Search] Add Criterion'; +export const DELETE_CRITERION = '[Search] Delete Criterion'; +export const UPDATE_OUTPUT_LIST = '[Search] Update Output List'; +export const OUTPUT_LIST_EMPTY = '[Search] Output List Empty'; +export const RETRIEVE_DATA = '[Search] Retrieve Data'; +export const RETRIEVE_DATA_SUCCESS = '[Search] Retrieve Data Success'; +export const RETRIEVE_DATA_FAIL = '[Search] Retrieve Data Fail'; +export const GET_DATA_LENGTH = '[Search] Get Data Length'; +export const GET_DATA_LENGTH_SUCCESS = '[Search] Get Data Length Success'; +export const GET_DATA_LENGTH_FAIL = '[Search] Get Data Length Fail'; +export const ADD_SELECTED_DATA = '[Search] Add Selected Data'; +export const DELETE_SELECTED_DATA = '[Search] Delete Selected Data'; +export const EXECUTE_PROCESS = '[Search] Execute Process'; +export const EXECUTE_PROCESS_WIP = '[Search] Execute Process WIP'; +export const EXECUTE_PROCESS_SUCCESS = '[Search] Execute Process Success'; +export const EXECUTE_PROCESS_FAIL = '[Search] Execute Process Fail'; +export const DESTROY_RESULTS = '[Search] Destroy Results'; + + +export class InitSearchByUrl implements Action { + readonly type = INIT_SEARCH_BY_URL; + + constructor(public payload: {} = null) { } +} + +export class ChangeStepAction implements Action { + readonly type = CHANGE_STEP; + + constructor(public payload: string) { } +} + +export class SelectDatasetAction implements Action { + readonly type = SELECT_DATASET; + + constructor(public payload: string) { } +} + +export class NewSearchAction implements Action { + readonly type = NEW_SEARCH; + + constructor(public payload: string) { } +} + +export class CriteriaChecked implements Action { + readonly type = CRITERIA_CHECKED; + + constructor(public payload: {} = null) { } +} + +export class OutputChecked implements Action { + readonly type = OUTPUT_CHECKED; + + constructor(public payload: {} = null) { } +} + +export class ResultChecked implements Action { + readonly type = RESULT_CHECKED; + + constructor(public payload: {} = null) { } +} + +export class IsConeSearchAddedAction implements Action { + readonly type = IS_CONE_SEARCH_ADDED; + + constructor(public payload: boolean) { } +} + +export class UpdateCriteriaListAction implements Action { + readonly type = UPDATE_CRITERIA_LIST; + + constructor(public payload: Criterion[]) { } +} + +export class AddCriterionAction implements Action { + readonly type = ADD_CRITERION; + + constructor(public payload: Criterion) { } +} + +export class DeleteCriterionAction implements Action { + readonly type = DELETE_CRITERION; + + constructor(public payload: number) { } +} + +export class UpdateOutputListAction implements Action { + readonly type = UPDATE_OUTPUT_LIST; + + constructor(public payload: number[]) { } +} + +export class RetrieveDataAction implements Action { + readonly type = RETRIEVE_DATA; + + // [nbItems, page, sortedCol, sortedOrder] + constructor(public payload: [number, number, number, string]) { } +} + +export class RetrieveDataSuccessAction implements Action { + readonly type = RETRIEVE_DATA_SUCCESS; + + constructor(public payload: any[]) { } +} + +export class RetrieveDataFailAction implements Action { + readonly type = RETRIEVE_DATA_FAIL; + + constructor(public payload: {} = null) { } +} + +export class GetDataLengthAction implements Action { + readonly type = GET_DATA_LENGTH; + + constructor(public payload: {} = null) { } +} + +export class GetDataLengthSuccessAction implements Action { + readonly type = GET_DATA_LENGTH_SUCCESS; + + constructor(public payload: number) { } +} + +export class GetDataLengthFailAction implements Action { + readonly type = GET_DATA_LENGTH_FAIL; + + constructor(public payload: {} = null) { } +} + +export class AddSelectedDataAction implements Action { + readonly type = ADD_SELECTED_DATA; + + constructor(public payload: number | string) { } +} + +export class DeleteSelectedDataAction implements Action { + readonly type = DELETE_SELECTED_DATA; + + constructor(public payload: number | string) { } +} + +export class ExecuteProcessAction implements Action { + readonly type = EXECUTE_PROCESS; + + constructor(public payload: string) { } +} + +export class ExecuteProcessWipAction implements Action { + readonly type = EXECUTE_PROCESS_WIP; + + constructor(public payload: string) { } +} + +export class ExecuteProcessSuccessAction implements Action { + readonly type = EXECUTE_PROCESS_SUCCESS; + + constructor(public payload: any) { } +} + +export class ExecuteProcessFailAction implements Action { + readonly type = EXECUTE_PROCESS_FAIL; + + constructor(public payload: {} = null) { } +} + +export class OutputListEmptyAction implements Action { + readonly type = OUTPUT_LIST_EMPTY; + + constructor(public payload: boolean) { } +} + +export class DestroyResultsAction implements Action { + readonly type = DESTROY_RESULTS; + + constructor(public payload: {} = null) { } +} + +export type Actions + = InitSearchByUrl + | ChangeStepAction + | SelectDatasetAction + | NewSearchAction + | CriteriaChecked + | OutputChecked + | ResultChecked + | IsConeSearchAddedAction + | UpdateCriteriaListAction + | AddCriterionAction + | DeleteCriterionAction + | UpdateOutputListAction + | OutputListEmptyAction + | RetrieveDataAction + | RetrieveDataSuccessAction + | RetrieveDataFailAction + | GetDataLengthAction + | GetDataLengthSuccessAction + | GetDataLengthFailAction + | AddSelectedDataAction + | DeleteSelectedDataAction + | ExecuteProcessAction + | ExecuteProcessWipAction + | ExecuteProcessSuccessAction + | ExecuteProcessFailAction + | DestroyResultsAction; diff --git a/src/app/search-multiple/store/search-multiple.effects.ts b/src/app/search-multiple/store/search-multiple.effects.ts new file mode 100644 index 0000000000000000000000000000000000000000..57f7537ed650c5f2041eb64d1d5b47cb558e684b --- /dev/null +++ b/src/app/search-multiple/store/search-multiple.effects.ts @@ -0,0 +1,315 @@ +import { Injectable } from '@angular/core'; +import { HttpErrorResponse } from '@angular/common/http'; + +import { ToastrService } from 'ngx-toastr'; +import { Effect, Actions, ofType } from '@ngrx/effects'; +import { Store, Action } from '@ngrx/store'; +import { of } from 'rxjs'; +import { map, tap, switchMap, withLatestFrom, catchError, delay } from 'rxjs/operators'; + +import * as attributeActions from '../../metamodel/action/attribute.action'; +import * as criteriaActions from '../../metamodel/action/criteria.action'; +import * as outputActions from '../../metamodel/action/output.action'; +import * as searchActions from './search.action'; +import * as coneSearchActions from '../../shared/cone-search/store/cone-search.action'; +import * as fromMetamodel from '../../metamodel/reducers'; +import * as fromRouter from '@ngrx/router-store'; +import * as fromSearch from './search.reducer'; +import * as fromConeSearch from '../../shared/cone-search/store/cone-search.reducer'; +import * as utils from '../../shared/utils'; +import { SearchMultipleService } from './search.service'; +import { getCriterionStr } from '../../shared/utils'; +import { ConeSearch } from "../../shared/cone-search/store/model"; + +@Injectable() +export class SearchMultipleEffects { + constructor( + private actions$: Actions, + private searchService: SearchMultipleService, + private toastr: ToastrService, + private store$: Store<{ + router: fromRouter.RouterReducerState<utils.RouterStateUrl>, + search: fromSearch.State, + metamodel: fromMetamodel.State, + coneSearch: fromConeSearch.State + }> + ) { } + + @Effect() + initSearchByUrlAction$ = this.actions$.pipe( + ofType(searchActions.INIT_SEARCH_BY_URL), + withLatestFrom(this.store$), + switchMap(([action, state]) => { + if (state.search.pristine) { + const datasetName = state.router.state.params.dname; + const actions: Action[] = [ + new attributeActions.LoadAttributeSearchMetaAction(datasetName), + new searchActions.SelectDatasetAction(datasetName), + new criteriaActions.LoadCriteriaSearchMetaAction(datasetName), + new outputActions.LoadOutputSearchMetaAction(datasetName) + ]; + if (state.router.state.queryParams.s[0] === '1') { + actions.push(new searchActions.CriteriaChecked()); + } + if (state.router.state.queryParams.s[1] === '1') { + actions.push(new searchActions.OutputChecked()); + } + if (state.router.state.queryParams.s[2] === '1') { + actions.push(new searchActions.ResultChecked()); + } + return actions; + } else { + return of({ type: '[No Action] ' + searchActions.INIT_SEARCH_BY_URL }); + } + }) + ); + + @Effect() + newSearchAction$ = this.actions$.pipe( + ofType(searchActions.NEW_SEARCH), + map((action: searchActions.NewSearchAction) => new searchActions.SelectDatasetAction(action.payload)) + ); + + @Effect() + loadDatasetAttributeListSuccessAction$ = this.actions$.pipe( + ofType(attributeActions.LOAD_ATTRIBUTE_SEARCH_META_SUCCESS), + withLatestFrom(this.store$), + switchMap(([action, state]) => { + const loadAttributeSearchMetaSuccessAction = action as attributeActions.LoadAttributeSearchMetaSuccessAction; + + const actions: Action[] = []; + + let defaultOutputList = loadAttributeSearchMetaSuccessAction.payload + .filter(attribute => attribute.selected && attribute.id_output_category) + .sort((a, b) => a.output_display - b.output_display) + .map(attribute => attribute.id); + + if (state.router.state.queryParams.a) { + defaultOutputList = state.router.state.queryParams.a.split(';').map((o: string) => parseInt(o, 10)); + } + + actions.push(new searchActions.UpdateOutputListAction(defaultOutputList)); + + let defaultCriteriaList = loadAttributeSearchMetaSuccessAction.payload + .filter(attribute => attribute.id_criteria_family && attribute.search_type && attribute.min) + .map(attribute => { + switch (attribute.search_type) { + case 'field': + case 'select': + case 'datalist': + case 'radio': + case 'date': + case 'date-time': + case 'time': + return { id: attribute.id, type: 'field', value: attribute.min.toString(), operator: attribute.operator }; + case 'list': + return { id: attribute.id, type: 'list', values: attribute.min.toString().split('|') }; + case 'between': + case 'between-date': + return { id: attribute.id, type: 'between', min: attribute.min.toString(), max: attribute.max.toString() }; + case 'select-multiple': + case 'checkbox': + const msValues = attribute.min.toString().split('|'); + const options = attribute.options.filter(option => msValues.includes(option.value)); + return { id: attribute.id, type: 'multiple', options }; + case 'json': + const [path, operator, value] = attribute.min.toString().split('|'); + return { id: attribute.id, type: 'json', path, operator, value }; + + default: + return null; + } + }); + + if (state.router.state.queryParams.c) { + defaultCriteriaList = state.router.state.queryParams.c.split(';').map((c: string) => { + const params = c.split('::'); + const attribute = loadAttributeSearchMetaSuccessAction.payload.find(a => a.id === parseInt(params[0], 10)); + switch (attribute.search_type) { + case 'field': + case 'select': + case 'datalist': + case 'radio': + case 'date': + case 'date-time': + case 'time': + return { id: parseInt(params[0], 10), type: 'field', operator: params[1], value: params[2] }; + case 'list': + return { id: parseInt(params[0], 10), type: 'list', values: params[2].split('|') }; + case 'between': + case 'between-date': + if (params[1] === 'bw') { + const bwValues = params[2].split('|'); + return { id: parseInt(params[0], 10), type: 'between', min: bwValues[0], max: bwValues[1] }; + } else if (params[1] === 'gte') { + return { id: parseInt(params[0], 10), type: 'between', min: params[2], max: null }; + } else { + return { id: parseInt(params[0], 10), type: 'between', min: null, max: params[2] }; + } + case 'select-multiple': + case 'checkbox': + const msValues = params[2].split('|'); + const options = attribute.options.filter(option => msValues.includes(option.value)); + return { id: parseInt(params[0], 10), type: 'multiple', options }; + case 'json': + const [path, operator, value] = params[2].split('|'); + return { id: parseInt(params[0], 10), type: 'json', path, operator, value }; + + default: + return null; + } + }); + } + + actions.push(new searchActions.UpdateCriteriaListAction(defaultCriteriaList)); + + if (state.router.state.queryParams.cs) { + const params = state.router.state.queryParams.cs.split(':'); + const coneSearch: ConeSearch = { + ra: parseFloat(params[0]), + dec: parseFloat(params[1]), + radius: parseInt(params[2], 10) + }; + actions.push(new searchActions.IsConeSearchAddedAction(true)); + actions.push(new coneSearchActions.AddConeSearchAction(coneSearch)); + } + + return actions; + }) + ); + + @Effect() + retrieveDataAction$ = this.actions$.pipe( + ofType(searchActions.RETRIEVE_DATA), + withLatestFrom(this.store$), + switchMap(([action, state]) => { + const retrieveDataAction = action as searchActions.RetrieveDataAction; + let query = state.search.datasetName + '?a=' + state.search.outputList.join(';'); + if (state.search.criteriaList.length > 0) { + query += '&c=' + state.search.criteriaList.map(criterion => getCriterionStr(criterion)).join(';'); + } + if (state.search.coneSearchAdded) { + query += '&cs=' + state.coneSearch.coneSearch.ra + ':' + state.coneSearch.coneSearch.dec + ':' + state.coneSearch.coneSearch.radius; + } + query += '&p=' + retrieveDataAction.payload[1] + ':' + retrieveDataAction.payload[0]; + query += '&o=' + retrieveDataAction.payload[2] + ':' + retrieveDataAction.payload[3]; + return this.searchService.retrieveData(query).pipe( + map((searchData: any[]) => new searchActions.RetrieveDataSuccessAction(searchData)), + catchError(() => of(new searchActions.RetrieveDataFailAction())) + ); + }) + ); + + @Effect({ dispatch: false }) + retrieveDataFailAction$ = this.actions$.pipe( + ofType(searchActions.RETRIEVE_DATA_FAIL), + tap(_ => this.toastr.error('Loading Failed!', 'The search data loading failed')) + ); + + @Effect() + getDataLengthAction$ = this.actions$.pipe( + ofType(searchActions.GET_DATA_LENGTH), + withLatestFrom(this.store$), + switchMap(([action, state]) => { + let query = state.search.datasetName + '?a=count'; + if (state.search.criteriaList.length > 0) { + query += '&c=' + state.search.criteriaList.map(criterion => getCriterionStr(criterion)).join(';'); + } + if (state.search.coneSearchAdded) { + query += '&cs=' + state.coneSearch.coneSearch.ra + ':' + state.coneSearch.coneSearch.dec + ':' + state.coneSearch.coneSearch.radius; + } + return this.searchService.getDataLength(query).pipe( + map((response: { nb: number }[]) => new searchActions.GetDataLengthSuccessAction(response[0].nb)), + catchError(() => of(new searchActions.GetDataLengthFailAction())) + ); + }) + ); + + @Effect({ dispatch: false }) + getDataLengthFailAction$ = this.actions$.pipe( + ofType(searchActions.GET_DATA_LENGTH_FAIL), + tap(_ => this.toastr.error('Loading Failed!', 'The data length loading failed')) + ); + + @Effect() + executeProcessAction$ = this.actions$.pipe( + ofType(searchActions.EXECUTE_PROCESS), + withLatestFrom(this.store$), + switchMap(([action, state]) => { + const executeProcessAction = action as searchActions.ExecuteProcessAction; + const dname = state.search.datasetName; + let query = '?a=' + state.search.outputList.join(';'); + query += '&c='; + query += state.metamodel.attribute.datasetAttributeList.find(a => a.search_flag === 'ID').id; + query += '::in::'; + query += state.search.selectedData.join('|'); + return this.searchService.executeProcess(executeProcessAction.payload, dname, query).pipe( + map((res: any) => new searchActions.ExecuteProcessWipAction(res.message)), + catchError(() => of(new searchActions.ExecuteProcessFailAction())) + ); + }) + ); + + @Effect() + executeProcessWipAction$ = this.actions$.pipe( + ofType(searchActions.EXECUTE_PROCESS_WIP), + withLatestFrom(this.store$), + switchMap(([action, state]) => { + const executeProcessWipAction = action as searchActions.ExecuteProcessWipAction; + const idProcess: string = executeProcessWipAction.payload; + return this.searchService.checkProcess(idProcess).pipe( + map(_ => { + return new searchActions.ExecuteProcessSuccessAction(idProcess); + }), + catchError((err: HttpErrorResponse) => { + if (err.status === 404) { + return of(new searchActions.ExecuteProcessWipAction(idProcess)).pipe(delay(5000)); + } + return of(new searchActions.ExecuteProcessFailAction()); + })); + }) + ); + + @Effect({ dispatch: false }) + executeProcessFailAction$ = this.actions$.pipe( + ofType(searchActions.EXECUTE_PROCESS_FAIL), + tap(_ => this.toastr.error('Action Failed!', 'The process failed')) + ); + + // @Effect() + // retrieveCoordinatesAction$ = this.actions$.pipe( + // ofType(searchActions.RETRIEVE_COORDINATES), + // withLatestFrom(this.store$), + // switchMap(([action, state]) => { + // const retrieveCoordinatesAction = action as searchActions.RetrieveCoordinatesAction; + // return this.searchService.retrieveCoordinates(retrieveCoordinatesAction.payload).pipe( + // map((response) => { + // const parser = new DOMParser(); + // const xml = parser.parseFromString(response,'text/xml'); + // if (xml.getElementsByTagName('Resolver').length === 0) { + // const name = xml.getElementsByTagName('name')[0].childNodes[0].nodeValue; + // return new searchActions.RetrieveCoordinatesFailAction(name); + // } + // const name = xml.getElementsByTagName('name')[0].childNodes[0].nodeValue; + // const ra = +xml.getElementsByTagName('jradeg')[0].childNodes[0].nodeValue; + // const dec = +xml.getElementsByTagName('jdedeg')[0].childNodes[0].nodeValue; + // return new searchActions.RetrieveCoordinatesSuccessAction({name, ra, dec}); + // }), + // catchError(() => of(new searchActions.RetrieveCoordinatesFailAction(null))) + // ); + // }) + // ); + // + // @Effect({ dispatch: false }) + // retrieveCoordinatesFailAction$ = this.actions$.pipe( + // ofType(searchActions.RETRIEVE_COORDINATES_FAIL), + // tap(action => { + // const retrieveCoordinatesFailAction = action as searchActions.RetrieveCoordinatesFailAction; + // if (retrieveCoordinatesFailAction.payload) { + // this.toastr.error(retrieveCoordinatesFailAction.payload + ' not found'); + // } else { + // this.toastr.error('Connection to Sesame Name Resolver failed', 'Resolver Failed!'); + // } + // }) + // ); +} diff --git a/src/app/search-multiple/store/search-multiple.reducer.spec.ts b/src/app/search-multiple/store/search-multiple.reducer.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..73a7b2c20b9999d1510efc4c86902096aed25f66 --- /dev/null +++ b/src/app/search-multiple/store/search-multiple.reducer.spec.ts @@ -0,0 +1,651 @@ +import * as fromSearch from './search.reducer'; +import * as searchActions from './search.action'; +import { CRITERIA_LIST } from '../../../settings/test-data' +import { FieldCriterion } from './model'; + +describe('[Search] Reducer', () => { + it('should return init state', () => { + const { initialState } = fromSearch; + const action = {} as searchActions.Actions; + const state = fromSearch.reducer(undefined, action); + + expect(state).toBe(initialState); + }); + + it('should change the currentStep', () => { + const { initialState } = fromSearch; + const action = new searchActions.ChangeStepAction('toto'); + const state = fromSearch.reducer(initialState, action); + + expect(state.pristine).toBeTruthy(); + expect(state.currentStep).toEqual('toto'); + expect(state.datasetName).toBeNull(); + expect(state.criteriaStepChecked).toBeFalsy(); + expect(state.outputStepChecked).toBeFalsy(); + expect(state.resultStepChecked).toBeFalsy(); + expect(state.coneSearchAdded).toBeFalsy(); + expect(state.criteriaList.length).toEqual(0); + expect(state.outputList.length).toEqual(0); + expect(state.searchData).toBeNull(); + expect(state.dataLength).toBeNull(); + expect(state.selectedData.length).toEqual(0); + expect(state.processWip).toBeFalsy(); + expect(state.processDone).toBeFalsy(); + expect(state.processId).toBeNull(); + expect(state.outputListEmpty).toBeNull(); + expect(state).not.toEqual(initialState); + }); + + it('should set the datasetName and change pristine', () => { + const { initialState } = fromSearch; + const action = new searchActions.SelectDatasetAction('toto'); + const state = fromSearch.reducer(initialState, action); + + expect(state.pristine).toBeFalsy(); + expect(state.currentStep).toBeNull(); + expect(state.datasetName).toBe('toto'); + expect(state.criteriaStepChecked).toBeFalsy(); + expect(state.outputStepChecked).toBeFalsy(); + expect(state.resultStepChecked).toBeFalsy(); + expect(state.coneSearchAdded).toBeFalsy(); + expect(state.criteriaList.length).toEqual(0); + expect(state.outputList.length).toEqual(0); + expect(state.searchData).toBeNull(); + expect(state.dataLength).toBeNull(); + expect(state.selectedData.length).toEqual(0); + expect(state.processWip).toBeFalsy(); + expect(state.processDone).toBeFalsy(); + expect(state.processId).toBeNull(); + expect(state.outputListEmpty).toBeNull(); + expect(state).not.toEqual(initialState); + }); + + it('should set currentStep to "dataset"', () => { + const { initialState } = fromSearch; + const action = new searchActions.NewSearchAction('toto'); + const state = fromSearch.reducer(initialState, action); + + expect(state.pristine).toBeTruthy(); + expect(state.currentStep).toEqual('dataset'); + expect(state.datasetName).toBeNull(); + expect(state.criteriaStepChecked).toBeFalsy(); + expect(state.outputStepChecked).toBeFalsy(); + expect(state.resultStepChecked).toBeFalsy(); + expect(state.coneSearchAdded).toBeFalsy(); + expect(state.criteriaList.length).toEqual(0); + expect(state.outputList.length).toEqual(0); + expect(state.searchData).toBeNull(); + expect(state.dataLength).toBeNull(); + expect(state.selectedData.length).toEqual(0); + expect(state.processWip).toBeFalsy(); + expect(state.processDone).toBeFalsy(); + expect(state.processId).toBeNull(); + expect(state.outputListEmpty).toBeNull(); + expect(state).not.toEqual(initialState); + }); + + it('should set criteriaStepChecked to true', () => { + const { initialState } = fromSearch; + const action = new searchActions.CriteriaChecked(); + const state = fromSearch.reducer(initialState, action); + + expect(state.pristine).toBeTruthy(); + expect(state.currentStep).toBeNull() + expect(state.datasetName).toBeNull(); + expect(state.criteriaStepChecked).toBeTruthy(); + expect(state.outputStepChecked).toBeFalsy(); + expect(state.resultStepChecked).toBeFalsy(); + expect(state.coneSearchAdded).toBeFalsy(); + expect(state.criteriaList.length).toEqual(0); + expect(state.outputList.length).toEqual(0); + expect(state.searchData).toBeNull(); + expect(state.dataLength).toBeNull(); + expect(state.selectedData.length).toEqual(0); + expect(state.processWip).toBeFalsy(); + expect(state.processDone).toBeFalsy(); + expect(state.processId).toBeNull(); + expect(state.outputListEmpty).toBeNull(); + expect(state).not.toEqual(initialState); + }); + + it('should set outputStepChecked to true', () => { + const { initialState } = fromSearch; + const action = new searchActions.OutputChecked(); + const state = fromSearch.reducer(initialState, action); + + expect(state.pristine).toBeTruthy(); + expect(state.currentStep).toBeNull() + expect(state.datasetName).toBeNull(); + expect(state.criteriaStepChecked).toBeFalsy(); + expect(state.outputStepChecked).toBeTruthy(); + expect(state.resultStepChecked).toBeFalsy(); + expect(state.coneSearchAdded).toBeFalsy(); + expect(state.criteriaList.length).toEqual(0); + expect(state.outputList.length).toEqual(0); + expect(state.searchData).toBeNull(); + expect(state.dataLength).toBeNull(); + expect(state.selectedData.length).toEqual(0); + expect(state.processWip).toBeFalsy(); + expect(state.processDone).toBeFalsy(); + expect(state.processId).toBeNull(); + expect(state.outputListEmpty).toBeNull(); + expect(state).not.toEqual(initialState); + }); + + it('should set resultStepChecked to true', () => { + const { initialState } = fromSearch; + const action = new searchActions.ResultChecked(); + const state = fromSearch.reducer(initialState, action); + + expect(state.pristine).toBeTruthy(); + expect(state.currentStep).toBeNull() + expect(state.datasetName).toBeNull(); + expect(state.criteriaStepChecked).toBeFalsy(); + expect(state.outputStepChecked).toBeFalsy(); + expect(state.resultStepChecked).toBeTruthy(); + expect(state.coneSearchAdded).toBeFalsy(); + expect(state.criteriaList.length).toEqual(0); + expect(state.outputList.length).toEqual(0); + expect(state.searchData).toBeNull(); + expect(state.dataLength).toBeNull(); + expect(state.selectedData.length).toEqual(0); + expect(state.processWip).toBeFalsy(); + expect(state.processDone).toBeFalsy(); + expect(state.processId).toBeNull(); + expect(state.outputListEmpty).toBeNull(); + expect(state).not.toEqual(initialState); + }); + + it('should set coneSearchAdded to true', () => { + const { initialState } = fromSearch; + const action = new searchActions.IsConeSearchAddedAction(true); + const state = fromSearch.reducer(initialState, action); + + expect(state.pristine).toBeTruthy(); + expect(state.currentStep).toBeNull() + expect(state.datasetName).toBeNull(); + expect(state.criteriaStepChecked).toBeFalsy(); + expect(state.outputStepChecked).toBeFalsy(); + expect(state.resultStepChecked).toBeFalsy(); + expect(state.coneSearchAdded).toBeTruthy(); + expect(state.criteriaList.length).toEqual(0); + expect(state.outputList.length).toEqual(0); + expect(state.searchData).toBeNull(); + expect(state.dataLength).toBeNull(); + expect(state.selectedData.length).toEqual(0); + expect(state.processWip).toBeFalsy(); + expect(state.processDone).toBeFalsy(); + expect(state.processId).toBeNull(); + expect(state.outputListEmpty).toBeNull(); + expect(state).not.toEqual(initialState); + }); + + + it('should set criteriaList', () => { + const { initialState } = fromSearch; + const action = new searchActions.UpdateCriteriaListAction(CRITERIA_LIST); + const state = fromSearch.reducer(initialState, action); + + expect(state.pristine).toBeTruthy(); + expect(state.currentStep).toBeNull() + expect(state.datasetName).toBeNull(); + expect(state.criteriaStepChecked).toBeFalsy(); + expect(state.outputStepChecked).toBeFalsy(); + expect(state.resultStepChecked).toBeFalsy(); + expect(state.coneSearchAdded).toBeFalsy(); + expect(state.criteriaList.length).toEqual(2); + expect(state.outputList.length).toEqual(0); + expect(state.searchData).toBeNull(); + expect(state.dataLength).toBeNull(); + expect(state.selectedData.length).toEqual(0); + expect(state.processWip).toBeFalsy(); + expect(state.processDone).toBeFalsy(); + expect(state.processId).toBeNull(); + expect(state.outputListEmpty).toBeNull(); + expect(state).not.toEqual(initialState); + }); + + it('should add criterion to criteriaList', () => { + const criterion: FieldCriterion = { id: 3, type: 'field', operator: 'eq', value: 'fd_crit_3' }; + const { initialState } = fromSearch; + const action = new searchActions.AddCriterionAction(criterion); + const state = fromSearch.reducer(initialState, action); + + expect(state.pristine).toBeTruthy(); + expect(state.currentStep).toBeNull() + expect(state.datasetName).toBeNull(); + expect(state.criteriaStepChecked).toBeFalsy(); + expect(state.outputStepChecked).toBeFalsy(); + expect(state.resultStepChecked).toBeFalsy(); + expect(state.coneSearchAdded).toBeFalsy(); + expect(state.criteriaList.length).toEqual(1); + expect(state.criteriaList[0].id).toEqual(3); + expect(state.outputList.length).toEqual(0); + expect(state.searchData).toBeNull(); + expect(state.dataLength).toBeNull(); + expect(state.selectedData.length).toEqual(0); + expect(state.processWip).toBeFalsy(); + expect(state.processDone).toBeFalsy(); + expect(state.processId).toBeNull(); + expect(state.outputListEmpty).toBeNull(); + expect(state).not.toEqual(initialState); + }); + + it('should remove criterion to criteriaList', () => { + const criterion: FieldCriterion = { id: 2, type: 'field', operator: 'eq', value: 'fd_crit_2' }; + const initialState = { ...fromSearch.initialState, criteriaList: CRITERIA_LIST }; + const action = new searchActions.DeleteCriterionAction(criterion.id); + const state = fromSearch.reducer(initialState, action); + + expect(state.pristine).toBeTruthy(); + expect(state.currentStep).toBeNull() + expect(state.datasetName).toBeNull(); + expect(state.criteriaStepChecked).toBeFalsy(); + expect(state.outputStepChecked).toBeFalsy(); + expect(state.resultStepChecked).toBeFalsy(); + expect(state.coneSearchAdded).toBeFalsy(); + expect(state.criteriaList.length).toEqual(1); + expect(state.criteriaList[0].id).toEqual(1); + expect(state.outputList.length).toEqual(0); + expect(state.searchData).toBeNull(); + expect(state.dataLength).toBeNull(); + expect(state.selectedData.length).toEqual(0); + expect(state.processWip).toBeFalsy(); + expect(state.processDone).toBeFalsy(); + expect(state.processId).toBeNull(); + expect(state.outputListEmpty).toBeNull(); + expect(state).not.toEqual(initialState); + }); + + it('should set outputList', () => { + const { initialState } = fromSearch; + const action = new searchActions.UpdateOutputListAction([1, 2]); + const state = fromSearch.reducer(initialState, action); + + expect(state.pristine).toBeTruthy(); + expect(state.currentStep).toBeNull() + expect(state.datasetName).toBeNull(); + expect(state.criteriaStepChecked).toBeFalsy(); + expect(state.outputStepChecked).toBeFalsy(); + expect(state.resultStepChecked).toBeFalsy(); + expect(state.coneSearchAdded).toBeFalsy(); + expect(state.criteriaList.length).toEqual(0); + expect(state.outputList.length).toEqual(2); + expect(state.outputList).toContain(1); + expect(state.outputList).toContain(2); + expect(state.searchData).toBeNull(); + expect(state.dataLength).toBeNull(); + expect(state.selectedData.length).toEqual(0); + expect(state.processWip).toBeFalsy(); + expect(state.processDone).toBeFalsy(); + expect(state.processId).toBeNull(); + expect(state.outputListEmpty).toBeNull(); + expect(state).not.toEqual(initialState); + }); + + it('should set outputListEmpty to false', () => { + const { initialState } = fromSearch; + const action = new searchActions.OutputListEmptyAction(false); + const state = fromSearch.reducer(initialState, action); + + expect(state.pristine).toBeTruthy(); + expect(state.currentStep).toBeNull() + expect(state.datasetName).toBeNull(); + expect(state.criteriaStepChecked).toBeFalsy(); + expect(state.outputStepChecked).toBeFalsy(); + expect(state.resultStepChecked).toBeFalsy(); + expect(state.coneSearchAdded).toBeFalsy(); + expect(state.criteriaList.length).toEqual(0); + expect(state.outputList.length).toEqual(0); + expect(state.searchData).toBeNull(); + expect(state.dataLength).toBeNull(); + expect(state.selectedData.length).toEqual(0); + expect(state.processWip).toBeFalsy(); + expect(state.processDone).toBeFalsy(); + expect(state.processId).toBeNull(); + expect(state.outputListEmpty).toBeFalsy(); + expect(state).not.toEqual(initialState); + }); + + it('should set searchData', () => { + const { initialState } = fromSearch; + const action = new searchActions.RetrieveDataSuccessAction(['data_1', 'data_2']); + const state = fromSearch.reducer(initialState, action); + + expect(state.pristine).toBeTruthy(); + expect(state.currentStep).toBeNull() + expect(state.datasetName).toBeNull(); + expect(state.criteriaStepChecked).toBeFalsy(); + expect(state.outputStepChecked).toBeFalsy(); + expect(state.resultStepChecked).toBeFalsy(); + expect(state.coneSearchAdded).toBeFalsy(); + expect(state.criteriaList.length).toEqual(0); + expect(state.outputList.length).toEqual(0); + expect(state.searchData.length).toBe(2); + expect(state.searchData).toContain('data_1'); + expect(state.searchData).toContain('data_2'); + expect(state.dataLength).toBeNull(); + expect(state.selectedData.length).toEqual(0); + expect(state.processWip).toBeFalsy(); + expect(state.processDone).toBeFalsy(); + expect(state.processId).toBeNull(); + expect(state.outputListEmpty).toBeNull(); + expect(state).not.toEqual(initialState); + }); + + it('should set dataLength', () => { + const { initialState } = fromSearch; + const action = new searchActions.GetDataLengthSuccessAction(12); + const state = fromSearch.reducer(initialState, action); + + expect(state.pristine).toBeTruthy(); + expect(state.currentStep).toBeNull() + expect(state.datasetName).toBeNull(); + expect(state.criteriaStepChecked).toBeFalsy(); + expect(state.outputStepChecked).toBeFalsy(); + expect(state.resultStepChecked).toBeFalsy(); + expect(state.coneSearchAdded).toBeFalsy(); + expect(state.criteriaList.length).toEqual(0); + expect(state.outputList.length).toEqual(0); + expect(state.searchData).toBeNull(); + expect(state.dataLength).toBe(12); + expect(state.selectedData.length).toEqual(0); + expect(state.processWip).toBeFalsy(); + expect(state.processDone).toBeFalsy(); + expect(state.processId).toBeNull(); + expect(state.outputListEmpty).toBeNull(); + expect(state).not.toEqual(initialState); + }); + + it('should add data to selectedData', () => { + const { initialState } = fromSearch; + const action = new searchActions.AddSelectedDataAction(1); + const state = fromSearch.reducer(initialState, action); + + expect(state.pristine).toBeTruthy(); + expect(state.currentStep).toBeNull() + expect(state.datasetName).toBeNull(); + expect(state.criteriaStepChecked).toBeFalsy(); + expect(state.outputStepChecked).toBeFalsy(); + expect(state.resultStepChecked).toBeFalsy(); + expect(state.coneSearchAdded).toBeFalsy(); + expect(state.criteriaList.length).toEqual(0); + expect(state.outputList.length).toEqual(0); + expect(state.searchData).toBeNull(); + expect(state.dataLength).toBeNull(); + expect(state.selectedData.length).toEqual(1); + expect(state.selectedData).toContain(1); + expect(state.processWip).toBeFalsy(); + expect(state.processDone).toBeFalsy(); + expect(state.processId).toBeNull(); + expect(state.outputListEmpty).toBeNull(); + expect(state).not.toEqual(initialState); + }); + + it('should remove data to selectedData', () => { + const initialState = { ...fromSearch.initialState, selectedData: ['data_1', 'data_2'] }; + const action = new searchActions.DeleteSelectedDataAction('data_2'); + const state = fromSearch.reducer(initialState, action); + + expect(state.pristine).toBeTruthy(); + expect(state.currentStep).toBeNull() + expect(state.datasetName).toBeNull(); + expect(state.criteriaStepChecked).toBeFalsy(); + expect(state.outputStepChecked).toBeFalsy(); + expect(state.resultStepChecked).toBeFalsy(); + expect(state.coneSearchAdded).toBeFalsy(); + expect(state.criteriaList.length).toEqual(0); + expect(state.outputList.length).toEqual(0); + expect(state.searchData).toBeNull(); + expect(state.dataLength).toBeNull(); + expect(state.selectedData.length).toEqual(1); + expect(state.selectedData).toContain('data_1'); + expect(state.selectedData).not.toContain('data_2'); + expect(state.processWip).toBeFalsy(); + expect(state.processDone).toBeFalsy(); + expect(state.processId).toBeNull(); + expect(state.outputListEmpty).toBeNull(); + expect(state).not.toEqual(initialState); + }); + + it('should set processWip to true and processDone to false', () => { + const { initialState } = fromSearch; + const action = new searchActions.ExecuteProcessAction('process_type'); + const state = fromSearch.reducer(initialState, action); + + expect(state.pristine).toBeTruthy(); + expect(state.currentStep).toBeNull() + expect(state.datasetName).toBeNull(); + expect(state.criteriaStepChecked).toBeFalsy(); + expect(state.outputStepChecked).toBeFalsy(); + expect(state.resultStepChecked).toBeFalsy(); + expect(state.coneSearchAdded).toBeFalsy(); + expect(state.criteriaList.length).toEqual(0); + expect(state.outputList.length).toEqual(0); + expect(state.searchData).toBeNull(); + expect(state.dataLength).toBeNull(); + expect(state.selectedData.length).toEqual(0); + expect(state.processWip).toBeTruthy(); + expect(state.processDone).toBeFalsy(); + expect(state.processId).toBeNull(); + expect(state.outputListEmpty).toBeNull(); + expect(state).not.toEqual(initialState); + }); + + it('should set processId', () => { + const { initialState } = fromSearch; + const action = new searchActions.ExecuteProcessWipAction('toto'); + const state = fromSearch.reducer(initialState, action); + + expect(state.pristine).toBeTruthy(); + expect(state.currentStep).toBeNull() + expect(state.datasetName).toBeNull(); + expect(state.criteriaStepChecked).toBeFalsy(); + expect(state.outputStepChecked).toBeFalsy(); + expect(state.resultStepChecked).toBeFalsy(); + expect(state.coneSearchAdded).toBeFalsy(); + expect(state.criteriaList.length).toEqual(0); + expect(state.outputList.length).toEqual(0); + expect(state.searchData).toBeNull(); + expect(state.dataLength).toBeNull(); + expect(state.selectedData.length).toEqual(0); + expect(state.processWip).toBeFalsy(); + expect(state.processDone).toBeFalsy(); + expect(state.processId).toBe('toto'); + expect(state.outputListEmpty).toBeNull(); + expect(state).not.toEqual(initialState); + }); + + it('should set processWip to false and processDone to true', () => { + const { initialState } = fromSearch; + const action = new searchActions.ExecuteProcessSuccessAction('toto'); + const state = fromSearch.reducer(initialState, action); + + expect(state.pristine).toBeTruthy(); + expect(state.currentStep).toBeNull() + expect(state.datasetName).toBeNull(); + expect(state.criteriaStepChecked).toBeFalsy(); + expect(state.outputStepChecked).toBeFalsy(); + expect(state.resultStepChecked).toBeFalsy(); + expect(state.coneSearchAdded).toBeFalsy(); + expect(state.criteriaList.length).toEqual(0); + expect(state.outputList.length).toEqual(0); + expect(state.searchData).toBeNull(); + expect(state.dataLength).toBeNull(); + expect(state.selectedData.length).toEqual(0); + expect(state.processWip).toBeFalsy(); + expect(state.processDone).toBeTruthy(); + expect(state.processId).toBeNull(); + expect(state.outputListEmpty).toBeNull(); + expect(state).not.toEqual(initialState); + }); + + it('should unset processId and set processWip and processDone to false', () => { + const initialState = { + ...fromSearch.initialState, + processId: 'toto', + processWip: true, + processDone: true + }; + const action = new searchActions.ExecuteProcessFailAction(); + const state = fromSearch.reducer(initialState, action); + + expect(state.pristine).toBeTruthy(); + expect(state.currentStep).toBeNull() + expect(state.datasetName).toBeNull(); + expect(state.criteriaStepChecked).toBeFalsy(); + expect(state.outputStepChecked).toBeFalsy(); + expect(state.resultStepChecked).toBeFalsy(); + expect(state.coneSearchAdded).toBeFalsy(); + expect(state.criteriaList.length).toEqual(0); + expect(state.outputList.length).toEqual(0); + expect(state.searchData).toBeNull(); + expect(state.dataLength).toBeNull(); + expect(state.selectedData.length).toEqual(0); + expect(state.processWip).toBeFalsy(); + expect(state.processDone).toBeFalsy(); + expect(state.processId).toBeNull(); + expect(state.outputListEmpty).toBeNull(); + expect(state).not.toEqual(initialState); + }); + + it('should unset searchData and dataLength', () => { + const initialState = { + ...fromSearch.initialState, + searchData: ['toto', 'titi', 'tutu'], + dataLength: 3 + }; + const action = new searchActions.DestroyResultsAction(); + const state = fromSearch.reducer(initialState, action); + + expect(state.pristine).toBeTruthy(); + expect(state.currentStep).toBeNull() + expect(state.datasetName).toBeNull(); + expect(state.criteriaStepChecked).toBeFalsy(); + expect(state.outputStepChecked).toBeFalsy(); + expect(state.resultStepChecked).toBeFalsy(); + expect(state.coneSearchAdded).toBeFalsy(); + expect(state.criteriaList.length).toEqual(0); + expect(state.outputList.length).toEqual(0); + expect(state.searchData).toBeNull(); + expect(state.dataLength).toBeNull(); + expect(state.selectedData.length).toEqual(0); + expect(state.processWip).toBeFalsy(); + expect(state.processDone).toBeFalsy(); + expect(state.processId).toBeNull(); + expect(state.outputListEmpty).toBeNull(); + expect(state).not.toEqual(initialState); + }); + + it('should get pristine', () => { + const action = {} as searchActions.Actions; + const state = fromSearch.reducer(undefined, action); + + expect(fromSearch.getPristine(state)).toBeTruthy(); + }); + + it('should get currentStep', () => { + const action = {} as searchActions.Actions; + const state = fromSearch.reducer(undefined, action); + + expect(fromSearch.getCurrentStep(state)).toBeNull(); + }); + + it('should get datasetName', () => { + const action = {} as searchActions.Actions; + const state = fromSearch.reducer(undefined, action); + + expect(fromSearch.getDatasetName(state)).toBeNull(); + }); + + it('should get criteriaStepChecked', () => { + const action = {} as searchActions.Actions; + const state = fromSearch.reducer(undefined, action); + + expect(fromSearch.getCriteriaStepChecked(state)).toBeFalsy(); + }); + + it('should get outputStepChecked', () => { + const action = {} as searchActions.Actions; + const state = fromSearch.reducer(undefined, action); + + expect(fromSearch.getOutputStepChecked(state)).toBeFalsy(); + }); + + it('should get resultStepChecked', () => { + const action = {} as searchActions.Actions; + const state = fromSearch.reducer(undefined, action); + + expect(fromSearch.getResultStepChecked(state)).toBeFalsy(); + }); + + it('should get coneSearchAdded', () => { + const action = {} as searchActions.Actions; + const state = fromSearch.reducer(undefined, action); + + expect(fromSearch.getIsConeSearchAdded(state)).toBeFalsy(); + }); + + it('should get criteriaList', () => { + const action = {} as searchActions.Actions; + const state = fromSearch.reducer(undefined, action); + + expect(fromSearch.getCriteriaList(state).length).toEqual(0); + }); + + it('should get outputList', () => { + const action = {} as searchActions.Actions; + const state = fromSearch.reducer(undefined, action); + + expect(fromSearch.getOutputList(state).length).toEqual(0); + }); + + it('should get outputListEmpty', () => { + const action = {} as searchActions.Actions; + const state = fromSearch.reducer(undefined, action); + + expect(fromSearch.getOutputListEmpty(state)).toBeNull(); + }); + + it('should get searchData', () => { + const action = {} as searchActions.Actions; + const state = fromSearch.reducer(undefined, action); + + expect(fromSearch.getSearchData(state)).toBeNull(); + }); + + it('should get dataLength', () => { + const action = {} as searchActions.Actions; + const state = fromSearch.reducer(undefined, action); + + expect(fromSearch.getDataLength(state)).toBeNull(); + }); + + it('should get selectedData', () => { + const action = {} as searchActions.Actions; + const state = fromSearch.reducer(undefined, action); + + expect(fromSearch.getSelectedData(state).length).toEqual(0); + }); + + it('should get processWip', () => { + const action = {} as searchActions.Actions; + const state = fromSearch.reducer(undefined, action); + + expect(fromSearch.getProcessWip(state)).toBeFalsy(); + }); + + it('should get processDone', () => { + const action = {} as searchActions.Actions; + const state = fromSearch.reducer(undefined, action); + + expect(fromSearch.getProcessDone(state)).toBeFalsy(); + }); + + it('should get processId', () => { + const action = {} as searchActions.Actions; + const state = fromSearch.reducer(undefined, action); + + expect(fromSearch.getProcessId(state)).toBeNull(); + }); +}); diff --git a/src/app/search-multiple/store/search-multiple.reducer.ts b/src/app/search-multiple/store/search-multiple.reducer.ts new file mode 100644 index 0000000000000000000000000000000000000000..75c7d65b9e1762b549fa9c7d574f11b312c5aba0 --- /dev/null +++ b/src/app/search-multiple/store/search-multiple.reducer.ts @@ -0,0 +1,212 @@ +import * as actions from './search.action'; + +import { Criterion } from './model'; +// import { Criterion ,Resolver, ConeSearch } from './model'; + +export interface State { + pristine: boolean; + currentStep: string; + datasetName: string; + criteriaStepChecked: boolean; + outputStepChecked: boolean; + resultStepChecked: boolean; + coneSearchAdded: boolean; + criteriaList: Criterion[]; + outputList: number[]; + searchData: any[]; + dataLength: number; + selectedData: any[]; + processWip: boolean; + processDone: boolean; + processId: string; + outputListEmpty: boolean; +} + +export const initialState: State = { + pristine: true, + currentStep: null, + datasetName: null, + criteriaStepChecked: false, + outputStepChecked: false, + resultStepChecked: false, + coneSearchAdded: false, + criteriaList: [], + outputList: [], + searchData: null, + dataLength: null, + selectedData: [], + processWip: false, + processDone: false, + processId: null, + outputListEmpty: null +}; + +export function reducer(state: State = initialState, action: actions.Actions): State { + switch (action.type) { + case actions.CHANGE_STEP: + const currentStep: string = action.payload; + return { + ...state, + currentStep + }; + + case actions.SELECT_DATASET: + const datasetName: string = action.payload; + return { + ...state, + pristine: false, + datasetName + }; + + case actions.NEW_SEARCH: + return { + ...initialState, + currentStep: 'dataset' + }; + + case actions.CRITERIA_CHECKED: + return { + ...state, + criteriaStepChecked: true + }; + + case actions.OUTPUT_CHECKED: + return { + ...state, + outputStepChecked: true + }; + + case actions.RESULT_CHECKED: + return { + ...state, + resultStepChecked: true + }; + + case actions.IS_CONE_SEARCH_ADDED: + const coneSearchAdded: boolean = action.payload; + + return { + ...state, + coneSearchAdded + }; + + case actions.UPDATE_CRITERIA_LIST: + const criteriaList: Criterion[] = action.payload; + return { + ...state, + criteriaList + }; + + case actions.ADD_CRITERION: + const criterion: Criterion = action.payload; + return { + ...state, + criteriaList: [...state.criteriaList, criterion] + }; + + case actions.DELETE_CRITERION: + const id: number = action.payload; + return { + ...state, + criteriaList: [...state.criteriaList.filter(c => c.id !== id)] + }; + + case actions.UPDATE_OUTPUT_LIST: + const outputList: number[] = action.payload; + return { + ...state, + outputList + }; + + case actions.OUTPUT_LIST_EMPTY: + const outputListEmpty: boolean = action.payload; + return { + ...state, + outputListEmpty + }; + + case actions.RETRIEVE_DATA_SUCCESS: + const searchData: any[] = action.payload; + return { + ...state, + searchData + }; + + case actions.GET_DATA_LENGTH_SUCCESS: + const dataLength: number = action.payload; + return { + ...state, + dataLength + }; + + case actions.ADD_SELECTED_DATA: + const addData: number | string = action.payload; + return { + ...state, + selectedData: [...state.selectedData, addData] + }; + + case actions.DELETE_SELECTED_DATA: + const deleteData: number | string = action.payload; + return { + ...state, + selectedData: [...state.selectedData.filter(d => d !== deleteData)] + }; + + case actions.EXECUTE_PROCESS: + return { + ...state, + processWip: true, + processDone: false + }; + + case actions.EXECUTE_PROCESS_WIP: + const processId: string = action.payload; + return { + ...state, + processId + }; + + case actions.EXECUTE_PROCESS_SUCCESS: + return { + ...state, + processWip: false, + processDone: true + }; + + case actions.EXECUTE_PROCESS_FAIL: + return { + ...state, + processWip: false, + processDone: false, + processId: null + }; + + case actions.DESTROY_RESULTS: + return { + ...state, + searchData: null, + dataLength: null + }; + + default: + return state; + } +} + +export const getPristine = (state: State) => state.pristine; +export const getCurrentStep = (state: State) => state.currentStep; +export const getDatasetName = (state: State) => state.datasetName; +export const getCriteriaStepChecked = (state: State) => state.criteriaStepChecked; +export const getOutputStepChecked = (state: State) => state.outputStepChecked; +export const getResultStepChecked = (state: State) => state.resultStepChecked; +export const getIsConeSearchAdded = (state: State) => state.coneSearchAdded; +export const getCriteriaList = (state: State) => state.criteriaList; +export const getOutputList = (state: State) => state.outputList; +export const getOutputListEmpty = (state: State) => state.outputListEmpty; +export const getSearchData = (state: State) => state.searchData; +export const getDataLength = (state: State) => state.dataLength; +export const getSelectedData = (state: State) => state.selectedData; +export const getProcessWip = (state: State) => state.processWip; +export const getProcessDone = (state: State) => state.processDone; +export const getProcessId = (state: State) => state.processId; diff --git a/src/app/search-multiple/store/search-multiple.selector.spec.ts b/src/app/search-multiple/store/search-multiple.selector.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..fd9df0a5c752d5f27b821cf453c17bda7e200dd9 --- /dev/null +++ b/src/app/search-multiple/store/search-multiple.selector.spec.ts @@ -0,0 +1,158 @@ +import * as searchSelector from './search.selector'; +import * as fromSearch from './search.reducer'; +import * as fromConeSearch from '../../shared/cone-search/store/cone-search.reducer'; +import { Criterion } from './model'; +import { CRITERIA_LIST } from 'src/settings/test-data'; +import { ConeSearch } from "../../shared/cone-search/store/model"; + +describe('[Search] Selector', () => { + it('should get pristine', () => { + const state = { search: { ...fromSearch.initialState }}; + expect(searchSelector.getPristine(state)).toBeTruthy(); + }); + + it('should get currentStep', () => { + const state = { search: { ...fromSearch.initialState }}; + expect(searchSelector.getCurrentStep(state)).toBeNull(); + }); + + it('should get datasetName', () => { + const state = { search: { ...fromSearch.initialState }}; + expect(searchSelector.getDatasetName(state)).toBeNull(); + }); + + it('should get criteriaStepChecked', () => { + const state = { search: { ...fromSearch.initialState }}; + expect(searchSelector.getCriteriaStepChecked(state)).toBeFalsy(); + }); + + it('should get outputStepChecked', () => { + const state = { search: { ...fromSearch.initialState }}; + expect(searchSelector.getOutputStepChecked(state)).toBeFalsy(); + }); + + it('should get resultStepChecked', () => { + const state = { search: { ...fromSearch.initialState }}; + expect(searchSelector.getResultStepChecked(state)).toBeFalsy(); + }); + + it('should get coneSearchAdded', () => { + const state = { search: { ...fromSearch.initialState }}; + expect(searchSelector.getIsConeSearchAdded(state)).toBeFalsy(); + }); + + it('should get criteriaList', () => { + const state = { search: { ...fromSearch.initialState }}; + expect(searchSelector.getCriteriaList(state).length).toEqual(0); + }); + + it('should get outputList', () => { + const state = { search: { ...fromSearch.initialState }}; + expect(searchSelector.getOutputList(state).length).toEqual(0); + }); + + it('should get searchData', () => { + const state = { search: { ...fromSearch.initialState }}; + expect(searchSelector.getSearchData(state)).toBeNull(); + }); + + it('should get dataLength', () => { + const state = { search: { ...fromSearch.initialState }}; + expect(searchSelector.getDataLength(state)).toBeNull(); + }); + + it('should get selectedData', () => { + const state = { search: { ...fromSearch.initialState }}; + expect(searchSelector.getSelectedData(state).length).toEqual(0); + }); + + it('should get queryParams without criteria', () => { + const outputList: number[] = [1, 2]; + const state = { + search: { + ...fromSearch.initialState, + outputList + }, + coneSearch: { ...fromConeSearch.initialState } + }; + const expected = { s: '000', a: '1;2' }; + + expect(searchSelector.getQueryParams(state)).toEqual(expected); + }); + + it('should get queryParams with criteria', () => { + const outputList: number[] = [1, 2]; + const criteriaList: Criterion[] = CRITERIA_LIST; + const state = { + search: { + ...fromSearch.initialState, + outputList, + criteriaList + }, + coneSearch: { ...fromConeSearch.initialState } + }; + const expected = { s: '000', a: '1;2', c: '1::eq::fd_crit_1;2::eq::fd_crit_2' }; + + expect(searchSelector.getQueryParams(state)).toEqual(expected); + }); + + it('should get queryParams with cone search', () => { + const outputList: number[] = [1, 2]; + const coneSearchAdded: boolean = true; + const coneSearch: ConeSearch = { ra: 3, dec: 4, radius: 5 }; + const state = { + search: { + ...fromSearch.initialState, + outputList, + coneSearchAdded + }, + coneSearch: { + ...fromConeSearch.initialState, + coneSearch + } + }; + const expected = { s: '000', a: '1;2', cs: '3:4:5' }; + + expect(searchSelector.getQueryParams(state)).toEqual(expected); + }); + + it('should get queryParams with checked steps', () => { + const criteriaStepChecked: boolean = true; + const outputStepChecked: boolean = true; + const resultStepChecked: boolean = true; + const outputList: number[] = [1, 2]; + const state = { + search: { + ...fromSearch.initialState, + criteriaStepChecked, + outputStepChecked, + resultStepChecked, + outputList + }, + coneSearch: { ...fromConeSearch.initialState } + }; + const expected = { s: '111', a: '1;2' }; + + expect(searchSelector.getQueryParams(state)).toEqual(expected); + }); + + it('should get processWip', () => { + const state = { search: { ...fromSearch.initialState }}; + expect(searchSelector.getProcessWip(state)).toBeFalsy(); + }); + + it('should get processDone', () => { + const state = { search: { ...fromSearch.initialState }}; + expect(searchSelector.getProcessDone(state)).toBeFalsy(); + }); + + it('should get processId', () => { + const state = { search: { ...fromSearch.initialState }}; + expect(searchSelector.getProcessId(state)).toBeNull(); + }); + + it('should get outputListEmpty', () => { + const state = { search: { ...fromSearch.initialState }}; + expect(searchSelector.getOutputListEmpty(state)).toBeNull(); + }); +}); \ No newline at end of file diff --git a/src/app/search-multiple/store/search-multiple.selector.ts b/src/app/search-multiple/store/search-multiple.selector.ts new file mode 100644 index 0000000000000000000000000000000000000000..ab19fd506edf6183b0128aa38f36de3b5f93a315 --- /dev/null +++ b/src/app/search-multiple/store/search-multiple.selector.ts @@ -0,0 +1,129 @@ +import { createSelector, createFeatureSelector } from '@ngrx/store'; + +import * as search from './search.reducer'; +import { Criterion, SearchQueryParams } from './model'; +import { getCriterionStr } from '../../shared/utils'; +import { getConeSearch } from "../../shared/cone-search/store/cone-search.selector"; +import { ConeSearch } from "../../shared/cone-search/store/model"; + +export const getSearchState = createFeatureSelector<search.State>('search'); + +export const getPristine = createSelector( + getSearchState, + search.getPristine +); + +export const getCurrentStep = createSelector( + getSearchState, + search.getCurrentStep +); + +export const getDatasetName = createSelector( + getSearchState, + search.getDatasetName +); + +export const getCriteriaStepChecked = createSelector( + getSearchState, + search.getCriteriaStepChecked +); + +export const getOutputStepChecked = createSelector( + getSearchState, + search.getOutputStepChecked +); + +export const getResultStepChecked = createSelector( + getSearchState, + search.getResultStepChecked +); + +export const getIsConeSearchAdded = createSelector( + getSearchState, + search.getIsConeSearchAdded +); + +export const getCriteriaList = createSelector( + getSearchState, + search.getCriteriaList +); + +export const getOutputList = createSelector( + getSearchState, + search.getOutputList +); + +export const getSearchData = createSelector( + getSearchState, + search.getSearchData +); + +export const getDataLength = createSelector( + getSearchState, + search.getDataLength +); + +export const getSelectedData = createSelector( + getSearchState, + search.getSelectedData +); + +export const getQueryParams = createSelector( + getIsConeSearchAdded, + getConeSearch, + getCriteriaList, + getOutputList, + getCriteriaStepChecked, + getOutputStepChecked, + getResultStepChecked, + ( + isConeSearchAdded: boolean, + coneSearch: ConeSearch, + criteriaList: Criterion[], + outputList: number[], + criteriaStepChecked: boolean, + outputStepChecked: boolean, + resultStepChecked: boolean) => { + let step = ''; + step += (criteriaStepChecked) ? '1' : '0'; + step += (outputStepChecked) ? '1' : '0'; + step += (resultStepChecked) ? '1' : '0'; + let queryParams: SearchQueryParams = { + s: step, + a: outputList.join(';') + }; + if (isConeSearchAdded) { + queryParams = { + ...queryParams, + cs: coneSearch.ra + ':' + coneSearch.dec + ':' + coneSearch.radius + }; + } + if (criteriaList.length > 0) { + queryParams = { + ...queryParams, + c: criteriaList.map(criterion => getCriterionStr(criterion)).join(';') + }; + } + return queryParams; + } +); + +export const getProcessWip = createSelector( + getSearchState, + search.getProcessWip +); + +export const getProcessDone = createSelector( + getSearchState, + search.getProcessDone +); + +export const getProcessId = createSelector( + getSearchState, + search.getProcessId +); + +export const getOutputListEmpty = createSelector( + getSearchState, + search.getOutputListEmpty +); diff --git a/src/app/search-multiple/store/search-multiple.service.ts b/src/app/search-multiple/store/search-multiple.service.ts new file mode 100644 index 0000000000000000000000000000000000000000..467dfd76ecebbf6962a450d1638e0d1ff5bb51b2 --- /dev/null +++ b/src/app/search-multiple/store/search-multiple.service.ts @@ -0,0 +1,34 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; + +import { environment } from '../../../environments/environment'; + +@Injectable() +export class SearchMultipleService { + API_PATH: string = environment.apiUrl; + instanceName: string = environment.instanceName; + + constructor(private http: HttpClient) { } + + retrieveData(query: string) { + return this.http.get<any[]>(this.API_PATH + '/search/' + query); + } + + getDataLength(query: string) { + return this.http.get<{ nb: number }[]>(this.API_PATH + '/search/' + query); + } + + executeProcess(typeProcess: string, dname: string, query: string) { + const url = this.API_PATH + '/service/' + this.instanceName + '/' + dname + '/' + typeProcess + query; + return this.http.get<any>(url); + } + + checkProcess(id: string) { + return this.http.head<any>('http://0.0.0.0:8085/' + id + '.csv'); + } + + // retrieveCoordinates(name: string) { + // const url = 'https://cdsweb.u-strasbg.fr/cgi-bin/nph-sesame/-ox/NSV?' + name; + // return this.http.get(url, { responseType: 'text' }); + // } +} diff --git a/src/app/search/store/search.effects.ts b/src/app/search/store/search.effects.ts index 83eafbc8b4ef4c26df81205189ea6e8b39adc912..d1df15775d6cbc1fed38c5de5e4f0c0ddd5611f1 100644 --- a/src/app/search/store/search.effects.ts +++ b/src/app/search/store/search.effects.ts @@ -275,41 +275,4 @@ export class SearchEffects { ofType(searchActions.EXECUTE_PROCESS_FAIL), tap(_ => this.toastr.error('Action Failed!', 'The process failed')) ); - - // @Effect() - // retrieveCoordinatesAction$ = this.actions$.pipe( - // ofType(searchActions.RETRIEVE_COORDINATES), - // withLatestFrom(this.store$), - // switchMap(([action, state]) => { - // const retrieveCoordinatesAction = action as searchActions.RetrieveCoordinatesAction; - // return this.searchService.retrieveCoordinates(retrieveCoordinatesAction.payload).pipe( - // map((response) => { - // const parser = new DOMParser(); - // const xml = parser.parseFromString(response,'text/xml'); - // if (xml.getElementsByTagName('Resolver').length === 0) { - // const name = xml.getElementsByTagName('name')[0].childNodes[0].nodeValue; - // return new searchActions.RetrieveCoordinatesFailAction(name); - // } - // const name = xml.getElementsByTagName('name')[0].childNodes[0].nodeValue; - // const ra = +xml.getElementsByTagName('jradeg')[0].childNodes[0].nodeValue; - // const dec = +xml.getElementsByTagName('jdedeg')[0].childNodes[0].nodeValue; - // return new searchActions.RetrieveCoordinatesSuccessAction({name, ra, dec}); - // }), - // catchError(() => of(new searchActions.RetrieveCoordinatesFailAction(null))) - // ); - // }) - // ); - // - // @Effect({ dispatch: false }) - // retrieveCoordinatesFailAction$ = this.actions$.pipe( - // ofType(searchActions.RETRIEVE_COORDINATES_FAIL), - // tap(action => { - // const retrieveCoordinatesFailAction = action as searchActions.RetrieveCoordinatesFailAction; - // if (retrieveCoordinatesFailAction.payload) { - // this.toastr.error(retrieveCoordinatesFailAction.payload + ' not found'); - // } else { - // this.toastr.error('Connection to Sesame Name Resolver failed', 'Resolver Failed!'); - // } - // }) - // ); } diff --git a/src/app/search/store/search.service.ts b/src/app/search/store/search.service.ts index 9af89f3be8a61db425bf348b537c1c83b09cde36..bb16196f8c17849f6f8bc56c14181c023bfba4dd 100644 --- a/src/app/search/store/search.service.ts +++ b/src/app/search/store/search.service.ts @@ -26,9 +26,4 @@ export class SearchService { checkProcess(id: string) { return this.http.head<any>('http://0.0.0.0:8085/' + id + '.csv'); } - - // retrieveCoordinates(name: string) { - // const url = 'https://cdsweb.u-strasbg.fr/cgi-bin/nph-sesame/-ox/NSV?' + name; - // return this.http.get(url, { responseType: 'text' }); - // } }