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-16922] Delete Tracked entity from profile Widget #3545

Merged
merged 17 commits into from
Mar 5, 2024
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
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
39 changes: 38 additions & 1 deletion cypress/e2e/WidgetsForEnrollmentPages/WidgetProfile/index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Then } from '@badeball/cypress-cucumber-preprocessor';
import { Then, Given, When } from '@badeball/cypress-cucumber-preprocessor';
import '../../sharedSteps';

Then('the profile details should be displayed', () => {
Expand Down Expand Up @@ -31,3 +31,40 @@ Then(/^the user sees the edit profile modal/, () =>
cy.contains('Cancel without saving').should('exist');
}),
);

Given('you add a new tracked entity in the Malaria focus investigation program', () => {
cy.visit('/#/new?programId=M3xtLkYBlKI&orgUnitId=DiszpKrYNg8');
cy.get('[data-test="capture-ui-input"]')
.eq(2)
.type(`Local id-${Math.round((new Date()).getTime() / 1000)}`)
.blur();
cy.contains('Save focus area')
.click();
cy.url().should('include', 'enrollmentEventEdit?');
});

When('you open the overflow menu and click the "Delete Focus area" button', () => {
cy.get('[data-test="widget-profile-overflow-menu"]')
.click();
cy.contains('Delete Focus area')
.click();
});

Then('you see the delete tracked entity confirmation modal', () => {
cy.get('[data-test="widget-profile-delete-modal"]').within(() => {
cy.contains(
'Are you sure you want to delete this Focus area? This will permanently remove the Focus area and all its associated enrollments and events in all programs.',
).should('exist');
});
});

When('you confirm by clicking the "Yes, delete Focus area" button', () => {
cy.get('[data-test="widget-profile-delete-modal"]').within(() => {
cy.contains('Yes, delete Focus area')
.click();
});
});

Then('you are redirected to the home page', () => {
cy.url().should('include', 'selectedTemplateId=M3xtLkYBlKI');
});
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,15 @@ Feature: The user interacts with the widgets on the enrollment add event page
And the user sees the owner organisation unit
And the user sees the last update date

Scenario: You can delete a tracked entity from the profile widget
Given you add a new tracked entity in the Malaria focus investigation program
When the user clicks the "Back to all stages and events" button
When the user clicks the "New Event" button
When you open the overflow menu and click the "Delete Focus area" button
Then you see the delete tracked entity confirmation modal
When you confirm by clicking the "Yes, delete Focus area" button
Then you are redirected to the home page

# TODO DHIS2-11482 - The test cases related with enrollment status edit are flaky. Move them to unit tests.
# Scenario: User can modify the enrollment from Active to Complete
# Given you land on the enrollment add event page by having typed #/enrollmentEventNew?programId=IpHINAT79UW&orgUnitId=DiszpKrYNg8&teiId=EaOyKGOIGRp&enrollmentId=wBU0RAsYjKE&stageId=A03MvHHogjR
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Then } from '@badeball/cypress-cucumber-preprocessor';
import { When, Then } from '@badeball/cypress-cucumber-preprocessor';
import '../sharedSteps';
import '../WidgetEnrollment';
import '../WidgetProfile';
Expand All @@ -14,3 +14,17 @@ Then('you can assign a user when scheduling the event', () => {
cy.get('[data-test="dhis2-uicore-chip"]').contains('Tracker demo User').should('exist');
});
});

When(/^the user clicks the "Back to all stages and events" button/, () =>
cy
.get('[data-test="widget-enrollment-event"]')
.contains('Back to all stages and events')
.click(),
);

When(/^the user clicks the "New Event" button/, () =>
cy
.get('[data-test="quick-action-button-report"]')
.contains('New Event')
.click(),
);
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,14 @@ Feature: The user interacts with the widgets on the enrollment dashboard
Then the profile widget attributes list contains the text Maria
And the scope selector list contains the text Maria

Scenario: You can delete a tracked entity from the profile widget
Given you add a new tracked entity in the Malaria focus investigation program
When the user clicks the "Back to all stages and events" button
When you open the overflow menu and click the "Delete Focus area" button
Then you see the delete tracked entity confirmation modal
When you confirm by clicking the "Yes, delete Focus area" button
Then you are redirected to the home page

Scenario: User can close the Enrollment Widget
Given you land on the enrollment dashboard page by having typed #/enrollment?enrollmentId=wBU0RAsYjKE
And the enrollment widget should be opened
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,11 @@ Then(/^the scope selector list contains the text (.*)$/, (name) => {
cy.contains(name).should('exist');
});
});

When(/^the user clicks the "Back to all stages and events" button/, () =>
cy
.get('[data-test="widget-enrollment-event"]')
.contains('Back to all stages and events')
.click(),
);

Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,13 @@ Feature: The user interacts with the widgets on the enrollment edit event
And the user sees the owner organisation unit
And the user sees the last update date

