diff --git a/docs/api/cozy-client/README.md b/docs/api/cozy-client/README.md index cbac37cb0b..7e2d1f8c00 100644 --- a/docs/api/cozy-client/README.md +++ b/docs/api/cozy-client/README.md @@ -930,6 +930,41 @@ Fetches a queryDefinition and run fetchMore on the query until the query is full *** +### useSettings + +▸ **useSettings**<`T`>(`slug`, `keys`): `UseSettingsReturnValue`<`T`> + +Query the cozy-app settings corresponding to the given slug and +return: + +* 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 (special cases are: 'instance' for global settings and 'passwords' for bitwarden settings) | +| `keys` | `T`\[] | The name of the setting to retrieve | + +*Returns* + +`UseSettingsReturnValue`<`T`> + +*Defined in* + +[packages/cozy-client/src/hooks/useSetting.js:24](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/hooks/useSetting.js#L24) + +*** + ### withClient ▸ **withClient**(`WrappedComponent`): `Function` diff --git a/docs/api/cozy-client/classes/CozyClient.md b/docs/api/cozy-client/classes/CozyClient.md index ec5c9a81f6..30b930f524 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,39 @@ 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) + +*** + +### getSettings + +▸ **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) | +| `keys` | `T`\[] | The names of the settings to retrieve | + +*Returns* + +`Promise`<`any`> + +* The value of the requested setting + +*Defined in* + +[packages/cozy-client/src/CozyClient.js:1767](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1767) *** @@ -937,7 +969,7 @@ the store up, which in turn will update the ``s and re-render the data. *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) *** @@ -959,7 +991,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) *** @@ -981,7 +1013,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) *** @@ -1007,7 +1039,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) *** @@ -1032,7 +1064,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) *** @@ -1053,7 +1085,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) *** @@ -1067,7 +1099,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) *** @@ -1089,7 +1121,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) *** @@ -1107,7 +1139,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) *** @@ -1188,7 +1220,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) *** @@ -1209,7 +1241,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) *** @@ -1235,7 +1267,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) *** @@ -1291,6 +1323,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 | @@ -1304,7 +1340,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) *** @@ -1331,7 +1367,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) *** @@ -1365,7 +1401,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) *** @@ -1391,7 +1427,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) *** @@ -1512,7 +1548,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) *** @@ -1532,7 +1568,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) *** @@ -1559,6 +1595,41 @@ Create or update a document on the server *** +### saveAfterFetchSettings + +▸ **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) | +| `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* + +`Promise`<`any`> + +* The result of the `client.save()` call + +*Defined in* + +[packages/cozy-client/src/CozyClient.js:1784](https://github.com/cozy/cozy-client/blob/master/packages/cozy-client/src/CozyClient.js#L1784) + +*** + ### saveAll ▸ **saveAll**(`docs`, `mutationOptions?`): `Promise`<`void`> @@ -1604,7 +1675,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) *** @@ -1628,7 +1699,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) *** @@ -1652,7 +1723,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) *** @@ -1690,7 +1761,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) *** @@ -1714,7 +1785,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) *** @@ -1728,7 +1799,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) *** @@ -1811,7 +1882,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) *** 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} +
+ ) +} +``` diff --git a/packages/cozy-client/src/CozyClient.js b/packages/cozy-client/src/CozyClient.js index 95d906d471..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 } 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' @@ -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} @@ -1749,6 +1753,37 @@ instantiation of the client.` ...newAppMetadata } } + + /** + * 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 {T[]} keys - The names of the settings to retrieve + * @returns {Promise} - The value of the requested setting + */ + async getSettings(slug, keys) { + return getSettings(this, slug, keys) + } + + /** + * 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 {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 saveAfterFetchSettings(slug, itemsOrSetter, setterKeys) { + return saveAfterFetchSettings(this, slug, itemsOrSetter, setterKeys) + } } CozyClient.hooks = CozyClient.hooks || {} 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/index.js b/packages/cozy-client/src/helpers/index.js index 78f6a48d0c..d0a4be8b27 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, + getSettings, + normalizeSettings, + saveAfterFetchSettings +} 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..4f0c0d5c07 --- /dev/null +++ b/packages/cozy-client/src/helpers/settings.js @@ -0,0 +1,224 @@ +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 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 {T[]} keys - The names of the settings to retrieve + * @returns {Promise>} - The values of the requested settings + */ +export const getSettings = async (client, slug, keys) => { + const query = getQuery(slug) + + const currentSettingsResult = await client.fetchQueryAndGetFromState({ + definition: query.definition, + options: query.options + }) + + const currentSettings = normalizeSettings(currentSettingsResult.data) + + // @ts-ignore + return extractKeys(currentSettings, keys) +} + +/** + * 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 {CozyClient} client - Cozy client instance + * @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 + */ +export const saveAfterFetchSettings = async ( + client, + slug, + itemsOrSetter, + setterKeys +) => { + const query = getQuery(slug) + + const currentSettingsResult = await client.fetchQueryAndGetFromState({ + definition: query.definition, + options: query.options + }) + + const currentSettings = normalizeSettings(currentSettingsResult.data) + + let items = undefined + if (typeof itemsOrSetter === 'function') { + const currentItems = extractKeys(currentSettings, setterKeys) + items = itemsOrSetter(currentItems) + } else { + const currentItems = extractKeys( + currentSettings, + Object.keys(itemsOrSetter) + ) + items = { + ...currentItems, + ...itemsOrSetter + } + } + + const newSettings = editSettings(slug, currentSettings, items) + + 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 {Record} items - The new values for the settings + * @returns {Object} a new Setting object containing the new value + */ +export const editSettings = (slug, currentSettings, items) => { + const type = getDoctype(slug) + + const newSettings = + slug === 'instance' + ? mergeInstance(currentSettings, items) + : mergeSettings(currentSettings, type, items) + + return newSettings +} + +const mergeInstance = (currentSettings, items) => { + const newSettings = { + _id: currentSettings._id, + _type: currentSettings._type, + _rev: currentSettings.meta.rev, + ...currentSettings, + attributes: { + ...currentSettings.attributes + } + } + + for (const [key, value] of Object.entries(items)) { + newSettings[key] = value + newSettings.attributes[key] = value + } + + return newSettings +} + +const mergeSettings = (currentSettings, type, items) => { + const newSettings = { + _type: type, + ...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 +} + +/** + * 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 (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 => { + 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..07b5a563df --- /dev/null +++ b/packages/cozy-client/src/helpers/settings.spec.js @@ -0,0 +1,493 @@ +import { + editSettings, + getSettings, + normalizeSettings, + saveAfterFetchSettings +} from './settings' +import { Q } from '../queries/dsl' + +import * as mocks from '../__tests__/mocks' + +describe('settings', () => { + describe('getSettings', () => { + it('should get settings for cozy-home', async () => { + const client = mocks.client() + + client.fetchQueryAndGetFromState.mockResolvedValue({ + data: [ + { + some_key: 'some_value' + } + ] + }) + + // @ts-ignore + const result = await getSettings(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.fetchQueryAndGetFromState).toHaveBeenCalledWith({ + definition: query.definition, + options: query.options + }) + expect(result.some_key).toEqual('some_value') + }) + + it('should get settings for mespapiers', async () => { + const client = mocks.client() + + client.fetchQueryAndGetFromState.mockResolvedValue({ + data: [ + { + some_mespapiers_key: 'some_mespapiers_value' + } + ] + }) + + const result = await getSettings( + // @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.fetchQueryAndGetFromState).toHaveBeenCalledWith({ + definition: query.definition, + options: query.options + }) + expect(result.some_mespapiers_key).toEqual('some_mespapiers_value') + }) + + it('should get settings for instance', async () => { + const client = mocks.client() + + client.fetchQueryAndGetFromState.mockResolvedValue({ + data: [ + { + some_global_key: 'some_global_value' + } + ] + }) + + // @ts-ignore + const result = await getSettings(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.fetchQueryAndGetFromState).toHaveBeenCalledWith({ + definition: query.definition, + options: query.options + }) + expect(result.some_global_key).toEqual('some_global_value') + }) + + it('should get settings for passwords', async () => { + const client = mocks.client() + + client.fetchQueryAndGetFromState.mockResolvedValue({ + data: [ + { + some_pass_key: 'some_pass_value' + } + ] + }) + + // @ts-ignore + const result = await getSettings(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.fetchQueryAndGetFromState).toHaveBeenCalledWith({ + definition: query.definition, + options: query.options + }) + expect(result.some_pass_key).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 getSettings(client, 'home', ['some_key']) + + 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('saveAfterFetchSettings', () => { + it('should set settings for instance', async () => { + const client = mocks.client() + + client.fetchQueryAndGetFromState.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 saveAfterFetchSettings( + // @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.fetchQueryAndGetFromState).toHaveBeenCalledWith({ + definition: query.definition, + options: 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.fetchQueryAndGetFromState.mockResolvedValue({ + data: [ + { + _type: 'io.cozy.passwords.settings', + some_pass_key: 'some_pass_value' + } + ] + }) + + await saveAfterFetchSettings( + // @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.fetchQueryAndGetFromState).toHaveBeenCalledWith({ + definition: query.definition, + options: 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.fetchQueryAndGetFromState.mockResolvedValue({ + data: [ + { + _type: 'io.cozy.passwords.settings', + some_existing_pass_key: 'some_pass_value' + } + ] + }) + + await saveAfterFetchSettings( + // @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.fetchQueryAndGetFromState).toHaveBeenCalledWith({ + definition: query.definition, + options: 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.fetchQueryAndGetFromState.mockResolvedValue({ + data: [ + { + _type: 'io.cozy.passwords.settings', + some_pass_key: 1 + } + ] + }) + + await saveAfterFetchSettings( + // @ts-ignore + client, + 'passwords', + currentValue => ({ + ...currentValue, + some_pass_key: currentValue.some_pass_key + 1 + }), + ['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.fetchQueryAndGetFromState).toHaveBeenCalledWith({ + definition: query.definition, + options: 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/hooks/index.js b/packages/cozy-client/src/hooks/index.js index 862539eb7a..a8fad08732 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 { useSettings } 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..fa3ff3a56f --- /dev/null +++ b/packages/cozy-client/src/hooks/useSetting.js @@ -0,0 +1,57 @@ +import { useCallback } from 'react' + +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 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 (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} + */ +export const useSettings = (slug, keys) => { + const query = getQuery(slug) + + const { data: settingsData, ...settingsQuery } = useQuery( + query.definition, + query.options + ) + + const { mutate, ...mutation } = useMutation() + + const save = useCallback( + items => { + const settings = normalizeSettings(settingsData) + + const newSettings = editSettings(slug, settings, items) + + return mutate(newSettings) + }, + [mutate, settingsData, slug] + ) + + const settings = normalizeSettings(settingsData) + + const settingValue = hasQueryBeenLoaded(settingsQuery) + ? extractKeys(settings, keys) + : undefined + + return { + query: settingsQuery, + values: settingValue, + save, + mutation + } +} diff --git a/packages/cozy-client/src/types.js b/packages/cozy-client/src/types.js index e969242645..a01ba37590 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) @@ -283,6 +291,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 @@ -298,13 +312,39 @@ 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. + * + * @template {string} T + * + * @callback SaveSettingsFunction + * @param {Partial>} items - The new setting's value + */ + +/** + * @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 + */ + /** * A reference to a document * diff --git a/packages/cozy-client/types/CozyClient.d.ts b/packages/cozy-client/types/CozyClient.d.ts index 7dfaf0690a..2570780388 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} @@ -713,6 +717,31 @@ 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` + * + * @template {string} T + * + * @param {string} slug - the cozy-app's slug containing the setting (can be 'instance' for global settings) + * @param {T[]} keys - The names of the settings to retrieve + * @returns {Promise} - The value of the requested setting + */ + 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 {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 + */ + 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/__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; diff --git a/packages/cozy-client/types/helpers/index.d.ts b/packages/cozy-client/types/helpers/index.d.ts index 75594d8daf..4faee4dae1 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, 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 new file mode 100644 index 0000000000..2f6c5baa47 --- /dev/null +++ b/packages/cozy-client/types/helpers/settings.d.ts @@ -0,0 +1,7 @@ +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, 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 704ae47df3..47d49e12f7 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 { 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 new file mode 100644 index 0000000000..a9cff52c96 --- /dev/null +++ b/packages/cozy-client/types/hooks/useSetting.d.ts @@ -0,0 +1 @@ +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 5b7ee7f82d..0a8a0a5a92 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 = { /** @@ -539,18 +542,18 @@ export type QueryOptions = { */ singleDocData?: boolean; }; +export type Query = { + definition: QueryDefinition; + options: QueryOptions; +}; export type FetchMoreAble = { fetchMore: Function; }; 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 */ @@ -564,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 SaveSettingsFunction = (items: Partial>) => any; +export type UseSettingsReturnValue = { + /** + * - The setting's value + */ + values: Record; + /** + * - Function to edit the setting + */ + save: SaveSettingsFunction; + /** + * - Function to edit the setting + */ + query: QueryStateWithoutData; + /** + * - Status of the current mutation + */ + mutation: UseMutationWithoutMutate; +}; /** * A reference to a document */