From 46aed5023040c09928f89a77907e68b42a5b94bf Mon Sep 17 00:00:00 2001 From: Alexis G Date: Wed, 20 Sep 2023 11:41:43 +0200 Subject: [PATCH 1/2] feat: Add `all` method to `AppsRegistryCollection` In order to be able to retrieve all the apps from the registry (ex: `client.query(Q('io.cozy.apps_registry'))`), we need to add the `all` method to `AppsRegistryCollection` --- docs/api/cozy-stack-client.md | 22 ++++++++ .../src/AppsRegistryCollection.js | 30 +++++++++++ .../src/AppsRegistryCollection.spec.js | 51 +++++++++++++++++++ 3 files changed, 103 insertions(+) diff --git a/docs/api/cozy-stack-client.md b/docs/api/cozy-stack-client.md index b65916e299..940313666c 100644 --- a/docs/api/cozy-stack-client.md +++ b/docs/api/cozy-stack-client.md @@ -276,6 +276,28 @@ The returned documents are not paginated by the stack. Extends `DocumentCollection` API along with specific methods for `io.cozy.apps_registry`. **Kind**: global class + +* [AppsRegistryCollection](#AppsRegistryCollection) + * [.all([option])](#AppsRegistryCollection+all) ⇒ Promise.<{data, meta, skip, next}> + * [.get(slug)](#AppsRegistryCollection+get) ⇒ Promise.<{data: object}> + + + +### appsRegistryCollection.all([option]) ⇒ Promise.<{data, meta, skip, next}> +Fetches all apps from the registry. + +**Kind**: instance method of [AppsRegistryCollection](#AppsRegistryCollection) +**Returns**: Promise.<{data, meta, skip, next}> - The JSON API conformant response. +**Throws**: + +- FetchError + + +| Param | Type | Description | +| --- | --- | --- | +| [option] | object | The fetch option | +| [option.limit] | number | Limit of apps to fetch | + ### appsRegistryCollection.get(slug) ⇒ Promise.<{data: object}> diff --git a/packages/cozy-stack-client/src/AppsRegistryCollection.js b/packages/cozy-stack-client/src/AppsRegistryCollection.js index 7840e458ab..457288c6ec 100644 --- a/packages/cozy-stack-client/src/AppsRegistryCollection.js +++ b/packages/cozy-stack-client/src/AppsRegistryCollection.js @@ -20,6 +20,36 @@ class AppsRegistryCollection extends DocumentCollection { this.endpoint = '/registry/' } + /** + * Fetches all apps from the registry. + * + * @param {object} [option] - The fetch option + * @param {number} [option.limit] - Limit of apps to fetch + * @returns {Promise<{data, meta, skip, next}>} The JSON API conformant response. + * @throws {FetchError} + */ + async all({ limit = 1000 } = {}) { + const resp = await this.stackClient.fetchJSON( + 'GET', + `${this.endpoint}?limit=${limit}` + ) + const dataNormalized = resp.data.map(d => { + return normalizeAppFromRegistry( + transformRegistryFormatToStackFormat(d), + this.doctype + ) + }) + + return { + data: dataNormalized, + meta: { + count: resp.meta.count + }, + skip: 0, + next: false + } + } + /** * Fetches an app from the registry. * diff --git a/packages/cozy-stack-client/src/AppsRegistryCollection.spec.js b/packages/cozy-stack-client/src/AppsRegistryCollection.spec.js index 7113065c80..341025bf17 100644 --- a/packages/cozy-stack-client/src/AppsRegistryCollection.spec.js +++ b/packages/cozy-stack-client/src/AppsRegistryCollection.spec.js @@ -8,6 +8,57 @@ import AppsRegistryCollection, { describe(`AppsRegistryCollection`, () => { const client = new CozyStackClient() + describe('all', () => { + const collection = new AppsRegistryCollection(client) + + beforeAll(() => { + client.fetchJSON.mockReturnValue( + Promise.resolve({ + data: [ + { + id: 'git://github.com/konnectors/alan.git', + _id: 'git://github.com/konnectors/alan.git', + _type: 'io.cozy.apps_registry', + latest_version: { + manifest: { + source: 'git://github.com/konnectors/alan.git' + } + } + } + ], + meta: { count: 1 } + }) + ) + }) + + it('should call the right default route', async () => { + await collection.all() + expect(client.fetchJSON).toHaveBeenCalledWith( + 'GET', + '/registry/?limit=1000' + ) + }) + + it('should call the right route with custom limit', async () => { + await collection.all({ limit: 10 }) + expect(client.fetchJSON).toHaveBeenCalledWith( + 'GET', + '/registry/?limit=10' + ) + }) + + it('should return a correct JSON API response', async () => { + const resp = await collection.all() + expect(resp).toConformToJSONAPI() + }) + + it('should return normalized documents', async () => { + const resp = await collection.all() + expect(resp.data[0]).toHaveDocumentIdentity() + expect(resp.data[0]._type).toEqual(APPS_REGISTRY_DOCTYPE) + }) + }) + describe('get', () => { const collection = new AppsRegistryCollection(client) From f3c174d1155654b03aa6505d2faa83ba5ca6e241 Mon Sep 17 00:00:00 2001 From: AlexisG Date: Wed, 20 Sep 2023 12:38:31 +0200 Subject: [PATCH 2/2] feat: Add `getAll` method to `AppsRegistryCollection` In order to be able to retrieve several applications from the registry according to their id (ex: `client.query(Q('io.cozy.apps_registry').getByIds([...]))`), we need to add `getAll` method to `AppsRegistryCollection` --- docs/api/cozy-stack-client.md | 17 ++++++ .../src/AppsRegistryCollection.js | 38 ++++++++++++ .../src/AppsRegistryCollection.spec.js | 60 +++++++++++++++++++ 3 files changed, 115 insertions(+) diff --git a/docs/api/cozy-stack-client.md b/docs/api/cozy-stack-client.md index 940313666c..a994a16076 100644 --- a/docs/api/cozy-stack-client.md +++ b/docs/api/cozy-stack-client.md @@ -279,6 +279,7 @@ Extends `DocumentCollection` API along with specific methods for `io.cozy.apps_r * [AppsRegistryCollection](#AppsRegistryCollection) * [.all([option])](#AppsRegistryCollection+all) ⇒ Promise.<{data, meta, skip, next}> + * [.getAll(slugs)](#AppsRegistryCollection+getAll) ⇒ Promise.<{data, meta, skip, next}> * [.get(slug)](#AppsRegistryCollection+get) ⇒ Promise.<{data: object}> @@ -298,6 +299,22 @@ Fetches all apps from the registry. | [option] | object | The fetch option | | [option.limit] | number | Limit of apps to fetch | + + +### appsRegistryCollection.getAll(slugs) ⇒ Promise.<{data, meta, skip, next}> +Fetches many apps from the registry by ids. + +**Kind**: instance method of [AppsRegistryCollection](#AppsRegistryCollection) +**Returns**: Promise.<{data, meta, skip, next}> - The JSON API conformant response. +**Throws**: + +- FetchError + + +| Param | Type | Description | +| --- | --- | --- | +| slugs | Array.<string> | The slugs of the apps to fetch | + ### appsRegistryCollection.get(slug) ⇒ Promise.<{data: object}> diff --git a/packages/cozy-stack-client/src/AppsRegistryCollection.js b/packages/cozy-stack-client/src/AppsRegistryCollection.js index 457288c6ec..b0ab5b7e6a 100644 --- a/packages/cozy-stack-client/src/AppsRegistryCollection.js +++ b/packages/cozy-stack-client/src/AppsRegistryCollection.js @@ -2,6 +2,7 @@ import { transformRegistryFormatToStackFormat } from 'cozy-client/dist/registry' import DocumentCollection, { normalizeDoc } from './DocumentCollection' import { FetchError } from './errors' +import { dontThrowNotFoundError } from './Collection' export const APPS_REGISTRY_DOCTYPE = 'io.cozy.apps_registry' @@ -50,6 +51,43 @@ class AppsRegistryCollection extends DocumentCollection { } } + /** + * Fetches many apps from the registry by ids. + * + * @param {Array} slugs - The slugs of the apps to fetch + * @returns {Promise<{data, meta, skip, next}>} The JSON API conformant response. + * @throws {FetchError} + */ + async getAll(slugs = []) { + try { + /* + * While waiting for the stack to propose a route that meets this need, we retrieve all the apps then we filter them before returning the result. + * (The limit of 1000 seems more than sufficient for this need) + */ + const resp = await this.stackClient.fetchJSON( + 'GET', + `${this.endpoint}?limit=1000` + ) + const dataFiltered = resp.data.filter(data => slugs.includes(data.slug)) + const dataNormalized = dataFiltered.map(d => { + return normalizeAppFromRegistry( + transformRegistryFormatToStackFormat(d), + this.doctype + ) + }) + return { + data: dataNormalized, + meta: { + count: resp.meta.count + }, + skip: 0, + next: false + } + } catch (error) { + return dontThrowNotFoundError(error) + } + } + /** * Fetches an app from the registry. * diff --git a/packages/cozy-stack-client/src/AppsRegistryCollection.spec.js b/packages/cozy-stack-client/src/AppsRegistryCollection.spec.js index 341025bf17..adc5e9107b 100644 --- a/packages/cozy-stack-client/src/AppsRegistryCollection.spec.js +++ b/packages/cozy-stack-client/src/AppsRegistryCollection.spec.js @@ -8,6 +8,66 @@ import AppsRegistryCollection, { describe(`AppsRegistryCollection`, () => { const client = new CozyStackClient() + describe('getAll', () => { + const collection = new AppsRegistryCollection(client) + const slugs = ['alan', 'caf'] + + beforeAll(() => { + client.fetchJSON.mockReturnValue( + Promise.resolve({ + data: [ + { + id: 'git://github.com/konnectors/alan.git', + slug: 'alan', + _type: 'io.cozy.apps_registry', + latest_version: { + manifest: { + source: 'git://github.com/konnectors/alan.git' + } + } + }, + { + id: 'git://github.com/konnectors/caf.git', + slug: 'caf', + _type: 'io.cozy.apps_registry', + latest_version: { + manifest: { + source: 'git://github.com/konnectors/caf.git' + } + } + } + ], + meta: { count: 2 } + }) + ) + }) + + it('should call the right route', async () => { + await collection.getAll(slugs) + expect(client.fetchJSON).toHaveBeenCalledWith( + 'GET', + '/registry/?limit=1000' + ) + }) + + it('should return the correct data', async () => { + const resp = await collection.getAll(slugs) + expect(resp.data.length).toBeLessThanOrEqual(slugs.length) + expect(resp.data.every(d => slugs.includes(d.slug))).toBe(true) + }) + + it('should return a correct JSON API response', async () => { + const resp = await collection.getAll(slugs) + expect(resp).toConformToJSONAPI() + }) + + it('should return normalized documents', async () => { + const resp = await collection.getAll(slugs) + expect(resp.data[0]).toHaveDocumentIdentity() + expect(resp.data[0]._type).toEqual(APPS_REGISTRY_DOCTYPE) + }) + }) + describe('all', () => { const collection = new AppsRegistryCollection(client)