From 11170592102e87b51276a5ad164f9f60a78494ff Mon Sep 17 00:00:00 2001
From: ievavold <ievavold@wisc.edu>
Date: Fri, 8 Feb 2019 15:54:13 -0600
Subject: [PATCH] streamline plan effects

---
 .../store/effects/plan.effects.ts             | 271 ++++++++----------
 1 file changed, 113 insertions(+), 158 deletions(-)

diff --git a/src/app/degree-planner/store/effects/plan.effects.ts b/src/app/degree-planner/store/effects/plan.effects.ts
index be19b72..cefbda2 100644
--- a/src/app/degree-planner/store/effects/plan.effects.ts
+++ b/src/app/degree-planner/store/effects/plan.effects.ts
@@ -1,8 +1,8 @@
 // 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];
+};
-- 
GitLab