add rubric redesign react-query setup

this commit adds the foundation for react-query to the rubrics redesign.
it also uses new graphql queries to return rubric data to the front end.

closes EVAL-3616
flag=enhanced_rubrics

test plan:
- navigate to /accounts/<id>/rubrics
- verify that if the account has rubrics, they are displayed in the
  table
- navigate to /courses/<id>/rubrics
- verify that if the course has rubrics, they are displayed in the table

Change-Id: Ib340a37ad1c3a3d28413b3e8cfce359aa3f5db7a
Reviewed-on: https://gerrit.instructure.com/c/canvas-lms/+/331718
Reviewed-by: Aaron Shafovaloff <ashafovaloff@instructure.com>
Reviewed-by: Derek Williams <derek.williams@instructure.com>
Reviewed-by: Cameron Ray <cameron.ray@instructure.com>
Tested-by: Service Cloud Jenkins <svc.cloudjenkins@instructure.com>
QA-Review: Derek Williams <derek.williams@instructure.com>
Product-Review: Cameron Ray <cameron.ray@instructure.com>
This commit is contained in:
Chris Soto 2023-10-31 11:12:17 -06:00 committed by Christopher Soto
parent d65358ae19
commit 541d6492c2
12 changed files with 299 additions and 41 deletions

View File

@ -93,5 +93,12 @@ module Types
def parent_accounts_connection
account.account_chain - [account]
end
field :rubrics_connection, RubricType.connection_type, null: true
def rubrics_connection
rubric_associations = account.rubric_associations.bookmarked.include_rubric.to_a
rubric_associations = Canvas::ICU.collate_by(rubric_associations.select(&:rubric_id).uniq(&:rubric_id)) { |r| r.rubric.title }
rubric_associations.map(&:rubric)
end
end
end

View File

@ -163,6 +163,13 @@ module Types
.order("name")
end
field :rubrics_connection, RubricType.connection_type, null: true
def rubrics_connection
rubric_associations = course.rubric_associations.bookmarked.include_rubric.to_a
rubric_associations = Canvas::ICU.collate_by(rubric_associations.select(&:rubric_id).uniq(&:rubric_id)) { |r| r.rubric.title }
rubric_associations.map(&:rubric)
end
field :users_connection, UserType.connection_type, null: true do
argument :user_ids,
[ID],

View File

@ -33,6 +33,11 @@ module Types
object.criteria
end
field :criteria_count, Integer, null: false
def criteria_count
object.criteria&.count || 0
end
field :free_form_criterion_comments, Boolean, null: false
def free_form_criterion_comments
!!object.free_form_criterion_comments
@ -45,5 +50,6 @@ module Types
field :points_possible, Float, null: true
field :title, String, null: true
field :workflow_state, String, null: false
end
end

View File

@ -135,6 +135,7 @@
"graphiql": "^0.14.2",
"graphiql-explorer": "^0.4.2",
"graphql": "^14",
"graphql-request": "^6.1.0",
"graphql-tag": "^2.8.0",
"i18n-js": "^3",
"ic-ajax": "~2.0.1",

View File

@ -146,4 +146,25 @@ describe Types::AccountType do
expect(account_type.resolve("standardGradeStatusesConnection { nodes { _id } }")).to be_nil
end
end
describe "RubricsConnection" do
before(:once) do
@rubric = Rubric.create!(context: account)
rubric_association_model(context: account, rubric: @rubric, association_object: account, purpose: "bookmark")
end
it "returns rubrics" do
expect(
account_type.resolve("rubricsConnection { edges { node { _id } } }")
).to eq [account.rubrics.first.to_param]
expect(
account_type.resolve("rubricsConnection { edges { node { criteriaCount } } }")
).to eq [0]
expect(
account_type.resolve("rubricsConnection { edges { node { workflowState } } }")
).to eq ["active"]
end
end
end

View File

