From 1da788709bb0989df4763868c9e2f25de211c05a Mon Sep 17 00:00:00 2001 From: Thomas Zemp Date: Fri, 18 Oct 2024 15:49:23 +0200 Subject: [PATCH] fix: display date values in calendar in audits, clean up --- .../data-details-sidebar/audit-log.js | 30 +++- .../data-details-sidebar/audit-log.test.js | 159 +++++++++++++++++- src/data-workspace/inputs/date-input.test.js | 8 +- src/shared/date/date-utils.js | 10 ++ src/shared/date/index.js | 2 +- 5 files changed, 199 insertions(+), 10 deletions(-) diff --git a/src/data-workspace/data-details-sidebar/audit-log.js b/src/data-workspace/data-details-sidebar/audit-log.js index 596e7de13..0762f7773 100644 --- a/src/data-workspace/data-details-sidebar/audit-log.js +++ b/src/data-workspace/data-details-sidebar/audit-log.js @@ -1,3 +1,4 @@ +import { useConfig } from '@dhis2/app-runtime' import i18n from '@dhis2/d2-i18n' import { CircularLoader, @@ -17,6 +18,8 @@ import { ExpandableUnit, useConnectionStatus, DateText, + convertFromIso8601ToString, + VALUE_TYPES, } from '../../shared/index.js' import styles from './audit-log.module.css' import useDataValueContext from './use-data-value-context.js' @@ -29,6 +32,8 @@ export default function AuditLog({ item }) { const { open, setOpen, openRef } = useOpenState(item) const dataValueContext = useDataValueContext(item, openRef.current) const timeZone = Intl.DateTimeFormat()?.resolvedOptions()?.timeZone + const { systemInfo = {} } = useConfig() + const { calendar = 'gregory' } = systemInfo if (!offline && (!open || dataValueContext.isLoading)) { return ( @@ -98,8 +103,8 @@ export default function AuditLog({ item }) { {audits.map((audit) => { const { modifiedBy: user, - previousValue, - value, + previousValue: nonParsedPreviousValue, + value: nonParsedValue, created, auditType, dataElement: de, @@ -107,6 +112,26 @@ export default function AuditLog({ item }) { period: pe, orgUnit: ou, } = audit + + const value = [ + VALUE_TYPES.DATETIME, + VALUE_TYPES.DATE, + ].includes(item.valueType) + ? convertFromIso8601ToString( + nonParsedValue, + calendar + ) + : nonParsedValue + const previousValue = [ + VALUE_TYPES.DATETIME, + VALUE_TYPES.DATE, + ].includes(item.valueType) + ? convertFromIso8601ToString( + nonParsedPreviousValue, + calendar + ) + : nonParsedPreviousValue + const key = `${de}-${pe}-${ou}-${coc}-${created}` return ( @@ -160,6 +185,7 @@ AuditLog.propTypes = { categoryOptionCombo: PropTypes.string.isRequired, dataElement: PropTypes.string.isRequired, comment: PropTypes.string, + valueType: PropTypes.string, }).isRequired, } diff --git a/src/data-workspace/data-details-sidebar/audit-log.test.js b/src/data-workspace/data-details-sidebar/audit-log.test.js index 23579cce1..5275efd60 100644 --- a/src/data-workspace/data-details-sidebar/audit-log.test.js +++ b/src/data-workspace/data-details-sidebar/audit-log.test.js @@ -1,3 +1,4 @@ +import { useConfig } from '@dhis2/app-runtime' import { waitFor, within } from '@testing-library/react' import userEvent from '@testing-library/user-event' import React from 'react' @@ -5,6 +6,13 @@ import { render } from '../../test-utils/index.js' import AuditLog from './audit-log.js' import useDataValueContext from './use-data-value-context.js' +jest.mock('@dhis2/app-runtime', () => ({ + ...jest.requireActual('@dhis2/app-runtime'), + useConfig: jest.fn(() => ({ + systemInfo: { serverTimeZoneId: 'Etc/UTC', calendar: 'gregory' }, + })), +})) + jest.mock('./use-data-value-context.js', () => ({ __esModule: true, default: jest.fn(), @@ -18,6 +26,7 @@ describe('', () => { } afterEach(() => { + jest.clearAllMocks() useDataValueContext.mockClear() }) @@ -79,7 +88,7 @@ describe('', () => { auditType: 'UPDATE', created: new Date('2021-02-01').toISOString(), modifiedBy: 'Firstname2 Lastname2', - prevValue: '19', + previousValue: '19', value: '21', }, { @@ -140,4 +149,152 @@ describe('', () => { getByText('audit dates are given in UTC time') ).toBeInTheDocument() }) + + it('renders the date/datetime values in system calendar (ethiopian)', async () => { + useConfig.mockImplementation(() => ({ + systemInfo: { + calendar: 'ethiopian', + }, + })) + const audits = [ + { + auditType: 'DELETE', + created: new Date('2021-03-01').toISOString(), + modifiedBy: 'Firstname Lastname', + value: '2021-09-10', + }, + { + auditType: 'UPDATE', + created: new Date('2021-02-01').toISOString(), + modifiedBy: 'Firstname2 Lastname2', + previousValue: '2021-09-10', + value: '2021-09-11', + }, + ] + + const dateItem = { + categoryOptionCombo: 'coc-id', + dataElement: 'de-id', + comment: 'This is a comment', + valueType: 'DATE', + } + + const data = { audits } + useDataValueContext.mockImplementation(() => ({ + isLoading: false, + data, + })) + + const { getByRole, getAllByRole, queryByRole, container } = render( + + ) + + // Expand and wait for data to load + userEvent.click(container.querySelector('summary')) + await waitFor(() => { + expect(getByRole('group')).toHaveAttribute('open') + expect(queryByRole('progressbar')).not.toBeInTheDocument() + }) + + // the number of rows is: the length of audits + 1 (for header row) + const auditRows = getAllByRole('row') + + expect(auditRows).toHaveLength(audits.length + 1) + + const firstChangeName = within(auditRows[1]).getByText( + 'Firstname Lastname', + {} + ) + expect(firstChangeName).toBeInTheDocument() + const firstChangeValue = within(auditRows[1]).getByText( + '2013-13-05', + {} + ) + expect(firstChangeValue).toBeInTheDocument() + + const secondChangeName = within(auditRows[2]).getByText( + 'Firstname2 Lastname2', + {} + ) + expect(secondChangeName).toBeInTheDocument() + const secondChangeValue = within(auditRows[2]).getByText( + '2014-01-01', + {} + ) + expect(secondChangeValue).toBeInTheDocument() + }) + + it('renders the date/datetime values in system calendar (gregorian)', async () => { + useConfig.mockImplementation(() => ({ + systemInfo: { + calendar: 'gregory', + }, + })) + const audits = [ + { + auditType: 'DELETE', + created: new Date('2021-03-01').toISOString(), + modifiedBy: 'Firstname Lastname', + value: '2021-09-10', + }, + { + auditType: 'UPDATE', + created: new Date('2021-02-01').toISOString(), + modifiedBy: 'Firstname2 Lastname2', + previousValue: '2021-09-10', + value: '2021-09-11', + }, + ] + + const dateItem = { + categoryOptionCombo: 'coc-id', + dataElement: 'de-id', + comment: 'This is a comment', + valueType: 'DATE', + } + + const data = { audits } + useDataValueContext.mockImplementation(() => ({ + isLoading: false, + data, + })) + + const { getByRole, getAllByRole, queryByRole, container } = render( + + ) + + // Expand and wait for data to load + userEvent.click(container.querySelector('summary')) + await waitFor(() => { + expect(getByRole('group')).toHaveAttribute('open') + expect(queryByRole('progressbar')).not.toBeInTheDocument() + }) + + // the number of rows is: the length of audits + 1 (for header row) + const auditRows = getAllByRole('row') + + expect(auditRows).toHaveLength(audits.length + 1) + + const firstChangeName = within(auditRows[1]).getByText( + 'Firstname Lastname', + {} + ) + expect(firstChangeName).toBeInTheDocument() + const firstChangeValue = within(auditRows[1]).getByText( + '2021-09-10', + {} + ) + expect(firstChangeValue).toBeInTheDocument() + + const secondChangeName = within(auditRows[2]).getByText( + 'Firstname2 Lastname2', + {} + ) + expect(secondChangeName).toBeInTheDocument() + const secondChangeValue = within(auditRows[2]).getByText( + '2021-09-11', + {} + ) + expect(secondChangeValue).toBeInTheDocument() + }) }) diff --git a/src/data-workspace/inputs/date-input.test.js b/src/data-workspace/inputs/date-input.test.js index 5b4b965e6..27905501e 100644 --- a/src/data-workspace/inputs/date-input.test.js +++ b/src/data-workspace/inputs/date-input.test.js @@ -224,9 +224,7 @@ describe('date input field', () => { // 2021-04-22 ISO = 2078-01-09 nepali const { getByRole } = render( - + ) @@ -269,9 +267,7 @@ describe('date input field', () => { // 2021-04-22 ISO = 2013-08-14 ethiopian const { getByRole } = render( - + ) diff --git a/src/shared/date/date-utils.js b/src/shared/date/date-utils.js index 7eecc8ff1..42650e87b 100644 --- a/src/shared/date/date-utils.js +++ b/src/shared/date/date-utils.js @@ -34,6 +34,11 @@ const formatDate = (date, withoutTimeStamp) => { } export const convertFromIso8601ToString = (date, calendar) => { + // skip if there is no date + if (!date) { + return undefined + } + // return without conversion if already a gregory date if (GREGORY_CALENDARS.includes(calendar)) { return date @@ -55,6 +60,11 @@ export const convertFromIso8601ToString = (date, calendar) => { } export const convertToIso8601ToString = (date, calendar) => { + // skip if there is no date + if (!date) { + return undefined + } + // return without conversion if already a gregory date if (GREGORY_CALENDARS.includes(calendar)) { return date diff --git a/src/shared/date/index.js b/src/shared/date/index.js index 8523b9417..ae33c594d 100644 --- a/src/shared/date/index.js +++ b/src/shared/date/index.js @@ -8,4 +8,4 @@ export { isDateAGreaterThanDateB, isDateALessThanDateB, } from './date-utils.js' -export { DateText } from './date-text.js' \ No newline at end of file +export { DateText } from './date-text.js'