Skip to content
Snippets Groups Projects
Commit 11170592 authored by Isaac Evavold's avatar Isaac Evavold
Browse files

streamline plan effects

parent aa6d1863
No related branches found
No related tags found
No related merge requests found
// Libraries
import { Injectable } from '@angular/core';
import { ROOT_EFFECTS_INIT, Actions, Effect, ofType } from '@ngrx/effects';
import { Observable, forkJoin } from 'rxjs';
import { map, flatMap, tap, withLatestFrom, filter } from 'rxjs/operators';
import { Observable, forkJoin, of } from 'rxjs';
import { map, flatMap, withLatestFrom, filter } from 'rxjs/operators';
import { GlobalState } from '@app/core/state';
import { Store } from '@ngrx/store';
......@@ -41,101 +41,68 @@ export class DegreePlanEffects {
init$: Observable<InitialPlanLoadResponse> = this.actions$.pipe(
ofType(ROOT_EFFECTS_INIT),
// Load the data that is not specific to a particular degree plan. Also pick
// the primary degree plan as the first visible degree plan.
// Load the list of degree plans and data used by all degree plans.
flatMap(() => {
return forkJoin(
this.api.getAllDegreePlans(),
this.api.getAllSubjects(),
this.api.getActiveTerms(),
).pipe(
map(([allDegreePlans, subjects, activeTerms]) => {
const visibleDegreePlan =
allDegreePlans.find(plan => plan.primary) || allDegreePlans[0];
return {
allDegreePlans,
subjects,
activeTermCodes: activeTerms.map(term => term.termCode),
visibleDegreePlan,
};
}),
);
const activeTermCodes = this.api
.getActiveTerms()
.pipe(map(terms => terms.map(term => term.termCode)));
return forkJoinWithKeys({
allDegreePlans: this.api.getAllDegreePlans(),
subjects: this.api.getAllSubjects(),
activeTermCodes,
});
}),
// Get term data for the degree plan specified by the roadmap ID and any
// courses that were 'saved for later' by the user.
flatMap(stdin => {
return forkJoin(
this.loadPlanTerms(
stdin.visibleDegreePlan,
stdin.subjects,
stdin.activeTermCodes,
),
this.loadSavedForLaterCourses(stdin.subjects),
).pipe(
map(([visibleTerms, savedForLater]) => {
const savedForLaterCourses = savedForLater.map(course => {
course.subject = stdin.subjects[course.subjectCode];
return course;
});
return new InitialPlanLoadResponse({
visibleDegreePlan: stdin.visibleDegreePlan,
visibleTerms,
savedForLaterCourses,
activeTermCodes: stdin.activeTermCodes,
allDegreePlans: stdin.allDegreePlans,
subjects: stdin.subjects,
});
}),
// Load data specific to the primary degree plan.
flatMap(({ allDegreePlans, subjects, activeTermCodes }) => {
const savedForLaterCourses = this.loadSavedForLaterCourses(subjects);
const visibleDegreePlan = pickPrimaryDegreePlan(allDegreePlans);
const visibleTerms = loadPlanTerms(
this.api,
visibleDegreePlan,
subjects,
activeTermCodes,
);
return forkJoinWithKeys({
visibleDegreePlan: of(visibleDegreePlan),
visibleTerms,
savedForLaterCourses,
activeTermCodes: of(activeTermCodes),
allDegreePlans: of(allDegreePlans),
subjects: of(subjects),
});
}),
map(payload => new InitialPlanLoadResponse(payload)),
);
@Effect()
switch$: Observable<ChangeVisiblePlanResponse> = this.actions$.pipe(
ofType<ChangeVisiblePlanRequest>(PlanActionTypes.ChangeVisiblePlanRequest),
// Get the most recent Degree Planner state object from the store. This is
// used to decide to fire either the `updateNote` API or `createNote` API.
withLatestFrom(this.store$.select(getDegreePlannerState)),
// Get the roadmap ID from the action and use that ID to lookup the
// corresponding degree plan object.
map(([action, state]) => {
return {
subjects: state.subjects,
visibleDegreePlan: state.allDegreePlans.find(plan => {
return plan.roadmapId === action.payload.newVisibleRoadmapId;
}) as DegreePlan,
activeTermCodes: state.activeTermCodes,
};
}),
// Get term data for the degree plan specified by the roadmap ID.
flatMap(oldData => {
return this.loadPlanTerms(
oldData.visibleDegreePlan,
oldData.subjects,
oldData.activeTermCodes,
).pipe(
map(visibleTerms => {
return {
visibleTerms,
visibleDegreePlan: oldData.visibleDegreePlan,
};
}),
flatMap(([action, state]) => {
const visibleDegreePlan = state.allDegreePlans.find(plan => {
return plan.roadmapId === action.payload.newVisibleRoadmapId;
}) as DegreePlan;
const visibleTerms = loadPlanTerms(
this.api,
visibleDegreePlan,
state.subjects,
state.activeTermCodes,
);
}),
// Wrap data in an Action for dispatch
map(data => {
return new ChangeVisiblePlanResponse({
visibleDegreePlan: data.visibleDegreePlan,
visibleTerms: data.visibleTerms,
return forkJoinWithKeys({
visibleDegreePlan: of(visibleDegreePlan),
visibleTerms,
});
}),
map(payload => new ChangeVisiblePlanResponse(payload)),
);
@Effect()
......@@ -145,12 +112,6 @@ export class DegreePlanEffects {
withLatestFrom(this.store$.select(getDegreePlannerState)),
filter(([_, state]) => typeof state.visibleDegreePlan !== undefined),
// Get the roadmap ID from the action.
tap(([action, state]) => {
console.log(action);
console.log(state);
}),
// Get term data for the degree plan specified by the roadmap ID.
flatMap(([action, state]) => {
// TODO error handle the API calls
......@@ -190,12 +151,6 @@ export class DegreePlanEffects {
withLatestFrom(this.store$.select(getDegreePlannerState)),
filter(([_, state]) => state.visibleDegreePlan !== undefined),
// Get the roadmap ID from the action.
tap(([action, state]) => {
console.log(action);
console.log(state);
}),
// Get term data for the degree plan specified by the roadmap ID.
flatMap(([action, state]) => {
// TODO error handle the API calls
......@@ -227,12 +182,6 @@ export class DegreePlanEffects {
withLatestFrom(this.store$.select(getDegreePlannerState)),
filter(([_, state]) => state.visibleDegreePlan !== undefined),
// Get the roadmap ID from the action.
// tap(([action, state]) => {
// console.log(action);
// console.log(state);
// }),
// Get term data for the degree plan specified by the roadmap ID.
flatMap(([action, state]) => {
// TODO error handle the API calls
......@@ -264,15 +213,6 @@ export class DegreePlanEffects {
withLatestFrom(this.store$.select(getDegreePlannerState)),
filter(([_, state]) => state.visibleDegreePlan !== undefined),
// tap(([action, state]) => {
// console.log('REMOVE SAVED FOR LATER ----------');
// console.log(action);
// console.log(state);
// console.log('---------------------------------');
// }),
// Get term data for the degree plan specified by the roadmap ID.
flatMap(([action, state]) => {
// TODO error handle the API calls
......@@ -309,16 +249,6 @@ export class DegreePlanEffects {
withLatestFrom(this.store$.select(getDegreePlannerState)),
filter(([_, state]) => state.visibleDegreePlan !== undefined),
// Get the roadmap ID from the action.
// tap(([action, state]) => {
// console.log('ADD SAVED FOR LATER ----------');
// console.log(action);
// console.log(state);
// console.log('---------------------------------');
// }),
// Get term data for the degree plan specified by the roadmap ID.
flatMap(([action, state]) => {
// TODO error handle the API calls
......@@ -357,49 +287,69 @@ export class DegreePlanEffects {
}),
);
}
}
private loadPlanTerms(
visibleDegreePlan: DegreePlan,
subjects: SubjectMapping,
activeTermCodes: string[],
) {
return forkJoin(
this.api.getAllNotes(visibleDegreePlan.roadmapId),
this.api.getAllTermCourses(visibleDegreePlan.roadmapId),
).pipe(
// Combine courses and notes by term.
map(([notes, termCourses]) => {
const noteTermCodes = notes.map(note => note.termCode);
const courseTermCodes = termCourses.map(term => term.termCode);
/**
* Using the notes & courses relevant to the current degree plan and
* the active terms, generate a sorted list of all unqiue term codes.
*/
const uniqueTermCodes = unique([
...noteTermCodes,
...courseTermCodes,
...activeTermCodes,
]).sort();
/**
* Group the notes and courses into a list of visible terms.
*/
const visibleTerms: PlannedTerm[] = uniqueTermCodes.map(termCode => {
const note = notes.find(matchesTermCode(termCode));
const termCourse = termCourses.find(matchesTermCode(termCode));
const courses = (termCourse ? termCourse.courses : []).map(base => ({
...base,
subject: subjects[base.subjectCode],
}));
return { termCode, note, courses };
});
const loadPlanTerms = (
api: DegreePlannerApiService,
visibleDegreePlan: DegreePlan,
subjects: SubjectMapping,
activeTermCodes: string[],
): Observable<PlannedTerm[]> => {
const notesAndTerms$ = forkJoinWithKeys({
notes: api.getAllNotes(visibleDegreePlan.roadmapId),
terms: api.getAllTermCourses(visibleDegreePlan.roadmapId),
});
const uniqueTerms$ = notesAndTerms$.pipe(
map(({ notes, terms }) => {
const noteTermCodes = notes.map(note => note.termCode);
const courseTermCodes = terms.map(term => term.termCode);
const uniqueTermCodes = unique([
...noteTermCodes,
...courseTermCodes,
...activeTermCodes,
]);
return uniqueTermCodes.sort();
}),
);
return visibleTerms;
}),
);
}
}
const visibleTerms$ = forkJoin(uniqueTerms$, notesAndTerms$).pipe(
map(([uniqueTerms, { notes, terms }]) => {
return uniqueTerms.map(termCode => {
const note = notes.find(matchesTermCode(termCode));
const term = terms.find(matchesTermCode(termCode));
const courses = (term ? term.courses : []).map(course => ({
...course,
subject: subjects[course.subjectCode],
}));
return { termCode, note, courses } as PlannedTerm;
});
}),
);
return visibleTerms$;
};
type SimpleMap = { [name: string]: any };
type ObservableMap<T = SimpleMap> = { [K in keyof T]: Observable<T[K]> };
const forkJoinWithKeys = <T = SimpleMap>(pairs: ObservableMap<T>) => {
const keys = Object.keys(pairs);
const observables = keys.map(key => pairs[key]);
return forkJoin(observables).pipe(
map<any[], T>(values => {
const valueMapping = {} as T;
keys.forEach((key, index) => {
valueMapping[key] = values[index];
});
return valueMapping;
}),
);
};
const unique = <T>(things: T[]): T[] => {
return things.filter((thing, index, all) => all.indexOf(thing) === index);
......@@ -408,3 +358,8 @@ const unique = <T>(things: T[]): T[] => {
const matchesTermCode = (termCode: string) => (thing: { termCode: string }) => {
return thing.termCode === termCode;
};
const pickPrimaryDegreePlan = (plans: DegreePlan[]): DegreePlan => {
const primary = plans.find(plan => plan.primary);
return primary ? primary : plans[0];
};
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment