Passed
Pull Request — master (#7)
by Muhammad Dyas
01:29
created

ActionHandler.closePoll   A

Complexity

Conditions 2

Size

Total Lines 15
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 14
dl 0
loc 15
rs 9.7
c 0
b 0
f 0
cc 2
1
import {chat_v1 as chatV1} from 'googleapis/build/src/apis/chat/v1';
2
import BaseHandler from './BaseHandler';
3
import NewPollFormCard from '../cards/NewPollFormCard';
4
import {addOptionToState, getStateFromCard} from '../helpers/state';
5
import {callMessageApi} from '../helpers/api';
6
import {createDialogActionResponse, createStatusActionResponse} from '../helpers/response';
7
import PollCard from '../cards/PollCard';
8
import {PollState, Voter, Votes} from '../helpers/interfaces';
9
import AddOptionFormCard from '../cards/AddOptionFormCard';
10
import {saveVotes} from '../helpers/vote';
11
import {MAX_NUM_OF_OPTIONS} from '../config/default';
12
import ClosePollFormCard from '../cards/ClosePollFormCard';
13
14
/*
15
This list methods are used in the poll chat message
16
 */
17
interface PollAction {
18
  saveOption(): Promise<chatV1.Schema$Message>;
19
20
  getEventPollState(): PollState;
21
}
22
23
export default class ActionHandler extends BaseHandler implements PollAction {
24
  async process(): Promise<chatV1.Schema$Message> {
25
    const action = this.event.common?.invokedFunction;
26
    switch (action) {
27
      case 'start_poll':
28
        return await this.startPoll();
29
      case 'vote':
30
        return this.recordVote();
31
      case 'add_option_form':
32
        return this.addOptionForm();
33
      case 'add_option':
34
        return await this.saveOption();
35
      case 'show_form':
36
        return createDialogActionResponse(new NewPollFormCard({topic: '', choices: []}).create());
37
      case 'close_poll_form':
38
        return createDialogActionResponse(new ClosePollFormCard().create());
39
      case 'close_poll':
40
        return await this.closePoll();
41
      default:
42
        return createStatusActionResponse('Unknown action!', 'UNKNOWN');
43
    }
44
  }
45
46
  /**
47
   * Handle the custom start_poll action.
48
   *
49
   * @returns {object} Response to send back to Chat
50
   */
51
  async startPoll() {
52
    // Get the form values
53
    const formValues = this.event.common?.formInputs;
54
    const topic = formValues?.['topic']?.stringInputs?.value?.[0]?.trim() ?? '';
55
    const isAnonymous = formValues?.['is_anonymous']?.stringInputs?.value?.[0] === '1';
56
    const allowAddOption = formValues?.['allow_add_option']?.stringInputs?.value?.[0] === '1';
57
    const choices = [];
58
    const votes: Votes = {};
59
60
    for (let i = 0; i < MAX_NUM_OF_OPTIONS; ++i) {
61
      const choice = formValues?.[`option${i}`]?.stringInputs?.value?.[0]?.trim();
62
      if (choice) {
63
        choices.push(choice);
64
        votes[i] = [];
65
      }
66
    }
67
68
    if (!topic || choices.length === 0) {
69
      // Incomplete form submitted, rerender
70
      const dialog = new NewPollFormCard({
71
        topic,
72
        choices,
73
      }).create();
74
      return {
75
        actionResponse: {
76
          type: 'DIALOG',
77
          dialogAction: {
78
            dialog: {
79
              body: dialog,
80
            },
81
          },
82
        },
83
      };
84
    }
85
    const pollCard = new PollCard({
86
      topic: topic, choiceCreator: undefined,
87
      author: this.event.user,
88
      choices: choices,
89
      votes: votes,
90
      anon: isAnonymous,
91
      optionable: allowAddOption,
92
    }).createCardWithId();
93
    // Valid configuration, make the voting card to display in the space
94
    const message = {
95
      cardsV2: [pollCard],
96
    };
97
    const request = {
98
      parent: this.event.space?.name,
99
      requestBody: message,
100
    };
101
    const apiResponse = await callMessageApi('create', request);
102
    if (apiResponse) {
103
      return createStatusActionResponse('Poll started.', 'OK');
104
    } else {
105
      return createStatusActionResponse('Failed to start poll.', 'UNKNOWN');
106
    }
107
  }
108
109
  /**
110
   * Handle the custom vote action. Updates the state to record
111
   * the user's vote then rerenders the card.
112
   *
113
   * @returns {object} Response to send back to Chat
114
   */
115
  recordVote() {
116
    const parameters = this.event.common?.parameters;
117
    if (!(parameters?.['index'])) {
118
      throw new Error('Index Out of Bounds');
119
    }
120
    const choice = parseInt(parameters['index']);
121
    const userId = this.event.user?.name ?? '';
122
    const userName = this.event.user?.displayName ?? '';
123
    const voter: Voter = {uid: userId, name: userName};
124
    const state = this.getEventPollState();
125
126
    // Add or update the user's selected option
127
    state.votes = saveVotes(choice, voter, state.votes!, state.anon);
128
    const card = new PollCard(state);
129
    return {
130
      thread: this.event.message?.thread,
131
      actionResponse: {
132
        type: 'UPDATE_MESSAGE',
133
      },
134
      cardsV2: [card.createCardWithId()],
135
    };
136
  }
137
138
  /**
139
   * Opens and starts a dialog that allows users to add details about a contact.
140
   *
141
   * @returns {object} open a dialog.
142
   */
143
  addOptionForm() {
144
    const state = this.getEventPollState();
145
    const dialog = new AddOptionFormCard(state).create();
146
    return createDialogActionResponse(dialog);
147
  };
148
149
  /**
150
   * Handle add new option input to the poll state
151
   * the user's vote then rerenders the card.
152
   *
153
   * @returns {object} Response to send back to Chat
154
   */
155
  async saveOption(): Promise<chatV1.Schema$Message> {
156
    const userName = this.event.user?.displayName ?? '';
157
    const state = this.getEventPollState();
158
    const formValues = this.event.common?.formInputs;
159
    const optionValue = formValues?.['value']?.stringInputs?.value?.[0]?.trim() || '';
160
    addOptionToState(optionValue, state, userName);
161
162
    const cardMessage = new PollCard(state).createMessage();
163
164
    const request = {
165
      name: this.event.message!.name,
166
      requestBody: cardMessage,
167
      updateMask: 'cardsV2',
168
    };
169
    const apiResponse = await callMessageApi('update', request);
170
    if (apiResponse) {
171
      return createStatusActionResponse('Option is added', 'OK');
172
    } else {
173
      return createStatusActionResponse('Failed to add option.', 'UNKNOWN');
174
    }
175
  }
176
177
  getEventPollState(): PollState {
178
    const state = getStateFromCard(this.event);
179
    if (!state) {
180
      throw new ReferenceError('no valid state in the event');
181
    }
182
    return JSON.parse(state);
183
  }
184
185
  async closePoll(): Promise<chatV1.Schema$Message> {
186
    const state = this.getEventPollState();
187
    state.closedTime = Date.now();
188
    const cardMessage = new PollCard(state).createMessage();
189
    const request = {
190
      name: this.event.message!.name,
191
      requestBody: cardMessage,
192
      updateMask: 'cardsV2',
193
    };
194
    const apiResponse = await callMessageApi('update', request);
195
    if (apiResponse) {
196
      return createStatusActionResponse('Poll is closed', 'OK');
197
    } else {
198
      return createStatusActionResponse('Failed to close poll.', 'UNKNOWN');
199
    }
200
  }
201
}
202