Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Test serializers #235

Open
wants to merge 13 commits into
base: master
Choose a base branch
from
16 changes: 7 additions & 9 deletions graphql/resolvers/class.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@ import {
import { Course, Section } from "../../types/types";
import { GraphQLError } from "graphql";

const serializer = new HydrateCourseSerializer();

const serializeValues = (results: PrismaCourse[]): Course[] => {
return results.map((result) => serializer.serializeCourse(result));
return results.map((result) =>
HydrateCourseSerializer.serializeCourse(result)
);
};

const getLatestClassOccurrence = async (
Expand All @@ -38,8 +38,7 @@ const getLatestClassOccurrence = async (
},
);
}

return serializer.serializeCourse(result);
return HydrateCourseSerializer.serializeCourse(result);
};

const getBulkClassOccurrences = async (
Expand Down Expand Up @@ -92,7 +91,7 @@ const getClassOccurrence = async (
);
}

return serializer.serializeCourse(result);
return HydrateCourseSerializer.serializeCourse(result);
};

const getClassOccurrenceById = async (id: string): Promise<Course> => {
Expand All @@ -111,7 +110,7 @@ const getClassOccurrenceById = async (id: string): Promise<Course> => {
},
);
}
return serializer.serializeCourse(result);
return HydrateCourseSerializer.serializeCourse(result);
};

const getSectionById = async (id: string): Promise<Section> => {
Expand All @@ -129,8 +128,7 @@ const getSectionById = async (id: string): Promise<Section> => {
},
);
}

const resSec: Section = serializer.serializeSection(result); // this mutates res
const resSec: Section = HydrateCourseSerializer.serializeSection(result); // this mutates res
const { termId, subject, classId } = keys.parseSectionHash(id);

