Artemis: Interactive Learning with Individual Feedback

Main features

Artemis supports the following exercises:

  1. Programming exercises with version control and automatic assessment with test cases and continuous integration

  2. Quiz exercises with multiple choice, drag and drop and short answer quiz questions

  3. Modeling exercises with semi-automatic assessment using machine learning concepts

  4. Textual exercises with manual (and experimental semi-automatic) assessment

  5. File upload exercises with manual assessment

All these exercises are supposed to be run either live in the lecture with instant feedback or as homework. Students can submit their solutions multiple times within the due date and use the (semi-)automatically provided feedback to improve their solution.

Exercises

Artemis supports the following exercises:

Programming Exercise


Overview

Conducting a programming exercise consists of 7 steps distributed among instructor, Artemis and students:

  1. Instructor prepares exercise: Set up a repository containing the exercise code and test cases, build instructions on the CI server, and configures the exercise in Artemis.

  2. Student starts exercise: Click on start exercise on Artemis which automatically generates a copy of the repository with the exercise code and configures a build plan accordingly.

  3. Optional: Student clones repository: Clone the personalized repository from the remote VCS to the local machine.

  4. Student solves exercise: Solve the exercise with an IDE of choice on the local computer or in the online editor.

  5. Student uploads solution: Upload changes of the source code to the VCS by committing and pushing them to the remote server (or by clicking submit in the online editor).

  6. CI server verifies solution: verify the student’s submission by executing the test cases (see step 1) and provide feedback which parts are correct or wrong.

  7. Student reviews personal result: Reviews build result and feedback using Artemis. In case of a failed build, reattempt to solve the exercise (step 4).

  8. Instructor reviews course results: Review overall results of all students, and react to common errors and problems.

The following activity diagram shows this exercise workflow.

Exercise Workflow

Exercise Workflow

Setup

The following sections describe the supported features and the process of creating a new programming exercise.

Features

Artemis and its version control and continuous integration infrastructure is independent of the programming language and thus supports teaching and learning with any programming language that can be compiled and tested on the command line. Instructors have a lot of freedom in defining the environment (e.g. using build agents and Docker images) in which student code is executed and tested. To simplify the setup of programming exercises, Artemis supports several templates that show how the setup works. Instructors can still use those templates to generate programming exercises and then adapt and customize the settings in the repositories and build plans.

  • The support for a specific programming language templates depends on the used continuous integration system. The table below gives an overview:

    Programming Language

    Bamboo

    Jenkins

    Java

    yes | yes

    Python

    yes | yes

    C

    yes | yes

    Haskell

    yes | yes

    Kotlin

    yes | no

    VHDL

    yes | no

    Assembler

    yes | no

    Swift

    yes | no

  • Not all templates support the same feature set. Depending on the feature set, some options might not be available during the creation of the programming exercise. The table below provides an overview of the supported features:

    Programming Language

    Sequential Test Runs

    Static Code Analysis

    Plagiarism Check

    Package Name

    Solution Repository Checkout

    Java

    yes

    yes

    yes

    yes

    no

    Python

    yes

    no

    yes

    no

    no

    C

    no

    no

    yes

    no

    no

    Haskell

    yes

    no

    no

    no

    yes

    Kotlin

    yes

    no

    no

    yes

    no

    VHDL

    no

    no

    no

    no

    no

    Assembler

    no

    no

    no

    no

    no

    Swift

    no

    no

    no

    no

    no

    • Sequential Test Runs: Artemis can generate a build plan which first executes structural and then behavioral tests. This feature can help students to better concentrate on the immediate challenge at hand.

    • Static Code Analysis: Artemis can generate a build plan which additionally executes static code analysis tools. Artemis categorizes the found issues and provides them as feedback for the students. This feature makes students aware of code quality issues in their submissions.

    • Plagiarism Checks: Artemis is able to automatically calculate the similarity between student submissions. A side-by-side view of similar submissions is available to confirm the plagiarism suspicion.

    • Package Name: A package name has to be provided

    • Solution Repository Checkout: Instructors are able to compare a student submission against a sample solution in the solution repository

Note

Only some templates for Bamboo support Sequential Test Runs at the moment.

Note

Instructors are still able to extend the generated programming exercises with additional features that are not available in one specific template.

We encourage instructors to contribute improvements to the existing templates or to provide new templates. Please contact Stephan Krusche and/or create Pull Requests in the Github repository.

Exercise Creation
  1. Open Course Management

  • Open course-management

  • Navigate into Exercises of your preferred course

    _images/course-management-course-dashboard.png
  1. Generate programming exercise

  • Click on Generate new programming exercise

    _images/course-management-exercise-dashboard.png
  • Fill out all mandatory values and click on generate

    _images/create-programming-1.png
    _images/create-programming-2.png

    Result: Programming Exercise

    _images/course-dashboard-exercise-programming.png

    Artemis creates the repositories:

    • Template: template code, can be empty, all students receive this code at the beginning of the exercises

    • Test: contains all test cases, e.g. based on JUnit, hidden for students

    • Solution: solution code, typically hidden for students, can be made available after the exercise

    Artemis creates two build plans

    • Template: also called BASE, basic configuration for the test + template repository, used to create student build plans

    • Solution: also called SOLUTION, configuration for the test + solution repository, used to manage test cases and to verify the exercise configuration

    _images/programming-view-1.png
    _images/programming-view-2.png
    _images/programming-view-3.png
  1. Update exercise code in repositories

  • Alternative 1: Clone the 3 repositories and adapt the code on your local computer in your preferred development environment (e.g. Eclipse).

    • To execute tests, copy the template (or solution) code into a folder assignment in the test repository and execute the tests (e.g. using maven clean test)

    • Commit and push your changes submit

    • Notes for Haskell: In addition to the assignment folder, the executables of the build file expect the solution repository checked out in the solution subdirectory of the test folder and also allow for a template subdirectory to easily test the template on your local machine. You can use the following script to conveniently checkout an exercise and create the right folder structure:

      #!/bin/sh
      # Arguments:
      # $1: exercise short name as specified on Artemis
      # $2: (optional) output folder name
      #
      # Note: you might want to adapt the `BASE` variable below according to your needs
      
      if [ -z "$1" ]; then
        echo "No exercise short name supplied."
        exit 1
      fi
      
      EXERCISE="$1"
      
      if [ -z "$2" ]; then
        # use the exercise name if no output folder name is specified
        NAME="$1"
      else
        NAME="$2"
      fi
      
      # default base URL to repositories; change this according to your needs
      BASE="ssh://git@bitbucket.ase.in.tum.de:7999/$EXERCISE/$EXERCISE"
      
      # clone the test repository
      git clone "$BASE-tests.git" "$NAME" && \
        # clone the template repository
        git clone "$BASE-exercise.git" "$NAME/template" && \
        # clone the solution repository
        git clone "$BASE-solution.git" "$NAME/solution" && \
        # create an assignment folder from the template repository
        cp -R "$NAME/template" "$NAME/assignment" && \
        # remove the .git folder from the assignment folder
        rm -r "$NAME/assignment/.git/"
      
  • Alternative 2: Open edit-in-editor in Artemis (in the browser) and adapt the code in online code editor

    • You can change between the different repos and submit the code when needed

  • Alternative 3: Use IntelliJ with the Orion plugin and change the code directly in IntelliJ

    Edit in Editor

    _images/instructor-editor.png
  • Check the results of the template and the solution build plan

  • They should not have the status build_failed

  • In case of a build_failed result, some configuration is wrong, please check the build errors on the corresponding build plan.

  • Hints: Test cases should only reference code, that is available in the template repository. In case this is not possible, please try out the option Sequential Test Runs

  1. Optional: Adapt the build plans

  • The build plans are preconfigured and typically do not need to be adapted

  • However, if you have additional build steps or different configurations, you can adapt the BASE and SOLUTION build plan as needed

  • When students start the programming exercise, the current version of the BASE build plan will be copied. All changes in the configuration will be considered

  1. Adapt the interactive problem statement

_images/course-dashboard-programming-edit.png
  • Click the edit button of the programming exercise or navigate into edit-in-editor and adapt the interactive problem statement.

  • The initial example shows how to integrate tasks, link tests and integrate interactive UML diagrams

  1. Configure Grading

_images/configure-grading.png
  1. Verify the exercise configuration

  • Open the view page of the programming exercise

    _images/solution-template-result.png
  • The template result should have a score of 0% with 0 of X passed

  • The solution result should have a score of 100% with X of X passed

  • Click on edit

    • Below the problem statement, you should see Test cases ok and Hints ok

    _images/programming-edit-status.png
Online Editor

The following screenshot shows the online code editor with interactive and dynamic exercise instructions on the right side. Tasks and UML diagram elements are referenced by test cases and update their color from red to green after students submit a new version and all test cases associated with a task or diagram element pass. This allows the students to immediately recognize which tasks are already fulfilled and is particularly helpful for programming beginners.

Online Editor

Online Editor

Testing with Artemis Java Test Sandbox

Artemis Java Test Sandbox (abbr. AJTS) is a JUnit 5 extension for easy and secure Java testing on Artemis.

Its main features are

  • a security manager to prevent students crashing the tests or cheating

  • more robust tests and builds due to limits on time, threads and io

  • support for public and hidden Artemis tests, where hidden ones obey a custom deadline

  • utilities for improved feedback in Artemis like processing multiline error messages or pointing to a possible location that caused an Exception

  • utilities to test exercises using System.out and System.in comfortably

For more information see https://github.com/ls1intum/artemis-java-test-sandbox

Quiz exercise

Modeling exercise

Textual exercise

File upload exercise

Exam Mode

Artemis now includes an online exam mode:

Instructors’ Guide


Timeline of an Artemis Online Exam
Timeline of an Artemis Exam

Timeline of an Artemis Online Exam

1. Creation and Configuration

During the exam creation and configuration, you can create your exam and configure it to fit your needs. Add exercises with different variants, register students, generate student exams and conduct test runs. For more information see 1.2 Create and Configure Exam.

1.1 Accessing the Exam Management Page
  • Log in to Artemis with your account credentials.

  • Click on course_management.

  • Click on exams for your course. It will open the Exam Management Screen.

  • Here you have access to all the exams of your course. All aspects of the exam are managed from the management screen.

  • You can create an exam by clicking on create_new_exam.

1.2 Create and Configure Exam
  • When you click on create_new_exam you are presented with the Create Exam view. Here you can set the basic information such as title, examiner etc. The timeline of the exam is defined by the dates: visible from, start of working time, end of working time, release date of results, begin of student review, end of student review.

  • The first three dates are mandatory when you create an exam. The rest can be set when required.

  • The grace period defines the amount of time the students have at their disposal to hand in their exam after the working time is over. This is set to 3 minutes by default.

  • You can also define the number of exercises in the exam. You can leave this out initally, however it must be set before you can generate the student exams. For more information, see 1.3 Exercise Groups.

  • Artemis will randomize the order of the exercises for each student if you activate randomize order of exercise groups.

  • Finally, you can fill out the exam start text and end text. Artemis will present these texts to the students during the exam conduction, at the Start- and End page respectively.

Create and Configure

Create and Configure the Exam

1.3 Exercise Groups
  • Artemis exam mode allows you to define multiple exercise variants so that each student can receive a unique exam. Artemis achieves this through exercise groups. Exercise groups represent an individual exercise slot for each student exam. Within one exercise group you can define different exercises.

  • Artemis selects one exercise per exercise group randomly, to generate the individual student exams.

  • You can distinguish between mandatory exercise groups and non-mandatory exercise groups.

  • Artemis always includes mandatory exercise groups in the individual exam of a student.

  • non-mandatory exercise groups can be left out, if there are more exercise groups than the number of exercises defined in the exam configuration.

  • By default, every exercise group is mandatory. You can set the mandetory flag when you add an exercise group initially, or later by clicking edit on the exercise group.

Exercise Groups with different Exercise Variants

Exercise Groups with different Exercise Variants

1.4 Add Exercises
  • Exercise groups can contain multiple exercises. For every student exam, Artemis will randomly select one exercise per exercise group.

    Note

    If you want all student to have the same exam, define only one exercise per exercise group.

  • To add exercises navigate to the Exercise Groups of the exam. On the header of each exercise group you will find the available exercise types. You can choose between creating a new exercise or importing an existing one from your courses.

Add different Exercises

Add different Exercises

  • For exercise types text, programming, and modeling you can also define example submissions and example assessments to guide your assessor team.

  • Assessors will review the example submissions and assessments in order to familiarise themselves with the exercise and assessment instructions, before they can assess the real submissions.

1.4.1 Programming Exercises

  • Programming exercises have multiple special options to adjust their behaviour:

  • You can check the option to allow manual assessment.

    Note

    If you do not set this flag, your assessors will not be able to manually assess the student’s submissions during the assessment process.

  • You can activate Run Tests once after Due Date. This will compile and run the test suite on all the student submissions once after the set date.

  • After you add a programming exercise you can configure the grading via configure_grading_button.

  • In the Configure Grading screen, you can tweak the weight of the tests, the bonus multiplier and add, bonus points.

  • You can hide tests so that they are not executed during the exam conduction. Students can not receive feedback from hidden tests during the exam conduction.

    Note

    If you hide all tests, the students will only be able to see if their submission compiles during the conduction. Set the due date after the exam end date to achieve this effect.

    Configure Grading

    Configure the Grading of a Programming Exercise

1.5 Register Students
  • To register students to the exam, navigate from the exam management to the Students page. Artemis offers two options to register students. You can:

    1. Add students manually my searching via the search bar.

    2. Bulk import students using a CSV file. You can do this by pressing the Import students button.

    Note

    Just registering the students to the exam will not allow them to participate in the exam. First, individual student exams must be generated.

  • You can also remove students from the exam. When you do so, you have the option to also delete their participations and submissions linked to the user’s student exam.

Register Students

Register Students Page

1.6 Manage Student Exams
  • Student exams represent the exam of a student. It consists of an individual set of exercises based on the configured exercise groups.

  • Student exams are managed via the Student Exams page.

  • Here you can have an overview of all student exams. When you press View on a student exam, you can view the details of the student, the allocated working time, his/her participation status, their summary, as well as their scores. Additionally, you will also be able to view which assessor is responsible for each exercise.

    Note

    You can change the individual working time of students from here.

  • To generate student exams you must click on generate_individual_exams. This will trigger Artemis to create a student exam for every registered user.

  • Artemis determines the number of exercises from the exam configuration and randomly selects one exercise per exercise group.

    Note

    generate_individual_exams button will be locked once the exam becomes visible to the students. You cannot perform changes to student exams once the exam conduction has started.

  • If you have added more students recently, you can choose to generate_missing_exams.

  • prepare_exercise_start creates a participation for each exercise for every registered user, based on their assigned exercises. It also creates the individual repositories and build plans for programming exercises. This action can take a while if there are many registered students due to the communication between the version control (VC) and continuous integration (CI) server.

    Warning

    You must trigger prepare_exercise_start before the exam conduction begins.

  • On the Student Exams page, you can also maintain the repositories of student exams. This functionality only affects programming exercises. You can choose to lock_repo and unlock_repo all student repositories.

    Note

    Artemis locks and unlocks the student repositories automatically based on the individual exam start and end date. These buttons are typically not necessary unless something went wrong.

  • Additionally, once the exam conduction ends, you can click on evaluate_quizzes. This action will evaluate all student exam submissions for all quiz exercises and assign an automatic result.

    Note

    If you do not press this button, the students quiz exercises will not be graded.

Student Exam Page

Student Exam Page

1.7 Conducting Test Runs
Delete Test Run

Test Run Management

  • Test runs are designed to offer the instructors confidence that the exam conduction will run smoothly. They allow you to experience the exam from the student’s perspective. A test run is distinct from a student exam and is not taken into consideration during the calculation of the exam scores.

  • You can manage your test runs from the Test Run page.

  • To create a new test run you can press create_test_run_button. This will open a popup where you can select an exercise for each exercise group. You can also set the working time. A test run will have as many exercises as there are exercise groups. It does consider the number of exercises set in the exam configuration.

    Note

    Exercise groups with no exercises are ignored.

Create Test Run

Create test run popup with one exercise variant selected for each exercise group.

  • When you start the test run, you conduct the exam similar to how a student would. You can create submissions for the different exercises and end the test run.

  • An instructor can also assess his test run submissions. To do this, you must have completed at least one test run. To navigate to the assessment screen of the test runs click assess_test_runs.

Conduct Test Run

Test run conduction marked with the banner on the top left.

Note

Only the creator of the test run is able to assess his submissions.

  • You can view the results of the assessment of the test run by clicking on summary. This page simulates the Student Exam Summary where the students can view their submissions and the results once they are published.

  • Here instructors can also use the complaint feature and respond to it to conclude the full exam timeline.

Note

You should delete test runs before the actual exam conduction takes place.

2. Conduction

