-
Notifications
You must be signed in to change notification settings - Fork 76
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
Mark soft conflict if not contiguous with trunk #1187
Conversation
I think this is a pretty interesting idea. I don't think it's necessary for this particular issue, but I think it's something we should keep in mind for the future. |
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 copied this branch tracking logic directly from Frontend, and there's probably more there than necessary for backend, but I just wanted to see it in action.
I'm going to pare back aspects of the Branch
class and related logic to only what Backend needs, which I think will make the Backend code clearer. However, even pared back, I like the Branch
class here. It moves the branch logic into a separate part of the code from the rest of the getWithConflictDetails()
function, which is already fairly complex. I also think it's very possible that we'll want to grow the Branch
class in the future.
Before I push my commit, I thought I'd leave a few comments about specific things I'm changing.
lib/data/entity.js
Outdated
@@ -417,6 +475,26 @@ const getWithConflictDetails = (defs, audits, relevantToConflict) => { | |||
|
|||
const relevantBaseVersions = new Set(); | |||
|
|||
// build up branches | |||
const branches = new Map(); | |||
for (const version of defs) { |
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 ends up looping over defs
twice, which I don't think we need to do. I think we can build up result
and branches
in the same loop, as no version/def needs to look ahead to a later version, including for branch information.
lib/data/entity.js
Outdated
if (existingBranch == null) { | ||
const newBranch = new Branch(version, defs[0]); | ||
branches.set(branchId, newBranch); | ||
version.branch = newBranch; |
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 know we do it in Frontend, but I think we should avoid mutating version
here unless it's necessary.
lib/data/entity.js
Outdated
const branches = new Map(); | ||
for (const version of defs) { | ||
const { branchId } = version; | ||
if (branchId != null && version.branchBaseVersion != 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 added version.branchBaseVersion != null
to Frontend mostly as a temporary measure, since at the time, there were submissions where the entity creation got a branch ID. I think we can remove the condition here.
This PR makes changes to the it('should mark an update that is not contiguous with its trunk version as a soft conflict', testOfflineEntities(async (service, container) => {
const asAlice = await service.login('alice');
const branchId = uuid();
// Update existing entity on server (change age from 22 to 24)
await asAlice.patch('/v1/projects/1/datasets/people/entities/12345678-1234-4123-8234-123456789abc?baseVersion=1')
.send({ data: { age: '24' } })
.expect(200);
// Send update (change status from null to arrived)
await asAlice.post('/v1/projects/1/forms/offlineEntity/submissions')
.send(testData.instances.offlineEntity.one
.replace('branchId=""', `branchId="${branchId}"`)
)
.set('Content-Type', 'application/xml')
.expect(200);
await exhaust(container);
await asAlice.patch('/v1/projects/1/datasets/people/entities/12345678-1234-4123-8234-123456789abc?resolve=true&baseVersion=3')
.expect(200);
// Send second update (change age from 22 to 26)
await asAlice.post('/v1/projects/1/forms/offlineEntity/submissions')
.send(testData.instances.offlineEntity.one
.replace('branchId=""', `branchId="${branchId}"`)
.replace('one', 'one-update2')
.replace('baseVersion="1"', 'baseVersion="2"')
.replace('<status>arrived</status>', '<age>26</age>')
)
.set('Content-Type', 'application/xml')
.expect(200);
await exhaust(container);
await asAlice.get('/v1/projects/1/datasets/people/entities/12345678-1234-4123-8234-123456789abc/versions')
.then(({ body: versions }) => {
versions.map(v => v.conflict).should.eql([null, null, 'soft', 'soft']);
});
await asAlice.get('/v1/projects/1/datasets/people/entities/12345678-1234-4123-8234-123456789abc')
.then(({ body: entity }) => {
// This assertion currently fails.
should(entity.conflict).equal('soft');
});
})); |
Also, @ktuite, I've pushed my commit. Would you mind taking a look at it? |
6e3b743
to
e3eb904
Compare
e3eb904
to
e86d3b5
Compare
e86d3b5
to
f4d54fc
Compare
@matthew-white I added some code in the |
|
||
await exhaust(container); | ||
|
||
// Send fourth update |
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.
fourth update from the branch (3rd update is missing). this will get held in the backlog and when it gets force-processed, it will take the latest version as the base version and therefore not have a conflict due to version mismatch...
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.
link to issue here and mention the scenario is mentioned there
add(version) { | ||
if (version.baseVersion === this.lastContiguousWithTrunk && | ||
version.version === version.baseVersion + 1) | ||
this.lastContiguousWithTrunk = version.version; | ||
} |
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 saw a scenario in a test where this logic didn't quite work out
conflict false false
branches.get(v.branchId).lastContiguousWithTrunk 2
v baseversion 1
add: version, base version and last contiguous 3 1 2
conflict true false
branches.get(v.branchId).lastContiguousWithTrunk 2
v baseversion 1
add: version, base version and last contiguous 4 3 2
conflict true true
branches.get(v.branchId).lastContiguousWithTrunk 2
v baseversion 3
Closes getodk/central#698
This PR makes two changes:
1: Update per-version conflict state
The
<entity uuid>/versions
endpoint returns aconflict
state for each version of the entity, which is computed from other information about the entity versions. These values should stay the same even when the top levelconflict
flag on the entity changes (or is manually resolved). These version-specific conflict states used to be detected just frombaseVersion+1 != version
.In this PR, we've copied branch-tracking logic from Frontend to check whether each version if it is contiguous with its trunk version, and return a
soft
conflict state if it is non-contiguous but otherwise not a hard conflict.We could also consider having backend return more of the details that frontend is currently computing about branch continuity.
2. Update top-level conflict state
The above work to make the per-version conflict state more descriptive wasn't enough. Consider a situation where an offline branch has been interrupted by another branch or online update, a conflict has been resolved, but then another update from that interrupted branch comes in. We want every update that is not contiguous with its trunk version to be a conflict, and that means updating the conflict state on the entity itself, too.
Before, we only noted a conflict when the base version wasn't what was expected. An offline update would not be counted as conflict as long as the previous action in the branch was present.
This PR adds a check that the new entity is not only contiguous with its base version, but is also contiguous with its specified trunk version. (This is done by looking for a version of a different branch that interrupts the given branch, rather than the other code that tracks the last contiguous version per branch.) If it is not contiguous, i.e. if there is an interruption, the top level
conflict
flag on the entity itself is set tosoft
instead ofnull
.What has been done to verify that this works as intended?
Tests.
Why is this the best possible solution? Were any other approaches considered?
See explanation above.
How does this change affect users? Describe intentional changes to behavior and behavior that could have accidentally been affected by code changes. In other words, what are the regression risks?
Highlights conflicts more.
Does this change require updates to the API documentation? If so, please update docs/api.yaml as part of this PR.
No.
Before submitting this PR, please make sure you have:
make test
and confirmed all checks still pass OR confirm CircleCI build passes