Skip to content

Commit

Permalink
feat(javascript): add waitForApiKey helper method (#738)
Browse files Browse the repository at this point in the history
  • Loading branch information
shortcuts committed Jun 27, 2022
1 parent 18477b8 commit 2288d17
Show file tree
Hide file tree
Showing 9 changed files with 187 additions and 36 deletions.
20 changes: 12 additions & 8 deletions scripts/cli/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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');
Expand All @@ -56,7 +60,7 @@ export function getClientChoices(job: Job, language?: LangArg): string[] {

return withoutAlgoliaSearch.filter((client) => client !== 'lite');
default:
return PROMPT_CLIENTS;
return clientList;
}
}

Expand Down Expand Up @@ -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) {
Expand All @@ -117,15 +121,15 @@ export async function prompt({
name: 'client',
message: 'Select a client',
default: ALL,
choices: decision.clientList,
choices: getClientChoices(job, decision.language),
},
]);

decision.client = [client];
}
} 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(
', '
Expand Down
27 changes: 2 additions & 25 deletions templates/javascript/api-single.mustache
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}),
Expand All @@ -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<CreateRetryablePromiseOptions<GetTaskResponse>, 'func' | 'validate'>): Promise<void> {
return new Promise<void>((resolve, reject) => {
createRetryablePromise<GetTaskResponse>({
...createRetryablePromiseOptions,
func: () => this.getTask({ indexName, taskID }),
validate: (response) => response.status === 'published',
}).then(() => resolve()).catch(reject);
});
},
{{> api/helpers}}
{{/isSearchClient}}
{{#operation}}
{{> api/operation/jsdoc}}
Expand Down
65 changes: 65 additions & 0 deletions templates/javascript/api/helpers.mustache
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/**
* 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<GetTaskResponse> {
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<ApiError | Key> {
if (operation === 'update') {
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.some((val, index) => val !== response[entry][index])
) {
return false;
}
} else if (values !== response[entry]) {
return false;
}
}

return true;
},
});
}

return createRetryablePromise({
...createRetryablePromiseOptions,
func: () => this.getApiKey({ key }).catch((error) => error),
validate: (error: ApiError) =>
operation === 'add' ? error.status !== 404 : error.status === 404,
});
},
6 changes: 5 additions & 1 deletion templates/javascript/api/imports.mustache
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import type {
RequestOptions,
QueryParameters,
{{#isSearchClient}}
CreateRetryablePromiseOptions,
ApiError,
{{/isSearchClient}}
} from '{{{npmNamespace}}}/client-common';

Expand All @@ -25,6 +25,10 @@ import { {{classname}} } from '{{filename}}';

{{#operations}}
import type {
{{#isSearchClient}}
WaitForTaskOptions,
WaitForApiKeyOptions,
{{/isSearchClient}}
{{#operation}}
{{#vendorExtensions}}
{{#x-create-wrapping-object}}
Expand Down
47 changes: 47 additions & 0 deletions templates/javascript/clientMethodProps.mustache
Original file line number Diff line number Diff line change
Expand Up @@ -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}}

Expand All @@ -33,4 +36,48 @@ export type {{#lambda.titlecase}}{{nickname}}{{/lambda.titlecase}}Props = {

{{/operation}}
{{/operations}}

{{#isSearchClient}}
type WaitForOptions<T> = Omit<
CreateRetryablePromiseOptions<T>,
'func' | 'validate'
>;

export type WaitForTaskOptions = WaitForOptions<GetTaskResponse> & {
/**
* The `indexName` where the operation was performed.
*/
indexName: string;
/**
* The `taskID` returned by the method response.
*/
taskID: number;
};

export type WaitForApiKeyOptions = WaitForOptions<Key> & {
/**
* 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<ApiKey>;
}
);
{{/isSearchClient}}

{{/apiInfo.apis.0}}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
title: Copy or move an index
title: Copy an index to another application
---

:::caution
Expand Down
53 changes: 53 additions & 0 deletions website/docs/clients/guides/wait-for-api-key-to-be-valid.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
---
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`.

<TabsLanguage>
<TabItem value="javascript">

> An `operation` can either be `add` | `update` | `delete`
```js
import { algoliasearch } from '@experimental-api-clients-automation/algoliasearch';

const client = algoliasearch('<YOUR_APP_ID>', '<YOUR_API_KEY>');

const { key } = await client.addApiKey({
acl: ['analytics', 'browse', 'editSettings'],
});

// Poll the task status with defaults values
await client.waitForApiKey({ operation: 'add', key });

// The fields to update on your API key
const updatesToPerform: ApiKey = {
acl: ['analytics', 'search'],
indexes: ['products'],
};

// Call for update
await client.updateApiKey({
key,
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,
});
```

</TabItem>
</TabsLanguage>
1 change: 1 addition & 0 deletions website/sidebars.js
Original file line number Diff line number Diff line change
Expand Up @@ -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',
],
Expand Down
2 changes: 1 addition & 1 deletion website/src/components/TabsLanguage.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export const languagesTabValues = [

export function TabsLanguage(props) {
return (
<Tabs groupId="language" defaultValue="java" values={props.values}>
<Tabs groupId="language" defaultValue="javascript" values={props.values}>
{props.children}
</Tabs>
);
Expand Down

0 comments on commit 2288d17

Please sign in to comment.