From fc074099126f722bd3c3cec73662dc83a8ed7c02 Mon Sep 17 00:00:00 2001 From: Ldoppea Date: Tue, 9 Apr 2024 15:48:35 +0200 Subject: [PATCH 01/11] feat: Add `getSetting` and `saveAfterFetchSetting` methods to CozyClient In our cozy-apps, we often need to handle specific settings for each cozy-app. This includes: - io.cozy.settings/io.cozy.settings.instance - io.cozy.settings/io.cozy.settings.bitwarden - io.cozy.home.settings - io.cozy.coachco2.settings - io.cozy.mespapiers.settings In the current code base, all those access are made with different code that can be heterogeneous We want to mutualize those access into two methods: - client.getSetting(slug, key) - client.saveAfterFetchSetting(slug, key, value) `getSetting` is responsible to retrieve the given `key` from a specific cozy-app setting `saveAfterFetchSetting` is responsible to update the given `key` for a specific cozy-app setting Note that `saveAfterFetchSetting` will first fetch the cozy-app settings before editing and saving them. This prevents from having to pass existing settings as parameter and also this ensure we always inject up to date data into the database --- docs/api/cozy-client/classes/CozyClient.md | 55 +++ packages/cozy-client/src/CozyClient.js | 29 +- packages/cozy-client/src/helpers/index.js | 8 + packages/cozy-client/src/helpers/settings.js | 182 ++++++++ .../cozy-client/src/helpers/settings.spec.js | 415 ++++++++++++++++++ packages/cozy-client/src/types.js | 6 + packages/cozy-client/types/CozyClient.d.ts | 21 + packages/cozy-client/types/helpers/index.d.ts | 1 + .../cozy-client/types/helpers/settings.d.ts | 6 + packages/cozy-client/types/types.d.ts | 4 + 10 files changed, 726 insertions(+), 1 deletion(-) create mode 100644 packages/cozy-client/src/helpers/settings.js create mode 100644 packages/cozy-client/src/helpers/settings.spec.js create mode 100644 packages/cozy-client/types/helpers/settings.d.ts diff --git a/docs/api/cozy-client/classes/CozyClient.md b/docs/api/cozy-client/classes/CozyClient.md index ec5c9a81f6..2687fbf939 100644 --- a/docs/api/cozy-client/classes/CozyClient.md +++ b/docs/api/cozy-client/classes/CozyClient.md @@ -927,6 +927,32 @@ the store up, which in turn will update the ``s and re-render the data. *** +### getSetting + +▸ **getSetting**(`slug`, `key`): `Promise`<`any`> + +Query the cozy-app settings corresponding to the given slug and +extract the value corresponding to the given `key` + +*Parameters* + +| Name | Type | Description | +| :------ | :------ | :------ | +| `slug` | `string` | the cozy-app's slug containing the setting (can be 'instance' for global settings) | +| `key` | `string` | The name of the setting to retrieve | + +*Returns* + +`Promise`<`any`> + +* The value of the requested setting + +*Defined in* + +[packages/cozy-client/src/CozyClient.js:1761](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1761) + +*** + ### getStackClient ▸ **getStackClient**(): `any` @@ -1559,6 +1585,35 @@ Create or update a document on the server *** +### saveAfterFetchSetting + +▸ **saveAfterFetchSetting**(`slug`, `key`, `valueOrSetter`): `Promise`<`any`> + +Save the given value into the corresponding cozy-app setting + +This methods will first query the cozy-app's settings before injecting the new value and then +save the new resulting settings into database + +*Parameters* + +| Name | Type | Description | +| :------ | :------ | :------ | +| `slug` | `string` | the cozy-app's slug containing the setting (can be 'instance' for global settings) | +| `key` | `string` | The new value of the setting to save | +| `valueOrSetter` | `any` | The new value of the setting to save. It can be the raw value, or a callback that should return a new value | + +*Returns* + +`Promise`<`any`> + +* The result of the `client.save()` call + +*Defined in* + +[packages/cozy-client/src/CozyClient.js:1776](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1776) + +*** + ### saveAll ▸ **saveAll**(`docs`, `mutationOptions?`): `Promise`<`void`> diff --git a/packages/cozy-client/src/CozyClient.js b/packages/cozy-client/src/CozyClient.js index 95d906d471..b4450155a5 100644 --- a/packages/cozy-client/src/CozyClient.js +++ b/packages/cozy-client/src/CozyClient.js @@ -17,7 +17,7 @@ import { responseToRelationship, attachRelationships } from './associations/helpers' -import { dehydrate } from './helpers' +import { dehydrate, getSetting, saveAfterFetchSetting } from './helpers' import { QueryDefinition, Mutations, Q } from './queries/dsl' import { authFunction } from './authentication/mobile' import optimizeQueryDefinitions from './queries/optimize' @@ -1749,6 +1749,33 @@ instantiation of the client.` ...newAppMetadata } } + + /** + * Query the cozy-app settings corresponding to the given slug and + * extract the value corresponding to the given `key` + * + * @param {string} slug - the cozy-app's slug containing the setting (can be 'instance' for global settings) + * @param {string} key - The name of the setting to retrieve + * @returns {Promise} - The value of the requested setting + */ + async getSetting(slug, key) { + return getSetting(this, slug, key) + } + + /** + * Save the given value into the corresponding cozy-app setting + * + * This methods will first query the cozy-app's settings before injecting the new value and then + * save the new resulting settings into database + * + * @param {string} slug - the cozy-app's slug containing the setting (can be 'instance' for global settings) + * @param {string} key - The new value of the setting to save + * @param {any | ((oldValue) => any)} valueOrSetter - The new value of the setting to save. It can be the raw value, or a callback that should return a new value + * @returns {Promise} - The result of the `client.save()` call + */ + async saveAfterFetchSetting(slug, key, valueOrSetter) { + return saveAfterFetchSetting(this, slug, key, valueOrSetter) + } } CozyClient.hooks = CozyClient.hooks || {} diff --git a/packages/cozy-client/src/helpers/index.js b/packages/cozy-client/src/helpers/index.js index 78f6a48d0c..6a84a28cf3 100644 --- a/packages/cozy-client/src/helpers/index.js +++ b/packages/cozy-client/src/helpers/index.js @@ -11,3 +11,11 @@ export { } from './urlHelper' export { dehydrate } from './dehydrateHelper' + +export { + editSettings, + getQuery, + getSetting, + normalizeSettings, + saveAfterFetchSetting +} from './settings' diff --git a/packages/cozy-client/src/helpers/settings.js b/packages/cozy-client/src/helpers/settings.js new file mode 100644 index 0000000000..0873a83ae7 --- /dev/null +++ b/packages/cozy-client/src/helpers/settings.js @@ -0,0 +1,182 @@ +import CozyClient from '../CozyClient' +import fetchPolicies from '../policies' +import { Q } from '../queries/dsl' + +const defaultFetchPolicy = fetchPolicies.olderThan(60 * 60 * 1000) + +/** + * Query the cozy-app settings corresponding to the given slug and + * extract the value corresponding to the given `key` + * + * @param {CozyClient} client - Cozy client instance + * @param {string} slug - the cozy-app's slug containing the setting (can be 'instance' for global settings) + * @param {string} key - The name of the setting to retrieve + * @returns {Promise} - The value of the requested setting + */ +export const getSetting = async (client, slug, key) => { + const query = getQuery(slug) + + const currentSettingsResult = await client.query( + query.definition(), + query.options + ) + + const currentSettings = normalizeSettings(currentSettingsResult.data) + + return currentSettings[key] +} + +/** + * Save the given value into the corresponding cozy-app setting + * + * This methods will first query the cozy-app's settings before injecting the new value and then + * save the new resulting settings into database + * + * @param {CozyClient} client - Cozy client instance + * @param {string} slug - the cozy-app's slug containing the setting (can be 'instance' for global settings) + * @param {string} key - The new value of the setting to save + * @param {any | ((oldValue) => any)} valueOrSetter - The new value of the setting to save. It can be the raw value, or a callback that should return a new value + * @returns {Promise} - The result of the `client.save()` call + */ +export const saveAfterFetchSetting = async ( + client, + slug, + key, + valueOrSetter +) => { + const query = getQuery(slug) + + const currentSettingsResult = await client.query( + query.definition(), + query.options + ) + + const currentSettings = normalizeSettings(currentSettingsResult.data) + + let value = undefined + if (typeof valueOrSetter === 'function') { + value = valueOrSetter(currentSettings[key]) + } else { + value = valueOrSetter + } + + const newSettings = editSettings(slug, currentSettings, key, value) + + return await client.save(newSettings) +} + +/** + * Convert a result from a `client.query()` or a `useQuery()` that can be + * an object or an array of objects into a single object + * + * @param {Array | Object} data - Result from a client.query or a useQuery + * @returns {Object} A single object containing the setting data + */ +export const normalizeSettings = data => { + const settingsData = Array.isArray(data) ? data[0] : data + + return settingsData || {} +} + +/** + * Edit the given settings by injecting `value` into the `key` entry + * This methods takes care of the kind of settings that is edited as there are + * some exceptions in settings formats (i.e. `io.cozy.settings.instance`) + * + * @param {string} slug - the cozy-app's slug containing the setting (can be 'instance' for global settings) + * @param {Object} currentSettings - the Setting object (ideally from a `client.query()` or a `useQuery()` and normalized using `normalizeSettings`) + * @param {string} key - The name of the setting to edit + * @param {any} value - The new value for the setting + * @returns {Object} a new Setting object containing the new value + */ +export const editSettings = (slug, currentSettings, key, value) => { + const type = getDoctype(slug) + + const newSettings = + slug === 'instance' + ? mergeInstance(currentSettings, key, value) + : mergeSettings(currentSettings, type, key, value) + + return newSettings +} + +const mergeInstance = (currentSettings, key, value) => { + return { + _id: currentSettings._id, + _type: currentSettings._type, + _rev: currentSettings.meta.rev, + ...currentSettings, + attributes: { + ...currentSettings.attributes, + [key]: value + }, + [key]: value + } +} + +const mergeSettings = (currentSettings, type, key, value) => { + return { + _type: type, + ...currentSettings, + [key]: value + } +} + +/** + * Create a Query that can be used to fetch the cozy-app settings for the given slug + * + * @param {string} slug - the cozy-app's slug containing the setting (can be 'instance' for global settings) + * @returns {import('../types').Query} - the Query that can be used to fetch the cozy-app settings + */ +export const getQuery = slug => { + if (slug === 'instance') { + return getNestedSettings(slug) + } + + if (slug === 'passwords') { + return getNestedSettings('bitwarden') + } + + return getRootSettings(slug) +} + +const getRootSettings = slug => { + const settingsDoctype = getDoctype(slug) + const query = { + definition: () => Q(settingsDoctype).limitBy(1), + options: { + as: settingsDoctype, + fetchPolicy: defaultFetchPolicy, + singleDocData: true + } + } + + return query +} + +const getNestedSettings = slug => { + const doctype = `io.cozy.settings` + const subDoctype = getDoctype(slug) + const query = { + definition: () => Q(doctype).getById(subDoctype), + options: { + as: `${doctype}/${subDoctype}`, + fetchPolicy: defaultFetchPolicy, + singleDocData: true + } + } + + return query +} + +const getDoctype = slug => { + if (['instance', 'bitwarden'].includes(slug)) { + return `io.cozy.settings.${slug}` + } + + if (slug === 'passwords') { + return 'io.cozy.settings.bitwarden' + } + + return `io.cozy.${slug}.settings` +} diff --git a/packages/cozy-client/src/helpers/settings.spec.js b/packages/cozy-client/src/helpers/settings.spec.js new file mode 100644 index 0000000000..7b161f1e2c --- /dev/null +++ b/packages/cozy-client/src/helpers/settings.spec.js @@ -0,0 +1,415 @@ +import { + editSettings, + getSetting, + normalizeSettings, + saveAfterFetchSetting +} from './settings' +import { Q } from '../queries/dsl' + +import * as mocks from '../__tests__/mocks' + +describe('settings', () => { + describe('getSetting', () => { + it('should get settings for cozy-home', async () => { + const client = mocks.client() + + client.query.mockResolvedValue({ + data: [ + { + some_key: 'some_value' + } + ] + }) + + // @ts-ignore + const result = await getSetting(client, 'home', 'some_key') + + const query = { + definition: Q('io.cozy.home.settings').limitBy(1), + options: { + as: 'io.cozy.home.settings', + fetchPolicy: expect.anything(), + singleDocData: true + } + } + expect(client.query).toHaveBeenCalledWith(query.definition, query.options) + expect(result).toEqual('some_value') + }) + + it('should get settings for mespapiers', async () => { + const client = mocks.client() + + client.query.mockResolvedValue({ + data: [ + { + some_mespapiers_key: 'some_mespapiers_value' + } + ] + }) + + const result = await getSetting( + // @ts-ignore + client, + 'mespapiers', + 'some_mespapiers_key' + ) + + const query = { + definition: Q('io.cozy.mespapiers.settings').limitBy(1), + options: { + as: 'io.cozy.mespapiers.settings', + fetchPolicy: expect.anything(), + singleDocData: true + } + } + expect(client.query).toHaveBeenCalledWith(query.definition, query.options) + expect(result).toEqual('some_mespapiers_value') + }) + + it('should get settings for instance', async () => { + const client = mocks.client() + + client.query.mockResolvedValue({ + data: [ + { + some_global_key: 'some_global_value' + } + ] + }) + + // @ts-ignore + const result = await getSetting(client, 'instance', 'some_global_key') + + const query = { + definition: Q('io.cozy.settings').getById('io.cozy.settings.instance'), + options: { + as: 'io.cozy.settings/io.cozy.settings.instance', + fetchPolicy: expect.anything(), + singleDocData: true + } + } + expect(client.query).toHaveBeenCalledWith(query.definition, query.options) + expect(result).toEqual('some_global_value') + }) + + it('should get settings for passwords', async () => { + const client = mocks.client() + + client.query.mockResolvedValue({ + data: [ + { + some_pass_key: 'some_pass_value' + } + ] + }) + + // @ts-ignore + const result = await getSetting(client, 'passwords', 'some_pass_key') + + const query = { + definition: Q('io.cozy.settings').getById('io.cozy.settings.bitwarden'), + options: { + as: 'io.cozy.settings/io.cozy.settings.bitwarden', + fetchPolicy: expect.anything(), + singleDocData: true + } + } + expect(client.query).toHaveBeenCalledWith(query.definition, query.options) + expect(result).toEqual('some_pass_value') + }) + }) + + describe('saveAfterFetchSetting', () => { + it('should set settings for instance', async () => { + const client = mocks.client() + + client.query.mockResolvedValue({ + data: [ + { + id: 'io.cozy.settings.instance', + _id: 'io.cozy.settings.instance', + _type: 'io.cozy.settings', + type: 'io.cozy.settings', + attributes: { + some_setting: 'some_setting_value', + some_instance_key: 'some_instance_value' + }, + meta: { + rev: 'SOME_UNIQ_REV_NUMBER' + }, + some_setting: 'some_setting_value', + some_instance_key: 'some_instance_value' + } + ] + }) + + await saveAfterFetchSetting( + // @ts-ignore + client, + 'instance', + 'some_instance_key', + 'some_new_instance_value' + ) + + const query = { + definition: Q('io.cozy.settings').getById('io.cozy.settings.instance'), + options: { + as: 'io.cozy.settings/io.cozy.settings.instance', + fetchPolicy: expect.anything(), + singleDocData: true + } + } + expect(client.query).toHaveBeenCalledWith(query.definition, query.options) + expect(client.save).toHaveBeenCalledWith({ + id: 'io.cozy.settings.instance', + _id: 'io.cozy.settings.instance', + _type: 'io.cozy.settings', + type: 'io.cozy.settings', + _rev: 'SOME_UNIQ_REV_NUMBER', + attributes: { + some_setting: 'some_setting_value', + some_instance_key: 'some_new_instance_value' + }, + some_setting: 'some_setting_value', + some_instance_key: 'some_new_instance_value', + meta: { + rev: 'SOME_UNIQ_REV_NUMBER' + } + }) + }) + + it('should set settings for passwords', async () => { + const client = mocks.client() + + client.query.mockResolvedValue({ + data: [ + { + _type: 'io.cozy.passwords.settings', + some_pass_key: 'some_pass_value' + } + ] + }) + + await saveAfterFetchSetting( + // @ts-ignore + client, + 'passwords', + 'some_pass_key', + 'some_new_pass_value' + ) + + const query = { + definition: Q('io.cozy.settings').getById('io.cozy.settings.bitwarden'), + options: { + as: 'io.cozy.settings/io.cozy.settings.bitwarden', + fetchPolicy: expect.anything(), + singleDocData: true + } + } + expect(client.query).toHaveBeenCalledWith(query.definition, query.options) + expect(client.save).toHaveBeenCalledWith({ + _type: 'io.cozy.passwords.settings', + some_pass_key: 'some_new_pass_value' + }) + }) + + it('should set settings for passwords even if key does not exist', async () => { + const client = mocks.client() + + client.query.mockResolvedValue({ + data: [ + { + _type: 'io.cozy.passwords.settings', + some_existing_pass_key: 'some_pass_value' + } + ] + }) + + await saveAfterFetchSetting( + // @ts-ignore + client, + 'passwords', + 'some_new_pass_key', + 'some_new_pass_value' + ) + + const query = { + definition: Q('io.cozy.settings').getById('io.cozy.settings.bitwarden'), + options: { + as: 'io.cozy.settings/io.cozy.settings.bitwarden', + fetchPolicy: expect.anything(), + singleDocData: true + } + } + expect(client.query).toHaveBeenCalledWith(query.definition, query.options) + expect(client.save).toHaveBeenCalledWith({ + _type: 'io.cozy.passwords.settings', + some_existing_pass_key: 'some_pass_value', + some_new_pass_key: 'some_new_pass_value' + }) + }) + + it('should set settings for passwords using method', async () => { + const client = mocks.client() + + client.query.mockResolvedValue({ + data: [ + { + _type: 'io.cozy.passwords.settings', + some_pass_key: 1 + } + ] + }) + + await saveAfterFetchSetting( + // @ts-ignore + client, + 'passwords', + 'some_pass_key', + currentValue => currentValue + 1 + ) + + const query = { + definition: Q('io.cozy.settings').getById('io.cozy.settings.bitwarden'), + options: { + as: 'io.cozy.settings/io.cozy.settings.bitwarden', + fetchPolicy: expect.anything(), + singleDocData: true + } + } + expect(client.query).toHaveBeenCalledWith(query.definition, query.options) + expect(client.save).toHaveBeenCalledWith({ + _type: 'io.cozy.passwords.settings', + some_pass_key: 2 + }) + }) + }) + + describe('normalizeSettings', () => { + it('should normalize data array into object', () => { + const result = normalizeSettings([ + { + id: 'SOME_DOCUMENT_ID', + _id: 'SOME_DOCUMENT_ID', + _type: 'io.cozy.home.settings', + _rev: 'SOME_DOCUMENT_REV', + cozyMetadata: { + createdAt: '2024-04-03T15:00:48.961Z', + metadataVersion: 1, + updatedAt: '2024-04-03T15:00:48.961Z', + updatedByApps: [] + }, + some_attribue: true + } + ]) + + expect(result).toStrictEqual({ + id: 'SOME_DOCUMENT_ID', + _id: 'SOME_DOCUMENT_ID', + _type: 'io.cozy.home.settings', + _rev: 'SOME_DOCUMENT_REV', + cozyMetadata: { + createdAt: '2024-04-03T15:00:48.961Z', + metadataVersion: 1, + updatedAt: '2024-04-03T15:00:48.961Z', + updatedByApps: [] + }, + some_attribue: true + }) + }) + + it('should normalize data object into object', () => { + const result = normalizeSettings({ + id: 'io.cozy.settings.instance', + _id: 'io.cozy.settings.instance', + _type: 'io.cozy.settings', + type: 'io.cozy.settings', + attributes: { + some_attributes: 'some_value' + }, + meta: { + rev: '2-8803d6fda4bd3216e3fbeb0a181979d5' + }, + links: { + self: '/settings/instance' + }, + some_attributes: 'some_value' + }) + + expect(result).toStrictEqual({ + id: 'io.cozy.settings.instance', + _id: 'io.cozy.settings.instance', + _type: 'io.cozy.settings', + type: 'io.cozy.settings', + attributes: { + some_attributes: 'some_value' + }, + meta: { + rev: '2-8803d6fda4bd3216e3fbeb0a181979d5' + }, + links: { + self: '/settings/instance' + }, + some_attributes: 'some_value' + }) + }) + }) + describe('editSettings', () => { + it('should inject value into settings', () => { + const result = editSettings( + 'passwords', + { + _type: 'io.cozy.passwords.settings', + some_pass_key: 'some_pass_value' + }, + 'some_pass_key', + 'some_new_pass_value' + ) + + expect(result).toStrictEqual({ + _type: 'io.cozy.passwords.settings', + some_pass_key: 'some_new_pass_value' + }) + }) + + it('should inject _rev value and edit attributes for instance settings', () => { + const result = editSettings( + 'instance', + { + id: 'io.cozy.settings.instance', + _id: 'io.cozy.settings.instance', + _type: 'io.cozy.settings', + type: 'io.cozy.settings', + attributes: { + some_setting: 'some_setting_value', + some_instance_key: 'some_instance_value' + }, + meta: { + rev: 'SOME_UNIQ_REV_NUMBER' + }, + some_setting: 'some_setting_value', + some_instance_key: 'some_instance_value' + }, + 'some_instance_key', + 'some_new_instance_value' + ) + + expect(result).toStrictEqual({ + id: 'io.cozy.settings.instance', + _id: 'io.cozy.settings.instance', + _type: 'io.cozy.settings', + type: 'io.cozy.settings', + _rev: 'SOME_UNIQ_REV_NUMBER', + attributes: { + some_setting: 'some_setting_value', + some_instance_key: 'some_new_instance_value' + }, + some_setting: 'some_setting_value', + some_instance_key: 'some_new_instance_value', + meta: { + rev: 'SOME_UNIQ_REV_NUMBER' + } + }) + }) + }) +}) diff --git a/packages/cozy-client/src/types.js b/packages/cozy-client/src/types.js index e969242645..52223407d9 100644 --- a/packages/cozy-client/src/types.js +++ b/packages/cozy-client/src/types.js @@ -283,6 +283,12 @@ import { QueryDefinition } from './queries/dsl' * compatibility but will be set to true in the future. */ +/** + * @typedef {object} Query + * @property {() => QueryDefinition} definition + * @property {QueryOptions} options + */ + /** * @typedef {object} FetchMoreAble * @property {Function} fetchMore diff --git a/packages/cozy-client/types/CozyClient.d.ts b/packages/cozy-client/types/CozyClient.d.ts index 7dfaf0690a..be7802bf1b 100644 --- a/packages/cozy-client/types/CozyClient.d.ts +++ b/packages/cozy-client/types/CozyClient.d.ts @@ -713,6 +713,27 @@ declare class CozyClient { * @param {import("./types").AppMetadata} newAppMetadata AppMetadata to update */ setAppMetadata(newAppMetadata: import("./types").AppMetadata): void; + /** + * Query the cozy-app settings corresponding to the given slug and + * extract the value corresponding to the given `key` + * + * @param {string} slug - the cozy-app's slug containing the setting (can be 'instance' for global settings) + * @param {string} key - The name of the setting to retrieve + * @returns {Promise} - The value of the requested setting + */ + getSetting(slug: string, key: string): Promise; + /** + * Save the given value into the corresponding cozy-app setting + * + * This methods will first query the cozy-app's settings before injecting the new value and then + * save the new resulting settings into database + * + * @param {string} slug - the cozy-app's slug containing the setting (can be 'instance' for global settings) + * @param {string} key - The new value of the setting to save + * @param {any | ((oldValue) => any)} valueOrSetter - The new value of the setting to save. It can be the raw value, or a callback that should return a new value + * @returns {Promise} - The result of the `client.save()` call + */ + saveAfterFetchSetting(slug: string, key: string, valueOrSetter: any): Promise; } declare namespace CozyClient { export const hooks: {}; diff --git a/packages/cozy-client/types/helpers/index.d.ts b/packages/cozy-client/types/helpers/index.d.ts index 75594d8daf..72ff09c325 100644 --- a/packages/cozy-client/types/helpers/index.d.ts +++ b/packages/cozy-client/types/helpers/index.d.ts @@ -1,2 +1,3 @@ export { dehydrate } from "./dehydrateHelper"; export { deconstructCozyWebLinkWithSlug, deconstructRedirectLink, generateWebLink, ensureFirstSlash, rootCozyUrl, InvalidRedirectLinkError, InvalidCozyUrlError, InvalidProtocolError, BlockedCozyError } from "./urlHelper"; +export { editSettings, getQuery, getSetting, normalizeSettings, saveAfterFetchSetting } from "./settings"; diff --git a/packages/cozy-client/types/helpers/settings.d.ts b/packages/cozy-client/types/helpers/settings.d.ts new file mode 100644 index 0000000000..e8ce49aab2 --- /dev/null +++ b/packages/cozy-client/types/helpers/settings.d.ts @@ -0,0 +1,6 @@ +export function getSetting(client: CozyClient, slug: string, key: string): Promise; +export function saveAfterFetchSetting(client: CozyClient, slug: string, key: string, valueOrSetter: any): Promise; +export function normalizeSettings(data: any[] | any): any; +export function editSettings(slug: string, currentSettings: any, key: string, value: any): any; +export function getQuery(slug: string): import('../types').Query; +import CozyClient from "../CozyClient"; diff --git a/packages/cozy-client/types/types.d.ts b/packages/cozy-client/types/types.d.ts index 5b7ee7f82d..a509f7fcfe 100644 --- a/packages/cozy-client/types/types.d.ts +++ b/packages/cozy-client/types/types.d.ts @@ -539,6 +539,10 @@ export type QueryOptions = { */ singleDocData?: boolean; }; +export type Query = { + definition: () => QueryDefinition; + options: QueryOptions; +}; export type FetchMoreAble = { fetchMore: Function; }; From a8a612a17751b7d2e26db83b1ed33133f647594d Mon Sep 17 00:00:00 2001 From: Ldoppea Date: Mon, 8 Apr 2024 19:12:36 +0200 Subject: [PATCH 02/11] feat: Add `useSetting` hook In previous commit we added `getSetting` and `saveAfterFetchSetting` methods into cozy-client Those methods are not Reactive so we always need to call `getSetting` on demand to have fresh data. Also `saveAfterFetchSetting` will always fetch data before updating and saving them into database even if the data is already up-to-date In order to improve this, this commit implements the `useSetting` hook that relies on `useQuery` and `useMutation` to keep data up-to-date and to mutate them --- docs/api/cozy-client/README.md | 29 +++++++++++ packages/cozy-client/src/hooks/index.js | 1 + packages/cozy-client/src/hooks/useSetting.js | 49 +++++++++++++++++++ packages/cozy-client/src/types.js | 39 +++++++++++++-- packages/cozy-client/types/hooks/index.d.ts | 1 + .../cozy-client/types/hooks/useSetting.d.ts | 1 + packages/cozy-client/types/types.d.ts | 44 ++++++++++++++--- 7 files changed, 152 insertions(+), 12 deletions(-) create mode 100644 packages/cozy-client/src/hooks/useSetting.js create mode 100644 packages/cozy-client/types/hooks/useSetting.d.ts diff --git a/docs/api/cozy-client/README.md b/docs/api/cozy-client/README.md index cbac37cb0b..c0d6b438b9 100644 --- a/docs/api/cozy-client/README.md +++ b/docs/api/cozy-client/README.md @@ -930,6 +930,35 @@ Fetches a queryDefinition and run fetchMore on the query until the query is full *** +### useSetting + +▸ **useSetting**(`slug`, `key`): `UseSettingReturnValue` + +Query the cozy-app settings corresponding to the given slug and +return: + +* the value corresponding to the given `key` +* the `save()` method that can be used to edit the setting's value +* the query that manages the state during the fetching of the setting +* the mutation that manages the state during the saving of the setting + +*Parameters* + +| Name | Type | Description | +| :------ | :------ | :------ | +| `slug` | `string` | the cozy-app's slug containing the setting (can be 'instance' for global settings) | +| `key` | `string` | The name of the setting to retrieve | + +*Returns* + +`UseSettingReturnValue` + +*Defined in* + +[packages/cozy-client/src/hooks/useSetting.js:20](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/hooks/useSetting.js#L20) + +*** + ### withClient ▸ **withClient**(`WrappedComponent`): `Function` diff --git a/packages/cozy-client/src/hooks/index.js b/packages/cozy-client/src/hooks/index.js index 862539eb7a..c56b4b2ab0 100644 --- a/packages/cozy-client/src/hooks/index.js +++ b/packages/cozy-client/src/hooks/index.js @@ -9,3 +9,4 @@ export { default as useAppsInMaintenance } from './useAppsInMaintenance' export { default as useQueryAll } from './useQueryAll' export { useMutation } from './useMutation' export { useInstanceInfo } from './useInstanceInfo' +export { useSetting } from './useSetting' diff --git a/packages/cozy-client/src/hooks/useSetting.js b/packages/cozy-client/src/hooks/useSetting.js new file mode 100644 index 0000000000..60158cb990 --- /dev/null +++ b/packages/cozy-client/src/hooks/useSetting.js @@ -0,0 +1,49 @@ +import { useCallback } from 'react' + +import { editSettings, getQuery, normalizeSettings } from '../helpers' + +import { useMutation } from './useMutation' +import useQuery from './useQuery' + +/** + * Query the cozy-app settings corresponding to the given slug and + * return: + * - the value corresponding to the given `key` + * - the `save()` method that can be used to edit the setting's value + * - the query that manages the state during the fetching of the setting + * - the mutation that manages the state during the saving of the setting + * + * @param {string} slug - the cozy-app's slug containing the setting (can be 'instance' for global settings) + * @param {string} key - The name of the setting to retrieve + * @returns {import("../types").UseSettingReturnValue} + */ +export const useSetting = (slug, key) => { + const query = getQuery(slug) + + const { data: settingsData, ...settingsQuery } = useQuery( + query.definition(), + query.options + ) + + const { mutate, ...mutation } = useMutation() + + const save = useCallback( + value => { + const settings = normalizeSettings(settingsData) + + const newSettings = editSettings(slug, settings, key, value) + + return mutate(newSettings) + }, + [key, mutate, settingsData, slug] + ) + + const settings = normalizeSettings(settingsData) + + return { + query: settingsQuery, + value: settings?.[key], + save, + mutation + } +} diff --git a/packages/cozy-client/src/types.js b/packages/cozy-client/src/types.js index 52223407d9..30f2bdfa49 100644 --- a/packages/cozy-client/src/types.js +++ b/packages/cozy-client/src/types.js @@ -243,7 +243,7 @@ import { QueryDefinition } from './queries/dsl' */ /** - * @typedef {object} QueryState + * @typedef {object} QueryStateWithoutData * @property {string} id * @property {QueryDefinition} definition * @property {QueryFetchStatus} fetchStatus @@ -255,12 +255,20 @@ import { QueryDefinition } from './queries/dsl' * @property {boolean} hasMore * @property {number} count * @property {number} fetchedPagesCount - * @property {object|Array} data * @property {string} bookmark * @property {object} [execution_stats] * @property {QueryOptions} [options] */ +/** + * @typedef {object} QueryStateData + * @property {object|Array} data + */ + +/** + * @typedef {QueryStateWithoutData & QueryStateData} QueryState + */ + /** * @typedef {object} AutoUpdateOptions * @param {boolean} update - Should documents be updated in the query (default: true) @@ -304,13 +312,36 @@ import { QueryDefinition } from './queries/dsl' */ /** - * @typedef {object} UseMutationReturnValue - * @property {Function} mutate - Function to save the document + * @typedef {object} UseMutationWithoutMutate * @property {QueryFetchStatus} mutationStatus - Status of the current mutation * @property {object} [error] - Error if the mutation failed * @property {object} [data] - Data return after the mutation */ +/** + * @typedef {object} UseMutationMutate + * @property {Function} mutate - Function to save the document + */ + +/** + * @typedef {UseMutationWithoutMutate & UseMutationMutate} UseMutationReturnValue + */ + +/** + * Update the setting with corresponding value and save it. + * + * @callback SaveSettingFunction + * @param {any} value - The new setting's value + */ + +/** + * @typedef {object} UseSettingReturnValue + * @property {any} value - The setting's value + * @property {SaveSettingFunction} save - Function to edit the setting + * @property {QueryStateWithoutData} query - Function to edit the setting + * @property {UseMutationWithoutMutate} mutation - Status of the current mutation + */ + /** * A reference to a document * diff --git a/packages/cozy-client/types/hooks/index.d.ts b/packages/cozy-client/types/hooks/index.d.ts index 704ae47df3..548ceaa6fc 100644 --- a/packages/cozy-client/types/hooks/index.d.ts +++ b/packages/cozy-client/types/hooks/index.d.ts @@ -6,4 +6,5 @@ export { default as useAppsInMaintenance } from "./useAppsInMaintenance"; export { default as useQueryAll } from "./useQueryAll"; export { useMutation } from "./useMutation"; export { useInstanceInfo } from "./useInstanceInfo"; +export { useSetting } from "./useSetting"; export { default as useQuery, useQueries } from "./useQuery"; diff --git a/packages/cozy-client/types/hooks/useSetting.d.ts b/packages/cozy-client/types/hooks/useSetting.d.ts new file mode 100644 index 0000000000..12c9c72de4 --- /dev/null +++ b/packages/cozy-client/types/hooks/useSetting.d.ts @@ -0,0 +1 @@ +export function useSetting(slug: string, key: string): import("../types").UseSettingReturnValue; diff --git a/packages/cozy-client/types/types.d.ts b/packages/cozy-client/types/types.d.ts index a509f7fcfe..3bf955388e 100644 --- a/packages/cozy-client/types/types.d.ts +++ b/packages/cozy-client/types/types.d.ts @@ -481,7 +481,7 @@ export type IndexedDocuments = { export type DocumentsStateSlice = { [x: string]: Record; }; -export type QueryState = { +export type QueryStateWithoutData = { id: string; definition: QueryDefinition; fetchStatus: QueryFetchStatus; @@ -493,11 +493,14 @@ export type QueryState = { hasMore: boolean; count: number; fetchedPagesCount: number; - data: object | any[]; bookmark: string; execution_stats?: object; options?: QueryOptions; }; +export type QueryStateData = { + data: object | any[]; +}; +export type QueryState = QueryStateWithoutData & QueryStateData; export type AutoUpdateOptions = any; export type QueryOptions = { /** @@ -549,12 +552,8 @@ export type FetchMoreAble = { export type FetchAble = { fetch: Function; }; -export type UseQueryReturnValue = QueryState & FetchMoreAble & FetchAble; -export type UseMutationReturnValue = { - /** - * - Function to save the document - */ - mutate: Function; +export type UseQueryReturnValue = QueryStateWithoutData & QueryStateData & FetchMoreAble & FetchAble; +export type UseMutationWithoutMutate = { /** * - Status of the current mutation */ @@ -568,6 +567,35 @@ export type UseMutationReturnValue = { */ data?: object; }; +export type UseMutationMutate = { + /** + * - Function to save the document + */ + mutate: Function; +}; +export type UseMutationReturnValue = UseMutationWithoutMutate & UseMutationMutate; +/** + * Update the setting with corresponding value and save it. + */ +export type SaveSettingFunction = (value: any) => any; +export type UseSettingReturnValue = { + /** + * - The setting's value + */ + value: any; + /** + * - Function to edit the setting + */ + save: SaveSettingFunction; + /** + * - Function to edit the setting + */ + query: QueryStateWithoutData; + /** + * - Status of the current mutation + */ + mutation: UseMutationWithoutMutate; +}; /** * A reference to a document */ From 399ed498714cecc40bd5d883f6ff489f9e89e5e1 Mon Sep 17 00:00:00 2001 From: Ldoppea Date: Wed, 10 Apr 2024 09:31:34 +0200 Subject: [PATCH 03/11] fix: Use `.fetchQueryAndGetFromState` instead of `.query` in settings `.query` would return `undefined` if called under the fetch policy's delay We want to retrieve the data from local storage in that scenario So the solution is to call `.fetchQueryAndGetFromState` instead of `.query` --- packages/cozy-client/src/__tests__/mocks.js | 3 +- packages/cozy-client/src/helpers/settings.js | 16 +++--- .../cozy-client/src/helpers/settings.spec.js | 56 +++++++++++++------ .../cozy-client/types/__tests__/mocks.d.ts | 1 + 4 files changed, 51 insertions(+), 25 deletions(-) diff --git a/packages/cozy-client/src/__tests__/mocks.js b/packages/cozy-client/src/__tests__/mocks.js index eaf6f683e8..f01d05fe79 100644 --- a/packages/cozy-client/src/__tests__/mocks.js +++ b/packages/cozy-client/src/__tests__/mocks.js @@ -22,7 +22,8 @@ export const client = implementations => { makeObservableQuery: jest.fn(), requestQuery: jest.fn(), all: jest.fn(), - setStore: jest.fn() + setStore: jest.fn(), + fetchQueryAndGetFromState: jest.fn() } mockImplementations(base, implementations) return base diff --git a/packages/cozy-client/src/helpers/settings.js b/packages/cozy-client/src/helpers/settings.js index 0873a83ae7..f7fbea2375 100644 --- a/packages/cozy-client/src/helpers/settings.js +++ b/packages/cozy-client/src/helpers/settings.js @@ -16,10 +16,10 @@ const defaultFetchPolicy = fetchPolicies.olderThan(60 * 60 * 1000) export const getSetting = async (client, slug, key) => { const query = getQuery(slug) - const currentSettingsResult = await client.query( - query.definition(), - query.options - ) + const currentSettingsResult = await client.fetchQueryAndGetFromState({ + definition: query.definition(), + options: query.options + }) const currentSettings = normalizeSettings(currentSettingsResult.data) @@ -46,10 +46,10 @@ export const saveAfterFetchSetting = async ( ) => { const query = getQuery(slug) - const currentSettingsResult = await client.query( - query.definition(), - query.options - ) + const currentSettingsResult = await client.fetchQueryAndGetFromState({ + definition: query.definition(), + options: query.options + }) const currentSettings = normalizeSettings(currentSettingsResult.data) diff --git a/packages/cozy-client/src/helpers/settings.spec.js b/packages/cozy-client/src/helpers/settings.spec.js index 7b161f1e2c..4c9f59b555 100644 --- a/packages/cozy-client/src/helpers/settings.spec.js +++ b/packages/cozy-client/src/helpers/settings.spec.js @@ -13,7 +13,7 @@ describe('settings', () => { it('should get settings for cozy-home', async () => { const client = mocks.client() - client.query.mockResolvedValue({ + client.fetchQueryAndGetFromState.mockResolvedValue({ data: [ { some_key: 'some_value' @@ -32,14 +32,17 @@ describe('settings', () => { singleDocData: true } } - expect(client.query).toHaveBeenCalledWith(query.definition, query.options) + expect(client.fetchQueryAndGetFromState).toHaveBeenCalledWith({ + definition: query.definition, + options: query.options + }) expect(result).toEqual('some_value') }) it('should get settings for mespapiers', async () => { const client = mocks.client() - client.query.mockResolvedValue({ + client.fetchQueryAndGetFromState.mockResolvedValue({ data: [ { some_mespapiers_key: 'some_mespapiers_value' @@ -62,14 +65,17 @@ describe('settings', () => { singleDocData: true } } - expect(client.query).toHaveBeenCalledWith(query.definition, query.options) + expect(client.fetchQueryAndGetFromState).toHaveBeenCalledWith({ + definition: query.definition, + options: query.options + }) expect(result).toEqual('some_mespapiers_value') }) it('should get settings for instance', async () => { const client = mocks.client() - client.query.mockResolvedValue({ + client.fetchQueryAndGetFromState.mockResolvedValue({ data: [ { some_global_key: 'some_global_value' @@ -88,14 +94,17 @@ describe('settings', () => { singleDocData: true } } - expect(client.query).toHaveBeenCalledWith(query.definition, query.options) + expect(client.fetchQueryAndGetFromState).toHaveBeenCalledWith({ + definition: query.definition, + options: query.options + }) expect(result).toEqual('some_global_value') }) it('should get settings for passwords', async () => { const client = mocks.client() - client.query.mockResolvedValue({ + client.fetchQueryAndGetFromState.mockResolvedValue({ data: [ { some_pass_key: 'some_pass_value' @@ -114,7 +123,10 @@ describe('settings', () => { singleDocData: true } } - expect(client.query).toHaveBeenCalledWith(query.definition, query.options) + expect(client.fetchQueryAndGetFromState).toHaveBeenCalledWith({ + definition: query.definition, + options: query.options + }) expect(result).toEqual('some_pass_value') }) }) @@ -123,7 +135,7 @@ describe('settings', () => { it('should set settings for instance', async () => { const client = mocks.client() - client.query.mockResolvedValue({ + client.fetchQueryAndGetFromState.mockResolvedValue({ data: [ { id: 'io.cozy.settings.instance', @@ -159,7 +171,10 @@ describe('settings', () => { singleDocData: true } } - expect(client.query).toHaveBeenCalledWith(query.definition, query.options) + expect(client.fetchQueryAndGetFromState).toHaveBeenCalledWith({ + definition: query.definition, + options: query.options + }) expect(client.save).toHaveBeenCalledWith({ id: 'io.cozy.settings.instance', _id: 'io.cozy.settings.instance', @@ -181,7 +196,7 @@ describe('settings', () => { it('should set settings for passwords', async () => { const client = mocks.client() - client.query.mockResolvedValue({ + client.fetchQueryAndGetFromState.mockResolvedValue({ data: [ { _type: 'io.cozy.passwords.settings', @@ -206,7 +221,10 @@ describe('settings', () => { singleDocData: true } } - expect(client.query).toHaveBeenCalledWith(query.definition, query.options) + expect(client.fetchQueryAndGetFromState).toHaveBeenCalledWith({ + definition: query.definition, + options: query.options + }) expect(client.save).toHaveBeenCalledWith({ _type: 'io.cozy.passwords.settings', some_pass_key: 'some_new_pass_value' @@ -216,7 +234,7 @@ describe('settings', () => { it('should set settings for passwords even if key does not exist', async () => { const client = mocks.client() - client.query.mockResolvedValue({ + client.fetchQueryAndGetFromState.mockResolvedValue({ data: [ { _type: 'io.cozy.passwords.settings', @@ -241,7 +259,10 @@ describe('settings', () => { singleDocData: true } } - expect(client.query).toHaveBeenCalledWith(query.definition, query.options) + expect(client.fetchQueryAndGetFromState).toHaveBeenCalledWith({ + definition: query.definition, + options: query.options + }) expect(client.save).toHaveBeenCalledWith({ _type: 'io.cozy.passwords.settings', some_existing_pass_key: 'some_pass_value', @@ -252,7 +273,7 @@ describe('settings', () => { it('should set settings for passwords using method', async () => { const client = mocks.client() - client.query.mockResolvedValue({ + client.fetchQueryAndGetFromState.mockResolvedValue({ data: [ { _type: 'io.cozy.passwords.settings', @@ -277,7 +298,10 @@ describe('settings', () => { singleDocData: true } } - expect(client.query).toHaveBeenCalledWith(query.definition, query.options) + expect(client.fetchQueryAndGetFromState).toHaveBeenCalledWith({ + definition: query.definition, + options: query.options + }) expect(client.save).toHaveBeenCalledWith({ _type: 'io.cozy.passwords.settings', some_pass_key: 2 diff --git a/packages/cozy-client/types/__tests__/mocks.d.ts b/packages/cozy-client/types/__tests__/mocks.d.ts index 9ea67a9754..5f2da82442 100644 --- a/packages/cozy-client/types/__tests__/mocks.d.ts +++ b/packages/cozy-client/types/__tests__/mocks.d.ts @@ -8,6 +8,7 @@ export function client(implementations: any): { requestQuery: jest.Mock; all: jest.Mock; setStore: jest.Mock; + fetchQueryAndGetFromState: jest.Mock; }; export function observableQuery(implementations: any): { currentResult: jest.Mock; From b0fa75484bcb7c890901bcfde61fa38726df29db Mon Sep 17 00:00:00 2001 From: Ldoppea Date: Tue, 9 Apr 2024 15:53:38 +0200 Subject: [PATCH 04/11] docs: Add info about `fetchQueryAndGetFromState` in `query` JSDoc --- packages/cozy-client/src/CozyClient.js | 4 ++++ packages/cozy-client/types/CozyClient.d.ts | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/packages/cozy-client/src/CozyClient.js b/packages/cozy-client/src/CozyClient.js index b4450155a5..813cea3b20 100644 --- a/packages/cozy-client/src/CozyClient.js +++ b/packages/cozy-client/src/CozyClient.js @@ -905,6 +905,10 @@ client.query(Q('io.cozy.bills'))`) * `getQueryFromState` or directly using ``. `` automatically * executes its query when mounted if no fetch policy has been indicated. * + * If the query is called under the fetch policy's delay, then the query + * is not executed and nothing is returned. If you need a result anyway, + * please use `fetchQueryAndGetFromState` instead + * * @param {QueryDefinition} queryDefinition - Definition that will be executed * @param {import("./types").QueryOptions} [options] - Options * @returns {Promise} diff --git a/packages/cozy-client/types/CozyClient.d.ts b/packages/cozy-client/types/CozyClient.d.ts index be7802bf1b..e04904b454 100644 --- a/packages/cozy-client/types/CozyClient.d.ts +++ b/packages/cozy-client/types/CozyClient.d.ts @@ -414,6 +414,10 @@ declare class CozyClient { * `getQueryFromState` or directly using ``. `` automatically * executes its query when mounted if no fetch policy has been indicated. * + * If the query is called under the fetch policy's delay, then the query + * is not executed and nothing is returned. If you need a result anyway, + * please use `fetchQueryAndGetFromState` instead + * * @param {QueryDefinition} queryDefinition - Definition that will be executed * @param {import("./types").QueryOptions} [options] - Options * @returns {Promise} From 0a25638b72821b1d041cbd4a2673701f8a2d595e Mon Sep 17 00:00:00 2001 From: Ldoppea Date: Tue, 9 Apr 2024 16:24:03 +0200 Subject: [PATCH 05/11] fix: Use object instead of method in settings queries --- packages/cozy-client/src/helpers/settings.js | 8 ++++---- packages/cozy-client/src/hooks/useSetting.js | 2 +- packages/cozy-client/src/types.js | 2 +- packages/cozy-client/types/types.d.ts | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/cozy-client/src/helpers/settings.js b/packages/cozy-client/src/helpers/settings.js index f7fbea2375..5460b1e797 100644 --- a/packages/cozy-client/src/helpers/settings.js +++ b/packages/cozy-client/src/helpers/settings.js @@ -17,7 +17,7 @@ export const getSetting = async (client, slug, key) => { const query = getQuery(slug) const currentSettingsResult = await client.fetchQueryAndGetFromState({ - definition: query.definition(), + definition: query.definition, options: query.options }) @@ -47,7 +47,7 @@ export const saveAfterFetchSetting = async ( const query = getQuery(slug) const currentSettingsResult = await client.fetchQueryAndGetFromState({ - definition: query.definition(), + definition: query.definition, options: query.options }) @@ -143,7 +143,7 @@ export const getQuery = slug => { const getRootSettings = slug => { const settingsDoctype = getDoctype(slug) const query = { - definition: () => Q(settingsDoctype).limitBy(1), + definition: Q(settingsDoctype).limitBy(1), options: { as: settingsDoctype, fetchPolicy: defaultFetchPolicy, @@ -158,7 +158,7 @@ const getNestedSettings = slug => { const doctype = `io.cozy.settings` const subDoctype = getDoctype(slug) const query = { - definition: () => Q(doctype).getById(subDoctype), + definition: Q(doctype).getById(subDoctype), options: { as: `${doctype}/${subDoctype}`, fetchPolicy: defaultFetchPolicy, diff --git a/packages/cozy-client/src/hooks/useSetting.js b/packages/cozy-client/src/hooks/useSetting.js index 60158cb990..13cc8983be 100644 --- a/packages/cozy-client/src/hooks/useSetting.js +++ b/packages/cozy-client/src/hooks/useSetting.js @@ -21,7 +21,7 @@ export const useSetting = (slug, key) => { const query = getQuery(slug) const { data: settingsData, ...settingsQuery } = useQuery( - query.definition(), + query.definition, query.options ) diff --git a/packages/cozy-client/src/types.js b/packages/cozy-client/src/types.js index 30f2bdfa49..a03c6a0f1e 100644 --- a/packages/cozy-client/src/types.js +++ b/packages/cozy-client/src/types.js @@ -293,7 +293,7 @@ import { QueryDefinition } from './queries/dsl' /** * @typedef {object} Query - * @property {() => QueryDefinition} definition + * @property {QueryDefinition} definition * @property {QueryOptions} options */ diff --git a/packages/cozy-client/types/types.d.ts b/packages/cozy-client/types/types.d.ts index 3bf955388e..c32548a60e 100644 --- a/packages/cozy-client/types/types.d.ts +++ b/packages/cozy-client/types/types.d.ts @@ -543,7 +543,7 @@ export type QueryOptions = { singleDocData?: boolean; }; export type Query = { - definition: () => QueryDefinition; + definition: QueryDefinition; options: QueryOptions; }; export type FetchMoreAble = { From 18a6c8fea4014c52f8ef990306c5428340e95e6a Mon Sep 17 00:00:00 2001 From: Ldoppea Date: Tue, 9 Apr 2024 16:24:28 +0200 Subject: [PATCH 06/11] docs: Update docs --- docs/api/cozy-client/classes/CozyClient.md | 98 +++++++++++----------- 1 file changed, 51 insertions(+), 47 deletions(-) diff --git a/docs/api/cozy-client/classes/CozyClient.md b/docs/api/cozy-client/classes/CozyClient.md index 2687fbf939..716a008efd 100644 --- a/docs/api/cozy-client/classes/CozyClient.md +++ b/docs/api/cozy-client/classes/CozyClient.md @@ -83,7 +83,7 @@ Cozy-Client will automatically call `this.login()` if provided with a token and *Defined in* -[packages/cozy-client/src/CozyClient.js:1627](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1627) +[packages/cozy-client/src/CozyClient.js:1631](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1631) *** @@ -199,7 +199,7 @@ Cozy-Client will automatically call `this.login()` if provided with a token and *Defined in* -[packages/cozy-client/src/CozyClient.js:1602](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1602) +[packages/cozy-client/src/CozyClient.js:1606](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1606) *** @@ -209,7 +209,7 @@ Cozy-Client will automatically call `this.login()` if provided with a token and *Defined in* -[packages/cozy-client/src/CozyClient.js:1532](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1532) +[packages/cozy-client/src/CozyClient.js:1536](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1536) *** @@ -239,7 +239,7 @@ Cozy-Client will automatically call `this.login()` if provided with a token and *Defined in* -[packages/cozy-client/src/CozyClient.js:1285](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1285) +[packages/cozy-client/src/CozyClient.js:1289](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1289) *** @@ -353,7 +353,7 @@ Contains the fetched token and the client information. These should be stored an *Defined in* -[packages/cozy-client/src/CozyClient.js:1448](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1448) +[packages/cozy-client/src/CozyClient.js:1452](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1452) *** @@ -371,7 +371,7 @@ This mechanism is described in https://github.com/cozy/cozy-client/blob/master/p *Defined in* -[packages/cozy-client/src/CozyClient.js:1429](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1429) +[packages/cozy-client/src/CozyClient.js:1433](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1433) *** @@ -387,7 +387,7 @@ Returns whether the client has been revoked on the server *Defined in* -[packages/cozy-client/src/CozyClient.js:1544](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1544) +[packages/cozy-client/src/CozyClient.js:1548](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1548) *** @@ -471,7 +471,7 @@ If `oauth` options are passed, stackClient is an OAuthStackClient. *Defined in* -[packages/cozy-client/src/CozyClient.js:1582](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1582) +[packages/cozy-client/src/CozyClient.js:1586](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1586) *** @@ -516,7 +516,7 @@ The document that has been deleted *Defined in* -[packages/cozy-client/src/CozyClient.js:1653](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1653) +[packages/cozy-client/src/CozyClient.js:1657](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1657) *** @@ -602,7 +602,7 @@ Makes sure that the query exists in the store *Defined in* -[packages/cozy-client/src/CozyClient.js:1535](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1535) +[packages/cozy-client/src/CozyClient.js:1539](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1539) *** @@ -654,7 +654,7 @@ Query state *Defined in* -[packages/cozy-client/src/CozyClient.js:1382](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1382) +[packages/cozy-client/src/CozyClient.js:1386](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1386) *** @@ -689,7 +689,7 @@ Query state *Defined in* -[packages/cozy-client/src/CozyClient.js:1260](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1260) +[packages/cozy-client/src/CozyClient.js:1264](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1264) *** @@ -733,7 +733,7 @@ Creates an association that is linked to the store. *Defined in* -[packages/cozy-client/src/CozyClient.js:1267](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1267) +[packages/cozy-client/src/CozyClient.js:1271](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1271) *** @@ -747,7 +747,7 @@ Creates an association that is linked to the store. *Defined in* -[packages/cozy-client/src/CozyClient.js:1635](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1635) +[packages/cozy-client/src/CozyClient.js:1639](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1639) *** @@ -771,7 +771,7 @@ Array of documents or null if the collection does not exist. *Defined in* -[packages/cozy-client/src/CozyClient.js:1303](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1303) +[packages/cozy-client/src/CozyClient.js:1307](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1307) *** @@ -796,7 +796,7 @@ Document or null if the object does not exist. *Defined in* -[packages/cozy-client/src/CozyClient.js:1320](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1320) +[packages/cozy-client/src/CozyClient.js:1324](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1324) *** @@ -851,7 +851,7 @@ One or more mutation to execute *Defined in* -[packages/cozy-client/src/CozyClient.js:1187](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1187) +[packages/cozy-client/src/CozyClient.js:1191](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1191) *** @@ -867,7 +867,7 @@ getInstanceOptions - Returns current instance options, such as domain or app slu *Defined in* -[packages/cozy-client/src/CozyClient.js:1662](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1662) +[packages/cozy-client/src/CozyClient.js:1666](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1666) *** @@ -894,7 +894,7 @@ Get a query from the internal store. *Defined in* -[packages/cozy-client/src/CozyClient.js:1341](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1341) +[packages/cozy-client/src/CozyClient.js:1345](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1345) *** @@ -923,7 +923,7 @@ the store up, which in turn will update the ``s and re-render the data. *Defined in* -[packages/cozy-client/src/CozyClient.js:1283](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1283) +[packages/cozy-client/src/CozyClient.js:1287](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1287) *** @@ -949,7 +949,7 @@ extract the value corresponding to the given `key` *Defined in* -[packages/cozy-client/src/CozyClient.js:1761](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1761) +[packages/cozy-client/src/CozyClient.js:1765](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1765) *** @@ -963,7 +963,7 @@ extract the value corresponding to the given `key` *Defined in* -[packages/cozy-client/src/CozyClient.js:1642](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1642) +[packages/cozy-client/src/CozyClient.js:1646](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1646) *** @@ -985,7 +985,7 @@ Sets public attribute and emits event related to revocation *Defined in* -[packages/cozy-client/src/CozyClient.js:1553](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1553) +[packages/cozy-client/src/CozyClient.js:1557](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1557) *** @@ -1007,7 +1007,7 @@ Emits event when token is refreshed *Defined in* -[packages/cozy-client/src/CozyClient.js:1564](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1564) +[packages/cozy-client/src/CozyClient.js:1568](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1568) *** @@ -1033,7 +1033,7 @@ the relationship *Defined in* -[packages/cozy-client/src/CozyClient.js:1230](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1230) +[packages/cozy-client/src/CozyClient.js:1234](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1234) *** @@ -1058,7 +1058,7 @@ Instead, the relationships will have null documents. *Defined in* -[packages/cozy-client/src/CozyClient.js:1207](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1207) +[packages/cozy-client/src/CozyClient.js:1211](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1211) *** @@ -1079,7 +1079,7 @@ Instead, the relationships will have null documents. *Defined in* -[packages/cozy-client/src/CozyClient.js:1241](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1241) +[packages/cozy-client/src/CozyClient.js:1245](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1245) *** @@ -1093,7 +1093,7 @@ Instead, the relationships will have null documents. *Defined in* -[packages/cozy-client/src/CozyClient.js:1405](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1405) +[packages/cozy-client/src/CozyClient.js:1409](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1409) *** @@ -1115,7 +1115,7 @@ loadInstanceOptionsFromDOM - Loads the dataset injected by the Stack in web page *Defined in* -[packages/cozy-client/src/CozyClient.js:1673](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1673) +[packages/cozy-client/src/CozyClient.js:1677](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1677) *** @@ -1133,7 +1133,7 @@ This method is not iso with loadInstanceOptionsFromDOM for now. *Defined in* -[packages/cozy-client/src/CozyClient.js:1694](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1694) +[packages/cozy-client/src/CozyClient.js:1698](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1698) *** @@ -1214,7 +1214,7 @@ and working. *Defined in* -[packages/cozy-client/src/CozyClient.js:1253](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1253) +[packages/cozy-client/src/CozyClient.js:1257](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1257) *** @@ -1235,7 +1235,7 @@ and working. *Defined in* -[packages/cozy-client/src/CozyClient.js:1029](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1029) +[packages/cozy-client/src/CozyClient.js:1033](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1033) *** @@ -1261,7 +1261,7 @@ Mutate a document *Defined in* -[packages/cozy-client/src/CozyClient.js:1047](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1047) +[packages/cozy-client/src/CozyClient.js:1051](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1051) *** @@ -1317,6 +1317,10 @@ Results from the query will be saved internally and can be retrieved via `getQueryFromState` or directly using ``. `` automatically executes its query when mounted if no fetch policy has been indicated. +If the query is called under the fetch policy's delay, then the query +is not executed and nothing is returned. If you need a result anyway, +please use `fetchQueryAndGetFromState` instead + *Parameters* | Name | Type | Description | @@ -1330,7 +1334,7 @@ executes its query when mounted if no fetch policy has been indicated. *Defined in* -[packages/cozy-client/src/CozyClient.js:912](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L912) +[packages/cozy-client/src/CozyClient.js:916](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L916) *** @@ -1357,7 +1361,7 @@ All documents matching the query *Defined in* -[packages/cozy-client/src/CozyClient.js:989](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L989) +[packages/cozy-client/src/CozyClient.js:993](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L993) *** @@ -1391,7 +1395,7 @@ All documents matching the query *Defined in* -[packages/cozy-client/src/CozyClient.js:1649](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1649) +[packages/cozy-client/src/CozyClient.js:1653](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1653) *** @@ -1417,7 +1421,7 @@ Contains the fetched token and the client information. *Defined in* -[packages/cozy-client/src/CozyClient.js:1399](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1399) +[packages/cozy-client/src/CozyClient.js:1403](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1403) *** @@ -1538,7 +1542,7 @@ Contains the fetched token and the client information. *Defined in* -[packages/cozy-client/src/CozyClient.js:1494](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1494) +[packages/cozy-client/src/CozyClient.js:1498](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1498) *** @@ -1558,7 +1562,7 @@ Contains the fetched token and the client information. *Defined in* -[packages/cozy-client/src/CozyClient.js:1171](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1171) +[packages/cozy-client/src/CozyClient.js:1175](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1175) *** @@ -1610,7 +1614,7 @@ save the new resulting settings into database *Defined in* -[packages/cozy-client/src/CozyClient.js:1776](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1776) +[packages/cozy-client/src/CozyClient.js:1780](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1780) *** @@ -1659,7 +1663,7 @@ Saves multiple documents in one batch *Defined in* -[packages/cozy-client/src/CozyClient.js:1746](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1746) +[packages/cozy-client/src/CozyClient.js:1750](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1750) *** @@ -1683,7 +1687,7 @@ set some data in the store. *Defined in* -[packages/cozy-client/src/CozyClient.js:1719](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1719) +[packages/cozy-client/src/CozyClient.js:1723](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1723) *** @@ -1707,7 +1711,7 @@ At any time put an error function *Defined in* -[packages/cozy-client/src/CozyClient.js:1732](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1732) +[packages/cozy-client/src/CozyClient.js:1736](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1736) *** @@ -1745,7 +1749,7 @@ use options.force = true. *Defined in* -[packages/cozy-client/src/CozyClient.js:1520](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1520) +[packages/cozy-client/src/CozyClient.js:1524](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1524) *** @@ -1769,7 +1773,7 @@ Contains the fetched token and the client information. These should be stored an *Defined in* -[packages/cozy-client/src/CozyClient.js:1415](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1415) +[packages/cozy-client/src/CozyClient.js:1419](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1419) *** @@ -1783,7 +1787,7 @@ Contains the fetched token and the client information. These should be stored an *Defined in* -[packages/cozy-client/src/CozyClient.js:1739](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1739) +[packages/cozy-client/src/CozyClient.js:1743](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1743) *** @@ -1866,7 +1870,7 @@ Contains the fetched token and the client information. These should be stored an *Defined in* -[packages/cozy-client/src/CozyClient.js:1022](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1022) +[packages/cozy-client/src/CozyClient.js:1026](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1026) *** From 1a8ad80bc8d64ceb66bc06c8456bed8aec1e2603 Mon Sep 17 00:00:00 2001 From: Ldoppea Date: Fri, 12 Apr 2024 16:21:52 +0200 Subject: [PATCH 07/11] feat: Add defaultValue parameter to `getSetting()` and to `useSetting()` --- docs/api/cozy-client/README.md | 13 +++---- packages/cozy-client/src/helpers/settings.js | 10 ++++-- .../cozy-client/src/helpers/settings.spec.js | 34 +++++++++++++++++++ packages/cozy-client/src/hooks/useSetting.js | 10 ++++-- .../cozy-client/types/helpers/settings.d.ts | 2 +- .../cozy-client/types/hooks/useSetting.d.ts | 2 +- 6 files changed, 59 insertions(+), 12 deletions(-) diff --git a/docs/api/cozy-client/README.md b/docs/api/cozy-client/README.md index c0d6b438b9..001c9b1d43 100644 --- a/docs/api/cozy-client/README.md +++ b/docs/api/cozy-client/README.md @@ -932,7 +932,7 @@ Fetches a queryDefinition and run fetchMore on the query until the query is full ### useSetting -▸ **useSetting**(`slug`, `key`): `UseSettingReturnValue` +▸ **useSetting**(`slug`, `key`, `defaultValue?`): `UseSettingReturnValue` Query the cozy-app settings corresponding to the given slug and return: @@ -944,10 +944,11 @@ return: *Parameters* -| Name | Type | Description | -| :------ | :------ | :------ | -| `slug` | `string` | the cozy-app's slug containing the setting (can be 'instance' for global settings) | -| `key` | `string` | The name of the setting to retrieve | +| Name | Type | Default value | Description | +| :------ | :------ | :------ | :------ | +| `slug` | `string` | `undefined` | the cozy-app's slug containing the setting (can be 'instance' for global settings) | +| `key` | `string` | `undefined` | The name of the setting to retrieve | +| `defaultValue` | `any` | `undefined` | - | *Returns* @@ -955,7 +956,7 @@ return: *Defined in* -[packages/cozy-client/src/hooks/useSetting.js:20](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/hooks/useSetting.js#L20) +[packages/cozy-client/src/hooks/useSetting.js:22](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/hooks/useSetting.js#L22) *** diff --git a/packages/cozy-client/src/helpers/settings.js b/packages/cozy-client/src/helpers/settings.js index 5460b1e797..e378328d0f 100644 --- a/packages/cozy-client/src/helpers/settings.js +++ b/packages/cozy-client/src/helpers/settings.js @@ -11,9 +11,15 @@ const defaultFetchPolicy = fetchPolicies.olderThan(60 * 60 * 1000) * @param {CozyClient} client - Cozy client instance * @param {string} slug - the cozy-app's slug containing the setting (can be 'instance' for global settings) * @param {string} key - The name of the setting to retrieve + * @param {any} [defaultValue] - The default value of the setting if it does not exist * @returns {Promise} - The value of the requested setting */ -export const getSetting = async (client, slug, key) => { +export const getSetting = async ( + client, + slug, + key, + defaultValue = undefined +) => { const query = getQuery(slug) const currentSettingsResult = await client.fetchQueryAndGetFromState({ @@ -23,7 +29,7 @@ export const getSetting = async (client, slug, key) => { const currentSettings = normalizeSettings(currentSettingsResult.data) - return currentSettings[key] + return currentSettings[key] ?? defaultValue } /** diff --git a/packages/cozy-client/src/helpers/settings.spec.js b/packages/cozy-client/src/helpers/settings.spec.js index 4c9f59b555..d37fe1ee32 100644 --- a/packages/cozy-client/src/helpers/settings.spec.js +++ b/packages/cozy-client/src/helpers/settings.spec.js @@ -129,6 +129,40 @@ describe('settings', () => { }) expect(result).toEqual('some_pass_value') }) + + it('should return undefined if no setting is found in database', async () => { + const client = mocks.client() + + client.fetchQueryAndGetFromState.mockResolvedValue({ + data: [ + { + some_other_key: 'some_other_value' + } + ] + }) + + // @ts-ignore + const result = await getSetting(client, 'home', 'some_key') + + expect(result).toBeUndefined() + }) + + it('should return defaultValue if corresponding parameter is passed and no setting is found in database', async () => { + const client = mocks.client() + + client.fetchQueryAndGetFromState.mockResolvedValue({ + data: [ + { + some_other_key: 'some_other_value' + } + ] + }) + + // @ts-ignore + const result = await getSetting(client, 'home', 'some_key', 0) + + expect(result).toEqual(0) + }) }) describe('saveAfterFetchSetting', () => { diff --git a/packages/cozy-client/src/hooks/useSetting.js b/packages/cozy-client/src/hooks/useSetting.js index 13cc8983be..524123bdfd 100644 --- a/packages/cozy-client/src/hooks/useSetting.js +++ b/packages/cozy-client/src/hooks/useSetting.js @@ -4,6 +4,7 @@ import { editSettings, getQuery, normalizeSettings } from '../helpers' import { useMutation } from './useMutation' import useQuery from './useQuery' +import { hasQueryBeenLoaded } from '../utils' /** * Query the cozy-app settings corresponding to the given slug and @@ -15,9 +16,10 @@ import useQuery from './useQuery' * * @param {string} slug - the cozy-app's slug containing the setting (can be 'instance' for global settings) * @param {string} key - The name of the setting to retrieve + * @param {any} [defaultValue] - The default value of the setting if it does not exist * @returns {import("../types").UseSettingReturnValue} */ -export const useSetting = (slug, key) => { +export const useSetting = (slug, key, defaultValue = undefined) => { const query = getQuery(slug) const { data: settingsData, ...settingsQuery } = useQuery( @@ -40,9 +42,13 @@ export const useSetting = (slug, key) => { const settings = normalizeSettings(settingsData) + const settingValue = hasQueryBeenLoaded(settingsQuery) + ? settings?.[key] ?? defaultValue + : undefined + return { query: settingsQuery, - value: settings?.[key], + value: settingValue, save, mutation } diff --git a/packages/cozy-client/types/helpers/settings.d.ts b/packages/cozy-client/types/helpers/settings.d.ts index e8ce49aab2..d886cda02c 100644 --- a/packages/cozy-client/types/helpers/settings.d.ts +++ b/packages/cozy-client/types/helpers/settings.d.ts @@ -1,4 +1,4 @@ -export function getSetting(client: CozyClient, slug: string, key: string): Promise; +export function getSetting(client: CozyClient, slug: string, key: string, defaultValue?: any): Promise; export function saveAfterFetchSetting(client: CozyClient, slug: string, key: string, valueOrSetter: any): Promise; export function normalizeSettings(data: any[] | any): any; export function editSettings(slug: string, currentSettings: any, key: string, value: any): any; diff --git a/packages/cozy-client/types/hooks/useSetting.d.ts b/packages/cozy-client/types/hooks/useSetting.d.ts index 12c9c72de4..f3d1048ecd 100644 --- a/packages/cozy-client/types/hooks/useSetting.d.ts +++ b/packages/cozy-client/types/hooks/useSetting.d.ts @@ -1 +1 @@ -export function useSetting(slug: string, key: string): import("../types").UseSettingReturnValue; +export function useSetting(slug: string, key: string, defaultValue?: any): import("../types").UseSettingReturnValue; From bae58fe6e3179e3387f7bfafcfb02cd0acb8a7e5 Mon Sep 17 00:00:00 2001 From: Ldoppea Date: Mon, 15 Apr 2024 08:39:46 +0200 Subject: [PATCH 08/11] feat: Remove defaultValue parameter from `getSetting()` & `useSetting()` --- docs/api/cozy-client/README.md | 13 ++++++------- packages/cozy-client/src/helpers/settings.js | 10 ++-------- .../cozy-client/src/helpers/settings.spec.js | 17 ----------------- packages/cozy-client/src/hooks/useSetting.js | 5 ++--- .../cozy-client/types/helpers/settings.d.ts | 2 +- .../cozy-client/types/hooks/useSetting.d.ts | 2 +- 6 files changed, 12 insertions(+), 37 deletions(-) diff --git a/docs/api/cozy-client/README.md b/docs/api/cozy-client/README.md index 001c9b1d43..a6fadd9790 100644 --- a/docs/api/cozy-client/README.md +++ b/docs/api/cozy-client/README.md @@ -932,7 +932,7 @@ Fetches a queryDefinition and run fetchMore on the query until the query is full ### useSetting -▸ **useSetting**(`slug`, `key`, `defaultValue?`): `UseSettingReturnValue` +▸ **useSetting**(`slug`, `key`): `UseSettingReturnValue` Query the cozy-app settings corresponding to the given slug and return: @@ -944,11 +944,10 @@ return: *Parameters* -| Name | Type | Default value | Description | -| :------ | :------ | :------ | :------ | -| `slug` | `string` | `undefined` | the cozy-app's slug containing the setting (can be 'instance' for global settings) | -| `key` | `string` | `undefined` | The name of the setting to retrieve | -| `defaultValue` | `any` | `undefined` | - | +| Name | Type | Description | +| :------ | :------ | :------ | +| `slug` | `string` | the cozy-app's slug containing the setting (can be 'instance' for global settings) | +| `key` | `string` | The name of the setting to retrieve | *Returns* @@ -956,7 +955,7 @@ return: *Defined in* -[packages/cozy-client/src/hooks/useSetting.js:22](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/hooks/useSetting.js#L22) +[packages/cozy-client/src/hooks/useSetting.js:21](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/hooks/useSetting.js#L21) *** diff --git a/packages/cozy-client/src/helpers/settings.js b/packages/cozy-client/src/helpers/settings.js index e378328d0f..5460b1e797 100644 --- a/packages/cozy-client/src/helpers/settings.js +++ b/packages/cozy-client/src/helpers/settings.js @@ -11,15 +11,9 @@ const defaultFetchPolicy = fetchPolicies.olderThan(60 * 60 * 1000) * @param {CozyClient} client - Cozy client instance * @param {string} slug - the cozy-app's slug containing the setting (can be 'instance' for global settings) * @param {string} key - The name of the setting to retrieve - * @param {any} [defaultValue] - The default value of the setting if it does not exist * @returns {Promise} - The value of the requested setting */ -export const getSetting = async ( - client, - slug, - key, - defaultValue = undefined -) => { +export const getSetting = async (client, slug, key) => { const query = getQuery(slug) const currentSettingsResult = await client.fetchQueryAndGetFromState({ @@ -29,7 +23,7 @@ export const getSetting = async ( const currentSettings = normalizeSettings(currentSettingsResult.data) - return currentSettings[key] ?? defaultValue + return currentSettings[key] } /** diff --git a/packages/cozy-client/src/helpers/settings.spec.js b/packages/cozy-client/src/helpers/settings.spec.js index d37fe1ee32..809f98ca25 100644 --- a/packages/cozy-client/src/helpers/settings.spec.js +++ b/packages/cozy-client/src/helpers/settings.spec.js @@ -146,23 +146,6 @@ describe('settings', () => { expect(result).toBeUndefined() }) - - it('should return defaultValue if corresponding parameter is passed and no setting is found in database', async () => { - const client = mocks.client() - - client.fetchQueryAndGetFromState.mockResolvedValue({ - data: [ - { - some_other_key: 'some_other_value' - } - ] - }) - - // @ts-ignore - const result = await getSetting(client, 'home', 'some_key', 0) - - expect(result).toEqual(0) - }) }) describe('saveAfterFetchSetting', () => { diff --git a/packages/cozy-client/src/hooks/useSetting.js b/packages/cozy-client/src/hooks/useSetting.js index 524123bdfd..31e6c20829 100644 --- a/packages/cozy-client/src/hooks/useSetting.js +++ b/packages/cozy-client/src/hooks/useSetting.js @@ -16,10 +16,9 @@ import { hasQueryBeenLoaded } from '../utils' * * @param {string} slug - the cozy-app's slug containing the setting (can be 'instance' for global settings) * @param {string} key - The name of the setting to retrieve - * @param {any} [defaultValue] - The default value of the setting if it does not exist * @returns {import("../types").UseSettingReturnValue} */ -export const useSetting = (slug, key, defaultValue = undefined) => { +export const useSetting = (slug, key) => { const query = getQuery(slug) const { data: settingsData, ...settingsQuery } = useQuery( @@ -43,7 +42,7 @@ export const useSetting = (slug, key, defaultValue = undefined) => { const settings = normalizeSettings(settingsData) const settingValue = hasQueryBeenLoaded(settingsQuery) - ? settings?.[key] ?? defaultValue + ? settings?.[key] : undefined return { diff --git a/packages/cozy-client/types/helpers/settings.d.ts b/packages/cozy-client/types/helpers/settings.d.ts index d886cda02c..e8ce49aab2 100644 --- a/packages/cozy-client/types/helpers/settings.d.ts +++ b/packages/cozy-client/types/helpers/settings.d.ts @@ -1,4 +1,4 @@ -export function getSetting(client: CozyClient, slug: string, key: string, defaultValue?: any): Promise; +export function getSetting(client: CozyClient, slug: string, key: string): Promise; export function saveAfterFetchSetting(client: CozyClient, slug: string, key: string, valueOrSetter: any): Promise; export function normalizeSettings(data: any[] | any): any; export function editSettings(slug: string, currentSettings: any, key: string, value: any): any; diff --git a/packages/cozy-client/types/hooks/useSetting.d.ts b/packages/cozy-client/types/hooks/useSetting.d.ts index f3d1048ecd..12c9c72de4 100644 --- a/packages/cozy-client/types/hooks/useSetting.d.ts +++ b/packages/cozy-client/types/hooks/useSetting.d.ts @@ -1 +1 @@ -export function useSetting(slug: string, key: string, defaultValue?: any): import("../types").UseSettingReturnValue; +export function useSetting(slug: string, key: string): import("../types").UseSettingReturnValue; From 980067920788a4dc2f0180327a483a258e81976f Mon Sep 17 00:00:00 2001 From: Ldoppea Date: Mon, 15 Apr 2024 08:55:13 +0200 Subject: [PATCH 09/11] feat: Allow to get or set multiple settings in a single call --- docs/api/cozy-client/README.md | 18 ++-- docs/api/cozy-client/classes/CozyClient.md | 30 ++++-- packages/cozy-client/src/CozyClient.js | 20 ++-- packages/cozy-client/src/helpers/index.js | 4 +- packages/cozy-client/src/helpers/settings.js | 102 ++++++++++++------ .../cozy-client/src/helpers/settings.spec.js | 99 +++++++++++------ packages/cozy-client/src/hooks/index.js | 2 +- packages/cozy-client/src/hooks/useSetting.js | 21 ++-- packages/cozy-client/src/types.js | 13 ++- packages/cozy-client/types/CozyClient.d.ts | 14 ++- packages/cozy-client/types/helpers/index.d.ts | 2 +- .../cozy-client/types/helpers/settings.d.ts | 7 +- packages/cozy-client/types/hooks/index.d.ts | 2 +- .../cozy-client/types/hooks/useSetting.d.ts | 2 +- packages/cozy-client/types/types.d.ts | 8 +- 15 files changed, 228 insertions(+), 116 deletions(-) diff --git a/docs/api/cozy-client/README.md b/docs/api/cozy-client/README.md index a6fadd9790..092e7beba6 100644 --- a/docs/api/cozy-client/README.md +++ b/docs/api/cozy-client/README.md @@ -930,32 +930,38 @@ Fetches a queryDefinition and run fetchMore on the query until the query is full *** -### useSetting +### useSettings -▸ **useSetting**(`slug`, `key`): `UseSettingReturnValue` +▸ **useSettings**<`T`>(`slug`, `keys`): `UseSettingsReturnValue`<`T`> Query the cozy-app settings corresponding to the given slug and return: -* the value corresponding to the given `key` +* the values corresponding to the given `keys` * the `save()` method that can be used to edit the setting's value * the query that manages the state during the fetching of the setting * the mutation that manages the state during the saving of the setting +#### Type parameters + +| Name | Type | +| :------ | :------ | +| `T` | extends `string` | + *Parameters* | Name | Type | Description | | :------ | :------ | :------ | | `slug` | `string` | the cozy-app's slug containing the setting (can be 'instance' for global settings) | -| `key` | `string` | The name of the setting to retrieve | +| `keys` | `T`\[] | The name of the setting to retrieve | *Returns* -`UseSettingReturnValue` +`UseSettingsReturnValue`<`T`> *Defined in* -[packages/cozy-client/src/hooks/useSetting.js:21](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/hooks/useSetting.js#L21) +[packages/cozy-client/src/hooks/useSetting.js:24](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/hooks/useSetting.js#L24) *** diff --git a/docs/api/cozy-client/classes/CozyClient.md b/docs/api/cozy-client/classes/CozyClient.md index 716a008efd..30b930f524 100644 --- a/docs/api/cozy-client/classes/CozyClient.md +++ b/docs/api/cozy-client/classes/CozyClient.md @@ -927,19 +927,25 @@ the store up, which in turn will update the ``s and re-render the data. *** -### getSetting +### getSettings -▸ **getSetting**(`slug`, `key`): `Promise`<`any`> +▸ **getSettings**<`T`>(`slug`, `keys`): `Promise`<`any`> Query the cozy-app settings corresponding to the given slug and extract the value corresponding to the given `key` +#### Type parameters + +| Name | Type | +| :------ | :------ | +| `T` | extends `string` | + *Parameters* | Name | Type | Description | | :------ | :------ | :------ | | `slug` | `string` | the cozy-app's slug containing the setting (can be 'instance' for global settings) | -| `key` | `string` | The name of the setting to retrieve | +| `keys` | `T`\[] | The names of the settings to retrieve | *Returns* @@ -949,7 +955,7 @@ extract the value corresponding to the given `key` *Defined in* -[packages/cozy-client/src/CozyClient.js:1765](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1765) +[packages/cozy-client/src/CozyClient.js:1767](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1767) *** @@ -1589,22 +1595,28 @@ Create or update a document on the server *** -### saveAfterFetchSetting +### saveAfterFetchSettings -▸ **saveAfterFetchSetting**(`slug`, `key`, `valueOrSetter`): `Promise`<`any`> +▸ **saveAfterFetchSettings**<`T`>(`slug`, `itemsOrSetter`, `setterKeys`): `Promise`<`any`> Save the given value into the corresponding cozy-app setting This methods will first query the cozy-app's settings before injecting the new value and then save the new resulting settings into database +#### Type parameters + +| Name | Type | +| :------ | :------ | +| `T` | extends `string` | + *Parameters* | Name | Type | Description | | :------ | :------ | :------ | | `slug` | `string` | the cozy-app's slug containing the setting (can be 'instance' for global settings) | -| `key` | `string` | The new value of the setting to save | -| `valueOrSetter` | `any` | The new value of the setting to save. It can be the raw value, or a callback that should return a new value | +| `itemsOrSetter` | `Record`<`string`, `any`> | (`oldValue`: `any`) => `Record`<`T`, `any`> | The new values of the settings to save. It can be a raw dictionnary, or a callback that should return a new dictionnary | +| `setterKeys` | `T`\[] | The new values of the settings to save. It can be a raw dictionnary, or a callback that should return a new dictionnary | *Returns* @@ -1614,7 +1626,7 @@ save the new resulting settings into database *Defined in* -[packages/cozy-client/src/CozyClient.js:1780](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1780) +[packages/cozy-client/src/CozyClient.js:1784](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1784) *** diff --git a/packages/cozy-client/src/CozyClient.js b/packages/cozy-client/src/CozyClient.js index 813cea3b20..bdd6f45027 100644 --- a/packages/cozy-client/src/CozyClient.js +++ b/packages/cozy-client/src/CozyClient.js @@ -17,7 +17,7 @@ import { responseToRelationship, attachRelationships } from './associations/helpers' -import { dehydrate, getSetting, saveAfterFetchSetting } from './helpers' +import { dehydrate, getSettings, saveAfterFetchSettings } from './helpers' import { QueryDefinition, Mutations, Q } from './queries/dsl' import { authFunction } from './authentication/mobile' import optimizeQueryDefinitions from './queries/optimize' @@ -1758,12 +1758,14 @@ instantiation of the client.` * Query the cozy-app settings corresponding to the given slug and * extract the value corresponding to the given `key` * + * @template {string} T + * * @param {string} slug - the cozy-app's slug containing the setting (can be 'instance' for global settings) - * @param {string} key - The name of the setting to retrieve + * @param {T[]} keys - The names of the settings to retrieve * @returns {Promise} - The value of the requested setting */ - async getSetting(slug, key) { - return getSetting(this, slug, key) + async getSettings(slug, keys) { + return getSettings(this, slug, keys) } /** @@ -1772,13 +1774,15 @@ instantiation of the client.` * This methods will first query the cozy-app's settings before injecting the new value and then * save the new resulting settings into database * + * @template {string} T + * * @param {string} slug - the cozy-app's slug containing the setting (can be 'instance' for global settings) - * @param {string} key - The new value of the setting to save - * @param {any | ((oldValue) => any)} valueOrSetter - The new value of the setting to save. It can be the raw value, or a callback that should return a new value + * @param {Record | ((oldValue) => Record)} itemsOrSetter - The new values of the settings to save. It can be a raw dictionnary, or a callback that should return a new dictionnary + * @param {T[]=} setterKeys - The new values of the settings to save. It can be a raw dictionnary, or a callback that should return a new dictionnary * @returns {Promise} - The result of the `client.save()` call */ - async saveAfterFetchSetting(slug, key, valueOrSetter) { - return saveAfterFetchSetting(this, slug, key, valueOrSetter) + async saveAfterFetchSettings(slug, itemsOrSetter, setterKeys) { + return saveAfterFetchSettings(this, slug, itemsOrSetter, setterKeys) } } diff --git a/packages/cozy-client/src/helpers/index.js b/packages/cozy-client/src/helpers/index.js index 6a84a28cf3..d0a4be8b27 100644 --- a/packages/cozy-client/src/helpers/index.js +++ b/packages/cozy-client/src/helpers/index.js @@ -15,7 +15,7 @@ export { dehydrate } from './dehydrateHelper' export { editSettings, getQuery, - getSetting, + getSettings, normalizeSettings, - saveAfterFetchSetting + saveAfterFetchSettings } from './settings' diff --git a/packages/cozy-client/src/helpers/settings.js b/packages/cozy-client/src/helpers/settings.js index 5460b1e797..449aa75f87 100644 --- a/packages/cozy-client/src/helpers/settings.js +++ b/packages/cozy-client/src/helpers/settings.js @@ -6,14 +6,16 @@ const defaultFetchPolicy = fetchPolicies.olderThan(60 * 60 * 1000) /** * Query the cozy-app settings corresponding to the given slug and - * extract the value corresponding to the given `key` + * extract the values corresponding to the given `keys` + * + * @template {string} T * * @param {CozyClient} client - Cozy client instance * @param {string} slug - the cozy-app's slug containing the setting (can be 'instance' for global settings) - * @param {string} key - The name of the setting to retrieve - * @returns {Promise} - The value of the requested setting + * @param {T[]} keys - The names of the settings to retrieve + * @returns {Promise>} - The values of the requested settings */ -export const getSetting = async (client, slug, key) => { +export const getSettings = async (client, slug, keys) => { const query = getQuery(slug) const currentSettingsResult = await client.fetchQueryAndGetFromState({ @@ -23,7 +25,8 @@ export const getSetting = async (client, slug, key) => { const currentSettings = normalizeSettings(currentSettingsResult.data) - return currentSettings[key] + // @ts-ignore + return extractKeys(currentSettings, keys) } /** @@ -32,17 +35,19 @@ export const getSetting = async (client, slug, key) => { * This methods will first query the cozy-app's settings before injecting the new value and then * save the new resulting settings into database * + * @template {string} T + * * @param {CozyClient} client - Cozy client instance * @param {string} slug - the cozy-app's slug containing the setting (can be 'instance' for global settings) - * @param {string} key - The new value of the setting to save - * @param {any | ((oldValue) => any)} valueOrSetter - The new value of the setting to save. It can be the raw value, or a callback that should return a new value + * @param {Record | ((oldValue) => Record)} itemsOrSetter - The new values of the settings to save. It can be a raw dictionnary, or a callback that should return a new dictionnary + * @param {T[]=} setterKeys - The new values of the settings to save. It can be a raw dictionnary, or a callback that should return a new dictionnary * @returns {Promise} - The result of the `client.save()` call */ -export const saveAfterFetchSetting = async ( +export const saveAfterFetchSettings = async ( client, slug, - key, - valueOrSetter + itemsOrSetter, + setterKeys ) => { const query = getQuery(slug) @@ -53,14 +58,22 @@ export const saveAfterFetchSetting = async ( const currentSettings = normalizeSettings(currentSettingsResult.data) - let value = undefined - if (typeof valueOrSetter === 'function') { - value = valueOrSetter(currentSettings[key]) + let items = undefined + if (typeof itemsOrSetter === 'function') { + const currentItems = extractKeys(currentSettings, setterKeys) + items = itemsOrSetter(currentItems) } else { - value = valueOrSetter + const currentItems = extractKeys( + currentSettings, + Object.keys(itemsOrSetter) + ) + items = { + ...currentItems, + ...itemsOrSetter + } } - const newSettings = editSettings(slug, currentSettings, key, value) + const newSettings = editSettings(slug, currentSettings, items) return await client.save(newSettings) } @@ -85,41 +98,70 @@ export const normalizeSettings = data => { * * @param {string} slug - the cozy-app's slug containing the setting (can be 'instance' for global settings) * @param {Object} currentSettings - the Setting object (ideally from a `client.query()` or a `useQuery()` and normalized using `normalizeSettings`) - * @param {string} key - The name of the setting to edit - * @param {any} value - The new value for the setting + * @param {Record} items - The new values for the settings * @returns {Object} a new Setting object containing the new value */ -export const editSettings = (slug, currentSettings, key, value) => { +export const editSettings = (slug, currentSettings, items) => { const type = getDoctype(slug) const newSettings = slug === 'instance' - ? mergeInstance(currentSettings, key, value) - : mergeSettings(currentSettings, type, key, value) + ? mergeInstance(currentSettings, items) + : mergeSettings(currentSettings, type, items) return newSettings } -const mergeInstance = (currentSettings, key, value) => { - return { +const mergeInstance = (currentSettings, items) => { + const newSettings = { _id: currentSettings._id, _type: currentSettings._type, _rev: currentSettings.meta.rev, ...currentSettings, attributes: { - ...currentSettings.attributes, - [key]: value - }, - [key]: value + ...currentSettings.attributes + } + } + + for (const [key, value] of Object.entries(items)) { + newSettings[key] = value + newSettings.attributes[key] = value } + + return newSettings } -const mergeSettings = (currentSettings, type, key, value) => { - return { +const mergeSettings = (currentSettings, type, items) => { + const newSettings = { _type: type, - ...currentSettings, - [key]: value + ...currentSettings + } + + for (const [key, value] of Object.entries(items)) { + newSettings[key] = value } + + return newSettings +} + +/** + * Extract values from given settings for requested keys + * + * @template {string} T + * + * @param {Record} settings - the Setting object (ideally from a `client.query()` or a `useQuery()` and normalized using `normalizeSettings`) + * @param {T[]} keys - The names of the settings to extract + * @returns {Record} - Dictionnary containing the values for the requested keys + */ +export const extractKeys = (settings, keys) => { + let result = {} + for (const key of keys) { + // @ts-ignore + result[key] = settings[key] + } + + // @ts-ignore + return result } /** diff --git a/packages/cozy-client/src/helpers/settings.spec.js b/packages/cozy-client/src/helpers/settings.spec.js index 809f98ca25..07b5a563df 100644 --- a/packages/cozy-client/src/helpers/settings.spec.js +++ b/packages/cozy-client/src/helpers/settings.spec.js @@ -1,15 +1,15 @@ import { editSettings, - getSetting, + getSettings, normalizeSettings, - saveAfterFetchSetting + saveAfterFetchSettings } from './settings' import { Q } from '../queries/dsl' import * as mocks from '../__tests__/mocks' describe('settings', () => { - describe('getSetting', () => { + describe('getSettings', () => { it('should get settings for cozy-home', async () => { const client = mocks.client() @@ -22,7 +22,7 @@ describe('settings', () => { }) // @ts-ignore - const result = await getSetting(client, 'home', 'some_key') + const result = await getSettings(client, 'home', ['some_key']) const query = { definition: Q('io.cozy.home.settings').limitBy(1), @@ -36,7 +36,7 @@ describe('settings', () => { definition: query.definition, options: query.options }) - expect(result).toEqual('some_value') + expect(result.some_key).toEqual('some_value') }) it('should get settings for mespapiers', async () => { @@ -50,11 +50,11 @@ describe('settings', () => { ] }) - const result = await getSetting( + const result = await getSettings( // @ts-ignore client, 'mespapiers', - 'some_mespapiers_key' + ['some_mespapiers_key'] ) const query = { @@ -69,7 +69,7 @@ describe('settings', () => { definition: query.definition, options: query.options }) - expect(result).toEqual('some_mespapiers_value') + expect(result.some_mespapiers_key).toEqual('some_mespapiers_value') }) it('should get settings for instance', async () => { @@ -84,7 +84,7 @@ describe('settings', () => { }) // @ts-ignore - const result = await getSetting(client, 'instance', 'some_global_key') + const result = await getSettings(client, 'instance', ['some_global_key']) const query = { definition: Q('io.cozy.settings').getById('io.cozy.settings.instance'), @@ -98,7 +98,7 @@ describe('settings', () => { definition: query.definition, options: query.options }) - expect(result).toEqual('some_global_value') + expect(result.some_global_key).toEqual('some_global_value') }) it('should get settings for passwords', async () => { @@ -113,7 +113,7 @@ describe('settings', () => { }) // @ts-ignore - const result = await getSetting(client, 'passwords', 'some_pass_key') + const result = await getSettings(client, 'passwords', ['some_pass_key']) const query = { definition: Q('io.cozy.settings').getById('io.cozy.settings.bitwarden'), @@ -127,7 +127,7 @@ describe('settings', () => { definition: query.definition, options: query.options }) - expect(result).toEqual('some_pass_value') + expect(result.some_pass_key).toEqual('some_pass_value') }) it('should return undefined if no setting is found in database', async () => { @@ -142,13 +142,50 @@ describe('settings', () => { }) // @ts-ignore - const result = await getSetting(client, 'home', 'some_key') + const result = await getSettings(client, 'home', ['some_key']) - expect(result).toBeUndefined() + expect(result.some_key).toBeUndefined() + }) + + it('should get settings for multiple keys', async () => { + const client = mocks.client() + + client.fetchQueryAndGetFromState.mockResolvedValue({ + data: [ + { + some_key_1: 'some_value_1', + some_key_2: 'some_value_2', + some_key_3: 'some_value_3' + } + ] + }) + + // @ts-ignore + const result = await getSettings(client, 'home', [ + 'some_key_1', + 'some_key_2' + ]) + + const query = { + definition: Q('io.cozy.home.settings').limitBy(1), + options: { + as: 'io.cozy.home.settings', + fetchPolicy: expect.anything(), + singleDocData: true + } + } + expect(client.fetchQueryAndGetFromState).toHaveBeenCalledWith({ + definition: query.definition, + options: query.options + }) + expect(result.some_key_1).toEqual('some_value_1') + expect(result.some_key_2).toEqual('some_value_2') + // @ts-ignore + expect(result.some_key_3).toBeUndefined() }) }) - describe('saveAfterFetchSetting', () => { + describe('saveAfterFetchSettings', () => { it('should set settings for instance', async () => { const client = mocks.client() @@ -172,12 +209,13 @@ describe('settings', () => { ] }) - await saveAfterFetchSetting( + await saveAfterFetchSettings( // @ts-ignore client, 'instance', - 'some_instance_key', - 'some_new_instance_value' + { + some_instance_key: 'some_new_instance_value' + } ) const query = { @@ -222,12 +260,11 @@ describe('settings', () => { ] }) - await saveAfterFetchSetting( + await saveAfterFetchSettings( // @ts-ignore client, 'passwords', - 'some_pass_key', - 'some_new_pass_value' + { some_pass_key: 'some_new_pass_value' } ) const query = { @@ -260,12 +297,11 @@ describe('settings', () => { ] }) - await saveAfterFetchSetting( + await saveAfterFetchSettings( // @ts-ignore client, 'passwords', - 'some_new_pass_key', - 'some_new_pass_value' + { some_new_pass_key: 'some_new_pass_value' } ) const query = { @@ -299,12 +335,15 @@ describe('settings', () => { ] }) - await saveAfterFetchSetting( + await saveAfterFetchSettings( // @ts-ignore client, 'passwords', - 'some_pass_key', - currentValue => currentValue + 1 + currentValue => ({ + ...currentValue, + some_pass_key: currentValue.some_pass_key + 1 + }), + ['some_pass_key'] ) const query = { @@ -403,8 +442,7 @@ describe('settings', () => { _type: 'io.cozy.passwords.settings', some_pass_key: 'some_pass_value' }, - 'some_pass_key', - 'some_new_pass_value' + { some_pass_key: 'some_new_pass_value' } ) expect(result).toStrictEqual({ @@ -431,8 +469,7 @@ describe('settings', () => { some_setting: 'some_setting_value', some_instance_key: 'some_instance_value' }, - 'some_instance_key', - 'some_new_instance_value' + { some_instance_key: 'some_new_instance_value' } ) expect(result).toStrictEqual({ diff --git a/packages/cozy-client/src/hooks/index.js b/packages/cozy-client/src/hooks/index.js index c56b4b2ab0..a8fad08732 100644 --- a/packages/cozy-client/src/hooks/index.js +++ b/packages/cozy-client/src/hooks/index.js @@ -9,4 +9,4 @@ export { default as useAppsInMaintenance } from './useAppsInMaintenance' export { default as useQueryAll } from './useQueryAll' export { useMutation } from './useMutation' export { useInstanceInfo } from './useInstanceInfo' -export { useSetting } from './useSetting' +export { useSettings } from './useSetting' diff --git a/packages/cozy-client/src/hooks/useSetting.js b/packages/cozy-client/src/hooks/useSetting.js index 31e6c20829..647714efdc 100644 --- a/packages/cozy-client/src/hooks/useSetting.js +++ b/packages/cozy-client/src/hooks/useSetting.js @@ -5,20 +5,23 @@ import { editSettings, getQuery, normalizeSettings } from '../helpers' import { useMutation } from './useMutation' import useQuery from './useQuery' import { hasQueryBeenLoaded } from '../utils' +import { extractKeys } from '../helpers/settings' /** * Query the cozy-app settings corresponding to the given slug and * return: - * - the value corresponding to the given `key` + * - the values corresponding to the given `keys` * - the `save()` method that can be used to edit the setting's value * - the query that manages the state during the fetching of the setting * - the mutation that manages the state during the saving of the setting * + * @template {string} T + * * @param {string} slug - the cozy-app's slug containing the setting (can be 'instance' for global settings) - * @param {string} key - The name of the setting to retrieve - * @returns {import("../types").UseSettingReturnValue} + * @param {T[]} keys - The name of the setting to retrieve + * @returns {import("../types").UseSettingsReturnValue} */ -export const useSetting = (slug, key) => { +export const useSettings = (slug, keys) => { const query = getQuery(slug) const { data: settingsData, ...settingsQuery } = useQuery( @@ -29,25 +32,25 @@ export const useSetting = (slug, key) => { const { mutate, ...mutation } = useMutation() const save = useCallback( - value => { + items => { const settings = normalizeSettings(settingsData) - const newSettings = editSettings(slug, settings, key, value) + const newSettings = editSettings(slug, settings, items) return mutate(newSettings) }, - [key, mutate, settingsData, slug] + [mutate, settingsData, slug] ) const settings = normalizeSettings(settingsData) const settingValue = hasQueryBeenLoaded(settingsQuery) - ? settings?.[key] + ? extractKeys(settings, keys) : undefined return { query: settingsQuery, - value: settingValue, + values: settingValue, save, mutation } diff --git a/packages/cozy-client/src/types.js b/packages/cozy-client/src/types.js index a03c6a0f1e..a01ba37590 100644 --- a/packages/cozy-client/src/types.js +++ b/packages/cozy-client/src/types.js @@ -330,14 +330,17 @@ import { QueryDefinition } from './queries/dsl' /** * Update the setting with corresponding value and save it. * - * @callback SaveSettingFunction - * @param {any} value - The new setting's value + * @template {string} T + * + * @callback SaveSettingsFunction + * @param {Partial>} items - The new setting's value */ /** - * @typedef {object} UseSettingReturnValue - * @property {any} value - The setting's value - * @property {SaveSettingFunction} save - Function to edit the setting + * @template {string} T + * @typedef {object} UseSettingsReturnValue + * @property {Record | undefined} values - The setting's value + * @property {SaveSettingsFunction} save - Function to edit the setting * @property {QueryStateWithoutData} query - Function to edit the setting * @property {UseMutationWithoutMutate} mutation - Status of the current mutation */ diff --git a/packages/cozy-client/types/CozyClient.d.ts b/packages/cozy-client/types/CozyClient.d.ts index e04904b454..2570780388 100644 --- a/packages/cozy-client/types/CozyClient.d.ts +++ b/packages/cozy-client/types/CozyClient.d.ts @@ -721,23 +721,27 @@ declare class CozyClient { * Query the cozy-app settings corresponding to the given slug and * extract the value corresponding to the given `key` * + * @template {string} T + * * @param {string} slug - the cozy-app's slug containing the setting (can be 'instance' for global settings) - * @param {string} key - The name of the setting to retrieve + * @param {T[]} keys - The names of the settings to retrieve * @returns {Promise} - The value of the requested setting */ - getSetting(slug: string, key: string): Promise; + getSettings(slug: string, keys: T[]): Promise; /** * Save the given value into the corresponding cozy-app setting * * This methods will first query the cozy-app's settings before injecting the new value and then * save the new resulting settings into database * + * @template {string} T + * * @param {string} slug - the cozy-app's slug containing the setting (can be 'instance' for global settings) - * @param {string} key - The new value of the setting to save - * @param {any | ((oldValue) => any)} valueOrSetter - The new value of the setting to save. It can be the raw value, or a callback that should return a new value + * @param {Record | ((oldValue) => Record)} itemsOrSetter - The new values of the settings to save. It can be a raw dictionnary, or a callback that should return a new dictionnary + * @param {T[]=} setterKeys - The new values of the settings to save. It can be a raw dictionnary, or a callback that should return a new dictionnary * @returns {Promise} - The result of the `client.save()` call */ - saveAfterFetchSetting(slug: string, key: string, valueOrSetter: any): Promise; + saveAfterFetchSettings(slug: string, itemsOrSetter: Record | ((oldValue: any) => Record), setterKeys?: T_1[]): Promise; } declare namespace CozyClient { export const hooks: {}; diff --git a/packages/cozy-client/types/helpers/index.d.ts b/packages/cozy-client/types/helpers/index.d.ts index 72ff09c325..4faee4dae1 100644 --- a/packages/cozy-client/types/helpers/index.d.ts +++ b/packages/cozy-client/types/helpers/index.d.ts @@ -1,3 +1,3 @@ export { dehydrate } from "./dehydrateHelper"; export { deconstructCozyWebLinkWithSlug, deconstructRedirectLink, generateWebLink, ensureFirstSlash, rootCozyUrl, InvalidRedirectLinkError, InvalidCozyUrlError, InvalidProtocolError, BlockedCozyError } from "./urlHelper"; -export { editSettings, getQuery, getSetting, normalizeSettings, saveAfterFetchSetting } from "./settings"; +export { editSettings, getQuery, getSettings, normalizeSettings, saveAfterFetchSettings } from "./settings"; diff --git a/packages/cozy-client/types/helpers/settings.d.ts b/packages/cozy-client/types/helpers/settings.d.ts index e8ce49aab2..2f6c5baa47 100644 --- a/packages/cozy-client/types/helpers/settings.d.ts +++ b/packages/cozy-client/types/helpers/settings.d.ts @@ -1,6 +1,7 @@ -export function getSetting(client: CozyClient, slug: string, key: string): Promise; -export function saveAfterFetchSetting(client: CozyClient, slug: string, key: string, valueOrSetter: any): Promise; +export function getSettings(client: CozyClient, slug: string, keys: T[]): Promise>; +export function saveAfterFetchSettings(client: CozyClient, slug: string, itemsOrSetter: Record | ((oldValue: any) => Record), setterKeys?: T[]): Promise; export function normalizeSettings(data: any[] | any): any; -export function editSettings(slug: string, currentSettings: any, key: string, value: any): any; +export function editSettings(slug: string, currentSettings: any, items: Record): any; +export function extractKeys(settings: Record, keys: T[]): Record; export function getQuery(slug: string): import('../types').Query; import CozyClient from "../CozyClient"; diff --git a/packages/cozy-client/types/hooks/index.d.ts b/packages/cozy-client/types/hooks/index.d.ts index 548ceaa6fc..47d49e12f7 100644 --- a/packages/cozy-client/types/hooks/index.d.ts +++ b/packages/cozy-client/types/hooks/index.d.ts @@ -6,5 +6,5 @@ export { default as useAppsInMaintenance } from "./useAppsInMaintenance"; export { default as useQueryAll } from "./useQueryAll"; export { useMutation } from "./useMutation"; export { useInstanceInfo } from "./useInstanceInfo"; -export { useSetting } from "./useSetting"; +export { useSettings } from "./useSetting"; export { default as useQuery, useQueries } from "./useQuery"; diff --git a/packages/cozy-client/types/hooks/useSetting.d.ts b/packages/cozy-client/types/hooks/useSetting.d.ts index 12c9c72de4..a9cff52c96 100644 --- a/packages/cozy-client/types/hooks/useSetting.d.ts +++ b/packages/cozy-client/types/hooks/useSetting.d.ts @@ -1 +1 @@ -export function useSetting(slug: string, key: string): import("../types").UseSettingReturnValue; +export function useSettings(slug: string, keys: T[]): import("../types").UseSettingsReturnValue; diff --git a/packages/cozy-client/types/types.d.ts b/packages/cozy-client/types/types.d.ts index c32548a60e..0a8a0a5a92 100644 --- a/packages/cozy-client/types/types.d.ts +++ b/packages/cozy-client/types/types.d.ts @@ -577,16 +577,16 @@ export type UseMutationReturnValue = UseMutationWithoutMutate & UseMutationMutat /** * Update the setting with corresponding value and save it. */ -export type SaveSettingFunction = (value: any) => any; -export type UseSettingReturnValue = { +export type SaveSettingsFunction = (items: Partial>) => any; +export type UseSettingsReturnValue = { /** * - The setting's value */ - value: any; + values: Record; /** * - Function to edit the setting */ - save: SaveSettingFunction; + save: SaveSettingsFunction; /** * - Function to edit the setting */ From c53f32c5da6d49f3de2d1a644f34505710c27d85 Mon Sep 17 00:00:00 2001 From: Ldoppea Date: Thu, 18 Apr 2024 18:12:17 +0200 Subject: [PATCH 10/11] docs: Improve `getSettings` and `useSettings` to mention bitwarden --- docs/api/cozy-client/README.md | 2 +- packages/cozy-client/src/helpers/settings.js | 4 ++-- packages/cozy-client/src/hooks/useSetting.js | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/api/cozy-client/README.md b/docs/api/cozy-client/README.md index 092e7beba6..7e2d1f8c00 100644 --- a/docs/api/cozy-client/README.md +++ b/docs/api/cozy-client/README.md @@ -952,7 +952,7 @@ return: | Name | Type | Description | | :------ | :------ | :------ | -| `slug` | `string` | the cozy-app's slug containing the setting (can be 'instance' for global settings) | +| `slug` | `string` | the cozy-app's slug containing the setting (special cases are: 'instance' for global settings and 'passwords' for bitwarden settings) | | `keys` | `T`\[] | The name of the setting to retrieve | *Returns* diff --git a/packages/cozy-client/src/helpers/settings.js b/packages/cozy-client/src/helpers/settings.js index 449aa75f87..4f0c0d5c07 100644 --- a/packages/cozy-client/src/helpers/settings.js +++ b/packages/cozy-client/src/helpers/settings.js @@ -38,7 +38,7 @@ export const getSettings = async (client, slug, keys) => { * @template {string} T * * @param {CozyClient} client - Cozy client instance - * @param {string} slug - the cozy-app's slug containing the setting (can be 'instance' for global settings) + * @param {string} slug - the cozy-app's slug containing the setting (special cases are: 'instance' for global settings and 'bitwarden' for cozy-pass) * @param {Record | ((oldValue) => Record)} itemsOrSetter - The new values of the settings to save. It can be a raw dictionnary, or a callback that should return a new dictionnary * @param {T[]=} setterKeys - The new values of the settings to save. It can be a raw dictionnary, or a callback that should return a new dictionnary * @returns {Promise} - The result of the `client.save()` call @@ -167,7 +167,7 @@ export const extractKeys = (settings, keys) => { /** * Create a Query that can be used to fetch the cozy-app settings for the given slug * - * @param {string} slug - the cozy-app's slug containing the setting (can be 'instance' for global settings) + * @param {string} slug - the cozy-app's slug containing the setting (special cases are: 'instance' for global settings and 'passwords' for bitwarden settings) * @returns {import('../types').Query} - the Query that can be used to fetch the cozy-app settings */ export const getQuery = slug => { diff --git a/packages/cozy-client/src/hooks/useSetting.js b/packages/cozy-client/src/hooks/useSetting.js index 647714efdc..fa3ff3a56f 100644 --- a/packages/cozy-client/src/hooks/useSetting.js +++ b/packages/cozy-client/src/hooks/useSetting.js @@ -17,7 +17,7 @@ import { extractKeys } from '../helpers/settings' * * @template {string} T * - * @param {string} slug - the cozy-app's slug containing the setting (can be 'instance' for global settings) + * @param {string} slug - the cozy-app's slug containing the setting (special cases are: 'instance' for global settings and 'passwords' for bitwarden settings) * @param {T[]} keys - The name of the setting to retrieve * @returns {import("../types").UseSettingsReturnValue} */ From de7a8c6c18a0ee2bd4f2996f48ed507306c491b2 Mon Sep 17 00:00:00 2001 From: Ldoppea Date: Thu, 18 Apr 2024 18:16:02 +0200 Subject: [PATCH 11/11] docs: Add documentation for `useSettings` hook --- docs/hooks.md | 58 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/docs/hooks.md b/docs/hooks.md index 057085f6c4..fb4ac5e29c 100644 --- a/docs/hooks.md +++ b/docs/hooks.md @@ -8,3 +8,61 @@ In addition to our [React Integration](../react-integration), Cozy Client comes - [useClient](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/hooks/useClient.js): Returns client of actual context - [useFetchJSON](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/hooks/useFetchJSON.js): Hook to use the generic fetchJSON method. Returns object with the same keys { data, fetchStatus, error } as useQuery - [useQuery](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/hooks/useQuery.js): Returns the queryState after fetching a queryDefinition +- [useSettings](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/hooks/useSetting.js): Query the cozy-app settings corresponding to the given slug + +### Accessing and Mutating cozy-app settings with `useSettings` + +Sometimes cozy-apps need to access settings related data + +Those settings can be specific to the cozy-app, or global to the Cozy's instance + +In order to ease manipulating those settings we provide the hook `useSettings` + +This hook is based on `useQuery` and `useMutation` to provide the setting's `values` +and a `save` method that allow to mutate the settings + +Additionally, the `query` and `mutation` object are provided in order to react to +their corresponding states (fetch status, mutation status, etc) + +```jsx +import { hasQueryBeenLoaded, useSettings } from 'cozy-client' + +function DefaultRedirectionSetting() { + const [inputValue, setInputValue] = useState('') + + const { values, save, query, mutation } = useSettings( + 'instance', // slug for acessing the global settings + ['default_redirection'], // setting names + ) + + const handleChange = event => { + setInputValue(event.target.value) + } + + const handleBlur = () => { + save({ + default_redirection: inputValue + }) + } + + useEffect(() => { + setInputValue(values.default_redirection) + }, [values]) + + return ( +
+ + {mutation.mutationStatus === 'loaded' ? '✓' : null} + {mutation.mutationStatus === 'failed' ? '✗' : null} +
+ ) +} +```