diff --git a/src/components/AssessmentCard.tsx b/src/components/AssessmentCard.tsx new file mode 100644 index 00000000..1f37d897 --- /dev/null +++ b/src/components/AssessmentCard.tsx @@ -0,0 +1,33 @@ +import React from "react"; +import Link from "next/link"; +import Image from "../components/Image"; + +export interface AssessmentCardProps { + assessmentHref: string; + image: File[]; + title: string; + body: string; +} + +export const AssessmentCard: React.FC = ({ + assessmentHref, + image, + title, + body, +}) => ( +
+
+
+ + + assessment page view + + +
+
+

{title}

+

{body}

+
+
+
+); diff --git a/src/components/Banner.module.scss b/src/components/Banner.module.scss new file mode 100644 index 00000000..fcf1f201 --- /dev/null +++ b/src/components/Banner.module.scss @@ -0,0 +1,6 @@ +@import "~bootstrap/scss/_functions.scss"; +@import "~bootstrap/scss/_variables.scss"; + +.container { + background-color: $blue-700; +} diff --git a/src/components/Banner.tsx b/src/components/Banner.tsx new file mode 100644 index 00000000..33e698f5 --- /dev/null +++ b/src/components/Banner.tsx @@ -0,0 +1,30 @@ +import React from "react"; +import classnames from "classnames"; + +import styles from "./Banner.module.scss"; + +const Row: React.FC = ({ children }) => ( +
{children}
+); + +const Column: React.FC = ({ children }) => ( +
{children}
+); + +export interface PageBannerProps { + title: string; + text: string; +} + +export const PageBanner: React.FC = ({ title, text }) => ( +
+
+ + +

{title}

+

{text}

+
+
+
+
+); diff --git a/src/components/ButtonToPage.tsx b/src/components/ButtonToPage.tsx new file mode 100644 index 00000000..14277be7 --- /dev/null +++ b/src/components/ButtonToPage.tsx @@ -0,0 +1,17 @@ +import React from "react"; +import Link from "next/link"; + +export interface ButtonToPageProps { + text: string; + page: string; +} + +export const ButtonToPage: React.FC = ({ text, page }) => ( +
+
+ + {text} + +
+
+); diff --git a/src/components/DemoCourse.tsx b/src/components/DemoCourse.tsx new file mode 100644 index 00000000..06f9fb87 --- /dev/null +++ b/src/components/DemoCourse.tsx @@ -0,0 +1,43 @@ +import React from "react"; +import classnames from "classnames"; +import Link from "next/link"; + +import styles from "./Banner.module.scss"; + +const Row: React.FC = ({ children }) => ( +
{children}
+); + +const Column: React.FC = ({ children }) => ( +
{children}
+); + +export interface DemoCourseActionProps { + title: string; + text: string; + button: string; +} + +export const DemoCourseAction: React.FC = ({ + title, + text, + button, +}) => ( +
+
+ + +

{title}

+

{text}

+
+
+
+
+ + {button} + +
+
+
+
+); diff --git a/src/components/HomepageHeading.tsx b/src/components/HomepageHeading.tsx index 02b0a076..bfcb9d06 100644 --- a/src/components/HomepageHeading.tsx +++ b/src/components/HomepageHeading.tsx @@ -4,5 +4,5 @@ import classnames from "classnames"; import styles from "./HomepageHeading.module.scss"; export const HomepageHeading: React.FC = ({ children }) => ( -

{children}

+

{children}

); diff --git a/src/components/NavLink.module.scss b/src/components/NavLink.module.scss new file mode 100644 index 00000000..2d9d943a --- /dev/null +++ b/src/components/NavLink.module.scss @@ -0,0 +1,16 @@ +@import "~bootstrap/scss/_functions.scss"; +@import "~bootstrap/scss/_variables.scss"; + +.nav-link { + color: rgba(255, 255, 255, 1); + padding: 0.5rem 1rem; + text-decoration: none; +} + +.nav-link:hover { + color: $yellow; +} + +.nav-link.active { + color: $yellow; +} diff --git a/src/components/NavLink.tsx b/src/components/NavLink.tsx index 38270675..a080cb43 100644 --- a/src/components/NavLink.tsx +++ b/src/components/NavLink.tsx @@ -3,6 +3,8 @@ import Link from "next/link"; import { useRouter } from "next/router"; import classnames from "classnames"; +import styles from "./NavLink.module.scss"; + interface NavLinkProps { href: string; } @@ -14,7 +16,9 @@ const NavLink: React.FC = ({ href, children }) => { return ( {children} diff --git a/src/lib/images/assessment.png b/src/lib/images/assessment.png new file mode 100644 index 00000000..5458e106 Binary files /dev/null and b/src/lib/images/assessment.png differ diff --git a/src/lib/images/question.png b/src/lib/images/question.png new file mode 100644 index 00000000..7ef008fd Binary files /dev/null and b/src/lib/images/question.png differ diff --git a/src/lib/markdown-page.tsx b/src/lib/markdown-page.tsx new file mode 100644 index 00000000..d51aafc7 --- /dev/null +++ b/src/lib/markdown-page.tsx @@ -0,0 +1,31 @@ +import React from "react"; + +import Head from "next/head"; +import { MDXRemote, MDXRemoteSerializeResult } from "next-mdx-remote"; + +import mdxComponents from "../lib/mdxComponents"; + +export interface MarkdownPageProps { + source: MDXRemoteSerializeResult; + title: string; + summary?: string; +} + +export const MarkdownPage: React.FC = ({ + source, + title, + summary, +}) => ( + + + {title} | PrairieLearn + +
+
+

{title}

+ {summary &&

{summary}

} +
+ +
+
+); diff --git a/src/lib/markdown.tsx b/src/lib/markdown.tsx new file mode 100644 index 00000000..92b16346 --- /dev/null +++ b/src/lib/markdown.tsx @@ -0,0 +1,42 @@ +import fs from "fs-extra"; +import matter from "gray-matter"; +import { MDXRemoteSerializeResult } from "next-mdx-remote"; +import { serialize } from "next-mdx-remote/serialize"; +import remarkMath from "remark-math"; +import rehypeKatex from "rehype-katex"; + +import loadCodePlugin from "../remarkPlugins/loadCode"; +import extractImages from "../remarkPlugins/extractImages"; +import { MarkdownPageProps } from "./markdown-page"; + +export async function serializeMarkdownDocument( + doc: string, + filepath?: string +): Promise { + return serialize(doc, { + mdxOptions: { + remarkPlugins: [loadCodePlugin, extractImages, remarkMath], + rehypePlugins: [rehypeKatex], + filepath, + }, + }); +} + +export async function getPropsForMarkdownFile( + filePath: string +): Promise { + const rawContents = await fs.readFile(filePath, "utf8"); + + const { + content, + data: { title = "NO TITLE", summary = null }, + } = matter(rawContents); + + const source = await serializeMarkdownDocument(content, filePath); + + return { + source, + title, + summary, + }; +} diff --git a/src/pages/assessments/examInstantFeedback/assessment-generator.png b/src/pages/assessments/examInstantFeedback/assessment-generator.png new file mode 100644 index 00000000..b80640a7 Binary files /dev/null and b/src/pages/assessments/examInstantFeedback/assessment-generator.png differ diff --git a/src/pages/assessments/examInstantFeedback/docs.md b/src/pages/assessments/examInstantFeedback/docs.md new file mode 100644 index 00000000..315c7acd --- /dev/null +++ b/src/pages/assessments/examInstantFeedback/docs.md @@ -0,0 +1,45 @@ +--- +title: Exam +summary: Summative assessments that are auto-graded with instant feedback and retry opportunities +prairielearn_url: https://www.prairielearn.org/pl/course_instance/128605/assessment/2310709 +--- + +## Auto-graded randomized exams + +Studies have shown that learning and retention of knowledge is enhanced through retrieval practice that incorporates feedback and increased use of formative assessments. +Here we describe how we use PrairieLearn to create quizzes where students get immediate feedback, shortening the feedback cycle between student learning and assessment performance. This shorter cycle enables the use of frequent and second-chance testing, especially in large courses, which has been shown to lead to significant improvements in learning outcomes and better final exam performance. + +We will use the assessment [E1: Auto-graded randomized exams](https://www.prairielearn.org/pl/course_instance/128605/assessment/2310709) to highlight some of the PrairieLearn features to deliver auto-graded and randomized exams. + +### Instant feedback with retry attempts + +Using the default `Exam` configuration in PrairieLearn, students will receive a fixed question variant for each question generator. This feature matches a traditional paper-and-pencil experience, where the student receives one exam with fixed parameters. + +By default, PrairieLearn will auto-grade each question in real-time, and provide students with the feedback about correctness. Depending on how instructors define the question points, students can try to fix incorrect answers for the same parameters, and submit other attempts for reduced credit, mimicking the concept of partial credit. + +![](student-retry.png) + +This short feedback cycle allows students to reach out to instructors right after the test, enabling them to promptly review and clarify any missed contepts, and consequently make adjustments for upcoming assessments. + +**Question 1** asks students to compute a variable $c$ given two parameters $a$ and $b$. The formula to compute $c$ is randomized (selected from a set of 4 different formulas) and the parameters $a$ and $b$ are randomized as well. Students have two attempts to complete this question: the first attempt for full credit and the second attempt for partial credit (1/3 points). + +**Question 2** asks students to enter the matrix corresponding to a displayed graph, which is generated in real-time based on randomized parameters. Students have two attempts to complete the question. They can also receive partial credit for each attempt, since each entry of the matrix is graded separately. + +**Question 3** is highly randomized, in essence mixing 4 different questions into one, since the circuit diagram changes (parallel and series), and the question prompt changes (compute current or resistance). +Since the solution involves multiple computation steps, students get 5 attempts to complete the question for reduced credit. + +### Creating exams from question pools + +Exams that are delivered asynchronously or in online unproctored environments create an opportunity for _collaborative cheating_, where a student can gain advantage by receiving information about the exam from another student. Generating random exams from pools of problems has been shown to mitigate collaborative cheating. In PrairieLearn, question generators can be selected from what we call **alternative groups or pools**. For example, an alternative group with 4 question generators can select 2 of them at random to create a version of the exam. In the figure below, an exam with 4 questions is created from a set of 8 question generators. + +![](assessment-generator.png) + +In addition, question generators will create different question variants based on randomized parameters. These question variants will finally build a student exam, where questions appear in random order. Our studies indicate that pools of 3-4 question generators are effective to mitigate cheating. + +### Using information from previous exams to generate randomized exams with reduced difficulty variance + +A concern with randomized exams is how one can ensure students receive problems of roughly similar difficulty. Problems can be binned into pools by topic coverage and difficulty, but it can be challenging to generate problems of identical difficulty. When creating question generators for the first time, a instructor can use previous experiences to decide which ones should be combined in an alternative group. PrairieLearn will collect data from these questions, which later can be used by instructors to improve fairness of these randomized exams. + +![](question-generator.png) + +**Question 4** is randomly selected out of a pool of 3 question generators, each one of them asking students to compute a different matrix and/or vector operation, including matrix multiplication and outer product. Each question generator also utilizes randomized parameters. One of the advantages of keeping similar question variants within separate question generators is the easy access to statistics, providing information regarding question and exam fairness. The disadvantage is the cumbersome bookkeeping of question generators, since one may have to coordinate changes to many files when updates are needed. diff --git a/src/pages/assessments/examInstantFeedback/index.tsx b/src/pages/assessments/examInstantFeedback/index.tsx new file mode 100644 index 00000000..526e575b --- /dev/null +++ b/src/pages/assessments/examInstantFeedback/index.tsx @@ -0,0 +1,32 @@ +import React from "react"; +import classnames from "classnames"; +import Link from "next/link"; +import Head from "next/head"; +import { GetStaticProps } from "next"; +import path from "path"; + +import { getPropsForMarkdownFile } from "../../../lib/markdown"; +import { MarkdownPage, MarkdownPageProps } from "../../../lib/markdown-page"; + +import { PageBanner } from "../../../components/Banner"; +import Stack from "../../../components/Stack"; +import Image from "../../../components/Image"; + +import styles from "./index.module.scss"; + +export default MarkdownPage; + +export const getStaticProps: GetStaticProps = async () => { + return { + props: await getPropsForMarkdownFile( + path.resolve( + process.cwd(), + "src", + "pages", + "assessments", + "examInstantFeedback", + "docs.md" + ) + ), + }; +}; diff --git a/src/pages/assessments/examInstantFeedback/question-generator.png b/src/pages/assessments/examInstantFeedback/question-generator.png new file mode 100644 index 00000000..90fe9d5c Binary files /dev/null and b/src/pages/assessments/examInstantFeedback/question-generator.png differ diff --git a/src/pages/assessments/examInstantFeedback/student-retry.png b/src/pages/assessments/examInstantFeedback/student-retry.png new file mode 100644 index 00000000..ca1a0fac Binary files /dev/null and b/src/pages/assessments/examInstantFeedback/student-retry.png differ diff --git a/src/pages/assessments/groupWork/docs.md b/src/pages/assessments/groupWork/docs.md new file mode 100644 index 00000000..923901ad --- /dev/null +++ b/src/pages/assessments/groupWork/docs.md @@ -0,0 +1,36 @@ +--- +title: Group work +summary: Collaborative learning activities +prairielearn_url: https://www.prairielearn.org/pl/course_instance/128605/assessment/2310480 +--- + +## Group Activities + +Research shows that collaborative learning can increase student persistence, improve learning outcomes, and foster better classroom cultures. Using PrairieLearn, instructors can provide group activities where students work collaborativelly in the same assessment, which is shared among all the group members. + +### Group formation + +PrairieLearn provides the option for instructors to pre-arrange teams, or for students to create their own teams. For pre-assigned groups, instructors can select one of the following options: + +- upload a CSV file with the group information +- let PrairieLearn assign students to groups at random, given a mininum and maximum group sizes + +Instructors can also let students self-assign to groups. This can be especially helpful when giving group activities during lecture, where groups can be created "on-the-fly" depending on the proximity of students. Instructors can also provide the minium and maximum group sizes under this configuration. + +For self-assignment, a student will create a group providing a group name. This student will receive a "join code" that can be used by others that want to join the group. Once the group reaches the minimum size, students are able to start the assessment. Every member of the group will have access to the same question variants, and consequently will also share the same grade. + +### Facilitating collaboration among teams + +The simple creation of students' teams will rarely guarantee that students will work collaboratively. However, successful and productive collaborations can be greatly improved by careful design of the task, assignment of team roles and the use of available technologies to both promote collaborations among students and support the instructors implementing these activities. + +Assessments that are based on higher level skills such as _Analyze_, _Evaluate_ and _Create_ from the [Bloom's Taxonomy](https://en.wikipedia.org/wiki/Bloom's_taxonomy) produce more opportunities for students' interactions, where they can learn from each other. Low level skills which require students to remember simple concepts, or apply simple calculations will emphasize the existence of domineering leaders and free loaders. When designing group tasks, we focus on questions that produce discussions and decision-making. + +Another strategy to enhance collaborative learning is to provide activities that can be self-managed by the team, such that instructors act only as facilitators instead of source of information. In the course demo, we provide an example that uses JupyterLab notebooks for the [group assessment](https://www.prairielearn.org/pl/course_instance/128605/assessment/2310480). These notebooks can include text, images, and equations for content presentation, and also ask students to perform computations (in this example, using a Python 3 Kernel). + +We can use PrairieLearn external grader to check content for correctness. This will help students self-manage their progress. Instructors can define `#grade` cells inside the JupyterLab notebook, which will be auto-graded for instant feedback (see image below). + +![](group-page1.png) + +Students in the same group will share the same JupyterLab, and the same submission history and scores. Currently the JupyterLab syncs upon saving (real-time synchronization is a work-in-progress). + +A lack of clarity and experience in assuming team roles can lead students to default into domineering team leaders or passive free-loaders. Evidence-based practices such as Process Oriented Guided Inquiry Learning ([POGIL](https://pogil.org)) have shown that providing students with structured roles can help them participate more equitably during collaborative learning. We are currently implementing POGIL roles in PrairieLearn. diff --git a/src/pages/assessments/groupWork/group-page1.png b/src/pages/assessments/groupWork/group-page1.png new file mode 100644 index 00000000..3548e076 Binary files /dev/null and b/src/pages/assessments/groupWork/group-page1.png differ diff --git a/src/pages/assessments/groupWork/index.tsx b/src/pages/assessments/groupWork/index.tsx new file mode 100644 index 00000000..48ee97b3 --- /dev/null +++ b/src/pages/assessments/groupWork/index.tsx @@ -0,0 +1,32 @@ +import React from "react"; +import classnames from "classnames"; +import Link from "next/link"; +import Head from "next/head"; +import { GetStaticProps } from "next"; +import path from "path"; + +import { getPropsForMarkdownFile } from "../../../lib/markdown"; +import { MarkdownPage, MarkdownPageProps } from "../../../lib/markdown-page"; + +import { PageBanner } from "../../../components/Banner"; +import Stack from "../../../components/Stack"; +import Image from "../../../components/Image"; + +import styles from "./index.module.scss"; + +export default MarkdownPage; + +export const getStaticProps: GetStaticProps = async () => { + return { + props: await getPropsForMarkdownFile( + path.resolve( + process.cwd(), + "src", + "pages", + "assessments", + "groupWork", + "docs.md" + ) + ), + }; +}; diff --git a/src/pages/assessments/homework/docs.md b/src/pages/assessments/homework/docs.md new file mode 100644 index 00000000..86af90f6 --- /dev/null +++ b/src/pages/assessments/homework/docs.md @@ -0,0 +1,51 @@ +--- +title: Homework +summary: Mastery-based homework assessments +prairielearn_url: https://www.prairielearn.org/pl/course_instance/128605/assessment/2310476 +--- + +## Mastery-based homework assessments + +Educational research and learning theory show that small numbers of single-practice problems may not be the most effective learning strategy for students. Mastery learning theory shows that different students require different amounts of practice to achieve proficiency in a given skill, and that all students require repeated practice. In addition, different learning skills require different learning approaches. + +In the following text, we will illustrate how PrairieLearn can be used to create different learning experiences for students, and how they can be adjusted based on individual learning goals. We often use these homework as formative assessments, where students receive immediate feedback and have the opportunity to use the feedback to enhance their learning. + +PrairieLearn supports the development of **question generators**, defined by a set of html and python code that generate different **question variants** based on randomized parameters. A homework is defined by a collection of question generators. + +### Drilling for mastery: unlimited variants with single attempt + +In this configuration, the question generator creates unlimited question variants, each one with a single attempt. Once students submit an answer to a question, they receive immediate feedback, indicating if the question was correct, partially correct, or incorrect. The feedback may also include more detailed explanation. No matter if a submission is correct or not, students have the ability to generate another question variant with a single attempt. Moreover, students are not penalized when submitting an incorrect attempt. + +![Unlimited variants with single attemp](./unlimited-variants.png) + +Instructors can define the number of times a student needs to correctly answer a question variant to earn full credit. Since question variants present a different version of the question, this repetition provides students with the needed practice to achieve mastery. Even after students reach full credit for a given question, they can continue to work on other question variants for additional practice. We see students coming back to homework assessments when reviewing for exams. + +Question generators based on skill levels such as _Remember_, _Understand_ and _Apply_ from the [Bloom's Taxonomy](https://en.wikipedia.org/wiki/Bloom's_taxonomy) often involve a solution process that requires information retrival to answer conceptual questions or computation of simple expressions. These skills are the most appropriate for questions using the unlimited variants with single retry option. +The [demo homework assessment](https://www.prairielearn.org/pl/course_instance/128605/assessment/2310476) includes some examples using this configuration: + +**Question 1** asks students to demonstrate basic thinking and remembering skills, by selecting the numbers that are prime (or odd, or even, etc). Students need to successully complete at least 3 question variants in order to receive full credit. + +**Question 2** asks students to demonstrate their understanding of binary and decimal numbers, by converting a decimal number to its binary representation. Students need to successully complete at least 2 question variants in order to receive full credit. + +**Question 3** asks students to apply knowledge of derivatives to compute first order derivatives of polynomial equations. They need to successully complete at least 3 question variants in order to receive full credit. + +### Repeated variant: unlimited variants with prescribed number of retry attempts + +More sophisticated skill levels can require multiple steps during the solution process, or combine knowledge of different topics. When creating more complex questions, instructors may want to provide students with additional attempts per question variant. This avoids unnecessary frustration of starting fresh on a new question variant when a small mistake is made during the solution process. Using this configuration, students can create a new question variant if they answer the question correctly or if they use all the retry attempts. + +![Unlimited variants with prescribed number of retry attempts](./set-retry-per-variant.png) + +Similarly to the above configuration, instructors can define the number of times a student needs to correctly answer a question variant to earn full credit. The [demo homework assessment](https://www.prairielearn.org/pl/course_instance/128605/assessment/2310476) includes some examples using this configuration: + +**Question 4** asks students to illustrate a vector with given position and orientation. Each question variant has two attempts, allowing students to retry the same question before generaring a new variant. In this example, students may have the correct understanding of the question, but potentially count the position incorrectly, or miss the correct orientation (clockwise or counter-clockwise). The second attempt gives students the opportunity to adjust their thinking, without having to start from the beginning. They need to successully complete at least 2 question variants in order to receive full credit. + +**Question 5** asks students to compute a quantity (stress) based on information obtained from a table that is randomized for each question variant. The computation of the stress involves several steps: finding the correct information from the table, identifying the correct equation and performing mathematical operations using the appropriate units. Since students can make small mistakes during the solution process, but still have the overall correct understanding of the problem, we provide 3 retry attempts for each question variant. Students need to successully complete at least 2 question variants in order to receive full credit. + +### Fixed variant: unlimited retry attempts for a single question variant + +There are some situations where we want students to receive a single question variant +and have unlimited attempts to complete the question successfully. This is desirable when the question involves a lot of computation, or includes specialized coding. + +![Unlimited retry attempts for a single question variant](./one-variant-unlimited-attempts.png) + +**Question 6** from [demo homework assessment](https://www.prairielearn.org/pl/course_instance/128605/assessment/2310476) asks students to apply knowledge from a given topic to contruct the solution of a "real-world" problem. Students have unlimited attempts to submit the correct solution for full credit. diff --git a/src/pages/assessments/homework/index.tsx b/src/pages/assessments/homework/index.tsx new file mode 100644 index 00000000..f3e075da --- /dev/null +++ b/src/pages/assessments/homework/index.tsx @@ -0,0 +1,32 @@ +import React from "react"; +import classnames from "classnames"; +import Link from "next/link"; +import Head from "next/head"; +import { GetStaticProps } from "next"; +import path from "path"; + +import { getPropsForMarkdownFile } from "../../../lib/markdown"; +import { MarkdownPage, MarkdownPageProps } from "../../../lib/markdown-page"; + +import { PageBanner } from "../../../components/Banner"; +import Stack from "../../../components/Stack"; +import Image from "../../../components/Image"; + +import styles from "./index.module.scss"; + +export default MarkdownPage; + +export const getStaticProps: GetStaticProps = async () => { + return { + props: await getPropsForMarkdownFile( + path.resolve( + process.cwd(), + "src", + "pages", + "assessments", + "homework", + "docs.md" + ) + ), + }; +}; diff --git a/src/pages/assessments/homework/one-variant-unlimited-attempts.png b/src/pages/assessments/homework/one-variant-unlimited-attempts.png new file mode 100644 index 00000000..4a9d7cb4 Binary files /dev/null and b/src/pages/assessments/homework/one-variant-unlimited-attempts.png differ diff --git a/src/pages/assessments/homework/set-retry-per-variant.png b/src/pages/assessments/homework/set-retry-per-variant.png new file mode 100644 index 00000000..259e2e62 Binary files /dev/null and b/src/pages/assessments/homework/set-retry-per-variant.png differ diff --git a/src/pages/assessments/homework/unlimited-variants.png b/src/pages/assessments/homework/unlimited-variants.png new file mode 100644 index 00000000..995cbe0b Binary files /dev/null and b/src/pages/assessments/homework/unlimited-variants.png differ diff --git a/src/pages/assessments/index.module.scss b/src/pages/assessments/index.module.scss new file mode 100644 index 00000000..1e02ab74 --- /dev/null +++ b/src/pages/assessments/index.module.scss @@ -0,0 +1,18 @@ +@import "~bootstrap/scss/_functions.scss"; +@import "~bootstrap/scss/_variables.scss"; + +// .grid { +// display: grid; +// grid-template-columns: repeat(auto-fill, minmax(16rem, 1fr)); +// grid-gap: 1rem; +// } + +.grid { + display: grid; + grid-template-rows: repeat(auto-fill, minmax(4rem, 1fr)); + grid-gap: 1rem; +} + +.container { + background-color: $gray-200; +} diff --git a/src/pages/assessments/index.tsx b/src/pages/assessments/index.tsx new file mode 100644 index 00000000..b416e3fb --- /dev/null +++ b/src/pages/assessments/index.tsx @@ -0,0 +1,66 @@ +import React from "react"; +import classnames from "classnames"; +import Link from "next/link"; +import Head from "next/head"; + +import { AssessmentCard } from "../../components/AssessmentCard"; +import { PageBanner } from "../../components/Banner"; +import { DemoCourseAction } from "../../components/DemoCourse"; +import Stack from "../../components/Stack"; +import Image from "../../components/Image"; + +import styles from "./index.module.scss"; +import assessmentImage from "../../lib/images/assessment.png"; + +export default function Assessment() { + return ( + + + Assessments | PrairieLearn + + + +
+ + + + + + + +
+ + +
+ ); +} diff --git a/src/pages/assessments/preLecture/docs.md b/src/pages/assessments/preLecture/docs.md new file mode 100644 index 00000000..2214da1a --- /dev/null +++ b/src/pages/assessments/preLecture/docs.md @@ -0,0 +1,23 @@ +--- +title: Lecture +summary: Introducing new concepts online with checkpoints and instant feedback +prairielearn_url: https://www.prairielearn.org/pl/course_instance/128605/assessment/2310475 +--- + +## Introducing course content online + +Learning can happen in a variety of ways: in classrooms with a teacher-centered style, in flipped lectures, individual work or collaborative activities, asynchronously or synchronously online. Just like there are different teaching methods, people also learn in different ways. Some learners prefer to read, some prefer to watch videos, and others like hands-on work. Mixing different learning approaches can improve the learner engagement. In this assessment, we combine a mixture of text, equations, plots, videos and interactive JupyterLabs to introduce a new topic where students can learn at their own pace. The assessment includes short formative questions, so that students can assess their own learning. + +We will use the assessment [L1: Learning new concepts asynchronously](https://www.prairielearn.org/pl/course_instance/128605/assessment/2310475) to highlight some of the PrairieLearn features to deliver course content online. + +#### Question 1 + +This question generator illustrates how we can combine text and equations to introduce new content to students. It follows with simple conceptual checkpoints to help students test their own understanding. Students have two attempts to complete these checkpoints ([repeated variant configuration](../../04-Homework/__docs/docs.md)), and they can create a new question variant if they answer them correctly or if they use all the retry attempts. At the end of each question variant, a video with the explanation of the question is displayed. Note that embedding videos inside questions is another method of content delivery that can be used with PrairieLearn. + +#### Question 2 + +Here we use a JupyterLab notebook for an interactive example to support the introduction of another concept. Students need to answer a simple multiple-choice question to test their understanding of the notebook. + +#### Question 3 + +The last question includes formative short questions that summarize the concepts introduced in the previous two questions. Several parameters are randomized, so that students can get more practice by generating different question variants, each one with two attempts. diff --git a/src/pages/assessments/preLecture/index.tsx b/src/pages/assessments/preLecture/index.tsx new file mode 100644 index 00000000..4956249d --- /dev/null +++ b/src/pages/assessments/preLecture/index.tsx @@ -0,0 +1,32 @@ +import React from "react"; +import classnames from "classnames"; +import Link from "next/link"; +import Head from "next/head"; +import { GetStaticProps } from "next"; +import path from "path"; + +import { getPropsForMarkdownFile } from "../../../lib/markdown"; +import { MarkdownPage, MarkdownPageProps } from "../../../lib/markdown-page"; + +import { PageBanner } from "../../../components/Banner"; +import Stack from "../../../components/Stack"; +import Image from "../../../components/Image"; + +import styles from "./index.module.scss"; + +export default MarkdownPage; + +export const getStaticProps: GetStaticProps = async () => { + return { + props: await getPropsForMarkdownFile( + path.resolve( + process.cwd(), + "src", + "pages", + "assessments", + "preLecture", + "docs.md" + ) + ), + }; +}; diff --git a/src/pages/contact/index.module.scss b/src/pages/contact/index.module.scss new file mode 100644 index 00000000..411d66ad --- /dev/null +++ b/src/pages/contact/index.module.scss @@ -0,0 +1,6 @@ +@import "~bootstrap/scss/_functions.scss"; +@import "~bootstrap/scss/_variables.scss"; + +.container { + background-color: $gray-200; +} diff --git a/src/pages/contact/index.tsx b/src/pages/contact/index.tsx index 99c0b259..7e74ff21 100644 --- a/src/pages/contact/index.tsx +++ b/src/pages/contact/index.tsx @@ -1,7 +1,13 @@ import React from "react"; import Head from "next/head"; +import classnames from "classnames"; import { ContactUsForm } from "../../components/ContactUsForm"; +import { PageBanner } from "../../components/Banner"; +import { DemoCourseAction } from "../../components/DemoCourse"; +import { HomepageHeading } from "../../components/HomepageHeading"; + +import styles from "./index.module.scss"; export default function Contact() { return ( @@ -9,19 +15,37 @@ export default function Contact() { Contact us | PrairieLearn -
-

