Skip to content

Commit

Permalink
Programming exercises: Fix "Go to Build Plan" URL in the exercise ove…
Browse files Browse the repository at this point in the history
…rview (ls1intum#5433)
  • Loading branch information
Strohgelaender authored Nov 5, 2022
1 parent afc9b19 commit e504309
Show file tree
Hide file tree
Showing 7 changed files with 61 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,27 @@ import { ProgrammingExercise } from 'app/entities/programming-exercise.model';
import { ModelingExercise } from 'app/entities/modeling-exercise.model';
import { TextExercise } from 'app/entities/text-exercise.model';
import { FileUploadExercise } from 'app/entities/file-upload-exercise.model';
import { Exercise } from 'app/entities/exercise.model';
import { Exercise, ExerciseType } from 'app/entities/exercise.model';
import { StudentParticipation } from 'app/entities/participation/student-participation.model';
import { Observable, map } from 'rxjs';
import { HttpClient, HttpResponse } from '@angular/common/http';
import { Injectable } from '@angular/core';
import dayjs from 'dayjs/esm';
import { convertDateFromServer } from 'app/utils/date.utils';
import { ProgrammingExerciseStudentParticipation } from 'app/entities/participation/programming-exercise-student-participation.model';
import { setBuildPlanUrlForProgrammingParticipations } from 'app/exercises/shared/participation/participation.utils';
import { ProfileService } from 'app/shared/layouts/profiles/profile.service';

@Injectable({ providedIn: 'root' })
export class CourseExerciseService {
private resourceUrl = SERVER_API_URL + `api/courses`;

constructor(private http: HttpClient, private participationWebsocketService: ParticipationWebsocketService, private accountService: AccountService) {}
constructor(
private http: HttpClient,
private participationWebsocketService: ParticipationWebsocketService,
private accountService: AccountService,
private profileService: ProfileService,
) {}

/**
* returns all programming exercises for the course corresponding to courseId
Expand Down Expand Up @@ -136,6 +144,12 @@ export class CourseExerciseService {
exercise.dueDate = exercise.dueDate ? dayjs(exercise.dueDate) : undefined;
exercise.releaseDate = exercise.releaseDate ? dayjs(exercise.releaseDate) : undefined;
exercise.studentParticipations = [participation];
if (participation.exercise.type === ExerciseType.PROGRAMMING && (participation.exercise as ProgrammingExercise).publishBuildPlanUrl) {
this.profileService.getProfileInfo().subscribe((profileInfo) => {
const programmingParticipations = participation.exercise!.studentParticipations as ProgrammingExerciseStudentParticipation[];
setBuildPlanUrlForProgrammingParticipations(profileInfo, programmingParticipations, (participation.exercise as ProgrammingExercise).projectKey);
});
}
}
this.participationWebsocketService.addParticipation(participation);
}
Expand Down
22 changes: 20 additions & 2 deletions src/main/webapp/app/exercises/shared/exercise/exercise.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Injectable } from '@angular/core';
import { HttpClient, HttpParams, HttpResponse } from '@angular/common/http';
import { Observable } from 'rxjs';
import dayjs from 'dayjs/esm';
import { Exercise, IncludedInOverallScore, ParticipationStatus } from 'app/entities/exercise.model';
import { Exercise, ExerciseType, IncludedInOverallScore, ParticipationStatus } from 'app/entities/exercise.model';
import { QuizExercise, QuizMode } from 'app/entities/quiz/quiz-exercise.model';
import { ParticipationService } from '../participation/participation.service';
import { map } from 'rxjs/operators';
Expand All @@ -15,6 +15,10 @@ import { User } from 'app/core/user/user.model';
import { getExerciseDueDate } from 'app/exercises/shared/exercise/exercise.utils';
import { convertDateFromClient, convertDateFromServer } from 'app/utils/date.utils';
import { EntityTitleService, EntityType } from 'app/shared/layouts/navbar/entity-title.service';
import { ProgrammingExerciseStudentParticipation } from 'app/entities/participation/programming-exercise-student-participation.model';
import { setBuildPlanUrlForProgrammingParticipations } from 'app/exercises/shared/participation/participation.utils';
import { ProgrammingExercise } from 'app/entities/programming-exercise.model';
import { ProfileService } from 'app/shared/layouts/profiles/profile.service';

export type EntityResponseType = HttpResponse<Exercise>;
export type EntityArrayResponseType = HttpResponse<Exercise[]>;
Expand All @@ -39,6 +43,7 @@ export class ExerciseService {
private accountService: AccountService,
private translateService: TranslateService,
private entityTitleService: EntityTitleService,
private profileService: ProfileService,
) {}

/**
Expand Down Expand Up @@ -426,6 +431,7 @@ export class ExerciseService {
ExerciseService.convertExerciseCategoriesFromServer(exerciseRes);
this.setAccessRightsExerciseEntityResponseType(exerciseRes);
this.sendExerciseTitleToTitleService(exerciseRes?.body);
this.setBuildPlanUrlToParticipations(exerciseRes?.body);
return exerciseRes;
}

Expand All @@ -437,7 +443,10 @@ export class ExerciseService {
ExerciseService.convertExerciseArrayDatesFromServer(exerciseResArray);
ExerciseService.convertExerciseCategoryArrayFromServer(exerciseResArray);
this.setAccessRightsExerciseEntityArrayResponseType(exerciseResArray);
exerciseResArray?.body?.forEach(this.sendExerciseTitleToTitleService.bind(this));
exerciseResArray?.body?.forEach((exercise) => {
this.sendExerciseTitleToTitleService(exercise);
this.setBuildPlanUrlToParticipations(exercise);
});
return exerciseResArray;
}

Expand All @@ -464,6 +473,15 @@ export class ExerciseService {
}
}

private setBuildPlanUrlToParticipations(exercise: Exercise | undefined | null) {
if (exercise?.type === ExerciseType.PROGRAMMING && (exercise as ProgrammingExercise).publishBuildPlanUrl) {
this.profileService.getProfileInfo().subscribe((profileInfo) => {
const programmingParticipations = exercise?.studentParticipations as ProgrammingExerciseStudentParticipation[];
setBuildPlanUrlForProgrammingParticipations(profileInfo, programmingParticipations, (exercise as ProgrammingExercise).projectKey);
});
}
}

public getLatestDueDate(exerciseId: number): Observable<dayjs.Dayjs | undefined> {
return this.http
.get<dayjs.Dayjs>(`${this.resourceUrl}/${exerciseId}/latest-due-date`, { observe: 'response' })
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,7 @@ export class ParticipationService {
if (participations?.length) {
combinedParticipation.repositoryUrl = participations[0].repositoryUrl;
combinedParticipation.buildPlanId = participations[0].buildPlanId;
combinedParticipation.buildPlanUrl = participations[0].buildPlanUrl;
this.mergeResultsAndSubmissions(combinedParticipation, participations);
}
return combinedParticipation;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ import { ExerciseCategory } from 'app/entities/exercise-category.model';
import { getFirstResultWithComplaintFromResults } from 'app/entities/submission.model';
import { ComplaintService } from 'app/complaints/complaint.service';
import { Complaint } from 'app/entities/complaint.model';
import { setBuildPlanUrlForProgrammingParticipations } from 'app/exercises/shared/participation/participation.utils';
import { SubmissionPolicyService } from 'app/exercises/programming/manage/services/submission-policy.service';
import { SubmissionPolicy } from 'app/entities/submission-policy.model';
import { ModelingExercise } from 'app/entities/modeling-exercise.model';
Expand Down Expand Up @@ -216,11 +215,6 @@ export class CourseExerciseDetailsComponent implements OnInit, OnDestroy {
(!programmingExercise.buildAndTestStudentSubmissionsAfterDueDate || now.isAfter(programmingExercise.buildAndTestStudentSubmissionsAfterDueDate)));

this.allowComplaintsForAutomaticAssessments = !!programmingExercise.allowComplaintsForAutomaticAssessments && isAfterDateForComplaint;
if (this.exercise?.studentParticipations && programmingExercise.projectKey) {
this.profileService.getProfileInfo().subscribe((profileInfo) => {
setBuildPlanUrlForProgrammingParticipations(profileInfo, this.exercise?.studentParticipations!, (this.exercise as ProgrammingExercise).projectKey);
});
}
this.hasSubmissionPolicy = false;
this.programmingExerciseSubmissionPolicyService.getSubmissionPolicyOfProgrammingExercise(this.exerciseId).subscribe((submissionPolicy) => {
this.submissionPolicy = submissionPolicy;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { ComponentFixture, TestBed, fakeAsync, inject, tick } from '@angular/core/testing';
import { HttpResponse } from '@angular/common/http';
import { ActivatedRoute, Router, RouterState } from '@angular/router';
import { ProfileInfo } from 'app/shared/layouts/profiles/profile-info.model';
import { ProfileService } from 'app/shared/layouts/profiles/profile.service';
import { Subject, of } from 'rxjs';
import { FormBuilder, NgForm, NgModel } from '@angular/forms';
import { ArtemisTestModule } from '../../test.module';
Expand Down Expand Up @@ -85,13 +87,17 @@ describe('User Management Update Component', () => {
jest.spyOn(service, 'authorities').mockReturnValue(of(['USER']));
const getAllSpy = jest.spyOn(languageHelper, 'getAll').mockReturnValue([]);

const profileInfoStub = jest.spyOn(TestBed.inject(ProfileService), 'getProfileInfo');
profileInfoStub.mockReturnValue(of({ activeProfiles: ['bamboo'] } as ProfileInfo));

// WHEN
comp.ngOnInit();

// THEN
expect(service.authorities).toHaveBeenCalledOnce();
expect(comp.authorities).toEqual(['USER']);
expect(getAllSpy).toHaveBeenCalledOnce();
expect(profileInfoStub).toHaveBeenCalledOnce();
}),
));

Expand All @@ -100,13 +106,16 @@ describe('User Management Update Component', () => {
fakeAsync((languageHelper: JhiLanguageHelper) => {
// GIVEN
const getAllSpy = jest.spyOn(languageHelper, 'getAll');
const profileInfoStub = jest.spyOn(TestBed.inject(ProfileService), 'getProfileInfo');
profileInfoStub.mockReturnValue(of({ activeProfiles: ['bamboo'] } as ProfileInfo));

// WHEN
comp.ngOnInit();

// THEN
expect(getAllSpy).toHaveBeenCalledOnce();
expect(comp.languages).toEqual(LANGUAGES);
expect(profileInfoStub).toHaveBeenCalledOnce();
}),
));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ import { MockRouter } from '../helpers/mocks/mock-router';
import { MockSyncStorage } from '../helpers/mocks/service/mock-sync-storage.service';
import { MockTranslateService } from '../helpers/mocks/service/mock-translate.service';
import { CourseExerciseService } from 'app/exercises/shared/course-exercises/course-exercise.service';
import { ProfileService } from 'app/shared/layouts/profiles/profile.service';
import { of } from 'rxjs';
import { ProfileInfo } from 'app/shared/layouts/profiles/profile-info.model';

describe('Course Management Service', () => {
let service: CourseExerciseService;
Expand Down Expand Up @@ -177,6 +180,8 @@ describe('Course Management Service', () => {
},
participation,
);
jest.spyOn(TestBed.inject(ProfileService), 'getProfileInfo').mockReturnValue(of({ buildPlanURLTemplate: 'testci.fake' } as ProfileInfo));

service
.startExercise(exerciseId)
.pipe(take(1))
Expand All @@ -201,6 +206,8 @@ describe('Course Management Service', () => {
},
participation,
);
jest.spyOn(TestBed.inject(ProfileService), 'getProfileInfo').mockReturnValue(of({ buildPlanURLTemplate: 'testci.fake' } as ProfileInfo));

service
.startPractice(exerciseId, useGradedParticipation)
.pipe(take(1))
Expand Down Expand Up @@ -230,6 +237,8 @@ describe('Course Management Service', () => {
},
participation,
);
jest.spyOn(TestBed.inject(ProfileService), 'getProfileInfo').mockReturnValue(of({ buildPlanURLTemplate: 'testci.fake' } as ProfileInfo));

service
.resumeProgrammingExercise(exerciseId, participationId)
.pipe(take(1))
Expand Down
6 changes: 6 additions & 0 deletions src/test/javascript/spec/test.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ import { ParseLinks } from 'app/core/util/parse-links.service';
import { MockTranslateService } from './helpers/mocks/service/mock-translate.service';
import { ThemeService } from 'app/core/theme/theme.service';
import { MockThemeService } from './helpers/mocks/service/mock-theme.service';
import { ProfileService } from 'app/shared/layouts/profiles/profile.service';
import { MockProfileService } from './helpers/mocks/service/mock-profile.service';

@NgModule({
imports: [HttpClientTestingModule, FontAwesomeModule],
Expand Down Expand Up @@ -59,6 +61,10 @@ import { MockThemeService } from './helpers/mocks/service/mock-theme.service';
provide: ThemeService,
useClass: MockThemeService,
},
{
provide: ProfileService,
useClass: MockProfileService,
},
],
declarations: [MockComponent(FaIconComponent)],
exports: [MockComponent(FaIconComponent)],
Expand Down

0 comments on commit e504309

Please sign in to comment.