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

fix: [DHIS2-16499] Handle different API response format #3517

Merged
merged 5 commits into from
Feb 1, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
14 changes: 8 additions & 6 deletions cypress/e2e/EnrollmentPage/BreakingTheGlass/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@ import '../../sharedSteps';
Given('the tei created by this test is cleared from the database', () => {
cy.buildApiUrl('tracker', 'trackedEntities?filter=w75KJ2mc4zz:like:Breaking&filter=zDhUuAYrxNC:like:TheGlass&trackedEntityType=nEenWmSyUEp&page=1&pageSize=5&ouMode=ACCESSIBLE')
.then(url => cy.request(url))
.then(({ body }) =>
body.instances.forEach(({ trackedEntity }) =>
cy.buildApiUrl('trackedEntityInstances', trackedEntity)
.then(trackedEntityUrl =>
cy.request('DELETE', trackedEntityUrl)),
));
.then(({ body }) => {
const apiTrackedEntities = body.trackedEntities || body.instances || [];
return apiTrackedEntities.forEach(({ trackedEntity }) =>
cy
.buildApiUrl('trackedEntityInstances', trackedEntity)
.then(trackedEntityUrl => cy.request('DELETE', trackedEntityUrl)),
);
});
});

And('you create a new tei in Child programme from Ngelehun CHC', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,25 +36,27 @@ Then('events should be retrieved from the api using the default query args', ()
.its('response.url')
.should('include', 'page=1');

cy.get('@result').its('response.body.instances').as('events');
cy.get('@result').its('response.body').as('events');
});

