make sure we can send limit and offset when querying.

list the diagnostic reports in the dropdown for filtering.
This commit is contained in:
Jason Kulatunga 2023-10-01 17:05:55 -07:00
parent c258ab44d4
commit 6d831f6ee7
No known key found for this signature in database
6 changed files with 72 additions and 23 deletions

1
.gitignore vendored
View File

@ -8,6 +8,7 @@
.idea/**/tasks.xml .idea/**/tasks.xml
.idea/**/dictionaries .idea/**/dictionaries
.idea/**/shelf .idea/**/shelf
.idea/dataSources.xml
# Sensitive or high-churn files # Sensitive or high-churn files
.idea/**/dataSources/ .idea/**/dataSources/

View File

@ -34,19 +34,20 @@ const (
const TABLE_ALIAS = "fhir" const TABLE_ALIAS = "fhir"
//Allows users to use SearchParameters to query resources // Allows users to use SearchParameters to query resources
// Can generate simple or complex queries, depending on the SearchParameter type: // Can generate simple or complex queries, depending on the SearchParameter type:
// //
// eg. Simple // eg. Simple
// //
//
// eg. Complex // eg. Complex
// SELECT fhir.* // SELECT fhir.*
// FROM fhir_observation as fhir, json_each(fhir.code) as codeJson // FROM fhir_observation as fhir, json_each(fhir.code) as codeJson
// WHERE ( // WHERE (
//
// (codeJson.value ->> '$.code' = "29463-7" AND codeJson.value ->> '$.system' = "http://loinc.org") // (codeJson.value ->> '$.code' = "29463-7" AND codeJson.value ->> '$.system' = "http://loinc.org")
// OR (codeJson.value ->> '$.code' = "3141-9" AND codeJson.value ->> '$.system' = "http://loinc.org") // OR (codeJson.value ->> '$.code' = "3141-9" AND codeJson.value ->> '$.system' = "http://loinc.org")
// OR (codeJson.value ->> '$.code' = "27113001" AND codeJson.value ->> '$.system' = "http://snomed.info/sct") // OR (codeJson.value ->> '$.code' = "27113001" AND codeJson.value ->> '$.system' = "http://snomed.info/sct")
//
// ) // )
// AND (user_id = "6efcd7c5-3f29-4f0d-926d-a66ff68bbfc2") // AND (user_id = "6efcd7c5-3f29-4f0d-926d-a66ff68bbfc2")
// GROUP BY `fhir`.`id` // GROUP BY `fhir`.`id`
@ -142,7 +143,7 @@ func (sr *SqliteRepository) sqlQueryResources(ctx context.Context, query models.
//defaults //defaults
selectClauses := []string{fmt.Sprintf("%s.*", TABLE_ALIAS)} selectClauses := []string{fmt.Sprintf("%s.*", TABLE_ALIAS)}
groupClause := fmt.Sprintf("%s.id", TABLE_ALIAS) groupClause := fmt.Sprintf("%s.id", TABLE_ALIAS)
orderClause := fmt.Sprintf("%s.sort_date ASC", TABLE_ALIAS) orderClause := fmt.Sprintf("%s.sort_date DESC", TABLE_ALIAS)
if query.Aggregations != nil { if query.Aggregations != nil {
//Handle Aggregations //Handle Aggregations
@ -210,12 +211,21 @@ func (sr *SqliteRepository) sqlQueryResources(ctx context.Context, query models.
fromClauses = lo.Uniq(fromClauses) fromClauses = lo.Uniq(fromClauses)
fromClauses = lo.Compact(fromClauses) fromClauses = lo.Compact(fromClauses)
return sr.GormClient.WithContext(ctx). fluentQuery := sr.GormClient.WithContext(ctx).
Select(strings.Join(selectClauses, ", ")). Select(strings.Join(selectClauses, ", ")).
Where(strings.Join(whereClauses, " AND "), whereNamedParameters). Where(strings.Join(whereClauses, " AND "), whereNamedParameters).
Group(groupClause). Group(groupClause).
Order(orderClause). Order(orderClause)
Table(strings.Join(fromClauses, ", ")), nil
//add limit and offset clauses if present
if query.Limit != nil {
fluentQuery = fluentQuery.Limit(*query.Limit)
}
if query.Offset != nil {
fluentQuery = fluentQuery.Offset(*query.Offset)
}
return fluentQuery.Table(strings.Join(fromClauses, ", ")), nil
} }
/// INTERNAL functionality. These functions are exported for testing, but are not available in the Interface /// INTERNAL functionality. These functions are exported for testing, but are not available in the Interface
@ -227,14 +237,17 @@ type SearchParameter struct {
Modifier string Modifier string
} }
//Lists in the SearchParameterValueOperatorTree are AND'd together, and items within each SearchParameterValueOperatorTree list are OR'd together // Lists in the SearchParameterValueOperatorTree are AND'd together, and items within each SearchParameterValueOperatorTree list are OR'd together
//For example, the following would be AND'd together, and then OR'd with the next SearchParameterValueOperatorTree // For example, the following would be AND'd together, and then OR'd with the next SearchParameterValueOperatorTree
// { //
// {SearchParameterValue{Value: "foo"}, SearchParameterValue{Value: "bar"}} // {
// {SearchParameterValue{Value: "baz"}}, // {SearchParameterValue{Value: "foo"}, SearchParameterValue{Value: "bar"}}
// } // {SearchParameterValue{Value: "baz"}},
//This would result in the following SQL: // }
// (value = "foo" OR value = "bar") AND (value = "baz") //
// This would result in the following SQL:
//
// (value = "foo" OR value = "bar") AND (value = "baz")
type SearchParameterValueOperatorTree [][]SearchParameterValue type SearchParameterValueOperatorTree [][]SearchParameterValue
type SearchParameterValue struct { type SearchParameterValue struct {
@ -243,7 +256,7 @@ type SearchParameterValue struct {
SecondaryValues map[string]interface{} SecondaryValues map[string]interface{}
} }
//SearchParameters are made up of parameter names and modifiers. For example, "name" and "name:exact" are both valid search parameters // SearchParameters are made up of parameter names and modifiers. For example, "name" and "name:exact" are both valid search parameters
// This function will parse the searchCodeWithModifier and return the SearchParameter // This function will parse the searchCodeWithModifier and return the SearchParameter
func ProcessSearchParameter(searchCodeWithModifier string, searchParamTypeLookup map[string]string) (SearchParameter, error) { func ProcessSearchParameter(searchCodeWithModifier string, searchParamTypeLookup map[string]string) (SearchParameter, error) {
searchParameter := SearchParameter{} searchParameter := SearchParameter{}
@ -284,8 +297,9 @@ func ProcessSearchParameter(searchCodeWithModifier string, searchParamTypeLookup
// top level is AND'd together, and each item within the lists are OR'd together // top level is AND'd together, and each item within the lists are OR'd together
// //
// For example, searchParamCodeValueOrValuesWithPrefix may be: // For example, searchParamCodeValueOrValuesWithPrefix may be:
// "code": "29463-7,3141-9,27113001" //
// "code": ["le29463-7", "gt3141-9", "27113001"] // "code": "29463-7,3141-9,27113001"
// "code": ["le29463-7", "gt3141-9", "27113001"]
func ProcessSearchParameterValueIntoOperatorTree(searchParameter SearchParameter, searchParamCodeValueOrValuesWithPrefix interface{}) (SearchParameterValueOperatorTree, error) { func ProcessSearchParameterValueIntoOperatorTree(searchParameter SearchParameter, searchParamCodeValueOrValuesWithPrefix interface{}) (SearchParameterValueOperatorTree, error) {
searchParamCodeValuesWithPrefix := []string{} searchParamCodeValuesWithPrefix := []string{}
@ -416,7 +430,7 @@ func NamedParameterWithSuffix(parameterName string, suffix string) string {
return fmt.Sprintf("%s_%s", parameterName, suffix) return fmt.Sprintf("%s_%s", parameterName, suffix)
} }
//SearchCodeToWhereClause converts a searchCode and searchCodeValue to a where clause and a map of named parameters // SearchCodeToWhereClause converts a searchCode and searchCodeValue to a where clause and a map of named parameters
func SearchCodeToWhereClause(searchParam SearchParameter, searchParamValue SearchParameterValue, namedParameterSuffix string) (string, map[string]interface{}, error) { func SearchCodeToWhereClause(searchParam SearchParameter, searchParamValue SearchParameterValue, namedParameterSuffix string) (string, map[string]interface{}, error) {
//add named parameters to the lookup map. Basically, this is a map of all the named parameters that will be used in the where clause we're generating //add named parameters to the lookup map. Basically, this is a map of all the named parameters that will be used in the where clause we're generating
@ -575,7 +589,7 @@ func AggregationParameterToClause(aggParameter SearchParameter) string {
} }
} }
//ProcessAggregationParameter processes the aggregation parameters which are fields with optional properties: // ProcessAggregationParameter processes the aggregation parameters which are fields with optional properties:
// Fields that are primitive types (number, uri) must not have any property specified: // Fields that are primitive types (number, uri) must not have any property specified:
// eg. `probability` // eg. `probability`
// //

View File

@ -11,6 +11,8 @@ type QueryResource struct {
Select []string `json:"select"` Select []string `json:"select"`
From string `json:"from"` From string `json:"from"`
Where map[string]interface{} `json:"where"` Where map[string]interface{} `json:"where"`
Limit *int `json:"limit,omitempty"`
Offset *int `json:"offset,omitempty"`
//aggregation fields //aggregation fields
Aggregations *QueryResourceAggregations `json:"aggregations"` Aggregations *QueryResourceAggregations `json:"aggregations"`
@ -56,7 +58,13 @@ func (q *QueryResource) Validate() error {
if strings.Contains(q.Aggregations.OrderBy, " ") { if strings.Contains(q.Aggregations.OrderBy, " ") {
return fmt.Errorf("order_by cannot have spaces (or aliases)") return fmt.Errorf("order_by cannot have spaces (or aliases)")
} }
}
if q.Limit != nil && *q.Limit < 0 {
return fmt.Errorf("'limit' must be greater than or equal to zero")
}
if q.Offset != nil && *q.Offset < 0 {
return fmt.Errorf("'offset' must be greater than or equal to zero")
} }
return nil return nil

View File

@ -3,8 +3,8 @@ export class DashboardWidgetQuery {
select: string[] select: string[]
from: string from: string
where: {[key: string]: string | string[]} where: {[key: string]: string | string[]}
// limit: number limit?: number
// offset: number offset?: number
//https://lodash.com/docs/4.17.15#unionBy //https://lodash.com/docs/4.17.15#unionBy
aggregations?: { aggregations?: {

View File

@ -22,8 +22,7 @@
<div ngbDropdownMenu aria-labelledby="dropdownReports"> <div ngbDropdownMenu aria-labelledby="dropdownReports">
<button ngbDropdownItem>All</button> <button ngbDropdownItem>All</button>
<button ngbDropdownItem [disabled]="true">-----</button> <button ngbDropdownItem [disabled]="true">-----</button>
<button ngbDropdownItem>Report 1</button> <button *ngFor="let diagnosticReport of diagnosticReports" ngbDropdownItem>{{diagnosticReport?.sort_title}} [{{diagnosticReport?.sort_date | amDateFormat: 'LL'}}]</button>
<button ngbDropdownItem>Report 2</button>
</div> </div>
</div> </div>
<div ngbDropdown class="d-inline-block float-right"> <div ngbDropdown class="d-inline-block float-right">

View File

@ -17,12 +17,39 @@ export class ReportLabsComponent implements OnInit {
isEmptyReport = false isEmptyReport = false
diagnosticReports: ResourceFhir[] = []
constructor( constructor(
private fastenApi: FastenApiService, private fastenApi: FastenApiService,
) { } ) { }
ngOnInit(): void { ngOnInit(): void {
this.loading = true this.loading = true
this.fastenApi.queryResources({
select: ["*"],
from: "DiagnosticReport",
where: {
"category": "http://terminology.hl7.org/CodeSystem/v2-0074|LAB",
},
limit: 5,
}).subscribe(results => {
this.diagnosticReports = results.data
console.log("ALL DIAGNOSTIC REPORTS", results)
})
this.fastenApi.queryResources({
select: ["*"],
from: "Observation",
where: {},
aggregations: {
order_by: "code:code"
}
}).subscribe(results => {
console.log("OBSERVATIONS GROUPED", results)
})
this.fastenApi.getResources("Observation").subscribe(results => { this.fastenApi.getResources("Observation").subscribe(results => {
this.loading = false this.loading = false
results = results || [] results = results || []