-
Notifications
You must be signed in to change notification settings - Fork 7
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
Conversation
3713d84
to
2128be7
Compare
Tests were failing because I accidentally missed a commit after a rebase onto dev, cherry-picked it back in now |
There was a problem hiding this 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>} | ||
*/ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Loving all the JsDocs! 😍
const mapping = new DataServiceMapping(); | ||
mapping.dataElementMapping = await this.resolveDataElements(dataElements, orgUnits); | ||
mapping.dataGroupMapping = await this.resolveDataGroups(dataGroups); | ||
return mapping; |
There was a problem hiding this comment.
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?
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); |
There was a problem hiding this comment.
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
// 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); | ||
}; |
There was a problem hiding this comment.
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 👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Reworked, this is gone
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, | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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?
There was a problem hiding this comment.
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
@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. |
There was a problem hiding this 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) { |
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
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...)
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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}`, |
There was a problem hiding this comment.
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 👍
There was a problem hiding this 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 👍
Issue #: RN-603
Replaces #4081
Changes:
Screenshots:
https://linear.app/bes/issue/RN-603#comment-2a6c52be