adding source summary endpoint

added generic resource list component
added resource-list outlet directive and component.
This commit is contained in:
Jason Kulatunga 2022-09-17 00:12:12 -07:00
parent 48c8873f18
commit 7bced71569
39 changed files with 581 additions and 178 deletions

View File

@ -22,5 +22,6 @@ type DatabaseRepository interface {
CreateSource(context.Context, *models.Source) error CreateSource(context.Context, *models.Source) error
GetSource(context.Context, string) (*models.Source, error) GetSource(context.Context, string) (*models.Source, error)
GetSourceSummary(context.Context, string) (*models.SourceSummary, error)
GetSources(context.Context) ([]models.Source, error) GetSources(context.Context) ([]models.Source, error)
} }

View File

@ -231,6 +231,40 @@ func (sr *sqliteRepository) GetSource(ctx context.Context, sourceId string) (*mo
return &sourceCred, results.Error return &sourceCred, results.Error
} }
func (sr *sqliteRepository) GetSourceSummary(ctx context.Context, sourceId string) (*models.SourceSummary, error) {
sourceUUID, err := uuid.Parse(sourceId)
if err != nil {
return nil, err
}
sourceSummary := &models.SourceSummary{}
source, err := sr.GetSource(ctx, sourceId)
if err != nil {
return nil, err
}
sourceSummary.Source = source
//group by resource type and return counts
// SELECT source_resource_type as resource_type, COUNT(*) as count FROM resource_fhirs WHERE source_id = "53c1e930-63af-46c9-b760-8e83cbc1abd9" GROUP BY source_resource_type;
var results []map[string]interface{}
sr.gormClient.WithContext(ctx).
Model(models.ResourceFhir{}).
Select("source_id, source_resource_type as resource_type, count(*) as count").
Group("source_resource_type").
Where(models.OriginBase{
UserID: sr.GetCurrentUser(ctx).ID,
SourceID: sourceUUID,
}).
Scan(&results)
sourceSummary.ResourceTypeCounts = results
return sourceSummary, nil
}
func (sr *sqliteRepository) GetSources(ctx context.Context) ([]models.Source, error) { func (sr *sqliteRepository) GetSources(ctx context.Context) ([]models.Source, error) {
var sourceCreds []models.Source var sourceCreds []models.Source

View File

@ -0,0 +1,6 @@
package models
type SourceSummary struct {
Source *Source `json:"source,omitempty"`
ResourceTypeCounts []map[string]interface{} `json:"resource_type_counts,omitempty"`
}

View File

@ -118,6 +118,19 @@ func GetSource(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"success": true, "data": sourceCred}) c.JSON(http.StatusOK, gin.H{"success": true, "data": sourceCred})
} }
func GetSourceSummary(c *gin.Context) {
logger := c.MustGet("LOGGER").(*logrus.Entry)
databaseRepo := c.MustGet("REPOSITORY").(database.DatabaseRepository)
sourceSummary, err := databaseRepo.GetSourceSummary(c, c.Param("sourceId"))
if err != nil {
logger.Errorln("An error occurred while retrieving source summary", err)
c.JSON(http.StatusInternalServerError, gin.H{"success": false})
return
}
c.JSON(http.StatusOK, gin.H{"success": true, "data": sourceSummary})
}
func ListSource(c *gin.Context) { func ListSource(c *gin.Context) {
logger := c.MustGet("LOGGER").(*logrus.Entry) logger := c.MustGet("LOGGER").(*logrus.Entry)
databaseRepo := c.MustGet("REPOSITORY").(database.DatabaseRepository) databaseRepo := c.MustGet("REPOSITORY").(database.DatabaseRepository)

View File

@ -49,6 +49,7 @@ func (ae *AppEngine) Setup(logger *logrus.Entry) *gin.Engine {
secure.POST("/source/manual", handler.CreateManualSource) secure.POST("/source/manual", handler.CreateManualSource)
secure.GET("/source", handler.ListSource) secure.GET("/source", handler.ListSource)
secure.GET("/source/:sourceId", handler.GetSource) secure.GET("/source/:sourceId", handler.GetSource)
secure.GET("/source/:sourceId/summary", handler.GetSourceSummary)
//in debug mode, this endpoint lets us request data directly from the source api //in debug mode, this endpoint lets us request data directly from the source api
secure.GET("/source/raw/:sourceType/*path", handler.RawRequestSource) secure.GET("/source/raw/:sourceType/*path", handler.RawRequestSource)

View File

@ -23,6 +23,8 @@ import { CanActivateAuthGuard } from './services/can-activate.auth-guard';
import {FastenApiService} from './services/fasten-api.service'; import {FastenApiService} from './services/fasten-api.service';
import {Router} from '@angular/router'; import {Router} from '@angular/router';
import { SourceDetailComponent } from './pages/source-detail/source-detail.component'; import { SourceDetailComponent } from './pages/source-detail/source-detail.component';
import { ResourceListOutletDirective } from './directive/resource-list-outlet.directive';
import {ResourceListComponent} from './components/resource-list/resource-list.component';
@NgModule({ @NgModule({
declarations: [ declarations: [
AppComponent, AppComponent,

View File

@ -1,31 +0,0 @@
<p class="mg-b-20">A clinical condition, problem, diagnosis, or other event, situation, issue, or clinical concept that has risen to a level of concern.</p>
<div class="table-responsive">
<table class="table table-bordered mg-b-0">
<thead>
<tr>
<th>Category</th>
<th>Reason</th>
<th>Period</th>
<th>Status</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let careplan of careplanList">
<td>
<b>{{careplan.category}}</b>
</td>
<td class="align-middle">
<div *ngFor="let reason of careplan.reason">
{{reason[0]}}
<span> - </span>
<span className="text-muted">
{{reason[1] || "no data"}}
</span>
</div>
</td>
<td class="align-middle">{{careplan.period?.start}} - {{careplan.period?.end}}</td>
<td class="align-middle">{{careplan.status}}</td>
</tr>
</tbody>
</table>
</div>

View File

@ -1,25 +0,0 @@
import {Component, Input, OnInit} from '@angular/core';
import {ResourceFhir} from '../../models/fasten/resource_fhir';
import {CarePlan} from '../../models/display/care-plan';
@Component({
selector: 'app-list-care-plan',
templateUrl: './list-care-plan.component.html',
styleUrls: ['./list-care-plan.component.scss']
})
export class ListCarePlanComponent implements OnInit {
@Input() resourceList: ResourceFhir[] = []
careplanList: CarePlan[] = []
constructor() { }
ngOnInit(): void {
let _careplanList = this.careplanList
this.resourceList.forEach((resource) => {
let careplan = new CarePlan(resource.payload)
_careplanList.push(careplan)
})
}
}

View File

@ -1 +0,0 @@
<p>list-explanation-of-benefit works!</p>

View File

@ -1,23 +0,0 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ListExplanationOfBenefitComponent } from './list-explanation-of-benefit.component';
describe('ListExplanationOfBenefitComponent', () => {
let component: ListExplanationOfBenefitComponent;
let fixture: ComponentFixture<ListExplanationOfBenefitComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ ListExplanationOfBenefitComponent ]
})
.compileComponents();
fixture = TestBed.createComponent(ListExplanationOfBenefitComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -1,15 +0,0 @@
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-list-explanation-of-benefit',
templateUrl: './list-explanation-of-benefit.component.html',
styleUrls: ['./list-explanation-of-benefit.component.scss']
})
export class ListExplanationOfBenefitComponent implements OnInit {
constructor() { }
ngOnInit(): void {
}
}

View File

@ -0,0 +1,15 @@
import {Component} from '@angular/core';
import {GenericColumnDefn, ListGenericResourceComponent} from './list-generic-resource.component';
import {attributeXTime} from './utils';
@Component({
selector: 'app-list-adverse-event',
templateUrl: './list-generic-resource.component.html',
styleUrls: ['./list-generic-resource.component.scss']
})
export class ListAdverseEventComponent extends ListGenericResourceComponent {
columnDefinitions: GenericColumnDefn[] = [
{ title: 'Event', versions: '*', format: 'code', getter: a => a.event.coding[0] },
{ title: 'Date', versions: '*', format: 'date', getter: a => a.date }
]
}

View File

@ -0,0 +1,17 @@
import {Component} from '@angular/core';
import {GenericColumnDefn, ListGenericResourceComponent} from './list-generic-resource.component';
import {attributeXTime} from './utils';
@Component({
selector: 'app-list-allergy-intolerance',
templateUrl: './list-generic-resource.component.html',
styleUrls: ['./list-generic-resource.component.scss']
})
export class ListAllergyIntoleranceComponent extends ListGenericResourceComponent {
columnDefinitions: GenericColumnDefn[] = [
{ title: 'Allergy', versions: '*', format: 'code', getter: a => a.code.coding[0] },
{ title: 'Date Recorded', versions: '*', format: 'date', getter: a => a.assertedDate || a.recordedDate },
{ title: 'Onset', versions: '*', format: 'date', getter: a => a.onsetDateTime },
{ title: 'Resolution Age', versions: '*', format: 'date', getter: a => a.extension.resolutionAge }
]
}

View File

@ -0,0 +1,24 @@
import {Component} from '@angular/core';
import {GenericColumnDefn, ListGenericResourceComponent} from './list-generic-resource.component';
import {attributeXTime} from './utils';
import {getPath} from '../../fhir/utils';
@Component({
selector: 'app-list-care-plan',
templateUrl: './list-generic-resource.component.html',
styleUrls: ['./list-generic-resource.component.scss']
})
export class ListCarePlanComponent extends ListGenericResourceComponent {
columnDefinitions: GenericColumnDefn[] = [
{ title: 'Category', versions: '*', format: 'code', getter: c => c.category[0].coding[0] },
{ title: 'Reason', versions: '*', getter: c => {
return (c.activity || []).map((a, i) => {
let reason = getPath(a, "detail.code.coding.0.display") || ""
return reason ? [reason, getPath(a, "detail.status") || "no data"] : []
})
} },
{ title: 'Period', versions: '*', format: 'period', getter: c => c.period },
{ title: 'Status', versions: '*', getter: a => a.status },
]
}

View File

@ -0,0 +1,17 @@
import {Component} from '@angular/core';
import {GenericColumnDefn, ListGenericResourceComponent} from './list-generic-resource.component';
import {attributeXTime} from './utils';
@Component({
selector: 'app-list-communication',
templateUrl: './list-generic-resource.component.html',
styleUrls: ['./list-generic-resource.component.scss']
})
export class ListCommunicationComponent extends ListGenericResourceComponent {
columnDefinitions: GenericColumnDefn[] = [
{ title: 'Reason', versions: '*', format: 'code', getter: c => c.reasonCode[0].coding[0] },
{ title: 'Sent', versions: '*', format: 'date', getter: c => c.sent },
{ title: 'Received', versions: '*', format: 'date', getter: c => c.received },
{ title: 'Status', versions: '*', getter: c => c.status }
]
}

View File

@ -1,5 +1,6 @@
import {Component} from '@angular/core'; import {Component, OnInit} from '@angular/core';
import {GenericColumnDefn, ListGenericResourceComponent} from './list-generic-resource.component'; import {GenericColumnDefn, ListGenericResourceComponent, ResourceListComponentInterface} from './list-generic-resource.component';
import {FORMATTERS} from './utils';
@Component({ @Component({
selector: 'app-list-condition', selector: 'app-list-condition',

View File

@ -0,0 +1,15 @@
import {Component} from '@angular/core';
import {GenericColumnDefn, ListGenericResourceComponent} from './list-generic-resource.component';
import {attributeXTime} from './utils';
@Component({
selector: 'app-list-coverage',
templateUrl: './list-generic-resource.component.html',
styleUrls: ['./list-generic-resource.component.scss']
})
export class ListCoverageComponent extends ListGenericResourceComponent {
columnDefinitions: GenericColumnDefn[] = [
{ title: 'Type', versions: '*', format: 'code', getter: c => c.type.coding[0] },
{ title: 'Period', versions: '*', format: 'period', getter: c => c.period }
]
}

View File

@ -0,0 +1,18 @@
import {Component} from '@angular/core';
import {GenericColumnDefn, ListGenericResourceComponent} from './list-generic-resource.component';
import {attributeXTime} from './utils';
@Component({
selector: 'app-list-device-request',
templateUrl: './list-generic-resource.component.html',
styleUrls: ['./list-generic-resource.component.scss']
})
export class ListDeviceRequestComponent extends ListGenericResourceComponent {
columnDefinitions: GenericColumnDefn[] = [
{ title: 'Device', versions: '*', format: 'code', getter: d => d.codeCodeableConcept.coding[0] },
{ title: 'Author Date', versions: '*', format: 'date', getter: d => d.authoredOn },
{ title: 'Do Not Perform', versions: '*', getter: d => d.modifierExtension.doNotPerform },
{ title: 'Do Not Perform Reason', versions: '*', format: 'code', getter: s => s.extension.doNotPerformReason.coding[0] }
]
}

View File

@ -0,0 +1,16 @@
import {Component} from '@angular/core';
import {GenericColumnDefn, ListGenericResourceComponent} from './list-generic-resource.component';
import {attributeXTime} from './utils';
@Component({
selector: 'app-list-document-reference',
templateUrl: './list-generic-resource.component.html',
styleUrls: ['./list-generic-resource.component.scss']
})
export class ListDocumentReferenceComponent extends ListGenericResourceComponent {
columnDefinitions: GenericColumnDefn[] = [
{ title: 'Date', versions: '*', format: 'date', getter: d => d.date },
{ title: 'Content', versions: '*', getter: d => atob(d.content[0].attachment.data) }
]
}

View File

@ -1,12 +1,12 @@
import {Component} from '@angular/core'; import {Component, OnChanges, OnInit} from '@angular/core';
import {GenericColumnDefn, ListGenericResourceComponent} from './list-generic-resource.component'; import {GenericColumnDefn, ListGenericResourceComponent, ResourceListComponentInterface} from './list-generic-resource.component';
@Component({ @Component({
selector: 'app-list-encounter', selector: 'app-list-encounter',
templateUrl: './list-generic-resource.component.html', templateUrl: './list-generic-resource.component.html',
styleUrls: ['./list-generic-resource.component.scss'] styleUrls: ['./list-generic-resource.component.scss']
}) })
export class ListEncounterComponent extends ListGenericResourceComponent { export class ListEncounterComponent extends ListGenericResourceComponent {
columnDefinitions: GenericColumnDefn[] = [ columnDefinitions: GenericColumnDefn[] = [
{ title: 'Encounter', versions: '*', format: 'code', getter: e => e.type[0].coding[0] }, { title: 'Encounter', versions: '*', format: 'code', getter: e => e.type[0].coding[0] },
{ title: 'Period', versions: '*', format: 'period', getter: e => e.period }, { title: 'Period', versions: '*', format: 'period', getter: e => e.period },

View File

@ -0,0 +1,11 @@
import {Component, OnChanges, OnInit} from '@angular/core';
import {GenericColumnDefn, ListGenericResourceComponent, ResourceListComponentInterface} from './list-generic-resource.component';
@Component({
selector: 'app-list-explanation-of-benefit',
templateUrl: './list-generic-resource.component.html',
styleUrls: ['./list-generic-resource.component.scss']
})
export class ListExplanationOfBenefitComponent extends ListGenericResourceComponent {
columnDefinitions: GenericColumnDefn[] = []
}

View File

@ -1,4 +1,6 @@
<div> <div>
<h3>INSIDE THE GERNERIC COMPONENT</h3>
<ngx-datatable <ngx-datatable
#table #table
class="bootstrap" class="bootstrap"

View File

@ -1,9 +1,17 @@
import {Component, Input, OnInit, ViewChild} from '@angular/core'; import {ChangeDetectorRef, Component, Input, OnChanges, OnInit, SimpleChanges, ViewChild} from '@angular/core';
import {DatatableComponent, ColumnMode} from '@swimlane/ngx-datatable'; import {DatatableComponent, ColumnMode} from '@swimlane/ngx-datatable';
import {ResourceFhir} from '../../models/fasten/resource_fhir'; import {ResourceFhir} from '../../models/fasten/resource_fhir';
import {FORMATTERS, getPath, obsValue, attributeXTime} from './utils'; import {FORMATTERS, getPath, obsValue, attributeXTime} from './utils';
import {observableToBeFn} from 'rxjs/internal/testing/TestScheduler'; import {observableToBeFn} from 'rxjs/internal/testing/TestScheduler';
//all Resource list components must implement this Interface
export interface ResourceListComponentInterface {
resourceList: ResourceFhir[];
markForCheck()
}
export class GenericColumnDefn { export class GenericColumnDefn {
title: string title: string
prop?: string prop?: string
@ -18,7 +26,7 @@ export class GenericColumnDefn {
templateUrl: './list-generic-resource.component.html', templateUrl: './list-generic-resource.component.html',
styleUrls: ['./list-generic-resource.component.scss'] styleUrls: ['./list-generic-resource.component.scss']
}) })
export class ListGenericResourceComponent implements OnInit { export class ListGenericResourceComponent implements OnInit, ResourceListComponentInterface {
@Input() resourceList: ResourceFhir[] = [] @Input() resourceList: ResourceFhir[] = []
@ -32,9 +40,19 @@ export class ListGenericResourceComponent implements OnInit {
columns = []; //{ prop: 'name' }, { name: 'Company' }, { name: 'Gender' } columns = []; //{ prop: 'name' }, { name: 'Company' }, { name: 'Gender' }
ColumnMode = ColumnMode; ColumnMode = ColumnMode;
constructor() {} constructor(public changeRef: ChangeDetectorRef) {}
ngOnInit(): void { ngOnInit(): void {
console.log("INIT CHILD")
this.renderList()
}
markForCheck(){
this.changeRef.markForCheck()
}
renderList(){
console.log("GENERIC RESOURCE INIT")
this.columns = this.columnDefinitions.map((defn) => { this.columns = this.columnDefinitions.map((defn) => {
let column = {name: defn.title, prop: defn.title.replace(/[^A-Z0-9]/ig, "_")} let column = {name: defn.title, prop: defn.title.replace(/[^A-Z0-9]/ig, "_")}
return column return column
@ -44,10 +62,13 @@ export class ListGenericResourceComponent implements OnInit {
let row = {} let row = {}
this.columnDefinitions.forEach((defn) => { this.columnDefinitions.forEach((defn) => {
let resourceProp = defn.getter(resource.payload) try{
let resourceFormatted = defn.format ? FORMATTERS[defn.format](resourceProp) : resourceProp let resourceProp = defn.getter(resource.payload)
row[defn.title.replace(/[^A-Z0-9]/ig, "_")] = resourceFormatted let resourceFormatted = defn.format ? FORMATTERS[defn.format](resourceProp) : resourceProp
//TODO: handle defaultValue row[defn.title.replace(/[^A-Z0-9]/ig, "_")] = resourceFormatted
}catch (e){
//ignore
}
}) })
console.log("ROW:", row) console.log("ROW:", row)
@ -55,6 +76,7 @@ export class ListGenericResourceComponent implements OnInit {
return row return row
}) })
} }
} }
/////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////

View File

@ -0,0 +1,19 @@
import {Component} from '@angular/core';
import {GenericColumnDefn, ListGenericResourceComponent} from './list-generic-resource.component';
import {attributeXTime} from './utils';
@Component({
selector: 'app-list-medication-administration',
templateUrl: './list-generic-resource.component.html',
styleUrls: ['./list-generic-resource.component.scss']
})
export class ListMedicationAdministrationComponent extends ListGenericResourceComponent {
columnDefinitions: GenericColumnDefn[] = [
{ title: 'Medication', versions: '*', format: 'code', getter: m => m.medicationCodeableConcept.coding[0] },
{ title: 'Route', versions: '*', format: 'code', getter: m => m.dosage.route.coding[0] },
{ title: 'Effective', versions: '*', getter: m => attributeXTime(m,'effective')},
{ title: 'Status', versions: '*', getter: m => m.status},
{ title: 'Status Reason', versions: '*', format: 'code', getter: m => m.statusReason[0].coding[0] },
{ title: 'Recorded', versions: '*', format: 'date', getter: m => m.extension.recorded }
]
}

View File

@ -0,0 +1,15 @@
import {Component} from '@angular/core';
import {GenericColumnDefn, ListGenericResourceComponent} from './list-generic-resource.component';
import {attributeXTime} from './utils';
@Component({
selector: 'app-list-medication-dispense',
templateUrl: './list-generic-resource.component.html',
styleUrls: ['./list-generic-resource.component.scss']
})
export class ListMedicationDispenseComponent extends ListGenericResourceComponent {
columnDefinitions: GenericColumnDefn[] = [
{ title: 'Medication', versions: '*', format: 'code', getter: m => m.medicationCodeableConcept.coding[0] },
{ title: 'Handed Over Date', versions: '*', format: 'date', getter: m => m.whenHandedOver}
]
}

View File

@ -0,0 +1,17 @@
import {Component} from '@angular/core';
import {GenericColumnDefn, ListGenericResourceComponent} from './list-generic-resource.component';
import {attributeXTime} from './utils';
@Component({
selector: 'app-list-nutrition-order',
templateUrl: './list-generic-resource.component.html',
styleUrls: ['./list-generic-resource.component.scss']
})
export class ListNutritionOrderComponent extends ListGenericResourceComponent {
columnDefinitions: GenericColumnDefn[] = [
{ title: 'Preference', versions: '*', format: 'code', getter: n => n.foodPreferenceModifier[0].coding[0] },
{ title: 'Exclusion', versions: '*', format: 'code', getter: n => n.excludeFoodModifier[0].coding[0] },
{ title: 'Date', versions: '*', format: 'date', getter: n => n.dateTime },
{ title: 'Status', versions: '*', getter: n => n.status }
]
}

View File

@ -0,0 +1,20 @@
import {Component} from '@angular/core';
import {GenericColumnDefn, ListGenericResourceComponent} from './list-generic-resource.component';
import {attributeXTime} from './utils';
@Component({
selector: 'app-list-service-request',
templateUrl: './list-generic-resource.component.html',
styleUrls: ['./list-generic-resource.component.scss']
})
export class ListServiceRequestComponent extends ListGenericResourceComponent {
columnDefinitions: GenericColumnDefn[] = [
{ title: 'Service', versions: '*', format: 'code', getter: s => s.code.coding[0] },
{ title: 'Author Date', versions: '*', format: 'date', getter: s => s.authoredOn },
{ title: 'Status', versions: '*', getter: s => s.status },
{ title: 'Reason', versions: '*', format: 'code', getter: s => s.reasonCode[0].coding[0] },
{ title: 'ID', versions: '*', getter: s => s.id },
{ title: 'Do Not Perform', versions: '*', getter: s => s.doNotPerform },
{ title: 'Reason Refused', versions: '*', format: 'code', getter: s => s.extension.reasonRefused.coding[0] }
]
}

View File

@ -0,0 +1,3 @@
<h2 class="az-content-title mg-t-40">{{resourceListType}}</h2>
<ng-template resourceListOutlet></ng-template>

View File

@ -1,18 +1,18 @@
import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ListCarePlanComponent } from './list-care-plan.component'; import { ResourceListComponent } from './resource-list.component';
describe('ListCarePlanComponent', () => { describe('ResourceListComponent', () => {
let component: ListCarePlanComponent; let component: ResourceListComponent;
let fixture: ComponentFixture<ListCarePlanComponent>; let fixture: ComponentFixture<ResourceListComponent>;
beforeEach(async () => { beforeEach(async () => {
await TestBed.configureTestingModule({ await TestBed.configureTestingModule({
declarations: [ ListCarePlanComponent ] declarations: [ ResourceListComponent ]
}) })
.compileComponents(); .compileComponents();
fixture = TestBed.createComponent(ListCarePlanComponent); fixture = TestBed.createComponent(ResourceListComponent);
component = fixture.componentInstance; component = fixture.componentInstance;
fixture.detectChanges(); fixture.detectChanges();
}); });

View File

@ -0,0 +1,152 @@
import {ChangeDetectionStrategy, Component, Input, OnChanges, OnInit, SimpleChanges, Type, ViewChild} from '@angular/core';
import {ResourceListOutletDirective} from '../../directive/resource-list-outlet.directive';
import {FastenApiService} from '../../services/fasten-api.service';
import {Source} from '../../models/fasten/source';
import {Observable, of} from 'rxjs';
import {ResourceFhir} from '../../models/fasten/resource_fhir';
import {ListAdverseEventComponent} from '../list-generic-resource/list-adverse-event.component';
import {ListCommunicationComponent} from '../list-generic-resource/list-communication.component';
import {ListConditionComponent} from '../list-generic-resource/list-condition.component';
import {ListCoverageComponent} from '../list-generic-resource/list-coverage.component';
import {ListDeviceRequestComponent} from '../list-generic-resource/list-device-request.component';
import {ListDocumentReferenceComponent} from '../list-generic-resource/list-document-reference.component';
import {ListEncounterComponent} from '../list-generic-resource/list-encounter.component';
import {ListImmunizationComponent} from '../list-generic-resource/list-immunization.component';
import {ListMedicationComponent} from '../list-generic-resource/list-medication.component';
import {ListMedicationAdministrationComponent} from '../list-generic-resource/list-medication-administration.component';
import {ListMedicationDispenseComponent} from '../list-generic-resource/list-medication-dispense.component';
import {ListMedicationRequestComponent} from '../list-generic-resource/list-medication-request.component';
import {ListNutritionOrderComponent} from '../list-generic-resource/list-nutrition-order.component';
import {ListObservationComponent} from '../list-generic-resource/list-observation.component';
import {ListProcedureComponent} from '../list-generic-resource/list-procedure.component';
import {ListServiceRequestComponent} from '../list-generic-resource/list-service-request.component';
import {map} from 'rxjs/operators';
import {ResponseWrapper} from '../../models/response-wrapper';
import {ListGenericResourceComponent, ResourceListComponentInterface} from '../list-generic-resource/list-generic-resource.component';
import {ListCarePlanComponent} from '../list-generic-resource/list-care-plan.component';
import {ListAllergyIntoleranceComponent} from '../list-generic-resource/list-allergy-intolerance.component';
@Component({
selector: 'source-resource-list',
changeDetection: ChangeDetectionStrategy.OnPush,
templateUrl: './resource-list.component.html',
styleUrls: ['./resource-list.component.scss']
})
export class ResourceListComponent implements OnInit, OnChanges {
@Input() source: Source;
@Input() resourceListType: string;
resourceListCache: { [name:string]: ResourceFhir[] } = {}
//location to dynamically load the resource list
@ViewChild(ResourceListOutletDirective, {static: true}) resourceListOutlet!: ResourceListOutletDirective;
constructor(private fastenApi: FastenApiService) { }
ngOnInit(): void {
this.loadComponent()
}
ngOnChanges(changes: SimpleChanges) {
this.loadComponent()
}
loadComponent() {
//clear the current outlet
const viewContainerRef = this.resourceListOutlet.viewContainerRef;
viewContainerRef.clear();
this.getResources().subscribe((resourceList) => {
let componentType = this.typeLookup(this.resourceListType)
if(componentType != null){
console.log("Attempting to create component", this.resourceListType, componentType)
const componentRef = viewContainerRef.createComponent<ResourceListComponentInterface>(componentType);
componentRef.instance.resourceList = resourceList;
componentRef.instance.markForCheck()
}
})
}
getResources(): Observable<ResourceFhir[]>{
if(!this.resourceListCache[this.resourceListType]){
// this resource type list has not been downloaded yet, do so now
return this.fastenApi.getResources(this.resourceListType, this.source.id)
.pipe(map((resourceList: ResourceFhir[]) => {
//cache this response so we can skip the request next time
this.resourceListCache[this.resourceListType] = resourceList
return resourceList
}))
} else {
return of(this.resourceListCache[this.resourceListType])
}
}
typeLookup(resourceType: string): Type<any> {
switch(resourceType) {
case "AllergyIntolerance": {
return ListAllergyIntoleranceComponent;
}
case "AdverseEvent": {
return ListAdverseEventComponent;
}
case "CarePlan": {
return ListCarePlanComponent;
}
case "Communication": {
return ListCommunicationComponent;
}
case "Condition": {
return ListConditionComponent;
}
case "Coverage": {
return ListCoverageComponent;
}
case "DeviceRequest": {
return ListDeviceRequestComponent;
}
case "DocumentReference": {
return ListDocumentReferenceComponent;
}
case "Encounter": {
return ListEncounterComponent;
}
case "Immunization": {
return ListImmunizationComponent;
}
case "Medication": {
return ListMedicationComponent;
}
case "MedicationAdministration": {
return ListMedicationAdministrationComponent;
}
case "MedicationDispense": {
return ListMedicationDispenseComponent;
}
case "MedicationRequest": {
return ListMedicationRequestComponent;
}
case "NutritionOrder": {
return ListNutritionOrderComponent;
}
case "Observation": {
return ListObservationComponent;
}
case "Procedure": {
return ListProcedureComponent;
}
case "ServiceRequest": {
return ListServiceRequestComponent;
}
default: {
console.error("UNKNOWN COMPONENT TYPE", resourceType)
return null
}
}
}
}

View File

@ -1,58 +1,93 @@
import { NgModule } from '@angular/core';
import { ComponentsSidebarComponent } from './components-sidebar/components-sidebar.component'; import { ComponentsSidebarComponent } from './components-sidebar/components-sidebar.component';
import { ListExplanationOfBenefitComponent } from './list-explanation-of-benefit/list-explanation-of-benefit.component';
import { ListGenericResourceComponent,} from './list-generic-resource/list-generic-resource.component';
import { ListPatientComponent } from './list-patient/list-patient.component';
import { NgModule } from '@angular/core';
import { NgxDatatableModule } from '@swimlane/ngx-datatable';
import { RouterModule } from '@angular/router'; import { RouterModule } from '@angular/router';
import { UtilitiesSidebarComponent } from './utilities-sidebar/utilities-sidebar.component'; import { UtilitiesSidebarComponent } from './utilities-sidebar/utilities-sidebar.component';
import { ListPatientComponent } from './list-patient/list-patient.component';
import { ListExplanationOfBenefitComponent } from './list-explanation-of-benefit/list-explanation-of-benefit.component';
import { ListCarePlanComponent } from './list-care-plan/list-care-plan.component';
import {BrowserModule} from '@angular/platform-browser'; import {BrowserModule} from '@angular/platform-browser';
import { ListGenericResourceComponent,} from './list-generic-resource/list-generic-resource.component'; import {ListAdverseEventComponent} from './list-generic-resource/list-adverse-event.component';
import {ListConditionComponent} from './list-generic-resource/list-condition.component' import {ListConditionComponent} from './list-generic-resource/list-condition.component'
import {ListEncounterComponent} from './list-generic-resource/list-encounter.component' import {ListEncounterComponent} from './list-generic-resource/list-encounter.component'
import {ListImmunizationComponent} from './list-generic-resource/list-immunization.component'
import {ListMedicationAdministrationComponent} from './list-generic-resource/list-medication-administration.component';
import {ListMedicationComponent} from './list-generic-resource/list-medication.component' import {ListMedicationComponent} from './list-generic-resource/list-medication.component'
import {ListMedicationDispenseComponent} from './list-generic-resource/list-medication-dispense.component';
import {ListMedicationRequestComponent} from './list-generic-resource/list-medication-request.component'
import {ListNutritionOrderComponent} from './list-generic-resource/list-nutrition-order.component';
import {ListObservationComponent} from './list-generic-resource/list-observation.component' import {ListObservationComponent} from './list-generic-resource/list-observation.component'
import {ListProcedureComponent} from './list-generic-resource/list-procedure.component' import {ListProcedureComponent} from './list-generic-resource/list-procedure.component'
import {ListImmunizationComponent} from './list-generic-resource/list-immunization.component' import {ListCommunicationComponent} from './list-generic-resource/list-communication.component';
import {ListMedicationRequestComponent} from './list-generic-resource/list-medication-request.component' import {ListDeviceRequestComponent} from './list-generic-resource/list-device-request.component';
import {ListCoverageComponent} from './list-generic-resource/list-coverage.component';
import { NgxDatatableModule } from '@swimlane/ngx-datatable'; import {ListServiceRequestComponent} from './list-generic-resource/list-service-request.component';
import {ListDocumentReferenceComponent} from './list-generic-resource/list-document-reference.component';
import { ResourceListComponent } from './resource-list/resource-list.component';
import {ResourceListOutletDirective} from '../directive/resource-list-outlet.directive';
import {ListCarePlanComponent} from './list-generic-resource/list-care-plan.component';
import {ListAllergyIntoleranceComponent} from './list-generic-resource/list-allergy-intolerance.component';
@NgModule({ @NgModule({
imports: [ imports: [
RouterModule, RouterModule,
BrowserModule, BrowserModule,
NgxDatatableModule NgxDatatableModule,
], ],
declarations: [ declarations: [
ComponentsSidebarComponent, ComponentsSidebarComponent,
UtilitiesSidebarComponent, UtilitiesSidebarComponent,
ListPatientComponent, ListAllergyIntoleranceComponent,
ListObservationComponent, ListAdverseEventComponent,
ListExplanationOfBenefitComponent,
ListImmunizationComponent,
ListEncounterComponent,
ListCarePlanComponent, ListCarePlanComponent,
ListGenericResourceComponent, ListCommunicationComponent,
ListMedicationComponent,
ListProcedureComponent,
ListConditionComponent, ListConditionComponent,
ListMedicationRequestComponent ListEncounterComponent,
ListExplanationOfBenefitComponent,
ListGenericResourceComponent,
ListImmunizationComponent,
ListMedicationAdministrationComponent,
ListMedicationComponent,
ListMedicationDispenseComponent,
ListMedicationRequestComponent,
ListNutritionOrderComponent,
ListObservationComponent,
ListPatientComponent,
ListDeviceRequestComponent,
ListProcedureComponent,
ListCoverageComponent,
ListServiceRequestComponent,
ListDocumentReferenceComponent,
ResourceListComponent,
ResourceListOutletDirective,
], ],
exports: [ exports: [
ComponentsSidebarComponent, ComponentsSidebarComponent,
UtilitiesSidebarComponent, ListAllergyIntoleranceComponent,
ListPatientComponent, ListAdverseEventComponent,
ListExplanationOfBenefitComponent, ListCarePlanComponent,
ListImmunizationComponent, ListCommunicationComponent,
ListEncounterComponent, ListConditionComponent,
ListCarePlanComponent, ListEncounterComponent,
ListGenericResourceComponent, ListExplanationOfBenefitComponent,
ListMedicationComponent, ListGenericResourceComponent,
ListObservationComponent, ListImmunizationComponent,
ListProcedureComponent, ListMedicationAdministrationComponent,
ListConditionComponent, ListMedicationComponent,
ListMedicationRequestComponent ListMedicationDispenseComponent,
ListMedicationRequestComponent,
ListNutritionOrderComponent,
ListObservationComponent,
ListPatientComponent,
ListProcedureComponent,
ListDeviceRequestComponent,
UtilitiesSidebarComponent,
ListCoverageComponent,
ListServiceRequestComponent,
ListDocumentReferenceComponent,
ResourceListComponent
] ]
}) })

View File

@ -0,0 +1,8 @@
import { ResourceListOutletDirective } from './resource-list-outlet.directive';
describe('ResourceListOutletDirective', () => {
it('should create an instance', () => {
const directive = new ResourceListOutletDirective();
expect(directive).toBeTruthy();
});
});

View File

@ -0,0 +1,10 @@
import {Directive, ViewContainerRef} from '@angular/core';
@Directive({
selector: '[resourceListOutlet]'
})
export class ResourceListOutletDirective {
constructor(public viewContainerRef: ViewContainerRef) { }
}

View File

@ -0,0 +1,12 @@
import {Source} from './source';
export class ResourceTypeCounts {
count: number
source_id: string
resource_type: string
}
export class SourceSummary {
source: Source
resource_type_counts: ResourceTypeCounts[]
}

View File

@ -2,8 +2,8 @@
<div class="container"> <div class="container">
<div class="az-content-left"> <div class="az-content-left">
<ul class="list-group"> <ul class="list-group">
<li *ngFor="let resourceGroup of resourcesGroupedByType | keyvalue" (click)="selectResourceType(resourceGroup.key)" class="list-group-item d-flex justify-content-between align-items-center"> <li *ngFor="let resourceGroup of resourceTypeCounts | keyvalue" (click)="selectResourceType(resourceGroup.key)" class="list-group-item d-flex justify-content-between align-items-center">
{{resourceGroup.key}} <span class="badge badge-primary badge-pill">{{resourceGroup.value.length}}</span> {{resourceGroup.key}} <span class="badge badge-primary badge-pill">{{resourceGroup.value}}</span>
</li> </li>
</ul> </ul>
</div> </div>
@ -14,16 +14,7 @@
<span>Details</span> <span>Details</span>
</div> </div>
<h2 class="az-content-title mg-t-40">{{selectedResourceType}}</h2> <source-resource-list [source]="selectedSource" [resourceListType]="selectedResourceType"></source-resource-list>
<app-list-condition *ngIf="selectedResourceType == 'Condition'" [resourceList]="selectedResources"></app-list-condition>
<app-list-care-plan *ngIf="selectedResourceType == 'CarePlan'" [resourceList]="selectedResources"></app-list-care-plan>
<app-list-encounter *ngIf="selectedResourceType == 'Encounter'" [resourceList]="selectedResources"></app-list-encounter>
<app-list-immunization *ngIf="selectedResourceType == 'Immunization'" [resourceList]="selectedResources"></app-list-immunization>
<app-list-medication *ngIf="selectedResourceType == 'Medication'" [resourceList]="selectedResources"></app-list-medication>
<app-list-medication-request *ngIf="selectedResourceType == 'MedicationRequest'" [resourceList]="selectedResources"></app-list-medication-request>
<app-list-observation *ngIf="selectedResourceType == 'Observation'" [resourceList]="selectedResources"></app-list-observation>
<app-list-procedure *ngIf="selectedResourceType == 'Procedure'" [resourceList]="selectedResources"></app-list-procedure>
</div><!-- az-content-body --> </div><!-- az-content-body -->
</div><!-- container --> </div><!-- container -->

View File

@ -1,7 +1,8 @@
import { Component, OnInit } from '@angular/core'; import {Component, OnInit, ViewChild} from '@angular/core';
import {ActivatedRoute, Router} from '@angular/router'; import {ActivatedRoute, Router} from '@angular/router';
import {Source} from '../../models/fasten/source'; import {Source} from '../../models/fasten/source';
import {FastenApiService} from '../../services/fasten-api.service'; import {FastenApiService} from '../../services/fasten-api.service';
import {ResourceListOutletDirective} from '../../directive/resource-list-outlet.directive';
@Component({ @Component({
@ -13,9 +14,8 @@ export class SourceDetailComponent implements OnInit {
selectedSource: Source = null selectedSource: Source = null
selectedResourceType: string = null selectedResourceType: string = null
selectedResources: any[] = []
resourcesGroupedByType: { [name: string]: any[] } = {} resourceTypeCounts: { [name: string]: number } = {}
constructor(private fastenApi: FastenApiService, private router: Router, private route: ActivatedRoute) { constructor(private fastenApi: FastenApiService, private router: Router, private route: ActivatedRoute) {
//check if the current Source was sent over using the router state storage: //check if the current Source was sent over using the router state storage:
@ -25,26 +25,16 @@ export class SourceDetailComponent implements OnInit {
} }
ngOnInit(): void { ngOnInit(): void {
if (this.selectedSource == null) { //always request the source summary
//lookup the source by ID. this.fastenApi.getSourceSummary(this.route.snapshot.paramMap.get('source_id')).subscribe((sourceSummary) => {
this.fastenApi.getSource(this.route.snapshot.paramMap.get('source_id')).subscribe((source) => { this.selectedSource = sourceSummary.source;
this.selectedSource = source; for(let resourceTypeCount of sourceSummary.resource_type_counts){
}); this.resourceTypeCounts[resourceTypeCount.resource_type] = resourceTypeCount.count
} }
});
this.fastenApi.getResources(undefined, this.route.snapshot.paramMap.get('source_id')).subscribe((resourceList) => {
console.log("RESOURCES", resourceList)
let _resourcesGroupedByType = this.resourcesGroupedByType
resourceList.forEach(resource => {
let resourceTypeList = _resourcesGroupedByType[resource.source_resource_type] || [];
resourceTypeList.push(resource)
_resourcesGroupedByType[resource.source_resource_type] = resourceTypeList
})
})
} }
selectResourceType(resourceType: string) { selectResourceType(resourceType: string) {
this.selectedResourceType = resourceType this.selectedResourceType = resourceType
this.selectedResources = this.resourcesGroupedByType[resourceType]
} }
} }

View File

@ -9,6 +9,7 @@ import {ResponseWrapper} from '../models/response-wrapper';
import {Source} from '../models/fasten/source'; import {Source} from '../models/fasten/source';
import {User} from '../models/fasten/user'; import {User} from '../models/fasten/user';
import {ResourceFhir} from '../models/fasten/resource_fhir'; import {ResourceFhir} from '../models/fasten/resource_fhir';
import {SourceSummary} from '../models/fasten/source-summary';
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
@ -110,6 +111,16 @@ export class FastenApiService {
); );
} }
getSourceSummary(sourceId: string): Observable<SourceSummary> {
return this._httpClient.get<any>(`${this.getBasePath()}/api/secure/source/${sourceId}/summary`)
.pipe(
map((response: ResponseWrapper) => {
console.log("SOURCE RESPONSE", response)
return response.data as SourceSummary
})
);
}
getResources(sourceResourceType?: string, sourceID?: string): Observable<ResourceFhir[]> { getResources(sourceResourceType?: string, sourceID?: string): Observable<ResourceFhir[]> {
let queryParams = {} let queryParams = {}
if(sourceResourceType){ if(sourceResourceType){