return { termId, subject, classId, ...resSec };
Expand Down
4 changes: 2 additions & 2 deletions scripts/populateES.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
courses: Course[],
): Promise<Promise<unknown>> {
// FIXME this pattern is bad
const serializedCourses = await new ElasticCourseSerializer().bulkSerialize(
const serializedCourses = await ElasticCourseSerializer.bulkSerialize(
courses,
true,
);
Expand All @@ -26,7 +26,7 @@
export async function bulkUpsertProfs(
profs: Professor[],
): Promise<Promise<unknown>> {
const serializedProfs = await new ElasticProfSerializer().bulkSerialize(
const serializedProfs = await ElasticProfSerializer().bulkSerialize(

Check failure on line 29 in scripts/populateES.ts

View workflow job for this annotation

GitHub Actions / Lint & Type checks

Value of type 'typeof ElasticProfSerializer' is not callable. Did you mean to include 'new'?
profs,
);
return elastic.bulkIndexFromMap(elastic.EMPLOYEE_ALIAS, serializedProfs);
Expand Down
79 changes: 50 additions & 29 deletions serializers/courseSerializer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
* See the license file in the root folder for details.
*/

import _ from "lodash";
import prisma from "../services/prisma";
import { Course, Section } from "../types/types";
import { Section as PrismaSection } from "@prisma/client";
Expand All @@ -19,14 +18,16 @@ import {
So, the course we take has to be a subset of Course.
The section we take has to be a subset of Section
*/
class CourseSerializer<C extends Partial<Course>, S extends Partial<Section>> {
// FIXME this pattern is bad
async bulkSerialize(
class CourseSerializer {
static async bulkSerialize<
C extends Partial<Course>,
S extends Partial<Section>
>(
instances: PrismaCourseWithSections[],
all = false,
): Promise<Record<string, SerializedCourse<C, S>>> {
const courses = instances.map((course) => {
return this.serializeCourse(course);
return this.serializeCourse<C>(course);
});

let sections: PrismaSection[] | undefined;
Expand All @@ -43,24 +44,33 @@ class CourseSerializer<C extends Partial<Course>, S extends Partial<Section>> {
});
}

const classToSections = _.groupBy(sections, "classHash");

return _(courses)
.keyBy(this.getClassHash)
.mapValues((course) => {
return this.bulkSerializeCourse(
const classToSections: Record<string, PrismaSection[]> = sections?.reduce(
(acc, section) => {
if (!acc[section.classHash]) {
acc[section.classHash] = [];
}
acc[section.classHash].push(section);
return acc;
},
{}
);

return Object.fromEntries(
courses.map((course) => [
this.getClassHash(course),
this.bulkSerializeCourse<C, S>(
course,
classToSections[this.getClassHash(course)] || [],
);
})
.value();
),
])
);
}

bulkSerializeCourse(
static bulkSerializeCourse<C, S>(
course: C,
sections: PrismaSection[],
): SerializedCourse<C, S> {
const serializedSections = this.serializeSections(sections, course);
const serializedSections = this.serializeSections<S>(sections, course);

return {
class: course,
Expand All @@ -69,22 +79,33 @@ class CourseSerializer<C extends Partial<Course>, S extends Partial<Section>> {
};
}

serializeSections(
static serializeSections<S>(
sections: PrismaSection[],
parentCourse: C,
): (S & Partial<C>)[] {
parentCourse: Partial<Course>
): (S & Partial<Course>)[] {

if (sections.length === 0) return [];

return sections
.map((section) => {
return this.serializeSection(section);
return this.serializeSection(section) as S;
})
.map((section) => {
return { ...section, ..._.pick(parentCourse, this.courseProps()) };
const picked: Partial<Course> = this.courseProps().reduce(
(acc, key) => {
if (key in parentCourse) {
acc[key] = parentCourse[key];
}
return acc;
},
{}
);

return { ...section, ...picked } as S & Partial<Course>;
});
}

serializeCourse(course: PrismaCourseWithSections): C {
static serializeCourse<C>(course: PrismaCourseWithSections): C {
return this._serializeCourse(course);
}

Expand All @@ -97,7 +118,7 @@ class CourseSerializer<C extends Partial<Course>, S extends Partial<Section>> {
the object to another type in-place.
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
private _serializeCourse(innerCourse: any): C {
private static _serializeCourse(innerCourse: any): any {
innerCourse.lastUpdateTime = innerCourse.lastUpdateTime.getTime();
innerCourse.desc = innerCourse.description;

Expand All @@ -109,33 +130,33 @@ class CourseSerializer<C extends Partial<Course>, S extends Partial<Section>> {
return this.finishCourseObj(innerCourse);
}

serializeSection(section: PrismaSection): S {
static serializeSection<S>(section: PrismaSection): S {
return this._serializeSection(section);
}

// See _serializeCourse for an explanation of this pattern.
// eslint-disable-next-line @typescript-eslint/no-explicit-any
private _serializeSection(section: any): S {
private static _serializeSection(section: any): any {
section.lastUpdateTime = section.lastUpdateTime.getTime();
return this.finishSectionObj(section);
}

// TODO this should definitely be eliminated
getClassHash(course: C): string {
static getClassHash(course: Partial<Course>): string {
return ["neu.edu", course.termId, course.subject, course.classId].join("/");
}

courseProps(): string[] {
static courseProps(): string[] {
throw new Error("not implemented");
}

// eslint-disable-next-line @typescript-eslint/no-unused-vars
finishCourseObj(_: Course): C {
static finishCourseObj(_: Course): Partial<Course> {
throw new Error("not implemented");
}

// eslint-disable-next-line @typescript-eslint/no-unused-vars
finishSectionObj(_: SerializedSection): S {
static finishSectionObj(_: SerializedSection): Partial<Section> {
throw new Error("not implemented");
}
}
Expand Down
34 changes: 19 additions & 15 deletions serializers/elasticCourseSerializer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,29 +2,33 @@
* This file is part of Search NEU and licensed under AGPL3.
* See the license file in the root folder for details.
*/
import _ from "lodash";

import CourseSerializer from "./courseSerializer";
import { ESCourse, ESSection } from "../types/serializerTypes";

class ElasticCourseSerializer extends CourseSerializer<ESCourse, ESSection> {
courseProps(): string[] {
class ElasticCourseSerializer extends CourseSerializer {
static courseProps(): string[] {
return [];
}

finishCourseObj(course): ESCourse {
return _.pick(course, [
"host",
"name",
"subject",
"classId",
"termId",
"nupath",
]);
static finishCourseObj(course): ESCourse {
const keys = ["host", "name", "subject", "classId", "termId", "nupath"];

return keys.reduce((acc, key) => {
if (key in course) {
acc[key] = course[key];
}
return acc;
}, {} as ESCourse);
}

finishSectionObj(section): ESSection {
return _.pick(section, ["profs", "classType", "crn", "campus", "honors"]);
static finishSectionObj(section): ESSection {
const keys = ["profs", "classType", "crn", "campus", "honors"];
return keys.reduce((acc, key) => {
if (key in section) {
acc[key] = section[key];
}
return acc;
}, {} as ESSection);
}
}

Expand Down
11 changes: 8 additions & 3 deletions serializers/elasticProfSerializer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,19 @@
* This file is part of Search NEU and licensed under AGPL3.
* See the license file in the root folder for details.
*/
import _ from "lodash";
import ProfSerializer from "./profSerializer";
import { Professor as PrismaProfessor } from "@prisma/client";
import { ESProfessor } from "../types/serializerTypes";

class ElasticProfSerializer extends ProfSerializer<ESProfessor> {
_serializeProf(prof: PrismaProfessor): ESProfessor {
return _.pick(prof, ["id", "name", "email", "phone"]);
static _serializeProf(prof: PrismaProfessor): ESProfessor {
const keys = ["id", "name", "email", "phone"];
return Object.keys(prof).reduce((acc, key) => {
if (keys.includes(key)) {
acc[key] = prof[key];
}
return acc;
}, {} as ESProfessor);
}
}

Expand Down
12 changes: 6 additions & 6 deletions serializers/hydrateCourseSerializer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,25 @@
* This file is part of Search NEU and licensed under AGPL3.
* See the license file in the root folder for details.
*/
import _ from "lodash";
import CourseSerializer from "./courseSerializer";
import { Course, Section } from "../types/types";
import { SerializedSection } from "../types/serializerTypes";

class HydrateCourseSerializer extends CourseSerializer<Course, Section> {
courseProps(): string[] {
class HydrateCourseSerializer extends CourseSerializer {
static courseProps(): string[] {
return ["lastUpdateTime", "termId", "host", "subject", "classId"];
}

finishCourseObj(course: Course): Course {
static finishCourseObj(course: Course): Course {
return course;
}

finishSectionObj(section: SerializedSection): Section {
static finishSectionObj(section: SerializedSection): Section {
// We know this will work, but Typescript doesn't
// In the main class, we add the fields from this.courseProps() to the section
// This creates a proper Section, but TS doesn't know we do that.
return _.omit(section, ["id", "classHash"]) as unknown as Section;
const { id, classHash, ...rest } = section;

Check warning on line 22 in serializers/hydrateCourseSerializer.ts

View workflow job for this annotation

GitHub Actions / Lint & Type checks

'id' is assigned a value but never used. Allowed unused vars must match /^_/u

Check warning on line 22 in serializers/hydrateCourseSerializer.ts

View workflow job for this annotation

GitHub Actions / Lint & Type checks

'classHash' is assigned a value but never used. Allowed unused vars must match /^_/u
return rest as unknown as Section;
}
}

Expand Down
2 changes: 1 addition & 1 deletion serializers/hydrateProfSerializer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import ProfSerializer from "./profSerializer";
import { Professor as PrismaProfessor } from "@prisma/client";

class HydrateProfSerializer extends ProfSerializer<PrismaProfessor> {
_serializeProf(prof: PrismaProfessor): PrismaProfessor {
static _serializeProf(prof: PrismaProfessor): PrismaProfessor {
return prof;
}
}
Expand Down
15 changes: 5 additions & 10 deletions serializers/hydrateSerializer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,10 @@
} from "../types/searchTypes";

class HydrateSerializer {
courseSerializer: HydrateCourseSerializer;
profSerializer: HydrateProfSerializer;
static courseSerializer: HydrateCourseSerializer;
static profSerializer: HydrateProfSerializer;

constructor() {
this.courseSerializer = new HydrateCourseSerializer();
this.profSerializer = new HydrateProfSerializer();
}

async bulkSerialize(instances: any[]): Promise<SearchResult[]> {
static async bulkSerialize(instances: any[]): Promise<SearchResult[]> {

Check warning on line 22 in serializers/hydrateSerializer.ts

View workflow job for this annotation

GitHub Actions / Lint & Type checks

Unexpected any. Specify a different type
const profs = instances.filter((instance) => {
return instance._source.type === "employee";
});
Expand All @@ -49,11 +44,11 @@
},
});

const serializedProfs = (await this.profSerializer.bulkSerialize(
const serializedProfs = (await HydrateProfSerializer.bulkSerialize(
profData,
)) as Record<string, ProfessorSearchResult>;

const serializedCourses = (await this.courseSerializer.bulkSerialize(
const serializedCourses = (await HydrateCourseSerializer.bulkSerialize(
courseData,
)) as Record<string, CourseSearchResult>;

Expand Down
Loading
Loading