Added dialog to delegate a certain number of teams to each TA team.

This commit is contained in:
Andrew Lalis 2018-09-01 10:47:35 +02:00
parent a44814b1df
commit 7c6f2527c0
13 changed files with 306 additions and 37 deletions

View File

@ -43,6 +43,7 @@ public class Main {
executor.registerCommand("define_ta_teams", new DefineTaTeams(app));
executor.registerCommand("list_errors", new ListErrors());
executor.registerCommand("delete_repos", new DeleteRepos());
executor.registerCommand("delegate_student_teams", new DelegateStudentTeams(app));
logger.info("GithubManager for Github Repositories in Educational Organizations.\n" +
"© Andrew Lalis (2018), All rights reserved.\n" +

View File

@ -19,6 +19,13 @@ public class Organization extends Observable {
*/
private List<StudentTeam> studentTeams;
/**
* A list of all teaching assistant teams in this organization. These are generated from requests to the Github API
* and possibly supplementary information. Each teaching assistant team maintains a list of all student teams for
* which it is responsible.
*/
private List<TATeam> taTeams;
/**
* A queue of errors that accumulates as the program runs. These will be output to the user after execution of
* critical sections, so that inevitable errors due to input imperfections are not overlooked.
@ -30,6 +37,7 @@ public class Organization extends Observable {
*/
public Organization() {
this.studentTeams = new ArrayList<>();
this.taTeams = new ArrayList<>();
this.errors = new ArrayList<>();
}
@ -41,22 +49,6 @@ public class Organization extends Observable {
return this.studentTeams.isEmpty();
}
// GETTERS
public List<StudentTeam> getStudentTeams() {
return this.studentTeams;
}
public List<Error> getErrors() {
return this.errors;
}
// SETTERS
public void setStudentTeams(List<StudentTeam> teams) {
this.studentTeams = teams;
this.setChanged();
this.notifyObservers();
}
/**
* Adds an error to the list of accumulated errors.
* @param newError The newly generated error to add.
@ -67,4 +59,30 @@ public class Organization extends Observable {
this.notifyObservers();
}
// GETTERS
public List<StudentTeam> getStudentTeams() {
return this.studentTeams;
}
public List<Error> getErrors() {
return this.errors;
}
public List<TATeam> getTaTeams() {
return this.taTeams;
}
// SETTERS
public void setStudentTeams(List<StudentTeam> teams) {
this.studentTeams = teams;
this.setChanged();
this.notifyObservers();
}
public void setTaTeams(List<TATeam> teams) {
this.taTeams = teams;
this.setChanged();
this.notifyObservers();
}
}

View File

@ -22,6 +22,11 @@ public class TATeam extends Team {
*/
private GHTeam githubTeam;
/**
* A list of all student teams for which this TA team is responsible.
*/
private List<StudentTeam> studentTeams;
/**
* Constructs a team without any teaching assistant members.
* @param name The name of the team.
@ -40,6 +45,15 @@ public class TATeam extends Team {
return Arrays.copyOf(this.getMembers(), this.memberCount(), TeachingAssistant[].class);
}
/**
* Adds the given student team to the list of teams that this TA team is responsible for.
* @param team A student team.
*/
public void addStudentTeam(StudentTeam team) {
this.studentTeams.add(team);
}
// GETTERS
public String getName() {
return this.name;
}
@ -48,6 +62,11 @@ public class TATeam extends Team {
return this.githubTeam;
}
public List<StudentTeam> getStudentTeams() {
return this.studentTeams;
}
// SETTERS
public void setGithubTeam(GHTeam team) {
this.githubTeam = team;
}

View File

@ -1,7 +1,7 @@
package nl.andrewlalis.ui.control.command.executables;
import nl.andrewlalis.git_api.GithubManager;
import nl.andrewlalis.ui.view.DefineTaTeamsDialog;
import nl.andrewlalis.ui.view.dialogs.DefineTaTeamsDialog;
import nl.andrewlalis.ui.view.InitializerApp;
/**

View File

@ -0,0 +1,29 @@
package nl.andrewlalis.ui.control.command.executables;
import nl.andrewlalis.git_api.GithubManager;
import nl.andrewlalis.ui.view.InitializerApp;
import nl.andrewlalis.ui.view.dialogs.DelegateStudentTeamsDialog;
/**
* An executable which opens up a dialog to allow a user to delegate how many student teams each TATeam gets to manage,
* and actually generate the student repositories using the manager.
*/
public class DelegateStudentTeams extends GithubExecutable {
/**
* A reference to the main application frame, used as the parent for the dialog which is shown.
*/
private InitializerApp app;
public DelegateStudentTeams(InitializerApp app) {
this.app = app;
}
@Override
protected boolean executeWithManager(GithubManager manager, String[] args) {
DelegateStudentTeamsDialog dialog = new DelegateStudentTeamsDialog(this.app, manager);
dialog.begin();
return true;
}
}

View File

@ -1,16 +0,0 @@
package nl.andrewlalis.ui.control.command.executables;
import nl.andrewlalis.git_api.GithubManager;
/**
* An executable which opens up a dialog to allow a user to delegate how many student teams each TATeam gets to manage,
* and actually generate the student repositories using the manager.
*/
public class GenerateStudentRepos extends GithubExecutable {
@Override
protected boolean executeWithManager(GithubManager manager, String[] args) {
return false;
}
}

View File

@ -1,9 +1,6 @@
package nl.andrewlalis.ui.control.command.executables;
import nl.andrewlalis.model.Organization;
import nl.andrewlalis.model.StudentTeam;
import nl.andrewlalis.model.error.Error;
import nl.andrewlalis.model.error.Severity;
import nl.andrewlalis.ui.control.command.Executable;
import nl.andrewlalis.ui.view.InitializerApp;
import nl.andrewlalis.util.FileUtils;

View File

@ -0,0 +1,24 @@
package nl.andrewlalis.ui.control.listeners;
import nl.andrewlalis.ui.control.command.CommandExecutor;
import nl.andrewlalis.ui.view.InitializerApp;
import java.awt.event.ActionEvent;
/**
* Listens for when a user performs an action to open the dialog to delegate student teams amongst the TA teams.
*/
public class DelegateStudentTeamsListener extends ExecutableListener {
public DelegateStudentTeamsListener(CommandExecutor executor, InitializerApp app) {
super(executor, app);
}
@Override
public void actionPerformed(ActionEvent actionEvent) {
this.executor.executeCommand("delegate_student_teams", new String[]{
app.getOrganizationName(),
app.getAccessToken()
});
}
}

View File

@ -121,6 +121,7 @@ public class InitializerApp extends JFrame {
commonActionsPanel.add(this.generateButtonPanel("Archive All", new ArchiveAllListener(this.executor, this)));
commonActionsPanel.add(this.generateButtonPanel("Read Students File", new ReadStudentsFileListener(this.executor, this)));
commonActionsPanel.add(this.generateButtonPanel("Delegate Student Teams", new DelegateStudentTeamsListener(this.executor, this)));
commonActionsPanel.add(this.generateButtonPanel("Generate Assignments Repo", new GenerateAssignmentsRepoListener(this.executor, this)));
// TODO: Enable this once the define teams dialog is complete.

View File

@ -1,6 +1,7 @@
package nl.andrewlalis.ui.view;
package nl.andrewlalis.ui.view.dialogs;
import nl.andrewlalis.git_api.GithubManager;
import nl.andrewlalis.ui.view.InitializerApp;
import nl.andrewlalis.ui.view.list_models.TATeamListCellRenderer;
import nl.andrewlalis.ui.view.list_models.TATeamListModel;
import nl.andrewlalis.ui.view.list_models.TeachingAssistantListCellRenderer;

View File

@ -0,0 +1,130 @@
package nl.andrewlalis.ui.view.dialogs;
import nl.andrewlalis.git_api.GithubManager;
import nl.andrewlalis.model.StudentTeam;
import nl.andrewlalis.model.TATeam;
import nl.andrewlalis.ui.view.InitializerApp;
import javax.swing.*;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import java.awt.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* This dialog shows a simple list of all teaching assistant teams, with a JSpinner next to the name, so that a user can
* set how many student teams each teaching assistant team would like to manage.
*/
public class DelegateStudentTeamsDialog extends JDialog {
/**
* The manager used to generate repositories.
*/
private GithubManager manager;
/**
* Indicates how many student teams do not have an assigned team.
*/
private NumberIndicatorField unmatchedStudentsCounter;
/**
* A list of JSpinner objects linked to specific team members.
*/
private List<TaTeamSpinner> teamSpinners;
/**
* A number indicating the total number of student teams.
*/
private final int totalStudentTeamsCount;
public DelegateStudentTeamsDialog(InitializerApp parentApp, GithubManager manager) {
super(parentApp, "Delegate Student Teams", true);
this.manager = manager;
this.teamSpinners = new ArrayList<>();
this.totalStudentTeamsCount = InitializerApp.organization.getStudentTeams().size();
this.setDefaultCloseOperation(DISPOSE_ON_CLOSE);
this.initUI();
}
/**
* Begins showing the dialog.
*/
public void begin() {
this.setVisible(true);
}
/**
* Prepares the user interface components.
*/
private void initUI() {
JPanel mainPanel = new JPanel(new BorderLayout());
mainPanel.add(this.generateTopPanel(), BorderLayout.NORTH);
mainPanel.add(this.generateSpinnersPanel(), BorderLayout.CENTER);
this.setContentPane(mainPanel);
this.pack();
this.setLocationRelativeTo(null);
}
/**
* @return A JPanel containing the top-most counter of how many student teams are unassigned, and a small label.
*/
private JPanel generateTopPanel() {
JPanel panel = new JPanel(new BorderLayout());
panel.add(new JLabel("Unassigned teams: "), BorderLayout.CENTER);
this.unmatchedStudentsCounter = new NumberIndicatorField(this.totalStudentTeamsCount);
panel.add(this.unmatchedStudentsCounter, BorderLayout.EAST);
panel.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20));
return panel;
}
private JPanel generateSpinnersPanel() {
JPanel spinnersPanel = new JPanel();
spinnersPanel.setLayout(new BoxLayout(spinnersPanel, BoxLayout.PAGE_AXIS));
List<TATeam> taTeams = this.manager.getTeams();
for (TATeam team : taTeams) {
spinnersPanel.add(this.generateTeamSpinnerPanel(team));
}
return spinnersPanel;
}
private JPanel generateTeamSpinnerPanel(TATeam team) {
JPanel panel = new JPanel(new BorderLayout());
JLabel teamLabel = new JLabel(team.getName());
teamLabel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
panel.add(teamLabel, BorderLayout.WEST);
TaTeamSpinner spinner = new TaTeamSpinner(team, this.totalStudentTeamsCount);
spinner.addChangeListener(changeEvent -> {
JSpinner s = (JSpinner) changeEvent.getSource();
int studentTeamsMatched = 0;
for (TaTeamSpinner teamSpinner : this.teamSpinners) {
studentTeamsMatched += (int)teamSpinner.getValue();
}
if (this.totalStudentTeamsCount - studentTeamsMatched < 0) {
s.setValue((int)s.getValue() + 1); // TODO: FIX! Causes stack overflow.
} else {
this.unmatchedStudentsCounter.setValue(this.totalStudentTeamsCount - studentTeamsMatched);
}
});
this.teamSpinners.add(spinner);
panel.add(spinner, BorderLayout.EAST);
return panel;
}
/**
* Gets a map containing an integer value for every TA team, which represents the number of student teams they will
* be responsible for.
* @return A map of TATeams and integer values.
*/
public Map<TATeam, Integer> getResult() {
Map<TATeam, Integer> results = new HashMap<>();
for (TaTeamSpinner spinner : this.teamSpinners) {
results.put(spinner.getTeam(), (int) spinner.getValue());
}
return results;
}
}

View File

@ -0,0 +1,42 @@
package nl.andrewlalis.ui.view.dialogs;
import javax.swing.*;
/**
* A custom JTextField which displays an integer value, and can be set and read more efficiently than if one were to
* parse back and forth between strings and integers.
*/
public class NumberIndicatorField extends JTextField {
/**
* The value currently displayed in the text field.
*/
private int value;
public NumberIndicatorField(int initialValue) {
this.setEditable(false);
this.setValue(initialValue);
}
/**
* Sets the displayed value to a new integer value.
* @param newValue The new value to set.
*/
public void setValue(int newValue) {
this.value = newValue;
this.setText(String.valueOf(newValue));
}
public void decrement() {
this.setValue(this.value - 1);
}
public void increment() {
this.setValue(this.value + 1);
}
public int getValue() {
return this.value;
}
}

View File

@ -0,0 +1,23 @@
package nl.andrewlalis.ui.view.dialogs;
import nl.andrewlalis.model.TATeam;
import javax.swing.*;
/**
* A modified JSpinner which keeps track of a TATeam, and also has a default spinner number model in use.
*/
public class TaTeamSpinner extends JSpinner {
private TATeam team;
public TaTeamSpinner(TATeam team, int max) {
super(new SpinnerNumberModel(0, 0, max, 1));
((DefaultEditor) this.getEditor()).getTextField().setEditable(false);
this.team = team;
}
public TATeam getTeam() {
return this.team;
}
}