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

RN-603 de dg multi mapping #4158

Merged
merged 20 commits into from
Sep 19, 2022
Merged

RN-603 de dg multi mapping #4158

merged 20 commits into from
Sep 19, 2022

Conversation

IgorNadj
Copy link
Contributor

@IgorNadj IgorNadj commented Sep 9, 2022

Issue #: RN-603

Replaces #4081

Changes:

  • Allow mapping a Data Element to multiple data services based on country.

Screenshots:

https://linear.app/bes/issue/RN-603#comment-2a6c52be

Base automatically changed from rn-625-disable-1-many-mapping-dhis to dev September 11, 2022 23:48
@IgorNadj IgorNadj force-pushed the rn-603-de-dg-multi-mapping branch from 3713d84 to 2128be7 Compare September 11, 2022 23:54
@IgorNadj
Copy link
Contributor Author

Tests were failing because I accidentally missed a commit after a rebase onto dev, cherry-picked it back in now

Copy link
Collaborator

@rohan-bes rohan-bes left a comment

Choose a reason for hiding this comment

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

Nice work @IgorNadj, really love how you broke out the dataService resolution logic into a separate class.

So they were cool going with a separate country for UNFPA Warehouse? That's great, makes life easier for us 😅

Couple of code style comments, but nothing major so pre-approving

* @param {DataSource[]} dataSources
* @param {Entity} [orgUnit]
* @returns {Promise<DataServiceMapping>}
*/
Copy link
Collaborator

Choose a reason for hiding this comment

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

Loving all the JsDocs! 😍

Comment on lines 54 to 57
const mapping = new DataServiceMapping();
mapping.dataElementMapping = await this.resolveDataElements(dataElements, orgUnits);
mapping.dataGroupMapping = await this.resolveDataGroups(dataGroups);
return mapping;
Copy link
Collaborator

Choose a reason for hiding this comment

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

It's not possible to make properties readonly in javascript is it? This class feels like it ought to have immutable data...
Regardless I feel it reads a touch nice to do this without assigning the properties explicitly?

Suggested change
const mapping = new DataServiceMapping();
mapping.dataElementMapping = await this.resolveDataElements(dataElements, orgUnits);
mapping.dataGroupMapping = await this.resolveDataGroups(dataGroups);
return mapping;
const dataElementMapping = await this.resolveDataElements(dataElements, orgUnits);
const dataGroupMapping = await this.resolveDataGroups(dataGroups);
return new DataServiceMapping(dataElementMapping, dataGroupMapping);

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah I was thinking of just waiting until this is typescript, then can lean on that for readonly keyword

Comment on lines 116 to 120
// If they all resolve to the same data service, this is fine
const resolveToTheSameDataService = (mappingA, mappingB) => {
if (mappingA.service_type !== mappingB.service_type) return false;
return isequal(mappingA.service_config, mappingB.service_config);
};
Copy link
Collaborator

Choose a reason for hiding this comment

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

Up to you, but might be nice to move this function out to somewhere external as it doesn't require any local state 👍

Copy link
Contributor Author

@IgorNadj IgorNadj Sep 15, 2022

Choose a reason for hiding this comment

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

Reworked, this is gone

Comment on lines 115 to 158
if (deMappings.length > 1) {
// If they all resolve to the same data service, this is fine
const resolveToTheSameDataService = (mappingA, mappingB) => {
if (mappingA.service_type !== mappingB.service_type) return false;
return isequal(mappingA.service_config, mappingB.service_config);
};
for (const mappingA of deMappings) {
for (const mappingB of deMappings) {
if (mappingA === mappingB) continue;
if (!resolveToTheSameDataService(mappingA, mappingB)) {
throw new Error(
`Conflicting mappings found for Data Source ${
dataElement.code
} when fetching for orgUnits ${orgUnits.map(o => o.code).join(',')}`,
);
}
}
}
// all data element mappings are the same, use first
const [deMapping] = deMappings;
resolved.push({
dataSource: dataElement,
service_type: deMapping.service_type,
config: deMapping.service_config,
});
continue;
}

if (deMappings.length === 1) {
const [deMapping] = deMappings;
resolved.push({
dataSource: dataElement,
service_type: deMapping.service_type,
config: deMapping.service_config,
});
continue;
}

