Compare commits
4 Commits
improvemen
...
master
Author | SHA1 | Date |
---|---|---|
Andrew Lalis | 6fd994f774 | |
Andrew Lalis | aedea10442 | |
Andrew Lalis | 23b84f45cf | |
Andrew Lalis | 87ddd8c0a3 |
Binary file not shown.
Binary file not shown.
After Width: | Height: | Size: 21 KiB |
Binary file not shown.
Binary file not shown.
After Width: | Height: | Size: 17 KiB |
Binary file not shown.
After Width: | Height: | Size: 21 KiB |
Binary file not shown.
After Width: | Height: | Size: 17 KiB |
Binary file not shown.
|
@ -0,0 +1,233 @@
|
|||
\documentclass{article}
|
||||
|
||||
\usepackage[a4paper]{geometry}
|
||||
\usepackage{graphicx}
|
||||
|
||||
\title{\textbf{Gitgroup Course Manager}\\
|
||||
\textit{Requirements, Design, and Planning}}
|
||||
\author{Andrew Lalis\\
|
||||
\texttt{S3050831}}
|
||||
|
||||
\begin{document}
|
||||
|
||||
\maketitle
|
||||
|
||||
\tableofcontents
|
||||
|
||||
\newpage
|
||||
|
||||
\section{Introduction}
|
||||
Being a teaching assistant for both the introductory and advanced object oriented programming courses at the University of Groningen, I could not help but notice certain inefficiencies in how the courses are managed with regard to student groups and their associated git repositories.\\
|
||||
|
||||
\noindent
|
||||
In particular, when the course is first created, all students must enter their information in a "sign-up form" (usually a Google Form). Then, a simple Python script is used to generate repositories for each student group. While quite straightforward and seemingly an elegant solution, limiting the automation of the course to the creation of repositories leads to a lot more difficulty in practice.\\
|
||||
|
||||
\noindent
|
||||
It is given that some students will drop the course, and this leads to a lot of single-student groups, all of which must be dealt with manually on a case-by-case basis. As a teaching assistant for such courses, I see firsthand the issues this causes. First, teaching assistants such as myself are more reluctant to make modifications to existing groups, not only because it takes a significant amount of time and administrative effort to manually change repository configurations, but also because from an organizational perspective, it is a challenge to keep every single teaching assistant up-to-date. Additionally, the sources of information regarding each student group are quite scattered, and as of right now, there is no one single, definitive source of information on which all teaching assistants can rely. Having a way to unify all the different components of managing Github-utilizing courses is where the \textbf{Gitgroup Course Manager} gets its inspiration.
|
||||
|
||||
\subsection{Vision}
|
||||
When this project is done, managing a course as the lead teaching assistant should be quick and painless, not just when creating student-group repositories, but also when making changes during the course's duration. The application should make basic tasks, such as moving students between groups or removing a student, trivial compared to the current state of affairs.\\
|
||||
|
||||
\noindent
|
||||
While using this application, the teaching assistant should be able to find information about any student in a matter of seconds, rather than minutes, and changes should be made with only a few clicks, rather than spending a quarter of an hour navigating the settings page of Github.\\
|
||||
|
||||
\noindent
|
||||
More specifically, the \textbf{Gitgroup Course Manager} is intended as a desktop application, to be used by the lead teaching assistant to perform all administrative tasks, and to serve as the definitive authority on the state of the course, in terms of current student groups and repositories.
|
||||
|
||||
\section{Requirements}
|
||||
While the vision provides a very subjective and vague overview of the application and its functionality, this section will go into more detail on the functional and non-functional requirements. The functional requirements will describe the explicit details of what the application should do, and the non-functional requirements will cover more abstract concepts such as reliability, security, and ease of use.\\
|
||||
|
||||
\noindent
|
||||
However, before beginning to describe these requirements, it is important to define the different types of users of this application. Luckily this application is not very large, so all potential users of the system can be categorized into the following two groups.
|
||||
|
||||
\subsection{Users}
|
||||
|
||||
\begin{itemize}
|
||||
\item \textbf{Teaching Assistants (TA)} - Administrators of the application who have full access to all functionality and student group information.
|
||||
|
||||
\item \textbf{Students} - Entities in the application, with no interactive permissions. Essentially, students make up the raw data used by the system, but may not make changes to the system.
|
||||
\end{itemize}
|
||||
|
||||
\noindent
|
||||
To ease the process of defining functional requirements, some user stories are included here which describe in layman's terms the essential features of the application, which can then be extrapolated upon in later sections. Note that user stories are written from the point of view of one of the two user types given above, and each story will identify this by starting with, \textit{"As a ..., I want ..."}. However, the vast majority of user stories will be from the teaching assistant's perspective, obviously.
|
||||
|
||||
\subsubsection{User Stories}
|
||||
\begin{enumerate}
|
||||
\item As a teaching assistant, I want to be able to use a Google Form's CSV output to generate a list of student groups, because this is the first step in initializing a new course.
|
||||
|
||||
\item As a teaching assistant, I want to let students enroll for groups using Nestor's built-in group enroll feature, because this makes enrollment much easier for students.
|
||||
|
||||
\item As a teaching assistant, I want the application to automatically assign students to groups based on their preferred partners, because this is a good feature to have for the students' benefit.
|
||||
|
||||
\item As a teaching assistant, I want to generate repositories for newly generated groups, possibly on a case-by-case basis, because this gives me the same precision as manually creating repositories, while also providing the ease of complete automation if preferred.
|
||||
|
||||
\item As a teaching assistant, I want to be able to view all students, the group they're in, and what repository is related to that group, if any, because this is needed for basic day-to-day productivity for all teaching assistants.
|
||||
|
||||
\item As a teaching assistant, I want to have access to all a student's information, and have the ability to modify it, because there are cases where students may incorrectly enter personal information.
|
||||
|
||||
\item As a teaching assistant, I want to be able to create a new empty student group, because this functionality is needed for when students are late in signing up for the course, or in other extraneous situations.
|
||||
|
||||
\item As a teaching assistant, I want to be able to remove empty student groups (including any associated repositories), because this is necessary housekeeping for an administrator of the course.
|
||||
|
||||
\item As a teaching assistant, I want to be able to add or remove a student from a group, because this is necessary when some students leave the course, or new ones join.
|
||||
|
||||
\item As a teaching assistant, I want to be able to assign a teaching assistant team to a group, so that they are responsible for micro-management of that group's activities and grades.
|
||||
|
||||
\item As a teaching assistant, I want to filter student groups by which teaching assistant they are assigned to, because this makes management easier for other teaching assistants.
|
||||
|
||||
\item As a teaching assistant, I want to see all open issues and pull requests on every group's repository, so that it is easier to view all open issues in one place, as opposed to the current workflow on Github.
|
||||
|
||||
\item As a teaching assistant, I want to provide short answers to simple issues from the application itself without having to navigate to the issue on Github's website.
|
||||
|
||||
\item As a teaching assistant, I want to be able to see the time at which a pull request was made, so that it is easy to determine if the pull request passed a certain deadline or not.
|
||||
|
||||
\item As a teaching assistant, I want to add branch protection rules and continuous integration to a branch for many groups at once, since this is by far the most tedious part of manually creating new repositories.
|
||||
|
||||
\item As a teaching assistant, I want the application to save everything permanently, so that even if, for example, a group is removed, their repository is not simply deleted but instead just archived, and individual student information is not deleted but archived.
|
||||
|
||||
\item As a teaching assistant, I want use the mapping of TA teams to student teams to create spreadsheets (using Google Sheets) which can be filled in with grades for each student group.
|
||||
|
||||
\item As a teaching assistant, I want to store filled-in grade sheets so that I can look up a student group's grade for a particular assignment, or even a particular category in an assignment, and make changes later if needed.
|
||||
|
||||
\item As a teaching assistant, I want to be able to export a CSV file containing the student group grades so that it can be uploaded to Nestor and made official.
|
||||
|
||||
\item As a teaching assistant, I want to retrieve some statistics about past assignments in terms of average grades, average grades per teaching assistant, per group, etc. because this will help in identifying cases where there is a discrepancy grading standards that can be addressed.
|
||||
|
||||
\item As a student, I want to provide my personal information to this course as few times as possible, because by complicating the sign-up process, more students are discouraged from signing up.
|
||||
|
||||
\item As a student, I want to be certain that my information is stored securely.
|
||||
\end{enumerate}
|
||||
|
||||
\subsection{Functional Requirements}
|
||||
As can be seen from the large amount of user stories that can be generated about this application, simply listing all the functional requirements is a rather cumbersome task. To better organize the requirements and make the list conceptually easier to understand, the requirements will be split into several categories based on the section of the application to which they apply.
|
||||
|
||||
\subsubsection{Course Initialization}
|
||||
In this first section, the requirements will be listed which pertain directly to course initialization, or the basic functionality that must be present for starting the course, creating groups and repositories, etc.
|
||||
|
||||
\begin{itemize}
|
||||
\item The application can parse CSV data about students from a Google Forms sign-up form, or from Nestor's group-enroll mechanic.
|
||||
|
||||
\item A list of groups can be generated from the parsed information about students, taking into account each students' preference for partners.
|
||||
|
||||
\item The application can automatically generate all repositories for newly generated groups, including adding branch protection and continuous integration to a branch.
|
||||
|
||||
\item Students are automatically invited to the repository for the group they're assigned to.
|
||||
|
||||
\item The application can parse CSV data about teaching assistants to build a list of all teaching assistants in the course, and optionally, if they are grouped together into teams.
|
||||
|
||||
\item A teaching assistant team can be assigned to each student group.
|
||||
\end{itemize}
|
||||
|
||||
\subsubsection{Course Management}
|
||||
In this section will be found all the requirements for general management of the course, for the duration of the course.
|
||||
|
||||
\begin{itemize}
|
||||
\item The user (teaching assistant) is able to view a list of students in the course, a list of student groups, a list of repositories, a list of teaching assistants, and a list of teaching assistant groups, such that it is possible to view a list of all entities in the application.
|
||||
|
||||
\item The teaching assistant may modify some information of an entity, while other information may be forbidden from editing.
|
||||
|
||||
\item Student groups can be created, edited (changing the members in a group), or removed by a teaching assistant.
|
||||
|
||||
\item The application should be able to provide a list of all open issues in all student group repositories, and a list of all open pull requests (including time and date, especially with regard to pull requests).
|
||||
|
||||
\item Teaching assistants may provide simple replies to issues from within the application, and if that is not enough, a link is provided to the issue online.
|
||||
|
||||
\item A command prompt should be available for advanced users to execute commands separately than through the interface.
|
||||
\end{itemize}
|
||||
|
||||
\subsubsection{Grading}
|
||||
In this section, there are a few requirements specific to grading which can be addressed separately from others, and in fact, the grading system will be implemented as a sub-system of the main application, as will be seen in the design section of this document.
|
||||
|
||||
\begin{itemize}
|
||||
\item The application can generate grading sheets for a certain assignment, given the number of categories (and grading weights), and that each group is assigned a teaching assistant team.
|
||||
|
||||
\item The application can parse filled-in grading sheets for a certain assignment, and stores each group's grade for that assignment.
|
||||
|
||||
\item A CSV file can be exported, containing grades in such a format as to be accepted by Nestor's grade center.
|
||||
|
||||
\item Reports can be generated for students or student groups, giving a detailed overview of the grades and any comments that were given along with the grade itself.
|
||||
\end{itemize}
|
||||
|
||||
\subsection{Non-Functional Requirements}
|
||||
For this application, because it will function as a desktop application, does not need to concern itself with the performance issues which are commonplace in web applications. Additionally, by operating on the local system, it does not suffer from uptime issues that web apps can deal with. Therefore, the non-functional requirements for this application are simplified to a subset of what many modern projects must deal with.
|
||||
|
||||
\begin{itemize}
|
||||
\item \textbf{Ease of use} - The application should be intuitive to use, and have necessary redundancies such that the user's interactive experience is similar to many other desktop applications. However, due to the time constraints, this may not be fully achieved.
|
||||
|
||||
\item \textbf{Data safety} - All changes to the data should be permanent, so that when the user sees an updated value in the interface, the user is confident that the value has been saved to the hard drive, and will persist so long as the computer does.
|
||||
|
||||
\item \textbf{Data security} - While not quite as large of an issue as it would be with a web application, steps should be taken to ensure a high level of security, especially with regard to student grades, so that data is consolidated in one place, accessible only to the teaching assistant.
|
||||
\end{itemize}
|
||||
|
||||
\section{Design}
|
||||
The application can be divided into three main conceptual components, the initialization of a new course, the management of an existing course, and grade recording. These three modules form the basis of the application design.
|
||||
|
||||
\begin{figure}[h]
|
||||
\centering
|
||||
\includegraphics[width=0.4\textwidth]{image/modules.png}
|
||||
\caption{The modules in this application, as well as the user's starting point.}
|
||||
\end{figure}
|
||||
|
||||
\noindent
|
||||
As shown in Figure 1, the user may begin the application either by initializing a new course, or continuing directly to the management module. From within the management module, the user may interact with the grading module.
|
||||
|
||||
\subsection{Initialization Module}
|
||||
In this first module, the teaching assistant is prompted to enter all information needed to initialize a course. This includes:
|
||||
|
||||
\begin{itemize}
|
||||
\item The Github Organization name.
|
||||
|
||||
\item The teaching assistant's access token.
|
||||
|
||||
\item The name of the repository which contains the assignments for the course.
|
||||
|
||||
\item Data about teaching assistants (Names, emails, Github usernames, etc.).
|
||||
|
||||
\item Student CSV data.
|
||||
\end{itemize}
|
||||
|
||||
\noindent
|
||||
With this information, it is then possible to let the application populate its database with all the student and teaching assistant information, and generate groups based on student preference. From there, the user is directed to the management module, wherein they will have access to all entities (students, groups, teaching assistants, etc.), and where most functionality of the application is implemented.
|
||||
|
||||
\subsection{Management Module}
|
||||
In the management module is where the user will spend the majority of their time with the application. It will consist of three sub-modules: the command-line interface, the entity list view, and the entity detail view.
|
||||
|
||||
\begin{figure}[h]
|
||||
\centering
|
||||
\includegraphics[width=0.6\textwidth]{image/managementModule.png}
|
||||
\caption{The management module.}
|
||||
\end{figure}
|
||||
|
||||
\subsubsection{Command-line Interface}
|
||||
In this section of the managment module, the user will be able to see program output in a console-like format, with some extra logging details available. The user will also be able to enter text commands in an input field, as a means of being able to perform all actions without having to navigate the interface. Additionally, this interface can serve as a useful debugging tool during development, as it gives the developer the ability to test all functiontionality of the application via the command line, instead of being forced to create all the user interfaces to gain access to abstract commands.
|
||||
|
||||
\subsubsection{Entity List View}
|
||||
This view will make up the majority of the management module's screen space, and is intended to command the majority of the user's attention. It is in this view that the user will be able to see a list of entities (students, student groups, teaching assistants, repositories, etc.) and interact with them. Selecting an entity in this list will display more detailed information about it in the entity detail view, which is discussed in the next section.\\
|
||||
|
||||
\noindent
|
||||
Context menus will be used to provide the majority of functionality, so that when a user right-clicks on any entity, that entity will provide a list of actions which could be done to it, and clicking any one of the associated menu buttons will trigger some action. Multiple entities may be selected at once, and then only certain \textit{multiple-entity actions} may be performed, such as removing the entity, assigning multiple students to the same group, etc.
|
||||
|
||||
\subsubsection{Entity Detail View}
|
||||
In this section will be shown all the detailed information about an entity which, in some cases, cannot all be displayed in the entity list view. Inside the entity detail view, it will be possible to edit the properties of an entity which could not be changed in the entity list view.
|
||||
|
||||
\subsection{Grading Module}
|
||||
This module, accessed in the same view as the management module, will provide additional functionalities that help a teaching assistant to organize grading. In particular, this module will provide the following facilities:
|
||||
|
||||
\begin{itemize}
|
||||
\item \textbf{Assignment sheet creation} - Guides the user through the creation of a grading sheet which can be filled in by teaching assistants during grading, with one or more weighted categories.
|
||||
|
||||
\item \textbf{Assignment sheet reading} - The user may provide a filled-in grading sheet which was created previously in the application, and use this for recording the grades of student groups.
|
||||
|
||||
\item \textbf{Grade reports} - The application will be able to generate PDF reports for individual students or student groups, which contain all grades, comments and other feedback for the included students.
|
||||
|
||||
\item \textbf{Export for Nestor} - Ideally, grades managed in this application should be able to be transferred to Nestor in only a few clicks, and this can be done by generating a CSV sheet which can be uploaded to Nestor.
|
||||
\end{itemize}
|
||||
|
||||
\newpage
|
||||
\section{Planning}
|
||||
With a solid plan for the application, as well as the lack of a way to perform comprehensive large-scale testing of the system as a whole, the waterfall model will be used for development of this project. While many software development platfoms are starting to use "Agile" development models in which a minimum viable product is released in a matter of one or two weeks, an application like this is not really viable until the majority of the requirements mentioned in this document are met. Otherwise, it is simply more effective to manually manage a course's student groups and repositories.\\
|
||||
|
||||
\noindent
|
||||
Therefore, the project will be divided into three phases of development: Planning and Design, Application Development, and finally, Testing and Bug Fixing. This document itself represents the majority of the Planning and Design phase, and then soon the project will move into the development phase, where the design is fixed while the product is being made. Once all requirements in this document are met, the project has reached the Testing and Bug Fixing phase, in which the application is tested with large data sets and many extraneous situations to ensure that normal use will not result in critical errors if and when the application is deployed into real-world use.
|
||||
|
||||
\end{document}
|
|
@ -48,6 +48,9 @@ public class Main {
|
|||
|
||||
initializeTestingData();
|
||||
StartView startView = new StartView(manager, "InitializerTesting", userOptions.get("token"));
|
||||
manager.setAccessToken(startView.getAccessToken());
|
||||
manager.setOrganizationName(startView.getOrganizationName());
|
||||
manager.deleteAllRepositories("AOOP");
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -260,6 +260,45 @@ public class GithubManager {
|
|||
this.inviteStudentsToRepos(team, assignmentsRepo);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a student team's repository, without inviting students or doing anything extra.
|
||||
* @param team The student team to create the repository for.
|
||||
* @param prefix A prefix to the repository name.
|
||||
* @return The name of the repository created, or null if an error occurred.
|
||||
*/
|
||||
public String setupStudentRepo(StudentTeam team, String prefix) {
|
||||
GHRepository repo;
|
||||
try {
|
||||
repo = this.createRepository(team.generateUniqueName(prefix), null, team.generateRepoDescription(), false, true, false);
|
||||
this.createDevelopmentBranch(repo);
|
||||
return repo.getName();
|
||||
} catch (IOException e) {
|
||||
logger.severe("Repository for student team " + team.getNumber() + " could not be created.");
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the repository with the given name.
|
||||
* @param name The name of the repository to delete.
|
||||
* @return True if the deletion was successful, or false if an error occurred.
|
||||
*/
|
||||
public boolean deleteRepository(String name) {
|
||||
try {
|
||||
GHRepository repo = this.getOrganization().getRepository(name);
|
||||
if (repo == null) {
|
||||
return false;
|
||||
}
|
||||
repo.delete();
|
||||
return true;
|
||||
} catch (IOException e) {
|
||||
logger.severe("Could not delete repository: " + name);
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes all repositories in the organization.
|
||||
* @param substring The substring which repository names should contain to be deleted.
|
||||
|
@ -302,12 +341,25 @@ public class GithubManager {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Archives a repository with the given name.
|
||||
* @param name The name of the repository to archive.
|
||||
* @return True if successful, false otherwise.
|
||||
*/
|
||||
public boolean archiveRepository(String name) {
|
||||
try {
|
||||
return this.archiveRepository(this.getOrganization().getRepository(name));
|
||||
} catch (IOException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Archives a repository so that it can no longer be manipulated.
|
||||
* TODO: Change to using Github API instead of Apache HttpUtils.
|
||||
* @param repo The repository to archive.
|
||||
*/
|
||||
private void archiveRepository(GHRepository repo) {
|
||||
private boolean archiveRepository(GHRepository repo) {
|
||||
try {
|
||||
HttpPatch patch = new HttpPatch("https://api.github.com/repos/" + repo.getFullName() + "?access_token=" + this.accessToken);
|
||||
CloseableHttpClient client = HttpClientBuilder.create().build();
|
||||
|
@ -321,9 +373,11 @@ public class GithubManager {
|
|||
throw new IOException("Could not archive repository: " + repo.getName() + ". Code: " + response.getStatusLine().getStatusCode());
|
||||
}
|
||||
logger.info("Archived repository: " + repo.getFullName());
|
||||
return true;
|
||||
} catch (IOException e) {
|
||||
logger.severe("Could not archive repository: " + repo.getName());
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -429,7 +483,7 @@ public class GithubManager {
|
|||
builder.issues(hasIssues);
|
||||
builder.description(description);
|
||||
builder.gitignoreTemplate("Java");
|
||||
builder.private_(false); // TODO: Testing value of false. Production uses true.
|
||||
builder.private_(isPrivate); // TODO: Testing value of false. Production uses true.
|
||||
GHRepository repo = builder.create();
|
||||
logger.fine("Created repository: " + repo.getName());
|
||||
return repo;
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package nl.andrewlalis.model;
|
||||
|
||||
import nl.andrewlalis.model.database.BaseEntity;
|
||||
import nl.andrewlalis.ui.view.components.Detailable;
|
||||
import nl.andrewlalis.util.Pair;
|
||||
|
||||
import javax.persistence.Column;
|
||||
|
@ -16,7 +15,7 @@ import java.util.List;
|
|||
*/
|
||||
@Entity(name = "Person")
|
||||
@Table(name = "persons")
|
||||
public abstract class Person extends BaseEntity implements Detailable {
|
||||
public abstract class Person extends BaseEntity {
|
||||
|
||||
/**
|
||||
* The unique identification number for this person. (P- or S-Number)
|
||||
|
@ -148,11 +147,6 @@ public abstract class Person extends BaseEntity implements Detailable {
|
|||
return this.getName() + ", " + this.getNumber();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDetailDescription() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Pair<String, String>> getDetailPairs() {
|
||||
List<Pair<String, String>> pairs = new ArrayList<>();
|
||||
|
|
|
@ -11,7 +11,6 @@ import java.util.logging.Logger;
|
|||
* Represents one student's github information.
|
||||
*/
|
||||
@Entity(name = "Student")
|
||||
@Table(name="students")
|
||||
public class Student extends Person {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(Student.class.getName());
|
||||
|
|
|
@ -5,7 +5,6 @@ import nl.andrewlalis.util.Pair;
|
|||
import javax.persistence.Column;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.ManyToOne;
|
||||
import javax.persistence.Table;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
|
@ -13,7 +12,6 @@ import java.util.List;
|
|||
* Represents one or more students' collective information.
|
||||
*/
|
||||
@Entity(name = "StudentTeam")
|
||||
@Table(name = "student_teams")
|
||||
public class StudentTeam extends Team {
|
||||
|
||||
/**
|
||||
|
|
|
@ -2,10 +2,7 @@ package nl.andrewlalis.model;
|
|||
|
||||
import org.kohsuke.github.GHTeam;
|
||||
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.OneToMany;
|
||||
import javax.persistence.Table;
|
||||
import javax.persistence.Transient;
|
||||
import javax.persistence.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
@ -14,7 +11,6 @@ import java.util.List;
|
|||
* Represents a teaching assistant team, which is itself a 'team' in the organization.
|
||||
*/
|
||||
@Entity(name = "TATeam")
|
||||
@Table(name = "ta_teams")
|
||||
public class TATeam extends Team {
|
||||
|
||||
/**
|
||||
|
@ -32,8 +28,20 @@ public class TATeam extends Team {
|
|||
* A list of all student teams for which this TA team is responsible.
|
||||
*/
|
||||
@OneToMany
|
||||
@JoinTable(
|
||||
name = "ta_teams_student_teams",
|
||||
joinColumns = { @JoinColumn(name = "ta_team_id") },
|
||||
inverseJoinColumns = { @JoinColumn(name = "student_team_id") }
|
||||
)
|
||||
private List<StudentTeam> studentTeams;
|
||||
|
||||
/**
|
||||
* Constructs an empty TA team.
|
||||
*/
|
||||
public TATeam() {
|
||||
// Do nothing here, since the no-arg constructor of Team will be called.
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a team without any teaching assistant members.
|
||||
* @param name The name of the team.
|
||||
|
|
|
@ -1,15 +1,20 @@
|
|||
package nl.andrewlalis.model;
|
||||
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.Table;
|
||||
|
||||
/**
|
||||
* Represents an administrator in the organization, who manages student teams.
|
||||
*/
|
||||
@Entity(name = "TeachingAssistant")
|
||||
@Table(name = "teaching_assistants")
|
||||
public class TeachingAssistant extends Person {
|
||||
|
||||
/**
|
||||
* Constructs an empty Teaching Assistant object.
|
||||
*/
|
||||
public TeachingAssistant() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a Teaching Assistant from only a github username.
|
||||
* @param githubUsername The person's github username.
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package nl.andrewlalis.model;
|
||||
|
||||
import nl.andrewlalis.model.database.BaseEntity;
|
||||
import nl.andrewlalis.ui.view.components.Detailable;
|
||||
import nl.andrewlalis.util.Pair;
|
||||
|
||||
import javax.persistence.*;
|
||||
|
@ -15,7 +14,7 @@ import java.util.List;
|
|||
*/
|
||||
@Entity(name = "Team")
|
||||
@Table(name = "teams")
|
||||
public abstract class Team extends BaseEntity implements Detailable {
|
||||
public abstract class Team extends BaseEntity {
|
||||
|
||||
/**
|
||||
* An identification number unique to this team alone.
|
||||
|
@ -176,11 +175,6 @@ public abstract class Team extends BaseEntity implements Detailable {
|
|||
return String.valueOf(this.getNumber());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDetailDescription() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Pair<String, String>> getDetailPairs() {
|
||||
List<Pair<String, String>> pairs = new ArrayList<>();
|
||||
|
|
|
@ -1,15 +1,14 @@
|
|||
package nl.andrewlalis.model.database;
|
||||
|
||||
import javax.persistence.GeneratedValue;
|
||||
import javax.persistence.GenerationType;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.MappedSuperclass;
|
||||
import nl.andrewlalis.ui.view.components.Detailable;
|
||||
|
||||
import javax.persistence.*;
|
||||
|
||||
/**
|
||||
* Defines a base entity which all others in the database extend from.
|
||||
*/
|
||||
@MappedSuperclass
|
||||
public abstract class BaseEntity {
|
||||
public abstract class BaseEntity implements Detailable {
|
||||
|
||||
/**
|
||||
* The number for this entity.
|
||||
|
@ -18,6 +17,12 @@ public abstract class BaseEntity {
|
|||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* Whether or not this entity is archived.
|
||||
*/
|
||||
@Column(name = "archived", nullable = false)
|
||||
private boolean archived = false;
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
@ -25,4 +30,12 @@ public abstract class BaseEntity {
|
|||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public boolean isArchived() {
|
||||
return archived;
|
||||
}
|
||||
|
||||
public void setArchived(boolean archived) {
|
||||
this.archived = archived;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,7 +19,9 @@ public class DbHelper {
|
|||
*/
|
||||
public static List<Student> getStudents() {
|
||||
Session session = DbUtil.getSessionFactory().openSession();
|
||||
List<Student> students = (List<Student>) session.createQuery("from Student").list();
|
||||
List<Student> students = (List<Student>) session.createQuery(
|
||||
"SELECT student FROM Student student"
|
||||
).getResultList();
|
||||
session.close();
|
||||
return students;
|
||||
}
|
||||
|
@ -48,9 +50,23 @@ public class DbHelper {
|
|||
*/
|
||||
public static List<StudentTeam> getStudentTeams() {
|
||||
Session session = DbUtil.getSessionFactory().openSession();
|
||||
List<StudentTeam> studentTeams = (List<StudentTeam>) session.createQuery("from StudentTeam").list();
|
||||
List<StudentTeam> studentTeams = (List<StudentTeam>) session.createQuery(
|
||||
"SELECT team FROM StudentTeam team"
|
||||
).getResultList();
|
||||
session.close();
|
||||
return studentTeams;
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes some transaction content.
|
||||
* @param content The content to execute.
|
||||
*/
|
||||
public static void executeTransactionContent(TransactionContent content) {
|
||||
Session session = DbUtil.getSessionFactory().openSession();
|
||||
Transaction tx = session.beginTransaction();
|
||||
content.doTransaction(session);
|
||||
tx.commit();
|
||||
session.close();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
package nl.andrewlalis.model.database;
|
||||
|
||||
import org.hibernate.Session;
|
||||
|
||||
/**
|
||||
* Defines a series of operations done within a transaction.
|
||||
*/
|
||||
public interface TransactionContent {
|
||||
|
||||
/**
|
||||
* Performs a transaction. Implement this method by updating, inserting, or deleting entities.
|
||||
* @param session The session in which operations are being done.
|
||||
*/
|
||||
void doTransaction(Session session);
|
||||
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
package nl.andrewlalis.ui.control.listeners.management_view;
|
||||
|
||||
import nl.andrewlalis.ui.view.components.tables.EntityTable;
|
||||
import nl.andrewlalis.ui.view.components.tables.popup_menu.EntityMenuItem;
|
||||
import nl.andrewlalis.ui.view.components.tables.popup_menu.EntitySelectionType;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.event.PopupMenuEvent;
|
||||
import javax.swing.event.PopupMenuListener;
|
||||
import java.awt.*;
|
||||
|
||||
/**
|
||||
* Listener to be attached to popup menus for entity tables, so that certain menu items are either enabled or disabled
|
||||
* depending on the type of selection in the table. Some menu items should always be enabled, some should only be
|
||||
* enabled for single selections, and some should be enabled for multi-selections.
|
||||
*/
|
||||
public class EntityTablePopupMenuListener implements PopupMenuListener {
|
||||
|
||||
/**
|
||||
* The table on which the menu for this listener is attached.
|
||||
*/
|
||||
private EntityTable table;
|
||||
|
||||
public EntityTablePopupMenuListener(EntityTable table) {
|
||||
this.table = table;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void popupMenuWillBecomeVisible(PopupMenuEvent popupMenuEvent) {
|
||||
JPopupMenu menu = (JPopupMenu) popupMenuEvent.getSource();
|
||||
|
||||
// First, determine the selection type.
|
||||
int selectionType = 0;
|
||||
if (this.table.getSelectedRowCount() == 1) {
|
||||
selectionType |= EntitySelectionType.SINGLE;
|
||||
} else if (this.table.getSelectedRowCount() > 1) {
|
||||
selectionType |= EntitySelectionType.MULTIPLE;
|
||||
}
|
||||
|
||||
// Iterate over all menu items, and let them decide to enable or disable themselves.
|
||||
for (Component component: menu.getComponents()) {
|
||||
if (component instanceof EntityMenuItem) {
|
||||
EntityMenuItem menuItem = (EntityMenuItem) component;
|
||||
menuItem.willBecomeVisible(selectionType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void popupMenuWillBecomeInvisible(PopupMenuEvent popupMenuEvent) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void popupMenuCanceled(PopupMenuEvent popupMenuEvent) {
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
package nl.andrewlalis.ui.control.listeners.management_view.student_actions;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.event.ActionEvent;
|
||||
|
||||
public class NewStudentListener extends StudentListener {
|
||||
|
||||
/**
|
||||
* Constructs a table row listener.
|
||||
*
|
||||
* @param table The table on which to get selected rows.
|
||||
*/
|
||||
public NewStudentListener(JTable table) {
|
||||
super(table);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent actionEvent) {
|
||||
System.out.println("New Student");
|
||||
}
|
||||
}
|
|
@ -1,28 +1,74 @@
|
|||
package nl.andrewlalis.ui.control.listeners.management_view.student_actions;
|
||||
|
||||
import nl.andrewlalis.ui.control.listeners.management_view.TableRowListener;
|
||||
import nl.andrewlalis.model.Student;
|
||||
import nl.andrewlalis.model.StudentTeam;
|
||||
import nl.andrewlalis.model.database.DbHelper;
|
||||
import nl.andrewlalis.ui.view.table_models.StudentTableModel;
|
||||
import nl.andrewlalis.ui.view.table_models.StudentTeamTableModel;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Listens for when the user intends to remove a selected student from the course entirely. This entails a few things:
|
||||
* 1. Remove them from any team they are in.
|
||||
* 2. Archive any repository that is empty as a result of removing them.
|
||||
* 3. Remove the student from the list of students.
|
||||
* 2. Remove the student from the list of students.
|
||||
* (This should not actually remove the record, just set it as removed.)
|
||||
*/
|
||||
public class RemoveFromCourseListener extends TableRowListener {
|
||||
public class RemoveFromCourseListener extends StudentListener {
|
||||
|
||||
public RemoveFromCourseListener(JTable table) {
|
||||
/**
|
||||
* A reference to the team table model, used for updating that table when a student is removed.
|
||||
*/
|
||||
private StudentTeamTableModel teamModel;
|
||||
|
||||
public RemoveFromCourseListener(JTable table, StudentTeamTableModel teamModel) {
|
||||
super(table);
|
||||
this.teamModel = teamModel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent actionEvent) {
|
||||
StudentTableModel model = (StudentTableModel) this.getTable().getModel();
|
||||
StudentTableModel model = this.getModel();
|
||||
|
||||
System.out.println(model.getStudentAt(this.getSelectedRow()));
|
||||
// Get all the selected students.
|
||||
List<Student> studentsToRemove = this.getSelectedStudents();
|
||||
|
||||
if (studentsToRemove.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// First get confirmation that the student should be removed.
|
||||
int response = JOptionPane.showConfirmDialog(
|
||||
SwingUtilities.getWindowAncestor(this.getTable()),
|
||||
"Are you sure you wish to remove the student(s)?\nThis action is permanent.",
|
||||
"Remove students",
|
||||
JOptionPane.YES_NO_OPTION
|
||||
);
|
||||
if (response == JOptionPane.NO_OPTION) {
|
||||
return;
|
||||
}
|
||||
|
||||
// First remove the student from any team they were in.
|
||||
// Then set the student as archived.
|
||||
DbHelper.executeTransactionContent(session -> {
|
||||
for (Student student : studentsToRemove) {
|
||||
|
||||
StudentTeam team = student.getAssignedTeam();
|
||||
// Only if the student's team is still active, should they be removed from it.
|
||||
if (team != null && !team.isArchived()) {
|
||||
student.getAssignedTeam().removeMember(student);
|
||||
session.update(student.getAssignedTeam());
|
||||
}
|
||||
|
||||
student.setArchived(true);
|
||||
session.update(student);
|
||||
}
|
||||
});
|
||||
|
||||
// Update the models afterwards.
|
||||
model.setStudentsList(DbHelper.getStudents());
|
||||
this.teamModel.setStudentTeamsList(DbHelper.getStudentTeams());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,12 +2,14 @@ package nl.andrewlalis.ui.control.listeners.management_view.student_actions;
|
|||
|
||||
import nl.andrewlalis.model.Student;
|
||||
import nl.andrewlalis.model.StudentTeam;
|
||||
import nl.andrewlalis.ui.control.listeners.management_view.TableRowListener;
|
||||
import nl.andrewlalis.model.database.DbHelper;
|
||||
import nl.andrewlalis.ui.view.dialogs.TeamChooserDialog;
|
||||
import nl.andrewlalis.ui.view.table_models.StudentTableModel;
|
||||
import nl.andrewlalis.ui.view.table_models.StudentTeamTableModel;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Listens for when the user wishes to set the team of a certain student. This should do the following:
|
||||
|
@ -15,17 +17,54 @@ import java.awt.event.ActionEvent;
|
|||
* 2. StudentTeam object is created or modified.
|
||||
* 3. The repository is updated automatically.
|
||||
*/
|
||||
public class SetTeamListener extends TableRowListener {
|
||||
public class SetTeamListener extends StudentListener {
|
||||
|
||||
public SetTeamListener(JTable table) {
|
||||
/**
|
||||
* A reference to the student teams model, which needs to update in conjunction with the students table.
|
||||
*/
|
||||
private StudentTeamTableModel teamModel;
|
||||
|
||||
/**
|
||||
* Creates a new listener for when a user sets a student's team to a new team.
|
||||
* @param table The table on which this listener is listening.
|
||||
* @param teamModel The model representing this list of teams. This is needed to update after a change.
|
||||
*/
|
||||
public SetTeamListener(JTable table, StudentTeamTableModel teamModel) {
|
||||
super(table);
|
||||
this.teamModel = teamModel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent actionEvent) {
|
||||
StudentTableModel model = (StudentTableModel) this.getTable().getModel();
|
||||
Student student = model.getStudentAt(this.getSelectedRow());
|
||||
StudentTableModel model = this.getModel();
|
||||
Student student = model.getStudentAt(this.getTable().convertRowIndexToModel(this.getSelectedRow()));
|
||||
List<StudentTeam> teams = this.teamModel.getTeams();
|
||||
|
||||
StudentTeam chosenTeam = (StudentTeam) new TeamChooserDialog(SwingUtilities.getWindowAncestor(this.getTable())).getSelectedTeam();
|
||||
// Get the selected team via a dialog.
|
||||
StudentTeam chosenTeam = (StudentTeam) new TeamChooserDialog(
|
||||
SwingUtilities.getWindowAncestor(this.getTable()),
|
||||
teams
|
||||
).getSelectedTeam();
|
||||
|
||||
// Check that the new team is not the current team.
|
||||
if (chosenTeam.equals(student.getAssignedTeam())) {
|
||||
JOptionPane.showMessageDialog(this.getTable(), "You must choose a new team.", "Same team", JOptionPane.WARNING_MESSAGE);
|
||||
return;
|
||||
}
|
||||
|
||||
// Perform the updates to the database here.
|
||||
DbHelper.executeTransactionContent(session -> {
|
||||
student.getAssignedTeam().removeMember(student);
|
||||
session.update(student.getAssignedTeam());
|
||||
|
||||
chosenTeam.addMember(student);
|
||||
session.update(chosenTeam);
|
||||
|
||||
student.assignToTeam(chosenTeam);
|
||||
session.update(student);
|
||||
});
|
||||
|
||||
this.teamModel.setStudentTeamsList(DbHelper.getStudentTeams());
|
||||
model.setStudentsList(DbHelper.getStudents());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
package nl.andrewlalis.ui.control.listeners.management_view.student_actions;
|
||||
|
||||
import nl.andrewlalis.model.Student;
|
||||
import nl.andrewlalis.ui.control.listeners.management_view.TableRowListener;
|
||||
import nl.andrewlalis.ui.view.table_models.StudentTableModel;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* An abstract listener for action which is triggered from the Students table.
|
||||
*/
|
||||
public abstract class StudentListener extends TableRowListener {
|
||||
|
||||
/**
|
||||
* Constructs a table row listener.
|
||||
*
|
||||
* @param table The table on which to get selected rows.
|
||||
*/
|
||||
public StudentListener(JTable table) {
|
||||
super(table);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The students model that this listener applies to.
|
||||
*/
|
||||
public StudentTableModel getModel() {
|
||||
return (StudentTableModel) this.getTable().getModel();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return A list of selected students.
|
||||
*/
|
||||
public List<Student> getSelectedStudents() {
|
||||
StudentTableModel model = (StudentTableModel) this.getTable().getModel();
|
||||
List<Student> selectedStudents = new ArrayList<>();
|
||||
for (int row : this.getTable().getSelectedRows()) {
|
||||
selectedStudents.add(model.getStudentAt(this.getTable().convertRowIndexToModel(row)));
|
||||
}
|
||||
return selectedStudents;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
package nl.andrewlalis.ui.control.listeners.management_view.student_team_actions;
|
||||
|
||||
import nl.andrewlalis.git_api.GithubManager;
|
||||
import nl.andrewlalis.model.StudentTeam;
|
||||
import nl.andrewlalis.model.database.DbHelper;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.event.ActionEvent;
|
||||
|
||||
/**
|
||||
* Listens for when the user wants to archive one or more repositories.
|
||||
*/
|
||||
public class ArchiveRepositoryListener extends StudentTeamListener {
|
||||
|
||||
/**
|
||||
* The manager, for interacting with Github, to perform the archiving.
|
||||
*/
|
||||
private GithubManager manager;
|
||||
|
||||
/**
|
||||
* Constructs a table row listener.
|
||||
*
|
||||
* @param table The table on which to get selected rows.
|
||||
* @param manager The manager used to interact with Github.
|
||||
*/
|
||||
public ArchiveRepositoryListener(JTable table, GithubManager manager) {
|
||||
super(table);
|
||||
this.manager = manager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent actionEvent) {
|
||||
for (StudentTeam team : this.getSelectedStudentTeams()) {
|
||||
if (team.isArchived()) {
|
||||
JOptionPane.showMessageDialog(
|
||||
this.getTable().getTopLevelAncestor(),
|
||||
"Team " + team.getNumber() + " is already archived.",
|
||||
"Already archived",
|
||||
JOptionPane.WARNING_MESSAGE
|
||||
);
|
||||
}
|
||||
if (team.getRepositoryName() == null) {
|
||||
JOptionPane.showMessageDialog(
|
||||
this.getTable().getTopLevelAncestor(),
|
||||
"Team " + team.getNumber() + " does not have a repository to archive." ,
|
||||
"No repository",
|
||||
JOptionPane.WARNING_MESSAGE
|
||||
);
|
||||
}
|
||||
|
||||
if (!team.isArchived() && team.getRepositoryName() != null) {
|
||||
new Thread(() -> {
|
||||
boolean success = this.manager.archiveRepository(team.getRepositoryName());
|
||||
if (success) {
|
||||
DbHelper.executeTransactionContent(session -> {
|
||||
team.setArchived(true);
|
||||
session.update(team);
|
||||
this.getModel().fireTableDataChanged();
|
||||
});
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
package nl.andrewlalis.ui.control.listeners.management_view.student_team_actions;
|
||||
|
||||
import nl.andrewlalis.git_api.GithubManager;
|
||||
import nl.andrewlalis.model.StudentTeam;
|
||||
import nl.andrewlalis.model.database.DbHelper;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.event.ActionEvent;
|
||||
|
||||
/**
|
||||
* Listens for when the user wishes to delete one or more repositories.
|
||||
*/
|
||||
public class DeleteRepositoryListener extends StudentTeamListener {
|
||||
|
||||
/**
|
||||
* The GithubManager used for deleting the repositories.
|
||||
*/
|
||||
private GithubManager manager;
|
||||
|
||||
/**
|
||||
* Constructs a table row listener.
|
||||
*
|
||||
* @param table The table on which to get selected rows.
|
||||
*/
|
||||
public DeleteRepositoryListener(JTable table, GithubManager manager) {
|
||||
super(table);
|
||||
this.manager = manager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent actionEvent) {
|
||||
for (StudentTeam team : this.getSelectedStudentTeams()) {
|
||||
|
||||
if (team.getRepositoryName() == null) {
|
||||
JOptionPane.showMessageDialog(
|
||||
this.getTable().getTopLevelAncestor(),
|
||||
"Team " + team.getNumber() + " does not have a repository.",
|
||||
"No repository",
|
||||
JOptionPane.WARNING_MESSAGE
|
||||
);
|
||||
}
|
||||
|
||||
if (team.getRepositoryName() != null) {
|
||||
new Thread(() -> {
|
||||
boolean success = this.manager.deleteRepository(team.getRepositoryName());
|
||||
if (success) {
|
||||
DbHelper.executeTransactionContent(session -> {
|
||||
team.setRepositoryName(null);
|
||||
this.getModel().fireTableDataChanged();
|
||||
session.update(team);
|
||||
});
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
package nl.andrewlalis.ui.control.listeners.management_view.student_team_actions;
|
||||
|
||||
import nl.andrewlalis.git_api.GithubManager;
|
||||
import nl.andrewlalis.model.StudentTeam;
|
||||
import nl.andrewlalis.model.database.DbHelper;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.event.ActionEvent;
|
||||
|
||||
/**
|
||||
* Listens for when the user wishes to create a repository for one or more selected teams.
|
||||
*/
|
||||
public class GenerateRepositoryListener extends StudentTeamListener {
|
||||
|
||||
/**
|
||||
* The GithubManager object used to manipulate repositories on Github.
|
||||
*/
|
||||
private GithubManager manager;
|
||||
|
||||
/**
|
||||
* Constructs a table row listener.
|
||||
*
|
||||
* @param table The table on which to get selected rows.
|
||||
*/
|
||||
public GenerateRepositoryListener(JTable table, GithubManager manager) {
|
||||
super(table);
|
||||
this.manager = manager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent actionEvent) {
|
||||
|
||||
String prefix = JOptionPane.showInputDialog(
|
||||
SwingUtilities.getWindowAncestor(this.getTable()),
|
||||
"Please give a prefix for the repository(s).",
|
||||
"Repository Prefix",
|
||||
JOptionPane.QUESTION_MESSAGE
|
||||
);
|
||||
if (prefix == null) return;
|
||||
|
||||
for (StudentTeam team : this.getSelectedStudentTeams()) {
|
||||
|
||||
if (team.isArchived()) {
|
||||
JOptionPane.showMessageDialog(
|
||||
this.getTable().getTopLevelAncestor(),
|
||||
"Team " + team.getNumber() + " is archived.",
|
||||
"Team archived",
|
||||
JOptionPane.WARNING_MESSAGE
|
||||
);
|
||||
}
|
||||
|
||||
if (team.getRepositoryName() != null) {
|
||||
JOptionPane.showMessageDialog(
|
||||
this.getTable().getTopLevelAncestor(),
|
||||
"Team " + team.getNumber() + " already has a repository.",
|
||||
"Repository exists",
|
||||
JOptionPane.WARNING_MESSAGE
|
||||
);
|
||||
}
|
||||
|
||||
if (!team.isArchived() && team.getRepositoryName() == null) {
|
||||
new Thread(() -> {
|
||||
DbHelper.executeTransactionContent(session -> {
|
||||
String name = this.manager.setupStudentRepo(team, prefix);
|
||||
team.setRepositoryName(name);
|
||||
this.getModel().fireTableDataChanged();
|
||||
session.update(team);
|
||||
});
|
||||
}).start();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
package nl.andrewlalis.ui.control.listeners.management_view.student_team_actions;
|
||||
|
||||
import nl.andrewlalis.git_api.GithubManager;
|
||||
import nl.andrewlalis.model.StudentTeam;
|
||||
import nl.andrewlalis.model.database.DbHelper;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.event.ActionEvent;
|
||||
|
||||
/**
|
||||
* Listens for when the user wants to remove a team from the list of teams. This should then archive that team, so that
|
||||
* it cannot be interacted with further, and all its information essentially becomes read-only.
|
||||
*/
|
||||
public class RemoveTeamListener extends StudentTeamListener {
|
||||
|
||||
/**
|
||||
* The Github manager for interacting with Github.
|
||||
*/
|
||||
private GithubManager manager;
|
||||
|
||||
/**
|
||||
* Constructs a table row listener.
|
||||
*
|
||||
* @param table The table on which to get selected rows.
|
||||
* @param manager The manager to interact
|
||||
*/
|
||||
public RemoveTeamListener(JTable table, GithubManager manager) {
|
||||
super(table);
|
||||
this.manager = manager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent actionEvent) {
|
||||
for (StudentTeam team : this.getSelectedStudentTeams()) {
|
||||
|
||||
if (team.isArchived()) {
|
||||
JOptionPane.showMessageDialog(
|
||||
this.getTable().getTopLevelAncestor(),
|
||||
"Team " + team.getNumber() + " is already archived.",
|
||||
"Already archived",
|
||||
JOptionPane.WARNING_MESSAGE
|
||||
);
|
||||
}
|
||||
|
||||
if (!team.isArchived()) {
|
||||
new Thread(() -> {
|
||||
DbHelper.executeTransactionContent(session -> {
|
||||
if (team.getRepositoryName() != null) {
|
||||
boolean success = this.manager.archiveRepository(team.getRepositoryName());
|
||||
if (!success) {
|
||||
JOptionPane.showMessageDialog(
|
||||
this.getTable().getTopLevelAncestor(),
|
||||
"Could not archive team " + team.getNumber() + "'s repository.",
|
||||
"Could not archive",
|
||||
JOptionPane.ERROR_MESSAGE
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
team.setArchived(true);
|
||||
session.update(team);
|
||||
});
|
||||
}).start();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
package nl.andrewlalis.ui.control.listeners.management_view.student_team_actions;
|
||||
|
||||
import nl.andrewlalis.model.StudentTeam;
|
||||
import nl.andrewlalis.ui.control.listeners.management_view.TableRowListener;
|
||||
import nl.andrewlalis.ui.view.table_models.StudentTeamTableModel;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* An abstract table row listener designed for use with only the student teams model.
|
||||
*/
|
||||
public abstract class StudentTeamListener extends TableRowListener {
|
||||
/**
|
||||
* Constructs a table row listener.
|
||||
*
|
||||
* @param table The table on which to get selected rows.
|
||||
*/
|
||||
public StudentTeamListener(JTable table) {
|
||||
super(table);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The student teams model that this listener applies to.
|
||||
*/
|
||||
public StudentTeamTableModel getModel() {
|
||||
return (StudentTeamTableModel) this.getTable().getModel();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return A list of teams which the user has selected.
|
||||
*/
|
||||
public List<StudentTeam> getSelectedStudentTeams() {
|
||||
StudentTeamTableModel model = (StudentTeamTableModel) this.getTable().getModel();
|
||||
List<StudentTeam> selectedTeams = new ArrayList<>();
|
||||
for (int row : this.getTable().getSelectedRows()) {
|
||||
selectedTeams.add(model.getStudentTeamAt(this.getTable().convertRowIndexToModel(row)));
|
||||
}
|
||||
return selectedTeams;
|
||||
}
|
||||
}
|
|
@ -3,9 +3,9 @@ package nl.andrewlalis.ui.view;
|
|||
import nl.andrewlalis.git_api.GithubManager;
|
||||
import nl.andrewlalis.model.database.DbHelper;
|
||||
import nl.andrewlalis.model.database.DbUtil;
|
||||
import nl.andrewlalis.ui.control.listeners.management_view.PopupSelector;
|
||||
import nl.andrewlalis.ui.control.listeners.management_view.student_actions.RemoveFromCourseListener;
|
||||
import nl.andrewlalis.ui.view.components.DetailPanel;
|
||||
import nl.andrewlalis.ui.view.components.tables.StudentTeamsTable;
|
||||
import nl.andrewlalis.ui.view.components.tables.StudentsTable;
|
||||
import nl.andrewlalis.ui.view.table_models.StudentTableModel;
|
||||
import nl.andrewlalis.ui.view.table_models.StudentTeamTableModel;
|
||||
|
||||
|
@ -63,6 +63,9 @@ public class ManagementView extends AbstractView {
|
|||
|
||||
this.detailPanel = new DetailPanel();
|
||||
|
||||
this.studentsModel = new StudentTableModel(DbHelper.getStudents());
|
||||
this.studentTeamModel = new StudentTeamTableModel();
|
||||
|
||||
contentPane.add(this.buildCommandPanel(), BorderLayout.WEST);
|
||||
contentPane.add(this.detailPanel, BorderLayout.EAST);
|
||||
contentPane.add(this.buildOverviewPanel(), BorderLayout.CENTER);
|
||||
|
@ -102,9 +105,15 @@ public class ManagementView extends AbstractView {
|
|||
// The real container for all the data views.
|
||||
JTabbedPane tabbedPane = new JTabbedPane();
|
||||
|
||||
tabbedPane.addTab("Students", this.buildStudentsTablePanel());
|
||||
tabbedPane.addTab("Student Teams", this.buildStudentTeamsTablePanel());
|
||||
tabbedPane.addTab("Teaching Assistants", this.buildTAsTablePanel());
|
||||
tabbedPane.addTab("Students", this.buildGenericTablePanel(
|
||||
new StudentsTable(this.studentsModel, this.studentTeamModel, this.detailPanel)
|
||||
));
|
||||
tabbedPane.addTab("Student Teams", this.buildGenericTablePanel(
|
||||
new StudentTeamsTable(this.studentTeamModel, this.detailPanel, this.getGithubManager())
|
||||
));
|
||||
tabbedPane.addTab("Teaching Assistants", this.buildGenericTablePanel(
|
||||
null
|
||||
));
|
||||
|
||||
overviewPanel.add(tabbedPane, BorderLayout.CENTER);
|
||||
|
||||
|
@ -121,6 +130,12 @@ public class ManagementView extends AbstractView {
|
|||
searchPanel.add(new JLabel("Search", SwingConstants.LEFT), BorderLayout.WEST);
|
||||
searchPanel.add(new JTextField(), BorderLayout.CENTER);
|
||||
|
||||
JButton refreshButton = new JButton("Refresh Tables");
|
||||
refreshButton.addActionListener(actionEvent -> {
|
||||
this.updateModels();
|
||||
});
|
||||
searchPanel.add(refreshButton, BorderLayout.EAST);
|
||||
|
||||
return searchPanel;
|
||||
}
|
||||
|
||||
|
@ -135,47 +150,6 @@ public class ManagementView extends AbstractView {
|
|||
return surroundingPanel;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return A JPanel to be put into a tab for display of a list of students.
|
||||
*/
|
||||
private JPanel buildStudentsTablePanel() {
|
||||
// Initialize the model, table, and a surrounding scroll pane.
|
||||
this.studentsModel = new StudentTableModel(DbHelper.getStudents());
|
||||
|
||||
JTable table = new JTable(this.studentsModel);
|
||||
table.setFillsViewportHeight(true);
|
||||
table.getSelectionModel().addListSelectionListener(listSelectionEvent -> {
|
||||
detailPanel.setDetailableEntity(studentsModel.getStudentAt(table.getSelectedRow()));
|
||||
});
|
||||
JPopupMenu menu = new JPopupMenu("Menu");
|
||||
JMenuItem removeItem = new JMenuItem("Remove from course");
|
||||
removeItem.addActionListener(new RemoveFromCourseListener(table));
|
||||
menu.add(removeItem);
|
||||
menu.addPopupMenuListener(new PopupSelector(table));
|
||||
table.setComponentPopupMenu(menu);
|
||||
|
||||
return this.buildGenericTablePanel(table);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return A JPanel to be put into a tab for display of a list of student teams.
|
||||
*/
|
||||
private JPanel buildStudentTeamsTablePanel() {
|
||||
this.studentTeamModel = new StudentTeamTableModel(DbHelper.getStudentTeams());
|
||||
|
||||
JTable table = new JTable(this.studentTeamModel);
|
||||
table.setFillsViewportHeight(true);
|
||||
table.getSelectionModel().addListSelectionListener(listSelectionEvent -> {
|
||||
detailPanel.setDetailableEntity(studentTeamModel.getStudentTeamAt(table.getSelectedRow()));
|
||||
});
|
||||
|
||||
return this.buildGenericTablePanel(table);
|
||||
}
|
||||
|
||||
private JPanel buildTAsTablePanel() {
|
||||
return new JPanel();
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates all models in the management view in accordance with the database.
|
||||
*/
|
||||
|
|
|
@ -4,6 +4,7 @@ import nl.andrewlalis.ui.view.table_models.DetailPairsModel;
|
|||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* The detail panel is meant for displaying the details of a specific entity. The actual content/details to display is
|
||||
|
@ -16,11 +17,6 @@ public class DetailPanel extends JPanel {
|
|||
*/
|
||||
private JTextField nameField;
|
||||
|
||||
/**
|
||||
* The description area shows the entity's description.
|
||||
*/
|
||||
private JTextArea descriptionTextArea;
|
||||
|
||||
/**
|
||||
* A model to represent the key-value pairs of this entity.
|
||||
*/
|
||||
|
@ -34,7 +30,6 @@ public class DetailPanel extends JPanel {
|
|||
this.setLayout(new BoxLayout(this, BoxLayout.PAGE_AXIS));
|
||||
this.add(new JLabel("Details", SwingConstants.CENTER));
|
||||
this.add(this.buildNamePanel());
|
||||
this.add(this.buildDescriptionPanel());
|
||||
this.add(this.buildPairsTablePanel());
|
||||
}
|
||||
|
||||
|
@ -43,9 +38,13 @@ public class DetailPanel extends JPanel {
|
|||
* @param entity The entity to get details from.
|
||||
*/
|
||||
public void setDetailableEntity(Detailable entity) {
|
||||
this.nameField.setText(entity.getDetailName());
|
||||
this.descriptionTextArea.setText(entity.getDetailDescription());
|
||||
this.detailPairsModel.setPairs(entity.getDetailPairs());
|
||||
if (entity == null) {
|
||||
this.nameField.setText(null);
|
||||
this.detailPairsModel.setPairs(new ArrayList<>());
|
||||
} else {
|
||||
this.nameField.setText(entity.getDetailName());
|
||||
this.detailPairsModel.setPairs(entity.getDetailPairs());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -62,20 +61,6 @@ public class DetailPanel extends JPanel {
|
|||
return namePanel;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return A JPanel containing the description text area.
|
||||
*/
|
||||
private JPanel buildDescriptionPanel() {
|
||||
this.descriptionTextArea = new JTextArea();
|
||||
this.descriptionTextArea.setEditable(false);
|
||||
|
||||
JPanel descriptionPanel = new JPanel(new BorderLayout());
|
||||
descriptionPanel.add(new JLabel("Description:", SwingConstants.CENTER), BorderLayout.NORTH);
|
||||
descriptionPanel.add(this.descriptionTextArea, BorderLayout.CENTER);
|
||||
|
||||
return descriptionPanel;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return A JPanel containing a table of properties.
|
||||
*/
|
||||
|
|
|
@ -14,11 +14,6 @@ public interface Detailable {
|
|||
*/
|
||||
String getDetailName();
|
||||
|
||||
/**
|
||||
* @return Some more information to display below the name for this object.
|
||||
*/
|
||||
String getDetailDescription();
|
||||
|
||||
/**
|
||||
* @return A String-to-String mapping for some key value pairs of properties to display.
|
||||
*/
|
||||
|
|
|
@ -0,0 +1,61 @@
|
|||
package nl.andrewlalis.ui.view.components.tables;
|
||||
|
||||
import nl.andrewlalis.ui.view.components.DetailPanel;
|
||||
import nl.andrewlalis.ui.view.table_models.AbstractEntityModel;
|
||||
|
||||
import javax.swing.*;
|
||||
|
||||
/**
|
||||
* An extension to the JTable component for displaying any arbitrary entity in the management view.
|
||||
*
|
||||
* Also sets some default settings for the table.
|
||||
*/
|
||||
public abstract class EntityTable extends JTable {
|
||||
|
||||
/**
|
||||
* The panel on which to display details of the selected entity.
|
||||
*/
|
||||
private DetailPanel detailPanel;
|
||||
|
||||
/**
|
||||
* Constructs a new table with the given model and a reference to a detail panel to show extra details about the entity.
|
||||
* @param model The table model to apply to this table.
|
||||
* @param detailPanel The panel on which to display details of the selected entity.
|
||||
*/
|
||||
protected EntityTable(AbstractEntityModel model, DetailPanel detailPanel) {
|
||||
super(model);
|
||||
this.detailPanel = detailPanel;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method should be called by child classes once all variables and additional requirements for the popup menu
|
||||
* are initialized. Also adds a list selection listener to display the selected entity by default.
|
||||
*/
|
||||
protected void setupTable() {
|
||||
AbstractEntityModel model = (AbstractEntityModel) this.getModel();
|
||||
|
||||
this.setFillsViewportHeight(true);
|
||||
this.getSelectionModel().addListSelectionListener(listSelectionEvent -> {
|
||||
int row = this.getSelectedRow();
|
||||
if (row >= 0 && row < model.getRowCount()) {
|
||||
this.detailPanel.setDetailableEntity(model.getEntityAt(this.convertRowIndexToModel(row)));
|
||||
}
|
||||
});
|
||||
|
||||
EntityTableCellRenderer renderer = new EntityTableCellRenderer(model);
|
||||
this.setDefaultRenderer(Integer.class, renderer);
|
||||
this.setDefaultRenderer(String.class, renderer);
|
||||
this.setDefaultRenderer(int.class, renderer);
|
||||
|
||||
this.setFont(this.getFont().deriveFont(14.0f));
|
||||
|
||||
this.setComponentPopupMenu(this.getPopupMenu());
|
||||
this.setAutoCreateRowSorter(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The popup menu for this table. This is defined by child classes.
|
||||
*/
|
||||
protected abstract JPopupMenu getPopupMenu();
|
||||
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
package nl.andrewlalis.ui.view.components.tables;
|
||||
|
||||
import nl.andrewlalis.model.database.BaseEntity;
|
||||
import nl.andrewlalis.ui.view.table_models.AbstractEntityModel;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.table.DefaultTableCellRenderer;
|
||||
import java.awt.*;
|
||||
|
||||
/**
|
||||
* This class extends the default table cell renderer to allow for some custom rendering of table cells for entity
|
||||
* tables.
|
||||
*/
|
||||
public class EntityTableCellRenderer extends DefaultTableCellRenderer {
|
||||
|
||||
/**
|
||||
* The model which is applied to the table that this cell renderer is applied to.
|
||||
*/
|
||||
private AbstractEntityModel model;
|
||||
|
||||
/**
|
||||
* Constructs a new cell renderer which is linked to the given model.
|
||||
* @param model A model of entities which is used for rendering cells.
|
||||
*/
|
||||
public EntityTableCellRenderer(AbstractEntityModel model) {
|
||||
this.model = model;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
|
||||
Component parentRenderer = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
|
||||
|
||||
if (value != null && !isSelected) {
|
||||
BaseEntity entity = this.model.getEntityAt(row);
|
||||
if (entity.isArchived()) {
|
||||
parentRenderer.setBackground(Color.LIGHT_GRAY);
|
||||
} else {
|
||||
parentRenderer.setBackground(Color.WHITE);
|
||||
}
|
||||
}
|
||||
|
||||
return parentRenderer;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,69 @@
|
|||
package nl.andrewlalis.ui.view.components.tables;
|
||||
|
||||
import nl.andrewlalis.git_api.GithubManager;
|
||||
import nl.andrewlalis.ui.control.listeners.management_view.student_team_actions.ArchiveRepositoryListener;
|
||||
import nl.andrewlalis.ui.control.listeners.management_view.student_team_actions.DeleteRepositoryListener;
|
||||
import nl.andrewlalis.ui.control.listeners.management_view.student_team_actions.GenerateRepositoryListener;
|
||||
import nl.andrewlalis.ui.control.listeners.management_view.student_team_actions.RemoveTeamListener;
|
||||
import nl.andrewlalis.ui.view.components.DetailPanel;
|
||||
import nl.andrewlalis.ui.view.components.tables.popup_menu.EntityMenuItem;
|
||||
import nl.andrewlalis.ui.view.components.tables.popup_menu.EntitySelectionType;
|
||||
import nl.andrewlalis.ui.view.table_models.AbstractEntityModel;
|
||||
|
||||
import javax.swing.*;
|
||||
|
||||
public class StudentTeamsTable extends EntityTable {
|
||||
|
||||
/**
|
||||
* A reference to the github manager, for cases where it's needed to interact with github repositories.
|
||||
*/
|
||||
private GithubManager manager;
|
||||
|
||||
/**
|
||||
* Constructs a new table with the given model and a reference to a detail panel to show extra details about the entity.
|
||||
*
|
||||
* @param model The table model to apply to this table.
|
||||
* @param detailPanel The panel on which to display details of the selected entity.
|
||||
* @param manager The GithubManager object used to interact with Github.
|
||||
*/
|
||||
public StudentTeamsTable(AbstractEntityModel model, DetailPanel detailPanel, GithubManager manager) {
|
||||
super(model, detailPanel);
|
||||
this.manager = manager;
|
||||
this.setupTable();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected JPopupMenu getPopupMenu() {
|
||||
|
||||
JPopupMenu menu = new JPopupMenu("menu");
|
||||
|
||||
// Item for generating the repository of a given team.
|
||||
JMenuItem generateRepositoryItem = new EntityMenuItem("Generate Repository", EntitySelectionType.SINGLE | EntitySelectionType.MULTIPLE);
|
||||
generateRepositoryItem.addActionListener(new GenerateRepositoryListener(this, this.manager));
|
||||
menu.add(generateRepositoryItem);
|
||||
|
||||
// Item for assigning a TA team to a student team.
|
||||
JMenuItem assignTATeamItem = new EntityMenuItem("Assign to TA Team", EntitySelectionType.SINGLE | EntitySelectionType.MULTIPLE);
|
||||
assignTATeamItem.addActionListener(actionEvent -> {
|
||||
System.out.println("Assign to TA team");
|
||||
});
|
||||
menu.add(assignTATeamItem);
|
||||
|
||||
// Item for archiving the repository of a student team.
|
||||
JMenuItem archiveRepositoryItem = new EntityMenuItem("Archive Repository", EntitySelectionType.SINGLE | EntitySelectionType.MULTIPLE);
|
||||
archiveRepositoryItem.addActionListener(new ArchiveRepositoryListener(this, this.manager));
|
||||
menu.add(archiveRepositoryItem);
|
||||
|
||||
// Item for deleting a repository. TODO: Remove this for production version! Only archiving is allowed.
|
||||
JMenuItem deleteRepositoryItem = new EntityMenuItem("Delete Repository", EntitySelectionType.SINGLE | EntitySelectionType.MULTIPLE);
|
||||
deleteRepositoryItem.addActionListener(new DeleteRepositoryListener(this, this.manager));
|
||||
menu.add(deleteRepositoryItem);
|
||||
|
||||
// Item for removing a team.
|
||||
JMenuItem removeTeamItem = new EntityMenuItem("Remove Team", EntitySelectionType.SINGLE | EntitySelectionType.MULTIPLE);
|
||||
removeTeamItem.addActionListener(new RemoveTeamListener(this, this.manager));
|
||||
menu.add(removeTeamItem);
|
||||
|
||||
return menu;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
package nl.andrewlalis.ui.view.components.tables;
|
||||
|
||||
import nl.andrewlalis.ui.control.listeners.management_view.EntityTablePopupMenuListener;
|
||||
import nl.andrewlalis.ui.control.listeners.management_view.student_actions.NewStudentListener;
|
||||
import nl.andrewlalis.ui.control.listeners.management_view.student_actions.RemoveFromCourseListener;
|
||||
import nl.andrewlalis.ui.control.listeners.management_view.student_actions.SetTeamListener;
|
||||
import nl.andrewlalis.ui.view.components.DetailPanel;
|
||||
import nl.andrewlalis.ui.view.components.tables.popup_menu.EntityMenuItem;
|
||||
import nl.andrewlalis.ui.view.components.tables.popup_menu.EntitySelectionType;
|
||||
import nl.andrewlalis.ui.view.table_models.StudentTableModel;
|
||||
import nl.andrewlalis.ui.view.table_models.StudentTeamTableModel;
|
||||
|
||||
import javax.swing.*;
|
||||
|
||||
/**
|
||||
* This table displays a list of students, and offers the user the ability to interact with entities in the list.
|
||||
*/
|
||||
public class StudentsTable extends EntityTable {
|
||||
|
||||
/**
|
||||
* A reference to the model for student teams.
|
||||
*/
|
||||
private StudentTeamTableModel studentTeamModel;
|
||||
|
||||
public StudentsTable(StudentTableModel studentTableModel, StudentTeamTableModel studentTeamModel, DetailPanel detailPanel) {
|
||||
super(studentTableModel, detailPanel);
|
||||
this.studentTeamModel = studentTeamModel;
|
||||
this.setupTable();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected JPopupMenu getPopupMenu() {
|
||||
// A context menu for the table.
|
||||
JPopupMenu menu = new JPopupMenu("Menu");
|
||||
|
||||
// Item for creating a new student (for example, if someone registered for the course late).
|
||||
JMenuItem newStudentItem = new EntityMenuItem("New Student", EntitySelectionType.SINGLE | EntitySelectionType.MULTIPLE);
|
||||
newStudentItem.addActionListener(new NewStudentListener(this));
|
||||
|
||||
// Item for setting a student's team.
|
||||
JMenuItem setTeamItem = new EntityMenuItem("Set team", EntitySelectionType.SINGLE);
|
||||
setTeamItem.addActionListener(new SetTeamListener(this, this.studentTeamModel));
|
||||
menu.add(setTeamItem);
|
||||
|
||||
// Item for removing a student from the course.
|
||||
JMenuItem removeItem = new EntityMenuItem("Remove from course", EntitySelectionType.SINGLE | EntitySelectionType.MULTIPLE);
|
||||
removeItem.addActionListener(new RemoveFromCourseListener(this, this.studentTeamModel));
|
||||
menu.add(removeItem);
|
||||
|
||||
menu.addPopupMenuListener(new EntityTablePopupMenuListener(this));
|
||||
|
||||
return menu;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
package nl.andrewlalis.ui.view.components.tables.popup_menu;
|
||||
|
||||
import javax.swing.*;
|
||||
|
||||
/**
|
||||
* An extension of the default menu item, with this type being able to enable/disable depending on the current selection
|
||||
* type of the table.
|
||||
*/
|
||||
public class EntityMenuItem extends JMenuItem {
|
||||
|
||||
/**
|
||||
* An integer representing the bitwise OR of possibly many selection types.
|
||||
*/
|
||||
private int enabledSelectionTypes;
|
||||
|
||||
public EntityMenuItem(String name, int enabledSelectionTypes) {
|
||||
super(name);
|
||||
this.enabledSelectionTypes = enabledSelectionTypes;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called just before the menu item is shown, thus allowing this menu item to enable or disable
|
||||
* itself depending on the selection type.
|
||||
* @param selectionType The type of selection that the user currently has on the table.
|
||||
*/
|
||||
public void willBecomeVisible(int selectionType) {
|
||||
this.setEnabled((selectionType & this.enabledSelectionTypes) > 0);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
package nl.andrewlalis.ui.view.components.tables.popup_menu;
|
||||
|
||||
/**
|
||||
* Describes different selection values. These are used when determining if a menu item should be shown or not.
|
||||
*/
|
||||
public class EntitySelectionType {
|
||||
|
||||
public static final int SINGLE = 1;
|
||||
public static final int MULTIPLE = 2;
|
||||
|
||||
}
|
|
@ -1,18 +1,85 @@
|
|||
package nl.andrewlalis.ui.view.dialogs;
|
||||
|
||||
import nl.andrewlalis.model.StudentTeam;
|
||||
import nl.andrewlalis.model.Team;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* With this dialog, a user can choose from a list of teams. This works for any type of team.
|
||||
* The user should have the option to create a new team.
|
||||
*/
|
||||
public class TeamChooserDialog extends JDialog {
|
||||
|
||||
public TeamChooserDialog(Window parent) {
|
||||
super(parent);
|
||||
/**
|
||||
* The combo box used in selecting a team. Will be populated by all team numbers.
|
||||
*/
|
||||
private JComboBox<Integer> teamChooserBox;
|
||||
|
||||
/**
|
||||
* The team which is selected.
|
||||
*/
|
||||
private Team selectedTeam;
|
||||
|
||||
/**
|
||||
* The model containing the list of teams.
|
||||
*/
|
||||
private List<StudentTeam> teams;
|
||||
|
||||
public TeamChooserDialog(Window parent, List<StudentTeam> teams) {
|
||||
super(parent, "Team Chooser", ModalityType.APPLICATION_MODAL);
|
||||
this.teams = teams;
|
||||
this.setContentPane(this.buildContentPane());
|
||||
this.pack();
|
||||
this.setLocationRelativeTo(null);
|
||||
this.setDefaultCloseOperation(DISPOSE_ON_CLOSE);
|
||||
this.setVisible(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The dialog's content pane, containing all user interface elements.
|
||||
*/
|
||||
private JPanel buildContentPane() {
|
||||
JPanel contentPane = new JPanel(new BorderLayout());
|
||||
|
||||
contentPane.add(new JLabel("Choose a team."), BorderLayout.NORTH);
|
||||
|
||||
// Main selection panel.
|
||||
// Create a list of numbers to represent the teams.
|
||||
Integer[] teamNumbers = new Integer[this.teams.size()];
|
||||
for (int row = 0; row < this.teams.size(); row++) {
|
||||
teamNumbers[row] = this.teams.get(row).getNumber();
|
||||
}
|
||||
|
||||
this.teamChooserBox = new JComboBox<>(teamNumbers);
|
||||
contentPane.add(this.teamChooserBox, BorderLayout.CENTER);
|
||||
|
||||
// Button panel for confirming or cancelling selection.
|
||||
JPanel confirmPanel = new JPanel();
|
||||
JButton doneButton = new JButton("Done");
|
||||
// Add a small action listener to set the selected team and dispose of the dialog.
|
||||
doneButton.addActionListener(actionEvent -> {
|
||||
selectedTeam = teams.get(teamChooserBox.getSelectedIndex());
|
||||
dispose();
|
||||
});
|
||||
confirmPanel.add(doneButton);
|
||||
|
||||
JButton cancelButton = new JButton("Cancel");
|
||||
// Add a small action listener to dispose of the dialog and return the null selected team.
|
||||
cancelButton.addActionListener(actionEvent -> dispose());
|
||||
confirmPanel.add(cancelButton);
|
||||
contentPane.add(confirmPanel, BorderLayout.SOUTH);
|
||||
|
||||
return contentPane;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The team which was selected by the user.
|
||||
*/
|
||||
public Team getSelectedTeam() {
|
||||
return null;
|
||||
return this.selectedTeam;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
package nl.andrewlalis.ui.view.table_models;
|
||||
|
||||
import nl.andrewlalis.model.database.BaseEntity;
|
||||
|
||||
import javax.swing.table.AbstractTableModel;
|
||||
|
||||
/**
|
||||
* A table model representing any possible entity that would be displayed in a table.
|
||||
*/
|
||||
public abstract class AbstractEntityModel extends AbstractTableModel {
|
||||
|
||||
/**
|
||||
* Gets the entity at a particular row in the model.
|
||||
* @param row The row at which to get the entity.
|
||||
* @return The entity in a certain row in the model.
|
||||
*/
|
||||
public abstract BaseEntity getEntityAt(int row);
|
||||
|
||||
}
|
|
@ -1,14 +1,14 @@
|
|||
package nl.andrewlalis.ui.view.table_models;
|
||||
|
||||
import nl.andrewlalis.model.Student;
|
||||
import nl.andrewlalis.model.database.BaseEntity;
|
||||
|
||||
import javax.swing.table.AbstractTableModel;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* This table model is used for the representation of a list of persons, with their basic information.
|
||||
*/
|
||||
public class StudentTableModel extends AbstractTableModel {
|
||||
public class StudentTableModel extends AbstractEntityModel {
|
||||
|
||||
/**
|
||||
* The list of data that is used in the table.
|
||||
|
@ -49,6 +49,11 @@ public class StudentTableModel extends AbstractTableModel {
|
|||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BaseEntity getEntityAt(int row) {
|
||||
return this.getStudentAt(row);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRowCount() {
|
||||
return studentsList.size();
|
||||
|
@ -64,6 +69,14 @@ public class StudentTableModel extends AbstractTableModel {
|
|||
return this.columns[i];
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> getColumnClass(int i) {
|
||||
if (this.studentsList.isEmpty()) {
|
||||
return Object.class;
|
||||
}
|
||||
return getValueAt(0, i).getClass();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getValueAt(int row, int col) {
|
||||
Student student = this.getStudentAt(row);
|
||||
|
|
|
@ -2,15 +2,15 @@ package nl.andrewlalis.ui.view.table_models;
|
|||
|
||||
import nl.andrewlalis.model.Student;
|
||||
import nl.andrewlalis.model.StudentTeam;
|
||||
import nl.andrewlalis.model.database.BaseEntity;
|
||||
|
||||
import javax.swing.table.AbstractTableModel;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* This table model represents the list of student teams.
|
||||
*/
|
||||
public class StudentTeamTableModel extends AbstractTableModel {
|
||||
public class StudentTeamTableModel extends AbstractEntityModel {
|
||||
|
||||
/**
|
||||
* The container for the data objects.
|
||||
|
@ -63,6 +63,18 @@ public class StudentTeamTableModel extends AbstractTableModel {
|
|||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BaseEntity getEntityAt(int row) {
|
||||
return this.getStudentTeamAt(row);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return A list of all teams in this model.
|
||||
*/
|
||||
public List<StudentTeam> getTeams() {
|
||||
return this.studentTeamsList;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRowCount() {
|
||||
return this.studentTeamsList.size();
|
||||
|
@ -82,6 +94,19 @@ public class StudentTeamTableModel extends AbstractTableModel {
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> getColumnClass(int i) {
|
||||
if (this.studentTeamsList.isEmpty()) {
|
||||
return Object.class;
|
||||
}
|
||||
Object value = this.getValueAt(0, i);
|
||||
if (value != null) {
|
||||
return value.getClass();
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getValueAt(int i, int i1) {
|
||||
StudentTeam team = this.getStudentTeamAt(i);
|
||||
|
|
|
@ -1,27 +1,28 @@
|
|||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<!DOCTYPE hibernate-configuration PUBLIC
|
||||
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
|
||||
"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
|
||||
"http://www.hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
|
||||
<hibernate-configuration>
|
||||
<session-factory>
|
||||
<!-- Database connection settings -->
|
||||
<property name="hibernate.connection.driver_class">org.h2.Driver</property>
|
||||
<property name="hibernate.connection.url">jdbc:h2:./initializer.h2</property>
|
||||
<property name="hibernate.dialect">org.hibernate.dialect.H2Dialect</property>
|
||||
<property name="hibernate.connection.username">root</property>
|
||||
<property name="hibernate.connection.password">root</property>
|
||||
<property name="hibernate.connection.driver_class">com.mysql.cj.jdbc.Driver</property>
|
||||
<property name="hibernate.connection.url">jdbc:mysql://localhost:3306/gitgroup?autoReconnect=true&useSSL=false</property>
|
||||
<property name="hibernate.dialect">org.hibernate.dialect.MySQL57Dialect</property>
|
||||
<property name="hibernate.connection.username">gitgroup_user</property>
|
||||
<property name="hibernate.connection.password">password</property>
|
||||
|
||||
<!-- JDBC connection pool -->
|
||||
<property name="connection.pool_size">1</property>
|
||||
<property name="connection.pool_size">100</property>
|
||||
|
||||
<!-- Debugging to show sql queries. -->
|
||||
<property name="show_sql">false</property>
|
||||
|
||||
<!-- Set Hibernate to create tables beforehand. -->
|
||||
<property name="hibernate.hbm2ddl.auto">create-drop</property>
|
||||
<property name="hibernate.hbm2ddl.auto">create</property>
|
||||
|
||||
<!-- Mapping -->
|
||||
<mapping class="nl.andrewlalis.model.Team"/>
|
||||
<mapping class="nl.andrewlalis.model.Person"/>
|
||||
<mapping class="nl.andrewlalis.model.Student"/>
|
||||
<mapping class="nl.andrewlalis.model.StudentTeam"/>
|
||||
<mapping class="nl.andrewlalis.model.TeachingAssistant"/>
|
||||
|
|
Loading…
Reference in New Issue