adding pagination to the explore page. fixes #216

simplified fallback component for table list
This commit is contained in:
Jason Kulatunga 2023-08-11 23:44:34 -06:00
parent 83114f0067
commit 410696c26f
17 changed files with 146 additions and 108 deletions

View File

@ -3,6 +3,8 @@ package pkg
type ResourceGraphType string
const (
ResourceListPageSize int = 20
ContextKeyTypeConfig string = "CONFIG"
ContextKeyTypeDatabase string = "REPOSITORY"
ContextKeyTypeLogger string = "LOGGER"

View File

@ -400,13 +400,18 @@ func (sr *SqliteRepository) ListResources(ctx context.Context, queryOptions mode
if err != nil {
return nil, err
}
results := queryBuilder.
queryBuilder = queryBuilder.
Where(queryParam).
Table(tableName).
Find(&wrappedResourceModels)
Table(tableName)
return wrappedResourceModels, results.Error
if queryOptions.Limit > 0 {
queryBuilder = queryBuilder.Limit(queryOptions.Limit).Offset(queryOptions.Offset)
}
return wrappedResourceModels, queryBuilder.Find(&wrappedResourceModels).Error
} else {
if queryOptions.Limit > 0 {
queryBuilder = queryBuilder.Limit(queryOptions.Limit).Offset(queryOptions.Offset)
}
//there is no FHIR Resource name specified, so we're querying across all FHIR resources
return sr.getResourcesFromAllTables(queryBuilder, queryParam)
}

View File

@ -17,4 +17,8 @@ type ListResourceQueryOptions struct {
SourceID string
SourceResourceType string
SourceResourceID string
//pagination
Limit int
Offset int
}

View File

@ -8,6 +8,7 @@ import (
"github.com/gin-gonic/gin"
"github.com/sirupsen/logrus"
"net/http"
"strconv"
"strings"
)
@ -48,7 +49,16 @@ func ListResourceFhir(c *gin.Context) {
if len(c.Query("sourceResourceID")) > 0 {
listResourceQueryOptions.SourceResourceID = c.Query("sourceResourceID")
}
if len(c.Query("page")) > 0 {
listResourceQueryOptions.Limit = pkg.ResourceListPageSize //hardcoded number of resources per page
pageNumb, err := strconv.Atoi(c.Query("page"))
if err != nil {
logger.Errorln("An error occurred while calculating page number", err)
c.JSON(http.StatusInternalServerError, gin.H{"success": false})
return
}
listResourceQueryOptions.Offset = pageNumb * listResourceQueryOptions.Limit
}
wrappedResourceModels, err := databaseRepo.ListResources(c, listResourceQueryOptions)
if c.Query("sortBy") == "title" {

View File

@ -1,14 +0,0 @@
<div class="alert alert-warning" role="alert">
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
<strong>Warning!</strong> Fasten does not know how to display this resource type (yet). Click an item in the list below to see the raw data
</div>
<ul class="list-group">
<li class="list-group-item" *ngFor="let resource of resourceList">
<a routerLink="/explore/{{resource.source_id}}/resource/{{resource.source_resource_id}}">{{resource.source_resource_id}}</a>
</li>
</ul>

View File

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

View File

@ -1,25 +0,0 @@
import {ChangeDetectorRef, Component, Input, OnInit} from '@angular/core';
import {ResourceFhir} from '../../models/fasten/resource_fhir';
import {ResourceListComponentInterface} from '../list-generic-resource/list-generic-resource.component';
import {Router} from '@angular/router';
@Component({
selector: 'app-list-fallback-resource',
templateUrl: './list-fallback-resource.component.html',
styleUrls: ['./list-fallback-resource.component.scss']
})
export class ListFallbackResourceComponent implements OnInit, ResourceListComponentInterface {
@Input() resourceList: ResourceFhir[] = []
constructor(public changeRef: ChangeDetectorRef, public router: Router) {}
ngOnInit(): void {
console.log("RESOURCE LIST INSIDE FALLBACK", this.resourceList)
}
markForCheck(){
this.changeRef.markForCheck()
}
}

View File

@ -0,0 +1,28 @@
<div>
<div class="alert alert-warning" role="alert">
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
<strong>Warning!</strong> Fasten does not know how to display this resource type (yet). Click an item in the list below to see the raw data
</div>
<ngx-datatable
#table
class="bootstrap"
[columns]="columns"
[columnMode]="ColumnMode.force"
[headerHeight]="50"
[footerHeight]="50"
rowHeight="auto"
[externalPaging]="true"
[count]="totalElements"
[offset]="currentPage.offset"
[limit]="20"
[rows]="rows"
[selectionType]="SelectionType.single"
(select)="onSelect($event)"
(page)="changePage($event)"
>
</ngx-datatable>
</div>

View File

@ -0,0 +1,14 @@
import {Component, OnChanges, OnInit} from '@angular/core';
import {GenericColumnDefn, ListGenericResourceComponent, ResourceListComponentInterface} from './list-generic-resource.component';
@Component({
selector: 'app-list-fallback',
templateUrl: './list-fallback.component.html',
styleUrls: ['./list-generic-resource.component.scss']
})
export class ListFallbackComponent extends ListGenericResourceComponent {
columnDefinitions: GenericColumnDefn[] = [
{ title: 'Id', versions: '*', getter: e => e.id },
{ title: 'Title', versions: '*', getter: e => e.reasonCode?.[0] },
]
}

View File

@ -19,11 +19,14 @@
[headerHeight]="50"
[footerHeight]="50"
rowHeight="auto"
[limit]="10"
[externalPaging]="true"
[count]="totalElements"
[offset]="currentPage.offset"
[limit]="20"
[rows]="rows"
[selectionType]="SelectionType.single"
(select)="onSelect($event)"
(page)="changePage($event)"
>
</ngx-datatable>
</div>

View File

@ -3,10 +3,15 @@ import {DatatableComponent, ColumnMode, SelectionType} from '@swimlane/ngx-datat
import {ResourceFhir} from '../../models/fasten/resource_fhir';
import {FORMATTERS, getPath, obsValue, attributeXTime} from './utils';
import {Router} from '@angular/router';
import {Observable, of} from 'rxjs';
import {map} from 'rxjs/operators';
import {FastenApiService} from '../../services/fasten-api.service';
//all Resource list components must implement this Interface
export interface ResourceListComponentInterface {
resourceList: ResourceFhir[];
resourceListType: string;
totalElements: number;
sourceId: string;
markForCheck()
}
@ -20,14 +25,22 @@ export class GenericColumnDefn {
defaultValue?: string
}
class PageInfo {
offset: number = 0 //this is the current page number. 0 is the first page. Matches the ng-datatable structure
}
@Component({
selector: 'app-list-generic-resource',
templateUrl: './list-generic-resource.component.html',
styleUrls: ['./list-generic-resource.component.scss']
})
export class ListGenericResourceComponent implements OnInit, ResourceListComponentInterface {
@Input() totalElements: number;
@Input() resourceListType: string;
@Input() sourceId: string;
@Input() resourceList: ResourceFhir[] = []
currentPage: PageInfo = {offset: 0}
// @Input() resourceList: ResourceFhir[] = []
// description: string
// c: ListGenericResourceComponentColumn[] = []
@ -40,25 +53,53 @@ export class ListGenericResourceComponent implements OnInit, ResourceListCompone
ColumnMode = ColumnMode;
SelectionType = SelectionType;
constructor(public changeRef: ChangeDetectorRef, public router: Router) {}
constructor(public changeRef: ChangeDetectorRef, public router: Router, public fastenApi: FastenApiService) {
}
ngOnInit(): void {
console.log("INIT GENERIC RESOURCE")
this.currentPage = {offset: 0}
this.renderList()
this.changePage(this.currentPage)
}
markForCheck(){
this.changeRef.markForCheck()
}
renderList(){
changePage(page: PageInfo){
console.log("Requesting page:" + JSON.stringify(page))
this.currentPage = page;
this.fastenApi.getResources(this.resourceListType, this.sourceId, null, this.currentPage.offset)
.subscribe((resourceList: ResourceFhir[]) => {
this.renderList(resourceList)
this.markForCheck()
});
}
// getResources(page?: number): Observable<ResourceFhir[]>{
// // if(this.resourceListType && !this.resourceListCache[this.resourceListType]){
// // // this resource type list has not been downloaded yet, do so now
// return
// .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] || [])
// // }
// }
renderList(resourceList: ResourceFhir[]){
console.log("GENERIC RESOURCE RENDERLSIT")
this.columns = this.columnDefinitions.map((defn) => {
let column = {name: defn.title, prop: defn.title.replace(/[^A-Z0-9]/ig, "_")}
return column
})
this.rows = this.resourceList.map((resource) => {
this.rows = resourceList.map((resource) => {
let row = {
source_id: resource.source_id,
source_resource_type: resource.source_resource_type,

View File

@ -29,7 +29,7 @@ import {ListAppointmentComponent} from '../list-generic-resource/list-appointmen
import {ListDeviceComponent} from '../list-generic-resource/list-device.component';
import {ListDiagnosticReportComponent} from '../list-generic-resource/list-diagnostic-report.component';
import {ListGoalComponent} from '../list-generic-resource/list-goal.component';
import {ListFallbackResourceComponent} from '../list-fallback-resource/list-fallback-resource.component';
import {ListFallbackComponent} from '../list-generic-resource/list-fallback.component';
@Component({
selector: 'source-resource-list',
@ -41,9 +41,7 @@ export class ResourceListComponent implements OnInit, OnChanges {
@Input() source: Source;
@Input() resourceListType: string;
resourceListCache: { [name:string]: ResourceFhir[] } = {}
@Input() selectedTotalElements: number;
//location to dynamically load the resource list
@ViewChild(ResourceListOutletDirective, {static: true}) resourceListOutlet!: ResourceListOutletDirective;
@ -63,30 +61,14 @@ export class ResourceListComponent implements OnInit, OnChanges {
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.totalElements = this.selectedTotalElements;
componentRef.instance.resourceListType = this.resourceListType;
componentRef.instance.sourceId = this.source.id;
componentRef.instance.markForCheck()
}
})
}
getResources(): Observable<ResourceFhir[]>{
if(this.resourceListType && !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] || [])
}
}
@ -164,7 +146,7 @@ export class ResourceListComponent implements OnInit, OnChanges {
}
default: {
console.warn("Unknown component type, using fallback", resourceType)
return ListFallbackResourceComponent;
return ListFallbackComponent;
}
}

View File

@ -12,6 +12,7 @@ import {ListEncounterComponent} from './list-generic-resource/list-encounter.com
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 {ListFallbackComponent} from './list-generic-resource/list-fallback.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';
@ -30,7 +31,6 @@ import {ListAppointmentComponent} from './list-generic-resource/list-appointment
import {ListDeviceComponent} from './list-generic-resource/list-device.component';
import {ListDiagnosticReportComponent} from './list-generic-resource/list-diagnostic-report.component';
import {ListGoalComponent} from './list-generic-resource/list-goal.component';
import { ListFallbackResourceComponent } from './list-fallback-resource/list-fallback-resource.component';
import {NgbCollapseModule, NgbModule} from '@ng-bootstrap/ng-bootstrap';
import { ToastComponent } from './toast/toast.component';
import { MomentModule } from 'ngx-moment';
@ -145,7 +145,7 @@ import { CodableConceptComponent } from './fhir/datatypes/codable-concept/codabl
ListGoalComponent,
ResourceListComponent,
ResourceListOutletDirective,
ListFallbackResourceComponent,
ListFallbackComponent,
ToastComponent,
ReportHeaderComponent,
ReportMedicalHistoryEditorComponent,
@ -155,6 +155,7 @@ import { CodableConceptComponent } from './fhir/datatypes/codable-concept/codabl
FhirResourceComponent,
FhirResourceOutletDirective,
FallbackComponent,
ListFallbackComponent,
DiagnosticReportComponent,
NlmTypeaheadComponent,
DocumentReferenceComponent,
@ -171,6 +172,7 @@ import { CodableConceptComponent } from './fhir/datatypes/codable-concept/codabl
DiagnosticReportComponent,
DocumentReferenceComponent,
FallbackComponent,
ListFallbackComponent,
FhirResourceComponent,
FhirResourceOutletDirective,
ImmunizationComponent,

View File

@ -63,7 +63,11 @@
</div>
<div class="az-content-body pd-lg-l-40 d-flex flex-column">
<source-resource-list [source]="selectedSource" [resourceListType]="selectedResourceType"></source-resource-list>
<source-resource-list
[source]="selectedSource"
[resourceListType]="selectedResourceType"
[selectedTotalElements]="selectedTotalElements"
></source-resource-list>
</div><!-- az-content-body -->
</div><!-- container -->

View File

@ -16,6 +16,7 @@ export class SourceDetailComponent implements OnInit {
selectedSource: Source = null
selectedPatient: ResourceFhir = null
selectedResourceType: string = null
selectedTotalElements: number = 0
resourceTypeCounts: { [name: string]: number } = {}
@ -43,6 +44,7 @@ export class SourceDetailComponent implements OnInit {
selectResourceType(resourceType: string) {
this.selectedResourceType = resourceType
this.selectedTotalElements = this.resourceTypeCounts[resourceType]
}
//functions to call on patient

View File

@ -138,7 +138,7 @@ export class FastenApiService {
);
}
getResources(sourceResourceType?: string, sourceID?: string, sourceResourceID?: string): Observable<ResourceFhir[]> {
getResources(sourceResourceType?: string, sourceID?: string, sourceResourceID?: string, page?: number): Observable<ResourceFhir[]> {
let queryParams = {}
if(sourceResourceType){
queryParams["sourceResourceType"] = sourceResourceType
@ -150,6 +150,9 @@ export class FastenApiService {
if(sourceResourceID){
queryParams["sourceResourceID"] = sourceResourceID
}
if(page !== undefined){
queryParams["page"] = page
}
return this._httpClient.get<any>(`${GetEndpointAbsolutePath(globalThis.location, environment.fasten_api_endpoint_base)}/secure/resource/fhir`, {params: queryParams})
.pipe(