diff --git a/.gitignore b/.gitignore index 6d9c1a35399..f84eb2f8915 100644 --- a/.gitignore +++ b/.gitignore @@ -28,4 +28,5 @@ node_modules npm-debug.log tools/*.jar tools/cloud_sql_proxy +/api/sa-key.json .DS_Store diff --git a/api/build.gradle b/api/build.gradle index 1d0f9f71191..55be6941706 100644 --- a/api/build.gradle +++ b/api/build.gradle @@ -120,6 +120,7 @@ dependencies { compile 'mysql:mysql-connector-java:+' compile 'com.google.cloud.sql:mysql-socket-factory:+' compile 'com.google.appengine:appengine:+' + compile 'com.google.appengine:appengine-api-1.0-sdk:+' compile('org.springframework.boot:spring-boot-starter-web:+') { exclude module: 'spring-boot-starter-tomcat' } @@ -136,6 +137,7 @@ dependencies { compile 'joda-time:joda-time:+' compile 'javax.inject:javax.inject:1' compile 'io.swagger:swagger-annotations:+' + compile 'com.google.cloud:google-cloud-bigquery:+' testCompile 'junit:junit:4.12' testCompile 'org.mockito:mockito-core:1.+' diff --git a/api/docker-compose.yaml b/api/docker-compose.yaml index 55ea3e1f7af..83decae660c 100644 --- a/api/docker-compose.yaml +++ b/api/docker-compose.yaml @@ -16,6 +16,8 @@ services: - gradle-cache:/root/.gradle - .:/w:cached command: ./gradlew appengineRun + environment: + - GOOGLE_APPLICATION_CREDENTIALS=/w/sa-key.json env_file: - db/vars.env ports: diff --git a/api/libproject/devstart.rb b/api/libproject/devstart.rb index 9c8e0dd39cc..f188d873248 100644 --- a/api/libproject/devstart.rb +++ b/api/libproject/devstart.rb @@ -1,5 +1,6 @@ require_relative "utils/common" require "io/console" +require "json" require "optparse" require "tempfile" @@ -58,6 +59,19 @@ def dev_up(args) common.run_inline %W{docker-compose up -d db} common.status "Running database migrations..." common.run_inline %W{docker-compose run db-migration} + common.status "Creating service account for API..." + iam_account = "bigqueryservice@all-of-us-workbench-test.iam.gserviceaccount.com" + common.run_inline %W{ + gcloud iam service-accounts keys create sa-key.json --iam-account #{iam_account} + } + at_exit { + # use JSON library to parse sa-key.json and extract key ID + keyFile = File.read("sa-key.json") + key_id = JSON.parse(keyFile)["private_key_id"] + common.run_inline %W{ + gcloud iam service-accounts keys delete #{key_id} --iam-account #{iam_account} + } + } common.status "Starting API. This can take a while. Thoughts on reducing development cycle time" common.status "are here:" common.status " https://github.com/all-of-us/workbench/blob/master/api/doc/2017/dev-cycle.md" diff --git a/api/src/main/java/org/pmiops/workbench/api/CohortBuilderController.java b/api/src/main/java/org/pmiops/workbench/api/CohortBuilderController.java new file mode 100644 index 00000000000..5311dcade54 --- /dev/null +++ b/api/src/main/java/org/pmiops/workbench/api/CohortBuilderController.java @@ -0,0 +1,98 @@ +package org.pmiops.workbench.api; + +import com.google.cloud.bigquery.*; +import org.pmiops.workbench.model.Criteria; +import org.pmiops.workbench.model.CriteriaListResponse; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.RestController; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.logging.Logger; + +@RestController +public class CohortBuilderController implements CohortBuilderApiDelegate { + + private static final Logger log = Logger.getLogger(CohortBuilderController.class.getName()); + + public static final String CRITERIA_QUERY = + "SELECT id,\n" + + "type,\n" + + "code,\n" + + "name,\n" + + "est_count,\n" + + "is_group,\n" + + "is_selectable,\n" + + "domain_id\n" + + "FROM `pmi-drc-api-test.synpuf.%s`\n" + + "WHERE parent_id = @parentId\n" + + "order by id asc"; + + @Override + public ResponseEntity getCriteriaByTypeAndParentId(String type, String parentId) { + + QueryResult result = getQueryResult(type, parentId); + + Map rm = getResultMapper(result); + + CriteriaListResponse criteriaResponse = new CriteriaListResponse(); + for (List row : result.iterateAll()) { + final Criteria criteria = new Criteria(); + criteria.setId(row.get(rm.get("id")).getLongValue()); + criteria.setType(row.get(rm.get("type")).getStringValue()); + criteria.setCode(row.get(rm.get("code")).getStringValue()); + criteria.setName(row.get(rm.get("name")).getStringValue()); + criteria.setCount(row.get(rm.get("est_count")).isNull() ? 0 : row.get(rm.get("est_count")).getLongValue()); + criteria.setGroup(row.get(rm.get("is_group")).getBooleanValue()); + criteria.setSelectable(row.get(rm.get("is_selectable")).getBooleanValue()); + criteria.setDomainId(row.get(rm.get("domain_id")).isNull() ? null : row.get(rm.get("domain_id")).getStringValue()); + criteriaResponse.addItemsItem(criteria); + } + + return ResponseEntity.ok(criteriaResponse); + } + + protected QueryResult getQueryResult(String type, String parentId) { + BigQuery bigquery = + new BigQueryOptions.DefaultBigqueryFactory().create(BigQueryOptions.getDefaultInstance()); + + QueryRequest queryRequest = + QueryRequest.newBuilder(getQueryString(type)) + .addNamedParameter("parentId", QueryParameterValue.int64(new Integer(parentId))) + .setUseLegacySql(false) + .build(); + + // Execute the query. + QueryResponse response = bigquery.query(queryRequest); + + // Wait for the job to finish + while (!response.jobCompleted()) { + response = bigquery.getQueryResults(response.getJobId()); + } + + // Check for errors. + if (response.hasErrors()) { + String firstError = ""; + if (response.getExecutionErrors().size() != 0) { + firstError = response.getExecutionErrors().get(0).getMessage(); + } + throw new RuntimeException(firstError); + } + + return response.getResult(); + } + + protected Map getResultMapper(QueryResult result) { + Map resultMapper = new HashMap(); + int i = 0; + for (Field field : result.getSchema().getFields()) { + resultMapper.put(field.getName(), i++); + } + return resultMapper; + } + + protected String getQueryString(String type) { + return String.format(CRITERIA_QUERY, type + "_criteria"); + } +} diff --git a/api/src/main/resources/workbench.yaml b/api/src/main/resources/workbench.yaml index 930ee5cb024..5e8645d7052 100644 --- a/api/src/main/resources/workbench.yaml +++ b/api/src/main/resources/workbench.yaml @@ -310,6 +310,30 @@ paths: required: true description: ID of the workspace containing the cohort definition + # Cohort Builder ####################################################################### + /api/v1/cohort-builder/criteria/{type}/{parentId}: + get: + tags: + - cohortBuilder + description: Returns builder criteria tree with the specified criteria type and parentId + operationId: "getCriteriaByTypeAndParentId" + parameters: + - in: path + name: type + type: string + required: true + description: the specific type of criteria to get + - in: path + name: parentId + type: string + required: true + description: fetch children of parentId + responses: + "200": + description: A collection of criteria + schema: + $ref: "#/definitions/CriteriaListResponse" + ########################################################################################## ## DEFINITIONS ########################################################################################## @@ -470,3 +494,50 @@ definitions: phoneNumber: description: the user's phone number type: string + + CriteriaListResponse: + type: "object" + required: + - "items" + properties: + items: + type: "array" + items: + $ref: "#/definitions/Criteria" + + Criteria: + type: "object" + required: + - "id" + - "type" + - "code" + - "name" + - "group" + - "selectable" + properties: + id: + description: id of the criteria + type: integer + format: int64 + type: + description: type of criteria + type: string + code: + description: code that identifies this criteria + type: string + name: + description: description of criteria + type: string + count: + description: est. count in the cdr + type: integer + format: int64 + group: + description: specifies if child or parent + type: boolean + selectable: + description: specifies if user can search with + type: boolean + domainId: + description: clue to determine which tables to search + type: string \ No newline at end of file diff --git a/api/src/main/webapp/WEB-INF/appengine-web.xml.template b/api/src/main/webapp/WEB-INF/appengine-web.xml.template index 350e73675f2..a1d43619ba1 100644 --- a/api/src/main/webapp/WEB-INF/appengine-web.xml.template +++ b/api/src/main/webapp/WEB-INF/appengine-web.xml.template @@ -2,6 +2,7 @@ api java8 true + all-of-us-workbench-test diff --git a/ui/src/app/app.module.ts b/ui/src/app/app.module.ts index c72b3dc579a..8a6dfab10ec 100644 --- a/ui/src/app/app.module.ts +++ b/ui/src/app/app.module.ts @@ -15,7 +15,7 @@ import {HomePageComponent} from 'app/views/home-page/component'; import {SignInService} from 'app/services/sign-in.service'; import {WorkspaceComponent} from 'app/views/workspace/component'; import {WorkspaceEditComponent} from 'app/views/workspace-edit/component'; -import {CohortsService, WorkspacesService, Configuration, ConfigurationParameters} from 'generated'; +import {CohortsService, WorkspacesService, Configuration, ConfigurationParameters, CohortBuilderService} from 'generated'; import {environment} from 'environments/environment'; import { SearchGroupComponent } from 'app/views/cohort-builder/search/search-group/search-group.component'; import { CriteriaTreeComponent } from 'app/views/cohort-builder/search/criteria-tree/criteria-tree.component'; @@ -31,6 +31,7 @@ import { AnnotationsComponent } from 'app/views/cohort-builder/review/annotation import { MedicationsComponent } from 'app/views/cohort-builder/review/medications/medications.component'; import { WizardSelectComponent } from 'app/views/cohort-builder/search/wizard-select/wizard-select.component'; import { WizardModalComponent } from 'app/views/cohort-builder/search/wizard-modal/wizard-modal.component'; +import { WizardTreeParentComponent } from './views/cohort-builder/search/wizard-tree-parent/wizard-tree-parent.component'; import { BroadcastService, SearchService } from './views/cohort-builder/search/service'; @@ -68,6 +69,7 @@ export function getConfiguration(signInService: SignInService): Configuration { MedicationsComponent, WizardSelectComponent, WizardModalComponent, + WizardTreeParentComponent, CohortEditComponent, HomePageComponent, WorkspaceComponent, @@ -84,7 +86,8 @@ export function getConfiguration(signInService: SignInService): Configuration { CohortsService, WorkspacesService, BroadcastService, - SearchService + SearchService, + CohortBuilderService ], // This specifies the top-level component, to load first. diff --git a/ui/src/app/views/cohort-builder/search/criteria-group/criteria-group.component.ts b/ui/src/app/views/cohort-builder/search/criteria-group/criteria-group.component.ts index e8fccd6777e..0333a41ed9c 100644 --- a/ui/src/app/views/cohort-builder/search/criteria-group/criteria-group.component.ts +++ b/ui/src/app/views/cohort-builder/search/criteria-group/criteria-group.component.ts @@ -46,7 +46,7 @@ export class CriteriaGroupComponent implements OnInit, OnDestroy { this.hasOccurrencesSelectList = this.searchService.getHasOccurrencesSelectList(); this.daysOrYearsSelectList = this.searchService.getDaysOrYearsSelectList(); - this.eventOccurredDuringSelectList = this.searchService.getEventOccurredDuringSelectList(); + // this.eventOccurredDuringSelectList = this.searchService.getEventOccurredDuringSelectList(); if (this.modifierList.length === 0) { this.ageAtEvent = new Modifier(); diff --git a/ui/src/app/views/cohort-builder/search/model/search-result.ts b/ui/src/app/views/cohort-builder/search/model/search-result.ts index d56945a2b2a..6df01caf11c 100644 --- a/ui/src/app/views/cohort-builder/search/model/search-result.ts +++ b/ui/src/app/views/cohort-builder/search/model/search-result.ts @@ -1,5 +1,4 @@ import { Criteria, Modifier, Subject, SearchParameter } from './'; -import { CriteriaGroupComponent } from '../criteria-group/criteria-group.component'; import { SearchResponse } from './search-response'; export class SearchResult { @@ -15,35 +14,27 @@ export class SearchResult { constructor() - constructor(description: string, criteriaGroup: CriteriaGroupComponent) + constructor(description: string, criteriaList: Criteria[], modifierList: Modifier[]) - constructor(description?: string, criteriaGroup?: CriteriaGroupComponent) { - if (description && criteriaGroup) { - this.update(description, criteriaGroup); + constructor(description?: string, criteriaList?: Criteria[], modifierList?: Modifier[]) { + if (description && criteriaList && modifierList) { + this.update(description, criteriaList, modifierList); } } - update(description: string, criteriaGroup: CriteriaGroupComponent) { + update(description: string, criteriaList: Criteria[], modifierList: Modifier[]) { this.description = description; - criteriaGroup.modifierList ? - this.modifierList = criteriaGroup.modifierList : - this.modifierList = []; - criteriaGroup.criteriaList ? - this.criteriaList = criteriaGroup.criteriaList : - this.criteriaList = []; + criteriaList ? this.criteriaList = criteriaList : this.criteriaList = []; + modifierList ? this.modifierList = modifierList : this.modifierList = []; this.count = -1; for (const criteria of this.criteriaList) { this.searchType = criteria.type; - this.values.push(new SearchParameter(criteria.code, - criteria.type.startsWith('DEMO') ? - criteria.type : - criteria.domainId)); + this.values.push(new SearchParameter(criteria.code, criteria.type.startsWith('DEMO') ? criteria.type : criteria.domainId)); } } updateWithResponse(response: SearchResponse) { this.resultSet = response.subjectList; - this.values = response.searchParameterList; this.count = response.subjectList.length; } diff --git a/ui/src/app/views/cohort-builder/search/service/broadcast.service.ts b/ui/src/app/views/cohort-builder/search/service/broadcast.service.ts index efa5047c741..952aecb1210 100644 --- a/ui/src/app/views/cohort-builder/search/service/broadcast.service.ts +++ b/ui/src/app/views/cohort-builder/search/service/broadcast.service.ts @@ -1,7 +1,8 @@ -import { Injectable } from '@angular/core'; +import { Injectable, ComponentRef } from '@angular/core'; import { Subject } from 'rxjs/Subject'; import { ReplaySubject } from 'rxjs/ReplaySubject'; -import { Criteria, SearchGroup, SearchResult } from '../model'; +import { Criteria, SearchGroup, SearchResult, Modifier } from '../model'; +import { SearchRequest } from '../model/search-request'; @Injectable() export class BroadcastService { @@ -12,6 +13,11 @@ export class BroadcastService { */ private selectedCriteria = new Subject(); + /** + * Represents the selected criteria type. + */ + private selectedCriteriaType = new ReplaySubject(1); + /** * Represents the selected search group for the * active modal. @@ -39,12 +45,28 @@ export class BroadcastService { */ private updatedCharts = new Subject(); + /** + * Represents the summary page that needs updating. + */ + private summaryCriteriaGroup = new Subject(); + + /** + * Represents the summary page that needs updating. + */ + private summaryModifierList = new Subject(); + /** * The Observable that allows other components * to subscribe to this criteria subject. */ public selectedCriteria$ = this.selectedCriteria.asObservable(); + /** + * The Observable that allows other components + * to subscribe to this criteria type subject. + */ + public selectedCriteriaType$ = this.selectedCriteriaType.asObservable(); + /** * The Observable that allows other components * to subscribe to this search group subject. @@ -75,6 +97,18 @@ export class BroadcastService { */ public updatedCharts$ = this.updatedCharts.asObservable(); + /** + * The Observable that allows other components + * to subscribe to this event. + */ + public summaryCriteriaGroup$ = this.summaryCriteriaGroup.asObservable(); + + /** + * The Observable that allows other components + * to subscribe to this event. + */ + public summaryModifierList$ = this.summaryModifierList.asObservable(); + /** * Add the specified criteria to the subject. * @@ -84,6 +118,15 @@ export class BroadcastService { this.selectedCriteria.next(criteria); } + /** + * Add the specified criteria type to the subject. + * + * @param criteriaType + */ + selectCriteriaType(criteriaType: string) { + this.selectedCriteriaType.next(criteriaType); + } + /** * Add the specified search group to the subject. * @@ -132,4 +175,12 @@ export class BroadcastService { updateCharts(gender: any[], race: any[]) { this.updatedCharts.next({'gender': gender, 'race': race}); } + + setSummaryCriteriaGroup(criteriaList: Criteria[]) { + this.summaryCriteriaGroup.next(criteriaList); + } + + setSummaryModifierList(modifierList: Modifier[]) { + this.summaryModifierList.next(modifierList); + } } diff --git a/ui/src/app/views/cohort-builder/search/service/search.service.ts b/ui/src/app/views/cohort-builder/search/service/search.service.ts index 14b8e8aae66..9ac26851aa3 100644 --- a/ui/src/app/views/cohort-builder/search/service/search.service.ts +++ b/ui/src/app/views/cohort-builder/search/service/search.service.ts @@ -1,4 +1,4 @@ -import { Injectable } from '@angular/core'; +import { Injectable, OnInit, OnDestroy } from '@angular/core'; import { Observable } from 'rxjs/Observable'; import 'rxjs/add/operator/map'; import { Criteria, CriteriaType, SearchRequest, SearchResponse } from '../model'; @@ -20,7 +20,7 @@ const AGE_AT_EVENT: string[] = ['Any', 'GTE >=', 'LTE <=', 'Between']; const EVENT_DATE: string[] = ['Any', 'Within x year(s)', 'GTE >=', 'LTE <=', 'Between']; -const EVENT_OCCURRED_DURING: string[] = ['Inpatient visit', 'Outpatient visit']; +const VISIT_TYPE: string[] = ['Any', 'Inpatient visit', 'Outpatient visit']; const DAYS_OR_YEARS: string[] = ['Days', 'Years']; @@ -49,8 +49,8 @@ export class SearchService { return HAS_OCCURRENCES; } - getEventOccurredDuringSelectList(): string[] { - return EVENT_OCCURRED_DURING; + getVisitTypeSelectList(): string[] { + return VISIT_TYPE; } getDaysOrYearsSelectList(): string[] { @@ -58,18 +58,20 @@ export class SearchService { } getParentNodes(type: string): Observable { - return this.http.get(this.baseUrl + '/' + type.toLowerCase() + '/0') + return this.http.get('/api/' + type.toLowerCase() + '/0') .map(res => res.json()); } getChildNodes(criteria: any): Observable { - return this.http.get(this.baseUrl + '/' + criteria.type.toLowerCase() + '/' + criteria.id) + return this.http.get('/api/' + criteria.type.toLowerCase() + '/' + criteria.id) .map(res => res.json()); } getResults(searchRequest: SearchRequest): Observable { - return this.http.post(this.baseUrl + '/search', searchRequest) - .map(res => res.json()); + return this.http.post('/api/searchrequest', searchRequest) + .map( + res => res.json() + ); } } diff --git a/ui/src/app/views/cohort-builder/search/wizard-modal/wizard-modal.component.css b/ui/src/app/views/cohort-builder/search/wizard-modal/wizard-modal.component.css index e69de29bb2d..815211843f8 100644 --- a/ui/src/app/views/cohort-builder/search/wizard-modal/wizard-modal.component.css +++ b/ui/src/app/views/cohort-builder/search/wizard-modal/wizard-modal.component.css @@ -0,0 +1,51 @@ +.modal-body { + height: 100% !important; + overflow: hidden; +} + +.clr-wizard-content { + height: 100% !important; +} + +.modal-dialog.modal-xl { + width: 88% !important; +} + +.clr-wizard-page { + height: 100% !important; +} + +.box { + border: 1px solid #d7d7d7; +} + +.clr-treenode-link { + display: -webkit-box; +} + +.scrolling-tree { + overflow: auto; + height: 100%; +} + +.row-height { + height: 100%; +} + +.codes-height { + height: 100% +} + +.criteria-group-height { + height: 100%; +} + +.scrolling-criteria-group { + height: 95%; + overflow: auto; +} + +/*.text-truncate {*/ + /*width: 230px;*/ +/*}*/ + diff --git a/ui/src/app/views/cohort-builder/search/wizard-modal/wizard-modal.component.html b/ui/src/app/views/cohort-builder/search/wizard-modal/wizard-modal.component.html index 2d4e7efa76d..6d974a8379c 100644 --- a/ui/src/app/views/cohort-builder/search/wizard-modal/wizard-modal.component.html +++ b/ui/src/app/views/cohort-builder/search/wizard-modal/wizard-modal.component.html @@ -1,171 +1,41 @@ - + Add Criteria To Cohort - Cancel - Back - Next - Finish - Choose {{criteriaType.toUpperCase()}} Codes - - + + + Cancel + Back + Next + + - -
-
- Modifiers -
-
-
- -
- - - - - -  and  - -
-
-
- -
- - - - - - - - - - -
-
-
- -
- - - - - -  within  - - - -   - - apart - -
-
-
- -
- -
-
-
-
-
-