Scenario: You can delete a tracked entity from the profile widget
Given you add a new tracked entity in the Malaria focus investigation program
When you open the overflow menu and click the "Delete Focus area" button
Then you see the delete tracked entity confirmation modal
When you confirm by clicking the "Yes, delete Focus area" button
Then you are redirected to the home page

# TODO DHIS2-11482 - The test cases related with enrollment status edit are flaky. Move them to unit tests.
# Scenario: User can modify the enrollment from Active to Complete
# Given you land on the enrollment edit event page by having typed #/enrollmentEventEdit?programId=IpHINAT79UW&orgUnitId=DiszpKrYNg8&teiId=EaOyKGOIGRp&enrollmentId=wBU0RAsYjKE&stageId=A03MvHHogjR
Expand Down
46 changes: 46 additions & 0 deletions i18n/en.pot
Original file line number Diff line number Diff line change
Expand Up @@ -935,6 +935,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."

Expand Down Expand Up @@ -1284,12 +1289,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}}"

Expand Down Expand Up @@ -1338,6 +1353,37 @@ msgstr "Try again or contact your system administrator for support"
msgid "Fix errors in the form to continue."
msgstr "Fix errors in the form to continue."

msgid "You don't have access to delete this {{TETName}}"
msgstr "You don't have access to delete this {{TETName}}"

msgid ""
"You can't delete this {{TETName}} when it has associated enrollments and "
"events"
msgstr ""
"You can't delete this {{TETName}} when it has associated enrollments and "
"events"

msgid "Delete {{TETName}}"
msgstr "Delete {{TETName}}"

msgid ""
"Are you sure you want to delete this {{TETName}}? This will permanently "
"remove the {{TETName}} and all its associated enrollments and events in all "
"programs."
msgstr ""
"Are you sure you want to delete this {{TETName}}? This will permanently "
"remove the {{TETName}} and all its associated enrollments and events in all "
"programs."

msgid "There was a problem deleting the {{TETName}}"
msgstr "There was a problem deleting the {{TETName}}"

msgid "Yes, delete {{TETName}}"
msgstr "Yes, delete {{TETName}}"

msgid "View changelog"
msgstr "View changelog"

