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
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,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
20 changes: 18 additions & 2 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-02-14T10:01:26.732Z\n"
"PO-Revision-Date: 2024-02-14T10:01:26.732Z\n"
"POT-Creation-Date: 2024-02-16T12:15:15.668Z\n"
"PO-Revision-Date: 2024-02-16T12:15:15.668Z\n"

msgid "Choose one or more dates..."
msgstr "Choose one or more dates..."
Expand Down Expand Up @@ -1404,6 +1404,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 };
};
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,12 @@ const styles = {
};


const LinkedEntitiesViewerPlain = ({ groupedLinkedEntities, onLinkedRecordClick, classes }: StyledProps) => (
const LinkedEntitiesViewerPlain = ({
groupedLinkedEntities,
onLinkedRecordClick,
onDeleteRelationship,
classes,
}: StyledProps) => (
<div
data-test="relationships"
className={classes.container}
Expand All @@ -35,6 +40,7 @@ const LinkedEntitiesViewerPlain = ({ groupedLinkedEntities, onLinkedRecordClick,
linkedEntities={linkedEntities}
columns={columns}
onLinkedRecordClick={onLinkedRecordClick}
onDeleteRelationship={onDeleteRelationship}
context={context}
/>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,14 @@ const styles = {
},
};

const LinkedEntityTablePlain = ({ linkedEntities, columns, onLinkedRecordClick, context, classes }: StyledProps) => {
const LinkedEntityTablePlain = ({
linkedEntities,
columns,
onLinkedRecordClick,
onDeleteRelationship,
context,
classes,
}: StyledProps) => {
const [visibleRowsCount, setVisibleRowsCount] = useState(DEFAULT_VISIBLE_ROWS_COUNT);

const visibleLinkedEntities = useMemo(() =>
Expand All @@ -39,12 +46,14 @@ const LinkedEntityTablePlain = ({ linkedEntities, columns, onLinkedRecordClick,
<DataTable>
<LinkedEntityTableHeader
columns={columns}
context={context}
/>
<LinkedEntityTableBody
linkedEntities={visibleLinkedEntities}
columns={columns}
onLinkedRecordClick={onLinkedRecordClick}
context={context}
onDeleteRelationship={onDeleteRelationship}
/>
</DataTable>
{showMoreButtonVisible && (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
import i18n from '@dhis2/d2-i18n';
import { convertServerToClient, convertClientToList } from '../../../../converters';
import type { Props, StyledProps } from './linkedEntityTableBody.types';
import { DeleteRelationship } from './DeleteRelationship';

const styles = {
row: {
Expand All @@ -26,16 +27,18 @@ const LinkedEntityTableBodyPlain = ({
columns,
onLinkedRecordClick,
context,
onDeleteRelationship,
classes,
}: StyledProps) => (
<DataTableBody>
<DataTableBody dataTest="relationship-table-body">
{
linkedEntities
.map(({ id: entityId, values, baseValues, navigation }) => {
const { pendingApiResponse } = baseValues || {};
const { pendingApiResponse, relationshipId } = baseValues || {};
return (
<DataTableRow
key={entityId}
dataTest={'relationship-table-row'}
className={pendingApiResponse ? classes.rowDisabled : classes.row}
>
{
Expand Down Expand Up @@ -76,6 +79,12 @@ const LinkedEntityTableBodyPlain = ({
</Tooltip>
);
})}
{context.display.showDeleteButton && (
<DeleteRelationship
handleDeleteRelationship={() => onDeleteRelationship({ relationshipId })}
disabled={pendingApiResponse}
/>
)}
</DataTableRow>
);
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
} from '@dhis2/ui';
import type { Props } from './linkedEntityTableHeader.types';

export const LinkedEntityTableHeader = ({ columns }: Props) => (
export const LinkedEntityTableHeader = ({ columns, context }: Props) => (
<DataTableHead>
<DataTableRow>
{
Expand All @@ -20,6 +20,9 @@ export const LinkedEntityTableHeader = ({ columns }: Props) => (
</DataTableColumnHeader>
))
}
{context.display.showDeleteButton && (
<DataTableColumnHeader />
)}
</DataTableRow>
</DataTableHead>
);
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { useGroupedLinkedEntities } from './useGroupedLinkedEntities';
import { LinkedEntitiesViewer } from './LinkedEntitiesViewer.component';
import type { Props, StyledProps } from './relationshipsWidget.types';
import { LoadingMaskElementCenter } from '../../../LoadingMasks';
import { useDeleteRelationship } from './DeleteRelationship/useDeleteRelationship';

const styles = {
header: {
Expand All @@ -30,6 +31,7 @@ const RelationshipsWidgetPlain = ({
}: StyledProps) => {
const [open, setOpenStatus] = useState(true);
const groupedLinkedEntities = useGroupedLinkedEntities(sourceId, relationshipTypes, relationships);
const { onDeleteRelationship } = useDeleteRelationship({ sourceId });

if (isLoading) {
return (
Expand All @@ -54,7 +56,7 @@ const RelationshipsWidgetPlain = ({

return (
<div
data-test="relationship-widget"
data-test="tracked-entity-relationship-widget"
>
<Widget
header={(
Expand All @@ -79,6 +81,7 @@ const RelationshipsWidgetPlain = ({
<LinkedEntitiesViewer
groupedLinkedEntities={groupedLinkedEntities}
onLinkedRecordClick={onLinkedRecordClick}
onDeleteRelationship={onDeleteRelationship}
/>
)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
// @flow
import type { GroupedLinkedEntities, LinkedRecordClick } from './types';
import type { OnDeleteRelationship } from './DeleteRelationship/useDeleteRelationship';

export type Props = $ReadOnly<{|
groupedLinkedEntities: GroupedLinkedEntities,
onLinkedRecordClick: LinkedRecordClick,
onDeleteRelationship: OnDeleteRelationship,
|}>;

export type StyledProps = $ReadOnly<{|
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
// @flow
import type { LinkedEntityData, TableColumn, LinkedRecordClick, Context } from './types';
import type { OnDeleteRelationship } from './DeleteRelationship/useDeleteRelationship';

export type Props = $ReadOnly<{|
linkedEntities: $ReadOnlyArray<LinkedEntityData>,
columns: $ReadOnlyArray<TableColumn>,
onLinkedRecordClick: LinkedRecordClick,
onDeleteRelationship: OnDeleteRelationship,
context: Context,
|}>;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
// @flow
import type { LinkedEntityData, TableColumn, Context, LinkedRecordClick } from './types';
import type { OnDeleteRelationship } from './DeleteRelationship/useDeleteRelationship';

export type Props = $ReadOnly<{|
linkedEntities: $ReadOnlyArray<LinkedEntityData>,
columns: $ReadOnlyArray<TableColumn>,
onLinkedRecordClick: LinkedRecordClick,
context: Context,
onDeleteRelationship: OnDeleteRelationship,
|}>;

export type StyledProps = $ReadOnly<{|
Expand Down
Loading
Loading