Contact us

-

- Want to learn more about how PrairieLearn can help your course or - institution? Need to schedule a demo? Interested in managed hosting? - Let us know below and someone from our team will be in touch soon. -

-
+ + +
+
+
+ Contact us! +
+
+
+ Need to schedule a demo? Want to have access to your own course + space? Let us know below and someone from our team will be in + touch soon. +
+
+
+ +
+ + ); } diff --git a/src/pages/gallery/elementIntro.md b/src/pages/gallery/elementIntro.md new file mode 100644 index 00000000..f7107bd2 --- /dev/null +++ b/src/pages/gallery/elementIntro.md @@ -0,0 +1,82 @@ +--- +title: Introduction to PrairieLearn questions +--- + +PrairieLearn questions are built on top of widely-used technologies like HTML, CSS, JavaScript, and Python. + +## question.html + +All questions start with a `question.html` file. This file determines what content students will see when they visit a question. + +Here's an extremely simple question: + +```html +

In what year was the University of Illinois Urbana-Champaign establised?

+ + +``` + +It shows the question ("In what year was the University of Illinois Urbana-Champaign established?") and an input for the student to submit their answer. + +However, this question isn't very useful yet. While it can accept an answer from a student, it has no notion of what the correct answer is, and thus no way to provide feedback to the student. To fix that, we'll use a PrairieLearn _element_. + +## Elements + +PrairieLearn elements provide reusable building blocks for questions. They encapsulate the logic needed to render inputs, collect and grade submissions, and provide useful feedback to students. You use them via special HTML tags with a `pl-` prefix. The [PrairieLearn documentation](https://prairielearn.readthedocs.io/en/latest/elements/) contains a full list of the available elements, but here are some of the most commonly-used ones: + +- `` accepts string values like "Illinois", "GATTACA", and "computer" +- `` accepts integer values like $0$, $-71$, and $5$ +- `` accepts symbolic values like $x^2$, $\sin(z)$, and $mc^2$ + +In the example question we're working with, we want to collect and grade a year value. The `` element contains all the logic necessary to do that. + +```html +