The exam conduction starts when the exam becomes visible to the students and ends when the latest working time is over. When the exam conduction begins, you cannot make any changes anymore to the exam configuration or individual student exams. When the conduction starts, the students can access and start their exam. They can submit their solutions to the exercises within the given individual working time. When a student submits the exam, he cannot make any changes anymore to his exercise submissions. For more information, see participating in the online exam.

3. Assessment

The assessment begins as soon as the latest student exam working time is over. During this period, your team can assess the submissions of the students and provide results. Artemis executes the test suites for programming exercises automatically and grades these. You can enhance the automatic grading with a manual review. You can also trigger the automatic grading of the quiz exercises via the Manage Student Exams Screen.

3.1 Assess Student Exams
  • Once the exam conduction is over and the latest individual working time has passed, your team can begin the assessment process.

  • This is done through the Assessment Dashboard.

    Note

    If the exam conduction is not over, you will not be able to access this page.

  • The assessment process is anonymised. Artemis omits personal student data from the assessors.

  • The Assessment Dashboard provides an overview over the current assessment progress per exercise. For each exercise, you can view how many submissions have already been assessed and how many are still left. The status of the student complaints is also displayed here.

Assessment Dashboard

Assessment Dashboard

  • To assess a submission for an exercise, you can click on exercise_dashboard.

  • Your assessors must first complete the example submissions and assessments, if you have attached those to the exercise, see 1.4 Add Exercises.

  • If there is a submission which has not been assessed yet, you can click start_new_assessment. This will fetch a random student submission of this exercise which you can then assess.

  • Artemis grades programming exercises automatically. However, if the exercise allows a manual assessment, you can review and enhance the automatic results.

  • You can trigger Artemis to automatically grade quiz exercises via the Manage Student Exams Screen. Therefore, quiz exercises do not appear in the Assessment Dashboard.

Programming Submission Assessment

Manually Assessing a Programming Submission

  • Artemis also allows you to detect plagiarism attempts.

  • Artemis conducts this by analyzing the similarities between all student submissions and flagging those which exceed a given threshold. You can compare all flagged submissions side by side and confirm plagiarism attempts.

  • Instructors can download a CSV report of accepted and rejected plagiarism attempts for further processing on external systems.

  • To apply the plagiarism check, you must navigate to the individual exercise. This can be done by navigating to:

    exams -> exercise_groups -> view on the specific exercise.

Plagiarism Editor

Detecting Plagiarism attempts on Modeling Exercises

  • At the bottom of the page you will find the option check_plagiarism.

4. Publication of Results

You can specify the moment when Artemis publishes the results of the exam, see 1.2 Create and Configure Exam. This is usually when the exam assessment ends, but you can specify this at any point in time. During the publication of the results, the student can view their results from their summary page. You can also view the exam statistics from the exam Scores page and export the data into external platforms such as TUM Online as a CSV file, see 4.1 Exam Scores.

4.1 Exam Scores
  • You can access the exam scores by clicking on scores. This view aggregates the results of the students and combines them to provide an overview over the students’ performance.

  • You can view the spread between different achieved scores, the average results per exercise, as well as the individual students’ results.

  • Additionally, you can choose to modify the dataset by selecting only include submitted exams or only include exercises with at least one non-empty submission.

Note

Unsubmitted exams are not eligable for the assessment process.

  • Review student performance using various metrics such as average, median and standard deviation.

  • Unsubmitted exams are not eligable for assessment and thereby appear as having no score. It can happen that an exercise is not part of any student exam. This is the case when Artemis selects a different exercise of the same exercise group for every student exam. Similarly to the unsubmitted exams, they can warp the results and statistics of the exam. By eliminating unsubmitted exams and exercises which were not part of the exam conduction, you can gain a more realistic overview of the performance of the students.

  • Review the students perceived difficulty of every exercise to improve exams in the future.

  • The exam scores can also be exported via export. This is useful to upload the results into university systems like TUM Online as a CSV file.

  • The exported CSV file includes the students name, username, email, registration number, their assigned exercises, and their score for every exercise.

  • The exported CSV file also contains the aggregated statistics of the exam conduction such as the number of participations and the average score per exercise.

Exam Scores page

Exam Scores Page

5. Student Review

During the review period, students have the opportunity to review the assessment of their exam. If they find inconsistencies, they can submit complaints about perceived mistakes made in the assessment. Students can provide their reasoning through a text message to clarify their objections. You can set the student review period in the exam configuration, see 1.2 Create and Configure Exam.

  • Students can submit complaints about their assessment in the Summary page.

  • During the student review, a complaint button will appear for every manually assessed exercise.

  • Students cannot submit complaints for automatically assessed exercises like quiz and programming exercises.

  • Students will be able to submit a complaint for programming exercises, if the automatic result has been reviewed manually by an assessor. This is only possible if manual assessment is enabled for the programming exercise.

    Note

    If you have found a mistake in the automatic assessment of quiz and programming exercises, you can edit those and re-trigger the evaluation for all participants.

  • For more information on how students can participate in the student review and submit complaints, see student summary guide.

6. Complaint Assessment

Artemis collects the complaints submitted by the students during the student review. You can access and review the complaints similar to the submissions from the Assessment Dashboard. Every assessor can evaluate a complaint about the assessment of his/her peers and either accept or reject the complaint. Artemis will automatically update the results of accepted complaints. You can view the updated scores immediately in the Scores page. There you can also export the updated data in CSV format, see 4.1 Exam Scores.

  • The complaints appear below the exercise submissions.

  • The original assessor of an assessment cannot respond to the complaint. A second assessor must review the complaint and respond to it.

  • Artemis tracks the progress of the complaint assessment and displays a progress bar in the Assessment Dashboard. This allows you to keep track of the complaint assessment and see how many open complaints are left.

Complaint Response

Assessor responding to a Complaint

Students’ Guide


General Information
Prerequisites
  • Stable internet connection

    Recommendation: Use a LAN connection if possible.

  • Browser

    Recommendation: Chromium (based), e.g. Google Chrome, newest version.

  • The following prerequisites are only required if your exam contains programming exercises:

    • Java IDE with JDK 15

      Recommendation: Eclipse IDE 2020‑09.

    • Git Client

      Recommendation: SourceTree

Offline Mode
  • The exam mode in Artemis tolerates issues with the Internet connection.

  • If you loose your connection, you can continue working on text-, quiz- and modeling exercises, but you might get warnings that your solutions cannot be saved.

  • If your Internet connection recovers, Artemis will save your solution.

  • Artemis tries to save your solution every 30 seconds, when you navigate between exercises, and when you click save or save_continue.

  • Programming exercises have 2 modes.

    1. Online code editor: can only be used when you are online.

      Note

      You have to click on submit! Otherwise your solution will not be pushed to the VC server and no build will be triggered.

    2. Local IDE: you only need to be online when you clone the repository and when you push your commits (i.e. submit your solution).

  • At the end of the online exercise, you must be online within a given grace period and submit your exam, otherwise it will not be graded.

Suggestions
  1. Do NOT reload the browser

    • If you reload the browser, the Welcome Screen screen opens and you must enter your name and confirm the checkbox again.

    • You should only reload if an error occurs that cannot be recovered otherwise!

  2. Participate in ONE browser window!

    • Working in multiple browser windows at the same time is not allowed!

    • It will lead to synchronization issues and is seen as suspicious behaviour that can be flagged as cheating.

    Reload

    Do not reload, you will receive a warning

Participating in the Artemis Online Exam
Accessing the Exam
  • Log in to Artemis with your account credentials.

  • The current exam should be displayed at the top of the Course Overview screen.

  • You can also access the exam by navigating to the course and then to the exams.

    Note

    The exam will become visible shortly before the working time starts.

Access Exam

Access Exam

Welcome Screen
  • The welcome screen gives you an overview of all the important information you need about the exam.

  • Carefully read through the instructions.

  • Once you have read them, confirm that you will follow the rule, sign with your full name and click start.

    Note

    Your full name represents your signature. You can find your full name as registered on Artemis below the input field.

  • After you confirm, if the exam working time has started, the Exam Conduction screen will automatically appear.

  • Otherwise, you must wait until the exam begins. A popup will appear which will notify you how much time is left before the planned start.

Welcome Screen

Welcome Screen, waiting for exam start

Exam Conduction
  • Once the exam working time starts and you have confirmed your participation, the Conduction screen will appear.

  • On the header, you will find the Exam Navigation Bar. You can use this bar to navigate between different exercises. For each exercise an icon will display your current status.

    • When there are unsaved or unsubmitted changes, the exercise representation on the navigation bar becomes unsaved.

    • When your changes are saved and submitted, the exercise representation on the navigation bar becomes saved.

    • started indicates that you have not started this exercise.

  • You can also navigate through the exercises when you are done with one by clicking save_continue. This action will save and submit your changes and move to the next exercise.

    Warning

    For programming exercises, you must manually press submit otherwise your solution will not be counted!

  • On the header, you will also find the hand_in_early button. If you press this, you will be sent to the exam End Screen.

  • The time left until the end of the exam is also shown next to the action buttons, or below, depending on your screen size.

    Note

    When the time is about to run out, the background of the timer will turn yellow to warn you.

Exam Navigation

Exam Navigation Bar

Participating in Quiz Exercises
  • Various question types can be included in quiz exam exercises. These are:

    1. Multiple choice questions

    2. Short Answer questions

    3. Drag and Drop questions

  • All questions are listed in the main screen below one another.

  • To navigate between them you can either scroll or use the question overview on the left. When you click on one of the question representations, your view will automatically scroll to the respective question.

  • To submit your solution, simply press save_continue.

    Note

    Your submission will automatically be saved every 30 seconds.

Participating in Quiz Exercises

Participating in Quiz Exercises

Participating in Text Exercises
  • The text exercise view is divided into two sections, the text editor, and the problem statement. The problem statement is docked to the right.

    Note

    On small screens, the problem statement is shown above the text editor.

  • If you want to focus only on the text editor, you can collapse the problem statement by pressing on right_arrow. This can be reverted by pressing the arrow again.

    Note

    You can also choose to resize the problem statement by dragging the outline box outline_box.

  • Within the editor you can type out your solution. The editor will automatically track your number of words and number of characters.

Participating in Text Exercises

Participating in Text Exercises

Participating in Modeling Exercises
  • The modeling exercise view is divided into two sections, the modeling editor, and the problem statement. The problem statement is docked to the right.

    Note

    On small screens, the problem statement is shown above the modeling editor.

  • If you want to focus only on the modeling editor, you can collapse the problem statement by pressing on right_arrow. This can be reverted by pressing the arrow again.

    Note

    You can also choose to resize the problem statement by dragging the outline box outline_box.

  • Within the editor you can model your solution. Depending on the diagram type, you will find the available elements on the right side of the editor. Simply drag and drop them into the editing field.

  • When you click on a dropped element, you can configure it by setting it’s name, it’s attributes, methods etc.

  • To connect elements you can simply drag an element’s edges to another element. The editor will then automatically connect those two.

  • If you are unclear about how to use the modeling editor, you can click on help. It will provide further information about how to use the modeling editor.

    Note

    If you need more space, you can work in fullscreen by clicking on fullscreen. This mode will use your whole screen for the modeling exercise thereby giving you more space to model your solution. To exit the fullscreen mode simply click exit_fullscreen.

Participating in Modeling Exercises

Participating in Modeling Exercises

Participating in Programming Exercises
  • Depending on your exam, programming exercises can come in three forms:

    1. Online Code Editor + support for local IDE

    2. Online Code Editor

    3. Support for local IDE

  • If your exercise allows the use of the code editor your screen will be divided into three sections, from left to right:

    1. The file browser

    2. The code Editor

    3. The instructions

  • The file browser displays the file structure of the assignment. You can access any file within the assignment. Artemis will display the selected file’s content in the code editor where you can edit it.

    • You can add new files and directories using the add_file and add_folder buttons.

    • You also have the ability to rename rename and delete delete files and folders, therefore caution is advised.

      Note

      If you accidentally delete or remove a file, you can use refresh_files, to load the last saved version from the server.

  • The code editor allows you to edit the content of specific files. It shows the line numbers and will also annotate the appropriate line, if a compilation error occurs.

  • The instructions are docked to the right.

  • If you want to focus only on the code editor, you can collapse the instructions by pressing on the right_arrow. This can be reverted by pressing the arrow again. Similarly, if you want to collapse the file browser, you can press the left_arrow above the file browser.

    Note

    You can also choose to resize any of the three sections by dragging the outline_box.

  • When you press save, your files are saved on the Artemis server. However, you must press submit for your solution to be counted!

  • When you press submit, your changes are pushed to the version control (VC) server and a build is started on the continuous integration (CI) server. This is indicated by the results changing from no_results_found to building_and_testing.

    Warning

    There is no auto-submit!

Participating in Programming Exercises

Participating in Programming Exercises with the online code editor and local IDE enabled

  • If your exercise allows the use of the local IDE you will have access to the button clone_repo.

  • When you click it you can choose to clone the exercise via HTTPS or SSH, if you have configured your private key.

    Note

    You must link a public key to your account in advance if you want to use SSH.

  • To work offline follow these steps:

    1. Clone the Exercise

    2. Import the project in your IDE

    3. Work on the code

    4. Commit and push the code. A push is equivalent to pressing the submit button.

Clone Repository

Clone the Repository

Warning

You are responsible for pushing/submitting your code. Your instructors cannot help you if you have saved, but did not submit.

  • Your instructors can decide to limit the real-time feedback in programming exercises during the online exam.

  • In that case, you will only see if your code compiles or not:

    1. build_failed means that your code does not compile!

    2. build_passed means that your code compiles but provides no further information about your final score.

    Warning

    Edit a programming exercise EITHER in the online editor OR in your local IDE! Otherwise, conflicts can occur that are hard to resolve.

End Screen
  • When you are finished with the exercises, or the time runs out you navigate to the End Screen.

  • This is done either by clicking on hand_in_early or automatically when the exam conduction time is over.

    Note

    If you navigated to this screen via hand_in_early, you have the option to return to the conduction by clicking on continue.

  • In this screen you should confirm that you followed all the rules and sign with your full name, similar to the Welcome Screen.

  • You are given an additional grace period to submit the exam after the conduction is over. This additional time is added to the timer shown on the top right.

    Warning

    Your exam will not be graded, should you fail to submit!

  • Once you submit your exam, no further changes can be made to any exercise.

End Screen

End Screen after Early Hand in

Summary
  • After you hand in, you can view the summary of your exam.

  • You always have access to the summary. You can find it by following the steps displayed in: Accessing the Exam.

  • The summary contains an aggregated view of all your submissions. For programming exercises, it also contains the latest commit hash and repository URL so you can review your code.

Summary

Summary before the results are published

  • Once the results have been published, you can view your score in the summary.

  • Additionally, if within the student review period, you have the option to complain about manual assessments made. To do this, simply click on complain and explain your rationale.

  • A second assessor, different from the original one will have the opportunity to review your complaint and respond to it.

    Note

    The results will automatically be updated, if your complaint was successful.

Complaint

Complaining about the Assessment of a Text Exercise


Setup Guide

In this guide you learn how to setup the development environment of Artemis. Artemis is based on JHipster, i.e. Spring Boot development on the application server using Java 15, and TypeScript development on the application client in the browser using Angular and Webpack. To get an overview of the used technology, have a look at the JHipster Technology stack and other tutorials on the JHipster homepage.

You can find tutorials how to setup JHipster in an IDE (IntelliJ IDEA Ultimate is recommended) on https://jhipster.github.io/configuring-ide. Note that the Community Edition of IntelliJ IDEA does not provide Spring Boot support (see the comparison matrix). Before you can build Artemis, you must install and configure the following dependencies/tools on your machine:

  1. Java JDK: We use Java (JDK 15) to develop and run the Artemis application server which is based on Spring Boot.

  2. MySQL Database Server 8: Artemis uses Hibernate to store entities in a MySQL database. Download and install the MySQL Community Server (8.0.x) and configure the ‘root’ user with an empty password. (In case you want to use a different password, make sure to change the value in application-dev.yml and in liquibase.gradle). The required Artemis scheme will be created / updated automatically at startup time of the server application. Alternatively, you can run the MySQL Database Server inside a Docker container using e.g. docker-compose -f src/main/docker/mysql.yml up

  3. Node.js: We use Node (>=14.11.0) to compile and run the client Angular application. Depending on your system, you can install Node either from source or as a pre-packaged bundle.

  4. Yarn: We use Yarn 1.x (>=1.22.5) to manage client side Node dependencies. Depending on your system, you can install Yarn either from source or as a pre-packaged bundle. To do so, please follow the instructions on the Yarn installation page.

Server Setup

To start the Artemis application server from the development environment, first import the project into IntelliJ and then make sure to install the Spring Boot plugins to run the main class de.tum.in.www1.artemis.ArtemisApp. Before the application runs, you have to configure the file application-artemis.yml in the folder src/main/resources/config.

