Design of vue lj data structures
See also: vue-canvas-learning-journal
Design and summarise current data structures.
Currently data structure management entirely in canvasApiData.ts
Usage of canvasApiData#
The root component (App.vue
) does the initial const canvasData = getCanvasData()
, which initiates the API call. All subsequent calls will be getting the singleton. They are also guaranteed to have the data available as the App.vue
does not add any sub-components to the DOM until the data is loaded.
Some of the sub-components currently have left over crud to be removed.
Other sub-components (the group set specific stuff) will/may need to do additional queries. Alternatively could do it all initially depending on whether a group set is determined to be a learning journal.
canvasApiData#
Exports a function getCanvasCourse
which generates a singleton object of type courseData
. It retrieves data from the Canvas API and then transforms it into a more useful object.
Property | Description |
---|---|
id |
the course id |
name |
the course name |
courseCode |
Canvas API course code |
hostname |
the Canvas instance's hostname (url) |
createdAt |
the date the course was created |
updated |
a counter that is incremented each time the object is updated |
groupSets |
an array of group sets currently undefined |
groupSetsById |
an object with group set ids as keys and group set data as values |
students |
an array of student data |
studentsById |
an object with student ids as keys and student data as values |
teachers |
an array of teacher data |
teachersById |
an object with teacher ids as keys and teacher data as values |
discussions |
Array of discussion data retrieved via REST API (no graphQL support) this data is then parsed into appropriate places within groupSets data structure |
courseObject |
deprecated object returned by the GraphQL query |
` @TODO assignments |
groupSets#
The groupSets
property is an array of objects representing an individual groupset, including
Property | Description |
---|---|
_id |
the group set id |
id |
the group set GraphQL id |
name |
the group set name |
memberLimit |
the maximum number of members allowed in a group |
selfSignUp |
whether students can self sign up for groups |
numGroups |
the number of groups in the group set - calculated by code |
numMembers |
Number of students who are members of groups within the group set |
discussionTopics |
Discussion topics (Canvas API objects) associated with this group set |
groupsConnection |
the original GraphQL property |
groups |
array of data about the groups that belong to the group set |
Groups#
The groups
property is an array of objects for each group belonging to the group set with the following properties
Property | Description |
---|---|
_id |
the group id |
name |
the group name |
updatedAt |
the date the group was last updated |
membersCount |
the number of members in the group |
canMessage |
whether members can message each other |
createdAt |
the date the group was created |
members |
array of data about the members of the group. Which is an array of objects with a nodes property that contains an array (only usually one for a learning journal) with basic user information (_id, name, email, avatarUrl) |
students and teachers#
The students
and teachers
properties array arrays of objects representing student/teacher info in the course with the following properties
Property | Description |
---|---|
_id |
the student's Canvas id |
name |
the student's name |
email |
the student's email address |
htmlUrl |
the student's avatar/about page for the current course |
- Usage of canvasApiData
- canvasApiData
- groupSets
- students and teachers
- Matching Python data structures
- Transformed GraphQL data
- On the question of stores
- Object update
{
"id": 7446794,
"name": "Collections Copy",
"groupSets": [],
"courseObject": {
"_id": "7446794",
"name": "Collections Copy",
"courseCode": "Collections",
"createdAt": "2023-08-05T20:53:16-06:00",
"groupSetsConnection": {
"nodes": [
{
"id": "R3JvdXBTZXQtMjkwMjMy",
"_id": "290232",
"name": "Your reflective journal",
"memberLimit": null,
"selfSignup": "disabled",
"groupsConnection": {
"nodes": [
{
"_id": "974036",
"name": "PtjMUwBqFOvn-d1WEezZi1CzZPaIPP1AfWGwEhDE5jU (PtjMUwBqFOvn-d1WEezZi1CzZPaIPP1AfWGwEhDE5jU)",
"updatedAt": "2023-12-12T21:11:39-07:00",
"membersCount": 1,
"canMessage": true,
"createdAt": "2023-12-12T21:05:07-07:00",
"membersConnection": {
"nodes": [
{
"_id": "3750916",
"createdAt": "2023-12-12T21:05:07-07:00",
"user": {
"_id": "109793678",
"name": "PtjMUwBqFOvn-d1WEezZi1CzZPaIPP1AfWGwEhDE5jU",
"email": "d.jones6@griffith.edu.au",
"avatarUrl": null
}
}
]
}
},
{
"_id": "974035",
"name": "Steven Booten (s.booten@griffith.edu.au)",
"updatedAt": "2023-12-12T21:11:39-07:00",
"membersCount": 1,
"canMessage": true,
"createdAt": "2023-12-12T21:05:06-07:00",
"membersConnection": {
"nodes": [
{
"_id": "3750915",
"createdAt": "2023-12-12T21:05:07-07:00",
"user": {
"_id": "109792759",
"name": "Steven Booten",
"email": "s.booten@griffith.edu.au",
"avatarUrl": null
}
}
]
}
},
{
"_id": "974034",
"name": "wzCp2-sXN4gCyYI8fUw__N4XCm1TDI8EPLNAhayh-Ek (wzCp2-sXN4gCyYI8fUw__N4XCm1TDI8EPLNAhayh-Ek)",
"updatedAt": "2023-12-12T21:11:39-07:00",
"membersCount": 1,
"canMessage": true,
"createdAt": "2023-12-12T21:05:05-07:00",
"membersConnection": {
"nodes": [
{
"_id": "3750914",
"createdAt": "2023-12-12T21:05:06-07:00",
"user": {
"_id": "109793756",
"name": "wzCp2-sXN4gCyYI8fUw__N4XCm1TDI8EPLNAhayh-Ek",
"email": "h.cook@griffith.edu.au",
"avatarUrl": null
}
}
]
}
}
]
}
},
{
"id": "R3JvdXBTZXQtMjkwMjky",
"_id": "290292",
"name": "Weekly learning activities",
"memberLimit": null,
"selfSignup": "disabled",
"groupsConnection": {
"nodes": [
{
"_id": "974338",
"name": "PtjMUwBqFOvn-d1WEezZi1CzZPaIPP1AfWGwEhDE5jU (PtjMUwBqFOvn-d1WEezZi1CzZPaIPP1AfWGwEhDE5jU)",
"updatedAt": "2023-12-13T13:34:40-07:00",
"membersCount": 1,
"canMessage": true,
"createdAt": "2023-12-13T13:34:39-07:00",
"membersConnection": {
"nodes": [
{
"_id": "3751770",
"createdAt": "2023-12-13T13:34:40-07:00",
"user": {
"_id": "109793678",
"name": "PtjMUwBqFOvn-d1WEezZi1CzZPaIPP1AfWGwEhDE5jU",
"email": "d.jones6@griffith.edu.au",
"avatarUrl": null
}
}
]
}
},
{
"_id": "974337",
"name": "Steven Booten (s.booten@griffith.edu.au)",
"updatedAt": "2023-12-13T13:34:39-07:00",
"membersCount": 1,
"canMessage": true,
"createdAt": "2023-12-13T13:34:38-07:00",
"membersConnection": {
"nodes": [
{
"_id": "3751769",
"createdAt": "2023-12-13T13:34:39-07:00",
"user": {
"_id": "109792759",
"name": "Steven Booten",
"email": "s.booten@griffith.edu.au",
"avatarUrl": null
}
}
]
}
},
{
"_id": "974339",
"name": "wzCp2-sXN4gCyYI8fUw__N4XCm1TDI8EPLNAhayh-Ek (wzCp2-sXN4gCyYI8fUw__N4XCm1TDI8EPLNAhayh-Ek)",
"updatedAt": "2023-12-13T13:34:40-07:00",
"membersCount": 1,
"canMessage": true,
"createdAt": "2023-12-13T13:34:40-07:00",
"membersConnection": {
"nodes": [
{
"_id": "3751771",
"createdAt": "2023-12-13T13:34:40-07:00",
"user": {
"_id": "109793756",
"name": "wzCp2-sXN4gCyYI8fUw__N4XCm1TDI8EPLNAhayh-Ek",
"email": "h.cook@griffith.edu.au",
"avatarUrl": null
}
}
]
}
}
]
}
},
{
"id": "R3JvdXBTZXQtMjk2NDk3",
"_id": "296497",
"name": "testing",
"memberLimit": null,
"selfSignup": "disabled",
"groupsConnection": {
"nodes": []
}
}
]
}
},
"hostName": "https://canvas.instructure.com",
"updated": 1
}
Matching Python data structures#
Todo
-
Extract from each group set's groups' members all the current members of the group set -- to identify if there are any students without a group
- Number of groups should match number of students for initial check
- then list of students compared with all students to find out who's missing
- Get list of discussion topics for a group set, including posts/entries
- Calculate stats for a group set/learning journal
Global
Python | Vue | Description | Used where |
---|---|---|---|
teachers | course_object.teachers | All user info based on enrolment_type "teacher" | |
staff_list | @todo calculate if/when needed | An array of staff user ids taken from teachers | |
students | course_object.students | All user info based on enrolment type 'student' | Obtained in Vue |
Group set level -- courseObject.groupSets.nodes
Python | Vue | Description | Used where |
---|---|---|---|
groupMembers | Collection of membership information | deprecated? not used in view in Python | |
users | Paginated list of user information for people in the group | deprecated | |
topic | Canvas topic object - posibly not there? | deprecated | |
stats | @todo further analysis in vue | num_groups, num_unanswered_student_posts, num_student_entries, num_no_student_entry, num_prompts, num_no_staff_entry | |
prompts | course_object.assignments gets all assignments. @todo extract out the discussion topics and perhaps get more info or explore another way of getting the info | Array of objects for the prompts for a group set |
Prompts level
Python | Vue | Description | Used where |
---|---|---|---|
prompt_stats | Statistics about the prompt across all groups | ||
assignment | Info about the assignment, including the parent discussion topic | ||
responses | Array of objects for all responses to a prompt |
Response level
Python | Vue | Description | Used where |
---|---|---|---|
topic_id | Discussion topic information (specific to the group) | ||
group_id | |||
prompt_topic | The actual discussion topic object | ||
entries | Full discussion topic | ||
entry_stats |
Transformed GraphQL data#
The GraphQL data isn't directly useful for the Vue components.
On the question of stores#
Beyond the basic (singleton) approach initially used there are purpose built stores for Vue. Pinia appears to be a current good one. Recommended by the Vue originator
But perhaps too heavy weight for usage now. This comparison of state management in Vue lists three options
-
global event bus
- Difficult to maintain as application grows. Also lead to data inconsistencies
- simple global store
- Vuex library (Pinia fits here)
Object update#
interface memberNode {
public _id: string
public createdAt: string
public user: {
public "_id": string
}
}
interface prompt {
// currently Canvas API REST response for view all topic
// @todo
// - add some analysis etc.
// - maybe add additional data
public unread_entries: []
public forced_entries: []
public entry_ratings: {}
public participants: []
public view: []
public new_entries: []
}
interface group {
public _id: string
public name : string
public updatedAt: string
public membersCount: number
public canMessage: boolean
public createdAt: string
public membersConnection: {
public nodes: memberNode[]
}
public members: memberNode[]
public prompts: {
[key: string]: prompt
}
}
/*enum discussion_type: {
threaded, // fully threaded
side_comment // only one level of nested comments
}
enum read_state {
read,
unread
}*/
interface author {
public id: number
public anonymous_id: string
public display_name: string
public avatar_image_url: string
public html_url: string
public pronouns: string
}
interface group_topic_children{
public id: number
public group_id: number
}
interface discussionTopics {
public id: number
public title: string
public last_reply_at: string
public created_at: string
public delayed_post_at: string
public assignment_id: number
public root_topic_id: number
public position: number
public podcast_has_student_posts: boolean
public discussion_type: string // values threaded or side_comment
public lock_at: string
public allow_rating: boolean
public only_graders_can_rate: boolean
public sort_by_rating: boolean
public is_section_specific: boolean
public anonymous_state: string
public user_name: string
public discussion_subentry_count: number
public permissions: {
public attach: boolean
public update: boolean
public reply: boolean
public delete: boolean
}
public require_initial_post: boolean
public user_can_see_posts: boolean
public podcast_url: string
public read_state: string // read or unread
public unread_count: number
public subscribed: boolean
public attachments: []
public published: boolean
public can_unpublish: boolean
public locked: boolean
public can_lock: boolean
public comments_disabled: boolean
public author: author
public html_url: string
public url: string
public pinned: boolean
public group_category_id: number
public can_group: boolean
public topic_children: number[]
public group_topic_children: group_topic_children[]
public locked_for_user: boolean
public message: string
public subscription_hold: string
public todo_date: string
public is_announcement: boolean
}
interface learningJournalStatus {
public privateJournal : boolean
public completedConfig : boolean
public promptsCreated : boolean
public groupsCreated : boolean
public selfSignUp : boolean
public studentsWithoutGroup : boolean
public multiStudentGroups : boolean
}
interface groupSets {
// Canvas API GraphQL
public id: string
public _id: string
public name: string
public memberLimit: string
public selfSignup: string
public groupsConnection: { "nodes" : group[]}
public groups: group[]
public numGroups: number
public numNonPrivateGroups: number
public numStudentsMembersOfGroups: number
public numStudents: number
public discussionTopics: discussionTopics[]
public numPrompts: number
public learningJournalStatus: learningJournalStatus
public groupsById: { [key: string]: group }
public discussionTopicsById: { [key: string]: discussionTopics }
}
interface user {
public _id: string
public name: string
public email: string
public avatarUrl: string
}
class canvasApiData
{
public id: number
public name: string
public hostName: string
public updated: number = 0 // TODO consider moving to a struct to include percent loaded etc
public courseCode: string = ''
public createdAt: string = '' // TODO move to a date type??
public groupSets: groupSet[] = []
public groupSetsById: { [key: string]: groupSet } = {}
public students: user[] = []
public teachers: user[] = []
public studentsById: { [key: string]: user } = {}
public teachersById: { [key: string]: user } = {}
public discussionTopics: discussionTopics[] = []
public learningJournalStatus: learningJournalStatus
}