@ -924,4 +924,25 @@ describe Types::CourseType do
expect(result).to eq @course.allow_final_grade_override
end
end
describe "RubricsConnection" do
before(:once) do
rubric_for_course
rubric_association_model(context: course, rubric: @rubric, association_object: course, purpose: "bookmark")
end
it "returns rubrics" do
expect(
course_type.resolve("rubricsConnection { edges { node { _id } } }")
).to eq [course.rubrics.first.to_param]
expect(
course_type.resolve("rubricsConnection { edges { node { criteriaCount } } }")
).to eq [1]
expect(
course_type.resolve("rubricsConnection { edges { node { workflowState } } }")
).to eq ["active"]
end
end
end

View File

@ -42,6 +42,10 @@ describe Types::RubricType do
).to eq(rubric.criteria.map { |c| c[:id].to_s })
end
it "criteria_count" do
expect(rubric_type.resolve("criteriaCount")).to eq rubric.criteria.count
end
it "free_form_criterion_comments" do
expect(
rubric_type.resolve("freeFormCriterionComments")

View File

@ -17,7 +17,9 @@
*/
import React from 'react'
import {useNavigate} from 'react-router-dom'
import {useNavigate, useParams} from 'react-router-dom'
import {useQuery} from '@canvas/query'
import LoadingIndicator from '@canvas/loading-indicator'
import {ScreenReaderContent} from '@instructure/ui-a11y-content'
import {Button} from '@instructure/ui-buttons'
import {Flex} from '@instructure/ui-flex'
@ -26,6 +28,12 @@ import {IconAddLine, IconSearchLine} from '@instructure/ui-icons'
import {TextInput} from '@instructure/ui-text-input'
import {View} from '@instructure/ui-view'
import {RubricTable} from './RubricTable'
import {RubricQueryResponse} from '../../types/Rubric'
import {
FetchRubricVariables,
fetchAccountRubrics,
fetchCourseRubrics,
} from '../../queries/ViewRubricQueries'
const {Item: FlexItem} = Flex
@ -39,28 +47,54 @@ type Rubric = {
export const ViewRubrics = () => {
const navigate = useNavigate()
const {accountId, courseId} = useParams()
const isAccount = !!accountId
const isCourse = !!courseId
// Temporary setup data
const mainRubricProps: Rubric[] = [
{
id: '1',
criterion: 3,
name: 'Quizzes Rubric',
points: 30,
locations: ['GD101', 'ART220', 'CS220', 'MS230'],
let queryVariables: FetchRubricVariables
let fetchQuery: (queryVariables: FetchRubricVariables) => Promise<RubricQueryResponse>
let queryKey: string = ''
if (isAccount) {
queryVariables = {accountId}
fetchQuery = fetchAccountRubrics
queryKey = `accountRubrics-${accountId}`
} else if (isCourse) {
queryVariables = {courseId}
fetchQuery = fetchCourseRubrics
queryKey = `courseRubrics-${courseId}`
}
const {data, isLoading} = useQuery({
queryKey: [queryKey],
queryFn: async () => fetchQuery(queryVariables),
})
if (isLoading) {
return <LoadingIndicator />
}
if (!data) {
return null
}
const {activeRubrics, archivedRubrics} = data.rubricsConnection.nodes.reduce(
(prev, curr) => {
const rubric = {
id: curr.id,
name: curr.title,
points: curr.pointsPossible,
criterion: curr.criteriaCount,
locations: [], // TODO: add locations once we have them
}
curr.workflowState === 'active'
? prev.activeRubrics.push(rubric)
: prev.archivedRubrics.push(rubric)
return prev
},
{id: '2', criterion: 5, name: 'ART 101 Rubric', points: 100, locations: []},
{id: '3', criterion: 1, name: 'Test 1', points: 0, locations: []},
]
const archivedRubricProps: Rubric[] = [
{
id: '4',
criterion: 3,
name: 'Old Rubric 1',
points: 30,
locations: ['GD101', 'ART220', 'CS220', 'MS230'],
},
]
{activeRubrics: [] as Rubric[], archivedRubrics: [] as Rubric[]}
)
return (
<View as="div">
@ -97,7 +131,7 @@ export const ViewRubrics = () => {
</Heading>
</View>
<View as="div" margin="medium 0">
<RubricTable rubrics={mainRubricProps} />
<RubricTable rubrics={activeRubrics} />
</View>
<View as="div" margin="large 0 0 0">
@ -106,7 +140,7 @@ export const ViewRubrics = () => {
</Heading>
</View>
<View as="div" margin="medium 0">
<RubricTable rubrics={archivedRubricProps} />
<RubricTable rubrics={archivedRubrics} />
</View>
</View>
)

View File

@ -0,0 +1,89 @@
/*
* Copyright (C) 2023 - 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'
import {executeQuery} from '@canvas/query/graphql'
import {RubricQueryResponse} from '../types/Rubric'
const COURSE_RUBRICS_QUERY = gql`
query CourseRubricsQuery($courseId: ID!) {
course(id: $courseId) {
rubricsConnection {
nodes {
id: _id
criteriaCount
pointsPossible
title
workflowState
}
}
}
}
`
const ACCOUNT_RUBRICS_QUERY = gql`
query AccountRubricsQuery($accountId: ID!) {
account(id: $accountId) {
rubricsConnection {
nodes {
id: _id
criteriaCount
pointsPossible
title
workflowState
}
}
}
}
`
type AccountRubricsQueryVariables = {
accountId: string
courseId?: never
}
type CourseRubricsQueryVariables = {
accountId?: never
courseId: string
}
type CourseRubricQueryResponse = {
course: RubricQueryResponse
}
type AccountRubricQueryResponse = {
account: RubricQueryResponse
}
export type FetchRubricVariables = AccountRubricsQueryVariables | CourseRubricsQueryVariables
export const fetchCourseRubrics = async (queryVariables: FetchRubricVariables) => {
const {course} = await executeQuery<CourseRubricQueryResponse>(
COURSE_RUBRICS_QUERY,
queryVariables
)
return course
}
export const fetchAccountRubrics = async (queryVariables: FetchRubricVariables) => {
const {account} = await executeQuery<AccountRubricQueryResponse>(
ACCOUNT_RUBRICS_QUERY,
queryVariables
)
return account
}

View File

@ -0,0 +1,29 @@
/*
* Copyright (C) 2023 - 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 RubricQueryResponse = {
rubricsConnection: {
nodes: {
id: string
criteriaCount: number
pointsPossible: number
title: string
workflowState: string
}[]
}
}

View File

@ -0,0 +1,36 @@
/*
* Copyright (C) 2023 - 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 {DocumentNode} from 'graphql'
import request, {Variables} from 'graphql-request'
import getCookie from '@instructure/get-cookie'
export interface QueryVariables extends Variables {
[key: string]: unknown
}
export const executeQuery = async <QueryResponse>(
query: DocumentNode,
variables: QueryVariables
) => {
return request<QueryResponse>('/api/graphql', query, variables, {
'X-Requested-With': 'XMLHttpRequest',
'GraphQL-Metrics': 'true',
'X-CSRF-Token': getCookie('_csrf_token'),
})
}

View File

@ -1599,10 +1599,10 @@
resolved "https://registry.yarnpkg.com/@fullstory/browser/-/browser-1.4.10.tgz#c51114b9179b025968fddae2a7a3d5f3b0ee8aaf"
integrity sha512-TLasSmwq0iNe7w9YT4IVt1H3BM+YWwUvBu32zaX7fZ8nBCvq8WoJCut/Pb9ovl38kChJpq7aO0H2kwBwYOgx2w==
"@graphql-typed-document-node/core@^3.0.0":
version "3.1.0"
resolved "https://registry.yarnpkg.com/@graphql-typed-document-node/core/-/core-3.1.0.tgz#0eee6373e11418bfe0b5638f654df7a4ca6a3950"
integrity sha512-wYn6r8zVZyQJ6rQaALBEln5B1pzxb9shV5Ef97kTvn6yVGrqyXVnDqnU24MXnFubR+rZjBY9NWuxX3FB2sTsjg==
"@graphql-typed-document-node/core@^3.0.0", "@graphql-typed-document-node/core@^3.2.0":
version "3.2.0"
resolved "https://registry.yarnpkg.com/@graphql-typed-document-node/core/-/core-3.2.0.tgz#5f3d96ec6b2354ad6d8a28bf216a1d97b5426861"
integrity sha512-mB9oAsNCm9aM3/SOv4YtBMqZbYj10R7dkq8byBqxGY/ncFwhf2oQzMV+LCRlWoDSEBJ3COiR1yeDvMtsoOsuFQ==
"@gulp-sourcemaps/identity-map@1.X":
version "1.0.2"
@ -10815,12 +10815,12 @@ cross-env@^6.0.0:
dependencies:
cross-spawn "^7.0.0"
cross-fetch@^3.0.4:
version "3.1.4"
resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.1.4.tgz#9723f3a3a247bf8b89039f3a380a9244e8fa2f39"
integrity sha512-1eAtFWdIubi6T4XPy6ei9iUFoKpUkIF971QLN8lIvvvwueI65+Nw5haMNKUwfJxabqlIIDODJKGrQ66gxC0PbQ==
cross-fetch@^3.0.4, cross-fetch@^3.1.5:
version "3.1.8"
resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.1.8.tgz#0327eba65fd68a7d119f8fb2bf9334a1a7956f82"
integrity sha512-cvA+JwZoU0Xq+h6WkMvAUqPEYy92Obet6UdKLfW60qn99ftItKjB5T+BkyWOFWe2pUyfQ+IJHmpOTznqk1M6Kg==
dependencies:
node-fetch "2.6.1"
node-fetch "^2.6.12"
cross-spawn@^4:
version "4.0.2"
@ -14516,6 +14516,14 @@ graphql-language-service-utils@^2.5.1:
graphql-language-service-types "^1.8.0"
nullthrows "^1.0.0"
graphql-request@^6.1.0:
version "6.1.0"
resolved "https://registry.yarnpkg.com/graphql-request/-/graphql-request-6.1.0.tgz#f4eb2107967af3c7a5907eb3131c671eac89be4f"
integrity sha512-p+XPfS4q7aIpKVcgmnZKhMNqhltk20hfXtkaIkTfjjmiKMJ5xrt5c743cL03y/K7y1rg3WrIC49xGiEQ4mxdNw==
dependencies:
"@graphql-typed-document-node/core" "^3.2.0"
cross-fetch "^3.1.5"
graphql-tag@^2.12.3, graphql-tag@^2.8.0:
version "2.12.5"
resolved "https://registry.yarnpkg.com/graphql-tag/-/graphql-tag-2.12.5.tgz#5cff974a67b417747d05c8d9f5f3cb4495d0db8f"
@ -19301,11 +19309,6 @@ node-environment-flags@1.0.5:
object.getownpropertydescriptors "^2.0.3"
semver "^5.7.0"
node-fetch@2.6.1:
version "2.6.1"
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052"
integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==
node-fetch@^1.0.1:
version "1.7.3"
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-1.7.3.tgz#980f6f72d85211a5347c6b2bc18c5b84c3eb47ef"
@ -19314,10 +19317,10 @@ node-fetch@^1.0.1:
encoding "^0.1.11"
is-stream "^1.0.1"
node-fetch@^2.6.1, node-fetch@^2.6.7:
version "2.6.7"
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad"
integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==
node-fetch@^2.6.1, node-fetch@^2.6.12, node-fetch@^2.6.7:
version "2.7.0"
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.7.0.tgz#d0f0fa6e3e2dc1d27efcd8ad99d550bda94d187d"
integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==
dependencies:
whatwg-url "^5.0.0"