Passed
Pull Request — master (#67)
by Muhammad Dyas
01:34
created

vote.ts ➔ saveVotes   D

Complexity

Conditions 13

Size

Total Lines 65
Code Lines 39

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 39
dl 0
loc 65
rs 4.2
c 0
b 0
f 0
cc 13

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

Complexity

Complex classes like vote.ts ➔ saveVotes often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

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