Skip to content

Commit

Permalink
Programming exercises: Add button to start online IDE from exercise d…
Browse files Browse the repository at this point in the history
…etails (ls1intum#8697)
  • Loading branch information
iyannsch authored Jun 27, 2024
1 parent 5f79dd3 commit 374d28c
Show file tree
Hide file tree
Showing 16 changed files with 165 additions and 2 deletions.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 11 additions & 0 deletions src/main/java/de/tum/in/www1/artemis/config/Constants.java
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,17 @@ public final class Constants {

public static final String PROFILE_SCHEDULING = "scheduling";

/**
* The name of the Spring profile used for Theia as an external online IDE.
*/
public static final String PROFILE_THEIA = "theia";

/**
* The InfoContributor's detail key for the Theia Portal URL
*/

public static final String THEIA_PORTAL_URL = "theiaPortalURL";

/**
* Size of an unsigned tinyInt in SQL, that is used in the database
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package de.tum.in.www1.artemis.service.theia;

import static de.tum.in.www1.artemis.config.Constants.PROFILE_THEIA;

import java.net.URL;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.actuate.info.Info;
import org.springframework.boot.actuate.info.InfoContributor;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Component;

import de.tum.in.www1.artemis.config.Constants;

@Profile(PROFILE_THEIA)
@Component
public class TheiaInfoContributor implements InfoContributor {

@Value("${theia.portal-url}")
private URL theiaPortalURL;

@Override
public void contribute(Info.Builder builder) {
builder.withDetail(Constants.THEIA_PORTAL_URL, theiaPortalURL);
}
}
4 changes: 4 additions & 0 deletions src/main/resources/config/application-dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -126,3 +126,7 @@ eureka:
enabled: false # By default, the JHipster Registry is not used in the "dev" profile
service-url:
defaultZone: http://admin:${jhipster.registry.password}@localhost:8761/eureka/

# Theia configuration
theia:
portal-url: https://theia-test.k8s.ase.cit.tum.de
2 changes: 2 additions & 0 deletions src/main/resources/config/application-theia.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
theia:
portal-url: https://your-theia-instance.com
2 changes: 2 additions & 0 deletions src/main/webapp/app/app.constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,5 @@ export const PROFILE_IRIS = 'iris';
export const PROFILE_LTI = 'lti';

export const PROFILE_ATHENA = 'athena';

export const PROFILE_THEIA = 'theia';
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,12 @@
<span class="d-none d-md-inline" jhiTranslate="artemisApp.exerciseActions.openRepository"></span>
</a>
}
@if (theiaEnabled) {
<a class="btn btn-primary" [class.btn-sm]="smallButtons" (click)="startOnlineIDE()" target="_blank" rel="noopener noreferrer">
<fa-icon [icon]="faDesktop" [fixedWidth]="true" />
<span class="d-none d-md-inline" jhiTranslate="artemisApp.exerciseActions.openOnlineIDE"></span>
</a>
}
@if (exercise.allowFeedbackRequests) {
@if (athenaEnabled) {
<a
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,14 @@ import { ProgrammingExercise } from 'app/entities/programming-exercise.model';
import { StudentParticipation } from 'app/entities/participation/student-participation.model';
import { ArtemisQuizService } from 'app/shared/quiz/quiz.service';
import { finalize } from 'rxjs/operators';
import { faCodeBranch, faEye, faFolderOpen, faPenSquare, faPlayCircle, faRedo, faUsers } from '@fortawesome/free-solid-svg-icons';
import { faCodeBranch, faDesktop, faEye, faFolderOpen, faPenSquare, faPlayCircle, faRedo, faUsers } from '@fortawesome/free-solid-svg-icons';
import { CourseExerciseService } from 'app/exercises/shared/course-exercises/course-exercise.service';
import { TranslateService } from '@ngx-translate/core';
import { ParticipationService } from 'app/exercises/shared/participation/participation.service';
import dayjs from 'dayjs/esm';
import { QuizExercise } from 'app/entities/quiz/quiz-exercise.model';
import { ProfileService } from 'app/shared/layouts/profiles/profile.service';
import { PROFILE_ATHENA, PROFILE_LOCALVC } from 'app/app.constants';
import { PROFILE_ATHENA, PROFILE_LOCALVC, PROFILE_THEIA } from 'app/app.constants';
import { AssessmentType } from 'app/entities/assessment-type.model';

@Component({
Expand Down Expand Up @@ -57,13 +57,17 @@ export class ExerciseDetailsStudentActionsComponent implements OnInit, OnChanges
routerLink: string;
repositoryLink: string;

theiaEnabled: boolean = false;
theiaPortalURL: string;

// Icons
faFolderOpen = faFolderOpen;
faUsers = faUsers;
faEye = faEye;
faPlayCircle = faPlayCircle;
faRedo = faRedo;
faCodeBranch = faCodeBranch;
faDesktop = faDesktop;
faPenSquare = faPenSquare;

private feedbackSent = false;
Expand Down Expand Up @@ -103,6 +107,19 @@ export class ExerciseDetailsStudentActionsComponent implements OnInit, OnChanges
this.profileService.getProfileInfo().subscribe((profileInfo) => {
this.localVCEnabled = profileInfo.activeProfiles?.includes(PROFILE_LOCALVC);
this.athenaEnabled = profileInfo.activeProfiles?.includes(PROFILE_ATHENA);

// The online IDE is only available with correct SpringProfile and if it's enabled for this exercise
if (profileInfo.activeProfiles?.includes(PROFILE_THEIA)) {
this.theiaEnabled = true;

// Set variables now, sanitize later on
this.theiaPortalURL = profileInfo.theiaPortalURL ?? '';

// Verify that Theia's portal URL is set
if (this.theiaPortalURL === '') {
this.theiaEnabled = false;
}
}
});
} else if (this.exercise.type === ExerciseType.MODELING) {
this.editorLabel = 'openModelingEditor';
Expand All @@ -123,6 +140,10 @@ export class ExerciseDetailsStudentActionsComponent implements OnInit, OnChanges
this.isTeamAvailable = !!(this.exercise.teamMode && this.exercise.studentAssignedTeamIdComputed && this.exercise.studentAssignedTeamId);
}

startOnlineIDE() {
window.open(this.theiaPortalURL, '_blank');
}

receiveNewParticipation(newParticipation: StudentParticipation) {
const studentParticipations = this.exercise.studentParticipations ?? [];
if (studentParticipations.map((participation) => participation.id).includes(newParticipation.id)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ export class ProfileInfo {
};
};
};
public theiaPortalURL: string;
}

export const hasEditableBuildPlan = (profileInfo: ProfileInfo): boolean => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,8 @@ export class ProfileService {

profileInfo.git = data.git;

profileInfo.theiaPortalURL = data.theiaPortalURL ?? '';

return profileInfo;
}),
)
Expand Down
1 change: 1 addition & 0 deletions src/main/webapp/i18n/de/exercise-actions.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
"openModelingEditor": "Modellierungseditor öffnen",
"importIntoIDE": "In deiner IDE öffnen",
"openRepository": "Repository öffnen",
"openOnlineIDE": "Online IDE öffnen",
"practiceMode": {
"title": "Übungsmodus",
"explanation": "Der Übungsmodus ermöglicht es dir nach der Einreichungsfrist zu Übungszwecken weiter an der Aufgabe zu arbeiten. Dies wird deine Bewertung nicht beeinflussen! Artemis wird dafür ein separates Repository aufsetzen, das du erneut clonen musst.",
Expand Down
1 change: 1 addition & 0 deletions src/main/webapp/i18n/en/exercise-actions.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"openModelingEditor": "Open modeling editor",
"importIntoIDE": "Open in your IDE",
"openRepository": "Open repository",
"openOnlineIDE": "Open online IDE",
"practiceMode": {
"title": "Practice Mode",
"explanation": "The practice mode allows you to keep working on the exercise after the submission due date for self-practice. This will not affect your grading! Artemis will setup a separate repository for you to clone as usual.",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package de.tum.in.www1.artemis.theia;

import static de.tum.in.www1.artemis.config.Constants.PROFILE_THEIA;
import static org.assertj.core.api.Assertions.assertThat;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.actuate.info.Info;
import org.springframework.context.annotation.Profile;

import de.tum.in.www1.artemis.config.Constants;
import de.tum.in.www1.artemis.service.theia.TheiaInfoContributor;

@Profile(PROFILE_THEIA)
class TheiaInfoContributorTest {

@Value("${theia.portal-url}")
private String expectedValue;

TheiaInfoContributor theiaInfoContributor;

@Test
void testContribute() {
Info.Builder builder = new Info.Builder();
theiaInfoContributor = new TheiaInfoContributor();
try {
theiaInfoContributor.contribute(builder);
}
catch (NullPointerException e) {
}

Info info = builder.build();
assertThat(info.getDetails().get(Constants.THEIA_PORTAL_URL)).isEqualTo(expectedValue);

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import { MockCourseExerciseService } from '../../../helpers/mocks/service/mock-c
import { MockSyncStorage } from '../../../helpers/mocks/service/mock-sync-storage.service';
import { ArtemisTestModule } from '../../../test.module';
import { AssessmentType } from 'app/entities/assessment-type.model';
import { PROFILE_THEIA } from 'app/app.constants';

describe('ExerciseDetailsStudentActionsComponent', () => {
let comp: ExerciseDetailsStudentActionsComponent;
Expand Down Expand Up @@ -603,4 +604,37 @@ describe('ExerciseDetailsStudentActionsComponent', () => {
expect(window.alert).toHaveBeenCalledWith('artemisApp.exercise.maxAthenaResultsReached');
expect(result).toBeFalse();
});

it.each([
[
'start theia button should be visible when profile is active and url is set',
{
activeProfiles: [PROFILE_THEIA],
theiaPortalURL: 'https://theia.test',
},
true,
],
[
'start theia button should not be visible when profile is active but url is not set',
{
activeProfiles: [PROFILE_THEIA],
},
false,
],
[
'start theia button should not be visible when profile is not active but url is set',
{
theiaPortalURL: 'https://theia.test',
},
false,
],
])('%s', (description, profileInfo, expectedVisibility) => {
getProfileInfoSub = jest.spyOn(profileService, 'getProfileInfo');
getProfileInfoSub.mockReturnValue(of(profileInfo as ProfileInfo));
comp.exercise = exercise;

fixture.detectChanges();

expect(comp.theiaEnabled).toBe(expectedVisibility);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ describe('CloneRepoButtonComponent', () => {
},
},
},
theiaPortalURL: 'https://theia-test.k8s.ase.cit.tum.de',
};

let participation: ProgrammingExerciseStudentParticipation = {};
Expand Down
2 changes: 2 additions & 0 deletions src/test/javascript/spec/service/profile.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ describe('Profile Service', () => {
},
},
},
theiaPortalURL: 'http://theia-test.k8s.ase.cit.tum.de',
};

const expectedProfileInfo: ProfileInfo = {
Expand Down Expand Up @@ -259,6 +260,7 @@ describe('Profile Service', () => {
},
},
},
theiaPortalURL: 'http://theia-test.k8s.ase.cit.tum.de',
};

beforeEach(() => {
Expand Down

0 comments on commit 374d28c

Please sign in to comment.