Skip to content

Commit

Permalink
Merge branch 'dev' into CSCEXAM-1209
Browse files Browse the repository at this point in the history
  • Loading branch information
lupari authored Dec 11, 2023
2 parents cbb670f + 6767ba2 commit 99a8d4f
Show file tree
Hide file tree
Showing 27 changed files with 601 additions and 270 deletions.
31 changes: 31 additions & 0 deletions app/controllers/ExamSectionController.java
Original file line number Diff line number Diff line change
Expand Up @@ -616,4 +616,35 @@ public Result getQuestionDistribution(Long id) {
node.put("distributed", isDistributed);
return ok(Json.toJson(node));
}

@Authenticated
@Restrict({ @Group("TEACHER"), @Group("ADMIN") })
public Result listSections(
Optional<String> filter,
Optional<List<Long>> courseIds,
Optional<List<Long>> examIds,
Optional<List<Long>> tagIds,
Http.Request request
) {
User user = request.attrs().get(Attrs.AUTHENTICATED_USER);
ExpressionList<ExamSection> query = DB.find(ExamSection.class).where();
if (!user.hasRole(Role.Name.ADMIN)) {
query = query.where().eq("creator.id", user.getId());
}
if (filter.isPresent()) {
String condition = String.format("%%%s%%", filter.get());
query = query.ilike("name", condition);
}
if (examIds.isPresent() && !examIds.get().isEmpty()) {
query = query.in("exam.id", examIds.get());
}
if (courseIds.isPresent() && !courseIds.get().isEmpty()) {
query = query.in("exam.course.id", courseIds.get());
}
if (tagIds.isPresent() && !tagIds.get().isEmpty()) {
query = query.in("examSectionQuestions.question.tags.id", tagIds.get());
}
Set<ExamSection> sections = query.findSet();
return ok(sections, PathProperties.parse("(*, creator(id))"));
}
}
6 changes: 3 additions & 3 deletions app/controllers/QuestionController.java
Original file line number Diff line number Diff line change
Expand Up @@ -90,8 +90,8 @@ public Result getQuestions(
return ok(Collections.emptySet());
}
PathProperties pp = PathProperties.parse(
"*, modifier(firstName, lastName) questionOwners(id, firstName, lastName, userIdentifier, email), " +
"attachment(id, fileName), options(defaultScore, correctOption, claimChoiceType), tags(name), examSectionQuestions(examSection(exam(state, examActiveEndDate, course(code)))))"
"*, modifier(firstName, lastName), questionOwners(id, firstName, lastName, userIdentifier, email), " +
"attachment(id, fileName), options(defaultScore, correctOption, claimChoiceType), tags(id, name), examSectionQuestions(examSection(exam(state, examActiveEndDate, course(code)))))"
);
Query<Question> query = DB.find(Question.class);
pp.apply(query);
Expand Down Expand Up @@ -241,7 +241,7 @@ private Question parseFromBody(Http.Request request, User user, Question existin
}
if (tag.isEmpty()) {
Tag newTag = new Tag();
newTag.setName(tagNode.get("name").asText());
newTag.setName(tagNode.get("name").asText().toLowerCase());
newTag.setCreatorWithDate(user);
newTag.setModifier(user);
tag = Optional.of(newTag);
Expand Down
26 changes: 24 additions & 2 deletions app/controllers/TagController.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,19 @@

import be.objectify.deadbolt.java.actions.Group;
import be.objectify.deadbolt.java.actions.Restrict;
import com.fasterxml.jackson.databind.JsonNode;
import controllers.base.BaseController;
import io.ebean.DB;
import io.ebean.ExpressionList;
import io.ebean.text.PathProperties;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.StreamSupport;
import models.Role;
import models.Tag;
import models.User;
import models.questions.Question;
import play.mvc.Http;
import play.mvc.Result;
import sanitizers.Attrs;
Expand All @@ -38,8 +41,8 @@ public class TagController extends BaseController {
@Restrict({ @Group("ADMIN"), @Group("TEACHER") })
public Result listTags(
Optional<String> filter,
Optional<List<Long>> examIds,
Optional<List<Long>> courseIds,
Optional<List<Long>> examIds,
Optional<List<Long>> sectionIds,
Http.Request request
) {
Expand All @@ -62,6 +65,25 @@ public Result listTags(
query = query.in("questions.examSectionQuestions.examSection.id", sectionIds.get());
}
Set<Tag> tags = query.findSet();
return ok(tags, PathProperties.parse("(*, creator(id))"));
return ok(tags, PathProperties.parse("(*, creator(id), questions(id))"));
}

@Restrict({ @Group("ADMIN"), @Group("TEACHER") })
public Result addTagToQuestions(Http.Request request) {
JsonNode body = request.body().asJson();
List<Long> questionIds = StreamSupport
.stream(body.get("questionIds").spliterator(), false)
.map(JsonNode::asLong)
.toList();
Long tagId = body.get("tagId").asLong();
List<Question> questions = DB.find(Question.class).where().idIn(questionIds).findList();
Tag tag = DB.find(Tag.class, tagId);
questions.forEach(question -> {
if (!question.getTags().contains(tag)) {
question.getTags().add(tag);
question.update();
}
});
return ok();
}
}
2 changes: 1 addition & 1 deletion app/util/xml/MoodleXmlImporterImpl.scala
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ class MoodleXmlImporterImpl @Inject()(fileHandler: FileHandler) extends MoodleXm
case h :: _ => h
case _ =>
val t = new Tag
t.setName(text)
t.setName(text.toLowerCase)
t.setCreator(user)
t
}
Expand Down
4 changes: 3 additions & 1 deletion conf/routes
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ DELETE /app/exams/:eid/sections/:sid/questions/:qid controlle
PUT /app/exams/:eid/sections/:sid/reorder controllers.ExamSectionController.reorderSectionQuestions(eid: Long, sid: Long, request: Request)
PUT /app/exams/:eid/reorder controllers.ExamSectionController.reorderSections(eid: Long, request: Request)
GET /app/exams/question/:id/distribution controllers.ExamSectionController.getQuestionDistribution(id: Long)
GET /app/sections controllers.ExamSectionController.listSections(filter: java.util.Optional[String], courseIds: java.util.Optional[LongList], examIds: java.util.Optional[LongList], tagIds: java.util.Optional[LongList], request: Request)

