Passed
Push — dev ( dd051f...65c6f9 )
by
unknown
06:06
created

resources/assets/js/store/Job/jobSelectorComplex.ts   A

Complexity

Total Complexity 6
Complexity/F 0

Size

Lines of Code 208
Function Count 0

Duplication

Duplicated Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
wmc 6
eloc 160
mnd 6
bc 6
fnc 0
dl 0
loc 208
rs 10
bpm 0
cpm 0
noi 0
c 0
b 0
f 0
1
import createCachedSelector from "re-reselect";
2
import uniq from "lodash/uniq";
3
import difference from "lodash/difference";
4
import intersection from "lodash/intersection";
5
import { createSelector } from "reselect";
6
import {
7
  getCriteriaIdsByJob,
8
  getCriteriaState,
9
  getCriteria,
10
} from "./jobSelector";
11
import {
12
  getRatingGuideAnswersByAssessment,
13
  getCurrentRatingGuideAnswers,
14
  getTempRatingGuideAnswers,
15
} from "../RatingGuideAnswer/ratingGuideAnswerSelectors";
16
import {
17
  RatingGuideAnswer,
18
  RatingGuideQuestion,
19
  Criteria,
20
  Skill,
21
} from "../../models/types";
22
import { RootState } from "../store";
23
import {
24
  getCurrentRatingGuideQuestionById,
25
  getTempRatingGuideQuestionById,
26
} from "../RatingGuideQuestion/ratingGuideQuestionSelectors";
27
import {
28
  getCurrentAssessments,
29
  getAssessmentsByType,
30
} from "../Assessment/assessmentSelector";
31
import {
32
  hasKey,
33
  notEmpty,
34
  mapToObjectTrans,
35
  getId,
36
} from "../../helpers/queries";
37
import { getSkillState } from "../Skill/skillSelector";
38
import { deepEqualSelectorOptions } from "../cachedSelectors";
39
40
/**
41
 * This file is for defining selectors that depend on selectors in other modules,
42
 * to avoid circular dependencies
43
 */
44
45
// CRITERIA SELECTORS
46
47
export const getCriteriaIdsByJobAndAssessmentType = createCachedSelector(
48
  getCriteriaIdsByJob,
49
  getAssessmentsByType,
50
  (jobCriteriaIds, assessments): number[] => {
51
    const assessmentCriteriaIds = assessments.map(
52
      (assessment): number => assessment.criterion_id,
53
    );
54
    return intersection(jobCriteriaIds, assessmentCriteriaIds);
55
  },
56
)((state, props): string => `${props.jobId}:${props.assessmentTypeId}`);
57
58
export const getCriteriaByJobAndAssessmentType = createCachedSelector(
59
  getCriteriaIdsByJobAndAssessmentType,
60
  getCriteriaState,
61
  (criteriaIds: number[], criteriaState): Criteria[] =>
62
    criteriaIds
63
      .map((id: number): Criteria | null =>
64
        hasKey(criteriaState, id) ? criteriaState[id] : null,
65
      )
66
      .filter(notEmpty),
67
)(
68
  (state, props): string =>
69
    `${props.assessmentTypeId}:${props.assessmentTypeId}`,
70
);
71
72
/**
73
 * Select a job's Criteria associated with an assessmentType, that
74
 * don't have a matching RatingGuideAnswer yet.
75
 * @param state
76
 * @param questionId RatingGuideQuestion id
77
 */
78
export const getCriteriaIdsUnansweredForAssessmentType = createCachedSelector(
79
  getCriteriaIdsByJobAndAssessmentType,
80
  getRatingGuideAnswersByAssessment,
81
  (requiredCriteriaIds, assessmentAnswers): number[] => {
82
    const answeredCriteriaIds: number[] = uniq(
83
      assessmentAnswers
84
        .map((answer: RatingGuideAnswer): number | null => answer.criterion_id)
85
        .filter(notEmpty),
86
    );
87
    return difference(requiredCriteriaIds, answeredCriteriaIds);
88
  },
89
)(
90
  (state, props): string =>
91
    `${props.assessmentTypeId} ${props.assessmentTypeId}`,
92
);
93
94
/**
95
 * Select all the Criteria associated with a RatingGuideQuestion's assessmentType, that
96
 * don't have a matching RatingGuideAnswer yet.
97
 */
