Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,5 @@ node_modules
npm-debug.log
tools/*.jar
tools/cloud_sql_proxy
/api/sa-key.json
.DS_Store
2 changes: 2 additions & 0 deletions api/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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:+'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Which classes required this?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So i get this error without that dependency: java.lang.RuntimeException: Google App Engine runtime detected (the environment variable "com.google.appengine.runtime.version" is set), but unable to resolve appengine-sdk classes. For more details see https://github.com/GoogleCloudPlatform/google-cloud-java/blob/master/APPENGINE.md

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting. Thanks for the info!

compile('org.springframework.boot:spring-boot-starter-web:+') {
exclude module: 'spring-boot-starter-tomcat'
}
Expand All @@ -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.+'
Expand Down
2 changes: 2 additions & 0 deletions api/docker-compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
14 changes: 14 additions & 0 deletions api/libproject/devstart.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
require_relative "utils/common"
require "io/console"
require "json"
require "optparse"
require "tempfile"

Expand Down Expand Up @@ -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 = "[email protected]"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm thinking we may just want to use [email protected] here, for a couple reasons:

  • Our server will need to talk to Firecloud, not just BigQuery; we'll be using the creds for that, too.
  • We already have to configure things so that this service account has access to FireCloud and BigQuery to get our test env working; we can avoid having additional configuration by reusing it for local development.

Note that if you use run_with_creds() / get_service_account_creds_file() -- see below -- it will use that service account.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we can certainly do that. I don't have access to create a service account under @appspot.gserviceaccount.com. I can currently only create under all-of-us-workbench-test.iam.gserviceaccount.com and @all-of-us-ehr-dev.iam.gserviceaccount.com

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You don't need to create a service account, it already exists. (It's the SA that our deployed AppEngine app in test uses.)

You will need a key for it, but you are an Owner on all-of-us-workbench-test, so you should have permissions to do that in cloud console or on the command line (like this).

common.run_inline %W{
gcloud iam service-accounts keys create sa-key.json --iam-account #{iam_account}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So this will use whatever project and account you might already be gcloud configured to (which may not be the right ones.)

We should probably use --project all-of-us-workbench-test here (assuming that's relevant for this command?) and maybe --account .

Either way, it probably makes sense to try to reuse logic from run_with_creds() or get_service_account_creds_file() in here, as it's doing much the same thing.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess i'm still a little confused about this and the above comments. Maybe we can discuss over a call?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Definitely. Let's chat on Monday.

}
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"
Expand Down
Original file line number Diff line number Diff line change
@@ -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<CriteriaListResponse> getCriteriaByTypeAndParentId(String type, String parentId) {

QueryResult result = getQueryResult(type, parentId);

Map<String, Integer> rm = getResultMapper(result);

CriteriaListResponse criteriaResponse = new CriteriaListResponse();
for (List<FieldValue> 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<String, Integer> getResultMapper(QueryResult result) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Eventually we'll probably want to move this and some of the code above into some BigQuery utils class. (Fine to leave it here for now.)

Map<String, Integer> resultMapper = new HashMap<String, Integer>();
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");
}
}
71 changes: 71 additions & 0 deletions api/src/main/resources/workbench.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
##########################################################################################
Expand Down Expand Up @@ -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
1 change: 1 addition & 0 deletions api/src/main/webapp/WEB-INF/appengine-web.xml.template
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
<service>api</service>
<runtime>java8</runtime>
<threadsafe>true</threadsafe>
<application>all-of-us-workbench-test</application>

<env-variables>
<env-var name="spring.datasource.driver-class-name" value="${DB_DRIVER}"/>
Expand Down
7 changes: 5 additions & 2 deletions ui/src/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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';


Expand Down Expand Up @@ -68,6 +69,7 @@ export function getConfiguration(signInService: SignInService): Configuration {
MedicationsComponent,
WizardSelectComponent,
WizardModalComponent,
WizardTreeParentComponent,
CohortEditComponent,
HomePageComponent,
WorkspaceComponent,
Expand All @@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
25 changes: 8 additions & 17 deletions ui/src/app/views/cohort-builder/search/model/search-result.ts
Original file line number Diff line number Diff line change
@@ -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 {
Expand All @@ -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;
}

Expand Down
Loading