############### Section material interface ###############
GET /app/materials controllers.ExamMaterialController.listMaterials(request: Request)
Expand Down Expand Up @@ -406,7 +407,8 @@ GET /app/availability/:roomId/:date controlle
GET /app/languages controllers.LanguageController.getSupportedLanguages

################# Tag interface ##################
GET /app/tags controllers.TagController.listTags(filter: java.util.Optional[String], examIds: java.util.Optional[LongList], courseIds: java.util.Optional[LongList], sectionIds: java.util.Optional[LongList], request: Request)
GET /app/tags controllers.TagController.listTags(filter: java.util.Optional[String], courseIds: java.util.Optional[LongList], examIds: java.util.Optional[LongList], sectionIds: java.util.Optional[LongList], request: Request)
POST /app/tags/questions controllers.TagController.addTagToQuestions(request: Request)

################# General Settings interface ##################

Expand Down
2 changes: 1 addition & 1 deletion project/plugins.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@ resolvers += "Typesafe repository" at "https://repo.typesafe.com/typesafe/releas

addSbtPlugin("org.playframework" % "sbt-plugin" % "3.0.0")

addSbtPlugin("org.playframework" % "sbt-play-ebean" % "8.0.0-M1")
addSbtPlugin("org.playframework" % "sbt-play-ebean" % "8.0.0")

// addSbtPlugin("net.virtual-void" % "sbt-dependency-graph" % "0.10.0-RC1")
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
</button>
<div ngbDropdownMenu>
<button ngbDropdownItem (click)="editQuestion()">{{ 'sitnet_edit' | translate }}</button>
<button ngbDropdownItem (click)="copyQuestion()">{{ 'sitnet_copy' | translate }}</button>
<button ngbDropdownItem (click)="removeQuestion()">{{ 'sitnet_remove' | translate }}</button>
</div>
</div>
Expand Down
7 changes: 7 additions & 0 deletions ui/src/app/exam/editor/sections/section-question.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ export class SectionQuestionComponent {
@Input() examId = 0;
@Output() removed = new EventEmitter<ExamSectionQuestion>();
@Output() updated = new EventEmitter<ExamSectionQuestion>();
@Output() copied = new EventEmitter<ExamSectionQuestion>();

constructor(
private http: HttpClient,
Expand Down Expand Up @@ -73,6 +74,12 @@ export class SectionQuestionComponent {
this.translate.instant('sitnet_remove_question'),
).subscribe({ next: () => this.removed.emit(this.sectionQuestion), error: (err) => this.toast.error(err) });

copyQuestion = () =>
this.Confirmation.open$(
this.translate.instant('sitnet_confirm'),
this.translate.instant('sitnet_copy_question'),
).subscribe({ next: () => this.copied.emit(this.sectionQuestion), error: (err) => this.toast.error(err) });

determineClaimOptionType(examOption: ExamSectionQuestionOption) {
return this.Question.determineClaimOptionTypeForExamQuestionOption(examOption);
}
Expand Down
1 change: 1 addition & 0 deletions ui/src/app/exam/editor/sections/section.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,7 @@
[lotteryOn]="section.lotteryOn"
(removed)="removeQuestion($event)"
(updated)="updateQuestion($event)"
(copied)="copyQuestion($event)"
>
</xm-section-question>
</div>
Expand Down
9 changes: 9 additions & 0 deletions ui/src/app/exam/editor/sections/section.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,15 @@ export class SectionComponent {
});
};