msgid "Profile widget could not be loaded. Please try again later"
msgstr "Profile widget could not be loaded. Please try again later"

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,10 @@ export const EnrollmentPageDefault = () => {
const outputEffects = useFilteredWidgetData(ruleEffects);
const hideWidgets = useHideWidgetByRuleLocations(program.programRules);

const onDeleteTrackedEntitySuccess = useCallback(() => {
history.push(`/?${buildUrlQueryString({ orgUnitId, programId })}`);
}, [history, orgUnitId, programId]);

const onDelete = () => {
history.push(`/enrollment?${buildUrlQueryString({ orgUnitId, programId, teiId })}`);
dispatch(deleteEnrollment({ enrollmentId }));
Expand Down Expand Up @@ -147,6 +151,7 @@ export const EnrollmentPageDefault = () => {
enrollmentId={enrollmentId}
onAddNew={onAddNew}
onDelete={onDelete}
onDeleteTrackedEntitySuccess={onDeleteTrackedEntitySuccess}
onViewAll={onViewAll}
onCreateNew={onCreateNew}
widgetEffects={outputEffects}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ export type Props = {|
ruleEffects?: Array<{id: string, type: $Values<effectActions>}>;
pageLayout: PageLayoutConfig,
availableWidgets: $ReadOnly<{ [key: string]: WidgetConfig }>,
onDeleteTrackedEntitySuccess: () => void,
|};

export type PlainProps = {|
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@ export const EnrollmentAddEventPageDefault = ({
history.push(`enrollment?${buildUrlQueryString({ programId, orgUnitId, teiId, enrollmentId })}`);
}, [history, programId, orgUnitId, teiId, enrollmentId]);

const onDeleteTrackedEntitySuccess = useCallback(() => {
history.push(`/?${buildUrlQueryString({ orgUnitId, programId })}`);
}, [history, orgUnitId, programId]);

const handleSave = useCallback(
(data, uid) => {
const nowClient = fromClientDate(new Date());
Expand Down Expand Up @@ -145,6 +149,7 @@ export const EnrollmentAddEventPageDefault = ({
onSave={handleSave}
onCancel={handleCancel}
onDelete={handleDelete}
onDeleteTrackedEntitySuccess={onDeleteTrackedEntitySuccess}
onAddNew={handleAddNew}
widgetEffects={outputEffects}
hideWidgets={hideWidgets}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export type Props = {|
widgetReducerName: string,
pageLayout: PageLayoutConfig,
availableWidgets: $ReadOnly<{ [key: string]: WidgetConfig }>,
onDeleteTrackedEntitySuccess: () => void,
...CssClasses,
|};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ export const EnrollmentEditEventPageComponent = ({
getAssignedUserSaveContext,
onSaveAssignee,
onSaveAssigneeError,
onDeleteTrackedEntitySuccess,
}: PlainProps) => (
<OrgUnitFetcher orgUnitId={orgUnitId}>
<TopBar
Expand Down Expand Up @@ -88,6 +89,7 @@ export const EnrollmentEditEventPageComponent = ({
getAssignedUserSaveContext={getAssignedUserSaveContext}
onSaveAssignee={onSaveAssignee}
onSaveAssigneeError={onSaveAssigneeError}
onDeleteTrackedEntitySuccess={onDeleteTrackedEntitySuccess}
/>
<NoticeBox formId={`${dataEntryIds.ENROLLMENT_EVENT}-${mode}`} />
</OrgUnitFetcher>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,10 @@ const EnrollmentEditEventPageWithContextPlain = ({
const programStage = [...program.stages?.values()].find(item => item.id === stageId);
const hideWidgets = useHideWidgetByRuleLocations(program.programRules.concat(programStage?.programRules));

const onDeleteTrackedEntitySuccess = useCallback(() => {
history.push(`/?${buildUrlQueryString({ orgUnitId, programId })}`);
}, [history, orgUnitId, programId]);

const onDelete = () => {
history.push(`/enrollment?${buildUrlQueryString({ orgUnitId, programId, teiId })}`);
dispatch(deleteEnrollment({ enrollmentId }));
Expand Down Expand Up @@ -204,6 +208,7 @@ const EnrollmentEditEventPageWithContextPlain = ({
trackedEntityName={trackedEntityName}
program={program}
onDelete={onDelete}
onDeleteTrackedEntitySuccess={onDeleteTrackedEntitySuccess}
onAddNew={onAddNew}
orgUnitId={orgUnitId}
eventDate={eventDate}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ export type PlainProps = {|
assignee: UserFormField | null,
onSaveAssignee: (newAssignee: UserFormField) => void,
onSaveAssigneeError: (prevAssignee: UserFormField | null) => void,
onDeleteTrackedEntitySuccess: () => void,
|};

export type Props = {|
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,11 +118,13 @@ export const ProfileWidget: WidgetConfig = {
program,
orgUnitId,
onUpdateTeiAttributeValues,
onDeleteTrackedEntitySuccess,
}): WidgetProfileProps => ({
teiId,
programId: program.id,
orgUnitId,
onUpdateTeiAttributeValues,
onDeleteSuccess: onDeleteTrackedEntitySuccess,
}),
};

Expand Down
Copy link
Member

@JoakimSM JoakimSM Mar 5, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I assume this is the same as src/core_modules/capture-core/components/Tooltips/ConditionalTooltip/ConditionalTooltip.component.js. Let's use that one even though we are in a "self-contained" Widget.

Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// @flow
import React from 'react';
import { Tooltip } from '@dhis2/ui';

type Props = {
enabled: boolean,
children: any,
};

export const ConditionalTooltip = (props: Props) => {
const { enabled, children, ...passOnProps } = props;

return enabled ?
(<Tooltip {...passOnProps}>
{ ({ onMouseOver, onMouseOut, ref }) => (
<span
ref={(btnRef) => {
if (btnRef) {
btnRef.onpointerenter = onMouseOver;
btnRef.onpointerleave = onMouseOut;
ref.current = btnRef;
}
}}
>
{children}
</span>
)}
</Tooltip>) : children;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// @flow
import React from 'react';
import i18n from '@dhis2/d2-i18n';
import { IconDelete16, MenuItem } from '@dhis2/ui';
import type { Props } from './DeleteMenuItem.types';
import { ConditionalTooltip } from './ConditionalTooltip';

const getTooltipContent = (canWriteData, canCascadeDeleteTei, trackedEntityTypeName) => {
if (!canWriteData) {
return i18n.t("You don't have access to delete this {{TETName}}", {
TETName: trackedEntityTypeName,
interpolation: { escapeValue: false },
});
}
if (!canCascadeDeleteTei) {
return i18n.t("You can't delete this {{TETName}} when it has associated enrollments and events", {
TETName: trackedEntityTypeName,
interpolation: { escapeValue: false },
});
}
return '';
};

export const DeleteMenuItem = ({
trackedEntityTypeName,
canCascadeDeleteTei,
canWriteData,
setActionsIsOpen,
setDeleteModalIsOpen,
}: Props) => {
const tooltipContent = getTooltipContent(canWriteData, canCascadeDeleteTei, trackedEntityTypeName);

return (
<ConditionalTooltip content={tooltipContent} enabled={!canWriteData || !canCascadeDeleteTei}>
<MenuItem
destructive
dense
icon={<IconDelete16 />}
label={i18n.t('Delete {{TETName}}', {
TETName: trackedEntityTypeName,
interpolation: { escapeValue: false },
})}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does it make sense to use a lowercased trackedEntityTypeName, similar to what we use other places?

onClick={() => {
setDeleteModalIsOpen(true);
setActionsIsOpen(false);
}}
disabled={!canWriteData || !canCascadeDeleteTei}
/>
</ConditionalTooltip>
);
};
Loading
Loading