src/helpers/vote.ts   A
last analyzed

Complexity

Total Complexity 22
Complexity/F 5.5

Size

Lines of Code 170
Function Count 4

Duplication

Duplicated Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
wmc 22
eloc 93
mnd 18
bc 18
fnc 4
dl 0
loc 170
rs 10
bpm 4.5
cpm 5.5
noi 0
c 0
b 0
f 0

4 Functions

Rating   Name   Duplication   Size   Complexity  
A vote.ts ➔ progressBarText 0 18 2
F vote.ts ➔ saveVotes 0 66 14
A vote.ts ➔ choice 0 33 1
B vote.ts ➔ choiceSection 0 39 5
1
import {chat_v1 as chatV1} from '@googleapis/chat';
2
import {PollState, Voter, Votes} from './interfaces';
3
4
/**
5
 * Creates a small progress bar to show percent of votes for an option. Since
6
 * width is limited, the percentage is scaled to 20 steps (5% increments).
7
 *
8
 * @param {number} choice - The choice index
9
 * @param {object} voter - The voter
10
 * @param {PollState} state - PollState
11
 * @returns {Votes} Map of cast votes keyed by choice index
12
 */
13
export function saveVotes(choice: number, voter: Voter, state: PollState) {
14
  const votes: Votes = state.votes!;
15
  const isAnonymous = state.anon || false;
16
  const maxVotes = state.voteLimit === 0 ? state.choices.length : state.voteLimit || 1;
17
  if (maxVotes === 1) {
18
    Object.keys(votes).forEach(function(choiceIndex) {
19
      if (votes[choiceIndex]) {
20
        const existed = votes[choiceIndex].findIndex((x) => x.uid === voter.uid);
21
        if (existed > -1) {
22
          votes[choiceIndex].splice(existed, 1);
23
        }
24
      }
25
    });
26
  } else {
27
    // get current voter total vote
28
    let voteCount = 0;
29
    let voted = false;
30
    Object.keys(votes).forEach(function(choiceIndex) {
31
      if (votes[choiceIndex]) {
32
        const existed = votes[choiceIndex].findIndex((x) => x.uid === voter.uid);
33
        if (existed > -1) {
34
          voteCount += 1;
35
        }
36
        if (existed > -1 && parseInt(choiceIndex) === choice) {
37
          voted = true;
38
        }
39
      }
40
    });
41
    if (voteCount >= maxVotes || voted) {
42
      let deleted = false;
43
      Object.keys(votes).forEach(function(choiceIndex) {
44
        if (votes[choiceIndex]) {
45
          const existed = votes[choiceIndex].findIndex((x) => x.uid === voter.uid);
46
          if (((voteCount >= maxVotes && existed > -1 && !voted) ||
47
            (voted && parseInt(choiceIndex) === choice)) && !deleted) {
48
            votes[choiceIndex].splice(existed, 1);
49
            deleted = true;
50
          }
51
        }
52
      });
53
    }
54
    if (voted) {
55
      return votes;
56
    }
57
  }
58
  if (isAnonymous) {
59
    delete voter.name;
60
  }
61
62
  if (votes[choice]) {
63
    votes[choice].push(voter);
64
  } else {
65
    votes[choice] = [voter];
66
  }
67
68
  return votes;
69
}
70
71
/**
72
 * Creates a small progress bar to show percent of votes for an option. Since
73
 * width is limited, the percentage is scaled to 20 steps (5% increments).
74
 *
75
 * @param {number} voteCount - Number of votes for this option
76
 * @param {number} totalVotes - Total votes cast in the poll
77
 * @returns {string} Text snippet with bar and vote totals
78
 */
79
export function progressBarText(voteCount: number, totalVotes: number) {
80
  if (voteCount === 0 || totalVotes === 0) {
81
    return '';
82
  }
83
84
  // For progress bar, calculate share of votes and scale it
85
  const percentage = (voteCount * 100) / totalVotes;
86
  const progress = Math.round((percentage / 100) * 35);
87
  return '█'.repeat(progress);
88
}
89
90
/**
91
 * Builds a line in the card for a single choice, including
92
 * the current totals and voting action.
93
 *
94
 * @param {number} i - Index to identify the choice
95
 * @param {object} state - Text of the choice
96
 * @param {number} totalVotes - Total votes cast in poll state
97
 * @param {string} serializedState - Serialized poll state to send in events
98
 * @param {string} creator - creator of the option
99
 * @returns {chatV1.Schema$GoogleAppsCardV1Section} card section
100
 */
101
export function choiceSection(i: number, state: PollState, totalVotes: number, serializedState: string, creator = '') {
102
  if (state.votes === undefined) {
103
    state.votes = {};
104
  }
105
106
  if (state.votes[i] === undefined) {
107
    state.votes[i] = [];
108
  }
109
  const voteCount = state.votes[i].length;
110
  const choiceTag = choice(i, state.choices[i], voteCount, totalVotes, serializedState);
111
  if (creator) {
112
    choiceTag.decoratedText!.topLabel = 'Added by ' + creator;
113
  }
114
  const section: chatV1.Schema$GoogleAppsCardV1Section = {
115
    widgets: [choiceTag],
116
  };
117
  if (state.votes[i].length > 0 && !state.anon) {
118
    section.collapsible = true;
119
    section.uncollapsibleWidgetsCount = 1;
120
    // @ts-ignore: already defined above
121
    section.widgets.push({
122
      'textParagraph': {
123
        'text': state.votes[i].map((u) => u.name).join(', '),
124
      },
125
    });
126
  }
127
  return section;
128
}
129
130
/**
131
 * Builds a line in the card for a single choice, including
132
 * the current totals and voting action.
133
 *
134
 * @param {number} index - Index to identify the choice
135
 * @param {string|undefined} text - Text of the choice
136
 * @param {number} voteCount - Current number of votes cast for this item
137
 * @param {number} totalVotes - Total votes cast in poll
138
 * @param {string} state - Serialized state to send in events
139
 * @returns {chatV1.Schema$GoogleAppsCardV1Widget} card widget
140
 */
141
function choice(
142
  index: number, text: string, voteCount: number, totalVotes: number,
143
  state: string): chatV1.Schema$GoogleAppsCardV1Widget {
144
  const progressBar = progressBarText(voteCount, totalVotes);
145
  return {
146
    decoratedText: {
147
      bottomLabel: `${progressBar} ${voteCount}`,
148
      text: text,
149
      button: {
150
        text: 'vote',
151
        onClick: {
152
          action: {
153
            function: 'vote',
154
            parameters: [
155
              {
156
                key: 'state',
157
                value: state,
158
              },
159
              {
160
                key: 'index',
161
                value: index.toString(10),
162
              },
163
            ],
164
          },
165
        },
166
      },
167
    },
168
  };
169
}
170