Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Programming exercises: Add button to start online IDE from exercise details #8697

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

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";
iyannsch marked this conversation as resolved.
Show resolved Hide resolved
iyannsch marked this conversation as resolved.
Show resolved Hide resolved

/**
* 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 {
iyannsch marked this conversation as resolved.
Show resolved Hide resolved

@Value("${theia.portal-url}")
private URL theiaPortalURL;
iyannsch marked this conversation as resolved.
Show resolved Hide resolved

@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';
iyannsch marked this conversation as resolved.
Show resolved Hide resolved
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>
iyannsch marked this conversation as resolved.
Show resolved Hide resolved
}
iyannsch marked this conversation as resolved.
Show resolved Hide resolved
@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;
iyannsch marked this conversation as resolved.
Show resolved Hide resolved
theiaPortalURL: string;

iyannsch marked this conversation as resolved.
Show resolved Hide resolved
// 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');
}
iyannsch marked this conversation as resolved.
Show resolved Hide resolved

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;
iyannsch marked this conversation as resolved.
Show resolved Hide resolved
}

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 ?? '';
iyannsch marked this conversation as resolved.
Show resolved Hide resolved

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) {
}
iyannsch marked this conversation as resolved.
Show resolved Hide resolved

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';
b-fein marked this conversation as resolved.
Show resolved Hide resolved

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);
});
b-fein marked this conversation as resolved.
Show resolved Hide resolved
});
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
Loading