Skip to content

Commit

Permalink
Programming exercises: Display checkout directories in build plan pre…
Browse files Browse the repository at this point in the history
…view (#8597)
  • Loading branch information
florian-glombik authored Jun 1, 2024
1 parent 46139d9 commit 1348a86
Show file tree
Hide file tree
Showing 40 changed files with 841 additions and 199 deletions.
2 changes: 1 addition & 1 deletion src/main/java/de/tum/in/www1/artemis/security/Role.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
*/
public enum Role {

// NOTE: we will soon rename "USER" to "STUDENT" in the database and add a new role "EDITOR"
// NOTE: we will soon rename "USER" to "STUDENT" in the database
ADMIN("ADMIN"), INSTRUCTOR("INSTRUCTOR"), EDITOR("EDITOR"), TEACHING_ASSISTANT("TA"), STUDENT("USER"), ANONYMOUS("ANONYMOUS");

public static final String ROLE_PREFIX = "ROLE_";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import de.tum.in.www1.artemis.domain.participation.ProgrammingExerciseParticipation;
import de.tum.in.www1.artemis.exception.ContinuousIntegrationException;
import de.tum.in.www1.artemis.service.connectors.ConnectorHealth;
import de.tum.in.www1.artemis.web.rest.dto.CheckoutDirectoriesDTO;

/**
* Abstract service for managing entities related to continuous integration.
Expand All @@ -41,7 +42,7 @@ enum BuildStatus {
* @param planKey the key of the plan
* @param repositoryUri the URI of the assignment repository (used to separate between exercise and solution)
* @param testRepositoryUri the URI of the test repository
* @param solutionRepositoryUri the URI of the solution repository. Only used for HASKELL exercises with checkoutSolutionRepository=true. Otherwise ignored.
* @param solutionRepositoryUri the URI of the solution repository. Only used for HASKELL exercises with checkoutSolutionRepository=true. Otherwise, ignored.
*/
void createBuildPlanForExercise(ProgrammingExercise exercise, String planKey, VcsRepositoryUri repositoryUri, VcsRepositoryUri testRepositoryUri,
VcsRepositoryUri solutionRepositoryUri);
Expand Down Expand Up @@ -124,7 +125,7 @@ String copyBuildPlan(ProgrammingExercise sourceExercise, String sourcePlanName,
* Get the build artifact (JAR/WAR), if any, of the latest build
*
* @param participation participation for which to get the build artifact
* @return the binary build artifact. Typically a JAR/WAR ResponseEntity.
* @return the binary build artifact. Typically, a JAR/WAR ResponseEntity.
*/
ResponseEntity<byte[]> retrieveLatestArtifact(ProgrammingExerciseParticipation participation);

Expand Down Expand Up @@ -238,7 +239,7 @@ public String forProgrammingLanguage(ProgrammingLanguage language) {
public String forProgrammingLanguage(ProgrammingLanguage language) {
return switch (language) {
case HASKELL, OCAML -> "solution";
default -> throw new IllegalArgumentException("Repository checkout path for solution repo has not yet been defined for " + language);
default -> throw new IllegalArgumentException("The solution repository is not checked out during the template/submission build plan for " + language);
};
}
}
Expand All @@ -255,4 +256,14 @@ interface CustomizableCheckoutPath {
*/
String forProgrammingLanguage(ProgrammingLanguage language);
}

/**
* Get the checkout directories for the template and submission build plan for a given programming language.
*
* @param programmingLanguage for which the checkout directories should be retrieved
* @param checkoutSolution whether the checkout solution repository shall be checked out during the template and submission build plan
* @return the paths of the checkout directories for the default repositories (exercise, solution, tests) for the
* template and submission build plan
*/
CheckoutDirectoriesDTO getCheckoutDirectories(ProgrammingLanguage programmingLanguage, boolean checkoutSolution);
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import de.tum.in.www1.artemis.domain.ProgrammingExercise;
import de.tum.in.www1.artemis.domain.ProgrammingSubmission;
import de.tum.in.www1.artemis.domain.VcsRepositoryUri;
import de.tum.in.www1.artemis.domain.enumeration.ProgrammingLanguage;
import de.tum.in.www1.artemis.domain.participation.ProgrammingExerciseParticipation;
import de.tum.in.www1.artemis.exception.ContinuousIntegrationException;
import de.tum.in.www1.artemis.exception.GitLabCIException;
Expand All @@ -37,6 +38,7 @@
import de.tum.in.www1.artemis.service.connectors.ci.AbstractContinuousIntegrationService;
import de.tum.in.www1.artemis.service.connectors.ci.CIPermission;
import de.tum.in.www1.artemis.service.connectors.ci.notification.dto.TestResultsDTO;
import de.tum.in.www1.artemis.web.rest.dto.CheckoutDirectoriesDTO;

@Profile("gitlabci")
@Service
Expand Down Expand Up @@ -322,4 +324,9 @@ public Optional<String> getWebHookUrl(String projectKey, String buildPlanId) {
log.error("Unsupported action: GitLabCIService.getWebHookUrl()");
return Optional.empty();
}

@Override
public CheckoutDirectoriesDTO getCheckoutDirectories(ProgrammingLanguage programmingLanguage, boolean checkoutSolution) {
throw new UnsupportedOperationException("Method not implemented, consult the build plans in GitLab for more information on the checkout directories.");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import de.tum.in.www1.artemis.domain.ProgrammingExercise;
import de.tum.in.www1.artemis.domain.VcsRepositoryUri;
import de.tum.in.www1.artemis.domain.enumeration.BuildPlanType;
import de.tum.in.www1.artemis.domain.enumeration.ProgrammingLanguage;
import de.tum.in.www1.artemis.domain.enumeration.RepositoryType;
import de.tum.in.www1.artemis.domain.participation.ProgrammingExerciseParticipation;
import de.tum.in.www1.artemis.exception.ContinuousIntegrationException;
Expand All @@ -36,6 +37,7 @@
import de.tum.in.www1.artemis.service.connectors.ci.notification.dto.TestResultsDTO;
import de.tum.in.www1.artemis.service.connectors.jenkins.build_plan.JenkinsBuildPlanService;
import de.tum.in.www1.artemis.service.connectors.jenkins.jobs.JenkinsJobService;
import de.tum.in.www1.artemis.web.rest.dto.CheckoutDirectoriesDTO;

@Profile("jenkins")
@Service
Expand Down Expand Up @@ -252,4 +254,9 @@ public void createProjectForExercise(ProgrammingExercise programmingExercise) th
throw new JenkinsException("Error creating folder for exercise " + programmingExercise, e);
}
}

@Override
public CheckoutDirectoriesDTO getCheckoutDirectories(ProgrammingLanguage programmingLanguage, boolean checkoutSolution) {
throw new UnsupportedOperationException("Method not implemented, consult the build plans in Jenkins for more information on the checkout directories.");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

import de.tum.in.www1.artemis.domain.ProgrammingExercise;
import de.tum.in.www1.artemis.domain.VcsRepositoryUri;
import de.tum.in.www1.artemis.domain.enumeration.ProgrammingLanguage;
import de.tum.in.www1.artemis.domain.participation.ProgrammingExerciseParticipation;
import de.tum.in.www1.artemis.exception.LocalCIException;
import de.tum.in.www1.artemis.repository.ProgrammingExerciseRepository;
Expand All @@ -27,6 +28,9 @@
import de.tum.in.www1.artemis.service.connectors.aeolus.Windfile;
import de.tum.in.www1.artemis.service.connectors.ci.AbstractContinuousIntegrationService;
import de.tum.in.www1.artemis.service.connectors.ci.CIPermission;
import de.tum.in.www1.artemis.service.connectors.ci.ContinuousIntegrationService;
import de.tum.in.www1.artemis.web.rest.dto.BuildPlanCheckoutDirectoriesDTO;
import de.tum.in.www1.artemis.web.rest.dto.CheckoutDirectoriesDTO;

/**
* Implementation of ContinuousIntegrationService for local CI. Contains methods for communication with the local CI system.
Expand Down Expand Up @@ -219,4 +223,50 @@ public boolean checkIfBuildPlanExists(String projectKey, String buildPlanId) {
private String getCleanPlanName(String name) {
return name.toUpperCase().replaceAll("[^A-Z0-9]", "");
}

@Override
public CheckoutDirectoriesDTO getCheckoutDirectories(ProgrammingLanguage programmingLanguage, boolean checkoutSolution) {
BuildPlanCheckoutDirectoriesDTO submissionBuildPlanCheckoutDirectories = getSubmissionBuildPlanCheckoutDirectories(programmingLanguage, checkoutSolution);
BuildPlanCheckoutDirectoriesDTO solutionBuildPlanCheckoutDirectories = getSolutionBuildPlanCheckoutDirectories(submissionBuildPlanCheckoutDirectories);

return new CheckoutDirectoriesDTO(submissionBuildPlanCheckoutDirectories, solutionBuildPlanCheckoutDirectories);
}

private BuildPlanCheckoutDirectoriesDTO getSubmissionBuildPlanCheckoutDirectories(ProgrammingLanguage programmingLanguage, boolean checkoutSolution) {
String exerciseCheckoutDirectory = ContinuousIntegrationService.RepositoryCheckoutPath.ASSIGNMENT.forProgrammingLanguage(programmingLanguage);
String testCheckoutDirectory = ContinuousIntegrationService.RepositoryCheckoutPath.TEST.forProgrammingLanguage(programmingLanguage);

exerciseCheckoutDirectory = startPathWithRootDirectory(exerciseCheckoutDirectory);
testCheckoutDirectory = startPathWithRootDirectory(testCheckoutDirectory);

String solutionCheckoutDirectory = null;

if (checkoutSolution) {
try {
String solutionCheckoutDirectoryPath = ContinuousIntegrationService.RepositoryCheckoutPath.SOLUTION.forProgrammingLanguage(programmingLanguage);
solutionCheckoutDirectory = startPathWithRootDirectory(solutionCheckoutDirectoryPath);
}
catch (IllegalArgumentException exception) {
// not checked out during template & submission build
}
}

return new BuildPlanCheckoutDirectoriesDTO(exerciseCheckoutDirectory, solutionCheckoutDirectory, testCheckoutDirectory);
}

private String startPathWithRootDirectory(String checkoutDirectoryPath) {
final String ROOT_DIRECTORY = "/";
if (checkoutDirectoryPath == null || checkoutDirectoryPath.isEmpty()) {
return ROOT_DIRECTORY;
}

return checkoutDirectoryPath.startsWith(ROOT_DIRECTORY) ? checkoutDirectoryPath : ROOT_DIRECTORY + checkoutDirectoryPath;
}

private BuildPlanCheckoutDirectoriesDTO getSolutionBuildPlanCheckoutDirectories(BuildPlanCheckoutDirectoriesDTO submissionBuildPlanCheckoutDirectories) {
String solutionCheckoutDirectory = submissionBuildPlanCheckoutDirectories.exerciseCheckoutDirectory();
String testCheckoutDirectory = submissionBuildPlanCheckoutDirectories.testCheckoutDirectory();

return new BuildPlanCheckoutDirectoriesDTO(null, solutionCheckoutDirectory, testCheckoutDirectory);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package de.tum.in.www1.artemis.web.rest.dto;

import com.fasterxml.jackson.annotation.JsonInclude;

@JsonInclude(JsonInclude.Include.NON_EMPTY)
public record BuildPlanCheckoutDirectoriesDTO(String exerciseCheckoutDirectory, String solutionCheckoutDirectory, String testCheckoutDirectory) {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package de.tum.in.www1.artemis.web.rest.dto;

import com.fasterxml.jackson.annotation.JsonInclude;

@JsonInclude(JsonInclude.Include.NON_EMPTY)
public record CheckoutDirectoriesDTO(BuildPlanCheckoutDirectoriesDTO submissionBuildPlanCheckoutDirectories, BuildPlanCheckoutDirectoriesDTO solutionBuildPlanCheckoutDirectories) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@
import de.tum.in.www1.artemis.service.programming.ProgrammingExerciseService;
import de.tum.in.www1.artemis.service.programming.ProgrammingExerciseTestCaseService;
import de.tum.in.www1.artemis.web.rest.dto.BuildLogStatisticsDTO;
import de.tum.in.www1.artemis.web.rest.dto.CheckoutDirectoriesDTO;
import de.tum.in.www1.artemis.web.rest.dto.ProgrammingExerciseResetOptionsDTO;
import de.tum.in.www1.artemis.web.rest.dto.SearchResultPageDTO;
import de.tum.in.www1.artemis.web.rest.dto.pageablesearch.SearchTermPageableSearchDTO;
Expand Down Expand Up @@ -878,4 +879,23 @@ public ResponseEntity<BuildLogStatisticsDTO> getBuildLogStatistics(@PathVariable
return ResponseEntity.ok(buildLogStatistics);
}

/**
* GET programming-exercises/repository-checkout-directories
*
* @param programmingLanguage for which the checkout directories should be retrieved
* @param checkoutSolution whether the checkout solution repository shall be checked out during the template and submission build plan,
* if not supplied set to true as default
* @return a DTO containing the checkout directories for the exercise, solution, and tests repository
* for the requested programming language for the submission and solution build.
*/
@GetMapping("programming-exercises/repository-checkout-directories")
@EnforceAtLeastEditor
@FeatureToggle(Feature.ProgrammingExercises)
public ResponseEntity<CheckoutDirectoriesDTO> getRepositoryCheckoutDirectories(@RequestParam(value = "programmingLanguage") ProgrammingLanguage programmingLanguage,
@RequestParam(value = "checkoutSolution", defaultValue = "true") boolean checkoutSolution) {
log.debug("REST request to get checkout directories for programming language: {}", programmingLanguage);

CheckoutDirectoriesDTO repositoriesCheckoutDirectoryDTO = continuousIntegrationService.orElseThrow().getCheckoutDirectories(programmingLanguage, checkoutSolution);
return ResponseEntity.ok(repositoriesCheckoutDirectoryDTO);
}
}
6 changes: 3 additions & 3 deletions src/main/resources/templates/ocaml/test/run.sh
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ cp_code solution
echo 'include Assignment' > solution/solution.ml
cp_code assignment

# select if tests are run by generated source code as studen toplevel code may run before the tests and be able to spoof a runtime signal
# select if tests are run by generated source code as student toplevel code may run before the tests and be able to spoof a runtime signal
echo "let runHidden = $RUN_HIDDEN" > test/runHidden.ml

# shellcheck disable=SC2046
Expand All @@ -69,8 +69,8 @@ if ! timeout -s SIGTERM $BUILD_TIMEOUT checker/checker.exe; then
echo "Unable to build submission, please ensure that your code builds and matches the provided interface" >&2
exit 0
fi
# build the sudent submission
# don't reference the tests or solution, so that we can show the build output to the sudent and not leak test / solution code
# build the student submission
# don't reference the tests or solution, so that we can show the build output to the student and not leak test / solution code
if ! timeout -s SIGTERM $BUILD_TIMEOUT dune build --force assignment; then
echo "Unable to build submission, please ensure that your code builds and matches the provided interface" >&2
exit 0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,9 +102,7 @@ <h3 class="section-headline" [id]="headlinesRecord[section.headline]">{{ section
</a>
}
<div class="auxiliaryRepositoryDescription">
@if (auxiliaryRepository.checkoutDirectory) {
<span>Checkout Directory: {{ auxiliaryRepository.checkoutDirectory }} </span>
} @else {
@if (!auxiliaryRepository.checkoutDirectory) {
<fa-icon [icon]="faExclamationTriangle" class="text-warning me-1" [ngbTooltip]="noCheckoutDirectorySetTooltip" />
<span jhiTranslate="artemisApp.programmingExercise.noCheckoutDirectorySet"></span>
}
Expand Down Expand Up @@ -285,6 +283,16 @@ <h3 class="section-headline" [id]="headlinesRecord[section.headline]">{{ section
}
</dd>
}
@case (DetailType.ProgrammingCheckoutDirectories) {
<dd id="detail-value-{{ detail.title }}">
<jhi-programming-exercise-repository-and-build-plan-details
[programmingExercise]="detail.data.exercise"
[programmingLanguage]="detail.data.programmingLanguage"
[isLocal]="detail.data.isLocal"
[checkoutSolutionRepository]="detail.data.exercise.checkoutSolutionRepository"
/>
</dd>
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ export enum DetailType {
ProgrammingProblemStatement = 'detail-problem-statement',
ProgrammingTimeline = 'detail-timeline',
ProgrammingBuildStatistics = 'detail-build-statistics',
ProgrammingCheckoutDirectories = 'detail-checkout-directories',
}

@Component({
Expand Down
16 changes: 13 additions & 3 deletions src/main/webapp/app/detail-overview-list/detail.model.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { GradingCriterion } from 'app/exercises/shared/structured-grading-criterion/grading-criterion.model';
import { ProgrammingExercise } from 'app/entities/programming-exercise.model';
import type { ProgrammingExercise, ProgrammingLanguage } from 'app/entities/programming-exercise.model';
import { TemplateProgrammingExerciseParticipation } from 'app/entities/participation/template-programming-exercise-participation.model';
import { SolutionProgrammingExerciseParticipation } from 'app/entities/participation/solution-programming-exercise-participation.model';
import { ProgrammingExerciseInstructorRepositoryType } from 'app/exercises/programming/manage/services/programming-exercise.service';
import { AuxiliaryRepository } from 'app/entities/programming-exercise-auxiliary-repository-model';
import { ProgrammingExerciseParticipationType } from 'app/entities/programming-exercise-participation.model';
import { ProgrammingExerciseGitDiffReport } from 'app/entities/hestia/programming-exercise-git-diff-report.model';
import { BuildLogStatisticsDTO } from 'app/exercises/programming/manage/build-log-statistics-dto';
import { BuildLogStatisticsDTO } from 'app/entities/build-log-statistics-dto';
import { DetailType } from 'app/detail-overview-list/detail-overview-list.component';
import { SafeHtml } from '@angular/platform-browser';
import { UMLDiagramType, UMLModel } from '@ls1intum/apollon';
Expand All @@ -33,7 +33,8 @@ type ShownDetail =
| ProgrammingDiffReportDetail
| ProgrammingProblemStatementDetail
| ProgrammingTimelineDetail
| ProgrammingBuildStatisticsDetail;
| ProgrammingBuildStatisticsDetail
| ProgrammingCheckoutDirectoriesDetail;

interface DetailBase {
type: DetailType;
Expand Down Expand Up @@ -129,3 +130,12 @@ interface ProgrammingBuildStatisticsDetail extends DetailBase {
buildLogStatistics: BuildLogStatisticsDTO;
};
}

interface ProgrammingCheckoutDirectoriesDetail extends DetailBase {
type: DetailType.ProgrammingCheckoutDirectories;
data: {
exercise: ProgrammingExercise;
programmingLanguage?: ProgrammingLanguage;
isLocal: boolean;
};
}
4 changes: 4 additions & 0 deletions src/main/webapp/app/detail-overview-list/detail.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import { ArtemisProgrammingExerciseLifecycleModule } from 'app/exercises/program
import { AssessmentInstructionsModule } from 'app/assessment/assessment-instructions/assessment-instructions.module';
import { IrisModule } from 'app/iris/iris.module';
import { ArtemisModelingEditorModule } from 'app/exercises/modeling/shared/modeling-editor.module';
import { ProgrammingExerciseBuildPlanCheckoutDirectoriesComponent } from 'app/exercises/programming/shared/build-details/programming-exercise-build-plan-checkout-directories.component';
import { ProgrammingExerciseRepositoryAndBuildPlanDetailsComponent } from 'app/exercises/programming/shared/build-details/programming-exercise-repository-and-build-plan-details.component';

@NgModule({
imports: [
Expand All @@ -27,6 +29,8 @@ import { ArtemisModelingEditorModule } from 'app/exercises/modeling/shared/model
AssessmentInstructionsModule,
IrisModule,
ArtemisModelingEditorModule,
ProgrammingExerciseBuildPlanCheckoutDirectoriesComponent,
ProgrammingExerciseRepositoryAndBuildPlanDetailsComponent,
],
declarations: [DetailOverviewListComponent],
exports: [DetailOverviewListComponent],
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export class BuildPlanCheckoutDirectoriesDTO {
exerciseCheckoutDirectory?: string;
solutionCheckoutDirectory?: string;
testCheckoutDirectory: string;
}
Loading

0 comments on commit 1348a86

Please sign in to comment.