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-16372] Delete Relationships #3520

Merged
merged 8 commits into from
Mar 3, 2024
146 changes: 146 additions & 0 deletions cypress/e2e/WidgetsForEnrollmentPages/WidgetTeiRelationship/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
import { When, Given, Then, Before } from '@badeball/cypress-cucumber-preprocessor';

Before({ tags: '@with-mocked-relationship-data' }, () => {
cy.intercept({
method: 'GET',
url: '**/tracker/relationships**',
}, {
statusCode: 200,
body: {
instances: [
{
relationship: 'mwswG3RMNuu',
relationshipType: 'XdP5nraLPZ0',
createdAt: '2024-02-01T11:50:25.479',
from: {
trackedEntity: {
trackedEntity: 'EaOyKGOIGRp',
trackedEntityType: 'nEenWmSyUEp',
orgUnit: 'DiszpKrYNg8',
attributes: [
{
attribute: 'cejWyOfXge6',
displayName: 'Gender',
createdAt: '2016-08-03T23:47:14.509',
updatedAt: '2016-08-03T23:47:14.509',
valueType: 'TEXT',
value: 'Female',
},
{
attribute: 'zDhUuAYrxNC',
displayName: 'Last name',
createdAt: '2016-08-03T23:47:14.517',
updatedAt: '2016-08-03T23:47:14.517',
valueType: 'TEXT',
value: 'Jones',
},
{
attribute: 'w75KJ2mc4zz',
code: 'MMD_PER_NAM',
displayName: 'First name',
createdAt: '2016-08-03T23:47:14.516',
updatedAt: '2016-08-03T23:47:14.516',
valueType: 'TEXT',
value: 'Anna',
},
],
},
},
to: {
trackedEntity: {
trackedEntity: 'G1NNqS1RDeO',
trackedEntityType: 'nEenWmSyUEp',
orgUnit: 'DiszpKrYNg8',
attributes: [
{
attribute: 'w75KJ2mc4zz',
code: 'MMD_PER_NAM',
displayName: 'First name',
createdAt: '2024-02-01T11:50:25.479',
updatedAt: '2024-02-01T11:50:25.479',
valueType: 'TEXT',
value: 'John',
},
{
attribute: 'lZGmxYbs97q',
code: 'MMD_PER_ID',
displayName: 'Unique ID',
createdAt: '2024-02-01T11:50:25.476',
updatedAt: '2024-02-01T11:50:25.476',
valueType: 'TEXT',
value: '0078200',
},
{
attribute: 'zDhUuAYrxNC',
displayName: 'Last name',
createdAt: '2024-02-01T11:50:25.479',
updatedAt: '2024-02-01T11:50:25.479',
valueType: 'TEXT',
value: 'Mayer',
},
],
},
},
},
],
},
}).as('getRelationships');
});

Given('the user can see the relationship widget', () => {
cy.get('[data-test="tracked-entity-relationship-widget"]')
.should('be.visible');
});

When('there is an existing relationship', () => {
cy.wait('@getRelationships');
cy.get('[data-test="tracked-entity-relationship-widget"]')
.within(() => {
cy.get('[data-test="relationship-table-row"]')
.contains('John');
});
});

When('the user clicks the delete button', () => {
cy.get('[data-test="tracked-entity-relationship-widget"]')
.within(() => {
cy.get('[data-test="relationship-table-row"]')
.contains('John')
.parent()
.within(() => {
cy.get('[data-test="delete-relationship-button"]')
.click();
});
});
});

When('the user can see the delete relationship modal', () => {
cy.get('[data-test="delete-relationship-modal"]').should('be.visible');
});

