Passed
Pull Request — master (#1)
by Muhammad Dyas
02:04
created

index.ts ➔ addOptionForm   C

Complexity

Conditions 9

Size

Total Lines 20
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 13
dl 0
loc 20
c 0
b 0
f 0
rs 6.6666
cc 9
1
// @#ts-nocheck
2
import {HttpFunction} from '@google-cloud/functions-framework/build/src/functions';
3
4
import {
5
  buildConfigurationForm,
6
  buildOptionsFromMessage,
7
} from './config-form';
8
import {buildVoteCard} from './vote-card';
9
import {saveVotes} from './helpers/vote';
10
import {buildAddOptionForm} from './add-option-form';
11
import {callMessageApi} from './helpers/api';
12
import {addOptionToState} from './helpers/option';
13
import {buildActionResponse} from './helpers/response';
14
import {MAX_NUM_OF_OPTIONS} from './config/default';
15
import {splitMessage} from './helpers/utils';
16
import {chat_v1 as chatV1} from 'googleapis/build/src/apis/chat/v1';
17
export const app: HttpFunction = async (req, res) => {
18
  if (!(req.method === 'POST' && req.body)) {
19
    res.status(400).send('');
20
  }
21
  const buttonCard = {
22
    'cardId': 'welcome-card',
23
    'card': {
24
      'sections': [
25
        {
26
          'widgets': [
27
            {
28
              'buttonList': {
29
                'buttons': [
30
                  {
31
                    'text': 'Create Poll',
32
                    'onClick': {
33
                      'action': {
34
                        'function': 'show_form',
35
                        'interaction': 'OPEN_DIALOG',
36
                        'parameters': [],
37
                      },
38
                    },
39
                  },
40
                  {
41
                    'text': 'Terms and Conditions',
42
                    'onClick': {
43
                      'openLink': {
44
                        'url': 'https://absolute-poll.yaskur.com/terms-and-condition',
45
                      },
46
                    },
47
                  },
48
                  {
49
                    'text': 'Contact Us',
50
                    'onClick': {
51
                      'openLink': {
52
                        'url': 'https://absolute-poll.yaskur.com/contact-us',
53
                      },
54
                    },
55
                  },
56
                ],
57
              },
58
            },
59
          ],
60
        },
61
      ],
62
    },
63
  };
64
  const event = req.body;
65
  console.log(event.type,
66
      event.common?.invokedFunction || event.message?.slashCommand?.commandId ||
67
      event.message?.argumentText,
68
      event.user.displayName, event.user.email, event.space.type,
69
      event.space.name);
70
  let reply: chatV1.Schema$Message;
71
  // Dispatch slash and action events
72
  if (event.type === 'MESSAGE') {
73
    const message = event.message;
74
    if (message.slashCommand?.commandId === '1') {
75
      reply = showConfigurationForm(event);
76
    } else if (message.slashCommand?.commandId === '2') {
77
      reply = {
78
        thread: event.message.thread,
79
        actionResponse: {
80
          type: 'NEW_MESSAGE',
81
        },
82
        text: 'Hi there! I can help you create polls to enhance collaboration and efficiency ' +
83
            'in decision-making using Google Chat™.\n' +
84
            '\n' +
85
            'Below is an example commands:\n' +
86
            '`/poll` - You will need to fill out the topic and answers in the form that will be displayed.\n' +
87
            '`/poll "Which is the best country to visit" "Indonesia"` - to create a poll with ' +
88
            '"Which is the best country to visit" as the topic and "Indonesia" as the answer\n' +
89
            '\n' +
90
            'We hope you find our service useful and please don\'t hesitate to contact us ' +
91
            'if you have any questions or concerns.',
92
      };
93
    } else if (message.text) {
94
      const argument = event.message?.argumentText?.trim().toLowerCase();
95
96
      reply = {
97
        thread: event.message.thread,
98
        actionResponse: {
99
          type: 'NEW_MESSAGE',
100
        },
101
        text: 'Hi! To create a poll, you can use the */poll* command. \n \n' +
102
            'Alternatively, you can create poll by mentioning me with question and answers. ' +
103
            'e.g *@Absolute Poll "Your Question" "Answer 1" "Answer 2"*',
104
      };
105
      const choices = splitMessage(argument);
106
      if (choices.length > 2) {
107
        const pollCard = buildVoteCard({
108
          choiceCreator: undefined,
109
          topic: choices.shift(),
110
          author: event.user,
111
          choices: choices,
112
          votes: {},
113
          anon: false,
114
          optionable: true,
115
        });
116
        const message = {
117
          cardsV2: [pollCard],
118
        };
119
        reply = {
120
          thread: event.message.thread,
121
          actionResponse: {
122
            type: 'NEW_MESSAGE',
123
          },
124
          ...message,
125
        };
126
      }
127
128
      if (argument === 'help') {
129
        reply.text = 'Hi there! I can help you create polls to enhance collaboration and efficiency ' +
130
            'in decision-making using Google Chat™.\n' +
131
            '\n' +
132
            'Below is an example commands:\n' +
133
            '`/poll` - You will need to fill out the topic and answers in the form that will be displayed.\n' +
134
            '`/poll "Which is the best country to visit" "Indonesia"` - to create a poll with ' +
135
            '"Which is the best country to visit" as the topic and "Indonesia" as the answer\n' +
136
            '\n' +
137
            'We hope you find our service useful and please don\'t hesitate to contact us ' +
138
            'if you have any questions or concerns.';
139
        // reply.cardsV2 = buttonCard;
140
      } else if (argument === 'test') {
141
        reply.text = 'test search on <a href=\'http://www.google.com\'>google</a> (https://google.com)[https://google.com]';
142
      }
143
    }
144
  } else if (event.type === 'CARD_CLICKED') {
145
    const action = event.common?.invokedFunction;
146
    if (action === 'start_poll') {
147
      reply = await startPoll(event);
148
    } else if (action === 'vote') {
149
      reply = recordVote(event);
150
    } else if (action === 'add_option_form') {
151
      reply = addOptionForm(event);
152
    } else if (action === 'add_option') {
153
      reply = await saveOption(event);
154
    } else if (action === 'show_form') {
155
      reply = showConfigurationForm(event, true);
156
    }
157
  } else if (event.type === 'ADDED_TO_SPACE') {
158
    const message = {
159
      text: undefined,
160
      cardsV2: undefined,
161
    };
162
    const spaceType = event.space.type;
163
    if (spaceType === 'ROOM') {
164
      message.text = 'Hi there! I\'d be happy to assist you in creating polls to improve collaboration and ' +
165
          'decision-making efficiency on Google Chat™.\n' +
166
          '\n' +
167
          'To create a poll, simply use the */poll* command or click on the "Create Poll" button below. ' +
168
          'You can also test our app in a direct message if you prefer.\n' +
169
          '\n' +
170
          'Alternatively, you can ' +
171
          'You can also test our app in a direct message if you prefer.\n' +
172
          '\n' +
173
          'We hope you find our service useful and please don\'t hesitate to contact us ' +
174
          'if you have any questions or concerns.';
175
    } else if (spaceType === 'DM') {
176
      message.text = 'Hey there! ' +
177
          'Before creating a poll in a group space, you can test it out here in a direct message.\n' +
178
          '\n' +
179
          'To create a poll, you can use the */poll* command or click on the "Create Poll" button below.\n' +
180
          '\n' +
181
          'Thank you for using our bot. We hope that it will prove to be a valuable tool for you and your team.\n' +
182
          '\n' +
183
          'Don\'t hesitate to reach out if you have any questions or concerns in the future.' +
184
          ' We are always here to help you and your team';
185
    }
186
187
    message.cardsV2 = buttonCard;
188
189
    reply = {
190
      actionResponse: {
191
        type: 'NEW_MESSAGE',
192
      },
193
      ...message,
194
    };
195
  }
196
  res.json(reply);
197
};
198
199
200
/**
201
 * Handles the slash command to display the config form.
202
 *
203
 * @param {object} event - chat event
204
 * @param {boolean} isBlank - fill with text from message or note
205
 * @returns {object} Response to send back to Chat
206
 */
207
function showConfigurationForm(event, isBlank = false) {
208
  // Seed the topic with any text after the slash command
209
  const message = isBlank ? '' : event.message?.argumentText?.trim();
210
  const options = buildOptionsFromMessage(message);
211
  const dialog = buildConfigurationForm(options);
212
  return {
213
    actionResponse: {
214
      type: 'DIALOG',
215
      dialogAction: {
216
        dialog: {
217
          body: dialog,
218
        },
219
      },
220
    },
221
  };
222
}
223
224
/**
225
 * Handle the custom start_poll action.
226
 *
227
 * @param {object} event - chat event
228
 * @returns {object} Response to send back to Chat
229
 */
230
async function startPoll(event) {
231
  // Get the form values
232
  const formValues = event.common?.formInputs;
233
  const topic = formValues?.['topic']?.stringInputs.value[0]?.trim();
234
  const isAnonymous = formValues?.['is_anonymous']?.stringInputs.value[0] ===
235
      '1';
236
  const allowAddOption = formValues?.['allow_add_option']?.stringInputs.value[0] ===
237
      '1';
238
  const choices = [];
239
  const votes = {};
240
241
  for (let i = 0; i < MAX_NUM_OF_OPTIONS; ++i) {
242
    const choice = formValues?.[`option${i}`]?.stringInputs.value[0]?.trim();
243
    if (choice) {
244
      choices.push(choice);
245
      votes[i] = [];
246
    }
247
  }
248
249
  if (!topic || choices.length === 0) {
250
    // Incomplete form submitted, rerender
251
    const dialog = buildConfigurationForm({
252
      topic,
253
      choices,
254
    });
255
    return {
256
      actionResponse: {
257
        type: 'DIALOG',
258
        dialogAction: {
259
          dialog: {
260
            body: dialog,
261
          },
262
        },
263
      },
264
    };
265
  }
266
267
  // Valid configuration, build the voting card to display
268
  // in the space
269
  const pollCard = buildVoteCard({
270
    topic: topic, choiceCreator: undefined,
271
    author: event.user,
272
    choices: choices,
273
    votes: votes,
274
    anon: isAnonymous,
275
    optionable: allowAddOption,
276
  });
277
  const message = {
278
    cardsV2: [pollCard],
279
  };
280
  const request = {
281
    parent: event.space.name,
282
    requestBody: message,
283
  };
284
  const apiResponse = await callMessageApi('create', request);
285
  if (apiResponse) {
286
    return buildActionResponse('Poll started.', 'OK');
287
  } else {
288
    return buildActionResponse('Failed to start poll.', 'UNKNOWN');
289
  }
290
}
291
292
/**
293
 * Handle the custom vote action. Updates the state to record
294
 * the user's vote then rerenders the card.
295
 *
296
 * @param {object} event - chat event
297
 * @returns {object} Response to send back to Chat
298
 */
299
function recordVote(event) {
300
  const parameters = event.common?.parameters;
301
302
  const choice = parseInt(parameters['index']);
303
  const userId = event.user.name;
304
  const userName = event.user.displayName;
305
  const voter = {uid: userId, name: userName};
306
  const state = JSON.parse(parameters['state']);
307
308
  // Add or update the user's selected option
309
  state.votes = saveVotes(choice, voter, state.votes, state.anon);
310
311
  const card = buildVoteCard(state);
312
  return {
313
    thread: event.message.thread,
314
    actionResponse: {
315
      type: 'UPDATE_MESSAGE',
316
    },
317
    cardsV2: [card],
318
  };
319
}
320
321
/**
322
 * Opens and starts a dialog that allows users to add details about a contact.
323
 *
324
 * @param {object} event the event object from Google Chat.
325
 *
326
 * @returns {object} open a dialog.
327
 */
328
function addOptionForm(event) {
329
  const card = event.message.cardsV2[0].card;
330
  const stateJson = card.sections[0].widgets[0].decoratedText?.button?.onClick?.action?.parameters[0].value ||
331
      card.sections[1].widgets[0].decoratedText?.button?.onClick?.action?.parameters[0].value;
332
  const state = JSON.parse(stateJson);
333
  const dialog = buildAddOptionForm(state);
334
  return {
335
    actionResponse: {
336
      type: 'DIALOG',
337
      dialogAction: {
338
        dialog: {
339
          body: dialog,
340
        },
341
      },
342
    },
343
  };
344
}
345
;
346
347
/**
348
 * Handle the custom vote action. Updates the state to record
349
 * the user's vote then rerenders the card.
350
 *
351
 * @param {object} event - chat event
352
 * @returns {object} Response to send back to Chat
353
 */
354
async function saveOption(event) {
355
  const userName = event.user.displayName;
356
357
  const parameters = event.common?.parameters;
358
  const state = JSON.parse(parameters['state']);
359
  const formValues = event.common?.formInputs;
360
  const optionValue = formValues?.['value']?.stringInputs.value[0]?.trim();
361
  addOptionToState(optionValue, state, userName);
362
363
  const card = buildVoteCard(state);
364
  const message = {
365
    cardsV2: [card],
366
  };
367
  const request = {
368
    name: event.message.name,
369
    requestBody: message,
370
    updateMask: 'cardsV2',
371
  };
372
  const apiResponse = await callMessageApi('update', request);
373
  if (apiResponse) {
374
    return buildActionResponse('Option is added', 'OK');
375
  } else {
376
    return buildActionResponse('Failed to add option.', 'UNKNOWN');
377
  }
378
}
379