Skip to content

Commit

Permalink
Add search on business unit (infosupport#152)
Browse files Browse the repository at this point in the history
* init prisma schema

* add Select Business Unit

* extend search capabilities

* edit route and conditional rendering

* lint

* fix test

* try to fix test

* test

* reset test

* tests survey

* survey test

* default view results page

* refactor

* Remove Results page component and related logic

* Refactor result fetching logic and improve query conditions in Results page

---------

Co-authored-by: Luca Celea <[email protected]>
  • Loading branch information
JefQuidousse2 and Luca Celea authored Dec 13, 2024
1 parent 445c0cd commit dfa3e1b
Show file tree
Hide file tree
Showing 13 changed files with 538 additions and 171 deletions.
8 changes: 8 additions & 0 deletions prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,12 @@ enum CommunicationMethod {
TEAMS
}

model BusinessUnit {
id String @id @default(cuid())
unit String @unique
users User[]
}

model Survey {
id String @id @default(cuid())
surveyName String @unique
Expand Down Expand Up @@ -74,6 +80,8 @@ model User {
questionResults QuestionResult[]
communicationPreferences CommunicationPreference[]
sessions Session[]
businessUnitId String?
businessUnit BusinessUnit? @relation(fields: [businessUnitId], references: [id])
}

model Account {
Expand Down
37 changes: 37 additions & 0 deletions prisma/seed.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,33 @@ function parseCSV(filePath) {
});
}

/**
* Parses a CSV file to extract businessUnits
* @param {string} filePath - Path to the CSV file.
* @returns {Promise<{ businessUnits: string[]}>} An object containing businessUnits
*/
function parseCSVBusinessUnit(filePath) {
return new Promise((resolve, reject) => {
/** @type {string[]} */
const businessUnits = [];

fs.createReadStream(filePath, { encoding: "utf-8" })
.pipe(csv({ separator: ";" }))
.on("data", (/** @type {Record<string, string>} */ row) => {
/** @type {string} */
const unitKey = Object.keys(row)[0] ?? "";
businessUnits.push(row[unitKey] ?? "");
})
.on("end", () => {
console.log(businessUnits);
resolve({ businessUnits });
})
.on("error", (error) => {
reject(error);
});
});
}

/**
* Main function to seed the database with survey data.
* @returns {Promise<void>} A Promise that resolves when seeding is complete.
Expand Down Expand Up @@ -115,6 +142,15 @@ async function main() {
},
});
}

const {businessUnits} = await parseCSVBusinessUnit("./import/businessUnits.csv");
for (const unit in businessUnits) {
await prisma.businessUnit.create({
data: {
unit: businessUnits[unit] ?? ""
},
})
}
} catch (error) {
console.error("An error occurred:", error);
}
Expand All @@ -129,3 +165,4 @@ main()
await prisma.$disconnect();
process.exit(1);
});

2 changes: 1 addition & 1 deletion src/app/find-the-expert/[role]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type { Metadata } from "next";
import type { Session } from "next-auth";

import { Suspense } from "react";
import { ShowRolesWrapper } from "~/app/result/[role]/page";
import { ShowRolesWrapper } from "~/app/result/page";
import ButtonSkeleton from "~/components/loading/button-loader";
import { Login } from "~/components/login";
import ShowDataTable from "~/components/show-data-table";
Expand Down
9 changes: 7 additions & 2 deletions src/app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { getServerAuthSession } from "~/server/auth";
import SelectRole from "../components/select-role";
import SelectRole from "../components/select-input";

import React, { Suspense } from "react";
import { type Session } from "next-auth";
Expand Down Expand Up @@ -97,14 +97,15 @@ const Home: React.FC = async () => {
const SelectRoleWrapper: React.FC<{ session: Session }> = async ({
session,
}) => {
const [roles, userRoles, userCommunicationMethods] = await Promise.all([
const [roles, userRoles, userCommunicationMethods, businessUnits] = await Promise.all([
db.role.findMany(),
db.user.findUnique({
where: {
id: session.user.id,
},
include: {
roles: true,
businessUnit: true
},
}),
db.user.findUnique({
Expand All @@ -115,9 +116,11 @@ const SelectRoleWrapper: React.FC<{ session: Session }> = async ({
communicationPreferences: true,
},
}),
db.businessUnit.findMany()
]);

const userSelectedRoles = userRoles?.roles ?? [];
const useSelectedBusinessUnit = userRoles?.businessUnit ?? undefined;
const communicationPreferences =
userCommunicationMethods?.communicationPreferences ?? [];
const methods = communicationPreferences.map((method) => method.methods);
Expand All @@ -129,6 +132,8 @@ const SelectRoleWrapper: React.FC<{ session: Session }> = async ({
roles={roles}
userSelectedRoles={userSelectedRoles}
methods={methodStrings}
businessUnits={businessUnits}
userSelectedBusinessUnit={useSelectedBusinessUnit}
/>
);
};
Expand Down
97 changes: 0 additions & 97 deletions src/app/result/[role]/page.tsx

This file was deleted.

169 changes: 169 additions & 0 deletions src/app/result/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
import { Suspense } from "react";
import ResultsWrapper from "~/components/results";
import {
type QuestionResult,
type Section,
type TransformedData,
} from "~/models/types";
import { db } from "~/server/db";
import { SelectRoleResults } from "../../components/select-role-results";

import type { BusinessUnit, Prisma } from "@prisma/client";
import { type Metadata } from "next";
import ButtonSkeleton from "~/components/loading/button-loader";
import LegendSkeleton from "~/components/loading/results-loader";
import { Login } from "~/components/login";
import SearchAnonymized from "~/components/ui/search-anonymized";
import { getServerAuthSession } from "~/server/auth";
import { generateRolesWithHref } from "~/utils/role-utils";

export const metadata: Metadata = {
title: "Results",
};

const Results = async (context: { searchParams: {role:string, unit:string}}) => {
const session = await getServerAuthSession();
return (
<div className="container flex flex-col items-center justify-center gap-12 px-4 py-16">
<h1 className="text-center text-5xl font-extrabold tracking-tight">
<span className="block text-custom-primary sm:inline">
Info Support
</span>
<span className="block sm:inline"> Tech Survey - Results</span>
</h1>
{!session && (
<>
<p className="text-center text-lg">
Unable to view anonymized results without logging in.
</p>
<Login session={session} text={"Log in"} />
</>
)}
{session && (
<>
<Suspense fallback={<ButtonSkeleton />}>
<ShowRolesWrapper path="/result" />
</Suspense>

<Suspense fallback={<LegendSkeleton />}>
<ShowResultsWrapper role={context.searchParams.role} unit={context.searchParams.unit}/>
</Suspense>
</>
)}
</div>
);
};

export const ShowRolesWrapper = async ({ path }: { path: string }) => {
const availableRoles = await generateRolesWithHref(path)();
const def = {
id: "",
href: path,
label: "No role",
current: false,
completed: false,
started: false,
currentCompleted: false
};
availableRoles.unshift(def as Section);
if (path.includes("expert")) {
return (
<SelectRoleResults roles={availableRoles} />
)
}

const availableUnits = await db.businessUnit.findMany();
const defaultUnit = {
id: "",
unit: "No unit"
};
availableUnits.unshift(defaultUnit as BusinessUnit);

return (
<SearchAnonymized roles={availableRoles} businessUnits={availableUnits}/>
)
};

const FetchQuestionResults = async ({role, unit} : {role:string, unit:string}) => {
// If both role and unit are undefined, return an empty array
if (!role && !unit) return [];

// Base include object reused in all queries
const includeConfig = {
question: {
include: {
roles: true,
},
},
};

// Dynamically build the where conditions
const whereConditions: Prisma.QuestionResultWhereInput = {};

if (role) {
whereConditions.question = {
roles: {
some: {
role: {
equals: role,
mode: "insensitive",
},
},
},
};
}

if (unit) {
whereConditions.user = {
businessUnit: {
unit: {
equals: unit,
mode: "insensitive",
},
},
};
}

return await db.questionResult.findMany({
where: whereConditions,
include: includeConfig,
});
}

const ShowResultsWrapper = async ({role, unit} : {role:string, unit:string}) => {
const userAnswersForRole: QuestionResult[] = await FetchQuestionResults({role,unit});
const answerOptions = await db.answerOption.findMany();

const transformedData: TransformedData = {};

userAnswersForRole.forEach(({ question, answerId }) => {
const questionText = question?.questionText ?? "";
const roles = question?.roles ?? [];

if (role != undefined) {
roles.forEach(({ role: roleName = "" }) => {
if (roleName && questionText && roleName == role) {
transformedData[roleName] ??= {};
transformedData[roleName]![questionText] ??= {};

const answerString =
answerOptions.find(({ id }) => id === answerId)?.option ?? "";
transformedData[roleName]![questionText]![answerString] =
(transformedData[roleName]![questionText]![answerString] ?? 0) + 1;
}
});
} else if (unit != undefined) {
transformedData[unit] ??= {};
transformedData[unit]![questionText] ??= {};

const answerString =
answerOptions.find(({ id }) => id === answerId)?.option ?? "";
transformedData[unit]![questionText]![answerString] =
(transformedData[unit]![questionText]![answerString] ?? 0) + 1;
}
});

return <ResultsWrapper data={transformedData} />;
};

export default Results;
Loading

0 comments on commit dfa3e1b

Please sign in to comment.