finishing pagination for medical history page.
This commit is contained in:
parent
345b2d5cfd
commit
1fda51074e
|
@ -28,7 +28,7 @@ type DatabaseRepository interface {
|
||||||
AddResourceAssociation(ctx context.Context, source *models.SourceCredential, resourceType string, resourceId string, relatedSource *models.SourceCredential, relatedResourceType string, relatedResourceId string) error
|
AddResourceAssociation(ctx context.Context, source *models.SourceCredential, resourceType string, resourceId string, relatedSource *models.SourceCredential, relatedResourceType string, relatedResourceId string) error
|
||||||
RemoveResourceAssociation(ctx context.Context, source *models.SourceCredential, resourceType string, resourceId string, relatedSource *models.SourceCredential, relatedResourceType string, relatedResourceId string) error
|
RemoveResourceAssociation(ctx context.Context, source *models.SourceCredential, resourceType string, resourceId string, relatedSource *models.SourceCredential, relatedResourceType string, relatedResourceId string) error
|
||||||
FindResourceAssociationsByTypeAndId(ctx context.Context, source *models.SourceCredential, resourceType string, resourceId string) ([]models.RelatedResource, error)
|
FindResourceAssociationsByTypeAndId(ctx context.Context, source *models.SourceCredential, resourceType string, resourceId string) ([]models.RelatedResource, error)
|
||||||
GetFlattenedResourceGraph(ctx context.Context, graphType pkg.ResourceGraphType, options models.ResourceGraphOptions) (map[string][]*models.ResourceBase, error)
|
GetFlattenedResourceGraph(ctx context.Context, graphType pkg.ResourceGraphType, options models.ResourceGraphOptions) (map[string][]*models.ResourceBase, *models.ResourceGraphMetadata, error)
|
||||||
AddResourceComposition(ctx context.Context, compositionTitle string, resources []*models.ResourceBase) error
|
AddResourceComposition(ctx context.Context, compositionTitle string, resources []*models.ResourceBase) error
|
||||||
//UpsertProfile(context.Context, *models.Profile) error
|
//UpsertProfile(context.Context, *models.Profile) error
|
||||||
//UpsertOrganziation(context.Context, *models.Organization) error
|
//UpsertOrganziation(context.Context, *models.Organization) error
|
||||||
|
|
|
@ -28,10 +28,17 @@ func (rp *VertexResourcePlaceholder) ID() string {
|
||||||
// Retrieve a list of all fhir resources (vertex), and a list of all associations (edge)
|
// Retrieve a list of all fhir resources (vertex), and a list of all associations (edge)
|
||||||
// Generate a graph
|
// Generate a graph
|
||||||
// return list of root nodes, and their flattened related resources.
|
// return list of root nodes, and their flattened related resources.
|
||||||
func (sr *SqliteRepository) GetFlattenedResourceGraph(ctx context.Context, graphType pkg.ResourceGraphType, options models.ResourceGraphOptions) (map[string][]*models.ResourceBase, error) {
|
func (sr *SqliteRepository) GetFlattenedResourceGraph(ctx context.Context, graphType pkg.ResourceGraphType, options models.ResourceGraphOptions) (map[string][]*models.ResourceBase, *models.ResourceGraphMetadata, error) {
|
||||||
currentUser, currentUserErr := sr.GetCurrentUser(ctx)
|
currentUser, currentUserErr := sr.GetCurrentUser(ctx)
|
||||||
if currentUserErr != nil {
|
if currentUserErr != nil {
|
||||||
return nil, currentUserErr
|
return nil, nil, currentUserErr
|
||||||
|
}
|
||||||
|
|
||||||
|
//initialize the graph results metadata
|
||||||
|
resourceGraphMetadata := models.ResourceGraphMetadata{
|
||||||
|
TotalElements: 0,
|
||||||
|
PageSize: 20, //TODO: replace this with pkg.DefaultPageSize
|
||||||
|
Page: options.Page,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get list of all (non-reciprocal) relationships
|
// Get list of all (non-reciprocal) relationships
|
||||||
|
@ -44,7 +51,7 @@ func (sr *SqliteRepository) GetFlattenedResourceGraph(ctx context.Context, graph
|
||||||
}).
|
}).
|
||||||
Find(&relatedResourceRelationships)
|
Find(&relatedResourceRelationships)
|
||||||
if result.Error != nil {
|
if result.Error != nil {
|
||||||
return nil, result.Error
|
return nil, nil, result.Error
|
||||||
}
|
}
|
||||||
|
|
||||||
//Generate Graph
|
//Generate Graph
|
||||||
|
@ -97,7 +104,7 @@ func (sr *SqliteRepository) GetFlattenedResourceGraph(ctx context.Context, graph
|
||||||
&resourcePlaceholder,
|
&resourcePlaceholder,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("an error occurred while adding vertex: %v", err)
|
return nil, nil, fmt.Errorf("an error occurred while adding vertex: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -134,7 +141,7 @@ func (sr *SqliteRepository) GetFlattenedResourceGraph(ctx context.Context, graph
|
||||||
// }
|
// }
|
||||||
adjacencyMap, err := g.AdjacencyMap()
|
adjacencyMap, err := g.AdjacencyMap()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error while generating AdjacencyMap: %v", err)
|
return nil, nil, fmt.Errorf("error while generating AdjacencyMap: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// For a directed graph, PredecessorMap is the complement of AdjacencyMap. This is because in a directed graph, only
|
// For a directed graph, PredecessorMap is the complement of AdjacencyMap. This is because in a directed graph, only
|
||||||
|
@ -143,7 +150,7 @@ func (sr *SqliteRepository) GetFlattenedResourceGraph(ctx context.Context, graph
|
||||||
// ie. "empty" verticies in this map are "root" nodes.
|
// ie. "empty" verticies in this map are "root" nodes.
|
||||||
predecessorMap, err := g.PredecessorMap()
|
predecessorMap, err := g.PredecessorMap()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error while generating PredecessorMap: %v", err)
|
return nil, nil, fmt.Errorf("error while generating PredecessorMap: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Doing this in one massive function, because passing graph by reference is difficult due to generics.
|
// Doing this in one massive function, because passing graph by reference is difficult due to generics.
|
||||||
|
@ -193,10 +200,11 @@ func (sr *SqliteRepository) GetFlattenedResourceGraph(ctx context.Context, graph
|
||||||
// Step 2: now that we've created a relationship graph using placeholders, we need to determine which page of resources to return
|
// Step 2: now that we've created a relationship graph using placeholders, we need to determine which page of resources to return
|
||||||
// and look up the actual resources from the database.
|
// and look up the actual resources from the database.
|
||||||
|
|
||||||
resourceListDictionary, err := sr.InflateResourceGraphAtPage(resourcePlaceholderListDictionary, options.Page)
|
resourceListDictionary, totalElements, err := sr.InflateResourceGraphAtPage(resourcePlaceholderListDictionary, options.Page)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error while paginating & inflating resource graph: %v", err)
|
return nil, nil, fmt.Errorf("error while paginating & inflating resource graph: %v", err)
|
||||||
}
|
}
|
||||||
|
resourceGraphMetadata.TotalElements = totalElements
|
||||||
|
|
||||||
// Step 3: define a function. When given a resource, should find all related resources, flatten the heirarchy and set the RelatedResourceFhir list
|
// Step 3: define a function. When given a resource, should find all related resources, flatten the heirarchy and set the RelatedResourceFhir list
|
||||||
flattenRelatedResourcesFn := func(resource *models.ResourceBase) {
|
flattenRelatedResourcesFn := func(resource *models.ResourceBase) {
|
||||||
|
@ -279,7 +287,7 @@ func (sr *SqliteRepository) GetFlattenedResourceGraph(ctx context.Context, graph
|
||||||
|
|
||||||
// Step 5: return the populated resource list dictionary
|
// Step 5: return the populated resource list dictionary
|
||||||
|
|
||||||
return resourceListDictionary, nil
|
return resourceListDictionary, &resourceGraphMetadata, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadResourceGraphAtPage - this function will take a dictionary of placeholder "sources" graph and load the actual resources from the database, for a specific page
|
// LoadResourceGraphAtPage - this function will take a dictionary of placeholder "sources" graph and load the actual resources from the database, for a specific page
|
||||||
|
@ -287,7 +295,8 @@ func (sr *SqliteRepository) GetFlattenedResourceGraph(ctx context.Context, graph
|
||||||
// - sort the root resources by date, desc
|
// - sort the root resources by date, desc
|
||||||
// - use the page number + page size to determine which root resources to return
|
// - use the page number + page size to determine which root resources to return
|
||||||
// - return a dictionary of "source" resource lists
|
// - return a dictionary of "source" resource lists
|
||||||
func (sr *SqliteRepository) InflateResourceGraphAtPage(resourcePlaceholderListDictionary map[string][]*VertexResourcePlaceholder, page int) (map[string][]*models.ResourceBase, error) {
|
func (sr *SqliteRepository) InflateResourceGraphAtPage(resourcePlaceholderListDictionary map[string][]*VertexResourcePlaceholder, page int) (map[string][]*models.ResourceBase, int, error) {
|
||||||
|
totalElements := 0
|
||||||
// Step 3a: since we cant calulate the sort order until the resources are loaded, we need to load all the root resources first.
|
// Step 3a: since we cant calulate the sort order until the resources are loaded, we need to load all the root resources first.
|
||||||
|
|
||||||
//TODO: maybe its more performant to query each resource by type/id/source, since they are indexed already?
|
//TODO: maybe its more performant to query each resource by type/id/source, since they are indexed already?
|
||||||
|
@ -307,7 +316,7 @@ func (sr *SqliteRepository) InflateResourceGraphAtPage(resourcePlaceholderListDi
|
||||||
|
|
||||||
tableName, err := databaseModel.GetTableNameByResourceType(resourceType)
|
tableName, err := databaseModel.GetTableNameByResourceType(resourceType)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, totalElements, err
|
||||||
}
|
}
|
||||||
var tableWrappedResourceModels []models.ResourceBase
|
var tableWrappedResourceModels []models.ResourceBase
|
||||||
sr.GormClient.
|
sr.GormClient.
|
||||||
|
@ -322,6 +331,9 @@ func (sr *SqliteRepository) InflateResourceGraphAtPage(resourcePlaceholderListDi
|
||||||
//sort
|
//sort
|
||||||
rootWrappedResourceModels = utils.SortResourceListByDate(rootWrappedResourceModels)
|
rootWrappedResourceModels = utils.SortResourceListByDate(rootWrappedResourceModels)
|
||||||
|
|
||||||
|
//calculate total elements
|
||||||
|
totalElements = len(rootWrappedResourceModels)
|
||||||
|
|
||||||
//paginate (by calculating window for the slice)
|
//paginate (by calculating window for the slice)
|
||||||
rootWrappedResourceModels = utils.PaginateResourceList(rootWrappedResourceModels, page, 20) //todo: replace size with pkg.ResourceListPageSize
|
rootWrappedResourceModels = utils.PaginateResourceList(rootWrappedResourceModels, page, 20) //todo: replace size with pkg.ResourceListPageSize
|
||||||
|
|
||||||
|
@ -336,7 +348,7 @@ func (sr *SqliteRepository) InflateResourceGraphAtPage(resourcePlaceholderListDi
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step 4: return the populated resource list dictionary
|
// Step 4: return the populated resource list dictionary
|
||||||
return resourceListDictionary, nil
|
return resourceListDictionary, totalElements, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
//We need to support the following types of graphs:
|
//We need to support the following types of graphs:
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
package models
|
||||||
|
|
||||||
|
type ResourceGraphMetadata struct {
|
||||||
|
TotalElements int `json:"total_elements"`
|
||||||
|
PageSize int `json:"page_size"`
|
||||||
|
Page int `json:"page"`
|
||||||
|
}
|
|
@ -140,12 +140,15 @@ func GetResourceFhirGraph(c *gin.Context) {
|
||||||
graphOptions.Page = pageNumb
|
graphOptions.Page = pageNumb
|
||||||
}
|
}
|
||||||
|
|
||||||
resourceListDictionary, err := databaseRepo.GetFlattenedResourceGraph(c, pkg.ResourceGraphType(graphType), graphOptions)
|
resourceListDictionary, resourceListMetadata, err := databaseRepo.GetFlattenedResourceGraph(c, pkg.ResourceGraphType(graphType), graphOptions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Errorln("An error occurred while retrieving list of resources", err)
|
logger.Errorln("An error occurred while retrieving list of resources", err)
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"success": false})
|
c.JSON(http.StatusInternalServerError, gin.H{"success": false})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.JSON(http.StatusOK, gin.H{"success": true, "data": resourceListDictionary})
|
c.JSON(http.StatusOK, gin.H{"success": true, "data": map[string]interface{}{
|
||||||
|
"results": resourceListDictionary,
|
||||||
|
"metadata": resourceListMetadata,
|
||||||
|
}})
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
import {ResourceFhir} from './resource_fhir';
|
||||||
|
|
||||||
|
export class ResourceGraphResponse {
|
||||||
|
results: {[resource_type: string]: ResourceFhir[]}
|
||||||
|
metadata: ResourceGraphMetadata
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ResourceGraphMetadata {
|
||||||
|
total_elements: number
|
||||||
|
page_size: number
|
||||||
|
page: number
|
||||||
|
}
|
|
@ -12,7 +12,7 @@
|
||||||
<div class="row mt-5 mb-3">
|
<div class="row mt-5 mb-3">
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
|
|
||||||
<div class="alert alert-warning" role="alert">
|
<div [hidden]="true" class="alert alert-warning" role="alert">
|
||||||
<strong>Warning!</strong> Fasten has detected medical Encounters that are not associated with a Condition.
|
<strong>Warning!</strong> Fasten has detected medical Encounters that are not associated with a Condition.
|
||||||
They are grouped under the "Unassigned" section below.
|
They are grouped under the "Unassigned" section below.
|
||||||
<br/>
|
<br/>
|
||||||
|
@ -34,6 +34,13 @@
|
||||||
<!-- Condition List -->
|
<!-- Condition List -->
|
||||||
<app-report-medical-history-condition *ngFor="let condition of conditions; let i = index" [conditionGroup]="condition"></app-report-medical-history-condition>
|
<app-report-medical-history-condition *ngFor="let condition of conditions; let i = index" [conditionGroup]="condition"></app-report-medical-history-condition>
|
||||||
<app-report-medical-history-explanation-of-benefit *ngFor="let eob of explanationOfBenefits; let i = index" [explanationOfBenefitGroup]="eob"></app-report-medical-history-explanation-of-benefit>
|
<app-report-medical-history-explanation-of-benefit *ngFor="let eob of explanationOfBenefits; let i = index" [explanationOfBenefitGroup]="eob"></app-report-medical-history-explanation-of-benefit>
|
||||||
|
|
||||||
|
<ngb-pagination
|
||||||
|
*ngIf="resourceGraphMetadata.total_elements > resourceGraphMetadata.page_size"
|
||||||
|
[(page)]="resourceGraphMetadata.page"
|
||||||
|
(pageChange)="pageChange($event)"
|
||||||
|
[pageSize]="resourceGraphMetadata.page_size"
|
||||||
|
[collectionSize]="resourceGraphMetadata.total_elements"></ngb-pagination>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
|
||||||
<ng-template #emptyReport>
|
<ng-template #emptyReport>
|
||||||
|
|
|
@ -4,6 +4,7 @@ import {ResourceFhir} from '../../models/fasten/resource_fhir';
|
||||||
import { ModalDismissReasons, NgbModal } from '@ng-bootstrap/ng-bootstrap';
|
import { ModalDismissReasons, NgbModal } from '@ng-bootstrap/ng-bootstrap';
|
||||||
import {ReportMedicalHistoryEditorComponent} from '../../components/report-medical-history-editor/report-medical-history-editor.component';
|
import {ReportMedicalHistoryEditorComponent} from '../../components/report-medical-history-editor/report-medical-history-editor.component';
|
||||||
import {forkJoin} from 'rxjs';
|
import {forkJoin} from 'rxjs';
|
||||||
|
import {ResourceGraphMetadata, ResourceGraphResponse} from '../../models/fasten/resource-graph-response';
|
||||||
// import {ReportEditorRelatedComponent} from '../../components/report-editor-related/report-editor-related.component';
|
// import {ReportEditorRelatedComponent} from '../../components/report-editor-related/report-editor-related.component';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
|
@ -21,6 +22,12 @@ export class MedicalHistoryComponent implements OnInit {
|
||||||
unassigned_encounters: ResourceFhir[] = []
|
unassigned_encounters: ResourceFhir[] = []
|
||||||
resourceLookup: {[name: string]: ResourceFhir} = {}
|
resourceLookup: {[name: string]: ResourceFhir} = {}
|
||||||
|
|
||||||
|
resourceGraphMetadata: ResourceGraphMetadata = {
|
||||||
|
total_elements: 0,
|
||||||
|
page_size: 0,
|
||||||
|
page: 1
|
||||||
|
}
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private fastenApi: FastenApiService,
|
private fastenApi: FastenApiService,
|
||||||
private modalService: NgbModal
|
private modalService: NgbModal
|
||||||
|
@ -28,12 +35,21 @@ export class MedicalHistoryComponent implements OnInit {
|
||||||
|
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
|
//load the first page
|
||||||
|
this.pageChange(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
pageChange(page: number){
|
||||||
this.loading = true
|
this.loading = true
|
||||||
this.fastenApi.getResourceGraph().subscribe(results => {
|
this.fastenApi.getResourceGraph(null, page).subscribe((response: ResourceGraphResponse) => {
|
||||||
this.loading = false
|
this.loading = false
|
||||||
this.conditions = [].concat(results["Condition"] || [], results["Composition"] || [])
|
this.resourceGraphMetadata = response.metadata
|
||||||
this.unassigned_encounters = results["Encounter"] || []
|
//page counter is 1 indexed but the backend is 0 indexed
|
||||||
this.explanationOfBenefits = results["ExplanationOfBenefit"] || []
|
this.resourceGraphMetadata.page = this.resourceGraphMetadata.page + 1
|
||||||
|
|
||||||
|
this.conditions = [].concat(response.results["Condition"] || [], response.results["Composition"] || [])
|
||||||
|
this.unassigned_encounters = response.results["Encounter"] || []
|
||||||
|
this.explanationOfBenefits = response.results["ExplanationOfBenefit"] || []
|
||||||
|
|
||||||
//populate a lookup table with all resources
|
//populate a lookup table with all resources
|
||||||
for(let condition of this.conditions){
|
for(let condition of this.conditions){
|
||||||
|
@ -66,6 +82,7 @@ export class MedicalHistoryComponent implements OnInit {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
openEditorRelated(): void {
|
openEditorRelated(): void {
|
||||||
const modalRef = this.modalService.open(ReportMedicalHistoryEditorComponent, {
|
const modalRef = this.modalService.open(ReportMedicalHistoryEditorComponent, {
|
||||||
size: 'xl',
|
size: 'xl',
|
||||||
|
|
|
@ -22,6 +22,7 @@ import * as fhirpath from 'fhirpath';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import {DashboardConfig} from '../models/widget/dashboard-config';
|
import {DashboardConfig} from '../models/widget/dashboard-config';
|
||||||
import {DashboardWidgetQuery} from '../models/widget/dashboard-widget-query';
|
import {DashboardWidgetQuery} from '../models/widget/dashboard-widget-query';
|
||||||
|
import {ResourceGraphResponse} from '../models/fasten/resource-graph-response';
|
||||||
import { fetchEventSource } from '@microsoft/fetch-event-source';
|
import { fetchEventSource } from '@microsoft/fetch-event-source';
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
|
@ -178,16 +179,21 @@ export class FastenApiService {
|
||||||
return this._httpClient.post<any>(`${GetEndpointAbsolutePath(globalThis.location, environment.fasten_api_endpoint_base)}/secure/query`, query)
|
return this._httpClient.post<any>(`${GetEndpointAbsolutePath(globalThis.location, environment.fasten_api_endpoint_base)}/secure/query`, query)
|
||||||
}
|
}
|
||||||
|
|
||||||
getResourceGraph(graphType?: string): Observable<{[resourceType: string]: ResourceFhir[]}> {
|
getResourceGraph(graphType?: string, page?:number): Observable<ResourceGraphResponse> {
|
||||||
if(!graphType){
|
if(!graphType){
|
||||||
graphType = "MedicalHistory"
|
graphType = "MedicalHistory"
|
||||||
}
|
}
|
||||||
|
let queryParams = {}
|
||||||
|
if(page){
|
||||||
|
//the backend is 0 indexed, but the frontend is 1 indexed
|
||||||
|
queryParams["page"] = page - 1
|
||||||
|
}
|
||||||
|
|
||||||
return this._httpClient.get<any>(`${GetEndpointAbsolutePath(globalThis.location, environment.fasten_api_endpoint_base)}/secure/resource/graph/${graphType}`)
|
return this._httpClient.get<any>(`${GetEndpointAbsolutePath(globalThis.location, environment.fasten_api_endpoint_base)}/secure/resource/graph/${graphType}`, {params: queryParams})
|
||||||
.pipe(
|
.pipe(
|
||||||
map((response: ResponseWrapper) => {
|
map((response: ResponseWrapper) => {
|
||||||
console.log("RESPONSE", response)
|
console.log("RESPONSE", response)
|
||||||
return response.data as {[name: string]: ResourceFhir[]}
|
return response.data as ResourceGraphResponse
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue