Skip to content

Commit

Permalink
feat: Apply data enhancement to realtime results for specific doctypes
Browse files Browse the repository at this point in the history
This enrichment closes the gap between the results of queries from the
cozy-stack and realtime. In fact, realtime returns the document
directly from the database, unlike the stack, which can add computed
fields. Without them, it can lead to instable behaviour in
applications. Some applications fix some issue by reimplementing the
RealTimeQueries component. This commit aims to provide unifed api to
avoid duplicated code. It can also be used as a reference when we want
to close the gap.
  • Loading branch information
cballevre committed Oct 7, 2024
1 parent 1743f00 commit 68a7512
Show file tree
Hide file tree
Showing 7 changed files with 279 additions and 99 deletions.
24 changes: 15 additions & 9 deletions docs/api/cozy-client/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -336,7 +336,7 @@ Deconstructed link

### dispatchCreate

**dispatchCreate**(`client`, `doctype`, `couchDBDoc`): `void`
**dispatchCreate**(`client`, `doctype`, `couchDBDoc`, `options?`): `Promise`<`void`>

Dispatches a create action for a document to update CozyClient store from realtime callbacks.

Expand All @@ -347,20 +347,22 @@ Dispatches a create action for a document to update CozyClient store from realti
| `client` | `any` | CozyClient instance |
| `doctype` | `string` | Doctype of the document to create |
| `couchDBDoc` | `CouchDBDocument` | Document to create |
| `options` | `Object` | - |
| `options.computeCreatedDoc` | `Function` | - |

*Returns*

`void`
`Promise`<`void`>

*Defined in*

[packages/cozy-client/src/store/realtime.js:58](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/store/realtime.js#L58)
[packages/cozy-client/src/store/realtime.js:53](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/store/realtime.js#L53)

***

### dispatchDelete

**dispatchDelete**(`client`, `doctype`, `couchDBDoc`): `void`
**dispatchDelete**(`client`, `doctype`, `couchDBDoc`, `options?`): `Promise`<`void`>

Dispatches a delete action for a document to update CozyClient store from realtime callbacks.

Expand All @@ -371,20 +373,22 @@ Dispatches a delete action for a document to update CozyClient store from realti
| `client` | `any` | CozyClient instance |
| `doctype` | `string` | Doctype of the document to create |
| `couchDBDoc` | `CouchDBDocument` | Document to create |
| `options` | `Object` | - |
| `options.computeDeletedDoc` | `Function` | - |

*Returns*

`void`
`Promise`<`void`>

*Defined in*

[packages/cozy-client/src/store/realtime.js:80](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/store/realtime.js#L80)
[packages/cozy-client/src/store/realtime.js:103](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/store/realtime.js#L103)

***

### dispatchUpdate

**dispatchUpdate**(`client`, `doctype`, `couchDBDoc`): `void`
**dispatchUpdate**(`client`, `doctype`, `couchDBDoc`, `options?`): `Promise`<`void`>

Dispatches a update action for a document to update CozyClient store from realtime callbacks.

Expand All @@ -395,14 +399,16 @@ Dispatches a update action for a document to update CozyClient store from realti
| `client` | `any` | CozyClient instance |
| `doctype` | `string` | Doctype of the document to create |
| `couchDBDoc` | `CouchDBDocument` | Document to create |
| `options` | `Object` | - |
| `options.computeUpdatedDoc` | `Function` | - |

*Returns*

`void`
`Promise`<`void`>

*Defined in*

[packages/cozy-client/src/store/realtime.js:69](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/store/realtime.js#L69)
[packages/cozy-client/src/store/realtime.js:78](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/store/realtime.js#L78)

***

Expand Down
41 changes: 30 additions & 11 deletions packages/cozy-client/src/RealTimeQueries.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,14 @@ import {
dispatchDelete,
dispatchUpdate
} from './store/realtime'
import { ensureFilePath } from './helpers/realtime'

/**
* Component that subscribes to a doctype changes and keep the
* internal store updated.
*
* @param {object} options - Options
* @param {import("./types").Doctype} options.doctype - The doctype to watch
* @param {object} options - Options
* @param {import("./types").Doctype} options.doctype - The doctype to watch
* @returns {null} The component does not display anything.
*/
const RealTimeQueries = ({ doctype }) => {
Expand All @@ -26,16 +27,34 @@ const RealTimeQueries = ({ doctype }) => {
)
}

const handleCreated = data => {
let options = {}
if (doctype === 'io.cozy.files') {
options.computeCreatedDoc = ensureFilePath
}
dispatchCreate(client, doctype, data, options)
}

const handleUpdated = data => {
let options = {}
if (doctype === 'io.cozy.files') {
options.computeUpdatedDoc = ensureFilePath
}
dispatchUpdate(client, doctype, data, options)
}

const handleDeleted = data => {
let options = {}
if (doctype === 'io.cozy.files') {
options.computeDeletedDoc = ensureFilePath
}
dispatchDelete(client, doctype, data, options)
}

const subscribe = async () => {
await realtime.subscribe('created', doctype, data =>
dispatchCreate(client, doctype, data)
)
await realtime.subscribe('updated', doctype, data =>
dispatchUpdate(client, doctype, data)
)
await realtime.subscribe('deleted', doctype, data =>
dispatchDelete(client, doctype, data)
)
await realtime.subscribe('created', doctype, handleCreated)
await realtime.subscribe('updated', doctype, handleUpdated)
await realtime.subscribe('deleted', doctype, handleDeleted)
}
subscribe()

Expand Down
177 changes: 126 additions & 51 deletions packages/cozy-client/src/RealTimeQueries.spec.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,26 @@ import CozyProvider from './Provider'

const setup = async doctype => {
const realtimeCallbacks = {}
const client = new createMockClient({})
const client = new createMockClient({
queries: {
'io.cozy.files/parent': {
doctype: 'io.cozy.files',
definition: {
doctype: 'io.cozy.files',
id: 'parent'
},
data: [
{
_id: 'parent',
_type: 'io.cozy.files',
name: 'Parent folder',
path: '/Parent'
}
]
}
}
})

client.plugins.realtime = {
subscribe: jest.fn((event, doctype, callback) => {
realtimeCallbacks[event] = callback
Expand All @@ -29,17 +48,26 @@ const setup = async doctype => {
}

describe('RealTimeQueries', () => {
it('notifies the cozy-client store', async () => {
it('should dispatch CREATE_DOCUMENT mutation for created io.cozy.files', async () => {
const { client, realtimeCallbacks, unmount } = await setup('io.cozy.files')

realtimeCallbacks['created']({ _id: 'mock-created', type: 'file' })
expect(client.dispatch).toHaveBeenCalledWith(
expect.objectContaining({
realtimeCallbacks['created']({
_id: 'mock-created',
type: 'file',
name: 'mock-created',
dir_id: 'parent'
})

await waitFor(() => {
expect(client.dispatch).toHaveBeenCalledWith({
definition: {
document: {
_id: 'mock-created',
id: 'mock-created',
_type: 'io.cozy.files',
dir_id: 'parent',
id: 'mock-created',
name: 'mock-created',
path: '/Parent/mock-created',
type: 'file'
},
mutationType: 'CREATE_DOCUMENT'
Expand All @@ -48,68 +76,115 @@ describe('RealTimeQueries', () => {
response: {
data: {
_id: 'mock-created',
id: 'mock-created',
_type: 'io.cozy.files',
dir_id: 'parent',
id: 'mock-created',
name: 'mock-created',
path: '/Parent/mock-created',
type: 'file'
}
},
type: 'RECEIVE_MUTATION_RESULT'
})
)
})

unmount()
await waitFor(() => {
expect(client.plugins.realtime.unsubscribe).toHaveBeenCalledTimes(3)
})
})

it('should dispatch UPDATE_DOCUMENT mutation for updated io.cozy.files', async () => {
const { client, realtimeCallbacks, unmount } = await setup('io.cozy.files')

realtimeCallbacks['updated']({ _id: 'mock-updated', type: 'file' })
expect(client.dispatch).toHaveBeenCalledWith({
definition: {
document: {
_id: 'mock-updated',
id: 'mock-updated',
_type: 'io.cozy.files',
type: 'file'
realtimeCallbacks['updated']({
_id: 'mock-updated',
type: 'file',
name: 'mock-updated',
dir_id: 'parent'
})

await waitFor(() => {
expect(client.dispatch).toHaveBeenCalledWith({
definition: {
document: {
_id: 'mock-updated',
_type: 'io.cozy.files',
dir_id: 'parent',
id: 'mock-updated',
name: 'mock-updated',
path: '/Parent/mock-updated',
type: 'file'
},
mutationType: 'UPDATE_DOCUMENT'
},
mutationType: 'UPDATE_DOCUMENT'
},
mutationId: '2',
response: {
data: {
_id: 'mock-updated',
id: 'mock-updated',
_type: 'io.cozy.files',
type: 'file'
}
},
type: 'RECEIVE_MUTATION_RESULT'
mutationId: '1',
response: {
data: {
_id: 'mock-updated',
_type: 'io.cozy.files',
dir_id: 'parent',
id: 'mock-updated',
name: 'mock-updated',
path: '/Parent/mock-updated',
type: 'file'
}
},
type: 'RECEIVE_MUTATION_RESULT'
})
})

realtimeCallbacks['deleted']({ _id: 'mock-deleted', type: 'file' })
expect(client.dispatch).toHaveBeenCalledWith({
definition: {
document: {
_id: 'mock-deleted',
id: 'mock-deleted',
_type: 'io.cozy.files',
type: 'file',
_deleted: true
unmount()
expect(client.plugins.realtime.unsubscribe).toHaveBeenCalledTimes(3)
})

it('should dispatch DELETE_DOCUMENT mutation for deleted io.cozy.files', async () => {
const { client, realtimeCallbacks, unmount } = await setup('io.cozy.files')

realtimeCallbacks['deleted']({
_id: 'mock-deleted',
type: 'file',
name: 'mock-deleted',
dir_id: 'parent'
})

await waitFor(() => {
expect(client.dispatch).toHaveBeenCalledWith({
definition: {
document: {
_deleted: true,
_type: 'io.cozy.files',
_id: 'mock-deleted',
dir_id: 'parent',
id: 'mock-deleted',
name: 'mock-deleted',
path: '/Parent/mock-deleted',
type: 'file'
},
mutationType: 'DELETE_DOCUMENT'
},
mutationType: 'DELETE_DOCUMENT'
},
mutationId: '3',
response: {
data: {
_id: 'mock-deleted',
id: 'mock-deleted',
_type: 'io.cozy.files',
type: 'file',
_deleted: true
}
},
type: 'RECEIVE_MUTATION_RESULT'
mutationId: '1',
response: {
data: {
_deleted: true,
_type: 'io.cozy.files',
_id: 'mock-deleted',
dir_id: 'parent',
id: 'mock-deleted',
name: 'mock-deleted',
path: '/Parent/mock-deleted',
type: 'file'
}
},
type: 'RECEIVE_MUTATION_RESULT'
})
})

unmount()
expect(client.plugins.realtime.unsubscribe).toHaveBeenCalledTimes(3)
})

it('deals with other doctypes than io.cozy.files', async () => {
it('should handle realtime events for other doctypes than io.cozy.files', async () => {
const { client, realtimeCallbacks, unmount } = await setup(
'io.cozy.oauth.clients'
)
Expand Down
Loading

0 comments on commit 68a7512

Please sign in to comment.