// No mappings, use default
resolved.push({
dataSource: dataElement,
service_type: dataElement.service_type,
config: dataElement.config,
});
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
if (deMappings.length > 1) {
// If they all resolve to the same data service, this is fine
const resolveToTheSameDataService = (mappingA, mappingB) => {
if (mappingA.service_type !== mappingB.service_type) return false;
return isequal(mappingA.service_config, mappingB.service_config);
};
for (const mappingA of deMappings) {
for (const mappingB of deMappings) {
if (mappingA === mappingB) continue;
if (!resolveToTheSameDataService(mappingA, mappingB)) {
throw new Error(
`Conflicting mappings found for Data Source ${
dataElement.code
} when fetching for orgUnits ${orgUnits.map(o => o.code).join(',')}`,
);
}
}
}
// all data element mappings are the same, use first
const [deMapping] = deMappings;
resolved.push({
dataSource: dataElement,
service_type: deMapping.service_type,
config: deMapping.service_config,
});
continue;
}
if (deMappings.length === 1) {
const [deMapping] = deMappings;
resolved.push({
dataSource: dataElement,
service_type: deMapping.service_type,
config: deMapping.service_config,
});
continue;
}
// No mappings, use default
resolved.push({
dataSource: dataElement,
service_type: dataElement.service_type,
config: dataElement.config,
});
if (deMappings.length === 0) {
// No mappings, use default
resolved.push({
dataSource: dataElement,
service_type: dataElement.service_type,
config: dataElement.config,
});
continue;
}
// If they all resolve to the same data service, this is fine
const resolveToTheSameDataService = (mappingA, mappingB) => {
if (mappingA.service_type !== mappingB.service_type) return false;
return isequal(mappingA.service_config, mappingB.service_config);
};
for (const mappingA of deMappings) {
for (const mappingB of deMappings) {
if (mappingA === mappingB) continue;
if (!resolveToTheSameDataService(mappingA, mappingB)) {
throw new Error(
`Conflicting mappings found for Data Source ${
dataElement.code
} when fetching for orgUnits ${orgUnits.map(o => o.code).join(',')}`,
);
}
}
}
// all data element mappings are the same, use first
const [deMapping] = deMappings;
resolved.push({
dataSource: dataElement,
service_type: deMapping.service_type,
config: deMapping.service_config,
});

Optional, but this cuts down the nesting and line count a bit if you like?

Copy link
Contributor Author

@IgorNadj IgorNadj Sep 15, 2022

Choose a reason for hiding this comment

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

Reworked, most of this is gone

@IgorNadj
Copy link
Contributor Author

@rohan-bes I added new commits to take care of the pull-with-multiple-countries problem. We discussed extracting this out to a class like PullGrouper but it seemed simple enough to keep it inline in DataBroker.

@IgorNadj IgorNadj requested a review from rohan-bes September 15, 2022 04:49
Copy link
Collaborator

@rohan-bes rohan-bes left a comment

Choose a reason for hiding this comment

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

Requesting a change to have each uniquePull just pull data for the dataElements/countries that it's mapped to. But also totally happy to talk it through if this is tricky to support for some reason I'm missing.

Aside from that it's looking great!

@@ -303,4 +298,71 @@ export class DataBroker {
dataServiceMapping,
};
}

async getPulls(dataSources, orgUnitCodes) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

We had discussed breaking this out into a separate class right? I'm not too fussed though, feel it's okay being here as well 👍

async getPulls(dataSources, orgUnitCodes) {
const orgUnits = await this.models.entity.find({ code: orgUnitCodes });

// Note: each service will pull for ALL org units and ALL data sources.
Copy link
Collaborator

Choose a reason for hiding this comment

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

Is there a reason we can't fetch for just the countries/data elements in each mapping? We work out which countries and dataElements/groups belong to each along the way, so we'd just need to combine them when building the uniqueMapping?

It feels a little odd to pull for dataElements and countries that we know aren't in the data service (and also could create issues if they actually are in the data service but Tupaia isn't configured to fetch them from there...)

Copy link
Contributor Author

@IgorNadj IgorNadj Sep 16, 2022

Choose a reason for hiding this comment

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

Discussed.

	ORIGINAL FETCH:
		orgUnits: FJ, TO
		de: DrugA, DrugB

	MAPPING:
		DrugA & FJ => superset
		DrugB & FJ => dhis (dhis-regional)
		DrugA & TO => dhis (dhis-tonga)
		DrugB & TO => dhis (dhis-tonga)


	UNIQUE FETCHES:
		superset:
			orgUnits: FJ
			de: DrugA
			mapping:
				DrugA: superset
		dhis:
			orgUnits: TO, FJ
			de: DrugA, DrugB
			mapping:
				DrugA: dhis-tonga
				DrugB: dhis-regional
				DrugB: dhis-tonga

The issue is that our DataServiceMapping class and mapping resolution doesn't play nice with double ups like DrugB in dhis there.

Options are to rework the mapping, or to leave it until we do the full implementation which would drop the need for a DataServiceMapping class at all, make all this trivial. I say we leave it.

* @param {DataServiceMapping} other
* @returns boolean
*/
equals(other) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

This warms an ex-Java dev's heart 😄 Bonus points for adding if (this === other) return true; at the start 👍

});
continue;
throw new Error(
`Multiple mappings found for Data Element ${dataElement.code} for country ${countryCode}`,
Copy link
Collaborator

Choose a reason for hiding this comment

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

I wonder if this should be a constraint in the database? It seems like we'd never there to be multiple dataElementDataservices with the same country_code and data_element_code. Optional though 👍

Copy link
Collaborator

@rohan-bes rohan-bes left a comment

Choose a reason for hiding this comment

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

Good discussion on this one @IgorNadj. Lets get the full data services refactor done and then this whole area of code will simplify itself 👍

@IgorNadj IgorNadj merged commit 7b15bde into dev Sep 19, 2022
@IgorNadj IgorNadj deleted the rn-603-de-dg-multi-mapping branch September 19, 2022 00:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants