Create the AssignmentTeacherView component

closes EGG-5
flag=assignment_enhancements_teacher_view

Test Plan:
- Go to RootAccount -> Settings -> Feature Options
- Enable the "Assignment Enhancements Teacher View" feature flag.
- Select a course and go to its assignments.
- Select an individual assignment (not one that uses an external tool)
- Ensure that the assignment name is displayed at the top left
of the page.
- Nothing else will be displayed on the page. It is still in the
works.

Change-Id: I84fc911730d8fb5d925173b33e58594058d6a8a0
Reviewed-on: https://gerrit.instructure.com/c/canvas-lms/+/354662
Tested-by: Service Cloud Jenkins <svc.cloudjenkins@instructure.com>
Reviewed-by: Angela Gomba <angela.gomba@instructure.com>
QA-Review: Angela Gomba <angela.gomba@instructure.com>
Product-Review: Sam Garza <sam.garza@instructure.com>
This commit is contained in:
Rida Hummdan 2024-08-12 11:11:23 -07:00
parent b9c369eaeb
commit 1696936f3a
110 changed files with 726 additions and 3 deletions

View File

@ -403,6 +403,14 @@ class AssignmentsController < ApplicationController
(!params.key?(:assignments_2) || value_to_boolean(params[:assignments_2])) &&
can_do(@context, @current_user, :read_as_admin)
css_bundle :assignments_2_teacher
js_bundle :assignments_show_teacher_deprecated
render html: "", layout: true
return
end
if @context.root_account.feature_enabled?(:assignment_enhancements_teacher_view) &&
can_do(@context, @current_user, :read_as_admin)
css_bundle :assignment_enhancements_teacher_view
js_bundle :assignments_show_teacher
render html: "", layout: true
return

View File

