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 verisons 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[] => Object.keys(tempQuestions).map(id => Number(id)), |
81
|
|
|
); |
82
|
|
|
|
83
|
|
|
/** |
84
|
|
|
* Returns edited version, if available |
85
|
|
|
* */ |
86
|
|
|
export const getCurrentRatingGuideQuestionById = createSelector( |
87
|
|
|
getCurrentQuestionState, |
88
|
|
|
(state: RootState, id: number) => id, |
89
|
|
|
(questions, id): RatingGuideQuestion | null => |
90
|
|
|
hasKey(questions, id) ? questions[id] : null, |
91
|
|
|
); |
92
|
|
|
|
93
|
|
|
export const getCanonRatingGuideQuestionById = createCachedSelector( |
94
|
|
|
getCanonQuestionState, |
95
|
|
|
(state: RootState, id: number): number => id, |
96
|
|
|
(questions, id): RatingGuideQuestion | null => |
97
|
|
|
hasKey(questions, id) ? questions[id] : null, |
98
|
|
|
)((state, id) => id); |
99
|
|
|
|
100
|
|
|
export const getEditRatingGuideQuestionById = createCachedSelector( |
101
|
|
|
getEditedQuestionState, |
102
|
|
|
(state: RootState, id: number): number => id, |
103
|
|
|
(questions, id): RatingGuideQuestion | null => |
104
|
|
|
hasKey(questions, id) ? questions[id] : null, |
105
|
|
|
)((state, id) => id); |
106
|
|
|
|
107
|
|
|
export const getTempRatingGuideQuestionById = createCachedSelector( |
108
|
|
|
getTempQuestionState, |
109
|
|
|
(state: RootState, id: number): number => id, |
110
|
|
|
(questions, id): RatingGuideQuestion | null => |
111
|
|
|
hasKey(questions, id) ? questions[id] : null, |
112
|
|
|
)((state, id) => id); |
113
|
|
|
|
114
|
|
|
export const getRatingGuideQuestionsByJob = createCachedSelector( |
115
|
|
|
getCurrentRatingGuideQuestions, |
116
|
|
|
(state: RootState, props: { jobId: number }): number => props.jobId, |
117
|
|
|
(questions, jobId): RatingGuideQuestion[] => |
118
|
|
|
questions.filter((question): boolean => question.job_poster_id === jobId), |
119
|
|
|
)((state, props): number => props.jobId); |
120
|
|
|
|
121
|
|
|
export const getRatingGuideQuestionIdsByJob = createCachedSelector( |
122
|
|
|
getRatingGuideQuestionsByJob, |
123
|
|
|
(questions): number[] => questions.map(getId), |
124
|
|
|
)((state, props): number => props.jobId); |
125
|
|
|
|
126
|
|
|
export const getRatingGuideQuestionsByJobAndAssessmentType = createCachedSelector( |
127
|
|
|
getCurrentRatingGuideQuestions, |
128
|
|
|
( |
129
|
|
|
state: RootState, |
130
|
|
|
props: { jobId: number; assessmentTypeId: number }, |
131
|
|
|
): number => props.jobId, |
132
|
|
|
( |
133
|
|
|
state: RootState, |
134
|
|
|
props: { jobId: number; assessmentTypeId: number }, |
135
|
|
|
): number => props.assessmentTypeId, |
136
|
|
|
(questions, jobId, assessmentTypeId): RatingGuideQuestion[] => |
137
|
|
|
questions.filter( |
138
|
|
|
(question): boolean => |
139
|
|
|
question.job_poster_id === jobId && |
140
|
|
|
question.assessment_type_id === assessmentTypeId, |
141
|
|
|
), |
142
|
|
|
)((state, props): string => `${props.jobId} ${props.assessmentTypeId}`); |
143
|
|
|
|
144
|
|
|
export const getRatingGuideQuestionIdsByJobAndAssessmentType = createCachedSelector( |
145
|
|
|
( |
146
|
|
|
state: RootState, |
147
|
|
|
props: { jobId: number; assessmentTypeId: number }, |
148
|
|
|
): number[] => |
149
|
|
|
getRatingGuideQuestionsByJobAndAssessmentType(state, props).map(getId), |
150
|
|
|
(questionsIds): number[] => questionsIds, |
151
|
|
|
)( |
152
|
|
|
(state, props): string => `${props.jobId} ${props.assessmentTypeId}`, |
153
|
|
|
deepEqualSelectorOptions, |
154
|
|
|
); |
155
|
|
|
|
156
|
|
|
export const getTempRatingGuideQuestionsByAssessment = createCachedSelector( |
157
|
|
|
getTempRatingGuideQuestions, |
158
|
|
|
( |
159
|
|
|
state: RootState, |
160
|
|
|
props: { jobId: number; assessmentTypeId: number }, |
161
|
|
|
): number => props.jobId, |
162
|
|
|
( |
163
|
|
|
state: RootState, |
164
|
|
|
props: { jobId: number; assessmentTypeId: number }, |
165
|
|
|
): number => props.assessmentTypeId, |
166
|
|
|
(questions, jobId, assessmentTypeId): RatingGuideQuestion[] => |
167
|
|
|
questions.filter( |
168
|
|
|
(question): boolean => |
169
|
|
|
question.job_poster_id === jobId && |
170
|
|
|
question.assessment_type_id === assessmentTypeId, |
171
|
|
|
), |
172
|
|
|
)((state, props): string => `${props.jobId} ${props.assessmentTypeId}`); |
173
|
|
|
|
174
|
|
|
export const getTempRatingGuideQuestionIdsByAssessment = createCachedSelector( |
175
|
|
|
( |
176
|
|
|
state: RootState, |
177
|
|
|
props: { jobId: number; assessmentTypeId: number }, |
178
|
|
|
): number[] => |
179
|
|
|
getTempRatingGuideQuestionsByAssessment(state, props).map(getId), |
180
|
|
|
(questionsIds): number[] => questionsIds, |
181
|
|
|
)( |
182
|
|
|
(state, props): string => `${props.jobId} ${props.assessmentTypeId}`, |
183
|
|
|
deepEqualSelectorOptions, |
184
|
|
|
); |
185
|
|
|
|
186
|
|
|
// TODO: test that this works like I think it does -- Tristan |
187
|
|
|
/** Returns true if there is an edited verision which differs from canonical version */ |
188
|
|
|
export const ratingGuideQuestionIsEdited = createCachedSelector( |
189
|
|
|
getCanonRatingGuideQuestionById, |
190
|
|
|
getEditRatingGuideQuestionById, |
191
|
|
|
(canon, edited): boolean => { |
192
|
|
|
if (canon === null) { |
193
|
|
|
return true; |
194
|
|
|
} |
195
|
|
|
return edited !== null && !isEqual(edited, canon); |
196
|
|
|
}, |
197
|
|
|
)((state: RootState, id: number): number => id); |
198
|
|
|
|
199
|
|
|
export const ratingGuideQuestionsAreEditedByAssessment = createCachedSelector( |
200
|
|
|
getRatingGuideQuestionIdsByJobAndAssessmentType, |
201
|
|
|
getCanonQuestionState, |
202
|
|
|
getEditedQuestionState, |
203
|
|
|
(questionIds, canonState, editedState): { [id: number]: boolean } => |
204
|
|
|
mapToObjectTrans( |
205
|
|
|
questionIds, |
206
|
|
|
(id): number => id, |
207
|
|
|
(questionId): boolean => { |
208
|
|
|
const canon = hasKey(canonState, questionId) |
209
|
|
|
? canonState[questionId] |
210
|
|
|
: null; |
211
|
|
|
const edited = hasKey(editedState, questionId) |
212
|
|
|
? editedState[questionId] |
213
|
|
|
: null; |
214
|
|
|
return edited !== null && !isEqual(edited, canon); |
215
|
|
|
}, |
216
|
|
|
), |
217
|
|
|
)((state, props): string => `${props.jobId}:${props.assessmentTypeId}`); |
218
|
|
|
|
219
|
|
|
export const tempRatingGuideQuestionIsSaving = createCachedSelector( |
220
|
|
|
getTempQuestionSaving, |
221
|
|
|
(state: RootState, id: number): number => id, |
222
|
|
|
(tempSaving, id): boolean => |
223
|
|
|
hasKey(tempSaving, id) ? tempSaving[id] : false, |
224
|
|
|
)((state: RootState, id: number): number => id); |
225
|
|
|
|
226
|
|
|
export const tempRatingGuideQuestionsAreSavingByAssessment = createCachedSelector( |
227
|
|
|
getTempRatingGuideQuestionIdsByAssessment, |
228
|
|
|
getTempQuestionSaving, |
229
|
|
|
(questionIds, savingState): { [id: number]: boolean } => |
230
|
|
|
mapToObjectTrans( |
231
|
|
|
questionIds, |
232
|
|
|
(id): number => id, |
233
|
|
|
(questionId): boolean => |
234
|
|
|
hasKey(savingState, questionId) ? savingState[questionId] : false, |
235
|
|
|
), |
236
|
|
|
)((state, props): string => `${props.jobId}:${props.assessmentTypeId}`); |
237
|
|
|
|
238
|
|
|
export const ratingGuideQuestionIsUpdating = createCachedSelector( |
239
|
|
|
getQuestionUpdates, |
240
|
|
|
(state: RootState, id: number): number => id, |
241
|
|
|
(updateCounts, id): boolean => |
242
|
|
|
hasKey(updateCounts, id) ? updateCounts[id] > 0 : false, |
243
|
|
|
)((state: RootState, id: number): number => id); |
244
|
|
|
|
245
|
|
|
export const ratingGuideQuestionsAreUpdatingByAssessment = createCachedSelector( |
246
|
|
|
getRatingGuideQuestionIdsByJobAndAssessmentType, |
247
|
|
|
getQuestionUpdates, |
248
|
|
|
(questionIds, updateCounts): { [id: number]: boolean } => |
249
|
|
|
questionIds.reduce( |
250
|
|
|
( |
251
|
|
|
result: { [id: number]: boolean }, |
252
|
|
|
questionId: number, |
253
|
|
|
): { [id: number]: boolean } => { |
254
|
|
|
// eslint-disable-next-line no-param-reassign |
255
|
|
|
result[questionId] = hasKey(updateCounts, questionId) |
256
|
|
|
? updateCounts[questionId] > 0 |
257
|
|
|
: false; |
258
|
|
|
return result; |
259
|
|
|
}, |
260
|
|
|
{}, |
261
|
|
|
), |
262
|
|
|
)((state, jobId, assessmentTypeId): string => `${jobId} ${assessmentTypeId}`); |
263
|
|
|
|