-
Notifications
You must be signed in to change notification settings - Fork 70
feat(sync-actions): add changeMasterVariant action generation
#304
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
Conversation
Codecov Report
@@ Coverage Diff @@
## master #304 +/- ##
=========================================
+ Coverage 98.11% 98.42% +0.3%
=========================================
Files 74 74
Lines 1647 1652 +5
Branches 363 366 +3
=========================================
+ Hits 1616 1626 +10
+ Misses 25 23 -2
+ Partials 6 3 -3
Continue to review full report at Codecov.
|
tdeekens
left a comment
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.
Just some explanatory comments.
| id: objectToRemove.id, | ||
| }), | ||
| [REMOVE_ACTIONS]: ({ id }, index) => { | ||
| // The master variant can not be removed |
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 can't remove the master variant. This was done before by treating it as in the masterVariant property which would non run through the array detection process.
| ) | ||
| actions = actions.concat(mActions) | ||
| } | ||
| const { variants } = diff |
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.
...all this duplication of handling changes goes away.
| .concat(addPriceActions) | ||
| } | ||
|
|
||
| export function generateChangeMasterVariantAction (oldObj, newObj) { |
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.
Specific handler to create the action.
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 it possible to maintain the consistency with previous similar methods by having something like actionsMapMasterVariant or so
| const extractMasterVariantId = (fromObj) => { | ||
| const variants = Array.isArray(fromObj.variants) ? fromObj.variants : [] | ||
|
|
||
| return variants[0] ? variants[0].id : undefined |
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 feels a bit wonky but eases the code further down. Generally caused by variants not having to be set and hence the id not necessarily having to be present.
| } | ||
| } | ||
|
|
||
| function moveMasterVariantsIntoVariants (before, 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.
This is the hook we pass in to process data before it hits the differ.
|
|
||
| // diff 'em | ||
| const diffed = diff(before, now) | ||
| const [ |
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 function feels generally flaky to me. It shuffles the arguments of new and before around quite a bit and I wonder if that's the intention or actually a bug which we then work around when consuming the diff.
| proccedBefore, processedNow, | ||
| ] = applyOnBeforeDiff(before, now, onBeforeDiff) | ||
|
|
||
| const diffed = differ(proccedBefore, processedNow) |
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 renamed the arg. The library seems to call a diff the same as the differ creating a diff which confused me quite a bit with this argument shuffling.
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.
Just a nitpick: maybe you should rename the variable to processedBefore, and also on line 21
| // When adding a new element you don't need the oldObj | ||
| config[ADD_ACTIONS](newObj[key][index]), | ||
| if (config[ADD_ACTIONS] && isCreateAction(arrayDelta, index)) { | ||
| const actionGenerator = config[ADD_ACTIONS] |
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.
Just general simplications in naming things here. The addition is to pass the index to the callback so it has the information.
| id: '123', | ||
| masterVariant: { | ||
| sku: 'v1', key: 'v2', attributes: [{ name: 'foo', value: 'new value' }], | ||
| id: 1, sku: 'v1', key: 'v2', attributes: [{ name: 'foo', value: 'new value' }], |
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 below felt like it didn't want to test for the creation of the masterVariant. However if there is no id it should be created.
| ], | ||
| } | ||
| describe('without master variant in `now`', () => { | ||
| describe('with master variant in `before`', () => { |
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 tries to BDD down the tests
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.
Personally I think the test description is not explanatory enough
A sample below
Actions › without master variant in `now` › with master variant in `before` › should not generate update action to remove master variant
compared to something like this
Actions › should build `addVariant` action
No hard opinion on this though. Usable for me
wizzy25
left a comment
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.
Thanks for this. feels a bit hacky but looks good nonetheless. I dropped some comments
And the failing build is a build issue. I will push a commit to this branch to fix it
| const oldMasterVariantId = extractMasterVariantId(oldObj) | ||
|
|
||
| // Previosuly no master variant existed | ||
| if (!oldMasterVariantId && newMasterVariantId) |
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 don't understand this part; there will always be a [previous] master variant
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 wasn't 100% about a. the cases here and b. how to guard against unexpected things. Moreover, the tests had cases where no masterVariant in before existed if I am not mistaken. I guess we could
- Both have an id and in
nowthe id changed? - Only
nowhas an id (aselse if-branch)
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.
However, then the loads of tests fail and I assume it's due some "flawed" fixture data maybe.
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 went through the code again and I think I like your implementation as it is.
PS. You should also bump the test coverage 😉
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.
Voila, a completely untested module now tested. I am begging for the % to increase enough. It was the only "red" one in the coverage report e1a5d56
| proccedBefore, processedNow, | ||
| ] = applyOnBeforeDiff(before, now, onBeforeDiff) | ||
|
|
||
| const diffed = differ(proccedBefore, processedNow) |
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.
Just a nitpick: maybe you should rename the variable to processedBefore, and also on line 21
remove package-lock file in int tests because we don't want to cache the dependencies as we always want to test the latest versions
|
I verified with the platform that a masterVariant: {
//No id
name: 'foo',
prices: {},
}are "flawed" as they won't happen. The |
ac4b76a to
5c6c66e
Compare
|
So from my side this is ready. To be released either on the @latest or @next tag. We can have it on @next first maybe, keep this open and test it in a branch in the MC? |
| const oldMasterVariantId = extractMasterVariantId(oldObj) | ||
|
|
||
| // Previosuly no master variant existed | ||
| if (!oldMasterVariantId && newMasterVariantId) |
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.
Referring to the comment above and your statement too: this won't happen due to the workings of our API and the implicit generation of a masterVariant.
|
|
||
| expect(actions).toEqual([ | ||
| { action: 'addVariant', attributes: [{ name: 'foo', value: 'bar' }], id: 2, sku: 'v1' }, | ||
| { action: 'changeMasterVariant', variantId: 2 }, |
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 should also delete the variant with id 1, shouldn't it?
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 is one of the semantics I wasn't 100% sure about. It felt safer not to remove than to just throw it away. I'll see what other reviewers say.
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 the goal is that now = before + all update actions, then we have to remove the variant. Otherwise we have one variant too much 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.
Yeap. You're right. I am thinking how we can bake it in.
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.
Yeap, I agree with us removing the update actions.
|
|
||
| expect(actions).toEqual([ | ||
| { action: 'changeMasterVariant', variantId: 1 }, | ||
| { action: 'setAttribute', name: 'foo-2', value: 'bar-3', variantId: 2 }, |
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.
👍
hisabimbola
left a comment
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.
Thanks for the refactor, I added minor comments that I think we can discuss about.
| if (newMasterVariantId && oldMasterVariantId !== newMasterVariantId) | ||
| return createChangeMasterVariantAction(newMasterVariantId) | ||
|
|
||
| return null |
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 think we can make this more consistent by returning [] if no actions is generated
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 can I didn't as it will and should always just return one action so the array there felt odd but I'll change it for consistency.
| .concat(addPriceActions) | ||
| } | ||
|
|
||
| export function generateChangeMasterVariantAction (oldObj, newObj) { |
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 it possible to maintain the consistency with previous similar methods by having something like actionsMapMasterVariant or so
| allActions.push(mapActionGroup('variants', () => | ||
| productActions.actionsMapVariants(diff, oldObj, newObj))) | ||
|
|
||
| const changeMasterVariantAction = |
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.
Buidling from previous comment, if we return empty array here, there will not be any need to check if value is present
|
|
||
| return [ | ||
| hasMasterVariant(before) ? move(before) : before, | ||
| hasMasterVariant(now) ? move(now) : 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.
I am not sure how often we will have this scenario but what if the masterVariant object is not passed in the now object.
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 is what can not happen in theory. Whenever a project has been created implicitly a master variant will be added. Theoretically we could remove these checks but our test data is sometimes a bit messy. E.g. not having a masterVariant in now of before.
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 mean from the user end, I think what you describing is the before(what already exists in the CTP)
If we understand each other 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.
Hm, so I think there are a couple of invariants in this discussions
- The
beforecan be assumed to always have a master variant - The
nowshould also always have a master variant
2.1 If it doesn't we can assume that the first variant ought to be the new master variant
If non the the cases hold be should either throw or do nothing which breaks things. By not breaking we can "hope" that the before (if without master variant) that the now also doesn't have one. Cause then we will not change the master variant. Which is non destructive in that sense.
| ], | ||
| } | ||
| describe('without master variant in `now`', () => { | ||
| describe('with master variant in `before`', () => { |
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.
Personally I think the test description is not explanatory enough
A sample below
Actions › without master variant in `now` › with master variant in `before` › should not generate update action to remove master variant
compared to something like this
Actions › should build `addVariant` action
No hard opinion on this though. Usable for me
| variants: [], | ||
| } | ||
|
|
||
| it('should not generate update action to remove master variant', |
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.
Building from comment above, this test fails
describe('no mastervariant in `now` obj', () => {
const before = {
id: '123',
version: 1,
masterVariant: {
id: 1,
sku: 'v1',
attributes: [{ name: 'foo', value: 'bar' }],
},
variants: [],
}
const now = {
id: '123',
// <-- no masterVariant
variants: [
{
id: 2,
sku: 'v1',
attributes: [{ name: 'foo', value: 'bar' }],
},
],
}
it('should generate a addVariant action', () => {
const actions = productsSync.buildActions(now, before)
expect(actions).toEqual([
{ action: 'addVariant', sku: 'v1', attributes: [{ name: 'foo', value: 'bar' }] },
])
})
})
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, cause that test lives in the grey zone mentioned above. What do we assume to be intended here? You had a master variant and now you don't want one. Which basically is breaking with our invariants.
Moreover, the test fails as the id 1 doesn't exist anymore.
- So should it be removed? (
1)? - Should
2be implicitly be the new master?
Did use user intend to do that? - Should we keep both?
I think with now you declare where you want to end up at. This test is flawed cause the state the user wants to end up in is not valid and we can only assume what was meant.
|
|
||
| expect(actions).toEqual([ | ||
| { action: 'addVariant', attributes: [{ name: 'foo', value: 'bar' }], id: 2, sku: 'v1' }, | ||
| { action: 'changeMasterVariant', variantId: 2 }, |
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.
Yeap, I agree with us removing the update actions.
| id: '123', | ||
| version: 1, | ||
| masterVariant: { | ||
| id: 2, |
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.
masterVariant cannot have a variantId of 2. I think it's always 1
Cc @yanns
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 can have an id of 2 otherwise you'd never be able to move the master variant. The id is immutable and sequentially assigned.
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, okay, good to know that variantId does not change.
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 confirm when @tdeekens says.
Master variant id is not always 1. The variant id is the invariant 🤣
| @@ -0,0 +1,89 @@ | |||
| import createMapActionGroup from '../../src/utils/create-map-action-group' | |||
|
|
|||
| describe('', () => { | |||
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 am not sure why this is empty string.
using createMapActionGroup might work since that's the method we are testing.
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.
Ups.
|
I also think we should add more comments in the code to explain a couple of decisions and why. I find the |
You should have seen my face yesterday. I got a bold spot on my head from scratching 😄 |
|
Regarding the BDD on a comment above I can't comment on
This gives context as in Given, When Then. Which I think is nicer to follow along as it sets up the context towards what the then is. Otherwise you have to peel the Given and When off the data which needs more knowledge. |
|
@hisabimbola I went over things a little. We now remove the variant if if doesn't exist anymore in variants. I also changed the test structure a bit to hopefully be nicer to read along. |
|
After talking to @hisabimbola we decided not to have validation on this PR. Changes it might be a breaking change to loads of consumers of the library. Furthermore, our test data does not always contain all required fields. This is ready then for a last round of review. @hisabimbola you want to publish it on the |
|
|
||
| const changeMasterVariantActions = | ||
| productActions.actionsMapMasterVariant(oldObj, newObj) | ||
| if (changeMasterVariantActions.length > 0) |
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 think we don't need the condition again, if it's empty array, it's fine for us to push it. it gets flattened out anyways below
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.
Ah ok. Thought better to be safe than sorry. Here you go f8a6616
hisabimbola
left a comment
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.

Summary
This changes the internal workings of sync-actions to allow for automatic generation of a
changeMasterVariantaction.Description
The sync-actions package treated the
masterVariantseparately when calculating update actions. This comes with a couple of down sides:if (masterVariant)forEach(variants)The latter is the issue this tries to solve while also enabling the the generation of a
changeMasterVariantupdate action. Previously, whenever themasterVariantchanged the library would assume the whole variant would need creation and would create a update actions accordingly. While also creating update actions to delete the variant which actually became the new master variant.The problem with this approach is that it is a bug and a feature at the same time. However, more of a bug than a feature in my opinion.
This feels unnecessary as it should just cover the following cases
masterVariantwas changed but none of the attributes changed and the variant previously existedmasterVariantwas changed some attributes changed and the variant existed previsoulymasterVariantdid not exist (must be new product) and all attributes are newTo achieve this and to minimize the amount of needed book keeping in the process I move the
masterVariantinto thevariantsarray at the first position.id,name, etc (sku and key don't seem implemented yet)Todo