Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: [DHIS2-16322] One Click Transfer #3519

Merged
merged 28 commits into from
Mar 5, 2024
Merged
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
6d5c11b
feat: temp
eirikhaugstulen Jan 5, 2024
0a15438
feat: temp
eirikhaugstulen Jan 5, 2024
d1cfe52
chore: temp
eirikhaugstulen Jan 11, 2024
45dd867
chore: temp
eirikhaugstulen Jan 25, 2024
83b06f8
feat: temp
eirikhaugstulen Jan 28, 2024
95be4b0
Merge remote-tracking branch 'origin/master' into eh/feat/EnrollmentT…
eirikhaugstulen Jan 28, 2024
a36ba4b
feat: implement transfers
eirikhaugstulen Jan 28, 2024
ed705ad
fix: change icon
eirikhaugstulen Jan 30, 2024
f0b7ad3
fix: pr-review
eirikhaugstulen Jan 31, 2024
e5dfd0f
fix: errorHandling
eirikhaugstulen Feb 4, 2024
576f5b2
fix: change useEffect to callback handler
eirikhaugstulen Feb 4, 2024
a58e77f
Merge remote-tracking branch 'origin/master' into eh/feat/DHIS2-16322…
eirikhaugstulen Feb 4, 2024
89df8aa
chore: add prop to enrollment widget config
eirikhaugstulen Feb 4, 2024
0d65fde
Merge remote-tracking branch 'origin/master' into eh/feat/DHIS2-16322…
eirikhaugstulen Feb 6, 2024
d25160e
chore: add live tests
eirikhaugstulen Feb 6, 2024
7404cb4
chore: add live tests
eirikhaugstulen Feb 7, 2024
3757800
chore: add live tests
eirikhaugstulen Feb 7, 2024
f541748
chore: pr-review
eirikhaugstulen Feb 8, 2024
eea194c
fix: keep focus on search
eirikhaugstulen Feb 18, 2024
f7785aa
fix: remove comment
eirikhaugstulen Feb 19, 2024
f88b394
feat: add gcTime in case of large amount of searches
eirikhaugstulen Feb 19, 2024
7d3c384
chore: change state labels
eirikhaugstulen Feb 19, 2024
13e5d5c
chore: remove comment
eirikhaugstulen Feb 27, 2024
a16b2a7
Merge remote-tracking branch 'origin/master' into eh/feat/DHIS2-16322…
eirikhaugstulen Feb 27, 2024
e0fb367
chore: commit to retrigger cypress
eirikhaugstulen Feb 29, 2024
21db76f
fix: update naming and program access
eirikhaugstulen Mar 1, 2024
8e2a3f5
docs: [DHIS2-16322] One click transfer docs (#3547)
eirikhaugstulen Mar 4, 2024
32f7028
chore: comment out flaky test
eirikhaugstulen Mar 4, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions cypress/e2e/EnrollmentPage/BreakingTheGlass.feature
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
Feature: Breaking the glass page

# TODO - Flaky tests should be fixed by TECH-1662
@skip
Scenario: User with search scope access tries to access an enrollment in a protected program
Given the tei created by this test is cleared from the database
And the data store is clean
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
import { When, Then } from '@badeball/cypress-cucumber-preprocessor';
import { When, Then, After, Given } from '@badeball/cypress-cucumber-preprocessor';
import { getCurrentYear } from '../../../support/date';
import { hasVersionSupport } from '../../../support/tagUtils';

After({ tags: '@with-transfer-ownership-data-cleanup' }, () => {
const teiQueryKey = hasVersionSupport('@v>=41') ? 'trackedEntity' : 'trackedEntityInstance';
cy.buildApiUrl('tracker', `ownership/transfer?program=IpHINAT79UW&ou=DiszpKrYNg8&${teiQueryKey}=EaOyKGOIGRp`)
.then(url => cy.request('PUT', url));
});

When('you click the enrollment widget toggle open close button', () => {
cy.get('[data-test="widget-enrollment"]').within(() => {
@@ -106,6 +113,10 @@ When(/^the user clicks on the delete action/, () =>
cy.get('[data-test="widget-enrollment-actions-delete"]').click(),
);

When(/^the user clicks on the transfer action/, () => {
cy.get('[data-test="widget-enrollment-actions-transfer"]').click();
});

Then(/^the user sees the delete enrollment modal/, () =>
cy.get('[data-test="widget-enrollment-actions-modal"]').within(() => {
cy.contains('Delete enrollment').should('exist');
@@ -116,3 +127,68 @@ Then(/^the user sees the delete enrollment modal/, () =>
cy.contains('Yes, delete enrollment').should('exist');
}),
);

Then(/^the user sees the transfer modal/, () =>
cy.get('[data-test="widget-enrollment-transfer-modal"]').within(() => {
cy.contains('Transfer Ownership').should('exist');
cy.contains(
'Choose the organisation unit to which enrollment ownership should be transferred.',
).should('exist');
cy.contains('Cancel').should('exist');
cy.contains('Transfer').should('exist');
}),
);

Then(/^the user sees the organisation unit tree/, () =>
cy.get('[data-test="widget-enrollment-transfer-modal"]').within(() => {
cy.get('[data-test="widget-enrollment-transfer-orgunit-tree"]').should(
'exist',
);
}),
);

Then(/^the user clicks on the organisation unit with text: (.*)/, orgunit =>
cy.get('[data-test="widget-enrollment-transfer-modal"]').within(() => {
cy.get('[data-test="widget-enrollment-transfer-orgunit-tree"]').within(
() => {
cy.contains(orgunit).click();
},
);
}),
);

Then(/^the user sees the organisation unit with text: (.*) is selected/, orgunit =>
cy.get('[data-test="widget-enrollment-transfer-modal"]').within(() => {
cy.get('[data-test="widget-enrollment-transfer-orgunit-tree"]').within(
() => {
cy.contains(orgunit).should('have.class', 'checked');
},
);
}),
);

Then(/^the user successfully transfers the enrollment/, () => {
cy.intercept(
{ method: 'PUT', url: '**/tracker/ownership/transfer**' },
).as('transferOwnership');

cy.get('[data-test="widget-enrollment-transfer-modal"]').within(() => {
cy.get('[data-test="widget-enrollment-transfer-button"]').click();
});

cy.wait('@transferOwnership');

cy.get('[data-test="widget-enrollment"]').within(() => {
cy.get('[data-test="widget-enrollment-owner-orgunit"]')
.contains('Owned by Sierra Leone')
.should('exist');
});
});

Given(/^the enrollment owner organisation unit is (.*)/, (orgunit) => {
cy.get('[data-test="widget-enrollment"]').within(() => {
cy.get('[data-test="widget-enrollment-owner-orgunit"]')
.contains(`Owned by ${orgunit}`)
.should('exist');
});
});
Original file line number Diff line number Diff line change
@@ -1,5 +1,34 @@
Feature: The user interacts with the widgets on the enrollment dashboard

Scenario: User can open the transfer modal
Given you land on the enrollment dashboard page by having typed #/enrollment?enrollmentId=wBU0RAsYjKE
Then the enrollment widget should be opened
When the user opens the enrollment actions menu
And the user clicks on the transfer action
Then the user sees the transfer modal

Scenario: User can select an organisation unit in the transfer modal
Given you land on the enrollment dashboard page by having typed #/enrollment?enrollmentId=wBU0RAsYjKE
Then the enrollment widget should be opened
When the user opens the enrollment actions menu
And the user clicks on the transfer action
And the user sees the transfer modal
And the user sees the organisation unit tree
When the user clicks on the organisation unit with text: Sierra Leone
Then the user sees the organisation unit with text: Sierra Leone is selected

@with-transfer-ownership-data-cleanup
Scenario: User can transfer the enrollment to another organisation unit
Given you land on the enrollment dashboard page by having typed #/enrollment?enrollmentId=wBU0RAsYjKE
Then the enrollment widget should be opened
And the enrollment owner organisation unit is Ngelehun CHC
When the user opens the enrollment actions menu
And the user clicks on the transfer action
And the user sees the transfer modal
And the user sees the organisation unit tree
When the user clicks on the organisation unit with text: Sierra Leone
Then the user successfully transfers the enrollment

# Scenarios linked to the enrollment dashboard
Scenario: The profile widget can be closed on the enrollment dashboard
Given you land on the enrollment dashboard page by having typed #/enrollment?enrollmentId=wBU0RAsYjKE
@@ -31,7 +60,7 @@ Feature: The user interacts with the widgets on the enrollment dashboard
And the user sets the birthday date to the current date
Then the user see the following text: The womans age is outside the normal range. With the birthdate entered, the age would be: 0

Scenario: The user updates the TEI attributes. The changes are reflected in the whole page.
Scenario: The user updates the TEI attributes. The changes are reflected in the whole page.
Given you land on the enrollment dashboard page by having typed #/enrollment?enrollmentId=TjP3J9cf0z1&orgUnitId=CgunjDKbM45&programId=WSGAb5XwJ3Y&teiId=jzIwoNXIZsK
When the user clicks the element containing the text: Edit
And the user sees the edit profile modal
@@ -124,6 +153,7 @@ Feature: The user interacts with the widgets on the enrollment dashboard
And the user clicks on the delete action
Then the user sees the delete enrollment modal


Scenario: User can add note on enrollment dashboard page
Given you land on the enrollment dashboard page by having typed #/enrollment?enrollmentId=wBU0RAsYjKE
Then the stages and events should be loaded
@@ -132,4 +162,4 @@ Feature: The user interacts with the widgets on the enrollment dashboard

Scenario: The program rules are triggered and the effects are displayed in the sidebar widgets
Given you land on the enrollment dashboard page by having typed #/enrollment?enrollmentId=wBU0RAsYjKE
Then the user can see the program rules effect in the indicator widget
Then the user can see the program rules effect in the indicator widget
31 changes: 23 additions & 8 deletions cypress/support/tagUtils/filterInstanceVersion.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@

const operation = {
'>=': (instanceVersion, testVersion) => instanceVersion >= testVersion,
'<=': (instanceVersion, testVersion) => instanceVersion <= testVersion,
'>': (instanceVersion, testVersion) => instanceVersion > testVersion,
'<': (instanceVersion, testVersion) => instanceVersion < testVersion,
'=': (instanceVersion, testVersion) => instanceVersion === testVersion,
};

export const filterInstanceVersion = (skip) => {
const { tags } = window.testState.pickle;
if (!tags || !tags.length) {
@@ -14,14 +23,6 @@ export const filterInstanceVersion = (skip) => {

const currentInstanceVersion = Number(/[.](\d+)/.exec(Cypress.env('dhis2InstanceVersion'))[1]);

const operation = {
'>=': (instanceVersion, testVersion) => instanceVersion >= testVersion,
'<=': (instanceVersion, testVersion) => instanceVersion <= testVersion,
'>': (instanceVersion, testVersion) => instanceVersion > testVersion,
'<': (instanceVersion, testVersion) => instanceVersion < testVersion,
'=': (instanceVersion, testVersion) => instanceVersion === testVersion,
};

const shouldRun = versionTags
.some((versionTag) => {
const version = Number(versionTag[2]);
@@ -38,3 +39,17 @@ export const filterInstanceVersion = (skip) => {
skip();
}
};

export const hasVersionSupport = (inputVersion) => {
const supportedVersion = /^@v([><=]*)(\d+)$/.exec(inputVersion);
const currentInstanceVersion = Number(/[.](\d+)/.exec(Cypress.env('dhis2InstanceVersion'))[1]);

const version = Number(supportedVersion[2]);
const operator = supportedVersion[1] || '=';

if (!operation[operator] || !currentInstanceVersion) {
return false;
}

return operation[operator](currentInstanceVersion, version);
};
2 changes: 1 addition & 1 deletion cypress/support/tagUtils/index.js
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
export { filterInstanceVersion } from './filterInstanceVersion';
export { filterInstanceVersion, hasVersionSupport } from './filterInstanceVersion';
export { shouldClearCookies } from './shouldClearCookies';
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions docs/user/using-the-capture-app.md
Original file line number Diff line number Diff line change
@@ -972,6 +972,10 @@ If the program only allows one enrollment per tracked entity instance, the **Add

![](resources/images/enrollment-dash-enrollment-widget-add-new.png)

In the enrollment actions, you could also choose to transfer the enrollment to another organisation unit. Click the transfer button and select the organisation unit you want to transfer the enrollment to.

![](resources/images/enrollment-dash-enrollment-widget-transfer.png)

#### Delete the enrollment

You can delete the enrollment by clicking the delete button and confirming the action in the modal.
30 changes: 30 additions & 0 deletions i18n/en.pot
Original file line number Diff line number Diff line change
@@ -1164,6 +1164,12 @@ msgstr "Remove mark for follow-up"
msgid "Mark for follow-up"
msgstr "Mark for follow-up"

msgid "Transfer"
msgstr "Transfer"

msgid "An error occurred while transferring ownership"
msgstr "An error occurred while transferring ownership"

msgid "Existing dates for auto-generated events will not be updated."
msgstr "Existing dates for auto-generated events will not be updated."

@@ -1191,6 +1197,30 @@ msgstr "Finish drawing before saving"
msgid "Set area"
msgstr "Set area"

msgid ""
"Transferring enrollment ownership from {{ownerOrgUnit}} to "
"{{newOrgUnit}}{{escape}}"
msgstr ""
"Transferring enrollment ownership from {{ownerOrgUnit}} to "
"{{newOrgUnit}}{{escape}}"

msgid ""
"You will lose access to the enrollment when transferring ownership to "
"{{organisationUnit}}."
msgstr ""
"You will lose access to the enrollment when transferring ownership to "
"{{organisationUnit}}."

msgid "Transfer Ownership"
msgstr "Transfer Ownership"

msgid ""
"Choose the organisation unit to which enrollment ownership should be "
"transferred."
msgstr ""
"Choose the organisation unit to which enrollment ownership should be "
"transferred."

msgid "Enrollment date"
msgstr "Enrollment date"

Original file line number Diff line number Diff line change
@@ -4,6 +4,7 @@ export const FEATURES = Object.freeze({
storeProgramStageWorkingList: 'storeProgramStageWorkingList',
multiText: 'multiText',
customIcons: 'customIcons',
newTransferQueryParam: 'newTransferQueryParam',
exportablePayload: 'exportablePayload',
});

@@ -14,6 +15,7 @@ const MINOR_VERSION_SUPPORT = Object.freeze({
[FEATURES.multiText]: 41,
[FEATURES.customIcons]: 41,
[FEATURES.exportablePayload]: 41,
[FEATURES.newTransferQueryParam]: 41,
});

export const hasAPISupportForFeature = (minorVersion: string | number, featureName: string) =>
Original file line number Diff line number Diff line change
@@ -142,6 +142,7 @@ const OrgUnitFieldPlain = (props: Props) => {
onBlur && onBlur(null);
};


const styles = maxTreeHeight ? { maxHeight: maxTreeHeight, overflowY: 'auto' } : null;
return (
<div
Original file line number Diff line number Diff line change
@@ -120,6 +120,10 @@ export const EnrollmentPageDefault = () => {
history.push(`/new?${buildUrlQueryString({ orgUnitId, programId, teiId })}`);
};

const onAccessLostFromTransfer = () => {
history.push(`/?${buildUrlQueryString({ orgUnitId, programId })}`);
};

const onEnrollmentError = message => dispatch(showEnrollmentError({ message }));

if (isLoading) {
@@ -158,6 +162,7 @@ export const EnrollmentPageDefault = () => {
onUpdateIncidentDate={onUpdateIncidentDate}
onEnrollmentError={onEnrollmentError}
ruleEffects={ruleEffects}
onAccessLostFromTransfer={onAccessLostFromTransfer}
/>
);
};
Original file line number Diff line number Diff line change
@@ -31,6 +31,7 @@ export type Props = {|
onLinkedRecordClick: LinkedRecordClick,
onUpdateEnrollmentDate: (enrollmentDate: string) => void,
onUpdateIncidentDate: (incidentDate: string) => void,
onAccessLostFromTransfer: () => void,
onEnrollmentError: (message: string) => void,
ruleEffects?: Array<{id: string, type: $Values<effectActions>}>;
pageLayout: PageLayoutConfig,
Original file line number Diff line number Diff line change
@@ -53,6 +53,7 @@ const EnrollmentAddEventPagePain = ({
onEnrollmentSuccess,
pageFailure,
ready,
onAccessLostFromTransfer,
classes,
...passOnProps
}: Props) => {
@@ -93,6 +94,7 @@ const EnrollmentAddEventPagePain = ({
onAddNew={onAddNew}
onEnrollmentError={onEnrollmentError}
onEnrollmentSuccess={onEnrollmentSuccess}
onAccessLostFromTransfer={onAccessLostFromTransfer}
/>
</div>
);
Original file line number Diff line number Diff line change
@@ -66,6 +66,10 @@ export const EnrollmentAddEventPageDefault = ({
const onEnrollmentError = message => dispatch(showEnrollmentError({ message }));
const onEnrollmentSuccess = () => dispatch(fetchEnrollments());

const onAccessLostFromTransfer = () => {
history.push(`/?${buildUrlQueryString({ orgUnitId, programId })}`);
};

const widgetReducerName = 'enrollmentEvent-newEvent';

const dataEntryHasChanges = useSelector(state => getDataEntryHasChanges(state, widgetReducerName));
@@ -155,6 +159,7 @@ export const EnrollmentAddEventPageDefault = ({
ready={Boolean(enrollment)}
onEnrollmentError={onEnrollmentError}
onEnrollmentSuccess={onEnrollmentSuccess}
onAccessLostFromTransfer={onAccessLostFromTransfer}
/>
</>
);
Original file line number Diff line number Diff line change
@@ -18,6 +18,7 @@ export type Props = {|
onDelete: () => void,
onAddNew: () => void,
onEnrollmentError: (message: string) => void,
onAccessLostFromTransfer?: () => void,
onEnrollmentSuccess: () => void,
widgetEffects: ?WidgetEffects,
hideWidgets: HideWidgets,
Original file line number Diff line number Diff line change
@@ -42,6 +42,7 @@ export const EnrollmentEditEventPageComponent = ({
getAssignedUserSaveContext,
onSaveAssignee,
onSaveAssigneeError,
onAccessLostFromTransfer,
}: PlainProps) => (
<OrgUnitFetcher orgUnitId={orgUnitId}>
<TopBar
@@ -88,6 +89,7 @@ export const EnrollmentEditEventPageComponent = ({
getAssignedUserSaveContext={getAssignedUserSaveContext}
onSaveAssignee={onSaveAssignee}
onSaveAssigneeError={onSaveAssigneeError}
onAccessLostFromTransfer={onAccessLostFromTransfer}
/>
<NoticeBox formId={`${dataEntryIds.ENROLLMENT_EVENT}-${mode}`} />
</OrgUnitFetcher>
Original file line number Diff line number Diff line change
@@ -175,6 +175,9 @@ const EnrollmentEditEventPageWithContextPlain = ({
const assignedUser: ApiAssignedUser = convertClientToServer(newAssignee, dataElementTypes.ASSIGNEE);
dispatch(setAssignee(assignedUser, newAssignee, eventId));
};
const onAccessLostFromTransfer = () => {
history.push(`/?${buildUrlQueryString({ orgUnitId, programId })}`);
};
const onSaveAssigneeError = (prevAssignee) => {
const assignedUser: ApiAssignedUser | typeof undefined = prevAssignee
// $FlowFixMe dataElementTypes flow error
@@ -219,6 +222,7 @@ const EnrollmentEditEventPageWithContextPlain = ({
getAssignedUserSaveContext={getAssignedUserSaveContext}
onSaveAssignee={onSaveAssignee}
onSaveAssigneeError={onSaveAssigneeError}
onAccessLostFromTransfer={onAccessLostFromTransfer}
/>
);
};
Loading