Skip to content

Commit

Permalink
Merge pull request #209 from haystack/develop
Browse files Browse the repository at this point in the history
stats on course content
  • Loading branch information
JumanaFM authored Feb 14, 2022
2 parents c177ba8 + ab2284d commit 51a981c
Show file tree
Hide file tree
Showing 3 changed files with 228 additions and 21 deletions.
169 changes: 152 additions & 17 deletions routes/annotations.js
Original file line number Diff line number Diff line change
Expand Up @@ -379,21 +379,157 @@ router.get('/new_annotation', (req, res) => {
});

/**
* Make new thread for a given annotation
* @name POST/api/annotations/annotation
* @param url: source url
* @param class: source class id
* @param content: text content of annotation
* @param range: json for location range
* @param author: id of author
* @param tags: list of ids of tag types
* @param userTags: list of ids of users tagged
* @param visibility: string enum
* @param anonymity: string enum
* @param replyRequest: boolean
* @param star: boolean
* @param bookmark: boolean
*/
* Get the stats for thetop-level annotation for a given source
* The stats are: my annotations, reply requests, unread, and total
* @name GET/api/annotations/stats
* @param url: source url
* @param class: source class id
* @return [{
* me: number of annotations written by user
* unread: number of unread annotations,
* replyRequests: number of replyRequests annotation,
* total: total number of annotations
* }]
*/
router.get('/stats', (req, res) => {
Source.findOne({
where: { [Op.and]: [{ filepath: req.query.url }, { class_id: req.query.class }] },
include: [{
association: 'Class',
include: [
{ association: 'Instructors', attributes: ['id'] },
{
association: 'GlobalSection', include: [{
association: 'MemberStudents', attributes: ['id']
}]
},
{
association: 'Sections', separate: true, include: [{ // with the hasMany Sections association, add a "separate: true" to make this join happen separately so that there are no duplicate joins
association: 'MemberStudents', attributes: ['id']
}]
}
]
}]
}).then(source => {
let instructors = new Set(source.Class.Instructors.map(user => user.id)) // convert to set so faster to check if a user is in this set
let globalSectionStudents = new Set(source.Class.GlobalSection.MemberStudents.map(user => user.id)) // convert to set so faster to check if a user is in this set
let isUserInstructor = instructors.has(req.user.id);
let isUserStudent = globalSectionStudents.has(req.user.id);

if (!isUserInstructor && !isUserStudent) {
res.status(200).json([]);
return;
}

let usersICanSee = new Set([]) // convert to set so faster to check if a user is in this set
let isSingleSectionClass = source.Class.Sections.length === 1

for (const section of source.Class.Sections) {
let memberIds = section.MemberStudents.map(user => user.id)
if ((isUserInstructor && section.is_global) || (isSingleSectionClass)) {
usersICanSee = new Set(memberIds)
break;
} else {
if (memberIds.indexOf(req.user.id) >= 0 && !section.is_global) {
usersICanSee = new Set(memberIds)
break
}
}
}
source.getLocations({
include:
[
{ association: 'HtmlLocation' },
{
association: 'Thread',
required: true,
include: [
{
association: 'HeadAnnotation', attributes: ['id', 'content', 'visibility', 'anonymity', 'created_at'],
include: [
{ association: 'Author', attributes: ['id', 'first_name', 'last_name', 'username'] },
{ association: 'ReplyRequesters', attributes: ['id', 'first_name', 'last_name', 'username'] },
]
},
{
association: 'AllAnnotations', separate: true, attributes: ['id', 'content', 'visibility', 'anonymity', 'created_at'],
include: [
{ association: 'Author', attributes: ['id', 'first_name', 'last_name', 'username'] },
{ association: 'ReplyRequesters', attributes: ['id', 'first_name', 'last_name', 'username'] },
]
},
{ association: 'SeenUsers', attributes: ['id', 'first_name', 'last_name', 'username'] },
]
}
]
}).then(locations => {
let me = 0
let unread = 0
let replyRequests = 0
let total = 0
let thread = 0

// TODO: is this the correct way to filter replies?
let goodLocations = locations.filter((location) => {
try {
let comment = location.Thread.AllAnnotations;
if (comment.visibility === 'MYSELF' && comment.Author.id !== req.user.id) {
return false;
}
if (comment.visibility === 'INSTRUCTORS' && !isUserInstructor) {
return false;
} if (req.query.sectioned === 'true' && isUserStudent && comment.Author.id !== req.user.id && !usersICanSee.has(comment.Author.id) && !instructors.has(comment.Author.id)) {
return false;
}
return true;
} catch(e) {
console.log(e)
return false;
}
})

goodLocations.forEach((location) => {

location.Thread.AllAnnotations.forEach((annot) => {
console.log(annot)
if (annot.Author.id === req.user.id ){
me += 1
}

replyRequests += annot.ReplyRequesters.length
total += 1
})
if (!(location.Thread.SeenUsers
.reduce((bool, user) => bool || user.id == req.user.id, false))){
unread += 1
}
thread += 1

});

res.status(200).json({ 'me': me, 'unread': unread, 'replyRequests': replyRequests, 'thread': thread, 'total': total });

})
});
});

/**
* Make new thread for a given annotation
* @name POST/api/annotations/annotation
* @param url: source url
* @param class: source class id
* @param content: text content of annotation
* @param range: json for location range
* @param author: id of author
* @param tags: list of ids of tag types
* @param userTags: list of ids of users tagged
* @param visibility: string enum
* @param anonymity: string enum
* @param replyRequest: boolean
* @param star: boolean
* @param bookmark: boolean
*/

