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:
parent
d65358ae19
commit
541d6492c2
|
@ -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
|
||||
|
|
|
@ -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],
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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>
|
||||
)
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}[]
|
||||
}
|
||||
}
|
|
@ -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'),
|
||||
})
|
||||
}
|
39
yarn.lock
39
yarn.lock
|
@ -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"
|
||||
|
||||
|
|
Loading…
Reference in New Issue