Completed
Push — master ( 8e01eb...25ec60 )
by Muhammad Dyas
17s queued 12s
created

ActionHandler.startPoll   A

Complexity

Conditions 4

Size

Total Lines 34
Code Lines 25

Duplication

Lines 0
Ratio 0 %

Importance

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