Skip to content

feat(javascript): add waitForApiKey helper method #738

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Jun 27, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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(
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry for this change, all was not allowed anymore 👼🏼

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