From fa54ee4fc5657094ee5b240d3f5d936e42cc6c09 Mon Sep 17 00:00:00 2001 From: eirikhaugstulen Date: Wed, 6 Mar 2024 04:33:02 +0700 Subject: [PATCH] feat: [DHIS2-16764][DHIS2-16759][DHIS2-16781] Changelog (#3540) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Minor-contributions-by: Joakim Storløkken Melseth --- .../WidgetChangelog/index.js | 46 +++++++ .../WidgetsForEnrollmentEditEvent.feature | 43 ++++-- .../WidgetsForEnrollmentEditEvent/index.js | 1 + i18n/en.pot | 50 ++++++- .../featuresSupport/support.js | 2 + .../Buttons/OverflowButton.component.js | 3 - .../EnrollmentEditEventPage.component.js | 2 + .../EnrollmentEditEventPage.container.js | 11 ++ .../EnrollmentEditEventPage.types.js | 1 + .../EventDetailsSection.component.js | 73 ++++++++++- .../EventDetailsSection.container.js | 1 + .../LayoutComponentConfig.js | 2 + .../EditEventDataEntry.container.js | 2 + .../EventChangelogWrapper.component.js | 58 ++++++++ .../EventChangelogWrapper.types.js | 13 ++ .../EventChangelogWrapper/index.js | 3 + .../WidgetEventEdit.container.js | 67 +++++++++- .../WidgetEventEdit/widgetEventEdit.types.js | 1 + .../OverflowMenu/OverflowMenu.component.js | 53 ++++---- .../OverflowMenu/OverflowMenu.container.js | 17 ++- .../OverflowMenu/OverflowMenu.types.js | 7 +- ...TrackedEntityChangelogWrapper.component.js | 63 +++++++++ .../TrackedEntityChangelogWrapper.types.js | 13 ++ .../TrackedEntityChangelogWrapper/index.js | 3 + .../WidgetProfile/WidgetProfile.component.js | 18 ++- .../WidgetProfile/hooks/useProgram.js | 16 ++- .../WidgetEventChangelog.js | 24 ++++ .../WidgetEventChangelog/index.js | 3 + .../WidgetTrackedEntityChangelog.js | 27 ++++ .../WidgetTrackedEntityChangelog/index.js | 2 + .../common/Changelog/Changelog.component.js | 94 +++++++++++++ .../common/Changelog/Changelog.constants.js | 17 +++ .../common/Changelog/Changelog.container.js | 62 +++++++++ .../common/Changelog/Changelog.types.js | 70 ++++++++++ .../common/Changelog/index.js | 3 + .../ChangelogTable/ChangelogChangeCell.js | 81 ++++++++++++ .../ChangelogTable/ChangelogTableHeader.js | 35 +++++ .../ChangelogTable/ChangelogTableRow.js | 29 ++++ .../common/ChangelogTable/index.js | 6 + .../WidgetsChangelog/common/hooks/index.js | 3 + .../common/hooks/useChangelogData.js | 124 ++++++++++++++++++ .../components/WidgetsChangelog/index.js | 4 + .../capture-core/converters/clientToList.js | 35 +++++ .../RenderFoundation/RenderFoundation.js | 4 + .../query/useApiDataQuery.js | 2 +- yarn.lock | 22 ++-- 46 files changed, 1145 insertions(+), 71 deletions(-) create mode 100644 cypress/e2e/WidgetsForEnrollmentPages/WidgetChangelog/index.js create mode 100644 src/core_modules/capture-core/components/WidgetEventEdit/EventChangelogWrapper/EventChangelogWrapper.component.js create mode 100644 src/core_modules/capture-core/components/WidgetEventEdit/EventChangelogWrapper/EventChangelogWrapper.types.js create mode 100644 src/core_modules/capture-core/components/WidgetEventEdit/EventChangelogWrapper/index.js create mode 100644 src/core_modules/capture-core/components/WidgetProfile/OverflowMenu/TrackedEntityChangelogWrapper/TrackedEntityChangelogWrapper.component.js create mode 100644 src/core_modules/capture-core/components/WidgetProfile/OverflowMenu/TrackedEntityChangelogWrapper/TrackedEntityChangelogWrapper.types.js create mode 100644 src/core_modules/capture-core/components/WidgetProfile/OverflowMenu/TrackedEntityChangelogWrapper/index.js create mode 100644 src/core_modules/capture-core/components/WidgetsChangelog/WidgetEventChangelog/WidgetEventChangelog.js create mode 100644 src/core_modules/capture-core/components/WidgetsChangelog/WidgetEventChangelog/index.js create mode 100644 src/core_modules/capture-core/components/WidgetsChangelog/WidgetTrackedEntityChangelog/WidgetTrackedEntityChangelog.js create mode 100644 src/core_modules/capture-core/components/WidgetsChangelog/WidgetTrackedEntityChangelog/index.js create mode 100644 src/core_modules/capture-core/components/WidgetsChangelog/common/Changelog/Changelog.component.js create mode 100644 src/core_modules/capture-core/components/WidgetsChangelog/common/Changelog/Changelog.constants.js create mode 100644 src/core_modules/capture-core/components/WidgetsChangelog/common/Changelog/Changelog.container.js create mode 100644 src/core_modules/capture-core/components/WidgetsChangelog/common/Changelog/Changelog.types.js create mode 100644 src/core_modules/capture-core/components/WidgetsChangelog/common/Changelog/index.js create mode 100644 src/core_modules/capture-core/components/WidgetsChangelog/common/ChangelogTable/ChangelogChangeCell.js create mode 100644 src/core_modules/capture-core/components/WidgetsChangelog/common/ChangelogTable/ChangelogTableHeader.js create mode 100644 src/core_modules/capture-core/components/WidgetsChangelog/common/ChangelogTable/ChangelogTableRow.js create mode 100644 src/core_modules/capture-core/components/WidgetsChangelog/common/ChangelogTable/index.js create mode 100644 src/core_modules/capture-core/components/WidgetsChangelog/common/hooks/index.js create mode 100644 src/core_modules/capture-core/components/WidgetsChangelog/common/hooks/useChangelogData.js create mode 100644 src/core_modules/capture-core/components/WidgetsChangelog/index.js diff --git a/cypress/e2e/WidgetsForEnrollmentPages/WidgetChangelog/index.js b/cypress/e2e/WidgetsForEnrollmentPages/WidgetChangelog/index.js new file mode 100644 index 0000000000..90d7e62767 --- /dev/null +++ b/cypress/e2e/WidgetsForEnrollmentPages/WidgetChangelog/index.js @@ -0,0 +1,46 @@ +import { Given, Then, When } from '@badeball/cypress-cucumber-preprocessor'; + +Given('you select view changelog in the event overflow button', () => { + cy.get('[data-test="widget-event-edit-overflow-button"]') + .click(); + + cy.get('[data-test="event-overflow-view-changelog"] > a') + .click({ force: true }); +}); + +Then('the changelog modal should be visible', () => { + cy.get('[data-test="changelog-modal"]') + .should('be.visible'); +}); + +Then(/^the number of changelog table rows should be (.*)$/, (numberOfRows) => { + cy.get('[data-test="changelog-data-table-body"]') + .within(() => { + cy.get('tr') + .should('have.length', numberOfRows); + }); +}); + +When(/^you change the page size to (.*)$/, (pageSize) => { + cy.get('[data-test="changelog-pagination-pagesize-select"]') + .click(); + + cy.get('[data-test="dhis2-uicore-select-menu-menuwrapper"]') + .contains(pageSize) + .click(); +}); + +Then('the changelog modal should contain data', () => { + cy.get('[data-test="changelog-data-table-body"]') + .should('be.visible'); +}); + +When('you move to the next page', () => { + cy.get('[data-test="changelog-pagination-page-next"]') + .click(); +}); + +Then('the table footer should display page 2', () => { + cy.get('[data-test="changelog-pagination-summary"]') + .contains('Page 2'); +}); diff --git a/cypress/e2e/WidgetsForEnrollmentPages/WidgetsForEnrollmentEditEvent.feature b/cypress/e2e/WidgetsForEnrollmentPages/WidgetsForEnrollmentEditEvent.feature index 5e2c3c33f5..b1702b8ffc 100644 --- a/cypress/e2e/WidgetsForEnrollmentPages/WidgetsForEnrollmentEditEvent.feature +++ b/cypress/e2e/WidgetsForEnrollmentPages/WidgetsForEnrollmentEditEvent.feature @@ -112,18 +112,43 @@ Feature: The user interacts with the widgets on the enrollment edit event Then list should contain the new comment: new test comment Scenario: You can assign a user to a event - Given you land on the enrollment edit event page by having typed /#/enrollmentEventEdit?eventId=veuwiLC2x0e&orgUnitId=g8upMTyEZGZ - When you assign the user Geetha in the view mode - Then the event has the user Geetha Alwan assigned - When you assign the user Tracker demo User in the edit mode - Then the event has the user Tracker demo User assigned - When you remove the assigned user - Then the event has no assignd user - - Scenario: User can complete the enrollment and the active events + Given you land on the enrollment edit event page by having typed /#/enrollmentEventEdit?eventId=veuwiLC2x0e&orgUnitId=g8upMTyEZGZ + When you assign the user Geetha in the view mode + Then the event has the user Geetha Alwan assigned + When you assign the user Tracker demo User in the edit mode + Then the event has the user Tracker demo User assigned + When you remove the assigned user + Then the event has no assignd user + + @v>=41 + Scenario: The user can view an event changelog on the enrollment edit event + Given you land on the enrollment edit event page by having typed /#/enrollmentEventEdit?eventId=QsAhMiZtnl2&orgUnitId=DiszpKrYNg8 + When you select view changelog in the event overflow button + Then the changelog modal should be visible + And the changelog modal should contain data + # One row is filtered out as the metadata is no longer there + And the number of changelog table rows should be 9 + + @v>=41 + Scenario: The user can change changelog page size + Given you land on the enrollment edit event page by having typed /#/enrollmentEventEdit?eventId=QsAhMiZtnl2&orgUnitId=DiszpKrYNg8 + When you select view changelog in the event overflow button + And you change the page size to 20 + # One row is filtered out as the metadata is no longer there + Then the number of changelog table rows should be 19 + + @v>=41 + Scenario: The user can move to the next page in the changelog + Given you land on the enrollment edit event page by having typed /#/enrollmentEventEdit?eventId=QsAhMiZtnl2&orgUnitId=DiszpKrYNg8 + When you select view changelog in the event overflow button + And you move to the next page + Then the table footer should display page 2 + + Scenario: User can complete the enrollment and the active events Given you land on the enrollment edit event page by having typed #/enrollmentEventEdit?eventId=OWpIzQ4xabC&orgUnitId=DiszpKrYNg8 And the enrollment widget should be opened And the user sees the enrollment status and the Baby Postnatal event status is active And the user opens the enrollment actions menu When the user completes the enrollment and the active events Then the user sees the enrollment status and the Baby Postnatal event status is completed + diff --git a/cypress/e2e/WidgetsForEnrollmentPages/WidgetsForEnrollmentEditEvent/index.js b/cypress/e2e/WidgetsForEnrollmentPages/WidgetsForEnrollmentEditEvent/index.js index ff80131398..a87c088484 100644 --- a/cypress/e2e/WidgetsForEnrollmentPages/WidgetsForEnrollmentEditEvent/index.js +++ b/cypress/e2e/WidgetsForEnrollmentPages/WidgetsForEnrollmentEditEvent/index.js @@ -3,4 +3,5 @@ import '../WidgetEnrollment'; import '../WidgetProfile'; import '../WidgetEventComment'; import '../WidgetAssignee'; +import '../WidgetChangelog'; diff --git a/i18n/en.pot b/i18n/en.pot index dfcf6a5bf2..d3f459463c 100644 --- a/i18n/en.pot +++ b/i18n/en.pot @@ -320,6 +320,11 @@ msgstr "{{programStageName}} completed" msgid "Would you like to complete the enrollment and all active events as well?" msgstr "Would you like to complete the enrollment and all active events as well?" +msgid "{{count}} event in {{programStageName}}" +msgid_plural "{{count}} event in {{programStageName}}" +msgstr[0] "{{count}} event in {{programStageName}}" +msgstr[1] "{{count}} events in {{programStageName}}" + msgid "Yes, complete enrollment and events" msgstr "Yes, complete enrollment and events" @@ -907,6 +912,9 @@ msgstr "You don't have access to edit this event" msgid "Edit event" msgstr "Edit event" +msgid "View changelog" +msgstr "View changelog" + msgid "Event details" msgstr "Event details" @@ -953,6 +961,11 @@ msgstr "Search {{uniqueAttrName}}" msgid "Search by attributes" msgstr "Search by attributes" +msgid "Fill in at least {{count}} attribute to search" +msgid_plural "Fill in at least {{count}} attribute to search" +msgstr[0] "Fill in at least {{count}} attribute to search" +msgstr[1] "Fill in at least {{count}} attributes to search" + msgid "Could not retrieve metadata. Please try again later." msgstr "Could not retrieve metadata. Please try again later." @@ -1338,12 +1351,22 @@ msgstr "Scheduled automatically for {{suggestedScheduleDate}}" msgid "The scheduled date matches the suggested date, but can be changed if needed." msgstr "The scheduled date matches the suggested date, but can be changed if needed." +msgid "The scheduled date is {{count}} days {{position}} the suggested date." +msgid_plural "The scheduled date is {{count}} days {{position}} the suggested date." +msgstr[0] "The scheduled date is {{count}} day {{position}} the suggested date." +msgstr[1] "The scheduled date is {{count}} days {{position}} the suggested date." + msgid "after" msgstr "after" msgid "before" msgstr "before" +msgid "There are {{count}} scheduled event in {{orgUnitName}} on this day." +msgid_plural "There are {{count}} scheduled event in {{orgUnitName}} on this day." +msgstr[0] "There are {{count}} scheduled event in {{orgUnitName}} on this day." +msgstr[1] "There are {{count}} scheduled events in {{orgUnitName}} on this day." + msgid "Scheduling an event in {{stageName}} for {{programName}} in {{orgUnitName}}" msgstr "Scheduling an event in {{stageName}} for {{programName}} in {{orgUnitName}}" @@ -1458,6 +1481,30 @@ msgstr "{{ scheduledEvents }} scheduled" msgid "Stages and Events" msgstr "Stages and Events" +msgid "Changelog" +msgstr "Changelog" + +msgid "No changes to display" +msgstr "No changes to display" + +msgid "Created" +msgstr "Created" + +msgid "Deleted" +msgstr "Deleted" + +msgid "Date" +msgstr "Date" + +msgid "User" +msgstr "User" + +msgid "Data item" +msgstr "Data item" + +msgid "Change" +msgstr "Change" + msgid "New {{trackedEntityTypeName}} relationship" msgstr "New {{trackedEntityTypeName}} relationship" @@ -1663,9 +1710,6 @@ msgstr "Error updating the Assignee" msgid "Set coordinate" msgstr "Set coordinate" -msgid "Date" -msgstr "Date" - msgid "Time" msgstr "Time" diff --git a/src/core_modules/capture-core-utils/featuresSupport/support.js b/src/core_modules/capture-core-utils/featuresSupport/support.js index 7ec91f9f14..c1937264de 100644 --- a/src/core_modules/capture-core-utils/featuresSupport/support.js +++ b/src/core_modules/capture-core-utils/featuresSupport/support.js @@ -6,6 +6,7 @@ export const FEATURES = Object.freeze({ customIcons: 'customIcons', newTransferQueryParam: 'newTransferQueryParam', exportablePayload: 'exportablePayload', + changelogs: 'changelogs', trackerImageEndpoint: 'trackerImageEndpoint', }); @@ -18,6 +19,7 @@ const MINOR_VERSION_SUPPORT = Object.freeze({ [FEATURES.exportablePayload]: 41, [FEATURES.trackerImageEndpoint]: 41, [FEATURES.newTransferQueryParam]: 41, + [FEATURES.changelogs]: 41, }); export const hasAPISupportForFeature = (minorVersion: string | number, featureName: string) => diff --git a/src/core_modules/capture-core/components/Buttons/OverflowButton.component.js b/src/core_modules/capture-core/components/Buttons/OverflowButton.component.js index b0d1585df2..476e950611 100644 --- a/src/core_modules/capture-core/components/Buttons/OverflowButton.component.js +++ b/src/core_modules/capture-core/components/Buttons/OverflowButton.component.js @@ -14,7 +14,6 @@ type Props = { dataTest?: string, small?: boolean, large?: boolean, - className: string, }; export const OverflowButton = ({ @@ -28,7 +27,6 @@ export const OverflowButton = ({ icon, dataTest, component, - className, }: Props) => { const [isOpen, setIsOpen] = useState(false); const anchorRef = useRef(null); @@ -51,7 +49,6 @@ export const OverflowButton = ({ large={large} onClick={toggle} icon={icon} - className={className} > {label} diff --git a/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/EnrollmentEditEventPage.component.js b/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/EnrollmentEditEventPage.component.js index 9d79b05577..f2e0b1dfe5 100644 --- a/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/EnrollmentEditEventPage.component.js +++ b/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/EnrollmentEditEventPage.component.js @@ -46,6 +46,7 @@ export const EnrollmentEditEventPageComponent = ({ onSaveAndCompleteEnrollment, onCancelEditEvent, onHandleScheduleSave, + onSaveExternal, getAssignedUserSaveContext, onSaveAssignee, onSaveAssigneeError, @@ -70,6 +71,7 @@ export const EnrollmentEditEventPageComponent = ({ pageLayout={pageLayout} currentPage={mode === EnrollmentPageKeys.EDIT_EVENT ? EnrollmentPageKeys.EDIT_EVENT : EnrollmentPageKeys.VIEW_EVENT} availableWidgets={WidgetsForEnrollmentEventEdit} + onSaveExternal={onSaveExternal} trackedEntityTypeId={trackedEntityTypeId} programStage={programStage} onGoBack={onGoBack} diff --git a/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/EnrollmentEditEventPage.container.js b/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/EnrollmentEditEventPage.container.js index d6271a045b..4142500c30 100644 --- a/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/EnrollmentEditEventPage.container.js +++ b/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/EnrollmentEditEventPage.container.js @@ -1,5 +1,6 @@ // @flow import React, { useEffect, useCallback } from 'react'; +import { useQueryClient } from 'react-query'; import { useDispatch, useSelector } from 'react-redux'; import { useHistory } from 'react-router-dom'; import { dataEntryIds } from 'capture-core/constants'; @@ -40,6 +41,8 @@ import { DefaultPageLayout } from './PageLayout/DefaultPageLayout.constants'; import { getProgramEventAccess } from '../../../metaData'; import { setAssignee, rollbackAssignee } from './EnrollmentEditEventPage.actions'; import { convertClientToServer } from '../../../converters'; +import { CHANGELOG_ENTITY_TYPES } from '../../WidgetsChangelog'; +import { ReactQueryAppNamespace } from '../../../utils/reactQueryHelpers'; import { statusTypes } from '../../../enrollment'; const getEventDate = (event) => { @@ -114,6 +117,7 @@ const EnrollmentEditEventPageWithContextPlain = ({ }: Props) => { const history = useHistory(); const dispatch = useDispatch(); + const queryClient = useQueryClient(); const { pageLayout, isLoading } = useEnrollmentPageLayout({ selectedScopeId: programId, dataStoreKey: DataStoreKeyByPage.ENROLLMENT_EVENT_EDIT, @@ -181,6 +185,12 @@ const EnrollmentEditEventPageWithContextPlain = ({ dispatch(updateEnrollmentEvent(eventId, eventData)); history.push(`enrollment?${buildUrlQueryString({ enrollmentId })}`); }; + + const onSaveExternal = () => { + const queryKey = [ReactQueryAppNamespace, 'changelog', CHANGELOG_ENTITY_TYPES.EVENT, eventId]; + queryClient.removeQueries(queryKey); + }; + const { teiDisplayName } = useTeiDisplayName(teiId, programId); // $FlowFixMe const { name: trackedEntityName, id: trackedEntityTypeId } = program?.trackedEntityType; @@ -259,6 +269,7 @@ const EnrollmentEditEventPageWithContextPlain = ({ scheduleDate={scheduleDate} onCancelEditEvent={onCancelEditEvent} onHandleScheduleSave={onHandleScheduleSave} + onSaveExternal={onSaveExternal} getAssignedUserSaveContext={getAssignedUserSaveContext} onSaveAssignee={onSaveAssignee} onSaveAssigneeError={onSaveAssigneeError} diff --git a/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/EnrollmentEditEventPage.types.js b/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/EnrollmentEditEventPage.types.js index 8dddec181a..ced8721f8f 100644 --- a/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/EnrollmentEditEventPage.types.js +++ b/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/EnrollmentEditEventPage.types.js @@ -37,6 +37,7 @@ export type PlainProps = {| onSaveAndCompleteEnrollment: (enrollment: Object) => void, onCancelEditEvent: (isScheduled: boolean) => void, onHandleScheduleSave: (eventData: Object) => void, + onSaveExternal: () => void, onAccessLostFromTransfer?: () => void, pageStatus: string, eventStatus?: string, diff --git a/src/core_modules/capture-core/components/Pages/ViewEvent/EventDetailsSection/EventDetailsSection.component.js b/src/core_modules/capture-core/components/Pages/ViewEvent/EventDetailsSection/EventDetailsSection.component.js index 7f6a2a440f..04b053bf3b 100644 --- a/src/core_modules/capture-core/components/Pages/ViewEvent/EventDetailsSection/EventDetailsSection.component.js +++ b/src/core_modules/capture-core/components/Pages/ViewEvent/EventDetailsSection/EventDetailsSection.component.js @@ -1,9 +1,10 @@ // @flow -import React from 'react'; +import React, { useState } from 'react'; import { useSelector } from 'react-redux'; import { dataEntryIds, dataEntryKeys } from 'capture-core/constants'; import { withStyles } from '@material-ui/core/'; -import { spacers, IconFileDocument24, Button } from '@dhis2/ui'; +import { spacers, IconFileDocument24, Button, IconMore16, FlyoutMenu, MenuItem } from '@dhis2/ui'; +import { useQueryClient } from 'react-query'; import i18n from '@dhis2/d2-i18n'; import { ConditionalTooltip } from 'capture-core/components/Tooltips/ConditionalTooltip'; import { ViewEventSection } from '../Section/ViewEventSection.component'; @@ -13,21 +14,34 @@ import { ViewEventDataEntry } from '../../../WidgetEventEdit/ViewEventDataEntry/ import type { ProgramStage } from '../../../../metaData'; import { useCoreOrgUnit } from '../../../../metadataRetrieval/coreOrgUnit'; import { NoticeBox } from '../../../NoticeBox'; +import { FEATURES, useFeature } from '../../../../../capture-core-utils'; +import { EventChangelogWrapper } from '../../../WidgetEventEdit/EventChangelogWrapper'; +import { OverflowButton } from '../../../Buttons'; +import { ReactQueryAppNamespace } from '../../../../utils/reactQueryHelpers'; +import { CHANGELOG_ENTITY_TYPES } from '../../../WidgetsChangelog'; const getStyles = () => ({ container: { flexGrow: 2, flexBasis: 0, }, - content: { + dataEntryContent: { display: 'flex', gap: spacers.dp8, }, dataEntryContainer: { flexGrow: 1, }, + headerContainer: { + display: 'flex', + width: '100%', + alignItems: 'center', + justifyContent: 'space-between', + }, actionsContainer: { flexShrink: 0, + display: 'flex', + gap: spacers.dp4, }, button: { whiteSpace: 'nowrap', @@ -38,12 +52,14 @@ const getStyles = () => ({ type Props = { showEditEvent: ?boolean, + eventId: string, onOpenEditEvent: (orgUnit: Object) => void, programStage: ProgramStage, eventAccess: { read: boolean, write: boolean }, classes: { container: string, - content: string, + headerContainer: string, + dataEntryContent: string, dataEntryContainer: string, actionsContainer: string, button: string, @@ -54,6 +70,7 @@ type Props = { const EventDetailsSectionPlain = (props: Props) => { const { classes, + eventId, onOpenEditEvent, showEditEvent, programStage, @@ -61,6 +78,15 @@ const EventDetailsSectionPlain = (props: Props) => { ...passOnProps } = props; const orgUnitId = useSelector(({ viewEventPage }) => viewEventPage.loadedValues?.orgUnit?.id); const { orgUnit, error } = useCoreOrgUnit(orgUnitId); + const queryClient = useQueryClient(); + const supportsChangelog = useFeature(FEATURES.changelogs); + const [changeLogIsOpen, setChangeLogIsOpen] = useState(false); + const [actionsIsOpen, setActionsIsOpen] = useState(false); + + const onSaveExternal = () => { + const queryKey = [ReactQueryAppNamespace, 'changelog', CHANGELOG_ENTITY_TYPES.EVENT, eventId]; + queryClient.removeQueries(queryKey); + }; if (error) { return error.errorComponent; @@ -76,6 +102,7 @@ const EventDetailsSectionPlain = (props: Props) => { dataEntryId={dataEntryIds.SINGLE_EVENT} formFoundation={formFoundation} orgUnit={orgUnit} + onSaveExternal={onSaveExternal} {...passOnProps} /> : // $FlowFixMe[cannot-spread-inexact] automated comment @@ -112,6 +139,26 @@ const EventDetailsSectionPlain = (props: Props) => { } + {supportsChangelog && ( + setActionsIsOpen(prev => !prev)} + secondary + small + icon={} + component={( + + { + setChangeLogIsOpen(true); + setActionsIsOpen(false); + }} + /> + + )} + /> + )} ); }; @@ -120,14 +167,26 @@ const EventDetailsSectionPlain = (props: Props) => { return orgUnit ? (
} + header={( +
+ + {renderActionsContainer()} +
+ )} > -
+
{renderDataEntryContainer()} - {renderActionsContainer()}
{showEditEvent && } + {supportsChangelog && changeLogIsOpen && ( + + )}
) : null; }; diff --git a/src/core_modules/capture-core/components/Pages/ViewEvent/EventDetailsSection/EventDetailsSection.container.js b/src/core_modules/capture-core/components/Pages/ViewEvent/EventDetailsSection/EventDetailsSection.container.js index 474b064aa4..5f0b9946b7 100644 --- a/src/core_modules/capture-core/components/Pages/ViewEvent/EventDetailsSection/EventDetailsSection.container.js +++ b/src/core_modules/capture-core/components/Pages/ViewEvent/EventDetailsSection/EventDetailsSection.container.js @@ -8,6 +8,7 @@ import { const mapStateToProps = (state: ReduxState) => ({ showEditEvent: state.viewEventPage.eventDetailsSection && state.viewEventPage.eventDetailsSection.showEditEvent, + eventId: state.viewEventPage.eventId, programId: state.currentSelections.programId, }); diff --git a/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/EnrollmentPageLayout/LayoutComponentConfig/LayoutComponentConfig.js b/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/EnrollmentPageLayout/LayoutComponentConfig/LayoutComponentConfig.js index 3f5e2015c1..f788fff37e 100644 --- a/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/EnrollmentPageLayout/LayoutComponentConfig/LayoutComponentConfig.js +++ b/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/EnrollmentPageLayout/LayoutComponentConfig/LayoutComponentConfig.js @@ -214,6 +214,7 @@ export const EditEventWorkspace: WidgetConfig = { eventStatus, onCancelEditEvent, onHandleScheduleSave, + onSaveExternal, initialScheduleDate, assignee, onSaveAndCompleteEnrollment, @@ -230,6 +231,7 @@ export const EditEventWorkspace: WidgetConfig = { eventStatus, onCancelEditEvent, onHandleScheduleSave, + onSaveExternal, initialScheduleDate, assignee, onSaveAndCompleteEnrollment, diff --git a/src/core_modules/capture-core/components/WidgetEventEdit/EditEventDataEntry/EditEventDataEntry.container.js b/src/core_modules/capture-core/components/WidgetEventEdit/EditEventDataEntry/EditEventDataEntry.container.js index b3e9384273..7b004d1dc3 100644 --- a/src/core_modules/capture-core/components/WidgetEventEdit/EditEventDataEntry/EditEventDataEntry.container.js +++ b/src/core_modules/capture-core/components/WidgetEventEdit/EditEventDataEntry/EditEventDataEntry.container.js @@ -92,7 +92,9 @@ const mapDispatchToProps = (dispatch: ReduxDispatch, props): any => ({ dispatch(startAsyncUpdateFieldForEditEvent(innerAction, onAsyncUpdateSuccess, onAsyncUpdateError)); }, onSave: (orgUnit: OrgUnit) => (eventId: string, dataEntryId: string, formFoundation: RenderFoundation) => { + const { onSaveExternal } = props; window.scrollTo(0, 0); + onSaveExternal && onSaveExternal(); dispatch(requestSaveEditEventDataEntry(eventId, dataEntryId, formFoundation, orgUnit)); }, onSaveAndCompleteEnrollment: (orgUnit: OrgUnit) => ( diff --git a/src/core_modules/capture-core/components/WidgetEventEdit/EventChangelogWrapper/EventChangelogWrapper.component.js b/src/core_modules/capture-core/components/WidgetEventEdit/EventChangelogWrapper/EventChangelogWrapper.component.js new file mode 100644 index 0000000000..e141ebdac2 --- /dev/null +++ b/src/core_modules/capture-core/components/WidgetEventEdit/EventChangelogWrapper/EventChangelogWrapper.component.js @@ -0,0 +1,58 @@ +// @flow +import React, { useMemo } from 'react'; +import type { DataElement } from '../../../metaData'; +import { dataElementTypes } from '../../../metaData'; +import type { Props } from './EventChangelogWrapper.types'; +import { WidgetEventChangelog } from '../../WidgetsChangelog'; + +export const EventChangelogWrapper = ({ formFoundation, eventId, ...passOnProps }: Props) => { + const dataItemDefinitions = useMemo(() => { + const elements = formFoundation.getElements(); + const contextLabels = formFoundation.getLabels(); + + const fieldElementsById = elements.reduce((acc, element: DataElement) => { + const { optionSet } = element; + const metadata = { + id: element.id, + name: element.formName, + type: element.type, + optionSet: undefined, + options: undefined, + }; + + if (optionSet && optionSet.options) { + metadata.optionSet = optionSet.id; + metadata.options = optionSet.options.map(option => ({ + code: option.code, + name: option.text, + })); + } + + acc[element.id] = metadata; + return acc; + }, {}); + + const fieldElementsContext = Object.keys(contextLabels).reduce((acc, key) => { + acc[key] = { + id: key, + name: contextLabels[key], + type: dataElementTypes.DATE, + }; + + return acc; + }, {}); + + return { + ...fieldElementsById, + ...fieldElementsContext, + }; + }, [formFoundation]); + + return ( + + ); +}; diff --git a/src/core_modules/capture-core/components/WidgetEventEdit/EventChangelogWrapper/EventChangelogWrapper.types.js b/src/core_modules/capture-core/components/WidgetEventEdit/EventChangelogWrapper/EventChangelogWrapper.types.js new file mode 100644 index 0000000000..274891017e --- /dev/null +++ b/src/core_modules/capture-core/components/WidgetEventEdit/EventChangelogWrapper/EventChangelogWrapper.types.js @@ -0,0 +1,13 @@ +// @flow +import type { RenderFoundation } from '../../../metaData'; + +type PassOnProps = {| + eventId: string, + isOpen: boolean, + setIsOpen: (boolean | boolean => boolean) => void, +|} + +export type Props = { + ...PassOnProps, + formFoundation: RenderFoundation, +}; diff --git a/src/core_modules/capture-core/components/WidgetEventEdit/EventChangelogWrapper/index.js b/src/core_modules/capture-core/components/WidgetEventEdit/EventChangelogWrapper/index.js new file mode 100644 index 0000000000..9ebea55c55 --- /dev/null +++ b/src/core_modules/capture-core/components/WidgetEventEdit/EventChangelogWrapper/index.js @@ -0,0 +1,3 @@ +// @flow + +export { EventChangelogWrapper } from './EventChangelogWrapper.component'; diff --git a/src/core_modules/capture-core/components/WidgetEventEdit/WidgetEventEdit.container.js b/src/core_modules/capture-core/components/WidgetEventEdit/WidgetEventEdit.container.js index 0569cc1036..e6c6c39137 100644 --- a/src/core_modules/capture-core/components/WidgetEventEdit/WidgetEventEdit.container.js +++ b/src/core_modules/capture-core/components/WidgetEventEdit/WidgetEventEdit.container.js @@ -1,8 +1,18 @@ // @flow -import React, { type ComponentType } from 'react'; +import React, { type ComponentType, useState } from 'react'; import { dataEntryIds, dataEntryKeys } from 'capture-core/constants'; import { useDispatch, useSelector } from 'react-redux'; -import { spacersNum, Button, colors, IconEdit24, IconArrowLeft24 } from '@dhis2/ui'; +import { + spacersNum, + Button, + colors, + IconEdit24, + IconArrowLeft24, + IconMore16, + FlyoutMenu, + MenuItem, + spacers, +} from '@dhis2/ui'; import { withStyles } from '@material-ui/core'; import i18n from '@dhis2/d2-i18n'; import { ConditionalTooltip } from 'capture-core/components/Tooltips/ConditionalTooltip'; @@ -17,6 +27,9 @@ import { LoadingMaskElementCenter } from '../LoadingMasks'; import { NonBundledDhis2Icon } from '../NonBundledDhis2Icon'; import { getProgramEventAccess } from '../../metaData'; import { useCategoryCombinations } from '../DataEntryDhis2Helpers/AOC/useCategoryCombinations'; +import { OverflowButton } from '../Buttons'; +import { EventChangelogWrapper } from './EventChangelogWrapper'; +import { FEATURES, useFeature } from '../../../capture-core-utils'; const styles = { header: { @@ -32,6 +45,8 @@ const styles = { }, menu: { display: 'flex', + alignItems: 'center', + padding: spacersNum.dp8, justifyContent: 'space-between', background: colors.white, borderTopLeftRadius: 3, @@ -41,6 +56,11 @@ const styles = { borderWidth: 1, borderBottomWidth: 0, }, + menuActions: { + display: 'flex', + alignItems: 'center', + gap: spacers.dp4, + }, button: { margin: spacersNum.dp8 }, tooltip: { display: 'inline-flex' }, }; @@ -53,6 +73,7 @@ export const WidgetEventEditPlain = ({ onGoBack, onCancelEditEvent, onHandleScheduleSave, + onSaveExternal, programId, orgUnitId, enrollmentId, @@ -65,8 +86,11 @@ export const WidgetEventEditPlain = ({ classes, }: PlainProps) => { const dispatch = useDispatch(); + const supportsChangelog = useFeature(FEATURES.changelogs); const { currentPageMode } = useEnrollmentEditEventPageMode(eventStatus); const { orgUnit, error } = useCoreOrgUnit(orgUnitId); + const [changeLogIsOpen, setChangeLogIsOpen] = useState(false); + const [actionsIsOpen, setActionsIsOpen] = useState(false); // "Edit event"-button depends on loadedValues. Delay rendering component until loadedValues has been initialized. const loadedValues = useSelector(({ viewEventPage }) => viewEventPage.loadedValues); @@ -80,13 +104,13 @@ export const WidgetEventEditPlain = ({ return orgUnit && loadedValues ? (
- {currentPageMode === dataEntryKeys.VIEW && ( -
+
} onClick={() => dispatch(startShowEditEventDataEntry(orgUnit, programCategory))} > - {i18n.t('Edit event')} + + {supportsChangelog && ( + setActionsIsOpen(prev => !prev)} + icon={} + small + secondary + dataTest={'widget-event-edit-overflow-button'} + component={( + + { + setChangeLogIsOpen(true); + setActionsIsOpen(false); + }} + /> + + )} + /> + )}
)}
@@ -146,6 +193,7 @@ export const WidgetEventEditPlain = ({ onCancelEditEvent={onCancelEditEvent} hasDeleteButton onHandleScheduleSave={onHandleScheduleSave} + onSaveExternal={onSaveExternal} initialScheduleDate={initialScheduleDate} allowGenerateNextVisit={programStage.allowGenerateNextVisit} askCompleteEnrollmentOnEventComplete={programStage.askCompleteEnrollmentOnEventComplete} @@ -159,6 +207,15 @@ export const WidgetEventEditPlain = ({ )}
+ + {supportsChangelog && changeLogIsOpen && ( + + )}
) : ; }; diff --git a/src/core_modules/capture-core/components/WidgetEventEdit/widgetEventEdit.types.js b/src/core_modules/capture-core/components/WidgetEventEdit/widgetEventEdit.types.js index 1e0ed4af92..766c3e42d6 100644 --- a/src/core_modules/capture-core/components/WidgetEventEdit/widgetEventEdit.types.js +++ b/src/core_modules/capture-core/components/WidgetEventEdit/widgetEventEdit.types.js @@ -9,6 +9,7 @@ export type Props = {| onGoBack: () => void, onCancelEditEvent: (isScheduled: boolean) => void, onHandleScheduleSave: (eventData: Object) =>void, + onSaveExternal: () => void, orgUnitId: string, programId: string, enrollmentId: string, diff --git a/src/core_modules/capture-core/components/WidgetProfile/OverflowMenu/OverflowMenu.component.js b/src/core_modules/capture-core/components/WidgetProfile/OverflowMenu/OverflowMenu.component.js index 8b260eb51e..f42b886630 100644 --- a/src/core_modules/capture-core/components/WidgetProfile/OverflowMenu/OverflowMenu.component.js +++ b/src/core_modules/capture-core/components/WidgetProfile/OverflowMenu/OverflowMenu.component.js @@ -1,30 +1,25 @@ // @flow import React, { useState } from 'react'; -import type { ComponentType } from 'react'; -import { FlyoutMenu, IconMore16, spacers } from '@dhis2/ui'; -import { withStyles } from '@material-ui/core'; +import { FlyoutMenu, IconMore16, MenuItem, MenuDivider } from '@dhis2/ui'; +import i18n from '@dhis2/d2-i18n'; import type { PlainProps } from './OverflowMenu.types'; import { DeleteMenuItem, DeleteModal } from './Delete'; import { OverflowButton } from '../../Buttons'; +import { TrackedEntityChangelogWrapper } from './TrackedEntityChangelogWrapper'; -const styles = { - iconButton: { - display: 'flex', - marginLeft: spacers.dp4, - }, -}; - -const MenuPlain = ({ +export const OverflowMenuComponent = ({ trackedEntity, trackedEntityTypeName, canWriteData, canCascadeDeleteTei, onDeleteSuccess, - classes, + displayChangelog, + teiId, + programAPI, }: PlainProps) => { const [actionsIsOpen, setActionsIsOpen] = useState(false); const [deleteModalIsOpen, setDeleteModalIsOpen] = useState(false); - // const [changelogIsOpen, setChangelogIsOpen] = useState(false); + const [changelogIsOpen, setChangelogIsOpen] = useState(false); return ( <> @@ -34,19 +29,20 @@ const MenuPlain = ({ icon={} small secondary - className={classes.iconButton} dataTest="widget-profile-overflow-menu" component={ - {/* To enable in DHIS2-16764 - { - setChangelogIsOpen(true); - setActionsIsOpen(false); - }} - /> - */} + { + displayChangelog && ( + { + setChangelogIsOpen(true); + setActionsIsOpen(false); + }} + />) + } + )} - {/* {changelogIsOpen && supportsChangelog && <> DHIS2-16764 } */} + {changelogIsOpen && ( + + )} ); }; - -export const OverflowMenuComponet: ComponentType<$Diff> = withStyles(styles)(MenuPlain); diff --git a/src/core_modules/capture-core/components/WidgetProfile/OverflowMenu/OverflowMenu.container.js b/src/core_modules/capture-core/components/WidgetProfile/OverflowMenu/OverflowMenu.container.js index d64198c192..f2b4fca631 100644 --- a/src/core_modules/capture-core/components/WidgetProfile/OverflowMenu/OverflowMenu.container.js +++ b/src/core_modules/capture-core/components/WidgetProfile/OverflowMenu/OverflowMenu.container.js @@ -1,19 +1,30 @@ // @flow import React from 'react'; import type { Props } from './OverflowMenu.types'; -import { OverflowMenuComponet } from './OverflowMenu.component'; +import { OverflowMenuComponent } from './OverflowMenu.component'; import { useAuthorities } from './hooks'; -export const OverflowMenu = ({ trackedEntityTypeName, canWriteData, trackedEntity, onDeleteSuccess }: Props) => { +export const OverflowMenu = ({ + trackedEntityTypeName, + canWriteData, + trackedEntity, + onDeleteSuccess, + displayChangelog, + teiId, + programAPI, +}: Props) => { const { canCascadeDeleteTei } = useAuthorities(); return ( - ); }; diff --git a/src/core_modules/capture-core/components/WidgetProfile/OverflowMenu/OverflowMenu.types.js b/src/core_modules/capture-core/components/WidgetProfile/OverflowMenu/OverflowMenu.types.js index 5e446fdb15..ad3cc687d4 100644 --- a/src/core_modules/capture-core/components/WidgetProfile/OverflowMenu/OverflowMenu.types.js +++ b/src/core_modules/capture-core/components/WidgetProfile/OverflowMenu/OverflowMenu.types.js @@ -5,6 +5,9 @@ export type Props = {| trackedEntityTypeName: string, canWriteData: boolean, onDeleteSuccess?: () => void, + displayChangelog: boolean, + teiId: string, + programAPI: any, |}; export type PlainProps = {| @@ -13,5 +16,7 @@ export type PlainProps = {| canWriteData: boolean, canCascadeDeleteTei: boolean, onDeleteSuccess?: () => void, - ...CssClasses, + displayChangelog: boolean, + teiId: string, + programAPI: any, |}; diff --git a/src/core_modules/capture-core/components/WidgetProfile/OverflowMenu/TrackedEntityChangelogWrapper/TrackedEntityChangelogWrapper.component.js b/src/core_modules/capture-core/components/WidgetProfile/OverflowMenu/TrackedEntityChangelogWrapper/TrackedEntityChangelogWrapper.component.js new file mode 100644 index 0000000000..6142b970cb --- /dev/null +++ b/src/core_modules/capture-core/components/WidgetProfile/OverflowMenu/TrackedEntityChangelogWrapper/TrackedEntityChangelogWrapper.component.js @@ -0,0 +1,63 @@ +// @flow +import React, { useMemo } from 'react'; +import { dataElementTypes, RenderFoundation, type DataElement } from '../../../../metaData'; +import { useFormFoundation } from '../../DataEntry/hooks'; +import { WidgetTrackedEntityChangelog } from '../../../WidgetsChangelog'; +import type { Props } from './TrackedEntityChangelogWrapper.types'; + +export const TrackedEntityChangelogWrapper = ({ programAPI, teiId, setIsOpen, ...passOnProps }: Props) => { + const formFoundation: RenderFoundation = useFormFoundation(programAPI); + + const dataItemDefinitions = useMemo(() => { + if (!Object.keys(formFoundation)?.length) return {}; + const elements = formFoundation.getElements(); + const contextLabels = formFoundation.getLabels(); + + const fieldElementsById = elements.reduce((acc, element: DataElement) => { + const { optionSet } = element; + const metadata = { + id: element.id, + name: element.formName, + type: element.type, + optionSet: undefined, + options: undefined, + }; + + if (optionSet && optionSet.options) { + metadata.optionSet = optionSet.id; + metadata.options = optionSet.options.map(option => ({ + code: option.code, + name: option.text, + })); + } + + acc[element.id] = metadata; + return acc; + }, {}); + + const fieldElementsContext = Object.keys(contextLabels).reduce((acc, key) => { + acc[key] = { + id: key, + name: contextLabels[key], + type: dataElementTypes.DATE, + }; + + return acc; + }, {}); + + return { + ...fieldElementsById, + ...fieldElementsContext, + }; + }, [formFoundation]); + + return ( + setIsOpen(false)} + programId={programAPI.id} + dataItemDefinitions={dataItemDefinitions} + /> + ); +}; diff --git a/src/core_modules/capture-core/components/WidgetProfile/OverflowMenu/TrackedEntityChangelogWrapper/TrackedEntityChangelogWrapper.types.js b/src/core_modules/capture-core/components/WidgetProfile/OverflowMenu/TrackedEntityChangelogWrapper/TrackedEntityChangelogWrapper.types.js new file mode 100644 index 0000000000..e0bb62461f --- /dev/null +++ b/src/core_modules/capture-core/components/WidgetProfile/OverflowMenu/TrackedEntityChangelogWrapper/TrackedEntityChangelogWrapper.types.js @@ -0,0 +1,13 @@ +// @flow +import type { TrackerProgram } from '../../../../metaData'; + +type PassOnProps = {| + teiId: string, + isOpen: boolean, + setIsOpen: (boolean | boolean => boolean) => void, +|} + +export type Props = { + ...PassOnProps, + programAPI: TrackerProgram, +}; diff --git a/src/core_modules/capture-core/components/WidgetProfile/OverflowMenu/TrackedEntityChangelogWrapper/index.js b/src/core_modules/capture-core/components/WidgetProfile/OverflowMenu/TrackedEntityChangelogWrapper/index.js new file mode 100644 index 0000000000..32bd78c86e --- /dev/null +++ b/src/core_modules/capture-core/components/WidgetProfile/OverflowMenu/TrackedEntityChangelogWrapper/index.js @@ -0,0 +1,3 @@ +// @flow + +export { TrackedEntityChangelogWrapper } from './TrackedEntityChangelogWrapper.component'; diff --git a/src/core_modules/capture-core/components/WidgetProfile/WidgetProfile.component.js b/src/core_modules/capture-core/components/WidgetProfile/WidgetProfile.component.js index 715c9868b0..66f14d57e5 100644 --- a/src/core_modules/capture-core/components/WidgetProfile/WidgetProfile.component.js +++ b/src/core_modules/capture-core/components/WidgetProfile/WidgetProfile.component.js @@ -1,4 +1,5 @@ // @flow +/* eslint-disable complexity */ import React, { useEffect, useState, useCallback, useMemo } from 'react'; import type { ComponentType } from 'react'; import { useSelector } from 'react-redux'; @@ -7,7 +8,8 @@ import { Button, spacers } from '@dhis2/ui'; import { withStyles } from '@material-ui/core'; import log from 'loglevel'; import { FlatList } from 'capture-ui'; -import { errorCreator } from 'capture-core-utils'; +import { useQueryClient } from 'react-query'; +import { errorCreator, FEATURES, useFeature } from 'capture-core-utils'; import { Widget } from '../Widget'; import { LoadingMaskElementCenter } from '../LoadingMasks'; import { NoticeBox } from '../NoticeBox'; @@ -20,6 +22,8 @@ import { useTeiDisplayName, } from './hooks'; import { DataEntry, dataEntryActionTypes, TEI_MODAL_STATE, convertClientToView } from './DataEntry'; +import { ReactQueryAppNamespace } from '../../utils/reactQueryHelpers'; +import { CHANGELOG_ENTITY_TYPES } from '../WidgetsChangelog'; import { OverflowMenu } from './OverflowMenu'; const styles = { @@ -35,6 +39,7 @@ const styles = { }, actions: { display: 'flex', + gap: '4px', }, }; @@ -50,6 +55,8 @@ const WidgetProfilePlain = ({ onDeleteSuccess, classes, }: PlainProps) => { + const supportsChangelog = useFeature(FEATURES.changelogs); + const queryClient = useQueryClient(); const [open, setOpenStatus] = useState(true); const [modalState, setTeiModalState] = useState(TEI_MODAL_STATE.CLOSE); const { loading: programsLoading, program, error: programsError } = useProgram(programId); @@ -81,6 +88,7 @@ const WidgetProfilePlain = ({ const error = programsError || trackedEntityInstancesError || userRolesError; const clientAttributesWithSubvalues = useClientAttributesWithSubvalues(teiId, program, trackedEntityInstanceAttributes); const teiDisplayName = useTeiDisplayName(program, storedAttributeValues, clientAttributesWithSubvalues, teiId); + const displayChangelog = supportsChangelog && program.trackedEntityType?.changelogEnabled; const displayInListAttributes = useMemo(() => clientAttributesWithSubvalues .filter(item => item.displayInList) @@ -92,6 +100,10 @@ const WidgetProfilePlain = ({ }; }), [clientAttributesWithSubvalues]); + const onSaveExternal = useCallback(() => { + queryClient.removeQueries([ReactQueryAppNamespace, 'changelog', CHANGELOG_ENTITY_TYPES.TRACKED_ENTITY, teiId]); + }, [queryClient, teiId]); + useEffect(() => { hasError && setTeiModalState(TEI_MODAL_STATE.OPEN_ERROR); }, [hasError]); @@ -145,6 +157,9 @@ const WidgetProfilePlain = ({ canWriteData={canWriteData} trackedEntity={trackedEntity} onDeleteSuccess={onDeleteSuccess} + displayChangelog={displayChangelog} + teiId={teiId} + programAPI={program} />
@@ -167,6 +182,7 @@ const WidgetProfilePlain = ({ trackedEntityInstanceId={teiId} onSaveSuccessActionType={dataEntryActionTypes.TEI_UPDATE_SUCCESS} onSaveErrorActionType={dataEntryActionTypes.TEI_UPDATE_ERROR} + onSaveExternal={onSaveExternal} modalState={modalState} geometry={geometry} trackedEntityName={trackedEntityTypeName} diff --git a/src/core_modules/capture-core/components/WidgetProfile/hooks/useProgram.js b/src/core_modules/capture-core/components/WidgetProfile/hooks/useProgram.js index caf940d79e..b9c9a72f64 100644 --- a/src/core_modules/capture-core/components/WidgetProfile/hooks/useProgram.js +++ b/src/core_modules/capture-core/components/WidgetProfile/hooks/useProgram.js @@ -16,7 +16,7 @@ const fields = 'programStageDataElements[compulsory,displayInReports,renderOptionsAsRadio,allowFutureDate,renderType[*],dataElement[id,displayName,displayShortName,displayFormName,valueType,translations[*],description,optionSetValue,style,optionSet[id,displayName,version,valueType,options[id,displayName,code,style, translations]]]]' + '],' + 'programTrackedEntityAttributes[trackedEntityAttribute[id,displayName,displayShortName,displayFormName,description,valueType,optionSetValue,unique,orgunitScope,pattern,translations[property,locale,value],optionSet[id,displayName,version,valueType,options[id,displayName,name,code,style,translations]]],displayInList,searchable,mandatory,renderOptionsAsRadio,allowFutureDate],' + - 'trackedEntityType[id,access,displayName,minAttributesRequiredToSearch,featureType,trackedEntityTypeAttributes[trackedEntityAttribute[id],displayInList,mandatory,searchable],translations[property,locale,value]],' + + 'trackedEntityType[id,access,displayName,allowAuditLog,minAttributesRequiredToSearch,featureType,trackedEntityTypeAttributes[trackedEntityAttribute[id],displayInList,mandatory,searchable],translations[property,locale,value]],' + 'userRoles[id,displayName]'; export const useProgram = (programId: string) => { @@ -35,5 +35,17 @@ export const useProgram = (programId: string) => { ), ); - return { error, loading, program: !loading && data?.programs }; + const programMetadata = useMemo(() => { + if (data?.programs) { + const program = data.programs; + if (program.trackedEntityType) { + program.trackedEntityType.changelogEnabled = program.trackedEntityType.allowAuditLog; + delete program.trackedEntityType.allowAuditLog; + } + return program; + } + return null; + }, [data]); + + return { error, loading, program: !loading && programMetadata }; }; diff --git a/src/core_modules/capture-core/components/WidgetsChangelog/WidgetEventChangelog/WidgetEventChangelog.js b/src/core_modules/capture-core/components/WidgetsChangelog/WidgetEventChangelog/WidgetEventChangelog.js new file mode 100644 index 0000000000..86137d295e --- /dev/null +++ b/src/core_modules/capture-core/components/WidgetsChangelog/WidgetEventChangelog/WidgetEventChangelog.js @@ -0,0 +1,24 @@ +// @flow +import React from 'react'; +import type { ItemDefinitions } from '../common/Changelog/Changelog.types'; +import { Changelog, CHANGELOG_ENTITY_TYPES } from '../common/Changelog'; + +type Props = { + eventId: string, + dataItemDefinitions: ItemDefinitions, + isOpen: boolean, + setIsOpen: (boolean | boolean => boolean) => void, +} + +export const WidgetEventChangelog = ({ + eventId, + setIsOpen, + ...passOnProps +}: Props) => ( + setIsOpen(false)} + entityId={eventId} + entityType={CHANGELOG_ENTITY_TYPES.EVENT} + /> +); diff --git a/src/core_modules/capture-core/components/WidgetsChangelog/WidgetEventChangelog/index.js b/src/core_modules/capture-core/components/WidgetsChangelog/WidgetEventChangelog/index.js new file mode 100644 index 0000000000..9b1c7babc7 --- /dev/null +++ b/src/core_modules/capture-core/components/WidgetsChangelog/WidgetEventChangelog/index.js @@ -0,0 +1,3 @@ +// @flow + +export { WidgetEventChangelog } from './WidgetEventChangelog'; diff --git a/src/core_modules/capture-core/components/WidgetsChangelog/WidgetTrackedEntityChangelog/WidgetTrackedEntityChangelog.js b/src/core_modules/capture-core/components/WidgetsChangelog/WidgetTrackedEntityChangelog/WidgetTrackedEntityChangelog.js new file mode 100644 index 0000000000..86e941142c --- /dev/null +++ b/src/core_modules/capture-core/components/WidgetsChangelog/WidgetTrackedEntityChangelog/WidgetTrackedEntityChangelog.js @@ -0,0 +1,27 @@ +// @flow +import React from 'react'; +import type { ItemDefinitions } from '../common/Changelog/Changelog.types'; +import { Changelog, CHANGELOG_ENTITY_TYPES } from '../common/Changelog'; + +type Props = { + teiId: string, + programId?: string, + dataItemDefinitions: ItemDefinitions, + isOpen: boolean, + close: () => void, +} + +export const WidgetTrackedEntityChangelog = ({ + teiId, + programId, + close, + ...passOnProps +}: Props) => ( + +); diff --git a/src/core_modules/capture-core/components/WidgetsChangelog/WidgetTrackedEntityChangelog/index.js b/src/core_modules/capture-core/components/WidgetsChangelog/WidgetTrackedEntityChangelog/index.js new file mode 100644 index 0000000000..70aaf658e9 --- /dev/null +++ b/src/core_modules/capture-core/components/WidgetsChangelog/WidgetTrackedEntityChangelog/index.js @@ -0,0 +1,2 @@ +// @flow +export { WidgetTrackedEntityChangelog } from './WidgetTrackedEntityChangelog'; diff --git a/src/core_modules/capture-core/components/WidgetsChangelog/common/Changelog/Changelog.component.js b/src/core_modules/capture-core/components/WidgetsChangelog/common/Changelog/Changelog.component.js new file mode 100644 index 0000000000..9f853a80e1 --- /dev/null +++ b/src/core_modules/capture-core/components/WidgetsChangelog/common/Changelog/Changelog.component.js @@ -0,0 +1,94 @@ +// @flow +import i18n from '@dhis2/d2-i18n'; +import { + Button, + ButtonStrip, + DataTable, + DataTableBody, DataTableCell, DataTableFoot, DataTableRow, + Modal, + ModalActions, + ModalContent, + ModalTitle, Pagination, +} from '@dhis2/ui'; +import React from 'react'; +import { ChangelogTableHeader, ChangelogTableRow } from '../ChangelogTable'; +import type { ChangelogProps } from './Changelog.types'; + +export const ChangelogComponent = ({ + isOpen, + close, + records, + pager, + setPage, + setPageSize, + sortDirection, + setSortDirection, +}: ChangelogProps) => ( + + {i18n.t('Changelog')} + + + + + + {records && records.length > 0 ? ( + + {records?.map(record => ( + + ))} + + ) : ( + + + + {i18n.t('No changes to display')} + + + + )} + {pager && ( + + + + + + + + )} + + + + + + + + + +); diff --git a/src/core_modules/capture-core/components/WidgetsChangelog/common/Changelog/Changelog.constants.js b/src/core_modules/capture-core/components/WidgetsChangelog/common/Changelog/Changelog.constants.js new file mode 100644 index 0000000000..0c43726b08 --- /dev/null +++ b/src/core_modules/capture-core/components/WidgetsChangelog/common/Changelog/Changelog.constants.js @@ -0,0 +1,17 @@ +// @flow + +export const CHANGE_TYPES = Object.freeze({ + CREATED: 'CREATE', + DELETED: 'DELETE', + UPDATED: 'UPDATE', +}); + +export const CHANGELOG_ENTITY_TYPES = Object.freeze({ + EVENT: 'event', + TRACKED_ENTITY: 'trackedEntity', +}); + +export const QUERY_KEYS_BY_ENTITY_TYPE = Object.freeze({ + [CHANGELOG_ENTITY_TYPES.EVENT]: 'events', + [CHANGELOG_ENTITY_TYPES.TRACKED_ENTITY]: 'trackedEntities', +}); diff --git a/src/core_modules/capture-core/components/WidgetsChangelog/common/Changelog/Changelog.container.js b/src/core_modules/capture-core/components/WidgetsChangelog/common/Changelog/Changelog.container.js new file mode 100644 index 0000000000..00754d95f1 --- /dev/null +++ b/src/core_modules/capture-core/components/WidgetsChangelog/common/Changelog/Changelog.container.js @@ -0,0 +1,62 @@ +// @flow +import React from 'react'; +import { Modal } from '@dhis2/ui'; +import { useChangelogData } from '../hooks'; +import { ChangelogComponent } from './Changelog.component'; +import { CHANGELOG_ENTITY_TYPES } from './index'; +import { LoadingMaskElementCenter } from '../../../LoadingMasks'; +import type { ItemDefinitions } from './Changelog.types'; + +type Props = { + entityId: string, + entityType: $Values, + isOpen: boolean, + close: () => void, + dataItemDefinitions: ItemDefinitions, + programId?: string, +} + +export const Changelog = ({ + entityId, + entityType, + programId, + isOpen, + close, + dataItemDefinitions, +}: Props) => { + const { + records, + pager, + isLoading, + setPage, + setPageSize, + sortDirection, + setSortDirection, + } = useChangelogData({ + entityId, + entityType, + programId, + dataItemDefinitions, + }); + + if (isLoading) { + return ( + + + + ); + } + + return ( + + ); +}; diff --git a/src/core_modules/capture-core/components/WidgetsChangelog/common/Changelog/Changelog.types.js b/src/core_modules/capture-core/components/WidgetsChangelog/common/Changelog/Changelog.types.js new file mode 100644 index 0000000000..56dccc68cd --- /dev/null +++ b/src/core_modules/capture-core/components/WidgetsChangelog/common/Changelog/Changelog.types.js @@ -0,0 +1,70 @@ +// @flow +import { CHANGE_TYPES } from './Changelog.constants'; +import { dataElementTypes } from '../../../../metaData'; + +type CreatedChange = {| + type: typeof CHANGE_TYPES.CREATED, + dataElement?: string, + attribute?: string, + currentValue: any, +|} + +type UpdatedChange = {| + type: typeof CHANGE_TYPES.UPDATED, + dataElement?: string, + attribute?: string, + previousValue: any, + currentValue: any, +|} + +type DeletedChange = {| + type: typeof CHANGE_TYPES.DELETED, + dataElement?: string, + attribute?: string, + previousValue: any, +|} + +export type Change = CreatedChange | DeletedChange | UpdatedChange; + +export type ItemDefinitions = { + [key: string]: { + id: string, + name: string, + type: $Keys, + optionSet?: string, + options?: Array<{ code: string, name: string }> + }, +} + +export type SortDirection = 'default' | 'asc' | 'desc'; + +export type SetSortDirection = (SortDirection) => void; + +type Pager = { + page: number, + pageSize: number, + nextPage: string, + previous: string, +} + +export type ChangelogRecord = { + reactKey: string, + date: string, + user: string, + dataItemId: string, + dataItemLabel: string, + changeType: string, + previousValue: string, + newValue: string +} + +export type ChangelogProps = { + isOpen: boolean, + close: () => void, + pager: ?Pager, + records: ?Array, + setPage: (number) => void, + setPageSize: (number) => void, + sortDirection: SortDirection, + setSortDirection: SetSortDirection, +} diff --git a/src/core_modules/capture-core/components/WidgetsChangelog/common/Changelog/index.js b/src/core_modules/capture-core/components/WidgetsChangelog/common/Changelog/index.js new file mode 100644 index 0000000000..888285f137 --- /dev/null +++ b/src/core_modules/capture-core/components/WidgetsChangelog/common/Changelog/index.js @@ -0,0 +1,3 @@ +// @flow +export { Changelog } from './Changelog.container'; +export { CHANGELOG_ENTITY_TYPES } from './Changelog.constants'; diff --git a/src/core_modules/capture-core/components/WidgetsChangelog/common/ChangelogTable/ChangelogChangeCell.js b/src/core_modules/capture-core/components/WidgetsChangelog/common/ChangelogTable/ChangelogChangeCell.js new file mode 100644 index 0000000000..e5dbef266a --- /dev/null +++ b/src/core_modules/capture-core/components/WidgetsChangelog/common/ChangelogTable/ChangelogChangeCell.js @@ -0,0 +1,81 @@ +// @flow +import React from 'react'; +import log from 'loglevel'; +import { colors, IconArrowRight16, spacers, Tag } from '@dhis2/ui'; +import i18n from '@dhis2/d2-i18n'; +import { withStyles } from '@material-ui/core/styles'; +import { CHANGE_TYPES } from '../Changelog/Changelog.constants'; +import { errorCreator } from '../../../../../capture-core-utils'; + +type Props = { + changeType: $Values, + previousValue?: string, + currentValue?: string, + classes: { + container: string, + previousValue: string, + currentValue: string, + } +} + +const styles = { + container: { + display: 'flex', + alignItems: 'center', + gap: spacers.dp4, + }, + previousValue: { + color: colors.grey700, + }, + currentValue: { + color: colors.grey900, + }, +}; + +const Updated = ({ previousValue, currentValue, classes }) => ( +
+ {previousValue} + + {currentValue} +
+); + +const Created = ({ currentValue, classes }) => ( +
+ {i18n.t('Created')} + {currentValue} +
+); + +const Deleted = ({ previousValue, classes }) => ( +
+ {previousValue} + + {i18n.t('Deleted')} +
+); + +const ChangelogComponentsByType = { + [CHANGE_TYPES.UPDATED]: Updated, + [CHANGE_TYPES.CREATED]: Created, + [CHANGE_TYPES.DELETED]: Deleted, +}; + +const ChangelogChangeCellPlain = ({ changeType, currentValue, previousValue, classes }: Props) => { + const ChangelogComponent = ChangelogComponentsByType[changeType]; + + if (!ChangelogComponent) { + log.error(errorCreator('Changelog component not found')({ changeType })); + return null; + } + + return ( + + ); +}; + +export const ChangelogChangeCell = withStyles(styles)(ChangelogChangeCellPlain); diff --git a/src/core_modules/capture-core/components/WidgetsChangelog/common/ChangelogTable/ChangelogTableHeader.js b/src/core_modules/capture-core/components/WidgetsChangelog/common/ChangelogTable/ChangelogTableHeader.js new file mode 100644 index 0000000000..9c3ebd671a --- /dev/null +++ b/src/core_modules/capture-core/components/WidgetsChangelog/common/ChangelogTable/ChangelogTableHeader.js @@ -0,0 +1,35 @@ +// @flow +import i18n from '@dhis2/d2-i18n'; +import { DataTableColumnHeader, DataTableHead, DataTableRow } from '@dhis2/ui'; +import React from 'react'; +import type { SetSortDirection, SortDirection } from '../Changelog/Changelog.types'; + +type Props = { + sortDirection: SortDirection, + setSortDirection: SetSortDirection, +}; + +export const ChangelogTableHeader = ({ + sortDirection, + setSortDirection, +}: Props) => ( + + + setSortDirection(direction)} + sortDirection={sortDirection} + > + {i18n.t('Date')} + + + {i18n.t('User')} + + + {i18n.t('Data item')} + + + {i18n.t('Change')} + + + +); diff --git a/src/core_modules/capture-core/components/WidgetsChangelog/common/ChangelogTable/ChangelogTableRow.js b/src/core_modules/capture-core/components/WidgetsChangelog/common/ChangelogTable/ChangelogTableRow.js new file mode 100644 index 0000000000..31c9a1ddbb --- /dev/null +++ b/src/core_modules/capture-core/components/WidgetsChangelog/common/ChangelogTable/ChangelogTableRow.js @@ -0,0 +1,29 @@ +// @flow +import React from 'react'; +import { DataTableCell, DataTableRow } from '@dhis2/ui'; +import { ChangelogChangeCell } from './ChangelogChangeCell'; +import type { ChangelogRecord } from '../Changelog/Changelog.types'; + +type Props = {| + record: ChangelogRecord, +|} + +export const ChangelogTableRow = ({ record }: Props) => ( + + + {record.date} + + + {record.user} + + + {record.dataItemLabel} + + + + + + +); diff --git a/src/core_modules/capture-core/components/WidgetsChangelog/common/ChangelogTable/index.js b/src/core_modules/capture-core/components/WidgetsChangelog/common/ChangelogTable/index.js new file mode 100644 index 0000000000..6f2fa32294 --- /dev/null +++ b/src/core_modules/capture-core/components/WidgetsChangelog/common/ChangelogTable/index.js @@ -0,0 +1,6 @@ +// @flow + +export { ChangelogTableHeader } from './ChangelogTableHeader'; +export { ChangelogChangeCell } from './ChangelogChangeCell'; +export { ChangelogTableRow } from './ChangelogTableRow'; + diff --git a/src/core_modules/capture-core/components/WidgetsChangelog/common/hooks/index.js b/src/core_modules/capture-core/components/WidgetsChangelog/common/hooks/index.js new file mode 100644 index 0000000000..1cc9b74cc8 --- /dev/null +++ b/src/core_modules/capture-core/components/WidgetsChangelog/common/hooks/index.js @@ -0,0 +1,3 @@ +// @flow + +export { useChangelogData } from './useChangelogData'; diff --git a/src/core_modules/capture-core/components/WidgetsChangelog/common/hooks/useChangelogData.js b/src/core_modules/capture-core/components/WidgetsChangelog/common/hooks/useChangelogData.js new file mode 100644 index 0000000000..7bc6cf97ff --- /dev/null +++ b/src/core_modules/capture-core/components/WidgetsChangelog/common/hooks/useChangelogData.js @@ -0,0 +1,124 @@ +// @flow +import moment from 'moment'; +import { v4 as uuid } from 'uuid'; +import log from 'loglevel'; +import { errorCreator } from 'capture-core-utils'; +import { useMemo, useState } from 'react'; +import { useTimeZoneConversion } from '@dhis2/app-runtime'; +import { useApiDataQuery } from '../../../../utils/reactQueryHelpers'; +import { CHANGELOG_ENTITY_TYPES, QUERY_KEYS_BY_ENTITY_TYPE } from '../Changelog/Changelog.constants'; +import type { Change, ChangelogRecord, ItemDefinitions, SortDirection } from '../Changelog/Changelog.types'; +import { convertServerToClient } from '../../../../converters'; +import { convert } from '../../../../converters/clientToList'; + +type Props = { + entityId: string, + programId?: string, + entityType: $Values, + dataItemDefinitions: ItemDefinitions, +} + +const DEFAULT_PAGE_SIZE = 10; +const DEFAULT_SORT_DIRECTION = 'default'; + +const getMetadataItemDefinition = ( + elementKey: string, + change: Change, + dataItemDefinitions: ItemDefinitions, +) => { + const { dataElement, attribute } = change; + const fieldId = dataElement ?? attribute; + const metadataElement = fieldId ? dataItemDefinitions[fieldId] : dataItemDefinitions[elementKey]; + + return { metadataElement, fieldId }; +}; + +export const useChangelogData = ({ + entityId, + entityType, + programId, + dataItemDefinitions, +}: Props) => { + const [page, setPage] = useState(1); + const [pageSize, setPageSize] = useState(DEFAULT_PAGE_SIZE); + const [sortDirection, setSortDirection] = useState(DEFAULT_SORT_DIRECTION); + const { fromServerDate } = useTimeZoneConversion(); + + const { data, isLoading, isError } = useApiDataQuery( + ['changelog', entityType, entityId, { sortDirection, page, pageSize, programId }], + { + resource: `tracker/${QUERY_KEYS_BY_ENTITY_TYPE[entityType]}/${entityId}/changeLogs`, + params: { + page, + pageSize, + program: programId, + ...{ + order: sortDirection === DEFAULT_SORT_DIRECTION ? undefined : `createdAt:${sortDirection}`, + }, + }, + }, + { + enabled: !!entityId, + }, + ); + + const records: ?Array = useMemo(() => { + if (!data) return undefined; + + return data.changeLogs.map((changelog) => { + const { change: apiChange, createdAt, createdBy } = changelog; + const elementKey = Object.keys(apiChange)[0]; + const change = apiChange[elementKey]; + + const { metadataElement, fieldId } = getMetadataItemDefinition( + elementKey, + change, + dataItemDefinitions, + ); + + if (!metadataElement) { + log.error(errorCreator('Could not find metadata for element')({ + ...changelog, + })); + return null; + } + + const { firstName, surname, username } = createdBy; + const { options } = metadataElement; + + const previousValue = convert( + convertServerToClient(change.previousValue, metadataElement.type), + metadataElement.type, + options, + ); + + const currentValue = convert( + convertServerToClient(change.currentValue, metadataElement.type), + metadataElement.type, + options, + ); + + return { + reactKey: uuid(), + date: moment(fromServerDate(createdAt)).format('YYYY-MM-DD HH:mm'), + user: `${firstName} ${surname} (${username})`, + dataItemId: fieldId, + changeType: changelog.type, + dataItemLabel: metadataElement.name, + previousValue, + currentValue, + }; + }).filter(Boolean); + }, [data, dataItemDefinitions, fromServerDate]); + + return { + records, + pager: data?.pager, + setPage, + setPageSize, + sortDirection, + setSortDirection, + isLoading, + isError, + }; +}; diff --git a/src/core_modules/capture-core/components/WidgetsChangelog/index.js b/src/core_modules/capture-core/components/WidgetsChangelog/index.js new file mode 100644 index 0000000000..767a8fadbb --- /dev/null +++ b/src/core_modules/capture-core/components/WidgetsChangelog/index.js @@ -0,0 +1,4 @@ +// @flow +export { CHANGELOG_ENTITY_TYPES } from './common/Changelog'; +export { WidgetEventChangelog } from './WidgetEventChangelog'; +export { WidgetTrackedEntityChangelog } from './WidgetTrackedEntityChangelog'; diff --git a/src/core_modules/capture-core/converters/clientToList.js b/src/core_modules/capture-core/converters/clientToList.js index ae4eefeeaa..444b563381 100644 --- a/src/core_modules/capture-core/converters/clientToList.js +++ b/src/core_modules/capture-core/converters/clientToList.js @@ -39,6 +39,10 @@ type ImageClientValue = { }; function convertFileForDisplay(clientValue: FileClientValue) { + // Fallback until https://dhis2.atlassian.net/browse/DHIS2-16994 is implemented + if (typeof clientValue === 'string' || clientValue instanceof String) { + return clientValue; + } return ( , d // $FlowFixMe dataElementTypes flow error return valueConvertersForType[type] ? valueConvertersForType[type](value) : value; } + + +// This function will replace the convertValue function in the future (as it should not require a dataElement class to use optionSet) +export function convert( + value: any, + type: $Keys, + options: ?Array<{ code: string, name: string}>, +) { + if (!value && value !== 0 && value !== false) { + return value; + } + + if (options) { + if (type === dataElementTypes.MULTI_TEXT) { + return options + .filter(option => value.includes(option.code)) + .map(option => option.name) + .join(', '); + } + return options + .find(option => option.code === value) + ?.name ?? value; + } + + // $FlowFixMe dataElementTypes flow error + return valueConvertersForType[type] ? valueConvertersForType[type](value) : value; +} diff --git a/src/core_modules/capture-core/metaData/RenderFoundation/RenderFoundation.js b/src/core_modules/capture-core/metaData/RenderFoundation/RenderFoundation.js index 9fdf44a12f..005495237d 100644 --- a/src/core_modules/capture-core/metaData/RenderFoundation/RenderFoundation.js +++ b/src/core_modules/capture-core/metaData/RenderFoundation/RenderFoundation.js @@ -106,6 +106,10 @@ export class RenderFoundation { return this._labels[id]; } + getLabels() { + return this._labels; + } + addSection(newSection: Section) { this._sections.set(newSection.id, newSection); } diff --git a/src/core_modules/capture-core/utils/reactQueryHelpers/query/useApiDataQuery.js b/src/core_modules/capture-core/utils/reactQueryHelpers/query/useApiDataQuery.js index 44b0ba996f..cab589a69f 100644 --- a/src/core_modules/capture-core/utils/reactQueryHelpers/query/useApiDataQuery.js +++ b/src/core_modules/capture-core/utils/reactQueryHelpers/query/useApiDataQuery.js @@ -6,7 +6,7 @@ import type { Result } from './useMetadataQuery.types'; import { ReactQueryAppNamespace } from '../reactQueryHelpers.const'; export const useApiDataQuery = ( - queryKey: Array, + queryKey: Array, queryObject: ResourceQuery, queryOptions: UseQueryOptions, ): Result => { diff --git a/yarn.lock b/yarn.lock index 79777a1f94..a84886d95d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1203,6 +1203,13 @@ dependencies: regenerator-runtime "^0.14.0" +"@babel/runtime@^7.12.0": + version "7.24.0" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.24.0.tgz#584c450063ffda59697021430cb47101b085951e" + integrity sha512-Chk32uHMg6TnQdvw2e9IlqPpFX/6NLuK0Ys2PqLb7/gL5uFn9mXvK715FGLlOLQrcO4qIkNHkvPGktzzXexsFw== + dependencies: + regenerator-runtime "^0.14.0" + "@babel/template@^7.10.4", "@babel/template@^7.22.15", "@babel/template@^7.3.3": version "7.22.15" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.22.15.tgz#09576efc3830f0430f4548ef971dde1350ef2f38" @@ -11069,17 +11076,12 @@ i18next-scanner@^2.10.3: vinyl "^2.2.0" vinyl-fs "^3.0.1" -i18next@*: - version "23.7.16" - resolved "https://registry.yarnpkg.com/i18next/-/i18next-23.7.16.tgz#7026d18b7a3ac9e2ecfeb78da5e4da5ca33312ef" - integrity sha512-SrqFkMn9W6Wb43ZJ9qrO6U2U4S80RsFMA7VYFSqp7oc7RllQOYDCdRfsse6A7Cq/V8MnpxKvJCYgM8++27n4Fw== +i18next@*, i18next@^10.3, i18next@^20.5.0: + version "20.6.1" + resolved "https://registry.yarnpkg.com/i18next/-/i18next-20.6.1.tgz#535e5f6e5baeb685c7d25df70db63bf3cc0aa345" + integrity sha512-yCMYTMEJ9ihCwEQQ3phLo7I/Pwycf8uAx+sRHwwk5U9Aui/IZYgQRyMqXafQOw5QQ7DM1Z+WyEXWIqSuJHhG2A== dependencies: - "@babel/runtime" "^7.23.2" - -i18next@^10.3: - version "10.6.0" - resolved "https://registry.yarnpkg.com/i18next/-/i18next-10.6.0.tgz#90ffd9f9bc617f34b9a12e037260f524445f7684" - integrity sha512-ycRlN145kQf8EsyDAzMfjqv1ZT1Jwp7P2H/07bP8JLWm+7cSLD4XqlJOvq4mKVS2y2mMIy10lX9ZeYUdQ0qSRw== + "@babel/runtime" "^7.12.0" iconv-lite@0.4.24, iconv-lite@^0.4.24: version "0.4.24"