From 7219ef151148c3d6dbf1559b255a0484627c93e4 Mon Sep 17 00:00:00 2001 From: Emmanuel Date: Tue, 29 Oct 2024 12:13:48 +0100 Subject: [PATCH 01/16] feat: first endpoints --- config/clients.config.json | 19 ++--- .../common/responses/CompositionNotFound.yml | 5 ++ specs/composition/common/parameters.yml | 11 +++ specs/composition/common/schemas/Batch.yml | 6 ++ .../common/schemas/GetTaskResponse.yml | 8 ++ .../common/schemas/batchCompositionAction.yml | 14 ++++ .../common/schemas/compositionBehavior.yml | 73 +++++++++++++++++++ .../schemas/listCompositionsResponse.yml | 32 ++++++++ specs/composition/paths/advanced/getTask.yml | 38 ++++++++++ .../manage_compositions/listCompositions.yml | 28 +++++++ .../paths/objects/multipleBatch.yml | 60 +++++++++++++++ specs/composition/paths/objects/objects.yml | 27 +++++++ specs/composition/spec.yml | 69 ++++++++++++++++++ 13 files changed, 377 insertions(+), 13 deletions(-) create mode 100644 specs/common/responses/CompositionNotFound.yml create mode 100644 specs/composition/common/parameters.yml create mode 100644 specs/composition/common/schemas/Batch.yml create mode 100644 specs/composition/common/schemas/GetTaskResponse.yml create mode 100644 specs/composition/common/schemas/batchCompositionAction.yml create mode 100644 specs/composition/common/schemas/compositionBehavior.yml create mode 100644 specs/composition/common/schemas/listCompositionsResponse.yml create mode 100644 specs/composition/paths/advanced/getTask.yml create mode 100644 specs/composition/paths/manage_compositions/listCompositions.yml create mode 100644 specs/composition/paths/objects/multipleBatch.yml create mode 100644 specs/composition/paths/objects/objects.yml create mode 100644 specs/composition/spec.yml diff --git a/config/clients.config.json b/config/clients.config.json index f16540bfb3..f741a9e4ca 100644 --- a/config/clients.config.json +++ b/config/clients.config.json @@ -113,12 +113,7 @@ "extension": ".java", "outputFolder": "src/test/java/com/algolia" }, - "supportedVersions": [ - "8", - "11", - "21", - "17" - ] + "supportedVersions": ["8", "11", "21", "17"] }, "javascript": { "clients": [ @@ -161,6 +156,10 @@ { "name": "search", "output": "clients/algoliasearch-client-javascript/packages/client-search" + }, + { + "name": "composition", + "output": "clients/algoliasearch-client-javascript/packages/client-composition" } ], "folder": "clients/algoliasearch-client-javascript", @@ -257,13 +256,7 @@ "extension": ".py", "outputFolder": "" }, - "supportedVersions": [ - "3.8", - "3.9", - "3.10", - "3.11", - "3.12" - ] + "supportedVersions": ["3.8", "3.9", "3.10", "3.11", "3.12"] }, "ruby": { "clients": [ diff --git a/specs/common/responses/CompositionNotFound.yml b/specs/common/responses/CompositionNotFound.yml new file mode 100644 index 0000000000..c105e9db04 --- /dev/null +++ b/specs/common/responses/CompositionNotFound.yml @@ -0,0 +1,5 @@ +description: Composition not found. +content: + application/json: + schema: + $ref: '../schemas/ErrorBase.yml' diff --git a/specs/composition/common/parameters.yml b/specs/composition/common/parameters.yml new file mode 100644 index 0000000000..c8a0ba2b88 --- /dev/null +++ b/specs/composition/common/parameters.yml @@ -0,0 +1,11 @@ +compositionID: + in: path + name: compositionID + description: Unique Composition ObjectID. + required: true + schema: + $ref: '#/compositionObjectID' + +compositionObjectID: + type: string + description: Unique Composition ObjectID. diff --git a/specs/composition/common/schemas/Batch.yml b/specs/composition/common/schemas/Batch.yml new file mode 100644 index 0000000000..4883e76ac8 --- /dev/null +++ b/specs/composition/common/schemas/Batch.yml @@ -0,0 +1,6 @@ +action: + type: string + enum: + - upsert + - delete + description: Type of Composition Batch operation. diff --git a/specs/composition/common/schemas/GetTaskResponse.yml b/specs/composition/common/schemas/GetTaskResponse.yml new file mode 100644 index 0000000000..7b4213b832 --- /dev/null +++ b/specs/composition/common/schemas/GetTaskResponse.yml @@ -0,0 +1,8 @@ +title: getTaskResponse +type: object +additionalProperties: false +properties: + status: + $ref: '../../../common/responses/common.yml#/taskStatus' +required: + - status diff --git a/specs/composition/common/schemas/batchCompositionAction.yml b/specs/composition/common/schemas/batchCompositionAction.yml new file mode 100644 index 0000000000..b12562ab96 --- /dev/null +++ b/specs/composition/common/schemas/batchCompositionAction.yml @@ -0,0 +1,14 @@ +deleteCompositionAction: + type: object + description: Operation arguments when deleting. + additionalProperties: false + properties: + objectID: + $ref: '../../../common/parameters.yml#/objectID' + required: + - objectID + +batchCompositionAction: + oneOf: + - $ref: './listCompositionsResponse.yml#/fetchedComposition' + - $ref: '#/deleteCompositionAction' diff --git a/specs/composition/common/schemas/compositionBehavior.yml b/specs/composition/common/schemas/compositionBehavior.yml new file mode 100644 index 0000000000..ff11391146 --- /dev/null +++ b/specs/composition/common/schemas/compositionBehavior.yml @@ -0,0 +1,73 @@ +compositionBehavior: + type: object + additionalProperties: false + properties: + injection: + title: injection + type: object + additionalProperties: false + properties: + main: + title: main + type: object + additionalProperties: false + properties: + source: + $ref: '#/compositionSource' + required: + - source + insets: + type: array + description: list of insets of the current Composition. + minItems: 0 + maxItems: 2 + items: + $ref: '#/compositionInset' + required: + - main + - insets + required: + - injection + +compositionSource: + type: object + additionalProperties: false + properties: + search: + title: search + type: object + additionalProperties: false + properties: + index: + type: string + description: Composition Main Index name. + example: Products + params: + $ref: '../../../common/schemas/SearchParams.yml#/baseSearchParamsWithoutQuery' + required: + - index + required: + - search + +compositionInset: + type: object + additionalProperties: false + properties: + name: + type: string + description: Inset name. + source: + $ref: '#/compositionSource' + position: + type: integer + minimum: 0 + maximum: 19 + length: + type: integer + minimum: 0 + maximum: 20 + required: + - name + - source + - position + - length diff --git a/specs/composition/common/schemas/listCompositionsResponse.yml b/specs/composition/common/schemas/listCompositionsResponse.yml new file mode 100644 index 0000000000..e25f016cc0 --- /dev/null +++ b/specs/composition/common/schemas/listCompositionsResponse.yml @@ -0,0 +1,32 @@ +listCompositionsResponse: + type: object + additionalProperties: false + properties: + items: + type: array + description: All compositions in your Algolia application. + items: + $ref: '#/fetchedComposition' + nbPages: + type: integer + description: Number of pages. + example: 100 + required: + - items + - nbPages + +fetchedComposition: + type: object + additionalProperties: false + properties: + objectID: + $ref: '../../../common/parameters.yml#/objectID' + description: + type: string + description: Composition name. + example: 'my lovely crafted composition' + behavior: + $ref: './compositionBehavior.yml#/compositionBehavior' + required: + - objectID + - behavior diff --git a/specs/composition/paths/advanced/getTask.yml b/specs/composition/paths/advanced/getTask.yml new file mode 100644 index 0000000000..5069d37f7e --- /dev/null +++ b/specs/composition/paths/advanced/getTask.yml @@ -0,0 +1,38 @@ +get: + tags: + - Compositions + operationId: getTask + x-acl: + - editSettings + - settings + - addObject + - deleteObject + - deleteIndex + description: | + Checks the status of a given task. + summary: Check task status + parameters: + - $ref: '../../common/parameters.yml#/compositionID' + - name: taskID + in: path + description: Unique task identifier. + required: true + schema: + type: integer + format: int64 + example: 1506303845001 + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '../../common/schemas/GetTaskResponse.yml' + '400': + $ref: '../../../common/responses/BadRequest.yml' + '402': + $ref: '../../../common/responses/FeatureNotEnabled.yml' + '403': + $ref: '../../../common/responses/MethodNotAllowed.yml' + '404': + $ref: '../../../common/responses/IndexNotFound.yml' diff --git a/specs/composition/paths/manage_compositions/listCompositions.yml b/specs/composition/paths/manage_compositions/listCompositions.yml new file mode 100644 index 0000000000..5403c25c4d --- /dev/null +++ b/specs/composition/paths/manage_compositions/listCompositions.yml @@ -0,0 +1,28 @@ +get: + tags: + - Compositions + operationId: listCompositions + x-acl: + - editSettings + - settings + summary: List compositions + description: | + Lists all compositions in the current Algolia application. + parameters: + - $ref: '../../../common/parameters.yml#/Page' + - $ref: '../../../common/parameters.yml#/HitsPerPage' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '../../common/schemas/listCompositionsResponse.yml#/listCompositionsResponse' + '400': + $ref: '../../../common/responses/BadRequest.yml' + '402': + $ref: '../../../common/responses/FeatureNotEnabled.yml' + '403': + $ref: '../../../common/responses/MethodNotAllowed.yml' + '404': + $ref: '../../../common/responses/CompositionNotFound.yml' diff --git a/specs/composition/paths/objects/multipleBatch.yml b/specs/composition/paths/objects/multipleBatch.yml new file mode 100644 index 0000000000..6ad1989a1d --- /dev/null +++ b/specs/composition/paths/objects/multipleBatch.yml @@ -0,0 +1,60 @@ +post: + tags: + - Compositions + operationId: multipleBatch + description: | + Adds, updates, or deletes compositions with a single API request. + x-acl: + - editSettings + summary: Batch action to multiple compositions + requestBody: + required: true + content: + application/json: + schema: + title: batchParams + description: Batch parameters. + type: object + additionalProperties: false + properties: + requests: + type: array + items: + title: multipleBatchRequest + type: object + additionalProperties: false + properties: + action: + $ref: '../../common/schemas/Batch.yml#/action' + body: + $ref: '../../common/schemas/batchCompositionAction.yml#/batchCompositionAction' + required: + - action + - body + required: + - requests + responses: + '200': + description: OK + content: + application/json: + schema: + title: multipleBatchResponse + type: object + additionalProperties: false + properties: + taskID: + type: object + description: Task IDs. One for each index. + additionalProperties: + $ref: '../../../common/responses/common.yml#/taskID' + required: + - taskID + '400': + $ref: '../../../common/responses/BadRequest.yml' + '402': + $ref: '../../../common/responses/FeatureNotEnabled.yml' + '403': + $ref: '../../../common/responses/MethodNotAllowed.yml' + '404': + $ref: '../../../common/responses/IndexNotFound.yml' diff --git a/specs/composition/paths/objects/objects.yml b/specs/composition/paths/objects/objects.yml new file mode 100644 index 0000000000..27d2e449c2 --- /dev/null +++ b/specs/composition/paths/objects/objects.yml @@ -0,0 +1,27 @@ +get: + tags: + - Compositions + operationId: getComposition + x-acl: + - editSettings + - settings + summary: Retrieve a composition + description: | + Retrieve a single composition in the current Algolia application. + parameters: + - $ref: '../../common/parameters.yml#/compositionID' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '../../common/schemas/listCompositionsResponse.yml#/fetchedComposition' + '400': + $ref: '../../../common/responses/BadRequest.yml' + '402': + $ref: '../../../common/responses/FeatureNotEnabled.yml' + '403': + $ref: '../../../common/responses/MethodNotAllowed.yml' + '404': + $ref: '../../../common/responses/CompositionNotFound.yml' diff --git a/specs/composition/spec.yml b/specs/composition/spec.yml new file mode 100644 index 0000000000..da0cbab836 --- /dev/null +++ b/specs/composition/spec.yml @@ -0,0 +1,69 @@ +openapi: 3.0.2 +info: + title: Composition API + description: Composition API. + version: 1.0.0 +components: + securitySchemes: + appId: + $ref: '../common/securitySchemes.yml#/appId' + apiKey: + $ref: '../common/securitySchemes.yml#/apiKey' +servers: + - url: https://{appId}.algolia.net + variables: + appId: + default: ALGOLIA_APPLICATION_ID + - url: https://{appId}-1.algolianet.com + variables: + appId: + default: ALGOLIA_APPLICATION_ID + - url: https://{appId}-2.algolianet.com + variables: + appId: + default: ALGOLIA_APPLICATION_ID + - url: https://{appId}-3.algolianet.com + variables: + appId: + default: ALGOLIA_APPLICATION_ID + - url: https://{appId}-dsn.algolia.net + variables: + appId: + default: ALGOLIA_APPLICATION_ID +security: + - appId: [] + apiKey: [] +tags: + - name: Compositions + description: | + Manage your compositions and composition settings. +x-tagGroups: + - name: Search + tags: + - Compositions +paths: + # ###################### + # ### Custom request ### + # ###################### + /{path}: + $ref: '../common/paths/customRequest.yml' + + # ######################### + # ### Objects Endpoints ### + # ######################### + /1/compositions/{compositionID}: + $ref: 'paths/objects/objects.yml' + /1/compositions/*/batch: + $ref: 'paths/objects/multipleBatch.yml' + + # ########################## + # ### Advanced Endpoints ### + # ########################## + /1/compositions/{compositionID}/task/{taskID}: + $ref: 'paths/advanced/getTask.yml' + + # ################################ + # ### Manage Compositions Endpoints ### + # ################################ + /1/compositions: + $ref: 'paths/manage_compositions/listCompositions.yml' From f9587304b69cb8d071cb54629262a15be7cd7999 Mon Sep 17 00:00:00 2001 From: Emmanuel Date: Wed, 30 Oct 2024 12:26:16 +0100 Subject: [PATCH 02/16] fix: avoid permission issues on wsl --- scripts/generate.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/generate.ts b/scripts/generate.ts index 65dc26141c..89e72d6bbc 100644 --- a/scripts/generate.ts +++ b/scripts/generate.ts @@ -1,4 +1,4 @@ -import { callGenerator, setupAndGen } from './common.js'; +import { callGenerator, run, setupAndGen } from './common.js'; import { getLanguageFolder } from './config.js'; import { formatter } from './formatter.js'; import { removeExistingCodegen } from './pre-gen/index.js'; @@ -15,6 +15,7 @@ export async function generate(generators: Generator[]): Promise { }); for (const lang of [...new Set(generators.map((gen) => gen.language))]) { + await run(`sudo chmod 777 -R ${getLanguageFolder(lang)}`); await formatter(lang, getLanguageFolder(lang)); } } From 1396195f5dc3bb071aa28290262d6e400f8c9a07 Mon Sep 17 00:00:00 2001 From: shortcuts Date: Wed, 30 Oct 2024 15:14:47 +0100 Subject: [PATCH 03/16] fix: check for wsl --- scripts/common.ts | 4 ++++ scripts/generate.ts | 6 ++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/scripts/common.ts b/scripts/common.ts index 28ebc98dd8..2ad17642bd 100644 --- a/scripts/common.ts +++ b/scripts/common.ts @@ -270,6 +270,10 @@ export async function callGenerator(gen: Generator): Promise { ); } +export function isWSL(): boolean { + return process.env.WSL_DISTRO_NAME !== undefined; +} + export async function setupAndGen( generators: Generator[], mode: GeneratorMode, diff --git a/scripts/generate.ts b/scripts/generate.ts index 89e72d6bbc..9ea4a3a977 100644 --- a/scripts/generate.ts +++ b/scripts/generate.ts @@ -1,4 +1,4 @@ -import { callGenerator, run, setupAndGen } from './common.js'; +import { callGenerator, isWSL, run, setupAndGen } from './common.js'; import { getLanguageFolder } from './config.js'; import { formatter } from './formatter.js'; import { removeExistingCodegen } from './pre-gen/index.js'; @@ -15,7 +15,9 @@ export async function generate(generators: Generator[]): Promise { }); for (const lang of [...new Set(generators.map((gen) => gen.language))]) { - await run(`sudo chmod 777 -R ${getLanguageFolder(lang)}`); + if (isWSL()) { + await run(`sudo chmod 777 -R ${getLanguageFolder(lang)}`); + } await formatter(lang, getLanguageFolder(lang)); } } From ea456fce87ada370744411c6bb4d9d3aea756a60 Mon Sep 17 00:00:00 2001 From: shortcuts Date: Wed, 30 Oct 2024 15:23:52 +0100 Subject: [PATCH 04/16] fix(generators): skip pre-releases in algoliasearch --- .../com/algolia/codegen/AlgoliaJavascriptGenerator.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/generators/src/main/java/com/algolia/codegen/AlgoliaJavascriptGenerator.java b/generators/src/main/java/com/algolia/codegen/AlgoliaJavascriptGenerator.java index c2d36a6b94..f65732e281 100644 --- a/generators/src/main/java/com/algolia/codegen/AlgoliaJavascriptGenerator.java +++ b/generators/src/main/java/com/algolia/codegen/AlgoliaJavascriptGenerator.java @@ -169,10 +169,16 @@ private void setDefaultGeneratorOptions() { continue; } + String version = Helpers.getPackageJsonVersion(name); + + if (version.contains("alpha") || version.contains("beta")) { + continue; + } + var dependency = new HashMap(); dependency.put("dependencyName", Helpers.createClientName((String) pkg.get("name"), "javascript")); dependency.put("dependencyPackage", "@algolia/" + name); - dependency.put("dependencyVersion", Helpers.getPackageJsonVersion(name)); + dependency.put("dependencyVersion", version); dependency.put("withInitMethod", !name.contains("search")); dependency.put( "dependencyHasRegionalHosts", From acc05109f4de4df99e07b5f951d818ab83cd0a0d Mon Sep 17 00:00:00 2001 From: shortcuts Date: Wed, 30 Oct 2024 15:24:08 +0100 Subject: [PATCH 05/16] fix(composition): set version to pre-release --- .../packages/client-composition/package.json | 67 +++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 clients/algoliasearch-client-javascript/packages/client-composition/package.json diff --git a/clients/algoliasearch-client-javascript/packages/client-composition/package.json b/clients/algoliasearch-client-javascript/packages/client-composition/package.json new file mode 100644 index 0000000000..29e1abd5fe --- /dev/null +++ b/clients/algoliasearch-client-javascript/packages/client-composition/package.json @@ -0,0 +1,67 @@ +{ + "version": "0.0.1-alpha.0", + "repository": { + "type": "git", + "url": "git+https://github.com/algolia/algoliasearch-client-javascript.git" + }, + "type": "module", + "license": "MIT", + "author": "Algolia", + "scripts": { + "build": "yarn clean && yarn tsup && yarn rollup -c rollup.config.js", + "clean": "rm -rf ./dist || true", + "test:bundle": "publint . && attw --pack ." + }, + "name": "@algolia/client-composition", + "description": "JavaScript client for client-composition", + "exports": { + ".": { + "node": { + "types": { + "import": "./dist/node.d.ts", + "module": "./dist/node.d.ts", + "require": "./dist/node.d.cts" + }, + "import": "./dist/builds/node.js", + "module": "./dist/builds/node.js", + "require": "./dist/builds/node.cjs" + }, + "worker": { + "types": "./dist/fetch.d.ts", + "default": "./dist/builds/fetch.js" + }, + "default": { + "types": "./dist/browser.d.ts", + "module": "./dist/builds/browser.js", + "import": "./dist/builds/browser.js", + "default": "./dist/builds/browser.umd.js" + } + }, + "./src/*": "./src/*.ts" + }, + "jsdelivr": "./dist/builds/browser.umd.js", + "unpkg": "./dist/builds/browser.umd.js", + "react-native": "./dist/builds/browser.js", + "files": [ + "dist", + "index.js", + "index.d.ts" + ], + "dependencies": { + "@algolia/client-common": "5.12.0", + "@algolia/requester-browser-xhr": "5.12.0", + "@algolia/requester-node-http": "5.12.0", + "@algolia/requester-fetch": "5.12.0" + }, + "devDependencies": { + "@arethetypeswrong/cli": "0.16.4", + "@types/node": "22.8.1", + "publint": "0.2.12", + "rollup": "4.24.2", + "tsup": "8.3.5", + "typescript": "5.6.3" + }, + "engines": { + "node": ">= 14.0.0" + } +} From 0b780c9c98ecab195507a92175f6466eb8a3d662 Mon Sep 17 00:00:00 2001 From: shortcuts Date: Wed, 30 Oct 2024 20:54:54 +0100 Subject: [PATCH 06/16] fix: build and playground --- .../bundlesize.config.json | 4 ++++ .../package.json | 2 +- .../algoliasearch-client-javascript/yarn.lock | 17 +++++++++++++ playground/javascript/node/composition.ts | 24 +++++++++++++++++++ playground/javascript/node/package.json | 1 + 5 files changed, 47 insertions(+), 1 deletion(-) create mode 100644 playground/javascript/node/composition.ts diff --git a/clients/algoliasearch-client-javascript/bundlesize.config.json b/clients/algoliasearch-client-javascript/bundlesize.config.json index d88e2da745..f327011db6 100644 --- a/clients/algoliasearch-client-javascript/bundlesize.config.json +++ b/clients/algoliasearch-client-javascript/bundlesize.config.json @@ -16,6 +16,10 @@ "path": "packages/client-analytics/dist/builds/browser.umd.js", "maxSize": "4.85KB" }, + { + "path": "packages/client-composition/dist/builds/browser.umd.js", + "maxSize": "4.05KB" + }, { "path": "packages/client-insights/dist/builds/browser.umd.js", "maxSize": "3.90KB" diff --git a/clients/algoliasearch-client-javascript/package.json b/clients/algoliasearch-client-javascript/package.json index e5106beab4..afd14949fe 100644 --- a/clients/algoliasearch-client-javascript/package.json +++ b/clients/algoliasearch-client-javascript/package.json @@ -6,7 +6,7 @@ "packages/*" ], "scripts": { - "build": "lerna run build --scope '@algolia/requester-testing' --scope '@algolia/logger-console' --scope 'algoliasearch' --include-dependencies", + "build": "lerna run build --scope '@algolia/requester-testing' --scope '@algolia/logger-console' --scope 'algoliasearch' --scope '@algolia/client-composition' --include-dependencies", "clean": "lerna run clean", "release:bump": "lerna version ${0:-patch} --no-changelog --no-git-tag-version --no-push --exact --force-publish --yes", "release:publish": "tsc --project scripts/tsconfig.json && node scripts/dist/scripts/publish.js", diff --git a/clients/algoliasearch-client-javascript/yarn.lock b/clients/algoliasearch-client-javascript/yarn.lock index 9b5c089e1b..fb8b4b3d9a 100644 --- a/clients/algoliasearch-client-javascript/yarn.lock +++ b/clients/algoliasearch-client-javascript/yarn.lock @@ -54,6 +54,23 @@ __metadata: languageName: unknown linkType: soft +"@algolia/client-composition@workspace:packages/client-composition": + version: 0.0.0-use.local + resolution: "@algolia/client-composition@workspace:packages/client-composition" + dependencies: + "@algolia/client-common": "npm:5.12.0" + "@algolia/requester-browser-xhr": "npm:5.12.0" + "@algolia/requester-fetch": "npm:5.12.0" + "@algolia/requester-node-http": "npm:5.12.0" + "@arethetypeswrong/cli": "npm:0.16.4" + "@types/node": "npm:22.8.1" + publint: "npm:0.2.12" + rollup: "npm:4.24.2" + tsup: "npm:8.3.5" + typescript: "npm:5.6.3" + languageName: unknown + linkType: soft + "@algolia/client-insights@npm:5.12.0, @algolia/client-insights@workspace:packages/client-insights": version: 0.0.0-use.local resolution: "@algolia/client-insights@workspace:packages/client-insights" diff --git a/playground/javascript/node/composition.ts b/playground/javascript/node/composition.ts new file mode 100644 index 0000000000..d9ec0f7c60 --- /dev/null +++ b/playground/javascript/node/composition.ts @@ -0,0 +1,24 @@ +import { ApiError } from '@algolia/client-common'; +import { compositionClient } from '@algolia/client-composition'; + +const appId = process.env.ALGOLIA_APPLICATION_ID || '**** APP_ID *****'; +const apiKey = process.env.ALGOLIA_ADMIN_KEY || '**** ADMIN_KEY *****'; + +// Init client with appId and apiKey +const client = compositionClient(appId, apiKey); + +async function testComposition() { + try { + const res = await client.listCompositions(); + + console.log(`[OK]`, res); + } catch (e) { + if (e instanceof ApiError) { + return console.log(`[${e.status}] ${e.message}`, e.stackTrace, e); + } + + console.log('[ERROR]', e); + } +} + +testComposition(); diff --git a/playground/javascript/node/package.json b/playground/javascript/node/package.json index b01e37f0eb..e5a2f03fa4 100644 --- a/playground/javascript/node/package.json +++ b/playground/javascript/node/package.json @@ -10,6 +10,7 @@ "@algolia/client-abtesting": "link:../../../clients/algoliasearch-client-javascript/packages/client-abtesting", "@algolia/client-analytics": "link:../../../clients/algoliasearch-client-javascript/packages/client-analytics", "@algolia/client-common": "link:../../../clients/algoliasearch-client-javascript/packages/client-common", + "@algolia/client-composition": "link:../../../clients/algoliasearch-client-javascript/packages/client-composition", "@algolia/client-insights": "link:../../../clients/algoliasearch-client-javascript/packages/client-insights", "@algolia/client-personalization": "link:../../../clients/algoliasearch-client-javascript/packages/client-personalization", "@algolia/client-query-suggestions": "link:../../../clients/algoliasearch-client-javascript/packages/client-query-suggestions", From 0076d1fece6759c811f737219600080d40f25b9e Mon Sep 17 00:00:00 2001 From: shortcuts Date: Wed, 30 Oct 2024 22:21:12 +0100 Subject: [PATCH 07/16] fix: cts --- specs/composition/spec.yml | 10 +- templates/javascript/tests/e2e/e2e.mustache | 10 ++ templates/javascript/tests/package.mustache | 1 + tests/CTS/client/composition/api.json | 166 ++++++++++++++++++ .../requests/composition/getComposition.json | 11 ++ tests/CTS/requests/composition/getTask.json | 12 ++ .../composition/listCompositions.json | 20 +++ .../requests/composition/multipleBatch.json | 11 ++ tests/output/javascript/yarn.lock | 25 ++- 9 files changed, 255 insertions(+), 11 deletions(-) create mode 100644 tests/CTS/client/composition/api.json create mode 100644 tests/CTS/requests/composition/getComposition.json create mode 100644 tests/CTS/requests/composition/getTask.json create mode 100644 tests/CTS/requests/composition/listCompositions.json create mode 100644 tests/CTS/requests/composition/multipleBatch.json diff --git a/specs/composition/spec.yml b/specs/composition/spec.yml index da0cbab836..f646805d5a 100644 --- a/specs/composition/spec.yml +++ b/specs/composition/spec.yml @@ -62,8 +62,14 @@ paths: /1/compositions/{compositionID}/task/{taskID}: $ref: 'paths/advanced/getTask.yml' - # ################################ + # ##################################### # ### Manage Compositions Endpoints ### - # ################################ + # ##################################### /1/compositions: $ref: 'paths/manage_compositions/listCompositions.yml' + + # ############### + # ### Helpers ### + # ############### + /setClientApiKey: + $ref: '../common/helpers/setClientApiKey.yml#/method' diff --git a/templates/javascript/tests/e2e/e2e.mustache b/templates/javascript/tests/e2e/e2e.mustache index 64fc2db86b..e7c5a168cb 100644 --- a/templates/javascript/tests/e2e/e2e.mustache +++ b/templates/javascript/tests/e2e/e2e.mustache @@ -6,7 +6,12 @@ import { union } from '../helpers.js'; import * as dotenv from 'dotenv'; dotenv.config({path:'../../.env'}); +{{^isCompositionClient}} import { {{{clientName}}} } from '{{{importPackage}}}'; +{{/isCompositionClient}} +{{#isCompositionClient}} +import { compositionClient } from '@algolia/client-composition'; +{{/isCompositionClient}} import type { RequestOptions } from '@algolia/client-common'; if (!process.env.ALGOLIA_APPLICATION_ID) { @@ -17,7 +22,12 @@ if (!process.env.{{e2eApiKey}}) { throw new Error("please provide an `{{e2eApiKey}}` env var for e2e tests"); } +{{^isCompositionClient}} const client = {{{clientName}}}(process.env.ALGOLIA_APPLICATION_ID, process.env.{{e2eApiKey}}){{^isSearchClient}}.{{{initMethod}}}({{#hasRegionalHost}} {region:'{{{defaultRegion}}}'} {{/hasRegionalHost}}){{/isSearchClient}}; +{{/isCompositionClient}} +{{#isCompositionClient}} +const client = compositionClient(process.env.ALGOLIA_APPLICATION_ID, process.env.{{e2eApiKey}}); +{{/isCompositionClient}} {{#blocksE2E}} describe('{{operationId}}', () => { diff --git a/templates/javascript/tests/package.mustache b/templates/javascript/tests/package.mustache index fe5838ef83..77f46508cd 100644 --- a/templates/javascript/tests/package.mustache +++ b/templates/javascript/tests/package.mustache @@ -7,6 +7,7 @@ }, "dependencies": { "algoliasearch": "link:../../../clients/algoliasearch-client-javascript/packages/algoliasearch", + "@algolia/client-composition": "link:../../../clients/algoliasearch-client-javascript/packages/client-composition", "@algolia/requester-testing": "link:../../../clients/algoliasearch-client-javascript/packages/requester-testing" }, "devDependencies": { diff --git a/tests/CTS/client/composition/api.json b/tests/CTS/client/composition/api.json new file mode 100644 index 0000000000..d3c192afe8 --- /dev/null +++ b/tests/CTS/client/composition/api.json @@ -0,0 +1,166 @@ +[ + { + "testName": "calls api with correct read host", + "autoCreateClient": false, + "steps": [ + { + "type": "createClient", + "parameters": { + "appId": "test-app-id", + "apiKey": "test-api-key" + } + }, + { + "type": "method", + "method": "customGet", + "parameters": { + "path": "test" + }, + "expected": { + "type": "host", + "match": "test-app-id-dsn.algolia.net" + } + } + ] + }, + { + "testName": "calls api with correct write host", + "autoCreateClient": false, + "steps": [ + { + "type": "createClient", + "parameters": { + "appId": "test-app-id", + "apiKey": "test-api-key" + } + }, + { + "type": "method", + "method": "customPost", + "parameters": { + "path": "test" + }, + "expected": { + "type": "host", + "match": "test-app-id.algolia.net" + } + } + ] + }, + { + "testName": "tests the retry strategy", + "autoCreateClient": false, + "steps": [ + { + "type": "createClient", + "parameters": { + "appId": "test-app-id", + "apiKey": "test-api-key", + "customHosts": [ + { + "port": 6676 + }, + { + "port": 6677 + }, + { + "port": 6678 + } + ] + } + }, + { + "type": "method", + "method": "customGet", + "parameters": { + "path": "1/test/retry/${{language}}" + }, + "expected": { + "type": "response", + "match": { + "message": "ok test server response" + } + } + } + ] + }, + { + "testName": "tests the retry strategy error", + "autoCreateClient": false, + "steps": [ + { + "type": "createClient", + "parameters": { + "appId": "test-app-id", + "apiKey": "test-api-key", + "customHosts": [ + { + "port": 6676 + } + ] + } + }, + { + "type": "method", + "method": "customGet", + "parameters": { + "path": "1/test/hang/${{language}}" + }, + "expected": { + "error": { + "csharp": "RetryStrategy failed to connect to Algolia. Reason: The operation has timed out.", + "dart": "UnreachableHostsException{errors: [AlgoliaTimeoutException{error: DioException [receive timeout]: The request took longer than 0:00:05.000000 to receive data. It was aborted. To get rid of this exception, try raising the RequestOptions.receiveTimeout above the duration of 0:00:05.000000 or improve the response time of the server.}]}", + "go": "failed to do request: all hosts have been contacted unsuccessfully, it can either be a server or a network error or wrong appID/key credentials were used. You can use 'ExposeIntermediateNetworkErrors: true' in the config to investigate.", + "java": "Error(s) while processing the retry strategy\\nCaused by: java.net.SocketTimeoutException: timeout", + "javascript": "Unreachable hosts - your application id may be incorrect. If the error persists, please reach out to the Algolia Support team: https://alg.li/support.", + "kotlin": "Error(s) while processing the retry strategy", + "php": "Impossible to connect, please check your Algolia Application Id.", + "python": "Unreachable hosts", + "ruby": "Unreachable hosts. Last error for %localhost%: Net::ReadTimeout with #", + "scala": "Error(s) while processing the retry strategy", + "swift": "All hosts are unreachable. You can use 'exposeIntermediateErrors: true' in the config to investigate." + } + } + } + ] + }, + { + "testName": "test the compression strategy", + "autoCreateClient": false, + "steps": [ + { + "type": "createClient", + "parameters": { + "appId": "test-app-id", + "apiKey": "test-api-key", + "customHosts": [ + { + "port": 6678 + } + ], + "gzip": true + } + }, + { + "type": "method", + "method": "customPost", + "parameters": { + "path": "1/test/gzip", + "parameters": {}, + "body": { + "message": "this is a compressed body" + } + }, + "expected": { + "type": "response", + "match": { + "message": "ok compression test server response", + "body": { + "message": "this is a compressed body" + } + } + } + } + ] + } +] diff --git a/tests/CTS/requests/composition/getComposition.json b/tests/CTS/requests/composition/getComposition.json new file mode 100644 index 0000000000..849e3100d7 --- /dev/null +++ b/tests/CTS/requests/composition/getComposition.json @@ -0,0 +1,11 @@ +[ + { + "parameters": { + "compositionID": "foo" + }, + "request": { + "path": "/1/compositions/foo", + "method": "GET" + } + } +] diff --git a/tests/CTS/requests/composition/getTask.json b/tests/CTS/requests/composition/getTask.json new file mode 100644 index 0000000000..95d0f32abc --- /dev/null +++ b/tests/CTS/requests/composition/getTask.json @@ -0,0 +1,12 @@ +[ + { + "parameters": { + "compositionID": "foo", + "taskID": "bar" + }, + "request": { + "path": "/1/compositions/foo/task/bar", + "method": "GET" + } + } +] diff --git a/tests/CTS/requests/composition/listCompositions.json b/tests/CTS/requests/composition/listCompositions.json new file mode 100644 index 0000000000..97f78c43ce --- /dev/null +++ b/tests/CTS/requests/composition/listCompositions.json @@ -0,0 +1,20 @@ +[ + { + "parameters": {}, + "request": { + "path": "/1/compositions", + "method": "GET" + } + }, + { + "parameters": {}, + "request": { + "path": "/1/compositions", + "method": "GET" + }, + "response": { + "items": [{"objectID":"foo","description":"bar","behavior":{"injection":{"main":{"source":{"search":{"index":"baz"}}}}}}], + "nbPages": 1 + } + } +] diff --git a/tests/CTS/requests/composition/multipleBatch.json b/tests/CTS/requests/composition/multipleBatch.json new file mode 100644 index 0000000000..fa3a48b8c6 --- /dev/null +++ b/tests/CTS/requests/composition/multipleBatch.json @@ -0,0 +1,11 @@ +[ + { + "parameters": { + "requests": [{"action":"upsert","body":{"objectID":"foo","behavior": {"injection":{"main":{"source":{"search":{"index":"bar"}}},"insets":[]}}}},{"action":"delete","body":{"objectID":"baz"}}] + }, + "request": { + "path": "/1/compositions/foo", + "method": "GET" + } + } +] diff --git a/tests/output/javascript/yarn.lock b/tests/output/javascript/yarn.lock index 057a0bee1b..0c74a9e063 100644 --- a/tests/output/javascript/yarn.lock +++ b/tests/output/javascript/yarn.lock @@ -5,6 +5,12 @@ __metadata: version: 8 cacheKey: 10 +"@algolia/client-composition@link:../../../clients/algoliasearch-client-javascript/packages/client-composition::locator=javascript-tests%40workspace%3A.": + version: 0.0.0-use.local + resolution: "@algolia/client-composition@link:../../../clients/algoliasearch-client-javascript/packages/client-composition::locator=javascript-tests%40workspace%3A." + languageName: node + linkType: soft + "@algolia/requester-testing@link:../../../clients/algoliasearch-client-javascript/packages/requester-testing::locator=javascript-tests%40workspace%3A.": version: 0.0.0-use.local resolution: "@algolia/requester-testing@link:../../../clients/algoliasearch-client-javascript/packages/requester-testing::locator=javascript-tests%40workspace%3A." @@ -341,12 +347,12 @@ __metadata: languageName: node linkType: hard -"@types/node@npm:22.7.7": - version: 22.7.7 - resolution: "@types/node@npm:22.7.7" +"@types/node@npm:22.8.1": + version: 22.8.1 + resolution: "@types/node@npm:22.8.1" dependencies: - undici-types: "npm:~6.19.2" - checksum: 10/ada6c5f850fa09621e21923d7b17c3f3b5264c3b39c0953006f4a8b0b3d4b6d77ac02e2bbf8bae1d493abf81668804624470d895dd4483875fde8382b6eb7933 + undici-types: "npm:~6.19.8" + checksum: 10/ae969e3d956dead1422c35d568ea5d48dd124a38a1a337cbd120fec6e13cc92b45c7308f91f1139fcd2337a67d4704d5614d6a2c444b1fb268f85e9f1d24c713 languageName: node linkType: hard @@ -946,8 +952,9 @@ __metadata: version: 0.0.0-use.local resolution: "javascript-tests@workspace:." dependencies: + "@algolia/client-composition": "link:../../../clients/algoliasearch-client-javascript/packages/client-composition" "@algolia/requester-testing": "link:../../../clients/algoliasearch-client-javascript/packages/requester-testing" - "@types/node": "npm:22.7.7" + "@types/node": "npm:22.8.1" algoliasearch: "link:../../../clients/algoliasearch-client-javascript/packages/algoliasearch" dotenv: "npm:16.4.5" typescript: "npm:5.6.3" @@ -1525,15 +1532,15 @@ __metadata: "typescript@patch:typescript@npm%3A5.6.3#optional!builtin": version: 5.6.3 - resolution: "typescript@patch:typescript@npm%3A5.6.3#optional!builtin::version=5.6.3&hash=b45daf" + resolution: "typescript@patch:typescript@npm%3A5.6.3#optional!builtin::version=5.6.3&hash=8c6c40" bin: tsc: bin/tsc tsserver: bin/tsserver - checksum: 10/dc4bec403cd33a204b655b1152a096a08e7bad2c931cb59ef8ff26b6f2aa541bf98f09fc157958a60c921b1983a8dde9a85b692f9de60fa8f574fd131e3ae4dd + checksum: 10/00504c01ee42d470c23495426af07512e25e6546bce7e24572e72a9ca2e6b2e9bea63de4286c3cfea644874da1467dcfca23f4f98f7caf20f8b03c0213bb6837 languageName: node linkType: hard -"undici-types@npm:~6.19.2": +"undici-types@npm:~6.19.8": version: 6.19.8 resolution: "undici-types@npm:6.19.8" checksum: 10/cf0b48ed4fc99baf56584afa91aaffa5010c268b8842f62e02f752df209e3dea138b372a60a963b3b2576ed932f32329ce7ddb9cb5f27a6c83040d8cd74b7a70 From 741b6cce94add27e70ad0cb8f4ee0fe6a98e22c6 Mon Sep 17 00:00:00 2001 From: shortcuts Date: Wed, 30 Oct 2024 22:42:33 +0100 Subject: [PATCH 08/16] fix(tests): init client --- .../com/algolia/codegen/cts/AlgoliaCTSGenerator.java | 1 + templates/javascript/tests/client/client.mustache | 11 ++++++++--- .../javascript/tests/client/createClient.mustache | 10 +++++----- templates/javascript/tests/requests/requests.mustache | 7 ++++++- 4 files changed, 20 insertions(+), 9 deletions(-) diff --git a/generators/src/main/java/com/algolia/codegen/cts/AlgoliaCTSGenerator.java b/generators/src/main/java/com/algolia/codegen/cts/AlgoliaCTSGenerator.java index 82136b24ec..b226296a71 100644 --- a/generators/src/main/java/com/algolia/codegen/cts/AlgoliaCTSGenerator.java +++ b/generators/src/main/java/com/algolia/codegen/cts/AlgoliaCTSGenerator.java @@ -148,6 +148,7 @@ public Map postProcessSupportingFileData(Map obj // We can put whatever we want in the bundle, and it will be accessible in the template bundle.put("mode", mode); bundle.put("is" + Helpers.capitalize(client) + "Client", true); + bundle.put("isStandaloneClient", client.equals("algoliasearch") || client.equals("composition")); bundle.put("isSearchClient", client.contains("search")); // just so algoliasearch is treated as a search client too bundle.put("client", Helpers.createClientName(importClientName, language) + "Client"); bundle.put("clientPrefix", Helpers.createClientName(importClientName, language)); diff --git a/templates/javascript/tests/client/client.mustache b/templates/javascript/tests/client/client.mustache index fcefd2ecc8..d5ed30e3ef 100644 --- a/templates/javascript/tests/client/client.mustache +++ b/templates/javascript/tests/client/client.mustache @@ -2,7 +2,12 @@ /* eslint-disable eslint/no-unused-vars */ import { describe, test, expect } from 'vitest'; +{{^isCompositionClient}} import { {{{clientName}}} } from '{{{importPackage}}}'; +{{/isCompositionClient}} +{{#isCompositionClient}} +import { compositionClient } from '@algolia/client-composition'; +{{/isCompositionClient}} import { nodeEchoRequester } from '@algolia/requester-testing'; import type { EchoResponse } from '@algolia/requester-testing'; @@ -10,7 +15,7 @@ const appId = 'test-app-id'; const apiKey = 'test-api-key'; function createClient() { - return {{{clientName}}}(appId, apiKey{{#isSearchClient}}, { requester: nodeEchoRequester() }{{/isSearchClient}}){{^isSearchClient}}.{{{initMethod}}}({ options: { requester: nodeEchoRequester() }, {{#hasRegionalHost}} region:'{{{defaultRegion}}}' {{/hasRegionalHost}} });{{/isSearchClient}}; + return {{#isSearchClient}}{{{clientName}}}{{/isSearchClient}}{{#isCompositionClient}}compositionClient{{/isCompositionClient}}(appId, apiKey{{#isStandaloneClient}}, { requester: nodeEchoRequester() }{{/isStandaloneClient}}){{^isStandaloneClient}}.{{{initMethod}}}({ options: { requester: nodeEchoRequester() }, {{#hasRegionalHost}} region:'{{{defaultRegion}}}' {{/hasRegionalHost}} });{{/isStandaloneClient}}; } {{#blocksClient}} @@ -19,8 +24,8 @@ function createClient() { describe('init', () => { test('sets authMode', async () => { - const qpClient = {{{clientName}}}('foo', 'bar'{{#isSearchClient}}, { requester: nodeEchoRequester(), authMode: 'WithinQueryParameters' }{{/isSearchClient}}){{^isSearchClient}}.{{{initMethod}}}({ options: { requester: nodeEchoRequester(), authMode: 'WithinQueryParameters' }, {{#hasRegionalHost}} region:'{{{defaultRegion}}}' {{/hasRegionalHost}} });{{/isSearchClient}}; - const headerClient = {{{clientName}}}('foo', 'bar'{{#isSearchClient}}, { requester: nodeEchoRequester(), authMode: 'WithinHeaders' }{{/isSearchClient}}){{^isSearchClient}}.{{{initMethod}}}({ options: { requester: nodeEchoRequester(), authMode: 'WithinHeaders' }, {{#hasRegionalHost}} region:'{{{defaultRegion}}}' {{/hasRegionalHost}} });{{/isSearchClient}}; + const qpClient = {{#isSearchClient}}{{{clientName}}}{{/isSearchClient}}{{#isCompositionClient}}compositionClient{{/isCompositionClient}}('foo', 'bar'{{#isStandaloneClient}}, { requester: nodeEchoRequester(), authMode: 'WithinQueryParameters' }{{/isStandaloneClient}}){{^isStandaloneClient}}.{{{initMethod}}}({ options: { requester: nodeEchoRequester(), authMode: 'WithinQueryParameters' }, {{#hasRegionalHost}} region:'{{{defaultRegion}}}' {{/hasRegionalHost}} });{{/isStandaloneClient}}; + const headerClient = {{#isSearchClient}}{{{clientName}}}{{/isSearchClient}}{{#isCompositionClient}}compositionClient{{/isCompositionClient}}('foo', 'bar'{{#isStandaloneClient}}, { requester: nodeEchoRequester(), authMode: 'WithinHeaders' }{{/isStandaloneClient}}){{^isStandaloneClient}}.{{{initMethod}}}({ options: { requester: nodeEchoRequester(), authMode: 'WithinHeaders' }, {{#hasRegionalHost}} region:'{{{defaultRegion}}}' {{/hasRegionalHost}} });{{/isStandaloneClient}}; const qpResult = (await qpClient.customGet({ path: '1/foo', diff --git a/templates/javascript/tests/client/createClient.mustache b/templates/javascript/tests/client/createClient.mustache index 3dc141151d..8a85420e8f 100644 --- a/templates/javascript/tests/client/createClient.mustache +++ b/templates/javascript/tests/client/createClient.mustache @@ -1,7 +1,7 @@ -{{^autoCreateClient}}const client = {{/autoCreateClient}}{{{clientName}}}( +{{^autoCreateClient}}const client = {{/autoCreateClient}}{{#isSearchClient}}{{{clientName}}}{{/isSearchClient}}{{#isCompositionClient}}compositionClient{{/isCompositionClient}}( '{{parametersWithDataTypeMap.appId.value}}', '{{parametersWithDataTypeMap.apiKey.value}}' - {{#isSearchClient}}, + {{#isStandaloneClient}}, { {{#useEchoRequester}} requester: nodeEchoRequester(), @@ -19,8 +19,8 @@ ] {{/hasCustomHosts}} } - {{/isSearchClient}} -){{^isSearchClient}}.{{{initMethod}}}( + {{/isStandaloneClient}} +){{^isStandaloneClient}}.{{{initMethod}}}( { options: { {{#useEchoRequester}} @@ -44,4 +44,4 @@ region: '{{{parametersWithDataTypeMap.region.value}}}' {{/hasRegionalHost}} }) -{{/isSearchClient}}; \ No newline at end of file +{{/isStandaloneClient}}; \ No newline at end of file diff --git a/templates/javascript/tests/requests/requests.mustache b/templates/javascript/tests/requests/requests.mustache index 4b77e28037..e1da55ea05 100644 --- a/templates/javascript/tests/requests/requests.mustache +++ b/templates/javascript/tests/requests/requests.mustache @@ -1,7 +1,12 @@ // {{generationBanner}} import { describe, test, expect } from 'vitest'; +{{^isCompositionClient}} import { {{{clientName}}} } from '{{{importPackage}}}'; +{{/isCompositionClient}} +{{#isCompositionClient}} +import { compositionClient } from '@algolia/client-composition'; +{{/isCompositionClient}} import { nodeEchoRequester } from '@algolia/requester-testing'; import type { EchoResponse } from '@algolia/requester-testing'; import type { ClientOptions } from '@algolia/client-common'; @@ -10,7 +15,7 @@ const appId = process.env.ALGOLIA_APPLICATION_ID || 'test_app_id'; const apiKey = process.env.ALGOLIA_SEARCH_KEY || 'test_api_key'; const clientOptions: ClientOptions = { requester: nodeEchoRequester() }; // this makes sure the types are correctly exported -const client = {{{clientName}}}(appId, apiKey{{#isSearchClient}}, { requester: nodeEchoRequester() }{{/isSearchClient}}){{^isSearchClient}}.{{{initMethod}}}({ options: clientOptions, {{#hasRegionalHost}} region:'{{{defaultRegion}}}' {{/hasRegionalHost}} });{{/isSearchClient}}; +const client = {{#isSearchClient}}{{{clientName}}}{{/isSearchClient}}{{#isCompositionClient}}compositionClient{{/isCompositionClient}}(appId, apiKey{{#isStandaloneClient}}, { requester: nodeEchoRequester() }{{/isStandaloneClient}}){{^isStandaloneClient}}.{{{initMethod}}}({ options: clientOptions, {{#hasRegionalHost}} region:'{{{defaultRegion}}}' {{/hasRegionalHost}} });{{/isStandaloneClient}}; {{#blocksRequests}} describe('{{operationId}}', () => { From 7eca000bed2d22febb1064f20489fbe290336b64 Mon Sep 17 00:00:00 2001 From: shortcuts Date: Thu, 31 Oct 2024 11:37:18 +0100 Subject: [PATCH 09/16] fix: conditional client name --- .../java/com/algolia/codegen/cts/AlgoliaCTSGenerator.java | 2 +- templates/javascript/tests/client/client.mustache | 6 +++--- templates/javascript/tests/client/createClient.mustache | 2 +- templates/javascript/tests/e2e/e2e.mustache | 7 +------ templates/javascript/tests/requests/requests.mustache | 2 +- 5 files changed, 7 insertions(+), 12 deletions(-) diff --git a/generators/src/main/java/com/algolia/codegen/cts/AlgoliaCTSGenerator.java b/generators/src/main/java/com/algolia/codegen/cts/AlgoliaCTSGenerator.java index b226296a71..3336e0b3c3 100644 --- a/generators/src/main/java/com/algolia/codegen/cts/AlgoliaCTSGenerator.java +++ b/generators/src/main/java/com/algolia/codegen/cts/AlgoliaCTSGenerator.java @@ -148,7 +148,7 @@ public Map postProcessSupportingFileData(Map obj // We can put whatever we want in the bundle, and it will be accessible in the template bundle.put("mode", mode); bundle.put("is" + Helpers.capitalize(client) + "Client", true); - bundle.put("isStandaloneClient", client.equals("algoliasearch") || client.equals("composition")); + bundle.put("isStandaloneClient", client.contains("search") || client.equals("composition")); bundle.put("isSearchClient", client.contains("search")); // just so algoliasearch is treated as a search client too bundle.put("client", Helpers.createClientName(importClientName, language) + "Client"); bundle.put("clientPrefix", Helpers.createClientName(importClientName, language)); diff --git a/templates/javascript/tests/client/client.mustache b/templates/javascript/tests/client/client.mustache index d5ed30e3ef..47c559c017 100644 --- a/templates/javascript/tests/client/client.mustache +++ b/templates/javascript/tests/client/client.mustache @@ -15,7 +15,7 @@ const appId = 'test-app-id'; const apiKey = 'test-api-key'; function createClient() { - return {{#isSearchClient}}{{{clientName}}}{{/isSearchClient}}{{#isCompositionClient}}compositionClient{{/isCompositionClient}}(appId, apiKey{{#isStandaloneClient}}, { requester: nodeEchoRequester() }{{/isStandaloneClient}}){{^isStandaloneClient}}.{{{initMethod}}}({ options: { requester: nodeEchoRequester() }, {{#hasRegionalHost}} region:'{{{defaultRegion}}}' {{/hasRegionalHost}} });{{/isStandaloneClient}}; + return {{^isCompositionClient}}{{{clientName}}}{{/isCompositionClient}}{{#isCompositionClient}}compositionClient{{/isCompositionClient}}(appId, apiKey{{#isStandaloneClient}}, { requester: nodeEchoRequester() }{{/isStandaloneClient}}){{^isStandaloneClient}}.{{{initMethod}}}({ options: { requester: nodeEchoRequester() }, {{#hasRegionalHost}} region:'{{{defaultRegion}}}' {{/hasRegionalHost}} });{{/isStandaloneClient}}; } {{#blocksClient}} @@ -24,8 +24,8 @@ function createClient() { describe('init', () => { test('sets authMode', async () => { - const qpClient = {{#isSearchClient}}{{{clientName}}}{{/isSearchClient}}{{#isCompositionClient}}compositionClient{{/isCompositionClient}}('foo', 'bar'{{#isStandaloneClient}}, { requester: nodeEchoRequester(), authMode: 'WithinQueryParameters' }{{/isStandaloneClient}}){{^isStandaloneClient}}.{{{initMethod}}}({ options: { requester: nodeEchoRequester(), authMode: 'WithinQueryParameters' }, {{#hasRegionalHost}} region:'{{{defaultRegion}}}' {{/hasRegionalHost}} });{{/isStandaloneClient}}; - const headerClient = {{#isSearchClient}}{{{clientName}}}{{/isSearchClient}}{{#isCompositionClient}}compositionClient{{/isCompositionClient}}('foo', 'bar'{{#isStandaloneClient}}, { requester: nodeEchoRequester(), authMode: 'WithinHeaders' }{{/isStandaloneClient}}){{^isStandaloneClient}}.{{{initMethod}}}({ options: { requester: nodeEchoRequester(), authMode: 'WithinHeaders' }, {{#hasRegionalHost}} region:'{{{defaultRegion}}}' {{/hasRegionalHost}} });{{/isStandaloneClient}}; + const qpClient = {{^isCompositionClient}}{{{clientName}}}{{/isCompositionClient}}{{#isCompositionClient}}compositionClient{{/isCompositionClient}}('foo', 'bar'{{#isStandaloneClient}}, { requester: nodeEchoRequester(), authMode: 'WithinQueryParameters' }{{/isStandaloneClient}}){{^isStandaloneClient}}.{{{initMethod}}}({ options: { requester: nodeEchoRequester(), authMode: 'WithinQueryParameters' }, {{#hasRegionalHost}} region:'{{{defaultRegion}}}' {{/hasRegionalHost}} });{{/isStandaloneClient}}; + const headerClient = {{^isCompositionClient}}{{{clientName}}}{{/isCompositionClient}}{{#isCompositionClient}}compositionClient{{/isCompositionClient}}('foo', 'bar'{{#isStandaloneClient}}, { requester: nodeEchoRequester(), authMode: 'WithinHeaders' }{{/isStandaloneClient}}){{^isStandaloneClient}}.{{{initMethod}}}({ options: { requester: nodeEchoRequester(), authMode: 'WithinHeaders' }, {{#hasRegionalHost}} region:'{{{defaultRegion}}}' {{/hasRegionalHost}} });{{/isStandaloneClient}}; const qpResult = (await qpClient.customGet({ path: '1/foo', diff --git a/templates/javascript/tests/client/createClient.mustache b/templates/javascript/tests/client/createClient.mustache index 8a85420e8f..1bbb0eca9b 100644 --- a/templates/javascript/tests/client/createClient.mustache +++ b/templates/javascript/tests/client/createClient.mustache @@ -1,4 +1,4 @@ -{{^autoCreateClient}}const client = {{/autoCreateClient}}{{#isSearchClient}}{{{clientName}}}{{/isSearchClient}}{{#isCompositionClient}}compositionClient{{/isCompositionClient}}( +{{^autoCreateClient}}const client = {{/autoCreateClient}}{{^isCompositionClient}}{{{clientName}}}{{/isCompositionClient}}{{#isCompositionClient}}compositionClient{{/isCompositionClient}}( '{{parametersWithDataTypeMap.appId.value}}', '{{parametersWithDataTypeMap.apiKey.value}}' {{#isStandaloneClient}}, diff --git a/templates/javascript/tests/e2e/e2e.mustache b/templates/javascript/tests/e2e/e2e.mustache index e7c5a168cb..079424f392 100644 --- a/templates/javascript/tests/e2e/e2e.mustache +++ b/templates/javascript/tests/e2e/e2e.mustache @@ -22,12 +22,7 @@ if (!process.env.{{e2eApiKey}}) { throw new Error("please provide an `{{e2eApiKey}}` env var for e2e tests"); } -{{^isCompositionClient}} -const client = {{{clientName}}}(process.env.ALGOLIA_APPLICATION_ID, process.env.{{e2eApiKey}}){{^isSearchClient}}.{{{initMethod}}}({{#hasRegionalHost}} {region:'{{{defaultRegion}}}'} {{/hasRegionalHost}}){{/isSearchClient}}; -{{/isCompositionClient}} -{{#isCompositionClient}} -const client = compositionClient(process.env.ALGOLIA_APPLICATION_ID, process.env.{{e2eApiKey}}); -{{/isCompositionClient}} +const client = {{^isCompositionClient}}{{{clientName}}}{{/isCompositionClient}}{{#isCompositionClient}}compositionClient{{/isCompositionClient}}(process.env.ALGOLIA_APPLICATION_ID, process.env.{{e2eApiKey}}){{^isStandaloneClient}}.{{{initMethod}}}({{#hasRegionalHost}} {region:'{{{defaultRegion}}}'} {{/hasRegionalHost}}){{/isStandaloneClient}}; {{#blocksE2E}} describe('{{operationId}}', () => { diff --git a/templates/javascript/tests/requests/requests.mustache b/templates/javascript/tests/requests/requests.mustache index e1da55ea05..96de0298f5 100644 --- a/templates/javascript/tests/requests/requests.mustache +++ b/templates/javascript/tests/requests/requests.mustache @@ -15,7 +15,7 @@ const appId = process.env.ALGOLIA_APPLICATION_ID || 'test_app_id'; const apiKey = process.env.ALGOLIA_SEARCH_KEY || 'test_api_key'; const clientOptions: ClientOptions = { requester: nodeEchoRequester() }; // this makes sure the types are correctly exported -const client = {{#isSearchClient}}{{{clientName}}}{{/isSearchClient}}{{#isCompositionClient}}compositionClient{{/isCompositionClient}}(appId, apiKey{{#isStandaloneClient}}, { requester: nodeEchoRequester() }{{/isStandaloneClient}}){{^isStandaloneClient}}.{{{initMethod}}}({ options: clientOptions, {{#hasRegionalHost}} region:'{{{defaultRegion}}}' {{/hasRegionalHost}} });{{/isStandaloneClient}}; +const client = {{^isCompositionClient}}{{{clientName}}}{{/isCompositionClient}}{{#isCompositionClient}}compositionClient{{/isCompositionClient}}(appId, apiKey{{#isStandaloneClient}}, { requester: nodeEchoRequester() }{{/isStandaloneClient}}){{^isStandaloneClient}}.{{{initMethod}}}({ options: clientOptions, {{#hasRegionalHost}} region:'{{{defaultRegion}}}' {{/hasRegionalHost}} });{{/isStandaloneClient}}; {{#blocksRequests}} describe('{{operationId}}', () => { From 951b87f423394c1d475ed329ac2e49b6e48149ce Mon Sep 17 00:00:00 2001 From: shortcuts Date: Thu, 31 Oct 2024 11:55:17 +0100 Subject: [PATCH 10/16] fix: requests tests --- tests/CTS/requests/composition/getTask.json | 4 ++-- tests/CTS/requests/composition/multipleBatch.json | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/CTS/requests/composition/getTask.json b/tests/CTS/requests/composition/getTask.json index 95d0f32abc..d86810bc1d 100644 --- a/tests/CTS/requests/composition/getTask.json +++ b/tests/CTS/requests/composition/getTask.json @@ -2,10 +2,10 @@ { "parameters": { "compositionID": "foo", - "taskID": "bar" + "taskID": 42 }, "request": { - "path": "/1/compositions/foo/task/bar", + "path": "/1/compositions/foo/task/42", "method": "GET" } } diff --git a/tests/CTS/requests/composition/multipleBatch.json b/tests/CTS/requests/composition/multipleBatch.json index fa3a48b8c6..ae40fdb9ce 100644 --- a/tests/CTS/requests/composition/multipleBatch.json +++ b/tests/CTS/requests/composition/multipleBatch.json @@ -4,8 +4,8 @@ "requests": [{"action":"upsert","body":{"objectID":"foo","behavior": {"injection":{"main":{"source":{"search":{"index":"bar"}}},"insets":[]}}}},{"action":"delete","body":{"objectID":"baz"}}] }, "request": { - "path": "/1/compositions/foo", - "method": "GET" + "path": "/1/compositions/*/batch", + "method": "POST" } } ] From ef1e5f5cdef8f9c2db52c000f52652d14d2a49cf Mon Sep 17 00:00:00 2001 From: shortcuts Date: Thu, 31 Oct 2024 16:52:47 +0100 Subject: [PATCH 11/16] fix: multipleBatch test --- .../requests/composition/multipleBatch.json | 30 ++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/tests/CTS/requests/composition/multipleBatch.json b/tests/CTS/requests/composition/multipleBatch.json index ae40fdb9ce..e585d1227d 100644 --- a/tests/CTS/requests/composition/multipleBatch.json +++ b/tests/CTS/requests/composition/multipleBatch.json @@ -5,7 +5,35 @@ }, "request": { "path": "/1/compositions/*/batch", - "method": "POST" + "method": "POST", + "body": { + "requests": [ + { + "action": "upsert", + "body": { + "behavior": { + "injection": { + "insets": [], + "main": { + "source": { + "search": { + "index": "bar" + } + } + } + } + }, + "objectID": "foo" + } + }, + { + "action": "delete", + "body": { + "objectID": "baz" + } + } + ] + } } } ] From 09ba5b69d0310346bc420d4f08bcb667968bf6fe Mon Sep 17 00:00:00 2001 From: Emmanuel Date: Mon, 4 Nov 2024 15:18:39 +0100 Subject: [PATCH 12/16] =?UTF-8?q?fix:=20fetchedComposition=20=E2=86=92=20c?= =?UTF-8?q?omposition?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- specs/composition/common/schemas/batchCompositionAction.yml | 2 +- specs/composition/common/schemas/listCompositionsResponse.yml | 4 ++-- specs/composition/paths/objects/objects.yml | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/specs/composition/common/schemas/batchCompositionAction.yml b/specs/composition/common/schemas/batchCompositionAction.yml index b12562ab96..5ee9a1a471 100644 --- a/specs/composition/common/schemas/batchCompositionAction.yml +++ b/specs/composition/common/schemas/batchCompositionAction.yml @@ -10,5 +10,5 @@ deleteCompositionAction: batchCompositionAction: oneOf: - - $ref: './listCompositionsResponse.yml#/fetchedComposition' + - $ref: './listCompositionsResponse.yml#/composition' - $ref: '#/deleteCompositionAction' diff --git a/specs/composition/common/schemas/listCompositionsResponse.yml b/specs/composition/common/schemas/listCompositionsResponse.yml index e25f016cc0..1cb19638fc 100644 --- a/specs/composition/common/schemas/listCompositionsResponse.yml +++ b/specs/composition/common/schemas/listCompositionsResponse.yml @@ -6,7 +6,7 @@ listCompositionsResponse: type: array description: All compositions in your Algolia application. items: - $ref: '#/fetchedComposition' + $ref: '#/composition' nbPages: type: integer description: Number of pages. @@ -15,7 +15,7 @@ listCompositionsResponse: - items - nbPages -fetchedComposition: +composition: type: object additionalProperties: false properties: diff --git a/specs/composition/paths/objects/objects.yml b/specs/composition/paths/objects/objects.yml index 27d2e449c2..5817adc75f 100644 --- a/specs/composition/paths/objects/objects.yml +++ b/specs/composition/paths/objects/objects.yml @@ -16,7 +16,7 @@ get: content: application/json: schema: - $ref: '../../common/schemas/listCompositionsResponse.yml#/fetchedComposition' + $ref: '../../common/schemas/listCompositionsResponse.yml#/composition' '400': $ref: '../../../common/responses/BadRequest.yml' '402': From b0779e2ceb7d0c0692c4c8f99ded63d4aaae94fb Mon Sep 17 00:00:00 2001 From: Emmanuel Date: Mon, 4 Nov 2024 15:19:10 +0100 Subject: [PATCH 13/16] fix: wrongly positioned --- specs/composition/common/schemas/compositionBehavior.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/specs/composition/common/schemas/compositionBehavior.yml b/specs/composition/common/schemas/compositionBehavior.yml index ff11391146..985921c4bf 100644 --- a/specs/composition/common/schemas/compositionBehavior.yml +++ b/specs/composition/common/schemas/compositionBehavior.yml @@ -42,8 +42,8 @@ compositionSource: type: string description: Composition Main Index name. example: Products - params: - $ref: '../../../common/schemas/SearchParams.yml#/baseSearchParamsWithoutQuery' + params: + $ref: '../../../common/schemas/SearchParams.yml#/baseSearchParamsWithoutQuery' required: - index required: From 7fc5f89d53eed5cdc44d6f935af27a4fd15d8e4a Mon Sep 17 00:00:00 2001 From: Emmanuel Date: Mon, 4 Nov 2024 17:16:40 +0100 Subject: [PATCH 14/16] feat: add waitForCompositionTask for composition client --- .../codegen/AlgoliaJavascriptGenerator.java | 3 ++ .../helpers/waitForCompositionTask.yml | 34 ++++++++++++++++++ specs/composition/spec.yml | 3 ++ .../javascript/clients/api-single.mustache | 3 ++ .../client/api/compositionHelpers.mustache | 35 +++++++++++++++++++ .../clients/client/api/imports.mustache | 6 ++++ .../client/model/clientMethodProps.mustache | 23 ++++++++++++ 7 files changed, 107 insertions(+) create mode 100644 specs/composition/helpers/waitForCompositionTask.yml create mode 100644 templates/javascript/clients/client/api/compositionHelpers.mustache diff --git a/generators/src/main/java/com/algolia/codegen/AlgoliaJavascriptGenerator.java b/generators/src/main/java/com/algolia/codegen/AlgoliaJavascriptGenerator.java index f65732e281..08d05a58f5 100644 --- a/generators/src/main/java/com/algolia/codegen/AlgoliaJavascriptGenerator.java +++ b/generators/src/main/java/com/algolia/codegen/AlgoliaJavascriptGenerator.java @@ -18,6 +18,7 @@ public class AlgoliaJavascriptGenerator extends TypeScriptNodeClientCodegen { private String CLIENT; private boolean isAlgoliasearchClient; + private boolean isAlgoliaCompositionClient; @Override public String getName() { @@ -30,6 +31,7 @@ public void processOpts() { CLIENT = Helpers.camelize((String) additionalProperties.get("client")); isAlgoliasearchClient = CLIENT.equals("algoliasearch"); + isAlgoliaCompositionClient = CLIENT.equals("composition"); // generator specific options setSupportsES6(true); @@ -155,6 +157,7 @@ private void setDefaultGeneratorOptions() { additionalProperties.put("isSearchClient", CLIENT.equals("search") || isAlgoliasearchClient); additionalProperties.put("isIngestionClient", CLIENT.equals("ingestion")); additionalProperties.put("isAlgoliasearchClient", isAlgoliasearchClient); + additionalProperties.put("isAlgoliaCompositionClient", isAlgoliaCompositionClient); additionalProperties.put("packageVersion", Helpers.getPackageJsonVersion(packageName)); additionalProperties.put("packageName", packageName); additionalProperties.put("npmPackageName", isAlgoliasearchClient ? packageName : "@algolia/" + packageName); diff --git a/specs/composition/helpers/waitForCompositionTask.yml b/specs/composition/helpers/waitForCompositionTask.yml new file mode 100644 index 0000000000..b84d09ce51 --- /dev/null +++ b/specs/composition/helpers/waitForCompositionTask.yml @@ -0,0 +1,34 @@ +method: + get: + x-helper: true + tags: + - Records + operationId: waitForCompositionTask + summary: Wait for operation to complete + description: | + Wait for a task to complete to ensure synchronized composition updates. + + All Algolia write operations are asynchronous. When you make a request for a write operation, for example, to upsert or delete a composition, Algolia creates a task on a queue and returns a taskID. The task itself runs separately, depending on the server load. + parameters: + - in: query + name: compositionID + description: The ID of the composition on which the operation was performed. + required: true + schema: + type: string + - in: query + name: taskID + description: The taskID returned by the operation. + required: true + schema: + type: integer + format: int64 + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '../common/schemas/GetTaskResponse.yml' + '400': + $ref: '../../common/responses/CompositionNotFound.yml' diff --git a/specs/composition/spec.yml b/specs/composition/spec.yml index f646805d5a..af85425db4 100644 --- a/specs/composition/spec.yml +++ b/specs/composition/spec.yml @@ -73,3 +73,6 @@ paths: # ############### /setClientApiKey: $ref: '../common/helpers/setClientApiKey.yml#/method' + + /waitForCompositionTask: + $ref: 'helpers/waitForCompositionTask.yml#/method' diff --git a/templates/javascript/clients/api-single.mustache b/templates/javascript/clients/api-single.mustache index 2018f8dd74..3723fec808 100644 --- a/templates/javascript/clients/api-single.mustache +++ b/templates/javascript/clients/api-single.mustache @@ -91,6 +91,9 @@ export function create{{#lambda.titlecase}}{{clientName}}{{/lambda.titlecase}}({ {{#isSearchClient}} {{> client/api/helpers}} {{/isSearchClient}} + {{#isAlgoliaCompositionClient}} + {{> client/api/compositionHelpers}} + {{/isAlgoliaCompositionClient}} {{#operation}} {{> client/api/operation/jsdoc}} {{nickname}}{{#vendorExtensions.x-is-generic}}{{/vendorExtensions.x-is-generic}}( {{> client/api/operation/parameters}} ) : Promise<{{#returnType}}{{{returnType}}}{{/returnType}}{{^returnType}}void{{/returnType}}{{#vendorExtensions.x-is-generic}}{{/vendorExtensions.x-is-generic}}> { diff --git a/templates/javascript/clients/client/api/compositionHelpers.mustache b/templates/javascript/clients/client/api/compositionHelpers.mustache new file mode 100644 index 0000000000..41a4168750 --- /dev/null +++ b/templates/javascript/clients/client/api/compositionHelpers.mustache @@ -0,0 +1,35 @@ +/** + * Helper: Wait for a composition-level task to be published (completed) for a given `compositionID` and `taskID`. + * + * @summary Helper method that waits for a task to be published (completed). + * @param WaitForCompositionTaskOptions - The `WaitForCompositionTaskOptions` object. + * @param WaitForCompositionTaskOptions.compositionID - The `compositionID` where the operation was performed. + * @param WaitForCompositionTaskOptions.taskID - The `taskID` returned in the method response. + * @param WaitForCompositionTaskOptions.maxRetries - The maximum number of retries. 50 by default. + * @param WaitForCompositionTaskOptions.timeout - The function to decide how long to wait between retries. + * @param requestOptions - The requestOptions to send along with the query, they will be forwarded to the `getTask` method and merged with the transporter requestOptions. + */ +waitForCompositionTask( + { + compositionID, + taskID, + maxRetries = 50, + timeout = (retryCount: number): number => + Math.min(retryCount * 200, 5000), + }: WaitForCompositionTaskOptions, + requestOptions?: RequestOptions +): Promise { + let retryCount = 0; + + return createIterablePromise({ + func: () => this.getTask({ compositionID, taskID }, requestOptions), + validate: (response) => response.status === 'published', + aggregator: () => (retryCount += 1), + error: { + validate: () => retryCount >= maxRetries, + message: () => + `The maximum number of retries exceeded. (${retryCount}/${maxRetries})`, + }, + timeout: () => timeout(retryCount), + }); +}, diff --git a/templates/javascript/clients/client/api/imports.mustache b/templates/javascript/clients/client/api/imports.mustache index 2f223e5bff..429f45f6a5 100644 --- a/templates/javascript/clients/client/api/imports.mustache +++ b/templates/javascript/clients/client/api/imports.mustache @@ -9,6 +9,9 @@ import { serializeQueryParameters, createIterablePromise, {{/isSearchClient}} + {{#isAlgoliaCompositionClient}} + createIterablePromise, + {{/isAlgoliaCompositionClient}} } from '@algolia/client-common'; import type { CreateClientOptions, @@ -39,6 +42,9 @@ import type { WaitForAppTaskOptions, WaitForTaskOptions, {{/isSearchClient}} + {{#isAlgoliaCompositionClient}} + WaitForCompositionTaskOptions, + {{/isAlgoliaCompositionClient}} {{#operation}} {{#vendorExtensions}} {{#x-create-wrapping-object}} diff --git a/templates/javascript/clients/client/model/clientMethodProps.mustache b/templates/javascript/clients/client/model/clientMethodProps.mustache index 3e454a837e..d258335492 100644 --- a/templates/javascript/clients/client/model/clientMethodProps.mustache +++ b/templates/javascript/clients/client/model/clientMethodProps.mustache @@ -184,5 +184,28 @@ export type ReplaceAllObjectsOptions = { } {{/isAlgoliasearchClient}} {{/isSearchClient}} +{{#isAlgoliaCompositionClient}} +export type WaitForCompositionTaskOptions = { + /** + * The maximum number of retries. 50 by default. + */ + maxRetries?: number; + + /** + * The function to decide how long to wait between retries. + */ + timeout?: (retryCount: number) => number; + + /** + * The `taskID` returned by the method response. + */ + + taskID: number; + /** + * The `compositionID` where the operation was performed. + */ + compositionID: string; +}; +{{/isAlgoliaCompositionClient}} {{/apiInfo.apis.0}} \ No newline at end of file From d5582f512f1df6296127965a26ea181b5b629ad0 Mon Sep 17 00:00:00 2001 From: Emmanuel Date: Tue, 5 Nov 2024 18:21:36 +0100 Subject: [PATCH 15/16] feat: run & rules endpoints --- specs/composition/common/schemas/Hit.yml | 21 +++ .../common/schemas/SearchParams.yml | 134 ++++++++++++++++++ .../common/schemas/SearchResponse.yml | 29 ++++ .../common/schemas/compositionBehavior.yml | 1 - .../common/schemas/compositionRule.yml | 81 +++++++++++ .../schemas/rulesBatchCompositionAction.yml | 14 ++ specs/composition/paths/rules/parameters.yml | 12 ++ specs/composition/paths/rules/rule.yml | 29 ++++ specs/composition/paths/rules/saveRules.yml | 58 ++++++++ specs/composition/paths/rules/searchRules.yml | 79 +++++++++++ specs/composition/paths/search/run.yml | 32 +++++ specs/composition/spec.yml | 21 +++ 12 files changed, 510 insertions(+), 1 deletion(-) create mode 100644 specs/composition/common/schemas/Hit.yml create mode 100644 specs/composition/common/schemas/SearchParams.yml create mode 100644 specs/composition/common/schemas/SearchResponse.yml create mode 100644 specs/composition/common/schemas/compositionRule.yml create mode 100644 specs/composition/common/schemas/rulesBatchCompositionAction.yml create mode 100644 specs/composition/paths/rules/parameters.yml create mode 100644 specs/composition/paths/rules/rule.yml create mode 100644 specs/composition/paths/rules/saveRules.yml create mode 100644 specs/composition/paths/rules/searchRules.yml create mode 100644 specs/composition/paths/search/run.yml diff --git a/specs/composition/common/schemas/Hit.yml b/specs/composition/common/schemas/Hit.yml new file mode 100644 index 0000000000..e9eef29a2d --- /dev/null +++ b/specs/composition/common/schemas/Hit.yml @@ -0,0 +1,21 @@ +hit: + type: object + description: | + Search result. + + A hit is a record from your index, augmented with special attributes for highlighting, snippeting, and ranking. + x-is-generic: true + additionalProperties: true + required: + - objectID + properties: + objectID: + $ref: '../../../common/parameters.yml#/objectID' + _highlightResult: + $ref: '../../../common/schemas/HighlightResult.yml#/highlightResultMap' + _snippetResult: + $ref: '../../../common/schemas/SnippetResult.yml#/snippetResultMap' + _rankingInfo: + $ref: '../../../common/schemas/Hit.yml#/rankingInfo' + _distinctSeqID: + $ref: '../../../common/schemas/Hit.yml#/distinctSeqID' diff --git a/specs/composition/common/schemas/SearchParams.yml b/specs/composition/common/schemas/SearchParams.yml new file mode 100644 index 0000000000..b7e00cde76 --- /dev/null +++ b/specs/composition/common/schemas/SearchParams.yml @@ -0,0 +1,134 @@ +searchParams: + title: Composition Search parameters as object + type: object + additionalProperties: false + properties: + query: + $ref: '../../../common/schemas/SearchParams.yml#/query' + filters: + $ref: '../../../common/schemas/SearchParams.yml#/filters' + page: + $ref: '#/page' + getRankingInfo: + type: boolean + description: Whether the search response should include detailed ranking information. + relevancyStrictness: + type: integer + facetFilters: + $ref: '../../../common/schemas/SearchParams.yml#/facetFilters' + optionalFilters: + $ref: '../../../common/schemas/SearchParams.yml#/optionalFilters' + numericFilters: + $ref: '../../../common/schemas/SearchParams.yml#/numericFilters' + hitsPerPage: + $ref: '../../../common/schemas/IndexSettings.yml#/hitsPerPage' + aroundLatLng: + $ref: '../../../common/schemas/SearchParams.yml#/aroundLatLng' + aroundLatLngViaIP: + $ref: '../../../common/schemas/SearchParams.yml#/aroundLatLngViaIP' + aroundRadius: + $ref: '../../../common/schemas/SearchParams.yml#/aroundRadius' + aroundPrecision: + $ref: '../../../common/schemas/SearchParams.yml#/aroundPrecision' + minimumAroundRadius: + type: integer + description: Minimum radius (in meters) for a search around a location when `aroundRadius` isn't set. + minimum: 1 + x-categories: + - Geo-Search + insideBoundingBox: + $ref: '../../../common/schemas/SearchParams.yml#/insideBoundingBox' + insidePolygon: + $ref: '../../../common/schemas/SearchParams.yml#/insidePolygon' + queryLanguages: + type: array + items: + $ref: '../../../common/schemas/IndexSettings.yml#/supportedLanguage' + example: + - es + description: | + Languages for language-specific query processing steps such as plurals, stop-word removal, and word-detection dictionaries. + + This setting sets a default list of languages used by the `removeStopWords` and `ignorePlurals` settings. + This setting also sets a dictionary for word detection in the logogram-based [CJK](https://www.algolia.com/doc/guides/managing-results/optimize-search-results/handling-natural-languages-nlp/in-depth/normalization/#normalization-for-logogram-based-languages-cjk) languages. + To support this, you must place the CJK language **first**. + + **You should always specify a query language.** + If you don't specify an indexing language, the search engine uses all [supported languages](https://www.algolia.com/doc/guides/managing-results/optimize-search-results/handling-natural-languages-nlp/in-depth/supported-languages/), + or the languages you specified with the `ignorePlurals` or `removeStopWords` parameters. + This can lead to unexpected search results. + For more information, see [Language-specific configuration](https://www.algolia.com/doc/guides/managing-results/optimize-search-results/handling-natural-languages-nlp/in-depth/language-specific-configurations/). + default: [] + x-categories: + - Languages + naturalLanguages: + type: array + items: + $ref: '../../../common/schemas/IndexSettings.yml#/supportedLanguage' + description: | + ISO language codes that adjust settings that are useful for processing natural language queries (as opposed to keyword searches): + + - Sets `removeStopWords` and `ignorePlurals` to the list of provided languages. + - Sets `removeWordsIfNoResults` to `allOptional`. + - Adds a `natural_language` attribute to `ruleContexts` and `analyticsTags`. + default: [] + x-categories: + - Languages + enableRules: + type: boolean + description: Whether to enable rules. + default: true + x-categories: + - Rules + ruleContexts: + type: array + items: + type: string + description: | + Assigns a rule context to the search query. + + [Rule contexts](https://www.algolia.com/doc/guides/managing-results/rules/rules-overview/how-to/customize-search-results-by-platform/#whats-a-context) are strings that you can use to trigger matching rules. + default: [] + example: [mobile] + x-categories: + - Rules + userToken: + $ref: '../../../common/schemas/SearchParams.yml#/userToken' + clickAnalytics: + type: boolean + description: | + Whether to include a `queryID` attribute in the response. + + The query ID is a unique identifier for a search query and is required for tracking [click and conversion events](https://www.algolia.com/guides/sending-events/getting-started/). + default: false + x-categories: + - Analytics + analytics: + type: boolean + description: Whether this search will be included in Analytics. + default: true + x-categories: + - Analytics + analyticsTags: + type: array + items: + type: string + description: Tags to apply to the query for [segmenting analytics data](https://www.algolia.com/doc/guides/search-analytics/guides/segments/). + default: [] + x-categories: + - Analytics + enableABTest: + type: boolean + description: Whether to enable A/B testing for this search. + default: true + x-categories: + - Advanced + enableReRanking: + type: boolean + description: | + Whether this search will use [Dynamic Re-Ranking](https://www.algolia.com/doc/guides/algolia-ai/re-ranking/). + + This setting only has an effect if you activated Dynamic Re-Ranking for this index in the Algolia dashboard. + default: true + x-categories: + - Filtering diff --git a/specs/composition/common/schemas/SearchResponse.yml b/specs/composition/common/schemas/SearchResponse.yml new file mode 100644 index 0000000000..5ffdff4275 --- /dev/null +++ b/specs/composition/common/schemas/SearchResponse.yml @@ -0,0 +1,29 @@ +searchResponse: + additionalProperties: true + allOf: + - $ref: '../../../common/schemas/SearchResponse.yml#/baseSearchResponse' + - $ref: '../../../common/schemas/SearchResponse.yml#/SearchPagination' + - $ref: '#/searchHits' + +searchHits: + type: object + additionalProperties: true + properties: + hits: + type: array + description: | + Search results (hits). + + Hits are records from your index that match the search criteria, augmented with additional attributes, such as, for highlighting. + items: + $ref: 'Hit.yml#/hit' + query: + $ref: '../../../common/schemas/SearchParams.yml#/query' + params: + type: string + description: URL-encoded string of all search parameters. + example: query=a&hitsPerPage=20 + required: + - hits + - query + - params diff --git a/specs/composition/common/schemas/compositionBehavior.yml b/specs/composition/common/schemas/compositionBehavior.yml index 985921c4bf..18798419e0 100644 --- a/specs/composition/common/schemas/compositionBehavior.yml +++ b/specs/composition/common/schemas/compositionBehavior.yml @@ -25,7 +25,6 @@ compositionBehavior: $ref: '#/compositionInset' required: - main - - insets required: - injection diff --git a/specs/composition/common/schemas/compositionRule.yml b/specs/composition/common/schemas/compositionRule.yml new file mode 100644 index 0000000000..5b270a1d24 --- /dev/null +++ b/specs/composition/common/schemas/compositionRule.yml @@ -0,0 +1,81 @@ +compositionRule: + type: object + additionalProperties: false + properties: + objectID: + $ref: '../../../common/parameters.yml#/objectID' + conditions: + type: array + minItems: 0 + maxItems: 25 + description: Conditions that trigger a composition rule. + items: + $ref: '#/condition' + consequence: + title: compositionRuleConsequence + type: object + description: Effect of the rule. + additionalProperties: false + properties: + params: + $ref: './compositionBehavior.yml#/compositionBehavior' + description: + type: string + description: Description of the rule's purpose to help you distinguish between different rules. + example: Display a promotional banner + enabled: + type: boolean + default: true + description: Whether the rule is active. + validity: + type: array + description: Time periods when the rule is active. + items: + $ref: '../../../common/schemas/Rule.yml#/timeRange' + required: + - objectID + - conditions + - consequence + +condition: + type: object + additionalProperties: false + properties: + pattern: + type: string + description: | + Query pattern that triggers the rule. + + You can use either a literal string, or a special pattern `{facet:ATTRIBUTE}`, where `ATTRIBUTE` is a facet name. + The rule is triggered if the query matches the literal string or a value of the specified facet. + For example, with `pattern: {facet:genre}`, the rule is triggered when users search for a genre, such as "comedy". + example: '{facet:genre}' + anchoring: + $ref: '#/anchoring' + alternatives: + type: boolean + description: Whether the pattern should match plurals, synonyms, and typos. + default: false + context: + $ref: '../../../common/schemas/Rule.yml#/context' + filters: + type: string + description: | + Filters that trigger the rule. + + You can add add filters using the syntax `facet:value` so that the rule is triggered, when the specific filter is selected. + You can use `filters` on its own or combine it with the `pattern` parameter. + example: 'genre:comedy' + +anchoring: + type: string + description: | + Which part of the search query the pattern should match: + + - `startsWith`. The pattern must match the beginning of the query. + - `endsWith`. The pattern must match the end of the query. + - `is`. The pattern must match the query exactly. + - `contains`. The pattern must match anywhere in the query. + + Empty queries are only allowed as patterns with `anchoring: is`. + enum: [is, startsWith, endsWith, contains] diff --git a/specs/composition/common/schemas/rulesBatchCompositionAction.yml b/specs/composition/common/schemas/rulesBatchCompositionAction.yml new file mode 100644 index 0000000000..b9c859b526 --- /dev/null +++ b/specs/composition/common/schemas/rulesBatchCompositionAction.yml @@ -0,0 +1,14 @@ +deleteCompositionRuleAction: + type: object + description: Operation arguments when deleting. + additionalProperties: false + properties: + objectID: + $ref: '../../../common/parameters.yml#/objectID' + required: + - objectID + +rulesBatchCompositionAction: + oneOf: + - $ref: './compositionRule.yml#/compositionRule' + - $ref: '#/deleteCompositionRuleAction' diff --git a/specs/composition/paths/rules/parameters.yml b/specs/composition/paths/rules/parameters.yml new file mode 100644 index 0000000000..ef2701465f --- /dev/null +++ b/specs/composition/paths/rules/parameters.yml @@ -0,0 +1,12 @@ +ObjectIDRule: + in: path + name: objectID + description: Unique identifier of a rule object. + required: true + schema: + $ref: '../../../common/parameters.yml#/ruleID' + +query: + type: string + description: Search query for rules. + default: '' diff --git a/specs/composition/paths/rules/rule.yml b/specs/composition/paths/rules/rule.yml new file mode 100644 index 0000000000..6b9721e32f --- /dev/null +++ b/specs/composition/paths/rules/rule.yml @@ -0,0 +1,29 @@ +get: + tags: + - Rules + operationId: getRule + x-acl: + - settings + summary: Retrieve a rule + description: | + Retrieves a rule by its ID. + To find the object ID of rules, use the [`search` operation](#tag/Rules/operation/searchRules). + + parameters: + - $ref: '../../common/parameters.yml#/compositionID' + - $ref: './parameters.yml#/ObjectIDRule' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '../../common/schemas/compositionRule.yml#/compositionRule' + '400': + $ref: '../../../common/responses/BadRequest.yml' + '402': + $ref: '../../../common/responses/FeatureNotEnabled.yml' + '403': + $ref: '../../../common/responses/MethodNotAllowed.yml' + '404': + $ref: '../../../common/responses/IndexNotFound.yml' diff --git a/specs/composition/paths/rules/saveRules.yml b/specs/composition/paths/rules/saveRules.yml new file mode 100644 index 0000000000..409820f2d8 --- /dev/null +++ b/specs/composition/paths/rules/saveRules.yml @@ -0,0 +1,58 @@ +post: + tags: + - Rules + operationId: saveRules + x-acl: + - editSettings + summary: Create or update or delete composition rules + description: | + Create or update or delete multiple composition rules. + x-codegen-request-body-name: rules + parameters: + - $ref: '../../common/parameters.yml#/compositionID' + requestBody: + required: true + content: + application/json: + schema: + title: compositionRulesBatchParams + description: Composition rules batch parameters. + type: object + additionalProperties: false + properties: + requests: + type: array + items: + title: rulesMultipleBatchRequest + type: object + additionalProperties: false + properties: + action: + $ref: '../../common/schemas/Batch.yml#/action' + body: + $ref: '../../common/schemas/rulesBatchCompositionAction.yml#/rulesBatchCompositionAction' + required: + - action + - body + responses: + '200': + description: OK + content: + application/json: + schema: + title: rulesMultipleBatchResponse + type: object + additionalProperties: false + properties: + taskID: + $ref: '../../../common/responses/common.yml#/taskID' + required: + - taskID + '400': + $ref: '../../../common/responses/BadRequest.yml' + '402': + $ref: '../../../common/responses/FeatureNotEnabled.yml' + '403': + $ref: '../../../common/responses/MethodNotAllowed.yml' + '404': + $ref: '../../../common/responses/IndexNotFound.yml' diff --git a/specs/composition/paths/rules/searchRules.yml b/specs/composition/paths/rules/searchRules.yml new file mode 100644 index 0000000000..accfa1b66b --- /dev/null +++ b/specs/composition/paths/rules/searchRules.yml @@ -0,0 +1,79 @@ +post: + tags: + - Rules + operationId: searchCompositionRules + x-use-read-transporter: true + x-cacheable: true + x-acl: + - settings + summary: Search for composition rules + description: Searches for composition rules in your index. + parameters: + - $ref: '../../common/parameters.yml#/compositionID' + requestBody: + content: + application/json: + schema: + title: searchCompositionRulesParams + type: object + description: Composition Rules search parameters. + additionalProperties: false + properties: + query: + $ref: './parameters.yml#/query' + anchoring: + $ref: '../../common/schemas/compositionRule.yml#/anchoring' + context: + type: string + description: Only return composition rules that match the context (exact match). + example: 'mobile' + page: + $ref: '../../../common/parameters.yml#/page' + hitsPerPage: + $ref: '../../../common/parameters.yml#/hitsPerPage' + enabled: + oneOf: + - type: boolean + description: | + If `true`, return only enabled composition rules. + If `false`, return only inactive composition rules. + By default, _all_ composition rules are returned. + - type: 'null' + default: null + responses: + '200': + description: OK + content: + application/json: + schema: + title: searchCompositionRulesResponse + type: object + additionalProperties: false + required: + - hits + - nbHits + - page + - nbPages + properties: + hits: + type: array + description: Composition rules that matched the search criteria. + items: + $ref: '../../common/schemas/compositionRule.yml#/compositionRule' + nbHits: + type: integer + description: Number of composition rules that matched the search criteria. + page: + type: integer + description: Current page. + nbPages: + type: integer + description: Number of pages. + '400': + $ref: '../../../common/responses/BadRequest.yml' + '402': + $ref: '../../../common/responses/FeatureNotEnabled.yml' + '403': + $ref: '../../../common/responses/MethodNotAllowed.yml' + '404': + $ref: '../../../common/responses/IndexNotFound.yml' diff --git a/specs/composition/paths/search/run.yml b/specs/composition/paths/search/run.yml new file mode 100644 index 0000000000..e1aed1a40a --- /dev/null +++ b/specs/composition/paths/search/run.yml @@ -0,0 +1,32 @@ +post: + tags: + - Search + operationId: runSingleComposition + x-use-read-transporter: true + x-cacheable: true + x-acl: + - search + summary: Run a Composition + description: Runs a query on a single composition and returns matching results. + parameters: + - $ref: '../../common/parameters.yml#/compositionID' + requestBody: + content: + application/json: + schema: + $ref: '../../../common/schemas/SearchParams.yml#/searchParams' + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '../../common/schemas/SearchResponse.yml#/searchResponse' + '400': + $ref: '../../../common/responses/BadRequest.yml' + '402': + $ref: '../../../common/responses/FeatureNotEnabled.yml' + '403': + $ref: '../../../common/responses/MethodNotAllowed.yml' + '404': + $ref: '../../../common/responses/IndexNotFound.yml' diff --git a/specs/composition/spec.yml b/specs/composition/spec.yml index af85425db4..8917dc98d2 100644 --- a/specs/composition/spec.yml +++ b/specs/composition/spec.yml @@ -37,6 +37,11 @@ tags: - name: Compositions description: | Manage your compositions and composition settings. + - name: Rules + description: | + Manage your compositions rules. + - name: Search + description: Search one or more indices for matching records or facet values. x-tagGroups: - name: Search tags: @@ -48,6 +53,12 @@ paths: /{path}: $ref: '../common/paths/customRequest.yml' + # ######################## + # ### Search Endpoints ### + # ######################## + /1/compositions/{compositionID}/run: + $ref: 'paths/search/run.yml' + # ######################### # ### Objects Endpoints ### # ######################### @@ -56,6 +67,16 @@ paths: /1/compositions/*/batch: $ref: 'paths/objects/multipleBatch.yml' + # ####################### + # ### Rules Endpoints ### + # ####################### + /1/compositions/{compositionID}/rules/{objectID}: + $ref: 'paths/rules/rule.yml' + /1/compositions/{compositionID}/rules/batch: + $ref: 'paths/rules/saveRules.yml' + /1/compositions/{compositionID}/rules/search: + $ref: 'paths/rules/searchRules.yml' + # ########################## # ### Advanced Endpoints ### # ########################## From c2ab89b0061db4d38ef530299ff5d1b6813d5269 Mon Sep 17 00:00:00 2001 From: Pierre Millot Date: Tue, 5 Nov 2024 19:04:12 +0100 Subject: [PATCH 16/16] fix some tests --- scripts/cts/testServer/timeout.ts | 12 +++++++----- tests/CTS/requests/composition/listCompositions.json | 4 ---- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/scripts/cts/testServer/timeout.ts b/scripts/cts/testServer/timeout.ts index 1309dd8c7e..efe3f2325e 100644 --- a/scripts/cts/testServer/timeout.ts +++ b/scripts/cts/testServer/timeout.ts @@ -17,7 +17,9 @@ export function assertValidTimeouts(expectedCount: number): void { for (const [lang, state] of Object.entries(timeoutState)) { let numberOfTestSuites = 1; - if (lang === 'python') { + // composition api is also testing the retry strategy in JS + // python has sync and async tests + if (lang === 'python' || lang === 'javascript') { numberOfTestSuites = 2; } @@ -26,8 +28,8 @@ export function assertValidTimeouts(expectedCount: number): void { expect(state.duration.length).to.equal(3 * numberOfTestSuites); for (let i = 0; i < numberOfTestSuites; i++) { - expect(state.timestamp[3 * i + 1] - state.timestamp[3 * i]).to.be.closeTo(state.duration[3 * i], 100); - expect(state.timestamp[3 * i + 2] - state.timestamp[3 * i + 1]).to.be.closeTo(state.duration[3 * i + 1], 100); + expect(state.timestamp[3 * i + 1] - state.timestamp[3 * i]).to.be.closeTo(state.duration[3 * i], 400); + expect(state.timestamp[3 * i + 2] - state.timestamp[3 * i + 1]).to.be.closeTo(state.duration[3 * i + 1], 400); // languages are not consistent yet for the delay between requests switch (lang) { @@ -35,14 +37,14 @@ export function assertValidTimeouts(expectedCount: number): void { expect(state.duration[3 * i] * 4).to.be.closeTo(state.duration[3 * i + 1], 300); break; case 'php': - expect(state.duration[3 * i] * 2).to.be.closeTo(state.duration[3 * i + 1], 200); + expect(state.duration[3 * i] * 2).to.be.closeTo(state.duration[3 * i + 1], 300); break; case 'swift': expect(state.duration[3 * i]).to.be.closeTo(state.duration[3 * i + 1], 800); break; default: // the delay should be the same, because the `retryCount` is per host instead of global - expect(state.duration[3 * i]).to.be.closeTo(state.duration[3 * i + 1], 150); + expect(state.duration[3 * i]).to.be.closeTo(state.duration[3 * i + 1], 300); break; } } diff --git a/tests/CTS/requests/composition/listCompositions.json b/tests/CTS/requests/composition/listCompositions.json index 97f78c43ce..aeddc5c455 100644 --- a/tests/CTS/requests/composition/listCompositions.json +++ b/tests/CTS/requests/composition/listCompositions.json @@ -11,10 +11,6 @@ "request": { "path": "/1/compositions", "method": "GET" - }, - "response": { - "items": [{"objectID":"foo","description":"bar","behavior":{"injection":{"main":{"source":{"search":{"index":"baz"}}}}}}], - "nbPages": 1 } } ]