@ -0,0 +1,20 @@
/*
* Copyright (C) 2024 - present Instructure, Inc.
*
* This file is part of Canvas.
*
* Canvas is free software: you can redistribute it and/or modify it under
* the terms of the GNU Affero General Public License as published by the Free
* Software Foundation, version 3 of the License.
*
* Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
* A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
* details.
*
* You should have received a copy of the GNU Affero General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
@import "base/environment";
@import "pages/assignment_enhancements_teacher_view/teacher_view.scss";

View File

@ -1224,6 +1224,18 @@ describe AssignmentsController do
end
end
describe "assignment_enhancements_teacher_view" do
before do
@course.root_account.enable_feature!(:assignment_enhancements_teacher_view)
@course.save!
end
it "does not render the 'old' assignment page layout" do
get :show, params: { course_id: @course.id, id: @assignment.id }
expect(response).not_to render_template("assignments/show")
end
end
it "does not show locked external tool assignments" do
user_session(@student)

View File

@ -1,7 +1,7 @@
# frozen_string_literal: true
#
# Copyright (C) 2019 - present Instructure, Inc.
# Copyright (C) 2024 - present Instructure, Inc.
#
# This file is part of Canvas.
#
@ -17,6 +17,8 @@
# You should have received a copy of the GNU Affero General Public License along
# with this program. If not, see <http://www.gnu.org/licenses/>.
require_relative "../../common"
class TeacherViewPageV2
class << self
include SeleniumDependencies
@ -32,11 +34,15 @@ class TeacherViewPageV2
# Methods & Actions
def visit(course, assignment)
course.account.enable_feature!(:assignments_2_teacher)
course.account.enable_feature!(:assignment_enhancements_teacher_view)
get "/courses/#{course.id}/assignments/#{assignment.id}"
wait_for(method: nil, timeout: 1) do
assignment_type
end
end
def assignment_title(title)
fj("h1:contains(#{title})")
end
end
end

View File

@ -0,0 +1,58 @@
# frozen_string_literal: true
#
# Copyright (C) 2024 - present Instructure, Inc.
#
# This file is part of Canvas.
#
# Canvas is free software: you can redistribute it and/or modify it under
# the terms of the GNU Affero General Public License as published by the Free
# Software Foundation, version 3 of the License.
#
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
# details.
#
# You should have received a copy of the GNU Affero General Public License along
# with this program. If not, see <http://www.gnu.org/licenses/>.
require_relative "page_objects/teacher_assignment_page_v2"
require_relative "../common"
require_relative "../rcs/pages/rce_next_page"
describe "as a teacher" do
specs_require_sharding
include RCENextPage
include_context "in-process server selenium tests"
context "on assignments 2 page" do
before(:once) do
Account.default.enable_feature!(:assignment_enhancements_teacher_view)
@course = course_factory(name: "course", active_course: true)
@student = student_in_course(name: "Student", course: @course, enrollment_state: :active).user
@teacher = teacher_in_course(name: "teacher", course: @course, enrollment_state: :active).user
end
context "assignment details" do
before(:once) do
@assignment = @course.assignments.create!(
name: "assignment",
due_at: 5.days.ago,
points_possible: 10,
submission_types: "online_text_entry"
)
end
before do
user_session(@teacher)
TeacherViewPageV2.visit(@course, @assignment)
wait_for_ajaximations
end
it "shows assignment title" do
expect(TeacherViewPageV2.assignment_title(@assignment.title)).to_not be_nil
end
end
end
end

View File

@ -40,6 +40,8 @@ const featureBundles: {
assignment_show: () => import('./features/assignment_show/index'),
assignments_peer_reviews: () => import('./features/assignments_peer_reviews/index'),
assignments_show_student: () => import('./features/assignments_show_student/index'),
assignments_show_teacher_deprecated: () =>
import('./features/assignments_show_teacher_deprecated/index'),
assignments_show_teacher: () => import('./features/assignments_show_teacher/index'),
authentication_providers: () => import('./features/authentication_providers/index'),
available_pronouns_list: () => import('./features/available_pronouns_list/index'),

View File

@ -0,0 +1,138 @@
/*
* Copyright (C) 2024 - present Instructure, Inc.
*
* This file is part of Canvas.
*
* Canvas is free software: you can redistribute it and/or modify it under
* the terms of the GNU Affero General Public License as published by the Free
* Software Foundation, version 3 of the License.
*
* Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
* A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
* details.
*
* You should have received a copy of the GNU Affero General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
export type CourseType = {
lid: string
}
export type ModuleType = {
lid: string
name: string
}
export type AssignmentGroupType = {
lid: string
name: string
}
export type LockInfoType = {
isLocked: boolean
}
type SetType = {
lid?: string
name?: string
__typename?: 'Section' | 'Group' | 'AdhocStudents'
}
export type OverrideType = {
id?: string
lid?: string
title?: string
dueAt?: string
lockAt?: string
unlockAt?: string
submissionTypes?: string[]
allowedAttempts?: number
allowedExtensions?: string[]
set?: SetType
}
export type UserType = {
lid?: string
gid?: string
name?: string
shortName?: string
sortableName?: string
avatarUrl?: string
email?: string
}
export type SubmissionHistoryType = {
attempt?: number
score?: number
submittedAt?: string
}
export type SubmissionDraftType = {
submissionAttempt?: string
}
type SubmissionHistoriesConnectionType = {
nodes?: SubmissionHistoryType[]
}
export type SubmissionType = {
gid?: string
lid?: string
attempt?: number
submissionStatus?: 'resubmitted' | 'missing' | 'late' | 'submitted' | 'unsubmitted'
grade?: string
gradingStatus?: null | 'excused' | 'needs_review' | 'needs_grading' | 'graded'
score?: number
state?: 'submitted' | 'unsubmitted' | 'pending_review' | 'graded' | 'deleted'
excused?: boolean
latePolicyStatus?: null | 'missing'
submittedAt?: string
user?: UserType
submissionHistoriesConnection?: SubmissionHistoriesConnectionType
submissionDraft?: SubmissionDraftType
}
type PageInfoType = {
startCursor?: string
endCursor?: string
hasNextPage?: boolean
hasPreviousPage?: boolean
}
type AssignmentOverridesType = {
pageInfo: PageInfoType
nodes: OverrideType[]
}
type SubmissionsType = {
pageInfo: PageInfoType
nodes: SubmissionType[]
}
export type TeacherAssignmentType = {
__typename: string
id: string
gid: string
lid: string
name: string
pointsPossible?: number | string
dueAt?: string
lockAt?: string
unlockAt?: string
description?: string
state: 'published' | 'unpublished' | 'deleted'
needsGradingCount?: number
onlyVisibleToOverrides?: boolean
assignmentGroup?: AssignmentGroupType
modules: ModuleType[]
course: CourseType
lockInfo: LockInfoType
submissionTypes: string[]
allowedExtensions: string[]
allowedAttempts?: number
anonymizeStudents?: boolean
assignmentOverrides: AssignmentOverridesType
submissions: SubmissionsType
}

View File

@ -0,0 +1,31 @@
/*
* Copyright (C) 2024 - present Instructure, Inc.
*
* This file is part of Canvas.
*
* Canvas is free software: you can redistribute it and/or modify it under
* the terms of the GNU Affero General Public License as published by the Free
* Software Foundation, version 3 of the License.
*
* Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
* A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
* details.
*
* You should have received a copy of the GNU Affero General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import gql from 'graphql-tag'
export const SET_WORKFLOW = gql`
mutation SetWorkflow($id: ID!, $workflow: AssignmentState!) {
updateAssignment(input: {id: $id, state: $workflow}) {
assignment {
__typename
id
state
}
}
}
`

View File

@ -0,0 +1,147 @@
/*
* Copyright (C) 2024 - present Instructure, Inc.
*
* This file is part of Canvas.
*
* Canvas is free software: you can redistribute it and/or modify it under
* the terms of the GNU Affero General Public License as published by the Free
* Software Foundation, version 3 of the License.
*
* Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
* A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
* details.
*
* You should have received a copy of the GNU Affero General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import gql from 'graphql-tag'
const userFields = gql`
fragment UserFields on User {
__typename
gid: id
lid: _id
name
shortName
sortableName
avatarUrl
email
}
`
const assignmentOverridesNodes = gql`
fragment AssignmentOverrides on AssignmentOverrideConnection {
nodes {
gid: id
lid: _id
title
dueAt
lockAt
unlockAt
set {
__typename
... on Section {
lid: _id
sectionName: name
}
... on Group {
lid: _id
groupName: name
}
... on AdhocStudents {
students {
lid: _id
studentName: name
}
}
}
}
}
`
export const TEACHER_QUERY = gql`
query GetAssignment($assignmentLid: ID!) {
assignment(id: $assignmentLid) {
__typename
id
lid: _id
gid: id
name
description
dueAt(applyOverrides: false)
unlockAt(applyOverrides: false)
lockAt(applyOverrides: false)
pointsPossible
state
needsGradingCount
onlyVisibleToOverrides
lockInfo {
isLocked
}
assignmentGroup {
lid: _id
name
}
modules {
lid: _id
name
}
submissionTypes
allowedExtensions
allowedAttempts
anonymizeStudents
course {
lid: _id
modulesConnection(first: 0) {
pageInfo {
hasNextPage
}
}
assignmentGroupsConnection(first: 0) {
pageInfo {
hasNextPage
}
}
}
assignmentOverrides {
pageInfo {
startCursor
endCursor
hasNextPage
hasPreviousPage
}
...AssignmentOverrides
}
submissions: submissionsConnection(
filter: {states: [submitted, unsubmitted, graded, ungraded, pending_review]}
) {
pageInfo {
startCursor
endCursor
hasNextPage
hasPreviousPage
}
nodes {
gid: id
lid: _id
attempt
submissionStatus
grade
gradingStatus
score
state
excused
latePolicyStatus
submittedAt
user {
...UserFields
}
}
}
}
}
${userFields}
${assignmentOverridesNodes}
`

View File

@ -0,0 +1,25 @@
/*
* Copyright (C) 2024 - present Instructure, Inc.
*
* This file is part of Canvas.
*
* Canvas is free software: you can redistribute it and/or modify it under
* the terms of the GNU Affero General Public License as published by the Free
* Software Foundation, version 3 of the License.
*
* Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
* A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
* details.
*
* You should have received a copy of the GNU Affero General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import renderAssignmentsApp from './react/index'
import ready from '@instructure/ready'
ready(() => {
const elt: HTMLElement | null = document.getElementById('content')
renderAssignmentsApp(elt)
})

View File

@ -2,5 +2,5 @@
"name": "@canvas-features/assignments_show_teacher",
"private": true,
"version": "1.0.0",
"owner": "EVAL"
"owner": "EGG"
}

View File

@ -0,0 +1,37 @@
/*
* Copyright (C) 2024 - present Instructure, Inc.
*
* This file is part of Canvas.
*
* Canvas is free software: you can redistribute it and/or modify it under
* the terms of the GNU Affero General Public License as published by the Free
* Software Foundation, version 3 of the License.
*
* Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
* A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
* details.
*
* You should have received a copy of the GNU Affero General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import React from 'react'
import {Heading} from '@instructure/ui-heading'
import type {TeacherAssignmentType} from '../../graphql/AssignmentTeacherTypes'
interface HeaderProps {
assignment: TeacherAssignmentType
}
const AssignmentHeader: React.FC<HeaderProps> = props => {
return (
<div id="assignments-2-teacher-header">
<Heading data-testid="assignment-name" level="h1">
{props.assignment?.name}
</Heading>
</div>
)
}
export default AssignmentHeader

View File

@ -0,0 +1,50 @@
/*
* Copyright (C) 2024 - present Instructure, Inc.
*
* This file is part of Canvas.
*
* Canvas is free software: you can redistribute it and/or modify it under
* the terms of the GNU Affero General Public License as published by the Free
* Software Foundation, version 3 of the License.
*
* Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
* A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
* details.
*
* You should have received a copy of the GNU Affero General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import React from 'react'
import {useQuery} from 'react-apollo'
import {Spinner} from '@instructure/ui-spinner'
import {View} from '@instructure/ui-view'
import {useScope as useI18nScope} from '@canvas/i18n'
import {TEACHER_QUERY} from '../../graphql/Queries'
import TeacherSavedView from './TeacherSavedView'
const I18n = useI18nScope('assignments_2')
interface TeacherQueryProps {
assignmentLid: string
}
const TeacherQuery: React.FC<TeacherQueryProps> = ({assignmentLid}) => {
const {loading, error, data} = useQuery(TEACHER_QUERY, {
variables: {assignmentLid},
})
if (loading) {
return (
<View as="div" textAlign="center" padding="large 0">
<Spinner size="large" renderTitle={I18n.t('Loading')} />
</View>
)
}
if (error) return <pre>Error: {JSON.stringify(error, null, 2)}</pre>
return <TeacherSavedView assignment={data.assignment} />
}
export default TeacherQuery

View File

@ -0,0 +1,31 @@
/*
* Copyright (C) 2024 - present Instructure, Inc.
*
* This file is part of Canvas.
*
* Canvas is free software: you can redistribute it and/or modify it under
* the terms of the GNU Affero General Public License as published by the Free
* Software Foundation, version 3 of the License.
*
* Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
* A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
* details.
*
* You should have received a copy of the GNU Affero General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import React from 'react'
import type {TeacherAssignmentType} from '../../graphql/AssignmentTeacherTypes'
import AssignmentHeader from './AssignmentHeader'
interface TeacherViewProps {
assignment: TeacherAssignmentType
}
const TeacherSavedView: React.FC<TeacherViewProps> = props => {
return <AssignmentHeader assignment={props.assignment} />
}
export default TeacherSavedView

View File

@ -0,0 +1,31 @@
/*
* Copyright (C) 2024 - present Instructure, Inc.
*
* This file is part of Canvas.
*
* Canvas is free software: you can redistribute it and/or modify it under
* the terms of the GNU Affero General Public License as published by the Free
* Software Foundation, version 3 of the License.
*
* Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
* A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
* details.
*
* You should have received a copy of the GNU Affero General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import React from 'react'
import {render} from '@testing-library/react'
import {mockAssignment} from '../../test-utils'
import AssignmentHeader from '../AssignmentHeader'
describe('assignment enhancement teacher view header', () => {
it('renders assignment name', () => {
const assignment = mockAssignment()
const getByTestId = render(<AssignmentHeader assignment={assignment} />).getByTestId
expect(getByTestId('assignment-name')).toBeInTheDocument()
expect(getByTestId('assignment-name')).toHaveTextContent(assignment.name)
})
})

View File

@ -0,0 +1,37 @@
/*
* Copyright (C) 2024 - present Instructure, Inc.
*
* This file is part of Canvas.
*
* Canvas is free software: you can redistribute it and/or modify it under
* the terms of the GNU Affero General Public License as published by the Free
* Software Foundation, version 3 of the License.
*
* Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
* A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
* details.
*
* You should have received a copy of the GNU Affero General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import React from 'react'
import ReactDOM from 'react-dom'
import {ApolloProvider} from 'react-apollo'
import TeacherQuery from './components/TeacherQuery'
import {createClient} from '@canvas/apollo'
import {ApolloClient} from 'apollo-client'
import type {InMemoryCache} from 'apollo-cache-inmemory'
export default function renderAssignmentsApp(elt: HTMLElement | null) {
const client: ApolloClient<InMemoryCache> = createClient()
if (ENV.ASSIGNMENT_ID) {
ReactDOM.render(
<ApolloProvider client={client}>
<TeacherQuery assignmentLid={ENV.ASSIGNMENT_ID.toString()} />
</ApolloProvider>,
elt
)
}
}

View File

@ -0,0 +1,84 @@
/*
* Copyright (C) 2024 - present Instructure, Inc.
*
* This file is part of Canvas.
*
* Canvas is free software: you can redistribute it and/or modify it under
* the terms of the GNU Affero General Public License as published by the Free
* Software Foundation, version 3 of the License.
*
* Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
* A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
* details.
*
* You should have received a copy of the GNU Affero General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import type {TeacherAssignmentType} from '../graphql/AssignmentTeacherTypes'
export function mockCourse(overrides = {}) {
return {
lid: 'course-lid',
assignmentGroupsConnection: {
pageInfo: mockPageInfo(),
nodes: [],
},
modulesConnection: {
pageInfo: mockPageInfo(),
nodes: [],
},
...overrides,
}
}
export function mockPageInfo(overrides = {}) {
return {
startCursor: 'startCursor',
endCursor: 'endCursor',
hasNextPage: false,
hasPreviousPage: false,
...overrides,
}
}
export function mockAssignment(overrides = {}): TeacherAssignmentType {
return {
__typename: 'Assignment',
id: 'assignment-gid',
gid: 'assignment-gid',
lid: 'assignment-lid',
name: 'Basic Mock Assignment',
pointsPossible: 5,
dueAt: '2018-11-28T13:00-05:00',
lockAt: '2018-11-29T13:00-05:00',
unlockAt: '2018-11-27T13:00-05:00',
description: 'assignment description',
state: 'published',
needsGradingCount: 0,
course: mockCourse(),
modules: [
{lid: '1', name: 'module 1'},
{lid: '2', name: 'module 2'},
],
assignmentGroup: {lid: '1', name: 'assignment group'},
lockInfo: {
isLocked: false,
},
submissionTypes: ['online_text_entry'],
allowedExtensions: [],
allowedAttempts: undefined,
anonymizeStudents: false,
onlyVisibleToOverrides: false,
assignmentOverrides: {
pageInfo: mockPageInfo(),
nodes: [],
},
submissions: {
pageInfo: mockPageInfo(),
nodes: [],
},
...overrides,
}
}

View File

@ -0,0 +1,6 @@
{
"name": "@canvas-features/assignments_show_teacher_deprecated",
"private": true,
"version": "1.0.0",
"owner": "EVAL"
}

Some files were not shown because too many files have changed in this diff Show More