Skip to content

Commit

Permalink
Add breadcrumbs to the dashboard views
Browse files Browse the repository at this point in the history
  • Loading branch information
acelaya committed Jun 3, 2024
1 parent d16402e commit b93b962
Show file tree
Hide file tree
Showing 5 changed files with 140 additions and 30 deletions.
22 changes: 10 additions & 12 deletions lms/static/scripts/frontend_apps/api-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -153,12 +153,21 @@ export type BaseDashboardStats = {
replies: number;
};

/**
* Response for `/api/dashboard/courses/{course_id}` call.
*/
export type Course = {
id: number;
title: string;
};

/**
* Response for `/api/dashboard/assignments/{assignment_id}` call.
*/
export type Assignment = {
id: number;
title: string;
course: Course;
};

/**
Expand All @@ -170,21 +179,10 @@ export type StudentStats = BaseDashboardStats & {

export type StudentsStats = StudentStats[];

/**
* Response for `/api/dashboard/courses/{course_id}` call.
*/
export type Course = {
id: number;
title: string;
};

/**
* Response for `/api/dashboard/courses/{course_id}/assignments/stats` call.
*/
export type AssignmentStats = {
id: number;
title: string;
course: Course;
export type AssignmentStats = Assignment & {
stats: BaseDashboardStats;
};

Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,12 @@
import {
Card,
CardContent,
CardHeader,
CardTitle,
} from '@hypothesis/frontend-shared';
import { Card, CardContent, CardHeader } from '@hypothesis/frontend-shared';
import { useParams } from 'wouter-preact';

import type { Assignment, StudentsStats } from '../../api-types';
import { useConfig } from '../../config';
import { useAPIFetch } from '../../utils/api';
import { formatDateTime } from '../../utils/date';
import { replaceURLParams } from '../../utils/url';
import DashboardBreadcrumbs from './DashboardBreadcrumbs';
import OrderableActivityTable from './OrderableActivityTable';

/**
Expand All @@ -27,21 +23,31 @@ export default function AssignmentActivity() {
replaceURLParams(routes.assignment_stats, { assignment_id: assignmentId }),
);

const title = `Assignment: ${assignment.data?.title}`;

return (
<Card>
<CardHeader fullWidth>
<CardTitle tagName="h2" data-testid="title">
<CardHeader fullWidth classes="flex-col !gap-x-0 !items-start">
{assignment.data && (
<div className="mb-3 mt-1">
<DashboardBreadcrumbs
links={[
{
title: assignment.data.course.title,
href: `/courses/${assignment.data.course.id}`,
},
]}
/>
</div>
)}
<h2 data-testid="title" className="text-lg text-brand font-semibold">
{assignment.isLoading && 'Loading...'}
{assignment.error && 'Could not load assignment title'}
{assignment.data && title}
</CardTitle>
{assignment.data && assignment.data.title}
</h2>
</CardHeader>
<CardContent>
<OrderableActivityTable
loading={students.isLoading}
title={assignment.isLoading ? 'Loading...' : title}
title={assignment.data?.title ?? 'Loading...'}
emptyMessage={
students.error ? 'Could not load students' : 'No students found'
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import {
ArrowLeftIcon,
CaretRightIcon,
Link,
} from '@hypothesis/frontend-shared';
import classnames from 'classnames';
import { Fragment } from 'preact';
import { Link as RouterLink } from 'wouter-preact';

type BreadcrumbLink = {
title: string;
href: string;
};

export type DashboardBreadcrumbsProps = {
links: BreadcrumbLink[];
};

function BreadcrumbLink({
title,
href,
classes,
}: BreadcrumbLink & { classes?: string }) {
return (
<RouterLink href={href} asChild>
<Link
underline="hover"
variant="text-light"
classes={classnames('truncate md:max-w-[350px]', classes)}
>
<ArrowLeftIcon className="md:hidden mr-2 inline" />
{title}
</Link>
</RouterLink>
);
}

export default function DashboardBreadcrumbs({
links,
}: DashboardBreadcrumbsProps) {
return (
<div
className="font-bold flex flex-col md:flex-row gap-0.5"
data-testid="breadcrumbs-container"
>
{links.map(({ title, href }, index) => {
const isLastLink = index === links.length - 1;

return (
<Fragment key={`${index}${href}`}>
<BreadcrumbLink
href={href}
title={title}
classes={classnames('md:inline', {
// In mobile devices, show only the last link
hidden: !isLastLink,
})}
/>
{!isLastLink && <CaretRightIcon />}
</Fragment>
);
})}
</div>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,14 @@ describe('AssignmentActivity', () => {
beforeEach(() => {
fakeUseAPIFetch = sinon.stub().callsFake(url => ({
isLoading: false,
data: url.endsWith('stats') ? students : { title: 'The title' },
data: url.endsWith('stats')
? students
: {
title: 'The title',
course: {
title: 'The course',
},
},
}));
fakeConfig = {
dashboard: {
Expand Down Expand Up @@ -72,7 +79,7 @@ describe('AssignmentActivity', () => {
fakeUseAPIFetch.returns({ isLoading: true });

const wrapper = createComponent();
const titleElement = wrapper.find('CardTitle[data-testid="title"]');
const titleElement = wrapper.find('[data-testid="title"]');
const tableElement = wrapper.find('OrderableActivityTable');

assert.equal(titleElement.text(), 'Loading...');
Expand All @@ -83,7 +90,7 @@ describe('AssignmentActivity', () => {
fakeUseAPIFetch.returns({ error: new Error('Something failed') });

const wrapper = createComponent();
const titleElement = wrapper.find('CardTitle[data-testid="title"]');
const titleElement = wrapper.find('[data-testid="title"]');
const tableElement = wrapper.find('OrderableActivityTable');

assert.equal(titleElement.text(), 'Could not load assignment title');
Expand All @@ -92,9 +99,9 @@ describe('AssignmentActivity', () => {

it('shows expected title', () => {
const wrapper = createComponent();
const titleElement = wrapper.find('CardTitle[data-testid="title"]');
const titleElement = wrapper.find('[data-testid="title"]');
const tableElement = wrapper.find('OrderableActivityTable');
const expectedTitle = `Assignment: The title`;
const expectedTitle = 'The title';

assert.equal(titleElement.text(), expectedTitle);
assert.equal(tableElement.prop('title'), expectedTitle);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { checkAccessibility } from '@hypothesis/frontend-testing';
import { mount } from 'enzyme';

import DashboardBreadcrumbs from '../DashboardBreadcrumbs';

describe('DashboardBreadcrumbs', () => {
function createComponent(props = {}) {
return mount(<DashboardBreadcrumbs {...props} />);
}

[['foo', 'bar'], [], ['one', 'two', 'three']].forEach(links => {
it('shows expected amount of links', () => {
const wrapper = createComponent({
title: 'Hello world',
links: links.map(title => ({ title, href: `/${title}` })),
});

assert.equal(wrapper.find('BreadcrumbLink').length, links.length);
});
});

it(
'should pass a11y checks',
checkAccessibility({
content: () =>
createComponent({
links: [
{ title: 'Foo', href: '/foo' },
{ title: 'Bar', href: '/bar' },
],
}),
}),
);
});

0 comments on commit b93b962

Please sign in to comment.