diff --git a/retail/interactive-tutorials/README.md b/retail/interactive-tutorials/README.md new file mode 100644 index 0000000000..bb5332f61b --- /dev/null +++ b/retail/interactive-tutorials/README.md @@ -0,0 +1,64 @@ + +## Get started with Google Cloud Retail + +### Select your project and enable the Retail API + +Google Cloud organizes resources into projects. This lets you +collect all the related resources for a single application in one place. + +If you don't have a Google Cloud project yet or you're not the Owner of an existing one, you can +[create a new project](https://console.cloud.google.com/projectcreate). + +After the project is created, set your PROJECT_ID to a ```project``` variable. +1. Run the following command in Terminal: + ```bash + gcloud config set project + ``` + +1. Check that the Retail API is enabled for your Project in the [Admin Console](https://console.cloud.google.com/ai/retail/). + +### Set up authentication + +To run a code sample from the Cloud Shell, you need to authenticate. To do this, use the Application Default Credentials. + +1. Set your user credentials to authenticate your requests to the Retail API + + ```bash + gcloud auth application-default login + ``` + +1. Type `Y` and press **Enter**. Click the link in Terminal. A browser window should appear asking you to log in using your Google account. + +1. Provide the Google Auth Library with access to your credentials and paste the code from the browser to the Terminal. + +1. Run the code sample and check the Retail API in action. + +**Note**: Click the copy button on the side of the code box to paste the command in the Cloud Shell terminal and run it. + +### Set the GCLOUD_PROJECT environment variable + +Because you are going to run the code samples in your own Google Cloud project, you should specify the **project_number** as an environment variable. It will be used in every request to the Retail API. + +1. You can find the ```project_number``` in the **Home/Dashboard/Project Info card**. + +1. Set the environment variable with the following command: + ```bash + export GCLOUD_PROJECT= + ``` + +### Install Google Cloud Retail libraries + +To install all the dependencies, run + +``` +cd cloudshell_open/retail-search-nodejs-samples +npm install +``` + +### Running the code samples +Samples are in the `search/`, `products/`, `events/` directories. +To execute an individual code sample, invoke `node` with a file as a parameter at the command line prompt, e.g.: + +``` +node search/search-simple-query.js +``` \ No newline at end of file diff --git a/retail/interactive-tutorials/search/search-simple-query.js b/retail/interactive-tutorials/search/search-simple-query.js new file mode 100644 index 0000000000..7a5bd03669 --- /dev/null +++ b/retail/interactive-tutorials/search/search-simple-query.js @@ -0,0 +1,76 @@ +// Copyright 2022 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +'use strict'; + +async function main() { + // [START retail_search_for_products_with_query_parameter] + // Call Retail API to search for a products in a catalog using only search query. + + // Imports the Google Cloud client library. + const {SearchServiceClient} = require('@google-cloud/retail'); + + const projectNumber = process.env['GCLOUD_PROJECT']; + + // Placement is used to identify the Serving Config name. + const placement = `projects/${projectNumber}/locations/global/catalogs/default_catalog/placements/default_search`; + + // Raw search query. + const query = 'Hoodie'; //TRY DIFFERENT QUERY PHRASES + + // A unique identifier for tracking visitors. + const visitorId = '12345'; + + // Maximum number of Products to return. + const pageSize = 10; + + // Instantiates a client. + const retailClient = new SearchServiceClient(); + + const IResponseParams = { + ISearchResult: 0, + ISearchRequest: 1, + ISearchResponse: 2, + }; + + const callSearch = async () => { + console.log('Search start'); + // Construct request + const request = { + placement, + query, + visitorId, + pageSize, + }; + console.log('Search request: ', request); + + // Run request + const response = await retailClient.search(request, { + autoPaginate: false, + }); + const searchResponse = response[IResponseParams.ISearchResponse]; + console.log('Search result: ', JSON.stringify(searchResponse, null, 4)); + console.log('Search end'); + }; + + callSearch(); + // [END retail_search_for_products_with_query_parameter] +} + +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); + +main(); diff --git a/retail/interactive-tutorials/search/search-with-boost-spec.js b/retail/interactive-tutorials/search/search-with-boost-spec.js new file mode 100644 index 0000000000..52db62db08 --- /dev/null +++ b/retail/interactive-tutorials/search/search-with-boost-spec.js @@ -0,0 +1,89 @@ +// Copyright 2022 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +'use strict'; + +async function main() { + // [START retail_search_product_with_boost_spec] + // Call Retail API to search for a products in a catalog, rerank the + // results boosting or burying the products that match defined condition. + + // Imports the Google Cloud client library. + const {SearchServiceClient} = require('@google-cloud/retail'); + + const projectNumber = process.env['GCLOUD_PROJECT']; + + // Placement is used to identify the Serving Config name. + const placement = `projects/${projectNumber}/locations/global/catalogs/default_catalog/placements/default_search`; + + // Raw search query. + const query = 'Hoodie'; + + // A unique identifier for tracking visitors. + const visitorId = '12345'; + + //Boost specification to boost certain products. + const boostSpec = { + conditionBoostSpecs: [ + { + condition: '(colorFamilies: ANY("Blue"))', // TRY OTHER CONDITIONS + boost: 0.0, // TRY DIFFERENT SCORES + }, + ], + }; + + // Maximum number of Products to return. + const pageSize = 10; + + // Instantiates a client + const retailClient = new SearchServiceClient(); + + const IResponseParams = { + ISearchResult: 0, + ISearchRequest: 1, + ISearchResponse: 2, + }; + + const callSearch = async () => { + console.log('Search start'); + // Construct request + const request = { + placement, + query, + visitorId, + boostSpec, + pageSize, + }; + + console.log('Search request: ', request); + + // Run request + const response = await retailClient.search(request, { + autoPaginate: false, + }); + const searchResponse = response[IResponseParams.ISearchResponse]; + console.log('Search result: ', JSON.stringify(searchResponse, null, 4)); + console.log('Search end'); + }; + + callSearch(); + // [END retail_search_product_with_boost_spec] +} + +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); + +main(); diff --git a/retail/interactive-tutorials/search/search-with-facet-spec.js b/retail/interactive-tutorials/search/search-with-facet-spec.js new file mode 100644 index 0000000000..7205c874bc --- /dev/null +++ b/retail/interactive-tutorials/search/search-with-facet-spec.js @@ -0,0 +1,80 @@ +// Copyright 2022 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +'use strict'; + +async function main() { + // [START retail_search_products_with_facet_spec] + // Call Retail API to search for a products in a catalog using only search query. + + // Imports the Google Cloud client library. + const {SearchServiceClient} = require('@google-cloud/retail'); + + const projectNumber = process.env['GCLOUD_PROJECT']; + + // Placement is used to identify the Serving Config name. + const placement = `projects/${projectNumber}/locations/global/catalogs/default_catalog/placements/default_search`; + + // Raw search query. + const query = 'Tee'; + + // A unique identifier for tracking visitors. + const visitorId = '12345'; + + //Facet specifications for faceted search. + const facetSpecs = [{facetKey: {key: 'colorFamilies'}}]; //PUT THE INTERVALS HERE + + // Maximum number of Products to return. + const pageSize = 10; + + // Instantiates a client. + const retailClient = new SearchServiceClient(); + + const IResponseParams = { + ISearchResult: 0, + ISearchRequest: 1, + ISearchResponse: 2, + }; + + const callSearch = async () => { + console.log('Search start'); + // Construct request + const request = { + placement, + query, + visitorId, + facetSpecs, + pageSize, + }; + console.log('Search request: ', request); + + // Run request + const response = await retailClient.search(request, { + autoPaginate: false, + }); + const searchResponse = response[IResponseParams.ISearchResponse]; + console.log('Search result: ', JSON.stringify(searchResponse, null, 4)); + console.log('Search end'); + }; + + callSearch(); + // [END retail_search_products_with_facet_spec] +} + +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); + +main(); diff --git a/retail/interactive-tutorials/search/search-with-filtering.js b/retail/interactive-tutorials/search/search-with-filtering.js new file mode 100644 index 0000000000..3147809df4 --- /dev/null +++ b/retail/interactive-tutorials/search/search-with-filtering.js @@ -0,0 +1,82 @@ +// Copyright 2022 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +'use strict'; + +async function main() { + // [START retail_search_for_products_with_filter] + // Call Retail API to search for a products in a catalog, filter the results by different product fields. + + // Imports the Google Cloud client library. + const {SearchServiceClient} = require('@google-cloud/retail'); + + const projectNumber = process.env['GCLOUD_PROJECT']; + + // Placement is used to identify the Serving Config name. + const placement = `projects/${projectNumber}/locations/global/catalogs/default_catalog/placements/default_search`; + + // Raw search query. + const query = 'Tee'; + + // A unique identifier for tracking visitors. + const visitorId = '12345'; + + // The filter syntax consists of an expression language for constructing a + // predicate from one or more fields of the products being filtered. + const filter = '(colorFamilies: ANY("Black"))'; // TRY DIFFERENT FILTER EXPRESSIONS + + // Maximum number of Products to return. + const pageSize = 10; + + // Instantiates a client. + const retailClient = new SearchServiceClient(); + + const IResponseParams = { + ISearchResult: 0, + ISearchRequest: 1, + ISearchResponse: 2, + }; + + const callSearch = async () => { + console.log('Search start'); + // Construct request + const request = { + placement, + query, + visitorId, + filter, + pageSize, + }; + + console.log('Search request: ', request); + + // Run request + const response = await retailClient.search(request, { + autoPaginate: false, + }); + const searchResponse = response[IResponseParams.ISearchResponse]; + console.log('Search result: ', JSON.stringify(searchResponse, null, 4)); + console.log('Search end'); + }; + + callSearch(); + // [END retail_search_for_products_with_filter] +} + +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); + +main(); diff --git a/retail/interactive-tutorials/search/search-with-ordering.js b/retail/interactive-tutorials/search/search-with-ordering.js new file mode 100644 index 0000000000..af99188f29 --- /dev/null +++ b/retail/interactive-tutorials/search/search-with-ordering.js @@ -0,0 +1,81 @@ +// Copyright 2022 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +'use strict'; + +async function main() { + // [START retail_search_for_products_with_ordering] + // Call Retail API to search for a products in a catalog, order the results by different product fields. + + // Imports the Google Cloud client library. + const {SearchServiceClient} = require('@google-cloud/retail'); + + const projectNumber = process.env['GCLOUD_PROJECT']; + + // Placement is used to identify the Serving Config name. + const placement = `projects/${projectNumber}/locations/global/catalogs/default_catalog/placements/default_search`; + + // Raw search query. + const query = 'Hoodie'; + + // A unique identifier for tracking visitors. + const visitorId = '12345'; + + // The order in which products are returned. + const orderBy = 'price desc'; // TRY DIFFERENT ORDER + + // Maximum number of Products to return. + const pageSize = 10; + + // Instantiates a client + const retailClient = new SearchServiceClient(); + + const IResponseParams = { + ISearchResult: 0, + ISearchRequest: 1, + ISearchResponse: 2, + }; + + const callSearch = async () => { + console.log('Search start'); + // Construct request + const request = { + placement, + query, + visitorId, + orderBy, + pageSize, + }; + + console.log('Search request: ', request); + + // Run request + const response = await retailClient.search(request, { + autoPaginate: false, + }); + const searchResponse = response[IResponseParams.ISearchResponse]; + console.log('Search result: ', JSON.stringify(searchResponse, null, 4)); + console.log('Search end'); + }; + + callSearch(); + // [END retail_search_for_products_with_ordering] +} + +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); + +main(); diff --git a/retail/interactive-tutorials/search/search-with-pagination.js b/retail/interactive-tutorials/search/search-with-pagination.js new file mode 100644 index 0000000000..3158f3565a --- /dev/null +++ b/retail/interactive-tutorials/search/search-with-pagination.js @@ -0,0 +1,91 @@ +// Copyright 2022 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +'use strict'; + +async function main() { + // [START retail_search_for_products_with_pagination] + + // Imports the Google Cloud client library. + const {SearchServiceClient} = require('@google-cloud/retail'); + + const projectNumber = process.env['GCLOUD_PROJECT']; + + // Placement is used to identify the Serving Config name. + const placement = `projects/${projectNumber}/locations/global/catalogs/default_catalog/placements/default_search`; + + // Raw search query. + const query = 'Hoodie'; + + // A unique identifier for tracking visitors. + const visitorId = '12345'; + + // Maximum number of Products to return. + const pageSize = 6; // TRY DIFFERENT PAGE SIZES, INCLUDING THOSE OVER 100 + + // A 0-indexed integer that specifies the current offset in search results. + const offset = 0; // TRY DIFFERENT OFFSETS TO SEE DIFFERENT PRODUCTS + + //A page token received from a previous search call. + let pageToken = ''; + + // Instantiates a client. + const retailClient = new SearchServiceClient(); + + const IResponseParams = { + ISearchResult: 0, + ISearchRequest: 1, + ISearchResponse: 2, + }; + + const callSearch = async () => { + console.log('Search start'); + // Construct request + const request = { + placement, + query, + visitorId, + pageSize, + offset, + pageToken, + }; + + // Run request + const response = await retailClient.search(request, { + autoPaginate: false, + }); + const searchResponse = response[IResponseParams.ISearchResponse]; + console.log('Search result: ', JSON.stringify(searchResponse, null, 4)); + pageToken = response[IResponseParams.ISearchResponse].nextPageToken; + console.log( + 'Next page token:', + response[IResponseParams.ISearchResponse].nextPageToken + ); + console.log('Search end'); + }; + + // Call search + await callSearch(); + + //PASTE CALL WITH NEXT PAGE TOKEN HERE: + + // [END retail_search_for_products_with_pagination] +} + +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); + +main(); diff --git a/retail/interactive-tutorials/search/search-with-query-expansion-spec.js b/retail/interactive-tutorials/search/search-with-query-expansion-spec.js new file mode 100644 index 0000000000..e70387f9e1 --- /dev/null +++ b/retail/interactive-tutorials/search/search-with-query-expansion-spec.js @@ -0,0 +1,83 @@ +// Copyright 2022 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +'use strict'; + +async function main() { + // [START retail_search_for_products_with_query_expansion_specification] + + // Imports the Google Cloud client library. + const {SearchServiceClient} = require('@google-cloud/retail'); + + const projectNumber = process.env['GCLOUD_PROJECT']; + + // Placement is used to identify the Serving Config name. + const placement = `projects/${projectNumber}/locations/global/catalogs/default_catalog/placements/default_search`; + + // Raw search query. + const query = 'Google Youth Hero Tee Grey'; + + // A unique identifier for tracking visitors. + const visitorId = '12345'; + + // The query expansion specification that specifies the conditions under which + // query expansion will occur. + const queryExpansionSpec = { + condition: 'AUTO', // TRY OTHER QUERY EXPANSION CONDITIONS HERE + }; + + //Maximum number of products to return + const pageSize = 10; + + // Instantiates a client. + const retailClient = new SearchServiceClient(); + + const IResponseParams = { + ISearchResult: 0, + ISearchRequest: 1, + ISearchResponse: 2, + }; + + const callSearch = async () => { + console.log('Search start'); + // Construct request + const request = { + placement, + query, + visitorId, + queryExpansionSpec, + pageSize, + }; + + console.log('Search request: ', request); + + // Run request + const response = await retailClient.search(request, { + autoPaginate: false, + }); + const searchResponse = response[IResponseParams.ISearchResponse]; + console.log('Search result: ', JSON.stringify(searchResponse, null, 4)); + console.log('Search end'); + }; + + callSearch(); + // [END retail_search_for_products_with_query_expansion_specification] +} + +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); + +main(); diff --git a/retail/interactive-tutorials/test/search-simple-query.test.js b/retail/interactive-tutorials/test/search-simple-query.test.js new file mode 100644 index 0000000000..ff74a29d24 --- /dev/null +++ b/retail/interactive-tutorials/test/search-simple-query.test.js @@ -0,0 +1,92 @@ +// Copyright 2022 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +'use strict'; + +const path = require('path'); +const cp = require('child_process'); +const {before, describe, it} = require('mocha'); +const {SearchServiceClient} = require('@google-cloud/retail'); +const {assert, expect} = require('chai'); + +const execSync = cmd => cp.execSync(cmd, {encoding: 'utf-8'}); +const cwd = path.join(__dirname, '..'); + +describe('Search simple query', () => { + describe('Search simple query run', () => { + let stdout; + + before(async () => { + stdout = execSync( + 'node interactive-tutorials/search/search-simple-query.js', + {cwd} + ); + }); + + it('should show that search successfully started', () => { + assert.match(stdout, /Search start/); + }); + + it('should show that search successfully finished', () => { + assert.match(stdout, /Search end/); + }); + }); + + describe('Search simple query sample result', () => { + const retailClient = new SearchServiceClient(); + + const projectNumber = process.env['GCLOUD_PROJECT']; + const request = { + placement: `projects/${projectNumber}/locations/global/catalogs/default_catalog/placements/default_search`, + query: 'Hoodie', + visitorId: '12345', + }; + const IResponseParams = { + ISearchResult: 0, + ISearchRequest: 1, + ISearchResponse: 2, + }; + let response = []; + + before(async () => { + response = await retailClient.search(request, {autoPaginate: false}); + }); + + it('should be a valid response', () => { + expect(response).to.be.an('array'); + expect(response.length).to.equal(3); + const searchResult = response[IResponseParams.ISearchResult]; + const searchResponse = response[IResponseParams.ISearchResponse]; + if (searchResult.length) { + expect(searchResponse.totalSize).to.be.above(0); + searchResult.forEach(resultItem => { + expect(resultItem, 'It should be an object').to.be.an('object'); + expect( + resultItem, + 'The object has no valid properties' + ).to.have.all.keys( + 'matchingVariantFields', + 'variantRollupValues', + 'id', + 'product', + 'matchingVariantCount' + ); + }); + } else { + expect(searchResult).to.be.an('array').that.is.empty; + expect(searchResponse.totalSize).to.equal(0); + } + }); + }); +}); diff --git a/retail/interactive-tutorials/test/search-with-boost-spec.test.js b/retail/interactive-tutorials/test/search-with-boost-spec.test.js new file mode 100644 index 0000000000..54d07dfd4d --- /dev/null +++ b/retail/interactive-tutorials/test/search-with-boost-spec.test.js @@ -0,0 +1,96 @@ +// Copyright 2022 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +'use strict'; + +const path = require('path'); +const cp = require('child_process'); +const {before, describe, it} = require('mocha'); +const {SearchServiceClient} = require('@google-cloud/retail'); +const {assert, expect} = require('chai'); + +const execSync = cmd => cp.execSync(cmd, {encoding: 'utf-8'}); +const cwd = path.join(__dirname, '..'); + +describe('Search with boost spec', () => { + describe('Search with boost spec run sample', () => { + let stdout; + + before(async () => { + stdout = execSync( + 'node interactive-tutorials/search/search-with-boost-spec.js', + {cwd} + ); + }); + + it('should show that search successfully started', () => { + assert.match(stdout, /Search start/); + }); + + it('should show that search successfully finished', () => { + assert.match(stdout, /Search end/); + }); + }); + + describe('Search with boost spec sample result', () => { + const retailClient = new SearchServiceClient(); + + const projectNumber = process.env['GCLOUD_PROJECT']; + const request = { + placement: `projects/${projectNumber}/locations/global/catalogs/default_catalog/placements/default_search`, + query: 'Hoodie', + visitorId: '12345', + boostSpec: { + condition: '(colorFamilies: ANY("Blue"))', + boost: 0.0, + }, + }; + const IResponseParams = { + ISearchResult: 0, + ISearchRequest: 1, + ISearchResponse: 2, + }; + let response = []; + + before(async () => { + response = await retailClient.search(request, {autoPaginate: false}); + }); + + it('should be a valid response', () => { + expect(response).to.be.an('array'); + expect(response.length).to.equal(3); + const searchResult = response[IResponseParams.ISearchResult]; + const searchResponse = response[IResponseParams.ISearchResponse]; + if (searchResult.length) { + expect(searchResponse.totalSize).to.be.above(0); + searchResult.forEach(resultItem => { + expect(resultItem, 'It should be an object').to.be.an('object'); + expect( + resultItem, + 'The object has no valid properties' + ).to.have.all.keys( + 'matchingVariantFields', + 'variantRollupValues', + 'id', + 'product', + 'matchingVariantCount' + ); + }); + } else { + expect(searchResult).to.be.an('array').that.is.empty; + expect(searchResponse.totalSize).to.equal(0); + } + }); + }); +}); diff --git a/retail/interactive-tutorials/test/search-with-facet-spec.test.js b/retail/interactive-tutorials/test/search-with-facet-spec.test.js new file mode 100644 index 0000000000..6f79d3fc5b --- /dev/null +++ b/retail/interactive-tutorials/test/search-with-facet-spec.test.js @@ -0,0 +1,103 @@ +// Copyright 2022 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +'use strict'; + +const path = require('path'); +const cp = require('child_process'); +const {before, describe, it} = require('mocha'); +const {SearchServiceClient} = require('@google-cloud/retail'); +const {assert, expect} = require('chai'); + +const execSync = cmd => cp.execSync(cmd, {encoding: 'utf-8'}); +const cwd = path.join(__dirname, '..'); + +describe('Search with facet spec', () => { + describe('Search with facet spec run sample', () => { + let stdout; + + before(async () => { + stdout = execSync( + 'node interactive-tutorials/search/search-with-facet-spec.js', + {cwd} + ); + }); + + it('should show that search successfully started', () => { + assert.match(stdout, /Search start/); + }); + + it('should show that search successfully finished', () => { + assert.match(stdout, /Search end/); + }); + }); + + describe('Search with facet spec result', () => { + const retailClient = new SearchServiceClient(); + const projectNumber = process.env['GCLOUD_PROJECT']; + const request = { + placement: `projects/${projectNumber}/locations/global/catalogs/default_catalog/placements/default_search`, + query: 'Tee', + visitorId: '12345', + facetSpecs: [{facetKey: {key: 'colorFamilies'}}], + pageSize: 10, + }; + const IResponseParams = { + ISearchResult: 0, + ISearchRequest: 1, + ISearchResponse: 2, + }; + let response = []; + + before(async () => { + response = await retailClient.search(request, {autoPaginate: false}); + }); + + it('should be a valid response', () => { + expect(response).to.be.an('array'); + expect(response.length).to.equal(3); + const searchResult = response[IResponseParams.ISearchResult]; + const searchResponse = response[IResponseParams.ISearchResponse]; + if (searchResult.length) { + expect(searchResponse.totalSize).to.be.above(0); + searchResult.forEach(resultItem => { + expect(resultItem, 'It should be an object').to.be.an('object'); + expect( + resultItem, + 'The object has no valid properties' + ).to.have.all.keys( + 'matchingVariantFields', + 'variantRollupValues', + 'id', + 'product', + 'matchingVariantCount' + ); + }); + } else { + expect(searchResult).to.be.an('array').that.is.empty; + expect(searchResponse.totalSize).to.equal(0); + } + }); + + it('should contain correct facets array', () => { + const searchResponse = response[IResponseParams.ISearchResponse]; + expect(searchResponse).to.be.an('object').that.is.not.empty; + expect(searchResponse.facets).to.be.an('array').that.is.not.empty; + + const facet = searchResponse.facets[0]; + expect(facet).to.be.an('object').that.is.not.empty; + expect(facet.key).to.equal('colorFamilies'); + }); + }); +}); diff --git a/retail/interactive-tutorials/test/search-with-filtering.test.js b/retail/interactive-tutorials/test/search-with-filtering.test.js new file mode 100644 index 0000000000..0aaa1b10ae --- /dev/null +++ b/retail/interactive-tutorials/test/search-with-filtering.test.js @@ -0,0 +1,116 @@ +// Copyright 2022 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +'use strict'; + +const path = require('path'); +const cp = require('child_process'); +const {before, describe, it} = require('mocha'); +const {SearchServiceClient} = require('@google-cloud/retail'); +const {assert, expect} = require('chai'); + +const execSync = cmd => cp.execSync(cmd, {encoding: 'utf-8'}); +const cwd = path.join(__dirname, '..'); + +describe('Search with filtering', () => { + describe('Search with filtering run sample', () => { + let stdout; + + before(async () => { + stdout = execSync( + 'node interactive-tutorials/search/search-with-filtering.js', + {cwd} + ); + }); + + it('should show that search successfully started', () => { + assert.match(stdout, /Search start/); + }); + + it('should show that search successfully finished', () => { + assert.match(stdout, /Search end/); + }); + }); + + describe('Search with filtering sample result', () => { + const retailClient = new SearchServiceClient(); + const projectNumber = process.env['GCLOUD_PROJECT']; + const request = { + placement: `projects/${projectNumber}/locations/global/catalogs/default_catalog/placements/default_search`, + query: 'Tee', + visitorId: '12345', + filter: '(colorFamilies: ANY("Black"))', + }; + const IResponseParams = { + ISearchResult: 0, + ISearchRequest: 1, + ISearchResponse: 2, + }; + let response = []; + + before(async () => { + response = await retailClient.search(request, {autoPaginate: false}); + }); + + it('should be a valid response', () => { + expect(response).to.be.an('array'); + expect(response.length).to.equal(3); + const searchResult = response[IResponseParams.ISearchResult]; + const searchResponse = response[IResponseParams.ISearchResponse]; + if (searchResult.length) { + expect(searchResponse.totalSize).to.be.above(0); + searchResult.forEach(resultItem => { + expect(resultItem, 'It should be an object').to.be.an('object'); + expect( + resultItem, + 'The object has no valid properties' + ).to.have.all.keys( + 'matchingVariantFields', + 'variantRollupValues', + 'id', + 'product', + 'matchingVariantCount' + ); + }); + } else { + expect(searchResult).to.be.an('array').that.is.empty; + expect(searchResponse.totalSize).to.equal(0); + } + }); + + it('should contain filtered values', () => { + const searchResult = response[IResponseParams.ISearchResult]; + if (searchResult.length) { + searchResult.forEach(item => { + expect( + item.product, + 'Object has no "colorInfo" property' + ).to.have.property('colorInfo'); + expect( + item.product.colorInfo, + 'Object has no "colorFamilies" array' + ).to.have.property('colorFamilies'); + expect( + item.product.colorInfo.colorFamilies, + '"colorFamilies" field does not containt filter condition value' + ) + .to.be.an('array') + .that.includes('Black'); + }); + } else { + expect(searchResult).to.be.an('array').that.is.empty; + } + }); + }); +}); diff --git a/retail/interactive-tutorials/test/search-with-ordering.test.js b/retail/interactive-tutorials/test/search-with-ordering.test.js new file mode 100644 index 0000000000..4ddd6f0c0a --- /dev/null +++ b/retail/interactive-tutorials/test/search-with-ordering.test.js @@ -0,0 +1,110 @@ +// Copyright 2022 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +'use strict'; + +const path = require('path'); +const cp = require('child_process'); +const {before, describe, it} = require('mocha'); +const {SearchServiceClient} = require('@google-cloud/retail'); +const {assert, expect} = require('chai'); + +const execSync = cmd => cp.execSync(cmd, {encoding: 'utf-8'}); +const cwd = path.join(__dirname, '..'); + +describe('Search with ordering', () => { + describe('Search with ordering run sample', () => { + let stdout; + + before(async () => { + stdout = execSync( + 'node interactive-tutorials/search/search-with-ordering.js', + {cwd} + ); + }); + + it('should show that search successfully started', () => { + assert.match(stdout, /Search start/); + }); + + it('should show that search successfully finished', () => { + assert.match(stdout, /Search end/); + }); + }); + + describe('Search with ordering sample result', () => { + const retailClient = new SearchServiceClient(); + const projectNumber = process.env['GCLOUD_PROJECT']; + const request = { + placement: `projects/${projectNumber}/locations/global/catalogs/default_catalog/placements/default_search`, + query: 'Hoodie', + visitorId: '12345', + orderBy: 'price desc', + }; + const IResponseParams = { + ISearchResult: 0, + ISearchRequest: 1, + ISearchResponse: 2, + }; + let response = []; + + before(async () => { + response = await retailClient.search(request, {autoPaginate: false}); + }); + + it('should be a valid response', () => { + expect(response).to.be.an('array'); + expect(response.length).to.equal(3); + const searchResult = response[IResponseParams.ISearchResult]; + const searchResponse = response[IResponseParams.ISearchResponse]; + if (searchResult.length) { + expect(searchResponse.totalSize).to.be.above(0); + searchResult.forEach(resultItem => { + expect(resultItem, 'It should be an object').to.be.an('object'); + expect( + resultItem, + 'The object has no valid properties' + ).to.have.all.keys( + 'matchingVariantFields', + 'variantRollupValues', + 'id', + 'product', + 'matchingVariantCount' + ); + }); + } else { + expect(searchResult).to.be.an('array').that.is.empty; + expect(searchResponse.totalSize).to.equal(0); + } + }); + + it('should be ordered', () => { + const searchResult = response[IResponseParams.ISearchResult]; + if (searchResult.length) { + const prices = []; + searchResult.forEach(item => { + prices.push(item.product.priceInfo.price); + }); + const sortedArrayDesc = [...prices].sort((a, b) => b - a); + expect( + prices, + 'It should be an ordered array' + ).to.include.ordered.members(sortedArrayDesc); + } else { + expect(searchResult, 'It should be an empty array').to.be.an('array') + .that.is.empty; + } + }); + }); +}); diff --git a/retail/interactive-tutorials/test/search-with-pagination.test.js b/retail/interactive-tutorials/test/search-with-pagination.test.js new file mode 100644 index 0000000000..59c1c24277 --- /dev/null +++ b/retail/interactive-tutorials/test/search-with-pagination.test.js @@ -0,0 +1,130 @@ +// Copyright 2022 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +'use strict'; + +const path = require('path'); +const cp = require('child_process'); +const {before, describe, it} = require('mocha'); +const {SearchServiceClient} = require('@google-cloud/retail'); +const {assert, expect} = require('chai'); + +const execSync = cmd => cp.execSync(cmd, {encoding: 'utf-8'}); +const cwd = path.join(__dirname, '..'); + +describe('Search with pagination', () => { + describe('Search with pagination run sample', () => { + let stdout; + + before(async () => { + stdout = execSync( + 'node interactive-tutorials/search/search-with-pagination.js', + {cwd} + ); + }); + + it('should show that search successfully started', () => { + assert.match(stdout, /Search start/); + }); + + it('should contain next page token', () => { + assert.match(stdout, /Next page token/); + }); + + it('should show that search successfully finished', () => { + assert.match(stdout, /Search end/); + }); + }); + + describe('Search with pagination sample result', () => { + const retailClient = new SearchServiceClient(); + const projectNumber = process.env['GCLOUD_PROJECT']; + const pageSize = 2; + const offset = 0; + const pageToken = ''; + const request = { + placement: `projects/${projectNumber}/locations/global/catalogs/default_catalog/placements/default_search`, + query: 'Hoodie', + visitorId: '12345', + pageSize, + offset, + pageToken, + }; + let response; + + const IResponseParams = { + ISearchResult: 0, + ISearchRequest: 1, + ISearchResponse: 2, + }; + + before(async () => { + response = await retailClient.search(request, {autoPaginate: false}); + }); + + it('should be a valid response', () => { + expect(response).to.be.an('array'); + expect(response.length).to.equal(3); + const searchResult = response[IResponseParams.ISearchResult]; + const searchResponse = response[IResponseParams.ISearchResponse]; + if (searchResult.length) { + expect(searchResponse.totalSize).to.be.above(0); + searchResult.forEach(resultItem => { + expect(resultItem, 'It should be an object').to.be.an('object'); + expect( + resultItem, + 'The object has no valid properties' + ).to.have.all.keys( + 'matchingVariantFields', + 'variantRollupValues', + 'id', + 'product', + 'matchingVariantCount' + ); + }); + } else { + expect(searchResult).to.be.an('array').that.is.empty; + expect(searchResponse.totalSize).to.equal(0); + } + }); + + it('should contain a fixed number of products', () => { + const searchResult = response[IResponseParams.ISearchResult]; + if (searchResult.length) { + expect(searchResult.length).to.equal(pageSize); + } else { + expect(searchResult, 'It should be an empty array').to.be.an('array') + .that.is.empty; + } + }); + + it('should be a valid search response object', () => { + const searchResponse = response[IResponseParams.ISearchResponse]; + expect(searchResponse, 'It should be an object').to.be.an('object'); + expect( + searchResponse, + 'The object has no valid properties' + ).to.include.all.keys( + 'results', + 'facets', + 'totalSize', + 'correctedQuery', + 'attributionToken', + 'nextPageToken', + 'queryExpansionInfo', + 'redirectUri' + ); + }); + }); +}); diff --git a/retail/interactive-tutorials/test/search-with-query-expansion-spec.test.js b/retail/interactive-tutorials/test/search-with-query-expansion-spec.test.js new file mode 100644 index 0000000000..246cc1df65 --- /dev/null +++ b/retail/interactive-tutorials/test/search-with-query-expansion-spec.test.js @@ -0,0 +1,107 @@ +// Copyright 2022 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +'use strict'; + +const path = require('path'); +const cp = require('child_process'); +const {before, describe, it} = require('mocha'); +const {SearchServiceClient} = require('@google-cloud/retail'); +const {assert, expect} = require('chai'); + +const execSync = cmd => cp.execSync(cmd, {encoding: 'utf-8'}); +const cwd = path.join(__dirname, '..'); + +describe('Search with query expansion spec', () => { + describe('Search with query expansion spec run sample', () => { + let stdout; + + before(async () => { + stdout = execSync( + 'node interactive-tutorials/search/search-with-query-expansion-spec.js', + { + cwd, + } + ); + }); + + it('should show that search successfully started', () => { + assert.match(stdout, /Search start/); + }); + + it('should show that search successfully finished', () => { + assert.match(stdout, /Search end/); + }); + }); + + describe('Search with query expansion spec sample result', () => { + const retailClient = new SearchServiceClient(); + const projectNumber = process.env['GCLOUD_PROJECT']; + const request = { + placement: `projects/${projectNumber}/locations/global/catalogs/default_catalog/placements/default_search`, + query: 'Google Youth Hero Tee Grey', + visitorId: '12345', + queryExpansionSpec: { + condition: 'AUTO', + }, + pageSize: 10, + }; + const IResponseParams = { + ISearchResult: 0, + ISearchRequest: 1, + ISearchResponse: 2, + }; + let response = []; + + before(async () => { + response = await retailClient.search(request, {autoPaginate: false}); + }); + + it('should be a valid response', () => { + expect(response).to.be.an('array'); + expect(response.length).to.equal(3); + const searchResult = response[IResponseParams.ISearchResult]; + const searchResponse = response[IResponseParams.ISearchResponse]; + if (searchResult.length) { + expect(searchResponse.totalSize).to.be.above(0); + searchResult.forEach(resultItem => { + expect(resultItem, 'It should be an object').to.be.an('object'); + expect( + resultItem, + 'The object has no valid properties' + ).to.have.all.keys( + 'matchingVariantFields', + 'variantRollupValues', + 'id', + 'product', + 'matchingVariantCount' + ); + }); + } else { + expect(searchResult).to.be.an('array').that.is.empty; + expect(searchResponse.totalSize).to.equal(0); + } + }); + + it('should contain expanded query', () => { + const searchResponse = response[IResponseParams.ISearchResponse]; + console.log(response); + expect( + searchResponse.queryExpansionInfo, + 'Search response does not contain query expansion info' + ).to.be.an('object'); + expect(searchResponse.queryExpansionInfo.expandedQuery).to.be.true; + }); + }); +});