Skip to content

Commit

Permalink
feat: [DHIS2-18328] Handle log entries for occurredAt, scheduledAt an…
Browse files Browse the repository at this point in the history
…d geometry (#3887)

* feat: geometry scheduledat occuredat in changelog

* fix: clean up

* fix: add check for featuretype

* feat: style improvements

* fix: convert changelog data in servertoclient

* feat: update clienttolist

* fix: revert new folder structure

* fix: code clean up

* fix: show coordinate

* feat: style improvements

* fix: change from property to fields

* fix: review comments

* fix: translation

* fix: revert type change
  • Loading branch information
henrikmv authored Jan 7, 2025
1 parent dcf1a95 commit f670971
Show file tree
Hide file tree
Showing 15 changed files with 161 additions and 38 deletions.
22 changes: 20 additions & 2 deletions i18n/en.pot
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ msgstr ""
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1)\n"
"POT-Creation-Date: 2024-12-05T11:39:04.447Z\n"
"PO-Revision-Date: 2024-12-05T11:39:04.447Z\n"
"POT-Creation-Date: 2024-12-15T15:25:38.375Z\n"
"PO-Revision-Date: 2024-12-15T15:25:38.375Z\n"

msgid "Choose one or more dates..."
msgstr "Choose one or more dates..."
Expand Down Expand Up @@ -101,6 +101,24 @@ msgstr "Date of enrollment"
msgid "Last updated"
msgstr "Last updated"

msgid "Lat"
msgstr "Lat"

msgid "Long"
msgstr "Long"

msgid "lat"
msgstr "lat"

msgid "long"
msgstr "long"

msgid "Show less"
msgstr "Show less"

msgid "Show more"
msgstr "Show more"

msgid "error encountered during field validation"
msgstr "error encountered during field validation"

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// @flow
import React from 'react';
import i18n from '@dhis2/d2-i18n';

type Props = $ReadOnly<{|
latitude: number | string,
Expand All @@ -8,9 +9,10 @@ type Props = $ReadOnly<{|

const toSixDecimal = value => (parseFloat(value) ? parseFloat(value).toFixed(6) : null);

export const MinimalCoordinates = ({ latitude, longitude }: Props) =>
(<div>
lat: {toSixDecimal(latitude)} <br />
long: {toSixDecimal(longitude)}
</div>);
export const MinimalCoordinates = ({ latitude, longitude }: Props) => (
<div>
{i18n.t('Lat')}: {toSixDecimal(latitude)}<br />
{i18n.t('Long')}: {toSixDecimal(longitude)}
</div>
);

Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// @flow
import React, { useState } from 'react';
import i18n from '@dhis2/d2-i18n';
import { withStyles } from '@material-ui/core/styles';
import { IconChevronUp16, IconChevronDown16, colors, spacers } from '@dhis2/ui';

type Props = $ReadOnly<{|
coordinates: Array<Array<number>>,
classes: {
buttonContainer: string,
viewButton: string,
},
|}>;

const styles = {
buttonContainer: {
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
},
viewButton: {
background: 'none',
border: 'none',
cursor: 'pointer',
color: colors.grey800,
marginTop: spacers.dp8,
display: 'flex',
alignItems: 'center',
'&:hover': {
textDecoration: 'underline',
color: 'black',
},
},
};

const PolygonCoordinatesPlain = ({ coordinates, classes }: Props) => {
const [showMore, setShowMore] = useState(false);
return (
<>
<div>
{coordinates.slice(0, showMore ? coordinates.length : 1).map((coordinatePair, index) => (
// eslint-disable-next-line react/no-array-index-key
<div key={index}>
{`${i18n.t('lat')}: ${coordinatePair[1]}`}<br />
{`${i18n.t('long')}: ${coordinatePair[0]}`}
</div>
))}
</div>
<div className={classes.buttonContainer}>
<button className={classes.viewButton} onClick={() => setShowMore(!showMore)}>
{showMore ? i18n.t('Show less') : i18n.t('Show more')}
{showMore ? <IconChevronUp16 /> : <IconChevronDown16 />}
</button>
</div>
</>
);
};

export const PolygonCoordinates = withStyles(styles)(PolygonCoordinatesPlain);
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// @flow
export { PolygonCoordinates } from './PolygonCoordinates';
3 changes: 3 additions & 0 deletions src/core_modules/capture-core/components/Coordinates/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// @flow
export { MinimalCoordinates } from './MinimalCoordinates';
export { PolygonCoordinates } from './PolygonCoordinates';
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ function getTrackerProgram(suggestedProgramId: string) {
log.error(
errorCreator('tracker program for id not found')({ suggestedProgramId, error }),
);
throw Error(i18n('Metadata error. see log for details'));
throw Error(i18n.t('Metadata error. see log for details'));
}
return trackerProgram;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ function getTrackerProgram(suggestedProgramId: string) {
log.error(
errorCreator('tracker program for id not found')({ suggestedProgramId, error }),
);
throw Error(i18n('Metadata error. see log for details'));
throw Error(i18n.t('Metadata error. see log for details'));
}
return trackerProgram;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// @flow
import React, { useMemo } from 'react';
import i18n from '@dhis2/d2-i18n';
import type { DataElement } from '../../../metaData';
import { dataElementTypes } from '../../../metaData';
import type { Props } from './EventChangelogWrapper.types';
Expand Down Expand Up @@ -42,9 +43,19 @@ export const EventChangelogWrapper = ({ formFoundation, eventId, eventData, ...p
return acc;
}, {});

const additionalFields = formFoundation.featureType !== 'None' ? {
geometry: {
id: 'geometry',
name: formFoundation.featureType === 'Polygon' ? i18n.t('Area') : i18n.t('Coordinate'),
type: formFoundation.featureType === 'Polygon' ?
dataElementTypes.POLYGON : dataElementTypes.COORDINATE,
},
} : null;

return {
...fieldElementsById,
...fieldElementsContext,
...additionalFields,
};
}, [formFoundation]);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,15 @@ type CreatedChange = {|
type: typeof CHANGE_TYPES.CREATED,
dataElement?: string,
attribute?: string,
field?: string,
currentValue: any,
|}

