|
1
|
|
|
import isEqual from "lodash/isEqual"; |
|
2
|
|
|
import { createSelector } from "reselect"; |
|
3
|
|
|
import createCachedSelector from "re-reselect"; |
|
4
|
|
|
import { RootState } from "../store"; |
|
5
|
|
|
import { RatingGuideQuestionState } from "./ratingGuideQuestionReducer"; |
|
6
|
|
|
import { RatingGuideQuestion } from "../../models/types"; |
|
7
|
|
|
import { getId, hasKey, mapToObjectTrans } from "../../helpers/queries"; |
|
8
|
|
|
import { deepEqualSelectorOptions } from "../cachedSelectors"; |
|
9
|
|
|
|
|
10
|
|
|
const stateSlice = (state: RootState): RatingGuideQuestionState => |
|
11
|
|
|
state.ratingGuideQuestion; |
|
12
|
|
|
|
|
13
|
|
|
const getCanonQuestionState = ( |
|
14
|
|
|
state: RootState, |
|
15
|
|
|
): { [id: number]: RatingGuideQuestion } => |
|
16
|
|
|
stateSlice(state).ratingGuideQuestions; |
|
17
|
|
|
|
|
18
|
|
|
const getTempQuestionState = ( |
|
19
|
|
|
state: RootState, |
|
20
|
|
|
): { [id: number]: RatingGuideQuestion } => |
|
21
|
|
|
stateSlice(state).tempRatingGuideQuestions; |
|
22
|
|
|
|
|
23
|
|
|
const getEditedQuestionState = ( |
|
24
|
|
|
state: RootState, |
|
25
|
|
|
): { [id: number]: RatingGuideQuestion } => |
|
26
|
|
|
stateSlice(state).editedRatingGuideQuestions; |
|
27
|
|
|
|
|
28
|
|
|
const getQuestionDeletes = (state: RootState): { [id: number]: number } => |
|
29
|
|
|
stateSlice(state).ratingGuideQuestionDeletes; |
|
30
|
|
|
|
|
31
|
|
|
const getTempQuestionSaving = (state: RootState): { [id: number]: boolean } => |
|
32
|
|
|
stateSlice(state).tempRatingGuideQuestionSaving; |
|
33
|
|
|
|
|
34
|
|
|
const getQuestionUpdates = (state: RootState): { [id: number]: number } => |
|
35
|
|
|
stateSlice(state).ratingGuideQuestionUpdates; |
|
36
|
|
|
|
|
37
|
|
|
const getCurrentQuestionState = createSelector( |
|
38
|
|
|
getCanonQuestionState, |
|
39
|
|
|
getEditedQuestionState, |
|
40
|
|
|
(canonQuestions, editedQuestions): { [id: number]: RatingGuideQuestion } => ({ |
|
41
|
|
|
...canonQuestions, |
|
42
|
|
|
...editedQuestions, |
|
43
|
|
|
}), |
|
44
|
|
|
); |
|
45
|
|
|
|
|
46
|
|
|
/** |
|
47
|
|
|
* Returns current versions of all assessments. |
|
48
|
|
|
* ie edited version if possible, |
|
49
|
|
|
* and not including those undergoing delete requests |
|
50
|
|
|
*/ |
|
51
|
|
|
export const getCurrentRatingGuideQuestions = createSelector( |
|
52
|
|
|
[getCanonQuestionState, getEditedQuestionState, getQuestionDeletes], |
|
53
|
|
|
(questionState, editedQuestionState, deleteCount): RatingGuideQuestion[] => { |
|
54
|
|
|
const currentRatingGuideQuestions = { |
|
55
|
|
|
...questionState, |
|
56
|
|
|
...editedQuestionState, |
|
57
|
|
|
}; |
|
58
|
|
|
return Object.values(currentRatingGuideQuestions).filter( |
|
59
|
|
|
(ratingGuideQuestion): boolean => |
|
60
|
|
|
!hasKey(deleteCount, ratingGuideQuestion.id) || |
|
61
|
|
|
deleteCount[ratingGuideQuestion.id] <= 0, |
|
62
|
|
|
); |
|
63
|
|
|
}, |
|
64
|
|
|
); |
|
65
|
|
|
|
|
66
|
|
|
export const getTempRatingGuideQuestions = createSelector( |
|
67
|
|
|
getTempQuestionState, |
|
68
|
|
|
(tempQuestionState): RatingGuideQuestion[] => |
|
69
|
|
|
Object.values(tempQuestionState), |
|
70
|
|
|
); |
|
71
|
|
|
|
|
72
|
|
|
export const getRatingGuideQuestionIds = createSelector( |
|
73
|
|
|
getCurrentQuestionState, |
|
74
|
|
|
(currentQuestions): number[] => |
|
75
|
|
|
Object.keys(currentQuestions).map((id) => Number(id)), |
|
76
|
|
|
); |
|
77
|
|
|
|
|
78
|
|
|
export const getTempRatingGuideQuestionIds = createSelector( |
|
79
|
|
|
getTempQuestionState, |
|
80
|
|
|
(tempQuestions): number[] => |
|
81
|
|
|
Object.keys(tempQuestions).map((id) => Number(id)), |
|
82
|
|
|
); |
|
83
|
|
|
|
|
84
|
|
|
/** |
|
85
|
|
|
* Returns edited version, if available |
|
86
|
|
|
* */ |
|
87
|
|
|
export const getCurrentRatingGuideQuestionById = createSelector( |
|
88
|
|
|
getCurrentQuestionState, |
|
89
|
|
|
(state: RootState, id: number) => id, |
|
90
|
|
|
(questions, id): RatingGuideQuestion | null => |
|
91
|
|
|
hasKey(questions, id) ? questions[id] : null, |
|
92
|
|
|
); |
|
93
|
|
|
|
|
94
|
|
|
export const getCanonRatingGuideQuestionById = createCachedSelector( |
|
95
|
|
|
getCanonQuestionState, |
|
96
|
|
|
(state: RootState, id: number): number => id, |
|
97
|
|
|
(questions, id): RatingGuideQuestion | null => |
|
98
|
|
|
hasKey(questions, id) ? questions[id] : null, |
|
99
|
|
|
)((state, id) => id); |
|
100
|
|
|
|
|
101
|
|
|
export const getEditRatingGuideQuestionById = createCachedSelector( |
|
102
|
|
|
getEditedQuestionState, |
|
103
|
|
|
(state: RootState, id: number): number => id, |
|
104
|
|
|
(questions, id): RatingGuideQuestion | null => |
|
105
|
|
|
hasKey(questions, id) ? questions[id] : null, |
|
106
|
|
|
)((state, id) => id); |
|
107
|
|
|
|
|
108
|
|
|
export const getTempRatingGuideQuestionById = createCachedSelector( |
|
109
|
|
|
getTempQuestionState, |
|
110
|
|
|
(state: RootState, id: number): number => id, |
|
111
|
|
|
(questions, id): RatingGuideQuestion | null => |
|
112
|
|
|
hasKey(questions, id) ? questions[id] : null, |
|
113
|
|
|
)((state, id) => id); |
|
114
|
|
|
|
|
115
|
|
|
export const getRatingGuideQuestionsByJob = createCachedSelector( |
|
116
|
|
|
getCurrentRatingGuideQuestions, |
|
117
|
|
|
(state: RootState, props: { jobId: number }): number => props.jobId, |
|
118
|
|
|
(questions, jobId): RatingGuideQuestion[] => |
|
119
|
|
|
questions.filter((question): boolean => question.job_poster_id === jobId), |
|
120
|
|
|
)((state, props): number => props.jobId); |
|
121
|
|
|
|
|
122
|
|
|
export const getRatingGuideQuestionIdsByJob = createCachedSelector( |
|
123
|
|
|
getRatingGuideQuestionsByJob, |
|
124
|
|
|
(questions): number[] => questions.map(getId), |
|
125
|
|
|
)((state, props): number => props.jobId); |
|
126
|
|
|
|
|
127
|
|
|
export const getRatingGuideQuestionsByJobAndAssessmentType = createCachedSelector( |
|
128
|
|
|
getCurrentRatingGuideQuestions, |
|
129
|
|
|
( |
|
130
|
|
|
state: RootState, |
|
131
|
|
|
props: { jobId: number; assessmentTypeId: number }, |
|
132
|
|
|
): number => props.jobId, |
|
133
|
|
|
( |
|
134
|
|
|
state: RootState, |
|
135
|
|
|
props: { jobId: number; assessmentTypeId: number }, |
|
136
|
|
|
): number => props.assessmentTypeId, |
|
137
|
|
|
(questions, jobId, assessmentTypeId): RatingGuideQuestion[] => |
|
138
|
|
|
questions.filter( |
|
139
|
|
|
(question): boolean => |
|
140
|
|
|
question.job_poster_id === jobId && |
|
141
|
|
|
question.assessment_type_id === assessmentTypeId, |
|
142
|
|
|
), |
|
143
|
|
|
)((state, props): string => `${props.jobId} ${props.assessmentTypeId}`); |
|
144
|
|
|
|
|
145
|
|
|
export const getRatingGuideQuestionIdsByJobAndAssessmentType = createCachedSelector( |
|
146
|
|
|
( |
|
147
|
|
|
state: RootState, |
|
148
|
|
|
props: { jobId: number; assessmentTypeId: number }, |
|
149
|
|
|
): number[] => |
|
150
|
|
|
getRatingGuideQuestionsByJobAndAssessmentType(state, props).map(getId), |
|
151
|
|
|
(questionsIds): number[] => questionsIds, |
|
152
|
|
|
)({ |
|
153
|
|
|
keySelector: (state, props): string => |
|
154
|
|
|
`${props.jobId} ${props.assessmentTypeId}`, |
|
155
|
|
|
...deepEqualSelectorOptions, |
|
156
|
|
|
}); |
|
157
|
|
|
|
|
158
|
|
|
export const getTempRatingGuideQuestionsByAssessment = createCachedSelector( |
|
159
|
|
|
getTempRatingGuideQuestions, |
|
160
|
|
|
( |
|
161
|
|
|
state: RootState, |
|
162
|
|
|
props: { jobId: number; assessmentTypeId: number }, |
|
163
|
|
|
): number => props.jobId, |
|
164
|
|
|
( |
|
165
|
|
|
state: RootState, |
|
166
|
|
|
props: { jobId: number; assessmentTypeId: number }, |
|
167
|
|
|
): number => props.assessmentTypeId, |
|
168
|
|
|
(questions, jobId, assessmentTypeId): RatingGuideQuestion[] => |
|
169
|
|
|
questions.filter( |
|
170
|
|
|
(question): boolean => |
|
171
|
|
|
question.job_poster_id === jobId && |
|
172
|
|
|
question.assessment_type_id === assessmentTypeId, |
|
173
|
|
|
), |
|
174
|
|
|
)((state, props): string => `${props.jobId} ${props.assessmentTypeId}`); |
|
175
|
|
|
|
|
176
|
|
|
export const getTempRatingGuideQuestionIdsByAssessment = createCachedSelector( |
|
177
|
|
|
( |
|
178
|
|
|
state: RootState, |
|
179
|
|
|
props: { jobId: number; assessmentTypeId: number }, |
|
180
|
|
|
): number[] => |
|
181
|
|
|
getTempRatingGuideQuestionsByAssessment(state, props).map(getId), |
|
182
|
|
|
(questionsIds): number[] => questionsIds, |
|
183
|
|
|
)({ |
|
184
|
|
|
keySelector: (state, props): string => |
|
185
|
|
|
`${props.jobId} ${props.assessmentTypeId}`, |
|
186
|
|
|
...deepEqualSelectorOptions, |
|
187
|
|
|
}); |
|
188
|
|
|
|
|
189
|
|
|
// TODO: test that this works like I think it does -- Tristan |
|
190
|
|
|
/** Returns true if there is an edited verision which differs from canonical version */ |
|
191
|
|
|
export const ratingGuideQuestionIsEdited = createCachedSelector( |
|
192
|
|
|
getCanonRatingGuideQuestionById, |
|
193
|
|
|
getEditRatingGuideQuestionById, |
|
194
|
|
|
(canon, edited): boolean => { |
|
195
|
|
|
if (canon === null) { |
|
196
|
|
|
return true; |
|
197
|
|
|
} |
|
198
|
|
|
return edited !== null && !isEqual(edited, canon); |
|
199
|
|
|
}, |
|
200
|
|
|
)((state: RootState, id: number): number => id); |
|
201
|
|
|
|
|
202
|
|
|
export const ratingGuideQuestionsAreEditedByAssessment = createCachedSelector( |
|
203
|
|
|
getRatingGuideQuestionIdsByJobAndAssessmentType, |
|
204
|
|
|
getCanonQuestionState, |
|
205
|
|
|
getEditedQuestionState, |
|
206
|
|
|
(questionIds, canonState, editedState): { [id: number]: boolean } => |
|
207
|
|
|
mapToObjectTrans( |
|
208
|
|
|
questionIds, |
|
209
|
|
|
(id): number => id, |
|
210
|
|
|
(questionId): boolean => { |
|
211
|
|
|
const canon = hasKey(canonState, questionId) |
|
212
|
|
|
? canonState[questionId] |
|
213
|
|
|
: null; |
|
214
|
|
|
const edited = hasKey(editedState, questionId) |
|
215
|
|
|
? editedState[questionId] |
|
216
|
|
|
: null; |
|
217
|
|
|
return edited !== null && !isEqual(edited, canon); |
|
218
|
|
|
}, |
|
219
|
|
|
), |
|
220
|
|
|
)((state, props): string => `${props.jobId}:${props.assessmentTypeId}`); |
|
221
|
|
|
|
|
222
|
|
|
export const tempRatingGuideQuestionIsSaving = createCachedSelector( |
|
223
|
|
|
getTempQuestionSaving, |
|
224
|
|
|
(state: RootState, id: number): number => id, |
|
225
|
|
|
(tempSaving, id): boolean => |
|
226
|
|
|
hasKey(tempSaving, id) ? tempSaving[id] : false, |
|
227
|
|
|
)((state: RootState, id: number): number => id); |
|
228
|
|
|
|
|
229
|
|
|
export const tempRatingGuideQuestionsAreSavingByAssessment = createCachedSelector( |
|
230
|
|
|
getTempRatingGuideQuestionIdsByAssessment, |
|
231
|
|
|
getTempQuestionSaving, |
|
232
|
|
|
(questionIds, savingState): { [id: number]: boolean } => |
|
233
|
|
|
mapToObjectTrans( |
|
234
|
|
|
questionIds, |
|
235
|
|
|
(id): number => id, |
|
236
|
|
|
(questionId): boolean => |
|
237
|
|
|
hasKey(savingState, questionId) ? savingState[questionId] : false, |
|
238
|
|
|
), |
|
239
|
|
|
)((state, props): string => `${props.jobId}:${props.assessmentTypeId}`); |
|
240
|
|
|
|
|
241
|
|
|
export const ratingGuideQuestionIsUpdating = createCachedSelector( |
|
242
|
|
|
getQuestionUpdates, |
|
243
|
|
|
(state: RootState, id: number): number => id, |
|
244
|
|
|
(updateCounts, id): boolean => |
|
245
|
|
|
hasKey(updateCounts, id) ? updateCounts[id] > 0 : false, |
|
246
|
|
|
)((state: RootState, id: number): number => id); |
|
247
|
|
|
|
|
248
|
|
|
export const ratingGuideQuestionsAreUpdatingByAssessment = createCachedSelector( |
|
249
|
|
|
getRatingGuideQuestionIdsByJobAndAssessmentType, |
|
250
|
|
|
getQuestionUpdates, |
|
251
|
|
|
(questionIds, updateCounts): { [id: number]: boolean } => |
|
252
|
|
|
questionIds.reduce( |
|
253
|
|
|
( |
|
254
|
|
|
result: { [id: number]: boolean }, |
|
255
|
|
|
questionId: number, |
|
256
|
|
|
): { [id: number]: boolean } => { |
|
257
|
|
|
// eslint-disable-next-line no-param-reassign |
|
258
|
|
|
result[questionId] = hasKey(updateCounts, questionId) |
|
259
|
|
|
? updateCounts[questionId] > 0 |
|
260
|
|
|
: false; |
|
261
|
|
|
return result; |
|
262
|
|
|
}, |
|
263
|
|
|
{}, |
|
264
|
|
|
), |
|
265
|
|
|
)((state, jobId, assessmentTypeId): string => `${jobId} ${assessmentTypeId}`); |
|
266
|
|
|
|