Skip to content

Commit

Permalink
initial attempt at working navbar breadcrumbs
Browse files Browse the repository at this point in the history
  • Loading branch information
Ben Lerner committed Feb 21, 2024
1 parent 1e5466b commit 87d8352
Show file tree
Hide file tree
Showing 9 changed files with 501 additions and 84 deletions.
30 changes: 30 additions & 0 deletions app/graphql/types/user_type.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,35 @@ def image_url
def is_me
object == context[:current_user]
end

field :role, Types::CourseRoleType, null: false do
argument :course_id, ID, required: false
argument :exam_id, ID, loads: Types::ExamType, required: false
end
def role(course_id: nil, exam: nil)
if course_id
if context[:current_user].professor_course_registrations.find{|r| r.course_id == course_id}
:professor
elsif context[:current_user].proctor_registrations.includes(:course).find{|r| r.course.id == course_id}
:proctor
elsif context[:current_user].student_registrations.includes(:course).find{|r| r.course.id == course_id}
:student
else
:none
end
elsif exam
if context[:current_user].professor_course_registrations.find{|r| r.course_id == exam.course_id}
:professor
elsif context[:current_user].proctor_registrations.find{|r| r.exam_id == exam.id}
:proctor
elsif context[:current_user].registrations.includes(:exam).find{|r| r.exam_id == exam.id}
:student
else
:none
end
else
:none
end
end
end
end
51 changes: 46 additions & 5 deletions app/packs/components/common/navbar/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
} from 'react-bootstrap';
import { getCSRFToken } from '@student/exams/show/helpers';
import { Link } from 'react-router-dom';
import { createPortal } from 'react-dom';
import { useLazyLoadQuery, graphql, useMutation } from 'react-relay';
import { AlertContext } from '@hourglass/common/alerts';
import NotLoggedIn from './NotLoggedIn';
Expand All @@ -28,19 +29,56 @@ async function logOut(): Promise<unknown> {

const RN: React.FC<{
className?: string;
}> = ({ className }) => (
items?: NavbarItem[];
}> = ({ className, items }) => (
<Suspense
fallback={(
<NotLoggedIn />
)}
>
<RNQuery className={className} />
<RNQuery className={className} items={items} />
</Suspense>
);
export type NavbarItem = [string, React.ReactNode?];

export const NavbarBreadcrumbs: React.FC<{
items: NavbarItem[]
}> = ({ items }) => {
const breadcrumbs = document.getElementById('navbar-breadcrumbs');
if (breadcrumbs) {
return (
createPortal(
<RenderNavbarBreadcrumbs items={items} />,
breadcrumbs,
)
);
}
return (
<h4>
Go to
<RenderNavbarBreadcrumbs items={items} />
</h4>
);
};

const RenderNavbarBreadcrumbs: React.FC<{
items: NavbarItem[]
}> = ({ items }) => (
<>
{items.map(([link, title], i) => (
// eslint-disable-next-line react/no-array-index-key
<span key={i}>
<span className="mx-1">&raquo;</span>
{link ? <Link to={link}>{title}</Link> : title}
</span>
))}
</>
);

const RNQuery: React.FC<{
className?: string;
}> = ({ className }) => {
items?: NavbarItem[];
}> = ({ className, items }) => {
const { alert } = useContext(AlertContext);
const queryData = useLazyLoadQuery<navbarQuery>(
graphql`
Expand Down Expand Up @@ -68,11 +106,14 @@ const RNQuery: React.FC<{
expand="md"
className={className}
>
<Navbar.Brand>
<Link to="/" className="d-inline-flex align-items-center">
<Navbar.Brand className="d-inline-flex align-items-center">
<Link to="/">
<img src={NavbarLogo} alt="Hourglass" className="px-2 d-inline-block" style={{ height: 20 }} />
<span className="d-inline-block">Hourglass</span>
</Link>
<span id="navbar-breadcrumbs">
{items && <RenderNavbarBreadcrumbs items={items} />}
</span>
</Navbar.Brand>
<Navbar.Toggle />
<Navbar.Collapse className="justify-content-end">
Expand Down
115 changes: 98 additions & 17 deletions app/packs/components/workflows/grading/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ import { CurrentGrading } from '@professor/exams/types';
import { combineCompletionAny, CompletionStatus, ShowRubrics } from '@grading/UseRubrics';
import { CREATE_COMMENT_MUTATION, addCommentConfig } from '@grading/createComment';
import { DateTime } from 'luxon';
import { NavbarItem, NavbarBreadcrumbs } from '@hourglass/common/navbar';

import './index.scss';

Expand All @@ -128,6 +129,8 @@ import { gradingMyGrading$data, gradingMyGrading$key } from './__generated__/gra
import { gradingQuery } from './__generated__/gradingQuery.graphql';
import { gradingAdminQuery } from './__generated__/gradingAdminQuery.graphql';
import { gradingGraderQuery } from './__generated__/gradingGraderQuery.graphql';
import { CourseRole, gradingRoleQuery } from './__generated__/gradingRoleQuery.graphql';
import { gradingOneRoleQuery } from './__generated__/gradingOneRoleQuery.graphql';

export function variantForPoints(points: number | string): AlertProps['variant'] {
if (typeof points === 'string') {
Expand Down Expand Up @@ -1130,12 +1133,14 @@ const Grade: React.FC<{
qnum: number;
pnum: number;
refreshProps: React.DependencyList;
role: CourseRole;
}> = (props) => {
const {
registrationKey,
qnum,
pnum,
refreshProps,
role,
} = props;
const res = useFragment(
graphql`
Expand Down Expand Up @@ -1231,7 +1236,11 @@ const Grade: React.FC<{
const [showChangeProblems, setShowChangeProblems] = useState(false);
const cancelChangeProblems = () => {
setShowChangeProblems(false);
history.replace(`/exams/${examId}/grading`);
if (role === 'PROFESSOR') {
history.replace(`/exams/${examId}/grading/admin`);
} else {
history.replace(`/exams/${examId}/grading`);
}
};
const changeProblems = () => {
mutateGradeNext({
Expand Down Expand Up @@ -1261,6 +1270,8 @@ const Grade: React.FC<{
if (err.cause?.[0]?.extensions.anyRemaining) {
setChangeProblemsMessage(err.message);
setShowChangeProblems(true);
} else if (role === 'PROFESSOR') {
history.replace(`/exams/${examId}/grading/admin`);
} else {
history.replace(`/exams/${examId}/grading`);
}
Expand Down Expand Up @@ -1302,7 +1313,11 @@ const Grade: React.FC<{
RELEASE_LOCK_MUTATION,
{
onCompleted: () => {
window.location.href = `/exams/${examId}/grading`;
if (role === 'PROFESSOR') {
history.replace(`/exams/${examId}/grading/admin`);
} else {
history.replace(`/exams/${examId}/grading`);
}
},
onError: (err) => {
alert({
Expand Down Expand Up @@ -1666,12 +1681,28 @@ const GradeOnePart: React.FC = () => (
);

const GradeOnePartQuery: React.FC = () => {
const { registrationId, qnum, pnum } = useParams<{
registrationId: string, qnum: string, pnum: string
const {
examId,
registrationId,
qnum,
pnum,
} = useParams<{
examId: string, registrationId: string, qnum: string, pnum: string
}>();
const role = useLazyLoadQuery<gradingOneRoleQuery>(
graphql`
query gradingOneRoleQuery($examId: ID!) {
me { role(examId: $examId) }
}`,
{ examId },
);
const res = useLazyLoadQuery<gradingQuery>(
graphql`
query gradingQuery($registrationId: ID!, $withRubric: Boolean!) {
query gradingQuery($examId: ID!, $registrationId: ID!, $withRubric: Boolean!, $skipCourse: Boolean!) {
exam(id: $examId) {
id name
course @skip(if: $skipCourse) { id title }
}
registration(id: $registrationId) {
...grading_one
...grading_showOne
Expand All @@ -1685,16 +1716,35 @@ const GradeOnePartQuery: React.FC = () => {
}
}
`,
{ registrationId, withRubric: true },
{
examId,
registrationId,
withRubric: true,
skipCourse: role.me.role !== 'PROFESSOR',
},
);

const items: NavbarItem[] = useMemo(() => (
res.exam.course
? [
[`/course/${res.exam.course.id}`, res.exam.course.title],
[`/exams/${res.exam.id}/admin`, res.exam.name],
[undefined, 'Grading'],
]
: [
[undefined, res.exam.name],
[undefined, 'Grading'],
]), [res.exam.course?.id, res.exam.course?.title, res.exam.id, res.exam.name]);
return (
<GradeOnePartHelp
registrationId={registrationId}
registration={res.registration}
qnum={qnum}
pnum={pnum}
/>
<>
<NavbarBreadcrumbs items={items} />
<GradeOnePartHelp
registrationId={registrationId}
registration={res.registration}
qnum={qnum}
pnum={pnum}
role={role.me.role}
/>
</>
);
};

Expand All @@ -1703,12 +1753,14 @@ const GradeOnePartHelp: React.FC<{
pnum: string,
registrationId: string,
registration: gradingQuery['response']['registration'],
role: CourseRole,
}> = (props) => {
const {
registrationId,
registration,
qnum,
pnum,
role,
} = props;
const { qpPairs } = registration.examVersion;
const indexOfQP = qpPairs.findIndex((qp) => (
Expand Down Expand Up @@ -1757,6 +1809,7 @@ const GradeOnePartHelp: React.FC<{
qnum={qp.qnum}
pnum={qp.pnum}
refreshProps={[indexOfQP, refresher]}
role={role}
/>
) : (
<ShowOnePart
Expand Down Expand Up @@ -2326,19 +2379,39 @@ const GradingGrader: React.FC = () => (

const GradingGraderQuery: React.FC = () => {
const { examId } = useParams<{ examId: string }>();
const role = useLazyLoadQuery<gradingRoleQuery>(
graphql`
query gradingRoleQuery($examId: ID!) {
me { displayName role(examId: $examId) }
}`,
{ examId },
);
const res = useLazyLoadQuery<gradingGraderQuery>(
graphql`
query gradingGraderQuery($examId: ID!) {
query gradingGraderQuery($examId: ID!, $skipCourse: Boolean!) {
exam(id: $examId) {
course @skip(if: $skipCourse) { id title }
id name
...gradingBeginGrading
...gradingMyGrading
}
}
`,
{ examId },
}`,
{ examId, skipCourse: role.me.role !== 'PROFESSOR' },
);
const items: NavbarItem[] = useMemo(() => (
res.exam.course
? [
[`/course/${res.exam.course.id}`, res.exam.course.title],
[`/exams/${res.exam.id}/admin`, res.exam.name],
[undefined, 'Grading'],
]
: [
[undefined, res.exam.name],
[undefined, 'Grading'],
]), [res.exam.id, res.exam.name]);
return (
<Container>
<NavbarBreadcrumbs items={items} />
<BeginGradingButton examKey={res.exam} />
<MyGrading examKey={res.exam} />
</Container>
Expand Down Expand Up @@ -2647,6 +2720,8 @@ const GradingAdminQuery: React.FC = () => {
graphql`
query gradingAdminQuery($examId: ID!) {
exam(id: $examId) {
id name
course { id title }
...gradingExamAdmin
...gradingBeginGrading
...gradingMyGrading
Expand All @@ -2655,8 +2730,14 @@ const GradingAdminQuery: React.FC = () => {
`,
{ examId },
);
const items: NavbarItem[] = useMemo(() => [
[`/courses/${res.exam.course.id}`, res.exam.course.title],
[`/course/${res.exam.id}/admin`, res.exam.name],
[undefined, 'Grading'],
], [res.exam.course.id, res.exam.course.title, res.exam.id, res.exam.name]);
return (
<>
<NavbarBreadcrumbs items={items} />
<ExamGradingAdministration examKey={res.exam} />
<BeginGradingButton examKey={res.exam} />
<MyGrading examKey={res.exam} />
Expand Down
10 changes: 8 additions & 2 deletions app/packs/components/workflows/proctor/exams/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import React, {
useRef,
Suspense,
} from 'react';
import RegularNavbar from '@hourglass/common/navbar';
import RegularNavbar, { NavbarItem } from '@hourglass/common/navbar';
import Select from 'react-select';
import { useParams } from 'react-router-dom';
import {
Expand Down Expand Up @@ -1974,6 +1974,7 @@ const ProctoringRecipients: React.FC<{
...exams_pins
id
name
course { id title }
examVersions(first: 100) @connection(key: "Exam_examVersions", filters: []) {
edges {
node {
Expand Down Expand Up @@ -2099,9 +2100,14 @@ const ProctoringRecipients: React.FC<{
})),
},
]), [recipients]);
const items: NavbarItem[] = useMemo(() => [
[`/courses/${res.course.id}`, res.course.title],
[`/course/${res.id}/admin`, res.name],
[undefined, 'Proctoring'],
], [res.course.id, res.course.title, res.id, res.name]);
return (
<>
<RegularNavbar className="row" />
<RegularNavbar className="row" items={items} />
<Row>
<Col>
<h1>
Expand Down
Loading

0 comments on commit 87d8352

Please sign in to comment.