artemis:
    repo-clone-path: ./repos/
    repo-download-clone-path: ./repos-download/
    encryption-password: <encrypt-password>     # arbitrary password for encrypting database values
    user-management:
        use-external: true
        external:
            url: https://jira.ase.in.tum.de
            user: <username>    # e.g. ga12abc
            password: <password>
            admin-group-name: tumuser
        ldap:
            url: <url>
            user-dn: <user-dn>
            password: <password>
            base: <base>
    version-control:
        url: https://bitbucket.ase.in.tum.de
        user: <username>    # e.g. ga12abc
        password: <password>
        token: <token>                 # VCS API token giving Artemis full Admin access. Not needed for Bamboo+Bitbucket
        ci-token: <token from the CI>   # Token generated by the CI (e.g. Jenkins) for webhooks from the VCS to the CI. Not needed for Bamboo+Bitbucket
    continuous-integration:
        url: https://bamboo.ase.in.tum.de
        user: <username>    # e.g. ga12abc
        token: <token>      # Enter a valid token generated by bamboo or leave this empty to use the fallback authentication user + password
        password: <password>
        vcs-application-link-name: LS1 Bitbucket Server     # If the VCS and CI are directly linked (normally only for Bitbucket + Bamboo)
        empty-commit-necessary: true                        # Do we need an empty commit for new exercises/repositories in order for the CI to register the repo
        # Hash/key of the ci-token, equivalent e.g. to the ci-token in version-control
        # Some CI systems, like Jenkins, offer a specific token that gets checked against any incoming notifications
        # from a VCS trying to trigger a build plan. Only if the notification request contains the correct token, the plan
        # is triggered. This can be seen as an alternative to sending an authenticated request to a REST API and then
        # triggering the plan.
        # In the case of Artemis, this is only really needed for the Jenkins + GitLab setup, since the GitLab plugin in
        # Jenkins only allows triggering the Jenkins jobs using such a token. Furthermore, in this case, the value of the
        # hudson.util.Secret is stored in the build plan, so you also have to specify this encrypted string here and NOT the actual token value itself!
        # You can get this by GETting any job.xml for a job with an activated GitLab step and your token value of choice.
        secret-push-token: <token hash>
        # Key of the saved credentials for the VCS service
        # Bamboo: not needed
        # Jenkins: You have to specify the key from the credentials page in Jenkins under which the user and
        #          password for the VCS are stored
        vcs-credentials: <credentials key>
        # Key of the credentials for the Artemis notification token
        # Bamboo: not needed
        # Jenkins: You have to specify the key from the credentials page in Jenkins under which the notification token is stored
        notification-token: <credentials key>
        # The actual value of the notification token to check against in Artemis. This is the token that gets send with
        # every request the CI system makes to Artemis containing a new result after a build.
        # Bamboo: The token value you use for the Server Notification Plugin
        # Jenkins: The token value you use for the Server Notification Plugin and is stored under the notification-token credential above
        authentication-token: <token>
    lti:
        id: artemis_lti
        oauth-key: artemis_lti_key
        oauth-secret: <secret>    # only important for online courses on the edX platform, can typically be ignored
        user-prefix-edx: edx_
        user-prefix-u4i: u4i_
        user-group-name-edx: edx
        user-group-name-u4i: u4i
    git:
        name: Artemis
        email: artemis@in.tum.de
    athene:
        submit-url: http://localhost/submit
        feedback-consistency-url: http://localhost:8001/feedback_consistency
        base64-secret: YWVuaXF1YWRpNWNlaXJpNmFlbTZkb283dXphaVF1b29oM3J1MWNoYWlyNHRoZWUzb2huZ2FpM211bGVlM0VpcAo=
        token-validity-in-seconds: 10800

Change all entries with <...> with proper values, e.g. your TUM Online account credentials to connect to the given instances of JIRA, Bitbucket and Bamboo. Alternatively, you can connect to your local JIRA, Bitbucket and Bamboo instances. It’s not necessary to fill all the fields, most of them can be left blank. Note that there is additional information about the setup for programming exercises provided:

Setup for Programming Exercises with Bamboo, Bitbucket and Jira

This page describes how to set up a programming exercise environment based on Bamboo, Bitbucket and Jira.

Please note that this setup will create a deployment that is very similiar to the one used in production but has one difference:
In production, the builds are performed within Docker containers that are created by Bamboo (or its build agents). As we run Bamboo in a Docker container in this setup, creating new Docker containers within that container is not recommended (e.g. see this article). There are some solution where one can pass the Docker socket to the Bamboo container, but none of these approachs work quite well here as Bamboo uses mounted directories that cause issues.

Therefore, a check is included within the BambooBuildPlanService that ensures that builds are not started in Docker agents if the development setup is present.

Prerequisites:

Docker-Compose

Before you start the docker-compose, check if the bamboo version in the build.gradle (search for com.atlassian.bamboo:bamboo-specs) is equal to the bamboo version number in the Dockerfile of bamboo stored in src/main/docker/bamboo/Dockerfile. If the version number is not equal adjust the version number in the Dockerfile.

Execute the docker-compose file atlassian.yml stored in src/main/docker e.g. with docker-compose -f src/main/docker/atlassian.yml up -d

Error Handling: It can happen that there is an overload with other docker networks ERROR: Pool overlaps with other one on this address space. Use the command docker network prune to resolve this issue.

Make also sure that docker has enough memory (~ 6GB). To adapt it, go to Preferecences -> Resources

Configure Bamboo, Bitbucket and Jira

By default, the Jira instance is reachable under localhost:8081, the Bamboo instance under localhost:8085 and the Bitbucket instance under localhost:7990.

Get evaluation licenses for Atlassian products: Atlassian Licenses

  1. Create an admin user with the same credentials in all 3 applications. Create a sample project in Jira. Also, you can select the evaluation/internal/test/dev setups if you are asked. Select a Bitbucket (Server) license if asked. Do not connect Bitbucket with Jira yet.

  2. Execute the shell script atlassian-setup.sh in the src/main/docker directory (e.g. with src/main/docker/./atlassian-setup.sh). This script creates groups, users ([STRIKEOUT:and adds them to the created groups] NOT YET) and disabled application links between the 3 applications
  3. Enable the created application links between all 3 application (OAuth Impersonate). The links should open automatically after the shell script has finished. If not open them manually:

You manually have to adjust the Display URL for the Bamboo → Bitbucket AND Bitbucket → Bamboo URl to http://localhost:7990 and http://localhost:8085 .

Bamboo:

_images/bamboo_bitbucket_applicationLink.png

Bamboo → Bitbucket

_images/bamboo_jira_applicationLink.png

Bamboo → Jira

Bitbucket:

_images/bitbucket_bamboo_applicationLink.png

Bitbucket → Bamboo

_images/bitbucket_jira_applicationLink.png

Bitbucket → Jira

Jira:

_images/jira_bamboo_applicationLink.png

Jira → Bamboo

_images/jira_bitbucket_applicationLink.png

Jira → Bitbucket

  1. The script has already created users and groups but you need to manually assign the users into their respective group in Jira. In our test setup, users 1-5 are students, 6-10 are tutors and 11-15 are instructors. The usernames are artemis_test_user_{1-15} and the password is again the username. When you create a course in artemis you have to manually choose the created groups(students, tutors, instructors).

  2. Use the user directories in Jira to synchronize the users in bitbucket and bamboo:

    • Go to Jira → User management → Jira user server → Add application → Create one application for bitbucket and one for bamboo → add the IP-address 0.0.0.0/0 to IP Addresses

      _images/jira_add_application.png
    • Go to Bitbucket and Bamboo → User Directories → Add Directories → Atlassian Crowd → use the URL http://jira:8080 as Server URL → use the application name and password which you used in the previous step. Also, you should decrease the synchronisation period (e.g. to 2 minutes). Press synchronise after adding the directory, the users and groups should now be available.

      _images/user_directories.png
  3. In Bamboo create a global variable named SERVER_PLUGIN_SECRET_PASSWORD, the value of this variable will be used as the secret. The value of this variable should be then stored in src/main/resources/config/application-artemis.yml as the value of artemis-authentication-token-value.

  4. Download the bamboo-server-notifaction-plugin and add it to bamboo. Go to Bamboo → Manage apps → Upload app → select the downloaded .jar file → Upload

  5. Add Maven and JDK:

    • Go to Bamboo → Server capabilities → Add capabilities menu → Capability type Executable → select type Maven 3.x → insert Maven 3 as executable label → insert /artemis as path.

    • Add capabilities menu → Capability type JDK → insert JDK as JDK label → insert /usr/lib/jvm/java-15-oracle as Java home.

  6. Generate a personal access token

    While username and password can still be used as a fallback, this option is already marked as deprecated and will be removed in the future.

    9.1 Personal access token for Bamboo.

    • Log in as the admin user and go to Bamboo -> Profile (top right corner) -> Personal access tokens -> Create token

      _images/bamboo-create-token.png
    • Insert the generated token into the file application-artemis.yml in the section continuous-integration:

    artemis:
        continuous-integration:
            user: <username>
            password: <password>
            token: #insert the token here
    

    9.2 Personal access token for Bitbucket.

    • Log in as the admin user and go to Bitbucket -> View Profile (top right corner) -> Manage account -> Personal access tokens -> Create token

      _images/bitbucket-create-token.png
    • Insert the generated token into the file application-artemis.yml in the section version-control:

    artemis:
        version-control:
            user: <username>
            password: <password>
            token: #insert the token here
    
  7. Disable XSRF checking Although XSRF checking is highly recommended, we currently have to disable it as Artemis does not yet support sending the required headers.

    • Log in as the admin user go to Bamboo -> Overview -> Security Settings

      Edit the settings and disable XSRF checking:

      _images/bamboo_xsrf_disable.png
Configure Artemis
  1. Modify src/main/resources/config/application-artemis.yml

    repo-clone-path: ./repos/
    repo-download-clone-path: ./repos-download/
    encryption-password: artemis-encrypt     # arbitrary password for encrypting database values
    user-management:
        use-external: true
        external:
            url: http://localhost:8081
            user:  <jira-admin-user>
            password: <jira-admin-password>
            admin-group-name: instructors
        internal-admin:
            username: artemis_admin
            password: artemis_admin
    version-control:
        url: http://localhost:7990
        user:  <bitbucket-admin-user>
        password: <bitbuckt-admin-password>
        token: <bitbucket-admin-token>
    continuous-integration:
        url: http://localhost:8085
        user:  <bamboo-admin-user>
        password: <bamboo-admin-password>
        token: <bamboo-admin-token>
        vcs-application-link-name: LS1 Bitbucket Server
        empty-commit-necessary: true
        artemis-authentication-token-value: <artemis-authentication-token-value>
    
  2. Modify the application-dev.yml

    server:
        port: 8080                                         # The port of artemis
        url: http://172.20.0.1:8080                        # needs to be an ip
        // url: http://docker.for.mac.host.internal:8080   # If the above one does not work for mac try this one
        // url: http://host.docker.internal:8080           # If the above one does not work for windows try this one
    

In addition, you have to start Artemis with the profiles bamboo, bitbucket and jira so that the correct adapters will be used, e.g.:

--spring.profiles.active=dev,bamboo,bitbucket,jira,artemis

Please read Setup Guide for more details.

How to verify the connection works?
Artemis → Jira

You can login to Artemis with the admin user you created in Jira

Artemis → Bitbucket

You can create a programming exercise

Artemis → Bamboo

You can create a programming exercise

Bitbucket → Bamboo

The build of the students repository gets started after pushing to it

Bitbucket → Artemis

When using the code editor, after clicking on Submit, the text Building and testing… should appear.

Bamboo → Artemis

The build result is displayed in the code editor.

Setup for Programming Exercises with Jenkins and GitLab

This page describes how to set up a programming exercise environment based on Jenkins and GitLab. Optional commands are in curly brackets {}.

The following assumes that all instances run on separate servers. If you have one single server, or your own NGINX instance, just skip all NGINX related steps and use the configurations provided under Separate NGINX Configurations

If you want to setup everything on your local development computer, ignore all NGINX related steps. Just make sure that you use unique port mappings for your Docker containers (e.g. 8081 for GitLab, 8082 for Jenkins, 8080 for Artemis)

Prerequisites:

Make sure that docker has enough memory (~ 6GB). To adapt it, go to Preferences -> Resources and restart Docker.

Artemis

In order to use Artemis with Jenkins as Continuous Integration Server and Gitlab as Version Control Server, you have to configure the file application-prod.yml (Production Server) or application-artemis.yml (Local Development) accordingly. Please note that all values in <..> have to be configured properly. These values will be explained below in the corresponding sections.

artemis:
    repo-clone-path: ./repos/
    repo-download-clone-path: ./repos-download/
    encryption-password: artemis-encrypt     # arbitrary password for encrypting database values
    user-management:
        use-external: false
        internal-admin:
             username: artemis_admin
             password: artemis_admin
    version-control:
        url: <https://gitlab-url>
        user: <gitlab-admin-user>
        password: <gitlab-admin-password>
        token: <token>
        ci-token: <ci-token>
    continuous-integration:
        user: <jenkins-admin-user>
        password: <jenkins-admin-password>
        url: <https://jenkins-url>
        empty-commit-necessary: false
        secret-push-token: <secret push token>
        vcs-credentials: <vcs-credentials>
        artemis-authentication-token-key: <artemis-authentication-token-key>
        artemis-authentication-token-value: <artemis-authentication-token-value>

In addition, you have to start Artemis with the profiles gitlab and jenkins so that the correct adapters will be used, e.g.:

--spring.profiles.active=dev,jenkins,gitlab,artemis

Please read Setup Guide for more details.

For a local setup on Windows you can use http://host.docker.internal appended by the chosen ports as the version-control and continuous-integration url.

Make sure to change the server.url value in application-dev.yml or application-prod.yml accordingly. This value will be used for the communication hooks from Gitlab to Artemis and from Jenkins to Artemis. In case you use a different port than 80 (http) or 443 (https) for the communication, you have to append it to the server.url value, e.g. 127.0.0.1:8080.

When you start Artemis for the first time, it will automatically create an admin user based on the default encryption password specified in the yml file above. In case you want to use a different encryption password, you can insert users manually into the jhi_user table. You can use Jasypt Online Encryption Tool to generate encryption strings. Use Two Way Encryption (With Secret Text).

Note: Sometimes Artemis does not generate the admin user which may lead to a startup error. You will have to create the user manually in the MySQL database and in Gitlab. Make sure both are set up correctly and follow these steps:

  1. Use the tool mentioned above to generate a password hash.

  2. Connect to the database via a client like MySQL Workbench and execute the following query to create the user. Replace artemis_admin and HASHED_PASSWORD with your chosen username and password:

    INSERT INTO `artemis`.`jhi_user` (`id`,`login`,`password_hash`,`first_name`,`last_name`,`email`,
    `activated`,`lang_key`,`activation_key`,`reset_key`,`created_by`,`created_date`,`reset_date`,
    `last_modified_by`,`last_modified_date`,`image_url`,`last_notification_read`,`registration_number`)
    VALUES (1,"artemis_admin","HASHED_PASSWORD","artemis","administrator","artemis_admin@localhost",
    1,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL);
    
  3. Give the user admin and user roles:

    INSERT INTO `artemis`.`jhi_user_authority` (`user_id`, `authority_name`) VALUES (1,"ROLE_ADMIN");
    INSERT INTO `artemis`.`jhi_user_authority` (`user_id`, `authority_name`) VALUES (1,"ROLE_USER");
    

