diff --git a/.gitignore b/.gitignore
index fd950fd4..565e020c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -36,6 +36,13 @@ cmake-build-release/
# IntelliJ
out/
+# VS Code files for those working on multiple tools
+.vscode/*
+!.vscode/settings.json
+!.vscode/tasks.json
+!.vscode/launch.json
+!.vscode/extensions.json
+
# mpeltonen/sbt-idea plugin
.idea_modules/
@@ -70,3 +77,4 @@ fasten.db-shm
fasten.db-wal
backend/resources/related_versions.json
+frontend/documentation.json
diff --git a/.vscode/launch.json b/.vscode/launch.json
new file mode 100644
index 00000000..36b549e1
--- /dev/null
+++ b/.vscode/launch.json
@@ -0,0 +1,26 @@
+{
+ "version": "0.2.0",
+ "configurations": [
+ {
+ "name": "Go Tests",
+ "type": "go",
+ "request": "launch",
+ "mode": "test",
+ "program": "${workspaceFolder}/backend",
+ "args": [
+ "-test.run"
+ ]
+ },
+ {
+ "type": "chrome",
+ "request": "attach",
+ "name": "Attach Karma Chrome",
+ "address": "localhost",
+ "port": 9333,
+ "pathMapping": {
+ "/": "${workspaceRoot}/frontend",
+ "/base/": "${workspaceRoot}/frontend"
+ }
+ }
+ ]
+}
diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 00000000..bc09be7f
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,6 @@
+{
+ "karmaTestExplorer.karmaConfFilePath": "frontend/karma.conf.js",
+ "karmaTestExplorer.projectWorkspaces": [
+ "frontend"
+ ]
+}
diff --git a/frontend/karma.conf.js b/frontend/karma.conf.js
index 99a445ac..733dd485 100644
--- a/frontend/karma.conf.js
+++ b/frontend/karma.conf.js
@@ -52,8 +52,8 @@ module.exports = function(config) {
customLaunchers: {
ChromeHeadlessCI: {
base: 'ChromeHeadless',
- flags: ['--no-sandbox', '--disable-gpu']
- }
+ flags: ['--no-sandbox', '--disable-gpu', '--remote-debugging-port=9333']
+ },
},
});
};
diff --git a/frontend/package.json b/frontend/package.json
index 7e65e596..bc89532f 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -39,7 +39,7 @@
"@webcomponents/webcomponentsjs": "^2.8.0",
"asmcrypto.js": "^2.3.2",
"bootstrap": "^4.4.1",
- "chart.js": "^4.0.1",
+ "chart.js": "^4.4.2",
"dwv": "^0.31.0",
"fhirpath": "^3.3.0",
"gridstack": "8.1.1",
@@ -77,14 +77,15 @@
"@types/jasminewd2": "~2.0.3",
"chromatic": "^6.19.8",
"codelyzer": "^5.1.2",
- "jasmine-core": "~3.5.0",
- "jasmine-spec-reporter": "~5.0.0",
- "karma": "~6.4.0",
- "karma-chrome-launcher": "~3.1.0",
- "karma-coverage": "^2.2.0",
+ "fishery": "^2.2.2",
+ "jasmine-core": "~5.1.2",
+ "jasmine-spec-reporter": "~7.0.0",
+ "karma": "~6.4.3",
+ "karma-chrome-launcher": "~3.2.0",
+ "karma-coverage": "^2.2.1",
"karma-coverage-istanbul-reporter": "~3.0.2",
- "karma-jasmine": "~4.0.0",
- "karma-jasmine-html-reporter": "^1.5.0",
+ "karma-jasmine": "~5.1.0",
+ "karma-jasmine-html-reporter": "^2.1.0",
"protractor": "~7.0.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
diff --git a/frontend/src/app/components/fhir-card/common/observation-bar-chart/observation-bar-chart.component.html b/frontend/src/app/components/fhir-card/common/observation-bar-chart/observation-bar-chart.component.html
new file mode 100644
index 00000000..9a43e5e2
--- /dev/null
+++ b/frontend/src/app/components/fhir-card/common/observation-bar-chart/observation-bar-chart.component.html
@@ -0,0 +1,9 @@
+
+
+
+
diff --git a/frontend/src/app/components/fhir-card/common/observation-bar-chart/observation-bar-chart.component.scss b/frontend/src/app/components/fhir-card/common/observation-bar-chart/observation-bar-chart.component.scss
new file mode 100644
index 00000000..e69de29b
diff --git a/frontend/src/app/components/fhir-card/common/observation-bar-chart/observation-bar-chart.component.spec.ts b/frontend/src/app/components/fhir-card/common/observation-bar-chart/observation-bar-chart.component.spec.ts
new file mode 100644
index 00000000..e3ea2278
--- /dev/null
+++ b/frontend/src/app/components/fhir-card/common/observation-bar-chart/observation-bar-chart.component.spec.ts
@@ -0,0 +1,22 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+import { ObservationBarChartComponent } from './observation-bar-chart.component';
+
+describe('ObservationBarChartComponent', () => {
+ let component: ObservationBarChartComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ imports: [ ObservationBarChartComponent ]
+ })
+ .compileComponents();
+
+ fixture = TestBed.createComponent(ObservationBarChartComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/frontend/src/app/components/fhir-card/common/observation-bar-chart/observation-bar-chart.component.ts b/frontend/src/app/components/fhir-card/common/observation-bar-chart/observation-bar-chart.component.ts
new file mode 100644
index 00000000..ab005b4b
--- /dev/null
+++ b/frontend/src/app/components/fhir-card/common/observation-bar-chart/observation-bar-chart.component.ts
@@ -0,0 +1,161 @@
+import { Component, Input, OnInit } from '@angular/core';
+import { formatDate } from '@angular/common';
+import { ObservationModel } from '../../../../../lib/models/resources/observation-model';
+import { ChartConfiguration } from 'chart.js';
+import { NgChartsModule } from 'ng2-charts';
+
+const defaultChartHeight = 65;
+const defaultChartEntryHeight = 30;
+
+@Component({
+ standalone: true,
+ selector: 'observation-bar-chart',
+ imports: [ NgChartsModule ],
+ templateUrl: './observation-bar-chart.component.html',
+ styleUrls: ['./observation-bar-chart.component.scss']
+})
+export class ObservationBarChartComponent implements OnInit {
+ @Input() observations: [ObservationModel]
+
+ chartHeight = defaultChartEntryHeight;
+
+ // based on https://stackoverflow.com/questions/38889716/chartjs-2-stacked-bar-with-marker-on-top
+ // https://stackoverflow.com/questions/62711919/chart-js-horizontal-lines-per-bar
+ barChartData =[
+ {
+ label: "Reference",
+ data: [],
+ dataLabels: [],
+ backgroundColor: "rgba(91, 71, 251,0.6)",
+ hoverBackgroundColor: "rgba(91, 71, 251,0.2)",
+ parsing: {
+ xAxisKey: 'range'
+ },
+ tooltip: {
+ callbacks: {
+ label: function(context) {
+ return `${context.dataset.label}: ${context.dataset.dataLabels[context.dataIndex]}`;
+ }
+ }
+ }
+ },
+ {
+ label: "Result",
+ data: [],
+ // @ts-ignore
+ dataLabels: [],
+ borderColor: "rgba(0,0,0,1)",
+ backgroundColor: "rgba(0,0,0,1)",
+ hoverBackgroundColor: "rgba(0,0,0,1)",
+ minBarLength: 3,
+ barPercentage: 1,
+ parsing: {
+ xAxisKey: 'value'
+ },
+ // @ts-ignore
+ tooltip: {
+ callbacks: {
+ label: function(context) {
+ let label = `${context.dataset.label}: ${context.parsed.x}`;
+
+ if (context.dataset.dataLabels[context.dataIndex]) {
+ return `${label} ${context.dataset.dataLabels[context.dataIndex]}`;
+ }
+ return label;
+ }
+ }
+ }
+ }
+ ] as ChartConfiguration<'bar'>['data']['datasets']
+
+ barChartLabels = [] // ["2020", "2018"] //["1","2","3","4","5","6","7","8"]
+
+ barChartOptions = {
+ indexAxis: 'y',
+ maintainAspectRatio: false,
+ legend:{
+ display: false,
+ },
+ autoPadding: true,
+ //add padding to fix tooltip cutoff
+ layout: {
+ padding: {
+ left: 0,
+ right: 4,
+ top: 0,
+ bottom: 10
+ }
+ },
+ scales: {
+ y: {
+ stacked: true,
+ ticks: {
+ beginAtZero: true,
+ fontSize: 10,
+ min: 0,
+ },
+ },
+ x: {
+ scaleLabel:{
+ display: false,
+ padding: 4,
+ },
+ ticks: {
+ beginAtZero: true,
+ fontSize: 10,
+ min: 0,
+ },
+ },
+ }
+ } as ChartConfiguration<'bar'>['options']
+
+ barChartColors = [
+ {
+ backgroundColor: 'white'
+ }
+ ];
+
+ constructor() { }
+
+ ngOnInit(): void {
+ if(!this.observations || !this.observations[0]) {
+ return;
+ }
+
+ let currentValues: number[] = []
+ let referenceRanges = []
+
+ for(let observation of this.observations) {
+ let refRange = observation.reference_range;
+
+ referenceRanges.push([refRange.low || 0, refRange.high || 0]);
+ currentValues.push(observation.value_quantity_value);
+
+ if (observation.effective_date) {
+ this.barChartLabels.push(formatDate(observation.effective_date, "mediumDate", "en-US", undefined));
+ } else {
+ this.barChartLabels.push('Unknown date');
+ }
+
+ this.barChartData[0]['dataLabels'].push(observation.referenceRangeDisplay());
+ this.barChartData[1]['dataLabels'].push(observation.value_quantity_unit);
+ }
+
+ let xAxisMax = Math.max(...currentValues) * 1.3;
+ this.barChartOptions.scales['x']['max'] = xAxisMax
+
+ let updatedRefRanges = referenceRanges.map(range => {
+ if (range[0] && !range[1]) {
+ return [range[0], xAxisMax]
+ } else {
+ return [range[0], range[1]]
+ }
+ });
+
+ // @ts-ignore
+ this.barChartData[0].data = updatedRefRanges
+ this.barChartData[1].data = currentValues.map(v => [v, v])
+
+ this.chartHeight = defaultChartHeight + (defaultChartEntryHeight * currentValues.length)
+ }
+}
diff --git a/frontend/src/app/components/fhir-card/common/observation-bar-chart/observation-bar-chart.stories.ts b/frontend/src/app/components/fhir-card/common/observation-bar-chart/observation-bar-chart.stories.ts
new file mode 100644
index 00000000..ccea23e5
--- /dev/null
+++ b/frontend/src/app/components/fhir-card/common/observation-bar-chart/observation-bar-chart.stories.ts
@@ -0,0 +1,64 @@
+import type { Meta, StoryObj } from '@storybook/angular';
+import { fhirVersions } from "../../../../../lib/models/constants";
+import { ObservationBarChartComponent } from './observation-bar-chart.component';
+import { ObservationModel } from 'src/lib/models/resources/observation-model';
+import { observationR4Factory } from 'src/lib/fixtures/factories/r4/resources/observation-r4-factory';
+
+// More on how to set up stories at: https://storybook.js.org/docs/angular/writing-stories/introduction
+const meta: Meta = {
+ title: 'Fhir Card/Common/ObservationBarChart',
+ component: ObservationBarChartComponent,
+ decorators: [
+ ],
+ tags: ['autodocs'],
+ render: (args: ObservationBarChartComponent) => ({
+ props: {
+ backgroundColor: null,
+ ...args,
+ },
+ }),
+ argTypes: {
+ observations: {
+ control: 'object',
+ }
+ },
+};
+
+export default meta;
+type Story = StoryObj;
+
+export const NoRange: Story = {
+ args: {
+ observations: [new ObservationModel(observationR4Factory.build(), fhirVersions.R4)]
+ }
+};
+
+export const Range: Story = {
+ args: {
+ observations: [new ObservationModel(observationR4Factory.referenceRange().build(), fhirVersions.R4)]
+ }
+};
+
+export const RangeOnlyLow: Story = {
+ args: {
+ observations: [new ObservationModel(observationR4Factory.referenceRangeOnlyLow().build(), fhirVersions.R4)]
+ }
+};
+
+export const RangeOnlyLowText: Story = {
+ args: {
+ observations: [new ObservationModel(observationR4Factory.referenceRangeStringOnlyLow().build(), fhirVersions.R4)]
+ }
+};
+
+export const RangeOnlyHigh: Story = {
+ args: {
+ observations: [new ObservationModel(observationR4Factory.referenceRangeOnlyHigh().build(), fhirVersions.R4)]
+ }
+};
+
+export const RangeOnlyHighText: Story = {
+ args: {
+ observations: [new ObservationModel(observationR4Factory.referenceRangeStringOnlyHigh().build(), fhirVersions.R4)]
+ }
+};
diff --git a/frontend/src/app/components/fhir-card/fhir-card.module.ts b/frontend/src/app/components/fhir-card/fhir-card.module.ts
index 60bbfece..67be9407 100644
--- a/frontend/src/app/components/fhir-card/fhir-card.module.ts
+++ b/frontend/src/app/components/fhir-card/fhir-card.module.ts
@@ -28,6 +28,7 @@ import {FhirCardComponent} from './fhir-card/fhir-card.component';
import {FhirCardOutletDirective} from './fhir-card/fhir-card-outlet.directive';
import { EncounterComponent } from './resources/encounter/encounter.component';
import { RtfComponent } from './datatypes/rtf/rtf.component';
+import { ObservationBarChartComponent } from './common/observation-bar-chart/observation-bar-chart.component';
@@ -36,6 +37,7 @@ import { RtfComponent } from './datatypes/rtf/rtf.component';
//common
CommonModule,
BadgeComponent,
+ ObservationBarChartComponent,
//datatypes
TableComponent,
BinaryTextComponent,
@@ -75,6 +77,7 @@ import { RtfComponent } from './datatypes/rtf/rtf.component';
//common
BadgeComponent,
TableComponent,
+ ObservationBarChartComponent,
//datatypes
BinaryTextComponent,
CodableConceptComponent,
diff --git a/frontend/src/app/components/fhir-card/resources/observation/observation.component.html b/frontend/src/app/components/fhir-card/resources/observation/observation.component.html
index 48f33fe3..5922653a 100644
--- a/frontend/src/app/components/fhir-card/resources/observation/observation.component.html
+++ b/frontend/src/app/components/fhir-card/resources/observation/observation.component.html
@@ -14,13 +14,7 @@
Observations are a central element in healthcare, used to support diagnosis, monitor progress, determine baselines and patterns and even capture demographic characteristics.
-
+
@@ -53,15 +54,12 @@
-
+
-
-
-
diff --git a/frontend/src/app/components/report-labs-observation/report-labs-observation.component.spec.ts b/frontend/src/app/components/report-labs-observation/report-labs-observation.component.spec.ts
index b92fd29d..d5aa5c1d 100644
--- a/frontend/src/app/components/report-labs-observation/report-labs-observation.component.spec.ts
+++ b/frontend/src/app/components/report-labs-observation/report-labs-observation.component.spec.ts
@@ -1,8 +1,7 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
-
import { ReportLabsObservationComponent } from './report-labs-observation.component';
-import {PipesModule} from '../../pipes/pipes.module';
-import {NgbCollapseModule} from '@ng-bootstrap/ng-bootstrap';
+import { PipesModule } from '../../pipes/pipes.module';
+import { NgbCollapse } from '@ng-bootstrap/ng-bootstrap';
describe('ReportLabsObservationComponent', () => {
let component: ReportLabsObservationComponent;
@@ -10,8 +9,8 @@ describe('ReportLabsObservationComponent', () => {
beforeEach(async () => {
await TestBed.configureTestingModule({
- imports: [PipesModule, NgbCollapseModule],
- declarations: [ ReportLabsObservationComponent ]
+ imports: [PipesModule],
+ declarations: [ ReportLabsObservationComponent, NgbCollapse ],
})
.compileComponents();
diff --git a/frontend/src/app/components/report-labs-observation/report-labs-observation.component.ts b/frontend/src/app/components/report-labs-observation/report-labs-observation.component.ts
index 3852ba5c..4bd242ee 100644
--- a/frontend/src/app/components/report-labs-observation/report-labs-observation.component.ts
+++ b/frontend/src/app/components/report-labs-observation/report-labs-observation.component.ts
@@ -1,11 +1,6 @@
-import {Component, Input, OnInit} from '@angular/core';
-import {ResourceFhir} from '../../models/fasten/resource_fhir';
-import {ChartConfiguration} from 'chart.js';
-// import { ChartConfiguration, ChartData, ChartEvent, ChartType } from 'chart.js';
-// import { BaseChartDirective } from 'ng2-charts';
-import * as fhirpath from 'fhirpath';
-import {formatDate} from '@angular/common';
-
+import { Component, Input, OnInit } from '@angular/core';
+import { ResourceFhir } from '../../models/fasten/resource_fhir';
+import { ObservationModel } from 'src/lib/models/resources/observation-model';
@Component({
selector: 'app-report-labs-observation',
@@ -13,186 +8,23 @@ import {formatDate} from '@angular/common';
styleUrls: ['./report-labs-observation.component.scss']
})
export class ReportLabsObservationComponent implements OnInit {
-
@Input() observations: ResourceFhir[] = []
@Input() observationCode: string
@Input() observationTitle: string
+ observationModels: ObservationModel[] = []
firstObservation: ResourceFhir = null
- // based on https://stackoverflow.com/questions/38889716/chartjs-2-stacked-bar-with-marker-on-top
- // https://stackoverflow.com/questions/62711919/chart-js-horizontal-lines-per-bar
-
-
- chartHeight = 60
-
- barChartData =[
- // {
- // label: "Current",
- // backgroundColor: 'rgba(255, 0, 128, 1)',
- // data: [],
- // xAxisID: "x-axis-current"
- // },
- // {
- // label: "Reference",
- // backgroundColor: 'rgba(99,189,50,0.2)',
- // data: [],
- // xAxisID: "x-axis-ref"
- // },
-
-
-
- {
- label: "Reference",
- data: [[55,102], [44,120]],
- backgroundColor: "rgba(91, 71, 251,0.6)",
- hoverBackgroundColor: "rgba(91, 71, 251,0.2)"
- },{
- label: "Current",
- data: [[80,81], [130,131]],
- borderColor: "rgba(0,0,0,1)",
- backgroundColor: "rgba(0,0,0,1)",
- hoverBackgroundColor: "rgba(0,0,0,1)",
- minBarLength: 3,
- barPercentage: 1,
- tooltip: {
-
- }
- // id: "x-axis-current",
- //important settings
-
- //set the width of the line ( or point )
- // pointRadius: 50,
- // don´t show line betrween points
- // showLine: false,
- //change points of line chart to line style ( little bit confusin why it´s named point anyway )
- // pointStyle: 'line',
-
- //chart type
- // type: "line",
- }
- ] as ChartConfiguration<'bar'>['data']['datasets']
-
- barChartLabels = [] // ["2020", "2018"] //["1","2","3","4","5","6","7","8"]
-
- barChartOptions = {
- indexAxis: 'y',
- legend:{
- display: false,
- },
- //add padding to fix tooltip cutoff
- layout: {
- padding: {
- left: 0,
- right: 4,
- top: 0,
- bottom: 10
- }
- },
- scales: {
- y: {
- stacked: true,
- ticks: {
- beginAtZero: true,
- fontSize: 10,
- min: 0,
- // max: 80
- },
- },
- x: {
- scaleLabel:{
- display: false,
- labelString: "xaxis",
- padding: 4,
- },
- // stacked: true,
- ticks: {
- beginAtZero: true,
- fontSize: 10,
- min: 0,
- // max: 80
- },
-
- },
- }
- } as ChartConfiguration<'bar'>['options']
-
- barChartColors = [
- {
- backgroundColor: 'white'
- }
- ];
-
constructor() { }
ngOnInit(): void {
-
- let currentValues: number[] = []
-
- let referenceRanges = []
-
//sort observations
this.observations = this.observations?.sort((a, b) => a.sort_date > b.sort_date ? -1 : a.sort_date < b.sort_date ? 1 : 0)
if(this.observations.length > 0){
this.firstObservation = this.observations[0]
}
- for(let observation of this.observations){
- //get label
- this.barChartLabels.push(
- formatDate(fhirpath.evaluate(observation.resource_raw, "Observation.effectiveDateTime")[0], "mediumDate", "en-US", undefined)
- )
- //get current value
- // let currentValue = fhirpath.evaluate(observation.resource_raw, "Observation.valueQuantity.value")[0]
- // if(currentValue != null){
- // currentValues.push([currentValue, currentValue])
- // } else {
- // currentValues.push([])
- // }
- currentValues.push(fhirpath.evaluate(observation.resource_raw, "Observation.valueQuantity.value")[0])
-
- //set chart x-axis label
- let units = fhirpath.evaluate(observation.resource_raw, "Observation.valueQuantity.unit")[0]
-
- //TODO: fix x-axis label
- // if(units){
- //
- // (this.barChartOptions as ChartConfiguration<'bar'>['options']).scales['x']['scaleLabel'].display = true
- // (this.barChartOptions as ChartConfiguration<'bar'>['options']).scales['y']['scaleLabel'].labelString = units
- // }
-
-
- //add low/high ref value blocks
- // let referenceLow = fhirpath.evaluate(observation.resource_raw, "Observation.referenceRange.low.value")[0]
- // let referenceHigh = fhirpath.evaluate(observation.resource_raw, "Observation.referenceRange.high.value")[0]
- // if (referenceLow != null && referenceHigh != null){
- // referenceRanges.push([referenceLow, referenceHigh])
- // } else {
- // referenceRanges.push([0,0])
- // }
- referenceRanges.push([
- fhirpath.evaluate(observation.resource_raw, "Observation.referenceRange.low.value")[0],
- fhirpath.evaluate(observation.resource_raw, "Observation.referenceRange.high.value")[0]
- ])
- }
-
-
-
- // @ts-ignore
- this.barChartData[0].data = referenceRanges
- this.barChartData[1].data = currentValues.map(v => [v, v])
- // this.barChartData[1].data = currentValues
-
- let suggestedMax = Math.max(...currentValues) * 1.1;
- this.barChartOptions.scales['x']['suggestedMax'] = suggestedMax
-
- console.log(this.observationTitle, this.barChartData[0].data, this.barChartData[1].data)
-
-
- if(currentValues.length > 1){
- this.chartHeight = 30 * currentValues.length
- }
+ this.observationModels = this.observations.map(ob => new ObservationModel(ob.resource_raw))
}
-
}
diff --git a/frontend/src/app/components/report-labs-observation/report-labs-observation.stories.ts b/frontend/src/app/components/report-labs-observation/report-labs-observation.stories.ts
index 7e21a1e6..258545b6 100644
--- a/frontend/src/app/components/report-labs-observation/report-labs-observation.stories.ts
+++ b/frontend/src/app/components/report-labs-observation/report-labs-observation.stories.ts
@@ -14,6 +14,7 @@ import { Observable, of } from 'rxjs';
import R4Example1Json from "../../../lib/fixtures/r4/resources/observation/example1.json";
import { Html as GlossaryLookupHtml } from '../glossary-lookup/glossary-lookup.stories';
+import { ObservationBarChartComponent } from '../fhir-card/common/observation-bar-chart/observation-bar-chart.component';
const withHttpClientProvider: DecoratorFunction = (storyFunc, context) => {
@@ -39,8 +40,8 @@ const meta: Meta = {
decorators: [
withHttpClientProvider,
moduleMetadata({
- imports: [PipesModule, GlossaryLookupComponent, NgChartsModule, RouterTestingModule, HttpClientModule],
- declarations: [NgbCollapse],
+ imports: [PipesModule, GlossaryLookupComponent, NgChartsModule, RouterTestingModule, HttpClientModule, ObservationBarChartComponent],
+ declarations: [ NgbCollapse ],
providers: [],
})
],
diff --git a/frontend/src/app/widgets/dashboard-widget/dashboard-widget.component.spec.ts b/frontend/src/app/widgets/dashboard-widget/dashboard-widget.component.spec.ts
index b7928fdc..77d13e51 100644
--- a/frontend/src/app/widgets/dashboard-widget/dashboard-widget.component.spec.ts
+++ b/frontend/src/app/widgets/dashboard-widget/dashboard-widget.component.spec.ts
@@ -248,8 +248,8 @@ describe('DashboardWidgetComponent', () => {
});
})
- describe('Vitals - ListWidget', () => {})
- describe('Resource Aggregation - DonutWidget', () => {})
+ // describe('Vitals - ListWidget', () => {})
+ // describe('Resource Aggregation - DonutWidget', () => {})
})
diff --git a/frontend/src/assets/scss/custom/_mixins.scss b/frontend/src/assets/scss/custom/_mixins.scss
index 679bd42a..596cf1bb 100755
--- a/frontend/src/assets/scss/custom/_mixins.scss
+++ b/frontend/src/assets/scss/custom/_mixins.scss
@@ -45,7 +45,7 @@
$color2: $color;
$base2: $base;
- $deg: ($perc/100*360)+deg;
+ $deg: calc($perc / 100 * 360) + deg;
$deg1: 90deg;
$deg2: $deg;
@@ -54,7 +54,7 @@
$base: $color;
$color: $base2;
$color2: $base2;
- $deg1: ($perc/100*360+90)+deg;
+ $deg1: calc($perc / 100 * 360 + 90) + deg;
$deg2: 0deg;
}
@@ -66,14 +66,14 @@
.slice {
&.one {
- clip: rect(0 $size $size/2 0);
+ clip: rect(0 $size calc($size / 2) 0);
-webkit-transform: rotate($deg1);
transform: rotate($deg1);
background: $color;
}
&.two {
- clip: rect(0 $size/2 $size 0);
+ clip: rect(0 calc($size / 2) $size 0);
-webkit-transform: rotate($deg2);
transform: rotate($deg2);
background: $color2;
diff --git a/frontend/src/lib/fixtures/factories/r4/resources/observation-r4-factory.ts b/frontend/src/lib/fixtures/factories/r4/resources/observation-r4-factory.ts
new file mode 100644
index 00000000..5c85d375
--- /dev/null
+++ b/frontend/src/lib/fixtures/factories/r4/resources/observation-r4-factory.ts
@@ -0,0 +1,152 @@
+import { Factory } from 'fishery';
+
+class ObservationR4Factory extends Factory<{}> {
+
+ valueString(value?: string) {
+ return this.params({
+ valueQuantity: null,
+ valueString: value || '5.5mmol/l'
+ })
+ }
+
+ referenceRange(high?: number, low?: number) {
+ return this.params({
+ referenceRange: [
+ {
+ low: {
+ value: low || 3.1,
+ unit: 'mmol/l',
+ system: 'http://unitsofmeasure.org',
+ code: 'mmol/L'
+ },
+ high: {
+ value: high || 6.5,
+ unit: 'mmol/l',
+ system: 'http://unitsofmeasure.org',
+ code: 'mmol/L'
+ }
+ }
+ ]
+ })
+ }
+
+ referenceRangeOnlyHigh(value?: number) {
+ return this.params({
+ referenceRange: [
+ {
+ high: {
+ value: value || 6.5,
+ unit: 'mmol/l',
+ system: 'http://unitsofmeasure.org',
+ code: 'mmol/L'
+ }
+ }
+ ]
+ });
+ };
+
+ referenceRangeOnlyLow(value?: number) {
+ return this.params({
+ referenceRange: [
+ {
+ low: {
+ value: value || 3.1,
+ unit: 'mmol/l',
+ system: 'http://unitsofmeasure.org',
+ code: 'mmol/L'
+ }
+ }
+ ]
+ });
+ };
+
+ referenceRangeString(range?: string) {
+ return this.params({
+ referenceRange: [
+ {
+ text: range || '3.1mmol/l-6.3mmol/l'
+ }
+ ]
+ });
+ };
+
+ referenceRangeStringOnlyHigh(high?: string) {
+ return this.params({
+ referenceRange: [
+ {
+ text: high || '<=5.5'
+ }
+ ]
+ });
+ };
+
+ referenceRangeStringOnlyLow(low?: | string) {
+ return this.params({
+ referenceRange: [
+ {
+ text: low || '>=4.5'
+ }
+ ]
+ });
+ };
+}
+
+export const observationR4Factory = ObservationR4Factory.define(() => (
+ {
+ resourceType: 'Observation',
+ id: 'f001',
+ text: {
+ status: 'generated',
+ div: "Generated Narrative with Details
id: example
status: final
category: Vital Signs (Details : {http://terminology.hl7.org/CodeSystem/observation-category code 'vital-signs' = 'Vital Signs', given as 'Vital Signs'})
code: Body Weight (Details : {LOINC code '29463-7' = 'Body weight', given as 'Body Weight'}; {LOINC code '3141-9' = 'Body weight Measured', given as 'Body weight Measured'}; {SNOMED CT code '27113001' = 'Body weight', given as 'Body weight'}; {http://acme.org/devices/clinical-codes code 'body-weight' = 'body-weight', given as 'Body Weight'})
subject: Patient/example
encounter: Encounter/example
effective: 28/03/2016
value: 185 lbs (Details: UCUM code [lb_av] = 'lb_av')
"
+ },
+ identifier: [
+ {
+ use: 'official',
+ system: 'http://www.bmc.nl/zorgportal/identifiers/observations',
+ value: '6323'
+ }
+ ],
+ status: 'final',
+ code: {
+ coding: [
+ {
+ system: 'http://loinc.org',
+ code: '15074-8',
+ display: 'Glucose [Moles/volume] in Blood'
+ }
+ ]
+ },
+ subject: {
+ reference: 'Patient/f001',
+ display: 'P. van de Heuvel'
+ },
+ effectiveDateTime: '2016-03-28',
+ effectivePeriod: {
+ start: '2013-04-02T09:30:10+01:00'
+ },
+ issued: '2013-04-03T15:30:10+01:00',
+ performer: [
+ {
+ reference: 'Practitioner/f005',
+ display: 'A. Langeveld'
+ }
+ ],
+ valueQuantity: {
+ value: 6.3,
+ unit: 'mmol/l',
+ system: 'http://unitsofmeasure.org',
+ code: 'mmol/L'
+ },
+ interpretation: [
+ {
+ coding: [
+ {
+ system: 'http://terminology.hl7.org/CodeSystem/v3-ObservationInterpretation',
+ code: 'H',
+ display: 'High'
+ }
+ ]
+ }
+ ],
+ }
+));
diff --git a/frontend/src/lib/models/resources/observation-model.spec.ts b/frontend/src/lib/models/resources/observation-model.spec.ts
index 7baec831..ace3bb7c 100644
--- a/frontend/src/lib/models/resources/observation-model.spec.ts
+++ b/frontend/src/lib/models/resources/observation-model.spec.ts
@@ -1,7 +1,95 @@
import { ObservationModel } from './observation-model';
+import { fhirVersions } from '../constants';
+import { observationR4Factory } from 'src/lib/fixtures/factories/r4/resources/observation-r4-factory';
describe('ObservationModel', () => {
it('should create an instance', () => {
expect(new ObservationModel({})).toBeTruthy();
});
+
+ describe('parsing value', () => {
+ it('reads from valueQuantity.value if set', () => {
+ let observation = new ObservationModel(observationR4Factory.build(), fhirVersions.R4);
+
+ expect(observation.value_quantity_value).toEqual(6.3);
+ });
+
+ it('parses valueString correctly when value is a number if valueQuantity.value not set', () => {
+ let observation = new ObservationModel(observationR4Factory.valueString().build(), fhirVersions.R4);
+
+ expect(observation.value_quantity_value).toEqual(5.5);
+ });
+ });
+
+ describe('parsing unit', () => {
+ it('reads from valueQuantity.unit if set', () => {
+ let observation = new ObservationModel(observationR4Factory.build(), fhirVersions.R4);
+
+ expect(observation.value_quantity_unit).toEqual('mmol/l');
+ });
+
+ it('reads from valueString if valueQuantity.unit not set', () => {
+ let observation = new ObservationModel(observationR4Factory.valueString().build(), fhirVersions.R4);
+
+ expect(observation.value_quantity_unit).toEqual('mmol/l');
+ });
+ });
+
+ describe('parsing reference range', () => {
+ it('parses referenceRange correctly when high and low are not set', () => {
+ let observation = new ObservationModel(observationR4Factory.build(), fhirVersions.R4);
+
+ expect(observation.reference_range).toEqual({ low: null, high: null });
+ });
+
+ it('parses referenceRange correctly when high and low are set', () => {
+ let observation = new ObservationModel(observationR4Factory.referenceRange().build(), fhirVersions.R4);
+
+ expect(observation.reference_range).toEqual({ low: 3.1, high: 6.5 });
+ });
+
+ describe('when referenceRange.text is set', () => {
+ it('parses values correctly when there is a high and a low', () => {
+ let tests = [
+ { text: '50.3-109.2', result: { low: 50.3, high: 109.2 } },
+ { text: '50.3mg/L-109.2mg/L', result: { low: 50.3, high: 109.2 } },
+ { text: '50.3-109.2mg/L', result: { low: 50.3, high: 109.2 } },
+ { text: '50.3mg/L-109.2', result: { low: 50.3, high: 109.2 } }
+ ]
+
+ for(let test of tests) {
+ let observation = new ObservationModel(observationR4Factory.referenceRangeString(test.text).build(), fhirVersions.R4);
+ expect(observation.reference_range).toEqual(test.result)
+ }
+ });
+
+ it('parses values correctly when there is only a low', () => {
+ let tests = [
+ { text: '>50.3', result: { low: 50.3, high: null } },
+ { text: '>50.3mg/L', result: { low: 50.3, high: null } },
+ { text: '>=50.3', result: { low: 50.3, high: null } },
+ { text: '>=50.3mg/L', result: { low: 50.3, high: null } }
+ ]
+
+ for(let test of tests) {
+ let observation = new ObservationModel(observationR4Factory.referenceRangeStringOnlyLow(test.text).build(), fhirVersions.R4);
+ expect(observation.reference_range).toEqual(test.result)
+ }
+ });
+
+ it('parses values correctly when there is only a high', () => {
+ let tests = [
+ { text: '<109.2', result: { low: null, high: 109.2 } },
+ { text: '<109.2mg/L', result: { low: null, high: 109.2 } },
+ { text: '<=109.2', result: { low: null, high: 109.2 } },
+ { text: '<=109.2mg/L', result: { low: null, high: 109.2 } }
+ ]
+
+ for(let test of tests) {
+ let observation = new ObservationModel(observationR4Factory.referenceRangeStringOnlyHigh(test.text).build(), fhirVersions.R4);
+ expect(observation.reference_range).toEqual(test.result)
+ }
+ });
+ });
+ });
});
diff --git a/frontend/src/lib/models/resources/observation-model.ts b/frontend/src/lib/models/resources/observation-model.ts
index baa8d4ab..ca3b1d34 100644
--- a/frontend/src/lib/models/resources/observation-model.ts
+++ b/frontend/src/lib/models/resources/observation-model.ts
@@ -2,17 +2,20 @@ import {fhirVersions, ResourceType} from '../constants';
import * as _ from "lodash";
import {CodableConceptModel, hasValue} from '../datatypes/codable-concept-model';
import {ReferenceModel} from '../datatypes/reference-model';
-import {CodingModel} from '../datatypes/coding-model';
import {FastenDisplayModel} from '../fasten/fasten-display-model';
import {FastenOptions} from '../fasten/fasten-options';
+interface referenceRangeHash {
+ low: number | null,
+ high: number | null
+}
export class ObservationModel extends FastenDisplayModel {
code: CodableConceptModel | undefined
effective_date: string
code_coding_display: string
code_text: string
- value_quantity_value: number | string
+ value_quantity_value: number
value_quantity_unit: string
status: string
value_codeable_concept_text: string
@@ -20,25 +23,19 @@ export class ObservationModel extends FastenDisplayModel {
value_codeable_concept_coding: string
value_quantity_value_number: number
subject: ReferenceModel | undefined
- reference_range: {
- low: {
- value: number
- }
- high: {
- value: number
- }
- }[] | undefined
+ fhirResource: any
+ reference_range: referenceRangeHash
constructor(fhirResource: any, fhirVersion?: fhirVersions, fastenOptions?: FastenOptions) {
super(fastenOptions)
+ this.fhirResource = fhirResource
this.source_resource_type = ResourceType.Observation
this.effective_date = _.get(fhirResource, 'effectiveDateTime');
this.code = _.get(fhirResource, 'code');
this.code_coding_display = _.get(fhirResource, 'code.coding.0.display');
this.code_text = _.get(fhirResource, 'code.text', '');
- this.value_quantity_value = _.get(fhirResource, 'valueQuantity.value', '');
- // const issued = _.get(fhirResource, 'issued', '');
- this.value_quantity_unit = _.get(fhirResource, 'valueQuantity.unit', '');
+ this.value_quantity_value = this.parseValue();
+ this.value_quantity_unit = this.parseUnit();
this.status = _.get(fhirResource, 'status', '');
this.value_codeable_concept_text = _.get(
fhirResource,
@@ -54,7 +51,97 @@ export class ObservationModel extends FastenDisplayModel {
[],
);
- this.reference_range = _.get(fhirResource, 'referenceRange', []);
+ this.reference_range = this.parseReferenceRange();
this.subject = _.get(fhirResource, 'subject');
}
+
+ private parseValue(): number {
+ // TODO: parseFloat would return NaN if it can't parse. Need to check and make sure that doesn't cause issues
+ return this.valueQuantity() || parseFloat(this.valueString())
+ }
+
+ private parseUnit(): string {
+ return this.valueUnit() || this.valueStringUnit()
+ }
+
+ // Look for the observation's numeric value. Use this first before valueString which is a backup if this can't be found.
+ private valueQuantity(): number {
+ // debugger
+ return _.get(this.fhirResource, "valueQuantity.value");
+ }
+
+ // Look for the observation's numeric value. Use this first before valueStringUnit which is a backup if this can't be found.
+ private valueUnit(): string {
+ return _.get(this.fhirResource, "valueQuantity.unit");
+ }
+
+ // Use if valueQuantity can't be found. This will check for valueString and attempt to parse the first number in the string
+ private valueString(): string {
+ return _.get(this.fhirResource, "valueString")?.match(/(?[\d.]*)(?.*)/).groups.value;
+ }
+
+ // Use if valueUnit can't be found.
+ private valueStringUnit(): string {
+ return _.get(this.fhirResource, "valueString")?.match(/(?[\d.]*)(?.*)/).groups.text;
+ }
+
+ private referenceRangeFromString(str: string): referenceRangeHash {
+ let matches = str?.match(/(?[\d.]*)?(?[^\d]*)?(?[\d.]*)?/)
+
+ if(!matches) {
+ return { low: null, high: null }
+ }
+
+ if (!!matches.groups['value1'] && !!matches.groups['value2']) {
+ return {
+ low: parseFloat(matches.groups['value1']),
+ high: parseFloat(matches.groups['value2'])
+ }
+ }
+
+ if (['<', '<='].includes(matches.groups['operator'])) {
+ return {
+ low: null,
+ high: parseFloat(matches.groups['value2'])
+ }
+ } else { // > >=
+ return {
+ low: parseFloat(matches.groups['value2']),
+ high: null
+ }
+ }
+ }
+
+ private parseReferenceRange(): referenceRangeHash {
+ let refRangeObject = _.get(this.fhirResource, "referenceRange.0")
+
+ if (refRangeObject?.low || refRangeObject?.high) {
+ return {
+ low: refRangeObject.low?.value,
+ high: refRangeObject.high?.value
+ }
+ }
+
+ return this.referenceRangeFromString(refRangeObject?.text)
+ }
+
+ public referenceRangeDisplay(): string {
+ // If text was sent just show it since we aren't storing difference between <= and <.
+ // Likely doesn't really matter, but might as well if we have that data.
+ if (_.get(this.fhirResource, 'referenceRange.0.text')) {
+ return _.get(this.fhirResource, 'referenceRange.0.text');
+ }
+
+ let refRange = this.parseReferenceRange()
+
+ if (refRange['low'] && refRange['high']) {
+ return `${refRange['low']}\u{2013}${refRange['high']}`;
+ } else if (refRange['low']) {
+ return `> ${refRange['low']}`;
+ } else if (refRange['high']) {
+ return `< ${refRange['high']}`;
+ }
+
+ return '';
+ }
}
diff --git a/frontend/yarn.lock b/frontend/yarn.lock
index 760aa0da..9b1613e7 100644
--- a/frontend/yarn.lock
+++ b/frontend/yarn.lock
@@ -4970,10 +4970,10 @@ chardet@^0.7.0:
resolved "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e"
integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==
-chart.js@^4.0.1:
- version "4.3.0"
- resolved "https://registry.npmjs.org/chart.js/-/chart.js-4.3.0.tgz#ac363030ab3fec572850d2d872956f32a46326a1"
- integrity sha512-ynG0E79xGfMaV2xAHdbhwiPLczxnNNnasrmPEXriXsPJGjmhOBYzFVEsB65w2qMDz+CaBJJuJD0inE/ab/h36g==
+chart.js@^4.4.2:
+ version "4.4.2"
+ resolved "https://registry.yarnpkg.com/chart.js/-/chart.js-4.4.2.tgz#95962fa6430828ed325a480cc2d5f2b4e385ac31"
+ integrity sha512-6GD7iKwFpP5kbSD4MeRRRlTnQvxfQREy36uEtm1hzHzcOqwWx0YEHuspuoNlslu+nciLIB7fjjsHkUv/FzFcOg==
dependencies:
"@kurkle/color" "^0.3.0"
@@ -5592,7 +5592,7 @@ debug@2.6.9, debug@^2.6.9:
dependencies:
ms "2.0.0"
-debug@4, debug@4.3.4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.2, debug@^4.3.3, debug@^4.3.4, debug@~4.3.1, debug@~4.3.2:
+debug@4, debug@4.3.4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.2, debug@^4.3.3, debug@^4.3.4, debug@~4.3.1, debug@~4.3.2, debug@~4.3.4:
version "4.3.4"
resolved "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865"
integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==
@@ -6052,15 +6052,15 @@ end-of-stream@^1.0.0, end-of-stream@^1.1.0, end-of-stream@^1.4.1:
dependencies:
once "^1.4.0"
-engine.io-parser@~5.0.3:
- version "5.0.6"
- resolved "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.0.6.tgz#7811244af173e157295dec9b2718dfe42a64ef45"
- integrity sha512-tjuoZDMAdEhVnSFleYPCtdL2GXwVTGtNjoeJd9IhIG3C1xs9uwxqRNEu5WpnDZCaozwVlK/nuQhpodhXSIMaxw==
+engine.io-parser@~5.2.1:
+ version "5.2.2"
+ resolved "https://registry.yarnpkg.com/engine.io-parser/-/engine.io-parser-5.2.2.tgz#37b48e2d23116919a3453738c5720455e64e1c49"
+ integrity sha512-RcyUFKA93/CXH20l4SoVvzZfrSDMOTUS3bWVpTt2FuFP+XYrL8i8oonHP7WInRyVHXh0n/ORtoeiE1os+8qkSw==
-engine.io@~6.2.1:
- version "6.2.1"
- resolved "https://registry.npmjs.org/engine.io/-/engine.io-6.2.1.tgz#e3f7826ebc4140db9bbaa9021ad6b1efb175878f"
- integrity sha512-ECceEFcAaNRybd3lsGQKas3ZlMVjN3cyWwMP25D2i0zWfyiytVbTpRPa34qrr+FHddtpBVOmq4H/DCv1O0lZRA==
+engine.io@~6.5.2:
+ version "6.5.4"
+ resolved "https://registry.yarnpkg.com/engine.io/-/engine.io-6.5.4.tgz#6822debf324e781add2254e912f8568508850cdc"
+ integrity sha512-KdVSDKhVKyOi+r5uEabrDLZw2qXStVvCsEB/LN3mw4WFi6Gx50jTyuxYVCwAAC0U46FdnzP/ScKRBTXb/NiEOg==
dependencies:
"@types/cookie" "^0.4.1"
"@types/cors" "^2.8.12"
@@ -6070,8 +6070,8 @@ engine.io@~6.2.1:
cookie "~0.4.1"
cors "~2.8.5"
debug "~4.3.1"
- engine.io-parser "~5.0.3"
- ws "~8.2.3"
+ engine.io-parser "~5.2.1"
+ ws "~8.11.0"
enhanced-resolve@^5.10.0, enhanced-resolve@^5.7.0:
version "5.13.0"
@@ -6840,6 +6840,13 @@ findit2@^2.2.3:
resolved "https://registry.npmjs.org/findit2/-/findit2-2.2.3.tgz#58a466697df8a6205cdfdbf395536b8bd777a5f6"
integrity sha512-lg/Moejf4qXovVutL0Lz4IsaPoNYMuxt4PA0nGqFxnJ1CTTGGlEO2wKgoDpwknhvZ8k4Q2F+eesgkLbG2Mxfog==
+fishery@^2.2.2:
+ version "2.2.2"
+ resolved "https://registry.yarnpkg.com/fishery/-/fishery-2.2.2.tgz#94d3d9380295dd3ce555021e9353c5348b8beb77"
+ integrity sha512-jeU0nDhPHJkupmjX+r9niKgVMTBDB8X+U/pktoGHAiWOSyNlMd0HhmqnjrpjUOCDPJYaSSu4Ze16h6dZOKSp2w==
+ dependencies:
+ lodash.mergewith "^4.6.2"
+
flatted@^3.2.7:
version "3.2.7"
resolved "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz#609f39207cb614b89d0765b477cb2d437fbf9787"
@@ -8138,25 +8145,25 @@ jake@^10.8.5:
filelist "^1.0.1"
minimatch "^3.0.4"
-jasmine-core@^3.6.0:
- version "3.99.1"
- resolved "https://registry.npmjs.org/jasmine-core/-/jasmine-core-3.99.1.tgz#5bfa4b2d76618868bfac4c8ff08bb26fffa4120d"
- integrity sha512-Hu1dmuoGcZ7AfyynN3LsfruwMbxMALMka+YtZeGoLuDEySVmVAPaonkNoBRIw/ectu8b9tVQCJNgp4a4knp+tg==
+jasmine-core@^4.1.0:
+ version "4.6.0"
+ resolved "https://registry.yarnpkg.com/jasmine-core/-/jasmine-core-4.6.0.tgz#6884fc3d5b66bf293e422751eed6d6da217c38f5"
+ integrity sha512-O236+gd0ZXS8YAjFx8xKaJ94/erqUliEkJTDedyE7iHvv4ZVqi+q+8acJxu05/WJDKm512EUNn809In37nWlAQ==
jasmine-core@~2.8.0:
version "2.8.0"
resolved "https://registry.npmjs.org/jasmine-core/-/jasmine-core-2.8.0.tgz#bcc979ae1f9fd05701e45e52e65d3a5d63f1a24e"
integrity sha512-SNkOkS+/jMZvLhuSx1fjhcNWUC/KG6oVyFUGkSBEr9n1axSNduWU8GlI7suaHXr4yxjet6KjrUZxUTE5WzzWwQ==
-jasmine-core@~3.5.0:
- version "3.5.0"
- resolved "https://registry.npmjs.org/jasmine-core/-/jasmine-core-3.5.0.tgz#132c23e645af96d85c8bca13c8758b18429fc1e4"
- integrity sha512-nCeAiw37MIMA9w9IXso7bRaLl+c/ef3wnxsoSAlYrzS+Ot0zTG6nU8G/cIfGkqpkjX2wNaIW9RFG0TwIFnG6bA==
+jasmine-core@~5.1.2:
+ version "5.1.2"
+ resolved "https://registry.yarnpkg.com/jasmine-core/-/jasmine-core-5.1.2.tgz#8f2789faa79ef1ffad7abab6bff8d4bd661094f7"
+ integrity sha512-2oIUMGn00FdUiqz6epiiJr7xcFyNYj3rDcfmnzfkBnHyBQ3cBQUs4mmyGsOb7TTLb9kxk7dBcmEmqhDKkBoDyA==
-jasmine-spec-reporter@~5.0.0:
- version "5.0.2"
- resolved "https://registry.npmjs.org/jasmine-spec-reporter/-/jasmine-spec-reporter-5.0.2.tgz#b61288ab074ad440dc2477c4d42840b0e74a6b95"
- integrity sha512-6gP1LbVgJ+d7PKksQBc2H0oDGNRQI3gKUsWlswKaQ2fif9X5gzhQcgM5+kiJGCQVurOG09jqNhk7payggyp5+g==
+jasmine-spec-reporter@~7.0.0:
+ version "7.0.0"
+ resolved "https://registry.yarnpkg.com/jasmine-spec-reporter/-/jasmine-spec-reporter-7.0.0.tgz#94b939448e63d4e2bd01668142389f20f0a8ea49"
+ integrity sha512-OtC7JRasiTcjsaCBPtMO0Tl8glCejM4J4/dNuOJdA8lBjz4PmWjYQ6pzb0uzpBNAWJMDudYuj9OdXJWqM2QTJg==
dependencies:
colors "1.4.0"
@@ -8405,10 +8412,10 @@ jszip@~3.7.0:
readable-stream "~2.3.6"
set-immediate-shim "~1.0.1"
-karma-chrome-launcher@~3.1.0:
- version "3.1.1"
- resolved "https://registry.npmjs.org/karma-chrome-launcher/-/karma-chrome-launcher-3.1.1.tgz#baca9cc071b1562a1db241827257bfe5cab597ea"
- integrity sha512-hsIglcq1vtboGPAN+DGCISCFOxW+ZVnIqhDQcCMqqCp+4dmJ0Qpq5QAjkbA0X2L9Mi6OBkHi2Srrbmm7pUKkzQ==
+karma-chrome-launcher@~3.2.0:
+ version "3.2.0"
+ resolved "https://registry.yarnpkg.com/karma-chrome-launcher/-/karma-chrome-launcher-3.2.0.tgz#eb9c95024f2d6dfbb3748d3415ac9b381906b9a9"
+ integrity sha512-rE9RkUPI7I9mAxByQWkGJFXfFD6lE4gC5nPuZdobf/QdTEJI6EU4yIay/cfU/xV4ZxlM5JiTv7zWYgA64NpS5Q==
dependencies:
which "^1.2.1"
@@ -8423,10 +8430,10 @@ karma-coverage-istanbul-reporter@~3.0.2:
istanbul-reports "^3.0.2"
minimatch "^3.0.4"
-karma-coverage@^2.2.0:
- version "2.2.0"
- resolved "https://registry.npmjs.org/karma-coverage/-/karma-coverage-2.2.0.tgz#64f838b66b71327802e7f6f6c39d569b7024e40c"
- integrity sha512-gPVdoZBNDZ08UCzdMHHhEImKrw1+PAOQOIiffv1YsvxFhBjqvo/SVXNk4tqn1SYqX0BJZT6S/59zgxiBe+9OuA==
+karma-coverage@^2.2.1:
+ version "2.2.1"
+ resolved "https://registry.yarnpkg.com/karma-coverage/-/karma-coverage-2.2.1.tgz#e1cc074f93ace9dc4fb7e7aeca7135879c2e358c"
+ integrity sha512-yj7hbequkQP2qOSb20GuNSIyE//PgJWHwC2IydLE6XRtsnaflv+/OSGNssPjobYUlhVVagy99TQpqUt3vAUG7A==
dependencies:
istanbul-lib-coverage "^3.2.0"
istanbul-lib-instrument "^5.1.0"
@@ -8435,17 +8442,17 @@ karma-coverage@^2.2.0:
istanbul-reports "^3.0.5"
minimatch "^3.0.4"
-karma-jasmine-html-reporter@^1.5.0:
- version "1.7.0"
- resolved "https://registry.npmjs.org/karma-jasmine-html-reporter/-/karma-jasmine-html-reporter-1.7.0.tgz#52c489a74d760934a1089bfa5ea4a8fcb84cc28b"
- integrity sha512-pzum1TL7j90DTE86eFt48/s12hqwQuiD+e5aXx2Dc9wDEn2LfGq6RoAxEZZjFiN0RDSCOnosEKRZWxbQ+iMpQQ==
+karma-jasmine-html-reporter@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/karma-jasmine-html-reporter/-/karma-jasmine-html-reporter-2.1.0.tgz#f951ad00b08d61d03595402c914d1a589c4930e3"
+ integrity sha512-sPQE1+nlsn6Hwb5t+HHwyy0A1FNCVKuL1192b+XNauMYWThz2kweiBVW1DqloRpVvZIJkIoHVB7XRpK78n1xbQ==
-karma-jasmine@~4.0.0:
- version "4.0.2"
- resolved "https://registry.npmjs.org/karma-jasmine/-/karma-jasmine-4.0.2.tgz#386db2a3e1acc0af5265c711f673f78f1e4938de"
- integrity sha512-ggi84RMNQffSDmWSyyt4zxzh2CQGwsxvYYsprgyR1j8ikzIduEdOlcLvXjZGwXG/0j41KUXOWsUCBfbEHPWP9g==
+karma-jasmine@~5.1.0:
+ version "5.1.0"
+ resolved "https://registry.yarnpkg.com/karma-jasmine/-/karma-jasmine-5.1.0.tgz#3af4558a6502fa16856a0f346ec2193d4b884b2f"
+ integrity sha512-i/zQLFrfEpRyQoJF9fsCdTMOF5c2dK7C7OmsuKg2D0YSsuZSfQDiLuaiktbuio6F2wiCsZSnSnieIQ0ant/uzQ==
dependencies:
- jasmine-core "^3.6.0"
+ jasmine-core "^4.1.0"
karma-source-map-support@1.4.0:
version "1.4.0"
@@ -8454,10 +8461,10 @@ karma-source-map-support@1.4.0:
dependencies:
source-map-support "^0.5.5"
-karma@~6.4.0:
- version "6.4.1"
- resolved "https://registry.npmjs.org/karma/-/karma-6.4.1.tgz#f2253716dd3a41aaa813fa9f54b6ee047e1127d9"
- integrity sha512-Cj57NKOskK7wtFWSlMvZf459iX+kpYIPXmkNUzP2WAFcA7nhr/ALn5R7sw3w+1udFDcpMx/tuB8d5amgm3ijaA==
+karma@~6.4.3:
+ version "6.4.3"
+ resolved "https://registry.yarnpkg.com/karma/-/karma-6.4.3.tgz#763e500f99597218bbb536de1a14acc4ceea7ce8"
+ integrity sha512-LuucC/RE92tJ8mlCwqEoRWXP38UMAqpnq98vktmS9SznSoUPPUJQbc91dHcxcunROvfQjdORVA/YFviH+Xci9Q==
dependencies:
"@colors/colors" "1.5.0"
body-parser "^1.19.0"
@@ -8478,7 +8485,7 @@ karma@~6.4.0:
qjobs "^1.2.0"
range-parser "^1.2.1"
rimraf "^3.0.2"
- socket.io "^4.4.1"
+ socket.io "^4.7.2"
source-map "^0.6.1"
tmp "^0.2.1"
ua-parser-js "^0.7.30"
@@ -8652,6 +8659,11 @@ lodash.get@~4.4.2:
resolved "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99"
integrity sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==
+lodash.mergewith@^4.6.2:
+ version "4.6.2"
+ resolved "https://registry.yarnpkg.com/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz#617121f89ac55f59047c7aec1ccd6654c6590f55"
+ integrity sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==
+
lodash@^4.17.20, lodash@^4.17.21:
version "4.17.21"
resolved "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
@@ -11349,30 +11361,34 @@ smart-buffer@^4.2.0:
resolved "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz#6e1d71fa4f18c05f7d0ff216dd16a481d0e8d9ae"
integrity sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==
-socket.io-adapter@~2.4.0:
- version "2.4.0"
- resolved "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.4.0.tgz#b50a4a9ecdd00c34d4c8c808224daa1a786152a6"
- integrity sha512-W4N+o69rkMEGVuk2D/cvca3uYsvGlMwsySWV447y99gUPghxq42BxqLNMndb+a1mm/5/7NeXVQS7RLa2XyXvYg==
+socket.io-adapter@~2.5.2:
+ version "2.5.4"
+ resolved "https://registry.yarnpkg.com/socket.io-adapter/-/socket.io-adapter-2.5.4.tgz#4fdb1358667f6d68f25343353bd99bd11ee41006"
+ integrity sha512-wDNHGXGewWAjQPt3pyeYBtpWSq9cLE5UW1ZUPL/2eGK9jtse/FpXib7epSTsz0Q0m+6sg6Y4KtcFTlah1bdOVg==
+ dependencies:
+ debug "~4.3.4"
+ ws "~8.11.0"
-socket.io-parser@~4.2.1:
- version "4.2.2"
- resolved "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.2.tgz#1dd384019e25b7a3d374877f492ab34f2ad0d206"
- integrity sha512-DJtziuKypFkMMHCm2uIshOYC7QaylbtzQwiMYDuCKy3OPkjLzu4B2vAhTlqipRHHzrI0NJeBAizTK7X+6m1jVw==
+socket.io-parser@~4.2.4:
+ version "4.2.4"
+ resolved "https://registry.yarnpkg.com/socket.io-parser/-/socket.io-parser-4.2.4.tgz#c806966cf7270601e47469ddeec30fbdfda44c83"
+ integrity sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==
dependencies:
"@socket.io/component-emitter" "~3.1.0"
debug "~4.3.1"
-socket.io@^4.4.1:
- version "4.5.4"
- resolved "https://registry.npmjs.org/socket.io/-/socket.io-4.5.4.tgz#a4513f06e87451c17013b8d13fdfaf8da5a86a90"
- integrity sha512-m3GC94iK9MfIEeIBfbhJs5BqFibMtkRk8ZpKwG2QwxV0m/eEhPIV4ara6XCF1LWNAus7z58RodiZlAH71U3EhQ==
+socket.io@^4.7.2:
+ version "4.7.4"
+ resolved "https://registry.yarnpkg.com/socket.io/-/socket.io-4.7.4.tgz#2401a2d7101e4bdc64da80b140d5d8b6a8c7738b"
+ integrity sha512-DcotgfP1Zg9iP/dH9zvAQcWrE0TtbMVwXmlV4T4mqsvY+gw+LqUGPfx2AoVyRk0FLME+GQhufDMyacFmw7ksqw==
dependencies:
accepts "~1.3.4"
base64id "~2.0.0"
+ cors "~2.8.5"
debug "~4.3.2"
- engine.io "~6.2.1"
- socket.io-adapter "~2.4.0"
- socket.io-parser "~4.2.1"
+ engine.io "~6.5.2"
+ socket.io-adapter "~2.5.2"
+ socket.io-parser "~4.2.4"
sockjs@^0.3.24:
version "0.3.24"
@@ -12778,10 +12794,10 @@ ws@^8.4.2:
resolved "https://registry.npmjs.org/ws/-/ws-8.12.0.tgz#485074cc392689da78e1828a9ff23585e06cddd8"
integrity sha512-kU62emKIdKVeEIOIKVegvqpXMSTAMLJozpHZaJNDYqBjzlSYXQGviYwN1osDLJ9av68qHd4a2oSjd7yD4pacig==
-ws@~8.2.3:
- version "8.2.3"
- resolved "https://registry.npmjs.org/ws/-/ws-8.2.3.tgz#63a56456db1b04367d0b721a0b80cae6d8becbba"
- integrity sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA==
+ws@~8.11.0:
+ version "8.11.0"
+ resolved "https://registry.yarnpkg.com/ws/-/ws-8.11.0.tgz#6a0d36b8edfd9f96d8b25683db2f8d7de6e8e143"
+ integrity sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==
xml2js@^0.4.17:
version "0.4.23"