98
export const getCriteriaUnansweredForQuestion = createCachedSelector(
99
  (
100
    state: RootState,
101
    props: {
102
      questionId: number;
103
      isTempQuestion: boolean;
104
    },
105
  ): RatingGuideQuestion | null =>
106
    getCurrentRatingGuideQuestionById(state, props.questionId),
107
  (
108
    state: RootState,
109
    props: {
110
      questionId: number;
111
      isTempQuestion: boolean;
112
    },
113
  ): RatingGuideQuestion | null =>
114
    getTempRatingGuideQuestionById(state, props.questionId),
115
  (state, props: { questionId: number; isTempQuestion: boolean }): boolean =>
116
    props.isTempQuestion,
117
  getCurrentAssessments,
118
  getCriteriaState,
119
  getCurrentRatingGuideAnswers,
120
  getTempRatingGuideAnswers,
121
  (
122
    currentQuestion,
123
    tempQuestion,
124
    isTempQuestion,
125
    assessments,
126
    criteriaState,
127
    answers,
128
    tempAnswers,
129
  ): Criteria[] => {
130
    const question = isTempQuestion ? tempQuestion : currentQuestion;
131
    if (question === null) {
132
      return [];
133
    }
134
    // All the assessments this question may test - BUT FROM ANY JOB
135
    const questionTypeAssessments = assessments.filter(
136
      (assessment): boolean =>
137
        assessment.assessment_type_id === question.assessment_type_id,
138
    );
139
140
    // All the criteria this question may test -
141
    const assessmentCriteria: Criteria[] = questionTypeAssessments
142
      .map((assessment): Criteria | null =>
143
        hasKey(criteriaState, assessment.criterion_id)
144
          ? criteriaState[assessment.criterion_id]
145
          : null,
146
      )
147
      .filter(notEmpty)
148
      .filter(
149
        (criterion): boolean =>
150
          criterion.job_poster_id === question.job_poster_id,
151
      );
152
153
    const questionCurrentAnswers = answers.filter(
154
      (answer): boolean => answer.rating_guide_question_id === question.id,
155
    );
156
    const questionTempAnswers = tempAnswers.filter(
157
      (answer): boolean => answer.rating_guide_question_id === question.id,
158
    );
159
160
    // All the current answers to this question
161
    const questionAnswers = questionCurrentAnswers.concat(questionTempAnswers);
162
163
    // The criteria already selected by an answer
164
    const selectedCriteriaIds: number[] = questionAnswers
165
      .map((a): number | null => a.criterion_id)
166
      .filter(notEmpty);
167
168
    // Filter out already selected answers
169
    return assessmentCriteria.filter(
170
      (criterion): boolean => !selectedCriteriaIds.includes(criterion.id),
171
    );
172
  },
173
)((state, props): string => `${props.questionId} ${props.isTempQuestion}`);
174
175
export const getCachedCriteriaUnansweredForQuestion = createCachedSelector(
176
  getCriteriaUnansweredForQuestion,
177
  (criteria): Criteria[] => criteria,
178
)({
179
  keySelector: (state, props): string =>
180
    `${props.questionId} ${props.isTempQuestion}`,
181
  ...deepEqualSelectorOptions,
182
});
183
184
export const getCriteriaUnansweredForAssessmentType = createCachedSelector(
185
  getCriteriaIdsUnansweredForAssessmentType,
186
  getCriteriaState,
187
  (criteriaIds: number[], criteriaState): Criteria[] =>
188
    criteriaIds
189
      .map((id: number): Criteria | null =>
190
        hasKey(criteriaState, id) ? criteriaState[id] : null,
191
      )
192
      .filter(notEmpty),
193
)(
194
  (state, props): string =>
195
    `${props.assessmentTypeId} ${props.assessmentTypeId}`,
196
);
197
198
export const getCriteriaToSkills = createSelector(
199
  getSkillState,
200
  getCriteria,
201
  (skillState, criteria): { [criteriaId: number]: Skill | null } =>
202
    mapToObjectTrans(criteria, getId, (criterion): Skill | null =>
203
      hasKey(skillState, criterion.skill_id)
204
        ? skillState[criterion.skill_id]
205
        : null,
206
    ),
207
);
208