diff --git a/retail/interactive-tutorials/README.md b/retail/interactive-tutorials/README.md new file mode 100644 index 00000000000..797fd27d352 --- /dev/null +++ b/retail/interactive-tutorials/README.md @@ -0,0 +1,169 @@ +#Retail Search Interactive Tutorials + +##Run tutorials in Cloud Shell + +To advance with the interactive tutorials, use Retail Search step-by-step manuals on the right side of the Cloud Shell IDE: +![Interactive tutorials](images/tutorial1.png) + +The interactive tutorial should open by default. If it didn’t, click on the Tutorial symbol in the menu bar to open the step-by-step manual: +![Toggle tutorial](images/tutorials2.png) + +For more details about the Cloud Shell environment, refer to the [Cloud Shell documentation](https://cloud.google.com/shell/docs). + +## Interactive tutorial flow + +Interactive guides are intended to help you understand the features provided by Google Cloud Retail Search and test the Retail API in action. + +To proceed with the tutorial, choose a language you’ll be deploying your project in: +![Select a programming language](images/tutorials3.png) + + +To begin with the tutorial workflow, click the Start button: +![Begin with the tutorial](images/tutorials4.png) + +Then, you can use Next and Previous buttons to navigate the tutorial pages. + +## Java code samples + +The code here demonstrates how to consume Google Retail Search API in Java + +## Get started with the Google Cloud Retail API + +The Retail API provides you with the following possibilities to: + - Create and maintaining the catalog data. + - Fine-tune the search configuration. + - Import and maintain the user events data. + +You can find the information about the Retail services in the [documentation](https://cloud.google.com/retail/docs) + + +If you would like to have a closer look at the Retail API features and try them yourself, +the best option is to use the [Interactive Tutorials](https://cloud.google.com/retail/docs/overview). The tutorials will be launched in the CloudShell environment, and you will be able to request the Retail services and check the response with minimum time and effort. + +The code samples in the directory **python-retail/samples/interactive-tutorials** are explicitly created for use with the Retail Interactive Tutorials. + +If, for some reason, you have decided to proceed with these code samples without the tutorial, please go through the following steps and set up the required preconditions. + +### 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. To check that the Retail API is enabled for your Project, go to the [Admin Console](https://console.cloud.google.com/ai/retail/). + +### Create service account + +To access the Retail API, you must create a service account. + +1. To create a service account, follow this [instruction](https://cloud.google.com/retail/docs/setting-up#service-account) + +1. Find your service account on the [IAM page](https://console.cloud.google.com/iam-admin/iam), + click `Edit` icon, add the 'Storage Admin' and 'BigQuery Admin' roles. It may take some time for changes to apply. + +1. Copy the service account email in the Principal field. + +### Set up authentication + +To run a code sample from the Cloud Shell, you need to be authenticated using the service account credentials. + +1. Login with your user credentials. + ```bash + gcloud auth login + ``` + +1. Type `Y` and press **Enter**. Click the link in a Terminal. A browser window should appear asking you to log in using your Gmail account. + +1. Provide the Google Auth Library with access to your credentials and paste the code from the browser to the Terminal. + +1. Upload your service account key JSON file and use it to activate the service account: + + ```bash + gcloud iam service-accounts keys create ~/key.json --iam-account + ``` + + ```bash + gcloud auth activate-service-account --key-file ~/key.json + ``` + +1. To request the Retail API, set your service account key JSON file as the GOOGLE_APPLICATION_CREDENTIALS environment variable : + ```bash + export GOOGLE_APPLICATION_CREDENTIALS=~/key.json + ``` + +### Set the GOOGLE_CLOUD_PROJECT environment variable + +You will run the code samples in your own Google Cloud project. To use the **project_id** in every request to the Retail API, you should first specify them as environment variables. + +1. Find the project ID in the Project Info card displayed on **Home/Dashboard**. + +1. Set the **project_id** with the following command: + ```bash + export GOOGLE_CLOUD_PROJECT= + +## Import Catalog Data + +This step is required if this is the first Retail API Tutorial you run. +Otherwise, you can skip it. + +### Upload catalog data to Cloud Storage + +There is a JSON file with valid products prepared in the `product` directory: +`product/resources/products.json`. + +Another file, `product/resources/products_some_invalid.json`, contains both valid and invalid products, and you will use it to check the error handling. + +In your own project, create a Cloud Storage bucket and put the JSON file there. +The bucket name must be unique. For convenience, you can name it `_`. + +1. To create the bucket and upload the JSON file, run the following command in the Terminal: + + ```bash + pmvn compile exec:java -Dexec.mainClass=CreateGcsBucket + ``` + + Now you can see the bucket is created in the [Cloud Storage](https://console.cloud.google.com/storage/browser), and the files are uploaded. + +1. The name of the created Retail Search bucket is printed in the Terminal. Copy the name and set it as the environment variable `BUCKET_NAME`: + + ```bash + export BUCKET_NAME= + ``` + +### Import products to the Retail Catalog + +To import the prepared products to a catalog, run the following command in the Terminal: + + ```bash + pmvn compile exec:java -Dexec.mainClass=ImportProductsGcs + ``` + +### Running code samples + +Use maven command to run specific code sample: + +``` +mvn compile exec:java -Dexec.mainClass="package.CodeSampleClass" +``` + +### Running unit tests + +Use maven command to run specific unit test class: + +``` +mvn test -Dtest=TestClassName +``` + +Use maven command to run all unit tests: + +``` +mvn test +``` diff --git a/retail/interactive-tutorials/images/tutorail1.img b/retail/interactive-tutorials/images/tutorail1.img new file mode 100644 index 00000000000..e69de29bb2d diff --git a/retail/interactive-tutorials/images/tutorial1.png b/retail/interactive-tutorials/images/tutorial1.png new file mode 100644 index 00000000000..edeea8376c2 Binary files /dev/null and b/retail/interactive-tutorials/images/tutorial1.png differ diff --git a/retail/interactive-tutorials/images/tutorials2.png b/retail/interactive-tutorials/images/tutorials2.png new file mode 100644 index 00000000000..3321a0de373 Binary files /dev/null and b/retail/interactive-tutorials/images/tutorials2.png differ diff --git a/retail/interactive-tutorials/images/tutorials3.png b/retail/interactive-tutorials/images/tutorials3.png new file mode 100644 index 00000000000..ae7518f01e8 Binary files /dev/null and b/retail/interactive-tutorials/images/tutorials3.png differ diff --git a/retail/interactive-tutorials/images/tutorials4.png b/retail/interactive-tutorials/images/tutorials4.png new file mode 100644 index 00000000000..f649a9b3a38 Binary files /dev/null and b/retail/interactive-tutorials/images/tutorials4.png differ diff --git a/retail/interactive-tutorials/pom.xml b/retail/interactive-tutorials/pom.xml index 31fc75430b3..7e1e061a843 100644 --- a/retail/interactive-tutorials/pom.xml +++ b/retail/interactive-tutorials/pom.xml @@ -66,4 +66,4 @@ - \ No newline at end of file + diff --git a/retail/interactive-tutorials/src/main/java/search/SearchSimpleQuery.java b/retail/interactive-tutorials/src/main/java/search/SearchSimpleQuery.java new file mode 100644 index 00000000000..4f4b9cb2015 --- /dev/null +++ b/retail/interactive-tutorials/src/main/java/search/SearchSimpleQuery.java @@ -0,0 +1,69 @@ +/* + * Copyright 2022 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. + */ + +// [START retail_search_for_products_with_query_parameter] + +/* + * Call Retail API to search for a products in a catalog + * using only search query. + */ + +package search; + +import com.google.cloud.retail.v2.SearchRequest; +import com.google.cloud.retail.v2.SearchResponse; +import com.google.cloud.retail.v2.SearchServiceClient; +import java.io.IOException; +import java.util.UUID; + +public class SearchSimpleQuery { + + public static void main(String[] args) throws IOException { + // TODO(developer): Replace these variables before running the sample. + String projectId = System.getenv("PROJECT_ID"); + String defaultCatalogName = + String.format("projects/%s/locations/global/catalogs/default_catalog", projectId); + String defaultSearchPlacementName = defaultCatalogName + "/placements/default_search"; + + getSearchResponse(defaultSearchPlacementName); + } + + public static SearchResponse getSearchResponse(String defaultSearchPlacementName) + throws IOException { + // TRY DIFFERENT QUERY PHRASES HERE: + String queryPhrase = "Hoodie"; + String visitorId = UUID.randomUUID().toString(); + int pageSize = 10; + + SearchRequest searchRequest = + SearchRequest.newBuilder() + .setPlacement(defaultSearchPlacementName) + .setQuery(queryPhrase) + .setVisitorId(visitorId) + .setPageSize(pageSize) + .build(); + System.out.println("Search request: " + searchRequest); + + try (SearchServiceClient client = SearchServiceClient.create()) { + SearchResponse searchResponse = client.search(searchRequest).getPage().getResponse(); + System.out.println("Search response: " + searchResponse); + + return searchResponse; + } + } +} + +// [END retail_search_for_products_with_query_parameter] diff --git a/retail/interactive-tutorials/src/main/java/search/SearchWithBoostSpec.java b/retail/interactive-tutorials/src/main/java/search/SearchWithBoostSpec.java new file mode 100644 index 00000000000..97f37577cb3 --- /dev/null +++ b/retail/interactive-tutorials/src/main/java/search/SearchWithBoostSpec.java @@ -0,0 +1,80 @@ +/* + * Copyright 2022 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. + */ + +// [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. + */ + +package search; + +import com.google.cloud.retail.v2.SearchRequest; +import com.google.cloud.retail.v2.SearchRequest.BoostSpec; +import com.google.cloud.retail.v2.SearchRequest.BoostSpec.ConditionBoostSpec; +import com.google.cloud.retail.v2.SearchResponse; +import com.google.cloud.retail.v2.SearchServiceClient; +import java.io.IOException; +import java.util.UUID; + +public class SearchWithBoostSpec { + + public static void main(String[] args) throws IOException { + // TODO(developer): Replace these variables before running the sample. + String projectId = System.getenv("PROJECT_ID"); + String defaultCatalogName = + String.format("projects/%s/locations/global/catalogs/default_catalog", projectId); + String defaultSearchPlacementName = defaultCatalogName + "/placements/default_search"; + + getSearchResponse(defaultSearchPlacementName); + } + + public static SearchResponse getSearchResponse(String defaultSearchPlacementName) + throws IOException { + // TRY DIFFERENT CONDITIONS HERE: + String searchQuery = "Tee"; + String condition = "(colorFamilies: ANY(\"Blue\"))"; + float boost = 0.0f; + int pageSize = 10; + String visitorId = UUID.randomUUID().toString(); + + BoostSpec boostSpec = + BoostSpec.newBuilder() + .addConditionBoostSpecs( + ConditionBoostSpec.newBuilder().setCondition(condition).setBoost(boost).build()) + .build(); + + SearchRequest searchRequest = + SearchRequest.newBuilder() + .setPlacement(defaultSearchPlacementName) + .setQuery(searchQuery) + .setVisitorId(visitorId) + .setBoostSpec(boostSpec) + .setPageSize(pageSize) + .build(); + System.out.println("Search request: " + searchRequest); + + try (SearchServiceClient client = SearchServiceClient.create()) { + SearchResponse searchResponse = client.search(searchRequest).getPage().getResponse(); + System.out.println("Search response: " + searchResponse); + + return searchResponse; + } + } +} + +// [END retail_search_product_with_boost_spec] diff --git a/retail/interactive-tutorials/src/main/java/search/SearchWithFacetSpec.java b/retail/interactive-tutorials/src/main/java/search/SearchWithFacetSpec.java new file mode 100644 index 00000000000..f1851289a12 --- /dev/null +++ b/retail/interactive-tutorials/src/main/java/search/SearchWithFacetSpec.java @@ -0,0 +1,71 @@ +/* + * Copyright 2022 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. + */ + +// [START retail_search_product_with_facet_spec] + +package search; + +import com.google.cloud.retail.v2.SearchRequest; +import com.google.cloud.retail.v2.SearchRequest.FacetSpec; +import com.google.cloud.retail.v2.SearchRequest.FacetSpec.FacetKey; +import com.google.cloud.retail.v2.SearchResponse; +import com.google.cloud.retail.v2.SearchServiceClient; +import java.io.IOException; +import java.util.UUID; + +public class SearchWithFacetSpec { + + public static void main(String[] args) throws IOException { + // TODO(developer): Replace these variables before running the sample. + String projectId = System.getenv("PROJECT_ID"); + String defaultCatalogName = + String.format("projects/%s/locations/global/catalogs/default_catalog", projectId); + String defaultSearchPlacementName = defaultCatalogName + "/placements/default_search"; + + getSearchResponse(defaultSearchPlacementName); + } + + public static SearchResponse getSearchResponse(String defaultSearchPlacementName) + throws IOException { + // TRY DIFFERENT CONDITIONS HERE: + String searchQuery = "Tee"; + String facetKeyParam = "colorFamilies"; + int pageSize = 10; + String visitorId = UUID.randomUUID().toString(); + + FacetKey facetKey = FacetKey.newBuilder().setKey(facetKeyParam).build(); + FacetSpec facetSpec = FacetSpec.newBuilder().setFacetKey(facetKey).build(); + + SearchRequest searchRequest = + SearchRequest.newBuilder() + .setPlacement(defaultSearchPlacementName) + .setQuery(searchQuery) + .setVisitorId(visitorId) + .addFacetSpecs(facetSpec) + .setPageSize(pageSize) + .build(); + System.out.println("Search request: " + searchRequest); + + try (SearchServiceClient client = SearchServiceClient.create()) { + SearchResponse searchResponse = client.search(searchRequest).getPage().getResponse(); + System.out.println("Search response: " + searchResponse); + + return searchResponse; + } + } +} + +// [END retail_search_product_with_facet_spec] diff --git a/retail/interactive-tutorials/src/test/java/search/SearchSimpleQueryTest.java b/retail/interactive-tutorials/src/test/java/search/SearchSimpleQueryTest.java new file mode 100644 index 00000000000..517ddb82542 --- /dev/null +++ b/retail/interactive-tutorials/src/test/java/search/SearchSimpleQueryTest.java @@ -0,0 +1,62 @@ +/* + * Copyright 2022 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. + */ + +package search; + +import com.google.cloud.retail.v2.SearchResponse; +import java.io.IOException; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import util.StreamGobbler; + +public class SearchSimpleQueryTest { + private String output; + private String defaultSearchPlacementName; + + @Before + public void setUp() throws IOException, InterruptedException, ExecutionException { + String projectNumber = System.getenv("PROJECT_NUMBER"); + String defaultCatalogName = + String.format("projects/%s/locations/global/catalogs/default_catalog", projectNumber); + defaultSearchPlacementName = defaultCatalogName + "/placements/default_search"; + Process exec = + Runtime.getRuntime() + .exec("mvn compile exec:java -Dexec.mainClass=search.SearchSimpleQuery"); + StreamGobbler streamGobbler = new StreamGobbler(exec.getInputStream()); + Future stringFuture = Executors.newSingleThreadExecutor().submit(streamGobbler); + + output = stringFuture.get(); + } + + @Test + public void testOutput() { + Assert.assertTrue(output.matches("(?s)^(.*Search request.*)$")); + Assert.assertTrue(output.matches("(?s)^(.*Search response.*)$")); + Assert.assertTrue(output.matches("(?s)^(.*results.*id.*)$")); + } + + @Test + public void testSearchSimpleQuery() throws IOException { + SearchResponse response = SearchSimpleQuery.getSearchResponse(defaultSearchPlacementName); + Assert.assertEquals(10, response.getResultsCount()); + String productTitle = response.getResults(0).getProduct().getTitle(); + Assert.assertTrue(productTitle.contains("Hoodie")); + } +} diff --git a/retail/interactive-tutorials/src/test/java/search/SearchWithBoostSpecTest.java b/retail/interactive-tutorials/src/test/java/search/SearchWithBoostSpecTest.java new file mode 100644 index 00000000000..85f06cb2165 --- /dev/null +++ b/retail/interactive-tutorials/src/test/java/search/SearchWithBoostSpecTest.java @@ -0,0 +1,62 @@ +/* + * Copyright 2022 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. + */ + +package search; + +import com.google.cloud.retail.v2.SearchResponse; +import java.io.IOException; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import util.StreamGobbler; + +public class SearchWithBoostSpecTest { + private String output; + private String defaultSearchPlacementName; + + @Before + public void setUp() throws IOException, InterruptedException, ExecutionException { + String projectNumber = System.getenv("PROJECT_NUMBER"); + String defaultCatalogName = + String.format("projects/%s/locations/global/catalogs/default_catalog", projectNumber); + defaultSearchPlacementName = defaultCatalogName + "/placements/default_search"; + Process exec = + Runtime.getRuntime() + .exec("mvn compile exec:java -Dexec.mainClass=search.SearchWithBoostSpec"); + StreamGobbler streamGobbler = new StreamGobbler(exec.getInputStream()); + Future stringFuture = Executors.newSingleThreadExecutor().submit(streamGobbler); + + output = stringFuture.get(); + } + + @Test + public void testOutput() { + Assert.assertTrue(output.matches("(?s)^(.*Search request.*)$")); + Assert.assertTrue(output.matches("(?s)^(.*Search response.*)$")); + Assert.assertTrue(output.matches("(?s)^(.*results.*id.*)$")); + } + + @Test + public void testSearchWithBoostSpec() throws IOException { + SearchResponse response = SearchWithBoostSpec.getSearchResponse(defaultSearchPlacementName); + Assert.assertEquals(10, response.getResultsCount()); + String productTitle = response.getResults(0).getProduct().getTitle(); + Assert.assertTrue(productTitle.contains("Tee")); + } +} diff --git a/retail/interactive-tutorials/src/test/java/search/SearchWithFacetSpecTest.java b/retail/interactive-tutorials/src/test/java/search/SearchWithFacetSpecTest.java new file mode 100644 index 00000000000..243acf7f22e --- /dev/null +++ b/retail/interactive-tutorials/src/test/java/search/SearchWithFacetSpecTest.java @@ -0,0 +1,64 @@ +/* + * Copyright 2022 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. + */ + +package search; + +import com.google.cloud.retail.v2.SearchResponse; +import java.io.IOException; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import util.StreamGobbler; + +public class SearchWithFacetSpecTest { + private String output; + private String defaultSearchPlacementName; + + @Before + public void setUp() throws IOException, InterruptedException, ExecutionException { + String projectNumber = System.getenv("PROJECT_NUMBER"); + String defaultCatalogName = + String.format("projects/%s/locations/global/catalogs/default_catalog", projectNumber); + defaultSearchPlacementName = defaultCatalogName + "/placements/default_search"; + Process exec = + Runtime.getRuntime() + .exec("mvn compile exec:java -Dexec.mainClass=search.SearchWithFacetSpec"); + StreamGobbler streamGobbler = new StreamGobbler(exec.getInputStream()); + Future stringFuture = Executors.newSingleThreadExecutor().submit(streamGobbler); + + output = stringFuture.get(); + } + + @Test + public void testOutput() { + Assert.assertTrue(output.matches("(?s)^(.*Search request.*)$")); + Assert.assertTrue(output.matches("(?s)^(.*Search response.*)$")); + Assert.assertTrue(output.matches("(?s)^(.*results.*id.*)$")); + Assert.assertTrue(output.matches("(?s)^(.*facets.*?colorFamilies.*)$")); + } + + @Test + public void testSearchWithFacetSpec() throws IOException { + SearchResponse response = SearchWithFacetSpec.getSearchResponse(defaultSearchPlacementName); + Assert.assertEquals(10, response.getResultsCount()); + String productTitle = response.getResults(0).getProduct().getTitle(); + Assert.assertTrue(productTitle.contains("Tee")); + Assert.assertEquals("colorFamilies", response.getFacets(0).getKey()); + } +} diff --git a/retail/interactive-tutorials/src/test/java/util/StreamGobbler.java b/retail/interactive-tutorials/src/test/java/util/StreamGobbler.java index a9c3c3a795b..d2cf05334a6 100644 --- a/retail/interactive-tutorials/src/test/java/util/StreamGobbler.java +++ b/retail/interactive-tutorials/src/test/java/util/StreamGobbler.java @@ -24,7 +24,6 @@ import java.util.stream.Collectors; public class StreamGobbler implements Callable { - private final InputStream inputStream; public StreamGobbler(InputStream inputStream) {