Then('the list should display the events retrieved from the api', () => {
cy.get('@events')
.then((events) => {
.then((body) => {
const apiEvents = body.events || body.instances || [];
cy.get('[data-test="event-working-lists"]')
.find('tr')
.should('have.length', events.length + 1);
.should('have.length', apiEvents.length + 1);
});

cy.get('@events')
.then((teis) => {
.then((body) => {
const apiEvents = body.events || body.instances || [];
cy.get('[data-test="event-working-lists"]')
.find('tr')
.each(($teiRow, index) => {
const rowId = $teiRow.get(0).getAttribute('data-test');
if (index > 1) {
expect(rowId).to.equal(teis[index - 1].event);
expect(rowId).to.equal(apiEvents[index - 1].event);
}
});
});
Expand Down Expand Up @@ -87,7 +89,7 @@ Then('events assigned to anyone should be retrieved from the api', () => {
.its('response.url')
.should('include', 'page=1');

cy.get('@result').its('response.body.instances').as('events');
cy.get('@result').its('response.body').as('events');
});

When('you apply the assignee filter', () => {
Expand Down Expand Up @@ -123,7 +125,7 @@ Then('active events that are assigned to anyone should be retrieved from the api
.its('response.url')
.should('include', 'page=1');

cy.get('@result').its('response.body.instances').as('events');
cy.get('@result').its('response.body').as('events');
});

When('you apply the current filter on the event working list', () => {
Expand Down Expand Up @@ -152,7 +154,7 @@ Then('events where age is between 10 and 20 should be retrieved from the api', (
.its('response.url')
.should('include', 'page=1');

cy.get('@result').its('response.body.instances').as('events');
cy.get('@result').its('response.body').as('events');
});

When('you click the next page button on the event working list', () => {
Expand All @@ -172,7 +174,7 @@ Then('new events should be retrieved from the api', () => {
.its('response.statusCode')
.should('equal', 200);

cy.get('@result').its('response.body.instances').as('events');
cy.get('@result').its('response.body').as('events');
});

When('you click the previous page button on the event working list', () => {
Expand Down Expand Up @@ -216,7 +218,7 @@ Then('an event batch capped at 50 records should be retrieved from the api', ()
.its('response.url')
.should('include', 'page=1');

cy.get('@result').its('response.body.instances').as('events');
cy.get('@result').its('response.body').as('events');
});

When('you click the report date column header', () => {
Expand Down Expand Up @@ -248,5 +250,5 @@ Then('events should be retrieved from the api ordered ascendingly by report date
.its('response.url')
.should('include', 'page=1');

cy.get('@resultAsc').its('response.body.instances').as('events');
cy.get('@resultAsc').its('response.body').as('events');
});
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ Then('teis should be retrieved from the api using the default query args', () =>
.its('response.url')
.should('include', 'page=1');

cy.get('@result').its('response.body.instances').as('teis');
cy.get('@result').its('response.body').as('teis');
});

Then('the first page of the default tei working list should be displayed', () => {
Expand Down Expand Up @@ -89,7 +89,7 @@ Then('teis with an active enrollment should be retrieved from the api', () => {
.its('response.url')
.should('include', 'page=1');

cy.get('@result').its('response.body.instances').as('teis');
cy.get('@result').its('response.body').as('teis');
});

When('you apply the assignee filter', () => {
Expand Down Expand Up @@ -118,7 +118,7 @@ Then('teis with active enrollments and unassigned events should be retrieved fro
.its('response.url')
.should('include', 'page=1');

cy.get('@result').its('response.body.instances').as('teis');
cy.get('@result').its('response.body').as('teis');
});

When('you apply the current filter on the tei working list', () => {
Expand All @@ -143,25 +143,27 @@ Then('teis with a first name containing John should be retrieved from the api',
.its('response.url')
.should('include', 'page=1');

cy.get('@result').its('response.body.instances').as('teis');
cy.get('@result').its('response.body').as('teis');
});

Then('the list should display the teis retrieved from the api', () => {
cy.get('@teis')
.then((teis) => {
.then((body) => {
const apiTrackedEntities = body.trackedEntities || body.instances || [];
cy.get('[data-test="tei-working-lists"]')
.find('tr')
.should('have.length', teis.length + 1);
.should('have.length', apiTrackedEntities.length + 1);
});

cy.get('@teis')
.then((teis) => {
.then((body) => {
const apiTrackedEntities = body.trackedEntities || body.instances || [];
cy.get('[data-test="tei-working-lists"]')
.find('tr')
.each(($teiRow, index) => {
const rowId = $teiRow.get(0).getAttribute('data-test');
if (index > 1) {
expect(rowId).to.equal(teis[index - 1].trackedEntity);
expect(rowId).to.equal(apiTrackedEntities[index - 1].trackedEntity);
}
});
});
Expand All @@ -184,7 +186,7 @@ Then('new teis should be retrieved from the api', () => {
.its('response.statusCode')
.should('eq', 200);

cy.get('@result').its('response.body.instances').as('teis');
cy.get('@result').its('response.body').as('teis');
});


Expand Down Expand Up @@ -222,7 +224,7 @@ Then('a tei batch capped at 50 records should be retrieved from the api', () =>
.its('response.url')
.should('include', 'page=1');

cy.get('@result').its('response.body.instances').as('teis');
cy.get('@result').its('response.body').as('teis');
});

When('you click the first page button on the tei working list', () => {
Expand Down Expand Up @@ -259,5 +261,5 @@ Then('teis should be retrieved from the api ordered ascendingly by first name',
.its('response.url')
.should('include', 'page=1');

cy.get('@result').its('response.body.instances').as('teis');
cy.get('@result').its('response.body').as('teis');
});
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,16 @@ export const FEATURES = Object.freeze({
programStageWorkingList: 'programStageWorkingList',
storeProgramStageWorkingList: 'storeProgramStageWorkingList',
customIcons: 'customIcons',
exportablePayload: 'exportablePayload',
});

// The first minor version that supports the feature
const MINOR_VERSION_SUPPORT = Object.freeze({
[FEATURES.programStageWorkingList]: 39,
[FEATURES.storeProgramStageWorkingList]: 40,
[FEATURES.customIcons]: 41,
[FEATURES.exportablePayload]: 41,
});

export const hasAPISupportForFeature = (minorVersion: string, featureName: string) =>
export const hasAPISupportForFeature = (minorVersion: string | number, featureName: string) =>
MINOR_VERSION_SUPPORT[featureName] <= Number(minorVersion) || false;
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// @flow
import { useMemo, useEffect } from 'react';
import { handleAPIResponse, REQUESTED_ENTITIES } from 'capture-core/utils/api';
import { useDataQuery } from '@dhis2/app-runtime';

export const useEventsInOrgUnit = (orgUnitId: string, selectedDate: string) => {
Expand Down Expand Up @@ -31,5 +32,6 @@ export const useEventsInOrgUnit = (orgUnitId: string, selectedDate: string) => {
}
}, [refetch, orgUnitId, selectedDate]);

return { error, events: !loading && data ? data.events.instances : [] };
const apiEvents = handleAPIResponse(REQUESTED_ENTITIES.events, data?.events);
return { error, events: !loading && data ? apiEvents : [] };
};
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,15 @@ import { convertFormToClient, convertClientToServer } from '../../../../converte
import { convertOptionSetValue } from '../../../../converters/serverToClient';
import { buildIcon } from '../../../../metaDataMemoryStoreBuilders/common/helpers';
import { OptionGroup } from '../../../../metaData/OptionSet/OptionGroup';
import { getFeatureType, getDataElement, getLabel, isNotValidOptionSet, escapeString } from '../helpers';
import {
getFeatureType,
getDataElement,
getLabel,
isNotValidOptionSet,
escapeString,
handleAPIResponse,
REQUESTED_ENTITIES,
} from '../helpers';
import type { QuerySingleResource } from '../../../../utils/api/api.types';

const OPTION_SET_NOT_FOUND = 'Optionset not found';
Expand Down Expand Up @@ -81,7 +89,8 @@ const buildDataElementUnique = (
});
}
return requestPromise.then((result) => {
const otherTrackedEntityInstances = result?.instances?.filter(item => item.trackedEntity !== contextProps.trackedEntityInstanceId) || [];
const apiTrackedEntities = handleAPIResponse(REQUESTED_ENTITIES.trackedEntities, result);
const otherTrackedEntityInstances = apiTrackedEntities.filter(item => item.trackedEntity !== contextProps.trackedEntityInstanceId);
const trackedEntityInstance = (otherTrackedEntityInstances && otherTrackedEntityInstances[0]) || {};

const data = {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// @flow

export const REQUESTED_ENTITIES = Object.freeze({
events: 'events',
trackedEntities: 'trackedEntities',
relationships: 'relationships',
});

export const handleAPIResponse = (resourceName: string, apiResponse: any) => {
if (!apiResponse) {
return [];
}
return apiResponse[resourceName] || apiResponse.instances || [];
};
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,4 @@ export { GEOMETRY, getFeatureType, getDataElement, getLabel } from './geometry';
export { convertClientToView } from './convertClientToView';
export { isNotValidOptionSet } from './isNotValidOptionSet';
export { escapeString } from './escapeString';
export { handleAPIResponse, REQUESTED_ENTITIES } from './handleAPIResponse';
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
// @flow
import i18n from '@dhis2/d2-i18n';
import { FEATURES, useFeature } from 'capture-core-utils';
import { useDataEngine, useAlert } from '@dhis2/app-runtime';
import { useMutation, useQueryClient } from 'react-query';
import { handleAPIResponse, REQUESTED_ENTITIES } from 'capture-core/utils/api';

type Props = {
teiId: string;
Expand All @@ -19,6 +21,7 @@ const addRelationshipMutation = {

export const useAddRelationship = ({ teiId, onMutate, onSuccess }: Props) => {
const queryClient = useQueryClient();
const queryKey: string = useFeature(FEATURES.exportablePayload) ? 'relationships' : 'instances';
const dataEngine = useDataEngine();
const { show: showSnackbar } = useAlert(
i18n.t('An error occurred while adding the relationship'),
Expand All @@ -36,11 +39,12 @@ export const useAddRelationship = ({ teiId, onMutate, onSuccess }: Props) => {
onError: (_, requestData) => {
showSnackbar();
const apiRelationshipId = requestData.clientRelationship.relationship;
const currentRelationships = queryClient.getQueryData([ReactQueryAppNamespace, 'relationships', teiId]);
const apiResponse = queryClient.getQueryData([ReactQueryAppNamespace, 'relationships', teiId]);
const apiRelationships = handleAPIResponse(REQUESTED_ENTITIES.relationships, apiResponse);

if (!currentRelationships?.instances) return;
if (apiRelationships.length === 0) return;

const newRelationships = currentRelationships.instances.reduce((acc, relationship) => {
const newRelationships = apiRelationships.reduce((acc, relationship) => {
if (relationship.relationship === apiRelationshipId) {
return acc;
}
Expand All @@ -50,26 +54,27 @@ export const useAddRelationship = ({ teiId, onMutate, onSuccess }: Props) => {

queryClient.setQueryData(
[ReactQueryAppNamespace, 'relationships', teiId],
{ instances: newRelationships },
{ [queryKey]: newRelationships },
);
},
onMutate: (...props) => {
onMutate && onMutate(...props);
const { clientRelationship } = props[0];
if (!clientRelationship) return;

queryClient.setQueryData([ReactQueryAppNamespace, 'relationships', teiId], (oldData) => {
const instances = oldData?.instances || [];
const updatedInstances = [clientRelationship, ...instances];
return { instances: updatedInstances };
queryClient.setQueryData([ReactQueryAppNamespace, 'relationships', teiId], (apiResponse) => {
const apiRelationships = handleAPIResponse(REQUESTED_ENTITIES.relationships, apiResponse);
const updatedInstances = [clientRelationship, ...apiRelationships];
return { [queryKey]: updatedInstances };
});
},
onSuccess: async (apiResponse, requestData) => {
const apiRelationshipId = apiResponse.bundleReport.typeReportMap.RELATIONSHIP.objectReports[0].uid;
const currentRelationships = queryClient.getQueryData([ReactQueryAppNamespace, 'relationships', teiId]);
if (!currentRelationships?.instances) return;
const apiRelationships = handleAPIResponse(REQUESTED_ENTITIES.relationships, currentRelationships);
if (apiRelationships.length === 0) return;

const newRelationships = currentRelationships.instances.map((relationship) => {
const newRelationships = apiRelationships.map((relationship) => {
if (relationship.relationship === apiRelationshipId) {
return {
...relationship,
Expand All @@ -81,7 +86,7 @@ export const useAddRelationship = ({ teiId, onMutate, onSuccess }: Props) => {

queryClient.setQueryData(
[ReactQueryAppNamespace, 'relationships', teiId],
{ instances: newRelationships },
{ [queryKey]: newRelationships },
);
onSuccess && onSuccess(apiResponse, requestData);
},
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// @flow
import { useMemo } from 'react';
import { handleAPIResponse, REQUESTED_ENTITIES } from 'capture-core/utils/api';
import { useApiDataQuery } from '../../../../utils/reactQueryHelpers';
import type { InputRelationshipData, RelationshipTypes } from '../Types';
import { determineLinkedEntity } from '../RelationshipsWidget/useGroupedLinkedEntities';
Expand Down Expand Up @@ -33,12 +34,13 @@ export const useRelationships = ({ entityId, searchMode, relationshipTypes }: Pr
query,
{
enabled: !!entityId,
select: ({ instances }: any) => {
if (!relationshipTypes?.length || !instances?.length) {
select: (apiResponse: any) => {
const apiRelationships = handleAPIResponse(REQUESTED_ENTITIES.relationships, apiResponse);
if (!relationshipTypes?.length || !apiRelationships?.length) {
return [];
}

return instances.reduce((acc, relationship) => {
return apiRelationships.reduce((acc, relationship) => {
const relationshipType = relationshipTypes
.find(relType => relType.id === relationship.relationshipType);
if (!relationshipType) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,6 @@ export const getEventListData = async ({
querySingleResource: QuerySingleResource,
}) => {
const mainColumns = getMainColumns(columnsMetaForDataFetching);

const { eventContainers, pagingData, request } = await getEvents(
createApiQueryArgs(queryArgs, mainColumns, categoryCombinationId),
absoluteApiPath,
Expand Down
Loading
Loading