From 9a60d65e1f356e89e7a2de4bd4deb6776889895f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Vannicatte?= Date: Thu, 23 Jun 2022 17:41:45 +0200 Subject: [PATCH 1/4] feat(javascript): add `waitForApiKey` helper method --- scripts/cli/utils.ts | 20 +++--- templates/javascript/api-single.mustache | 27 +------- templates/javascript/api/helpers.mustache | 67 +++++++++++++++++++ templates/javascript/api/imports.mustache | 6 +- .../javascript/clientMethodProps.mustache | 47 +++++++++++++ .../copy-index-to-another-application.md | 2 +- .../guides/wait-for-api-key-to-be-valid.mdx | 43 ++++++++++++ website/sidebars.js | 1 + 8 files changed, 178 insertions(+), 35 deletions(-) create mode 100644 templates/javascript/api/helpers.mustache create mode 100644 website/docs/clients/guides/wait-for-api-key-to-be-valid.mdx diff --git a/scripts/cli/utils.ts b/scripts/cli/utils.ts index 87ae04583e..5a1296096c 100644 --- a/scripts/cli/utils.ts +++ b/scripts/cli/utils.ts @@ -25,13 +25,17 @@ type Prompt = { interactive: boolean; }; -export function getClientChoices(job: Job, language?: LangArg): string[] { - const withoutAlgoliaSearch = PROMPT_CLIENTS.filter( +export function getClientChoices( + job: Job, + language?: LangArg, + clientList = PROMPT_CLIENTS +): string[] { + const withoutAlgoliaSearch = clientList.filter( (client) => client !== 'algoliasearch' ); if (!language) { - return job === 'specs' ? withoutAlgoliaSearch : PROMPT_CLIENTS; + return job === 'specs' ? withoutAlgoliaSearch : clientList; } const isJavaScript = language === ALL || language === 'javascript'; @@ -41,7 +45,7 @@ export function getClientChoices(job: Job, language?: LangArg): string[] { case 'build': // Only `JavaScript` provide a lite client, others can build anything but it. if (isJavaScript) { - return PROMPT_CLIENTS.filter((client) => client !== 'lite'); + return clientList.filter((client) => client !== 'lite'); } return withoutAlgoliaSearch.filter((client) => client !== 'lite'); @@ -56,7 +60,7 @@ export function getClientChoices(job: Job, language?: LangArg): string[] { return withoutAlgoliaSearch.filter((client) => client !== 'lite'); default: - return PROMPT_CLIENTS; + return clientList; } } @@ -107,7 +111,7 @@ export async function prompt({ decision.language = langArg; } - decision.clientList = getClientChoices(job, decision.language); + decision.clientList = getClientChoices(job, decision.language, CLIENTS); if (!clientArg || !clientArg.length) { if (interactive) { @@ -117,7 +121,7 @@ export async function prompt({ name: 'client', message: 'Select a client', default: ALL, - choices: decision.clientList, + choices: getClientChoices(job, decision.language), }, ]); @@ -125,7 +129,7 @@ export async function prompt({ } } else { clientArg.forEach((client) => { - if (!decision.clientList.includes(client)) { + if (!PROMPT_CLIENTS.includes(client)) { throw new Error( `The '${clientArg}' client can't run with the given job: '${job}'.\n\nAllowed choices are: ${decision.clientList.join( ', ' diff --git a/templates/javascript/api-single.mustache b/templates/javascript/api-single.mustache index d29ec1a480..74484f2ce7 100644 --- a/templates/javascript/api-single.mustache +++ b/templates/javascript/api-single.mustache @@ -18,7 +18,7 @@ export function create{{capitalizedApiName}}({ hosts: getDefaultHosts({{^hasRegionalHost}}appIdOption{{/hasRegionalHost}}{{#hasRegionalHost}}regionOption{{/hasRegionalHost}}), ...options, algoliaAgent: getAlgoliaAgent({ - algoliaAgents: algoliaAgents, + algoliaAgents, client: '{{{algoliaAgent}}}', version: apiClientVersion, }), @@ -40,30 +40,7 @@ export function create{{capitalizedApiName}}({ return { addAlgoliaAgent, {{#isSearchClient}} - /** - * Wait for a task to complete with `indexName` and `taskID`. - * - * @summary Wait for a task to complete. - * @param waitForTaskProps - The waitForTaskProps object. - * @param waitForTaskProps.indexName - The index in which to perform the request. - * @param waitForTaskProps.taskID - The unique identifier of the task to wait for. - */ - waitForTask({ - indexName, - taskID, - ...createRetryablePromiseOptions, - }: { - indexName: string; - taskID: number; - } & Omit, 'func' | 'validate'>): Promise { - return new Promise((resolve, reject) => { - createRetryablePromise({ - ...createRetryablePromiseOptions, - func: () => this.getTask({ indexName, taskID }), - validate: (response) => response.status === 'published', - }).then(() => resolve()).catch(reject); - }); - }, + {{> api/helpers}} {{/isSearchClient}} {{#operation}} {{> api/operation/jsdoc}} diff --git a/templates/javascript/api/helpers.mustache b/templates/javascript/api/helpers.mustache new file mode 100644 index 0000000000..69af251d18 --- /dev/null +++ b/templates/javascript/api/helpers.mustache @@ -0,0 +1,67 @@ +/** + * Helper: Wait for a task to complete with `indexName` and `taskID`. + * + * @summary Wait for a task to complete. + * @param waitForTaskOptions - The waitForTaskOptions object. + * @param waitForTaskOptions.indexName - The `indexName` where the operation was performed. + * @param waitForTaskOptions.taskID - The `taskID` returned in the method response. + */ +waitForTask({ + indexName, + taskID, + ...createRetryablePromiseOptions +}: WaitForTaskOptions): Promise { + return createRetryablePromise({ + ...createRetryablePromiseOptions, + func: () => this.getTask({ indexName, taskID }), + validate: (response) => response.status === 'published', + }); +}, + +/** + * Helper: Wait for an API key to be valid, updated or deleted based on a given `operation`. + * + * @summary Wait for an API key task to be processed. + * @param waitForApiKeyOptions - The waitForApiKeyOptions object. + * @param waitForApiKeyOptions.operation - The `operation` that was done on a `key`. + * @param waitForApiKeyOptions.key - The `key` that has been added, deleted or updated. + * @param waitForApiKeyOptions.apiKey - Necessary to know if an `update` operation has been processed, compare fields of the response with it. + */ +waitForApiKey({ + operation, + key, + apiKey, + ...createRetryablePromiseOptions +}: WaitForApiKeyOptions): Promise { + if (operation === 'update') { + const validate = (response: ApiKey): boolean => { + for (const [entry, values] of Object.entries(apiKey)) { + if (Array.isArray(values)) { + if ( + values.length === response[entry].length && + values.every((val, index) => val === response[entry][index]) + ) { + return true; + } + } else if (values === response[entry]) { + return true; + } + } + + return false; + }; + + return createRetryablePromise({ + ...createRetryablePromiseOptions, + func: () => this.getApiKey({ key }), + validate, + }); + } + + return createRetryablePromise({ + ...createRetryablePromiseOptions, + func: () => this.getApiKey({ key }).catch((error) => error), + validate: (error: ApiError) => + operation === 'add' ? error.status !== 404 : error.status === 404, + }); +}, diff --git a/templates/javascript/api/imports.mustache b/templates/javascript/api/imports.mustache index 9cd54b1a84..e49d5e4643 100644 --- a/templates/javascript/api/imports.mustache +++ b/templates/javascript/api/imports.mustache @@ -15,7 +15,7 @@ import type { RequestOptions, QueryParameters, {{#isSearchClient}} - CreateRetryablePromiseOptions, + ApiError, {{/isSearchClient}} } from '{{{npmNamespace}}}/client-common'; @@ -25,6 +25,10 @@ import { {{classname}} } from '{{filename}}'; {{#operations}} import type { + {{#isSearchClient}} + WaitForTaskOptions, + WaitForApiKeyOptions, + {{/isSearchClient}} {{#operation}} {{#vendorExtensions}} {{#x-create-wrapping-object}} diff --git a/templates/javascript/clientMethodProps.mustache b/templates/javascript/clientMethodProps.mustache index 85a86dfb8c..8d0dd38108 100644 --- a/templates/javascript/clientMethodProps.mustache +++ b/templates/javascript/clientMethodProps.mustache @@ -7,6 +7,9 @@ import { {{classname}} } from '{{filename}}'; {{! Imports for the legacy search method signature }} {{#operations}}{{#operation}}{{#vendorExtensions.x-legacy-signature}}{{> api/operation/legacySearchCompatible/imports}}{{/vendorExtensions.x-legacy-signature}}{{/operation}}{{/operations}} +{{! Imports for the helpers method of the search client }} +{{#isSearchClient}}import type { CreateRetryablePromiseOptions } from '@experimental-api-clients-automation/client-common';{{/isSearchClient}} + {{#operations}} {{#operation}} @@ -33,4 +36,48 @@ export type {{#lambda.titlecase}}{{nickname}}{{/lambda.titlecase}}Props = { {{/operation}} {{/operations}} + +{{#isSearchClient}} +type WaitForOptions = Omit< + CreateRetryablePromiseOptions, + 'func' | 'validate' +>; + +export type WaitForTaskOptions = WaitForOptions & { + /** + * The `indexName` where the operation was performed. + */ + indexName: string; + /** + * The `taskID` returned by the method response. + */ + taskID: number; +}; + +export type WaitForApiKeyOptions = WaitForOptions & { + /** + * The API Key. + */ + key: string; +} & ( + | { + /** + * The operation that has been performed, used to compute the stop condition. + */ + operation: 'add' | 'delete'; + apiKey?: never; + } + | { + /** + * The operation that has been performed, used to compute the stop condition. + */ + operation: 'update'; + /** + * The updated fields, used to compute the stop condition. + */ + apiKey: Partial; + } + ); +{{/isSearchClient}} + {{/apiInfo.apis.0}} diff --git a/website/docs/clients/guides/copy-index-to-another-application.md b/website/docs/clients/guides/copy-index-to-another-application.md index d6ff43e9db..cb31dfa81d 100644 --- a/website/docs/clients/guides/copy-index-to-another-application.md +++ b/website/docs/clients/guides/copy-index-to-another-application.md @@ -1,5 +1,5 @@ --- -title: Copy or move an index +title: Copy an index to another application --- :::caution diff --git a/website/docs/clients/guides/wait-for-api-key-to-be-valid.mdx b/website/docs/clients/guides/wait-for-api-key-to-be-valid.mdx new file mode 100644 index 0000000000..144dc7dfa5 --- /dev/null +++ b/website/docs/clients/guides/wait-for-api-key-to-be-valid.mdx @@ -0,0 +1,43 @@ +--- +title: Wait for an API key to be valid +--- + +import { TabsLanguage } from '../../../src/components/TabsLanguage'; +import TabItem from '@theme/TabItem'; + +> The `waitForApiKey` method is only available in the `search` client context. + +Adding, updating or deleting API keys is not always instantaneous, which is why you might want to ensure the job has been processed before jumping to an other task. + +We provide a `waitForApiKey` helper method for you to easily wait for a specific `operation` made on a `key`. + +> An `operation` can either be `add` | `update` | `delete` + + + + +```js +import { algoliasearch } from '@experimental-api-clients-automation/algoliasearch'; + +const client = algoliasearch('', ''); + +const { key } = await client.addApiKey({ + acl: ['analytics', 'browse', 'editSettings'], +}); + +// Poll the task status with defaults values +await client.waitForApiKey({ operation: 'add', key }); + +// Poll the task status with your options +await client.waitForTask({ + operation: 'add', + key, + // Number of maximum retries to do + maxRetries: 100, + // The time to wait between tries + timeout: (retryCount: number): number => Math.min(retryCount * 200, 5000), +}); +``` + + + diff --git a/website/sidebars.js b/website/sidebars.js index 91657cbc13..63cd42f84f 100644 --- a/website/sidebars.js +++ b/website/sidebars.js @@ -79,6 +79,7 @@ const sidebars = { 'clients/guides/retrieving-facets', 'clients/guides/customized-client-usage', 'clients/guides/wait-for-a-task-to-finish', + 'clients/guides/wait-for-api-key-to-be-valid', 'clients/guides/copy-or-move-index', 'clients/guides/copy-index-to-another-application', ], From 9ed5b78cc43831d20f9e1abcb422fb1d2894817c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Vannicatte?= Date: Fri, 24 Jun 2022 10:27:25 +0200 Subject: [PATCH 2/4] doc fix --- .../guides/wait-for-api-key-to-be-valid.mdx | 24 +++++++++++++------ website/src/components/TabsLanguage.js | 2 +- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/website/docs/clients/guides/wait-for-api-key-to-be-valid.mdx b/website/docs/clients/guides/wait-for-api-key-to-be-valid.mdx index 144dc7dfa5..647175f0bc 100644 --- a/website/docs/clients/guides/wait-for-api-key-to-be-valid.mdx +++ b/website/docs/clients/guides/wait-for-api-key-to-be-valid.mdx @@ -28,14 +28,24 @@ const { key } = await client.addApiKey({ // Poll the task status with defaults values await client.waitForApiKey({ operation: 'add', key }); -// Poll the task status with your options -await client.waitForTask({ - operation: 'add', +// The fields to update on your API key +const updatesToPerform: ApiKey = { + acl: ['analytics', 'search'], + indexes: ['products'], +}; + +// Call for update +await client.updateApiKey({ key, - // Number of maximum retries to do - maxRetries: 100, - // The time to wait between tries - timeout: (retryCount: number): number => Math.min(retryCount * 200, 5000), + apiKey: updatesToPerform, +}); + +// Wait for update to be done +await client.waitForApiKey({ + operation: 'update', + key, + // We provide the updated fields to check if the changes have been applied + apiKey: updatesToPerform, }); ``` diff --git a/website/src/components/TabsLanguage.js b/website/src/components/TabsLanguage.js index 21638c270f..8adba2364d 100644 --- a/website/src/components/TabsLanguage.js +++ b/website/src/components/TabsLanguage.js @@ -9,7 +9,7 @@ export const languagesTabValues = [ export function TabsLanguage(props) { return ( - + {props.children} ); From 08963fbc2f797518f1be494d793daa4908579b2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Vannicatte?= Date: Fri, 24 Jun 2022 15:09:09 +0200 Subject: [PATCH 3/4] review --- templates/javascript/api/helpers.mustache | 32 +++++++++---------- .../guides/wait-for-api-key-to-be-valid.mdx | 4 +-- 2 files changed, 17 insertions(+), 19 deletions(-) diff --git a/templates/javascript/api/helpers.mustache b/templates/javascript/api/helpers.mustache index 69af251d18..0ce77fe558 100644 --- a/templates/javascript/api/helpers.mustache +++ b/templates/javascript/api/helpers.mustache @@ -34,27 +34,25 @@ waitForApiKey({ ...createRetryablePromiseOptions }: WaitForApiKeyOptions): Promise { if (operation === 'update') { - const validate = (response: ApiKey): boolean => { - for (const [entry, values] of Object.entries(apiKey)) { - if (Array.isArray(values)) { - if ( - values.length === response[entry].length && - values.every((val, index) => val === response[entry][index]) - ) { + return createRetryablePromise({ + ...createRetryablePromiseOptions, + func: () => this.getApiKey({ key }), + validate: (response) => { + for (const [entry, values] of Object.entries(apiKey)) { + if (Array.isArray(values)) { + if ( + values.length === response[entry].length && + values.every((val, index) => val === response[entry][index]) + ) { + return true; + } + } else if (values === response[entry]) { return true; } - } else if (values === response[entry]) { - return true; } - } - - return false; - }; - return createRetryablePromise({ - ...createRetryablePromiseOptions, - func: () => this.getApiKey({ key }), - validate, + return false; + }, }); } diff --git a/website/docs/clients/guides/wait-for-api-key-to-be-valid.mdx b/website/docs/clients/guides/wait-for-api-key-to-be-valid.mdx index 647175f0bc..9ece4901b4 100644 --- a/website/docs/clients/guides/wait-for-api-key-to-be-valid.mdx +++ b/website/docs/clients/guides/wait-for-api-key-to-be-valid.mdx @@ -11,11 +11,11 @@ Adding, updating or deleting API keys is not always instantaneous, which is why We provide a `waitForApiKey` helper method for you to easily wait for a specific `operation` made on a `key`. -> An `operation` can either be `add` | `update` | `delete` - +> An `operation` can either be `add` | `update` | `delete` + ```js import { algoliasearch } from '@experimental-api-clients-automation/algoliasearch'; From 47fa74dc4719092b45a61e584718d3a40c849688 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Vannicatte?= Date: Mon, 27 Jun 2022 09:59:33 +0200 Subject: [PATCH 4/4] full body check --- templates/javascript/api/helpers.mustache | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/templates/javascript/api/helpers.mustache b/templates/javascript/api/helpers.mustache index 0ce77fe558..95712383ed 100644 --- a/templates/javascript/api/helpers.mustache +++ b/templates/javascript/api/helpers.mustache @@ -41,17 +41,17 @@ waitForApiKey({ for (const [entry, values] of Object.entries(apiKey)) { if (Array.isArray(values)) { if ( - values.length === response[entry].length && - values.every((val, index) => val === response[entry][index]) + values.length !== response[entry].length || + values.some((val, index) => val !== response[entry][index]) ) { - return true; + return false; } - } else if (values === response[entry]) { - return true; + } else if (values !== response[entry]) { + return false; } } - return false; + return true; }, }); }