Skip to content

Commit

Permalink
Merge pull request #1447 from tidepool-org/release-1.81.0
Browse files Browse the repository at this point in the history
Release 1.81.0 to develop
  • Loading branch information
clintonium-119 authored Oct 17, 2024
2 parents f13f74b + 3a9769c commit 4c3bb88
Show file tree
Hide file tree
Showing 48 changed files with 1,786 additions and 1,253 deletions.
7 changes: 5 additions & 2 deletions app/components/chart/settings.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'}`;
}
Expand Down Expand Up @@ -538,15 +541,15 @@ const Settings = ({

{selectedSettingsId ? renderChart() : renderMissingSettingsMessage()}

<Flex mt={4} mb={5} pl={manufacturer === 'tandem' ? '20px' : 0}>
<Box mt={4} mb={5} pl={manufacturer === 'tandem' ? '20px' : 0} sx={{ float: 'left', clear: 'both' }}>
<Button
className="btn-refresh"
variant="secondary"
onClick={onClickRefresh}
>
{t('Refresh')}
</Button>
</Flex>
</Box>
</div>
</div>
</div>
Expand Down
2 changes: 1 addition & 1 deletion app/components/clinic/TideDashboardConfigForm.js
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ export const TideDashboardConfigForm = props => {
}}
/>

{getFieldError('tags', formikContext) && (
{getFieldError('tags', formikContext, false) && (
<Caption ml={2} mt={2} sx={{ color: 'feedback.danger' }}>
{errors.tags}
</Caption>
Expand Down
2 changes: 1 addition & 1 deletion app/components/elements/Pill.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'],
Expand Down
7 changes: 4 additions & 3 deletions app/components/elements/RadioGroup.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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};
Expand Down
13 changes: 7 additions & 6 deletions app/components/elements/Stepper.js
Original file line number Diff line number Diff line change
Expand Up @@ -332,7 +332,7 @@ export function Stepper(props) {
? steps[activeStep].subSteps[activeSubStep]
: steps[activeStep];

return (
return step ? (
<Flex sx={{ justifyContent: 'flex-end' }} className="step-actions" mt={3} {...themeProps.actions}>
{!step.hideBack && (
<Button
Expand Down Expand Up @@ -376,19 +376,20 @@ export function Stepper(props) {
</Button>
)}
</Flex>
);
) : null;
};

const renderActiveStepPanel = () => {
const Panel = stepHasSubSteps(activeStep)
? steps[activeStep].subSteps[activeSubStep].panelContent
: steps[activeStep].panelContent;
? steps[activeStep]?.subSteps?.[activeSubStep]?.panelContent
: steps[activeStep]?.panelContent;

return React.cloneElement(Panel, {
return Panel ? React.cloneElement(Panel || null, {
key: activeStep,
id: `${id}-step-panel-${activeStep}`,
'aria-labelledby': getStepId(activeStep),
});
steps,
}) : null;
};

return (
Expand Down
1 change: 1 addition & 0 deletions app/components/elements/TabGroup.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ const StyledTab = styled(Tab)`
font-family: inherit;
min-height: auto;
min-width: auto;
max-width: 300px;
text-transform: none;
padding: 12px ${space[4]}px;
opacity: 1;
Expand Down
4 changes: 2 additions & 2 deletions app/core/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
59 changes: 53 additions & 6 deletions app/core/forms.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
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';
import isString from 'lodash/isString';
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';

import i18next from './language';
Expand Down Expand Up @@ -33,13 +37,22 @@ 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, { 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 } = formikContext;
const value = get(values, fieldPath);

const forceTouched = forceTouchedIfFilled && (
(isFinite(value) && parseFloat(value) >= 0) ||
((isString(value) || isPlainObject(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
Expand Down Expand Up @@ -86,12 +99,46 @@ 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
*/
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 = (parentFieldPath, dependantFields, formikContext, setDependantsTouched = true, debounceValidateMs = 250) => async e => {
formikContext.handleChange(e);
await formikContext.setFieldTouched(parentFieldPath, true, true);
await formikContext.validateField(parentFieldPath);

const debouncedValidate = () => debounce(async fieldPath => {
if (setDependantsTouched) {
await formikContext.setFieldTouched(fieldPath, true, true);
await formikContext.validateField(fieldPath);
}
}, debounceValidateMs);

each(dependantFields, dependantField => {
const scheduleIndexPlaceholder = dependantField.indexOf('.$.');

if (scheduleIndexPlaceholder > 0) {
const fieldParts = dependantField.split('.$.');
const fieldArrayValues = get(formikContext.values, fieldParts[0]);

each(fieldArrayValues, (fieldArrayValue, index) => {
debouncedValidate()(`${fieldParts[0]}.${index}.${fieldParts[1]}`);
});
} else {
debouncedValidate()(dependantField);
}
});
};
49 changes: 35 additions & 14 deletions app/core/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,17 @@ 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
*/
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
Expand All @@ -288,11 +299,10 @@ 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, queryParams) => {
utils.getTimePrefsForDataProcessing = (latestUpload, latestDiabetesDatum, queryParams) => {
var timePrefsForTideline;
var browserTimezone = new Intl.DateTimeFormat().resolvedOptions().timeZone;

Expand Down Expand Up @@ -331,22 +341,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);
Expand Down
2 changes: 1 addition & 1 deletion app/pages/clinicianedit/clinicianedit.js
Original file line number Diff line number Diff line change
Expand Up @@ -327,7 +327,7 @@ export const ClinicianEdit = (props) => {
>
<Checkbox
{...getCommonFormikFieldProps('prescriberPermission', formikContext, 'checked')}
label={t('Prescribing access')}
label={t('Tidepool Loop Start Orders Finalization Permission')}
themeProps={{ bg: 'lightestGrey' }}
/>
</Box>
Expand Down
2 changes: 1 addition & 1 deletion app/pages/clinicinvite/clinicinvite.js
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,7 @@ export const ClinicInvite = (props) => {
>
<Checkbox
{...getCommonFormikFieldProps('prescriberPermission', formikContext, 'checked')}
label={t('Prescribing access')}
label={t('Tidepool Loop Start Orders Finalization Permission')}
themeProps={{ sx: { bg: 'lightestGrey' } }}
/>
</Box>
Expand Down
3 changes: 1 addition & 2 deletions app/pages/clinicworkspace/ClinicPatients.js
Original file line number Diff line number Diff line change
Expand Up @@ -3199,8 +3199,7 @@ export const ClinicPatients = (props) => {
{pageCount > 1 && (
<Pagination
px="5%"
sx={{ position: 'absolute', bottom: '-50px' }}
width="100%"
sx={{ width: '100%', position: 'absolute', bottom: '-50px' }}
id="clinic-patients-pagination"
count={pageCount}
disabled={pageCount < 2}
Expand Down
2 changes: 1 addition & 1 deletion app/pages/clinicworkspace/clinicworkspace.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ export const ClinicWorkspace = (props) => {
if (showPrescriptions) {
tabs.push({
name: 'prescriptions',
label: t('Prescriptions'),
label: t('Tidepool Loop Start Orders'),
metric: 'Clinic - View prescriptions',
});
}
Expand Down
Loading

0 comments on commit 4c3bb88

Please sign in to comment.