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