copyQuestion = (sq: ExamSectionQuestion) =>
this.http.post<Question>(`/app/question/${sq.question.id}`, {}).subscribe({
next: (copy) => {
this.insertExamQuestion(copy, sq.sequenceNumber);
this.toast.info(this.translate.instant('sitnet_question_copied'));
},
error: (err) => this.toast.error(err),
});

updateQuestion = (sq: ExamSectionQuestion) => {
const index = this.section.sectionQuestions.findIndex((q) => q.id == sq.id);
this.section.sectionQuestions[index] = sq;
Expand Down
1 change: 1 addition & 0 deletions ui/src/app/exam/exam.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ export interface Tag {
id?: number;
name: string;
creator?: User;
questions: Question[];
}

export interface Question {
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<div id="sitnet-dialog" role="dialog" aria-modal="true" id="library">
<div class="modal-header">
<div class="student-enroll-dialog-wrap">
<h1 class="student-enroll-title">{{ 'sitnet_transfer_questions' | translate }}</h1>
</div>
</div>
<div class="modal-body">
<div class="question-pick-organisation-box">
<!-- org pick TODO: make a component out of this-->
<div class="row">
<div class="col-12">
<div ngbDropdown #orgPicker="ngbDropdown">
<button ngbDropdownToggle class="btn btn-outline-dark" type="button" id="dropDownToggle">
{{ 'sitnet_faculty_name' | translate }}&nbsp;
<span class="caret"></span>
</button>
<ul ngbDropdownMenu aria-labelledby="dropDownMenu">
<li
ngbDropdownItem
*ngFor="let org of organisations"
role="presentation"
(click)="organisation = org"
>
<a role="menuitem">{{ org.code }}&nbsp;({{ org.name }})</a>
</li>
</ul>
</div>
</div>
</div>
<div class="row align-items-center">
<div class="col-12 mt-2">
<button class="btn green whitetext" [disabled]="!organisation" (click)="transfer()">
{{ 'sitnet_copy' | translate }}
</button>
<span *ngIf="organisation" class="padl10 vertm">({{ organisation.code }})</span>
</div>
</div>
</div>
</div>
<div class="modal-footer">
<div class="student-message-dialog-button-save">
<button class="btn btn-sm btn-primary" (click)="activeModal.close()" autofocus>
{{ 'sitnet_close' | translate }}
</button>
</div>
</div>
</div>
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import { HttpClient } from '@angular/common/http';
import type { OnInit } from '@angular/core';
import { Component, Input } from '@angular/core';
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
import { TranslateService } from '@ngx-translate/core';
import { ToastrService } from 'ngx-toastr';

Expand All @@ -27,15 +28,20 @@ type Organisation = {

@Component({
selector: 'xm-library-transfer',
templateUrl: './library-transfer.component.html',
templateUrl: './library-transfer-dialog.component.html',
})
export class LibraryTransferComponent implements OnInit {
export class LibraryTransferDialogComponent implements OnInit {
@Input() selections: number[] = [];
organisations: Organisation[] = [];
organisation?: Organisation;
showOrganisationSelection = false;

constructor(private http: HttpClient, private translate: TranslateService, private toast: ToastrService) {}
constructor(
public activeModal: NgbActiveModal,
private http: HttpClient,
private translate: TranslateService,
private toast: ToastrService,
) {}

ngOnInit() {
this.http.get<Organisation[]>('/app/iop/organisations').subscribe((resp) => {
Expand Down
39 changes: 0 additions & 39 deletions ui/src/app/question/library/export/library-transfer.component.html

This file was deleted.

Loading

0 comments on commit 99a8d4f

Please sign in to comment.