Added assigning to student teams, and proper repository generation.

This commit is contained in:
Andrew Lalis 2019-04-21 19:48:12 +02:00 committed by andrewlalis
parent fabe05ba54
commit b1bd5c6e04
22 changed files with 589 additions and 68 deletions

View File

@ -27,11 +27,5 @@ public class TeachingAssistantAssistantApplication implements CommandLineRunner
@Override
public void run(String... args) throws Exception {
System.out.println("Running startup...");
//
// String exampleDate = "2019/04/15 4:13:41 PM GMT+2 ";
// // Parse the timestamp.
// DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy/MM/dd h:mm:ss a O ");
// ZonedDateTime dateTime = ZonedDateTime.parse(exampleDate, formatter);
// System.out.println("Read time: " + dateTime.toString());
}
}

View File

@ -48,6 +48,6 @@ public class TeachingAssistants {
course.addParticipant(teachingAssistant);
this.courseRepository.save(course);
});
return "teaching_assistants/entity";
return "redirect:/teaching_assistants";
}
}

View File

@ -43,6 +43,12 @@ public class ImportStudents {
return "courses/entity/import_students";
}
/**
* Performs the actual importing when this controller receives a post request.
* @param code The course code.
* @param file The file which the user has uploaded.
* @return Redirect to the course which will be shown.
*/
@PostMapping(
value = "/courses/{code}/import_students"
)

View File

@ -0,0 +1,59 @@
package nl.andrewlalis.teaching_assistant_assistant.controllers.courses.entity.student_teams;
import nl.andrewlalis.teaching_assistant_assistant.model.Course;
import nl.andrewlalis.teaching_assistant_assistant.model.people.Student;
import nl.andrewlalis.teaching_assistant_assistant.model.people.teams.StudentTeam;
import nl.andrewlalis.teaching_assistant_assistant.model.people.teams.TeachingAssistantTeam;
import nl.andrewlalis.teaching_assistant_assistant.model.repositories.CourseRepository;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.util.List;
import java.util.Optional;
/**
* Controller for exporting team information into readable files.
*/
@Controller
public class ExportStudentTeams {
private CourseRepository courseRepository;
protected ExportStudentTeams(CourseRepository courseRepository) {
this.courseRepository = courseRepository;
}
@GetMapping("/courses/{code}/student_teams/export")
public void export(@PathVariable String code, HttpServletResponse response) throws IOException {
Optional<Course> optionalCourse = this.courseRepository.findByCode(code);
if (!optionalCourse.isPresent()) {
response.sendError(404, "Course with code " + code + " not found");
return;
}
Course course = optionalCourse.get();
response.setContentType("text/*");
OutputStreamWriter writer = new OutputStreamWriter(response.getOutputStream());
writer.write("Student Teams Export for course: " + course.getName() + "\n");
for (TeachingAssistantTeam teachingAssistantTeam : course.getTeachingAssistantTeams()) {
writer.write("Teaching Assistant Team " + teachingAssistantTeam.getId() + ", Github team: " + teachingAssistantTeam.getGithubTeamName() + "\n");
List<StudentTeam> assignedTeams = teachingAssistantTeam.getAssignedStudentTeams();
for (StudentTeam studentTeam : assignedTeams) {
writer.write("\tStudent Team " + studentTeam.getId() + ": ");
for (Student student : studentTeam.getStudents()) {
writer.write(student.getFullName() + " (S" + student.getStudentNumber() + "), ");
}
writer.write("\n");
}
}
response.flushBuffer();
}
}

View File