router.post('/annotation', (req, res) => {
let range = req.body.range;
Source.findOne({ where: { [Op.and]: [{ filepath: req.body.url }, { class_id: req.body.class }] } })
Expand Down Expand Up @@ -974,7 +1110,6 @@ router.post('/bookmark/:id', (req, res) => {
);
});


function simplifyUser(user, role) {
const id = user.id;
user = user.get({ plain: true });
Expand All @@ -985,4 +1120,4 @@ function simplifyUser(user, role) {
}


module.exports = router;
module.exports = router
13 changes: 12 additions & 1 deletion routes/files.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,18 @@ router.get('/class/:id', (req, res) => {
*/
router.get('/folder/:id', (req, res) => {
FileSystemObject.findByPk(req.params.id)
.then((file) => file.getChildren({include:[{association:'Source', include:[{association:'Assignment', required: false}]}]}))
.then((file) => file.getChildren({include:[{association:'Source',
include:[{association:'Assignment', required: false},
{association:'Class'},
{association: 'Locations', required: false,
include:[{association: 'Thread', required: false,
include: [{association: 'AllAnnotations', required: false,
include: [{association: 'ReplyRequesters', required: false}]
}]
}]
}]
}]
}))
.then((files) => {
res.status(200).json(files);
});
Expand Down
67 changes: 64 additions & 3 deletions src/components/course/CourseContents.vue
Original file line number Diff line number Diff line change
Expand Up @@ -87,9 +87,15 @@
v-if="props.column.field === 'filename'"
class="clickable filename">
<font-awesome-icon :icon="fileIcon"></font-awesome-icon>
<span>&nbsp</span>
<span>&nbsp;</span>
<a :href="props.row.Source.filepath">{{props.row.filename}}</a>
</span>
<span v-else-if="props.column.field === 'annotations'" style="display:flex; justify-content:space-around;">
<div class="annotations"> Mine: {{annotations.filter(a => a.filepath === props.row.Source.filepath)[0]["me"]}} </div>
<div class="annotations"> Unread: {{annotations.filter(a => a.filepath === props.row.Source.filepath)[0]["unread"]}} </div>
<div class="annotations"> Reply Requests: {{annotations.filter(a => a.filepath === props.row.Source.filepath)[0]["replyRequests"]}} </div>
<div class="annotations"> Total: {{annotations.filter(a => a.filepath === props.row.Source.filepath)[0]["total"]}} </div>
</span>
<span v-else-if="props.column.field === 'Source.Assignment.deadlineString'">
<span>
{{ props.row.Source.Assignment ?
Expand Down Expand Up @@ -229,6 +235,11 @@
// enabled: true,
// },
},
{
label: 'Comments',
field: 'annotations',
sortable: false
},
{
label: 'Assignment Due',
field: 'Source.Assignment.deadlineString',
Expand Down Expand Up @@ -269,7 +280,8 @@
newFoldername: ""
},
deleteText: "Delete",
showDeleted: false
showDeleted: false,
annotations: []
}
},
computed:{
Expand Down Expand Up @@ -317,7 +329,7 @@
showDeleted: function() {
if(this.showDeleted) this.fileColumns[0].label = "Restore"
else this.fileColumns[0].label = "Edit"
}
},
},
methods:{
addFolder: function() {
Expand Down Expand Up @@ -366,13 +378,38 @@
if (file.Source && file.Source.Assignment) {
file.Source.Assignment.deadlineString = moment(String(file.Source.Assignment.deadline)).format('MM/DD/YYYY HH:mm')
}
if (file.Source && file.Source.Class){
this.numberAnnotations(file.Source.filepath, file.Source.Class.id)
}
}
this.contents = res.data
})
},
numberAnnotations: function(filepath, class_id){
const token = localStorage.getItem("nb.user");
const config = {headers: { Authorization: 'Bearer ' + token }}
axios.get(`/api/annotations/stats?url=${escape(filepath)}&class=${class_id}`, config)
.then((res) => {
res.data.filepath = filepath
this.annotations.push(res.data)
})
},
getRequestReply: function(locations){
let numReqs = 0
for (let i = 0; i < locations.length; i++){
if(locations[i].Thread){
for(let j = 0; j < locations[i].Thread.AllAnnotations.length; j++){
numReqs += locations[i].Thread.AllAnnotations[j].ReplyRequesters.length
}
}
}
return numReqs
},
switchDirectory: function(directory) {
this.showDeleted = false
this.$emit('switch-directory', directory)
Expand Down Expand Up @@ -522,6 +559,21 @@
},
mounted: function() {
this.loadFiles()
window.setInterval(()=> {
let new_annotations = []
for (let file of this.contents){
if (file.Source && file.Source.Class){
const token = localStorage.getItem("nb.user");
const config = {headers: { Authorization: 'Bearer ' + token }}
axios.get(`/api/annotations/stats?url=${escape(file.Source.filepath)}&class=${file.Source.Class.id}`, config)
.then((res) => {
res.data.filepath = file.Source.filepath
new_annotations.push(res.data)
})
}
}
this.annotations = new_annotations
}, 60000)
},
components: {
FontAwesomeIcon,
Expand Down Expand Up @@ -763,4 +815,13 @@
background-color: #0069d9;
}
.annotations {
color: #5f6266;
background-color: #f1f4f7;
border-radius: 5px;
padding: 3px;
margin: 1px;
font-size: 80%;
text-align: center;
}
</style>

0 comments on commit 51a981c

Please sign in to comment.