Skip to content

Commit acc1817

Browse files
PierrickVouletpierrick
andauthored
feat: add selection input code sample (#264)
* feat: add selection input code sample * Update copyright notice from Google Inc. to LLC --------- Co-authored-by: pierrick <[email protected]>
1 parent c62b765 commit acc1817

File tree

19 files changed

+1515
-0
lines changed

19 files changed

+1515
-0
lines changed
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Google Chat selection input app as Google Workspace add-on
2+
3+
Please see related guide about [selection input](https://developers.google.com/workspace/add-ons/chat/collect-information#suggest-multiselect).
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
/**
2+
* Copyright 2025 Google Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
// [START selection_input]
18+
/**
19+
* Responds to a Message trigger in Google Chat.
20+
*
21+
* @param {Object} event the event object from Google Chat
22+
* @return {Object} Response from the Chat app.
23+
*/
24+
function onMessage(event) {
25+
// Replies with a card that contains the multiselect menu.
26+
return { hostAppDataAction: { chatDataAction: { createMessageAction: { message: {
27+
cardsV2: [{
28+
cardId: "contactSelector",
29+
card: { sections:[{ widgets: [{
30+
selectionInput: {
31+
name: "contacts",
32+
type: "MULTI_SELECT",
33+
label: "Selected contacts",
34+
multiSelectMaxSelectedItems: 3,
35+
multiSelectMinQueryLength: 1,
36+
externalDataSource: { function: "queryContacts" },
37+
// Suggested items loaded by default.
38+
// The list is static here but it could be dynamic.
39+
items: [getSuggestedContact("3")]
40+
}
41+
}]}]}
42+
}]
43+
}}}}};
44+
}
45+
46+
/**
47+
* Get contact suggestions based on text typed by users.
48+
*
49+
* @param {Object} event the event object that contains the user's query
50+
* @return {Object} suggestions
51+
*/
52+
function queryContacts(event) {
53+
const query = event.commonEventObject.parameters["autocomplete_widget_query"];
54+
return { action: { modifyOperations: [{ updateWidget: { selectionInputWidgetSuggestions: { suggestions: [
55+
// The list is static here but it could be dynamic.
56+
getSuggestedContact("1"), getSuggestedContact("2"), getSuggestedContact("3"), getSuggestedContact("4"), getSuggestedContact("5")
57+
// Only return items based on the query from the user.
58+
].filter(e => !query || e.text.includes(query)) }}}]}};
59+
}
60+
61+
/**
62+
* Generate a suggested contact given an ID.
63+
*
64+
* @param {String} id The ID of the contact to return.
65+
* @return {Object} The contact formatted as a selection item in the menu.
66+
*/
67+
function getSuggestedContact(id) {
68+
return {
69+
value: id,
70+
startIconUri: "https://www.gstatic.com/images/branding/product/2x/contacts_48dp.png",
71+
text: "Contact " + id
72+
};
73+
}
74+
// [END selection_input]
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# This file specifies files that are *not* uploaded to Google Cloud
2+
# using gcloud. It follows the same syntax as .gitignore, with the addition of
3+
# "#!include" directives (which insert the entries of the given .gitignore-style
4+
# file at that point).
5+
#
6+
# For more information, run:
7+
# $ gcloud topic gcloudignore
8+
#
9+
.gcloudignore
10+
# If you would like to upload your .git directory, .gitignore file or files
11+
# from your .gitignore file, remove the corresponding line
12+
# below:
13+
.git
14+
.gitignore
15+
16+
# Target directory for maven builds
17+
target/
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Google Chat selection input app as Google Workspace add-on
2+
3+
Please see related guide about [selection input](https://developers.google.com/workspace/add-ons/chat/collect-information#suggest-multiselect).

java/chat/selection-input/pom.xml

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!--
3+
Copyright 2025 Google LLC
4+
5+
Licensed under the Apache License, Version 2.0 (the "License");
6+
you may not use this file except in compliance with the License.
7+
You may obtain a copy of the License at
8+
9+
http://www.apache.org/licenses/LICENSE-2.0
10+
11+
Unless required by applicable law or agreed to in writing, software
12+
distributed under the License is distributed on an "AS IS" BASIS,
13+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
See the License for the specific language governing permissions and
15+
limitations under the License.
16+
-->
17+
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
18+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
19+
<modelVersion>4.0.0</modelVersion>
20+
21+
<name>selection-input</name>
22+
23+
<groupId>com.google.chat</groupId>
24+
<artifactId>selection-input</artifactId>
25+
<version>1.0-SNAPSHOT</version>
26+
27+
<properties>
28+
<maven.compiler.target>17</maven.compiler.target>
29+
<maven.compiler.source>17</maven.compiler.source>
30+
</properties>
31+
32+
<dependencyManagement>
33+
<dependencies>
34+
<dependency>
35+
<!-- Import dependency management from Spring Boot -->
36+
<groupId>org.springframework.boot</groupId>
37+
<artifactId>spring-boot-dependencies</artifactId>
38+
<version>3.2.3</version>
39+
<type>pom</type>
40+
<scope>import</scope>
41+
</dependency>
42+
43+
<dependency>
44+
<groupId>org.springframework.cloud</groupId>
45+
<artifactId>spring-cloud-dependencies</artifactId>
46+
<version>Greenwich.SR1</version>
47+
<type>pom</type>
48+
<scope>import</scope>
49+
</dependency>
50+
51+
<dependency>
52+
<groupId>com.fasterxml.jackson</groupId>
53+
<artifactId>jackson-bom</artifactId>
54+
<version>2.17.2</version> <type>pom</type>
55+
<scope>import</scope>
56+
</dependency>
57+
</dependencies>
58+
</dependencyManagement>
59+
60+
<dependencies>
61+
<dependency>
62+
<groupId>org.springframework.boot</groupId>
63+
<artifactId>spring-boot-starter-web</artifactId>
64+
<version>3.2.3</version>
65+
<exclusions>
66+
<!-- Exclude the Tomcat dependency -->
67+
<exclusion>
68+
<groupId>org.springframework.boot</groupId>
69+
<artifactId>spring-boot-starter-tomcat</artifactId>
70+
</exclusion>
71+
</exclusions>
72+
</dependency>
73+
74+
<dependency>
75+
<groupId>org.springframework.boot</groupId>
76+
<artifactId>spring-boot-starter-jetty</artifactId>
77+
<version>3.2.3</version>
78+
</dependency>
79+
80+
<dependency>
81+
<groupId>com.google.apis</groupId>
82+
<artifactId>google-api-services-chat</artifactId>
83+
<version>v1-rev20241008-2.0.0</version>
84+
</dependency>
85+
86+
<dependency>
87+
<groupId>com.fasterxml.jackson.core</groupId>
88+
<artifactId>jackson-databind</artifactId>
89+
</dependency>
90+
</dependencies>
91+
92+
<build>
93+
<plugins>
94+
<plugin>
95+
<groupId>org.springframework.boot</groupId>
96+
<artifactId>spring-boot-maven-plugin</artifactId>
97+
<version>3.2.3</version>
98+
<executions>
99+
<execution>
100+
<goals>
101+
<goal>repackage</goal>
102+
</goals>
103+
</execution>
104+
</executions>
105+
</plugin>
106+
107+
<plugin>
108+
<groupId>com.google.cloud.tools</groupId>
109+
<artifactId>appengine-maven-plugin</artifactId>
110+
<version>2.7.0</version>
111+
<configuration>
112+
<version>GCLOUD_CONFIG</version>
113+
</configuration>
114+
</plugin>
115+
</plugins>
116+
</build>
117+
</project>
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# Copyright 2025 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
runtime: java17
16+
instance_class: F1
17+
18+
# Explicitly set the memory limit and maximum heap size for the Spring Boot app
19+
env_variables:
20+
JAVA_TOOL_OPTIONS: "-XX:MaxRAM=256m -XX:ActiveProcessorCount=2 -Xmx32m"
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
/**
2+
* Copyright 2025 Google LLC
3+
*
4+
* <p>Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
5+
* except in compliance with the License. You may obtain a copy of the License at
6+
*
7+
* <p>http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* <p>Unless required by applicable law or agreed to in writing, software distributed under the
10+
* License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
11+
* express or implied. See the License for the specific language governing permissions and
12+
* limitations under the License.
13+
*/
14+
package com.google.chat.selectionInput;
15+
16+
import java.util.List;
17+
18+
import org.springframework.boot.SpringApplication;
19+
import org.springframework.boot.autoconfigure.SpringBootApplication;
20+
import org.springframework.web.bind.annotation.PostMapping;
21+
import org.springframework.web.bind.annotation.RequestBody;
22+
import org.springframework.web.bind.annotation.ResponseBody;
23+
import org.springframework.web.bind.annotation.RestController;
24+
25+
import com.fasterxml.jackson.databind.JsonNode;
26+
import com.google.api.client.json.GenericJson;
27+
import com.google.api.services.chat.v1.model.CardWithId;
28+
import com.google.api.services.chat.v1.model.GoogleAppsCardV1Action;
29+
import com.google.api.services.chat.v1.model.GoogleAppsCardV1Card;
30+
import com.google.api.services.chat.v1.model.GoogleAppsCardV1Section;
31+
import com.google.api.services.chat.v1.model.GoogleAppsCardV1SelectionInput;
32+
import com.google.api.services.chat.v1.model.GoogleAppsCardV1SelectionItem;
33+
import com.google.api.services.chat.v1.model.GoogleAppsCardV1Widget;
34+
import com.google.api.services.chat.v1.model.Message;
35+
36+
// [START selection_input]
37+
@SpringBootApplication
38+
@RestController
39+
// Web app that responds to events sent from a Google Chat space.
40+
public class App {
41+
private static final String FUNCTION_URL = "your-function-url";
42+
43+
public static void main(String[] args) {
44+
SpringApplication.run(App.class, args);
45+
}
46+
47+
/**
48+
* Handle requests from Google Chat
49+
*
50+
* @param event the event object sent by Google Chat
51+
* @return The response to be sent back to Google Chat
52+
*/
53+
@PostMapping("/")
54+
@ResponseBody
55+
public GenericJson onEvent(@RequestBody JsonNode event) throws Exception {
56+
// Stores the Google Chat event
57+
JsonNode chatEvent = event.at("/chat");
58+
59+
// Handle user interaction with multiselect.
60+
if (!chatEvent.at("/widgetUpdatedPayload").isEmpty()) {
61+
return queryContacts(event);
62+
}
63+
64+
// Replies with a card that contains the multiselect menu.
65+
Message message = new Message().setCardsV2(List.of(new CardWithId()
66+
.setCardId("contactSelector")
67+
.setCard(new GoogleAppsCardV1Card()
68+
.setSections(List.of(new GoogleAppsCardV1Section().setWidgets(List.of(new GoogleAppsCardV1Widget()
69+
.setSelectionInput(new GoogleAppsCardV1SelectionInput()
70+
.setName("contacts")
71+
.setType("MULTI_SELECT")
72+
.setLabel("Selected contacts")
73+
.setMultiSelectMaxSelectedItems(3)
74+
.setMultiSelectMinQueryLength(1)
75+
.setExternalDataSource(new GoogleAppsCardV1Action().setFunction(FUNCTION_URL))
76+
// Suggested items loaded by default.
77+
// The list is static here but it could be dynamic.
78+
.setItems(List.of(getSuggestedContact("3")))))))))));
79+
80+
GenericJson createMessageAction = new GenericJson();
81+
createMessageAction.set("message", message);
82+
83+
GenericJson chatDataAction = new GenericJson();
84+
chatDataAction.set("createMessageAction", createMessageAction);
85+
86+
GenericJson hostAppDataAction = new GenericJson();
87+
hostAppDataAction.set("chatDataAction", chatDataAction);
88+
89+
GenericJson renderActions = new GenericJson();
90+
renderActions.set("hostAppDataAction", hostAppDataAction);
91+
return renderActions;
92+
}
93+
94+
/**
95+
* Get contact suggestions based on text typed by users.
96+
*
97+
* @param event the event object that contains the user's query.
98+
* @return The response with contact suggestions.
99+
*/
100+
GenericJson queryContacts(JsonNode event) throws Exception {
101+
String query = event.at("/commonEventObject/parameters/autocomplete_widget_query").asText();
102+
List<GoogleAppsCardV1SelectionItem> suggestions = List.of(
103+
// The list is static here but it could be dynamic.
104+
getSuggestedContact("1"), getSuggestedContact("2"), getSuggestedContact("3"), getSuggestedContact("4"), getSuggestedContact("5")
105+
// Only return items based on the query from the user
106+
).stream().filter(e -> query == null || e.getText().indexOf(query) > -1).toList();
107+
108+
GenericJson selectionInputWidgetSuggestions = new GenericJson();
109+
selectionInputWidgetSuggestions.set("suggestions", suggestions);
110+
111+
GenericJson updateWidget = new GenericJson();
112+
updateWidget.set("selectionInputWidgetSuggestions", selectionInputWidgetSuggestions);
113+
114+
GenericJson modifyOperation = new GenericJson();
115+
modifyOperation.set("updateWidget", updateWidget);
116+
117+
GenericJson action = new GenericJson();
118+
action.set("modifyOperations", List.of(modifyOperation));
119+
120+
GenericJson renderActions = new GenericJson();
121+
renderActions.set("action", action);
122+
return renderActions;
123+
}
124+
125+
/**
126+
* Generate a suggested contact given an ID.
127+
*
128+
* @param id The ID of the contact to return.
129+
* @return The contact formatted as a selection item in the menu.
130+
*/
131+
GoogleAppsCardV1SelectionItem getSuggestedContact(String id) {
132+
return new GoogleAppsCardV1SelectionItem()
133+
.setValue(id)
134+
.setStartIconUri("https://www.gstatic.com/images/branding/product/2x/contacts_48dp.png")
135+
.setText("Contact " + id);
136+
}
137+
}
138+
// [END selection_input]
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# Copyright 2025 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
# Set the port to the PORT environment variable
16+
server.port=${PORT:8080}

0 commit comments

Comments
 (0)