@ -1,7 +1,9 @@
package nl.andrewlalis.teaching_assistant_assistant.controllers.courses.entity.student_teams;
import nl.andrewlalis.teaching_assistant_assistant.model.Course;
import nl.andrewlalis.teaching_assistant_assistant.model.people.teams.StudentTeam;
import nl.andrewlalis.teaching_assistant_assistant.model.repositories.CourseRepository;
import nl.andrewlalis.teaching_assistant_assistant.model.repositories.StudentTeamRepository;
import nl.andrewlalis.teaching_assistant_assistant.util.github.GithubManager;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
@ -16,9 +18,11 @@ import java.util.Optional;
public class GenerateRepositories {
private CourseRepository courseRepository;
private StudentTeamRepository studentTeamRepository;
protected GenerateRepositories(CourseRepository courseRepository) {
protected GenerateRepositories(CourseRepository courseRepository, StudentTeamRepository studentTeamRepository) {
this.courseRepository = courseRepository;
this.studentTeamRepository = studentTeamRepository;
}
@GetMapping("/courses/{courseCode}/student_teams/generate_repositories")
@ -37,12 +41,25 @@ public class GenerateRepositories {
System.out.println("Post received for " + courseCode);
Optional<Course> optionalCourse = this.courseRepository.findByCode(courseCode);
optionalCourse.ifPresent(course -> {
course.setGithubOrganizationName("InitializerTesting");
GithubManager manager;
try {
GithubManager manager = new GithubManager(course.getApiKey());
manager.generateStudentTeamRepository(course.getStudentTeams().get(0));
manager = new GithubManager(course.getApiKey());
} catch (IOException e) {
e.printStackTrace();
return;
}
for (StudentTeam studentTeam : course.getStudentTeams()) {
System.out.println("Generating repository for team " + studentTeam.getId());
String repositoryName = manager.generateStudentTeamRepository(studentTeam);
if (repositoryName == null) {
System.err.println("An error occurred while generating a repository for student team " + studentTeam.getId());
continue;
}
studentTeam.setGithubRepositoryName(repositoryName);
this.studentTeamRepository.save(studentTeam);
System.out.println("Done\n");
}
});

View File

@ -0,0 +1,112 @@
package nl.andrewlalis.teaching_assistant_assistant.controllers.courses.entity.students;
import nl.andrewlalis.teaching_assistant_assistant.model.Course;
import nl.andrewlalis.teaching_assistant_assistant.model.people.Student;
import nl.andrewlalis.teaching_assistant_assistant.model.repositories.CourseRepository;
import nl.andrewlalis.teaching_assistant_assistant.util.github.GithubManager;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
@Controller
public class InviteAllToRepository {
private CourseRepository courseRepository;
protected InviteAllToRepository(CourseRepository courseRepository) {
this.courseRepository = courseRepository;
}
@GetMapping("/courses/{code}/students/invite_all")
public String get(@PathVariable String code, Model model) {
Optional<Course> optionalCourse =this.courseRepository.findByCode(code);
optionalCourse.ifPresent(course -> model.addAttribute("course", course));
return "courses/entity/students/invite_all";
}
@PostMapping("/courses/{code}/students/invite_all")
public String post(
@PathVariable String code,
@RequestParam(value = "repository_name") String repositoryName,
@RequestParam(value = "api_keys") String apiKeys,
@RequestParam(value = "usernames", required = false) String usernames
) {
Optional<Course> optionalCourse = this.courseRepository.findByCode(code);
optionalCourse.ifPresent(course -> {
// Get a list of all the github usernames to invite.
List<String> githubUsernames = new ArrayList<>();
if (usernames != null && !usernames.isEmpty()) {
String[] usernamesRaw = usernames.split("\n");
for (String username : usernamesRaw) {
githubUsernames.add(username.trim());
}
} else {
for (Student student : course.getStudents()) {
githubUsernames.add(student.getGithubUsername());
}
}
// Get a list of all our available keys.
String[] rawKeys = apiKeys.split("\n");
List<String> keys = new ArrayList<>();
for (String rawKey : rawKeys) {
keys.add(rawKey.trim());
}
String fullRepositoryName = course.getGithubOrganizationName() + '/' + repositoryName;
int inviteCounter = 0;
GithubManager manager;
try {
manager = new GithubManager(keys.remove(0));
} catch (IOException e) {
e.printStackTrace();
return;
}
List<String> failedNames = new ArrayList<>();
while (!githubUsernames.isEmpty()) {
if (inviteCounter == 50) {
System.out.println("Used up 50 invites on key.");
try {
manager = new GithubManager(keys.remove(0));
inviteCounter = 0;
} catch (IOException e) {
e.printStackTrace();
failedNames.addAll(githubUsernames);
return;
}
}
String username = githubUsernames.remove(0);
try {
manager.addCollaborator(fullRepositoryName, username, "pull");
inviteCounter++;
System.out.println("\tInvited " + username);
} catch (IOException e) {
//e.printStackTrace();
System.err.println("Could not add " + username + " to repository " + fullRepositoryName);
failedNames.add(username);
}
}
System.err.println("The following github usernames have not been added.");
for (String username : failedNames) {
System.out.println("\t" + username);
}
});
return "redirect:/courses/{code}/students";
}
}

View File

@ -0,0 +1,70 @@
package nl.andrewlalis.teaching_assistant_assistant.controllers.courses.entity.teaching_assistant_teams;
import nl.andrewlalis.teaching_assistant_assistant.model.Course;
import nl.andrewlalis.teaching_assistant_assistant.model.people.teams.StudentTeam;
import nl.andrewlalis.teaching_assistant_assistant.model.people.teams.TeachingAssistantTeam;
import nl.andrewlalis.teaching_assistant_assistant.model.repositories.CourseRepository;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import java.util.*;
@Controller
public class AssignToStudentTeams {
private CourseRepository courseRepository;
protected AssignToStudentTeams(CourseRepository courseRepository) {
this.courseRepository = courseRepository;
}
@GetMapping("/courses/{code}/teaching_assistant_teams/assign_to_student_teams")
public String get(@PathVariable String code, Model model) {
Optional<Course> optionalCourse = this.courseRepository.findByCode(code);
optionalCourse.ifPresent(course -> model.addAttribute("course", course));
return "courses/entity/teaching_assistant_teams/assign_to_student_teams";
}
/**
* Randomly assigns a teaching assistant team to each student team in such a way that all teaching assistant teams
* should receive an equal number of student teams.
* @param code The code for the course in which to perform this action.
* @param seed A seed to use to determine randomness.
* @return The view for the list of student teams in this course, to see the results of the action.
*/
@PostMapping("/courses/{code}/teaching_assistant_teams/assign_to_student_teams")
public String post(@PathVariable String code, @RequestParam(value = "seed") int seed) {
Optional<Course> optionalCourse = this.courseRepository.findByCode(code);
optionalCourse.ifPresent(course -> {
List<StudentTeam> studentTeams = course.getStudentTeams();
LinkedList<Integer> studentTeamQueue = new LinkedList<>();
for (int i = 0; i < studentTeams.size(); i++) {
studentTeamQueue.add(i);
}
Collections.shuffle(studentTeamQueue, new Random(seed));
while (!studentTeamQueue.isEmpty()) {
for (TeachingAssistantTeam taTeam : course.getTeachingAssistantTeams()) {
if (studentTeamQueue.isEmpty()) {
break;
}
StudentTeam studentTeam = studentTeams.get(studentTeamQueue.removeFirst());
studentTeam.setAssignedTeachingAssistantTeam(taTeam);
taTeam.addAssignedStudentTeam(studentTeam);
}
}
this.courseRepository.save(course);
});
return "redirect:/courses/{code}/student_teams";
}
}

View File

@ -40,7 +40,6 @@ public class CreateTeachingAssistantTeam {
@PathVariable String code,
@RequestParam(value = "github_team_name") String githubTeamName,
@RequestParam(value = "id_1") long id1,
@RequestParam(value = "id_2") long id2,
Model model
) {
TeachingAssistantTeam team = new TeachingAssistantTeam();
@ -48,17 +47,12 @@ public class CreateTeachingAssistantTeam {
Optional<Course> optionalCourse = this.courseRepository.findByCode(code);
Optional<TeachingAssistant> optionalTeachingAssistant1 = this.teachingAssistantRepository.findById(id1);
Optional<TeachingAssistant> optionalTeachingAssistant2 = this.teachingAssistantRepository.findById(id2);
System.out.println("Course code: " + code + ", Team name: " + githubTeamName + ", TA 1: " + id1 + ", TA 2: " + id2);
if (optionalCourse.isPresent() && optionalTeachingAssistant1.isPresent() && optionalTeachingAssistant2.isPresent()) {
System.out.println("All data available.");
if (optionalCourse.isPresent() && optionalTeachingAssistant1.isPresent()) {
Course course = optionalCourse.get();
team.setCourse(course);
team.addMember(optionalTeachingAssistant1.get());
team.addMember(optionalTeachingAssistant2.get());
course.addTeachingAssistantTeam(team);
this.courseRepository.save(course);

View File

@ -61,4 +61,16 @@ public class TeachingAssistantTeam extends Team {
public void setGithubTeamName(String name) {
this.githubTeamName = name;
}
public List<StudentTeam> getAssignedStudentTeams() {
return this.assignedStudentTeams;
}
public void addAssignedStudentTeam(StudentTeam studentTeam) {
this.assignedStudentTeams.add(studentTeam);
}
public void removeAssignedStudentTeam(StudentTeam studentTeam) {
this.assignedStudentTeams.remove(studentTeam);
}
}

View File

@ -1,14 +1,23 @@
package nl.andrewlalis.teaching_assistant_assistant.util.github;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import nl.andrewlalis.teaching_assistant_assistant.model.people.Student;
import nl.andrewlalis.teaching_assistant_assistant.model.people.teams.StudentTeam;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.kohsuke.github.*;
import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URL;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
/**
* Encapsulates much of the github functionality that is needed.
@ -16,70 +25,178 @@ import java.util.List;
public class GithubManager {
private GitHub github;
private String apiKey;
/**
* Constructs this manager with an API key.
* @param apiKey The api key to use.
* @throws IOException if the key is invalid.
*/
public GithubManager(String apiKey) throws IOException {
this.github = GitHub.connectUsingOAuth(apiKey);
this.apiKey = apiKey;
}
/**
* Constructs this manager with a username/password login combination.
* @param username The username to use.
* @param password The password for the above username.
* @throws IOException if the username or password are invalid.
*/
public GithubManager(String username, String password) throws IOException {
this.github = GitHub.connectUsingPassword(username, password);
}
public void generateStudentTeamRepository(StudentTeam team) throws IOException {
GHOrganization organization = this.github.getOrganization(team.getCourse().getGithubOrganizationName());
/**
* Generates a new Github Repository for the given student team.
* @param team The team to create a repository for.
* @return The name of the created repository.
*/
public String generateStudentTeamRepository(StudentTeam team) {
GHOrganization organization;
try {
organization = this.github.getOrganization(team.getCourse().getGithubOrganizationName());
} catch (IOException e) {
e.printStackTrace();
System.err.println("Could not get Github organization with name: " + team.getCourse().getGithubOrganizationName());
return null;
}
Calendar today = Calendar.getInstance();
int year = today.get(Calendar.YEAR);
String repositoryName = year + "_Team_" + team.getId();
// Get the TA team which manages this repository.
GHTeam teachingAssistantGithubTeam = organization.getTeamByName(team.getAssignedTeachingAssistantTeam().getGithubTeamName());
GHTeam teachingAssistantGithubTeam;
try {
teachingAssistantGithubTeam = organization.getTeamByName(team.getAssignedTeachingAssistantTeam().getGithubTeamName());
} catch (IOException e) {
e.printStackTrace();
System.err.println("Could not get team by name: " + team.getAssignedTeachingAssistantTeam().getGithubTeamName());
return null;
}
// Create the repo
GHCreateRepositoryBuilder repositoryBuilder = organization.createRepository(repositoryName);
repositoryBuilder.wiki(false);
repositoryBuilder.issues(false);
repositoryBuilder.issues(true);
repositoryBuilder.description("University of Groningen OOP Student Repository");
repositoryBuilder.private_(false);
repositoryBuilder.private_(true);
repositoryBuilder.autoInit(false);
repositoryBuilder.team(teachingAssistantGithubTeam);
GHRepository repository = repositoryBuilder.create();
GHRepository repository;
try {
repository = repositoryBuilder.create();
} catch (IOException e) {
e.printStackTrace();
System.err.println("Could not create repository: " + repositoryName);
return null;
}
// Add getting started file.
try {
this.addRepositoryToTeam(teachingAssistantGithubTeam, repository);
} catch (IOException e) {
e.printStackTrace();
System.err.println("Could not add repository " + repositoryName + " to team " + teachingAssistantGithubTeam.getName());
return null;
}
try {
this.addStarterFile(repository, "program_resources/getting_started.md", "getting_started.md");
} catch (IOException e) {
e.printStackTrace();
System.err.println("Could not add the starter file to the repository: " + repositoryName);
return null;
}
try {
this.createDevelopmentBranch(repository);
} catch (IOException e) {
e.printStackTrace();
System.err.println("Could not create development branch for repository: " + repositoryName);
return null;
}
try {
this.protectMasterBranch(repository, teachingAssistantGithubTeam);
} catch (IOException e) {
e.printStackTrace();
System.err.println("Could not add protections to the master branch of " + repositoryName);
return null;
}
try {
this.addStudentsAsCollaborators(repository, team);
} catch (IOException e) {
e.printStackTrace();
System.err.println("Could not add students as collaborators to " + repositoryName);
return null;
}
return repositoryName;
}
private void addStarterFile(GHRepository repository, String filePath, String fileName) throws IOException {
GHContentBuilder contentBuilder = repository.createContent();
contentBuilder.branch("master");
contentBuilder.message("Initial Commit");
File f = new File(getClass().getClassLoader().getResource("program_resources/getting_started.md").getFile());
contentBuilder.content(Files.readAllBytes(f.toPath()));
contentBuilder.path("getting_started.md");
contentBuilder.commit();
// Create the development branch.
try {
String sha1 = repository.getBranch(repository.getDefaultBranch()).getSHA1();
repository.createRef("refs/heads/development", sha1);
} catch (IOException e) {
e.printStackTrace();
URL resource = getClass().getClassLoader().getResource(filePath);
if (resource == null) {
throw new IOException("Could not get resource identified by " + filePath);
}
File f = new File(resource.getFile());
// Protect master branch.
contentBuilder.content(Files.readAllBytes(f.toPath()));
contentBuilder.path(fileName);
contentBuilder.commit();
}
private void protectMasterBranch(GHRepository repository, GHTeam adminTeam) throws IOException {
GHBranchProtectionBuilder protectionBuilder = repository.getBranch("master").enableProtection();
protectionBuilder.includeAdmins(false);
protectionBuilder.restrictPushAccess();
protectionBuilder.teamPushAccess(teachingAssistantGithubTeam);
protectionBuilder.teamPushAccess(adminTeam);
protectionBuilder.addRequiredChecks("ci/circleci");
protectionBuilder.enable();
}
List<GHUser> studentUsers = new ArrayList<>(team.getMembers().size());
team.getStudents().forEach(student -> {
try {
studentUsers.add(this.github.getUser(student.getGithubUsername()));
} catch (IOException e) {
e.printStackTrace();
}
});
//repository.addCollaborators(studentUsers);
private void addStudentsAsCollaborators(GHRepository repository, StudentTeam studentTeam) throws IOException {
for (Student student : studentTeam.getStudents()) {
this.addCollaborator(repository.getFullName(), student.getGithubUsername(), "push");
}
}
private void createDevelopmentBranch(GHRepository repository) throws IOException {
String sha1 = repository.getBranch(repository.getDefaultBranch()).getSHA1();
repository.createRef("refs/heads/development", sha1);
}
public void addCollaborator(String repositoryName, String githubUsername, String permission) throws IOException {
try {
String url = "https://api.github.com/repos/" + repositoryName + "/collaborators/" + githubUsername + "?access_token=" + this.apiKey;
HttpPut put = new HttpPut(url);
CloseableHttpClient client = HttpClientBuilder.create().build();
ObjectMapper mapper = new ObjectMapper();
ObjectNode root = mapper.createObjectNode();
root.put("permission", permission);
String json = mapper.writeValueAsString(root);
put.setEntity(new StringEntity(json));
HttpResponse response = client.execute(put);
if (response.getStatusLine().getStatusCode() != 201) {
throw new IOException("Error adding collaborator via url " + url + " : " + response.getStatusLine().getStatusCode() + " " + response.getStatusLine().getReasonPhrase());
}
} catch (JsonProcessingException | UnsupportedEncodingException e) {
e.printStackTrace();
}
}
private void addRepositoryToTeam(GHTeam team, GHRepository repository) throws IOException {
team.add(repository, GHOrganization.Permission.ADMIN);
}
}

View File

@ -4,7 +4,7 @@ spring.datasource.username=root
spring.datasource.password=root
spring.datasource.url=jdbc:mysql://localhost:3306/teaching_assistant_assistant?serverTimezone=UTC
#
#spring.jpa.properties.hibernate.show_sql=false
spring.jpa.properties.hibernate.show_sql=false
#spring.jpa.properties.hibernate.use_sql_comments=true
#spring.jpa.properties.hibernate.format_sql=true
#spring.jpa.properties.hibernate.type=trace
@ -12,4 +12,3 @@ spring.datasource.url=jdbc:mysql://localhost:3306/teaching_assistant_assistant?s
spring.security.user.name=tester
spring.security.user.password=tester
spring.data.rest.e

View File

@ -3,11 +3,17 @@ Welcome to the University of Groningen's Object Oriented Programming course! We'
## Setting Up
To begin, you'll need to download this repository you're looking at right now. To do that, run the following command (where `repository_url` is the URL of this repository):
```
git clone <repository_url>
```
Now you should have a local directory with the same name as this repository. Checkout your `development` branch, since you do not have permission as a student to make changes to the master branch. From here, we need to copy the files from the assignments repository into ours. To do this, we first add a new 'remote' to our git repository, and then pull from that repository's `master` branch.
Now you should have a local directory with the same name as this repository. Checkout your `development` branch, since you do not have permission as a student to make changes to the master branch.
From here, we need to copy the files from the assignments repository into ours. To do this, we first add a new 'remote' to our local git repository, and then pull from that repository's `master` branch.
> Note that if you have not received an invitation to the 2019_assignments repository yet, you'll have to wait until you can complete this part. Thank you for your patience.
```
git checkout development
@ -19,4 +25,38 @@ git pull assignments master --allow-unrelated-histories
Now you should see all the files just as they appear in the assignments repository.
## Developing Your Code and Submitting
While previous knowledge of git may be useful, it should not really be needed for you to do well in this course.
While previous knowledge of git may be useful, it should not really be needed for you to do well in this course, as long as you know how to submit each assignment.
When you have finished working on your code, you should first add any new files which you have created (`-A` means "all files". Be careful, since you should not upload compiled class files).
```
git add -A
```
Now, commit all changes that have been made (`-a` means "all changed files", and `-m` allows you to supply a commit message. If you do not add `-m`, git will try to open a text editor for you to write a message in).
```
git commit -a -m "This is a short message about what I did."
```
To make these committed changes public, you need to upload them to the remote repository. This is done by 'pushing'.
```
git push
```
If you refresh the page for your repository on github.com, you should now see changes in whatever branch you pushed to (most likely `development`).
### Submission
Once you've committed and push a few times, and you feel that your code is ready to be submitted, create a new pull request from your `development` branch onto the `master` branch. When you do this, a continuous integration service will compile your code and run `mvn integration-test` to see that all tests (if any) pass. If your code does not compile or pass all tests, don't bother making a submission; we won't grade it. Fix it and submit a working version.
If you have made a pull request, but then would like to make some last-minute changes, you can do this by simply adding more commits to the `development` branch. Any pushed commits will simply be added to an existing pull request.
## Questions, Comments, Concerns
Should you have any questions about Github, the above workflow, or other technical questions, please first see if Google can help, and then if you still cannot figure out what to do, make an issue in your repository that adheres to the following guidelines:
* What is the problem?
* What have you already tried to do to fix it?
* How can my teaching assistant replicate the problem?
By giving us this information, you make it much easier for us to give meaningful feedback as quickly as possible.

View File

@ -32,8 +32,16 @@
<a th:href="@{/students/{id}(id=${student.getId()})}" th:text="${student.getFullName()}"></a><br>
</div>
</td>
<td th:text="${studentTeam.getGithubRepositoryName()}"></td>
<td>
<span th:if="${studentTeam.getGithubRepositoryName() == null}">None</span>
<a
target="_blank"
th:if="${studentTeam.getGithubRepositoryName() != null}"
th:href="${'https://github.com/' + studentTeam.getCourse().getGithubOrganizationName() + '/' + studentTeam.getGithubRepositoryName()}"
th:text="${studentTeam.getGithubRepositoryName()}"></a>
</td>
<td>
<span th:if="${studentTeam.getAssignedTeachingAssistantTeam() == null}">None</span>
<a
th:if="${studentTeam.getAssignedTeachingAssistantTeam() != null}"
th:href="@{/courses/{code}/teaching_assistant_teams/{teamId}
@ -49,6 +57,9 @@
<div class="sidebar_block">
<a th:href="@{/courses/{code}/student_teams/generate_repositories(code=${course.getCode()})}">Generate Repositories</a>
</div>
<div class="sidebar_block">
<a th:href="@{/courses/{code}/student_teams/export(code=${course.getCode()})}">Export</a>
</div>
</div>
</body>

View File

@ -16,6 +16,14 @@
<span th:text="${student_team.getGithubRepositoryName()}"></span>
</a>
</li>
<li>
Assigned Teaching Assistant Team:
<a
th:if="${student_team.getAssignedTeachingAssistantTeam() != null}"
th:href="@{/courses/{code}/teaching_assistant_teams/{id}(code=${student_team.getCourse().getCode()}, id=${student_team.getAssignedTeachingAssistantTeam().getId()})}"
th:text="${student_team.getAssignedTeachingAssistantTeam().getId()}"
></a>
</li>
<li>
Members:
<ul>
@ -31,7 +39,18 @@
</div>
<div id="sidebar">
<div class="sidebar_block">
<a
th:if="${student_team.getGithubRepositoryName() == null}"
th:href="@{/courses/{code}/student_teams/{id}/generate_repository
(code=${student_team.getCourse().getCode()}, id=${student_team.getAssignedTeachingAssistantTeam().getId()})}"
>Generate Repository</a>
<a
th:if="${student_team.getGithubRepositoryName() != null}"
th:href="@{/courses/{code}/student_teams/{id}/delete_repository
(code=${student_team.getCourse().getCode()}, id=${student_team.getAssignedTeachingAssistantTeam().getId()})}"
>Delete Repository</a>
</div>
</div>
</body>

View File

@ -9,6 +9,10 @@
<div id="content">
<h1>Generate Repositories for all Student Teams in <span th:text="${course.getName()}"></span></h1>
<p>
Be careful, this may take a little while...
</p>
<form
method="post"
action="#"

View File

@ -21,7 +21,9 @@
</div>
<div id="sidebar">
<div class="sidebar_block">
<a th:href="@{/courses/{code}/students/invite_all(code=${course.getCode()})}">Invite All Students To Repository</a>
</div>
</div>
</body>

View File

@ -0,0 +1,40 @@
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org" th:replace="~{layouts/basic_page :: layout (~{::title}, ~{::#content}, ~{::#sidebar})}">
<head>
<meta charset="UTF-8">
<title>Generate Repositories</title>
</head>
<body>
<div id="content">
<h1>Invite All Students in <span th:text="${course.getName()}"></span> to Repository</h1>
<p>
Be careful, this may take a little while...
</p>
<form
method="post"
action="#"
enctype="application/x-www-form-urlencoded"
th:action="@{/courses/{code}/students/invite_all(code=${course.getCode()})}"
>
<label for="repository_name_input">Repository Name:</label>
<input id="repository_name_input" type="text" name="repository_name"/>
<label for="keys_input">Api Keys (separate by newline):</label>
<textarea id="keys_input" name="api_keys"></textarea>
<label for="usernames_input">Usernames (separate by newline):</label>
<textarea id="usernames_input" name="usernames"></textarea>
<button type="submit">Submit</button>
</form>
</div>
<div id="sidebar">
</div>
</body>
</html>

View File

@ -39,6 +39,9 @@
<div class="sidebar_block">
<a th:href="@{/courses/{code}/teaching_assistant_teams/create(code=${course.getCode()})}">Create Teaching Assistant Team</a>
</div>
<div class="sidebar_block">
<a th:href="@{/courses/{code}/teaching_assistant_teams/assign_to_student_teams(code=${course.getCode()})}">Assign Teams to Student Teams</a>
</div>
</div>
</body>

View File

@ -0,0 +1,21 @@
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" th:replace="~{layouts/basic_page :: layout (~{::title}, ~{::#content}, ~{::#sidebar})}">
<head>
<title>Assign To Student Teams</title>
</head>
<body>
<div id="content">
<form action="#" th:action="@{/courses/{code}/teaching_assistant_teams/assign_to_student_teams(code=${course.getCode()})}" enctype="application/x-www-form-urlencoded" method="post">
<label for="seed_input">Random Seed:</label>
<input id="seed_input" type="number" name="seed" value="0" required/>
<button type="submit">Submit</button>
</form>
</div>
<div id="sidebar">
</div>
</body>
</html>

View File

@ -6,9 +6,6 @@
<body>
<div id="content">
<p>
Create your course here
</p>
<form action="#" th:action="@{/courses/{code}/teaching_assistant_teams(code=${course.getCode()})}" enctype="application/x-www-form-urlencoded" method="post">
<label for="ta_team_github_team_name">Github team name:</label>
<input id="ta_team_github_team_name" type="text" name="github_team_name"/>
@ -18,19 +15,12 @@
<option th:each="ta: ${course.getTeachingAssistants()}" th:value="${ta.getId()}" th:text="${ta.getFullName()}"></option>
</select>
<label for="ta_team_member_2">Select the first Team Member:</label>
<select id="ta_team_member_2" name="id_2">
<option th:each="ta: ${course.getTeachingAssistants()}" th:value="${ta.getId()}" th:text="${ta.getFullName()}"></option>
</select>
<button type="submit">Submit</button>
</form>
</div>
<div id="sidebar">
<div class="sidebar_block">
block
</div>
</div>
</body>

View File

@ -22,6 +22,16 @@
</li>
</ul>
</li>
<li>
Assigned Student Teams:
<ul>
<li th:each="studentTeam: ${teachingAssistantTeam.getAssignedStudentTeams()}">
<a th:href="@{/courses/{code}/student_teams/{id}(code=${course.getCode()}, id=${studentTeam.getId()})}">
<span th:text="${studentTeam.getId()}"></span>
</a>
</li>
</ul>
</li>
</ul>
</div>

View File

@ -16,6 +16,7 @@
</td>
<td th:text="${student.getStudentNumber()}"></td>
<td th:text="${student.getEmailAddress()}"></td>
<td th:text="${student.getGithubUsername()}"></td>
</tr>
</table>
</div>