Passed
Push — feature/connect-application-st... ( a6f23b...9d3dc6 )
by Chris
04:02
created

resources/assets/js/store/RatingGuideQuestion/ratingGuideQuestionReducer.ts   A

Complexity

Total Complexity 6
Complexity/F 6

Size

Lines of Code 318
Function Count 1

Duplication

Duplicated Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 249
dl 0
loc 318
rs 10
c 0
b 0
f 0
wmc 6
mnd 5
bc 5
fnc 1
bpm 5
cpm 6
noi 0

1 Function

Rating   Name   Duplication   Size   Complexity  
A ratingGuideQuestionReducer.ts ➔ hasIdenticalItem 0 6 4
1
/* eslint-disable @typescript-eslint/camelcase */
2
import isEqual from "lodash/isEqual";
3
import { RatingGuideQuestion } from "../../models/types";
4
import {
5
  AssessmentPlanAction,
6
  FETCH_ASSESSMENT_PLAN_STARTED,
7
  FETCH_ASSESSMENT_PLAN_SUCCEEEDED,
8
  FETCH_ASSESSMENT_PLAN_FAILED,
9
} from "../AssessmentPlan/assessmentPlanActions";
10
import {
11
  RatingGuideQuestionAction,
12
  CREATE_TEMP_RATING_GUIDE_QUESTION,
13
  DELETE_RATING_GUIDE_QUESTION_FAILED,
14
  DELETE_RATING_GUIDE_QUESTION_STARTED,
15
  DELETE_RATING_GUIDE_QUESTION_SUCCEEDED,
16
  DELETE_TEMP_RATING_GUIDE_QUESTION,
17
  EDIT_RATING_GUIDE_QUESTION,
18
  EDIT_TEMP_RATING_GUIDE_QUESTION,
19
  STORE_NEW_RATING_GUIDE_QUESTION_FAILED,
20
  STORE_NEW_RATING_GUIDE_QUESTION_STARTED,
21
  STORE_NEW_RATING_GUIDE_QUESTION_SUCCEEDED,
22
  UPDATE_RATING_GUIDE_QUESTION_FAILED,
23
  UPDATE_RATING_GUIDE_QUESTION_STARTED,
24
  UPDATE_RATING_GUIDE_QUESTION_SUCCEEDED,
25
} from "./ratingGuideQuestionActions";
26
import {
27
  getId,
28
  mapToObject,
29
  deleteProperty,
30
  hasKey,
31
} from "../../helpers/queries";
32
33
export interface RatingGuideQuestionState {
34
  ratingGuideQuestions: {
35
    // Stores rating guide answers that are synced with the server
36
    [id: number]: RatingGuideQuestion;
37
  };
38
  editedRatingGuideQuestions: {
39
    // For storing rating guide answers that have been edited locally
40
    [id: number]: RatingGuideQuestion;
41
  };
42
  tempRatingGuideQuestions: {
43
    // For storing local rating guide answers that have never been saved to server
44
    [id: number]: RatingGuideQuestion;
45
  };
46
  tempRatingGuideQuestionSaving: {
47
    // Tracks whether a RatingGuideQuestion is currently being saved to server
48
    [id: number]: boolean;
49
  };
50
  ratingGuideQuestionUpdates: {
51
    // Tracks the number of pending updates
52
    [id: number]: number;
53
  };
54
  ratingGuideQuestionDeletes: {
55
    // Tracks the number of pending delete requests
56
    [id: number]: number;
57
  };
58
}
59
60
export const initState = (): RatingGuideQuestionState => ({
61
  ratingGuideQuestions: {},
62
  editedRatingGuideQuestions: {},
63
  tempRatingGuideQuestions: {},
64
  tempRatingGuideQuestionSaving: {},
65
  ratingGuideQuestionUpdates: {},
66
  ratingGuideQuestionDeletes: {},
67
});
68
69
/**
70
 * Return editedAssessments, with assessment removed if it is present and identical.
71
 * This is useful for not deleting a temp state when the first of several queued async updates completes.
72
 *
73
 */
