Skip to content

Commit

Permalink
Refactor update (#26)
Browse files Browse the repository at this point in the history
* update

* treat topics and posts differently

* csv is topics only

* update to be reply focused

* rm course ids and update headers

* update readme with new cols and description

* readme example

* add COURSE_IDs to .env

* rm unused function

* print canvasapi error where available

* changes from review
  • Loading branch information
alisonmyers authored May 22, 2024
1 parent 62eba16 commit c9c9a39
Show file tree
Hide file tree
Showing 7 changed files with 1,411 additions and 840 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
output.csv
.env
node_modules
.DS_Store
41 changes: 28 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,35 +1,50 @@
[![JavaScript Style Guide](https://img.shields.io/badge/code_style-standard-brightgreen.svg)](https://standardjs.com)
# Canvas Discussion
This project pulls via the Canvas API the discussions from the specified Canvas course and exports it as CSV. The columns exported are:
* 'author_id',
* 'author_name',
This project pulls data via the Canvas API the discussions for the specified Canvas course(s) and exports the results as CSV. The columns exported are:
* 'topic_id',
* 'topic_title',
* 'topic_message',
* 'topic_author_id',
* 'topic_author_name',
* 'topic_timestamp',
* 'post_author_id',
* 'post_author_name',
* 'post_id',
* 'post_parent_id',
* 'discussion_topic_title',
* 'discussion_topic_message',
* 'post_message',
* 'count_of_likes',
* 'timestamp'
* 'post_likes',
* 'post_timestamp'

Where a `topic` corresponds to a `discussion_topic` and `post` refers to replies to the `discussion_topic`. If a `discussion_topic` has no posts then you will see the `topic_` columns filled with no corresponding `post_` data. A `post` may have a `post_parent_id ` if it is part of a threaded response.

## Getting Started
These instructions will get you a copy of the project up and running on your local machine for use with your own API tokens and Canvas domains.

### Prerequisites

1. **Install [Node 10 or greater](https://nodejs.org)**.
2. **Install [Git](https://git-scm.com/downloads)**.
1. **Install [Git](https://git-scm.com/downloads)**.

### Installation and execution of script

1. Clone this repo. `git clone https://github.com/ubccapico/canvas-discussion.git`
1. Then cd into the repo. `cd canvas-discussion`
1. Run the installation script. `npm install` (If you see `babel-node: command not found`, you've missed this step.)
1. Generate Canvas API token and copy it to clipboard.
1. Generate Canvas API token and copy it to clipboard
> - See [Get Started with the Canvas API](https://learninganalytics.ubc.ca/guides/get-started-with-the-canvas-api/) for more information.
> - ⚠️ Your Canvas API token is the equivalent to your username and password and must be treated as such (following any security guidelines of your home institution).
1. Create a `.env` file.
2. Add the following: `CANVAS_API_TOKEN={YOUR API TOKEN}` and `CANVAS_API_DOMAIN={YOUR API DOMAIN}`. An example `CANVAS_API_DOMAIN` is `https://{school}.instructure.com/api/v1`
3. Add your course ID to `index.js`, where it says: `//{course id} add course ID here!`
4. Run the script. `npm start`.
5. An `output.csv` file should be generated with discussion data in the output folder.
1. Add the following: `CANVAS_API_TOKEN={YOUR API TOKEN}`, `CANVAS_API_DOMAIN={YOUR API DOMAIN}`, `COURSE_IDS={YOUR COURSE ID(s)}`. > - At UBC the `CANVAS_API_DOMAIN` is `https://ubc.instructure.com/api/v1`
> - At another institution it might be something like `https://{school}.instructure.com/api/v1`
Your .env file should look like
```
CANVAS_API_TOKEN=22322...
CANVAS_API_DOMAIN=https://ubc.instructure.com/api/v1
COURSE_IDS=1111,1112
```
1. Run the script. `npm start`.
1. A `{course_id}-discussion.csv` file should be generated with discussion data in the output folder for each provided course_id.
## Authors
Expand Down
49 changes: 31 additions & 18 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
const capi = require('node-canvas-api')
const { flatten } = require('./util')
const writeToCSV = require('./writeToCSV')
require('dotenv').config();

// Check for COURSE_IDS in environment variables
if (!process.env.COURSE_IDS) {
console.error('Error: COURSE_IDS environment variable is not defined.');
process.exit(1); // Exit the script with a non-zero status
}

const getDiscussionTopicIds = courseId => capi.getDiscussionTopics(courseId)
.then(discussions => discussions.map(x => x.id))
Expand All @@ -16,13 +23,13 @@ const getNestedReplies = (replyObj, participants, topicId) => {
: ''

return [{
authorId: replyObj.user_id,
authorName: authorName,
message: replyObj.message,
likes: replyObj.rating_sum,
timestamp: replyObj.created_at,
parentId: replyObj.parent_id || topicId,
id: replyObj.id
postAuthorId: replyObj.user_id,
postAuthorName: authorName,
postMessage: replyObj.message,
postLikes: replyObj.rating_sum || 0,
postTimestamp: replyObj.created_at,
postParentId: replyObj.parent_id || '',
postId: replyObj.id
}, ...replies]
}

Expand All @@ -36,32 +43,38 @@ const getDiscussions = async courseId => {
]))
)
return discussionAndTopic.map(([discussion, topic]) => {
const topicId = topic.id
const topicTitle = topic.title
const topicMessage = topic.message
const author = topic.author
const timestamp = topic.created_at
const topicId = topic.id
const topicCreatedAt = topic.created_at
const participants = discussion.participants
const replies = discussion.view.length > 0
? discussion.view
.filter(x => !x.deleted)
.map(reply => getNestedReplies(reply, participants, topicId))
: []

return {
topicId,
topicTitle,
topicMessage,
id: topicId,
authorId: author.id || '',
authorName: author.display_name || '',
timestamp,
topicAuthorId: author.id || '',
topicAuthorName: author.display_name || '',
topicCreatedAt,
replies
}
})
}

const courseIds = process.env.COURSE_IDS.split(',').map(id => id.trim());

Promise.all([
//{course id} add course ID here!
].map(courseId => getDiscussions(courseId)
.then(discussions => writeToCSV(courseId, discussions))
))
Promise.all(
courseIds.map(courseId =>
getDiscussions(courseId)
.then(discussions => writeToCSV(courseId, discussions))
)
).catch(error => {
const detailedErrorMessage = error.message || `An unexpected error occurred: ${error}`
console.error('Error processing discussions:', detailedErrorMessage)
});
Loading

0 comments on commit c9c9a39

Please sign in to comment.