4. Create a user in Gitlab (http://your-gitlab-domain/admin/users/new) and make sure that the username, email, and password are the same as the user from the database:

_images/gitlab_admin_user.png

Starting the Artemis server should now succeed.

GitLab
Gitlab Server Setup
  1. Pull the latest GitLab Docker image

    docker pull gitlab/gitlab-ce:latest
    
Start Gitlab
  1. Run the image (and change the values for hostname and ports). Add -p 2222:22 if cloning/pushing via ssh should be possible. As Gitlab runs in a docker container and the default port for SSH (22) is typically used by the host running Docker, we change the port Gitlab uses for SSH to 2222. This can be adjusted if needed.

    Make sure to remove the comments from the command before running it.

    docker run -itd --name gitlab \
        --hostname your.gitlab.domain.com \   # Specify the hostname
        --restart always \
        -m 3000m \                            # Optional argument to limit the memory usage of Gitlab
        -p 8081:80 -p 443:443 \               # Alternative 1: If you are NOT running your own NGINX instance
        -p <some port of your choosing>:80 \  # Alternative 2: If you ARE running your own NGINX instance
        -p 2222:22 \                          # Remove this if cloning via SSH should not be supported
        -v gitlab_data:/var/opt/gitlab \
        -v gitlab_logs:/var/log/gitlab \
        -v gitlab_config:/etc/gitlab \
        gitlab/gitlab-ce:latest
    
  2. Wait a couple of minutes until the container is deployed and GitLab is set up, then open the instance in you browser and set a first admin password of your choosing. You can then login using the username root and your password.

  3. We recommend to rename the root admin user to artemis. To rename the user, click on the image on the top right and select Settings. Now select Account on the left and change the username. Use the same password in the Artemis configuration file application-artemis.yml

    artemis:
        version-control:
            user: artemis
            password: the.password.you.chose
    
  4. If you run your own NGINX or if you install Gitlab on a local development computer, then skip the next steps (6-7)

  5. Configure Gitlab to automatically generate certificates using LetsEncrypt. Edit the Gitlab configuration

    docker exec -it gitlab /bin/bash
    nano /etc/gitlab/gitlab.rb
    

    And add the following part

    letsencrypt['enable'] = true                          # GitLab 10.5 and 10.6 require this option
    external_url "https://your.gitlab.domain.com"         # Must use https protocol
    letsencrypt['contact_emails'] = ['gitlab@your.gitlab.domain.com'] # Optional
    
    nginx['redirect_http_to_https'] = true
    nginx['redirect_http_to_https_port'] = 80
    
  6. Reconfigure gitlab to generate the certificate.

    # Save your changes and finally run
    gitlab-ctl reconfigure
    

    If this command fails, try using

    gitlab-ctl renew-le-certs
    
  7. Login to GitLab using the Artemis admin account and go to the profile settings (upper right corned → Settings)

    _images/gitlab_setting_button.png
Gitlab Access Token
  1. Go to Access Tokens

_images/gitlab_access_tokens_button.png
  1. Create a new token named “Artemis” and give it all rights.

_images/artemis_gitlab_access_token.png
  1. Copy the generated token and insert it into the Artemis configuration file application-artemis.yml

    artemis:
        version-control:
            token: your.generated.api.token
    
  2. (Optional, only necessary for local setup) Allow outbound requests to local network

    There is a known limitation for the local setup: webhook URLs for the communication between Gitlab and Artemis and between Gitlab and Jenkins cannot include local IP addresses. This option can be deactivated in Gitlab on <https://gitlab-url>/admin/application_settings/network → Outbound requests. Another possible solution is to register a local URL, e.g. using ngrok, to be available over a domain the Internet.

  3. Adjust the monitoring-endpoint whitelist. Run the following command

    docker exec -it gitlab /bin/bash
    

    Then edit the Gitlab configuration

    nano /etc/gitlab/gitlab.rb
    

    Add the following lines

    gitlab_rails['monitoring_whitelist'] = ['0.0.0.0/0']
    gitlab_rails['gitlab_shell_ssh_port'] = 2222
    

    This will disable the firewall for all IP addresses. If you only want to allow the server that runs Artemis to query the information, replace 0.0.0.0/0 with ARTEMIS.SERVER.IP.ADRESS/32

    If you use SSH and use a different port than 2222, you have to adjust the port above.

  4. Disable prometheus. As we encountered issues with the prometheus log files not being deleted and therefore filling up the disk space, we decided to disable prometheus within Gitlab. If you also want to disable prometheus, edit the configuration again using

    nano /etc/gitlab/gitlab.rb
    

    and add the following line

    prometheus_monitoring['enable'] = false
    

    The issue with more details can be found here.

Reconfigure Gitlab

gitlab-ctl reconfigure
Upgrade GitLab

You can upgrade GitLab by downloading the latest Docker image and starting a new container with the old volumes:

docker stop gitlab
docker rename gitlab gitlab_old
docker pull gitlab/gitlab-ce:latest

See https://hub.docker.com/r/gitlab/gitlab-ce/ for the latest version. You can also specify an earlier one.

Start a GitLab container just as described in Start-Gitlab and wait for a couple of minutes. GitLab should configure itself automatically. If there are no issues, you can delete the old container using docker rm gitlab_old and the olf image (see docker images) using docker rmi <old-image-id>. You can also remove all old images using docker image prune -a

Jenkins
Jenkins Server Setup
  1. Pull the latest Jenkins LTS Docker image

    Run the following command to get the latest jenkins LTS docker image.

    docker pull jenkins/jenkins:lts
    
  2. Create a custom docker image

    In order to install and use Maven with Java in the Jenkins container, you have to first install maven, then download Java and finally configure Maven to use Java instead of the default version.

    To perform all these steps automatically, you can prepare a Docker image:

    Create a dockerfile with the content found here <src/main/docker/jenkins/Dockerfile>. Copy it in a file named Dockerfile, e.g. in the folder /opt/jenkins/ using vim Dockerfile.

    Now run the command docker build --no-cache -t jenkins-artemis .

    This might take a while because Docker will download Java, but this is only required once.

  3. If you run your own NGINX or if you install Jenkins on a local development computer, then skip the next steps (4-6)

  4. Create a file increasing the maximum file size for the nginx proxy. The nginx-proxy uses a default file limit that is too small for the plugin that will be uploaded later. Skip this step if you have your own NGINX instance.

    echo "client_max_body_size 16m;" > client_max_body_size.conf
    
  5. Run the NGINX proxy docker container, this will automatically setup all reverse proxies and force https on all connections. (This image would also setup proxies for all other running containers that have the VIRTUAL_HOST and VIRTUAL_PORT environment variables). Skip this step if you have your own NGINX instance.

    docker run -itd --name nginx_proxy \
        -p 80:80 -p 443:443 \
        --restart always \
        -v /var/run/docker.sock:/tmp/docker.sock:ro \
        -v /etc/nginx/certs \
        -v /etc/nginx/vhost.d \
        -v /usr/share/nginx/html \
        -v $(pwd)/client_max_body_size.conf:/etc/nginx/conf.d/client_max_body_size.conf:ro \
        jwilder/nginx-proxy
    
  6. The nginx proxy needs another docker-container to generate letsencrypt certificates. Run the following command to start it (make sure to change the email-address). Skip this step if you have your own NGINX instance.

    docker run --detach \
        --name nginx_proxy-letsencrypt \
        --volumes-from nginx_proxy \
        --volume /var/run/docker.sock:/var/run/docker.sock:ro \
        --env "DEFAULT_EMAIL=mail@yourdomain.tld" \
        jrcs/letsencrypt-nginx-proxy-companion
    
Start Jenkins
  1. Run Jenkins by executing the following command (change the hostname and choose which port alternative you need)

    docker run -itd --name jenkins \
        --restart always \
        -v jenkins_data:/var/jenkins_home \
        -v /var/run/docker.sock:/var/run/docker.sock \
        -v /usr/bin/docker:/usr/bin/docker:ro \
        -e VIRTUAL_HOST=your.jenkins.domain -e VIRTUAL_PORT=8080 \    # Alternative 1: If you are NOT using a separate NGINX instance
        -e LETSENCRYPT_HOST=your.jenkins.domain \                     # Only needed if Alternative 1 is used
        -p 8082:8080 \                                                # Alternative 2: If you ARE using a separate NGINX instance OR you ARE installing Jenkins on a local development computer
        -u root \
        jenkins/jenkins:lts
    

    If you still need the old setup with python & maven installed locally, use jenkins-artemis instead of jenkins/jenkins:lts. Also note that you can omit the -u root, -v /var/run/docker.sock:/var/run/docker.sock and -v /usr/bin/docker:/usr/bin/docker:ro parameters, if you do not want to run Docker builds on the Jenkins master (but e.g. use remote agents).

  2. Open Jenkins in your browser (e.g. localhost:8082) and setup the

    admin user account (install all suggested plugins). You can get the initial admin password using the following command.

    # Jenkins highlights the password in the logs, you can't miss it
    docker logs -f jenkins
    or alternatively
    docker exec jenkins cat /var/jenkins_home/secrets/initialAdminPassword
    
  3. Set the chosen credentials in the Artemis configuration

    application-artemis.yml

    artemis:
        continuous-integration:
            user: your.chosen.username
            password: your.chosen.password
    
Required Jenkins Plugins

You will need to install the following plugins (apart from the recommended ones that got installed during the setup process):

  1. GitLab for enabling webhooks to and from GitLab

  2. Timestamper for adding the time to every line of the build output (Timestamper might already be installed)

  3. Pipeline for defining the build description using declarative files (Pipeline might already be installed)

    Note: This is a suite of plugins that will install multiple plugins

  4. Pipeline Maven to use maven within the pipelines.

The plugins above (and the pipeline-setup associated with it) got introduced in Artemis 4.7.3. If you are using exercises that were created before 4.7.3, you also have to install these plugins:

Please note that this setup is deprecated and will be removed in the future. Please migrate to the new pipeline-setup if possible.

  1. Multiple SCMs for combining the exercise test and assignment repositories in one build

  2. Post Build Task for preparing build results to be exported to Artemis

  3. Xvfb for exercises based on GUI libraries, for which tests have to have some virtual display

Choose “Download now and install after restart” and checking the “Restart Jenkins when installation is complete and no jobs are running” box

Timestamper Configuration

Go to Manage Jenkins → Configure System. There you will find the Timestamper configuration, use the following value for both formats:

'<b>'yyyy-MM-dd'T'HH:mm:ssX'</b> '
_images/timestamper_config.png
Server Notification Plugin

Artemis needs to receive a notification after every build, which contains the test results and additional commit information. For that purpose, we developed a Jenkins plugin, that can aggregate and POST JUnit formatted results to any URL.

You can download the current release of the plugin here (Download the .hpi file). Go to the Jenkins plugin page (Manage Jenkins → Manage Plugins) and install the downloaded file under the Advanced tab under Upload Plugin

_images/jenkins_custom_plugin.png
Jenkins Credentials

Go to Manage Jenkins -> Security -> Manage Credentials → Jenkins → Global credentials and create the following credentials

GitLab API Token
  1. Create a new access token in GitLab named Jenkins and give it api rights and read_repository rights. For detailed instructions on how to create such a token follow Gitlab Access Token.

    _images/gitlab_jenkins_token_rights.png
  2. Copy the generated token and create new Jenkins credentials:

    1. Kind: GitLab API token

    2. Scope: Global

    3. API token: your.copied.token

    4. Leave the ID field blank

    5. The description is up to you

  3. Go to the Jenkins settings Manage Jenkins → Configure System. There you will find the GitLab settings. Fill in the URL of your GitLab instance and select the just created API token in the credentials dropdown. After you click on “Test Connection”, everything should work fine. If you have problems finding the right URL for your local docker setup, you can try http://host.docker.internal:8081 for Windows or http://docker.for.mac.host.internal:8081 for Mac if GitLab is reachable over port 8081.

    _images/jenkins_gitlab_configuration.png
Server Notification Token
  1. Create a new Jenkins credential containing the token, which gets send by the server notification plugin to Artemis with every build result:

    1. Kind: Secret text

    2. Scope: Global

    3. Secret: your.secret_token_value (choose any value you want, copy it for the nex step)

    4. Leave the ID field blank

    5. The description is up to you

  2. Copy the generated ID of the new credentials and put it into the Artemis configuration application-artemis.yml

    artemis:
        continuous-integration:
            artemis-authentication-token-key: the.id.of.the.notification.token.credential
    
  3. Copy the actual value you chose for the token and put it into the Artemis configuration application-artemis.yml

    artemis:
        continuous-integration:
            artemis-authentication-token-value: the.actual.value.of.the.notification.token
    
GitLab Repository Access
  1. Create a new Jenkins credentials containing the username and password of the GitLab administrator account:

    1. Kind: Username with password

    2. Scope: Global

    3. Username: the_username_you_chose_for_the_gitlab_admin_user

    4. Password: the_password_you_chose_for_the_gitlab_admin_user

    5. Leave the ID field blank

    6. The description is up to you

  2. Copy the generated ID (e.g. ea0e3c08-4110-4g2f-9c83-fb2cdf6345fa) of the new credentials and put it into the Artemis configuration file application-artemis.yml

    artemis:
        continuous-integration:
            vcs-credentials: the.id.of.the.username.and.password.credentials.from.jenkins
    
GitLab to Jenkins push notification token

GitLab has to notify Jenkins build plans if there are any new commits to the repository. The push notification that gets sent here is secured by a token generated by Jenkins. In order to get this token, you have to do the following steps:

  1. Create a new item in Jenkins (use the Freestyle project type) and name it TestProject

  2. In the project configuration, go to Build Triggers → Build when a change is pushed to GitLab and activate this option

  3. Click on Advanced.

  4. You will now have a couple of new options here, one of them being a “Secret token”.

  5. Click on the “Generate” button right below the text box for that token.

  6. Copy the generated value, let’s call it $gitlab-push-token

  7. Apply these change to the plan (i.e. click on Apply)

_images/jenkins_test_project.png
  1. Perform a GET request to the following URL (e.g. with Postman) using Basic Authentication and the username and password you chose for the Jenkins admin account:

    GET https://your.jenkins.domain/job/TestProject/config.xml
    
  2. You will get the whole configuration XML of the just created build plan, there you will find the following tag:

    <secretToken>{$some-long-encrypted-value}</secretToken>
    
_images/jenkins_project_config_xml.png

Job configuration XML

  1. Copy the secret-push-token value in the line <secretToken>{secret-push-token}</secretToken>. This is the encrypted value of the gitlab-push-token you generated in step 5.

  2. Now, you can delete this test project and input the following values into your Artemis configuration application-artemis.yml (replace the placeholders with the actual values you wrote down)

    artemis:
        version-control:
            ci-token: $gitlab-push-token
        continuous-integration:
            secret-push-token: $some-long-encrytped-value
    
  3. In a local setup, you have to disable CSRF otherwise some API endpoints will return HTTP Status 403 Forbidden. This is done by creating a groovy script inside the jenkins docker container at jenkins_home/init.groovy with the following contents:

    import jenkins.model.Jenkins
    def instance = Jenkins.instance
    instance.setCrumbIssuer(null)
    

    In order to save the script, first create a file called jenkins-disable-csrf.groovy with the groovy code from above.

    Then create a init.groovy file in your Jenkins container:

    docker exec jenkins /bin/bash -c "cd /var/jenkins_home; touch init.groovy"
    

    Now we need to pipe the script into the container:

    docker exec -i jenkins dd of=/var/jenkins_home/init.groovy < jenkins-disable-csrf.groovy
    

    To make sure that the commands worked as intended, the following command should output the script from above:

    docker exec jenkins cat /var/jenkins_home/init.groovy
    

    The last step is to disable the use-crumb option in application-jenkins.yml:

    jenkins::
        use-crumb: false
    
Build agents

You can either run the builds locally (that means on the machine that hosts Jenkins) or on remote build agents.

Configuring local build agents

Go to Manage Jenkins > Manage Nodes and Clouds > master Configure your master node like this (adjust the number of executors, if needed). Make sure to add the docker label.

_images/jenkins_local_node.png

Jenkins local node

Installing remote build agents

You might want to run the builds on additional Jenkins agents, especially if a large amount of students should use the system at the same time. Jenkins supports remote build agents: The actual compilation of the students submissions happens on these other machines but the whole process is transparent to Artemis.

This guide explains setting up a remote agent on an Ubuntu virtual machine that supports docker builds.

Prerequisites: 1. Install Docker on the remote machine: https://docs.docker.com/engine/install/ubuntu/

  1. Add a new user to the remote machine that Jenkins will use: sudo adduser --disabled-password --gecos "" jenkins

  2. Add the jenkins user to the docker group (This allows the jenkins user to interact with docker): sudo usermod -a -G docker jenkins

  3. Generate a new SSH key locally (e.g. using ssh-keygen) and add the public key to the .ssh/authorized_keys file of the jenkins user on the agent VM.

  4. Validate that you can connect to the build agent machine using SSH and the generated private key and validate that you can use docker (docker ps should not show an error)

  5. Log in with your normal account on the build agent machine and install Java: sudo apt install default-jre

  6. Add a new secret in Jenkins, enter private key you just generated and add the passphrase, if set:

    _images/jenkins_ssh_credentials.png

    Jenkins SSH Credentials

  7. Add a new node (select a name and select Permanent Agent):

    Set the number of executors so that it matches your machine’s specs: This is the number of concurrent builds this agent can handle. It is recommended to match the number of cores of the machine, but you might want to adjust this later if needed.

    Set the remote root directory to /home/jenkins/remote_agent.

    Set the usage to Only build jobs with label expressions matching this node. This ensures that only docker-jobs will be built on this agent, and not other jobs.

    Add a label docker to the agent.

    Set the launch method to Launch via SSH and add the host of the machine. Select the credentials you just created and select Manually trusted key Verification Strategy as Host key verification Strategy. Save it.

    _images/jenkins_node.png

    Add a Jenkins node

  8. Wait for some moments while jenkins installs it’s remote agent on the agent’s machine.

    You can track the progress using the Log page when selecting the agent. System information should also be available.

  9. Change the settings of the master node to be used only for specific jobs. This ensures that the docker tasks are not executed on the master agent but on the remote agent.

_images/jenkins_master_node.png

Adjust Jenkins master node settings

  1. You are finished, the new agent should now also process builds.

Caching

You can configure caching for e.g. Maven repositories. See Adjustments for programming exercises for more details.

Upgrade Jenkins

Build the latest version of the jenkins-artemis Docker image, stop the running container and mount the Jenkins data volume to the new LTS container. Make sure to perform this command in the folder where the Dockerfile was created (e.g. /opt/jenkins/):

docker stop jenkins
docker rename jenkins jenkins_old
docker build --no-cache -t jenkins-artemis .

Now start a new Jenkins container just as described in Start-Jenkins.

Jenkins should be up and running again. If there are no issues, you can delete the old container using docker rm jenkins_old and the old image (see docker images) using docker rmi <old-image-id>. You can also remove all old images using docker image prune -a

You should also update the Jenkins plugins regularly due to security reasons. You can update them directly in the Web User Interface in the Plugin Manager.

Separate NGINX Configurations

There are some placeholders in the following configurations. Replace them with your setup specific values ### GitLab

server {
    listen 443 ssl http2;
    server_name your.gitlab.domain;
    ssl_session_cache shared:GitLabSSL:10m;
    include /etc/nginx/common/common_ssl.conf;
    add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload";
    add_header X-Frame-Options DENY;
    add_header Referrer-Policy same-origin;
    client_max_body_size 10m;
    client_body_buffer_size 1m;

    location / {
        proxy_pass              http://localhost:<your exposed GitLab HTTP port (default 80)>;
        proxy_read_timeout      300;
        proxy_connect_timeout   300;
        proxy_http_version      1.1;
        proxy_redirect          http://         https://;

        proxy_set_header    Host                $http_host;
        proxy_set_header    X-Real-IP           $remote_addr;
        proxy_set_header    X-Forwarded-For     $proxy_add_x_forwarded_for;
        proxy_set_header    X-Forwarded-Proto   $scheme;

        gzip off;
    }
}
Jenkins
server {
    listen 443 ssl http2;
    server_name your.jenkins.domain;
    ssl_session_cache shared:JenkinsSSL:10m;
    include /etc/nginx/common/common_ssl.conf;
    add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload";
    add_header X-Frame-Options DENY;
    add_header Referrer-Policy same-origin;
    client_max_body_size 10m;
    client_body_buffer_size 1m;

    location / {
        proxy_pass              http://localhost:<your exposed Jenkins HTTP port (default 8081)>;
        proxy_set_header        Host                $host:$server_port;
        proxy_set_header        X-Real-IP           $remote_addr;
        proxy_set_header        X-Forwarded-For     $proxy_add_x_forwarded_for;
        proxy_set_header        X-Forwarded-Proto   $scheme;
        proxy_redirect          http://             https://;

        # Required for new HTTP-based CLI
        proxy_http_version 1.1;
        proxy_request_buffering off;
        proxy_buffering off; # Required for HTTP-based CLI to work over SSL

        # workaround for https://issues.jenkins-ci.org/browse/JENKINS-45651
        add_header 'X-SSH-Endpoint' 'your.jenkins.domain.com:50022' always;
    }

    error_page 502 /502.html;
    location /502.html {
        root /usr/share/nginx/html;
        internal;
    }
}
/etc/nginx/common/common_ssl.conf

If you haven’t done so, generate the DH param file: sudo openssl dhparam -out /etc/nginx/dhparam.pem 4096

ssl_certificate     <path to your fullchain certificate>;
ssl_certificate_key <path to the private key of your certificate>;
ssl_protocols       TLSv1.2 TLSv1.3;
ssl_dhparam /etc/nginx/dhparam.pem;
ssl_prefer_server_ciphers   on;
ssl_ciphers ECDH+CHACHA20:EECDH+AESGCM:EDH+AESGCM:!AES128;
ssl_ecdh_curve secp384r1;
ssl_session_timeout  10m;
ssl_session_cache shared:SSL:10m;
ssl_session_tickets off;
ssl_stapling on;
ssl_stapling_verify on;
resolver <if you have any, specify them here> valid=300s;
resolver_timeout 5s;

#Deployment Artemis / GitLab / Jenkins using Docker on Local machine

Execute the following steps in addition to the ones described above:

Preparation
  1. Create a Docker network named “artemis” with docker network create artemis

Gitlab
  1. Add the Gitlab container to the created network with docker network connect artemis gitlab

  2. Get the URL of the Gitlab container with the first IP returned by docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' gitlab

  3. Use this IP in the application-artemis.yml file at artemis.version-control.url

Jenkins
  1. Add the Jenkins container to the created network with docker network connect artemis jenkins

  2. Get the URL of the Gitlab container with the first IP returned by docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' jenkins

  3. Use this IP in the application-artemis.yml file at artemis.continuous-integration.url

Artemis
  1. In docker-compose.yml

    1. Make sure to use unique ports, e.g. 8080 for Artemis, 8081 for Gitlab and 8082 for Jenkins

    2. Change the SPRING_PROFILES_ACTIVE to dev,jenkins,gitlab,artemis

  2. In src/main/resources/config/application-dev.yml

    1. At spring.profiles.active: add & gitlab & jenkins

    2. At spring.liquibase: add the new property change-log: classpath:config/liquibase/master.xml

    3. At server: use port 8080 for Artemis

  3. Run docker-compose up

  4. After the container has been deployed run docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' artemis_artemis-server and copy the first resulting IP.

  5. In src/main/resources/config/application-dev.yml at server: at url: paste the copied IP

  6. Stop the Artemis docker container with Control-C and re-run docker-compose up

Setup of Artemis with multiple instances
Setup with one instance

Artemis usually runs with one instance of the application server:

_images/deployment_before.drawio.png
Setup with multiple instances

There are certain scenarios, where a setup with multiple instances of the application server is required. This can e.g. be due to special requirements regarding fault tolerance or performance.

Artemis also supports this setup (which is also used at the Chair for Applied Software Engineering at TUM).

Multiple instances of the application server are used to distribute the load:

_images/deployment_after_simple.drawio.png

A load balancer (typically a reverse proxy such as nginx) is added, that distributes the requests to the different instances.

Note: This documentation focuses on the practical setup of this distributed setup. More details regarding the theoretical aspects can be found in the Bachelor’s Thesis Securing and Scaling Artemis WebSocket Architecture, which can be found here: pdf.

Additional synchronization

All instances of the application server use the same database, but other parts of the system also have to be synchronized:

  1. Database cache

  2. WebSocket messages

  3. File system

Each of these three aspects is synchronized using a different solution

Database cache

Artemis uses a cache provider that supports distributed caching: Hazelcast.

All instances of Artemis form a so-called cluster that allows them to synchronize their cache. You can use the configuration argument spring.hazelcast.interface to configure the interface on which Hazelcast will listen.

_images/deployment_hazelcast.drawio.png

One problem that arises with a distributed setup is that all instances have to know each other in order to create this cluster. This is problematic if the instances change dynamically. Artemis uses a discovery service to solve the issue (named JHipster Registry).

Disovery service

JHipster registry contains Eureka, the discovery service where all instances can register themselves and fetch the other registered instances.

Eureka can be configured like this within Artemis:

# Eureka configuration
eureka:
    client:
        enabled: true
        service-url:
            defaultZone: {{ artemis_eureka_urls }}
instance:
    prefer-ip-address: true
    ip-address: {{ artemis_ip_address }}
    appname: Artemis
    instanceId: Artemis:{{ artemis_eureka_instance_id }}

logging:
    file:
        name: '/opt/artemis/artemis.log'

{{ artemis_eureka_urls }} must be the URL where Eureka is reachable, {{ artemis_ip_address }} must be the IP under which this instance is reachable and {{ artemis_eureka_instance_id }} must be a unique identifier for this instance. You also have to setup the value jhipster.registry.password to the password of the registry (which you will set later).

Setup

Installing

  1. Create the directory

sudo mkdir /opt/registry/
sudo mkdir /opt/registry/config-server
  1. Download the application

Download the latest version of the jhipster-registry from GitHub, e.g. by using

sudo wget -O /opt/registry/registry.jar https://github.com/jhipster/jhipster-registry/releases/download/v6.2.0/jhipster-registry-6.2.0.jar

Service configuration

  1. sudo vim /etc/systemd/system/registry.service

[Unit]
Description=Registry
After=syslog.target

[Service]
User=artemis
WorkingDirectory=/opt/registry
ExecStart=/usr/bin/java \
    -Xmx256m \
    -jar registry.jar \
    --spring.profiles.active=prod,native
SuccessExitStatus=143
StandardOutput=/opt/registry/registry.log
#StandardError=inherit

[Install]
WantedBy=multi-user.target
  1. Set Permissions in Registry Folder

sudo chown -R artemis:artemis /opt/registry
sudo chmod g+rwx /opt/registry
  1. Enable the service

sudo systemctl daemon-reload
sudo systemctl enable registry.service
  1. Start Service (only after performing steps 1-3 of the configuration)

sudo systemctl start registry
  1. Logging

sudo journalctl -f -n 1000 -u registry

Configuration

  1. sudo vim /opt/registry/application-prod.yml

logging:
    file:
        name: '/opt/registry/registry.log'

jhipster:
    security:
        authentication:
        jwt:
            base64-secret: THE-SAME-TOKEN-THAT-IS-USED-ON-THE-ARTEMIS-INSTANCES
    registry:
        password: AN-ADMIN-PASSWORD-THAT-MUST-BE-CHANGED
spring:
    security:
        user:
            password: AN-ADMIN-PASSWORD-THAT-MUST-BE-CHANGED
  1. sudo vim /opt/registry/bootstrap-prod.yml

jhipster:
    security:
        authentication:
        jwt:
            base64-secret: THE-SAME-TOKEN-THAT-IS-USED-ON-THE-ARTEMIS-INSTANCES
            secret: ''

spring:
    cloud:
        config:
        server:
            bootstrap: true
            composite:
            - type: native
              search-locations: file:./config-server
  1. sudo vim /opt/registry/config-server/application.yml

# Common configuration shared between all applications
configserver:
    name: Artemis JHipster Registry
    status: Connected to the Artemis JHipster Registry

jhipster:
    security:
        authentication:
        jwt:
            secret: ''
            base64-secret: THE-SAME-TOKEN-THAT-IS-USED-ON-THE-ARTEMIS-INSTANCES

eureka:
    client:
        service-url:
            defaultZone: http://admin:${jhipster.registry.password}@localhost:8761/eureka/

nginx config

You still have to make the registry available:

  1. sudo vim /etc/nginx/sites-available/registry.conf

server {
    listen 443 ssl http2;
    server_name REGISTRY_FQDN;
    ssl_session_cache shared:RegistrySSL:10m;
    include /etc/nginx/common/common_ssl.conf;
    add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload";
    add_header X-Frame-Options DENY;
    add_header Referrer-Policy same-origin;
    client_max_body_size 10m;
    client_body_buffer_size 1m;

    location / {
        proxy_pass              http://localhost:8761;
        proxy_read_timeout      300;
        proxy_connect_timeout   300;
        proxy_http_version      1.1;
        proxy_redirect          http://         https://;

        proxy_set_header    Host                $http_host;
        proxy_set_header    X-Real-IP           $remote_addr;
        proxy_set_header    X-Forwarded-For     $proxy_add_x_forwarded_for;
        proxy_set_header    X-Forwarded-Proto   $scheme;

        gzip off;
    }
}
  1. sudo ln -s /etc/nginx/sites-available/registry.conf /etc/nginx/sites-enabled/

This enables the registry in nginx

  1. sudo service nginx restart

This will apply the config changes and the registry will be reachable.

WebSockets

WebSockets should also be synchronized (so that a user connected to one instance can perform an action which causes an update to users on different instances, without having to reload the page - such as quiz starts). We use a so-called broker for this (named Apache ActiveMQ Artemis).

It relays message between instances:

_images/deployment_broker.drawio.png

Setup

  1. Create a folder to store ActiveMQ

sudo mkdir /opt/activemq-distribution
  1. Download ActiveMQ here: http://activemq.apache.org/components/artemis/download/

sudo wget -O /opt/activemq-distribution/activemq.tar.gz https://downloads.apache.org/activemq/activemq-artemis/2.13.0/apache-artemis-2.13.0-bin.tar.gz
  1. Extract the downloaded contents

cd /opt/activemq-distribution
sudo tar -xf activemq.tar.gz
  1. Navigate to the folder with the CLI

cd /opt/activemq-distribution/apache-artemis-2.13.0/bin
  1. Create a broker in the /opt/broker/broker1 directory, replace USERNAME and PASSWORD accordingly

sudo ./artemis create --user USERNAME --password PASSWORD --require-login /opt/broker/broker1
  1. Adjust the permissions

sudo chown -R artemis:artemis /opt/broker
sudo chmod g+rwx /opt/broker
  1. Adjust the configuration of the broker: sudo vim /opt/broker/broker1/etc/broker.xml

<?xml version='1.0'?>
<configuration xmlns="urn:activemq"
            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
            xmlns:xi="http://www.w3.org/2001/XInclude"
            xsi:schemaLocation="urn:activemq /schema/artemis-configuration.xsd">

<core xmlns="urn:activemq:core" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="urn:activemq:core ">

    <name>0.0.0.0</name>

    <journal-pool-files>10</journal-pool-files>

    <acceptors>
        <!-- STOMP Acceptor. -->
        <acceptor name="stomp">tcp://0.0.0.0:61613?tcpSendBufferSize=1048576;tcpReceiveBufferSize=1048576;protocols=STOMP;useEpoll=true;heartBeatToConnectionTtlModifier=6</acceptor>
    </acceptors>

    <connectors>
        <connector name="netty-connector">tcp://localhost:61616</connector>
    </connectors>

    <security-settings>
        <security-setting match="#">
            <permission type="createNonDurableQueue" roles="amq"/>
            <permission type="deleteNonDurableQueue" roles="amq"/>
            <permission type="createDurableQueue" roles="amq"/>
            <permission type="deleteDurableQueue" roles="amq"/>
            <permission type="createAddress" roles="amq"/>
            <permission type="deleteAddress" roles="amq"/>
            <permission type="consume" roles="amq"/>
            <permission type="browse" roles="amq"/>
            <permission type="send" roles="amq"/>
            <!-- we need this otherwise ./artemis data imp wouldn't work -->
            <permission type="manage" roles="amq"/>
        </security-setting>
    </security-settings>

    <address-settings>
        <!--default for catch all-->
        <address-setting match="#">
            <dead-letter-address>DLQ</dead-letter-address>
            <expiry-address>ExpiryQueue</expiry-address>
            <redelivery-delay>0</redelivery-delay>
            <!-- with -1 only the global-max-size is in use for limiting -->
            <max-size-bytes>-1</max-size-bytes>
            <message-counter-history-day-limit>10</message-counter-history-day-limit>
            <address-full-policy>PAGE</address-full-policy>
            <auto-create-queues>true</auto-create-queues>
            <auto-create-addresses>true</auto-create-addresses>
            <auto-create-jms-queues>true</auto-create-jms-queues>
            <auto-create-jms-topics>true</auto-create-jms-topics>
        </address-setting>
    </address-settings>
</core>
</configuration>
  1. Service configuration: sudo vim /etc/systemd/system/broker1.service

[Unit]
Description=ActiveMQ-Broker
After=network.target

[Service]
User=artemis
WorkingDirectory=/opt/broker/broker1
ExecStart=/opt/broker/broker1/bin/artemis run


[Install]
WantedBy=multi-user.target
  1. Enable the service

sudo systemctl daemon-reload
sudo systemctl enable broker1
sudo systemctl start broker1

Configuration of Artemis

Add the following values to your Artemis config:

spring:
    websocket:
        broker:
            username: USERNAME
            password: PASSWORD
            addresses: "localhost:61613"

USERNAME and PASSWORD are the values used in step 5. Replace localhost if the broker runs on a separate machine.

File system

The last (and also easiest) part to configure is the file system: You have to provide a folder that is shared between all instances of the application server (e.g. by using NFS).

You then have to set the following values in the application config:

artemis:
    repo-clone-path: {{ artemis_repo_basepath }}/repos/
    repo-download-clone-path: {{ artemis_repo_basepath }}/repos-download/
    file-upload-path: {{ artemis_repo_basepath }}/uploads
    submission-export-path: {{ artemis_repo_basepath }}/exports

Where {{ artemis_repo_basepath }} is the path to the shared folder

The file system stores (as its names suggests) files, these are e.g. submissions to file upload exercises, repositories that are checked out for the online editor, course icons, etc.

Scheduling

Artemis uses scheduled tasks in various scenarios: e.g. to lock repositories on due date, clean up unused resources, etc. As we now run multiple instances of Artemis, we have to ensure that the scheduled tasks are not executed multiple times. Artemis uses to approaches for this:

  1. Tasks for quizzes (e.g. evaluation once the quiz is due) are automatically distributed (using Hazelcast)

  2. Tasks for other exercises are only scheduled on one instance:

You must add the Scheduling profile to exactly one instance of your cluster. This instance will then perform scheduled tasks whereas the other instances will not.

nginx configuration

You have to change the nginx configuration (of Artemis) to ensure that the load is distributed between all instances. This can be done by defining an upstream (containing all instances) and forwarding all requests to this upstream.

upstream artemis {
    server instance1:8080;
    server instance2:8080;
}
Overview

All instances can now communicate with each other on 3 different layers:

  • Database cache

  • WebSockets

  • File system

You can see the state of all connected instances within the registry:

It relays message between instances:

_images/registry.png
Adjustments for programming exercises

There are several variables that can be configured when using programming exercises. They are presented in this separate page to keep the ‘normal’ setup guide shorter.

Path variables

There are variables for several paths:

  • artemis.repo-clone-path

    Repositories that the Artemis server needs are stored in this folder. This e.g. affects repositories from students which use the online code editor or the template/solution repositories of new exercises, as they are pushed to the VCS after modification.

    Files in this directory are usually not critical, as the latest pushed version of these repositories are also stored at the VCS. However, changed that are saved in the online code editor but not yet commited will be lost when this folder is deleted.

  • artemis.repo-download-clone-path

    Repositories that were downloaded from Artemis are stored in this directory.

    Files in this directory can be removed without loss of data, if the downloaded repositories are still present at the VCS. No changes to the data in the VCS are stored in this directory (or they can be retrieved by performing the download-action again).

  • artemis.template-path

    Templates are available within Artemis. The templates should fit to most environments, but there might be cases where one wants to change the templates.

    This value specifies the path to the templates which should overwrite the default ones. Note that this is the path to the folder where the templates folder is located, not the path to the templates folder itself.

Templates

Templates are shipped with Artemis (they can be found within the src/main/resources/templates folder in Github). These templates should fit well for many deployments, but one might want to change some of them for special deployments.

As of now, you can overwrite the jenkins folders that is present within the src/main/resources/templates folder. Files that are present in the file system will be used, if a file is not present in the file system, it is loaded from the classpath (e.g. the .war archive).

We plan to make other folders configurable as well, but this is not supported yet.

Jenkins template

The build process in Jenkins is stored in a config.xml-file (src/main/resources/templates/jenkins) that shares common steps for all programming languages (e.g. triggering a build when a push to GitLab occurred). It is extended by a Jenkinsfile that is dependent on the used programming language which will be included in the generic config.xml file. The builds steps (including used docker images, the checkout process, the actual build steps, and the reporting of the results to Artemis) is included in the Jenkinsfile.

A sample Jenkinsfile can be found at src/main/resources/templates/jenkins/java/Jenkinsfile. Note that the Jenkinsfile must start either

  • with pipeline (there must not be a comment before pipeline, but there can be one at any other position, if the Jenkinsfile-syntax allows it)

  • or the special comment // ARTEMIS: JenkinsPipeline in the first line.

The variables #dockerImage, #testRepository, #assignmentRepository, #jenkinsNotificationToken and #notificationsUrl will automatically be replaced (for the normal Jenkinsfile, within the Jenkinsfile-staticCodeAnalysis, #staticCodeAnalysisScript is also replaced).

You should not need to touch any of these variables, except the #dockerImage variable, if you want to use a different agent setup (e.g. a Kubernetes setup).

Caching example for Maven

The Docker image used to run the maven-tests already contains a set of commonly used dependencies (see artemis-maven-docker). This significantly speeds up builds as the dependencies do not have to be downloaded every time a build is started. However, the dependencies included in the Docker image might not match the dependencies required in your tests (e.g. because you added new dependencies or the Docker image is outdated).

You can cache the maven-dependencies also on the machine that runs the builds (that means, outside the docker container) using the following steps:

Adjust the agent-args and add the environment block.

agent {
    docker {
        image '#dockerImage'
        label 'docker'
        args '-v $HOME/maven-cache-docker:/var/maven'
    }
}
environment {
  JAVA_TOOL_OPTIONS = '-Duser.home=/var/maven'
}
stages {
    stage('Checkout') {

You have to add permissions to the folder (which will be located at the $HOME folder of the user that jenkins uses), e.g. with sudo chmod 777 maven-cache-docker -R.

Note that this might allow students to access shared resources (e.g. jars used by Maven), and they might be able to overwrite them. You can use Artemis-java-testing-sandbox to prevent this by restricting the resources the student’s code can access.

Note

Be careful that you don’t commit changes to application-artemis.yml. To avoid this, follow the best practice when configuring your local development environment:

  1. Create a file named application-local.yml under src/main/resources/config.

  2. Copy the contents of application-artemis.yml into the new file.

  3. Update configuration values in application-local.yml.

By default, changes to application-local.yml will be ignored by git so you don’t accidentally share your credentials or other local configuration options.

If you use a password, you need to adapt it in gradle/liquibase.gradle.

Run the server via a run configuration in IntelliJ

The project comes with some pre-configured run / debug configurations that are stored in the .idea directory. When you import the project into IntelliJ the run configurations will also be imported.

The recommended way is to run the server and the client separated. This provides fast rebuilds of the server and hot module replacement in the client.

  • Artemis (Server): The server will be started separated from the client. The startup time decreases significantly.

  • Artemis (Client): Will execute yarn install and yarn start. The client will be available at http://localhost:9000/ with hot module replacement enabled (also see Client Setup).

Other run / debug configurations
  • Artemis (Server & Client): Will start the server and the client. The client will be available at http://localhost:8080/ with hot module replacement disabled.

  • Artemis (Server, Jenkins & Gitlab): The server will be started separated from the client with the profiles dev,jenkins,gitlab,artemis instead of dev,bamboo,bitbucket,jira,artemis.

  • Artemis (Server, Athene): The server will be started separated from the client with athene profile enabled (see Athene Service).

Typical problems with Liquibase checksums

One typical problem in the development setup is that an exception occurs during the database initialization. Artemis uses Liquibase to automatically upgrade the database scheme after changes to the data model. This ensures that the changes can also be applied to the production server. In case you encounter errors with liquibase checksum values, run the following command in your terminal / command line:

./gradlew liquibaseClearChecksums
Run the server with Spring Boot and Spring profiles

The Artemis server should startup by running the main class de.tum.in.www1.artemis.ArtemisApp using Spring Boot.

Note

Artemis uses Spring profiles to segregate parts of the application configuration and make it only available in certain environments. For development purposes, the following program arguments can be used to enable the dev profile and the profiles for JIRA, Bitbucket and Bamboo:

--spring.profiles.active=dev,bamboo,bitbucket,jira,artemis,scheduling

If you use IntelliJ (Community or Ultimate) you can set the active profiles by

  • Choosing Run | Edit Configurations...

  • Going to the Configuration Tab

  • Expanding the Environment section to reveal VM Options and setting them to -Dspring.profiles.active=dev,bamboo,bitbucket,jira,artemis,scheduling

Set Spring profiles with IntelliJ Ultimate

If you use IntelliJ Ultimate, add the following entry to the section Active Profiles (within Spring Boot) in the server run configuration:

dev,bamboo,bitbucket,jira,artemis,scheduling
Run the server with the command line (Gradle wrapper)

If you want to run the application via the command line instead, make sure to pass the active profiles to the gradlew command like this:

./gradlew bootRun --args='--spring.profiles.active=dev,bamboo,bitbucket,jira,artemis,scheduling'

As an alternative, you might want to use Jenkins and Gitlab with an internal user management in Artemis, then you would use the profiles:

dev,jenkins,gitlab,artemis,scheduling

Client Setup

You need to install Node and Yarn on your local machine.

Using IntelliJ

If you are using IntelliJ you can use the pre-configured Artemis (Client) run configuration that will be delivered with this repository:

  • Choose Run | Edit Configurations...

  • Select the Artemis (Client) configuration from the npm section

  • Now you can run the configuration in the upper right corner of IntelliJ

Using the command line

You should be able to run the following command to install development tools and dependencies. You will only need to run this command when dependencies change in package.json.

yarn install

To start the client application in the browser, use the following command:

yarn start

This compiles TypeScript code to JavaScript code, starts the hot module replacement feature in Webpack (i.e. whenever you change a TypeScript file and save, the client is automatically reloaded with the new code) and will start the client application in your browser on http://localhost:9000. If you have activated the JIRA profile (see above in Server Setup) and if you have configured application-artemis.yml correctly, then you should be able to login with your TUM Online account.

For more information, review Working with Angular. For further instructions on how to develop with JHipster, have a look at Using JHipster in development.

Customize your Artemis instance

You can define the following custom assets for Artemis to be used instead of the TUM defaults:

  • The logo next to the “Artemis” heading on the navbar → ${artemisRunDirectory}/public/images/logo.png

  • The favicon → ${artemisRunDirectory}/public/images/favicon.ico

  • The privacy statement HTML → ${artemisRunDirectory}/public/content/privacy_statement.html

  • The contact email address in the application-{dev,prod}.yml configuration file under the key info.contact

  • The imprint link in the application-{dev,prod}.yml configuration file under the key info.imprint

Alternative: Using docker-compose

A full functioning development environment can also be set up using docker-compose:

  1. Install docker and docker-compose

  2. Configure the credentials in application-artemis.yml in the folder src/main/resources/config as described above

  3. Run docker-compose up

  4. Go to http://localhost:9000

The client and the server will run in different containers. As yarn is used with its live reload mode to build and run the client, any change in the client’s codebase will trigger a rebuild automatically. In case of changes in the codebase of the server one has to restart the artemis-server container via docker-compose restart artemis-server.

(Native) Running and Debugging from IDEs is currently not supported.

Get a shell into the containers:
  • app container: docker exec -it $(docker-compose ps -q artemis-app) sh

  • mysql container: docker exec -it $(docker-compose ps -q artemis-mysql) mysql

Other useful commands:
  • Stop the server: docker-compose stop artemis-server (restart via docker-compose start artemis-server)

  • Stop the client: docker-compose stop artemis-client (restart via docker-compose start artemis-client)

Athene Service

The semi-automatic text assessment relies on the Athene service. To enable automatic text assessments, special configuration is required:

Enable the athene Spring profile:
--spring.profiles.active=dev,bamboo,bitbucket,jira,artemis,scheduling,athene
Configure API Endpoints:

The Athene service is running on a dedicated machine and is adressed via HTTP. We need to extend the configuration in the file src/main/resources/config/application-artemis.yml like so:

artemis:
  # ...
  athene:
    submit-url: http://localhost/submit
    feedback-consistency-url: http://localhost:8001/feedback_consistency
    base64-secret: YWVuaXF1YWRpNWNlaXJpNmFlbTZkb283dXphaVF1b29oM3J1MWNoYWlyNHRoZWUzb2huZ2FpM211bGVlM0VpcAo=
    token-validity-in-seconds: 10800

Coding and design guidelines

Server

WORK IN PROGRESS

0. Folder structure

The main application is stored under /src/main and the main folders are:

  • resources - script, config files and templates are stored here.
    • config - different configurations (production, development, etc.) for application.
      • liquibase - contains master.xml file where all the changelogs from the changelog folder are specified.

        When you want to do some changes to the database, you will need to add a new changelog file here. To understand how to create new changelog file you can check existing changelog files or read documentation: https://www.liquibase.org/documentation/databasechangelog.html.

  • java - Artemis Spring Boot application is located here. It contains the following folders:
    • config - different classes for configuring database, Sentry, Liquibase, etc.

    • domain - all the entities and data classes are located here (the model of the server application).

    • exception - store custom types of exceptions here. We encourage to create custom exceptions to help other developers understand what problem exactly happened.

      This can also be helpful when we want to provide specific exception handling logic.

    • security - contains different POJOs (simple classes that don’t implement/extend any interface/class and don’t have annotations) and component classes related to security.

    • repository - used to access or change objects in the database. There are several techniques to query database: named queries, queries with SpEL expressions and Entity Graphs.

    • service - represents the controller of the server application. Add the application logic here. Retrieve and change objects using repositories.

    • web - contains two folders:
      • rest - contains REST controllers that act as the view of the server application. Validate input and security here, but do not include complex application logic

      • websocket - contains controllers that handle real-time communication with the client based on the Websocket protocol. Use the MessagingTemplate to push data to the client or to notify the client about events.

1. Naming convention

All variables, methods and classes should use CamelCase style. The only difference: the first letter of any class should be capital. Most importantly use intention-revealing, pronounceable names.

2. Single responsibility principle

One method should be responsible for only one action, it should do it well and do nothing else. Reduce coupling, if our method does two or three different things at a time then we should consider splitting the functionality.

3. Small methods

There is no standard pattern for method length among the developers. Someone can say 5, in some cases even 20 lines of code is okay. Just try to make methods as small as possible.

4. Duplication

Avoid code duplication. If we cannot reuse a method elsewhere, then the method is probably bad and we should consider a better way to write this method. Use Abstraction to abstract common things in one place.

5. Variables and methods declaration
  • Encapsulate the code you feel might change in future.

  • Make variables and methods private by default and increase access step by step by changing them from a private to package-private or protected first and not public right away.

  • Classes, methods or functions should be open for extension and closed for modification (open closed design principle).

  • Program for the interface and not for implementation, you should use interface type on variables, return types of a method or argument type of methods. Just like using SuperClass type to store object rather using SubClass.

  • The use of interface is to facilitate polymorphism, a client should not implement an interface method if its not needed.

6. Structure your code correctly
  • Default packages are not allowed. It can cause particular problems for Spring Boot applications that use the @ComponentScan, @EntityScan or @SpringBootApplication annotations since every class from every jar is read.

  • All variables in the class should be declared at the top of the class.

  • If a variable is used only in one method then it would be better to declare it as a local variable of this method.

  • Methods should be declared in the same order as they are used (from top to bottom).

  • More important methods should be declared at the top of a class and minor methods at the end.

7. Database
  • Write performant queries that can also deal with more than 1000 objects in a reasonable time.

  • Prefer one query that fetches additional data instead of many small queries, but don’t overdo it. A good rule of thumb is to query not more than 3 associations at the same time.

  • Think about lazy vs. eager fetching when modeling the data types.

  • Simple datatypes: immediately think about whether null should be supported as additional state or not. In most cases it is preferable to avoid null.

  • Use Timestamp instead of Datetime.

8. Comments

Only write comments for complicated algorithms, to help other developers better understand them. We should only add a comment, if our code is not self-explanatory.

9. Utility

Utility methods can and should be placed in a class named for specific functionality, not “miscellaneous stuff related to project”. Most of the time, our static methods belong in a related class.

10. Auto configuration

Spring Boot favors Java-based configuration. Although it is possible to use Sprint Boot with XML sources, it is generally not recommended. You don’t have to put all your @Configuration into a single class. The @Import annotation can be used to import additional configuration classes. One of the flagship features of Spring Boot is its use of Auto-configuration. This is the part of Spring Boot that makes your code simply work. It gets activated when a particular jar file is detected on the classpath. The simplest way to make use of it is to rely on the Spring Boot Starters.

11. Keep your @RestController’s clean and focused
  • RestControllers should be stateless.

  • RestControllers are by default singletons.

  • RestControllers should not execute business logic but rely on delegation.

  • RestControllers should deal with the HTTP layer of the application.

  • RestControllers should be oriented around a use-case/business-capability.

Route naming conventions:

  • Always use kebab-case (e.g. “/exampleAssessment” → “/example-assessment”).

  • The routes should follow the general structure entity > entityId > sub-entity … (e.g. “/exercises/{exerciseId}/participations”).

  • Use plural for a route’s entities.

  • Specify the key entity at the end of the route (e.g. “text-editor/participations/{participationId}” should be changed to “participations/{participationId}/text-editor”).

  • Use consistent routes that start with courses, exercises or lectures to simplify access control. Do not start routes with other entity names.

Additional notes on the controller methods:

  • POST should return the newly created entity

  • Verify that API endpoints perform appropriate authorization and authentication consistent with the rest of the code base.
    • Always use @PreAuthorize to only allow certain roles to access the method.

    • Perform additional security checks using the AuthorizationCheckService.

  • Check for other common weaknesses, e.g., weak configuration, malicious user input, missing log events, etc.

  • Never trust user input and check if the passed data exists in the database.

  • Handle exceptions and errors with a standard response. Errors are very important in REST APIs. They inform clients that something went wrong, after all.

  • Always use different response status codes to notify the client about errors on the server, e.g.:
    • Forbidden - the user is not authorized to access the controller.

    • Bad Request - the request was wrong.

    • Not Found - can’t find the requested data or it should be not accessible yet.

12. Dependency injection
  • Some of you may argue with this, but by favoring constructor injection you can keep your business logic free from Spring. Not only is the @Autowired annotation optional on constructors, you also get the benefit of being able to easily instantiate your bean without Spring.

  • Use setter based DI only for optional dependencies.

  • Avoid circular dependencies, try constructor and setter based DI for such cases.

13. Keep it simple and stupid
  • Don’t write complex code.

  • Don’t write code when you are tired or in a bad mood.

  • Optimization vs Readability: always write code that is simple to read and which will be understandable for developers. Because the time and resources spent on hard-to-read code cost much more than what we gain through optimization

  • Commit messages should describe both what the commit changes and how it does it.

  • ARCHITECTURE FIRST: writing code without thinking of the system’s architecture is useless, in the same way as dreaming about your desires without a plan of achieving them.

14. General best practices
  • Always use the least possible access level, prefer using private over public access modifier (package-private or protected can be used as well).

  • Previously we used transactions very randomly, now we want to avoid using Transactional. Transactions can kill performance, introduce locking issues and database concurrency problems, and add complexity to our application. Good read: https://codete.com/blog/5-common-spring-transactional-pitfalls/

  • Define a constant if the same value is used more than once. Constants allow you to change code later a lot easier. Instead of looking for the places where this variable was used, you only need to change it in only one place.

  • Facilitate code reuse. Always move duplicated code to reusable methods. IntelliJ is very good at suggesting duplicated lines and even automatically extracting them. Also don’t be shy to use Generics.

  • Always qualify a static class member reference with its class name and not with a reference or expression of that class’s type.

  • Prefer using primitive types to classes, e.g. long instead of Long.

  • Use ./gradlew spotlessCheck and ./gradlew spotlessApply to check Java code style and to automatically fix it.

Some parts of these guidelines are adapted from https://medium.com/@madhupathy/ultimate-clean-code-guide-for-java-spring-based-applications-4d4c9095cc2a

Client

WORK IN PROGRESS

0. General

The Artemis client is an Angular project. Keep https://angular.io/guide/styleguide in mind.

Some general aspects:

  • Never invoke methods from the html template. The automatic change tracking in Angular will kill the application performance

  • The Artemis client uses lazy loading to keep the initial bundle size below 2 MB.

  • Code quality and test coverage are important. Try to reuse code and avoid code duplication. Write meaningful tests!

1. Names
  1. Use PascalCase for type names.

  2. Do not use “I” as a prefix for interface names.

  3. Use PascalCase for enum values.

  4. Use camelCase for function names.

  5. Use camelCase for property names and local variables.

  6. Do not use “_” as a prefix for private properties.

  7. Use whole words in names when possible.

2. Components
  1. 1 file per logical component (e.g. parser, scanner, emitter, checker).

  2. Do not add new files. :)

  3. files with “.generated.*” suffix are auto-generated, do not hand-edit them.

3. Types
  1. Do not export types/functions unless you need to share it across multiple components.

  2. Do not introduce new types/values to the global namespace.

  3. Shared types should be defined in ‘types.ts’.

  4. Within a file, type definitions should come first.

4. null and undefined
  1. Use undefined. Do not use null.

5. General Assumptions
  1. Consider objects like Nodes, Symbols, etc. as immutable outside the component that created them. Do not change them.

  2. Consider arrays as immutable by default after creation.

6. Comments
  1. Use JSDoc style comments for functions, interfaces, enums, and classes.

7. Strings
  1. Use single quotes for strings.

  2. All strings visible to the user need to be localized (make an entry in the corresponding *.json file).

8. Style
  1. Use arrow functions over anonymous function expressions.

  2. Always surround arrow function parameters.

    For example, x => x + x is wrong but the following are correct:

    1. (x) => x + x

    2. (x,y) => x + y

    3. <T>(x: T, y: T) => x === y

  3. Always surround loop and conditional bodies with curly braces. Statements on the same line are allowed to omit braces.

  4. Open curly braces always go on the same line as whatever necessitates them.

  5. Parenthesized constructs should have no surrounding whitespace.

    A single space follows commas, colons, and semicolons in those constructs. For example:

    1. for (var i = 0, n = str.length; i < 10; i++) { }

    2. if (x < 10) { }

    3. function f(x: number, y: string): void { }

  6. Use a single declaration per variable statement (i.e. use var x = 1; var y = 2; over var x = 1, y = 2;).

  7. else goes on the same line from the closing curly brace.

  8. Use 4 spaces per indentation.

We use prettier to style code automatically and eslint to find additional issues. You can find the corresponding commands to invoked those tools in package.json.

9. Testing

If you are new to client testing, it is highly recommended that you work through the testing part of the angular tutorial: https://angular.io/guide/testing

We use Jest (https://jestjs.io/) as our client testing framework.

There are different tools available to support client testing. A common combination you can see in our codebase is:

The most basic test looks similar to this:

import * as chai from 'chai';
import * as sinonChai from 'sinon-chai';
import * as sinon from 'sinon';
import { ComponentFixture, TestBed } from '@angular/core/testing';

chai.use(sinonChai);
const expect = chai.expect;

describe('SomeComponent', () => {
    let someComponentFixture: ComponentFixture<SomeComponent>;
    let someComponent: SomeComponent;

    beforeEach(() => {
        TestBed.configureTestingModule({
            imports: [],
            declarations: [
                SomeComponent,
                MockPipe(SomePipeUsedInTemplate),
                MockComponent(SomeComponentUsedInTemplate),
                MockDirective(SomeDirectiveUsedInTemplate),
            ],
            providers: [
                MockProvider(SomeServiceUsedInComponent),
            ],
            schemas: [],
        })
            .compileComponents()
            .then(() => {
                someComponentFixture = TestBed.createComponent(SomeComponent);
                someComponent = someComponentFixture.componentInstance;
            });
    });

    afterEach(function () {
        sinon.restore();
    });

    it('should initialize', () => {
        someComponentFixture.detectChanges();
        expect(SomeComponent).to.be.ok;
    });
});

Some guidelines:

  1. A component should be tested in isolation without any dependencies if possible. Do not simply import the whole production module. Only import real dependencies if it is essential for the test that the real dependency is used. Instead mock pipes, directives and components that the component under test depends upon. A very useful technique is writing stubs for child components: https://angular.io/guide/testing-components-scenarios#stubbing-unneeded-components. This has the benefit of being able to test the interaction with the child components.

    • Services should be mocked if they simply return some data from the server. However, if the service has some form of logic included (for exampling converting dates to moments), and this logic is important for the component, do not mock the service methods, but mock the http requests and responses from the api. This allows us to test the interaction of the component with the service and in addition test that the service logic works correctly. A good explanation can be found in the official angular documentation: https://angular.io/guide/http#testing-http-requests

    import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
    describe('SomeComponent', () => {
      beforeEach(() => {
          TestBed.configureTestingModule({
              imports: [HttpClientTestingModule],
          });
    
          ...
          httpMock = injector.get(HttpTestingController);
      });
    
      afterEach(() => {
          ...
          httpMock.verify();
      });
    
      it('should make get request', async () => {
          const returnedFromApi = {some: 'data'};
    
          component.callServiceMethod()
              .subscribe((data) => expect(data).toMatchObject({body: returnedFromApi}));
    
          const req = httpMock.expectOne({ method: 'GET' });
          req.flush(JSON.stringify(returnedFromApi));
      });
    });
    
  2. Do not overuse NO_ERRORS_SCHEMA (https://angular.io/guide/testing-components-scenarios#no_errors_schema). This tells angular to ignore the attributes and unrecognized elements, prefer to use component stubs as mentioned above.

  3. When using sinon, use sandboxes (https://sinonjs.org/releases/latest/sandbox/). Sandboxes remove the need to keep track of every fake created, which greatly simplifies cleanup and improves readability. Since sinon@5.0.0, the sinon object is a default sandbox. Unless you have a very advanced setup or need a special configuration, you probably want to only use that one.

  4. Make sure to have at least 80% test coverage. Running yarn test --coverage to create a coverage report. You can also simply run the tests in IntelliJ IDEA with coverage activated.

  5. It is preferable to test a component through the interaction of the user with the template. This decouples the test from the concrete implementation used in the component. For example if you have a component that loads and displays some data when the user clicks a button, you should query for that button, simulate a click and then assert that the data has been loaded and that the expected template changes have occurred.

  6. Do not remove the template during tests. The template is a crucial part of a component and should not be removed during test. Do not do this:

import * as chai from 'chai';
import * as sinonChai from 'sinon-chai';
import * as sinon from 'sinon';

chai.use(sinonChai);
const expect = chai.expect;

describe('SomeComponent', () => {
    let someComponentFixture: ComponentFixture<SomeComponent>;
    let someComponent: SomeComponent;

    beforeEach(() => {
        TestBed.configureTestingModule({
            imports: [],
            declarations: [
                SomeComponent,
            ],
            providers: [
            ],
            schemas: [],
        })
            .overrideTemplate(SomeComponent, '') // DO NOT DO THIS
            .compileComponents()
            .then(() => {
                someComponentFixture = TestBed.createComponent(SomeComponent);
                someComponent = someComponentFixture.componentInstance;
            });
    });
});
10. Preventing Memory Leaks

It is crucial that you try to prevent memory leaks in both your components and your tests.

What are memory leaks?

A very good explanation that you should definitely read to understand the problem: https://auth0.com/blog/four-types-of-leaks-in-your-javascript-code-and-how-to-get-rid-of-them/

In essence:

  • JS is a garbage collected language

  • Modern garbage collectors improve on this algorithm in different ways, but the essence is the same: reachable pieces of memory are marked as such and the rest is considered garbage.

  • Unwanted references are references to pieces of memory that the developer knows he or she won’t be needing anymore but that for some reason are kept inside the tree of an active root. In the context of JavaScript, unwanted references are variables kept somewhere in the code that will not be used anymore and point to a piece of memory that could otherwise be freed.

What are common reasons for memory leaks?

https://auth0.com/blog/four-types-of-leaks-in-your-javascript-code-and-how-to-get-rid-of-them/:

  • Accidental global variables

  • Forgotten timers or callbacks

  • Out of DOM references

  • Closures

https://making.close.com/posts/finding-the-cause-of-a-memory-leak-in-jest Mocks not being restored after the end of a test, especially when it involves global objects.

https://www.twilio.com/blog/prevent-memory-leaks-angular-observable-ngondestroy RXJS subscriptions not being unsubscribed.

What are ways to identify memory leaks?

Number 1: Manually checking the heap usage and identifying heap dumps for causes of memory leaks https://chanind.github.io/javascript/2019/10/12/jest-tests-memory-leak.html

Corresponding commands from the article for our project (enter in the root directory of the project):

node --expose-gc ./node_modules/.bin/jest --runInBand --logHeapUsage --config ./src/test/javascript/jest.config.js --env=jsdom
node --inspect-brk --expose-gc ./node_modules/.bin/jest --runInBand --logHeapUsage --config ./src/test/javascript/jest.config.js --env=jsdom

A live demonstration of this technique to find the reason for memory leaks in the GitLab repository: https://www.youtube.com/watch?v=GOYmouFrGrE

Number 2: Using the experimental leak detection feature from jest

 --detectLeaks **EXPERIMENTAL**: Detect memory leaks in tests.
                                 After executing a test, it will try to garbage collect the global object used,
                                 and fail if it was leaked [boolean] [default: false]

--runInBand, -i Run all tests serially in the current process
  (rather than creating a worker pool of child processes that run tests). This is sometimes useful for debugging, but such use cases are pretty rare.

Navigate into src/test/javascript and run either

jest --detectLeaks --runInBand

or

jest --detectLeaks

Some parts of these guidelines are adapted from https://github.com/microsoft/TypeScript-wiki/blob/master/Coding-guidelines.md

Database Relationships

WORK IN PROGRESS

1. Retrieving and Building Objects

The cost of retrieving and building an object’s relationships far exceeds the cost of selecting the object. This is especially true for relationships where it would trigger the loading of every child through the relationship hierarchy. The solution to this issue is lazy fetching (lazy loading). Lazy fetching allows the fetching of a relationship to be deferred until it is accessed. This is important not only to avoid the database access, but also to avoid the cost of building the objects if they are not needed.

In JPA lazy fetching can be set on any relationship using the fetch attribute. The fetch can be set to either LAZY or EAGER as defined in the FetchType enum. The default fetch type is LAZY for all relationships except for OneToOne and ManyToOne, but in general it is a good idea to make every relationship LAZY. The EAGER default for OneToOne and ManyToOne is for implementation reasons (more easier to implement), not because it is a good idea.

We always use FetchType.LAZY, unless there is a very strong case to be made for FetchType.EAGER.

Note

Additional effort to use FetchType.LAZY does not count as a strong argument.

2. Relationships

A relationship is a reference from one object to another. In a relational database relationships are defined through foreign keys. The source row contains the primary key of the target row to define the relationship (and sometimes the inverse). A query must be performed to read the target objects of the relationship using the foreign key and primary key information. If there is a relationship to a collection of other objects, a Collection or array type is used to hold the contents of the relationship. In a relational database, collection relations are either defined by the target objects having a foreign key back to the source object’s primary key, or by having an intermediate join table to store the relationship (containing both objects’ primary keys).

In this section, we depict common entity relationships we use in Artemis and show some code snippets.

  • OneToOne A unique reference from one object to another. It is also inverse of itself. Example: one Complaint has a reference to one Result.

// Complaint.java
@OneToOne
@JoinColumn(unique = true)
private Result result;
  • OneToMany A Collection or Map of objects. It is the inverse of a ManyToOne relationship. Example: one Result has a list of Feedback elements. For ordered OneToMany relations see ordered collections.

// Result.java
@OneToMany(mappedBy = "result", cascade = CascadeType.ALL, orphanRemoval = true)
@OrderColumn
@JsonIgnoreProperties(value = "result", allowSetters = true)
@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
@JsonView(QuizView.Before.class)
private List<Feedback> feedbacks = new ArrayList<>();
  • ManyToOne A reference from one object to another. It is the inverse of an OneToMany relationship. Example: one Feedback has a reference to one Result.

// Feedback.java
@ManyToOne
@JsonIgnoreProperties("feedbacks")
private Result result;
  • ManyToMany A Collection or Map of objects. It is the inverse of itself. Example: one Exercise has a list of LearningGoal elements, one LearningGoal has list of Exercise elements. In other words: many exercises are connected to many learning goals and vice-versa.

// Exercise.java
@ManyToMany(mappedBy = "exercises")
public Set<LearningGoal> learningGoals = new HashSet<>();

// LearningGoal.java
@ManyToMany
@JoinTable(name = "learning_goal_exercise", joinColumns = @JoinColumn(name = "learning_goal_id", referencedColumnName = "id"), inverseJoinColumns = @JoinColumn(name = "exercise_id", referencedColumnName = "id"))
@JsonIgnoreProperties("learningGoals")
private Set<Exercise> exercises = new HashSet<>();

Warning

For OneToMany, ManyToOne, and ManyToMany relationships you must not forget to mark the associated elements with @JsonIgnoreProperties(). Without this, the object serialization process will be stuck in an endless loop and throw an error. For more information check out the examples listed above and see: Jackson and JsonIgnoreType.

2. Cascade Types

Entity relationships often depend on the existence of another entity — for example, the Result-Feedback relationship. Without the Result, the Feedback entity doesn’t have any meaning of its own. When we delete the Result entity, our Feedback entity should also get deleted. For more information see: jpa cascade types.

  • CascadeType.ALL Propagates all operations mentioned below from the parent object to the to child object.

// Result.java
@OneToMany(mappedBy = "result", cascade = CascadeType.ALL, orphanRemoval = true)
@OrderColumn
@JsonIgnoreProperties(value = "result", allowSetters = true)
@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
@JsonView(QuizView.Before.class)
private List<Feedback> feedbacks = new ArrayList<>();
  • CascadeType.PERSIST When persisting a parent entity, it also persists the child entities held in its fields. This cascade rule is helpful for relationships where the parent acts as a container to the child entity. If you do not use this, you have to ensure that you persist the child entity first, otherwise an error will be thrown. Example: The code below propagates the persist operation from parent AnswerCounter to child AnswerOption. When an AnswerCounter is persisted, its AnswerOption is persisted as well.

// AnswerCounter.java
@OneToOne(cascade = { CascadeType.PERSIST })
@JoinColumn(unique = true)
private AnswerOption answer;
  • CascadeType.MERGE If you merge the source entity (saved/updated/synchronized) to the database, the merge is cascaded to the target of the association. This rule applies to existing objects only. Use this type to always merge/synchronize the existing data in the table with the data in the object. Example below: whenever we merge a Result to the database, i.e. save the changes on the object, the Assessor object is also merged/saved.

// Result.java
@OneToOne(cascade = CascadeType.MERGE, fetch = FetchType.LAZY)
@JoinColumn(unique = false)
private User assessor;
  • CascadeType.REMOVE If the source entity is removed, the target of the association is also removed. Example below: propagates remove operation from parent Submission to child Result. When a Submission is deleted, the corresponding Result is also deleted.

// Submission.java
@OneToOne(mappedBy = "submission", fetch = FetchType.LAZY, cascade = CascadeType.REMOVE)
@JsonIgnoreProperties({ "submission", "participation" })
@JoinColumn(unique = true)
private Result result;
  • CascadeType.REFRESH If the source entity is refreshed, it cascades the refresh to the target of the association. This is used to refresh the data in the object and its associations. This is useful for cases where there is a change which needs to be synchronized FROM the database.

Not used in Artemis yet.

Best Practices
  • If you want to create a @OneToMany relationship or @ManyToMany relationship, first think about if it is important for the association to be ordered. If you do not need the association to be ordered, then always go for a Set instead of List. If you are unsure, start with a Set.

    • Unordered Collection: A Set comes with certain advantages such as ensuring that there are no duplicates and null values in your collection. There are also performance arguments to use a Set, especially for @ManyToMany relationships. For more information see this stackoverflow thread. E.g.:

      // Course.java
      @OneToMany(mappedBy = "course", fetch = FetchType.LAZY)
      @Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
      @JsonIgnoreProperties("course")
      private Set<Exercise> exercises = new HashSet<>();
      
  • Ordered Collection: When you want to order the collection of objects of the relationship, then always use a List. It is important to note here that there is no inherent order in a database table. One could argue that you can use the id field for the ordering, but there are edge cases where this can lead to problems. Therefore, for a ordered collection, always annotate it with @OrderColumn. An order column indicates to hibernate that we want to order our collection based on a specific column of our data table. By default, the column name it expects is tablenameS_order. For ordered collections, we also recommend that you annotate them with CascadeType.ALL and orphanRemoval = true. E.g.:

    //Result.java
    @OneToMany(mappedBy = "result", cascade = CascadeType.ALL, orphanRemoval = true)
    @OrderColumn
    @JsonIgnoreProperties(value = "result", allowSetters = true)
    @Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
    @JsonView(QuizView.Before.class)
    private List<Feedback> feedbacks = new ArrayList<>();
    

    Note

    Hibernate will take care of the ordering for you but you must create the order column in the database. This is not created automatically!

    With ordered collections, you have to be very careful with the way you persist the objects in the database. You must first persist the child object without a relation to the parent object. Then, you recreate the association and persist the parent object. Example of how to correctly persist objects in an ordered collection:

    // ProgrammingAssessmentService
    List<Feedback> savedFeedbacks = new ArrayList<>();
    result.getFeedbacks().forEach(feedback -> {
       // cut association to parent object
       feedback.setResult(null);
       // persist the child object without an association to the parent object. IMPORTANT: Use the object returned from the database!
       feedback = feedbackRepository.save(feedback);
       // restore the association to the parent object
       feedback.setResult(result);
       savedFeedbacks.add(feedback);
    });
    
    // set the association of the parent to its child objects which are now persisted in the database
    result.setFeedbacks(savedFeedbacks);
    // persist the parent object
    return resultRepository.save(result);
    
Solutions for known issues
  • org.hibernate.LazyInitializationException : could not initialize proxy no Session caused by fetchType.LAZY. You must explicitly load the associated object from the database before trying to access those. Example of how to eagerly fetch the feedbacks with the result:

// ResultRepository.java
@Query("select r from Result r left join fetch r.feedbacks where r.id = :resultId")
Optional<Result> findByIdWithEagerFeedbacks(@Param("resultId") Long id);
  • JpaSystemException: null index column for collection caused by @OrderColumn annotation:

There is a problem with the way you save the associated objects. You must follow this procedure:

  1. Save the child entity (e.g., Feedback) without connection to the parent entity (e.g., Result)

  2. Add back the connection of the child entity to the parent entity.

  3. Save the parent entity.

  4. Always use the returned value after saving the entity, see: feedback = feedbackRepository.save(feedback);

Note

For more information see ordered collections.

  • There are null values in your ordered collection: You must annotate the ordered collection with CascadeType.ALL and orphanRemoval = true. E.g:

    //Result.java
    @OneToMany(mappedBy = "result", cascade = CascadeType.ALL, orphanRemoval = true)
    @OrderColumn
    @JsonIgnoreProperties(value = "result", allowSetters = true)
    @Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
    @JsonView(QuizView.Before.class)
    private List<Feedback> feedbacks = new ArrayList<>();
    

System Design

Top-Level Design

The following diagram shows the top-level design of Artemis which is decomposed into an application client (running as Angular web app in the browser) and an application server (based on Spring Boot). For programming exercises, the application server connects to a version control system (VCS) and a continuous integration system (CIS). Authentication is handled by an external user management system (UMS).

Top-Level Design

Top-Level Design

While Artemis includes generic adapters to these three external systems with a defined protocol that can be instantiated to connect to any VCS, CIS or UMS, it also provides 3 concrete implementations for these adapters to connect to:

  1. VCS: Atlassian Bitbucket Server

  2. CIS: Atlassian Bamboo Server

  3. UMS: Atlassian JIRA Server (more specifically Atlassian Crowd on the JIRA Server)

Deployment

The following UML deployment diagram shows a typical deployment of Artemis application server and application client. Student, Instructor and Teaching Assistant (TA) computers are all equipped equally with the Artemis application client being displayed in the browser.

The Continuous Integration Server typically delegates the build jobs to local build agents within the university infrastructure or to remote build agents, e.g. hosted in the Amazon Cloud (AWS).

Deployment Overview

Deployment Overview

Data Model

The Artemis application server used the following data model in the MySQL database. It supports multiple courses with multiple exercises. Each student in the participating student group can participate in the exercise by clicking the Start Exercise button. Then a repository and a build plan for the student (User) will be created and configured. The initialization state variable (Enum) helps to track the progress of this complex operation and allows to recover from errors. A student can submit multiple solutions by committing and pushing the source code changes to a given example code into the version control system or using the user interface. Each submission is automatically tested by the continuous integration server, which notifies the Artemis application server, when a new result exists. In addition, teaching assistants can assess student solutions and “manually” create results. The current data model is more complex and supports different types of exercises such as programming exercises, modeling exercises, quiz, and text exercises.

Data Model

Data Model

Server Architecture

The following UML component diagram shows more details of the Artemis application server architecture and its REST interfaces to the application client.

Server Architecture

Server Architecture

Using local user management

If you want to test in a local environment using different users, it makes sense to rely on local instead of external user management.

  1. Go to the application-artemis.yml file, and set use-external in the user-management section to false.

  2. Remove the jira profile from your local Run Configuration for the Server.

Removing the jira profile

Remove the jira profile from the list shown in IntelliJ

Setup Guide for Guided Tutorials in Artemis

This guide gives you instructions on how to setup and create guided tutorials for Artemis:

Create GuidedTour object

A guided tutorial can be created by instantiating a GuidedTour object. This object has the mandatory attributes settingsKey, the identifier for the tutorial which will be stored in the database, and steps, which is an array that stores all tutorial steps. A tutorial can have different types of tutorial steps:

  1. TextTourStep: tutorial step with only text content

  2. ImageTourStep: tutorial step with text content and embedded image

  3. VideoTourStep: tutorial step with text content and embedded video

  4. UserInteractionTourStep: tutorial step which requires a certain interaction from the user to proceed to the next step.

  5. ModelingTaskTourStep: tutorial step with text content and modeling task for the Apollon editor that is assessed for the step

  6. AssessmentTaskTourStep: tutorial step with text content and a tutor assessment task for example submissions (currently only implemented for text assessments).

TextTourStep with highlighted element
TextTourStep with highlighted element

TextTourStep with highlighted element

TextTourStep with multiple markdown elements
TextTourStep with multiple markdown elements

TextTourStep with multiple markdown elements

ImageTourStep
ImageTourStep

ImageTourStep

VideoTourStep
VideoTourStep

VideoTourStep

UserInteractionTourStep
UserInteractionTourStep

UserInteractionTourStep

ModelingTaskTourStep
ModelingTaskTourStep

ModelingTaskTourStep

AssessmentTaskTourStep
AssessmentTaskTourStep

AssessmentTaskTourStep

Example implementation of a GuidedTour object

In this example, the GuidedTour object is created and assigned to the constant exampleTutorial, which one can use to embed the tutorial to a component of choice.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
import { Orientation, UserInteractionEvent } from '../../src/main/webapp/app/guided-tour/guided-tour.constants';
import { GuidedTour } from '../../src/main/webapp/app/guided-tour/guided-tour.model';
import { ImageTourStep, ModelingTaskTourStep, TextTourStep, VideoTourStep } from '../../src/main/webapp/app/guided-tour/guided-tour-step.model';
import { GuidedTourModelingTask, personUML } from '../../src/main/webapp/app/guided-tour/guided-tour-task.model';

export const exampleTutorial: GuidedTour = {
    settingsKey: 'example_tutorial',
    steps: [
        new TextTourStep({
            highlightSelector: '.guided-tour-overview',
            headlineTranslateKey: 'tour.courseOverview.overviewMenu.headline',
            contentTranslateKey: 'tour.courseOverview.overviewMenu.content',
            highlightPadding: 10,
            orientation: Orientation.BOTTOM,
        }),
        new ImageTourStep({
            headlineTranslateKey: 'tour.courseOverview.welcome.headline',
            subHeadlineTranslateKey: 'tour.courseOverview.welcome.subHeadline',
            contentTranslateKey: 'tour.courseOverview.welcome.content',
            imageUrl: 'https://ase.in.tum.de/lehrstuhl_1/images/teaching/interactive/InteractiveLearning.png',
        }),
        new VideoTourStep({
            headlineTranslateKey: 'tour.courseExerciseOverview.installPrerequisites.sourceTreeSetup.headline',
            contentTranslateKey: 'tour.courseExerciseOverview.installPrerequisites.sourceTreeSetup.content',
            hintTranslateKey: 'tour.courseExerciseOverview.installPrerequisites.sourceTreeSetup.hint',
            videoUrl: 'tour.courseExerciseOverview.installPrerequisites.sourceTreeSetup.videoUrl',
        }),
        new ModelingTaskTourStep({
            highlightSelector: 'jhi-modeling-editor .guided-tour.modeling-editor .modeling-editor',
            headlineTranslateKey: 'tour.modelingExercise.executeTasks.headline',
            contentTranslateKey: 'tour.modelingExercise.executeTasks.content',
            highlightPadding: 5,
            orientation: Orientation.TOP,
            userInteractionEvent: UserInteractionEvent.MODELING,
            modelingTask: new GuidedTourModelingTask(personUML.name, 'tour.modelingExercise.executeTasks.personClass'),
        }),
        // ...
    ],
};

Mandatory attributes

  1. TextTourStep: The mandatory fields are headlineTranslateKey and contentTranslateKey.

  2. ImageTourStep: The ImageTourStep extends the TextTourStep and has imageUrl as an additional mandatory attribute.

  3. VideoTourStep: The VideoTourStep extends the TextTourStep and has videoUrl as an additional mandatory attribute.

  4. UserInterActionTourStep: The UserInterActionTourStep extends the TextTourStep and is used to include interactions tasks for the user during the tour step. It has the additional mandatory attribute userInteractionEvent, which defines the interaction type, and the optional attribute triggerNextStep.

  5. ModelingTaskTourStep: The ModelingTaskTourStep extends the UserInterActionTourStep and has modelingTask as an additional mandatory attribute.

  6. AssessmentTaskTourStep: The AssessmentTaskTourStep extends the UserInterActionTourStep and has assessmentTask as an additional mandatory attribute.

Optional attributes

There are many optional attributes that can be defined for a tour step. These attributes and their definition can be found in the abstract class TourStep. Below, you can find a list of attributes that are used more often:

  1. highlightSelector: For the highlightSelector you have to enter a CSS selector for the HTML element that you want to highlight for this step. For better maintainability of the guided tutorials, it is strongly advised to create new selectors with the prefix guided-tour within the DOM and use it as the highlight selector.

  2. orientation: We can define an orientation for every tour step individually. The tour step orientation is used to define the position of the tour step next to highlighted element.

  3. highlightPadding: This attribute sets the additional padding around the highlight element.

  4. userInteractionEvent: Some steps require user interactions, e.g. certain click events, before the next tour step can be enabled. The supported user interactions are defined in the enum UserInteractionEvent.

  5. pageUrl: If you want to create a multi-page tutorial, i.e. a tutorial that guides the user through multiple component pages, then you have to use this attribute. The pageUrl should be added to the first tutorial step of every page and if the URL has identifiers in the URL such as course or exercise ids then these numbers should be replaced with the regex (\d+)+. An example of multi-page tutorials can be found in the tutor-assessment-tour.ts file.

Add translations

In order to allow internationalization, the values for the attributes headlineTranslateKey, subHeadlineTranslateKey, contentTranslateKey and hintTranslateKey reference the text snippets which are stored in JSON translation document. Further attributes that need translations are videoUrl for VideoTourStep and taskTranslateKey for the modelingTask in the ModelingTaskTourStep. One JSON document that is used for the translations of guided tutorials is the file guidedTour.json.

Embed in component file

There are multiple service methods to embed a guided tutorial in an application component file. We use the GuidedTutorialService in the component through dependency injection and invoke the fitting method to enable the tutorial for the component:

The enableTourForCourseOverview method is used when the tutorial should be enabled for a certain course in a component, which displays a list of courses (e.g. overview.component.ts). It returns the course for which the tutorial is enabled, if available, otherwise null.

public enableTourForCourseOverview(courses: Course[], guidedTour: GuidedTour, init: boolean): Course | null {

The enableTourForCourseExerciseComponent method is used when the tutorial should be enabled for a certain course and exercise in a component, which displays a list of exercises for a course (e.g. course-exercises.component.ts). It returns the exercise for which the tutorial is enabled, if available, otherwise null.

public enableTourForCourseExerciseComponent(course: Course | null, guidedTour: GuidedTour, init: boolean): Exercise | null {

The enableTourForExercise method is used when the tutorial should be enabled for a certain exercise (e.g. course-exercise-details.component.ts). It returns the exercise for which the tutorial is enabled, if available, otherwise null.

public enableTourForExercise(exercise: Exercise, guidedTour: GuidedTour, init: boolean) {
Example of integrating the GuidedTour exampleTutorial into a component file
constructor( private guidedTourService: GuidedTourService ) {}

...

this.courseForGuidedTour = this.guidedTourService.enableTourForCourseOverview(this.courses, exampleTutorial, true);

Extend configuration file

The mapping of guided tutorials to certain courses and exercises is configured in the application-dev.yml and application-prod.yml files. The yaml configuration below shows that the guided tutorials are only enabled for the course with the short name artemistutorial. The configuration for tours shows a list of mappings tutorialSettingsKeyexerciseIdentifier. The exerciseIdentifier for programming exercises is the exercise short name, otherwise it’s the exercise title.

The optional course-group-students property is used to automatically add the given tutorial’s course group to all the new created users. This functionality can be extended to users with instructor or teaching assistant roles, adding the optional course-group-instructors and/or course-group-tutors properties. In this case, newly created users with instructor or teaching assistant roles will be assigned to their respectively tutorial’s course groups.

info:
    guided-tour:
        course-group-students: 'artemis-artemistutorial-students'
        courseShortName: 'artemistutorial'
        tours:
            - cancel_tour: ''
            - code_editor_tour: 'tutorial'
            - course_overview_tour: ''
            - course_exercise_overview_tour: 'tutorial'
            - modeling_tour: 'UML Class Diagram'
            - programming_exercise_fail_tour: 'tutorial'
            - programming_exercise_success_tour: 'tutorial'
            - tutor_assessment_tour: 'Patterns in Software Engineering'

Writing test cases for guided tutorials

Through Jest client tests it is possible to start the guided tutorials and go through all the tutorial steps while checking for the highlight selectors. An example test suite for the courseOverviewTour can be found in the overview.component.spec.ts file.

Test Servers

Test Server 1-3

Deployment via Bamboo. Only for branches of the ls1intum/Artemis repository, no forks. Guide available in the Artemis Developer Confluence Space: Deploying changes to test server.

Test Server 5

Pull requests on GitHub can be deployed to TS5, including forks. To invoke a deployment, you need to be part of the ls1intum GitHub organization.

Start the deployment using the Test Server Deployment Workflow. (Refer to the GitHub documentation “Manually running a workflow”.) Supply the pull request number to initiate the deployment (e.g. 42 for PR #42). TS5 is locked to a pull request using the lock:artemistest5 label. The workflow applies the lock label automatically on deployment. Remove the label from the PR once the test server is free to use by other developers.

User Registration

Artemis supports user registration based on the Jhipster template. User registration has to be enabled in one application-*.yml file and can be customized.

Example:

artemis:
    user-management:
        use-external: false
        registration:
            enabled: true
            allowed-email-pattern: '[a-zA-Z0-9_\-\.\+]+@(tum\.de|in\.tum\.de|mytum\.de)'
            allowed-email-pattern-readable: '@tum.de, @in.tum.de, @mytum.de'
spring:
    mail:
        host: <host>
        port: 25
        username: <username>
        password: <password>
        protocol: smtp
        tls: true
        properties.mail.smtp:
            auth: true
            starttls:
                enable: true
            ssl:
                trust: <host>

jhipster:
    mail:
        base-url: https://artemis.ase.in.tum.de
        from: artemis.in@tum.de

Users can register a new account on the start page based on allowed-email-pattern. If no email pattern is defined, any email address can be used. Upon registration, users receive an email to activate their account.

You can find more information on how to configure the email server on the official Jhipster documentation.