diff --git a/src/main/java/nl/andrewlalis/Main.java b/src/main/java/nl/andrewlalis/Main.java index cb1d021..a9674db 100644 --- a/src/main/java/nl/andrewlalis/Main.java +++ b/src/main/java/nl/andrewlalis/Main.java @@ -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" + diff --git a/src/main/java/nl/andrewlalis/model/Organization.java b/src/main/java/nl/andrewlalis/model/Organization.java index 8a5df40..7cb2268 100644 --- a/src/main/java/nl/andrewlalis/model/Organization.java +++ b/src/main/java/nl/andrewlalis/model/Organization.java @@ -19,6 +19,13 @@ public class Organization extends Observable { */ private List 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 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 getStudentTeams() { - return this.studentTeams; - } - - public List getErrors() { - return this.errors; - } - - // SETTERS - public void setStudentTeams(List 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 getStudentTeams() { + return this.studentTeams; + } + + public List getErrors() { + return this.errors; + } + + public List getTaTeams() { + return this.taTeams; + } + + // SETTERS + public void setStudentTeams(List teams) { + this.studentTeams = teams; + this.setChanged(); + this.notifyObservers(); + } + + public void setTaTeams(List teams) { + this.taTeams = teams; + this.setChanged(); + this.notifyObservers(); + } + } diff --git a/src/main/java/nl/andrewlalis/model/TATeam.java b/src/main/java/nl/andrewlalis/model/TATeam.java index 4296b7f..02ad327 100644 --- a/src/main/java/nl/andrewlalis/model/TATeam.java +++ b/src/main/java/nl/andrewlalis/model/TATeam.java @@ -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 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 getStudentTeams() { + return this.studentTeams; + } + + // SETTERS public void setGithubTeam(GHTeam team) { this.githubTeam = team; } diff --git a/src/main/java/nl/andrewlalis/ui/control/command/executables/DefineTaTeams.java b/src/main/java/nl/andrewlalis/ui/control/command/executables/DefineTaTeams.java index a39dd56..add587d 100644 --- a/src/main/java/nl/andrewlalis/ui/control/command/executables/DefineTaTeams.java +++ b/src/main/java/nl/andrewlalis/ui/control/command/executables/DefineTaTeams.java @@ -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; /** diff --git a/src/main/java/nl/andrewlalis/ui/control/command/executables/DelegateStudentTeams.java b/src/main/java/nl/andrewlalis/ui/control/command/executables/DelegateStudentTeams.java new file mode 100644 index 0000000..150fd84 --- /dev/null +++ b/src/main/java/nl/andrewlalis/ui/control/command/executables/DelegateStudentTeams.java @@ -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; + } + +} diff --git a/src/main/java/nl/andrewlalis/ui/control/command/executables/GenerateStudentRepos.java b/src/main/java/nl/andrewlalis/ui/control/command/executables/GenerateStudentRepos.java deleted file mode 100644 index cec7f12..0000000 --- a/src/main/java/nl/andrewlalis/ui/control/command/executables/GenerateStudentRepos.java +++ /dev/null @@ -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; - } - -} diff --git a/src/main/java/nl/andrewlalis/ui/control/command/executables/ReadStudentsFile.java b/src/main/java/nl/andrewlalis/ui/control/command/executables/ReadStudentsFile.java index 5ca9656..8d14826 100644 --- a/src/main/java/nl/andrewlalis/ui/control/command/executables/ReadStudentsFile.java +++ b/src/main/java/nl/andrewlalis/ui/control/command/executables/ReadStudentsFile.java @@ -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; diff --git a/src/main/java/nl/andrewlalis/ui/control/listeners/DelegateStudentTeamsListener.java b/src/main/java/nl/andrewlalis/ui/control/listeners/DelegateStudentTeamsListener.java new file mode 100644 index 0000000..b9969ea --- /dev/null +++ b/src/main/java/nl/andrewlalis/ui/control/listeners/DelegateStudentTeamsListener.java @@ -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() + }); + } +} diff --git a/src/main/java/nl/andrewlalis/ui/view/InitializerApp.java b/src/main/java/nl/andrewlalis/ui/view/InitializerApp.java index c279862..9aeebad 100644 --- a/src/main/java/nl/andrewlalis/ui/view/InitializerApp.java +++ b/src/main/java/nl/andrewlalis/ui/view/InitializerApp.java @@ -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. diff --git a/src/main/java/nl/andrewlalis/ui/view/DefineTaTeamsDialog.java b/src/main/java/nl/andrewlalis/ui/view/dialogs/DefineTaTeamsDialog.java similarity index 97% rename from src/main/java/nl/andrewlalis/ui/view/DefineTaTeamsDialog.java rename to src/main/java/nl/andrewlalis/ui/view/dialogs/DefineTaTeamsDialog.java index 65aa8ac..22d25b7 100644 --- a/src/main/java/nl/andrewlalis/ui/view/DefineTaTeamsDialog.java +++ b/src/main/java/nl/andrewlalis/ui/view/dialogs/DefineTaTeamsDialog.java @@ -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; diff --git a/src/main/java/nl/andrewlalis/ui/view/dialogs/DelegateStudentTeamsDialog.java b/src/main/java/nl/andrewlalis/ui/view/dialogs/DelegateStudentTeamsDialog.java new file mode 100644 index 0000000..9589926 --- /dev/null +++ b/src/main/java/nl/andrewlalis/ui/view/dialogs/DelegateStudentTeamsDialog.java @@ -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 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 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 getResult() { + Map results = new HashMap<>(); + for (TaTeamSpinner spinner : this.teamSpinners) { + results.put(spinner.getTeam(), (int) spinner.getValue()); + } + return results; + } +} diff --git a/src/main/java/nl/andrewlalis/ui/view/dialogs/NumberIndicatorField.java b/src/main/java/nl/andrewlalis/ui/view/dialogs/NumberIndicatorField.java new file mode 100644 index 0000000..5e7a5d8 --- /dev/null +++ b/src/main/java/nl/andrewlalis/ui/view/dialogs/NumberIndicatorField.java @@ -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; + } + +} diff --git a/src/main/java/nl/andrewlalis/ui/view/dialogs/TaTeamSpinner.java b/src/main/java/nl/andrewlalis/ui/view/dialogs/TaTeamSpinner.java new file mode 100644 index 0000000..23e5c6e --- /dev/null +++ b/src/main/java/nl/andrewlalis/ui/view/dialogs/TaTeamSpinner.java @@ -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; + } +}