-
-
Notifications
You must be signed in to change notification settings - Fork 72
feat: Nutrient modifiers (Get/Send product APIs) #1042
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
base: master
Are you sure you want to change the base?
Conversation
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.
Hi @g123k!
I have a doubt about the relevancy of using the same modifier for all PerSize
s of a given nutrient:
- I'm not sure it's always true
- That makes the code harder to read (the keys of the private maps being different), with no added value
Please also read my other comments.
lib/src/model/nutriments.dart
Outdated
_valueMap[_getTag(nutrient, perSize)] = value; | ||
_modifierMap[nutrient.offTag] = modifier; |
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.
Could a modifier be different for different PerSize
s?
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'm not sure, but I can support it
test/api_get_product_test.dart
Outdated
expect(nutriments.getModifier(Nutrient.energyKJ), isNull); | ||
expect(nutriments.getModifier(Nutrient.energyKCal), isNull); | ||
expect(nutriments.getModifier(Nutrient.sugars), isNull); | ||
expect(nutriments.getModifier(Nutrient.salt), isNull); | ||
expect(nutriments.getModifier(Nutrient.fiber), isNull); | ||
expect(nutriments.getModifier(Nutrient.fat), isNull); | ||
expect(nutriments.getModifier(Nutrient.saturatedFat), isNull); | ||
expect(nutriments.getModifier(Nutrient.proteins), isNull); |
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.
Would be more fun if at least one modifier was not null. Or get rid of that test.
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.
My goal was to test a product with no modifier.
But I can remove it, no pb
test/api_get_product_test.dart
Outdated
@@ -1430,4 +1432,81 @@ void main() { | |||
expect(productResult.product!.dataQualityWarningsTags, isNull); | |||
}); | |||
}); | |||
|
|||
test('check nutrients modifier (set)', () async { |
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.
Can't understand the title, more specifically: "(set)".
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 forgot to include a few changes.
Indeed, it wasn't understandable
test/api_get_product_test.dart
Outdated
expect(nutriments.getModifier(Nutrient.proteins), isNull); | ||
}); | ||
|
||
test('check nutrients modifier (without)', () async { |
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.
Can't understand the title, more specifically: "(without)".
test/api_save_product_test.dart
Outdated
@@ -513,5 +518,97 @@ Like that: | |||
expect(product.noNutritionData, isFalse); | |||
expect(product.nutriments, isNotNull); | |||
}); | |||
|
|||
test('Nutriments modifiers', () async { |
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.
The test makes sense, but as you don't save anything (to the server) I guess it should be somewhere else. Or commented as "not really saving anything".
And another test actually saving to the server would make sense.
Co-authored-by: monsieurtanuki <[email protected]>
Co-authored-by: monsieurtanuki <[email protected]>
Hi @monsieurtanuki! Thanks for your review. |
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.
Thank you @g123k for your work!
My bad: unfortunately I was a bit wrong in my review, as I didn't get that the server provided only a single modifier for a nutrient, regardless of the PerSize
.
Therefore it would make much more sense to remove the PerSize
parameter from the _getModifierTag
method. Good thing that this method does exist, though, as we just have to remove the parameter and let the syntactic analyzer detect the related errors.
Please have a look at my comments regarding tests: in one case you play the same tests on the same product with the same modifiers. As a consequence you don't really test the "save modifier' feature, you test that it did work once. I suggest that you always change the modifier values, and that you include the null
modifier in the "save modifier" test, as it's really a use case.
/// Returns the modifier key for a [nutrient] | ||
String _getModifierTag(final Nutrient nutrient, final PerSize perSize) => | ||
'${nutrient.offTag}_${perSize.offTag}'; | ||
|
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.
Oh I'm sorry, I've just had a look again at the data provided by the server, I didn't get that the modifier was meant for a nutrient, not for nutrient + persize.
Anyway, that's better to use a method, the impact would be minor on the rest of the code.
/// Returns the modifier key for a [nutrient] | |
String _getModifierTag(final Nutrient nutrient, final PerSize perSize) => | |
'${nutrient.offTag}_${perSize.offTag}'; | |
/// Returns the modifier local key map for a [nutrient] | |
String _getModifierTag(final Nutrient nutrient) => nutrient.offTag; |
_modifierMap[_getModifierTag(nutrient, PerSize.serving)] = modifier; | ||
_modifierMap[_getModifierTag(nutrient, PerSize.oneHundredGrams)] = | ||
modifier; |
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.
_modifierMap[_getModifierTag(nutrient, PerSize.serving)] = modifier; | |
_modifierMap[_getModifierTag(nutrient, PerSize.oneHundredGrams)] = | |
modifier; | |
_modifierMap[_getModifierTag(nutrient] = modifier; |
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.
The problem is that the server may have a different value per serving AND 100g.
In this case, the last one will win.
Solution 1: keep the current behavior
Solution 2: save the value depending on the PerSize
value of the array.
final Map<String, NutrientModifier> _modifierMap = | ||
<String, NutrientModifier>{}; |
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.
Oops, I did it again: if I read the comment above it makes sense to store null
values.
final Map<String, NutrientModifier> _modifierMap = | |
<String, NutrientModifier>{}; | |
final Map<String, NutrientModifier?> _modifierMap = | |
<String, NutrientModifier?>{}; |
NutrientModifier? getModifier( | ||
final Nutrient nutrient, final PerSize perSize) => | ||
_modifierMap[_getModifierTag(nutrient, perSize)]; |
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.
NutrientModifier? getModifier( | |
final Nutrient nutrient, final PerSize perSize) => | |
_modifierMap[_getModifierTag(nutrient, perSize)]; | |
NutrientModifier? getModifier(final Nutrient nutrient) => | |
_modifierMap[_getModifierTag(nutrient)]; |
_valueMap.remove(_getTag(nutrient, perSize)); | ||
_modifierMap[_getModifierTag(nutrient, perSize)] = modifier!; |
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.
_valueMap.remove(_getTag(nutrient, perSize)); | |
_modifierMap[_getModifierTag(nutrient, perSize)] = modifier!; | |
_valueMap[_getTag(nutrient, perSize)] = null; | |
_modifierMap[_getModifierTag(nutrient)] = modifier; |
expect( | ||
nutriments.getModifier(Nutrient.salt, PerSize.oneHundredGrams), | ||
NutrientModifier.lessThan, | ||
); | ||
expect( | ||
nutriments.getModifier(Nutrient.sodium, PerSize.oneHundredGrams), | ||
NutrientModifier.lessThan, | ||
); |
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.
expect( | |
nutriments.getModifier(Nutrient.salt, PerSize.oneHundredGrams), | |
NutrientModifier.lessThan, | |
); | |
expect( | |
nutriments.getModifier(Nutrient.sodium, PerSize.oneHundredGrams), | |
NutrientModifier.lessThan, | |
); | |
expect(nutriments.getModifier(Nutrient.salt), NutrientModifier.lessThan); | |
expect(nutriments.getModifier(Nutrient.sodium), NutrientModifier.lessThan); |
expect( | ||
nutriments.getModifier(Nutrient.fiber, PerSize.oneHundredGrams), | ||
NutrientModifier.notProvided, | ||
); | ||
expect( | ||
nutriments.getValue(Nutrient.fiber, PerSize.oneHundredGrams), | ||
isNull, | ||
); | ||
expect( | ||
nutriments.getValue(Nutrient.fiber, PerSize.serving), | ||
isNull, | ||
); |
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.
expect( | |
nutriments.getModifier(Nutrient.fiber, PerSize.oneHundredGrams), | |
NutrientModifier.notProvided, | |
); | |
expect( | |
nutriments.getValue(Nutrient.fiber, PerSize.oneHundredGrams), | |
isNull, | |
); | |
expect( | |
nutriments.getValue(Nutrient.fiber, PerSize.serving), | |
isNull, | |
); | |
expect(nutriments.getModifier(Nutrient.fiber), NutrientModifier.notProvided); | |
expect(nutriments.getValue(Nutrient.fiber), isNull); | |
expect(nutriments.getValue(Nutrient.fiber), isNull); |
@@ -406,6 +406,11 @@ Like that: | |||
nutriments.getValue(nutrient, perSize), | |||
reason: 'should be the same values for $nutrient', | |||
); | |||
expect( | |||
nutriments.getModifier(nutrient, perSize), |
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.
nutriments.getModifier(nutrient, perSize), | |
nutriments.getModifier(nutrient), |
|
||
test('Nutriments modifiers (server call)', () async { | ||
Product product = Product( | ||
barcode: '1234567890123', |
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.
The problem is that you're always setting the same values for the same product. So if you're lucky it may work the first time, and if you're not lucky for some reason it doesn't work anymore (e.g. server somehow changed) and you won't be able to see that.
You may rather start with a getProductV3
, and change each modifier.
result.product!.nutriments?.getModifier( | ||
Nutrient.proteins, | ||
PerSize.oneHundredGrams, | ||
), | ||
NutrientModifier.notProvided, |
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.
Rather keeps the nutrient x modifier in a map, then save the product, then get the product, then check that the product matches the map.
Hi @g123k! I've just run some tests in PROD on the website, and I don't think there's such thing as "modifier for serving" - it looks like there's only one modifier for both serving and 100g, something like The test was on "salt" for https://world.openfoodfacts.org/api/v3/product/8712000050368/?fields=nutriments
"salt": 0.01,
"salt_100g": 0.01,
"salt_modifier": "\u003C",
"salt_serving": 0.025,
"salt_unit": "g",
"salt_value": 0.01,
"salt": 0.026,
"salt_100g": 0.0104,
"salt_modifier": "\u003C",
"salt_serving": 0.026,
"salt_unit": "g",
"salt_value": 0.026,
"salt": 0.027,
"salt_100g": 0.0108,
"salt_modifier": "\u003E",
"salt_serving": 0.027,
"salt_unit": "g",
"salt_value": 0.027,
"salt": 0.01,
"salt_100g": 0.01,
"salt_modifier": "\u003C",
"salt_serving": 0.025,
"salt_unit": "g",
"salt_value": 0.01, |
Hi everyone!
Finally, a working solution for nutrient modifiers, when we get / save a product.
On the GET API, we have
_modifier
fields.On the POST one, we must include the modifier character within the value.