-
Notifications
You must be signed in to change notification settings - Fork 13
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix: Realtime conflict into Profile page
The document sent by cozy-stack in real time is the CouchDB document. Unlike a call to the JSON Api, the document does not contain the attribute field. When a change was made, it generated a conflict error. I modified the RealTimeQueries so that I could modify the real-time object before integrating it into the cozy-client. This makes it possible to have the same version everywhere and avoid conflicts. See also: cozy/cozy-client#1412
- Loading branch information
Showing
3 changed files
with
224 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,146 @@ | ||
import { memo, useEffect } from 'react' | ||
import { useClient, Mutations } from 'cozy-client' | ||
import { receiveMutationResult } from 'cozy-client/dist/store' | ||
|
||
/** | ||
* Normalizes an object representing a CouchDB document | ||
* | ||
* Ensures existence of `_type` | ||
* | ||
* @public | ||
* @param {CouchDBDocument} couchDBDoc - object representing the document | ||
* @returns {CozyClientDocument} full normalized document | ||
*/ | ||
const normalizeDoc = (couchDBDoc, doctype) => { | ||
return { | ||
id: couchDBDoc._id, | ||
_type: doctype, | ||
...couchDBDoc | ||
} | ||
} | ||
|
||
/** | ||
* DispatchChange | ||
* | ||
* @param {CozyClient} client CozyClient instane | ||
* @param {Doctype} doctype Doctype of the document to update | ||
* @param {CouchDBDocument} couchDBDoc Document to update | ||
* @param {Mutation} mutationDefinitionCreator Mutation to apply | ||
*/ | ||
const dispatchChange = ( | ||
client, | ||
doctype, | ||
couchDBDoc, | ||
mutationDefinitionCreator | ||
) => { | ||
const data = normalizeDoc(couchDBDoc, doctype) | ||
const response = { | ||
data | ||
} | ||
|
||
const options = {} | ||
client.dispatch( | ||
receiveMutationResult( | ||
client.generateRandomId(), | ||
response, | ||
options, | ||
mutationDefinitionCreator(data) | ||
) | ||
) | ||
} | ||
|
||
/** | ||
* The document real time comes without attributes, there are only at the root. | ||
* That's why we need to merge the attributes from the document in the store. | ||
* @param {CozyClient} client - CozyClient instane | ||
* @param {CouchDBDocument} docFromRealTime - object representing the document from real time | ||
* @returns {object} merged document | ||
*/ | ||
export const computeDocumentFromRealTime = (client, docFromRealTime) => { | ||
const { _id, _type, _rev, ...attributes } = docFromRealTime | ||
const docFromState = client.getDocumentFromState('io.cozy.settings', _id) | ||
|
||
if (docFromState) { | ||
return { | ||
...docFromState, | ||
_id, | ||
_type, | ||
_rev, | ||
...attributes, | ||
attributes: { | ||
...docFromState?.attributes, | ||
...attributes | ||
}, | ||
meta: { | ||
rev: _rev | ||
} | ||
} | ||
} | ||
|
||
return { | ||
...docFromRealTime, | ||
attributes: { | ||
...attributes | ||
} | ||
} | ||
} | ||
|
||
/** | ||
* Component that subscribes to io.cozy.settings document changes and keep the | ||
* internal store updated. This is a copy of RealTimeQueries from cozy-client | ||
* with a tweak to merge the changes with the existing document from the store. | ||
* You can have more detail on the problematic we are solving here: | ||
* https://github.com/cozy/cozy-client/issues/1412 | ||
* | ||
* @param {object} options - Options | ||
* @param {Doctype} options.doctype - The doctype to watch | ||
* @returns {null} The component does not display anything. | ||
*/ | ||
const SettingsRealTimeQueries = ({ doctype = 'io.cozy.settings' }) => { | ||
const client = useClient() | ||
|
||
useEffect(() => { | ||
const realtime = client.plugins.realtime | ||
|
||
if (!realtime) { | ||
throw new Error( | ||
'You must include the realtime plugin to use RealTimeQueries' | ||
) | ||
} | ||
|
||
const dispatchCreate = couchDBDoc => { | ||
const doc = computeDocumentFromRealTime(client, couchDBDoc) | ||
dispatchChange(client, doctype, doc, Mutations.createDocument) | ||
} | ||
const dispatchUpdate = couchDBDoc => { | ||
const doc = computeDocumentFromRealTime(client, couchDBDoc) | ||
dispatchChange(client, doctype, doc, Mutations.updateDocument) | ||
} | ||
const dispatchDelete = couchDBDoc => { | ||
const doc = computeDocumentFromRealTime(client, couchDBDoc) | ||
dispatchChange( | ||
client, | ||
doctype, | ||
{ ...doc, _deleted: true }, | ||
Mutations.deleteDocument | ||
) | ||
} | ||
|
||
const subscribe = async () => { | ||
await realtime.subscribe('created', doctype, dispatchCreate) | ||
await realtime.subscribe('updated', doctype, dispatchUpdate) | ||
await realtime.subscribe('deleted', doctype, dispatchDelete) | ||
} | ||
subscribe() | ||
|
||
return () => { | ||
realtime.unsubscribe('created', doctype, dispatchCreate) | ||
realtime.unsubscribe('updated', doctype, dispatchUpdate) | ||
realtime.unsubscribe('deleted', doctype, dispatchDelete) | ||
} | ||
}, [client, doctype]) | ||
|
||
return null | ||
} | ||
|
||
export default memo(SettingsRealTimeQueries) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
import { computeDocumentFromRealTime } from 'components/SettingsRealTimeQueries' | ||
|
||
describe('computeDocumentFromRealTime', () => { | ||
const client = { | ||
getDocumentFromState: jest.fn() | ||
} | ||
|
||
beforeEach(() => { | ||
jest.clearAllMocks() | ||
}) | ||
|
||
it('should merge attributes from real time document with existing document in store', () => { | ||
const docFromRealTime = { | ||
_id: 'io.cozy.settings.instance', | ||
_type: 'io.cozy.settings', | ||
_rev: 'rev-2', | ||
public_name: 'Alice2' | ||
} | ||
const docFromState = { | ||
_id: 'io.cozy.settings.instance', | ||
_type: 'io.cozy.settings', | ||
_rev: 'rev-1', | ||
attributes: { | ||
public_name: 'Alice', | ||
password_defined: true | ||
} | ||
} | ||
client.getDocumentFromState.mockReturnValueOnce(docFromState) | ||
|
||
const result = computeDocumentFromRealTime(client, docFromRealTime) | ||
|
||
expect(result).toEqual({ | ||
_id: 'io.cozy.settings.instance', | ||
_type: 'io.cozy.settings', | ||
_rev: 'rev-2', | ||
public_name: 'Alice2', | ||
attributes: { | ||
public_name: 'Alice2', | ||
password_defined: true | ||
}, | ||
meta: { | ||
rev: 'rev-2' | ||
} | ||
}) | ||
expect(client.getDocumentFromState).toHaveBeenCalledWith( | ||
'io.cozy.settings', | ||
'io.cozy.settings.instance' | ||
) | ||
}) | ||
|
||
it('should add attributes from real time document if no existing document in store', () => { | ||
const docFromRealTime = { | ||
_id: 'io.cozy.settings.instance', | ||
_type: 'io.cozy.settings', | ||
_rev: 'rev-1', | ||
public_name: 'Alice' | ||
} | ||
client.getDocumentFromState.mockReturnValueOnce(null) | ||
|
||
const result = computeDocumentFromRealTime(client, docFromRealTime) | ||
|
||
expect(result).toEqual({ | ||
_id: 'io.cozy.settings.instance', | ||
_type: 'io.cozy.settings', | ||
_rev: 'rev-1', | ||
public_name: 'Alice', | ||
attributes: { | ||
public_name: 'Alice' | ||
} | ||
}) | ||
expect(client.getDocumentFromState).toHaveBeenCalledWith( | ||
'io.cozy.settings', | ||
'io.cozy.settings.instance' | ||
) | ||
}) | ||
}) |