1+ /**
2+ * Copyright 2024 Google LLC
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+ * https://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+ // [START add_ons_preview_link]
17+ // [START add_ons_case_preview_link]
18+
19+ /**
20+ * Entry point for a support case link preview.
21+ *
22+ * @param {!Object } event The event object.
23+ * @return {!Card } The resulting preview link card.
24+ */
25+ function caseLinkPreview ( event ) {
26+
27+ // If the event object URL matches a specified pattern for support case links.
28+ if ( event . docs . matchedUrl . url ) {
29+
30+ // Uses the event object to parse the URL and identify the case details.
31+ const caseDetails = parseQuery ( event . docs . matchedUrl . url ) ;
32+
33+ // Builds a preview card with the case name, and description
34+ const caseHeader = CardService . newCardHeader ( )
35+ . setTitle ( `Case ${ caseDetails [ "name" ] [ 0 ] } ` ) ;
36+ const caseDescription = CardService . newTextParagraph ( )
37+ . setText ( caseDetails [ "description" ] [ 0 ] ) ;
38+
39+ // Returns the card.
40+ // Uses the text from the card's header for the title of the smart chip.
41+ return CardService . newCardBuilder ( )
42+ . setHeader ( caseHeader )
43+ . addSection ( CardService . newCardSection ( ) . addWidget ( caseDescription ) )
44+ . build ( ) ;
45+ }
46+ }
47+
48+ /**
49+ * Extracts the URL parameters from the given URL.
50+ *
51+ * @param {!string } url The URL to parse.
52+ * @return {!Map } A map with the extracted URL parameters.
53+ */
54+ function parseQuery ( url ) {
55+ const query = url . split ( "?" ) [ 1 ] ;
56+ if ( query ) {
57+ return query . split ( "&" )
58+ . reduce ( function ( o , e ) {
59+ var temp = e . split ( "=" ) ;
60+ var key = temp [ 0 ] . trim ( ) ;
61+ var value = temp [ 1 ] . trim ( ) ;
62+ value = isNaN ( value ) ? value : Number ( value ) ;
63+ if ( o [ key ] ) {
64+ o [ key ] . push ( value ) ;
65+ } else {
66+ o [ key ] = [ value ] ;
67+ }
68+ return o ;
69+ } , { } ) ;
70+ }
71+ return null ;
72+ }
73+
74+ // [END add_ons_case_preview_link]
75+ // [END add_ons_preview_link]
76+
77+ // [START add_ons_3p_resources]
78+ // [START add_ons_3p_resources_create_case_card]
79+
80+ /**
81+ * Produces a support case creation form card.
82+ *
83+ * @param {!Object } event The event object.
84+ * @param {!Object= } errors An optional map of per-field error messages.
85+ * @param {boolean } isUpdate Whether to return the form as an update card navigation.
86+ * @return {!Card|!ActionResponse } The resulting card or action response.
87+ */
88+ function createCaseInputCard ( event , errors , isUpdate ) {
89+
90+ const cardHeader = CardService . newCardHeader ( )
91+ . setTitle ( 'Create a support case' )
92+
93+ const cardSectionTextInput1 = CardService . newTextInput ( )
94+ . setFieldName ( 'name' )
95+ . setTitle ( 'Name' )
96+ . setMultiline ( false ) ;
97+
98+ const cardSectionTextInput2 = CardService . newTextInput ( )
99+ . setFieldName ( 'description' )
100+ . setTitle ( 'Description' )
101+ . setMultiline ( true ) ;
102+
103+ const cardSectionSelectionInput1 = CardService . newSelectionInput ( )
104+ . setFieldName ( 'priority' )
105+ . setTitle ( 'Priority' )
106+ . setType ( CardService . SelectionInputType . DROPDOWN )
107+ . addItem ( 'P0' , 'P0' , false )
108+ . addItem ( 'P1' , 'P1' , false )
109+ . addItem ( 'P2' , 'P2' , false )
110+ . addItem ( 'P3' , 'P3' , false ) ;
111+
112+ const cardSectionSelectionInput2 = CardService . newSelectionInput ( )
113+ . setFieldName ( 'impact' )
114+ . setTitle ( 'Impact' )
115+ . setType ( CardService . SelectionInputType . CHECK_BOX )
116+ . addItem ( 'Blocks a critical customer operation' , 'Blocks a critical customer operation' , false ) ;
117+
118+ const cardSectionButtonListButtonAction = CardService . newAction ( )
119+ . setPersistValues ( true )
120+ . setFunctionName ( 'submitCaseCreationForm' )
121+ . setParameters ( { } ) ;
122+
123+ const cardSectionButtonListButton = CardService . newTextButton ( )
124+ . setText ( 'Create' )
125+ . setTextButtonStyle ( CardService . TextButtonStyle . TEXT )
126+ . setOnClickAction ( cardSectionButtonListButtonAction ) ;
127+
128+ const cardSectionButtonList = CardService . newButtonSet ( )
129+ . addButton ( cardSectionButtonListButton ) ;
130+
131+ // Builds the form inputs with error texts for invalid values.
132+ const cardSection = CardService . newCardSection ( ) ;
133+ if ( errors ?. name ) {
134+ cardSection . addWidget ( createErrorTextParagraph ( errors . name ) ) ;
135+ }
136+ cardSection . addWidget ( cardSectionTextInput1 ) ;
137+ if ( errors ?. description ) {
138+ cardSection . addWidget ( createErrorTextParagraph ( errors . description ) ) ;
139+ }
140+ cardSection . addWidget ( cardSectionTextInput2 ) ;
141+ if ( errors ?. priority ) {
142+ cardSection . addWidget ( createErrorTextParagraph ( errors . priority ) ) ;
143+ }
144+ cardSection . addWidget ( cardSectionSelectionInput1 ) ;
145+ if ( errors ?. impact ) {
146+ cardSection . addWidget ( createErrorTextParagraph ( errors . impact ) ) ;
147+ }
148+
149+ cardSection . addWidget ( cardSectionSelectionInput2 ) ;
150+ cardSection . addWidget ( cardSectionButtonList ) ;
151+
152+ const card = CardService . newCardBuilder ( )
153+ . setHeader ( cardHeader )
154+ . addSection ( cardSection )
155+ . build ( ) ;
156+
157+ if ( isUpdate ) {
158+ return CardService . newActionResponseBuilder ( )
159+ . setNavigation ( CardService . newNavigation ( ) . updateCard ( card ) )
160+ . build ( ) ;
161+ } else {
162+ return card ;
163+ }
164+ }
165+
166+ // [END add_ons_3p_resources_create_case_card]
167+ // [START add_ons_3p_resources_submit_create_case]
168+
169+ /**
170+ * Submits the creation form. If valid, returns a render action
171+ * that inserts a new link into the document. If invalid, returns an
172+ * update card navigation that re-renders the creation form with error messages.
173+ *
174+ * @param {!Object } event The event object with form input values.
175+ * @return {!ActionResponse|!SubmitFormResponse } The resulting response.
176+ */
177+ function submitCaseCreationForm ( event ) {
178+ const caseDetails = {
179+ name : event . formInput . name ,
180+ description : event . formInput . description ,
181+ priority : event . formInput . priority ,
182+ impact : ! ! event . formInput . impact ,
183+ } ;
184+
185+ const errors = validateFormInputs ( caseDetails ) ;
186+ if ( Object . keys ( errors ) . length > 0 ) {
187+ return createCaseInputCard ( event , errors , /* isUpdate= */ true ) ;
188+ } else {
189+ const title = `Case ${ caseDetails . name } ` ;
190+ // Adds the case details as parameters to the generated link URL.
191+ const url = 'https://example.com/support/cases/?' + generateQuery ( caseDetails ) ;
192+ return createLinkRenderAction ( title , url ) ;
193+ }
194+ }
195+
196+ /**
197+ * Build a query path with URL parameters.
198+ *
199+ * @param {!Map } parameters A map with the URL parameters.
200+ * @return {!string } The resulting query path.
201+ */
202+ function generateQuery ( parameters ) {
203+ return Object . entries ( parameters ) . flatMap ( ( [ k , v ] ) =>
204+ Array . isArray ( v ) ? v . map ( e => `${ k } =${ encodeURIComponent ( e ) } ` ) : `${ k } =${ encodeURIComponent ( v ) } `
205+ ) . join ( "&" ) ;
206+ }
207+
208+ // [END add_ons_3p_resources_submit_create_case]
209+ // [START add_ons_3p_resources_validate_inputs]
210+
211+ /**
212+ * Validates case creation form input values.
213+ *
214+ * @param {!Object } caseDetails The values of each form input submitted by the user.
215+ * @return {!Object } A map from field name to error message. An empty object
216+ * represents a valid form submission.
217+ */
218+ function validateFormInputs ( caseDetails ) {
219+ const errors = { } ;
220+ if ( ! caseDetails . name ) {
221+ errors . name = 'You must provide a name' ;
222+ }
223+ if ( ! caseDetails . description ) {
224+ errors . description = 'You must provide a description' ;
225+ }
226+ if ( ! caseDetails . priority ) {
227+ errors . priority = 'You must provide a priority' ;
228+ }
229+ if ( caseDetails . impact && caseDetails . priority !== 'P0' && caseDetails . priority !== 'P1' ) {
230+ errors . impact = 'If an issue blocks a critical customer operation, priority must be P0 or P1' ;
231+ }
232+
233+ return errors ;
234+ }
235+
236+ /**
237+ * Returns a text paragraph with red text indicating a form field validation error.
238+ *
239+ * @param {string } errorMessage A description of input value error.
240+ * @return {!TextParagraph } The resulting text paragraph.
241+ */
242+ function createErrorTextParagraph ( errorMessage ) {
243+ return CardService . newTextParagraph ( )
244+ . setText ( '<font color=\"#BA0300\"><b>Error:</b> ' + errorMessage + '</font>' ) ;
245+ }
246+
247+ // [END add_ons_3p_resources_validate_inputs]
248+ // [START add_ons_3p_resources_link_render_action]
249+
250+ /**
251+ * Returns a submit form response that inserts a link into the document.
252+ *
253+ * @param {string } title The title of the link to insert.
254+ * @param {string } url The URL of the link to insert.
255+ * @return {!SubmitFormResponse } The resulting submit form response.
256+ */
257+ function createLinkRenderAction ( title , url ) {
258+ return {
259+ renderActions : {
260+ action : {
261+ links : [ {
262+ title : title ,
263+ url : url
264+ } ]
265+ }
266+ }
267+ } ;
268+ }
269+
270+ // [END add_ons_3p_resources_link_render_action]
271+ // [END add_ons_3p_resources]
0 commit comments