From d0ffbd5955f34755d077ff8d57e2e5c22ceafe8b Mon Sep 17 00:00:00 2001 From: Ashesh <3626859+Ashesh3@users.noreply.github.com> Date: Tue, 12 Sep 2023 17:40:58 +0530 Subject: [PATCH 1/7] Refactor workflow to properly wait for migrations (#6272) * Refactor workflow to properly wait for migrations * use while * Fix command and remove cypress videos --- .github/workflows/cypress.yaml | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/.github/workflows/cypress.yaml b/.github/workflows/cypress.yaml index f66f9a37bd8..130f360d2ef 100644 --- a/.github/workflows/cypress.yaml +++ b/.github/workflows/cypress.yaml @@ -24,14 +24,22 @@ jobs: repository: coronasafe/care path: care - - name: Run docker compose up on care 🐳 + - name: Start care docker containers 🐳 run: | - cd care + cd care make docker_config_file=docker-compose.pre-built.yaml up - sleep 60s + while docker compose exec backend bash -c "python manage.py showmigrations 2>/dev/null | cat | grep -q '\[ \]'"; do + >&2 echo "Migrations are not yet applied - sleeping" + sleep 5 + done + echo "Migrations are applied" + cd .. + + - name: Load dummy data into care backend 📂 + run: | + cd care docker compose exec backend bash -c "python manage.py load_dummy_data" cd .. - # Voluntarily kept 60 seconds delay to wait for migrations to complete. - name: Check care is up ♻ run: curl -o /dev/null -s -w "%{http_code}\n" http://localhost:9000 @@ -93,10 +101,3 @@ jobs: name: cypress-screenshots path: cypress/screenshots - # Test run video was always captured, so this action uses "always()" condition - - name: Upload cypress videos 📹 - uses: actions/upload-artifact@v3 - if: always() - with: - name: cypress-videos - path: cypress/videos From 8c242d650395990170ef5ee21b8a104c89bf4f51 Mon Sep 17 00:00:00 2001 From: Ashesh <3626859+Ashesh3@users.noreply.github.com> Date: Tue, 12 Sep 2023 19:21:04 +0530 Subject: [PATCH 2/7] Asset CSV export (#6262) --- src/Common/constants.tsx | 2 +- src/Components/Assets/AssetsList.tsx | 20 ++++++++++++++++++-- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/src/Common/constants.tsx b/src/Common/constants.tsx index e8ab4867764..72a939b92fd 100644 --- a/src/Common/constants.tsx +++ b/src/Common/constants.tsx @@ -993,7 +993,7 @@ export const XLSXAssetImportSchema = { return ip; }, }, - "Config: Camera Access Key": { + "Config - Camera Access Key": { prop: "camera_access_key", type: String, }, diff --git a/src/Components/Assets/AssetsList.tsx b/src/Components/Assets/AssetsList.tsx index 6183a47783c..3789d7cfd51 100644 --- a/src/Components/Assets/AssetsList.tsx +++ b/src/Components/Assets/AssetsList.tsx @@ -324,7 +324,7 @@ const AssetsList = () => { }, }, { - label: "Export Assets", + label: "Export Assets (JSON)", action: () => authorizedForImportExport && listAssets({ @@ -333,7 +333,23 @@ const AssetsList = () => { limit: totalCount, }), type: "json", - filePrefix: `assets_${facility?.name}`, + filePrefix: `assets_${facility?.name ?? "all"}`, + options: { + icon: , + disabled: totalCount === 0 || !authorizedForImportExport, + }, + }, + { + label: "Export Assets (CSV)", + action: () => + authorizedForImportExport && + listAssets({ + ...qParams, + csv: true, + limit: totalCount, + }), + type: "csv", + filePrefix: `assets_${facility?.name ?? "all"}`, options: { icon: , disabled: totalCount === 0 || !authorizedForImportExport, From 87f36ba0332f69fce09ef08dc182449513fa2059 Mon Sep 17 00:00:00 2001 From: Rithvik Nishad Date: Tue, 12 Sep 2023 19:24:12 +0530 Subject: [PATCH 3/7] add readmission (#6239) --- src/Components/Facility/ConsultationCard.tsx | 10 ++++++++++ src/Components/Facility/models.tsx | 1 + src/Components/Patient/ManagePatients.tsx | 9 +++++++++ src/Components/Patient/PatientInfoCard.tsx | 17 +++++++++++++---- 4 files changed, 33 insertions(+), 4 deletions(-) diff --git a/src/Components/Facility/ConsultationCard.tsx b/src/Components/Facility/ConsultationCard.tsx index 467729f0f73..f6b4484b477 100644 --- a/src/Components/Facility/ConsultationCard.tsx +++ b/src/Components/Facility/ConsultationCard.tsx @@ -5,6 +5,7 @@ import ButtonV2 from "../Common/components/ButtonV2"; import { NonReadOnlyUsers } from "../../Utils/AuthorizeFor"; import RelativeDateUserMention from "../Common/RelativeDateUserMention"; import useConfig from "../../Common/hooks/useConfig"; +import Chip from "../../CAREUI/display/Chip"; interface ConsultationProps { itemData: ConsultationModel; @@ -70,6 +71,15 @@ export const ConsultationCard = (props: ConsultationProps) => { {formatDateTime(itemData.admission_date)} + {itemData.is_readmission && ( + + )} diff --git a/src/Components/Facility/models.tsx b/src/Components/Facility/models.tsx index 012ca1d68b2..cc34140e7a9 100644 --- a/src/Components/Facility/models.tsx +++ b/src/Components/Facility/models.tsx @@ -142,6 +142,7 @@ export interface ConsultationModel { cause_of_death?: string; death_datetime?: string; death_confirmed_doctor?: string; + is_readmission?: boolean; } export interface PatientStatsModel { id?: number; diff --git a/src/Components/Patient/ManagePatients.tsx b/src/Components/Patient/ManagePatients.tsx index b29c112c5a1..54d22b5f54c 100644 --- a/src/Components/Patient/ManagePatients.tsx +++ b/src/Components/Patient/ManagePatients.tsx @@ -603,6 +603,15 @@ export const PatientManager = () => { text="Review Missed" /> )} + {patient.last_consultation?.is_readmission && ( + + )} {patient.disease_status === "POSITIVE" && ( )} - + • - - Domiciliary Care{" "} - + + + Domiciliary Care + + > + )} + {consultation?.is_readmission && ( + <> + • + + + Readmitted > )} From 3fdf1d503d7f2ad7e46e03e5fc0e1bf87bf26426 Mon Sep 17 00:00:00 2001 From: "Tasnimul H. Tauhid" Date: Tue, 12 Sep 2023 19:25:19 +0530 Subject: [PATCH 4/7] Add Consultation button is now conditionally active (#6260) * Add Consultation button is now conditionally active * updated logic for add consultation button to be active --- src/Components/Patient/PatientHome.tsx | 31 +++++++++++++++++++------- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/src/Components/Patient/PatientHome.tsx b/src/Components/Patient/PatientHome.tsx index 34e5ed1cb01..3d39f377462 100644 --- a/src/Components/Patient/PatientHome.tsx +++ b/src/Components/Patient/PatientHome.tsx @@ -374,6 +374,13 @@ export const PatientHome = (props: any) => { ); }; + const isPatientEligibleForNewConsultation = (patientData: PatientModel) => { + return !patientData.last_consultation || + patientData.last_consultation?.discharge_date + ? true + : false; + }; + return ( { - patientData.is_active && - (!patientData?.last_consultation || - patientData?.last_consultation?.discharge_date) && + isPatientEligibleForNewConsultation(patientData) && navigate( `/facility/${patientData?.facility}/patient/${id}/consultation` ) } > - - + + From 8a704af2abe62a73cfa4e27d80fc0c573c30163e Mon Sep 17 00:00:00 2001 From: yaswanth sai vendra <75136628+yaswanthsaivendra@users.noreply.github.com> Date: Tue, 12 Sep 2023 19:26:13 +0530 Subject: [PATCH 5/7] Added None option in admitted_to bed filters so that we can filter for patient who are admitted but no bed assigned (#5550) * added None option in admitted_to bed filters * changed the id from 8 to None * make text user friendly --------- Co-authored-by: Rithvik Nishad --- src/Common/constants.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Common/constants.tsx b/src/Common/constants.tsx index 72a939b92fd..f5801e97e4b 100644 --- a/src/Common/constants.tsx +++ b/src/Common/constants.tsx @@ -345,6 +345,7 @@ export const ADMITTED_TO = [ { id: "2", text: "ICU" }, { id: "6", text: "Bed with oxygen support" }, { id: "7", text: "Regular" }, + { id: "None", text: "No bed assigned" }, ]; export const RESPIRATORY_SUPPORT = [ From f72a1cf12f6ba80bf03c1df6ce5cfb2c90fc150c Mon Sep 17 00:00:00 2001 From: Mohammed Nihal <57055998+nihal467@users.noreply.github.com> Date: Wed, 13 Sep 2023 07:49:18 +0530 Subject: [PATCH 6/7] verify asset filter (#6275) --- cypress/e2e/assets_spec/asset_homepage.cy.ts | 12 +++++- cypress/pageobject/Asset/AssetFilters.ts | 39 +++++++++++++++++++- cypress/pageobject/Asset/AssetSearch.ts | 4 +- src/CAREUI/interactive/SlideOver.tsx | 1 + src/Components/Assets/AssetFilter.tsx | 2 +- 5 files changed, 54 insertions(+), 4 deletions(-) diff --git a/cypress/e2e/assets_spec/asset_homepage.cy.ts b/cypress/e2e/assets_spec/asset_homepage.cy.ts index 6e9ceb9676b..fa24adf5b21 100644 --- a/cypress/e2e/assets_spec/asset_homepage.cy.ts +++ b/cypress/e2e/assets_spec/asset_homepage.cy.ts @@ -62,8 +62,18 @@ describe("Asset Tab", () => { "Dummy Facility 1", "INTERNAL", "ACTIVE", - "ONVIF Camera" + "ONVIF Camera", + "Camera Loc" ); + assetFilters.clickadvancefilter(); + assetFilters.clickslideoverbackbutton(); // to verify the back button doesn't clear applied filters + assetFilters.assertFacilityText("Dummy Facility 1"); + assetFilters.assertAssetTypeText("INTERNAL"); + assetFilters.assertAssetClassText("ONVIF"); + assetFilters.assertStatusText("ACTIVE"); + assetFilters.assertLocationText("Camera Locations"); + assetFilters.clickadvancefilter(); + assetFilters.clearFilters(); }); // Verify the pagination in the page diff --git a/cypress/pageobject/Asset/AssetFilters.ts b/cypress/pageobject/Asset/AssetFilters.ts index 57cc893bdb7..a16b61f4fc5 100644 --- a/cypress/pageobject/Asset/AssetFilters.ts +++ b/cypress/pageobject/Asset/AssetFilters.ts @@ -3,7 +3,8 @@ export class AssetFilters { facilityName: string, assetType: string, assetStatus: string, - assetClass: string + assetClass: string, + assetLocation: string ) { cy.contains("Advanced Filters").click(); cy.get("input[name='Facilities']") @@ -27,6 +28,42 @@ export class AssetFilters { .then(() => { cy.get("[role='option']").contains(assetClass).click(); }); + cy.get("#Facilities-location") + .click() + .type(assetLocation) + .then(() => { + cy.get("[role='option']").contains(assetLocation).click(); + }); cy.contains("Apply").click(); } + clearFilters() { + cy.intercept("GET", "**/api/v1/asset/**").as("clearAssets"); + cy.get("#clear-filter").click(); + cy.wait("@clearAssets").its("response.statusCode").should("eq", 200); + cy.url().should("match", /\/assets$/); + } + clickadvancefilter() { + cy.intercept("GET", "**/api/v1/getallfacilities/**").as("advancefilter"); + cy.get("#advanced-filter").click(); + cy.wait("@advancefilter").its("response.statusCode").should("eq", 200); + } + clickslideoverbackbutton() { + cy.get("#close-slide-over").click(); + } + // Assertions + assertFacilityText(text) { + cy.get("[data-testid=Facility]").should("contain", text); + } + assertAssetTypeText(text) { + cy.get("[data-testid='Asset Type']").should("contain", text); + } + assertAssetClassText(text) { + cy.get("[data-testid='Asset Class']").should("contain", text); + } + assertStatusText(text) { + cy.get("[data-testid=Status]").should("contain", text); + } + assertLocationText(text) { + cy.get("[data-testid=Location]").should("contain", text); + } } diff --git a/cypress/pageobject/Asset/AssetSearch.ts b/cypress/pageobject/Asset/AssetSearch.ts index fea6a975983..b1ccb2f71c9 100644 --- a/cypress/pageobject/Asset/AssetSearch.ts +++ b/cypress/pageobject/Asset/AssetSearch.ts @@ -1,6 +1,6 @@ export class AssetSearchPage { typeSearchKeyword(keyword: string) { - cy.get("#search").clear(); + cy.get("#search").click().clear(); cy.get("#search").click().type(keyword); } @@ -9,7 +9,9 @@ export class AssetSearchPage { } clickAssetByName(assetName: string) { + cy.intercept("GET", "**/api/v1/asset/**").as("clearAssets"); cy.get("[data-testid='created-asset-list']").contains(assetName).click(); + cy.wait("@clearAssets").its("response.statusCode").should("eq", 200); } verifyBadgeContent(expectedText: string) { diff --git a/src/CAREUI/interactive/SlideOver.tsx b/src/CAREUI/interactive/SlideOver.tsx index 3eae13869cb..34e1c615f94 100644 --- a/src/CAREUI/interactive/SlideOver.tsx +++ b/src/CAREUI/interactive/SlideOver.tsx @@ -109,6 +109,7 @@ export default function SlideOver({ > { setOpen(false); diff --git a/src/Components/Assets/AssetFilter.tsx b/src/Components/Assets/AssetFilter.tsx index 299d67e5ab0..4fc4ff6d7f2 100644 --- a/src/Components/Assets/AssetFilter.tsx +++ b/src/Components/Assets/AssetFilter.tsx @@ -148,7 +148,7 @@ function AssetFilter(props: any) { Location handleLocationSelect((selectedId as string) || "") } From cc6b570d3f0b1e912484fa66d02ef12012c59d44 Mon Sep 17 00:00:00 2001 From: Rithvik Nishad Date: Wed, 13 Sep 2023 14:50:28 +0530 Subject: [PATCH 7/7] Vitals Monitor (HL7): Add support to detect multiple ecg wavenames (#6274) * fixes #6198; add support to detect multiple ecg wavenames * Update HL7DeviceClient.ts * fix heart rate pulse rate fallback --- .../VitalsMonitor/HL7DeviceClient.ts | 34 +++++++++++++++---- .../VitalsMonitor/HL7PatientVitalsMonitor.tsx | 2 +- 2 files changed, 29 insertions(+), 7 deletions(-) diff --git a/src/Components/VitalsMonitor/HL7DeviceClient.ts b/src/Components/VitalsMonitor/HL7DeviceClient.ts index 72f6406193a..7e13622b31f 100644 --- a/src/Components/VitalsMonitor/HL7DeviceClient.ts +++ b/src/Components/VitalsMonitor/HL7DeviceClient.ts @@ -1,11 +1,31 @@ import { EventEmitter } from "events"; import { VitalsDataBase, VitalsValueBase, VitalsWaveformBase } from "./types"; -const WAVEFORM_KEY_MAP: Record = { - II: "ecg-waveform", - Pleth: "pleth-waveform", - Respiration: "spo2-waveform", -}; +const ECG_WAVENAME_KEYS = [ + "I", + "II", + "III", + "aVR", + "aVL", + "aVF", + "V1", + "V2", + "V3", + "V4", + "V5", + "V6", +] as const; + +const WAVEFORM_KEY_MAP: Record = + { + Pleth: "pleth-waveform", + Respiration: "spo2-waveform", + + // Maps each ECG wave name to the event "ecg-waveform" + ...(Object.fromEntries( + ECG_WAVENAME_KEYS.map((key) => [key, "ecg-waveform"]) + ) as Record), + }; /** * Provides the API for connecting to the Vitals Monitor WebSocket and emitting @@ -74,8 +94,10 @@ export interface HL7VitalsValueData extends VitalsDataBase, VitalsValueBase { | "body-temperature2"; } +type EcgWaveName = (typeof ECG_WAVENAME_KEYS)[number]; + export interface HL7VitalsWaveformData extends VitalsWaveformBase { - "wave-name": "II" | "Pleth" | "Respiration"; + "wave-name": EcgWaveName | "Pleth" | "Respiration"; } export interface HL7VitalsBloodPressureData extends VitalsDataBase { diff --git a/src/Components/VitalsMonitor/HL7PatientVitalsMonitor.tsx b/src/Components/VitalsMonitor/HL7PatientVitalsMonitor.tsx index a340c21e28b..c0b089df8b0 100644 --- a/src/Components/VitalsMonitor/HL7PatientVitalsMonitor.tsx +++ b/src/Components/VitalsMonitor/HL7PatientVitalsMonitor.tsx @@ -88,7 +88,7 @@ export default function HL7PatientVitalsMonitor(props: IVitalsComponentProps) { {/* Pulse Rate */} ❤️