74
const deleteEditedIfIdentical = (
75
  editedRatingGuideQuestions: { [id: number]: RatingGuideQuestion },
76
  ratingGuideQuestion: RatingGuideQuestion,
77
): { [id: number]: RatingGuideQuestion } => {
78
  const { id } = ratingGuideQuestion;
79
  if (
80
    hasKey(editedRatingGuideQuestions, id) &&
81
    isEqual(editedRatingGuideQuestions[id], ratingGuideQuestion)
82
  ) {
83
    return deleteProperty(editedRatingGuideQuestions, id);
84
  }
85
  return editedRatingGuideQuestions;
86
};
87
88
const incrementUpdates = (
89
  updates: { [id: number]: number },
90
  id: number,
91
): { [id: number]: number } => {
92
  const oldVal = hasKey(updates, id) ? updates[id] : 0;
93
  return {
94
    ...updates,
95
    [id]: oldVal + 1,
96
  };
97
};
98
99
const decrementUpdates = (
100
  updates: { [id: number]: number },
101
  id: number,
102
): { [id: number]: number } => {
103
  const oldVal = hasKey(updates, id) ? updates[id] : 0;
104
  const newVal = Math.max(oldVal - 1, 0); // update count cannot be less than 0.
105
  return {
106
    ...updates,
107
    [id]: newVal,
108
  };
109
};
110
111
function hasIdenticalItem<T extends { id: number }>(
112
  items: { [id: number]: T },
113
  item: T,
114
): boolean {
115
  return hasKey(items, item.id) && isEqual(items[item.id], item);
116
}
117
118
const addTempRatingGuideQuestion = (
119
  state: RatingGuideQuestionState,
120
  jobPosterId: number,
121
  assessmentTypeId: number,
122
  newQuestion: string | null,
123
): RatingGuideQuestionState => {
124
  const currentIds = Object.values(state.tempRatingGuideQuestions).map(getId);
125
  const newId = Math.max(...currentIds, 0) + 1;
126
  return {
127
    ...state,
128
    tempRatingGuideQuestions: {
129
      ...state.tempRatingGuideQuestions,
130
      [newId]: {
131
        id: newId,
132
        job_poster_id: jobPosterId,
133
        assessment_type_id: assessmentTypeId,
134
        question: newQuestion,
135
      },
136
    },
137
  };
138
};
139
140
export const ratingGuideQuestionReducer = (
141
  state = initState(),
142
  action: AssessmentPlanAction | RatingGuideQuestionAction,
143
): RatingGuideQuestionState => {
144
  switch (action.type) {
145
    case FETCH_ASSESSMENT_PLAN_STARTED:
146
      return state;
147
    case FETCH_ASSESSMENT_PLAN_SUCCEEEDED:
148
      return {
149
        ...state,
150
        ratingGuideQuestions: {
151
          ...state.ratingGuideQuestions,
152
          ...mapToObject(action.payload.ratingGuideQuestions, getId),
153
        },
154
      };
155
    case FETCH_ASSESSMENT_PLAN_FAILED:
156
      return state;
157
    case EDIT_RATING_GUIDE_QUESTION:
158
      return {
159
        ...state,
160
        editedRatingGuideQuestions: {
161
          ...state.editedRatingGuideQuestions,
162
          [action.payload.ratingGuideQuestion.id]:
163
            action.payload.ratingGuideQuestion,
164
        },
165
      };
166
    case UPDATE_RATING_GUIDE_QUESTION_STARTED:
167
      return {
168
        ...state,
169
        ratingGuideQuestionUpdates: incrementUpdates(
170
          state.ratingGuideQuestionUpdates,
171
          action.payload.ratingGuideQuestion.id,
172
        ),
173
      };
174
    case UPDATE_RATING_GUIDE_QUESTION_SUCCEEDED:
175
      return {
176
        ...state,
177
        ratingGuideQuestions: {
178
          ...state.ratingGuideQuestions,
179
          [action.payload.ratingGuideQuestion.id]:
180
            action.payload.ratingGuideQuestion,
181
        },
182
        editedRatingGuideQuestions: deleteEditedIfIdentical(
183
          state.editedRatingGuideQuestions,
184
          action.payload.ratingGuideQuestion,
185
        ),
186
        ratingGuideQuestionUpdates: decrementUpdates(
187
          state.ratingGuideQuestionUpdates,
188
          action.payload.ratingGuideQuestion.id,
189
        ),
190
      };
191
    case UPDATE_RATING_GUIDE_QUESTION_FAILED:
192
      // TODO: do something with error
193
      // TODO: should the temp state really be deleted?
194
      return {
195
        ...state,
196
        editedRatingGuideQuestions: deleteEditedIfIdentical(
197
          state.editedRatingGuideQuestions,
198
          action.meta,
199
        ),
200
        ratingGuideQuestionUpdates: decrementUpdates(
201
          state.ratingGuideQuestionUpdates,
202
          action.meta.id,
203
        ),
204
      };
205
    case DELETE_RATING_GUIDE_QUESTION_STARTED:
206
      return {
207
        ...state,
208
        ratingGuideQuestionDeletes: incrementUpdates(
209
          state.ratingGuideQuestionDeletes,
210
          action.payload.id,
211
        ),
212
      };
213
    case DELETE_RATING_GUIDE_QUESTION_SUCCEEDED:
214
      // TODO: should this delete both canonical and edited ratingGuideQuestions?
215
      // ...For now, I don't know of any situations where we wouldn't want both.
216
      return {
217
        ...state,
218
        ratingGuideQuestions: deleteProperty(
219
          state.ratingGuideQuestions,
220
          action.payload.id,
221
        ),
222
        editedRatingGuideQuestions: deleteProperty(
223
          state.editedRatingGuideQuestions,
224
          action.payload.id,
225
        ),
226
        ratingGuideQuestionDeletes: decrementUpdates(
227
          state.ratingGuideQuestionDeletes,
228
          action.payload.id,
229
        ),
230
      };
231
    case DELETE_RATING_GUIDE_QUESTION_FAILED:
232
      return {
233
        ...state,
234
        ratingGuideQuestionDeletes: decrementUpdates(
235
          state.ratingGuideQuestionDeletes,
236
          action.meta.id,
237
        ),
238
      };
239
    case CREATE_TEMP_RATING_GUIDE_QUESTION:
240
      return addTempRatingGuideQuestion(
241
        state,
242
        action.payload.jobPosterId,
243
        action.payload.assessmentTypeId,
244
        action.payload.question,
245
      );
246
    case EDIT_TEMP_RATING_GUIDE_QUESTION:
247
      return {
248
        ...state,
249
        tempRatingGuideQuestions: {
250
          ...state.tempRatingGuideQuestions,
251
          [action.payload.ratingGuideQuestion.id]:
252
            action.payload.ratingGuideQuestion,
253
        },
254
      };
255
    case DELETE_TEMP_RATING_GUIDE_QUESTION:
256
      return {
257
        ...state,
258
        tempRatingGuideQuestions: deleteProperty(
259
          state.tempRatingGuideQuestions,
260
          action.payload.id,
261
        ),
262
      };
263
    case STORE_NEW_RATING_GUIDE_QUESTION_STARTED:
264
      return {
265
        ...state,
266
        tempRatingGuideQuestionSaving: {
267
          ...state.tempRatingGuideQuestionSaving,
268
          [action.payload.ratingGuideQuestion.id]: true,
269
        },
270
      };
271
    case STORE_NEW_RATING_GUIDE_QUESTION_SUCCEEDED:
272
      return {
273
        ...state,
274
        ratingGuideQuestions: {
275
          ...state.ratingGuideQuestions,
276
          [action.payload.ratingGuideQuestion.id]:
277
            action.payload.ratingGuideQuestion,
278
        },
279
        tempRatingGuideQuestionSaving: deleteProperty(
280
          state.tempRatingGuideQuestionSaving,
281
          action.payload.oldRatingGuideQuestion.id,
282
        ),
283
        // If temp ratingGuideQuestion differs from saved, move it to edited (with updated id)
284
        // If temp ratingGuideQuestion is equal to new saved, simply remove it from temp.
285
        editedRatingGuideQuestions: hasIdenticalItem(
286
          state.tempRatingGuideQuestions,
287
          action.payload.oldRatingGuideQuestion,
288
        )
289
          ? state.editedRatingGuideQuestions
290
          : {
291
              ...state.editedRatingGuideQuestions,
292
              [action.payload.ratingGuideQuestion.id]: {
293
                ...state.tempRatingGuideQuestions[
294
                  action.payload.oldRatingGuideQuestion.id
295
                ],
296
                id: action.payload.ratingGuideQuestion.id,
297
              },
298
            },
299
        tempRatingGuideQuestions: deleteProperty(
300
          state.tempRatingGuideQuestions,
301
          action.payload.oldRatingGuideQuestion.id,
302
        ),
303
      };
304
    case STORE_NEW_RATING_GUIDE_QUESTION_FAILED:
305
      return {
306
        ...state,
307
        tempRatingGuideQuestionSaving: {
308
          ...state.tempRatingGuideQuestionSaving,
309
          [action.meta.id]: false,
310
        },
311
      };
312
    default:
313
      return state;
314
  }
315
};
316
317
export default ratingGuideQuestionReducer;
318