Skip to content

Commit 5fbeb88

Browse files
committed
Modify sagas to use new combineSagaHandlers function
1 parent fed151c commit 5fbeb88

File tree

17 files changed

+739
-808
lines changed

17 files changed

+739
-808
lines changed

src/commons/redux/utils.ts

Lines changed: 26 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,12 @@ import {
44
type ActionCreatorWithPreparedPayload,
55
createAction
66
} from '@reduxjs/toolkit';
7-
import * as Sentry from '@sentry/browser';
87
import type { SagaIterator } from 'redux-saga';
9-
import { type StrictEffect, takeLatest, takeLeading } from 'redux-saga/effects';
8+
import { type StrictEffect, takeEvery, takeLatest, takeLeading } from 'redux-saga/effects';
109

11-
import { safeTakeEvery } from '../sagas/SafeEffects';
12-
import { objectEntries } from '../utils/TypeHelper';
10+
import { safeTakeEvery, wrapSaga } from '../sagas/SafeEffects';
11+
import type { SourceActionType } from '../utils/ActionsHelper';
12+
import { ActionTypeToCreator, objectEntries } from '../utils/TypeHelper';
1313

1414
/**
1515
* Creates actions, given a base name and base actions
@@ -42,57 +42,36 @@ export function createActions<BaseName extends string, BaseActions extends Recor
4242
);
4343
}
4444

45-
function wrapSaga<T extends (...args: any[]) => Generator>(saga: T) {
46-
return function* (...args: Parameters<T>) {
47-
try {
48-
return yield* saga(...args);
49-
} catch (error) {
50-
handleUncaughtError(error);
51-
}
52-
};
53-
}
45+
type SagaHandler<T extends SourceActionType['type']> = (
46+
action: ReturnType<ActionTypeToCreator<T>>
47+
) => Generator<StrictEffect>;
5448

55-
type SagaHandler<
56-
T extends ActionCreatorWithPreparedPayload<any, any> | ActionCreatorWithoutPayload<any>
57-
> = (action: ReturnType<T>) => Generator<StrictEffect>;
49+
type SagaHandlers = {
50+
[K in SourceActionType['type']]?:
51+
| SagaHandler<K>
52+
| Partial<Record<'takeEvery' | 'takeLatest' | 'takeLeading', SagaHandler<K>>>;
53+
};
5854

59-
export function combineSagaHandlers<
60-
TActions extends Record<
61-
string,
62-
ActionCreatorWithPreparedPayload<any, any> | ActionCreatorWithoutPayload<any>
63-
>
64-
>(
65-
actions: TActions,
66-
handlers: {
67-
// TODO: Maybe this can be stricter? And remove the optional type after migration is fully done
68-
[K in keyof TActions]?:
69-
| SagaHandler<TActions[K]>
70-
| { takeLeading: SagaHandler<TActions[K]> }
71-
| { takeLatest: SagaHandler<TActions[K]> };
72-
},
73-
others?: (takeEvery: typeof saferTakeEvery) => SagaIterator
74-
): () => SagaIterator {
55+
export function combineSagaHandlers(handlers: SagaHandlers) {
7556
return function* (): SagaIterator {
76-
for (const [actionName, saga] of objectEntries(handlers)) {
57+
for (const [actionName, saga] of objectEntries(handlers as SagaHandlers)) {
7758
if (saga === undefined) {
7859
continue;
7960
} else if (typeof saga === 'function') {
80-
yield safeTakeEvery(actions[actionName].type, saga);
81-
} else if ('takeLeading' in saga) {
82-
yield takeLeading(actions[actionName].type, wrapSaga(saga.takeLeading));
83-
} else if ('takeLatest' in saga) {
84-
yield takeLatest(actions[actionName].type, wrapSaga(saga.takeLatest));
85-
} else {
86-
throw new Error(`Unknown saga handler type for ${actionName as string}`);
61+
yield takeEvery(actionName, wrapSaga(saga));
62+
continue;
63+
}
64+
65+
if (saga.takeEvery) {
66+
yield takeEvery(actionName, wrapSaga(saga.takeEvery));
67+
}
68+
69+
if (saga.takeLeading) {
70+
yield takeLeading(actionName, wrapSaga(saga.takeLeading));
8771
}
88-
}
8972

90-
if (others) {
91-
const obj = others(saferTakeEvery);
92-
while (true) {
93-
const { done, value } = obj.next();
94-
if (done) break;
95-
yield value;
73+
if (saga.takeLatest) {
74+
yield takeLatest(actionName, wrapSaga(saga.takeLatest));
9675
}
9776
}
9877
};
@@ -105,16 +84,3 @@ export function saferTakeEvery<
10584
>(actionPattern: Action, fn: (action: ReturnType<Action>) => Generator<StrictEffect<any>>) {
10685
return safeTakeEvery(actionPattern.type, fn);
10786
}
108-
109-
function handleUncaughtError(error: any) {
110-
if (process.env.NODE_ENV === 'development') {
111-
// react-error-overlay is a "special" package that's automatically included
112-
// in development mode by CRA
113-
114-
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
115-
// @ts-ignore
116-
import('react-error-overlay').then(reo => reo.reportRuntimeError(error));
117-
}
118-
Sentry.captureException(error);
119-
console.error(error);
120-
}

src/commons/sagas/AchievementSaga.ts

Lines changed: 34 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
import { call, delay, put, select } from 'redux-saga/effects';
22
import AchievementActions from 'src/features/achievement/AchievementActions';
33

4-
import { AchievementGoal, EventType } from '../../features/achievement/AchievementTypes';
4+
import { type AchievementGoal, EventType } from '../../features/achievement/AchievementTypes';
55
import { updateGoalProcessed } from '../achievement/AchievementManualEditor';
66
import AchievementInferencer from '../achievement/utils/AchievementInferencer';
77
import { goalIncludesEvents, incrementCount } from '../achievement/utils/EventHandler';
8-
import { OverallState } from '../application/ApplicationTypes';
9-
import { Tokens } from '../application/types/SessionTypes';
8+
import type { OverallState } from '../application/ApplicationTypes';
9+
import type { Tokens } from '../application/types/SessionTypes';
1010
import { combineSagaHandlers } from '../redux/utils';
11+
import SideContentActions from '../sideContent/SideContentActions';
12+
import { getLocation } from '../sideContent/SideContentHelper';
1113
import { SideContentType } from '../sideContent/SideContentTypes';
1214
import { actions } from '../utils/ActionsHelper';
1315
import Constants from '../utils/Constants';
@@ -26,19 +28,17 @@ import {
2628
updateOwnGoalProgress
2729
} from './RequestsSaga';
2830

29-
const AchievementSaga = combineSagaHandlers(AchievementActions, {
30-
bulkUpdateAchievements: function* (action) {
31+
const AchievementSaga = combineSagaHandlers({
32+
[AchievementActions.bulkUpdateAchievements.type]: function* (action) {
3133
const tokens: Tokens = yield selectTokens();
32-
3334
const achievements = action.payload;
34-
3535
const resp = yield call(bulkUpdateAchievements, achievements, tokens);
3636

3737
if (!resp) {
3838
return;
3939
}
4040
},
41-
bulkUpdateGoals: function* (action) {
41+
[AchievementActions.bulkUpdateGoals.type]: function* (action) {
4242
const tokens: Tokens = yield selectTokens();
4343

4444
const goals = action.payload;
@@ -49,7 +49,7 @@ const AchievementSaga = combineSagaHandlers(AchievementActions, {
4949
return;
5050
}
5151
},
52-
getAchievements: function* () {
52+
[AchievementActions.getAchievements.type]: function* () {
5353
const tokens: Tokens = yield selectTokens();
5454

5555
const achievements = yield call(getAchievements, tokens);
@@ -58,7 +58,7 @@ const AchievementSaga = combineSagaHandlers(AchievementActions, {
5858
yield put(actions.saveAchievements(achievements));
5959
}
6060
},
61-
getGoals: function* (action) {
61+
[AchievementActions.getGoals.type]: function* (action) {
6262
const tokens: Tokens = yield selectTokens();
6363

6464
const studentCourseRegId = action.payload;
@@ -69,7 +69,7 @@ const AchievementSaga = combineSagaHandlers(AchievementActions, {
6969
yield put(actions.saveGoals(goals));
7070
}
7171
},
72-
getOwnGoals: function* (action) {
72+
[AchievementActions.getOwnGoals.type]: function* () {
7373
const tokens: Tokens = yield selectTokens();
7474

7575
const goals = yield call(getOwnGoals, tokens);
@@ -78,7 +78,7 @@ const AchievementSaga = combineSagaHandlers(AchievementActions, {
7878
yield put(actions.saveGoals(goals));
7979
}
8080
},
81-
getUsers: function* (action) {
81+
[AchievementActions.getUsers.type]: function* () {
8282
const tokens: Tokens = yield selectTokens();
8383

8484
const users = yield call(getAllUsers, tokens);
@@ -87,7 +87,7 @@ const AchievementSaga = combineSagaHandlers(AchievementActions, {
8787
yield put(actions.saveUsers(users));
8888
}
8989
},
90-
removeAchievement: function* (action) {
90+
[AchievementActions.removeAchievement.type]: function* (action) {
9191
const tokens: Tokens = yield selectTokens();
9292

9393
const achievement = action.payload;
@@ -98,7 +98,7 @@ const AchievementSaga = combineSagaHandlers(AchievementActions, {
9898
return;
9999
}
100100
},
101-
removeGoal: function* (action) {
101+
[AchievementActions.removeGoal.type]: function* (action) {
102102
const tokens: Tokens = yield selectTokens();
103103

104104
const definition = action.payload;
@@ -109,7 +109,7 @@ const AchievementSaga = combineSagaHandlers(AchievementActions, {
109109
return;
110110
}
111111
},
112-
updateOwnGoalProgress: function* (action) {
112+
[AchievementActions.updateOwnGoalProgress.type]: function* (action) {
113113
const tokens: Tokens = yield selectTokens();
114114

115115
const progress = action.payload;
@@ -120,7 +120,7 @@ const AchievementSaga = combineSagaHandlers(AchievementActions, {
120120
return;
121121
}
122122
},
123-
updateGoalProgress: function* (action) {
123+
[AchievementActions.updateGoalProgress.type]: function* (action) {
124124
const tokens: Tokens = yield selectTokens();
125125

126126
const { studentCourseRegId, progress } = action.payload;
@@ -132,10 +132,10 @@ const AchievementSaga = combineSagaHandlers(AchievementActions, {
132132
}
133133
if (resp.ok) {
134134
yield put(actions.getGoals(studentCourseRegId));
135-
updateGoalProcessed();
135+
yield call(updateGoalProcessed);
136136
}
137137
},
138-
addEvent: function* (action) {
138+
[AchievementActions.addEvent.type]: function* ({ payload: { eventNames, workspaceLocation } }) {
139139
let loggedEvents: EventType[][] = [];
140140
let timeoutSet: boolean = false;
141141
const updateInterval = 3000;
@@ -144,25 +144,25 @@ const AchievementSaga = combineSagaHandlers(AchievementActions, {
144144
const enableAchievements = yield select(
145145
(state: OverallState) => state.session.enableAchievements
146146
);
147-
if (action.payload.find(e => e === EventType.ERROR)) {
148-
// TODO update this to work with new side content system
149-
// Flash the home icon if there is an error and the user is in the CSE machine or subst viz tab
150-
const introIcon = document.getElementById(SideContentType.introduction + '-icon');
151-
const cseTab = document.getElementById(
152-
'bp5-tab-panel_side-content-tabs_' + SideContentType.cseMachine
153-
);
154-
const substTab = document.getElementById(
155-
'bp5-tab-panel_side-content-tabs_' + SideContentType.substVisualizer
156-
);
147+
if (workspaceLocation !== undefined && eventNames.find(e => e === EventType.ERROR)) {
148+
const selectedTab: SideContentType | undefined = yield select((state: OverallState) => {
149+
const [loc, storyEnv] = getLocation(workspaceLocation);
150+
return loc === 'stories'
151+
? state.sideContent.stories[storyEnv].selectedTab
152+
: state.sideContent[loc].selectedTab;
153+
});
154+
157155
if (
158-
(cseTab && cseTab.ariaHidden === 'false') ||
159-
(substTab && substTab.ariaHidden === 'false')
156+
selectedTab === SideContentType.cseMachine ||
157+
selectedTab === SideContentType.substVisualizer
160158
) {
161-
introIcon?.classList.add('side-content-tab-alert-error');
159+
yield put(
160+
SideContentActions.beginAlertSideContent(SideContentType.introduction, workspaceLocation)
161+
);
162162
}
163163
}
164164
if (role && enableAchievements && !Constants.playgroundOnly) {
165-
loggedEvents.push(action.payload);
165+
loggedEvents.push(eventNames);
166166

167167
if (!timeoutSet && role) {
168168
// make sure that only one action every interval will handleEvent
@@ -175,7 +175,7 @@ const AchievementSaga = combineSagaHandlers(AchievementActions, {
175175
}
176176
}
177177
},
178-
handleEvent: function* (action) {
178+
[AchievementActions.handleEvent.type]: function* (action) {
179179
const tokens: Tokens = yield selectTokens();
180180

181181
// get the most recent list of achievements
@@ -219,7 +219,7 @@ const AchievementSaga = combineSagaHandlers(AchievementActions, {
219219
}
220220
}
221221
},
222-
getUserAssessmentOverviews: function* (action) {
222+
[AchievementActions.getUserAssessmentOverviews.type]: function* (action) {
223223
const tokens: Tokens = yield selectTokens();
224224

225225
const assessmentOverviews = yield call(getUserAssessmentOverviews, action.payload, tokens);

0 commit comments

Comments
 (0)