From 43fbe8a8e91335ca07dc9cde3dc3a0e28c6a8f7b Mon Sep 17 00:00:00 2001 From: clintonium-119 Date: Mon, 5 Aug 2024 10:34:21 -0400 Subject: [PATCH 001/112] Bump viz and tideline --- package.json | 4 ++-- yarn.lock | 20 ++++++++++---------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/package.json b/package.json index f875e1a45f..1d029671b4 100644 --- a/package.json +++ b/package.json @@ -66,7 +66,7 @@ "@storybook/react": "7.5.0", "@storybook/react-webpack5": "7.5.0", "@testing-library/react-hooks": "8.0.1", - "@tidepool/viz": "1.42.0-web-3005-web-3306-web-3007-web-3008-loop-stats.2", + "@tidepool/viz": "1.42.0-rc.1", "async": "2.6.4", "autoprefixer": "10.4.16", "babel-core": "7.0.0-bridge.0", @@ -184,7 +184,7 @@ "terser": "5.22.0", "terser-webpack-plugin": "5.3.9", "theme-ui": "0.16.1", - "tideline": "1.30.0-web-3005-web-3306-web-3007-web-3008-loop-stats.1", + "tideline": "1.30.0-rc.1", "tidepool-platform-client": "0.59.0", "tidepool-standard-action": "0.1.1", "ua-parser-js": "1.0.36", diff --git a/yarn.lock b/yarn.lock index 55bec0c20c..e09c5383d8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5268,9 +5268,9 @@ __metadata: languageName: node linkType: hard -"@tidepool/viz@npm:1.42.0-web-3005-web-3306-web-3007-web-3008-loop-stats.2": - version: 1.42.0-web-3005-web-3306-web-3007-web-3008-loop-stats.2 - resolution: "@tidepool/viz@npm:1.42.0-web-3005-web-3306-web-3007-web-3008-loop-stats.2" +"@tidepool/viz@npm:1.42.0-rc.1": + version: 1.42.0-rc.1 + resolution: "@tidepool/viz@npm:1.42.0-rc.1" dependencies: bluebird: 3.7.2 bows: 1.7.2 @@ -5330,7 +5330,7 @@ __metadata: react-dom: 16.x react-redux: 8.x redux: 4.x - checksum: d64737a66f6f59d7df463e34035e8b7584ec0d37c4a71ae1224598a072999ec13c5ef43699fa9e7b077e33b41783ffa398a71b7a68cd448cde188b7e4809e88a + checksum: 40087a7188186a2471314d0714b55a7be8186ae4e8fcaad8a2acbf2ca181cbc0f73b9e9d282b9b62ac98bd2fafc9efeabc20947ba012f72ce388ca272df7db1d languageName: node linkType: hard @@ -7427,7 +7427,7 @@ __metadata: "@storybook/react": 7.5.0 "@storybook/react-webpack5": 7.5.0 "@testing-library/react-hooks": 8.0.1 - "@tidepool/viz": 1.42.0-web-3005-web-3306-web-3007-web-3008-loop-stats.2 + "@tidepool/viz": 1.42.0-rc.1 async: 2.6.4 autoprefixer: 10.4.16 babel-core: 7.0.0-bridge.0 @@ -7552,7 +7552,7 @@ __metadata: terser: 5.22.0 terser-webpack-plugin: 5.3.9 theme-ui: 0.16.1 - tideline: 1.30.0-web-3005-web-3306-web-3007-web-3008-loop-stats.1 + tideline: 1.30.0-rc.1 tidepool-platform-client: 0.59.0 tidepool-standard-action: 0.1.1 ua-parser-js: 1.0.36 @@ -22992,9 +22992,9 @@ __metadata: languageName: node linkType: hard -"tideline@npm:1.30.0-web-3005-web-3306-web-3007-web-3008-loop-stats.1": - version: 1.30.0-web-3005-web-3306-web-3007-web-3008-loop-stats.1 - resolution: "tideline@npm:1.30.0-web-3005-web-3306-web-3007-web-3008-loop-stats.1" +"tideline@npm:1.30.0-rc.1": + version: 1.30.0-rc.1 + resolution: "tideline@npm:1.30.0-rc.1" dependencies: bows: 1.7.2 classnames: 2.3.2 @@ -23014,7 +23014,7 @@ __metadata: peerDependencies: babel-core: 6.x || 7.0.0-bridge.0 lodash: ^4.17.21 - checksum: cc11a683c8b29931493de204aa151bfed638d2c485f278207caa7889528fc6a921d74515de255b3331162de8677f2861e7f86699e455d1f3ec8110476e982fec + checksum: 4d642bb5800ea84bc309601c121968175f6a9c841e31b079de204a1c87450973f00ef62fa9a33d73ade951c99ab757c825aa99acade206d6a7b7586d958546c6 languageName: node linkType: hard From 9024b9f37fbc005e05c9fb457ad0916f5878a434 Mon Sep 17 00:00:00 2001 From: clintonium-119 Date: Mon, 5 Aug 2024 10:34:40 -0400 Subject: [PATCH 002/112] v1.81.0-rc.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 1d029671b4..5af26a5033 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "node": "20.8.0" }, "packageManager": "yarn@3.6.4", - "version": "1.81.0-web-3005-web-3306-web-3007-web-3008-loop-stats.1", + "version": "1.81.0-rc.1", "private": true, "scripts": { "test": "TZ=UTC NODE_ENV=test NODE_OPTIONS='--max-old-space-size=4096' yarn karma start", From 0869885aa4b174470ac5039ddd675ad2b8a6fe77 Mon Sep 17 00:00:00 2001 From: clintonium-119 Date: Mon, 5 Aug 2024 13:10:00 -0400 Subject: [PATCH 003/112] Only call generateAGPImagesSucces if no errors were thrown --- app/pages/patientdata/patientdata.js | 4 +- test/unit/pages/patientdata.test.js | 83 ++++++++++++++++------------ 2 files changed, 52 insertions(+), 35 deletions(-) diff --git a/app/pages/patientdata/patientdata.js b/app/pages/patientdata/patientdata.js index 2f6e645fbc..65eaf371b7 100644 --- a/app/pages/patientdata/patientdata.js +++ b/app/pages/patientdata/patientdata.js @@ -920,6 +920,7 @@ export const PatientDataClass = createReactClass({ generateAGPImages: async function(props = this.props, reportTypes = []) { const promises = []; + let errored = false await _.each(reportTypes, async reportType => { let images; @@ -927,6 +928,7 @@ export const PatientDataClass = createReactClass({ try{ images = await vizUtils.agp.generateAGPFigureDefinitions({ ...props.pdf.data?.[reportType] }); } catch(e) { + errored = true return props.generateAGPImagesFailure(e); } @@ -955,7 +957,7 @@ export const PatientDataClass = createReactClass({ }, {}); props.generateAGPImagesSuccess(processedImages); - } else { + } else if (!errored) { props.generateAGPImagesSuccess(results); } }, diff --git a/test/unit/pages/patientdata.test.js b/test/unit/pages/patientdata.test.js index f9fed40d53..94bde09066 100644 --- a/test/unit/pages/patientdata.test.js +++ b/test/unit/pages/patientdata.test.js @@ -3332,23 +3332,6 @@ describe('PatientData', function () { describe('generateAGPImages', () => { let wrapper; let instance; - before(() => { - PD.__Rewire__('vizUtils', { - agp: { - generateAGPFigureDefinitions: sinon.stub() - .onFirstCall().resolves(['stubbed image data']) - .onSecondCall().rejects(new Error('failed image generation')), - }, - }); - PD.__Rewire__('Plotly', { - toImage: sinon.stub().returns('stubbed image data') - }); - }); - - after(() => { - PD.__ResetDependency__('vizUtils'); - PD.__ResetDependency__('Plotly'); - }); beforeEach(() => { wrapper = shallow(); @@ -3357,27 +3340,59 @@ describe('PatientData', function () { defaultProps.generateAGPImagesFailure.resetHistory(); }); - it('should call generateAGPImagesSuccess with image data upon successful image generation', done => { - instance.generateAGPImages(undefined, ['agpCGM']); - wrapper.update(); - setTimeout(() => { - sinon.assert.callCount(defaultProps.generateAGPImagesFailure, 0); - sinon.assert.callCount(defaultProps.generateAGPImagesSuccess, 1); - sinon.assert.calledWithMatch(defaultProps.generateAGPImagesSuccess, { - agpCGM: { 0: 'stubbed image data' }, + context('successful image generation', () => { + before(() => { + PD.__Rewire__('vizUtils', { + agp: { + generateAGPFigureDefinitions: sinon.stub().resolves(['stubbed image data']), + }, + }); + PD.__Rewire__('Plotly', { + toImage: sinon.stub().returns('stubbed image data') + }); + }); + + after(() => { + PD.__ResetDependency__('vizUtils'); + PD.__ResetDependency__('Plotly'); + }); + + it('should call generateAGPImagesSuccess with image data upon successful image generation', done => { + instance.generateAGPImages(undefined, ['agpCGM']); + wrapper.update(); + setTimeout(() => { + sinon.assert.callCount(defaultProps.generateAGPImagesFailure, 0); + sinon.assert.callCount(defaultProps.generateAGPImagesSuccess, 1); + sinon.assert.calledWithMatch(defaultProps.generateAGPImagesSuccess, { + agpCGM: { 0: 'stubbed image data' }, + }); + done(); }); - done(); }); }); - it('should call generateAGPImagesFailure with error upon failed image generation', done => { - instance.generateAGPImages(undefined, ['agpCGM']); - wrapper.update(); - setTimeout(() => { - sinon.assert.callCount(defaultProps.generateAGPImagesSuccess, 0); - sinon.assert.callCount(defaultProps.generateAGPImagesFailure, 1); - expect(defaultProps.generateAGPImagesFailure.getCall(0).lastArg.message).to.equal('failed image generation'); - done(); + context('failed image generation', () => { + before(() => { + PD.__Rewire__('vizUtils', { + agp: { + generateAGPFigureDefinitions: sinon.stub().rejects(new Error('failed image generation')), + }, + }); + }); + + after(() => { + PD.__ResetDependency__('vizUtils'); + }); + + it('should call generateAGPImagesFailure with error upon failed image generation', done => { + instance.generateAGPImages(undefined, ['agpCGM']); + wrapper.update(); + setTimeout(() => { + sinon.assert.callCount(defaultProps.generateAGPImagesSuccess, 0); + sinon.assert.callCount(defaultProps.generateAGPImagesFailure, 1); + expect(defaultProps.generateAGPImagesFailure.getCall(0).lastArg.message).to.equal('failed image generation'); + done(); + }); }); }); }); From 6332ee8cd919ae02c7613b9534c25f57bd0bd047 Mon Sep 17 00:00:00 2001 From: clintonium-119 Date: Mon, 5 Aug 2024 16:23:43 -0400 Subject: [PATCH 004/112] Bump tideline --- package.json | 2 +- yarn.lock | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 5af26a5033..876af45197 100644 --- a/package.json +++ b/package.json @@ -184,7 +184,7 @@ "terser": "5.22.0", "terser-webpack-plugin": "5.3.9", "theme-ui": "0.16.1", - "tideline": "1.30.0-rc.1", + "tideline": "1.30.0-rc.2", "tidepool-platform-client": "0.59.0", "tidepool-standard-action": "0.1.1", "ua-parser-js": "1.0.36", diff --git a/yarn.lock b/yarn.lock index e09c5383d8..7eea8d1196 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7552,7 +7552,7 @@ __metadata: terser: 5.22.0 terser-webpack-plugin: 5.3.9 theme-ui: 0.16.1 - tideline: 1.30.0-rc.1 + tideline: 1.30.0-rc.2 tidepool-platform-client: 0.59.0 tidepool-standard-action: 0.1.1 ua-parser-js: 1.0.36 @@ -22992,9 +22992,9 @@ __metadata: languageName: node linkType: hard -"tideline@npm:1.30.0-rc.1": - version: 1.30.0-rc.1 - resolution: "tideline@npm:1.30.0-rc.1" +"tideline@npm:1.30.0-rc.2": + version: 1.30.0-rc.2 + resolution: "tideline@npm:1.30.0-rc.2" dependencies: bows: 1.7.2 classnames: 2.3.2 @@ -23014,7 +23014,7 @@ __metadata: peerDependencies: babel-core: 6.x || 7.0.0-bridge.0 lodash: ^4.17.21 - checksum: 4d642bb5800ea84bc309601c121968175f6a9c841e31b079de204a1c87450973f00ef62fa9a33d73ade951c99ab757c825aa99acade206d6a7b7586d958546c6 + checksum: bc0c370495110766a1e54b645195dd3801c89c051a8cc0e8ae340c0d7851bdf3bb41492f9593c2383cb1bbcc00073f20a22dd1f978c4b36ce7ce54efff9b82f6 languageName: node linkType: hard From 51e6914ac5b04550e3b7393a8e2c8bfa3059164e Mon Sep 17 00:00:00 2001 From: clintonium-119 Date: Mon, 5 Aug 2024 16:23:47 -0400 Subject: [PATCH 005/112] v1.81.0-rc.2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 876af45197..57d559a3b2 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "node": "20.8.0" }, "packageManager": "yarn@3.6.4", - "version": "1.81.0-rc.1", + "version": "1.81.0-rc.2", "private": true, "scripts": { "test": "TZ=UTC NODE_ENV=test NODE_OPTIONS='--max-old-space-size=4096' yarn karma start", From 8eb565175fb56bf9dde8d05fbed879f806b74cfc Mon Sep 17 00:00:00 2001 From: clintonium-119 Date: Wed, 7 Aug 2024 09:29:13 -0400 Subject: [PATCH 006/112] v1.81.0-rc.3 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 57d559a3b2..49d24edf4f 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "node": "20.8.0" }, "packageManager": "yarn@3.6.4", - "version": "1.81.0-rc.2", + "version": "1.81.0-rc.3", "private": true, "scripts": { "test": "TZ=UTC NODE_ENV=test NODE_OPTIONS='--max-old-space-size=4096' yarn karma start", From 57eaf00afeda50043d9e43609d25b788148297eb Mon Sep 17 00:00:00 2001 From: clintonium-119 Date: Fri, 9 Aug 2024 13:22:31 -0400 Subject: [PATCH 007/112] Prefer latest diabetes datum timezoneOffset for determining timezone to display in. --- app/core/utils.js | 35 ++++++++----- app/pages/patientdata/patientdata.js | 12 ++++- test/unit/utils/utils.test.js | 77 +++++++++++++++------------- 3 files changed, 75 insertions(+), 49 deletions(-) diff --git a/app/core/utils.js b/app/core/utils.js index f4400ddb3e..f6b2ae8bad 100644 --- a/app/core/utils.js +++ b/app/core/utils.js @@ -292,7 +292,7 @@ utils.roundBgTarget = (value, units) => { return parseFloat((nearest * Math.round(value / nearest)).toFixed(precision)); } -utils.getTimePrefsForDataProcessing = (latestUpload, queryParams) => { +utils.getTimePrefsForDataProcessing = (latestUpload, latestDiabetesDatum, queryParams) => { var timePrefsForTideline; var browserTimezone = new Intl.DateTimeFormat().resolvedOptions().timeZone; @@ -331,22 +331,33 @@ utils.getTimePrefsForDataProcessing = (latestUpload, queryParams) => { setNewTimePrefs(queryParams.timezone, false); console.log('Displaying in timezone from query params:', queryParams.timezone); } - else if (!_.isEmpty(latestUpload) && (!_.isEmpty(latestUpload.timezone) || _.isFinite(latestUpload.timezoneOffset))) { - let timezone = latestUpload.timezone; - - // If timezone is empty, set to the nearest Etc/GMT timezone using the timezoneOffset - if (_.isEmpty(timezone)) { + else if (_.isFinite(latestDiabetesDatum?.timezoneOffset)) { + // If the timeone on the latest upload record at the time of the latest diabetes datum has the + // same UTC offset, we use that, since it will also have DST changeover info available. + if (!_.isEmpty(latestUpload?.timezone)) { + const uploadTimezoneOffsetAtLatestDiabetesTime = moment.utc(latestDiabetesDatum.normalTime).tz(latestUpload.timezone).utcOffset(); + if (uploadTimezoneOffsetAtLatestDiabetesTime === latestDiabetesDatum.timezoneOffset) { + setNewTimePrefs(latestUpload.timezone) + console.log('Defaulting to display in timezone of most recent upload at', latestUpload.normalTime, latestUpload.timezone); + } + } + // Otherwise, we calculate the nearest 'Etc/GMT' timezone from the timezone offset of the latest diabetes datum. + if(!timePrefsForTideline) { // GMT offsets signs in Etc/GMT timezone names are reversed from the actual offset - const offsetSign = Math.sign(latestUpload.timezoneOffset) === -1 ? '+' : '-'; - const offsetDuration = moment.duration(Math.abs(latestUpload.timezoneOffset), 'minutes'); + const offsetSign = Math.sign(latestDiabetesDatum.timezoneOffset) === -1 ? '+' : '-'; + const offsetDuration = moment.duration(Math.abs(latestDiabetesDatum.timezoneOffset), 'minutes'); let offsetHours = offsetDuration.hours(); const offsetMinutes = offsetDuration.minutes(); if (offsetMinutes >= 30) offsetHours += 1; - timezone = `Etc/GMT${offsetSign}${offsetHours}`; + const nearestTimezone = `Etc/GMT${offsetSign}${offsetHours}`; + setNewTimePrefs(nearestTimezone); + console.log('Defaulting to display in the nearest timezone of most recent diabetes datum timezone offset at', latestDiabetesDatum.normalTime, nearestTimezone); } - - setNewTimePrefs(timezone); - console.log('Defaulting to display in timezone of most recent upload at', latestUpload.normalTime, timezone); + } + // Fallback to latest upload timezone if there is no diabetes data with timezone offsets + else if (!_.isEmpty(latestUpload) && (!_.isEmpty(latestUpload.timezone))) { + setNewTimePrefs(latestUpload.timezone); + console.log('Defaulting to display in timezone of most recent upload at', latestUpload.normalTime, latestUpload.timezone); } else if (browserTimezone) { setNewTimePrefs(browserTimezone); diff --git a/app/pages/patientdata/patientdata.js b/app/pages/patientdata/patientdata.js index 65eaf371b7..fc08e5736f 100644 --- a/app/pages/patientdata/patientdata.js +++ b/app/pages/patientdata/patientdata.js @@ -59,6 +59,7 @@ import { Box, Flex } from 'theme-ui'; import Checkbox from '../../components/elements/Checkbox'; import PopoverLabel from '../../components/elements/PopoverLabel'; import { Paragraph2 } from '../../components/elements/FontStyles'; +import { DIABETES_DATA_TYPES } from '../../core/constants'; const { Loader } = vizComponents; const { getLocalizedCeiling, getTimezoneFromTimePrefs } = vizUtils.datetime; @@ -1704,7 +1705,16 @@ export const PatientDataClass = createReactClass({ let timePrefs = this.state.timePrefs; if (_.isEmpty(timePrefs)) { const latestUpload = _.get(nextProps, 'data.metaData.latestDatumByType.upload'); - timePrefs = utils.getTimePrefsForDataProcessing(latestUpload, this.props.queryParams); + + const latestDiabetesDatum = _.maxBy( + _.filter( + _.values(_.get(nextProps, 'data.metaData.latestDatumByType', {})), + ({ type }) => _.includes(DIABETES_DATA_TYPES, type) + ), + 'time' + ); + + timePrefs = utils.getTimePrefsForDataProcessing(latestUpload, latestDiabetesDatum, this.props.queryParams); stateUpdates.timePrefs = timePrefs; } diff --git a/test/unit/utils/utils.test.js b/test/unit/utils/utils.test.js index 4d1591698c..43752fa6c3 100644 --- a/test/unit/utils/utils.test.js +++ b/test/unit/utils/utils.test.js @@ -397,14 +397,15 @@ describe('utils', () => { }); describe('getTimePrefsForDataProcessing', () => { - const latestUpload = { type: 'upload', time: '2018-02-02T00:00:00.000Z', timezone: 'US/Pacific' }; + const latestUpload = { type: 'upload', normalTime: '2018-02-02T00:00:00.000Z', timezone: 'US/Pacific' }; + const latestDiabetesDatum = { type: 'cbg', normalTime: '2018-02-02T01:00:00.000Z', timezoneOffset: -480 }; // US/Pacific will have an 8 hour offset (480 mins) at this time of year const queryParams = {}; context('Timezone provided from queryParam', () => { it('should set a valid timezone from a query param', () => { const queryParamsWithValidTimezone = _.assign({}, queryParams, { timezone: 'UTC' }); - expect(utils.getTimePrefsForDataProcessing(latestUpload, queryParamsWithValidTimezone)).to.eql({ + expect(utils.getTimePrefsForDataProcessing(latestUpload, latestDiabetesDatum, queryParamsWithValidTimezone)).to.eql({ timezoneAware: true, timezoneName: 'UTC', }); @@ -419,33 +420,31 @@ describe('utils', () => { const queryParamsWithInvalidTimezone = _.assign({}, queryParams, { timezone: 'invalid' }); - expect(utils.getTimePrefsForDataProcessing(latestUpload, queryParamsWithInvalidTimezone)).to.eql({ + expect(utils.getTimePrefsForDataProcessing(latestUpload, latestDiabetesDatum, queryParamsWithInvalidTimezone)).to.eql({ timezoneAware: false, }); DateTimeFormatStub.restore(); }); + }); - it('should fall back to timezone-naive display time when given an invalid timezone and cannot determine timezone from browser', () => { - const DateTimeFormatStub = sinon.stub(Intl, 'DateTimeFormat').returns({ - resolvedOptions: () => { - return { timeZone: undefined }; - }, + context('Timezone provided from most recent upload', () => { + it('should set a valid timezone from `latestUpload.timezone` if latest diabetes datum is undefined', () => { + expect(utils.getTimePrefsForDataProcessing(latestUpload, undefined, queryParams)).to.eql({ + timezoneAware: true, + timezoneName: 'US/Pacific', }); + }); - const queryParamsWithInvalidTimezone = _.assign({}, queryParams, { timezone: 'invalid' }); - - expect(utils.getTimePrefsForDataProcessing(latestUpload, queryParamsWithInvalidTimezone)).to.eql({ - timezoneAware: false, + it('should set a valid timezone from `latestUpload.timezone` if latest diabetes datum does not have a timezoneOffset', () => { + expect(utils.getTimePrefsForDataProcessing(latestUpload, { ...latestDiabetesDatum, timezoneOffset: undefined }, queryParams)).to.eql({ + timezoneAware: true, + timezoneName: 'US/Pacific', }); - - DateTimeFormatStub.restore(); }); - }); - context('Timezone provided from most recent upload', () => { - it('should set a valid timezone from `latestUpload.timezone`', () => { - expect(utils.getTimePrefsForDataProcessing(latestUpload, queryParams)).to.eql({ + it('should set a valid timezone from `latestUpload.timezone` if latest diabetes datum has a timezoneOffset that matches that of the latest upload timezone', () => { + expect(utils.getTimePrefsForDataProcessing(latestUpload, latestDiabetesDatum, queryParams)).to.eql({ timezoneAware: true, timezoneName: 'US/Pacific', }); @@ -454,7 +453,7 @@ describe('utils', () => { it('should fall back to browser time when given an invalid timezone', () => { const dataWithInvalidTimezone = { type: 'upload', - time: '2018-02-10T00:00:00.000Z', + normalTime: '2018-02-10T00:00:00.000Z', timezone: 'invalid', }; @@ -464,7 +463,7 @@ describe('utils', () => { }, }); - expect(utils.getTimePrefsForDataProcessing(dataWithInvalidTimezone, queryParams)).to.eql({ + expect(utils.getTimePrefsForDataProcessing(dataWithInvalidTimezone, {}, queryParams)).to.eql({ timezoneAware: true, timezoneName: 'Europe/Budapest', }); @@ -475,7 +474,7 @@ describe('utils', () => { it('should fall back to timezone-naive display time when given an invalid timezone and cannot determine timezone from browser', () => { const dataWithInvalidTimezone = { type: 'upload', - time: '2018-02-10T00:00:00.000Z', + normalTime: '2018-02-10T00:00:00.000Z', timezone: 'invalid', }; @@ -485,7 +484,7 @@ describe('utils', () => { }, }); - expect(utils.getTimePrefsForDataProcessing(dataWithInvalidTimezone, queryParams)).to.eql({ + expect(utils.getTimePrefsForDataProcessing(dataWithInvalidTimezone, undefined, queryParams)).to.eql({ timezoneAware: false, }); @@ -493,11 +492,13 @@ describe('utils', () => { }); }); - context('Timezone offset provided from most recent upload', () => { - it('should set a valid timezone from `latestUpload.timezoneOffset`', () => { + context('Timezone offset provided from most recent diabetes datum', () => { + it('should set a valid timezone from `latestDiabetesDatum.timezoneOffset`', () => { expect(utils.getTimePrefsForDataProcessing({ ...latestUpload, timezone: undefined, + }, { + ...latestDiabetesDatum, timezoneOffset: -420, }, queryParams)).to.eql({ timezoneAware: true, @@ -508,6 +509,8 @@ describe('utils', () => { expect(utils.getTimePrefsForDataProcessing({ ...latestUpload, timezone: undefined, + }, { + ...latestDiabetesDatum, timezoneOffset: -(420 + 29), }, queryParams)).to.eql({ timezoneAware: true, @@ -517,6 +520,8 @@ describe('utils', () => { expect(utils.getTimePrefsForDataProcessing({ ...latestUpload, timezone: undefined, + }, { + ...latestDiabetesDatum, timezoneOffset: -(420 + 30), }, queryParams)).to.eql({ timezoneAware: true, @@ -524,10 +529,10 @@ describe('utils', () => { }); }); - it('should fall back to browser time when given an invalid timezone', () => { - const dataWithInvalidTimezone = { - type: 'upload', - time: '2018-02-10T00:00:00.000Z', + it('should fall back to browser time when given an invalid timezone offset', () => { + const datumWithInvalidTimezoneOffset = { + type: 'cbg', + normalTime: '2018-02-10T00:00:00.000Z', timezoneOffset: -1000, // Too large: will not match an Etc/GMT timezone }; @@ -537,7 +542,7 @@ describe('utils', () => { }, }); - expect(utils.getTimePrefsForDataProcessing(dataWithInvalidTimezone, queryParams)).to.eql({ + expect(utils.getTimePrefsForDataProcessing(undefined, datumWithInvalidTimezoneOffset, queryParams)).to.eql({ timezoneAware: true, timezoneName: 'Europe/Budapest', }); @@ -545,10 +550,10 @@ describe('utils', () => { DateTimeFormatStub.restore(); }); - it('should fall back to timezone-naive display time when given an invalid timezone and cannot determine timezone from browser', () => { - const dataWithInvalidTimezone = { - type: 'upload', - time: '2018-02-10T00:00:00.000Z', + it('should fall back to timezone-naive display time when given an invalid timezone offset and cannot determine timezone from browser', () => { + const datumWithInvalidTimezoneOffset = { + type: 'cbg', + normalTime: '2018-02-10T00:00:00.000Z', timezoneOffset: -1000, // Too large: will not match an Etc/GMT timezone }; @@ -558,7 +563,7 @@ describe('utils', () => { }, }); - expect(utils.getTimePrefsForDataProcessing(dataWithInvalidTimezone, queryParams)).to.eql({ + expect(utils.getTimePrefsForDataProcessing(undefined, datumWithInvalidTimezoneOffset, queryParams)).to.eql({ timezoneAware: false, }); @@ -574,7 +579,7 @@ describe('utils', () => { }, }); - expect(utils.getTimePrefsForDataProcessing([], {})).to.eql({ + expect(utils.getTimePrefsForDataProcessing(undefined, undefined, {})).to.eql({ timezoneAware: true, timezoneName: 'Europe/Budapest', }); @@ -589,7 +594,7 @@ describe('utils', () => { }, }); - expect(utils.getTimePrefsForDataProcessing([], {})).to.be.undefined; + expect(utils.getTimePrefsForDataProcessing(undefined, undefined, {})).to.be.undefined; DateTimeFormatStub.restore(); }); From d4d5831fcbb6533cdd15889364d847fac4ee1cbc Mon Sep 17 00:00:00 2001 From: clintonium-119 Date: Fri, 9 Aug 2024 13:22:54 -0400 Subject: [PATCH 008/112] v1.81.0-rc.4 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 49d24edf4f..e5f07e6689 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "node": "20.8.0" }, "packageManager": "yarn@3.6.4", - "version": "1.81.0-rc.3", + "version": "1.81.0-rc.4", "private": true, "scripts": { "test": "TZ=UTC NODE_ENV=test NODE_OPTIONS='--max-old-space-size=4096' yarn karma start", From 41291fb892bc0dd2265070af52bea27aac3cd344 Mon Sep 17 00:00:00 2001 From: clintonium-119 Date: Wed, 28 Aug 2024 09:03:34 -0400 Subject: [PATCH 009/112] v1.81.0-rc.5 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e5f07e6689..a650c97c15 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "node": "20.8.0" }, "packageManager": "yarn@3.6.4", - "version": "1.81.0-rc.4", + "version": "1.81.0-rc.5", "private": true, "scripts": { "test": "TZ=UTC NODE_ENV=test NODE_OPTIONS='--max-old-space-size=4096' yarn karma start", From bca907a145806d9ddcf9e5369034262875f89d8b Mon Sep 17 00:00:00 2001 From: clintonium-119 Date: Fri, 30 Aug 2024 10:17:37 -0400 Subject: [PATCH 010/112] Hide access code for prescriptions in draft or pending state --- app/pages/prescription/Prescriptions.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/pages/prescription/Prescriptions.js b/app/pages/prescription/Prescriptions.js index db6ee1f043..7b8efbb643 100644 --- a/app/pages/prescription/Prescriptions.js +++ b/app/pages/prescription/Prescriptions.js @@ -288,8 +288,8 @@ const Prescriptions = props => { }, [selectedClinicId]); - const renderAccessCode = ({ accessCode }) => { - return ( + const renderAccessCode = ({ accessCode, state }) => { + return includes(['draft', 'pending'], state) ? '' : ( { // Prevent clicks from propogating up to the table row click handlers From 871a0cce667421f4396ac027d842981a84aaef0a Mon Sep 17 00:00:00 2001 From: clintonium-119 Date: Fri, 30 Aug 2024 10:26:23 -0400 Subject: [PATCH 011/112] Use the 'maxSegments' pump property from device service to set max schedules value --- app/pages/prescription/prescriptionFormConstants.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/pages/prescription/prescriptionFormConstants.js b/app/pages/prescription/prescriptionFormConstants.js index 01c02f0eef..627790b7f5 100644 --- a/app/pages/prescription/prescriptionFormConstants.js +++ b/app/pages/prescription/prescriptionFormConstants.js @@ -100,7 +100,6 @@ export const roundValueToIncrement = (value, increment = 1) => { }; export const pumpRanges = (pump, bgUnits = defaultUnits.bloodGlucose, values) => { - const isPalmtree = pump?.id === deviceIdMap.palmtree; const maxBasalRate = max(map(get(values, 'initialSettings.basalRateSchedule'), 'rate')); const ranges = { @@ -108,7 +107,7 @@ export const pumpRanges = (pump, bgUnits = defaultUnits.bloodGlucose, values) => min: max([getPumpGuardrail(pump, 'basalRates.absoluteBounds.minimum', 0.05), 0.05]), max: min([getPumpGuardrail(pump, 'basalRates.absoluteBounds.maximum', 30), 30]), increment: getPumpGuardrail(pump, 'basalRates.absoluteBounds.increment', 0.05), - schedules: { max: isPalmtree ? 24 : 48, minutesIncrement: 30 }, + schedules: { max: pump?.basalRates?.maxSegments || 48, minutesIncrement: 30 }, }, basalRateMaximum: { min: max(filter([ From 84b3e64e5e5ca6e6734cbd542cee15a4e13b787e Mon Sep 17 00:00:00 2001 From: clintonium-119 Date: Fri, 30 Aug 2024 10:29:56 -0400 Subject: [PATCH 012/112] Fix for device path to maxSegments guardrail --- app/pages/prescription/prescriptionFormConstants.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/pages/prescription/prescriptionFormConstants.js b/app/pages/prescription/prescriptionFormConstants.js index 627790b7f5..b9ce76d20e 100644 --- a/app/pages/prescription/prescriptionFormConstants.js +++ b/app/pages/prescription/prescriptionFormConstants.js @@ -107,7 +107,7 @@ export const pumpRanges = (pump, bgUnits = defaultUnits.bloodGlucose, values) => min: max([getPumpGuardrail(pump, 'basalRates.absoluteBounds.minimum', 0.05), 0.05]), max: min([getPumpGuardrail(pump, 'basalRates.absoluteBounds.maximum', 30), 30]), increment: getPumpGuardrail(pump, 'basalRates.absoluteBounds.increment', 0.05), - schedules: { max: pump?.basalRates?.maxSegments || 48, minutesIncrement: 30 }, + schedules: { max: pump?.guardRails?.basalRates?.maxSegments || 48, minutesIncrement: 30 }, }, basalRateMaximum: { min: max(filter([ From e803c9bb4273ade0462198bb57a7a1027e34884a Mon Sep 17 00:00:00 2001 From: clintonium-119 Date: Fri, 30 Aug 2024 10:36:04 -0400 Subject: [PATCH 013/112] Remove default fallback basal rate --- app/pages/prescription/prescriptionFormConstants.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/pages/prescription/prescriptionFormConstants.js b/app/pages/prescription/prescriptionFormConstants.js index b9ce76d20e..487ba9ff6c 100644 --- a/app/pages/prescription/prescriptionFormConstants.js +++ b/app/pages/prescription/prescriptionFormConstants.js @@ -327,7 +327,7 @@ export const defaultValues = (pump, bgUnits = defaultUnits.bloodGlucose, values } return { - basalRate: recommendedBasalRate || 0.05, + basalRate: recommendedBasalRate || getPumpGuardrail(pump, 'basalRates.defaultValue', undefined), basalRateMaximum: isFinite(maxBasalRate) ? parseFloat((maxBasalRate * (isPediatric ? 3 : 3.5)).toFixed(2)) : getPumpGuardrail(pump, 'basalRateMaximum.defaultValue', 0.05), From 185bf95f1f91e2ffe4e090312a3df3981e6c33d8 Mon Sep 17 00:00:00 2001 From: clintonium-119 Date: Fri, 30 Aug 2024 13:14:47 -0400 Subject: [PATCH 014/112] Fix pagination styles on patient and invites lists --- app/pages/clinicworkspace/ClinicPatients.js | 3 +-- app/pages/share/PatientInvites.js | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/app/pages/clinicworkspace/ClinicPatients.js b/app/pages/clinicworkspace/ClinicPatients.js index 1bf0f62be4..cd728e1053 100644 --- a/app/pages/clinicworkspace/ClinicPatients.js +++ b/app/pages/clinicworkspace/ClinicPatients.js @@ -3199,8 +3199,7 @@ export const ClinicPatients = (props) => { {pageCount > 1 && ( { {pendingInvites.length > rowsPerPage && ( Date: Fri, 30 Aug 2024 13:15:48 -0400 Subject: [PATCH 015/112] Fix pagination on prescriptions list --- app/pages/prescription/Prescriptions.js | 30 +++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/app/pages/prescription/Prescriptions.js b/app/pages/prescription/Prescriptions.js index 7b8efbb643..305302f1b9 100644 --- a/app/pages/prescription/Prescriptions.js +++ b/app/pages/prescription/Prescriptions.js @@ -47,6 +47,7 @@ import Pill from '../../components/elements/Pill'; import Popover from '../../components/elements/Popover'; import PopoverMenu from '../../components/elements/PopoverMenu'; import Table from '../../components/elements/Table'; +import Pagination from '../../components/elements/Pagination'; import TextInput from '../../components/elements/TextInput'; import { Body1, MediumTitle } from '../../components/elements/FontStyles'; import { dateRegex, prescriptionStateOptions } from './prescriptionFormConstants'; @@ -67,6 +68,9 @@ const Prescriptions = props => { const { showPrescriptions } = useFlags(); const ldClient = useLDClient(); const ldContext = ldClient.getContext(); + const [page, setPage] = useState(1); + const [pageCount, setPageCount] = useState(); + const rowsPerPage = 10; const { deletingPrescription, @@ -159,6 +163,14 @@ const Prescriptions = props => { prescription => activeStates[prescription.state] ); + const handlePageChange = (event, newValue) => { + setPage(newValue); + }; + + useEffect(() => { + setPageCount(Math.ceil(data.length / rowsPerPage)); + }, [data]); + function handleSearchChange(event) { setSearchText(event.target.value); } @@ -507,15 +519,29 @@ const Prescriptions = props => { id="prescriptions-table" data={data} columns={columns} - rowsPerPage={10} + rowsPerPage={rowsPerPage} + page={page} searchText={searchText} emptyText={t('There are no prescriptions to show.')} onClickRow={handleRowClick} orderBy="createdTime" order="desc" - pagination={data.length > 10} /> + {data.length > rowsPerPage && ( + + )} + Date: Fri, 30 Aug 2024 15:44:40 -0400 Subject: [PATCH 016/112] [release] bump viz --- package.json | 2 +- yarn.lock | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 39db2d6b83..f8fe60bf6c 100644 --- a/package.json +++ b/package.json @@ -66,7 +66,7 @@ "@storybook/react": "7.5.0", "@storybook/react-webpack5": "7.5.0", "@testing-library/react-hooks": "8.0.1", - "@tidepool/viz": "1.42.0-rc.1", + "@tidepool/viz": "1.42.0-rc.2", "async": "2.6.4", "autoprefixer": "10.4.16", "babel-core": "7.0.0-bridge.0", diff --git a/yarn.lock b/yarn.lock index 41b3f253c6..ba210684d1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5268,9 +5268,9 @@ __metadata: languageName: node linkType: hard -"@tidepool/viz@npm:1.42.0-rc.1": - version: 1.42.0-rc.1 - resolution: "@tidepool/viz@npm:1.42.0-rc.1" +"@tidepool/viz@npm:1.42.0-rc.2": + version: 1.42.0-rc.2 + resolution: "@tidepool/viz@npm:1.42.0-rc.2" dependencies: bluebird: 3.7.2 bows: 1.7.2 @@ -5330,7 +5330,7 @@ __metadata: react-dom: 16.x react-redux: 8.x redux: 4.x - checksum: 40087a7188186a2471314d0714b55a7be8186ae4e8fcaad8a2acbf2ca181cbc0f73b9e9d282b9b62ac98bd2fafc9efeabc20947ba012f72ce388ca272df7db1d + checksum: 3fd41ada15753b717c00c5441f6f059fb86901481fd34a7e08a23c0798f11e281c1a445a5383d65fd06666441ce4e3f8bc72620d08c8656e208d2b33e91c32a0 languageName: node linkType: hard @@ -7427,7 +7427,7 @@ __metadata: "@storybook/react": 7.5.0 "@storybook/react-webpack5": 7.5.0 "@testing-library/react-hooks": 8.0.1 - "@tidepool/viz": 1.42.0-rc.1 + "@tidepool/viz": 1.42.0-rc.2 async: 2.6.4 autoprefixer: 10.4.16 babel-core: 7.0.0-bridge.0 From 9eec1023ac25f2ce4bfd50014642bd49b7205900 Mon Sep 17 00:00:00 2001 From: Chris McGee Date: Fri, 30 Aug 2024 15:45:01 -0400 Subject: [PATCH 017/112] v1.81.0-rc.6 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f8fe60bf6c..909c3fdf8f 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "node": "20.8.0" }, "packageManager": "yarn@3.6.4", - "version": "1.81.0-rc.5", + "version": "1.81.0-rc.6", "private": true, "scripts": { "test": "TZ=UTC NODE_ENV=test NODE_OPTIONS='--max-old-space-size=4096' yarn karma start", From 1f6056e2c268f357bdc485d7f0f2db1ff9ee1920 Mon Sep 17 00:00:00 2001 From: clintonium-119 Date: Wed, 4 Sep 2024 10:06:45 -0400 Subject: [PATCH 018/112] Make step generation and validation dynamic rather than use static arrays --- app/pages/prescription/PrescriptionForm.js | 222 ++++++++-------- app/pages/prescription/accountFormSteps.js | 33 +-- .../prescription/prescriptionFormConstants.js | 245 ++++++++++++++---- app/pages/prescription/profileFormSteps.js | 36 +-- app/pages/prescription/reviewFormStep.js | 45 ++-- .../settingsCalculatorFormSteps.js | 30 --- .../prescription/therapySettingsFormStep.js | 19 +- .../prescriptionFormConstants.test.js | 4 - 8 files changed, 330 insertions(+), 304 deletions(-) diff --git a/app/pages/prescription/PrescriptionForm.js b/app/pages/prescription/PrescriptionForm.js index 5664691d7e..0e07c33ece 100644 --- a/app/pages/prescription/PrescriptionForm.js +++ b/app/pages/prescription/PrescriptionForm.js @@ -9,8 +9,7 @@ import moment from 'moment'; import { FastField, withFormik, useFormikContext } from 'formik'; import { PersistFormikValues } from 'formik-persist-values'; import each from 'lodash/each'; -import every from 'lodash/every'; -import find from 'lodash/find'; +import filter from 'lodash/filter'; import forEach from 'lodash/forEach'; import get from 'lodash/get'; import isEmpty from 'lodash/isEmpty'; @@ -19,8 +18,8 @@ import keyBy from 'lodash/keyBy'; import keys from 'lodash/keys'; import noop from 'lodash/noop'; import omit from 'lodash/omit'; +import reduce from 'lodash/reduce'; import remove from 'lodash/remove'; -import slice from 'lodash/slice'; import flattenDeep from 'lodash/flattenDeep'; import cloneDeep from 'lodash/cloneDeep'; import isUndefined from 'lodash/isUndefined'; @@ -28,6 +27,7 @@ import isInteger from 'lodash/isInteger'; import isArray from 'lodash/isArray'; import { default as _values } from 'lodash/values'; import includes from 'lodash/includes'; +import last from 'lodash/last'; import { utils as vizUtils } from '@tidepool/viz'; import { Box, Flex, Text } from 'theme-ui'; import canonicalize from 'canonicalize'; @@ -36,11 +36,6 @@ import { useFlags, useLDClient } from 'launchdarkly-react-client-sdk'; import { fieldsAreValid } from '../../core/forms'; import prescriptionSchema from './prescriptionSchema'; -import accountFormSteps from './accountFormSteps'; -import profileFormSteps from './profileFormSteps'; -import settingsCalculatorFormSteps from './settingsCalculatorFormSteps'; -import therapySettingsFormStep from './therapySettingsFormStep'; -import reviewFormStep from './reviewFormStep'; import ClinicWorkspaceHeader from '../../components/clinic/ClinicWorkspaceHeader'; import Button from '../../components/elements/Button'; import Pill from '../../components/elements/Pill'; @@ -57,9 +52,9 @@ import { cgmDeviceOptions, defaultUnits, deviceIdMap, + getFormSteps, prescriptionStateOptions, pumpDeviceOptions, - stepValidationFields, validCountryCodes, } from './prescriptionFormConstants'; @@ -293,7 +288,6 @@ export const PrescriptionForm = props => { const stepperId = 'prescription-form-steps'; const bgUnits = get(values, 'initialSettings.bloodGlucoseUnits', defaultUnits.bloodGlucose); const pumpId = get(values, 'initialSettings.pumpId', deviceIdMap.palmtree); - const pump = find(devices.pumps, { id: pumpId }); const prescriptionState = get(prescription, 'state', 'draft'); const prescriptionStates = keyBy(prescriptionStateOptions, 'value'); const isEditable = includes(['draft', 'pending'], prescriptionState); @@ -332,6 +326,8 @@ export const PrescriptionForm = props => { const activeStepsParam = params().get(activeStepParamKey); const [formPersistReady, setFormPersistReady] = useState(false); + const [formSteps, setFormSteps] = useState([]); + const [skippedFields, setSkippedFields] = useState([]); const [stepAsyncState, setStepAsyncState] = useState(asyncStates.initial); const [activeStep, setActiveStep] = useState(activeStepsParam ? parseInt(activeStepsParam.split(',')[0], 10) : undefined); const [activeSubStep, setActiveSubStep] = useState(activeStepsParam ? parseInt(activeStepsParam.split(',')[1], 10) : undefined); @@ -339,8 +335,7 @@ export const PrescriptionForm = props => { const [initialFocusedInput, setInitialFocusedInput] = useState(); const [singleStepEditValues, setSingleStepEditValues] = useState(values); const isSingleStepEdit = !!pendingStep.length; - const validationFields = [ ...stepValidationFields ]; - const isLastStep = () => activeStep === validationFields.length - 1; + const isLastStep = () => activeStep === formSteps.length - 1; const handlers = { activeStepUpdate: ([step, subStep], fromStep = [], initialFocusedInput) => { @@ -379,13 +374,32 @@ export const PrescriptionForm = props => { 'therapySettingsReviewed', ]; + // Delete fields that are intentionally skipped for the selected pump + if (skippedFields.length) { + fieldsToDelete.push(...(filter(skippedFields, fieldPath => { + const pathParts = fieldPath.split('.'); + const value = get(values, pathParts); + + // Skipped top-level fields can always be deleted from the payload + // Only delete nested skipped fields if they are empty. + return pathParts.length > 1 ? isEmpty(value) : true; + }))); + } + // Also delete any fields from future form steps if empty // We can't simply delete all future steps, as the clinician may have returned to the current // step via 'Back' button navigation and we don't want to lose existing data previously // entered in the later steps. if (!isLastStep()) { + const fieldsInFutureSteps = reduce(formSteps, (fields, step, index) => { + if (index <= activeStep) return fields; + + fields.push(...map(step.subSteps, 'fields')); + return fields; + }, []); + const emptyFieldsInFutureSteps = remove( - flattenDeep(slice(validationFields, activeStep + 1)), + flattenDeep(fieldsInFutureSteps), fieldPath => { const value = get(values, fieldPath); @@ -435,121 +449,87 @@ export const PrescriptionForm = props => { }, }; - const accountFormStepsProps = accountFormSteps(schema, initialFocusedInput, values); - const profileFormStepsProps = profileFormSteps(schema, devices, values); - const settingsCalculatorFormStepsProps = settingsCalculatorFormSteps(schema, handlers, values); - const therapySettingsFormStepProps = therapySettingsFormStep(schema, pump, values); - const reviewFormStepProps = reviewFormStep(schema, pump, handlers, values, isEditable, isPrescriber); - - const stepProps = step => ({ - ...step, - completeText: isSingleStepEdit ? t('Update and Review') : step.completeText, - backText: isSingleStepEdit ? t('Cancel Update') : step.backText, - hideBack: isSingleStepEdit ? false : step.hideBack, - disableBack: isSingleStepEdit ? false : step.disableBack, - onComplete: isSingleStepEdit ? handlers.singleStepEditComplete : step.onComplete, - onBack: isSingleStepEdit ? handlers.singleStepEditComplete.bind(null, true) : step.onBack, - }); + useEffect(() => { + const pumpDevices = pumpDeviceOptions(devices); + const cgmDevices = cgmDeviceOptions(devices); + + const newSkippedFields = [ + 'calculator', + 'mrn', + 'phoneNumber', + 'training', + ]; + + // Skip device selection substep and set default pump and cgm IDs if there aren't multiple choices available + const skipDeviceSelection = cgmDevices.length === 1 && pumpDevices.length === 1; + if (skipDeviceSelection) { + if (!values.initialSettings?.cgmId) setFieldValue('initialSettings.cgmId', cgmDevices[0].value); + if (!values.initialSettings?.pumpId) setFieldValue('initialSettings.pumpId', pumpDevices[0].value); + newSkippedFields.push('initialSettings.pumpId', 'initialSettings.cgmId'); + } - const subStepProps = subSteps => map(subSteps, subStep => stepProps(subStep)); + const newFormSteps = getFormSteps(schema, devices, values, handlers, { + skippedFields: newSkippedFields, + initialFocusedInput, + isEditable, + isPrescriber, + isSingleStepEdit, + stepAsyncState, + }); - const steps = [ - { - ...accountFormStepsProps, - onComplete: isSingleStepEdit ? noop : handlers.stepSubmit, - asyncState: isSingleStepEdit ? null : stepAsyncState, - subSteps: subStepProps(accountFormStepsProps.subSteps), - }, - { - ...profileFormStepsProps, - onComplete: isSingleStepEdit ? noop : handlers.stepSubmit, - asyncState: isSingleStepEdit ? null : stepAsyncState, - subSteps: subStepProps(profileFormStepsProps.subSteps), - }, - { - ...settingsCalculatorFormStepsProps, - onComplete: handlers.stepSubmit, - asyncState: stepAsyncState, - subSteps: subStepProps(settingsCalculatorFormStepsProps.subSteps), - }, - { - ...stepProps(therapySettingsFormStepProps), - onComplete: isSingleStepEdit ? handlers.singleStepEditComplete : handlers.stepSubmit, - asyncState: isSingleStepEdit ? null : stepAsyncState, - }, - { - ...reviewFormStepProps, - onComplete: handlers.stepSubmit, - asyncState: stepAsyncState, - }, - ]; - - const pumpDevices = pumpDeviceOptions(devices); - const cgmDevices = cgmDeviceOptions(devices); - - // Skip device selection substep and set default pump and cgm IDs if there aren't multiple choices available - const skipDeviceSelection = cgmDevices.length === 1 && pumpDevices.length === 1; - if (skipDeviceSelection) { - if (!values.initialSettings?.cgmId) setFieldValue('initialSettings.cgmId', cgmDevices[0].value); - if (!values.initialSettings?.pumpId) setFieldValue('initialSettings.pumpId', pumpDevices[0].value); - validationFields[1].splice(2, 1); - steps[1].subSteps.splice(3, 1); - } - - // Skip calculator step if selected pump, or all available pump options are set to skip aace calculator - const skipCalculator = !!(pumpDevices.length && every(pumpDevices, { skipCalculator: true })) || !!find(devices, { value: pumpId })?.skipCalculator; - if (skipCalculator) { - validationFields.splice(2, 1); - steps.splice(2, 1); - } + setFormSteps(newFormSteps); + setSkippedFields(newSkippedFields); + }, [values, devices, initialFocusedInput, isEditable, isPrescriber, isSingleStepEdit, stepAsyncState]) useEffect(() => { - let initialValues = { ...values } - - // Hydrate the locally stored values only in the following cases, allowing us to persist data - // entered in form substeps but not yet saved to the database - // 1. It's a new prescription and there is no locally stored id, and there are step and substep query params - // 2. We're editing an existing prescription, and the locally stored id matches the id in the url param - if ( - (isNewPrescriptionFlow() && !storedValues?.id && !isUndefined(activeStep) && !isUndefined(activeSubStep)) || - (id && id === storedValues?.id) - ) { - initialValues = { ...values, ...storedValues }; - setValues(initialValues); - } + if (formSteps.length && !formPersistReady) { + let initialValues = { ...values } + + // Hydrate the locally stored values only in the following cases, allowing us to persist data + // entered in form substeps but not yet saved to the database + // 1. It's a new prescription and there is no locally stored id, and there are step and substep query params + // 2. We're editing an existing prescription, and the locally stored id matches the id in the url param + if ( + (isNewPrescriptionFlow() && !storedValues?.id && !isUndefined(activeStep) && !isUndefined(activeSubStep)) || + (id && id === storedValues?.id) + ) { + initialValues = { ...values, ...storedValues }; + setValues(initialValues); + } - // After hydrating any relevant values, we delete the localStorage values so formikPersist has a clean start - delete localStorage[storageKey]; - - // Determine the latest incomplete step, and default to starting there - if (isEditable) { - let firstInvalidStep; - let firstInvalidSubStep; - let currentStep = 0; - let currentSubStep = 0; - - while (isUndefined(firstInvalidStep) && currentStep < validationFields.length) { - while (currentSubStep < validationFields[currentStep].length) { - if (!fieldsAreValid(validationFields[currentStep][currentSubStep], schema, initialValues)) { - firstInvalidStep = currentStep; - firstInvalidSubStep = currentSubStep; - break; + // After hydrating any relevant values, we delete the localStorage values so formikPersist has a clean start + delete localStorage[storageKey]; + + // Determine the latest incomplete step, and default to starting there + if (isEditable) { + let firstInvalidStep; + let firstInvalidSubStep; + let currentStep = 0; + let currentSubStep = 0; + + while (isUndefined(firstInvalidStep) && currentStep < formSteps.length) { + while (currentSubStep < formSteps[currentStep].subSteps.length) { + if (!fieldsAreValid(formSteps[currentStep].subSteps[currentSubStep].fields, schema, initialValues)) { + firstInvalidStep = currentStep; + firstInvalidSubStep = currentSubStep; + break; + } + currentSubStep++ } - currentSubStep++ + + currentStep++; + currentSubStep = 0; } - currentStep++; - currentSubStep = 0; + setActiveStep(isInteger(firstInvalidStep) ? firstInvalidStep : formSteps.length - 1); + setActiveSubStep(isInteger(firstInvalidSubStep) ? firstInvalidSubStep : 0); } - setActiveStep(isInteger(firstInvalidStep) ? firstInvalidStep : steps.length - 1); - setActiveSubStep(isInteger(firstInvalidSubStep) ? firstInvalidSubStep : 0); + // Now that any hydration is complete and we've cleared locally stored values, + // we're ready for formikPersist to take over form persistence + setFormPersistReady(true); } - - // Now that any hydration is complete and we've cleared locally stored values, - // we're ready for formikPersist to take over form persistence - setFormPersistReady(true); - }, []); + }, [formSteps.length]); // Save whether or not we are editing a single step to the formik form status for easy reference useEffect(() => { @@ -636,7 +616,7 @@ export const PrescriptionForm = props => { log('Step to', newStep.join(',')); }, - steps, + steps: formSteps, themeProps: { wrapper: { padding: 4, @@ -684,11 +664,11 @@ export const PrescriptionForm = props => { - {isEditable && !isUndefined(activeStep) && } + {formSteps.length && isEditable && !isUndefined(activeStep) && } - {!isEditable && ( + {formSteps.length && !isEditable && ( - {reviewFormStepProps.panelContent} + {last(formSteps).subSteps[0].panelContent} )} diff --git a/app/pages/prescription/accountFormSteps.js b/app/pages/prescription/accountFormSteps.js index b4b6dab917..15dc999c1d 100644 --- a/app/pages/prescription/accountFormSteps.js +++ b/app/pages/prescription/accountFormSteps.js @@ -2,22 +2,17 @@ import React from 'react'; import { withTranslation } from 'react-i18next'; import { FastField, useFormikContext } from 'formik'; import { Box } from 'theme-ui'; -import bows from 'bows'; import InputMask from 'react-input-mask'; import get from 'lodash/get'; -import { fieldsAreValid, getFieldError } from '../../core/forms'; +import { getFieldError } from '../../core/forms'; import { useInitialFocusedInput } from '../../core/hooks'; -import i18next from '../../core/language'; import RadioGroup from '../../components/elements/RadioGroup'; import TextInput from '../../components/elements/TextInput'; import { Caption, Headline } from '../../components/elements/FontStyles'; -import { dateRegex, stepValidationFields, typeOptions } from './prescriptionFormConstants'; +import { dateRegex, typeOptions } from './prescriptionFormConstants'; import { fieldsetStyles, condensedInputStyles } from './prescriptionFormStyles'; -const t = i18next.t.bind(i18next); -const log = bows('PrescriptionAccount'); - export const AccountType = withTranslation()(props => { const { t } = props; const initialFocusedInputRef = useInitialFocusedInput(); @@ -184,27 +179,3 @@ export const PatientEmail = withTranslation()(props => { ); }); - -const accountFormSteps = (schema, initialFocusedInput, values) => ({ - label: t('Create Patient Account'), - subSteps: [ - { - disableComplete: !fieldsAreValid(stepValidationFields[0][0], schema, values), - hideBack: true, - onComplete: () => log('Account Type Complete'), - panelContent: , - }, - { - disableComplete: !fieldsAreValid(stepValidationFields[0][1], schema, values), - onComplete: () => log('Patient Info Complete'), - panelContent: , - }, - { - disableComplete: !fieldsAreValid(stepValidationFields[0][2], schema, values), - onComplete: () => log('Patient Email Complete'), - panelContent: , - }, - ], -}); - -export default accountFormSteps; diff --git a/app/pages/prescription/prescriptionFormConstants.js b/app/pages/prescription/prescriptionFormConstants.js index 487ba9ff6c..ae6854dfb2 100644 --- a/app/pages/prescription/prescriptionFormConstants.js +++ b/app/pages/prescription/prescriptionFormConstants.js @@ -1,22 +1,35 @@ import React from 'react'; import { Trans } from 'react-i18next'; import { Link } from 'theme-ui'; +import bows from 'bows'; +import isEmpty from 'lodash/isEmpty'; import isFinite from 'lodash/isFinite'; import get from 'lodash/get'; import map from 'lodash/map'; import max from 'lodash/max'; import mean from 'lodash/mean'; import min from 'lodash/min'; +import noop from 'lodash/noop'; import filter from 'lodash/filter'; import includes from 'lodash/includes'; +import reduce from 'lodash/reduce'; +import reject from 'lodash/reject'; import moment from 'moment'; import i18next from '../../core/language'; import { LBS_PER_KG, MGDL_PER_MMOLL, MGDL_UNITS } from '../../core/constants'; import utils from '../../core/utils'; import { getFloatFromUnitsAndNanos } from '../../core/data'; +import { fieldsAreValid } from '../../core/forms'; + +import { AccountType, PatientEmail, PatientInfo } from './accountFormSteps'; +import { PatientDevices, PatientGender, PatientMRN, PatientPhone } from './profileFormSteps'; +import { CalculatorInputs, CalculatorMethod } from './settingsCalculatorFormSteps'; +import { TherapySettings } from './therapySettingsFormStep'; +import { PrescriptionReview } from './reviewFormStep'; const t = i18next.t.bind(i18next); +const log = bows('PrescriptionForm'); export const dateFormat = 'YYYY-MM-DD'; export const phoneRegex = /^\(?([0-9]{3})\)?[-. ]?([0-9]{3})[-. ]?([0-9]{4})$/; @@ -50,7 +63,6 @@ export const deviceDetails = { Find information on how to prescribe Palmtree products here. ), - skipCalculator: true, }, }; @@ -480,46 +492,191 @@ export const weightUnitOptions = [ export const validCountryCodes = [1]; -export const stepValidationFields = [ - [ - ['accountType'], - ['firstName', 'lastName', 'birthday'], - ['caregiverFirstName', 'caregiverLastName', 'email', 'emailConfirm'], - ], - [ - ['phoneNumber.number'], - ['mrn'], - ['sex'], - ['initialSettings.pumpId', 'initialSettings.cgmId'], - ], - [ - ['calculator.method'], - [ - 'calculator.totalDailyDose', - 'calculator.totalDailyDoseScaleFactor', - 'calculator.weight', - 'calculator.weightUnits', - 'calculator.recommendedBasalRate', - 'calculator.recommendedInsulinSensitivity', - 'calculator.recommendedCarbohydrateRatio', - ], - ], - [ - [ - 'training', - 'initialSettings.glucoseSafetyLimit', - 'initialSettings.insulinModel', - 'initialSettings.basalRateMaximum.value', - 'initialSettings.bolusAmountMaximum.value', - 'initialSettings.bloodGlucoseTargetSchedule', - 'initialSettings.bloodGlucoseTargetPhysicalActivity', - 'initialSettings.bloodGlucoseTargetPreprandial', - 'initialSettings.basalRateSchedule', - 'initialSettings.carbohydrateRatioSchedule', - 'initialSettings.insulinSensitivitySchedule', - ], - ], - [ - ['therapySettingsReviewed'], - ], -]; +export const getFormSteps = (schema, devices, values, handlers, options = {}) => { + const { + skippedFields = [], + isEditable, + isPrescriber, + initialFocusedInput, + isSingleStepEdit, + stepAsyncState, + } = options; + + const pumpId = get(values, 'initialSettings.pumpId', deviceIdMap.palmtree); + const pump = find(devices.pumps, { id: pumpId }); + + const allSteps = [ + { + key: 'account', + label: t('Create Patient Account'), + onComplete: isSingleStepEdit ? noop : handlers.stepSubmit, + asyncState: isSingleStepEdit ? null : stepAsyncState, + subSteps: [ + { + fields: ['accountType'], + hideBack: true, + onComplete: () => log('Account Type Complete'), + panelContent: , + }, + { + fields: ['firstName', 'lastName', 'birthday'], + onComplete: () => log('Patient Info Complete'), + panelContent: , + }, + { + fields: ['caregiverFirstName', 'caregiverLastName', 'email', 'emailConfirm'], + onComplete: () => log('Patient Email Complete'), + panelContent: , + }, + ], + }, + { + key: 'profile', + label: t('Complete Patient Profile'), + onComplete: isSingleStepEdit ? noop : handlers.stepSubmit, + asyncState: isSingleStepEdit ? null : stepAsyncState, + subSteps: [ + { + fields: ['phoneNumber.number'], + onComplete: () => log('Patient Phone Number Complete'), + panelContent: + }, + { + fields: ['mrn'], + onComplete: () => log('Patient MRN Complete'), + panelContent: , + }, + { + fields: ['sex'], + onComplete: () => log('Patient Gender Complete'), + panelContent: , + }, + { + fields: ['initialSettings.pumpId', 'initialSettings.cgmId'], + onComplete: () => log('Patient Devices Complete'), + panelContent: , + }, + ], + }, + { + key: 'calculator', + label: t('Therapy Settings Calculator'), + optional: true, + onSkip: handlers.clearCalculator, + onEnter: handlers.goToFirstSubStep, + onComplete: handlers.stepSubmit, + asyncState: stepAsyncState, + subSteps: [ + { + fields: ['calculator.method'], + onComplete: () => log('Calculator Method Complete'), + panelContent: { + handlers.clearCalculatorInputs(); + handlers.clearCalculatorResults(); + }} />, + }, + { + fields: [ + 'calculator.totalDailyDose', + 'calculator.totalDailyDoseScaleFactor', + 'calculator.weight', + 'calculator.weightUnits', + 'calculator.recommendedBasalRate', + 'calculator.recommendedInsulinSensitivity', + 'calculator.recommendedCarbohydrateRatio', + ], + onComplete: () => log('Calculator Inputs Complete'), + panelContent: + }, + ], + }, + { + key: 'therapySettings', + label: t('Enter Therapy Settings'), + onComplete: isSingleStepEdit ? handlers.singleStepEditComplete : handlers.stepSubmit, + asyncState: isSingleStepEdit ? null : stepAsyncState, + subSteps: [ + { + fields: [ + 'training', + 'initialSettings.glucoseSafetyLimit', + 'initialSettings.insulinModel', + 'initialSettings.basalRateMaximum.value', + 'initialSettings.bolusAmountMaximum.value', + 'initialSettings.bloodGlucoseTargetSchedule', + 'initialSettings.bloodGlucoseTargetPhysicalActivity', + 'initialSettings.bloodGlucoseTargetPreprandial', + 'initialSettings.basalRateSchedule', + 'initialSettings.carbohydrateRatioSchedule', + 'initialSettings.insulinSensitivitySchedule', + ], + panelContent: , + }, + ], + }, + { + key: 'review', + label: t('Review and {{action}} Prescription', { action: isPrescriber ? 'Send' : 'Save' }), + onComplete: handlers.stepSubmit, + asyncState: stepAsyncState, + subSteps: [ + { + fields: ['therapySettingsReviewed'], + completeText: t('{{action}} Prescription', { action: isPrescriber ? 'Send Final' : 'Save Pending' }), + panelContent: + }, + ], + }, + ]; + + const addCommonStepProps = step => ({ + ...step, + completeText: isSingleStepEdit ? t('Update and Review') : step.completeText, + backText: isSingleStepEdit ? t('Cancel Update') : step.backText, + hideBack: isSingleStepEdit ? false : step.hideBack, + disableBack: isSingleStepEdit ? false : step.disableBack, + onComplete: isSingleStepEdit ? handlers.singleStepEditComplete : step.onComplete, + onBack: isSingleStepEdit ? handlers.singleStepEditComplete.bind(null, true) : step.onBack, + }); + + const enabledFields = []; + + const formSteps = reduce(allSteps, (result, step) => { + if (includes(skippedFields, step.key)) return result; + + const subSteps = reduce(step.subSteps, (subStepResult, subStep) => { + const fields = reject(subStep.fields, field => includes(skippedFields, field) || includes(skippedFields, field.split('.')[0])); + if (!fields.length) return subStepResult; + + enabledFields.push(...fields); + + let disableComplete = !fieldsAreValid(fields, schema, values); + + if (!disableComplete) { + if (includes(fields, 'calculator.method')) { + disableComplete = isEmpty(get(values, 'calculator.method')); + } + if (includes(fields, 'therapySettingsReviewed')) { + disableComplete = !fieldsAreValid(enabledFields, schema, values); + } + } + + subStepResult.push(addCommonStepProps({ + ...subStep, + fields, + disableComplete, + })); + + return subStepResult; + }, []); + + if (subSteps.length) result.push(addCommonStepProps({ + ...step, + subSteps, + })); + + return result; + }, []); + + return formSteps; +}; diff --git a/app/pages/prescription/profileFormSteps.js b/app/pages/prescription/profileFormSteps.js index d45fbaacb2..4f1f9a8688 100644 --- a/app/pages/prescription/profileFormSteps.js +++ b/app/pages/prescription/profileFormSteps.js @@ -2,15 +2,13 @@ import React from 'react'; import { withTranslation } from 'react-i18next'; import { FastField, Field, useFormikContext } from 'formik'; import { Box, Flex } from 'theme-ui'; -import bows from 'bows'; import get from 'lodash/get'; import isEmpty from 'lodash/isEmpty'; import map from 'lodash/map'; import InputMask from 'react-input-mask'; -import { fieldsAreValid, getFieldError } from '../../core/forms'; +import { getFieldError } from '../../core/forms'; import { useInitialFocusedInput } from '../../core/hooks'; -import i18next from '../../core/language'; import RadioGroup from '../../components/elements/RadioGroup'; import Checkbox from '../../components/elements/Checkbox'; import TextInput from '../../components/elements/TextInput'; @@ -27,12 +25,8 @@ import { sexOptions, cgmDeviceOptions, pumpDeviceOptions, - stepValidationFields, } from './prescriptionFormConstants'; -const t = i18next.t.bind(i18next); -const log = bows('PrescriptionProfile'); - export const PatientPhone = withTranslation()(props => { const { t } = props; const formikContext = useFormikContext(); @@ -194,31 +188,3 @@ export const PatientDevices = withTranslation()(props => { ); }); - -const profileFormSteps = (schema, devices, values) => ({ - label: t('Complete Patient Profile'), - subSteps: [ - { - disableComplete: !fieldsAreValid(stepValidationFields[1][0], schema, values), - onComplete: () => log('Patient Phone Number Complete'), - panelContent: - }, - { - disableComplete: !fieldsAreValid(stepValidationFields[1][1], schema, values), - onComplete: () => log('Patient MRN Complete'), - panelContent: , - }, - { - disableComplete: !fieldsAreValid(stepValidationFields[1][2], schema, values), - onComplete: () => log('Patient Gender Complete'), - panelContent: , - }, - { - disableComplete: !fieldsAreValid(stepValidationFields[1][3], schema, values), - onComplete: () => log('Patient Devices Complete'), - panelContent: , - }, - ], -}); - -export default profileFormSteps; diff --git a/app/pages/prescription/reviewFormStep.js b/app/pages/prescription/reviewFormStep.js index c217ac3719..a3b315af7d 100644 --- a/app/pages/prescription/reviewFormStep.js +++ b/app/pages/prescription/reviewFormStep.js @@ -3,22 +3,22 @@ import PropTypes from 'prop-types'; import { withTranslation } from 'react-i18next'; import { FastField, useFormikContext } from 'formik'; import { Box, Flex, BoxProps } from 'theme-ui'; -import bows from 'bows'; import compact from 'lodash/compact'; import find from 'lodash/find'; -import flattenDeep from 'lodash/flattenDeep'; import get from 'lodash/get'; +import includes from 'lodash/includes'; import isEmpty from 'lodash/isEmpty'; import map from 'lodash/map'; +import reject from 'lodash/reject'; import capitalize from 'lodash/capitalize'; import isArray from 'lodash/isArray'; import EditRoundedIcon from '@material-ui/icons/EditRounded'; import FileCopyRoundedIcon from '@material-ui/icons/FileCopyRounded'; import { components as vizComponents } from '@tidepool/viz'; -import { fieldsAreValid, getThresholdWarning, getFieldError } from '../../core/forms'; +import { getThresholdWarning, getFieldError } from '../../core/forms'; import { useInitialFocusedInput } from '../../core/hooks'; -import { dateRegex, insulinModelOptions, stepValidationFields, warningThresholds } from './prescriptionFormConstants'; +import { dateRegex, insulinModelOptions, warningThresholds } from './prescriptionFormConstants'; import i18next from '../../core/language'; import { convertMsPer24ToTimeString } from '../../core/datetime'; import { Body1, Headline, Paragraph1 } from '../../components/elements/FontStyles'; @@ -34,7 +34,6 @@ import { const { ClipboardButton } = vizComponents; const t = i18next.t.bind(i18next); -const log = bows('PrescriptionReview'); const fieldsetPropTypes = { ...BoxProps, @@ -44,8 +43,8 @@ const fieldsetPropTypes = { const emptyValueText = t('Not specified'); -const patientRows = (values, formikContext) => { - return [ +const patientRows = (values, formikContext, skippedFields = []) => { // TODO: Skip skipped fields + const rows = [ { label: t('Email'), value: get(values, 'email', emptyValueText), @@ -55,7 +54,8 @@ const patientRows = (values, formikContext) => { { label: t('Mobile Number'), value: get(values, 'phoneNumber.number', emptyValueText), - error: getFieldError('phoneNumber.number', formikContext, true), + error: get(values, 'phoneNumber.number') && getFieldError('phoneNumber.number', formikContext, true), + skipped: includes(skippedFields, 'phoneNumber'), step: [1, 0], }, { @@ -81,17 +81,20 @@ const patientRows = (values, formikContext) => { label: t('MRN'), value: get(values, 'mrn', emptyValueText), error: getFieldError('mrn', formikContext, true), + skipped: includes(skippedFields, 'mrn'), step: [1, 1], }, ]; + + return reject(rows, { skipped: true }); }; -const therapySettingsRows = (pump, formikContext) => { +const therapySettingsRows = (pump, formikContext, skippedFields = []) => { const { values } = formikContext; const bgUnits = get(values, 'initialSettings.bloodGlucoseUnits'); const thresholds = warningThresholds(pump, bgUnits, values); - return [ + const rows = [ { id: 'cpt-training', label: t('CPT Training Required'), @@ -100,6 +103,7 @@ const therapySettingsRows = (pump, formikContext) => { return values.training === 'inModule' ? t('Not required') : t('Required'); })(), error: getFieldError('training', formikContext, true), + skipped: includes(skippedFields, 'training'), }, { id: 'glucose-safety-limit', @@ -285,6 +289,8 @@ const therapySettingsRows = (pump, formikContext) => { ), }, ]; + + return reject(rows, { skipped: true }); }; export const PatientInfo = props => { @@ -293,6 +299,7 @@ export const PatientInfo = props => { currentStep, handlers: { activeStepUpdate }, isEditable, + skippedFields, ...themeProps } = props; @@ -309,7 +316,7 @@ export const PatientInfo = props => { } = values; const patientName = [firstName, lastName].join(' '); - const rows = patientRows(values, formikContext); + const rows = patientRows(values, formikContext, skippedFields); const Row = ({ label, value, step, initialFocusedInput, error }) => ( @@ -356,6 +363,7 @@ export const TherapySettings = props => { handlers: { activeStepUpdate, generateTherapySettingsOrderText, handleCopyTherapySettingsClicked }, isEditable, pump, + skippedFields, ...themeProps } = props; @@ -371,7 +379,7 @@ export const TherapySettings = props => { const patientName = [firstName, lastName].join(' '); - const rows = therapySettingsRows(pump, formikContext); + const rows = therapySettingsRows(pump, formikContext, skippedFields); const Row = ({ label, value, warning, id, index, error }) => { let rowValues = isArray(value) ? value : [value]; @@ -489,8 +497,8 @@ export const TherapySettings = props => { label: t('Name'), value: patientName, }, - ...patientRows(values, formikContext), - ], therapySettingsRows(pump, formikContext))} + ...patientRows(values, formikContext, patientRows), + ], therapySettingsRows(pump, formikContext, skippedFields))} /> @@ -577,12 +585,3 @@ export const PrescriptionReview = withTranslation()(props => { ); }); - -const reviewFormStep = (schema, pump, handlers, values, isEditable, isPrescriber) => ({ - label: t('Review and {{action}} Prescription', { action: isPrescriber ? 'Send' : 'Save' }), - completeText: t('{{action}} Prescription', { action: isPrescriber ? 'Send Final' : 'Save Pending' }), - disableComplete: !fieldsAreValid(flattenDeep(stepValidationFields), schema, values), - panelContent: -}); - -export default reviewFormStep; diff --git a/app/pages/prescription/settingsCalculatorFormSteps.js b/app/pages/prescription/settingsCalculatorFormSteps.js index a2e3565485..5b0e0e7254 100644 --- a/app/pages/prescription/settingsCalculatorFormSteps.js +++ b/app/pages/prescription/settingsCalculatorFormSteps.js @@ -2,14 +2,12 @@ import React from 'react'; import { withTranslation } from 'react-i18next'; import { FastField, useFormikContext } from 'formik'; import { Box, Flex } from 'theme-ui'; -import bows from 'bows'; import get from 'lodash/get'; import includes from 'lodash/includes'; import isEmpty from 'lodash/isEmpty'; import { fieldsAreValid, getFieldError } from '../../core/forms'; import { useInitialFocusedInput } from '../../core/hooks'; -import i18next from '../../core/language'; import Button from '../../components/elements/Button'; import RadioGroup from '../../components/elements/RadioGroup'; import Select from '../../components/elements/Select'; @@ -27,14 +25,10 @@ import { calculateRecommendedTherapySettings, calculatorMethodOptions, roundValueToIncrement, - stepValidationFields, totalDailyDoseScaleFactorOptions, weightUnitOptions, } from './prescriptionFormConstants'; -const t = i18next.t.bind(i18next); -const log = bows('PrescriptionCalculator'); - export const CalculatorMethod = withTranslation()(props => { const { t, onMethodChange } = props; const formikContext = useFormikContext(); @@ -196,27 +190,3 @@ export const CalculatorInputs = withTranslation()(props => { ); }); - -const settingsCalculatorFormSteps = (schema, handlers, values ) => ({ - label: t('Therapy Settings Calculator'), - optional: true, - onSkip: handlers.clearCalculator, - onEnter: handlers.goToFirstSubStep, - subSteps: [ - { - disableComplete: isEmpty(get(values, stepValidationFields[2][0][0])) || !fieldsAreValid(stepValidationFields[2][0], schema, values), - onComplete: () => log('Calculator Method Complete'), - panelContent: { - handlers.clearCalculatorInputs(); - handlers.clearCalculatorResults(); - }} />, - }, - { - disableComplete: !fieldsAreValid(stepValidationFields[2][1], schema, values), - onComplete: () => log('Calculator Inputs Complete'), - panelContent: - }, - ], -}); - -export default settingsCalculatorFormSteps; diff --git a/app/pages/prescription/therapySettingsFormStep.js b/app/pages/prescription/therapySettingsFormStep.js index 643102338b..fcef7fa48f 100644 --- a/app/pages/prescription/therapySettingsFormStep.js +++ b/app/pages/prescription/therapySettingsFormStep.js @@ -3,15 +3,14 @@ import PropTypes from 'prop-types'; import { withTranslation } from 'react-i18next'; import { FastField, Field, useFormikContext } from 'formik'; import { Box, Flex, Text, BoxProps } from 'theme-ui'; -import bows from 'bows'; import each from 'lodash/each'; import get from 'lodash/get'; +import includes from 'lodash/includes'; import map from 'lodash/map'; import max from 'lodash/max'; -import { fieldsAreValid, getFieldError, getThresholdWarning } from '../../core/forms'; +import { getFieldError, getThresholdWarning } from '../../core/forms'; import { useInitialFocusedInput } from '../../core/hooks'; -import i18next from '../../core/language'; import { Paragraph2, Body2, Headline, Title } from '../../components/elements/FontStyles'; import RadioGroup from '../../components/elements/RadioGroup'; import PopoverLabel from '../../components/elements/PopoverLabel'; @@ -26,7 +25,6 @@ import { pumpRanges, roundValueToIncrement, shouldUpdateDefaultValue, - stepValidationFields, trainingOptions, warningThresholds, } from './prescriptionFormConstants'; @@ -38,9 +36,6 @@ import { scheduleGroupStyles, } from './prescriptionFormStyles'; -const t = i18next.t.bind(i18next); -const log = bows('PrescriptionTherapySettings'); - const fieldsetPropTypes = { ...BoxProps, t: PropTypes.func.isRequired, @@ -655,18 +650,10 @@ export const TherapySettings = withTranslation()(props => { {hasCalculatorResults(values) && } - + {!includes(props.skippedFields, 'training') && } {values.training === 'inModule' && } ); }); - -const therapySettingsFormStep = (schema, pump, values) => ({ - label: t('Enter Therapy Settings'), - disableComplete: !fieldsAreValid(stepValidationFields[3][0], schema, values), - panelContent: -}); - -export default therapySettingsFormStep; diff --git a/test/unit/pages/prescription/prescriptionFormConstants.test.js b/test/unit/pages/prescription/prescriptionFormConstants.test.js index e571234426..1fef8179fc 100644 --- a/test/unit/pages/prescription/prescriptionFormConstants.test.js +++ b/test/unit/pages/prescription/prescriptionFormConstants.test.js @@ -73,10 +73,6 @@ describe('prescriptionFormConstants', function() { _.each(prescriptionFormConstants.deviceDetails, (details, deviceId) => { expect(details.description).to.be.an('object'); expect(details.description.props).to.be.an('object').and.have.keys(['children']); - - if (deviceId === prescriptionFormConstants.deviceIdMap.palmtree) { - expect(details.skipCalculator).to.be.true; - } }); }); From e7be489e6a7db65139f78f0f94a7b669c9a036c2 Mon Sep 17 00:00:00 2001 From: clintonium-119 Date: Wed, 4 Sep 2024 10:07:28 -0400 Subject: [PATCH 019/112] Allow Stepper to render empty without errors if steps array is empty on init --- app/components/elements/Stepper.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/app/components/elements/Stepper.js b/app/components/elements/Stepper.js index 7b2ea138be..69ce9d1828 100644 --- a/app/components/elements/Stepper.js +++ b/app/components/elements/Stepper.js @@ -332,7 +332,7 @@ export function Stepper(props) { ? steps[activeStep].subSteps[activeSubStep] : steps[activeStep]; - return ( + return step ? ( {!step.hideBack && ( {title} diff --git a/app/pages/prescription/Prescriptions.js b/app/pages/prescription/Prescriptions.js index 305302f1b9..7baf8dbb20 100644 --- a/app/pages/prescription/Prescriptions.js +++ b/app/pages/prescription/Prescriptions.js @@ -209,22 +209,22 @@ const Prescriptions = props => { const items = [ { icon: isEditable ? EditRoundedIcon : VisibilityRoundedIcon, - iconLabel: isEditable ? t('Update Prescription') : t('View Prescription'), + iconLabel: isEditable ? t('Update Tidepool Loop Start Order') : t('View Tidepool Loop Start Order'), iconPosition: 'left', id: isEditable ? 'update' : 'view', onClick: handleOpenPrescription(prescription), - text: isEditable ? t('Update Prescription') : t('View Prescription'), + text: isEditable ? t('Update Tidepool Loop Start Order') : t('View Tidepool Loop Start Order'), variant: 'actionListItem', }, ]; if (isEditable) items.push({ icon: DeleteForeverRoundedIcon, - iconLabel: 'Delete Prescription', + iconLabel: 'Delete Tidepool Loop Start Order', iconPosition: 'left', id: 'delete', onClick: handleDeletePrescription(prescription), - text: t('Delete Prescription'), + text: t('Delete Tidepool Loop Start Order'), variant: 'actionListItemDanger', disabled: !isEditable, }); @@ -286,15 +286,15 @@ const Prescriptions = props => { , [t] ); const copyCodeButtonSuccessText = useMemo(() => {t('✓')}, [t]); const copyCodeButtonOnClick = useCallback(() => { - trackMetric('Clinic - Copy prescription access code', { + trackMetric('Clinic - Copy prescription activation code', { clinicId: selectedClinicId, }); }, [selectedClinicId]); @@ -332,7 +332,7 @@ const Prescriptions = props => { > {accessCode} { { title: t('MRN'), field: 'mrn', align: 'left', sortable: true, searchable: true }, { title: t('Date of birth'), field: 'birthday', align: 'left', sortable: true, searchable: true }, { title: t('Status'), field: 'state', render: renderState, align: 'left', sortable: true }, - { title: t('Access Code'), field: 'accessCode', render: renderAccessCode, align: 'left', sortable: false }, + { title: t('Activation Code'), field: 'accessCode', render: renderAccessCode, align: 'left', sortable: false }, { title: '', field: 'more', render: renderMore, align: 'right', className: 'action-menu' }, ]; @@ -392,7 +392,7 @@ const Prescriptions = props => { px={[2, 3]} sx={{ fontSize: 0, lineHeight: ['inherit', null, 1] }} > - {t('Add New Prescription')} + {t('Add New Tidepool Loop Start Order')} @@ -549,12 +549,12 @@ const Prescriptions = props => { onClose={closeDeleteDialog} > - Delete Prescription for {patientNameFromPrescription(deleteDialog.prescription)} + Delete Tidepool Loop Start Order for {patientNameFromPrescription(deleteDialog.prescription)} - Are you sure you want to delete this prescription? + Are you sure you want to delete this Tidepool Loop start order? @@ -569,7 +569,7 @@ const Prescriptions = props => { processing={deletingPrescription.inProgress} onClick={handleConfirmDeletePrescription} > - Delete Prescription + Delete Tidepool Loop Start Order diff --git a/app/pages/prescription/prescriptionFormConstants.js b/app/pages/prescription/prescriptionFormConstants.js index b9e74ab631..a51d85b965 100644 --- a/app/pages/prescription/prescriptionFormConstants.js +++ b/app/pages/prescription/prescriptionFormConstants.js @@ -618,13 +618,13 @@ export const getFormSteps = (schema, devices, values, handlers, options = {}) => }, { key: 'review', - label: t('Review and {{action}} Prescription', { action: isPrescriber ? 'Send' : 'Save' }), + label: t('Review and {{action}} Tidepool Loop Start Order', { action: isPrescriber ? 'Send' : 'Save' }), onComplete: handlers.stepSubmit, asyncState: stepAsyncState, subSteps: [ { fields: ['therapySettingsReviewed'], - completeText: t('{{action}} Prescription', { action: isPrescriber ? 'Send Final' : 'Save Pending' }), + completeText: t('{{action}} Tidepool Loop Start Order', { action: isPrescriber ? 'Send Final' : 'Save Pending' }), panelContent: }, ], diff --git a/test/unit/pages/Prescriptions.test.js b/test/unit/pages/Prescriptions.test.js index ca6e82a153..7d593d2d41 100644 --- a/test/unit/pages/Prescriptions.test.js +++ b/test/unit/pages/Prescriptions.test.js @@ -352,7 +352,7 @@ describe('Prescriptions', () => { const table = wrapper.find(Table); expect(table).to.have.length(1); expect(table.find('tr')).to.have.length(3); // header row + 2 prescriptions - const editButton = table.find('tr').at(1).find('Button[iconLabel="View Prescription"]'); + const editButton = table.find('tr').at(1).find('Button[iconLabel="View Tidepool Loop Start Order"]'); editButton.simulate('click'); expect(store.getActions()).to.eql([ @@ -367,7 +367,7 @@ describe('Prescriptions', () => { const table = wrapper.find(Table); expect(table).to.have.length(1); expect(table.find('tr')).to.have.length(3); // header row + 2 prescriptions - const editButton = table.find('tr').at(2).find('Button[iconLabel="Update Prescription"]'); + const editButton = table.find('tr').at(2).find('Button[iconLabel="Update Tidepool Loop Start Order"]'); editButton.simulate('click'); expect(store.getActions()).to.eql([ @@ -397,7 +397,7 @@ describe('Prescriptions', () => { const table = wrapper.find(Table); expect(table).to.have.length(1); expect(table.find('tr')).to.have.length(3); // header row + 2 prescriptions - const removeButton = table.find('tr').at(2).find('Button[iconLabel="Delete Prescription"]'); + const removeButton = table.find('tr').at(2).find('Button[iconLabel="Delete Tidepool Loop Start Order"]'); expect(wrapper.find('Dialog#prescription-delete').props().open).to.be.false; removeButton.simulate('click'); @@ -408,7 +408,7 @@ describe('Prescriptions', () => { expect(defaultProps.trackMetric.callCount).to.equal(1); const confirmRemoveButton = wrapper.find('Dialog#prescription-delete').find('Button#prescription-delete-confirm'); - expect(confirmRemoveButton.text()).to.equal('Delete Prescription'); + expect(confirmRemoveButton.text()).to.equal('Delete Tidepool Loop Start Order'); store.clearActions(); diff --git a/test/unit/pages/clinicworkspace.test.js b/test/unit/pages/clinicworkspace.test.js index e86d783018..a01bb97cac 100644 --- a/test/unit/pages/clinicworkspace.test.js +++ b/test/unit/pages/clinicworkspace.test.js @@ -187,7 +187,7 @@ describe('ClinicWorkspace', () => { }); it('should render the prescriptions tab by default when `prescriptions` route param provided', () => { - expect(wrapper('prescriptions').find('button[aria-selected=true]').hostNodes().text()).to.equal('Prescriptions'); + expect(wrapper('prescriptions').find('button[aria-selected=true]').hostNodes().text()).to.equal('Tidepool Loop Start Orders'); expect(wrapper().find('div[role="tabpanel"][hidden=false]').parent('#prescriptionsTab')).to.have.lengthOf(1); }); }); diff --git a/test/unit/pages/prescription/PrescriptionForm.test.js b/test/unit/pages/prescription/PrescriptionForm.test.js index 81532c85e1..9171ebc896 100644 --- a/test/unit/pages/prescription/PrescriptionForm.test.js +++ b/test/unit/pages/prescription/PrescriptionForm.test.js @@ -145,7 +145,7 @@ describe('PrescriptionForm', () => { expect(steps.at(1).find('.MuiStepLabel-label').hostNodes().text()).to.equal('Complete Patient Profile'); expect(steps.at(2).find('.MuiStepLabel-label').hostNodes().text()).to.equal('Therapy Settings Calculator'); expect(steps.at(3).find('.MuiStepLabel-label').hostNodes().text()).to.equal('Enter Therapy Settings'); - expect(steps.at(4).find('.MuiStepLabel-label').hostNodes().text()).to.equal('Review and Save Prescription'); + expect(steps.at(4).find('.MuiStepLabel-label').hostNodes().text()).to.equal('Review and Save Tidepool Loop Start Order'); }); it('should not render the calculator form steps when using the default-provided skipFields props', () => { @@ -180,7 +180,7 @@ describe('PrescriptionForm', () => { expect(steps.at(1).find('.MuiStepLabel-label').hostNodes().text()).to.equal('Complete Patient Profile'); expect(steps.at(2).find('.MuiStepLabel-label').hostNodes().text()).to.equal('Enter Therapy Settings'); - expect(steps.at(3).find('.MuiStepLabel-label').hostNodes().text()).to.equal('Review and Save Prescription'); + expect(steps.at(3).find('.MuiStepLabel-label').hostNodes().text()).to.equal('Review and Save Tidepool Loop Start Order'); }); it('should render the calculator form steps when using custom skipFields prop that does not exclude calculator', () => { @@ -216,7 +216,7 @@ describe('PrescriptionForm', () => { expect(steps.at(1).find('.MuiStepLabel-label').hostNodes().text()).to.equal('Complete Patient Profile'); expect(steps.at(2).find('.MuiStepLabel-label').hostNodes().text()).to.equal('Therapy Settings Calculator'); expect(steps.at(3).find('.MuiStepLabel-label').hostNodes().text()).to.equal('Enter Therapy Settings'); - expect(steps.at(4).find('.MuiStepLabel-label').hostNodes().text()).to.equal('Review and Save Prescription'); + expect(steps.at(4).find('.MuiStepLabel-label').hostNodes().text()).to.equal('Review and Save Tidepool Loop Start Order'); }); it('should render the form actions, with only the `next` button on the first step', () => { diff --git a/test/unit/pages/prescription/prescriptionFormConstants.test.js b/test/unit/pages/prescription/prescriptionFormConstants.test.js index bb9edfc6be..02c9d72d92 100644 --- a/test/unit/pages/prescription/prescriptionFormConstants.test.js +++ b/test/unit/pages/prescription/prescriptionFormConstants.test.js @@ -1389,19 +1389,19 @@ describe('prescriptionFormConstants', function() { }); it('should include the step label for a clinician with prescriber permissions', () => { - expect(reviewFormStep().label).to.equal('Review and Send Prescription'); + expect(reviewFormStep().label).to.equal('Review and Send Tidepool Loop Start Order'); }); it('should include the step label for a clinician without prescriber permissions', () => { - expect(reviewFormStep(defaultValues, { ...defaultOptions, isPrescriber: false }).label).to.equal('Review and Save Prescription'); + expect(reviewFormStep(defaultValues, { ...defaultOptions, isPrescriber: false }).label).to.equal('Review and Save Tidepool Loop Start Order'); }); it('should include the custom next button text for a clinician with prescriber permissions', () => { - expect(reviewFormStep().subSteps[0].completeText).to.equal('Send Final Prescription'); + expect(reviewFormStep().subSteps[0].completeText).to.equal('Send Final Tidepool Loop Start Order'); }); it('should include the custom next button text for a clinician without prescriber permissions', () => { - expect(reviewFormStep(defaultValues, { ...defaultOptions, isPrescriber: false }).subSteps[0].completeText).to.equal('Save Pending Prescription'); + expect(reviewFormStep(defaultValues, { ...defaultOptions, isPrescriber: false }).subSteps[0].completeText).to.equal('Save Pending Tidepool Loop Start Order'); }); it('should include panel content with pump and handlers passed along as props', () => { From ab3348f6c6b05a60b11f9370be7ff155b229f340 Mon Sep 17 00:00:00 2001 From: clintonium-119 Date: Fri, 6 Sep 2024 09:28:47 -0400 Subject: [PATCH 029/112] v1.81.0-rc.8 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 66d234a135..6177bf3afe 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "node": "20.8.0" }, "packageManager": "yarn@3.6.4", - "version": "1.81.0-web-3087-prescription-copy-updates.1", + "version": "1.81.0-rc.8", "private": true, "scripts": { "test": "TZ=UTC NODE_ENV=test NODE_OPTIONS='--max-old-space-size=4096' yarn karma start", From 05d0b40c80cd0f900ada4e45a236aae5030aac83 Mon Sep 17 00:00:00 2001 From: clintonium-119 Date: Fri, 6 Sep 2024 09:57:10 -0400 Subject: [PATCH 030/112] Trigger CI From 7ff4fc9dc525ca20ae345e7261afc4fd2635c1c1 Mon Sep 17 00:00:00 2001 From: clintonium-119 Date: Mon, 9 Sep 2024 13:27:18 -0400 Subject: [PATCH 031/112] Bump tideline and viz --- package.json | 4 ++-- yarn.lock | 20 ++++++++++---------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/package.json b/package.json index 6177bf3afe..4622e7b523 100644 --- a/package.json +++ b/package.json @@ -66,7 +66,7 @@ "@storybook/react": "7.5.0", "@storybook/react-webpack5": "7.5.0", "@testing-library/react-hooks": "8.0.1", - "@tidepool/viz": "1.42.0-rc.2", + "@tidepool/viz": "1.42.0-rc.3", "async": "2.6.4", "autoprefixer": "10.4.16", "babel-core": "7.0.0-bridge.0", @@ -183,7 +183,7 @@ "terser": "5.22.0", "terser-webpack-plugin": "5.3.9", "theme-ui": "0.16.1", - "tideline": "1.30.0-rc.2", + "tideline": "1.30.0-rc.3", "tidepool-platform-client": "0.59.0", "tidepool-standard-action": "0.1.1", "ua-parser-js": "1.0.36", diff --git a/yarn.lock b/yarn.lock index ba210684d1..97dc95938f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5268,9 +5268,9 @@ __metadata: languageName: node linkType: hard -"@tidepool/viz@npm:1.42.0-rc.2": - version: 1.42.0-rc.2 - resolution: "@tidepool/viz@npm:1.42.0-rc.2" +"@tidepool/viz@npm:1.42.0-rc.3": + version: 1.42.0-rc.3 + resolution: "@tidepool/viz@npm:1.42.0-rc.3" dependencies: bluebird: 3.7.2 bows: 1.7.2 @@ -5330,7 +5330,7 @@ __metadata: react-dom: 16.x react-redux: 8.x redux: 4.x - checksum: 3fd41ada15753b717c00c5441f6f059fb86901481fd34a7e08a23c0798f11e281c1a445a5383d65fd06666441ce4e3f8bc72620d08c8656e208d2b33e91c32a0 + checksum: 24b2b311d8253f7054c5083ffa93273d527fbbcfb292c6f40e026aae06dcddac23ec3205630ff9a3844d5de2edbd85ecab2503227c17272c29daa9d591ed6005 languageName: node linkType: hard @@ -7427,7 +7427,7 @@ __metadata: "@storybook/react": 7.5.0 "@storybook/react-webpack5": 7.5.0 "@testing-library/react-hooks": 8.0.1 - "@tidepool/viz": 1.42.0-rc.2 + "@tidepool/viz": 1.42.0-rc.3 async: 2.6.4 autoprefixer: 10.4.16 babel-core: 7.0.0-bridge.0 @@ -7551,7 +7551,7 @@ __metadata: terser: 5.22.0 terser-webpack-plugin: 5.3.9 theme-ui: 0.16.1 - tideline: 1.30.0-rc.2 + tideline: 1.30.0-rc.3 tidepool-platform-client: 0.59.0 tidepool-standard-action: 0.1.1 ua-parser-js: 1.0.36 @@ -22991,9 +22991,9 @@ __metadata: languageName: node linkType: hard -"tideline@npm:1.30.0-rc.2": - version: 1.30.0-rc.2 - resolution: "tideline@npm:1.30.0-rc.2" +"tideline@npm:1.30.0-rc.3": + version: 1.30.0-rc.3 + resolution: "tideline@npm:1.30.0-rc.3" dependencies: bows: 1.7.2 classnames: 2.3.2 @@ -23013,7 +23013,7 @@ __metadata: peerDependencies: babel-core: 6.x || 7.0.0-bridge.0 lodash: ^4.17.21 - checksum: bc0c370495110766a1e54b645195dd3801c89c051a8cc0e8ae340c0d7851bdf3bb41492f9593c2383cb1bbcc00073f20a22dd1f978c4b36ce7ce54efff9b82f6 + checksum: 38519275a0bf05aeaf6865da1a50856ec0142dd1d41f424b4bcee434a9a98f3cf2d41cb54c113059ea330e196aa84f410ed5a42c2f77c813ef7bd7d7869f3c15 languageName: node linkType: hard From 1b90642843f27407be4809cdbd97a9aeb409792b Mon Sep 17 00:00:00 2001 From: Chris McGee Date: Mon, 9 Sep 2024 13:55:25 -0400 Subject: [PATCH 032/112] [WEB-3082] fetch all uploads and pumpSettings --- app/pages/patientdata/patientdata.js | 9 ++++- test/unit/pages/patientdata.test.js | 60 +++++++++++++++++++++++++++- 2 files changed, 67 insertions(+), 2 deletions(-) diff --git a/app/pages/patientdata/patientdata.js b/app/pages/patientdata/patientdata.js index 82ffe252cd..8e3cbcdfec 100644 --- a/app/pages/patientdata/patientdata.js +++ b/app/pages/patientdata/patientdata.js @@ -1254,7 +1254,7 @@ export const PatientDataClass = createReactClass({ this.fetchEarlierData({ returnData: false, showLoading: true, - startDate: moment.utc().subtract(10, 'year').toISOString(), + noDates: true, type: 'pumpSettings,upload', }); @@ -2090,6 +2090,7 @@ export const PatientDataClass = createReactClass({ * @param {boolean} [options.medtronic=this.props.medtronic] - Whether to include Medtronic data. * @param {boolean} [options.useCache=false] - Whether to use cached data. * @param {boolean} [options.initial=false] - Whether this is the initial data fetch. + * @param {boolean} [options.noDates=false] - Whether to fetch data without start and end dates.. * * @returns {void} */ @@ -2110,8 +2111,14 @@ export const PatientDataClass = createReactClass({ medtronic: this.props.medtronic, useCache: false, initial: false, + noDates: false, }); + if (fetchOpts.noDates) { + fetchOpts.startDate = undefined; + fetchOpts.endDate = undefined; + } + const count = this.state.fetchEarlierDataCount + 1; this.setState({ diff --git a/test/unit/pages/patientdata.test.js b/test/unit/pages/patientdata.test.js index 8c777bcae8..669a0ad457 100644 --- a/test/unit/pages/patientdata.test.js +++ b/test/unit/pages/patientdata.test.js @@ -4163,27 +4163,32 @@ describe('PatientData', function () { let instance; let props; let setStateSpy; + let logSpy; beforeEach(() => { props = _.assign({}, defaultProps, { onFetchEarlierData: sinon.stub(), + trackMetric: sinon.stub(), + log: sinon.stub(), }); - wrapper = shallow(); instance = wrapper.instance(); setStateSpy = sinon.spy(instance, 'setState'); + logSpy = sinon.spy(instance, 'log'); }); afterEach(() => { props.onFetchEarlierData.reset(); props.trackMetric.reset(); setStateSpy.resetHistory(); + logSpy.resetHistory(); }); after(() => { setStateSpy.restore(); + logSpy.restore(); }); context('currently fetching data', () => { @@ -4225,6 +4230,7 @@ describe('PatientData', function () { medtronic: undefined, initial: false, useCache: false, + noDates: false, }, '40'); }); @@ -4364,6 +4370,58 @@ describe('PatientData', function () { clinicId: 'clinic123', }); }); + + it('should set startDate and endDate to undefined if noDates is true', () => { + const fetchedUntil = '2018-01-01T00:00:00.000Z'; + + wrapper.setProps({ + currentPatientInViewId: '40', + data: { + fetchedUntil, + }, + }); + + const options = { + noDates: true, + }; + + instance.fetchEarlierData(options); + + sinon.assert.calledOnce(props.onFetchEarlierData); + sinon.assert.calledWithMatch(props.onFetchEarlierData, { + startDate: undefined, + endDate: undefined, + }, '40'); + }); + + it('should call the log method', () => { + instance.fetchEarlierData(); + + sinon.assert.calledOnce(logSpy); + sinon.assert.calledWith(logSpy, 'fetching'); + }); + + it('should pass the initial option correctly', () => { + const fetchedUntil = '2018-01-01T00:00:00.000Z'; + + wrapper.setProps({ + currentPatientInViewId: '40', + data: { + fetchedUntil, + }, + }); + + const options = { + initial: true, + }; + + instance.fetchEarlierData(options); + + sinon.assert.calledOnce(props.onFetchEarlierData); + sinon.assert.calledWithMatch(props.onFetchEarlierData, { + initial: true, + }, '40'); + }); }); }); From c44f38c4df9ab3e6145e115af859ece983914e29 Mon Sep 17 00:00:00 2001 From: Chris McGee Date: Mon, 9 Sep 2024 13:58:02 -0400 Subject: [PATCH 033/112] [WEB-3035] add <1 day option --- app/components/chart/settings.js | 3 ++ test/unit/components/chart/settings.test.js | 33 +++++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/app/components/chart/settings.js b/app/components/chart/settings.js index d7fd901112..468c206e04 100644 --- a/app/components/chart/settings.js +++ b/app/components/chart/settings.js @@ -35,6 +35,9 @@ const log = bows('Settings View'); function formatDuration(milliseconds) { const days = Math.round(milliseconds / (1000 * 60 * 60 * 24)); + if (days === 0) { + return '<1 day'; + } if (days <= 31) { return `${days} day${days === 1 ? '' : 's'}`; } diff --git a/test/unit/components/chart/settings.test.js b/test/unit/components/chart/settings.test.js index a6a9c8534a..20840fcec4 100644 --- a/test/unit/components/chart/settings.test.js +++ b/test/unit/components/chart/settings.test.js @@ -720,6 +720,39 @@ describe('Settings', () => { ); }); + it('formats duration correctly for very short periods in settings selection options', () => { + clock.jump(new Date('2023-01-03T00:00:00Z').getTime()); + mountWrapper({ + data: { + data: { + combined: [ + { + type: 'pumpSettings', + normalTime: moment('2023-01-01T20:00:00Z').valueOf(), + source: 'source1', + }, + { + type: 'pumpSettings', + normalTime: moment('2023-01-02T00:00:00Z').valueOf(), + source: 'source1', + }, + ], + }, + timePrefs: { timezoneName: 'UTC' }, + }, + }); + wrapper.update(); + const settingsRadioGroup = wrapper.find('RadioGroup#settings'); + const radioOptions = settingsRadioGroup.find('Radio'); + expect(radioOptions).to.have.lengthOf(2); + expect(radioOptions.at(0).text()).to.equal( + 'Jan 02, 2023 - Jan 03, 2023 : Active for 1 day' + ); + expect(radioOptions.at(1).text()).to.equal( + 'Jan 01, 2023 - Jan 02, 2023 : Active for <1 day' + ); + }); + it('formats duration correctly for longer periods in settings selection options', () => { clock.jump(new Date('2023-03-01T00:00:00Z').getTime()); mountWrapper({ From 81b563d7d3a9ec406ad9470ac2be02b93ad52e2a Mon Sep 17 00:00:00 2001 From: clintonium-119 Date: Mon, 9 Sep 2024 16:55:12 -0400 Subject: [PATCH 034/112] v1.81.0-rc.9 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 4622e7b523..b0b7453534 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "node": "20.8.0" }, "packageManager": "yarn@3.6.4", - "version": "1.81.0-rc.8", + "version": "1.81.0-rc.9", "private": true, "scripts": { "test": "TZ=UTC NODE_ENV=test NODE_OPTIONS='--max-old-space-size=4096' yarn karma start", From 027ad2ced3454aa8d82d78fdcb5fe0f5daf13ff8 Mon Sep 17 00:00:00 2001 From: Chris McGee Date: Mon, 9 Sep 2024 17:06:06 -0400 Subject: [PATCH 035/112] v1.81.0-rc.10 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 6177bf3afe..59a9f5dd66 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "node": "20.8.0" }, "packageManager": "yarn@3.6.4", - "version": "1.81.0-rc.8", + "version": "1.81.0-rc.10", "private": true, "scripts": { "test": "TZ=UTC NODE_ENV=test NODE_OPTIONS='--max-old-space-size=4096' yarn karma start", From 754d30b2f9f78105cdd42407ad36497f1d1bcd15 Mon Sep 17 00:00:00 2001 From: clintonium-119 Date: Tue, 10 Sep 2024 10:31:07 -0400 Subject: [PATCH 036/112] Disable form persistence on single-step edits --- app/pages/prescription/PrescriptionForm.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/pages/prescription/PrescriptionForm.js b/app/pages/prescription/PrescriptionForm.js index db0b615f7b..edae0097de 100644 --- a/app/pages/prescription/PrescriptionForm.js +++ b/app/pages/prescription/PrescriptionForm.js @@ -668,7 +668,7 @@ export const PrescriptionForm = props => { )} - {formPersistReady && } + {formPersistReady && !isSingleStepEdit && } ); }; From 849e780d0aeb98b0b904c91b73e40e6ecc9ef9ea Mon Sep 17 00:00:00 2001 From: clintonium-119 Date: Tue, 10 Sep 2024 10:48:53 -0400 Subject: [PATCH 037/112] v1.81.0-rc.12 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e62a423304..0e00820c8d 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "node": "20.8.0" }, "packageManager": "yarn@3.6.4", - "version": "1.81.0-rc.11", + "version": "1.81.0-rc.12", "private": true, "scripts": { "test": "TZ=UTC NODE_ENV=test NODE_OPTIONS='--max-old-space-size=4096' yarn karma start", From c1e6c199c62b0054d3fba8bce4d72b5bad5f2d9f Mon Sep 17 00:00:00 2001 From: clintonium-119 Date: Tue, 10 Sep 2024 12:42:04 -0400 Subject: [PATCH 038/112] Update cyan pill pallete for better contrast --- app/components/elements/Pill.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/components/elements/Pill.js b/app/components/elements/Pill.js index 84b4108aa1..7b0d7cdb26 100644 --- a/app/components/elements/Pill.js +++ b/app/components/elements/Pill.js @@ -8,7 +8,7 @@ import baseTheme from '../../themes/baseTheme'; const namedPalletMap = { blues: ['blues.0', 'blues.9'], - cyans: ['cyans.0', 'cyans.9'], + cyans: ['cyans.0', '#15798E'], grays: ['grays.0', 'grays.9'], greens: ['greens.0', 'greens.9'], indigos: ['indigos.0', 'indigos.9'], From e460267f993afed5e6a4b86581a8725e78f4ce2c Mon Sep 17 00:00:00 2001 From: clintonium-119 Date: Tue, 10 Sep 2024 13:00:03 -0400 Subject: [PATCH 039/112] Update radio focus styles --- app/components/elements/RadioGroup.js | 7 ++++--- app/themes/base/inputs.js | 4 ++++ 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/app/components/elements/RadioGroup.js b/app/components/elements/RadioGroup.js index a9ed25714b..d29dfb9c3c 100644 --- a/app/components/elements/RadioGroup.js +++ b/app/components/elements/RadioGroup.js @@ -15,8 +15,9 @@ import { const StyledRadio = styled(Base)` color: ${colors.border.default}; - width: 1.5em; - height: 1.5em; + width: 2em; + height: 2em; + padding: .25em; margin-right: 0.5em; cursor: pointer; @@ -40,7 +41,7 @@ const StyledRadio = styled(Base)` const StyledRadioLabel = styled(Text)` display: inline-block; margin-right: 2em; - margin-top: .05em; + margin-top: .3em; &.disabled { color: ${colors.text.primaryDisabled}; diff --git a/app/themes/base/inputs.js b/app/themes/base/inputs.js index 61f913f6ef..b47e8cd3a3 100644 --- a/app/themes/base/inputs.js +++ b/app/themes/base/inputs.js @@ -70,6 +70,10 @@ export default ({ borders, colors, fonts, radii, fontSizes, fontWeights, space } borderRadius: 0, color: colors.text.primary, fontSize: 'inherit', + + 'input:focus~svg': { + backgroundColor: 'rgba(90,152,248,0.24)', + }, }; const checkboxes = { From 359151c7d9da88d68b8ecbe126f9fd8ccfc96488 Mon Sep 17 00:00:00 2001 From: clintonium-119 Date: Tue, 10 Sep 2024 14:12:03 -0400 Subject: [PATCH 040/112] Copy updates for therapy settings form --- app/pages/prescription/Prescriptions.js | 2 +- .../prescription/therapySettingsFormStep.js | 20 +++++++++---------- locales/en/translation.json | 20 +++++++++---------- locales/es/translation.json | 20 +++++++++---------- locales/fr/translation.json | 20 +++++++++---------- .../pages/prescription/ScheduleForm.test.js | 2 +- 6 files changed, 42 insertions(+), 42 deletions(-) diff --git a/app/pages/prescription/Prescriptions.js b/app/pages/prescription/Prescriptions.js index 7baf8dbb20..37a2e867a6 100644 --- a/app/pages/prescription/Prescriptions.js +++ b/app/pages/prescription/Prescriptions.js @@ -392,7 +392,7 @@ const Prescriptions = props => { px={[2, 3]} sx={{ fontSize: 0, lineHeight: ['inherit', null, 1] }} > - {t('Add New Tidepool Loop Start Order')} + {t('Create New Tidepool Loop Start Order')} diff --git a/app/pages/prescription/therapySettingsFormStep.js b/app/pages/prescription/therapySettingsFormStep.js index fcef7fa48f..ac1e269aed 100644 --- a/app/pages/prescription/therapySettingsFormStep.js +++ b/app/pages/prescription/therapySettingsFormStep.js @@ -190,7 +190,7 @@ export const GlucoseSettings = props => { { @@ -357,11 +357,11 @@ export const InsulinSettings = props => { { @@ -388,11 +388,11 @@ export const InsulinSettings = props => { { @@ -422,11 +422,11 @@ export const InsulinSettings = props => { { initialValues={{ ...formikContext.values }} > Date: Tue, 10 Sep 2024 14:25:23 -0400 Subject: [PATCH 041/112] v1.81.0-rc.13 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 0e00820c8d..c0642ebca3 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "node": "20.8.0" }, "packageManager": "yarn@3.6.4", - "version": "1.81.0-rc.12", + "version": "1.81.0-rc.13", "private": true, "scripts": { "test": "TZ=UTC NODE_ENV=test NODE_OPTIONS='--max-old-space-size=4096' yarn karma start", From 947537020f33155e970ed6c721cf0dfe8bdfcd75 Mon Sep 17 00:00:00 2001 From: clintonium-119 Date: Thu, 12 Sep 2024 10:49:53 -0400 Subject: [PATCH 042/112] Update glucose safety limit required message --- app/pages/prescription/prescriptionSchema.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/pages/prescription/prescriptionSchema.js b/app/pages/prescription/prescriptionSchema.js index b179126a85..666144e1c0 100644 --- a/app/pages/prescription/prescriptionSchema.js +++ b/app/pages/prescription/prescriptionSchema.js @@ -156,7 +156,7 @@ export default (devices, pumpId, bgUnits = defaultUnits.bloodGlucose, values) => glucoseSafetyLimit: yup.number() .min(ranges.glucoseSafetyLimit.min, rangeError('glucoseSafetyLimit')) .max(ranges.glucoseSafetyLimit.max, rangeError('glucoseSafetyLimit')) - .required(t('Suspend threshold is required')), + .required(t('Glucose safety limit is required')), basalRateMaximum: yup.object().shape({ value: yup.number() .min(ranges.basalRateMaximum.min, rangeError('basalRateMaximum')) From 28de90116b3875a031466c4c0a63db97546cfba9 Mon Sep 17 00:00:00 2001 From: clintonium-119 Date: Thu, 12 Sep 2024 11:33:37 -0400 Subject: [PATCH 043/112] Fix js error when a device guardrail is provided with a null value --- app/pages/prescription/prescriptionFormConstants.js | 5 ++++- .../pages/prescription/prescriptionFormConstants.test.js | 5 +++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/app/pages/prescription/prescriptionFormConstants.js b/app/pages/prescription/prescriptionFormConstants.js index a51d85b965..f4a6553d2b 100644 --- a/app/pages/prescription/prescriptionFormConstants.js +++ b/app/pages/prescription/prescriptionFormConstants.js @@ -94,7 +94,10 @@ export const defaultUnits = { weight: 'kg', }; -export const getPumpGuardrail = (pump, path, fallbackValue) => getFloatFromUnitsAndNanos(get(pump, `guardRails.${path}`)) || fallbackValue; +export const getPumpGuardrail = (pump, path, fallbackValue) => { + const guardrail = get(pump, `guardRails.${path}`); + return guardrail ? getFloatFromUnitsAndNanos(get(pump, `guardRails.${path}`)) : fallbackValue; +}; export const getBgInTargetUnits = (bgValue, bgUnits, targetUnits) => { if (bgUnits === targetUnits || !isFinite(bgValue)) return bgValue; diff --git a/test/unit/pages/prescription/prescriptionFormConstants.test.js b/test/unit/pages/prescription/prescriptionFormConstants.test.js index 02c9d72d92..c2640799a3 100644 --- a/test/unit/pages/prescription/prescriptionFormConstants.test.js +++ b/test/unit/pages/prescription/prescriptionFormConstants.test.js @@ -130,6 +130,7 @@ describe('prescriptionFormConstants', function() { nanos: 12000000, }, }, + guardRail4: null, }, }; @@ -141,6 +142,10 @@ describe('prescriptionFormConstants', function() { it('should fall back to provided value if guard rail cannot be provided from path', () => { expect(prescriptionFormConstants.getPumpGuardrail(pump, 'guardRail3', 'foo')).to.equal('foo'); }); + + it('should fall back to provided value if guard rail provided from path is null', () => { + expect(prescriptionFormConstants.getPumpGuardrail(pump, 'guardRail4', 'bar')).to.equal('bar'); + }); }); describe('getBgInTargetUnits', () => { From 896a019c0a3d56acf373fb86979cf2348161f6ac Mon Sep 17 00:00:00 2001 From: clintonium-119 Date: Thu, 12 Sep 2024 11:38:41 -0400 Subject: [PATCH 044/112] v1.81.0-rc.14 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index c0642ebca3..81fbb2c7cc 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "node": "20.8.0" }, "packageManager": "yarn@3.6.4", - "version": "1.81.0-rc.13", + "version": "1.81.0-rc.14", "private": true, "scripts": { "test": "TZ=UTC NODE_ENV=test NODE_OPTIONS='--max-old-space-size=4096' yarn karma start", From 6ab1bd3812d2a64233eed408d2a6c6e91a6927a8 Mon Sep 17 00:00:00 2001 From: Chris McGee Date: Thu, 12 Sep 2024 19:53:40 -0400 Subject: [PATCH 045/112] [WEB-3082] customize fetch options for settings refresh --- app/pages/patientdata/patientdata.js | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/app/pages/patientdata/patientdata.js b/app/pages/patientdata/patientdata.js index 8e3cbcdfec..13c71861af 100644 --- a/app/pages/patientdata/patientdata.js +++ b/app/pages/patientdata/patientdata.js @@ -1308,7 +1308,7 @@ export const PatientDataClass = createReactClass({ } // Prior to refetching data, we need to remove current data from the data worker - // Refetch will occur in UNSAFE_componentWillRecieveProps after data worker is emptied + // Refetch will occur in UNSAFE_componentWillReceiveProps after data worker is emptied this.props.dataWorkerRemoveDataRequest(null, this.props.currentPatientInViewId); }, @@ -1683,7 +1683,7 @@ export const PatientDataClass = createReactClass({ endpoints: undefined, refreshChartType: this.state.chartType, }, () => { - this.props.onRefresh(this.props.currentPatientInViewId); + this.props.onRefresh(this.props.currentPatientInViewId, this.state.refreshChartType); this.props.removeGeneratedPDFS(); }); }); @@ -2372,7 +2372,22 @@ let mergeProps = (stateProps, dispatchProps, ownProps) => { fetchers: getFetchers(dispatchProps, ownProps, stateProps, api, { carelink, dexcom, medtronic }), history: ownProps.history, uploadUrl: api.getUploadUrl(), - onRefresh: dispatchProps.fetchPatientData.bind(null, api, { carelink, dexcom, medtronic }), + onRefresh: (patientId, chartType) => { + const fetchOptions = { + carelink, + dexcom, + medtronic + }; + if(chartType === 'settings') { + _.extend(fetchOptions, { + type: 'pumpSettings,upload', + initial: false, + startDate: undefined, + endDate: undefined, + }); + } + return dispatchProps.fetchPatientData(api, fetchOptions, patientId); + }, onFetchMessageThread: dispatchProps.fetchMessageThread.bind(null, api), onCloseMessageThread: dispatchProps.closeMessageThread, onSaveComment: api.team.replyToMessageThread.bind(api), From d5cb48896a766376d56aac6dbeaabbd74d1171fc Mon Sep 17 00:00:00 2001 From: Chris McGee Date: Fri, 13 Sep 2024 12:25:46 -0400 Subject: [PATCH 046/112] v1.81.0-rc.15 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 81fbb2c7cc..79d4e1c0e3 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "node": "20.8.0" }, "packageManager": "yarn@3.6.4", - "version": "1.81.0-rc.14", + "version": "1.81.0-rc.15", "private": true, "scripts": { "test": "TZ=UTC NODE_ENV=test NODE_OPTIONS='--max-old-space-size=4096' yarn karma start", From 164c7be08c16c6746333935c1df42b4fff7f1fa0 Mon Sep 17 00:00:00 2001 From: clintonium-119 Date: Fri, 13 Sep 2024 14:13:27 -0400 Subject: [PATCH 047/112] Round all therapy settings values in error messages to the nearest increment --- app/core/utils.js | 15 +++++++++++++-- app/pages/prescription/prescriptionSchema.js | 13 +++++++------ 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/app/core/utils.js b/app/core/utils.js index f6b2ae8bad..7f49271816 100644 --- a/app/core/utils.js +++ b/app/core/utils.js @@ -276,6 +276,18 @@ utils.translateBg = (value, targetUnits) => { return parseFloat((value / MGDL_PER_MMOLL).toFixed(1)); } +/** + * Round to the nearest increment + * + * @param {Number} value a numerical value to round + * @param {String} nearest increment to round to + * @param {String} precision precision to display + */ +utils.roundToNearest = (value, nearest) => { + const [units, decimals = ''] = nearest.toString().split('.'); + return parseFloat((nearest * Math.round(value / nearest)).toFixed(decimals.length)); +} + /** * Round a target BG value as appropriate * mg/dL - to the nearest 5 @@ -288,8 +300,7 @@ utils.translateBg = (value, targetUnits) => { */ utils.roundBgTarget = (value, units) => { const nearest = units === MGDL_UNITS ? 5 : 0.1; - const precision = units === MGDL_UNITS ? 0 : 1; - return parseFloat((nearest * Math.round(value / nearest)).toFixed(precision)); + return utils.roundToNearest(value, nearest); } utils.getTimePrefsForDataProcessing = (latestUpload, latestDiabetesDatum, queryParams) => { diff --git a/app/pages/prescription/prescriptionSchema.js b/app/pages/prescription/prescriptionSchema.js index 666144e1c0..27cf0f2359 100644 --- a/app/pages/prescription/prescriptionSchema.js +++ b/app/pages/prescription/prescriptionSchema.js @@ -3,6 +3,7 @@ import i18next from '../../core/language'; import find from 'lodash/find'; import includes from 'lodash/includes'; import map from 'lodash/map'; +import max from 'lodash/max'; import moment from 'moment'; import { MGDL_UNITS, MMOLL_UNITS, MS_IN_DAY } from '../../core/constants'; import utils from '../../core/utils'; @@ -51,9 +52,9 @@ export default (devices, pumpId, bgUnits = defaultUnits.bloodGlucose, values) => cgms: cgmDeviceOptions(devices), } - const rangeError = key => t('Please select a value between {{min}}-{{max}}', { - min: ranges[key].min, - max: ranges[key].max, + const rangeError = (key) => t('Please select a value between {{min}}-{{max}}', { + min: utils.roundToNearest(ranges[key].min, ranges[key].increment || 1), + max: utils.roundToNearest(ranges[key].max, ranges[key].increment || 1), }); return yup.object().shape({ @@ -176,7 +177,7 @@ export default (devices, pumpId, bgUnits = defaultUnits.bloodGlucose, values) => bloodGlucoseTargetSchedule: yup.array().of( yup.object().shape({ high: yup.number() - .min(yup.ref('low') || ranges.bloodGlucoseTarget.min, rangeError('bloodGlucoseTarget').replace(ranges.bloodGlucoseTarget.min, '${min}')) + .min(utils.roundToNearest(max([parseFloat(yup.ref('low')), ranges.bloodGlucoseTarget.min]), ranges.bloodGlucoseTarget.increment), rangeError('bloodGlucoseTarget').replace(ranges.bloodGlucoseTarget.min, '${min}')) .max(ranges.bloodGlucoseTarget.max, rangeError('bloodGlucoseTarget')) .required(t('High target is required')), low: yup.number() @@ -192,7 +193,7 @@ export default (devices, pumpId, bgUnits = defaultUnits.bloodGlucose, values) => ), bloodGlucoseTargetPhysicalActivity: yup.object().shape({ high: yup.number() - .min(yup.ref('low') || ranges.bloodGlucoseTargetPhysicalActivity.min, rangeError('bloodGlucoseTargetPhysicalActivity').replace(ranges.bloodGlucoseTargetPhysicalActivity.min, '${min}')) + .min(utils.roundToNearest(max([parseFloat(yup.ref('low')), ranges.bloodGlucoseTargetPhysicalActivity.min]), ranges.bloodGlucoseTargetPhysicalActivity.increment), rangeError('bloodGlucoseTargetPhysicalActivity').replace(ranges.bloodGlucoseTargetPhysicalActivity.min, '${min}')) .max(ranges.bloodGlucoseTargetPhysicalActivity.max, rangeError('bloodGlucoseTargetPhysicalActivity')) .required(t('High target is required')), low: yup.number() @@ -202,7 +203,7 @@ export default (devices, pumpId, bgUnits = defaultUnits.bloodGlucose, values) => }), bloodGlucoseTargetPreprandial: yup.object().shape({ high: yup.number() - .min(yup.ref('low') || ranges.bloodGlucoseTargetPreprandial.min, rangeError('bloodGlucoseTargetPreprandial').replace(ranges.bloodGlucoseTargetPreprandial.min, '${min}')) + .min(utils.roundToNearest(max([parseFloat(yup.ref('low')), ranges.bloodGlucoseTargetPreprandial.min]), ranges.bloodGlucoseTargetPreprandial.increment), rangeError('bloodGlucoseTargetPreprandial').replace(ranges.bloodGlucoseTargetPreprandial.min, '${min}')) .max(ranges.bloodGlucoseTargetPreprandial.max, rangeError('bloodGlucoseTargetPreprandial')) .required(t('High target is required')), low: yup.number() From e675f6487450aca6a0a14ada23252e67aa625b23 Mon Sep 17 00:00:00 2001 From: clintonium-119 Date: Fri, 13 Sep 2024 14:14:50 -0400 Subject: [PATCH 048/112] Default to a clinic's preffered bg units for new prescription creation. --- app/pages/prescription/PrescriptionForm.js | 171 +++++++++++---------- 1 file changed, 88 insertions(+), 83 deletions(-) diff --git a/app/pages/prescription/PrescriptionForm.js b/app/pages/prescription/PrescriptionForm.js index edae0097de..b6c03b3cfe 100644 --- a/app/pages/prescription/PrescriptionForm.js +++ b/app/pages/prescription/PrescriptionForm.js @@ -71,7 +71,9 @@ const prescriptionFormWrapper = Component => props => { const loggedInUserId = useSelector((state) => state.blip.loggedInUserId); const devices = useSelector((state) => state.blip.devices); const prescriptions = useSelector((state) => state.blip.prescriptions); + const clinics = useSelector((state) => state.blip.clinics); const selectedClinicId = useSelector((state) => state.blip.selectedClinicId); + const clinic = get(clinics, selectedClinicId); const prescriptionId = props.match?.params?.id; const prescription = get(keyBy(prescriptions, 'id'), prescriptionId); @@ -110,94 +112,98 @@ const prescriptionFormWrapper = Component => props => { {fetchingDevices.completed && fetchingClinicPrescriptions.completed - ? + ? : } ); } -export const prescriptionForm = (bgUnits = defaultUnits.bloodGlucose) => ({ - mapPropsToStatus: props => ({ - hydratedValues: null, - isPrescriptionEditFlow: !!props.prescription, - }), - mapPropsToValues: props => { - const selectedPumpId = get(props, 'prescription.latestRevision.attributes.initialSettings.pumpId'); - - return { - id: get(props, 'prescription.id'), - state: get(props, 'prescription.latestRevision.attributes.state', 'draft'), - accountType: get(props, 'prescription.latestRevision.attributes.accountType'), - firstName: get(props, 'prescription.latestRevision.attributes.firstName'), - caregiverFirstName: get(props, 'prescription.latestRevision.attributes.caregiverFirstName'), - caregiverLastName: get(props, 'prescription.latestRevision.attributes.caregiverLastName'), - lastName: get(props, 'prescription.latestRevision.attributes.lastName'), - birthday: get(props, 'prescription.latestRevision.attributes.birthday'), - email: get(props, 'prescription.latestRevision.attributes.email'), - emailConfirm: get(props, 'prescription.latestRevision.attributes.email'), - phoneNumber: { - countryCode: get(props, 'prescription.latestRevision.attributes.phoneNumber.countryCode', validCountryCodes[0]), - number: get(props, 'prescription.latestRevision.attributes.phoneNumber.number'), - }, - mrn: get(props, 'prescription.latestRevision.attributes.mrn'), - sex: get(props, 'prescription.latestRevision.attributes.sex'), - calculator: { - method: get(props, 'prescription.latestRevision.attributes.calculator.method'), - weight: get(props, 'prescription.latestRevision.attributes.calculator.weight'), - weightUnits: get(props, 'prescription.latestRevision.attributes.calculator.weightUnits', defaultUnits.weight), - totalDailyDose: get(props, 'prescription.latestRevision.attributes.calculator.totalDailyDose'), - totalDailyDoseScaleFactor: get(props, 'prescription.latestRevision.attributes.calculator.totalDailyDoseScaleFactor', 1), - recommendedBasalRate: get(props, 'prescription.latestRevision.attributes.calculator.recommendedBasalRate'), - recommendedInsulinSensitivity: get(props, 'prescription.latestRevision.attributes.calculator.recommendedInsulinSensitivity'), - recommendedCarbohydrateRatio: get(props, 'prescription.latestRevision.attributes.calculator.recommendedCarbohydrateRatio'), - }, - initialSettings: { - bloodGlucoseUnits: get(props, 'prescription.latestRevision.attributes.initialSettings.bloodGlucoseUnits', defaultUnits.bloodGlucose), - pumpId: selectedPumpId, - cgmId: get(props, 'prescription.latestRevision.attributes.initialSettings.cgmId'), - insulinModel: get(props, 'prescription.latestRevision.attributes.initialSettings.insulinModel'), - glucoseSafetyLimit: get(props, 'prescription.latestRevision.attributes.initialSettings.glucoseSafetyLimit'), - basalRateMaximum: { - value: get(props, 'prescription.latestRevision.attributes.initialSettings.basalRateMaximum.value'), - units: defaultUnits.basalRate, +export const prescriptionForm = () => { + const getBgUnits = props => get(props, 'prescription.latestRevision.attributes.initialSettings.bloodGlucoseUnits', props.clinic?.preferredBgUnits || defaultUnits.bloodGlucose) + + return { + mapPropsToStatus: props => ({ + hydratedValues: null, + isPrescriptionEditFlow: !!props.prescription, + }), + mapPropsToValues: props => { + const selectedPumpId = get(props, 'prescription.latestRevision.attributes.initialSettings.pumpId'); + + return { + id: get(props, 'prescription.id'), + state: get(props, 'prescription.latestRevision.attributes.state', 'draft'), + accountType: get(props, 'prescription.latestRevision.attributes.accountType'), + firstName: get(props, 'prescription.latestRevision.attributes.firstName'), + caregiverFirstName: get(props, 'prescription.latestRevision.attributes.caregiverFirstName'), + caregiverLastName: get(props, 'prescription.latestRevision.attributes.caregiverLastName'), + lastName: get(props, 'prescription.latestRevision.attributes.lastName'), + birthday: get(props, 'prescription.latestRevision.attributes.birthday'), + email: get(props, 'prescription.latestRevision.attributes.email'), + emailConfirm: get(props, 'prescription.latestRevision.attributes.email'), + phoneNumber: { + countryCode: get(props, 'prescription.latestRevision.attributes.phoneNumber.countryCode', validCountryCodes[0]), + number: get(props, 'prescription.latestRevision.attributes.phoneNumber.number'), }, - bolusAmountMaximum: { - value: get(props, 'prescription.latestRevision.attributes.initialSettings.bolusAmountMaximum.value'), - units: defaultUnits.bolusAmount, + mrn: get(props, 'prescription.latestRevision.attributes.mrn'), + sex: get(props, 'prescription.latestRevision.attributes.sex'), + calculator: { + method: get(props, 'prescription.latestRevision.attributes.calculator.method'), + weight: get(props, 'prescription.latestRevision.attributes.calculator.weight'), + weightUnits: get(props, 'prescription.latestRevision.attributes.calculator.weightUnits', defaultUnits.weight), + totalDailyDose: get(props, 'prescription.latestRevision.attributes.calculator.totalDailyDose'), + totalDailyDoseScaleFactor: get(props, 'prescription.latestRevision.attributes.calculator.totalDailyDoseScaleFactor', 1), + recommendedBasalRate: get(props, 'prescription.latestRevision.attributes.calculator.recommendedBasalRate'), + recommendedInsulinSensitivity: get(props, 'prescription.latestRevision.attributes.calculator.recommendedInsulinSensitivity'), + recommendedCarbohydrateRatio: get(props, 'prescription.latestRevision.attributes.calculator.recommendedCarbohydrateRatio'), }, - bloodGlucoseTargetSchedule: get(props, 'prescription.latestRevision.attributes.initialSettings.bloodGlucoseTargetSchedule', [{ - start: 0, - }]), - bloodGlucoseTargetPhysicalActivity: get(props, 'prescription.latestRevision.attributes.initialSettings.bloodGlucoseTargetPhysicalActivity'), - bloodGlucoseTargetPreprandial: get(props, 'prescription.latestRevision.attributes.initialSettings.bloodGlucoseTargetPreprandial'), - basalRateSchedule: get(props, 'prescription.latestRevision.attributes.initialSettings.basalRateSchedule', [{ - start: 0, - }]), - carbohydrateRatioSchedule: get(props, 'prescription.latestRevision.attributes.initialSettings.carbohydrateRatioSchedule', [{ - start: 0, - }]), - insulinSensitivitySchedule: get(props, 'prescription.latestRevision.attributes.initialSettings.insulinSensitivitySchedule', [{ - start: 0, - }]), - }, - training: get(props, 'prescription.latestRevision.attributes.training'), - therapySettings: get(props, 'prescription.latestRevision.attributes.therapySettings', 'initial'), - therapySettingsReviewed: get(props, 'prescription.therapySettingsReviewed', false), - }; - }, - validationSchema: props => { - if (!schema) schema = prescriptionSchema( - props.devices, - get(props, 'prescription.latestRevision.attributes.initialSettings.pumpId'), - bgUnits, - get(props, 'prescription.latestRevision.attributes') - ); - - return schema; - }, - displayName: 'PrescriptionForm', -}); + initialSettings: { + bloodGlucoseUnits: getBgUnits(props), + pumpId: selectedPumpId, + cgmId: get(props, 'prescription.latestRevision.attributes.initialSettings.cgmId'), + insulinModel: get(props, 'prescription.latestRevision.attributes.initialSettings.insulinModel'), + glucoseSafetyLimit: get(props, 'prescription.latestRevision.attributes.initialSettings.glucoseSafetyLimit'), + basalRateMaximum: { + value: get(props, 'prescription.latestRevision.attributes.initialSettings.basalRateMaximum.value'), + units: defaultUnits.basalRate, + }, + bolusAmountMaximum: { + value: get(props, 'prescription.latestRevision.attributes.initialSettings.bolusAmountMaximum.value'), + units: defaultUnits.bolusAmount, + }, + bloodGlucoseTargetSchedule: get(props, 'prescription.latestRevision.attributes.initialSettings.bloodGlucoseTargetSchedule', [{ + start: 0, + }]), + bloodGlucoseTargetPhysicalActivity: get(props, 'prescription.latestRevision.attributes.initialSettings.bloodGlucoseTargetPhysicalActivity'), + bloodGlucoseTargetPreprandial: get(props, 'prescription.latestRevision.attributes.initialSettings.bloodGlucoseTargetPreprandial'), + basalRateSchedule: get(props, 'prescription.latestRevision.attributes.initialSettings.basalRateSchedule', [{ + start: 0, + }]), + carbohydrateRatioSchedule: get(props, 'prescription.latestRevision.attributes.initialSettings.carbohydrateRatioSchedule', [{ + start: 0, + }]), + insulinSensitivitySchedule: get(props, 'prescription.latestRevision.attributes.initialSettings.insulinSensitivitySchedule', [{ + start: 0, + }]), + }, + training: get(props, 'prescription.latestRevision.attributes.training'), + therapySettings: get(props, 'prescription.latestRevision.attributes.therapySettings', 'initial'), + therapySettingsReviewed: get(props, 'prescription.therapySettingsReviewed', false), + }; + }, + validationSchema: props => { + if (!schema) schema = prescriptionSchema( + props.devices, + get(props, 'prescription.latestRevision.attributes.initialSettings.pumpId'), + getBgUnits(props), + get(props, 'prescription.latestRevision.attributes') + ); + + return schema; + }, + displayName: 'PrescriptionForm', + }; +}; export const generateTherapySettingsOrderText = (patientRows = [], therapySettingsRows = []) => { const textUtil = new TextUtil(); @@ -258,6 +264,7 @@ export const PrescriptionForm = props => { const { t, api, + clinic, devices, location, prescription, @@ -287,13 +294,11 @@ export const PrescriptionForm = props => { const loggedInUserId = useSelector((state) => state.blip.loggedInUserId); const selectedClinicId = useSelector((state) => state.blip.selectedClinicId); const stepperId = 'prescription-form-steps'; - const bgUnits = get(values, 'initialSettings.bloodGlucoseUnits', defaultUnits.bloodGlucose); + const bgUnits = get(values, 'initialSettings.bloodGlucoseUnits', clinic?.preferredBgUnits || defaultUnits.bloodGlucose); const pumpId = get(values, 'initialSettings.pumpId', deviceIdMap.palmtree); const prescriptionState = get(prescription, 'state', 'draft'); const prescriptionStates = keyBy(prescriptionStateOptions, 'value'); const isEditable = includes(['draft', 'pending'], prescriptionState); - const clinics = useSelector((state) => state.blip.clinics); - const clinic = get(clinics, selectedClinicId); const isPrescriber = includes(get(clinic, ['clinicians', loggedInUserId, 'roles'], []), 'PRESCRIBER'); const { showPrescriptions } = useFlags(); const ldClient = useLDClient(); From 2920d5591cfe2195f85146adaab3f900019eab47 Mon Sep 17 00:00:00 2001 From: clintonium-119 Date: Fri, 13 Sep 2024 14:16:22 -0400 Subject: [PATCH 049/112] Trigger validation on any dependant fields when editing therapy settings values --- app/core/forms.js | 36 +++++++++++++++++-- app/pages/prescription/ScheduleForm.js | 6 +++- .../prescription/prescriptionFormConstants.js | 24 ++++++++++--- .../prescription/therapySettingsFormStep.js | 19 +++++++--- 4 files changed, 74 insertions(+), 11 deletions(-) diff --git a/app/core/forms.js b/app/core/forms.js index 0ac6441655..00571cec35 100644 --- a/app/core/forms.js +++ b/app/core/forms.js @@ -1,3 +1,5 @@ +import debounce from 'lodash/debounce'; +import each from 'lodash/each'; import includes from 'lodash/includes'; import map from 'lodash/map'; import get from 'lodash/get'; @@ -86,8 +88,8 @@ export const getCommonFormikFieldProps = (fieldpath, formikContext, valueProp = /** * Add an empty option to a list of select or radio options - * @param {Array} options - Array of options - * @param {String} label - Display text to use for empty option + * @param {Array} options Array of options + * @param {String} label Display text to use for empty option * @param {*} value - Default empty value * @returns a new options array */ @@ -95,3 +97,33 @@ export const addEmptyOption = (options = [], label = t('Select one'), value = '' { value, label }, ...options, ]); + +/** + * Formik Field onChange handler for fields that require validation of other fields when changing values + * @param {Array} dependantFields Array of dependant field paths + * @param {Object} formikContext Context provided by useFormikContext() + * @returns {Function} onChange handler function + */ +export const onChangeWithDependantFields = (dependantFields, formikContext, setDependantsTouched = true) => e => { + formikContext.handleChange(e); + + const debouncedValidate = () => debounce(async fieldPath => { + setDependantsTouched && await formikContext.setFieldTouched(fieldPath, true, true); + await formikContext.validateField(fieldPath); + }, 500); + + each(dependantFields, async dependantField => { + const scheduleIndexPlaceholder = dependantField.indexOf('.$.'); + + if (scheduleIndexPlaceholder > 0) { + const fieldParts = dependantField.split('.$.'); + const fieldArrayValues = get(formikContext.values, fieldParts[0]); + + each(fieldArrayValues, async (fieldArrayValue, index) => { + debouncedValidate()(`${fieldParts[0]}.${index}.${fieldParts[1]}`); + }); + } else { + debouncedValidate()(dependantField); + } + }); +}; diff --git a/app/pages/prescription/ScheduleForm.js b/app/pages/prescription/ScheduleForm.js index 8d3ef7f18b..d69d8aa3e6 100644 --- a/app/pages/prescription/ScheduleForm.js +++ b/app/pages/prescription/ScheduleForm.js @@ -11,7 +11,7 @@ import isInteger from 'lodash/isInteger'; import sortedLastIndexBy from 'lodash/sortedLastIndexBy'; import DeleteOutlineRoundedIcon from '@material-ui/icons/DeleteOutlineRounded'; -import { getFieldError, getThresholdWarning } from '../../core/forms'; +import { getFieldError, getThresholdWarning, onChangeWithDependantFields } from '../../core/forms'; import { useFieldArray } from '../../core/hooks'; import i18next from '../../core/language'; import TextInput from '../../components/elements/TextInput'; @@ -28,6 +28,7 @@ const t = i18next.t.bind(i18next); const ScheduleForm = props => { const { addButtonText, + dependantFields, fieldArrayName, fields, max, @@ -125,6 +126,7 @@ const ScheduleForm = props => { setFieldTouched(`${fieldArrayName}.${index}.${field.name}`); setFieldValue(`${fieldArrayName}.${index}.${field.name}`, roundValueToIncrement(e.target.value, field.increment)) }} + onChange={onChangeWithDependantFields(field.dependantFields, formikContext, field.setDependantsTouched)} {...inlineInputStyles} /> {(fieldIndex < fields.length - 1 ) && separator && ( @@ -178,8 +180,10 @@ ScheduleForm.propTypes = { addButtonText: PropTypes.string, fieldArrayName: PropTypes.string, fields: PropTypes.arrayOf(PropTypes.shape({ + dependantFields: PropTypes.arrayOf(PropTypes.string), label: PropTypes.string, name: PropTypes.string, + setDependantsTouched: PropTypes.bool, min: PropTypes.number, max: PropTypes.number, increment: PropTypes.number, diff --git a/app/pages/prescription/prescriptionFormConstants.js b/app/pages/prescription/prescriptionFormConstants.js index f4a6553d2b..608d5a8bc6 100644 --- a/app/pages/prescription/prescriptionFormConstants.js +++ b/app/pages/prescription/prescriptionFormConstants.js @@ -129,7 +129,7 @@ export const pumpRanges = (pump, bgUnits = defaultUnits.bloodGlucose, values) => basalRateMaximum: { min: max(filter([ getPumpGuardrail(pump, 'basalRateMaximum.absoluteBounds.minimum', 0), - max(map(get(values, 'initialSettings.basalRateSchedule'), 'rate')), + min([maxBasalRate, getPumpGuardrail(pump, 'basalRateMaximum.absoluteBounds.maximum', 30)]), ], isFinite)), max: min(filter([ getPumpGuardrail(pump, 'basalRateMaximum.absoluteBounds.maximum', 30), @@ -197,6 +197,22 @@ export const pumpRanges = (pump, bgUnits = defaultUnits.bloodGlucose, values) => return ranges; }; +export const dependantFields = { + 'initialSettings.glucoseSafetyLimit': [ + 'initialSettings.bloodGlucoseTargetSchedule.$.low', + 'initialSettings.bloodGlucoseTargetSchedule.$.high', + 'initialSettings.bloodGlucoseTargetPreprandial.low', + 'initialSettings.bloodGlucoseTargetPreprandial.high', + 'initialSettings.bloodGlucoseTargetPhysicalActivity.low', + 'initialSettings.bloodGlucoseTargetPhysicalActivity.high', + ], + 'initialSettings.bloodGlucoseTargetSchedule.low': ['initialSettings.glucoseSafetyLimit'], + 'initialSettings.bloodGlucoseTargetPreprandial.low': ['initialSettings.glucoseSafetyLimit'], + 'initialSettings.bloodGlucoseTargetPhysicalActivity.low': ['initialSettings.glucoseSafetyLimit'], + 'initialSettings.basalRateSchedule.rate': ['initialSettings.basalRateMaximum.value'], + 'initialSettings.carbohydrateRatioSchedule.amount': ['initialSettings.basalRateMaximum.value'], +}; + export const warningThresholds = (pump, bgUnits = defaultUnits.bloodGlucose, values) => { const lowWarning = t('The value you have chosen is lower than Tidepool generally recommends.'); const highWarning = t('The value you have chosen is higher than Tidepool generally recommends.'); @@ -312,7 +328,7 @@ export const warningThresholds = (pump, bgUnits = defaultUnits.bloodGlucose, val * @param {Object} values form values provided by formik context * @returns {Object} default values keyed by setting */ -export const defaultValues = (pump, bgUnits = defaultUnits.bloodGlucose, values = {}) => { +export const defaultValues = (pump, bgUnits = defaultUnits.bloodGlucose, values = {}, touched = {}) => { const { calculator: { recommendedBasalRate, @@ -345,8 +361,8 @@ export const defaultValues = (pump, bgUnits = defaultUnits.bloodGlucose, values return { basalRate: recommendedBasalRate || getPumpGuardrail(pump, 'basalRates.defaultValue', undefined), - basalRateMaximum: isFinite(maxBasalRate) - ? parseFloat((maxBasalRate * (isPediatric ? 3 : 3.5)).toFixed(2)) + basalRateMaximum: isFinite(maxBasalRate) && !touched?.initialSettings?.basalRateMaximum?.value + ? utils.roundToNearest(min([parseFloat((maxBasalRate * (isPediatric ? 3 : 3.5))), 7.466666]), getPumpGuardrail(pump, 'basalRateMaximum.increment', 0.05)) : getPumpGuardrail(pump, 'basalRateMaximum.defaultValue', 0.05), bloodGlucoseTarget, bloodGlucoseTargetPhysicalActivity: { diff --git a/app/pages/prescription/therapySettingsFormStep.js b/app/pages/prescription/therapySettingsFormStep.js index ac1e269aed..88def61b17 100644 --- a/app/pages/prescription/therapySettingsFormStep.js +++ b/app/pages/prescription/therapySettingsFormStep.js @@ -9,7 +9,7 @@ import includes from 'lodash/includes'; import map from 'lodash/map'; import max from 'lodash/max'; -import { getFieldError, getThresholdWarning } from '../../core/forms'; +import { getFieldError, getThresholdWarning, onChangeWithDependantFields } from '../../core/forms'; import { useInitialFocusedInput } from '../../core/hooks'; import { Paragraph2, Body2, Headline, Title } from '../../components/elements/FontStyles'; import RadioGroup from '../../components/elements/RadioGroup'; @@ -20,6 +20,7 @@ import SettingsCalculatorResults from './SettingsCalculatorResults'; import { defaultValues, + dependantFields, hasCalculatorResults, insulinModelOptions, pumpRanges, @@ -170,6 +171,7 @@ export const GlucoseSettings = props => { setFieldTouched('initialSettings.glucoseSafetyLimit'); setFieldValue('initialSettings.glucoseSafetyLimit', roundValueToIncrement(e.target.value, ranges.glucoseSafetyLimit.increment)); }} + onChange={onChangeWithDependantFields(dependantFields['initialSettings.glucoseSafetyLimit'], formikContext)} step={ranges.glucoseSafetyLimit.increment} {...ranges.glucoseSafetyLimit} {...{ ...inputStyles, themeProps: { mb: 4 }}} @@ -194,6 +196,7 @@ export const GlucoseSettings = props => { fieldArrayName='initialSettings.bloodGlucoseTargetSchedule' fields={[ { + dependantFields: dependantFields['initialSettings.bloodGlucoseTargetSchedule.low'], label: t('Lower Target'), name: 'low', suffix: bgUnits, @@ -242,6 +245,7 @@ export const GlucoseSettings = props => { setFieldTouched('initialSettings.bloodGlucoseTargetPreprandial.low'); setFieldValue('initialSettings.bloodGlucoseTargetPreprandial.low', roundValueToIncrement(e.target.value, ranges.bloodGlucoseTargetPreprandial.increment)); }} + onChange={onChangeWithDependantFields(dependantFields['initialSettings.bloodGlucoseTargetPreprandial.low'], formikContext)} step={ranges.bloodGlucoseTargetPreprandial.increment} {...ranges.bloodGlucoseTargetPreprandial} {...inlineInputStyles} @@ -293,6 +297,7 @@ export const GlucoseSettings = props => { setFieldTouched('initialSettings.bloodGlucoseTargetPhysicalActivity.low'); setFieldValue('initialSettings.bloodGlucoseTargetPhysicalActivity.low', roundValueToIncrement(e.target.value, ranges.bloodGlucoseTargetPhysicalActivity.increment)); }} + onChange={onChangeWithDependantFields(dependantFields['initialSettings.bloodGlucoseTargetPhysicalActivity.low'], formikContext)} step={ranges.bloodGlucoseTargetPhysicalActivity.increment} {...ranges.bloodGlucoseTargetPhysicalActivity} {...inlineInputStyles} @@ -361,8 +366,10 @@ export const InsulinSettings = props => { fieldArrayName='initialSettings.carbohydrateRatioSchedule' fields={[ { + dependantFields: dependantFields['initialSettings.carbohydrateRatioSchedule.amount'], label: t('1 U of Insulin Covers (g/U)'), name: 'amount', + setDependantsTouched: false, suffix: t('g/U'), threshold: thresholds.carbRatio, type: 'number', @@ -392,8 +399,10 @@ export const InsulinSettings = props => { fieldArrayName='initialSettings.basalRateSchedule' fields={[ { + dependantFields: dependantFields['initialSettings.basalRateSchedule.rate'], label: t('Basal Rate Values (U/hr)'), name: 'rate', + setDependantsTouched: false, suffix: t('U/hr'), threshold: thresholds.basalRate, type: 'number', @@ -548,6 +557,7 @@ export const TherapySettings = withTranslation()(props => { const { setFieldValue, + touched, values, } = formikContext; @@ -574,9 +584,10 @@ export const TherapySettings = withTranslation()(props => { glucoseSafetyLimit, ]); - const defaults = React.useMemo(() => defaultValues(props.pump, bgUnits, values), [ + const defaults = React.useMemo(() => defaultValues(props.pump, bgUnits, values, touched), [ maxBasalRate, - values.calculator, + touched?.initialSettings?.basalRateMaximum?.value, + values?.calculator, ]); const fieldsWithDefaults = [ @@ -640,7 +651,7 @@ export const TherapySettings = withTranslation()(props => { each(fieldsWithDefaults, field => { React.useEffect(() => { - if (shouldUpdateDefaultValue(field.path, formikContext)) { + if (field.defaultValue && shouldUpdateDefaultValue(field.path, formikContext)) { setFieldValue(field.path, roundValueToIncrement(field.defaultValue, field.increment)); } }, field.dependancies || [field.defaultValue]); From 9c1a9438c5534e959a7da33f1c9204964c9a027b Mon Sep 17 00:00:00 2001 From: clintonium-119 Date: Fri, 13 Sep 2024 16:56:02 -0400 Subject: [PATCH 050/112] More validation tweaks --- .../clinic/TideDashboardConfigForm.js | 2 +- app/core/forms.js | 18 +++++++++++++++--- .../prescription/prescriptionFormConstants.js | 13 +++++++++---- 3 files changed, 25 insertions(+), 8 deletions(-) diff --git a/app/components/clinic/TideDashboardConfigForm.js b/app/components/clinic/TideDashboardConfigForm.js index ff65d928fc..e3d37b8c89 100644 --- a/app/components/clinic/TideDashboardConfigForm.js +++ b/app/components/clinic/TideDashboardConfigForm.js @@ -105,7 +105,7 @@ export const TideDashboardConfigForm = props => { }} /> - {getFieldError('tags', formikContext) && ( + {getFieldError('tags', formikContext, false) && ( {errors.tags} diff --git a/app/core/forms.js b/app/core/forms.js index 00571cec35..6ea82d39ef 100644 --- a/app/core/forms.js +++ b/app/core/forms.js @@ -3,8 +3,9 @@ import each from 'lodash/each'; import includes from 'lodash/includes'; import map from 'lodash/map'; import get from 'lodash/get'; -import isString from 'lodash/isString'; +import isEmpty from 'lodash/isEmpty'; import isNumber from 'lodash/isNumber'; +import isString from 'lodash/isString'; import trim from 'lodash/trim'; import i18next from './language'; @@ -38,10 +39,19 @@ export const fieldsAreValid = (fieldNames, schema, values) => * @param {Boolean} forceTouched treat field as touched to force showing error prior to user interaction * @returns error string or null */ -export const getFieldError = (fieldPath, { errors, touched, initialValues }, forceTouched) => - (get(touched, fieldPath, forceTouched) || get(initialValues, fieldPath)) && get(errors, fieldPath) +export const getFieldError = (fieldPath, formikContext, forceTouchedIfFilled = true) => { + const { errors, touched, initialValues, values, status } = formikContext; + const value = get(values, fieldPath); + + const forceTouched = !status.validatingChanges && forceTouchedIfFilled && ( + (isFinite(value) && parseFloat(value) >= 0) || + (isString(value) && !isEmpty(value)) + ); + + return (get(touched, fieldPath, forceTouched) || get(initialValues, fieldPath)) && get(errors, fieldPath) ? get(errors, fieldPath) : null; +}; /** * Returns the warning message for a value outside of the given threshold @@ -110,10 +120,12 @@ export const onChangeWithDependantFields = (dependantFields, formikContext, setD const debouncedValidate = () => debounce(async fieldPath => { setDependantsTouched && await formikContext.setFieldTouched(fieldPath, true, true); await formikContext.validateField(fieldPath); + formikContext.setStatus({...formikContext.status, validatingChanges: false }); }, 500); each(dependantFields, async dependantField => { const scheduleIndexPlaceholder = dependantField.indexOf('.$.'); + formikContext.setStatus({...formikContext.status, validatingChanges: true }); if (scheduleIndexPlaceholder > 0) { const fieldParts = dependantField.split('.$.'); diff --git a/app/pages/prescription/prescriptionFormConstants.js b/app/pages/prescription/prescriptionFormConstants.js index 608d5a8bc6..1f5d2d492b 100644 --- a/app/pages/prescription/prescriptionFormConstants.js +++ b/app/pages/prescription/prescriptionFormConstants.js @@ -129,7 +129,7 @@ export const pumpRanges = (pump, bgUnits = defaultUnits.bloodGlucose, values) => basalRateMaximum: { min: max(filter([ getPumpGuardrail(pump, 'basalRateMaximum.absoluteBounds.minimum', 0), - min([maxBasalRate, getPumpGuardrail(pump, 'basalRateMaximum.absoluteBounds.maximum', 30)]), + max([maxBasalRate, getPumpGuardrail(pump, 'basalRateMaximum.absoluteBounds.minimum', 0.5)]), ], isFinite)), max: min(filter([ getPumpGuardrail(pump, 'basalRateMaximum.absoluteBounds.maximum', 30), @@ -137,7 +137,7 @@ export const pumpRanges = (pump, bgUnits = defaultUnits.bloodGlucose, values) => 70 / min(map(get(values, 'initialSettings.carbohydrateRatioSchedule'), 'amount')), parseFloat((maxBasalRate * 6.4).toFixed(2)) ]), - ], isFinite)), + ], val => isFinite(val) && parseInt(val) > 0)), increment: getPumpGuardrail(pump, 'basalRateMaximum.absoluteBounds.increment', 0.05), }, bloodGlucoseTarget: { @@ -362,8 +362,13 @@ export const defaultValues = (pump, bgUnits = defaultUnits.bloodGlucose, values return { basalRate: recommendedBasalRate || getPumpGuardrail(pump, 'basalRates.defaultValue', undefined), basalRateMaximum: isFinite(maxBasalRate) && !touched?.initialSettings?.basalRateMaximum?.value - ? utils.roundToNearest(min([parseFloat((maxBasalRate * (isPediatric ? 3 : 3.5))), 7.466666]), getPumpGuardrail(pump, 'basalRateMaximum.increment', 0.05)) - : getPumpGuardrail(pump, 'basalRateMaximum.defaultValue', 0.05), + ? utils.roundToNearest( + min([ + parseFloat((maxBasalRate * (isPediatric ? 3 : 3.5))), + getPumpGuardrail(pump, 'basalRateMaximum.absoluteBounds.maximum', 30) + ]), + getPumpGuardrail(pump, 'basalRateMaximum.increment', 0.05) + ) : getPumpGuardrail(pump, 'basalRateMaximum.defaultValue', 0.05), bloodGlucoseTarget, bloodGlucoseTargetPhysicalActivity: { low: getBgInTargetUnits(150, MGDL_UNITS, bgUnits), From aa8df849ee15500a507dc438f8a31731fe786644 Mon Sep 17 00:00:00 2001 From: clintonium-119 Date: Fri, 13 Sep 2024 18:02:42 -0400 Subject: [PATCH 051/112] More min/max validation tweaks --- .../prescription/prescriptionFormConstants.js | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/app/pages/prescription/prescriptionFormConstants.js b/app/pages/prescription/prescriptionFormConstants.js index 1f5d2d492b..c95cd50d0b 100644 --- a/app/pages/prescription/prescriptionFormConstants.js +++ b/app/pages/prescription/prescriptionFormConstants.js @@ -118,18 +118,22 @@ export const roundValueToIncrement = (value, increment = 1) => { export const pumpRanges = (pump, bgUnits = defaultUnits.bloodGlucose, values) => { const maxBasalRate = max(map(get(values, 'initialSettings.basalRateSchedule'), 'rate')); + const maxAllowedBasalRate = getPumpGuardrail(pump, 'basalRates.absoluteBounds.maximum', 30); const ranges = { basalRate: { - min: max([getPumpGuardrail(pump, 'basalRates.absoluteBounds.minimum', 0.05), 0.05]), - max: min([getPumpGuardrail(pump, 'basalRates.absoluteBounds.maximum', 30), 30]), + min: getPumpGuardrail(pump, 'basalRates.absoluteBounds.minimum', 0.05), + max: maxAllowedBasalRate, increment: getPumpGuardrail(pump, 'basalRates.absoluteBounds.increment', 0.05), schedules: { max: pump?.guardRails?.basalRates?.maxSegments || 48, minutesIncrement: 30 }, }, basalRateMaximum: { min: max(filter([ getPumpGuardrail(pump, 'basalRateMaximum.absoluteBounds.minimum', 0), - max([maxBasalRate, getPumpGuardrail(pump, 'basalRateMaximum.absoluteBounds.minimum', 0.5)]), + min([ + max([maxBasalRate, getPumpGuardrail(pump, 'basalRateMaximum.absoluteBounds.minimum', 0.5)]), + maxAllowedBasalRate, + ]), ], isFinite)), max: min(filter([ getPumpGuardrail(pump, 'basalRateMaximum.absoluteBounds.maximum', 30), @@ -166,8 +170,8 @@ export const pumpRanges = (pump, bgUnits = defaultUnits.bloodGlucose, values) => increment: getBgStepInTargetUnits(getPumpGuardrail(pump, 'preprandialCorrectionRange.absoluteBounds.increment', 1), MGDL_UNITS, bgUnits), }, bolusAmountMaximum: { - min: max([getPumpGuardrail(pump, 'bolusAmountMaximum.absoluteBounds.minimum', 0.05), 0.05]), - max: min([getPumpGuardrail(pump, 'bolusAmountMaximum.absoluteBounds.maximum', 30), 30]), + min: getPumpGuardrail(pump, 'bolusAmountMaximum.absoluteBounds.minimum', 0.05), + max: getPumpGuardrail(pump, 'bolusAmountMaximum.absoluteBounds.maximum', 30), increment: getPumpGuardrail(pump, 'bolusAmountMaximum.absoluteBounds.increment', 0.05), }, carbRatio: { From 1c8bfd2c7696e6f549abc7823376258b3d527a39 Mon Sep 17 00:00:00 2001 From: clintonium-119 Date: Fri, 13 Sep 2024 22:13:27 -0400 Subject: [PATCH 052/112] Enlarge font size on add new prescription button --- app/pages/prescription/Prescriptions.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/pages/prescription/Prescriptions.js b/app/pages/prescription/Prescriptions.js index 37a2e867a6..5f401efcb6 100644 --- a/app/pages/prescription/Prescriptions.js +++ b/app/pages/prescription/Prescriptions.js @@ -390,7 +390,7 @@ const Prescriptions = props => { variant="primary" onClick={handleAddNew} px={[2, 3]} - sx={{ fontSize: 0, lineHeight: ['inherit', null, 1] }} + sx={{ fontSize: 1, lineHeight: ['inherit', null, 1] }} > {t('Create New Tidepool Loop Start Order')} From 3603a4c8b51daec47b044cd3e9a4fd50ead10a89 Mon Sep 17 00:00:00 2001 From: clintonium-119 Date: Fri, 13 Sep 2024 22:31:42 -0400 Subject: [PATCH 053/112] Tweak realtime field dependancy validation, and validate on load as well. --- app/core/forms.js | 41 ++++++++++++---- app/pages/prescription/ScheduleForm.js | 2 +- .../prescription/therapySettingsFormStep.js | 48 +++++++++++++++++-- 3 files changed, 76 insertions(+), 15 deletions(-) diff --git a/app/core/forms.js b/app/core/forms.js index 6ea82d39ef..b78085aca1 100644 --- a/app/core/forms.js +++ b/app/core/forms.js @@ -43,7 +43,7 @@ export const getFieldError = (fieldPath, formikContext, forceTouchedIfFilled = t const { errors, touched, initialValues, values, status } = formikContext; const value = get(values, fieldPath); - const forceTouched = !status.validatingChanges && forceTouchedIfFilled && ( + const forceTouched = !status.validatingChanges?.[fieldPath] && forceTouchedIfFilled && ( // TODO: once all things work see if I actually need to track validation changes... (isFinite(value) && parseFloat(value) >= 0) || (isString(value) && !isEmpty(value)) ); @@ -108,30 +108,53 @@ export const addEmptyOption = (options = [], label = t('Select one'), value = '' ...options, ]); +/** + * Set field-level validating form status + * + * @param {String} fieldPath path to the field in dot notation + * @param {Boolean} status is field currently being validated + * @param {Object} formikContext Context provided by useFormikContext() + */ +export const setFieldValidatingChanges = (fieldPath, status, formikContext) => { + formikContext.setStatus({ + ...formikContext.status, + validatingChanges: { + ...formikContext.status.validatingChanges || {}, + [fieldPath]: status, + }, + }); +}; + /** * Formik Field onChange handler for fields that require validation of other fields when changing values * @param {Array} dependantFields Array of dependant field paths * @param {Object} formikContext Context provided by useFormikContext() * @returns {Function} onChange handler function */ -export const onChangeWithDependantFields = (dependantFields, formikContext, setDependantsTouched = true) => e => { +export const onChangeWithDependantFields = (parentFieldPath, dependantFields, formikContext, setDependantsTouched = true) => async e => { + setFieldValidatingChanges(parentFieldPath, true, formikContext); formikContext.handleChange(e); + await formikContext.setFieldTouched(parentFieldPath, true, true); + await formikContext.validateField(parentFieldPath); + setFieldValidatingChanges(parentFieldPath, false, formikContext); const debouncedValidate = () => debounce(async fieldPath => { - setDependantsTouched && await formikContext.setFieldTouched(fieldPath, true, true); - await formikContext.validateField(fieldPath); - formikContext.setStatus({...formikContext.status, validatingChanges: false }); - }, 500); + setFieldValidatingChanges(fieldPath, true, formikContext); + if (setDependantsTouched) { + await formikContext.setFieldTouched(fieldPath, true, true); + await formikContext.validateField(fieldPath); + } + setFieldValidatingChanges(fieldPath, false, formikContext); + }, 250); - each(dependantFields, async dependantField => { + each(dependantFields, dependantField => { const scheduleIndexPlaceholder = dependantField.indexOf('.$.'); - formikContext.setStatus({...formikContext.status, validatingChanges: true }); if (scheduleIndexPlaceholder > 0) { const fieldParts = dependantField.split('.$.'); const fieldArrayValues = get(formikContext.values, fieldParts[0]); - each(fieldArrayValues, async (fieldArrayValue, index) => { + each(fieldArrayValues, (fieldArrayValue, index) => { debouncedValidate()(`${fieldParts[0]}.${index}.${fieldParts[1]}`); }); } else { diff --git a/app/pages/prescription/ScheduleForm.js b/app/pages/prescription/ScheduleForm.js index d69d8aa3e6..c0d8efdfaa 100644 --- a/app/pages/prescription/ScheduleForm.js +++ b/app/pages/prescription/ScheduleForm.js @@ -126,7 +126,7 @@ const ScheduleForm = props => { setFieldTouched(`${fieldArrayName}.${index}.${field.name}`); setFieldValue(`${fieldArrayName}.${index}.${field.name}`, roundValueToIncrement(e.target.value, field.increment)) }} - onChange={onChangeWithDependantFields(field.dependantFields, formikContext, field.setDependantsTouched)} + onChange={onChangeWithDependantFields(`${fieldArrayName}.${index}.${field.name}`, field.dependantFields, formikContext, field.setDependantsTouched)} {...inlineInputStyles} /> {(fieldIndex < fields.length - 1 ) && separator && ( diff --git a/app/pages/prescription/therapySettingsFormStep.js b/app/pages/prescription/therapySettingsFormStep.js index 88def61b17..9d1cb3ecf4 100644 --- a/app/pages/prescription/therapySettingsFormStep.js +++ b/app/pages/prescription/therapySettingsFormStep.js @@ -4,12 +4,17 @@ import { withTranslation } from 'react-i18next'; import { FastField, Field, useFormikContext } from 'formik'; import { Box, Flex, Text, BoxProps } from 'theme-ui'; import each from 'lodash/each'; +import flatten from 'lodash/flatten'; import get from 'lodash/get'; import includes from 'lodash/includes'; +import isEmpty from 'lodash/isEmpty'; +import isString from 'lodash/isString'; import map from 'lodash/map'; import max from 'lodash/max'; +import uniq from 'lodash/uniq'; +import { default as _values } from 'lodash/values'; -import { getFieldError, getThresholdWarning, onChangeWithDependantFields } from '../../core/forms'; +import { getFieldError, getThresholdWarning, onChangeWithDependantFields, setFieldValidatingChanges } from '../../core/forms'; import { useInitialFocusedInput } from '../../core/hooks'; import { Paragraph2, Body2, Headline, Title } from '../../components/elements/FontStyles'; import RadioGroup from '../../components/elements/RadioGroup'; @@ -171,7 +176,7 @@ export const GlucoseSettings = props => { setFieldTouched('initialSettings.glucoseSafetyLimit'); setFieldValue('initialSettings.glucoseSafetyLimit', roundValueToIncrement(e.target.value, ranges.glucoseSafetyLimit.increment)); }} - onChange={onChangeWithDependantFields(dependantFields['initialSettings.glucoseSafetyLimit'], formikContext)} + onChange={onChangeWithDependantFields('initialSettings.glucoseSafetyLimit', dependantFields['initialSettings.glucoseSafetyLimit'], formikContext)} step={ranges.glucoseSafetyLimit.increment} {...ranges.glucoseSafetyLimit} {...{ ...inputStyles, themeProps: { mb: 4 }}} @@ -245,7 +250,7 @@ export const GlucoseSettings = props => { setFieldTouched('initialSettings.bloodGlucoseTargetPreprandial.low'); setFieldValue('initialSettings.bloodGlucoseTargetPreprandial.low', roundValueToIncrement(e.target.value, ranges.bloodGlucoseTargetPreprandial.increment)); }} - onChange={onChangeWithDependantFields(dependantFields['initialSettings.bloodGlucoseTargetPreprandial.low'], formikContext)} + onChange={onChangeWithDependantFields('initialSettings.bloodGlucoseTargetPreprandial.low', dependantFields['initialSettings.bloodGlucoseTargetPreprandial.low'], formikContext)} step={ranges.bloodGlucoseTargetPreprandial.increment} {...ranges.bloodGlucoseTargetPreprandial} {...inlineInputStyles} @@ -297,7 +302,7 @@ export const GlucoseSettings = props => { setFieldTouched('initialSettings.bloodGlucoseTargetPhysicalActivity.low'); setFieldValue('initialSettings.bloodGlucoseTargetPhysicalActivity.low', roundValueToIncrement(e.target.value, ranges.bloodGlucoseTargetPhysicalActivity.increment)); }} - onChange={onChangeWithDependantFields(dependantFields['initialSettings.bloodGlucoseTargetPhysicalActivity.low'], formikContext)} + onChange={onChangeWithDependantFields('initialSettings.bloodGlucoseTargetPhysicalActivity.low', dependantFields['initialSettings.bloodGlucoseTargetPhysicalActivity.low'], formikContext)} step={ranges.bloodGlucoseTargetPhysicalActivity.increment} {...ranges.bloodGlucoseTargetPhysicalActivity} {...inlineInputStyles} @@ -469,7 +474,7 @@ export const InsulinSettings = props => { id="initialSettings.basalRateMaximum.value" name="initialSettings.basalRateMaximum.value" suffix={t('U/hr')} - error={getFieldError('initialSettings.basalRateMaximum.value', formikContext)} + error={getFieldError('initialSettings.basalRateMaximum.value', formikContext, false)} warning={getThresholdWarning(get(values,'initialSettings.basalRateMaximum.value'), thresholds.basalRateMaximum)} onBlur={e => { setFieldTouched('initialSettings.basalRateMaximum.value'); @@ -556,6 +561,7 @@ export const TherapySettings = withTranslation()(props => { const formikContext = useFormikContext(); const { + initialValues, setFieldValue, touched, values, @@ -657,6 +663,38 @@ export const TherapySettings = withTranslation()(props => { }, field.dependancies || [field.defaultValue]); }); + React.useEffect(() => { + // Validate all filled dependant fields on initial form hydration + const filledFieldValidator = () => async fieldPath => { + const value = get(values, fieldPath); + + if ( + (isFinite(value) && parseFloat(value) >= 0) || + (isString(value) && !isEmpty(value)) + ) { + setFieldValidatingChanges(fieldPath, true, formikContext); + await formikContext.setFieldTouched(fieldPath, true, true); + await formikContext.validateField(fieldPath); + setFieldValidatingChanges(fieldPath, false, formikContext); + } + }; + + each(uniq(flatten(_values(dependantFields))), dependantField => { + const scheduleIndexPlaceholder = dependantField.indexOf('.$.'); + + if (scheduleIndexPlaceholder > 0) { + const fieldParts = dependantField.split('.$.'); + const fieldArrayValues = get(formikContext.values, fieldParts[0]); + + each(fieldArrayValues, (fieldArrayValue, index) => { + filledFieldValidator()(`${fieldParts[0]}.${index}.${fieldParts[1]}`); + }); + } else { + filledFieldValidator()(dependantField); + } + }); + }, [initialValues]); + return ( From 884aefb799662b8f350217b03d4c4560cd36b7f9 Mon Sep 17 00:00:00 2001 From: clintonium-119 Date: Fri, 13 Sep 2024 22:40:08 -0400 Subject: [PATCH 054/112] Exclude basal rate maximum from initial validation to allow dynamic defaults to be set without triggering validation --- app/pages/prescription/therapySettingsFormStep.js | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/app/pages/prescription/therapySettingsFormStep.js b/app/pages/prescription/therapySettingsFormStep.js index 9d1cb3ecf4..1b419c3c5b 100644 --- a/app/pages/prescription/therapySettingsFormStep.js +++ b/app/pages/prescription/therapySettingsFormStep.js @@ -4,6 +4,7 @@ import { withTranslation } from 'react-i18next'; import { FastField, Field, useFormikContext } from 'formik'; import { Box, Flex, Text, BoxProps } from 'theme-ui'; import each from 'lodash/each'; +import filter from 'lodash/filter'; import flatten from 'lodash/flatten'; import get from 'lodash/get'; import includes from 'lodash/includes'; @@ -561,6 +562,7 @@ export const TherapySettings = withTranslation()(props => { const formikContext = useFormikContext(); const { + dirty, initialValues, setFieldValue, touched, @@ -664,6 +666,9 @@ export const TherapySettings = withTranslation()(props => { }); React.useEffect(() => { + // We need to not validate prior to initial values going in. + if (!dirty) return; + // Validate all filled dependant fields on initial form hydration const filledFieldValidator = () => async fieldPath => { const value = get(values, fieldPath); @@ -679,7 +684,10 @@ export const TherapySettings = withTranslation()(props => { } }; - each(uniq(flatten(_values(dependantFields))), dependantField => { + const fields = uniq(flatten(_values(dependantFields))); + const excludedFields = ['initialSettings.basalRateMaximum.value']; + + each(filter(fields, field => !includes(excludedFields, field)), dependantField => { const scheduleIndexPlaceholder = dependantField.indexOf('.$.'); if (scheduleIndexPlaceholder > 0) { @@ -693,7 +701,7 @@ export const TherapySettings = withTranslation()(props => { filledFieldValidator()(dependantField); } }); - }, [initialValues]); + }, [initialValues, dirty]); return ( From 77525a66e4ab9fce425fdae74093e24dcb325964 Mon Sep 17 00:00:00 2001 From: clintonium-119 Date: Sat, 14 Sep 2024 18:05:40 -0400 Subject: [PATCH 055/112] Don't send prescription fields from future steps if they are invalid --- app/pages/prescription/PrescriptionForm.js | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/app/pages/prescription/PrescriptionForm.js b/app/pages/prescription/PrescriptionForm.js index b6c03b3cfe..d61ba05212 100644 --- a/app/pages/prescription/PrescriptionForm.js +++ b/app/pages/prescription/PrescriptionForm.js @@ -34,7 +34,7 @@ import canonicalize from 'canonicalize'; import { sha512 } from 'crypto-hash'; import { useFlags, useLDClient } from 'launchdarkly-react-client-sdk'; -import { fieldsAreValid } from '../../core/forms'; +import { fieldsAreValid, getFieldError } from '../../core/forms'; import prescriptionSchema from './prescriptionSchema'; import ClinicWorkspaceHeader from '../../components/clinic/ClinicWorkspaceHeader'; import Button from '../../components/elements/Button'; @@ -396,6 +396,9 @@ export const PrescriptionForm = props => { // We can't simply delete all future steps, as the clinician may have returned to the current // step via 'Back' button navigation and we don't want to lose existing data previously // entered in the later steps. + + // We should, however, remove any future fields that don't pass field validation, since they + // would result in an error anyhow if submitted, and prevent forward navigation if (!isLastStep()) { const fieldsInFutureSteps = reduce(formSteps, (fields, step, index) => { if (index <= activeStep) return fields; @@ -404,7 +407,7 @@ export const PrescriptionForm = props => { return fields; }, []); - const emptyFieldsInFutureSteps = remove( + const emptyOrInvalidFieldsInFutureSteps = remove( flattenDeep(fieldsInFutureSteps), fieldPath => { const value = get(values, fieldPath); @@ -422,7 +425,7 @@ export const PrescriptionForm = props => { } // Return empty values for non-array fields - return isEmpty(value); + return isEmpty(value) || getFieldError(fieldPath, formikContext, true); } ); @@ -430,7 +433,7 @@ export const PrescriptionForm = props => { // N.B. There are some fieldpaths we check that end in '.value' or '.number'. If those keys // are empty, we exclude the parent object. fieldsToDelete.push(...map( - emptyFieldsInFutureSteps, + emptyOrInvalidFieldsInFutureSteps, fieldPath => fieldPath.replace(/\.(value|number)$/, '') )); } From 12d7f35af82b0e0569a10c4ce72a607987faed2f Mon Sep 17 00:00:00 2001 From: clintonium-119 Date: Sat, 14 Sep 2024 18:07:30 -0400 Subject: [PATCH 056/112] Update validation method to ensure high values in ranges are >= low values * yup references changed how they work for number fields, and the previous approach no longer works --- app/pages/prescription/prescriptionSchema.js | 32 ++++++++++++-------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/app/pages/prescription/prescriptionSchema.js b/app/pages/prescription/prescriptionSchema.js index 27cf0f2359..2601e2cd52 100644 --- a/app/pages/prescription/prescriptionSchema.js +++ b/app/pages/prescription/prescriptionSchema.js @@ -52,11 +52,17 @@ export default (devices, pumpId, bgUnits = defaultUnits.bloodGlucose, values) => cgms: cgmDeviceOptions(devices), } - const rangeError = (key) => t('Please select a value between {{min}}-{{max}}', { + const rangeError = key => t('Please select a value between {{min}}-{{max}}', { min: utils.roundToNearest(ranges[key].min, ranges[key].increment || 1), max: utils.roundToNearest(ranges[key].max, ranges[key].increment || 1), }); + const equalToOrAboveLow = key => (value, ctx) => { + const defaultMin = utils.roundToNearest(ranges[key].min, ranges[key].increment || 1); + const min = utils.roundToNearest(max([parseFloat(ctx.parent.low), ranges[key].min]), ranges[key].increment || 1); + return value >= parseFloat(min) || ctx.createError({ message: rangeError(key).replace(defaultMin, min) }); + }; + return yup.object().shape({ id: yup.string(), state: yup.string() @@ -176,14 +182,14 @@ export default (devices, pumpId, bgUnits = defaultUnits.bloodGlucose, values) => }), bloodGlucoseTargetSchedule: yup.array().of( yup.object().shape({ - high: yup.number() - .min(utils.roundToNearest(max([parseFloat(yup.ref('low')), ranges.bloodGlucoseTarget.min]), ranges.bloodGlucoseTarget.increment), rangeError('bloodGlucoseTarget').replace(ranges.bloodGlucoseTarget.min, '${min}')) - .max(ranges.bloodGlucoseTarget.max, rangeError('bloodGlucoseTarget')) - .required(t('High target is required')), low: yup.number() .min(ranges.bloodGlucoseTarget.min, rangeError('bloodGlucoseTarget')) .max(ranges.bloodGlucoseTarget.max, rangeError('bloodGlucoseTarget')) .required(t('Low target is required')), + high: yup.number() + .test('min', null, equalToOrAboveLow('bloodGlucoseTarget')) + .max(ranges.bloodGlucoseTarget.max, rangeError('bloodGlucoseTarget')) + .required(t('High target is required')), start: yup.number() .integer() .min(0) @@ -192,24 +198,24 @@ export default (devices, pumpId, bgUnits = defaultUnits.bloodGlucose, values) => }), ), bloodGlucoseTargetPhysicalActivity: yup.object().shape({ - high: yup.number() - .min(utils.roundToNearest(max([parseFloat(yup.ref('low')), ranges.bloodGlucoseTargetPhysicalActivity.min]), ranges.bloodGlucoseTargetPhysicalActivity.increment), rangeError('bloodGlucoseTargetPhysicalActivity').replace(ranges.bloodGlucoseTargetPhysicalActivity.min, '${min}')) - .max(ranges.bloodGlucoseTargetPhysicalActivity.max, rangeError('bloodGlucoseTargetPhysicalActivity')) - .required(t('High target is required')), low: yup.number() .min(ranges.bloodGlucoseTargetPhysicalActivity.min, rangeError('bloodGlucoseTargetPhysicalActivity')) .max(ranges.bloodGlucoseTargetPhysicalActivity.max, rangeError('bloodGlucoseTargetPhysicalActivity')) .required(t('Low target is required')), - }), - bloodGlucoseTargetPreprandial: yup.object().shape({ high: yup.number() - .min(utils.roundToNearest(max([parseFloat(yup.ref('low')), ranges.bloodGlucoseTargetPreprandial.min]), ranges.bloodGlucoseTargetPreprandial.increment), rangeError('bloodGlucoseTargetPreprandial').replace(ranges.bloodGlucoseTargetPreprandial.min, '${min}')) - .max(ranges.bloodGlucoseTargetPreprandial.max, rangeError('bloodGlucoseTargetPreprandial')) + .test('min', null, equalToOrAboveLow('bloodGlucoseTargetPhysicalActivity')) + .max(ranges.bloodGlucoseTargetPhysicalActivity.max, rangeError('bloodGlucoseTargetPhysicalActivity')) .required(t('High target is required')), + }), + bloodGlucoseTargetPreprandial: yup.object().shape({ low: yup.number() .min(ranges.bloodGlucoseTargetPreprandial.min, rangeError('bloodGlucoseTargetPreprandial')) .max(ranges.bloodGlucoseTargetPreprandial.max, rangeError('bloodGlucoseTargetPreprandial')) .required(t('Low target is required')), + high: yup.number() + .test('min', null, equalToOrAboveLow('bloodGlucoseTargetPreprandial')) + .max(ranges.bloodGlucoseTargetPreprandial.max, rangeError('bloodGlucoseTargetPreprandial')) + .required(t('High target is required')), }), basalRateSchedule: yup.array().of( yup.object().shape({ From 2e141b2d07a16a0eada38101939c293f65916824 Mon Sep 17 00:00:00 2001 From: clintonium-119 Date: Sat, 14 Sep 2024 19:10:18 -0400 Subject: [PATCH 057/112] Remove validatingFields status tracking - not needed --- app/core/forms.js | 23 +------------------ .../prescription/therapySettingsFormStep.js | 4 +--- 2 files changed, 2 insertions(+), 25 deletions(-) diff --git a/app/core/forms.js b/app/core/forms.js index b78085aca1..3d653a2bce 100644 --- a/app/core/forms.js +++ b/app/core/forms.js @@ -43,7 +43,7 @@ export const getFieldError = (fieldPath, formikContext, forceTouchedIfFilled = t const { errors, touched, initialValues, values, status } = formikContext; const value = get(values, fieldPath); - const forceTouched = !status.validatingChanges?.[fieldPath] && forceTouchedIfFilled && ( // TODO: once all things work see if I actually need to track validation changes... + const forceTouched = forceTouchedIfFilled && ( (isFinite(value) && parseFloat(value) >= 0) || (isString(value) && !isEmpty(value)) ); @@ -108,23 +108,6 @@ export const addEmptyOption = (options = [], label = t('Select one'), value = '' ...options, ]); -/** - * Set field-level validating form status - * - * @param {String} fieldPath path to the field in dot notation - * @param {Boolean} status is field currently being validated - * @param {Object} formikContext Context provided by useFormikContext() - */ -export const setFieldValidatingChanges = (fieldPath, status, formikContext) => { - formikContext.setStatus({ - ...formikContext.status, - validatingChanges: { - ...formikContext.status.validatingChanges || {}, - [fieldPath]: status, - }, - }); -}; - /** * Formik Field onChange handler for fields that require validation of other fields when changing values * @param {Array} dependantFields Array of dependant field paths @@ -132,19 +115,15 @@ export const setFieldValidatingChanges = (fieldPath, status, formikContext) => { * @returns {Function} onChange handler function */ export const onChangeWithDependantFields = (parentFieldPath, dependantFields, formikContext, setDependantsTouched = true) => async e => { - setFieldValidatingChanges(parentFieldPath, true, formikContext); formikContext.handleChange(e); await formikContext.setFieldTouched(parentFieldPath, true, true); await formikContext.validateField(parentFieldPath); - setFieldValidatingChanges(parentFieldPath, false, formikContext); const debouncedValidate = () => debounce(async fieldPath => { - setFieldValidatingChanges(fieldPath, true, formikContext); if (setDependantsTouched) { await formikContext.setFieldTouched(fieldPath, true, true); await formikContext.validateField(fieldPath); } - setFieldValidatingChanges(fieldPath, false, formikContext); }, 250); each(dependantFields, dependantField => { diff --git a/app/pages/prescription/therapySettingsFormStep.js b/app/pages/prescription/therapySettingsFormStep.js index 1b419c3c5b..97f9b4b2d3 100644 --- a/app/pages/prescription/therapySettingsFormStep.js +++ b/app/pages/prescription/therapySettingsFormStep.js @@ -15,7 +15,7 @@ import max from 'lodash/max'; import uniq from 'lodash/uniq'; import { default as _values } from 'lodash/values'; -import { getFieldError, getThresholdWarning, onChangeWithDependantFields, setFieldValidatingChanges } from '../../core/forms'; +import { getFieldError, getThresholdWarning, onChangeWithDependantFields } from '../../core/forms'; import { useInitialFocusedInput } from '../../core/hooks'; import { Paragraph2, Body2, Headline, Title } from '../../components/elements/FontStyles'; import RadioGroup from '../../components/elements/RadioGroup'; @@ -677,10 +677,8 @@ export const TherapySettings = withTranslation()(props => { (isFinite(value) && parseFloat(value) >= 0) || (isString(value) && !isEmpty(value)) ) { - setFieldValidatingChanges(fieldPath, true, formikContext); await formikContext.setFieldTouched(fieldPath, true, true); await formikContext.validateField(fieldPath); - setFieldValidatingChanges(fieldPath, false, formikContext); } }; From b665550721934492e024bd184eb57b57bde540c5 Mon Sep 17 00:00:00 2001 From: clintonium-119 Date: Sat, 14 Sep 2024 21:44:59 -0400 Subject: [PATCH 058/112] Prevent errors persisted in localStorage from reaching the final review step --- app/core/forms.js | 4 +- app/pages/prescription/PrescriptionForm.js | 16 ++++--- .../prescription/prescriptionFormConstants.js | 5 ++- app/pages/prescription/reviewFormStep.js | 6 ++- .../prescription/therapySettingsFormStep.js | 44 ++----------------- 5 files changed, 25 insertions(+), 50 deletions(-) diff --git a/app/core/forms.js b/app/core/forms.js index 3d653a2bce..5407694908 100644 --- a/app/core/forms.js +++ b/app/core/forms.js @@ -36,11 +36,11 @@ export const fieldsAreValid = (fieldNames, schema, values) => * Returns the error state of a field in a way that's sensible for our components * @param {String} fieldPath path to the field in dot notation * @param {Object} formikContext context provided by useFormikContext() - * @param {Boolean} forceTouched treat field as touched to force showing error prior to user interaction + * @param {Boolean} forceTouchedIfFilled treat field as touched to force showing error prior to user interaction if it has a value within it * @returns error string or null */ export const getFieldError = (fieldPath, formikContext, forceTouchedIfFilled = true) => { - const { errors, touched, initialValues, values, status } = formikContext; + const { errors, touched, initialValues, values } = formikContext; const value = get(values, fieldPath); const forceTouched = forceTouchedIfFilled && ( diff --git a/app/pages/prescription/PrescriptionForm.js b/app/pages/prescription/PrescriptionForm.js index d61ba05212..ee17a302aa 100644 --- a/app/pages/prescription/PrescriptionForm.js +++ b/app/pages/prescription/PrescriptionForm.js @@ -363,9 +363,9 @@ export const PrescriptionForm = props => { singleStepEditComplete: (cancelFieldUpdates) => { if (cancelFieldUpdates) { - resetForm({values: cloneDeep(singleStepEditValues) }); + setTimeout(() => resetForm({values: cloneDeep(singleStepEditValues) }), 100); } else { - resetForm({ values: cloneDeep(values) }); + setTimeout(() => resetForm({ values: cloneDeep(values) }), 100); } handlers.activeStepUpdate(pendingStep); @@ -561,7 +561,6 @@ export const PrescriptionForm = props => { if (prescriptionId) setFieldValue('id', prescriptionId); if (isLastStep()) { - let messageAction = isRevision ? t('updated') : t('created'); if (isPrescriber) messageAction = t('finalized and sent'); @@ -574,7 +573,7 @@ export const PrescriptionForm = props => { } else { if (prescriptionId && isNewPrescriptionFlow()) { // Redirect to normal prescription edit flow once we have a prescription ID - setStoredValues({ ...values, id: prescriptionId }) + setStoredValues({ ...values, id: prescriptionId }); dispatch(push(`/prescriptions/${prescriptionId}`)); } } @@ -615,6 +614,10 @@ export const PrescriptionForm = props => { setSingleStepEditValues(values) } else { handlers.activeStepUpdate(newStep); + setTimeout(() => { + formikContext.validateForm(); + if (isLastStep()) delete localStorage[storageKey]; + }, 0); } log('Step to', newStep.join(',')); @@ -657,7 +660,10 @@ export const PrescriptionForm = props => { - + From 4bf224a0f246e30d17df6d22aea485d15d25d306 Mon Sep 17 00:00:00 2001 From: clintonium-119 Date: Fri, 27 Sep 2024 10:37:52 -0400 Subject: [PATCH 086/112] Bump viz --- package.json | 2 +- yarn.lock | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index ab0f181fe9..b5ff75168d 100644 --- a/package.json +++ b/package.json @@ -66,7 +66,7 @@ "@storybook/react": "7.5.0", "@storybook/react-webpack5": "7.5.0", "@testing-library/react-hooks": "8.0.1", - "@tidepool/viz": "1.42.0-rc.3", + "@tidepool/viz": "1.42.0-rc.5", "async": "2.6.4", "autoprefixer": "10.4.16", "babel-core": "7.0.0-bridge.0", diff --git a/yarn.lock b/yarn.lock index 97dc95938f..4d5f1172fb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5268,9 +5268,9 @@ __metadata: languageName: node linkType: hard -"@tidepool/viz@npm:1.42.0-rc.3": - version: 1.42.0-rc.3 - resolution: "@tidepool/viz@npm:1.42.0-rc.3" +"@tidepool/viz@npm:1.42.0-rc.5": + version: 1.42.0-rc.5 + resolution: "@tidepool/viz@npm:1.42.0-rc.5" dependencies: bluebird: 3.7.2 bows: 1.7.2 @@ -5330,7 +5330,7 @@ __metadata: react-dom: 16.x react-redux: 8.x redux: 4.x - checksum: 24b2b311d8253f7054c5083ffa93273d527fbbcfb292c6f40e026aae06dcddac23ec3205630ff9a3844d5de2edbd85ecab2503227c17272c29daa9d591ed6005 + checksum: fea8248714871e6a1d64643c3d38ae8bf3e5164e2e856364f1d84c1774d8de6beac7441365c7211380442937cab514240879877af5c022b8595b1f10edf27fca languageName: node linkType: hard @@ -7427,7 +7427,7 @@ __metadata: "@storybook/react": 7.5.0 "@storybook/react-webpack5": 7.5.0 "@testing-library/react-hooks": 8.0.1 - "@tidepool/viz": 1.42.0-rc.3 + "@tidepool/viz": 1.42.0-rc.5 async: 2.6.4 autoprefixer: 10.4.16 babel-core: 7.0.0-bridge.0 From 3c49d42dcebd032bd8a129a5f920d424621b65d7 Mon Sep 17 00:00:00 2001 From: clintonium-119 Date: Fri, 27 Sep 2024 10:38:19 -0400 Subject: [PATCH 087/112] v1.81.0-rc.20 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index b5ff75168d..f3782dd642 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "node": "20.8.0" }, "packageManager": "yarn@3.6.4", - "version": "1.81.0-rc.19", + "version": "1.81.0-rc.20", "private": true, "scripts": { "test": "TZ=UTC NODE_ENV=test NODE_OPTIONS='--max-old-space-size=4096' yarn karma start", From 58cecd22f5b94429e9b72fa17ead18e6bb416ed7 Mon Sep 17 00:00:00 2001 From: clintonium-119 Date: Fri, 27 Sep 2024 12:08:42 -0400 Subject: [PATCH 088/112] Bump viz --- package.json | 4 ++-- yarn.lock | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/package.json b/package.json index f3782dd642..c0d0c3b7cb 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "node": "20.8.0" }, "packageManager": "yarn@3.6.4", - "version": "1.81.0-rc.20", + "version": "1.81.0-rc.19", "private": true, "scripts": { "test": "TZ=UTC NODE_ENV=test NODE_OPTIONS='--max-old-space-size=4096' yarn karma start", @@ -66,7 +66,7 @@ "@storybook/react": "7.5.0", "@storybook/react-webpack5": "7.5.0", "@testing-library/react-hooks": "8.0.1", - "@tidepool/viz": "1.42.0-rc.5", + "@tidepool/viz": "1.42.0-rc.6", "async": "2.6.4", "autoprefixer": "10.4.16", "babel-core": "7.0.0-bridge.0", diff --git a/yarn.lock b/yarn.lock index 4d5f1172fb..1db3837fbe 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5268,9 +5268,9 @@ __metadata: languageName: node linkType: hard -"@tidepool/viz@npm:1.42.0-rc.5": - version: 1.42.0-rc.5 - resolution: "@tidepool/viz@npm:1.42.0-rc.5" +"@tidepool/viz@npm:1.42.0-rc.6": + version: 1.42.0-rc.6 + resolution: "@tidepool/viz@npm:1.42.0-rc.6" dependencies: bluebird: 3.7.2 bows: 1.7.2 @@ -5330,7 +5330,7 @@ __metadata: react-dom: 16.x react-redux: 8.x redux: 4.x - checksum: fea8248714871e6a1d64643c3d38ae8bf3e5164e2e856364f1d84c1774d8de6beac7441365c7211380442937cab514240879877af5c022b8595b1f10edf27fca + checksum: 8ff4565cc71f14c250bd6e918b73fc86706157b6307a7128c5dcdd9873ae38de28d9ebaad38c5cac52cc77e1a2cf01a072fcb99f30e5b59caccac319ea94f32c languageName: node linkType: hard @@ -7427,7 +7427,7 @@ __metadata: "@storybook/react": 7.5.0 "@storybook/react-webpack5": 7.5.0 "@testing-library/react-hooks": 8.0.1 - "@tidepool/viz": 1.42.0-rc.5 + "@tidepool/viz": 1.42.0-rc.6 async: 2.6.4 autoprefixer: 10.4.16 babel-core: 7.0.0-bridge.0 From 7c4fc782b2bed42963b39ecb5015e0d08b399ff7 Mon Sep 17 00:00:00 2001 From: clintonium-119 Date: Fri, 27 Sep 2024 15:15:23 -0400 Subject: [PATCH 089/112] Bump viz --- package.json | 2 +- yarn.lock | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index c0d0c3b7cb..7dfe966696 100644 --- a/package.json +++ b/package.json @@ -66,7 +66,7 @@ "@storybook/react": "7.5.0", "@storybook/react-webpack5": "7.5.0", "@testing-library/react-hooks": "8.0.1", - "@tidepool/viz": "1.42.0-rc.6", + "@tidepool/viz": "1.42.0-rc.9", "async": "2.6.4", "autoprefixer": "10.4.16", "babel-core": "7.0.0-bridge.0", diff --git a/yarn.lock b/yarn.lock index 1db3837fbe..374b516aa7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5268,9 +5268,9 @@ __metadata: languageName: node linkType: hard -"@tidepool/viz@npm:1.42.0-rc.6": - version: 1.42.0-rc.6 - resolution: "@tidepool/viz@npm:1.42.0-rc.6" +"@tidepool/viz@npm:1.42.0-rc.9": + version: 1.42.0-rc.9 + resolution: "@tidepool/viz@npm:1.42.0-rc.9" dependencies: bluebird: 3.7.2 bows: 1.7.2 @@ -5330,7 +5330,7 @@ __metadata: react-dom: 16.x react-redux: 8.x redux: 4.x - checksum: 8ff4565cc71f14c250bd6e918b73fc86706157b6307a7128c5dcdd9873ae38de28d9ebaad38c5cac52cc77e1a2cf01a072fcb99f30e5b59caccac319ea94f32c + checksum: d94243e44ab1bcb65af59bf48783da66d7a667cacf6efd488b8fcbf7884e72fae2dd2df9dba325e194d3c5ec64ef9986a2454f38f48592a0e80e244f3d84cc7b languageName: node linkType: hard @@ -7427,7 +7427,7 @@ __metadata: "@storybook/react": 7.5.0 "@storybook/react-webpack5": 7.5.0 "@testing-library/react-hooks": 8.0.1 - "@tidepool/viz": 1.42.0-rc.6 + "@tidepool/viz": 1.42.0-rc.9 async: 2.6.4 autoprefixer: 10.4.16 babel-core: 7.0.0-bridge.0 From 7a4ff9612daa861cb04b0e92d963e70cfe179a92 Mon Sep 17 00:00:00 2001 From: clintonium-119 Date: Mon, 7 Oct 2024 10:47:20 -0400 Subject: [PATCH 090/112] Bump viz --- package.json | 2 +- yarn.lock | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 7dfe966696..30d01bb437 100644 --- a/package.json +++ b/package.json @@ -66,7 +66,7 @@ "@storybook/react": "7.5.0", "@storybook/react-webpack5": "7.5.0", "@testing-library/react-hooks": "8.0.1", - "@tidepool/viz": "1.42.0-rc.9", + "@tidepool/viz": "1.42.0-rc.10", "async": "2.6.4", "autoprefixer": "10.4.16", "babel-core": "7.0.0-bridge.0", diff --git a/yarn.lock b/yarn.lock index 374b516aa7..bbb1881968 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5268,9 +5268,9 @@ __metadata: languageName: node linkType: hard -"@tidepool/viz@npm:1.42.0-rc.9": - version: 1.42.0-rc.9 - resolution: "@tidepool/viz@npm:1.42.0-rc.9" +"@tidepool/viz@npm:1.42.0-rc.10": + version: 1.42.0-rc.10 + resolution: "@tidepool/viz@npm:1.42.0-rc.10" dependencies: bluebird: 3.7.2 bows: 1.7.2 @@ -5330,7 +5330,7 @@ __metadata: react-dom: 16.x react-redux: 8.x redux: 4.x - checksum: d94243e44ab1bcb65af59bf48783da66d7a667cacf6efd488b8fcbf7884e72fae2dd2df9dba325e194d3c5ec64ef9986a2454f38f48592a0e80e244f3d84cc7b + checksum: aa6d3b25da3da6504eb2fec23f9512dd69cd490c423242e3e546dbf8bb7bf46070a324c4e5f6602353ce4532aba005de6c3c95230fe229fb1a51728b634c59ef languageName: node linkType: hard @@ -7427,7 +7427,7 @@ __metadata: "@storybook/react": 7.5.0 "@storybook/react-webpack5": 7.5.0 "@testing-library/react-hooks": 8.0.1 - "@tidepool/viz": 1.42.0-rc.9 + "@tidepool/viz": 1.42.0-rc.10 async: 2.6.4 autoprefixer: 10.4.16 babel-core: 7.0.0-bridge.0 From 3b81a29d1d26ef76153fd41e363b64a960a59027 Mon Sep 17 00:00:00 2001 From: clintonium-119 Date: Mon, 7 Oct 2024 10:48:56 -0400 Subject: [PATCH 091/112] v1.81.0-rc.21 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 30d01bb437..022adc43a6 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "node": "20.8.0" }, "packageManager": "yarn@3.6.4", - "version": "1.81.0-rc.19", + "version": "1.81.0-rc.21", "private": true, "scripts": { "test": "TZ=UTC NODE_ENV=test NODE_OPTIONS='--max-old-space-size=4096' yarn karma start", From 999af0737f1942e62224c8c8b2e8e4a8d4085661 Mon Sep 17 00:00:00 2001 From: clintonium-119 Date: Mon, 7 Oct 2024 10:56:10 -0400 Subject: [PATCH 092/112] Bump viz --- package.json | 2 +- yarn.lock | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 022adc43a6..4c6f76da12 100644 --- a/package.json +++ b/package.json @@ -66,7 +66,7 @@ "@storybook/react": "7.5.0", "@storybook/react-webpack5": "7.5.0", "@testing-library/react-hooks": "8.0.1", - "@tidepool/viz": "1.42.0-rc.10", + "@tidepool/viz": "1.42.0-rc.11", "async": "2.6.4", "autoprefixer": "10.4.16", "babel-core": "7.0.0-bridge.0", diff --git a/yarn.lock b/yarn.lock index bbb1881968..5675847f02 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5268,9 +5268,9 @@ __metadata: languageName: node linkType: hard -"@tidepool/viz@npm:1.42.0-rc.10": - version: 1.42.0-rc.10 - resolution: "@tidepool/viz@npm:1.42.0-rc.10" +"@tidepool/viz@npm:1.42.0-rc.11": + version: 1.42.0-rc.11 + resolution: "@tidepool/viz@npm:1.42.0-rc.11" dependencies: bluebird: 3.7.2 bows: 1.7.2 @@ -5330,7 +5330,7 @@ __metadata: react-dom: 16.x react-redux: 8.x redux: 4.x - checksum: aa6d3b25da3da6504eb2fec23f9512dd69cd490c423242e3e546dbf8bb7bf46070a324c4e5f6602353ce4532aba005de6c3c95230fe229fb1a51728b634c59ef + checksum: 20399f255cc1a72c8d05333c4b171227b94d3194c86be45f7a39e804e6df0739cf2fa90301fdce792abaf6fde5495280e0c64015a66e6da29449a3c48b3e62e6 languageName: node linkType: hard @@ -7427,7 +7427,7 @@ __metadata: "@storybook/react": 7.5.0 "@storybook/react-webpack5": 7.5.0 "@testing-library/react-hooks": 8.0.1 - "@tidepool/viz": 1.42.0-rc.10 + "@tidepool/viz": 1.42.0-rc.11 async: 2.6.4 autoprefixer: 10.4.16 babel-core: 7.0.0-bridge.0 From 03d4eeed058eadf383ead848b2bf4796e03924c5 Mon Sep 17 00:00:00 2001 From: clintonium-119 Date: Mon, 7 Oct 2024 14:53:12 -0400 Subject: [PATCH 093/112] Remove cgm simulator option from rx flow --- app/pages/prescription/prescriptionFormConstants.js | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/app/pages/prescription/prescriptionFormConstants.js b/app/pages/prescription/prescriptionFormConstants.js index 5641ca3768..f4f5cb4763 100644 --- a/app/pages/prescription/prescriptionFormConstants.js +++ b/app/pages/prescription/prescriptionFormConstants.js @@ -46,7 +46,6 @@ export const deviceIdMap = { export const validDeviceIds = { cgms: [ deviceIdMap.dexcomG6, - deviceIdMap.cgmSimulator, ], pumps: [ deviceIdMap.palmtree, @@ -62,11 +61,7 @@ export const deviceDetails = { ), }, [deviceIdMap.cgmSimulator]: { - description: ( - - Find information on how to prescribe Dexcom G6 sensors and transmitters and more here. - - ), + description: null, }, [deviceIdMap.palmtree]: { description: ( From 14449feb938e6f032fa6299712aedab4393058a1 Mon Sep 17 00:00:00 2001 From: clintonium-119 Date: Mon, 7 Oct 2024 15:02:24 -0400 Subject: [PATCH 094/112] Update tests --- test/unit/pages/prescription/prescriptionFormConstants.test.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test/unit/pages/prescription/prescriptionFormConstants.test.js b/test/unit/pages/prescription/prescriptionFormConstants.test.js index 0fcf82974d..12675657b9 100644 --- a/test/unit/pages/prescription/prescriptionFormConstants.test.js +++ b/test/unit/pages/prescription/prescriptionFormConstants.test.js @@ -77,8 +77,7 @@ describe('prescriptionFormConstants', function() { ]); _.each(prescriptionFormConstants.deviceDetails, (details, deviceId) => { - expect(details.description).to.be.an('object'); - expect(details.description.props).to.be.an('object').and.have.keys(['children']); + expect(details).to.have.keys(['description']); }); }); From 581b395fafdf29b7121de01060b2f39432c598ea Mon Sep 17 00:00:00 2001 From: clintonium-119 Date: Mon, 7 Oct 2024 15:02:45 -0400 Subject: [PATCH 095/112] v1.81.0-rc.22 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 4c6f76da12..ab657ee911 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "node": "20.8.0" }, "packageManager": "yarn@3.6.4", - "version": "1.81.0-rc.21", + "version": "1.81.0-rc.22", "private": true, "scripts": { "test": "TZ=UTC NODE_ENV=test NODE_OPTIONS='--max-old-space-size=4096' yarn karma start", From 9f8e6340fa9f1b1473fd3d9cc705b945a5ed9520 Mon Sep 17 00:00:00 2001 From: clintonium-119 Date: Tue, 8 Oct 2024 14:35:37 -0400 Subject: [PATCH 096/112] Bump tideline and viz --- package.json | 4 ++-- yarn.lock | 20 ++++++++++---------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/package.json b/package.json index ab657ee911..03e9dc4be1 100644 --- a/package.json +++ b/package.json @@ -66,7 +66,7 @@ "@storybook/react": "7.5.0", "@storybook/react-webpack5": "7.5.0", "@testing-library/react-hooks": "8.0.1", - "@tidepool/viz": "1.42.0-rc.11", + "@tidepool/viz": "1.42.0-rc.12", "async": "2.6.4", "autoprefixer": "10.4.16", "babel-core": "7.0.0-bridge.0", @@ -183,7 +183,7 @@ "terser": "5.22.0", "terser-webpack-plugin": "5.3.9", "theme-ui": "0.16.1", - "tideline": "1.30.0-rc.3", + "tideline": "1.30.0-rc.4", "tidepool-platform-client": "0.59.0", "tidepool-standard-action": "0.1.1", "ua-parser-js": "1.0.36", diff --git a/yarn.lock b/yarn.lock index 5675847f02..bd283b362d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5268,9 +5268,9 @@ __metadata: languageName: node linkType: hard -"@tidepool/viz@npm:1.42.0-rc.11": - version: 1.42.0-rc.11 - resolution: "@tidepool/viz@npm:1.42.0-rc.11" +"@tidepool/viz@npm:1.42.0-rc.12": + version: 1.42.0-rc.12 + resolution: "@tidepool/viz@npm:1.42.0-rc.12" dependencies: bluebird: 3.7.2 bows: 1.7.2 @@ -5330,7 +5330,7 @@ __metadata: react-dom: 16.x react-redux: 8.x redux: 4.x - checksum: 20399f255cc1a72c8d05333c4b171227b94d3194c86be45f7a39e804e6df0739cf2fa90301fdce792abaf6fde5495280e0c64015a66e6da29449a3c48b3e62e6 + checksum: af73fedbdff2ce721668b99c75e405edc6cb963e39cd78e11c37ae0f4c02f31e2cd3577e96f7c860e31aa9c9d9248dfa121532063daabc0ecfa128277b347ba0 languageName: node linkType: hard @@ -7427,7 +7427,7 @@ __metadata: "@storybook/react": 7.5.0 "@storybook/react-webpack5": 7.5.0 "@testing-library/react-hooks": 8.0.1 - "@tidepool/viz": 1.42.0-rc.11 + "@tidepool/viz": 1.42.0-rc.12 async: 2.6.4 autoprefixer: 10.4.16 babel-core: 7.0.0-bridge.0 @@ -7551,7 +7551,7 @@ __metadata: terser: 5.22.0 terser-webpack-plugin: 5.3.9 theme-ui: 0.16.1 - tideline: 1.30.0-rc.3 + tideline: 1.30.0-rc.4 tidepool-platform-client: 0.59.0 tidepool-standard-action: 0.1.1 ua-parser-js: 1.0.36 @@ -22991,9 +22991,9 @@ __metadata: languageName: node linkType: hard -"tideline@npm:1.30.0-rc.3": - version: 1.30.0-rc.3 - resolution: "tideline@npm:1.30.0-rc.3" +"tideline@npm:1.30.0-rc.4": + version: 1.30.0-rc.4 + resolution: "tideline@npm:1.30.0-rc.4" dependencies: bows: 1.7.2 classnames: 2.3.2 @@ -23013,7 +23013,7 @@ __metadata: peerDependencies: babel-core: 6.x || 7.0.0-bridge.0 lodash: ^4.17.21 - checksum: 38519275a0bf05aeaf6865da1a50856ec0142dd1d41f424b4bcee434a9a98f3cf2d41cb54c113059ea330e196aa84f410ed5a42c2f77c813ef7bd7d7869f3c15 + checksum: 0df59515fb2015d0ced5f9a5837e4b9cf9b4d68785d26d158be18f62a852197b43ac33cd1b0187e70f8b7a6f23c90c84d94cd9bb3f59ce24dfa838a5996dd5b2 languageName: node linkType: hard From 3d04bdb3d3bbffc1e122281f62357072d3fe798e Mon Sep 17 00:00:00 2001 From: clintonium-119 Date: Tue, 8 Oct 2024 14:35:58 -0400 Subject: [PATCH 097/112] v1.81.0-rc.23 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 03e9dc4be1..b1a91d0ec4 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "node": "20.8.0" }, "packageManager": "yarn@3.6.4", - "version": "1.81.0-rc.22", + "version": "1.81.0-rc.23", "private": true, "scripts": { "test": "TZ=UTC NODE_ENV=test NODE_OPTIONS='--max-old-space-size=4096' yarn karma start", From 2eb65b1224633cca9a733e27b0ba7fabfa07b156 Mon Sep 17 00:00:00 2001 From: clintonium-119 Date: Fri, 11 Oct 2024 12:14:25 -0400 Subject: [PATCH 098/112] Bump viz --- package.json | 2 +- yarn.lock | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index b1a91d0ec4..741e43eb15 100644 --- a/package.json +++ b/package.json @@ -66,7 +66,7 @@ "@storybook/react": "7.5.0", "@storybook/react-webpack5": "7.5.0", "@testing-library/react-hooks": "8.0.1", - "@tidepool/viz": "1.42.0-rc.12", + "@tidepool/viz": "1.42.0-rc.13", "async": "2.6.4", "autoprefixer": "10.4.16", "babel-core": "7.0.0-bridge.0", diff --git a/yarn.lock b/yarn.lock index bd283b362d..202bc4483c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5268,9 +5268,9 @@ __metadata: languageName: node linkType: hard -"@tidepool/viz@npm:1.42.0-rc.12": - version: 1.42.0-rc.12 - resolution: "@tidepool/viz@npm:1.42.0-rc.12" +"@tidepool/viz@npm:1.42.0-rc.13": + version: 1.42.0-rc.13 + resolution: "@tidepool/viz@npm:1.42.0-rc.13" dependencies: bluebird: 3.7.2 bows: 1.7.2 @@ -5330,7 +5330,7 @@ __metadata: react-dom: 16.x react-redux: 8.x redux: 4.x - checksum: af73fedbdff2ce721668b99c75e405edc6cb963e39cd78e11c37ae0f4c02f31e2cd3577e96f7c860e31aa9c9d9248dfa121532063daabc0ecfa128277b347ba0 + checksum: bd4b363f29344c7347f6a6f92caa79e71bfc4db96903c8930ae404c1e03f58198e93c5c50d0a406eeace8f07edee55b8dfa922b44d3af612a9264df3c391c142 languageName: node linkType: hard @@ -7427,7 +7427,7 @@ __metadata: "@storybook/react": 7.5.0 "@storybook/react-webpack5": 7.5.0 "@testing-library/react-hooks": 8.0.1 - "@tidepool/viz": 1.42.0-rc.12 + "@tidepool/viz": 1.42.0-rc.13 async: 2.6.4 autoprefixer: 10.4.16 babel-core: 7.0.0-bridge.0 From 8d1830c9ab4bfd043fe06135ebd078f19ba2e328 Mon Sep 17 00:00:00 2001 From: clintonium-119 Date: Fri, 11 Oct 2024 12:14:41 -0400 Subject: [PATCH 099/112] v1.81.0-rc.24 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 741e43eb15..7dbc546bdc 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "node": "20.8.0" }, "packageManager": "yarn@3.6.4", - "version": "1.81.0-rc.23", + "version": "1.81.0-rc.24", "private": true, "scripts": { "test": "TZ=UTC NODE_ENV=test NODE_OPTIONS='--max-old-space-size=4096' yarn karma start", From 0ff8f5e0ad3738877b50c0aeb6b2bc9f644e0957 Mon Sep 17 00:00:00 2001 From: clintonium-119 Date: Tue, 15 Oct 2024 10:48:51 -0400 Subject: [PATCH 100/112] Bump viz --- package.json | 2 +- yarn.lock | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 7dbc546bdc..c15e37f165 100644 --- a/package.json +++ b/package.json @@ -66,7 +66,7 @@ "@storybook/react": "7.5.0", "@storybook/react-webpack5": "7.5.0", "@testing-library/react-hooks": "8.0.1", - "@tidepool/viz": "1.42.0-rc.13", + "@tidepool/viz": "1.42.0-rc.14", "async": "2.6.4", "autoprefixer": "10.4.16", "babel-core": "7.0.0-bridge.0", diff --git a/yarn.lock b/yarn.lock index 202bc4483c..0246e62f4b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5268,9 +5268,9 @@ __metadata: languageName: node linkType: hard -"@tidepool/viz@npm:1.42.0-rc.13": - version: 1.42.0-rc.13 - resolution: "@tidepool/viz@npm:1.42.0-rc.13" +"@tidepool/viz@npm:1.42.0-rc.14": + version: 1.42.0-rc.14 + resolution: "@tidepool/viz@npm:1.42.0-rc.14" dependencies: bluebird: 3.7.2 bows: 1.7.2 @@ -5330,7 +5330,7 @@ __metadata: react-dom: 16.x react-redux: 8.x redux: 4.x - checksum: bd4b363f29344c7347f6a6f92caa79e71bfc4db96903c8930ae404c1e03f58198e93c5c50d0a406eeace8f07edee55b8dfa922b44d3af612a9264df3c391c142 + checksum: 2889b82080201def5158386abdd19dd785accb2ab64b911b3db0bc56d1c63d0e60784bc399c7a44d3678b94cc3453e5aa8ddc2ff49303bcade94c9656dc40029 languageName: node linkType: hard @@ -7427,7 +7427,7 @@ __metadata: "@storybook/react": 7.5.0 "@storybook/react-webpack5": 7.5.0 "@testing-library/react-hooks": 8.0.1 - "@tidepool/viz": 1.42.0-rc.13 + "@tidepool/viz": 1.42.0-rc.14 async: 2.6.4 autoprefixer: 10.4.16 babel-core: 7.0.0-bridge.0 From 79ea5591358d63789c4d29e433832a07c02ec98f Mon Sep 17 00:00:00 2001 From: clintonium-119 Date: Tue, 15 Oct 2024 10:49:04 -0400 Subject: [PATCH 101/112] v1.81.0-rc.25 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index c15e37f165..6b92c52d94 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "node": "20.8.0" }, "packageManager": "yarn@3.6.4", - "version": "1.81.0-rc.24", + "version": "1.81.0-rc.25", "private": true, "scripts": { "test": "TZ=UTC NODE_ENV=test NODE_OPTIONS='--max-old-space-size=4096' yarn karma start", From 89f107d55871ba711859a576c630945614a6117e Mon Sep 17 00:00:00 2001 From: Clint Beacock Date: Wed, 16 Oct 2024 22:45:19 -0400 Subject: [PATCH 102/112] [RELEASE-1] Bump tideline and viz --- package.json | 4 ++-- yarn.lock | 20 ++++++++++---------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/package.json b/package.json index 6b92c52d94..4525417927 100644 --- a/package.json +++ b/package.json @@ -66,7 +66,7 @@ "@storybook/react": "7.5.0", "@storybook/react-webpack5": "7.5.0", "@testing-library/react-hooks": "8.0.1", - "@tidepool/viz": "1.42.0-rc.14", + "@tidepool/viz": "1.42.0", "async": "2.6.4", "autoprefixer": "10.4.16", "babel-core": "7.0.0-bridge.0", @@ -183,7 +183,7 @@ "terser": "5.22.0", "terser-webpack-plugin": "5.3.9", "theme-ui": "0.16.1", - "tideline": "1.30.0-rc.4", + "tideline": "1.30.0", "tidepool-platform-client": "0.59.0", "tidepool-standard-action": "0.1.1", "ua-parser-js": "1.0.36", diff --git a/yarn.lock b/yarn.lock index 0246e62f4b..93d37340e1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5268,9 +5268,9 @@ __metadata: languageName: node linkType: hard -"@tidepool/viz@npm:1.42.0-rc.14": - version: 1.42.0-rc.14 - resolution: "@tidepool/viz@npm:1.42.0-rc.14" +"@tidepool/viz@npm:1.42.0": + version: 1.42.0 + resolution: "@tidepool/viz@npm:1.42.0" dependencies: bluebird: 3.7.2 bows: 1.7.2 @@ -5330,7 +5330,7 @@ __metadata: react-dom: 16.x react-redux: 8.x redux: 4.x - checksum: 2889b82080201def5158386abdd19dd785accb2ab64b911b3db0bc56d1c63d0e60784bc399c7a44d3678b94cc3453e5aa8ddc2ff49303bcade94c9656dc40029 + checksum: dadaf71e4149b50438c23453829e3bbaf1cc0481b0ac41b1a6ab91f144366cf10ae3db9a3ff7bf56857f62a3cf14c22017713175d2a0df3548d1bbeadccc6de3 languageName: node linkType: hard @@ -7427,7 +7427,7 @@ __metadata: "@storybook/react": 7.5.0 "@storybook/react-webpack5": 7.5.0 "@testing-library/react-hooks": 8.0.1 - "@tidepool/viz": 1.42.0-rc.14 + "@tidepool/viz": 1.42.0 async: 2.6.4 autoprefixer: 10.4.16 babel-core: 7.0.0-bridge.0 @@ -7551,7 +7551,7 @@ __metadata: terser: 5.22.0 terser-webpack-plugin: 5.3.9 theme-ui: 0.16.1 - tideline: 1.30.0-rc.4 + tideline: 1.30.0 tidepool-platform-client: 0.59.0 tidepool-standard-action: 0.1.1 ua-parser-js: 1.0.36 @@ -22991,9 +22991,9 @@ __metadata: languageName: node linkType: hard -"tideline@npm:1.30.0-rc.4": - version: 1.30.0-rc.4 - resolution: "tideline@npm:1.30.0-rc.4" +"tideline@npm:1.30.0": + version: 1.30.0 + resolution: "tideline@npm:1.30.0" dependencies: bows: 1.7.2 classnames: 2.3.2 @@ -23013,7 +23013,7 @@ __metadata: peerDependencies: babel-core: 6.x || 7.0.0-bridge.0 lodash: ^4.17.21 - checksum: 0df59515fb2015d0ced5f9a5837e4b9cf9b4d68785d26d158be18f62a852197b43ac33cd1b0187e70f8b7a6f23c90c84d94cd9bb3f59ce24dfa838a5996dd5b2 + checksum: faf7028567b20f5136df1d2f34c70be89929c89269a68664bfad16a5cdd5dee3c8287e49ab95a641fa772f1d806c9dddd40352c3fad07bf01565b029f01d5032 languageName: node linkType: hard From 2d54313d4b5016eac1dd4af180c9f5665734c202 Mon Sep 17 00:00:00 2001 From: Clint Beacock Date: Wed, 16 Oct 2024 22:45:39 -0400 Subject: [PATCH 103/112] [RELEASE-1] v1.81.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 4525417927..b15648ff8a 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "node": "20.8.0" }, "packageManager": "yarn@3.6.4", - "version": "1.81.0-rc.25", + "version": "1.81.0", "private": true, "scripts": { "test": "TZ=UTC NODE_ENV=test NODE_OPTIONS='--max-old-space-size=4096' yarn karma start", From 5b757fe21b9eb4ec2176b7f894a938b9070556f6 Mon Sep 17 00:00:00 2001 From: clintonium-119 Date: Thu, 17 Oct 2024 09:59:44 -0400 Subject: [PATCH 104/112] Fix for some types of valid future fields being deleted in rx step submission --- app/core/forms.js | 3 ++- app/pages/prescription/PrescriptionForm.js | 6 +++--- test/unit/app/core/forms.test.js | 9 ++++++++- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/app/core/forms.js b/app/core/forms.js index d37e679f21..6c7d8a4c5e 100644 --- a/app/core/forms.js +++ b/app/core/forms.js @@ -5,6 +5,7 @@ import map from 'lodash/map'; import get from 'lodash/get'; import isEmpty from 'lodash/isEmpty'; import isNumber from 'lodash/isNumber'; +import isPlainObject from 'lodash/isPlainObject'; import isString from 'lodash/isString'; import trim from 'lodash/trim'; @@ -45,7 +46,7 @@ export const getFieldError = (fieldPath, formikContext, forceTouchedIfFilled = t const forceTouched = forceTouchedIfFilled && ( (isFinite(value) && parseFloat(value) >= 0) || - (isString(value) && !isEmpty(value)) + ((isString(value) || isPlainObject(value)) && !isEmpty(value)) ); return (get(touched, fieldPath, forceTouched) || get(initialValues, fieldPath)) && get(errors, fieldPath) diff --git a/app/pages/prescription/PrescriptionForm.js b/app/pages/prescription/PrescriptionForm.js index 0f59c1cacc..a483805654 100644 --- a/app/pages/prescription/PrescriptionForm.js +++ b/app/pages/prescription/PrescriptionForm.js @@ -425,11 +425,11 @@ export const PrescriptionForm = props => { ]; if (includes(scheduleArrays, fieldPath) && value.length === 1) { - return keys(value[0]).length = 1; + return keys(value[0]).length === 1; } - // Return empty values for non-array fields - return isEmpty(value) || getFieldError(fieldPath, formikContext, true); + // Return undefined fields, or invalid non-array fields if they contain values + return isUndefined(value) || getFieldError(fieldPath, formikContext, true); } ); diff --git a/test/unit/app/core/forms.test.js b/test/unit/app/core/forms.test.js index 1f13f3f85a..29658fba2a 100644 --- a/test/unit/app/core/forms.test.js +++ b/test/unit/app/core/forms.test.js @@ -16,6 +16,7 @@ describe('forms', function() { touchedAndNoError: undefined, notTouchedAndError: 'error!', notTouchedAndErrorAndFilled: 'error!', + notTouchedAndErrorAndNonEmptyObject: 'error!', notTouchedAndNoError: undefined, initiallySetAndError: 'error!', notInitiallySetAndError: 'error!', @@ -26,6 +27,7 @@ describe('forms', function() { touchedAndNoError: true, notTouchedAndError: undefined, notTouchedAndErrorAndFilled: undefined, + notTouchedAndErrorAndNonEmptyObject: undefined, notTouchedAndNoError: undefined, initiallySetAndError: undefined, notInitiallySetAndError: undefined, @@ -36,6 +38,7 @@ describe('forms', function() { touchedAndNoError: undefined, notTouchedAndError: undefined, notTouchedAndErrorAndFilled: 'filled', + notTouchedAndErrorAndNonEmptyObject: { path: 'filled' }, notTouchedAndNoError: undefined, initiallySetAndError: 'foo', notInitiallySetAndError: undefined, @@ -77,7 +80,7 @@ describe('forms', function() { }); }); - describe('getFieldError', () => { + describe.only('getFieldError', () => { it('should return `null` when field has not been touched and is not in an error state', () => { expect(formUtils.getFieldError('notTouchedAndNoError', formikContext)).to.be.null; }); @@ -94,6 +97,10 @@ describe('forms', function() { expect(formUtils.getFieldError('notTouchedAndErrorAndFilled', formikContext, true)).to.equal('error!'); }); + it('should return an error when a non-empty object field has not been touched and is in an error state, and the forceTouchedIfFilled argument is `true`', () => { + expect(formUtils.getFieldError('notTouchedAndErrorAndNonEmptyObject', formikContext, true)).to.equal('error!'); + }); + it('should return `null` when field has been touched and is not in an error state', () => { expect(formUtils.getFieldError('touchedAndNoError', formikContext)).to.be.null; }); From 143f5c769482c9de39b8eff8b694ec02882abf18 Mon Sep 17 00:00:00 2001 From: clintonium-119 Date: Thu, 17 Oct 2024 10:10:39 -0400 Subject: [PATCH 105/112] Reenable all tests to run --- test/unit/app/core/forms.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/unit/app/core/forms.test.js b/test/unit/app/core/forms.test.js index 29658fba2a..df7d711bbd 100644 --- a/test/unit/app/core/forms.test.js +++ b/test/unit/app/core/forms.test.js @@ -80,7 +80,7 @@ describe('forms', function() { }); }); - describe.only('getFieldError', () => { + describe('getFieldError', () => { it('should return `null` when field has not been touched and is not in an error state', () => { expect(formUtils.getFieldError('notTouchedAndNoError', formikContext)).to.be.null; }); From 78330c191eb1bdb493304191a0f499fcdd646f19 Mon Sep 17 00:00:00 2001 From: clintonium-119 Date: Thu, 17 Oct 2024 12:39:26 -0400 Subject: [PATCH 106/112] Allow passing options to getPrescriptionsForClinic --- app/core/api.js | 4 ++-- app/redux/actions/async.js | 8 ++++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/app/core/api.js b/app/core/api.js index eb49302df2..d99f7522e4 100644 --- a/app/core/api.js +++ b/app/core/api.js @@ -872,8 +872,8 @@ api.metrics.track = function(eventName, properties, cb) { api.prescription = {}; -api.prescription.getAllForClinic = function(clinicId, cb) { - return tidepool.getPrescriptionsForClinic(clinicId, cb); +api.prescription.getAllForClinic = function(clinicId, options, cb) { + return tidepool.getPrescriptionsForClinic(clinicId, options, cb); }; api.prescription.create = function(clinicId, prescription, cb) { diff --git a/app/redux/actions/async.js b/app/redux/actions/async.js index f6d4cfaf7e..f4ff865bf1 100644 --- a/app/redux/actions/async.js +++ b/app/redux/actions/async.js @@ -1229,11 +1229,15 @@ export function fetchPatientData(api, options, id) { * @param {Object} api - an instance of the API wrapper * @param {String} clinicId - Id of the clinic */ -export function fetchClinicPrescriptions(api, clinicId) { +export function fetchClinicPrescriptions(api, clinicId, options = {}) { + _.defaults(options, { + size: 1000, + }); + return (dispatch) => { dispatch(sync.fetchClinicPrescriptionsRequest()); - api.prescription.getAllForClinic(clinicId, (err, prescriptions) => { + api.prescription.getAllForClinic(clinicId, options, (err, prescriptions) => { if (err) { dispatch(sync.fetchClinicPrescriptionsFailure( createActionError(ErrorMessages.ERR_FETCHING_CLINIC_PRESCRIPTIONS, err), err From 697c35b847bed0e0e8795c61e2c69c4e65e732bc Mon Sep 17 00:00:00 2001 From: clintonium-119 Date: Thu, 17 Oct 2024 12:39:52 -0400 Subject: [PATCH 107/112] Bump platform-client --- package.json | 2 +- yarn.lock | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index b15648ff8a..a03e46479d 100644 --- a/package.json +++ b/package.json @@ -184,7 +184,7 @@ "terser-webpack-plugin": "5.3.9", "theme-ui": "0.16.1", "tideline": "1.30.0", - "tidepool-platform-client": "0.59.0", + "tidepool-platform-client": "0.60.0-web-3242-increase-prescriptions-fetch-limit.1", "tidepool-standard-action": "0.1.1", "ua-parser-js": "1.0.36", "url-loader": "4.1.1", diff --git a/yarn.lock b/yarn.lock index 93d37340e1..dc6bdfd084 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7552,7 +7552,7 @@ __metadata: terser-webpack-plugin: 5.3.9 theme-ui: 0.16.1 tideline: 1.30.0 - tidepool-platform-client: 0.59.0 + tidepool-platform-client: 0.60.0-web-3242-increase-prescriptions-fetch-limit.1 tidepool-standard-action: 0.1.1 ua-parser-js: 1.0.36 url-loader: 4.1.1 @@ -23017,15 +23017,15 @@ __metadata: languageName: node linkType: hard -"tidepool-platform-client@npm:0.59.0": - version: 0.59.0 - resolution: "tidepool-platform-client@npm:0.59.0" +"tidepool-platform-client@npm:0.60.0-web-3242-increase-prescriptions-fetch-limit.1": + version: 0.60.0-web-3242-increase-prescriptions-fetch-limit.1 + resolution: "tidepool-platform-client@npm:0.60.0-web-3242-increase-prescriptions-fetch-limit.1" dependencies: async: 0.9.0 lodash: 4.17.21 superagent: 5.2.2 uuid: 3.1.0 - checksum: 9ea493f5e7f02657561bac509eee863869176744462ff478ab88df219031bf5847c70bd5773ec68502f4a3bf0f00c5053797f6a73849c598dd9f31dad0825fcd + checksum: 58c8d7e8d65c404885050b6b31a40c6b9f21cb3232ab41ae7c100f70d5bd707ca784459c7685df26ce4ceac08a9390d825b8845f94ae1f3820640d1b2fb59aff languageName: node linkType: hard From f31814fd2ad8aeb898213de1cf7f81c39a990a25 Mon Sep 17 00:00:00 2001 From: clintonium-119 Date: Thu, 17 Oct 2024 12:41:23 -0400 Subject: [PATCH 108/112] v1.81.0-rc.26 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a03e46479d..e7221f88c3 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "node": "20.8.0" }, "packageManager": "yarn@3.6.4", - "version": "1.81.0", + "version": "1.81.0-rc.26", "private": true, "scripts": { "test": "TZ=UTC NODE_ENV=test NODE_OPTIONS='--max-old-space-size=4096' yarn karma start", From eb82e7519a9e0e28bea77b059a82da5003a5b8cb Mon Sep 17 00:00:00 2001 From: clintonium-119 Date: Thu, 17 Oct 2024 13:12:46 -0400 Subject: [PATCH 109/112] Update tests --- test/unit/app/core/api.test.js | 4 ++-- test/unit/redux/actions/async.test.js | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/test/unit/app/core/api.test.js b/test/unit/app/core/api.test.js index c0b90b50d0..3c79d07ed9 100644 --- a/test/unit/app/core/api.test.js +++ b/test/unit/app/core/api.test.js @@ -751,8 +751,8 @@ describe('api', () => { describe('getAllForClinic', () => { it('should call tidepool.getPrescriptionsForClinic with the appropriate args', () => { const cb = sinon.stub(); - api.prescription.getAllForClinic(cb); - sinon.assert.calledWith(tidepool.getPrescriptionsForClinic, cb); + api.prescription.getAllForClinic('clinicId', { foo: 'bar' } ,cb); + sinon.assert.calledWith(tidepool.getPrescriptionsForClinic, 'clinicId', { foo: 'bar' }, cb); }); }); diff --git a/test/unit/redux/actions/async.test.js b/test/unit/redux/actions/async.test.js index ab764fa88e..d0b77566cb 100644 --- a/test/unit/redux/actions/async.test.js +++ b/test/unit/redux/actions/async.test.js @@ -4683,7 +4683,7 @@ describe('Actions', () => { let api = { prescription: { - getAllForClinic: sinon.stub().callsArgWith(1, null, prescriptions), + getAllForClinic: sinon.stub().callsArgWith(2, null, prescriptions), }, }; @@ -4708,7 +4708,7 @@ describe('Actions', () => { let api = { prescription: { - getAllForClinic: sinon.stub().callsArgWith(1, {status: 500, body: 'Error!'}, null), + getAllForClinic: sinon.stub().callsArgWith(2, {status: 500, body: 'Error!'}, null), }, }; From 46cf40f981f37fc17a3e9ffc01c250dc1f1ce7c7 Mon Sep 17 00:00:00 2001 From: clintonium-119 Date: Thu, 17 Oct 2024 13:26:41 -0400 Subject: [PATCH 110/112] Bump platform-client --- package.json | 2 +- yarn.lock | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index e7221f88c3..c115dae108 100644 --- a/package.json +++ b/package.json @@ -184,7 +184,7 @@ "terser-webpack-plugin": "5.3.9", "theme-ui": "0.16.1", "tideline": "1.30.0", - "tidepool-platform-client": "0.60.0-web-3242-increase-prescriptions-fetch-limit.1", + "tidepool-platform-client": "0.60.0-rc.1", "tidepool-standard-action": "0.1.1", "ua-parser-js": "1.0.36", "url-loader": "4.1.1", diff --git a/yarn.lock b/yarn.lock index dc6bdfd084..c82e2842e1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7552,7 +7552,7 @@ __metadata: terser-webpack-plugin: 5.3.9 theme-ui: 0.16.1 tideline: 1.30.0 - tidepool-platform-client: 0.60.0-web-3242-increase-prescriptions-fetch-limit.1 + tidepool-platform-client: 0.60.0-rc.1 tidepool-standard-action: 0.1.1 ua-parser-js: 1.0.36 url-loader: 4.1.1 @@ -23017,15 +23017,15 @@ __metadata: languageName: node linkType: hard -"tidepool-platform-client@npm:0.60.0-web-3242-increase-prescriptions-fetch-limit.1": - version: 0.60.0-web-3242-increase-prescriptions-fetch-limit.1 - resolution: "tidepool-platform-client@npm:0.60.0-web-3242-increase-prescriptions-fetch-limit.1" +"tidepool-platform-client@npm:0.60.0-rc.1": + version: 0.60.0-rc.1 + resolution: "tidepool-platform-client@npm:0.60.0-rc.1" dependencies: async: 0.9.0 lodash: 4.17.21 superagent: 5.2.2 uuid: 3.1.0 - checksum: 58c8d7e8d65c404885050b6b31a40c6b9f21cb3232ab41ae7c100f70d5bd707ca784459c7685df26ce4ceac08a9390d825b8845f94ae1f3820640d1b2fb59aff + checksum: 5a57e872ce20fbb70dc1bf26dd20a5839daa17990439e7e79cb3ed64034dda3be3e1ac71d31b313d292fa4d09de6b17d48a51ebdf7387fa1ef02b43c8d05ea16 languageName: node linkType: hard From e3212ee7365ee9c18f887684cdf30f1c1d22c44e Mon Sep 17 00:00:00 2001 From: clintonium-119 Date: Thu, 17 Oct 2024 15:33:23 -0400 Subject: [PATCH 111/112] Bump platform-client --- package.json | 2 +- yarn.lock | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index c115dae108..6332773eaa 100644 --- a/package.json +++ b/package.json @@ -184,7 +184,7 @@ "terser-webpack-plugin": "5.3.9", "theme-ui": "0.16.1", "tideline": "1.30.0", - "tidepool-platform-client": "0.60.0-rc.1", + "tidepool-platform-client": "0.60.0", "tidepool-standard-action": "0.1.1", "ua-parser-js": "1.0.36", "url-loader": "4.1.1", diff --git a/yarn.lock b/yarn.lock index c82e2842e1..0bb8009114 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7552,7 +7552,7 @@ __metadata: terser-webpack-plugin: 5.3.9 theme-ui: 0.16.1 tideline: 1.30.0 - tidepool-platform-client: 0.60.0-rc.1 + tidepool-platform-client: 0.60.0 tidepool-standard-action: 0.1.1 ua-parser-js: 1.0.36 url-loader: 4.1.1 @@ -23017,15 +23017,15 @@ __metadata: languageName: node linkType: hard -"tidepool-platform-client@npm:0.60.0-rc.1": - version: 0.60.0-rc.1 - resolution: "tidepool-platform-client@npm:0.60.0-rc.1" +"tidepool-platform-client@npm:0.60.0": + version: 0.60.0 + resolution: "tidepool-platform-client@npm:0.60.0" dependencies: async: 0.9.0 lodash: 4.17.21 superagent: 5.2.2 uuid: 3.1.0 - checksum: 5a57e872ce20fbb70dc1bf26dd20a5839daa17990439e7e79cb3ed64034dda3be3e1ac71d31b313d292fa4d09de6b17d48a51ebdf7387fa1ef02b43c8d05ea16 + checksum: 19775556a0f551c4a5c11980b5d985d43437fe819fe4e8bdf0fb9eaa7099dab31e8107a74fd5ed5e9ba4f823531176da2dab690a2c2c44c0bb2198c56232e899 languageName: node linkType: hard From 3a9769c83465e45346557b9d09b824100ca35215 Mon Sep 17 00:00:00 2001 From: clintonium-119 Date: Thu, 17 Oct 2024 15:34:09 -0400 Subject: [PATCH 112/112] v1.81.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 6332773eaa..a66e633fda 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "node": "20.8.0" }, "packageManager": "yarn@3.6.4", - "version": "1.81.0-rc.26", + "version": "1.81.0", "private": true, "scripts": { "test": "TZ=UTC NODE_ENV=test NODE_OPTIONS='--max-old-space-size=4096' yarn karma start",