This is the codeAbility Sharing Platform! Learn more about the codeAbility Sharing Platform.

Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • development/sharing/codeability-sharing-platform
1 result
Show changes
Showing
with 331 additions and 400 deletions
......@@ -5,21 +5,46 @@ import { PeerReviewingComponent } from './peer-reviewing.component';
import { TranslateModule } from '@ngx-translate/core';
import { LocalStorageService, SessionStorageService } from 'ngx-webstorage';
import { RouterTestingModule } from '@angular/router/testing';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { FontAwesomeModule } from '@fortawesome/angular-fontawesome';
import { MultiSelectDropdownExerciseComponent } from 'app/admin/review-management/multi-select-dropdown-exercise/multi-select-dropdown-exercise.component';
import { ExerciseDetailsModalComponent } from 'app/exercise/exercise-details/exercise-details.component';
import { FilterPipe } from 'app/admin/review-management/filter.pipe';
import { faPlus } from '@fortawesome/free-solid-svg-icons';
import { FaIconLibrary } from '@fortawesome/angular-fontawesome';
import { of } from 'rxjs';
import { UserService } from 'app/entities/user/user.service';
describe('PeerReviewingComponent', () => {
let component: PeerReviewingComponent;
let fixture: ComponentFixture<PeerReviewingComponent>;
const userServiceSpy = { getUser: jest.fn() };
userServiceSpy.getUser.mockReturnValue(of({ email: 'test@example.com' }));
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [HttpClientTestingModule, HttpClientModule, TranslateModule.forRoot(), RouterTestingModule],
declarations: [PeerReviewingComponent],
providers: [LocalStorageService, SessionStorageService],
imports: [
HttpClientTestingModule,
HttpClientModule,
TranslateModule.forRoot(),
RouterTestingModule,
ReactiveFormsModule,
FormsModule,
FontAwesomeModule,
],
declarations: [PeerReviewingComponent, ExerciseDetailsModalComponent, MultiSelectDropdownExerciseComponent, FilterPipe],
providers: [LocalStorageService, SessionStorageService, { provide: UserService, useValue: userServiceSpy }],
}).compileComponents();
});
beforeEach(() => {
const iconLibrary: FaIconLibrary = TestBed.inject(FaIconLibrary);
// add the faPlus icon to the library
iconLibrary.addIcons(faPlus);
fixture = TestBed.createComponent(PeerReviewingComponent);
component = fixture.componentInstance;
component.ngOnInit(); // manually triggering ngOnInit
fixture.detectChanges();
});
......
import { Component, EventEmitter, OnInit, Output } from '@angular/core';
import { ReviewManagementService } from 'app/admin/review-management/review-management.service';
import { Review } from 'app/admin/review-management/review.model';
import { Review, ReviewComment } from 'app/admin/review-management/review.model';
import { ReviewRequest } from 'app/admin/review-management/reviewRequest.model';
import { UserManagementService } from 'app/admin/user-management/service/user-management.service';
import { User } from 'app/admin/user-management/user-management.model';
......@@ -8,9 +8,9 @@ import { Account } from 'app/core/auth/account.model';
import { AccountService } from 'app/core/auth/account.service';
import { ExerciseService } from 'app/exercise/service/exercise.service';
import { SearchService } from 'app/search/service/search-service';
import { Exercise, searchResultToExercise } from 'app/shared/model/exercise.model';
import { SearchResultDTO } from 'app/shared/model/search/search-result-dto.model';
import { ExtendedSearchResultDTO, SearchResultDTO } from 'app/shared/model/search/search-result-dto.model';
import { SearchResultsDTO } from 'app/shared/model/search/search-results-dto.model';
import { jsPDF } from 'jspdf';
@Component({
selector: 'jhi-peer-reviewing',
......@@ -21,20 +21,30 @@ export class PeerReviewingComponent implements OnInit {
isChecked = false;
currentAccount: Account | null = null;
user: User | null = null;
results: Exercise[] = [];
allExercises: Exercise[] = [];
selectedExercise: Exercise | undefined;
results: SearchResultDTO[] = [];
allExercises: SearchResultDTO[] = [];
selectedExercise: SearchResultDTO | undefined;
reviews: Review[] = [];
otherReviews: Review[] = [];
userReviews: Review[] = [];
selectedReview = new Review('', [''], [''], ['']);
options = ['IMPROVEMENTS_REQUESTED', 'REVIEW_REJECTED', 'REVIEW_COMPLETED'];
selectedResult?: Exercise;
selectedReview = new Review(
'',
[''],
[''],
[new ReviewComment(false, false, '', false, false, '', false, false, '', false, '', false, false, '')]
);
statusOptions = [
{ value: 'REVIEW_IN_PROGRESS', translationKey: 'review.create.reviewInProgress' },
{ value: 'IMPROVEMENTS_REQUESTED', translationKey: 'review.create.improvementsRequested' },
{ value: 'REVIEW_REJECTED', translationKey: 'review.create.reviewRejected' },
{ value: 'REVIEW_COMPLETED', translationKey: 'review.create.reviewCompleted' },
];
selectedResult?: SearchResultDTO;
selectedStatus = '';
showOtherHistory: boolean[] = [];
showHistory: boolean[] = [];
@Output() exerciseSelectionEvent = new EventEmitter<Exercise>();
@Output() exerciseSelectionEvent = new EventEmitter<SearchResultDTO>();
constructor(
private userService: UserManagementService,
......@@ -45,16 +55,18 @@ export class PeerReviewingComponent implements OnInit {
) {}
ngOnInit(): void {
this.accountService.identity().subscribe(account => (this.currentAccount = account));
this.getUserFromLogin();
this.searchForExercises();
this.loadAll();
this.accountService.identity().subscribe(account => {
this.currentAccount = account;
this.getUserFromLogin();
});
}
getUserFromLogin(): void {
if (this.currentAccount != null && this.currentAccount.login != null) {
this.userService.getLoggedInUser().subscribe(val => {
this.user = val;
this.searchForExercises();
this.loadAll();
});
}
}
......@@ -62,10 +74,12 @@ export class PeerReviewingComponent implements OnInit {
loadAll(): void {
this.reviewManagementService.getAllByUser().subscribe(reviews => {
this.reviews = reviews;
this.userReviews = reviews.filter(review => review.requestedBy == this.user!.email);
this.showHistory = Array.from(Array(this.userReviews.length).keys()).map(() => false);
this.otherReviews = reviews.filter(review => !(review.requestedBy == this.user!.email));
this.showOtherHistory = Array.from(Array(this.otherReviews.length).keys()).map(() => false);
if (this.user && this.user.email) {
this.userReviews = reviews.filter(review => review.requestedBy == this.user!.email);
this.showHistory = this.userReviews?.length ? Array.from(Array(this.userReviews.length).keys()).map(() => false) : [];
this.otherReviews = reviews.filter(review => review.requestedBy != this.user!.email);
this.showOtherHistory = this.otherReviews?.length ? Array.from(Array(this.otherReviews.length).keys()).map(() => false) : [];
}
});
}
......@@ -76,33 +90,30 @@ export class PeerReviewingComponent implements OnInit {
}
searchForExercises(): void {
this.searchService.getResultsForLoggedInUser().subscribe(
(data: SearchResultsDTO) => {
const searchResults = data.searchResult.map(searchResultToExercise);
this.searchService.getResultsForLoggedInUser().subscribe({
next: (data: SearchResultsDTO) => {
this.results = this.results
.concat(searchResults)
.filter((value, index, self) => self.findIndex(t => t.title === value.title) === index);
.concat(data.searchResult)
.filter((value, index, self) => self.findIndex(t => t.metadata.title === value.metadata.title) === index);
},
() => alert('Search failed')
);
this.searchService.getAllResources().subscribe(
(data: SearchResultDTO[]) => {
const searchResults = data.map(searchResultToExercise);
error: () => alert('Search failed'),
});
this.searchService.getAllResources().subscribe({
next: (data: SearchResultDTO[]) => {
this.allExercises = this.allExercises
.concat(searchResults)
.filter((value, index, self) => self.findIndex(t => t.title === value.title) === index);
.concat(data)
.filter((value, index, self) => self.findIndex(t => t.metadata.title === value.metadata.title) === index);
},
() => (this.allExercises = [])
);
});
}
shareExercise(exercise: Exercise) {
shareExercise(exercise: SearchResultDTO) {
this.selectedExercise = exercise;
}
selectExercise(gitlabURL: string): Exercise {
const exercise: Exercise = this.exerciseService.populateExerciseWithData(
this.allExercises.find(exercise => exercise.gitlabURL == gitlabURL)!
selectExercise(gitlabURL: string): ExtendedSearchResultDTO {
const exercise: ExtendedSearchResultDTO = this.exerciseService.populateExerciseWithData(
this.allExercises.find(exercise => exercise.project.url == gitlabURL)!
);
return exercise;
}
......@@ -112,13 +123,22 @@ export class PeerReviewingComponent implements OnInit {
this.searchService
.requestReview(
new ReviewRequest(
this.selectedExercise!.title,
this.selectedExercise!.originalResult.exerciseId,
this.selectedExercise!.metadata.title,
this.selectedExercise!.exerciseId,
[this.currentAccount.email],
this.selectedExercise!.gitlabURL
this.selectedExercise!.project.url
)
)
.subscribe(() => this.loadAll());
.subscribe({
next: () => {
// This function runs when the request is successful.
this.loadAll();
},
error: () => {
// This function runs when the request fails.
alert('It seems like a Review for this Exercise already exists.'); // You can customize this message.
},
});
}
}
......@@ -152,4 +172,69 @@ export class PeerReviewingComponent implements OnInit {
isOtherHistoryVisible(index: number): boolean {
return this.showOtherHistory[index];
}
downloadPDF() {
const doc = new jsPDF();
let y = 10;
this.selectedReview.comments!.forEach((comment, i) => {
if (y > 280) {
doc.addPage();
y = 10;
}
doc.text(`User: ${this.selectedReview.users![i]}`, 10, y);
y += 10;
if (y + 40 > 280) {
doc.addPage();
y = 10;
}
doc.text(`Clarity and Understandability:`, 10, y);
doc.text(`Description is clear: ${String(comment.descriptionIsClear)}`, 20, y + 10);
doc.text(`Requirements are clear: ${String(comment.requirementsAreClear)}`, 20, y + 20);
doc.text(`Comment: ${String(comment.descriptionComment)}`, 20, y + 30);
y += 40;
if (y + 30 > 280) {
doc.addPage();
y = 10;
}
doc.text(`Completeness:`, 10, y);
doc.text(`Information required is complete: ${String(comment.informationRequiredComplete)}`, 20, y + 10);
doc.text(`All materials are available: ${String(comment.allMaterialsAvailable)}`, 20, y + 20);
y += 30;
if (y + 40 > 280) {
doc.addPage();
y = 10;
}
doc.text(`Format and Structure:`, 10, y);
doc.text(`Is structured: ${String(comment.isStructured)}`, 20, y + 10);
doc.text(`Sub-exercises are clear: ${String(comment.subExercisesAreClear)}`, 20, y + 20);
doc.text(`Comment: ${String(comment.structuredComment)}`, 20, y + 30);
y += 40;
if (y + 30 > 280) {
doc.addPage();
y = 10;
}
doc.text(`Validity and Correctness:`, 10, y);
doc.text(`Solution is correct: ${String(comment.solutionIsCorrect)}`, 20, y + 10);
doc.text(`Comment: ${String(comment.solutionComment)}`, 20, y + 20);
y += 30;
if (y + 40 > 280) {
doc.addPage();
y = 10;
}
doc.text(`Requirements and Metadata:`, 10, y);
doc.text(`Metadata is complete: ${String(comment.metadataIsComplete)}`, 20, y + 10);
doc.text(`Metadata is correct: ${String(comment.metadataIsCorrect)}`, 20, y + 20);
doc.text(`Comment: ${String(comment.metadataComment)}`, 20, y + 30);
y += 40;
});
doc.save('comments.pdf');
}
}
......@@ -2,235 +2,6 @@
<div class="row justify-content-center">
<div class="col-md-8">
<h1 class="alert alert-warning" jhiTranslate="register.title" data-cy="registerTitle">Self Registration is disabled</h1>
<!-- <div class="alert alert-success" *ngIf="success" jhiTranslate="register.messages.success">
<strong>Registration saved!</strong> Please check your email for confirmation.
</div>
<div class="alert alert-danger" *ngIf="error" jhiTranslate="register.messages.error.fail">
<strong>Registration failed!</strong> Please try again later.
</div>
<div class="alert alert-danger" *ngIf="errorUserExists" jhiTranslate="register.messages.error.userexists">
<strong>Login name already registered!</strong> Please choose another one.
</div>
<div class="alert alert-danger" *ngIf="errorEmailExists" jhiTranslate="register.messages.error.emailexists">
<strong>Email is already in use!</strong> Please choose another one.
</div>
<div class="alert alert-danger" *ngIf="doNotMatch" jhiTranslate="global.messages.error.dontmatch">
The password and its confirmation do not match!
</div>
</div>
</div>
<div class="row justify-content-center">
<div class="col-md-8">
<form name="form" role="form" (ngSubmit)="register()" [formGroup]="registerForm" *ngIf="!success">
<div class="form-group">
<label class="form-control-label" for="login" jhiTranslate="global.form.username.label">Username</label>
<input
type="text"
class="form-control"
id="login"
name="login"
placeholder="{{ 'global.form.username.placeholder' | translate }}"
formControlName="login"
data-cy="username"
#login
/>
<div *ngIf="registerForm.get('login')!.invalid && (registerForm.get('login')!.dirty || registerForm.get('login')!.touched)">
<small
class="form-text text-danger"
*ngIf="registerForm.get('login')?.errors?.required"
jhiTranslate="register.messages.validate.login.required"
>
Your username is required.
</small>
<small
class="form-text text-danger"
*ngIf="registerForm.get('login')?.errors?.minlength"
jhiTranslate="register.messages.validate.login.minlength"
>
Your username is required to be at least 1 character.
</small>
<small
class="form-text text-danger"
*ngIf="registerForm.get('login')?.errors?.maxlength"
jhiTranslate="register.messages.validate.login.maxlength"
>
Your username cannot be longer than 50 characters.
</small>
<small
class="form-text text-danger"
*ngIf="registerForm.get('login')?.errors?.pattern"
jhiTranslate="register.messages.validate.login.pattern"
>
Your username can only contain letters and digits.
</small>
</div>
</div>
<div class="form-group">
<label class="form-control-label" for="email" jhiTranslate="global.form.email.label">Email</label>
<input
type="email"
class="form-control"
id="email"
name="email"
placeholder="{{ 'global.form.email.placeholder' | translate }}"
formControlName="email"
data-cy="email"
/>
<div *ngIf="registerForm.get('email')!.invalid && (registerForm.get('email')!.dirty || registerForm.get('email')!.touched)">
<small
class="form-text text-danger"
*ngIf="registerForm.get('email')?.errors?.required"
jhiTranslate="global.messages.validate.email.required"
>
Your email is required.
</small>
<small
class="form-text text-danger"
*ngIf="registerForm.get('email')?.errors?.invalid"
jhiTranslate="global.messages.validate.email.invalid"
>
Your email is invalid.
</small>
<small
class="form-text text-danger"
*ngIf="registerForm.get('email')?.errors?.minlength"
jhiTranslate="global.messages.validate.email.minlength"
>
Your email is required to be at least 5 characters.
</small>
<small
class="form-text text-danger"
*ngIf="registerForm.get('email')?.errors?.maxlength"
jhiTranslate="global.messages.validate.email.maxlength"
>
Your email cannot be longer than 100 characters.
</small>
</div>
</div>
<div class="form-group">
<label class="form-control-label" for="password" jhiTranslate="global.form.newpassword.label">New password</label>
<input
type="password"
class="form-control"
id="password"
name="password"
placeholder="{{ 'global.form.newpassword.placeholder' | translate }}"
formControlName="password"
data-cy="firstPassword"
/>
<div
*ngIf="registerForm.get('password')!.invalid && (registerForm.get('password')!.dirty || registerForm.get('password')!.touched)"
>
<small
class="form-text text-danger"
*ngIf="registerForm.get('password')?.errors?.required"
jhiTranslate="global.messages.validate.newpassword.required"
>
Your password is required.
</small>
<small
class="form-text text-danger"
*ngIf="registerForm.get('password')?.errors?.minlength"
jhiTranslate="global.messages.validate.newpassword.minlength"
>
Your password is required to be at least 4 characters.
</small>
<small
class="form-text text-danger"
*ngIf="registerForm.get('password')?.errors?.maxlength"
jhiTranslate="global.messages.validate.newpassword.maxlength"
>
Your password cannot be longer than 50 characters.
</small>
</div>
<jhi-password-strength-bar [passwordToCheck]="registerForm.get('password')!.value"></jhi-password-strength-bar>
</div>
<div class="form-group">
<label class="form-control-label" for="confirmPassword" jhiTranslate="global.form.confirmpassword.label"
>New password confirmation</label
>
<input
type="password"
class="form-control"
id="confirmPassword"
name="confirmPassword"
placeholder="{{ 'global.form.confirmpassword.placeholder' | translate }}"
formControlName="confirmPassword"
data-cy="secondPassword"
/>
<div
*ngIf="
registerForm.get('confirmPassword')!.invalid &&
(registerForm.get('confirmPassword')!.dirty || registerForm.get('confirmPassword')!.touched)
"
>
<small
class="form-text text-danger"
*ngIf="registerForm.get('confirmPassword')?.errors?.required"
jhiTranslate="global.messages.validate.confirmpassword.required"
>
Your confirmation password is required.
</small>
<small
class="form-text text-danger"
*ngIf="registerForm.get('confirmPassword')?.errors?.minlength"
jhiTranslate="global.messages.validate.confirmpassword.minlength"
>
Your confirmation password is required to be at least 4 characters.
</small>
<small
class="form-text text-danger"
*ngIf="registerForm.get('confirmPassword')?.errors?.maxlength"
jhiTranslate="global.messages.validate.confirmpassword.maxlength"
>
Your confirmation password cannot be longer than 50 characters.
</small>
</div>
</div>
<button
type="submit"
[disabled]="registerForm.invalid"
class="btn btn-primary"
jhiTranslate="register.form.button"
data-cy="submit"
>
Register
</button>
</form>
<div class="mt-3 alert alert-warning">
<span jhiTranslate="global.messages.info.authenticated.prefix">If you want to </span>
<a class="alert-link" routerLink="/login" jhiTranslate="global.messages.info.authenticated.link">sign in</a
><span jhiTranslate="global.messages.info.authenticated.suffix"
>, you can try the default accounts:<br />- Administrator (login="admin" and password="admin") <br />- User (login="user" and
password="user").</span
>
</div> -->
</div>
</div>
</div>
......@@ -64,11 +64,11 @@ export class SettingsComponent implements OnInit {
}
public triggerInfoMailSending(): void {
this.testService.triggerInfoMail().subscribe(
(m: Message) => {
this.testService.triggerInfoMail().subscribe({
next: (m: Message) => {
alert(m.message);
},
() => alert('Message sending failed!')
);
error: () => alert('Message sending failed!'),
});
}
}
import { Pipe, PipeTransform } from '@angular/core';
import { Exercise } from 'app/shared/model/exercise.model';
import { SearchResultDTO } from 'app/shared/model/search/search-result-dto.model';
@Pipe({
name: 'filterExerciseTitle',
})
export class FilterPipe implements PipeTransform {
transform(value: Exercise[], searchValue: string): any {
transform(value: SearchResultDTO[], searchValue: string): any {
if (!searchValue) return value;
return value.filter(v => v.title.toLowerCase().indexOf(searchValue.toLowerCase()) > -1);
return value.filter(v => v.metadata.title.toLowerCase().indexOf(searchValue.toLowerCase()) > -1);
}
}
......@@ -11,10 +11,14 @@
/>
</div>
<div class="pt-2">
<label *ngFor="let a of list! | filterExerciseTitle: search" class="row">
<input type="radio" [checked]="checkedList?.includes(a.title)" (change)="getSelectedValue(!checkedList?.includes(a.title), a)" />
<label *ngFor="let a of list! | filterExerciseTitle: search" class="row" style="padding-left: 20px">
<input
type="radio"
[checked]="checkedList?.includes(a.metadata.title)"
(change)="getSelectedValue(!checkedList?.includes(a.title), a)"
/>
<span>
<a href="{{ a.gitlabURL }}" style="color: rgb(33, 37, 41)" target="_blank">{{ a.title }}</a></span
<a href="{{ a.project.url }}" style="color: rgb(33, 37, 41)" target="_blank">{{ a.metadata.title }}</a></span
>
</label>
</div>
......
import { Component, Input, Output, EventEmitter, OnChanges } from '@angular/core';
import { Exercise } from 'app/shared/model/exercise.model';
import { Component, Input, Output, EventEmitter } from '@angular/core';
import { SearchResultDTO } from 'app/shared/model/search/search-result-dto.model';
@Component({
selector: 'jhi-multi-select-dropdown-exercise',
templateUrl: './multi-select-dropdown-exercise.component.html',
styleUrls: ['./multi-select-dropdown-exercise.component.scss'],
})
export class MultiSelectDropdownExerciseComponent implements OnChanges {
@Input() list: Exercise[] | undefined;
export class MultiSelectDropdownExerciseComponent {
@Input() list: SearchResultDTO[] | undefined;
@Output() shareExercise = new EventEmitter();
@Input() checkedList: string[] | undefined;
currentSelected!: Exercise;
currentSelected!: SearchResultDTO;
search = '';
constructor() {
console.log(this.checkedList);
}
ngOnChanges() {
console.log(this.checkedList);
}
getSelectedValue(status: Boolean, value: Exercise) {
getSelectedValue(status: Boolean, value: SearchResultDTO) {
if (status) {
this.checkedList = [];
this.checkedList.push(value.title);
this.checkedList.push(value.metadata.title);
} else {
this.checkedList = [];
}
......
......@@ -11,7 +11,7 @@
/>
</div>
<div class="pt-2">
<label *ngFor="let a of list! | filterEmail: search">
<label *ngFor="let a of list! | filterEmail: search" style="padding-left: 20px">
<input type="checkbox" [checked]="checkedList.includes(a.email)" (change)="getSelectedValue(!checkedList.includes(a.email), a)" />
<span style="color: rgb(33, 37, 41)">{{ a.email }}</span>
</label>
......
......@@ -5,7 +5,6 @@ import { User } from 'app/admin/user-management/user-management.model';
import { Account } from 'app/core/auth/account.model';
import { AccountService } from 'app/core/auth/account.service';
import { SearchService } from 'app/search/service/search-service';
import { Exercise, searchResultToExercise } from 'app/shared/model/exercise.model';
import { SearchResultDTO } from 'app/shared/model/search/search-result-dto.model';
import { ReviewManagementService } from '../review-management.service';
import { Review } from '../review.model';
......@@ -13,13 +12,12 @@ import { Review } from '../review.model';
@Component({
selector: 'jhi-review-management-update',
templateUrl: './review-management-update.component.html',
styleUrls: ['./review-management-update.component.scss'],
})
export class ReviewManagementUpdateComponent implements OnInit {
currentAccount: Account | null = null;
users: User[] | undefined;
results: Exercise[] = [];
selectedExercise: Exercise | undefined;
results: SearchResultDTO[] = [];
selectedExercise: SearchResultDTO | undefined;
reviews: Review[] = [];
search = '';
reviewToEdit: Review | undefined;
......@@ -57,41 +55,40 @@ export class ReviewManagementUpdateComponent implements OnInit {
this.userService.loadAll().subscribe(users => {
this.users = users;
});
this.searchService.getAllResources().subscribe(
(data: SearchResultDTO[]) => {
const searchResults = data.map(searchResultToExercise);
this.searchService.getAllResources().subscribe({
next: (data: SearchResultDTO[]) => {
this.results = this.results
.concat(searchResults)
.filter((value, index, self) => self.findIndex(t => t.title === value.title) === index);
.concat(data)
.filter((value, index, self) => self.findIndex(t => t.metadata.title === value.metadata.title) === index);
},
() => (this.results = [])
);
error: () => (this.results = []),
});
}
shareCheckedUserList(users: string[]) {
this.reviewToEdit!.users = users;
}
shareExercise(exercise: Exercise) {
shareExercise(exercise: SearchResultDTO) {
this.selectedExercise = exercise;
this.reviewToEdit!.resource = exercise.title;
this.reviewToEdit!.resource = exercise.metadata.title;
}
submitReview() {
this.selectedExercise = this.results.find(exercise => exercise.title === this.reviewToEdit!.resource);
const res = this.reviewToEdit!.users!.filter(o => this.selectedExercise?.contributor?.some(x => x.email == o));
this.selectedExercise = this.results.find(exercise => exercise.metadata.title === this.reviewToEdit!.resource);
const res = this.reviewToEdit!.users!.filter(o => this.selectedExercise?.metadata.contributor?.some(x => x.email == o));
if (res.length > 0) {
alert('The selected users: ' + res.toString() + ' ,are already contributors of this exercise. Please select other users.');
return;
}
const res2 = this.reviewToEdit!.users!.filter(o => this.selectedExercise?.creator.some(x => x.email == o));
const res2 = this.reviewToEdit!.users!.filter(o => this.selectedExercise?.metadata.creator.some(x => x.email == o));
if (res2.length > 0) {
alert('The selected users: ' + res2.toString() + ' ,are already creators of this exercise. Please select other users.');
return;
}
const res3 = this.reviewToEdit!.users!.filter(o => this.selectedExercise?.publisher.some(x => x.email == o));
const res3 = this.reviewToEdit!.users!.filter(o => this.selectedExercise?.metadata.publisher.some(x => x.email == o));
if (res3.length > 0) {
alert('The selected users: ' + res3.toString() + ' ,are already publisher of this exercise. Please select other users.');
......@@ -99,10 +96,12 @@ export class ReviewManagementUpdateComponent implements OnInit {
}
if (
this.reviews.filter(e => e.resource == this.selectedExercise?.title).length > 0 &&
this.reviewToEdit?.resource != this.selectedExercise?.title
this.reviews.filter(e => e.resource == this.selectedExercise?.metadata.title).length > 0 &&
this.reviewToEdit?.resource != this.selectedExercise?.metadata.title
) {
alert('The selected exercise: ' + this.selectedExercise!.title + ' ,is already in the database. Please select other exercise.');
alert(
'The selected exercise: ' + this.selectedExercise!.metadata.title + ' ,is already in the database. Please select other exercise.'
);
return;
}
......
......@@ -109,7 +109,7 @@
(click)="selectedReview = review"
>
<fa-icon icon="times"></fa-icon>
<span class="d-none d-md-inline" jhiTranslate="review.create.comments">View comments</span>
<span class="d-none d-md-inline" jhiTranslate="review.create.comments">View Feedback</span>
</button>
</div>
</td>
......@@ -119,7 +119,10 @@
<fa-icon icon="check-circle"></fa-icon>
<span class="d-none d-md-inline" jhiTranslate="review.create.rewardBadge">Reward Badge</span>
</button>
<button *ngIf="review.badgeRewarded" type="button" (click)="removeBadge(review)" class="m-1 btn btn-warning btn-sm">
<fa-icon icon="times-circle"></fa-icon>
<span class="d-none d-md-inline" jhiTranslate="review.create.removeBadge">Remove Badge</span>
</button>
<button
*ngIf="!review.badgeRewarded"
type="submit"
......
......@@ -3,7 +3,6 @@ import { ActivatedRoute } from '@angular/router';
import { Account } from 'app/core/auth/account.model';
import { AccountService } from 'app/core/auth/account.service';
import { SearchService } from 'app/search/service/search-service';
import { Exercise, searchResultToExercise } from 'app/shared/model/exercise.model';
import { SearchResultDTO } from 'app/shared/model/search/search-result-dto.model';
import { UserManagementService } from '../user-management/service/user-management.service';
import { User } from '../user-management/user-management.model';
......@@ -20,11 +19,11 @@ import { ReviewRequest } from './reviewRequest.model';
export class ReviewManagementComponent implements OnInit, OnChanges {
currentAccount: Account | null = null;
users: User[] | undefined;
results: Exercise[] = [];
results: SearchResultDTO[] = [];
selectedUsers: string[] | undefined;
selectedExercise: Exercise | undefined;
selectedExercise: SearchResultDTO | undefined;
reviews: Review[] = [];
selectedReview = new Review('', [''], [''], ['']);
selectedReview = new Review('', [''], [''], []);
showHistory: boolean[] = [];
constructor(
......@@ -51,15 +50,14 @@ export class ReviewManagementComponent implements OnInit, OnChanges {
this.userService.loadAll().subscribe(users => {
this.users = users;
});
this.searchService.getAllResources().subscribe(
(data: SearchResultDTO[]) => {
const searchResults = data.map(searchResultToExercise);
this.searchService.getAllResources().subscribe({
next: (data: SearchResultDTO[]) => {
this.results = this.results
.concat(searchResults)
.filter((value, index, self) => self.findIndex(t => t.title === value.title) === index);
.concat(data)
.filter((value, index, self) => self.findIndex(t => t.metadata.title === value.metadata.title) === index);
},
() => (this.results = [])
);
error: () => (this.results = []),
});
this.reviewManagementService.getAllWithStatistics().subscribe(reviews => {
this.reviews = reviews;
this.showHistory = Array.from(Array(this.reviews.length).keys()).map(() => false);
......@@ -69,43 +67,45 @@ export class ReviewManagementComponent implements OnInit, OnChanges {
shareCheckedUserList(users: string[]) {
this.selectedUsers = users;
}
shareExercise(exercise: Exercise) {
shareExercise(exercise: SearchResultDTO) {
this.selectedExercise = exercise;
}
submitReview() {
const res = this.selectedUsers!.filter(o => this.selectedExercise?.contributor?.some(x => x.email == o));
const res = this.selectedUsers!.filter(o => this.selectedExercise?.metadata.contributor?.some(x => x.email == o));
if (res.length > 0) {
alert('The selected users: ' + res.toString() + ' ,are already contributors of this exercise. Please select other users.');
return;
}
const res2 = this.selectedUsers!.filter(o => this.selectedExercise?.creator.some(x => x.email == o));
const res2 = this.selectedUsers!.filter(o => this.selectedExercise?.metadata.creator.some(x => x.email == o));
if (res2.length > 0) {
alert('The selected users: ' + res2.toString() + ' ,are already creators of this exercise. Please select other users.');
return;
}
const res3 = this.selectedUsers!.filter(o => this.selectedExercise?.publisher.some(x => x.email == o));
const res3 = this.selectedUsers!.filter(o => this.selectedExercise?.metadata.publisher.some(x => x.email == o));
if (res3.length > 0) {
alert('The selected users: ' + res3.toString() + ' ,are already publisher of this exercise. Please select other users.');
return;
}
if (this.reviews.filter(e => e.resource == this.selectedExercise?.title).length > 0) {
alert('The selected exercise: ' + this.selectedExercise!.title + ' ,is already in the database. Please select other exercise.');
if (this.reviews.filter(e => e.resource == this.selectedExercise?.metadata.title).length > 0) {
alert(
'The selected exercise: ' + this.selectedExercise!.metadata.title + ' ,is already in the database. Please select other exercise.'
);
return;
}
this.reviewManagementService
.create(
new ReviewRequest(
this.selectedExercise!.title,
this.selectedExercise!.originalResult.exerciseId,
this.selectedExercise!.metadata.title,
this.selectedExercise!.exerciseId,
this.selectedUsers!,
this.selectedExercise!.gitlabURL
this.selectedExercise!.project.url
)
)
.subscribe(() => {
......@@ -131,4 +131,10 @@ export class ReviewManagementComponent implements OnInit, OnChanges {
this.reviewManagementService.rewardBadge(review).subscribe(() => this.loadAll());
}
removeBadge(review: Review) {
review.status = review.status?.map(() => 'REVIEW_IN_PROGRESS');
this.reviewManagementService.removeBadge(review).subscribe(() => this.loadAll());
}
}
......@@ -44,11 +44,11 @@ describe('ReviewManagementService', () => {
reviewImproved: 0,
reviewCompleted: 0,
unknownStats: 0,
reviewsRequestedByUser: 2,
reviewInProgressByUser: 0,
reviewImprovementRequestedByUser: 2,
reviewCompletedByUser: 0,
unknownStatsByUser: 0,
reviewsRequestedByReviewer: 2,
reviewInProgressByReviewer: 0,
reviewImprovementRequestedByReviewer: 2,
reviewCompletedByReviewer: 0,
unkownStatsByReviewer: 0,
badgesRewarded: 1,
});
expect(isInvoked).toEqual(2);
......
......@@ -45,13 +45,17 @@ export class ReviewManagementService {
}
notifyReviewer(review: Review): Observable<Review> {
return this.http.post<Review>(this.resourceUrl + '/notifyReviewers', review);
return this.http.post<Review>(this.resourceUrl + '/notifyReviewers', review).pipe(tap(() => this.resetStatistics()));
}
rewardBadge(review: Review): Observable<Review> {
return this.http.post<Review>(this.resourceUrl + '/rewardBadge', review).pipe(tap(() => this.resetStatistics()));
}
removeBadge(review: Review): Observable<Review> {
return this.http.post<Review>(this.resourceUrl + '/removeBadge', review).pipe(tap(() => this.resetStatistics()));
}
private reviewStatisticsCache: BehaviorSubject<ReviewStatisticsDTO> | undefined;
public getReviewStatistics(): Observable<ReviewStatisticsDTO> {
......
......@@ -3,13 +3,13 @@ export class Review {
resource: string | undefined;
users: string[] | undefined;
status: string[] | undefined;
comments: string[] | undefined;
comments: ReviewComment[] | undefined;
requestedBy: string | undefined;
link: string | undefined;
badgeRewarded: boolean | undefined;
resourceID: string | undefined;
constructor(resource: string, users: string[], status: string[], comments: string[]) {
constructor(resource: string, users: string[], status: string[], comments: ReviewComment[]) {
this.resource = resource;
this.users = users;
this.status = status;
......@@ -17,15 +17,72 @@ export class Review {
}
}
export class ReviewComment {
descriptionIsClear: boolean | undefined;
requirementsAreClear: boolean | undefined;
descriptionComment: string | undefined;
informationRequiredComplete: boolean | undefined;
allMaterialsAvailable: boolean | undefined;
informationRequiredComment: string | undefined;
isStructured: boolean | undefined;
subExercisesAreClear: boolean | undefined;
structuredComment: string | undefined;
solutionIsCorrect: boolean | undefined;
solutionComment: string | undefined;
metadataIsComplete: boolean | undefined;
metadataIsCorrect: boolean | undefined;
metadataComment: string | undefined;
constructor(
descriptionIsClear: boolean,
requirementsAreClear: boolean,
descriptionComment: string,
informationRequiredComplete: boolean,
allMaterialsAvailable: boolean,
informationRequiredComment: string,
isStructured: boolean,
subExercisesAreClear: boolean,
structuredComment: string,
solutionIsCorrect: boolean,
solutionComment: string,
metadataIsComplete: boolean,
metadataIsCorrect: boolean,
metadataComment: string
) {
this.descriptionIsClear = descriptionIsClear;
this.requirementsAreClear = requirementsAreClear;
this.descriptionComment = descriptionComment;
this.informationRequiredComplete = informationRequiredComplete;
this.allMaterialsAvailable = allMaterialsAvailable;
this.informationRequiredComment = informationRequiredComment;
this.isStructured = isStructured;
this.subExercisesAreClear = subExercisesAreClear;
this.structuredComment = structuredComment;
this.solutionIsCorrect = solutionIsCorrect;
this.solutionComment = solutionComment;
this.metadataIsComplete = metadataIsComplete;
this.metadataIsCorrect = metadataIsCorrect;
this.metadataComment = metadataComment;
}
}
export class ReviewStatisticsDTO {
reviewInProgress = 0;
reviewImprovementRequested = 0;
reviewImproved = 0;
reviewCompleted = 0;
reviewRequested = 0;
unknownStats = 0;
reviewsRequestedByUser = 0;
reviewInProgressByUser = 0;
reviewImprovementRequestedByUser = 0;
reviewCompletedByUser = 0;
unknownStatsByUser = 0;
reviewsAssignedToReviewer = 0;
reviewImprovedForReviewer = 0;
reviewRejectedByReviewer = 0;
reviewInProgressByReviewer = 0;
reviewImprovementRequestedByReviewer = 0;
reviewCompletedByReviewer = 0;
unkownStatsByReviewer = 0;
badgesRewarded = 0;
}
......@@ -6,9 +6,6 @@
</div>
<div class="modal-body">
<!-- TODO
<jhi-alert-error></jhi-alert-error>
-->
<p
id="jhi-delete-userWatchList-heading"
jhiTranslate="gitsearchApp.userWatchList.delete.question"
......
......@@ -77,10 +77,10 @@ export class UserWatchListUpdateComponent implements OnInit {
}
protected subscribeToSaveResponse(result: Observable<HttpResponse<IUserWatchList>>): void {
result.subscribe(
() => this.onSaveSuccess(),
() => this.onSaveError()
);
result.subscribe({
next: () => this.onSaveSuccess(),
error: () => this.onSaveError(),
});
}
protected onSaveSuccess(): void {
......
......@@ -31,9 +31,6 @@
<table class="table table-striped" aria-describedby="page-heading">
<thead>
<tr jhiSort [(predicate)]="predicate" [(ascending)]="ascending">
<!-- TODO
[callback]="reset.bind(this)">
-->
<th scope="col" jhiSortBy="name">
<span jhiTranslate="gitsearchApp.userWatchList.name">Name</span> <fa-icon icon="sort"></fa-icon>
</th>
......@@ -95,10 +92,6 @@
</button>
</div>
</div>
<!--
<jhi-alert-error></jhi-alert-error>
<jhi-alert></jhi-alert>
-->
<div class="mt-5 col-12 col-md-7 col-lg-8 col-xl-9">
<div *ngIf="results.length === 0">
<span jhiTranslate="search.noResults">No results found</span>
......@@ -110,7 +103,7 @@
<jhi-exercise-card
*ngFor="let result of results"
class="card-container col-12 col-lg-6 col-xl-4"
[exercise]="result"
[referencedExercise]="result"
(exerciseSelectionEvent)="selectExercise($event)"
>
</jhi-exercise-card>
......@@ -118,4 +111,7 @@
</div>
</div>
<jhi-exercise-modal-details [exercise]="selectedResult" (exerciseChangedEvent)="selectExercise($event)"></jhi-exercise-modal-details>
<jhi-exercise-modal-details
[referencedExercise]="selectedResult"
(exerciseChangedEvent)="selectExercise($event)"
></jhi-exercise-modal-details>
......@@ -8,7 +8,6 @@ import { ParseLinks } from 'app/core/util/parse-links.service';
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
import { IUserWatchList } from 'app/shared/model/user-watch-list.model';
import { Exercise, searchResultToExercise } from 'app/shared/model/exercise.model';
import { ITEMS_PER_PAGE } from 'app/config/pagination.constants';
import { UserWatchListService } from 'app/entities/user-watch-list/user-watch-list.service';
......@@ -40,8 +39,8 @@ export class BookmarkComponent implements OnInit, OnDestroy {
ascending: boolean;
currentSearch: string;
results: Exercise[] = [];
selectedResult?: Exercise;
results: SearchResultDTO[] = [];
selectedResult?: SearchResultDTO;
hitCount = 0;
constructor(
......@@ -133,7 +132,7 @@ export class BookmarkComponent implements OnInit, OnDestroy {
this.loadAll();
}
selectExercise(exercise: Exercise): void {
selectExercise(exercise: SearchResultDTO): void {
this.selectedResult = exercise;
}
......@@ -174,14 +173,14 @@ export class BookmarkComponent implements OnInit, OnDestroy {
this.eventSubscriber = this.eventManager.subscribe('userWatchListListModification', () => this.reset());
}
private parseSearchResultsDTO(searchResultsDTO: SearchResultsDTO): Exercise[] {
private parseSearchResultsDTO(searchResultsDTO: SearchResultsDTO): SearchResultDTO[] {
this.hitCount = searchResultsDTO.hitCount;
// fix string to date conversion: unfortunatelly dates are not converted correctly :-()
searchResultsDTO.searchResult.forEach(hit => {
hit.project.last_activity_at = new Date(hit.project.last_activity_at);
hit.file.indexing_date = new Date(hit.file.indexing_date);
});
return searchResultsDTO.searchResult.map(searchResultToExercise);
return searchResultsDTO.searchResult;
}
delete(userWatchList: IUserWatchList): void {
......@@ -210,8 +209,8 @@ export class BookmarkComponent implements OnInit, OnDestroy {
onScroll(): void {
if (this.selectedWatchList) {
if (!this.allLoaded) {
this.userWatchListService.findExercises(this.selectedWatchList, this.page).subscribe(
(data: SearchResultsDTO) => {
this.userWatchListService.findExercises(this.selectedWatchList, this.page).subscribe({
next: (data: SearchResultsDTO) => {
const searchResults = this.parseSearchResultsDTO(data);
if (data.pageStartIndex + searchResults.length < this.results.length) {
// we are replacing already existing results inside the array
......@@ -237,8 +236,8 @@ export class BookmarkComponent implements OnInit, OnDestroy {
}
++this.page; // TODO this may result in a race condition, if requests are not returned in sequence :-()
},
() => alert('Search failed')
);
error: () => alert('Search failed'),
});
}
}
}
......@@ -253,8 +252,8 @@ export class BookmarkComponent implements OnInit, OnDestroy {
const headersLink = headers.get('link');
this.links = this.parseLinks.parse(headersLink ? headersLink : '');
if (data) {
for (let i = 0; i < data.length; i++) {
this.userWatchLists.push(data[i]);
for (const dat of data) {
this.userWatchLists.push(dat);
}
}
}
......@@ -262,8 +261,8 @@ export class BookmarkComponent implements OnInit, OnDestroy {
getCommonActions(): PluginActionInfo[] {
const result = lodash
.chain(this.results)
.map(function (ex: Exercise): PluginActionInfo[] {
return ex.originalResult.supportedActions;
.map(function (ex: SearchResultDTO): PluginActionInfo[] {
return ex.supportedActions;
})
.flatten()
.uniqWith(equalPluginActionInfo);
......@@ -273,29 +272,22 @@ export class BookmarkComponent implements OnInit, OnDestroy {
}
startAction(action: PluginActionInfo): void {
const selectedExercises = lodash
.chain(this.results)
.map(function (ex: Exercise): SearchResultDTO {
return ex.originalResult;
const selectedExercises = lodash.chain(this.results).filter((sr: SearchResultDTO) =>
sr.supportedActions.some(function (a: PluginActionInfo): boolean {
return equalPluginActionInfo(a, action);
})
.filter((sr: SearchResultDTO) =>
sr.supportedActions.some(function (a: PluginActionInfo): boolean {
return equalPluginActionInfo(a, action);
})
);
);
const basketInfo: ShoppingBasketInfo = {
plugin: action.plugin,
action: action.action,
itemInfos: selectedExercises.value(),
};
this.pluginService.getRedirectLink(basketInfo).subscribe(
(redirectInfo: ShoppingBasketRedirectInfoDTO) => {
// alert('redirecting to ' + redirectInfo.redirectURL);
// location.href = redirectInfo.redirectURL;
this.pluginService.getRedirectLink(basketInfo).subscribe({
next: (redirectInfo: ShoppingBasketRedirectInfoDTO) => {
window.open(redirectInfo.redirectURL, action.action);
},
() => alert('Search failed')
);
error: () => alert('Search failed'),
});
}
}
......@@ -2,7 +2,7 @@ import { Injectable } from '@angular/core';
import { HttpResponse } from '@angular/common/http';
import { Resolve, ActivatedRouteSnapshot, Routes, Router } from '@angular/router';
import { Observable, of, EMPTY } from 'rxjs';
import { flatMap } from 'rxjs/operators';
import { mergeMap } from 'rxjs/operators';
import { Authority } from 'app/config/authority.constants';
import { UserRouteAccessService } from 'app/core/auth/user-route-access.service';
......@@ -20,7 +20,7 @@ export class BookmarksResolve implements Resolve<IUserWatchList> {
const id = route.params['id'];
if (id) {
return this.service.find(id).pipe(
flatMap((userWatchList: HttpResponse<UserWatchList>) => {
mergeMap((userWatchList: HttpResponse<UserWatchList>) => {
if (userWatchList.body) {
return of(userWatchList.body);
} else {
......