Selected ICD9 Codes

-
-
-
- 001-Cholera -
-
-
-
- OR - 002-Typhoid and paratyphoid fevers -
-
-
-
- OR - 003-Other salmonella infections -
-
-
-
- OR - 004-Shinellosis -
-
-
-
-
-
+ + + + + + + + + + + + - - Totals -
-
-
- - × - - Contains group - 001-Cholera - [001...] -
-
20
-
-
-
- - × - - OR - Contains group - 002-Typhoid and paratyphoid fevers - [002...] -
-
16
-
-
-
- - × - - OR - Contains group - 003-Other salmonella infections - [003...] -
-
32
-
-
-
- - × - - OR - Contains group - 004-Shinellosis - [004...] -
-
50
-
-
-
Total Count: 100
-
+ + + + + + + + + + +
diff --git a/ui/src/app/views/cohort-builder/search/wizard-modal/wizard-modal.component.ts b/ui/src/app/views/cohort-builder/search/wizard-modal/wizard-modal.component.ts index 61eb07769c6..d3b8254f468 100644 --- a/ui/src/app/views/cohort-builder/search/wizard-modal/wizard-modal.component.ts +++ b/ui/src/app/views/cohort-builder/search/wizard-modal/wizard-modal.component.ts @@ -1,61 +1,81 @@ -import { Component, OnInit, ViewChild } from '@angular/core'; -import { Modifier } from '../model'; -import { SearchService } from '../service'; -import {Wizard} from 'clarity-angular/wizard/wizard'; +import { Component, OnInit, ViewChild, ViewEncapsulation, OnDestroy, ComponentRef } from '@angular/core'; +import { Wizard } from 'clarity-angular/wizard/wizard'; +import { SearchGroup, SearchResult, Criteria, Modifier, SearchRequest } from '../model'; +import { BroadcastService, SearchService } from '../service'; +import { Subscription } from 'rxjs/Subscription'; @Component({ selector: 'app-wizard-modal', templateUrl: './wizard-modal.component.html', - styleUrls: ['./wizard-modal.component.css'] + styleUrls: ['./wizard-modal.component.css'], + encapsulation: ViewEncapsulation.None }) -export class WizardModalComponent implements OnInit { +export class WizardModalComponent implements OnInit, OnDestroy { @ViewChild('wizard') wizard: Wizard; - modifierList: Modifier[] = []; - ageAtEventSelectList: string[] = []; - eventDateSelectList: string[] = []; - hasOccurrencesSelectList: string[] = []; - eventOccurredDuringSelectList: string[] = []; - daysOrYearsSelectList: string[] = []; - private alive = true; - ageAtEvent: Modifier; - eventDate: Modifier; - hasOccurrences: Modifier; + private selectedSearchGroup: SearchGroup; + private selectedSearchResult: SearchResult; + private criteriaList: Criteria[] = []; + private modifierList: Modifier[] = []; criteriaType: string; + wizardModalRef: ComponentRef; + private criteriaTypeSubscription: Subscription; + private searchGroupSubscription: Subscription; + private criteriaListSubscription: Subscription; + private modifierListSubscription: Subscription; - constructor(private searchService: SearchService) { } + constructor(private broadcastService: BroadcastService, + private searchService: SearchService) { } ngOnInit() { - this.ageAtEventSelectList = this.searchService.getAgeAtEventSelectList(); - - this.eventDateSelectList = this.searchService.getEventDateSelectList(); + this.criteriaTypeSubscription = this.broadcastService.selectedCriteriaType$ + .subscribe(criteriaType => this.criteriaType = criteriaType); + this.searchGroupSubscription = this.broadcastService.selectedSearchGroup$ + .subscribe(searchGroup => this.selectedSearchGroup = searchGroup); + this.criteriaListSubscription = this.broadcastService.summaryCriteriaGroup$ + .subscribe(criteriaList => this.criteriaList = criteriaList); + this.modifierListSubscription = this.broadcastService.summaryModifierList$ + .subscribe(modifierList => this.modifierList = modifierList); + } - this.hasOccurrencesSelectList = this.searchService.getHasOccurrencesSelectList(); - this.daysOrYearsSelectList = this.searchService.getDaysOrYearsSelectList(); + public doCustomClick(type: string): void { + if ('finish' === type) { + this.wizard.finish(); + this.updateSearchResults(); + } + } - this.eventOccurredDuringSelectList = this.searchService.getEventOccurredDuringSelectList(); + public doCancel() { + this.wizard.finish(); + this.wizardModalRef.destroy(); + } - if (this.modifierList.length === 0) { - this.ageAtEvent = new Modifier(); - this.ageAtEvent.operator = this.ageAtEventSelectList[0]; - this.ageAtEvent.name = 'ageAtEvent'; - this.modifierList.push(this.ageAtEvent); + updateSearchResults() { + this.updateOrCreateSearchResult(); - this.eventDate = new Modifier(); - this.eventDate.operator = this.eventDateSelectList[0]; - this.eventDate.name = 'eventDate'; - this.modifierList.push(this.eventDate); + this.searchGroupSubscription = this.searchService.getResults( + new SearchRequest(this.criteriaType.toUpperCase(), this.selectedSearchResult)) + .subscribe(response => { + this.selectedSearchResult.updateWithResponse(response); + this.broadcastService.updateCounts(this.selectedSearchGroup, this.selectedSearchResult); + this.wizardModalRef.destroy(); + }); + } - this.hasOccurrences = new Modifier(); - this.hasOccurrences.operator = this.hasOccurrencesSelectList[0]; - this.hasOccurrences.value3 = this.daysOrYearsSelectList[0]; - this.hasOccurrences.name = 'hasOccurrences'; - this.modifierList.push(this.hasOccurrences); + updateOrCreateSearchResult() { + if (this.selectedSearchResult) { + this.selectedSearchResult.update(this.criteriaType.toUpperCase() + ' Group', this.criteriaList, this.modifierList); } else { - this.ageAtEvent = this.modifierList[0]; - this.eventDate = this.modifierList[1]; - this.hasOccurrences = this.modifierList[2]; + this.selectedSearchResult = new SearchResult(this.criteriaType.toUpperCase() + ' Group', this.criteriaList, this.modifierList); + this.selectedSearchGroup.results.push(this.selectedSearchResult); } } + ngOnDestroy() { + this.criteriaTypeSubscription.unsubscribe(); + this.searchGroupSubscription.unsubscribe(); + this.criteriaListSubscription.unsubscribe(); + this.modifierListSubscription.unsubscribe(); + } + } diff --git a/ui/src/app/views/cohort-builder/search/wizard-select/wizard-select.component.ts b/ui/src/app/views/cohort-builder/search/wizard-select/wizard-select.component.ts index ebbcfa761c8..08b4cc655db 100644 --- a/ui/src/app/views/cohort-builder/search/wizard-select/wizard-select.component.ts +++ b/ui/src/app/views/cohort-builder/search/wizard-select/wizard-select.component.ts @@ -1,7 +1,6 @@ -import { Component, OnInit, ComponentFactoryResolver, - ViewChild, ViewContainerRef } from '@angular/core'; +import { Component, OnInit, ComponentFactoryResolver, ViewChild, ViewContainerRef } from '@angular/core'; import { CriteriaType } from '../model'; -import { SearchService } from '../service'; +import { SearchService, BroadcastService } from '../service'; import { WizardModalComponent } from '../wizard-modal/wizard-modal.component'; @Component({ @@ -16,6 +15,7 @@ export class WizardSelectComponent implements OnInit { parent: ViewContainerRef; constructor(private searchService: SearchService, + private broadcastService: BroadcastService, private componentFactoryResolver: ComponentFactoryResolver) { } ngOnInit() { @@ -23,10 +23,10 @@ export class WizardSelectComponent implements OnInit { } openWizard(criteriaType: string) { - const wizardModalComponent = - this.componentFactoryResolver.resolveComponentFactory(WizardModalComponent); + const wizardModalComponent = this.componentFactoryResolver.resolveComponentFactory(WizardModalComponent); const wizardModalRef = this.parent.createComponent(wizardModalComponent); - wizardModalRef.instance.criteriaType = criteriaType; + this.broadcastService.selectCriteriaType(criteriaType); + wizardModalRef.instance.wizardModalRef = wizardModalRef; wizardModalRef.instance.wizard.open(); } diff --git a/ui/src/app/views/cohort-builder/search/wizard-tree-parent/wizard-tree-parent.component.css b/ui/src/app/views/cohort-builder/search/wizard-tree-parent/wizard-tree-parent.component.css new file mode 100644 index 00000000000..e69de29bb2d diff --git a/ui/src/app/views/cohort-builder/search/wizard-tree-parent/wizard-tree-parent.component.html b/ui/src/app/views/cohort-builder/search/wizard-tree-parent/wizard-tree-parent.component.html new file mode 100644 index 00000000000..bdfeb5b72bd --- /dev/null +++ b/ui/src/app/views/cohort-builder/search/wizard-tree-parent/wizard-tree-parent.component.html @@ -0,0 +1,46 @@ +
+
+ + + + + Loading... + + + Loading... + + + + + + + + + + + +
+
+ +
+
+ diff --git a/ui/src/app/views/cohort-builder/search/wizard-tree-parent/wizard-tree-parent.component.ts b/ui/src/app/views/cohort-builder/search/wizard-tree-parent/wizard-tree-parent.component.ts new file mode 100644 index 00000000000..6b2c06899d4 --- /dev/null +++ b/ui/src/app/views/cohort-builder/search/wizard-tree-parent/wizard-tree-parent.component.ts @@ -0,0 +1,46 @@ +import { Component, OnInit, Input, OnDestroy, ViewEncapsulation } from '@angular/core'; +import { BroadcastService } from '../service'; +import { SearchParameter } from '../model'; +import { Subscription } from 'rxjs/Subscription'; +import 'rxjs/add/operator/mergeMap'; +import { CohortBuilderService } from 'generated'; +import { Criteria } from 'generated'; + +@Component({ + selector: 'app-wizard-tree-parent', + templateUrl: 'wizard-tree-parent.component.html', + styleUrls: ['wizard-tree-parent.component.css'], + encapsulation: ViewEncapsulation.None +}) +export class WizardTreeParentComponent implements OnInit, OnDestroy { + + nodes: Criteria[]; + private subscription: Subscription; + loading: boolean; + + constructor(private cohortBuilderService: CohortBuilderService, + private broadcastService: BroadcastService) {} + + ngOnInit(): void { + this.loading = true; + this.subscription = + this.broadcastService.selectedCriteriaType$ + .mergeMap(criteriaType => this.cohortBuilderService.getCriteriaByTypeAndParentId(criteriaType, '0')) + .subscribe(nodes => { + this.nodes = nodes.items; + this.loading = false; + }); + } + + public selectCriteria(node: any): void { + node.values.push(new SearchParameter(node.code, node.domainId)); + this.broadcastService.selectCriteria(node); + } + + ngOnDestroy() { + this.subscription.unsubscribe(); + } + +} + +