type UpdatedChange = {|
type: typeof CHANGE_TYPES.UPDATED,
dataElement?: string,
attribute?: string,
field?: string,
previousValue: any,
currentValue: any,
|}
Expand All @@ -21,6 +23,7 @@ type DeletedChange = {|
type: typeof CHANGE_TYPES.DELETED,
dataElement?: string,
attribute?: string,
field?: string,
previousValue: any,
|}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,19 +23,23 @@ const styles = {
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
whiteSpace: 'normal',
height: '100%',
},
buttonContainer: {
display: 'flex',
justifyContent: 'center',
},
previousValue: {
color: colors.grey700,
wordBreak: 'break-word',
},
currentValue: {
color: colors.grey900,
wordBreak: 'break-word',
maxWidth: '82%',
},
arrow: {
margin: `0 ${spacers.dp4}`,
margin: spacers.dp4,
},
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@ const fetchFormattedValues = async ({
elementKey: string,
change: Change,
) => {
const fieldId = change.dataElement || change.attribute;
const { dataElement, attribute, field } = change;
const fieldId = dataElement ?? attribute ?? field;
if (!fieldId) {
log.error('Could not find fieldId in change:', change);
return { metadataElement: null, fieldId: null };
Expand Down Expand Up @@ -115,6 +116,7 @@ const fetchFormattedValues = async ({
reactKey: fieldId ? `${createdAt}-${fieldId}` : attributeOptionsKey,
date: pipe(convertServerToClient, convertClientToList)(fromServerDate(createdAt), dataElementTypes.DATETIME),
user: `${firstName} ${surname} (${username})`,
dataItemId: fieldId,
changeType: type,
dataItemLabel: metadataElement.name,
previousValue,
Expand Down
44 changes: 24 additions & 20 deletions src/core_modules/capture-core/converters/clientToList.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { PreviewImage } from 'capture-ui';
import { dataElementTypes, type DataElement } from '../metaData';
import { convertMomentToDateFormatString } from '../utils/converters/date';
import { stringifyNumber } from './common/stringifyNumber';
import { MinimalCoordinates } from '../components/MinimalCoordinates';
import { MinimalCoordinates, PolygonCoordinates } from '../components/Coordinates';
import { TooltipOrgUnit } from '../components/Tooltips/TooltipOrgUnit';

function convertDateForListDisplay(rawValue: string): string {
Expand Down Expand Up @@ -87,41 +87,45 @@ function convertStatusForDisplay(clientValue: Object) {
);
}

function convertOrgUnitForDisplay(clientValue: string | {id: string}) {
function convertOrgUnitForDisplay(clientValue: string | { id: string }) {
const orgUnitId = typeof clientValue === 'string' ? clientValue : clientValue.id;
return (
<TooltipOrgUnit orgUnitId={orgUnitId} />
);
}

function convertPolygonForDisplay(clientValue: Object) {
return <PolygonCoordinates coordinates={clientValue} />;
}

const valueConvertersForType = {
[dataElementTypes.NUMBER]: stringifyNumber,
[dataElementTypes.INTEGER]: stringifyNumber,
[dataElementTypes.INTEGER_POSITIVE]: stringifyNumber,
[dataElementTypes.INTEGER_ZERO_OR_POSITIVE]: stringifyNumber,
[dataElementTypes.INTEGER_NEGATIVE]: stringifyNumber,
[dataElementTypes.INTEGER_RANGE]: value => convertRangeForDisplay(stringifyNumber, value),
[dataElementTypes.INTEGER_POSITIVE_RANGE]: value => convertRangeForDisplay(stringifyNumber, value),
[dataElementTypes.INTEGER_ZERO_OR_POSITIVE_RANGE]: value => convertRangeForDisplay(stringifyNumber, value),
[dataElementTypes.INTEGER_NEGATIVE_RANGE]: value => convertRangeForDisplay(stringifyNumber, value),
[dataElementTypes.PERCENTAGE]: (value: number) => `${stringifyNumber(value)} %`,
[dataElementTypes.AGE]: convertDateForListDisplay,
[dataElementTypes.ASSIGNEE]: (rawValue: Object) => `${rawValue.name} (${rawValue.username})`,
[dataElementTypes.BOOLEAN]: (rawValue: boolean) => (rawValue ? i18n.t('Yes') : i18n.t('No')),
[dataElementTypes.COORDINATE]: MinimalCoordinates,
[dataElementTypes.DATE]: convertDateForListDisplay,
[dataElementTypes.DATE_RANGE]: value => convertRangeForDisplay(convertDateForListDisplay, value),
[dataElementTypes.DATETIME]: convertDateTimeForListDisplay,
[dataElementTypes.DATETIME_RANGE]: value => convertRangeForDisplay(convertDateTimeForListDisplay, value),
[dataElementTypes.TIME]: convertTimeForListDisplay,
[dataElementTypes.TIME_RANGE]: value => convertRangeForDisplay(convertTimeForListDisplay, value),
[dataElementTypes.TRUE_ONLY]: () => i18n.t('Yes'),
[dataElementTypes.BOOLEAN]: (rawValue: boolean) => (rawValue ? i18n.t('Yes') : i18n.t('No')),
[dataElementTypes.COORDINATE]: MinimalCoordinates,
[dataElementTypes.AGE]: convertDateForListDisplay,
[dataElementTypes.FILE_RESOURCE]: convertFileForDisplay,
[dataElementTypes.IMAGE]: convertImageForDisplay,
[dataElementTypes.ORGANISATION_UNIT]: convertOrgUnitForDisplay,
[dataElementTypes.ASSIGNEE]: (rawValue: Object) => `${rawValue.name} (${rawValue.username})`,
[dataElementTypes.INTEGER]: stringifyNumber,
[dataElementTypes.INTEGER_NEGATIVE]: stringifyNumber,
[dataElementTypes.INTEGER_NEGATIVE_RANGE]: value => convertRangeForDisplay(stringifyNumber, value),
[dataElementTypes.INTEGER_POSITIVE]: stringifyNumber,
[dataElementTypes.INTEGER_POSITIVE_RANGE]: value => convertRangeForDisplay(stringifyNumber, value),
[dataElementTypes.INTEGER_RANGE]: value => convertRangeForDisplay(stringifyNumber, value),
[dataElementTypes.INTEGER_ZERO_OR_POSITIVE]: stringifyNumber,
[dataElementTypes.INTEGER_ZERO_OR_POSITIVE_RANGE]: value => convertRangeForDisplay(stringifyNumber, value),
[dataElementTypes.NUMBER]: stringifyNumber,
[dataElementTypes.NUMBER_RANGE]: convertNumberRangeForDisplay,
[dataElementTypes.ORGANISATION_UNIT]: convertOrgUnitForDisplay,
[dataElementTypes.PERCENTAGE]: (value: number) => `${stringifyNumber(value)} %`,
[dataElementTypes.POLYGON]: convertPolygonForDisplay,
[dataElementTypes.STATUS]: convertStatusForDisplay,
[dataElementTypes.TIME]: convertTimeForListDisplay,
[dataElementTypes.TIME_RANGE]: value => convertRangeForDisplay(convertTimeForListDisplay, value),
[dataElementTypes.TRUE_ONLY]: () => i18n.t('Yes'),
};

export function convertValue(value: any, type: $Keys<typeof dataElementTypes>, dataElement?: ?DataElement) {
Expand Down
2 changes: 1 addition & 1 deletion src/core_modules/capture-core/converters/clientToView.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { PreviewImage } from 'capture-ui';
import { dataElementTypes, type DataElement } from '../metaData';
import { convertMomentToDateFormatString } from '../utils/converters/date';
import { stringifyNumber } from './common/stringifyNumber';
import { MinimalCoordinates } from '../components/MinimalCoordinates';
import { MinimalCoordinates } from '../components/Coordinates';
import { TooltipOrgUnit } from '../components/Tooltips/TooltipOrgUnit';


Expand Down
25 changes: 20 additions & 5 deletions src/core_modules/capture-core/converters/serverToClient.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,24 @@ export function convertOptionSetValue(value: any, type: $Keys<typeof dataElement
return optionSetConvertersForType[type] ? optionSetConvertersForType[type](value) : value;
}

function convertCoordinateToClient(value: any) {
const coordinates = typeof value === 'string' ?
value.replace(/[\(\)\[\]\s]/g, '').split(',').map(Number) : value;

return { latitude: coordinates[1], longitude: coordinates[0] };
}

function convertPolygonToClient(value: any) {
if (typeof value === 'string') {
const coordinates = value.replace(/[()]/g, '').split(',').map(Number);
const coordinatesArray = [];
for (let i = 0; i < coordinates.length; i += 2) {
coordinatesArray.push([coordinates[i], coordinates[i + 1]]);
}
return coordinatesArray;
}
return value;
}

const valueConvertersForType = {
[dataElementTypes.NUMBER]: parseNumber,
Expand All @@ -55,11 +73,8 @@ const valueConvertersForType = {
[dataElementTypes.DATETIME]: (d2Value: string) => moment(d2Value).toISOString(),
[dataElementTypes.TRUE_ONLY]: (d2Value: string) => ((d2Value === 'true') || null),
[dataElementTypes.BOOLEAN]: (d2Value: string) => (d2Value === 'true'),
[dataElementTypes.COORDINATE]: (d2Value: string | Array<string>) => {
const arr = typeof d2Value === 'string' ? JSON.parse(d2Value) : d2Value;
return { latitude: arr[1], longitude: arr[0] };
},
[dataElementTypes.POLYGON]: (d2Value: Array<number>) => d2Value,
[dataElementTypes.COORDINATE]: (d2Value: string | Array<string>) => convertCoordinateToClient(d2Value),
[dataElementTypes.POLYGON]: (d2Value: string | Array<Array<number>>) => convertPolygonToClient(d2Value),
[dataElementTypes.ASSIGNEE]: convertAssignedUserToClient,
};

Expand Down

0 comments on commit f670971

Please sign in to comment.