In what year was the University of Illinois Urbana-Champaign established?

+ + +``` + +The `answers-name` attribute is a unique key used to store the answer, and the `correct-answer` attribute specifies the string that will be treated as the correct answer. + +We now have a question that can correctly grade itslef - a student entering "1868" as their answer will recieve full marks. + +## Templates + +The above question is still relatively simple. It can grade itself, but that's about it. PrairieLearn really shines when you can introduce an element of randomization to questions. + +To facilitate randomization, `question.html` files support the [Mustache template syntax](https://mustache.github.io). This allows you to easily include dynamically-generated values in your questions. Let's modify our previous `question.html` file to support randomization: + +```html +

In what year was the {{params.university_name}} established?

+ + +``` + +We replaced the hardcoded university name with `{{params.university_name}}` and dropped the hardcoded `correct-answer="1868"` from the `` element. Now we can randomly select a different university/year pair for each student using this question. To actually generate the parameters, we'll introduce a new file: `server.py`. + +## server.py + +Adding a `server.py` file allows you to write custom logic for generating question, parsing and grading submissions, and providing feedback. You have the full power of Python and many of its libraries (such as [numpy](https://numpy.org), [matplotlib](https://matplotlib.org), and [scipy](https://www.scipy.org)) at your disposal. + +For our example question, we need to randomly select a university name and the year it was established. We can accomplish that with the following Python code in `server.py`: + +```python +import random + +UNIVERSITY_YEARS = { + "University of Illinois at Urbana-Champaign": "1868", + "University of British Columbia": "1908", + "University of California, Berkeley": "1868" +} + +def generate(data): + university_name, year = random.choice(list(UNIVERSITY_YEARS.items())) + + data["pararms"]["university_name"] = university_name + data["correct_answers"]["year"] = year +``` + +`generate(...)` is a special function that PrairieLearn will call when it needs to generate a new variant of your question. The `data` argument is a dictionary that PrairieLearn uses to maintain state and pass that state between different elements in a given question. Let's look at the two values we insert into `data` in more detail. + +- `data["params"]["university_name"]` will be used by the `{{params.university_name}}` template in `question.html` +- `data["correct_answers"]["year"]` will be used by the `` to determine what the correct answer is. Note how the key `"year"` matches the attribute `answers-name="year"` on the element - this is how `` knows which value from `data["correct_answers"]` to use. diff --git a/src/pages/gallery/elementIntro.tsx b/src/pages/gallery/elementIntro.tsx new file mode 100644 index 00000000..767c406b --- /dev/null +++ b/src/pages/gallery/elementIntro.tsx @@ -0,0 +1,15 @@ +import { GetStaticProps } from "next"; +import path from "path"; + +import { getPropsForMarkdownFile } from "../../lib/markdown"; +import { MarkdownPage, MarkdownPageProps } from "../../lib/markdown-page"; + +export default MarkdownPage; + +export const getStaticProps: GetStaticProps = async () => { + return { + props: await getPropsForMarkdownFile( + path.resolve(process.cwd(), "src", "pages", "gallery", "elementIntro.md") + ), + }; +}; diff --git a/src/pages/gallery/index.module.scss b/src/pages/gallery/index.module.scss index cc99b893..301b8f46 100644 --- a/src/pages/gallery/index.module.scss +++ b/src/pages/gallery/index.module.scss @@ -1,5 +1,12 @@ +@import "~bootstrap/scss/_functions.scss"; +@import "~bootstrap/scss/_variables.scss"; + .grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(16rem, 1fr)); grid-gap: 1rem; } + +.container { + background-color: $gray-200; +} diff --git a/src/pages/gallery/index.tsx b/src/pages/gallery/index.tsx index 74afe805..d504ce29 100644 --- a/src/pages/gallery/index.tsx +++ b/src/pages/gallery/index.tsx @@ -5,17 +5,17 @@ import Link from "next/link"; import Head from "next/head"; import Image from "../../components/Image"; + +import { HomepageHeading } from "../../components/HomepageHeading"; +import { PageBanner } from "../../components/Banner"; +import { DemoCourseAction } from "../../components/DemoCourse"; +import { ButtonToPage } from "../../components/ButtonToPage"; + import { getQuestions } from "../../lib/gallery/questions"; -import { getAssessments } from "../../lib/gallery/assessments"; import styles from "./index.module.scss"; -import Stack from "../../components/Stack"; - -interface Assessment { - title: string; - slug: string; - summary: string; -} +import assessmentImage from "../../lib/images/assessment.png"; +import questionImage from "../../lib/images/question.png"; interface Question { title: string; @@ -25,102 +25,133 @@ interface Question { } interface GalleryIndexProps { - assessments: Assessment[]; questions: Question[]; } -const GalleryIndex: React.FC = ({ - assessments, - questions, -}) => { +const GalleryIndex: React.FC = ({ questions }) => { return ( Gallery | PrairieLearn -
-
-

Gallery

-

- Explore all the functionality PrairieLearn has to offer. -

+ + +
+
+
+
+ assessment page view +
+
+ Assessments +

+ Assessments are collections of questions that are graded + together. Use them to create homework, exam, quiz, pre-lecture, + group work, or any other assignment you have in your course. +

+

+ An assessment defines point allocations for individual + questions, rules to control access based on date or user, + instructions for students, and more! +

+ +
+
-

Assessments

-

- Assessments are collections of questions that are graded together; - they can be used for homeworks, exams, in-lecture content, group work, - and more. An assessment defines point allocations for individual - questions, rules to control access based on date or user, instructions - for students, and more. They can use a variety of grading schemes. -

-
+ +
+
+
+
+ question example + +
+
+ Questions +

+ Questions are the fundamental unit of content in PrairieLearn. + While questions can be completely static, the key feature of + PrairieLearn questions is the ability to generate, display, and + grade many unique variants of the same base question. +

+

+ Write it once, use many times! Since questions + are defined as code, they can be reused in many future + assessment.s And students can keep trying new variants of + difficult problems until they've mastered the topic—no need + for you to manually write new questions. +

+
+
-

Questions

-

- Questions are the fundamental unit of content in PrairieLearn. While - some questions are completely static, most PrairieLearn questions will - contain logic to generate, display, and grade many unique variants of - the same base question. -

-

- If you're new to PrairieLearn, you should check out the{" "} - - introduction to PrairieLearn questions - {" "} - to learn the key concepts used throughout these examples and all - PrairieLearn questions. -

-

- Once you're familiar with the basics, check out the below - questions to see some examples of questions that take full advantage - of the PrairieLearn platform. -

-
- {questions.map((question) => { - const galleryHref = `/gallery/question/${question.slug}`; - return ( -
- {question.imageUrl && ( - - {/* Fit all images within 4:3 aspect ratio box*/} - - Preview of question - - - )} -
- - -

{question.title}

-
- -

{question.summary}

-
-
- ); - })} +
+ +
+
+
+ Question Galery +

+ Check out this question gallery with example questions that take + full advantage of the PrairieLearn platform. +

+ +
+ {questions.map((question) => { + const galleryHref = `/gallery/question/${question.slug}`; + return ( +
+ {question.imageUrl && ( + + {/* Fit all images within 4:3 aspect ratio box*/} + + question preview image + + + )} +
+ + +

{question.title}

+
+ +

{question.summary}

+
+
+ ); + })} +
+
+ + ); }; @@ -128,14 +159,6 @@ const GalleryIndex: React.FC = ({ export default GalleryIndex; export const getStaticProps: GetStaticProps = async () => { - // Get assessments and filter out only the props we need on this page - const rawAssessments = await getAssessments(); - const assessments = rawAssessments.map(({ title, slug, summary }) => ({ - title, - slug, - summary, - })); - const rawQuestions = await getQuestions(); const questions = rawQuestions.map(({ title, slug, summary, image }) => ({ title, @@ -146,7 +169,6 @@ export const getStaticProps: GetStaticProps = async () => { return { props: { - assessments, questions, }, }; diff --git a/src/pages/pricing/index.tsx b/src/pages/pricing/index.tsx index 6a2526a3..d0e945f5 100644 --- a/src/pages/pricing/index.tsx +++ b/src/pages/pricing/index.tsx @@ -4,6 +4,7 @@ import Link from "next/link"; import CheckIcon from "../../components/CheckIcon"; import Stack from "../../components/Stack"; +import { PageBanner } from "../../components/Banner"; import styles from "./index.module.scss"; @@ -56,10 +57,8 @@ export default function Pricing() { Pricing | PrairieLearn + ;
-
-

Pricing

-