diff --git a/retail/snippets/package.json b/retail/snippets/package.json new file mode 100644 index 0000000000..afb0cb80b2 --- /dev/null +++ b/retail/snippets/package.json @@ -0,0 +1,22 @@ +{ + "name": "node-snippets", + "license": "Apache-2.0", + "author": "Google LLC", + "engines": { + "node": ">=18.0.0" + }, + "files": [ + "*.js" + ], + "scripts": { + "test": "c8 mocha test/*.test.js --timeout 180000" + }, + "devDependencies": { + "c8": "^10.1.3", + "chai": "^4.5.0", + "mocha": "^10.8.2" + }, + "dependencies": { + "@google-cloud/retail": "^4.3.0" + } +} diff --git a/retail/snippets/searchOffset.js b/retail/snippets/searchOffset.js new file mode 100644 index 0000000000..c27a27397e --- /dev/null +++ b/retail/snippets/searchOffset.js @@ -0,0 +1,71 @@ +// Copyright 2026 Google LLC +// +// 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'; + +// [START retail_v2_search_offset] +const {SearchServiceClient} = require('@google-cloud/retail'); + +const client = new SearchServiceClient(); + +/** + * Search for products with an offset using Vertex AI Search for commerce. + * Performs a search request starting from a specified position. + * + * @param {string} projectId The Google Cloud project ID. + * @param {string} placementId The placement name for the search. + * @param {string} visitorId A unique identifier for the user. + * @param {string} query The search term. + * @param {number} offset The number of results to skip. + */ +async function searchOffset(projectId, placementId, visitorId, query, offset) { + const placementPath = client.servingConfigPath( + projectId, + 'global', + 'default_catalog', + placementId + ); + + const branchPath = client.branchPath( + projectId, + 'global', + 'default_catalog', + 'default_branch' + ); + + const request = { + placement: placementPath, + branch: branchPath, + visitorId: visitorId, + query: query, + pageSize: 10, + offset: offset, + }; + + try { + // Set {autoPaginate: false} to manually control the pagination + const [results] = await client.search(request, {autoPaginate: false}); + console.log(`--- Results for offset: ${offset} ---`); + for (const result of results) { + console.log(`Product ID: ${result.id}`); + console.log(`Title: ${result.product.title}`); + console.log(`Scores: ${JSON.stringify(result.modelScores || {})}`); + } + } catch (error) { + console.error('Error searching using offset:', error.message || error); + } +} + +// [END retail_v2_search_offset] +module.exports = {searchOffset}; diff --git a/retail/snippets/searchPagination.js b/retail/snippets/searchPagination.js new file mode 100644 index 0000000000..88bd79c4b2 --- /dev/null +++ b/retail/snippets/searchPagination.js @@ -0,0 +1,101 @@ +// Copyright 2026 Google LLC +// +// 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'; + +// [START retail_v2_search_pagination] +const {SearchServiceClient} = require('@google-cloud/retail'); + +const client = new SearchServiceClient(); + +/** + * Search for products with pagination using Vertex AI Search for commerce. + * Performs a search request, then uses the next_page_token to get the next page. + * + * @param {string} projectId - The Google Cloud project ID. + * @param {string} placementId - The placement name for the search. + * @param {string} visitorId - A unique identifier for the user. + * @param {string} query - The search term. + */ +async function searchPagination(projectId, placementId, visitorId, query) { + const placementPath = client.servingConfigPath( + projectId, + 'global', + 'default_catalog', + placementId + ); + + const branchPath = client.branchPath( + projectId, + 'global', + 'default_catalog', + 'default_branch' + ); + + // First page request + const firstRequest = { + placement: placementPath, + branch: branchPath, + visitorId: visitorId, + query: query, + pageSize: 1, + }; + + try { + // Set {autoPaginate: false} to manually control the pagination + // and extract the raw response which contains the next_page_token. + const [firstPageResults, , firstRawResponse] = await client.search( + firstRequest, + {autoPaginate: false} + ); + + console.log('--- First Page ---'); + for (const result of firstPageResults) { + console.log(`Product ID: ${result.id}`); + } + + const nextPageToken = firstRawResponse.nextPageToken; + + if (nextPageToken) { + // Second page request using pageToken + const secondRequest = { + placement: placementPath, + branch: branchPath, + visitorId: visitorId, + query: query, + pageSize: 1, + pageToken: nextPageToken, + }; + + const [secondPageResults] = await client.search(secondRequest, { + autoPaginate: false, + }); + + console.log('--- Second Page ---'); + for (const result of secondPageResults) { + console.log(`Product ID: ${result.id}`); + } + } else { + console.log('No more pages.'); + } + } catch (error) { + console.error( + 'Failed to complete paginated search:', + error.message || error + ); + } +} +// [END retail_v2_search_pagination] + +module.exports = {searchPagination}; diff --git a/retail/snippets/searchRequest.js b/retail/snippets/searchRequest.js new file mode 100644 index 0000000000..5858c523b1 --- /dev/null +++ b/retail/snippets/searchRequest.js @@ -0,0 +1,80 @@ +// Copyright 2026 Google LLC +// +// 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'; + +// [START retail_v2_search_request] +const {SearchServiceClient} = require('@google-cloud/retail'); + +const client = new SearchServiceClient(); + +/** + * Search for products using Vertex AI Search for commerce. + * + * Performs a search request for a specific placement. + * Handles both text search (using query) and browse search (using pageCategories). + * + * @param {string} projectId - The Google Cloud project ID. + * @param {string} placementId - The placement name for the search. + * @param {string} visitorId - A unique identifier for the user. + * @param {string} query - The search term for text search. + * @param {string[]} pageCategories - The categories for browse search. + */ +async function searchRequest( + projectId, + placementId, + visitorId, + query = '', + pageCategories = [] +) { + const placementPath = client.servingConfigPath( + projectId, + 'global', + 'default_catalog', + placementId + ); + + const branchPath = client.branchPath( + projectId, + 'global', + 'default_catalog', + 'default_branch' + ); + + const request = { + placement: placementPath, + branch: branchPath, + visitorId: visitorId, + query: query, + pageCategories: pageCategories, + pageSize: 10, + }; + + try { + // Set {autoPaginate: false} to manually control the pagination + const [results] = await client.search(request, {autoPaginate: false}); + console.log('--- Search Results ---'); + for (const result of results) { + console.log(`Product ID: ${result.id}`); + console.log(`Title: ${result.product.title}`); + console.log(`Scores: ${JSON.stringify(result.modelScores || {})}`); + } + } catch (error) { + console.error('Error executing search request:', error.message || error); + } +} + +// [END retail_v2_search_request] + +module.exports = {searchRequest}; diff --git a/retail/snippets/test/snippets.test.js b/retail/snippets/test/snippets.test.js new file mode 100644 index 0000000000..20d204ed2c --- /dev/null +++ b/retail/snippets/test/snippets.test.js @@ -0,0 +1,141 @@ +// Copyright 2026 Google LLC +// +// 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 {assert} = require('chai'); +const {ProductServiceClient} = require('@google-cloud/retail'); +const {searchOffset} = require('../searchOffset'); +const {searchPagination} = require('../searchPagination'); +const {searchRequest} = require('../searchRequest'); +const {setTimeout} = require('timers/promises'); + +describe('Snippets System Tests', function () { + this.timeout(180000); + + const productClient = new ProductServiceClient(); + + const projectId = process.env.GOOGLE_CLOUD_PROJECT; + const placementId = 'default_search'; + const visitorId = 'test-visitor'; + const query = 'Mocha'; + + // Generate a unique ID for the test product + const productId = `test_product_${Date.now()}`; + + let originalConsoleLog; + let consoleOutput = []; + + before(async () => { + assert.isOk( + projectId, + 'GOOGLE_CLOUD_PROJECT environment variable is required to run tests.' + ); + + const parent = productClient.branchPath( + projectId, + 'global', + 'default_catalog', + 'default_branch' + ); + + const product = { + title: 'Mocha Test Product Offset', + type: 'PRIMARY', + categories: ['Test Category'], + availability: 'IN_STOCK', + priceInfo: { + price: 15.0, + currencyCode: 'USD', + }, + }; + + console.log(`Creating test product: ${productId}...`); + await productClient.createProduct({ + parent: parent, + product: product, + productId: productId, + }); + console.log('Waiting 30 seconds for search indexes to be created...'); + await setTimeout(30000); + }); + + after(async () => { + const name = productClient.productPath( + projectId, + 'global', + 'default_catalog', + 'default_branch', + productId + ); + + console.log(`Cleaning up: Deleting test product ${productId}...`); + try { + await productClient.deleteProduct({name: name}); + console.log('Product deleted successfully.'); + } catch (error) { + console.error( + `Error deleting product (manual cleanup may be required): ${error.message}` + ); + } + }); + + // Intercept the console before each test to capture the sample's logs + beforeEach(() => { + consoleOutput = []; + originalConsoleLog = console.log; + console.log = (...args) => { + consoleOutput.push(args.join(' ')); + }; + }); + + // Restore the console after each test + afterEach(() => { + console.log = originalConsoleLog; + }); + + it('should execute searchOffset and find the created product', async () => { + const offset = 0; // Using offset 0 for the real test to ensure we find the first result + await searchOffset(projectId, placementId, visitorId, query, offset); + + const output = consoleOutput.join('\n'); + assert.include(output, `--- Results for offset: ${offset} ---`); + assert.include(output, `Product ID: ${productId}`); + }); + + it('should execute searchPagination and handle pages', async () => { + await searchPagination(projectId, placementId, visitorId, ''); + + const output = consoleOutput.join('\n'); + + assert.include(output, '--- First Page ---'); + assert.include(output, '--- Second Page ---'); + }); + + it('should execute searchRequest successfully', async () => { + const pageCategories = ['Test Category']; + + await searchRequest( + projectId, + placementId, + visitorId, + query, + pageCategories + ); + + const output = consoleOutput.join('\n'); + assert.include(output, '--- Search Results ---'); + assert.include(output, `Product ID: ${productId}`); + }); +});