Passed
Push — task/batch-request-skills-step ( 9a97f0 )
by Yonathan
07:29
created

resources/assets/js/store/Experience/experienceReducer.ts   A

Complexity

Total Complexity 30
Complexity/F 2.14

Size

Lines of Code 627
Function Count 14

Duplication

Duplicated Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
wmc 30
eloc 499
c 0
b 0
f 0
dl 0
loc 627
rs 10
mnd 16
bc 16
fnc 14
bpm 1.1428
cpm 2.1427
noi 0

14 Functions

Rating   Name   Duplication   Size   Complexity  
A experienceReducer.ts ➔ isWork 0 3 1
A experienceReducer.ts ➔ isAward 0 2 1
A experienceReducer.ts ➔ isEducation 0 4 1
A experienceReducer.ts ➔ fetchExperienceByApplicant 0 22 2
A experienceReducer.ts ➔ deleteExperience 0 20 2
A experienceReducer.ts ➔ fetchExperienceByApplication 0 23 2
B experienceReducer.ts ➔ setExperienceSkills 0 50 3
A experienceReducer.ts ➔ deleteBatchExperienceSkills 0 16 1
B experienceReducer.ts ➔ setExperience 0 45 8
A experienceReducer.ts ➔ massageType 0 12 1
A experienceReducer.ts ➔ deleteExperienceSkill 0 17 1
A experienceReducer.ts ➔ isCommunity 0 4 1
A experienceReducer.ts ➔ isPersonal 0 2 1
A experienceReducer.ts ➔ deleteExpSkillsForExperience 0 19 3
1
import { combineReducers } from "redux";
2
import {
3
  ExperienceWork,
4
  ExperienceEducation,
5
  ExperienceCommunity,
6
  ExperienceAward,
7
  ExperiencePersonal,
8
  Experience,
9
  ExperienceSkill,
10
} from "../../models/types";
11
import {
12
  ExperienceAction,
13
  FETCH_EXPERIENCE_BY_APPLICANT_SUCCEEDED,
14
  FETCH_EXPERIENCE_BY_APPLICATION_SUCCEEDED,
15
  CREATE_EXPERIENCE_SUCCEEDED,
16
  FETCH_EXPERIENCE_BY_APPLICANT_STARTED,
17
  FETCH_EXPERIENCE_BY_APPLICANT_FAILED,
18
  FETCH_EXPERIENCE_BY_APPLICATION_STARTED,
19
  FETCH_EXPERIENCE_BY_APPLICATION_FAILED,
20
  UPDATE_EXPERIENCE_STARTED,
21
  UPDATE_EXPERIENCE_SUCCEEDED,
22
  UPDATE_EXPERIENCE_FAILED,
23
  DELETE_EXPERIENCE_STARTED,
24
  DELETE_EXPERIENCE_SUCCEEDED,
25
  DELETE_EXPERIENCE_FAILED,
26
  CREATE_EXPERIENCE_SKILL_SUCCEEDED,
27
  UPDATE_EXPERIENCE_SKILL_SUCCEEDED,
28
  DELETE_EXPERIENCE_SKILL_SUCCEEDED,
29
  UPDATE_EXPERIENCE_SKILL_STARTED,
30
  DELETE_EXPERIENCE_SKILL_STARTED,
31
  UPDATE_EXPERIENCE_SKILL_FAILED,
32
  DELETE_EXPERIENCE_SKILL_FAILED,
33
  BATCH_CREATE_EXPERIENCE_SKILLS_SUCCEEDED,
34
  BATCH_UPDATE_EXPERIENCE_SKILLS_SUCCEEDED,
35
  BATCH_DELETE_EXPERIENCE_SKILLS_SUCCEEDED,
36
} from "./experienceActions";
37
import {
38
  mapToObject,
39
  getId,
40
  uniq,
41
  deleteProperty,
42
  mapObjectValues,
43
  flatten,
44
} from "../../helpers/queries";
45
46
export interface ExperienceSection<T> {
47
  byId: {
48
    [id: number]: T;
49
  };
50
  idsByApplicant: {
51
    [applicantId: number]: number[];
52
  };
53
  idsByApplication: {
54
    [applicationId: number]: number[];
55
  };
56
}
57
58
export interface EntityState {
59
  work: ExperienceSection<ExperienceWork>;
60
  education: ExperienceSection<ExperienceEducation>;
61
  community: ExperienceSection<ExperienceCommunity>;
62
  award: ExperienceSection<ExperienceAward>;
63
  personal: ExperienceSection<ExperiencePersonal>;
64
  experienceSkills: {
65
    byId: { [id: number]: ExperienceSkill };
66
    idsByWork: { [workId: number]: number[] };
67
    idsByEducation: { [educationId: number]: number[] };
68
    idsByCommunity: { [communityId: number]: number[] };
69
    idsByAward: { [awardId: number]: number[] };
70
    idsByPersonal: { [personalId: number]: number[] };
71
  };
72
}
73
74
export interface UiState {
75
  updatingByApplicant: {
76
    [id: number]: boolean;
77
  };
78
  updatingByApplication: {
79
    [id: number]: boolean;
80
  };
81
  updatingByTypeAndId: {
82
    work: {
83
      [id: number]: boolean;
84
    };
85
    education: {
86
      [id: number]: boolean;
87
    };
88
    community: {
89
      [id: number]: boolean;
90
    };
91
    award: {
92
      [id: number]: boolean;
93
    };
94
    personal: {
95
      [id: number]: boolean;
96
    };
97
  };
98
  updatingExperienceSkill: {
99
    [id: number]: boolean;
100
  };
101
}
102
103
export interface ExperienceState {
104
  entities: EntityState;
105
  ui: UiState;
106
}
107
108
export const initEntities = (): EntityState => ({
109
  work: {
110
    byId: {},
111
    idsByApplicant: {},
112
    idsByApplication: {},
113
  },
114
  education: {
115
    byId: {},
116
    idsByApplicant: {},
117
    idsByApplication: {},
118
  },
119
  community: {
120
    byId: {},
121
    idsByApplicant: {},
122
    idsByApplication: {},
123
  },
124
  award: {
125
    byId: {},
126
    idsByApplicant: {},
127
    idsByApplication: {},
128
  },
129
  personal: {
130
    byId: {},
131
    idsByApplicant: {},
132
    idsByApplication: {},
133
  },
134
  experienceSkills: {
135
    byId: {},
136
    idsByWork: {},
137
    idsByEducation: {},
138
    idsByCommunity: {},
139
    idsByAward: {},
140
    idsByPersonal: {},
141
  },
142
});
143
144
export const initUi = (): UiState => ({
145
  updatingByApplicant: {},
146
  updatingByApplication: {},
147
  updatingByTypeAndId: {
148
    work: {},
149
    education: {},
150
    community: {},
151
    award: {},
152
    personal: {},
153
  },
154
  updatingExperienceSkill: {},
155
});
156
157
export const initExperienceState = (): ExperienceState => ({
158
  entities: initEntities(),
159
  ui: initUi(),
160
});
161
162
function isWork(experience: Experience): experience is ExperienceWork {
163
  return experience.type === "experience_work";
164
}
165
function isEducation(
166
  experience: Experience,
167
): experience is ExperienceEducation {
168
  return experience.type === "experience_education";
169
}
170
function isCommunity(
171
  experience: Experience,
172
): experience is ExperienceCommunity {
173
  return experience.type === "experience_community";
174
}
175
function isAward(experience: Experience): experience is ExperienceAward {
176
  return experience.type === "experience_award";
177
}
178
function isPersonal(experience: Experience): experience is ExperiencePersonal {
179
  return experience.type === "experience_personal";
180
}
181
182
type EntityType = "work" | "education" | "community" | "award" | "personal";
183
184
const experienceTypeGuards = {
185
  work: isWork,
186
  education: isEducation,
187
  community: isCommunity,
188
  award: isAward,
189
  personal: isPersonal,
190
};
191
192
function massageType(experienceType: Experience["type"]): EntityType {
193
  /* eslint-disable @typescript-eslint/camelcase */
194
  const mapping: { [key in Experience["type"]]: EntityType } = {
195
    experience_work: "work",
196
    experience_education: "education",
197
    experience_community: "community",
198
    experience_award: "award",
199
    experience_personal: "personal",
200
  };
201
  /* eslint-enable @typescript-eslint/camelcase */
202
  return mapping[experienceType];
203
}
204
205
function fetchExperienceByApplication<T extends EntityType>(
206
  state: EntityState,
207
  action: ExperienceAction,
208
  type: T,
209
): EntityState[T] {
210
  const subState = state[type];
211
  if (action.type !== FETCH_EXPERIENCE_BY_APPLICATION_SUCCEEDED) {
212
    return subState;
213
  }
214
  const typeFilter = experienceTypeGuards[type];
215
  const experiences = action.payload
216
    .map((response) => response.experience)
217
    .filter(typeFilter);
218
  return {
219
    ...subState,
220
    byId: {
221
      ...subState.byId,
222
      ...mapToObject(experiences, getId),
223
    },
224
    idsByApplication: {
225
      ...subState.idsByApplicant,
226
      [action.meta.applicationId]: experiences.map(getId),
227
    },
228
  };
229
}
230
function fetchExperienceByApplicant<T extends EntityType>(
231
  state: EntityState,
232
  action: ExperienceAction,
233
  type: T,
234
): EntityState[T] {
235
  const subState = state[type];
236
  if (action.type !== FETCH_EXPERIENCE_BY_APPLICANT_SUCCEEDED) {
237
    return subState;
238
  }
239
  const typeFilter = experienceTypeGuards[type];
240
  const experiences = action.payload
241
    .map((response) => response.experience)
242
    .filter(typeFilter);
243
  return {
244
    ...subState,
245
    byId: {
246
      ...subState.byId,
247
      ...mapToObject(experiences, getId),
248
    },
249
    idsByApplicant: {
250
      ...subState.idsByApplicant,
251
      [action.meta.applicantId]: experiences.map(getId),
252
    },
253
  };
254
}
255
256
function setExperience<T extends EntityType>(
257
  state: EntityState,
258
  action: ExperienceAction,
259
  type: T,
260
): EntityState[T] {
261
  const subState = state[type];
262
  if (
263
    (action.type !== CREATE_EXPERIENCE_SUCCEEDED &&
264
      action.type !== UPDATE_EXPERIENCE_SUCCEEDED) ||
265
    massageType(action.meta.type) !== type
266
  ) {
267
    return subState;
268
  }
269
  const { experience } = action.payload;
270
  const ownerId = experience.experienceable_id;
271
  const idsByApplicant =
272
    experience.experienceable_type === "applicant"
273
      ? {
274
          ...subState.idsByApplicant,
275
          [ownerId]: uniq([
276
            ...(subState.idsByApplicant[ownerId] ?? []),
277
            experience.id,
278
          ]),
279
        }
280
      : subState.idsByApplicant;
281
  const idsByApplication =
282
    experience.experienceable_type === "application"
283
      ? {
284
          ...subState.idsByApplication,
285
          [ownerId]: uniq([
286
            ...(subState.idsByApplication[ownerId] ?? []),
287
            experience.id,
288
          ]),
289
        }
290
      : subState.idsByApplication;
291
292
  return {
293
    ...subState,
294
    byId: {
295
      ...subState.byId,
296
      [experience.id]: experience,
297
    },
298
    idsByApplicant,
299
    idsByApplication,
300
  };
301
}
302
303
function deleteExperience<T extends EntityType>(
304
  state: EntityState,
305
  action: ExperienceAction,
306
  type: T,
307
): EntityState[T] {
308
  const subState = state[type];
309
  if (
310
    action.type !== DELETE_EXPERIENCE_SUCCEEDED ||
311
    massageType(action.meta.type) !== type
312
  ) {
313
    return subState;
314
  }
315
  const dropId = (ids: number[]): number[] =>
316
    ids.filter((id) => id !== action.meta.id);
317
  return {
318
    ...subState,
319
    byId: deleteProperty(subState.byId, action.meta.id),
320
    idsByApplicant: mapObjectValues(subState.idsByApplicant, dropId),
321
    idsByApplication: mapObjectValues(subState.idsByApplication, dropId),
322
  };
323
}
324
325
function setExperienceSkills(
326
  state: EntityState,
327
  experienceSkills: ExperienceSkill[],
328
): EntityState["experienceSkills"] {
329
  const newExpSkills = mapToObject(experienceSkills, getId);
330
  const workSkills = experienceSkills.filter(
331
    (expSkill) => expSkill.experience_type === "experience_work",
332
  );
333
  const educationSkills = experienceSkills.filter(
334
    (expSkill) => expSkill.experience_type === "experience_education",
335
  );
336
  const communitySkills = experienceSkills.filter(
337
    (expSkill) => expSkill.experience_type === "experience_community",
338
  );
339
  const awardSkills = experienceSkills.filter(
340
    (expSkill) => expSkill.experience_type === "experience_award",
341
  );
342
  const personalSkills = experienceSkills.filter(
343
    (expSkill) => expSkill.experience_type === "experience_personal",
344
  );
345
346
  interface ExpToSkillIds {
347
    [expId: number]: number[];
348
  }
349
  const reducer = (
350
    acc: ExpToSkillIds,
351
    expSkill: ExperienceSkill,
352
  ): ExpToSkillIds => {
353
    const prevIds = acc[expSkill.experience_id] ?? [];
354
    return {
355
      ...acc,
356
      [expSkill.experience_id]: uniq([expSkill.id, ...prevIds]),
357
    };
358
  };
359
  return {
360
    byId: { ...state.experienceSkills.byId, ...newExpSkills },
361
    idsByWork: workSkills.reduce(reducer, state.experienceSkills.idsByWork),
362
    idsByEducation: educationSkills.reduce(
363
      reducer,
364
      state.experienceSkills.idsByEducation,
365
    ),
366
    idsByCommunity: communitySkills.reduce(
367
      reducer,
368
      state.experienceSkills.idsByCommunity,
369
    ),
370
    idsByAward: awardSkills.reduce(reducer, state.experienceSkills.idsByAward),
371
    idsByPersonal: personalSkills.reduce(
372
      reducer,
373
      state.experienceSkills.idsByPersonal,
374
    ),
375
  };
376
}
377
378
/* eslint-disable @typescript-eslint/camelcase */
379
const experienceSkillKeys = {
380
  experience_work: "idsByWork",
381
  experience_education: "idsByEducation",
382
  experience_community: "idsByCommunity",
383
  experience_award: "idsByAward",
384
  experience_personal: "idsByPersonal",
385
};
386
/* eslint-enable @typescript-eslint/camelcase */
387
388
function deleteExpSkillsForExperience(
389
  state: EntityState,
390
  experienceId: number,
391
  experienceType: Experience["type"],
392
): EntityState["experienceSkills"] {
393
  const experienceKey = experienceSkillKeys[experienceType];
394
  const expSkillIds: number[] =
395
    state.experienceSkills[experienceKey][experienceId] ?? [];
396
  return {
397
    ...state.experienceSkills,
398
    [experienceKey]: deleteProperty(
399
      state.experienceSkills[experienceKey],
400
      experienceId,
401
    ),
402
    byId: expSkillIds.reduce(
403
      (byId, deleteId) => deleteProperty(byId, deleteId),
404
      state.experienceSkills.byId,
405
    ),
406
  };
407
}
408
409
function deleteExperienceSkill(
410
  state: EntityState,
411
  experienceSkillId: number,
412
  experienceId: number,
413
  experienceType: ExperienceSkill["experience_type"],
414
): EntityState["experienceSkills"] {
415
  const experienceKey = experienceSkillKeys[experienceType];
416
  return {
417
    ...state.experienceSkills,
418
    [experienceKey]: {
419
      ...state.experienceSkills[experienceKey],
420
      [experienceId]: state.experienceSkills[experienceKey][
421
        experienceId
422
      ].filter((id) => id !== experienceSkillId),
423
    },
424
    byId: deleteProperty(state.experienceSkills.byId, experienceSkillId),
425
  };
426
}
427
428
function deleteBatchExperienceSkills(
429
  state: EntityState,
430
  experienceSkills: ExperienceSkill[],
431
): EntityState {
432
  return experienceSkills.reduce((newState, expSkill) => {
433
    return {
434
      ...newState,
435
      experienceSkills: deleteExperienceSkill(
436
        newState,
437
        expSkill.id,
438
        expSkill.experience_id,
439
        expSkill.experience_type,
440
      ),
441
    };
442
  }, state);
443
}
444
445
export const entitiesReducer = (
446
  state = initEntities(),
447
  action: ExperienceAction,
448
): EntityState => {
449
  switch (action.type) {
450
    case FETCH_EXPERIENCE_BY_APPLICANT_SUCCEEDED:
451
      return {
452
        ...state,
453
        work: fetchExperienceByApplicant(state, action, "work"),
454
        education: fetchExperienceByApplicant(state, action, "education"),
455
        community: fetchExperienceByApplicant(state, action, "community"),
456
        award: fetchExperienceByApplicant(state, action, "award"),
457
        personal: fetchExperienceByApplicant(state, action, "personal"),
458
        experienceSkills: setExperienceSkills(
459
          state,
460
          flatten(action.payload.map((response) => response.experienceSkills)),
461
        ),
462
      };
463
    case FETCH_EXPERIENCE_BY_APPLICATION_SUCCEEDED:
464
      return {
465
        ...state,
466
        work: fetchExperienceByApplication(state, action, "work"),
467
        education: fetchExperienceByApplication(state, action, "education"),
468
        community: fetchExperienceByApplication(state, action, "community"),
469
        award: fetchExperienceByApplication(state, action, "award"),
470
        personal: fetchExperienceByApplication(state, action, "personal"),
471
        experienceSkills: setExperienceSkills(
472
          state,
473
          flatten(action.payload.map((response) => response.experienceSkills)),
474
        ),
475
      };
476
    case CREATE_EXPERIENCE_SUCCEEDED:
477
    case UPDATE_EXPERIENCE_SUCCEEDED:
478
      return {
479
        ...state,
480
        [massageType(action.meta.type)]: setExperience(
481
          state,
482
          action,
483
          massageType(action.meta.type),
484
        ),
485
      };
486
    case DELETE_EXPERIENCE_SUCCEEDED:
487
      return {
488
        ...state,
489
        [massageType(action.meta.type)]: deleteExperience(
490
          state,
491
          action,
492
          massageType(action.meta.type),
493
        ),
494
        experienceSkills: deleteExpSkillsForExperience(
495
          state,
496
          action.meta.id,
497
          action.meta.type,
498
        ),
499
      };
500
    case CREATE_EXPERIENCE_SKILL_SUCCEEDED:
501
    case UPDATE_EXPERIENCE_SKILL_SUCCEEDED:
502
      return {
503
        ...state,
504
        experienceSkills: setExperienceSkills(state, [action.payload]),
505
      };
506
    case DELETE_EXPERIENCE_SKILL_SUCCEEDED:
507
      return {
508
        ...state,
509
        experienceSkills: deleteExperienceSkill(
510
          state,
511
          action.meta.id,
512
          action.meta.experienceId,
513
          action.meta.experienceType,
514
        ),
515
      };
516
    case BATCH_CREATE_EXPERIENCE_SKILLS_SUCCEEDED:
517
    case BATCH_UPDATE_EXPERIENCE_SKILLS_SUCCEEDED:
518
      return {
519
        ...state,
520
        experienceSkills: setExperienceSkills(state, action.payload),
521
      };
522
    case BATCH_DELETE_EXPERIENCE_SKILLS_SUCCEEDED:
523
      return {
524
        ...deleteBatchExperienceSkills(state, action.meta),
525
      };
526
    default:
527
      return state;
528
  }
529
};
530
531
export const uiReducer = (
532
  state = initUi(),
533
  action: ExperienceAction,
534
): UiState => {
535
  switch (action.type) {
536
    case FETCH_EXPERIENCE_BY_APPLICANT_STARTED:
537
      return {
538
        ...state,
539
        updatingByApplicant: {
540
          ...state.updatingByApplicant,
541
          [action.meta.applicantId]: true,
542
        },
543
      };
544
    case FETCH_EXPERIENCE_BY_APPLICANT_SUCCEEDED:
545
    case FETCH_EXPERIENCE_BY_APPLICANT_FAILED:
546
      return {
547
        ...state,
548
        updatingByApplicant: {
549
          ...state.updatingByApplicant,
550
          [action.meta.applicantId]: false,
551
        },
552
      };
553
    case FETCH_EXPERIENCE_BY_APPLICATION_STARTED:
554
      return {
555
        ...state,
556
        updatingByApplication: {
557
          ...state.updatingByApplication,
558
          [action.meta.applicationId]: true,
559
        },
560
      };
561
    case FETCH_EXPERIENCE_BY_APPLICATION_SUCCEEDED:
562
    case FETCH_EXPERIENCE_BY_APPLICATION_FAILED:
563
      return {
564
        ...state,
565
        updatingByApplication: {
566
          ...state.updatingByApplication,
567
          [action.meta.applicationId]: false,
568
        },
569
      };
570
    case UPDATE_EXPERIENCE_STARTED:
571
    case DELETE_EXPERIENCE_STARTED:
572
      return {
573
        ...state,
574
        updatingByTypeAndId: {
575
          ...state.updatingByTypeAndId,
576
          [massageType(action.meta.type)]: {
577
            ...state.updatingByTypeAndId[massageType(action.meta.type)],
578
            [action.meta.id]: true,
579
          },
580
        },
581
      };
582
    case UPDATE_EXPERIENCE_SUCCEEDED:
583
    case DELETE_EXPERIENCE_SUCCEEDED:
584
    case UPDATE_EXPERIENCE_FAILED:
585
    case DELETE_EXPERIENCE_FAILED:
586
      return {
587
        ...state,
588
        updatingByTypeAndId: {
589
          ...state.updatingByTypeAndId,
590
          [massageType(action.meta.type)]: {
591
            ...state.updatingByTypeAndId[massageType(action.meta.type)],
592
            [action.meta.id]: false,
593
          },
594
        },
595
      };
596
    case UPDATE_EXPERIENCE_SKILL_STARTED:
597
    case DELETE_EXPERIENCE_SKILL_STARTED:
598
      return {
599
        ...state,
600
        updatingExperienceSkill: {
601
          ...state.updatingExperienceSkill,
602
          [action.meta.id]: true,
603
        },
604
      };
605
    case UPDATE_EXPERIENCE_SKILL_SUCCEEDED:
606
    case UPDATE_EXPERIENCE_SKILL_FAILED:
607
    case DELETE_EXPERIENCE_SKILL_SUCCEEDED:
608
    case DELETE_EXPERIENCE_SKILL_FAILED:
609
      return {
610
        ...state,
611
        updatingExperienceSkill: {
612
          ...state.updatingExperienceSkill,
613
          [action.meta.id]: false,
614
        },
615
      };
616
    default:
617
      return state;
618
  }
619
};
620
621
export const experienceReducer = combineReducers({
622
  entities: entitiesReducer,
623
  ui: uiReducer,
624
});
625
626
export default experienceReducer;
627