When('the user clicks the confirm delete button', () => {
cy.intercept({
method: 'POST',
url: '**/tracker?importStrategy=DELETE&async=false**',
}).as('deleteRelationship');

cy.get('[data-test="delete-relationship-modal"]')
.within(() => {
cy.get('[data-test="delete-relationship-confirmation-button"]')
.click();
});

cy.wait('@deleteRelationship')
.its('request.body')
.should('deep.equal', {
relationships: [{ relationship: 'mwswG3RMNuu' }],
Copy link
Contributor

Choose a reason for hiding this comment

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

Reminder after the bug in the API is fixed: To keep DB consistency, the relationship that was deleted should be added back. Otherwise, the there is an existing relationship step will fail the next time the scenario is run.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This data is actually just mocked, so no need to add it again. We intercept the request for relationships in the before hook and then assert that we send the actual call to the api with the correct data! WDYT?

Copy link
Contributor

Choose a reason for hiding this comment

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

Testing against mocked data versus the API and DB has advantages and disadvantages 🤔 . By using mock data, we will no longer benefit from having the latest API changes available. We will need to ensure that the mocks are always up to date, otherwise, we may not detect when we introduce bugs.
Let's discuss it more in-depth in our next FE check-in meeting!

});
});

Then('the user can see the relationship widget without the deleted relationship', () => {
cy.get('[data-test="tracked-entity-relationship-widget"]')
.within(() => {
cy.get('[data-test="relationship-table-body"]')
.should('not.exist');
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,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
Expand Down Expand Up @@ -132,4 +132,15 @@ 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

# TODO: Enable this test before merge. Blocked by a bug in the API.
# @with-mocked-relationship-data
# Scenario: The user is able to delete a relationship
# Given you land on the enrollment dashboard page by having typed #/enrollment?enrollmentId=wBU0RAsYjKE
# Then the user can see the relationship widget
# And there is an existing relationship
# When the user clicks the delete button
# Then the user can see the delete relationship modal
# When the user clicks the confirm delete button
# Then the user can see the relationship widget without the deleted relationship
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { When, Then } from '@badeball/cypress-cucumber-preprocessor';
import moment from 'moment';
import '../sharedSteps';
import '../WidgetTeiRelationship';
import '../WidgetEnrollment';
import '../WidgetProfile';
import '../WidgetEnrollmentComment';
Expand Down
23 changes: 18 additions & 5 deletions i18n/en.pot
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ msgstr ""
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1)\n"
"POT-Creation-Date: 2024-01-25T12:12:47.253Z\n"
"PO-Revision-Date: 2024-01-25T12:12:47.253Z\n"
"POT-Creation-Date: 2024-01-30T15:04:26.286Z\n"
"PO-Revision-Date: 2024-01-30T15:04:26.286Z\n"

msgid "Choose one or more dates..."
msgstr "Choose one or more dates..."
Expand Down Expand Up @@ -1377,9 +1377,6 @@ msgstr "This stage can only have one event"
msgid "Events could not be retrieved. Please try again later."
msgstr "Events could not be retrieved. Please try again later."

msgid "Assigned to"
msgstr "Assigned to"

msgid "{{ totalEvents }} events"
msgstr "{{ totalEvents }} events"

Expand Down Expand Up @@ -1416,6 +1413,22 @@ msgstr "Something went wrong while loading relationships. Please try again later
msgid "{{trackedEntityTypeName}} relationships"
msgstr "{{trackedEntityTypeName}} relationships"

msgid "Delete relationship"
msgstr "Delete relationship"

msgid ""
"Deleting the relationship is permanent and cannot be undone. Are you sure "
"you want to delete this relationship?"
msgstr ""
"Deleting the relationship is permanent and cannot be undone. Are you sure "
"you want to delete this relationship?"

msgid "Yes, delete relationship"
msgstr "Yes, delete relationship"

msgid "An error occurred while deleting the relationship."
msgstr "An error occurred while deleting the relationship."

msgid "To open this relationship, please wait until saving is complete"
msgstr "To open this relationship, please wait until saving is complete"

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
// @flow

import React, { useState } from 'react';
import i18n from '@dhis2/d2-i18n';
import {
DataTableCell,
IconDelete16,
Modal,
ModalContent,
ModalTitle,
ModalActions,
ButtonStrip,
Button,
colors,
} from '@dhis2/ui';
import { IconButton } from 'capture-ui';
import { withStyles } from '@material-ui/core/styles';

type Props = {
handleDeleteRelationship: () => void,
disabled?: boolean,
classes: {
tableCell: string,
},
}

const styles = {
tableCell: {
display: 'flex',
justifyContent: 'center',
},
};

export const DeleteRelationshipPlain = ({ handleDeleteRelationship, disabled, classes }: Props) => {
const [isModalOpen, setIsModalOpen] = useState(false);
return (
<>
<DataTableCell className={classes.tableCell}>
<IconButton
simonadomnisoru marked this conversation as resolved.
Show resolved Hide resolved
onClick={() => {
if (disabled) return;
setIsModalOpen(true);
}}
dataTest={'delete-relationship-button'}
>
<IconDelete16 color={colors.red600} />
</IconButton>
</DataTableCell>

{isModalOpen && (
<Modal
hide={!isModalOpen}
onClose={() => setIsModalOpen(false)}
dataTest={'delete-relationship-modal'}
>
<ModalTitle>{i18n.t('Delete relationship')}</ModalTitle>
<ModalContent>
{i18n.t('Deleting the relationship is permanent and cannot be undone. Are you sure you want to delete this relationship?')}
</ModalContent>

<ModalActions>
<ButtonStrip>
<Button onClick={() => setIsModalOpen(false)}>
{i18n.t('No, cancel')}
</Button>

<Button
destructive
dataTest={'delete-relationship-confirmation-button'}
onClick={() => {
handleDeleteRelationship();
setIsModalOpen(false);
}}
>
{i18n.t('Yes, delete relationship')}
</Button>
</ButtonStrip>
</ModalActions>
</Modal>
)}
</>
);
};

export const DeleteRelationship = withStyles(styles)(DeleteRelationshipPlain);
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// @flow

export { DeleteRelationship } from './DeleteRelationship';
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
// @flow
import i18n from '@dhis2/d2-i18n';
import log from 'loglevel';
import { errorCreator, FEATURES, useFeature } from 'capture-core-utils';
import { handleAPIResponse, REQUESTED_ENTITIES } from 'capture-core/utils/api';
import { useMutation, useQueryClient } from 'react-query';
import { useAlert, useDataEngine } from '@dhis2/app-runtime';
import { ReactQueryAppNamespace } from '../../../../../utils/reactQueryHelpers';

type Props = {
sourceId: string,
};

export type OnDeleteRelationship = ({ relationshipId: string }) => void;

const deleteRelationshipMutation = {
resource: 'tracker?importStrategy=DELETE&async=false',
type: 'create',
data: ({ relationshipId }) => ({
relationships: [
{
relationship: relationshipId,
},
],
}),
};
export const useDeleteRelationship = ({ sourceId }: Props): { onDeleteRelationship: OnDeleteRelationship } => {
const dataEngine = useDataEngine();
const queryKey: string = useFeature(FEATURES.exportablePayload) ? 'relationships' : 'instances';
const queryClient = useQueryClient();
const { show: showError } = useAlert(
i18n.t('An error occurred while deleting the relationship.'),
{
critical: true,
},
);
const { mutate: onDeleteRelationship } = useMutation(
({ relationshipId }) => dataEngine.mutate(deleteRelationshipMutation, { variables: { relationshipId } }),
{
onMutate: ({ relationshipId }) => {
const prevRelationships = queryClient
.getQueryData([ReactQueryAppNamespace, 'relationships', sourceId]);

const apiRelationships = handleAPIResponse(REQUESTED_ENTITIES.relationships, prevRelationships);

const newRelationships = apiRelationships
?.filter(({ relationship }) => relationship !== relationshipId);

queryClient.setQueryData(
[ReactQueryAppNamespace, 'relationships', sourceId],
{ [queryKey]: newRelationships });

return { prevRelationships };
},
onError: (error, { relationshipId }, context) => {
log.error(errorCreator('An error occurred while deleting the relationship')({ error, relationshipId }));
showError();

if (!context?.prevRelationships) return;
queryClient.setQueryData(
[ReactQueryAppNamespace, 'relationships', sourceId],
context.prevRelationships,
);
},
},
);

return { onDeleteRelationship };
};
Loading
Loading