From 667a11d69be4e0ed89e6c9f276e5c1ef890dcfec Mon Sep 17 00:00:00 2001 From: andrewlalis Date: Tue, 28 Aug 2018 21:28:49 +0200 Subject: [PATCH 01/14] Added icon image, and dialog class. --- src/main/java/nl/andrewlalis/Main.java | 30 ++---------------- .../ui/control/OutputTextHandler.java | 17 +++++++--- .../ui/control/command/CommandExecutor.java | 6 ++-- .../listeners/ReadStudentsFileListener.java | 17 ++++++++++ .../ui/view/DefineTaTeamsDialog.java | 13 ++++++++ .../andrewlalis/ui/view/InitializerApp.java | 13 +++++--- .../andrewlalis/ui/view/OutputTextPane.java | 16 +++++++--- .../java/nl/andrewlalis/util/Logging.java | 25 +++++++-------- src/main/resources/image/icon.png | Bin 0 -> 8714 bytes 9 files changed, 80 insertions(+), 57 deletions(-) create mode 100644 src/main/java/nl/andrewlalis/ui/view/DefineTaTeamsDialog.java create mode 100644 src/main/resources/image/icon.png diff --git a/src/main/java/nl/andrewlalis/Main.java b/src/main/java/nl/andrewlalis/Main.java index b6e7202..7241c30 100644 --- a/src/main/java/nl/andrewlalis/Main.java +++ b/src/main/java/nl/andrewlalis/Main.java @@ -34,12 +34,7 @@ public class Main { Map userOptions = CommandLine.parseArgs(args); // Initialize logger. - try { - Logging.setup(true); // TODO: Replace true with command line arg. - - } catch (IOException e) { - logger.severe("Unable to save log to file."); - } + Logging.setup(); // Command executor which will be used by all actions the user can do. CommandExecutor executor = new CommandExecutor(); @@ -47,6 +42,7 @@ public class Main { // Initialize User Interface. InitializerApp app = new InitializerApp(executor); app.begin(); + app.setAccessToken(userOptions.get("token")); Database db = new Database("database/initializer.sqlite"); db.initialize(); @@ -56,28 +52,6 @@ public class Main { executor.registerCommand("generateassignments", new GenerateAssignmentsRepo()); logger.info("GithubManager for Github Repositories in Educational Organizations. Program initialized."); - - - - // Get studentTeams from CSV file. -// List studentTeams = getStudentTeamsFromCSV(userOptions.get("input"), Integer.parseInt(userOptions.get("teamsize"))); -// -// GithubManager githubManager = new GithubManager( -// userOptions.get("organization"), -// userOptions.get("token"), -// "assignments_2018", -// "teaching-assistants", -// "advoop_2018" -// ); - - try { - //githubManager.initializeGithubRepos(studentTeams); - //githubManager.archiveAllRepositories("team"); - } catch (Exception e) { - e.printStackTrace(); - } } - - } diff --git a/src/main/java/nl/andrewlalis/ui/control/OutputTextHandler.java b/src/main/java/nl/andrewlalis/ui/control/OutputTextHandler.java index 875c148..b964e24 100644 --- a/src/main/java/nl/andrewlalis/ui/control/OutputTextHandler.java +++ b/src/main/java/nl/andrewlalis/ui/control/OutputTextHandler.java @@ -6,6 +6,7 @@ import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Date; import java.util.logging.Handler; +import java.util.logging.Level; import java.util.logging.LogRecord; /** @@ -26,11 +27,19 @@ public class OutputTextHandler extends Handler { public void publish(LogRecord logRecord) { DateFormat df = new SimpleDateFormat("dd/MM/yyyy HH:mm:ss"); String dateString = df.format(new Date(logRecord.getMillis())); - String sourceLocationString = logRecord.getSourceClassName() + "::" + logRecord.getSourceMethodName(); this.outputPane.printStyled(dateString + ' ', "gray_italics"); - this.outputPane.printStyled(logRecord.getLevel().getName() + ": ", "bold"); - this.outputPane.printStyled(sourceLocationString + "\n\t", "bold"); - this.outputPane.printStyled(logRecord.getMessage() + '\n', "smaller"); + String style = "default"; + Level level = logRecord.getLevel(); + if (level == Level.SEVERE) { + style = "error_red"; + } else if (level == Level.FINE + || level == Level.FINER + || level == Level.FINEST) { + style = "smaller"; + } else if (level == Level.WARNING) { + style = "warning_orange"; + } + this.outputPane.printStyled(logRecord.getMessage() + '\n', style); } @Override diff --git a/src/main/java/nl/andrewlalis/ui/control/command/CommandExecutor.java b/src/main/java/nl/andrewlalis/ui/control/command/CommandExecutor.java index dd5d84a..d66bb01 100644 --- a/src/main/java/nl/andrewlalis/ui/control/command/CommandExecutor.java +++ b/src/main/java/nl/andrewlalis/ui/control/command/CommandExecutor.java @@ -63,8 +63,10 @@ public class CommandExecutor { */ public void executeCommand(String commandName, String[] args) { if (this.commands.containsKey(commandName)) { - logger.info(commandName + ' ' + Arrays.toString(args)); - this.commands.get(commandName).execute(args); + logger.info("Command executed: " + commandName + ' ' + Arrays.toString(args)); + if (!this.commands.get(commandName).execute(args)) { + logger.warning("Command did not execute successfully."); + } } else { logger.warning(commandName + " is not a valid command."); } diff --git a/src/main/java/nl/andrewlalis/ui/control/listeners/ReadStudentsFileListener.java b/src/main/java/nl/andrewlalis/ui/control/listeners/ReadStudentsFileListener.java index 788c431..4a270c8 100644 --- a/src/main/java/nl/andrewlalis/ui/control/listeners/ReadStudentsFileListener.java +++ b/src/main/java/nl/andrewlalis/ui/control/listeners/ReadStudentsFileListener.java @@ -4,7 +4,9 @@ import nl.andrewlalis.ui.control.command.CommandExecutor; import nl.andrewlalis.ui.view.InitializerApp; import javax.swing.*; +import javax.swing.filechooser.FileFilter; import java.awt.event.ActionEvent; +import java.io.File; /** * Listens for when the user performs an action to read all students from a file, and output the contents to a database. @@ -18,6 +20,21 @@ public class ReadStudentsFileListener extends ExecutableListener { @Override public void actionPerformed(ActionEvent actionEvent) { JFileChooser chooser = new JFileChooser(); + chooser.setAcceptAllFileFilterUsed(false); + chooser.addChoosableFileFilter(new FileFilter() { + @Override + public boolean accept(File file) { + if (file.isDirectory()) { + return true; + } + return file.getName().toLowerCase().endsWith(".csv"); + } + + @Override + public String getDescription() { + return "CSV Files (*.csv)"; + } + }); int fileResponse = chooser.showOpenDialog(this.app); if (fileResponse == JFileChooser.APPROVE_OPTION) { diff --git a/src/main/java/nl/andrewlalis/ui/view/DefineTaTeamsDialog.java b/src/main/java/nl/andrewlalis/ui/view/DefineTaTeamsDialog.java new file mode 100644 index 0000000..3a3e641 --- /dev/null +++ b/src/main/java/nl/andrewlalis/ui/view/DefineTaTeamsDialog.java @@ -0,0 +1,13 @@ +package nl.andrewlalis.ui.view; + +import nl.andrewlalis.ui.control.command.executables.DefineTaTeams; + +import javax.swing.*; + +public class DefineTaTeamsDialog extends JDialog { + + public DefineTaTeamsDialog(InitializerApp parentApp) { + super(parentApp); + } + +} diff --git a/src/main/java/nl/andrewlalis/ui/view/InitializerApp.java b/src/main/java/nl/andrewlalis/ui/view/InitializerApp.java index 7db93eb..526840f 100644 --- a/src/main/java/nl/andrewlalis/ui/view/InitializerApp.java +++ b/src/main/java/nl/andrewlalis/ui/view/InitializerApp.java @@ -2,11 +2,7 @@ package nl.andrewlalis.ui.view; import nl.andrewlalis.ui.control.OutputTextHandler; import nl.andrewlalis.ui.control.command.CommandExecutor; -import nl.andrewlalis.ui.control.command.executables.ArchiveRepos; -import nl.andrewlalis.ui.control.listeners.ArchiveAllListener; -import nl.andrewlalis.ui.control.listeners.CommandFieldKeyListener; -import nl.andrewlalis.ui.control.listeners.GenerateAssignmentsRepoListener; -import nl.andrewlalis.ui.control.listeners.ReadStudentsFileListener; +import nl.andrewlalis.ui.control.listeners.*; import javax.swing.*; import java.awt.*; @@ -45,6 +41,8 @@ public class InitializerApp extends JFrame { this.executor = executor; // UI initialization. + ImageIcon icon = new ImageIcon(getClass().getResource("/image/icon.png")); + this.setIconImage(icon.getImage()); this.initFrame(); } @@ -120,6 +118,7 @@ public class InitializerApp extends JFrame { commonActionsPanel.add(generateAssignmentsRepoButton); JButton defineTaTeamsButton = new JButton("Define TA Teams"); + defineTaTeamsButton.addActionListener(new DefineTaTeamsListener(this.executor, this)); commonActionsPanel.add(defineTaTeamsButton); githubManagerPanel.add(commonActionsPanel, BorderLayout.CENTER); @@ -194,4 +193,8 @@ public class InitializerApp extends JFrame { return this.accessTokenField.getText().trim(); } + public void setAccessToken(String accessToken) { + this.accessTokenField.setText(accessToken); + } + } diff --git a/src/main/java/nl/andrewlalis/ui/view/OutputTextPane.java b/src/main/java/nl/andrewlalis/ui/view/OutputTextPane.java index 64569cd..62ab0c7 100644 --- a/src/main/java/nl/andrewlalis/ui/view/OutputTextPane.java +++ b/src/main/java/nl/andrewlalis/ui/view/OutputTextPane.java @@ -1,10 +1,7 @@ package nl.andrewlalis.ui.view; import javax.swing.*; -import javax.swing.text.BadLocationException; -import javax.swing.text.Style; -import javax.swing.text.StyleConstants; -import javax.swing.text.StyledDocument; +import javax.swing.text.*; import java.awt.*; import java.util.HashMap; import java.util.Map; @@ -26,6 +23,9 @@ public class OutputTextPane extends JTextPane { this.initStyles(); this.setEditable(false); this.setAutoscrolls(true); + + DefaultCaret caret = (DefaultCaret) this.getCaret(); + caret.setUpdatePolicy(DefaultCaret.ALWAYS_UPDATE); } private void initStyles() { @@ -47,6 +47,14 @@ public class OutputTextPane extends JTextPane { Style smaller = this.addStyle("smaller", defaultStyle); smaller.addAttribute(StyleConstants.FontSize, 11); this.styles.put("smaller", smaller); + + Style errorRed = this.addStyle("error_red", bold); + errorRed.addAttribute(StyleConstants.Foreground, new Color(255, 0, 0)); + this.styles.put("error_red", errorRed); + + Style warningOrange = this.addStyle("warning_orange", defaultStyle); + warningOrange.addAttribute(StyleConstants.Foreground, new Color(255, 127, 0)); + this.styles.put("warning_orange", warningOrange); } /** diff --git a/src/main/java/nl/andrewlalis/util/Logging.java b/src/main/java/nl/andrewlalis/util/Logging.java index 9161693..f0014ca 100644 --- a/src/main/java/nl/andrewlalis/util/Logging.java +++ b/src/main/java/nl/andrewlalis/util/Logging.java @@ -11,24 +11,21 @@ public class Logging { private static FileHandler outputFile; private static SimpleFormatter formatter; - public static void setup(boolean verbose) throws IOException { + public static void setup() { Logger logger = Logger.getGlobal(); - outputFile = new FileHandler("log/latest.log"); - formatter = new SimpleFormatter(); - - outputFile.setFormatter(formatter); - outputFile.setLevel(Level.FINEST); - - if (verbose) { - Handler systemOut = new ConsoleHandler(); - systemOut.setLevel(Level.ALL); - - logger.addHandler(systemOut); + try { + outputFile = new FileHandler("log/latest.log"); + formatter = new SimpleFormatter(); + outputFile.setFormatter(formatter); + outputFile.setLevel(Level.FINEST); + logger.addHandler(outputFile); + } catch (IOException e) { + logger.warning("Unable to save log to output file."); + e.printStackTrace(); } - logger.addHandler(outputFile); - logger.setLevel(Level.ALL); + logger.setLevel(Level.ALL); Logger.getLogger("").setLevel(Level.OFF); } diff --git a/src/main/resources/image/icon.png b/src/main/resources/image/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..b3ecc63b86432f49641767cc99dc944c91689beb GIT binary patch literal 8714 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 Date: Thu, 30 Aug 2018 15:38:22 +0200 Subject: [PATCH 02/14] Added basic dialog view of members and teams. --- src/main/java/nl/andrewlalis/Main.java | 20 ++--- .../nl/andrewlalis/git_api/GithubManager.java | 44 ++++++++- .../java/nl/andrewlalis/model/Person.java | 11 +++ .../java/nl/andrewlalis/model/TATeam.java | 31 +------ .../andrewlalis/model/TeachingAssistant.java | 11 +++ .../command/executables/DefineTaTeams.java | 21 ++++- .../command/executables/GithubExecutable.java | 3 + .../control/listeners/ArchiveAllListener.java | 2 +- .../listeners/DefineTaTeamsListener.java | 8 +- .../GenerateAssignmentsRepoListener.java | 2 +- .../listeners/ReadStudentsFileListener.java | 5 +- .../ui/view/DefineTaTeamsDialog.java | 89 ++++++++++++++++++- .../andrewlalis/ui/view/InitializerApp.java | 3 +- .../ui/view/list_models/TATeamListModel.java | 31 +++++++ .../TeachingAssistantListCellRenderer.java | 20 +++++ .../TeachingAssistantsListModel.java | 31 +++++++ src/main/resources/sql/table_init.sql | 36 +++++--- 17 files changed, 302 insertions(+), 66 deletions(-) create mode 100644 src/main/java/nl/andrewlalis/ui/view/list_models/TATeamListModel.java create mode 100644 src/main/java/nl/andrewlalis/ui/view/list_models/TeachingAssistantListCellRenderer.java create mode 100644 src/main/java/nl/andrewlalis/ui/view/list_models/TeachingAssistantsListModel.java diff --git a/src/main/java/nl/andrewlalis/Main.java b/src/main/java/nl/andrewlalis/Main.java index 7241c30..d036670 100644 --- a/src/main/java/nl/andrewlalis/Main.java +++ b/src/main/java/nl/andrewlalis/Main.java @@ -1,24 +1,16 @@ package nl.andrewlalis; import nl.andrewlalis.model.database.Database; -import nl.andrewlalis.git_api.GithubManager; -import nl.andrewlalis.model.Student; -import nl.andrewlalis.model.StudentTeam; import nl.andrewlalis.ui.control.command.CommandExecutor; -import nl.andrewlalis.ui.control.command.Executable; import nl.andrewlalis.ui.control.command.executables.ArchiveRepos; +import nl.andrewlalis.ui.control.command.executables.DefineTaTeams; import nl.andrewlalis.ui.control.command.executables.GenerateAssignmentsRepo; import nl.andrewlalis.ui.control.command.executables.ReadStudentsFileToDB; import nl.andrewlalis.ui.view.InitializerApp; import nl.andrewlalis.util.CommandLine; import nl.andrewlalis.util.Logging; -import nl.andrewlalis.util.TeamGenerator; -import javax.swing.*; -import java.io.IOException; -import java.util.List; import java.util.Map; -import java.util.logging.Level; import java.util.logging.Logger; /** @@ -45,11 +37,13 @@ public class Main { app.setAccessToken(userOptions.get("token")); Database db = new Database("database/initializer.sqlite"); - db.initialize(); - executor.registerCommand("readstudents", new ReadStudentsFileToDB(db)); - executor.registerCommand("archiveall", new ArchiveRepos()); - executor.registerCommand("generateassignments", new GenerateAssignmentsRepo()); + executor.registerCommand("read_students", new ReadStudentsFileToDB(db)); + executor.registerCommand("archive_all", new ArchiveRepos()); + executor.registerCommand("generate_assignments", new GenerateAssignmentsRepo()); + executor.registerCommand("define_ta_teams", new DefineTaTeams(app)); + + db.initialize(); logger.info("GithubManager for Github Repositories in Educational Organizations. Program initialized."); } diff --git a/src/main/java/nl/andrewlalis/git_api/GithubManager.java b/src/main/java/nl/andrewlalis/git_api/GithubManager.java index 5de6ec7..5829129 100644 --- a/src/main/java/nl/andrewlalis/git_api/GithubManager.java +++ b/src/main/java/nl/andrewlalis/git_api/GithubManager.java @@ -2,9 +2,7 @@ package nl.andrewlalis.git_api; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; -import nl.andrewlalis.model.Student; -import nl.andrewlalis.model.StudentTeam; -import nl.andrewlalis.model.TATeam; +import nl.andrewlalis.model.*; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpPatch; import org.apache.http.entity.StringEntity; @@ -15,6 +13,7 @@ import org.kohsuke.github.*; import java.io.IOException; import java.util.ArrayList; import java.util.List; +import java.util.Map; import java.util.logging.Logger; /** @@ -53,6 +52,45 @@ public class GithubManager { } } + /** + * Gets a list of teams in the organization. + * @return A List of all TA teams in the organization. + */ + public List getTeams() { + List teams = new ArrayList<>(); + try { + for (Map.Entry entry : this.organization.getTeams().entrySet()) { + TATeam team = new TATeam(entry.getKey(), -1); + team.setGithubTeam(entry.getValue()); + for (GHUser user : entry.getValue().getMembers()) { + team.addMember(new TeachingAssistant(-1, user.getName(), user.getEmail(), user.getLogin())); + } + teams.add(team); + } + } catch (IOException e) { + logger.severe("Could not get a list of teams in the organization.\n" + e.getMessage()); + e.printStackTrace(); + } + return teams; + } + + /** + * Gets a list of all teaching assistants, or members, in the organization. + * @return A List of teaching assistants, and empty if an error occurred. + */ + public List getMembers() { + List teachingAssistants = new ArrayList<>(); + try { + for (GHUser member : this.organization.getMembers()) { + teachingAssistants.add(new TeachingAssistant(-1, member.getName(), member.getEmail(), member.getLogin())); + } + } catch (IOException e) { + logger.severe("Could not get list of members in the organization.\n" + e.getMessage()); + e.printStackTrace(); + } + return teachingAssistants; + } + /** * Initializes the github repository for all studentTeams given. * diff --git a/src/main/java/nl/andrewlalis/model/Person.java b/src/main/java/nl/andrewlalis/model/Person.java index 66b0fb7..0c06a40 100644 --- a/src/main/java/nl/andrewlalis/model/Person.java +++ b/src/main/java/nl/andrewlalis/model/Person.java @@ -26,6 +26,17 @@ public abstract class Person { */ protected String githubUsername; + /** + * Constructs a Person from only a github username, which is, in some cases, enough to perform a lot of actions. + * @param githubUsername The person's github username. + */ + public Person(String githubUsername) { + this.number = -1; + this.name = null; + this.emailAddress = null; + this.githubUsername = githubUsername; + } + /** * Constructs a Person from all the basic information needed. * @param number Either an S- or P-Number without the letter prefix. diff --git a/src/main/java/nl/andrewlalis/model/TATeam.java b/src/main/java/nl/andrewlalis/model/TATeam.java index 2e252df..49a5bc9 100644 --- a/src/main/java/nl/andrewlalis/model/TATeam.java +++ b/src/main/java/nl/andrewlalis/model/TATeam.java @@ -1,6 +1,5 @@ package nl.andrewlalis.model; -import org.kohsuke.github.GHOrganization; import org.kohsuke.github.GHTeam; import java.util.ArrayList; @@ -10,20 +9,13 @@ import java.util.List; * Represents a teaching assistant team, which is itself a 'team' in the organization. This class is used for parsing * json from requests to github to get a list of all teams in the organization. */ -public class TATeam { - - private List teachingAssistants; +public class TATeam extends Team { /** * The team's display name. */ private String name; - /** - * The team's unique identifier. - */ - private int id; - /** * The Github team associated with this team. */ @@ -35,27 +27,8 @@ public class TATeam { * @param id The unique identifier for this team. */ public TATeam(String name, int id) { + super(id); this.name = name; - this.id = id; - this.teachingAssistants = new ArrayList(); - } - - /** - * Constructs a team with a list of teaching assistants that are part of it. - * @param teachingAssistants The list of teaching assistants that are part of the team. - */ - public TATeam(List teachingAssistants, String name, int id) { - this.teachingAssistants = teachingAssistants; - this.name = name; - this.id = id; - } - - /** - * Gets the unique identification for this TA team. - * @return An integer representing the id of this team. - */ - public int getId() { - return this.id; } public String getName() { diff --git a/src/main/java/nl/andrewlalis/model/TeachingAssistant.java b/src/main/java/nl/andrewlalis/model/TeachingAssistant.java index 918bb5c..7362c48 100644 --- a/src/main/java/nl/andrewlalis/model/TeachingAssistant.java +++ b/src/main/java/nl/andrewlalis/model/TeachingAssistant.java @@ -1,7 +1,18 @@ package nl.andrewlalis.model; +/** + * Represents an administrator in the organization, who manages student teams. + */ public class TeachingAssistant extends Person { + /** + * Constructs a Teaching Assistant from only a github username. + * @param githubUsername The person's github username. + */ + public TeachingAssistant(String githubUsername) { + super(githubUsername); + } + /** * Constructs a Teaching Assistant from all the basic information needed, much like its parent, Person. * 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 cccd3e8..a39dd56 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,13 +1,30 @@ package nl.andrewlalis.ui.control.command.executables; import nl.andrewlalis.git_api.GithubManager; +import nl.andrewlalis.ui.view.DefineTaTeamsDialog; +import nl.andrewlalis.ui.view.InitializerApp; - +/** + * This executable is slightly different from the others, in that it opens up a user interface to make editing TA teams + * possible. Therefore, executing this command opens the 'DefineTaTeams' dialog, within which a user can make changes + * to the TA teams in the organization. + */ public class DefineTaTeams extends GithubExecutable { + /** + * An instance of the main application frame; used when constructing the dialog. + */ + private InitializerApp app; + + public DefineTaTeams(InitializerApp app) { + this.app = app; + } + @Override protected boolean executeWithManager(GithubManager manager, String[] args) { - return false; + DefineTaTeamsDialog dialog = new DefineTaTeamsDialog(this.app, manager); + dialog.begin(); + return true; } } diff --git a/src/main/java/nl/andrewlalis/ui/control/command/executables/GithubExecutable.java b/src/main/java/nl/andrewlalis/ui/control/command/executables/GithubExecutable.java index e7362bd..1cde937 100644 --- a/src/main/java/nl/andrewlalis/ui/control/command/executables/GithubExecutable.java +++ b/src/main/java/nl/andrewlalis/ui/control/command/executables/GithubExecutable.java @@ -10,6 +10,9 @@ import nl.andrewlalis.ui.control.command.Executable; * Requires two arguments: * 1. The organization name. * 2. The organization's access token. + * + * Any additional arguments are added to a new String[] array which is passed along to child classes, so that they do + * not have to filter out the mandatory first two arguments. */ public abstract class GithubExecutable implements Executable { diff --git a/src/main/java/nl/andrewlalis/ui/control/listeners/ArchiveAllListener.java b/src/main/java/nl/andrewlalis/ui/control/listeners/ArchiveAllListener.java index 6d3cbf2..98e4b86 100644 --- a/src/main/java/nl/andrewlalis/ui/control/listeners/ArchiveAllListener.java +++ b/src/main/java/nl/andrewlalis/ui/control/listeners/ArchiveAllListener.java @@ -19,7 +19,7 @@ public class ArchiveAllListener extends ExecutableListener { public void actionPerformed(ActionEvent actionEvent) { String response = JOptionPane.showInputDialog(this.app, "Enter a substring to archive repositories by.", "Enter a substring", JOptionPane.QUESTION_MESSAGE); if (response != null) { - this.executor.executeCommand("archiveall", new String[]{ + this.executor.executeCommand("archive_all", new String[]{ this.app.getOrganizationName(), this.app.getAccessToken(), response diff --git a/src/main/java/nl/andrewlalis/ui/control/listeners/DefineTaTeamsListener.java b/src/main/java/nl/andrewlalis/ui/control/listeners/DefineTaTeamsListener.java index 0e85be3..fec0429 100644 --- a/src/main/java/nl/andrewlalis/ui/control/listeners/DefineTaTeamsListener.java +++ b/src/main/java/nl/andrewlalis/ui/control/listeners/DefineTaTeamsListener.java @@ -5,6 +5,9 @@ import nl.andrewlalis.ui.view.InitializerApp; import java.awt.event.ActionEvent; +/** + * Listens for when the user wants to open the 'DefineTaTeams' dialog. + */ public class DefineTaTeamsListener extends ExecutableListener { public DefineTaTeamsListener(CommandExecutor executor, InitializerApp app) { @@ -13,6 +16,9 @@ public class DefineTaTeamsListener extends ExecutableListener { @Override public void actionPerformed(ActionEvent actionEvent) { - + this.executor.executeCommand("define_ta_teams", new String[]{ + this.app.getOrganizationName(), + this.app.getAccessToken() + }); } } diff --git a/src/main/java/nl/andrewlalis/ui/control/listeners/GenerateAssignmentsRepoListener.java b/src/main/java/nl/andrewlalis/ui/control/listeners/GenerateAssignmentsRepoListener.java index 6143821..ba74466 100644 --- a/src/main/java/nl/andrewlalis/ui/control/listeners/GenerateAssignmentsRepoListener.java +++ b/src/main/java/nl/andrewlalis/ui/control/listeners/GenerateAssignmentsRepoListener.java @@ -22,7 +22,7 @@ public class GenerateAssignmentsRepoListener extends ExecutableListener { String description = JOptionPane.showInputDialog(this.app, "Enter a description for the repository.", "Repository Description", JOptionPane.QUESTION_MESSAGE); String teamName = JOptionPane.showInputDialog(this.app, "Enter the name of the TA team containing all teaching assistants.", "TA Team Name", JOptionPane.QUESTION_MESSAGE); if (teamName != null) { - this.executor.executeCommand("generateassignments", new String[]{ + this.executor.executeCommand("generate_assignments", new String[]{ this.app.getOrganizationName(), this.app.getAccessToken(), repoName, diff --git a/src/main/java/nl/andrewlalis/ui/control/listeners/ReadStudentsFileListener.java b/src/main/java/nl/andrewlalis/ui/control/listeners/ReadStudentsFileListener.java index 4a270c8..046e78d 100644 --- a/src/main/java/nl/andrewlalis/ui/control/listeners/ReadStudentsFileListener.java +++ b/src/main/java/nl/andrewlalis/ui/control/listeners/ReadStudentsFileListener.java @@ -10,6 +10,9 @@ import java.io.File; /** * Listens for when the user performs an action to read all students from a file, and output the contents to a database. + * + * Because filename and team size are not provided via arguments when a user clicks on a button, these are obtained via + * a JFileChooser and JOptionPane input dialog. If all inputs are valid, then the command is executed. */ public class ReadStudentsFileListener extends ExecutableListener { @@ -40,7 +43,7 @@ public class ReadStudentsFileListener extends ExecutableListener { if (fileResponse == JFileChooser.APPROVE_OPTION) { String teamSizeString = JOptionPane.showInputDialog(this.app, "Enter the student team size.", "Team Size", JOptionPane.QUESTION_MESSAGE); if (teamSizeString != null) { - this.executor.executeCommand("readstudents", new String[]{ + this.executor.executeCommand("read_students", new String[]{ chooser.getSelectedFile().getName(), teamSizeString }); diff --git a/src/main/java/nl/andrewlalis/ui/view/DefineTaTeamsDialog.java b/src/main/java/nl/andrewlalis/ui/view/DefineTaTeamsDialog.java index 3a3e641..6e5faef 100644 --- a/src/main/java/nl/andrewlalis/ui/view/DefineTaTeamsDialog.java +++ b/src/main/java/nl/andrewlalis/ui/view/DefineTaTeamsDialog.java @@ -1,13 +1,96 @@ package nl.andrewlalis.ui.view; -import nl.andrewlalis.ui.control.command.executables.DefineTaTeams; +import nl.andrewlalis.git_api.GithubManager; +import nl.andrewlalis.ui.view.list_models.TATeamListModel; +import nl.andrewlalis.ui.view.list_models.TeachingAssistantListCellRenderer; +import nl.andrewlalis.ui.view.list_models.TeachingAssistantsListModel; import javax.swing.*; +import java.awt.*; +/** + * This class represents a pop-up dialog that appears when the user wants to manipulate teams in the organization. With + * this dialog, it will be possible to do the following: + * - View all members of the organization. + * - View all teams in the organization. + * - Create new teams from a selection of members. + * - Invite new members to the organization. + */ public class DefineTaTeamsDialog extends JDialog { - public DefineTaTeamsDialog(InitializerApp parentApp) { - super(parentApp); + /** + * The manager used to manipulate the organization. + */ + private GithubManager manager; + + public DefineTaTeamsDialog(InitializerApp parentApp, GithubManager manager) { + super(parentApp, "Define TA Teams", true); + this.setDefaultCloseOperation(DISPOSE_ON_CLOSE); + this.manager = manager; + + this.initUI(); } + /** + * Sets the dialog as visible. + */ + public void begin() { + this.setVisible(true); + } + + /** + * Constructs all UI components. + */ + private void initUI() { + JPanel mainPanel = new JPanel(new BorderLayout()); + mainPanel.add(new JLabel("Hello world", SwingConstants.CENTER), BorderLayout.NORTH); + + mainPanel.add(this.initMembersPanel(), BorderLayout.WEST); + mainPanel.add(this.initTeamsPanel(), BorderLayout.EAST); + + this.setContentPane(mainPanel); + this.setPreferredSize(new Dimension(600, 800)); + this.pack(); + this.setLocationRelativeTo(null); + } + + /** + * @return A JPanel containing the list of teams in the organization. + */ + private JPanel initTeamsPanel() { + JPanel teamsPanel = new JPanel(new BorderLayout()); + teamsPanel.add(new JLabel("Teams", SwingConstants.CENTER), BorderLayout.NORTH); + + ListModel model = new TATeamListModel(this.manager.getTeams()); + JList teamsList = new JList(model); + teamsList.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); + + JScrollPane teamsListScrollPane = new JScrollPane(teamsList); + teamsListScrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS); + teamsListScrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED); + teamsPanel.add(teamsListScrollPane, BorderLayout.CENTER); + + return teamsPanel; + } + + /** + * @return A JPanel containing the list of members of the organization. + */ + private JPanel initMembersPanel() { + JPanel membersPanel = new JPanel(new BorderLayout()); + membersPanel.add(new JLabel("Members", SwingConstants.CENTER), BorderLayout.NORTH); + + ListModel model = new TeachingAssistantsListModel(this.manager.getMembers()); + JList membersList = new JList(); + membersList.setModel(model); + membersList.setCellRenderer(new TeachingAssistantListCellRenderer()); + membersList.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); + + JScrollPane membersListScrollPane = new JScrollPane(membersList); + membersListScrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS); + membersListScrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED); + membersPanel.add(membersListScrollPane, BorderLayout.CENTER); + + return membersPanel; + } } diff --git a/src/main/java/nl/andrewlalis/ui/view/InitializerApp.java b/src/main/java/nl/andrewlalis/ui/view/InitializerApp.java index 526840f..c92e0f5 100644 --- a/src/main/java/nl/andrewlalis/ui/view/InitializerApp.java +++ b/src/main/java/nl/andrewlalis/ui/view/InitializerApp.java @@ -50,7 +50,6 @@ public class InitializerApp extends JFrame { * Begins showing the application */ public void begin() { - this.pack(); this.setVisible(true); } @@ -79,6 +78,8 @@ public class InitializerApp extends JFrame { mainPanel.add(this.initGithubManagerPanel(), BorderLayout.EAST); this.setContentPane(mainPanel); + this.pack(); + this.setLocationRelativeTo(null); this.initLoggingHandler(); } diff --git a/src/main/java/nl/andrewlalis/ui/view/list_models/TATeamListModel.java b/src/main/java/nl/andrewlalis/ui/view/list_models/TATeamListModel.java new file mode 100644 index 0000000..1565f58 --- /dev/null +++ b/src/main/java/nl/andrewlalis/ui/view/list_models/TATeamListModel.java @@ -0,0 +1,31 @@ +package nl.andrewlalis.ui.view.list_models; + +import nl.andrewlalis.model.TATeam; + +import javax.swing.*; +import java.util.List; + +/** + * A list model for displaying TATeams. + */ +public class TATeamListModel extends AbstractListModel { + + /** + * A list of teams. + */ + private List teams; + + public TATeamListModel(List taTeams) { + this.teams = taTeams; + } + + @Override + public int getSize() { + return this.teams.size(); + } + + @Override + public Object getElementAt(int i) { + return this.teams.get(i); + } +} diff --git a/src/main/java/nl/andrewlalis/ui/view/list_models/TeachingAssistantListCellRenderer.java b/src/main/java/nl/andrewlalis/ui/view/list_models/TeachingAssistantListCellRenderer.java new file mode 100644 index 0000000..0c8b91f --- /dev/null +++ b/src/main/java/nl/andrewlalis/ui/view/list_models/TeachingAssistantListCellRenderer.java @@ -0,0 +1,20 @@ +package nl.andrewlalis.ui.view.list_models; + +import nl.andrewlalis.model.TeachingAssistant; + +import javax.swing.*; +import java.awt.*; + +public class TeachingAssistantListCellRenderer extends DefaultListCellRenderer { + + @Override + public Component getListCellRendererComponent(JList jList, Object o, int i, boolean b, boolean b1) { + super.getListCellRendererComponent(jList, o, i, b, b1); + if (o instanceof TeachingAssistant) { + TeachingAssistant ta = (TeachingAssistant) o; + this.setText(ta.getGithubUsername()); + this.setToolTipText(ta.getName()); + } + return this; + } +} diff --git a/src/main/java/nl/andrewlalis/ui/view/list_models/TeachingAssistantsListModel.java b/src/main/java/nl/andrewlalis/ui/view/list_models/TeachingAssistantsListModel.java new file mode 100644 index 0000000..2cff0c2 --- /dev/null +++ b/src/main/java/nl/andrewlalis/ui/view/list_models/TeachingAssistantsListModel.java @@ -0,0 +1,31 @@ +package nl.andrewlalis.ui.view.list_models; + +import nl.andrewlalis.model.TeachingAssistant; + +import javax.swing.*; +import java.util.List; + +/** + * A list model for displaying a list of teaching assistants as members of an organization. + */ +public class TeachingAssistantsListModel extends AbstractListModel { + + /** + * The list of teaching assistants which this model uses. + */ + private List teachingAssistants; + + public TeachingAssistantsListModel(List teachingAssistants) { + this.teachingAssistants = teachingAssistants; + } + + @Override + public int getSize() { + return this.teachingAssistants.size(); + } + + @Override + public Object getElementAt(int i) { + return this.teachingAssistants.get(i); + } +} diff --git a/src/main/resources/sql/table_init.sql b/src/main/resources/sql/table_init.sql index e5ffa1d..9f0d8f2 100644 --- a/src/main/resources/sql/table_init.sql +++ b/src/main/resources/sql/table_init.sql @@ -16,12 +16,18 @@ CREATE TABLE IF NOT EXISTS persons ( email_address TEXT NOT NULL, github_username TEXT NOT NULL UNIQUE, person_type_id INTEGER NOT NULL, + team_id INTEGER NULL, FOREIGN KEY (person_type_id) REFERENCES person_types(id) ON DELETE CASCADE - ON UPDATE CASCADE + ON UPDATE CASCADE, + FOREIGN KEY (team_id) + REFERENCES teams(id) + ON DELETE CASCADE + ON UPDATE CASCADE ); +-- Team tables for all types of teams. CREATE TABLE IF NOT EXISTS team_types ( id INTEGER PRIMARY KEY, name TEXT NOT NULL UNIQUE @@ -33,6 +39,22 @@ CREATE TABLE IF NOT EXISTS teams ( FOREIGN KEY (team_type_id) REFERENCES team_types(id) ON DELETE CASCADE + ON UPDATE CASCADE +); + +CREATE TABLE IF NOT EXISTS team_members ( + id INTEGER PRIMARY KEY, + team_id INTEGER NOT NULL, + person_id INTEGER NOT NULL, + FOREIGN KEY (team_id) + REFERENCES teams(id) + ON DELETE CASCADE + ON UPDATE CASCADE, + FOREIGN KEY (person_id) + REFERENCES persons(id) + ON DELETE CASCADE + ON UPDATE CASCADE, + UNIQUE (team_id, person_id) ); CREATE TABLE IF NOT EXISTS teaching_assistant_teams ( @@ -59,17 +81,13 @@ CREATE TABLE IF NOT EXISTS student_teams ( ON UPDATE CASCADE ); +-- Student-specific tables. CREATE TABLE IF NOT EXISTS students ( person_id INTEGER PRIMARY KEY, - team_id INTEGER NOT NULL, chose_partner INTEGER NOT NULL, FOREIGN KEY (person_id) REFERENCES persons(id) ON DELETE CASCADE - ON UPDATE CASCADE, - FOREIGN KEY (team_id) - REFERENCES teams(id) - ON DELETE CASCADE ON UPDATE CASCADE ); @@ -83,16 +101,12 @@ CREATE TABLE IF NOT EXISTS student_preferred_partners ( UNIQUE (student_id, partner_id) ); +-- TeachingAssistant-specific tables. CREATE TABLE IF NOT EXISTS teaching_assistants ( person_id INTEGER PRIMARY KEY, - team_id INTEGER NOT NULL, FOREIGN KEY (person_id) REFERENCES persons(id) ON DELETE CASCADE - ON UPDATE CASCADE, - FOREIGN KEY (team_id) - REFERENCES teams(id) - ON DELETE CASCADE ON UPDATE CASCADE ); From ed2a337bdc072a8396c0727fb6fc1aa715e555cc Mon Sep 17 00:00:00 2001 From: andrewlalis Date: Thu, 30 Aug 2018 15:54:13 +0200 Subject: [PATCH 03/14] standard size for lists. --- .../java/nl/andrewlalis/model/TATeam.java | 9 +++++ .../ui/view/DefineTaTeamsDialog.java | 6 ++++ .../list_models/TATeamListCellRenderer.java | 33 +++++++++++++++++++ .../TeachingAssistantListCellRenderer.java | 3 ++ 4 files changed, 51 insertions(+) create mode 100644 src/main/java/nl/andrewlalis/ui/view/list_models/TATeamListCellRenderer.java diff --git a/src/main/java/nl/andrewlalis/model/TATeam.java b/src/main/java/nl/andrewlalis/model/TATeam.java index 49a5bc9..4296b7f 100644 --- a/src/main/java/nl/andrewlalis/model/TATeam.java +++ b/src/main/java/nl/andrewlalis/model/TATeam.java @@ -3,6 +3,7 @@ package nl.andrewlalis.model; import org.kohsuke.github.GHTeam; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; /** @@ -31,6 +32,14 @@ public class TATeam extends Team { this.name = name; } + /** + * Gets a list of teaching assistants, as a convenience method to avoid having to do an array cast. + * @return An array of all teaching assistant members of this team. + */ + public TeachingAssistant[] getTeachingAssistants() { + return Arrays.copyOf(this.getMembers(), this.memberCount(), TeachingAssistant[].class); + } + public String getName() { return this.name; } diff --git a/src/main/java/nl/andrewlalis/ui/view/DefineTaTeamsDialog.java b/src/main/java/nl/andrewlalis/ui/view/DefineTaTeamsDialog.java index 6e5faef..65aa8ac 100644 --- a/src/main/java/nl/andrewlalis/ui/view/DefineTaTeamsDialog.java +++ b/src/main/java/nl/andrewlalis/ui/view/DefineTaTeamsDialog.java @@ -1,6 +1,7 @@ package nl.andrewlalis.ui.view; import nl.andrewlalis.git_api.GithubManager; +import nl.andrewlalis.ui.view.list_models.TATeamListCellRenderer; import nl.andrewlalis.ui.view.list_models.TATeamListModel; import nl.andrewlalis.ui.view.list_models.TeachingAssistantListCellRenderer; import nl.andrewlalis.ui.view.list_models.TeachingAssistantsListModel; @@ -18,6 +19,8 @@ import java.awt.*; */ public class DefineTaTeamsDialog extends JDialog { + private static final Dimension LIST_SIZE = new Dimension(200, -1); + /** * The manager used to manipulate the organization. */ @@ -63,6 +66,8 @@ public class DefineTaTeamsDialog extends JDialog { ListModel model = new TATeamListModel(this.manager.getTeams()); JList teamsList = new JList(model); + teamsList.setCellRenderer(new TATeamListCellRenderer()); + teamsList.setPreferredSize(LIST_SIZE); teamsList.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); JScrollPane teamsListScrollPane = new JScrollPane(teamsList); @@ -84,6 +89,7 @@ public class DefineTaTeamsDialog extends JDialog { JList membersList = new JList(); membersList.setModel(model); membersList.setCellRenderer(new TeachingAssistantListCellRenderer()); + membersList.setPreferredSize(LIST_SIZE); membersList.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); JScrollPane membersListScrollPane = new JScrollPane(membersList); diff --git a/src/main/java/nl/andrewlalis/ui/view/list_models/TATeamListCellRenderer.java b/src/main/java/nl/andrewlalis/ui/view/list_models/TATeamListCellRenderer.java new file mode 100644 index 0000000..5c78dd9 --- /dev/null +++ b/src/main/java/nl/andrewlalis/ui/view/list_models/TATeamListCellRenderer.java @@ -0,0 +1,33 @@ +package nl.andrewlalis.ui.view.list_models; + +import nl.andrewlalis.model.TATeam; +import nl.andrewlalis.model.TeachingAssistant; + +import javax.swing.*; +import java.awt.*; + +/** + * Determines how list cells are rendered for a list of TATeams. In this case, it shows the team name, and the tooltip + * text provides a list of members. + */ +public class TATeamListCellRenderer extends DefaultListCellRenderer { + + @Override + public Component getListCellRendererComponent(JList jList, Object o, int i, boolean b, boolean b1) { + super.getListCellRendererComponent(jList, o, i, b, b1); + if (o instanceof TATeam) { + TATeam team = (TATeam) o; + this.setText(team.getName()); + StringBuilder sb = new StringBuilder(); + TeachingAssistant[] teachingAssistants = team.getTeachingAssistants(); + for (int j = 0; j < teachingAssistants.length; j++) { + sb.append(teachingAssistants[j].getGithubUsername()); + if (j < teachingAssistants.length - 1) { + sb.append('\n'); + } + } + this.setToolTipText(sb.toString()); + } + return this; + } +} diff --git a/src/main/java/nl/andrewlalis/ui/view/list_models/TeachingAssistantListCellRenderer.java b/src/main/java/nl/andrewlalis/ui/view/list_models/TeachingAssistantListCellRenderer.java index 0c8b91f..96f970b 100644 --- a/src/main/java/nl/andrewlalis/ui/view/list_models/TeachingAssistantListCellRenderer.java +++ b/src/main/java/nl/andrewlalis/ui/view/list_models/TeachingAssistantListCellRenderer.java @@ -5,6 +5,9 @@ import nl.andrewlalis.model.TeachingAssistant; import javax.swing.*; import java.awt.*; +/** + * Controls what is rendered in a single list cell when displaying a list of TeachingAssistants. + */ public class TeachingAssistantListCellRenderer extends DefaultListCellRenderer { @Override From ab2414291f77ca2b4bf6b2d5f75b7410c3905e38 Mon Sep 17 00:00:00 2001 From: andrewlalis Date: Thu, 30 Aug 2018 21:51:33 +0200 Subject: [PATCH 04/14] Added Organization model to replace database in short term. --- src/main/java/nl/andrewlalis/Main.java | 7 +- .../nl/andrewlalis/model/Organization.java | 46 +++++ .../nl/andrewlalis/model/StudentTeam.java | 39 +++- .../andrewlalis/model/database/Database.java | 187 +++++++++--------- .../andrewlalis/ui/view/InitializerApp.java | 20 +- src/main/resources/sql/table_init.sql | 3 +- 6 files changed, 203 insertions(+), 99 deletions(-) create mode 100644 src/main/java/nl/andrewlalis/model/Organization.java diff --git a/src/main/java/nl/andrewlalis/Main.java b/src/main/java/nl/andrewlalis/Main.java index d036670..a1c4d51 100644 --- a/src/main/java/nl/andrewlalis/Main.java +++ b/src/main/java/nl/andrewlalis/Main.java @@ -1,5 +1,6 @@ package nl.andrewlalis; +import nl.andrewlalis.model.Organization; import nl.andrewlalis.model.database.Database; import nl.andrewlalis.ui.control.command.CommandExecutor; import nl.andrewlalis.ui.control.command.executables.ArchiveRepos; @@ -31,13 +32,17 @@ public class Main { // Command executor which will be used by all actions the user can do. CommandExecutor executor = new CommandExecutor(); + // Main application model. + Organization organization = new Organization(); + // Initialize User Interface. - InitializerApp app = new InitializerApp(executor); + InitializerApp app = new InitializerApp(executor, organization); app.begin(); app.setAccessToken(userOptions.get("token")); Database db = new Database("database/initializer.sqlite"); + executor.registerCommand("read_students", new ReadStudentsFileToDB(db)); executor.registerCommand("archive_all", new ArchiveRepos()); executor.registerCommand("generate_assignments", new GenerateAssignmentsRepo()); diff --git a/src/main/java/nl/andrewlalis/model/Organization.java b/src/main/java/nl/andrewlalis/model/Organization.java new file mode 100644 index 0000000..65d7a0a --- /dev/null +++ b/src/main/java/nl/andrewlalis/model/Organization.java @@ -0,0 +1,46 @@ +package nl.andrewlalis.model; + +import com.sun.org.apache.xpath.internal.operations.Or; + +import java.util.ArrayList; +import java.util.List; + +/** + * This class represents the overarching model container for the entire application, and holds in memory all student + * teams created, teaching assistant teams, and any other state information that would be needed by the user interface + * or runtime executables. + */ +public class Organization { + + /** + * A list of all student teams in this organization. This is generated from a CSV file supplied after many students + * have filled in a Google form. + */ + private List studentTeams; + + /** + * Constructs a new Organization object with all instance variables initialized to empty values. + */ + public Organization() { + this.studentTeams = new ArrayList<>(); + } + + /** + * Determines if there are student teams available. + * @return True if there is at least one student team, or false otherwise. + */ + public boolean hasStudentTeams() { + return this.studentTeams.isEmpty(); + } + + // GETTERS + public List getStudentTeams() { + return this.studentTeams; + } + + // SETTERS + public void setStudentTeams(List teams) { + this.studentTeams = teams; + } + +} diff --git a/src/main/java/nl/andrewlalis/model/StudentTeam.java b/src/main/java/nl/andrewlalis/model/StudentTeam.java index 8967d27..7da57ff 100644 --- a/src/main/java/nl/andrewlalis/model/StudentTeam.java +++ b/src/main/java/nl/andrewlalis/model/StudentTeam.java @@ -1,5 +1,7 @@ package nl.andrewlalis.model; +import org.kohsuke.github.GHRepository; + import java.util.Arrays; /** @@ -7,6 +9,16 @@ import java.util.Arrays; */ public class StudentTeam extends Team{ + /** + * The repository belonging to this team. + */ + private GHRepository repository; + + /** + * The TATeam responsible for this student team. + */ + private TATeam taTeam; + public StudentTeam() { super(-1); } @@ -24,16 +36,19 @@ public class StudentTeam extends Team{ * A team is valid if and only if: * - The student count is equal to the team size. * - Each student is unique. - * - Each student's preferred partners match all the others. + * - Each student's preferred partners match all the others, or a student has no preferences. * @param teamSize The preferred size of teams. * @return True if the team is valid, and false otherwise. */ public boolean isValid(int teamSize) { if (this.memberCount() == teamSize) { for (Student studentA : this.getStudents()) { - for (Student studentB : this.getStudents()) { - if (!studentA.equals(studentB) && !studentA.getPreferredPartners().contains(studentB.getNumber())) { - return false; + // If the student doesn't have an preferred partners, then assume that this is valid. + if (!studentA.getPreferredPartners().isEmpty()) { + for (Student studentB : this.getStudents()) { + if (!studentA.equals(studentB) && !studentA.getPreferredPartners().contains(studentB.getNumber())) { + return false; + } } } } @@ -73,4 +88,20 @@ public class StudentTeam extends Team{ } return sb.toString(); } + + public GHRepository getRepository() { + return this.repository; + } + + public void setRepository(GHRepository repo) { + this.repository = repo; + } + + public TATeam getTaTeam() { + return this.taTeam; + } + + public void setTaTeam(TATeam team) { + this.taTeam = team; + } } diff --git a/src/main/java/nl/andrewlalis/model/database/Database.java b/src/main/java/nl/andrewlalis/model/database/Database.java index 5ac46e9..a62149d 100644 --- a/src/main/java/nl/andrewlalis/model/database/Database.java +++ b/src/main/java/nl/andrewlalis/model/database/Database.java @@ -82,7 +82,7 @@ public class Database { * @param personType The type of person to store, using a constant defined above. * @return True if successful, false otherwise. */ - private boolean storePerson(Person person, int personType) { + public boolean insertPerson(Person person, int personType) { try { logger.finest("Storing person: " + person); String sql = "INSERT INTO persons (id, name, email_address, github_username, person_type_id) VALUES (?, ?, ?, ?, ?);"; @@ -92,113 +92,31 @@ public class Database { stmt.setString(3, person.getEmailAddress()); stmt.setString(4, person.getGithubUsername()); stmt.setInt(5, personType); - stmt.execute(); - return true; + return stmt.execute(); } catch (SQLException e) { logger.severe("SQLException while inserting Person: " + person + '\n' + e.getMessage()); return false; } } - /** - * Stores a teaching assistant without a team. - * @param ta The teaching assistant to store. - * @return True if successful, false otherwise. - */ - public boolean storeTeachingAssistant(TeachingAssistant ta) { - return this.storeTeachingAssistant(ta, TEAM_NONE); - } - - /** - * Stores a teaching assistant in the database. - * @param ta The teaching assistant to store. - * @param teamId The teaching assistant's team id. - * @return True if successful, false otherwise. - */ - public boolean storeTeachingAssistant(TeachingAssistant ta, int teamId) { - if (!storePerson(ta, PERSON_TYPE_TA)) { - return false; - } - try { - String sql = "INSERT INTO teaching_assistants (person_id, team_id) VALUES (?, ?);"; - PreparedStatement stmt = this.connection.prepareStatement(sql); - stmt.setInt(1, ta.getNumber()); - stmt.setInt(2, teamId); - stmt.execute(); - return true; - } catch (SQLException e) { - logger.severe("SQL Exception while inserting TeachingAssistant.\n" + e.getMessage()); - e.printStackTrace(); - return false; - } - } - - /** - * Stores a team in the database. - * @param team The team to store. - * @param type The type of team that this is. - * @return True if successful, false otherwise. - */ - public boolean storeTeam(Team team, int type) { - try { - String sql = "INSERT INTO teams (id, team_type_id) VALUES (?, ?);"; - PreparedStatement stmt = this.connection.prepareStatement(sql); - stmt.setInt(1, team.getId()); - stmt.setInt(2, type); - stmt.execute(); - return true; - } catch (SQLException e) { - logger.severe("SQLException while inserting team: " + team + '\n' + e.getMessage()); - return false; - } - } - - /** - * Stores a list of student teams in the database. - * @param teams The list of teams to store. - * @return True if successful, or false if an error occurred. - */ - public boolean storeStudentTeams(List teams) { - for (StudentTeam team : teams) { - if (!this.storeTeam(team, TEAM_TYPE_STUDENT)) { - return false; - } - for (Student student : team.getStudents()) { - if (!this.storeStudent(student, team.getId())) { - return false; - } - } - } - return true; - } - - /** - * Stores a student without a team. - * @param student The student to store. - * @return True if successful, false otherwise. - */ - public boolean storeStudent(Student student) { - return this.storeStudent(student, TEAM_NONE); - } - /** * Stores a student in the database. * @param student The student to store. - * @param teamId The team id for the team the student is in. * @return True if the operation was successful, false otherwise. */ - public boolean storeStudent(Student student, int teamId) { + public boolean insertStudent(Student student) { logger.finest("Storing student: " + student); - if (!storePerson(student, PERSON_TYPE_STUDENT)) { + if (!insertPerson(student, PERSON_TYPE_STUDENT)) { return false; } try { - String sql = "INSERT INTO students (person_id, team_id, chose_partner) VALUES (?, ?, ?);"; + String sql = "INSERT INTO students (person_id, chose_partner) VALUES (?, ?);"; PreparedStatement stmt = this.connection.prepareStatement(sql); stmt.setInt(1, student.getNumber()); - stmt.setInt(2, teamId); - stmt.setInt(3, student.getPreferredPartners().size() > 0 ? 1 : 0); - stmt.execute(); + stmt.setInt(2, student.getPreferredPartners().size() > 0 ? 1 : 0); + if (!stmt.execute()) { + return false; + } // Storing partners. String sqlPartner = "INSERT INTO student_preferred_partners (student_id, partner_id) VALUES (?, ?);"; PreparedStatement stmtPartner = this.connection.prepareStatement(sqlPartner); @@ -215,6 +133,93 @@ public class Database { } } + /** + * Stores a teaching assistant in the database. + * @param ta The teaching assistant to store. + * @return True if successful, false otherwise. + */ + public boolean insertTeachingAssistant(TeachingAssistant ta) { + if (!insertPerson(ta, PERSON_TYPE_TA)) { + return false; + } + try { + String sql = "INSERT INTO teaching_assistants (person_id) VALUES (?);"; + PreparedStatement stmt = this.connection.prepareStatement(sql); + stmt.setInt(1, ta.getNumber()); + stmt.execute(); + return true; + } catch (SQLException e) { + logger.severe("SQL Exception while inserting TeachingAssistant.\n" + e.getMessage()); + e.printStackTrace(); + return false; + } + } + + /** + * Stores a team in the database, and any persons who do not yet exist. + * @param team The team to store. + * @param type The type of team that this is. + * @param personType The type of people that this team is made of. + * @return True if successful, false otherwise. + */ + public boolean insertTeam(Team team, int type, int personType) { + try { + String sql = "INSERT INTO teams (id, team_type_id) VALUES (?, ?);"; + PreparedStatement stmt = this.connection.prepareStatement(sql); + stmt.setInt(1, team.getId()); + stmt.setInt(2, type); + if (stmt.execute()) { + for (Person p : team.getMembers()) { + this.insertPerson(p, personType); + String sqlMembers = "INSERT INTO team_members (team_id, person_id) VALUES (?, ?);"; + PreparedStatement stmtMembers = this.connection.prepareStatement(sqlMembers); + stmtMembers.setInt(1, team.getId()); + stmtMembers.setInt(2, p.getNumber()); + stmtMembers.execute(); + } + } + return true; + } catch (SQLException e) { + logger.severe("SQLException while inserting team: " + team + '\n' + e.getMessage()); + return false; + } + } + + /** + * Stores a student team in the database. + * @param team The team to store. + * @return True if successful, false otherwise. + */ + public boolean insertStudentTeam(StudentTeam team) { + if (!this.insertTeam(team, TEAM_TYPE_STUDENT, PERSON_TYPE_STUDENT)) { + return false; + } + try { + String sql = "INSERT INTO student_teams (team_id, repository_name, teaching_assistant_team_id) VALUES (?, ?, ?);"; + PreparedStatement stmt = this.connection.prepareStatement(sql); + stmt.setInt(1, team.getId()); + stmt.setString(2, (team.getRepository() == null) ? null : team.getRepository().getName()); + stmt.setInt(3, (team.getTaTeam() == null) ? TEAM_NONE : team.getTaTeam().getId()); + return stmt.execute(); + } catch (SQLException e) { + logger.severe("SQLException while inserting student team: " + team + '\n' + e.getMessage()); + e.printStackTrace(); + return false; + } + } + + /** + * Stores a list of student teams in the database. + * @param teams The list of teams to store. + * @return True if successful, or false if an error occurred. + */ + public boolean storeStudentTeams(List teams) { + for (StudentTeam team : teams) { + this.insertStudentTeam(team); + } + return true; + } + /** * Retrieves a list of preferred partners that each student has set. * @param studentId The student id to search by. diff --git a/src/main/java/nl/andrewlalis/ui/view/InitializerApp.java b/src/main/java/nl/andrewlalis/ui/view/InitializerApp.java index c92e0f5..59155d4 100644 --- a/src/main/java/nl/andrewlalis/ui/view/InitializerApp.java +++ b/src/main/java/nl/andrewlalis/ui/view/InitializerApp.java @@ -1,11 +1,15 @@ package nl.andrewlalis.ui.view; +import com.sun.org.apache.xpath.internal.operations.Or; +import nl.andrewlalis.model.Organization; +import nl.andrewlalis.model.StudentTeam; import nl.andrewlalis.ui.control.OutputTextHandler; import nl.andrewlalis.ui.control.command.CommandExecutor; import nl.andrewlalis.ui.control.listeners.*; import javax.swing.*; import java.awt.*; +import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; @@ -37,8 +41,22 @@ public class InitializerApp extends JFrame { */ private CommandExecutor executor; - public InitializerApp(CommandExecutor executor) { + /** + * The organization object, which contains all important state information. + */ + private Organization organization; + + /** + * Constructs a new instance of the main application frame, with both an executor, and organization model. + * + * @param executor The command executor, which is passed to any action listeners, so that buttons in this interface + * may execute commands in the same way that the command line can. + * @param organization A reference to the application's organization model, which holds all important runtime state + * information. + */ + public InitializerApp(CommandExecutor executor, Organization organization) { this.executor = executor; + this.organization = organization; // UI initialization. ImageIcon icon = new ImageIcon(getClass().getResource("/image/icon.png")); diff --git a/src/main/resources/sql/table_init.sql b/src/main/resources/sql/table_init.sql index 9f0d8f2..0bba75d 100644 --- a/src/main/resources/sql/table_init.sql +++ b/src/main/resources/sql/table_init.sql @@ -43,7 +43,7 @@ CREATE TABLE IF NOT EXISTS teams ( ); CREATE TABLE IF NOT EXISTS team_members ( - id INTEGER PRIMARY KEY, + id INTEGER PRIMARY KEY AUTOINCREMENT, team_id INTEGER NOT NULL, person_id INTEGER NOT NULL, FOREIGN KEY (team_id) @@ -69,7 +69,6 @@ CREATE TABLE IF NOT EXISTS teaching_assistant_teams ( CREATE TABLE IF NOT EXISTS student_teams ( team_id INTEGER PRIMARY KEY, repository_name TEXT, - group_id INTEGER NOT NULL UNIQUE, teaching_assistant_team_id INTEGER, FOREIGN KEY (team_id) REFERENCES teams(id) From 6d7efbe97f582135658775bd0fa5193d83873b19 Mon Sep 17 00:00:00 2001 From: andrewlalis Date: Thu, 30 Aug 2018 22:05:17 +0200 Subject: [PATCH 05/14] Changed ReadStudentsFileToDB to just read to the memory organization object. --- src/main/java/nl/andrewlalis/Main.java | 11 +++-------- ...entsFileToDB.java => ReadStudentsFile.java} | 18 +++++++++--------- 2 files changed, 12 insertions(+), 17 deletions(-) rename src/main/java/nl/andrewlalis/ui/control/command/executables/{ReadStudentsFileToDB.java => ReadStudentsFile.java} (65%) diff --git a/src/main/java/nl/andrewlalis/Main.java b/src/main/java/nl/andrewlalis/Main.java index a1c4d51..ad2a75b 100644 --- a/src/main/java/nl/andrewlalis/Main.java +++ b/src/main/java/nl/andrewlalis/Main.java @@ -1,12 +1,11 @@ package nl.andrewlalis; import nl.andrewlalis.model.Organization; -import nl.andrewlalis.model.database.Database; import nl.andrewlalis.ui.control.command.CommandExecutor; import nl.andrewlalis.ui.control.command.executables.ArchiveRepos; import nl.andrewlalis.ui.control.command.executables.DefineTaTeams; import nl.andrewlalis.ui.control.command.executables.GenerateAssignmentsRepo; -import nl.andrewlalis.ui.control.command.executables.ReadStudentsFileToDB; +import nl.andrewlalis.ui.control.command.executables.ReadStudentsFile; import nl.andrewlalis.ui.view.InitializerApp; import nl.andrewlalis.util.CommandLine; import nl.andrewlalis.util.Logging; @@ -40,16 +39,12 @@ public class Main { app.begin(); app.setAccessToken(userOptions.get("token")); - Database db = new Database("database/initializer.sqlite"); - - - executor.registerCommand("read_students", new ReadStudentsFileToDB(db)); + // Initialize executable commands. + executor.registerCommand("read_students", new ReadStudentsFile(organization)); executor.registerCommand("archive_all", new ArchiveRepos()); executor.registerCommand("generate_assignments", new GenerateAssignmentsRepo()); executor.registerCommand("define_ta_teams", new DefineTaTeams(app)); - db.initialize(); - logger.info("GithubManager for Github Repositories in Educational Organizations. Program initialized."); } diff --git a/src/main/java/nl/andrewlalis/ui/control/command/executables/ReadStudentsFileToDB.java b/src/main/java/nl/andrewlalis/ui/control/command/executables/ReadStudentsFile.java similarity index 65% rename from src/main/java/nl/andrewlalis/ui/control/command/executables/ReadStudentsFileToDB.java rename to src/main/java/nl/andrewlalis/ui/control/command/executables/ReadStudentsFile.java index 664f7bd..f275885 100644 --- a/src/main/java/nl/andrewlalis/ui/control/command/executables/ReadStudentsFileToDB.java +++ b/src/main/java/nl/andrewlalis/ui/control/command/executables/ReadStudentsFile.java @@ -1,7 +1,7 @@ package nl.andrewlalis.ui.control.command.executables; +import nl.andrewlalis.model.Organization; import nl.andrewlalis.model.StudentTeam; -import nl.andrewlalis.model.database.Database; import nl.andrewlalis.ui.control.command.Executable; import nl.andrewlalis.util.FileUtils; @@ -9,24 +9,23 @@ import java.util.List; /** * Execute this class to read students from a supplied filename and teamsize, and store their - * information in the database. + * information in the application's organization model. * Requires the following arguments: * * 1. filename * 2. teamsize */ -public class ReadStudentsFileToDB implements Executable { +public class ReadStudentsFile implements Executable { /** - * The database used to store the students. + * A reference to the application's organization model. */ - private Database db; + private Organization organization; - public ReadStudentsFileToDB(Database db) { - this.db = db; + public ReadStudentsFile(Organization organization) { + this.organization = organization; } - @Override public boolean execute(String[] args) { if (args.length < 2) { @@ -38,6 +37,7 @@ public class ReadStudentsFileToDB implements Executable { if (teams == null) { return false; } - return this.db.storeStudentTeams(teams); + this.organization.setStudentTeams(teams); + return true; } } From 6d423858146752b08b02364691d71e6cb243cc42 Mon Sep 17 00:00:00 2001 From: andrewlalis Date: Thu, 30 Aug 2018 23:34:04 +0200 Subject: [PATCH 06/14] Did some serious refactoring. --- src/main/java/nl/andrewlalis/Main.java | 12 +- .../nl/andrewlalis/git_api/GithubManager.java | 149 ++++++++++-------- .../nl/andrewlalis/model/Organization.java | 28 +++- .../nl/andrewlalis/model/error/Error.java | 45 ++++++ .../nl/andrewlalis/model/error/Severity.java | 12 ++ .../executables/GenerateStudentRepos.java | 4 + .../command/executables/ReadStudentsFile.java | 14 +- .../andrewlalis/ui/view/InitializerApp.java | 10 +- .../nl/andrewlalis/util/TeamGenerator.java | 5 + 9 files changed, 190 insertions(+), 89 deletions(-) create mode 100644 src/main/java/nl/andrewlalis/model/error/Error.java create mode 100644 src/main/java/nl/andrewlalis/model/error/Severity.java diff --git a/src/main/java/nl/andrewlalis/Main.java b/src/main/java/nl/andrewlalis/Main.java index ad2a75b..f614faa 100644 --- a/src/main/java/nl/andrewlalis/Main.java +++ b/src/main/java/nl/andrewlalis/Main.java @@ -31,21 +31,23 @@ public class Main { // Command executor which will be used by all actions the user can do. CommandExecutor executor = new CommandExecutor(); - // Main application model. - Organization organization = new Organization(); + // Main application model is stored as a static variable that is accessible everywhere. + InitializerApp.organization = new Organization(); // Initialize User Interface. - InitializerApp app = new InitializerApp(executor, organization); + InitializerApp app = new InitializerApp(executor); app.begin(); app.setAccessToken(userOptions.get("token")); // Initialize executable commands. - executor.registerCommand("read_students", new ReadStudentsFile(organization)); + executor.registerCommand("read_students", new ReadStudentsFile(InitializerApp.organization)); executor.registerCommand("archive_all", new ArchiveRepos()); executor.registerCommand("generate_assignments", new GenerateAssignmentsRepo()); executor.registerCommand("define_ta_teams", new DefineTaTeams(app)); - logger.info("GithubManager for Github Repositories in Educational Organizations. Program initialized."); + logger.info("GithubManager for Github Repositories in Educational Organizations.\n" + + "© Andrew Lalis (2018), All rights reserved.\n" + + "Program initialized."); } } diff --git a/src/main/java/nl/andrewlalis/git_api/GithubManager.java b/src/main/java/nl/andrewlalis/git_api/GithubManager.java index 5829129..48db804 100644 --- a/src/main/java/nl/andrewlalis/git_api/GithubManager.java +++ b/src/main/java/nl/andrewlalis/git_api/GithubManager.java @@ -2,7 +2,13 @@ package nl.andrewlalis.git_api; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; -import nl.andrewlalis.model.*; +import nl.andrewlalis.model.Student; +import nl.andrewlalis.model.StudentTeam; +import nl.andrewlalis.model.TATeam; +import nl.andrewlalis.model.TeachingAssistant; +import nl.andrewlalis.model.error.Error; +import nl.andrewlalis.model.error.Severity; +import nl.andrewlalis.ui.view.InitializerApp; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpPatch; import org.apache.http.entity.StringEntity; @@ -81,7 +87,7 @@ public class GithubManager { public List getMembers() { List teachingAssistants = new ArrayList<>(); try { - for (GHUser member : this.organization.getMembers()) { + for (GHUser member : this.organization.listMembers().asList()) { teachingAssistants.add(new TeachingAssistant(-1, member.getName(), member.getEmail(), member.getLogin())); } } catch (IOException e) { @@ -91,34 +97,6 @@ public class GithubManager { return teachingAssistants; } - /** - * Initializes the github repository for all studentTeams given. - * - * Creates for the entire organization: - * - an assignments repository with protected master branch and TA permissions. - * Creates for each team: - * - a repository - * - protected master branch - * - development branch - * - adds students to repository - * - adds all students to assignments repository. - * @param studentTeams The list of student studentTeams. - * @param teamAll The team of all teaching assistants. - * @param assignmentsRepoName The name of the assignments repo. - * @throws Exception If an error occurs while initializing the github repositories. - */ - public void initializeGithubRepos(List studentTeams, TATeam teamAll, String assignmentsRepoName) throws Exception { - this.setupAssignmentsRepo(assignmentsRepoName, "fuck the police", teamAll.getName()); - - StudentTeam t = new StudentTeam(); - Student s = new Student(3050831, "Andrew Lalis", "andrewlalisofficial@gmail.com", "andrewlalis", null); - t.addMember(s); - t.setId(42); - - this.setupStudentTeam(t, teamAll.getGithubTeam(), "advoop_2018"); - // TODO: Finish this method. - } - /** * Sets up the organization's assignments repository, and grants permissions to all teaching assistants. * @param assignmentsRepoName The name of the assignments repository. @@ -131,6 +109,7 @@ public class GithubManager { // Check if the repository already exists. GHRepository existingRepo = this.organization.getRepository(assignmentsRepoName); if (existingRepo != null) { + InitializerApp.organization.addError(new Error(Severity.MINOR, "Assignments repository already existed, deleting it.")); existingRepo.delete(); logger.fine("Deleted pre-existing assignments repository."); } @@ -155,46 +134,45 @@ public class GithubManager { * @param team The student team to set up. * @param taTeam The team of teaching assistants that is responsible for these students. * @param prefix The prefix to append to the front of the repo name. - * @throws IOException If an HTTP request fails. */ - public void setupStudentTeam(StudentTeam team, GHTeam taTeam, String prefix) throws IOException { + public void setupStudentTeam(StudentTeam team, TATeam taTeam, String prefix) { // First check that the assignments repo exists, otherwise no invitations can be sent. if (this.assignmentsRepo == null) { logger.warning("Assignments repository must be created before student repositories."); return; } - GHRepository repo = this.createRepository(team.generateUniqueName(prefix), taTeam, team.generateRepoDescription(), false, true, false); + GHRepository repo = this.createRepository(team.generateUniqueName(prefix), taTeam.getGithubTeam(), team.generateRepoDescription(), false, true, false); if (repo == null) { logger.severe("Repository for student team " + team.getId() + " could not be created."); return; } - this.protectMasterBranch(repo, taTeam); + this.protectMasterBranch(repo, taTeam.getGithubTeam()); this.createDevelopmentBranch(repo); + this.addTATeamAsAdmin(repo, taTeam.getGithubTeam()); + this.inviteStudentsToRepos(team, repo); - taTeam.add(repo, GHOrganization.Permission.ADMIN); - logger.fine("Added team " + taTeam.getName() + " as admin to repository: " + repo.getName()); - - List users = new ArrayList<>(); - for (Student student : team.getStudents()) { - GHUser user = this.github.getUser(student.getGithubUsername()); - users.add(user); - } - - repo.addCollaborators(users); - this.assignmentsRepo.addCollaborators(users); + team.setRepository(repo); + team.setTaTeam(taTeam); } /** * Deletes all repositories in the organization. - * @throws IOException if an error occurs with sending requests. + * @param substring The substring which repository names should contain to be deleted. */ - public void deleteAllRepositories() throws IOException { + public void deleteAllRepositories(String substring) { List repositories = this.organization.listRepositories().asList(); for (GHRepository repo : repositories) { - repo.delete(); + if (repo.getName().contains(substring)) { + try { + repo.delete(); + } catch (IOException e) { + InitializerApp.organization.addError(new Error(Severity.HIGH, "Could not delete repository: " + repo.getName())); + e.printStackTrace(); + } + } } } @@ -202,7 +180,7 @@ public class GithubManager { * Archives all repositories whose name contains the given substring. * @param sub Any repository containing this substring will be archived. */ - public void archiveAllRepositories(String sub) throws IOException { + public void archiveAllRepositories(String sub) { List repositories = this.organization.listRepositories().asList(); for (GHRepository repo : repositories) { if (repo.getName().contains(sub)) { @@ -215,21 +193,61 @@ public class GithubManager { * 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. - * @throws IOException If an error occurs with the HTTP request. */ - public void archiveRepository(GHRepository repo) throws IOException { - HttpPatch patch = new HttpPatch("https://api.github.com/repos/" + repo.getFullName() + "?access_token=" + this.accessToken); - CloseableHttpClient client = HttpClientBuilder.create().build(); - ObjectMapper mapper = new ObjectMapper(); - ObjectNode root = mapper.createObjectNode(); - root.put("archived", true); - String json = mapper.writeValueAsString(root); - patch.setEntity(new StringEntity(json)); - HttpResponse response = client.execute(patch); - if (response.getStatusLine().getStatusCode() != 200) { - throw new IOException("Could not archive repository: " + repo.getName() + ". Code: " + response.getStatusLine().getStatusCode()); + private void archiveRepository(GHRepository repo) { + try { + HttpPatch patch = new HttpPatch("https://api.github.com/repos/" + repo.getFullName() + "?access_token=" + this.accessToken); + CloseableHttpClient client = HttpClientBuilder.create().build(); + ObjectMapper mapper = new ObjectMapper(); + ObjectNode root = mapper.createObjectNode(); + root.put("archived", true); + String json = mapper.writeValueAsString(root); + patch.setEntity(new StringEntity(json)); + HttpResponse response = client.execute(patch); + if (response.getStatusLine().getStatusCode() != 200) { + throw new IOException("Could not archive repository: " + repo.getName() + ". Code: " + response.getStatusLine().getStatusCode()); + } + logger.info("Archived repository: " + repo.getFullName()); + } catch (IOException e) { + InitializerApp.organization.addError(new Error(Severity.HIGH, "Could not archive repository: " + repo.getName())); + } + } + + /** + * Invites students in a team to their repository, and the assignments repository. + * @param team The team of students to invite as collaborators. + * @param repo The repository created for the students. + */ + private void inviteStudentsToRepos(StudentTeam team, GHRepository repo) { + try { + logger.finest("Adding students from team: " + team.getId() + " as collaborators."); + List users = new ArrayList<>(); + for (Student student : team.getStudents()) { + GHUser user = this.github.getUser(student.getGithubUsername()); + users.add(user); + } + + repo.addCollaborators(users); + this.assignmentsRepo.addCollaborators(users); + } catch (IOException e) { + InitializerApp.organization.addError(new Error(Severity.HIGH, "Students in team: " + team + " could not be added as collaborators to assignments repo or their repository.")); + logger.warning("Could not add students as collaborators to assignments or their repo."); + } + } + + /** + * Adds a teaching assistant team as admins to a particular student repository. + * @param studentRepo The student repository. + * @param taTeam The team to give admin rights. + */ + private void addTATeamAsAdmin(GHRepository studentRepo, GHTeam taTeam) { + try { + taTeam.add(studentRepo, GHOrganization.Permission.ADMIN); + logger.fine("Added team " + taTeam.getName() + " as admin to repository: " + studentRepo.getName()); + } catch (IOException e) { + InitializerApp.organization.addError(new Error(Severity.HIGH, "Could not add TA Team: " + taTeam.getName() + " as ADMIN to repository: " + studentRepo.getName())); + logger.severe("Could not add TA Team: " + taTeam.getName() + " as admins to repository: " + studentRepo.getName()); } - logger.info("Archived repository: " + repo.getFullName()); } /** @@ -248,6 +266,7 @@ public class GithubManager { protectionBuilder.enable(); logger.fine("Protected master branch of repository: " + repo.getName()); } catch (IOException e) { + InitializerApp.organization.addError(new Error(Severity.HIGH, "Could not protect master branch of repository: " + repo.getName())); logger.severe("Could not protect master branch of repository: " + repo.getName()); e.printStackTrace(); } @@ -263,6 +282,7 @@ public class GithubManager { repo.createRef("refs/heads/development", sha1); logger.fine("Created development branch of repository: " + repo.getName()); } catch (IOException e) { + InitializerApp.organization.addError(new Error(Severity.HIGH, "Could not create development branch of repository: " + repo.getName())); logger.severe("Could not create development branch for repository: " + repo.getName() + '\n' + e.getMessage()); e.printStackTrace(); } @@ -276,7 +296,7 @@ public class GithubManager { * @param hasWiki Whether the repo has a wiki enabled. * @param hasIssues Whether the repo has issues enabled. * @param isPrivate Whether or not the repository is private. - * @return The repository that was created, or + * @return The repository that was created, or null if it could not be created. */ private GHRepository createRepository(String name, GHTeam taTeam, String description, boolean hasWiki, boolean hasIssues, boolean isPrivate){ try { @@ -286,12 +306,13 @@ public class GithubManager { builder.issues(hasIssues); builder.description(description); builder.gitignoreTemplate("Java"); - builder.private_(isPrivate); // TODO: Change this to true for production + builder.private_(isPrivate); GHRepository repo = builder.create(); logger.fine("Created repository: " + repo.getName()); return repo; } catch (IOException e) { logger.severe("Could not create repository: " + name + '\n' + e.getMessage()); + InitializerApp.organization.addError(new Error(Severity.CRITICAL, "Could not create repository: " + name)); e.printStackTrace(); return null; } diff --git a/src/main/java/nl/andrewlalis/model/Organization.java b/src/main/java/nl/andrewlalis/model/Organization.java index 65d7a0a..8a5df40 100644 --- a/src/main/java/nl/andrewlalis/model/Organization.java +++ b/src/main/java/nl/andrewlalis/model/Organization.java @@ -1,16 +1,17 @@ package nl.andrewlalis.model; -import com.sun.org.apache.xpath.internal.operations.Or; +import nl.andrewlalis.model.error.Error; import java.util.ArrayList; import java.util.List; +import java.util.Observable; /** * This class represents the overarching model container for the entire application, and holds in memory all student * teams created, teaching assistant teams, and any other state information that would be needed by the user interface * or runtime executables. */ -public class Organization { +public class Organization extends Observable { /** * A list of all student teams in this organization. This is generated from a CSV file supplied after many students @@ -18,11 +19,18 @@ public class Organization { */ private List studentTeams; + /** + * 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. + */ + private List errors; + /** * Constructs a new Organization object with all instance variables initialized to empty values. */ public Organization() { this.studentTeams = new ArrayList<>(); + this.errors = new ArrayList<>(); } /** @@ -38,9 +46,25 @@ public class Organization { 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. + */ + public void addError(Error newError) { + this.errors.add(newError); + this.setChanged(); + this.notifyObservers(); } } diff --git a/src/main/java/nl/andrewlalis/model/error/Error.java b/src/main/java/nl/andrewlalis/model/error/Error.java new file mode 100644 index 0000000..eade31c --- /dev/null +++ b/src/main/java/nl/andrewlalis/model/error/Error.java @@ -0,0 +1,45 @@ +package nl.andrewlalis.model.error; + +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Date; + +/** + * Represents an error that occurs, but does not stop the application while running. Different from an exception in that + * errors are meant to be added to a list and processed at a later time, and not hinder the operations in the present. + */ +public class Error { + + /** + * The time at which this error occurred. + */ + private long timestamp; + + /** + * The severity of the error. + */ + private Severity severity; + + /** + * A custom error message generated at the time of the error. + */ + private String message; + + /** + * Constructs a new Error with a severity and message. + * @param severity The severity of the message. + * @param message The custom generated error message for this error. + */ + public Error(Severity severity, String message) { + this.timestamp = System.currentTimeMillis(); + this.severity = severity; + this.message = message; + } + + @Override + public String toString() { + DateFormat df = new SimpleDateFormat("dd/MM/yyyy HH:mm:ss"); + String dateString = df.format(new Date(this.timestamp)); + return dateString + ' ' + this.severity.toString() + ": " + this.message; + } +} diff --git a/src/main/java/nl/andrewlalis/model/error/Severity.java b/src/main/java/nl/andrewlalis/model/error/Severity.java new file mode 100644 index 0000000..2ab20cf --- /dev/null +++ b/src/main/java/nl/andrewlalis/model/error/Severity.java @@ -0,0 +1,12 @@ +package nl.andrewlalis.model.error; + +/** + * Represents the different levels of severity for errors. + */ +public enum Severity { + CRITICAL, // Anything which happens and could have serious side-effects or massive implications system-wide. + HIGH, // Errors which should be at the top of priority to sort out after a procedure is executed. + MEDIUM, // Medium errors are, as the name implies, of a medium priority. + LOW, // Low severity errors are the smallest errors that should be processed soon to avoid issues. + MINOR // Minor errors are so insignificant that they may not even require user intervention. +} 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 index 41f7975..cec7f12 100644 --- a/src/main/java/nl/andrewlalis/ui/control/command/executables/GenerateStudentRepos.java +++ b/src/main/java/nl/andrewlalis/ui/control/command/executables/GenerateStudentRepos.java @@ -2,6 +2,10 @@ 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 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 f275885..5ca9656 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 @@ -2,7 +2,10 @@ 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; import java.util.List; @@ -17,15 +20,6 @@ import java.util.List; */ public class ReadStudentsFile implements Executable { - /** - * A reference to the application's organization model. - */ - private Organization organization; - - public ReadStudentsFile(Organization organization) { - this.organization = organization; - } - @Override public boolean execute(String[] args) { if (args.length < 2) { @@ -37,7 +31,7 @@ public class ReadStudentsFile implements Executable { if (teams == null) { return false; } - this.organization.setStudentTeams(teams); + InitializerApp.organization.setStudentTeams(teams); return true; } } diff --git a/src/main/java/nl/andrewlalis/ui/view/InitializerApp.java b/src/main/java/nl/andrewlalis/ui/view/InitializerApp.java index 59155d4..de06018 100644 --- a/src/main/java/nl/andrewlalis/ui/view/InitializerApp.java +++ b/src/main/java/nl/andrewlalis/ui/view/InitializerApp.java @@ -1,15 +1,12 @@ package nl.andrewlalis.ui.view; -import com.sun.org.apache.xpath.internal.operations.Or; import nl.andrewlalis.model.Organization; -import nl.andrewlalis.model.StudentTeam; import nl.andrewlalis.ui.control.OutputTextHandler; import nl.andrewlalis.ui.control.command.CommandExecutor; import nl.andrewlalis.ui.control.listeners.*; import javax.swing.*; import java.awt.*; -import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; @@ -44,19 +41,16 @@ public class InitializerApp extends JFrame { /** * The organization object, which contains all important state information. */ - private Organization organization; + public static Organization organization; /** * Constructs a new instance of the main application frame, with both an executor, and organization model. * * @param executor The command executor, which is passed to any action listeners, so that buttons in this interface * may execute commands in the same way that the command line can. - * @param organization A reference to the application's organization model, which holds all important runtime state - * information. */ - public InitializerApp(CommandExecutor executor, Organization organization) { + public InitializerApp(CommandExecutor executor) { this.executor = executor; - this.organization = organization; // UI initialization. ImageIcon icon = new ImageIcon(getClass().getResource("/image/icon.png")); diff --git a/src/main/java/nl/andrewlalis/util/TeamGenerator.java b/src/main/java/nl/andrewlalis/util/TeamGenerator.java index b765e7b..96800ed 100644 --- a/src/main/java/nl/andrewlalis/util/TeamGenerator.java +++ b/src/main/java/nl/andrewlalis/util/TeamGenerator.java @@ -2,6 +2,9 @@ package nl.andrewlalis.util; import nl.andrewlalis.model.Student; import nl.andrewlalis.model.StudentTeam; +import nl.andrewlalis.model.error.Error; +import nl.andrewlalis.model.error.Severity; +import nl.andrewlalis.ui.view.InitializerApp; import org.apache.commons.csv.CSVFormat; import org.apache.commons.csv.CSVRecord; @@ -36,6 +39,7 @@ public class TeamGenerator { logger.fine("Generating teams of size " + teamSize); if (teamSize < 1) { logger.severe("Invalid team size."); + InitializerApp.organization.addError(new Error(Severity.CRITICAL, "Invalid team size while generating teams from CSV.")); throw new IllegalArgumentException("StudentTeam size must be greater than or equal to 1. Got " + teamSize); } logger.fine("Parsing CSV file."); @@ -47,6 +51,7 @@ public class TeamGenerator { studentMap = readAllStudents(records, teamSize); } catch (ArrayIndexOutOfBoundsException e) { logger.severe("StudentTeam size does not match column count in records."); + InitializerApp.organization.addError(new Error(Severity.CRITICAL, "Team size does not match column count in records.")); throw new IllegalArgumentException("StudentTeam size does not match column count in records."); } From 10555a5d0c2081ac105052916ce181075551b347 Mon Sep 17 00:00:00 2001 From: andrewlalis Date: Thu, 30 Aug 2018 23:41:22 +0200 Subject: [PATCH 07/14] Added list_errors command. --- src/main/java/nl/andrewlalis/Main.java | 8 ++--- .../command/executables/ArchiveRepos.java | 11 ++----- .../command/executables/ListErrors.java | 32 +++++++++++++++++++ 3 files changed, 37 insertions(+), 14 deletions(-) create mode 100644 src/main/java/nl/andrewlalis/ui/control/command/executables/ListErrors.java diff --git a/src/main/java/nl/andrewlalis/Main.java b/src/main/java/nl/andrewlalis/Main.java index f614faa..869813e 100644 --- a/src/main/java/nl/andrewlalis/Main.java +++ b/src/main/java/nl/andrewlalis/Main.java @@ -2,10 +2,7 @@ package nl.andrewlalis; import nl.andrewlalis.model.Organization; import nl.andrewlalis.ui.control.command.CommandExecutor; -import nl.andrewlalis.ui.control.command.executables.ArchiveRepos; -import nl.andrewlalis.ui.control.command.executables.DefineTaTeams; -import nl.andrewlalis.ui.control.command.executables.GenerateAssignmentsRepo; -import nl.andrewlalis.ui.control.command.executables.ReadStudentsFile; +import nl.andrewlalis.ui.control.command.executables.*; import nl.andrewlalis.ui.view.InitializerApp; import nl.andrewlalis.util.CommandLine; import nl.andrewlalis.util.Logging; @@ -40,10 +37,11 @@ public class Main { app.setAccessToken(userOptions.get("token")); // Initialize executable commands. - executor.registerCommand("read_students", new ReadStudentsFile(InitializerApp.organization)); + executor.registerCommand("read_students", new ReadStudentsFile()); executor.registerCommand("archive_all", new ArchiveRepos()); executor.registerCommand("generate_assignments", new GenerateAssignmentsRepo()); executor.registerCommand("define_ta_teams", new DefineTaTeams(app)); + executor.registerCommand("list_errors", new ListErrors()); 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/ui/control/command/executables/ArchiveRepos.java b/src/main/java/nl/andrewlalis/ui/control/command/executables/ArchiveRepos.java index 5e43269..ef10441 100644 --- a/src/main/java/nl/andrewlalis/ui/control/command/executables/ArchiveRepos.java +++ b/src/main/java/nl/andrewlalis/ui/control/command/executables/ArchiveRepos.java @@ -2,8 +2,6 @@ package nl.andrewlalis.ui.control.command.executables; import nl.andrewlalis.git_api.GithubManager; -import java.io.IOException; - /** * Represents the action archive all repositories with a certain substring in their name. * It takes the following arguments: @@ -17,12 +15,7 @@ public class ArchiveRepos extends GithubExecutable { if (args.length < 1) { return false; } - try { - manager.archiveAllRepositories(args[0]); - return true; - } catch (IOException e) { - e.printStackTrace(); - return false; - } + manager.archiveAllRepositories(args[0]); + return true; } } diff --git a/src/main/java/nl/andrewlalis/ui/control/command/executables/ListErrors.java b/src/main/java/nl/andrewlalis/ui/control/command/executables/ListErrors.java new file mode 100644 index 0000000..61dc7bb --- /dev/null +++ b/src/main/java/nl/andrewlalis/ui/control/command/executables/ListErrors.java @@ -0,0 +1,32 @@ +package nl.andrewlalis.ui.control.command.executables; + +import nl.andrewlalis.model.error.Error; +import nl.andrewlalis.ui.control.command.Executable; +import nl.andrewlalis.ui.view.InitializerApp; + +import java.util.logging.Logger; + +/** + * This executable lists all errors that have occurred so far in the runtime of the program, and have not been resolved. + */ +public class ListErrors implements Executable { + + /** + * The logger for outputting debug info. + */ + private static final Logger logger = Logger.getLogger(ListErrors.class.getName()); + static { + logger.setParent(Logger.getGlobal()); + } + + @Override + public boolean execute(String[] args) { + StringBuilder sb = new StringBuilder("Runtime Errors:\n"); + for (Error error : InitializerApp.organization.getErrors()) { + sb.append(error); + } + logger.info(sb.toString()); + return true; + } + +} From a44814b1df459eafdd348a2eb4bd194fc592e3ae Mon Sep 17 00:00:00 2001 From: andrewlalis Date: Sat, 1 Sep 2018 08:25:45 +0200 Subject: [PATCH 08/14] Added delete repos command, made ListErrors better, and formatted buttons more nicely. --- src/main/java/nl/andrewlalis/Main.java | 1 + .../nl/andrewlalis/git_api/GithubManager.java | 1 + .../command/executables/DeleteRepos.java | 17 ++++++++ .../command/executables/ListErrors.java | 3 ++ .../listeners/DeleteReposListener.java | 32 +++++++++++++++ .../andrewlalis/ui/view/InitializerApp.java | 40 +++++++++++++------ 6 files changed, 81 insertions(+), 13 deletions(-) create mode 100644 src/main/java/nl/andrewlalis/ui/control/command/executables/DeleteRepos.java create mode 100644 src/main/java/nl/andrewlalis/ui/control/listeners/DeleteReposListener.java diff --git a/src/main/java/nl/andrewlalis/Main.java b/src/main/java/nl/andrewlalis/Main.java index 869813e..cb1d021 100644 --- a/src/main/java/nl/andrewlalis/Main.java +++ b/src/main/java/nl/andrewlalis/Main.java @@ -42,6 +42,7 @@ public class Main { executor.registerCommand("generate_assignments", new GenerateAssignmentsRepo()); executor.registerCommand("define_ta_teams", new DefineTaTeams(app)); executor.registerCommand("list_errors", new ListErrors()); + executor.registerCommand("delete_repos", new DeleteRepos()); 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/git_api/GithubManager.java b/src/main/java/nl/andrewlalis/git_api/GithubManager.java index 48db804..aa4c23f 100644 --- a/src/main/java/nl/andrewlalis/git_api/GithubManager.java +++ b/src/main/java/nl/andrewlalis/git_api/GithubManager.java @@ -168,6 +168,7 @@ public class GithubManager { if (repo.getName().contains(substring)) { try { repo.delete(); + logger.info("Deleted repository: " + repo.getName()); } catch (IOException e) { InitializerApp.organization.addError(new Error(Severity.HIGH, "Could not delete repository: " + repo.getName())); e.printStackTrace(); diff --git a/src/main/java/nl/andrewlalis/ui/control/command/executables/DeleteRepos.java b/src/main/java/nl/andrewlalis/ui/control/command/executables/DeleteRepos.java new file mode 100644 index 0000000..d38b615 --- /dev/null +++ b/src/main/java/nl/andrewlalis/ui/control/command/executables/DeleteRepos.java @@ -0,0 +1,17 @@ +package nl.andrewlalis.ui.control.command.executables; + +import nl.andrewlalis.git_api.GithubManager; + +/** + * Deletes all repositories with a given substring. + */ +public class DeleteRepos extends GithubExecutable { + @Override + protected boolean executeWithManager(GithubManager manager, String[] args) { + if (args.length < 1) { + return false; + } + manager.deleteAllRepositories(args[0]); + return true; + } +} diff --git a/src/main/java/nl/andrewlalis/ui/control/command/executables/ListErrors.java b/src/main/java/nl/andrewlalis/ui/control/command/executables/ListErrors.java index 61dc7bb..92804c0 100644 --- a/src/main/java/nl/andrewlalis/ui/control/command/executables/ListErrors.java +++ b/src/main/java/nl/andrewlalis/ui/control/command/executables/ListErrors.java @@ -22,6 +22,9 @@ public class ListErrors implements Executable { @Override public boolean execute(String[] args) { StringBuilder sb = new StringBuilder("Runtime Errors:\n"); + if (InitializerApp.organization.getErrors().isEmpty()) { + sb.append("None"); + } for (Error error : InitializerApp.organization.getErrors()) { sb.append(error); } diff --git a/src/main/java/nl/andrewlalis/ui/control/listeners/DeleteReposListener.java b/src/main/java/nl/andrewlalis/ui/control/listeners/DeleteReposListener.java new file mode 100644 index 0000000..d615bde --- /dev/null +++ b/src/main/java/nl/andrewlalis/ui/control/listeners/DeleteReposListener.java @@ -0,0 +1,32 @@ +package nl.andrewlalis.ui.control.listeners; + +import nl.andrewlalis.ui.control.command.CommandExecutor; +import nl.andrewlalis.ui.view.InitializerApp; + +import javax.swing.*; +import java.awt.event.ActionEvent; + +/** + * A listener for when the user wants to delete repositories. + */ +public class DeleteReposListener extends ExecutableListener { + + public DeleteReposListener(CommandExecutor executor, InitializerApp app) { + super(executor, app); + } + + @Override + public void actionPerformed(ActionEvent actionEvent) { + String input = JOptionPane.showInputDialog(this.app, "Please enter a string which, if a repository contains it, results in deleting the repository.", "Enter Substing", JOptionPane.QUESTION_MESSAGE); + if (input != null) { + int decision = JOptionPane.showConfirmDialog(null, "Are you sure you want to delete all repositories that contain \"" + "\" in their name?", "Delete Repositories", JOptionPane.YES_NO_OPTION); + if (decision == JOptionPane.YES_OPTION) { + this.executor.executeCommand("delete_repos", new String[]{ + app.getOrganizationName(), + app.getAccessToken(), + input + }); + } + } + } +} diff --git a/src/main/java/nl/andrewlalis/ui/view/InitializerApp.java b/src/main/java/nl/andrewlalis/ui/view/InitializerApp.java index de06018..c279862 100644 --- a/src/main/java/nl/andrewlalis/ui/view/InitializerApp.java +++ b/src/main/java/nl/andrewlalis/ui/view/InitializerApp.java @@ -7,6 +7,7 @@ import nl.andrewlalis.ui.control.listeners.*; import javax.swing.*; import java.awt.*; +import java.awt.event.ActionListener; import java.util.logging.Level; import java.util.logging.Logger; @@ -118,23 +119,22 @@ public class InitializerApp extends JFrame { JPanel commonActionsPanel = new JPanel(); commonActionsPanel.setLayout(new BoxLayout(commonActionsPanel, BoxLayout.PAGE_AXIS)); - JButton archiveAllButton = new JButton("Archive All"); - archiveAllButton.addActionListener(new ArchiveAllListener(this.executor, this)); - commonActionsPanel.add(archiveAllButton); + 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("Generate Assignments Repo", new GenerateAssignmentsRepoListener(this.executor, this))); - JButton generateStudentTeamsButton = new JButton("Read teams from file"); - generateStudentTeamsButton.addActionListener(new ReadStudentsFileListener(this.executor, this)); - commonActionsPanel.add(generateStudentTeamsButton); + // TODO: Enable this once the define teams dialog is complete. +// JButton defineTaTeamsButton = new JButton("Define TA Teams"); +// defineTaTeamsButton.addActionListener(new DefineTaTeamsListener(this.executor, this)); +// commonActionsPanel.add(f); - JButton generateAssignmentsRepoButton = new JButton("Generate Assignments Repo"); - generateAssignmentsRepoButton.addActionListener(new GenerateAssignmentsRepoListener(this.executor, this)); - commonActionsPanel.add(generateAssignmentsRepoButton); + commonActionsPanel.add(this.generateButtonPanel("Delete Repos", new DeleteReposListener(this.executor, this))); - JButton defineTaTeamsButton = new JButton("Define TA Teams"); - defineTaTeamsButton.addActionListener(new DefineTaTeamsListener(this.executor, this)); - commonActionsPanel.add(defineTaTeamsButton); + // Extra panel to push buttons to the top. + JPanel buttonAlignmentPanel = new JPanel(new BorderLayout()); + buttonAlignmentPanel.add(commonActionsPanel, BorderLayout.NORTH); - githubManagerPanel.add(commonActionsPanel, BorderLayout.CENTER); + githubManagerPanel.add(buttonAlignmentPanel, BorderLayout.CENTER); return githubManagerPanel; } @@ -190,6 +190,20 @@ public class InitializerApp extends JFrame { return newPanel; } + /** + * Generates a button with an attached action listener. + * @param buttonText The text to display on the button. + * @param listener The listener to attach to the button. + * @return A BorderLayout JPanel which contains the button in the CENTER location. + */ + private JPanel generateButtonPanel(String buttonText, ActionListener listener) { + JPanel panel = new JPanel(new BorderLayout()); + JButton button = new JButton(buttonText); + button.addActionListener(listener); + panel.add(button, BorderLayout.CENTER); + return panel; + } + /** * Gets the organization name entered in the relevant field. * @return The organization name the user has entered. From 7c6f2527c0b7a2958c5a1110a8eda1ade64941e7 Mon Sep 17 00:00:00 2001 From: andrewlalis Date: Sat, 1 Sep 2018 10:47:35 +0200 Subject: [PATCH 09/14] Added dialog to delegate a certain number of teams to each TA team. --- src/main/java/nl/andrewlalis/Main.java | 1 + .../nl/andrewlalis/model/Organization.java | 50 ++++--- .../java/nl/andrewlalis/model/TATeam.java | 19 +++ .../command/executables/DefineTaTeams.java | 2 +- .../executables/DelegateStudentTeams.java | 29 ++++ .../executables/GenerateStudentRepos.java | 16 --- .../command/executables/ReadStudentsFile.java | 3 - .../DelegateStudentTeamsListener.java | 24 ++++ .../andrewlalis/ui/view/InitializerApp.java | 1 + .../{ => dialogs}/DefineTaTeamsDialog.java | 3 +- .../dialogs/DelegateStudentTeamsDialog.java | 130 ++++++++++++++++++ .../ui/view/dialogs/NumberIndicatorField.java | 42 ++++++ .../ui/view/dialogs/TaTeamSpinner.java | 23 ++++ 13 files changed, 306 insertions(+), 37 deletions(-) create mode 100644 src/main/java/nl/andrewlalis/ui/control/command/executables/DelegateStudentTeams.java delete mode 100644 src/main/java/nl/andrewlalis/ui/control/command/executables/GenerateStudentRepos.java create mode 100644 src/main/java/nl/andrewlalis/ui/control/listeners/DelegateStudentTeamsListener.java rename src/main/java/nl/andrewlalis/ui/view/{ => dialogs}/DefineTaTeamsDialog.java (97%) create mode 100644 src/main/java/nl/andrewlalis/ui/view/dialogs/DelegateStudentTeamsDialog.java create mode 100644 src/main/java/nl/andrewlalis/ui/view/dialogs/NumberIndicatorField.java create mode 100644 src/main/java/nl/andrewlalis/ui/view/dialogs/TaTeamSpinner.java 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; + } +} From f39f5da40783301a9651cb9cf3f6fcff4e4343f0 Mon Sep 17 00:00:00 2001 From: andrewlalis Date: Sun, 2 Sep 2018 10:07:39 +0200 Subject: [PATCH 10/14] Added ability to split up student teams amongst teaching assistant teams. --- .../nl/andrewlalis/git_api/GithubManager.java | 10 ++-- .../java/nl/andrewlalis/model/Person.java | 9 ++-- .../java/nl/andrewlalis/model/TATeam.java | 1 + .../executables/DelegateStudentTeams.java | 46 +++++++++++++++- .../executables/SetupStudentRepos.java | 24 +++++++++ .../DelegateStudentTeamsDialog.java | 54 +++++++++++++++++-- .../NumberIndicatorField.java | 2 +- .../TaTeamSpinner.java | 2 +- 8 files changed, 133 insertions(+), 15 deletions(-) create mode 100644 src/main/java/nl/andrewlalis/ui/control/command/executables/SetupStudentRepos.java rename src/main/java/nl/andrewlalis/ui/view/dialogs/{ => delegateStudentTeams}/DelegateStudentTeamsDialog.java (70%) rename src/main/java/nl/andrewlalis/ui/view/dialogs/{ => delegateStudentTeams}/NumberIndicatorField.java (93%) rename src/main/java/nl/andrewlalis/ui/view/dialogs/{ => delegateStudentTeams}/TaTeamSpinner.java (89%) diff --git a/src/main/java/nl/andrewlalis/git_api/GithubManager.java b/src/main/java/nl/andrewlalis/git_api/GithubManager.java index aa4c23f..8a4c67a 100644 --- a/src/main/java/nl/andrewlalis/git_api/GithubManager.java +++ b/src/main/java/nl/andrewlalis/git_api/GithubManager.java @@ -20,6 +20,7 @@ import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.Random; import java.util.logging.Logger; /** @@ -65,11 +66,12 @@ public class GithubManager { public List getTeams() { List teams = new ArrayList<>(); try { + Random rand = new Random(); for (Map.Entry entry : this.organization.getTeams().entrySet()) { - TATeam team = new TATeam(entry.getKey(), -1); + TATeam team = new TATeam(entry.getKey(), entry.getValue().getId()); team.setGithubTeam(entry.getValue()); - for (GHUser user : entry.getValue().getMembers()) { - team.addMember(new TeachingAssistant(-1, user.getName(), user.getEmail(), user.getLogin())); + for (GHUser user : entry.getValue().listMembers().asList()) { + team.addMember(new TeachingAssistant(rand.nextInt(), user.getName(), user.getEmail(), user.getLogin())); } teams.add(team); } @@ -135,7 +137,7 @@ public class GithubManager { * @param taTeam The team of teaching assistants that is responsible for these students. * @param prefix The prefix to append to the front of the repo name. */ - public void setupStudentTeam(StudentTeam team, TATeam taTeam, String prefix) { + public void setupStudentRepo(StudentTeam team, TATeam taTeam, String prefix) { // First check that the assignments repo exists, otherwise no invitations can be sent. if (this.assignmentsRepo == null) { logger.warning("Assignments repository must be created before student repositories."); diff --git a/src/main/java/nl/andrewlalis/model/Person.java b/src/main/java/nl/andrewlalis/model/Person.java index 0c06a40..50b420a 100644 --- a/src/main/java/nl/andrewlalis/model/Person.java +++ b/src/main/java/nl/andrewlalis/model/Person.java @@ -83,10 +83,13 @@ public abstract class Person { return false; } Person p = (Person)o; + boolean emailSame = (p.getEmailAddress() != null && p.getEmailAddress().equals(this.getEmailAddress())); + boolean githubSame = (p.getGithubUsername() != null && p.getGithubUsername().equals(this.getGithubUsername())); + boolean nameSame = (p.getName() != null && p.getName().equalsIgnoreCase(this.getName())); return p.getNumber() == this.getNumber() - || p.getEmailAddress().equals(this.getEmailAddress()) - || p.getGithubUsername().equals(this.getGithubUsername()) - || p.getName().equalsIgnoreCase(this.getName()); + || emailSame + || githubSame + || nameSame; } /** diff --git a/src/main/java/nl/andrewlalis/model/TATeam.java b/src/main/java/nl/andrewlalis/model/TATeam.java index 02ad327..99dc211 100644 --- a/src/main/java/nl/andrewlalis/model/TATeam.java +++ b/src/main/java/nl/andrewlalis/model/TATeam.java @@ -35,6 +35,7 @@ public class TATeam extends Team { public TATeam(String name, int id) { super(id); this.name = name; + this.studentTeams = new ArrayList<>(); } /** 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 index 150fd84..414f92f 100644 --- a/src/main/java/nl/andrewlalis/ui/control/command/executables/DelegateStudentTeams.java +++ b/src/main/java/nl/andrewlalis/ui/control/command/executables/DelegateStudentTeams.java @@ -1,8 +1,17 @@ package nl.andrewlalis.ui.control.command.executables; import nl.andrewlalis.git_api.GithubManager; +import nl.andrewlalis.model.StudentTeam; +import nl.andrewlalis.model.TATeam; import nl.andrewlalis.ui.view.InitializerApp; -import nl.andrewlalis.ui.view.dialogs.DelegateStudentTeamsDialog; +import nl.andrewlalis.ui.view.dialogs.delegateStudentTeams.DelegateStudentTeamsDialog; + +import javax.swing.*; +import java.util.List; +import java.util.Map; +import java.util.Stack; +import java.util.concurrent.ThreadLocalRandom; +import java.util.logging.Logger; /** * An executable which opens up a dialog to allow a user to delegate how many student teams each TATeam gets to manage, @@ -15,14 +24,49 @@ public class DelegateStudentTeams extends GithubExecutable { */ private InitializerApp app; + /** + * The logger for outputting debug info. + */ + private static final Logger logger = Logger.getLogger(DelegateStudentTeams.class.getName()); + static { + logger.setParent(Logger.getGlobal()); + } + public DelegateStudentTeams(InitializerApp app) { this.app = app; } @Override protected boolean executeWithManager(GithubManager manager, String[] args) { + if (InitializerApp.organization.getStudentTeams().isEmpty()) { + JOptionPane.showMessageDialog(this.app, "There are no student teams! Please read some from a CSV file first.", "No Student Teams", JOptionPane.ERROR_MESSAGE); + return false; + } DelegateStudentTeamsDialog dialog = new DelegateStudentTeamsDialog(this.app, manager); dialog.begin(); + + if (dialog.isSuccessful()) { + Map results = dialog.getResult(); + List teams = InitializerApp.organization.getStudentTeams(); + int initialTeamsSize = teams.size(); + Stack teamsStack = new Stack<>(); + // Randomize the ordering of the student teams here. + for (int i = 0; i < initialTeamsSize; i++) { + teamsStack.push(teams.remove(ThreadLocalRandom.current().nextInt(0, teams.size()))); + } + + for (Map.Entry entry : results.entrySet()) { + TATeam team = entry.getKey(); + logger.fine("Team: " + team.getName() + " has " + entry.getValue() + " student teams."); + for (int i = 0; i < entry.getValue(); i++) { + team.addStudentTeam(teamsStack.pop()); + } + InitializerApp.organization.getTaTeams().add(team); + } + } else { + return false; + } + return true; } diff --git a/src/main/java/nl/andrewlalis/ui/control/command/executables/SetupStudentRepos.java b/src/main/java/nl/andrewlalis/ui/control/command/executables/SetupStudentRepos.java new file mode 100644 index 0000000..140c6e2 --- /dev/null +++ b/src/main/java/nl/andrewlalis/ui/control/command/executables/SetupStudentRepos.java @@ -0,0 +1,24 @@ +package nl.andrewlalis.ui.control.command.executables; + +import nl.andrewlalis.git_api.GithubManager; +import nl.andrewlalis.model.StudentTeam; +import nl.andrewlalis.model.TATeam; +import nl.andrewlalis.ui.view.InitializerApp; + +import java.util.List; + +public class SetupStudentRepos extends GithubExecutable { + @Override + protected boolean executeWithManager(GithubManager manager, String[] args) { + if (args.length < 1) { + return false; + } + List taTeams = InitializerApp.organization.getTaTeams(); + for (TATeam team : taTeams) { + for (StudentTeam studentTeam : team.getStudentTeams()) { + manager.setupStudentRepo(studentTeam, team, args[0]); + } + } + return true; + } +} diff --git a/src/main/java/nl/andrewlalis/ui/view/dialogs/DelegateStudentTeamsDialog.java b/src/main/java/nl/andrewlalis/ui/view/dialogs/delegateStudentTeams/DelegateStudentTeamsDialog.java similarity index 70% rename from src/main/java/nl/andrewlalis/ui/view/dialogs/DelegateStudentTeamsDialog.java rename to src/main/java/nl/andrewlalis/ui/view/dialogs/delegateStudentTeams/DelegateStudentTeamsDialog.java index 9589926..a4499de 100644 --- a/src/main/java/nl/andrewlalis/ui/view/dialogs/DelegateStudentTeamsDialog.java +++ b/src/main/java/nl/andrewlalis/ui/view/dialogs/delegateStudentTeams/DelegateStudentTeamsDialog.java @@ -1,14 +1,13 @@ -package nl.andrewlalis.ui.view.dialogs; +package nl.andrewlalis.ui.view.dialogs.delegateStudentTeams; 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.awt.event.ActionEvent; +import java.awt.event.ActionListener; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -40,6 +39,11 @@ public class DelegateStudentTeamsDialog extends JDialog { */ private final int totalStudentTeamsCount; + /** + * A variable used to check if the result of this dialog was successful. + */ + private boolean successful; + public DelegateStudentTeamsDialog(InitializerApp parentApp, GithubManager manager) { super(parentApp, "Delegate Student Teams", true); this.manager = manager; @@ -64,6 +68,7 @@ public class DelegateStudentTeamsDialog extends JDialog { JPanel mainPanel = new JPanel(new BorderLayout()); mainPanel.add(this.generateTopPanel(), BorderLayout.NORTH); mainPanel.add(this.generateSpinnersPanel(), BorderLayout.CENTER); + mainPanel.add(this.generateSubmitPanel(), BorderLayout.SOUTH); this.setContentPane(mainPanel); this.pack(); @@ -92,6 +97,12 @@ public class DelegateStudentTeamsDialog extends JDialog { return spinnersPanel; } + /** + * Generates a panel containing a label and JSpinner for a particular TATeam. Also adds a change listener which + * makes sure that the total number of student teams given to TATeams never exceeds the total. + * @param team The team to link to this panel. + * @return The JPanel created. + */ private JPanel generateTeamSpinnerPanel(TATeam team) { JPanel panel = new JPanel(new BorderLayout()); JLabel teamLabel = new JLabel(team.getName()); @@ -105,7 +116,7 @@ public class DelegateStudentTeamsDialog extends JDialog { studentTeamsMatched += (int)teamSpinner.getValue(); } if (this.totalStudentTeamsCount - studentTeamsMatched < 0) { - s.setValue((int)s.getValue() + 1); // TODO: FIX! Causes stack overflow. + s.setValue(s.getPreviousValue()); } else { this.unmatchedStudentsCounter.setValue(this.totalStudentTeamsCount - studentTeamsMatched); } @@ -115,6 +126,39 @@ public class DelegateStudentTeamsDialog extends JDialog { return panel; } + /** + * Creates the panel at the bottom of the dialog which shows the 'okay' and 'cancel' buttons. + * @return The JPanel created. + */ + private JPanel generateSubmitPanel() { + JPanel panel = new JPanel(); + JButton okayButton = new JButton("Okay"); + okayButton.addActionListener(actionEvent -> { + if (unmatchedStudentsCounter.getValue() > 0) { + JOptionPane.showMessageDialog(getParent(), "There are still teams remaining!", "Not all teams assigned.", JOptionPane.INFORMATION_MESSAGE); + } else { + successful = true; + dispose(); + } + }); + JButton cancelButton = new JButton("Cancel"); + cancelButton.addActionListener(actionEvent -> { + successful = false; + dispose(); + }); + panel.add(okayButton); + panel.add(cancelButton); + return panel; + } + + /** + * Determines if the user successfully delegated all student teams to TATeams. + * @return True if every student team is (theoretically) matched to a TATeam. + */ + public boolean isSuccessful() { + return this.successful; + } + /** * Gets a map containing an integer value for every TA team, which represents the number of student teams they will * be responsible for. diff --git a/src/main/java/nl/andrewlalis/ui/view/dialogs/NumberIndicatorField.java b/src/main/java/nl/andrewlalis/ui/view/dialogs/delegateStudentTeams/NumberIndicatorField.java similarity index 93% rename from src/main/java/nl/andrewlalis/ui/view/dialogs/NumberIndicatorField.java rename to src/main/java/nl/andrewlalis/ui/view/dialogs/delegateStudentTeams/NumberIndicatorField.java index 5e7a5d8..0fe7e3f 100644 --- a/src/main/java/nl/andrewlalis/ui/view/dialogs/NumberIndicatorField.java +++ b/src/main/java/nl/andrewlalis/ui/view/dialogs/delegateStudentTeams/NumberIndicatorField.java @@ -1,4 +1,4 @@ -package nl.andrewlalis.ui.view.dialogs; +package nl.andrewlalis.ui.view.dialogs.delegateStudentTeams; import javax.swing.*; diff --git a/src/main/java/nl/andrewlalis/ui/view/dialogs/TaTeamSpinner.java b/src/main/java/nl/andrewlalis/ui/view/dialogs/delegateStudentTeams/TaTeamSpinner.java similarity index 89% rename from src/main/java/nl/andrewlalis/ui/view/dialogs/TaTeamSpinner.java rename to src/main/java/nl/andrewlalis/ui/view/dialogs/delegateStudentTeams/TaTeamSpinner.java index 23e5c6e..3fcd13e 100644 --- a/src/main/java/nl/andrewlalis/ui/view/dialogs/TaTeamSpinner.java +++ b/src/main/java/nl/andrewlalis/ui/view/dialogs/delegateStudentTeams/TaTeamSpinner.java @@ -1,4 +1,4 @@ -package nl.andrewlalis.ui.view.dialogs; +package nl.andrewlalis.ui.view.dialogs.delegateStudentTeams; import nl.andrewlalis.model.TATeam; From b54b1fe32d7a9ff3cc98a2fa2e32dcc609ecf947 Mon Sep 17 00:00:00 2001 From: andrewlalis Date: Sat, 8 Sep 2018 16:50:18 +0200 Subject: [PATCH 11/14] Use absolute path in CSV reader now. --- src/main/java/nl/andrewlalis/Main.java | 1 + src/main/java/nl/andrewlalis/model/Student.java | 17 ++++++++++++++++- .../listeners/ReadStudentsFileListener.java | 2 +- .../java/nl/andrewlalis/util/FileUtils.java | 1 + .../java/nl/andrewlalis/util/TeamGenerator.java | 4 ++++ 5 files changed, 23 insertions(+), 2 deletions(-) diff --git a/src/main/java/nl/andrewlalis/Main.java b/src/main/java/nl/andrewlalis/Main.java index a9674db..f2c82f4 100644 --- a/src/main/java/nl/andrewlalis/Main.java +++ b/src/main/java/nl/andrewlalis/Main.java @@ -44,6 +44,7 @@ public class Main { executor.registerCommand("list_errors", new ListErrors()); executor.registerCommand("delete_repos", new DeleteRepos()); executor.registerCommand("delegate_student_teams", new DelegateStudentTeams(app)); + executor.registerCommand("setup_student_repos", new SetupStudentRepos()); 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/Student.java b/src/main/java/nl/andrewlalis/model/Student.java index ec8712b..67e2f3e 100644 --- a/src/main/java/nl/andrewlalis/model/Student.java +++ b/src/main/java/nl/andrewlalis/model/Student.java @@ -1,13 +1,23 @@ package nl.andrewlalis.model; +import nl.andrewlalis.model.error.Error; +import nl.andrewlalis.model.error.Severity; +import nl.andrewlalis.ui.view.InitializerApp; + import java.util.List; import java.util.Map; +import java.util.logging.Logger; /** * Represents one student's github information. */ public class Student extends Person { + private static final Logger logger = Logger.getLogger(Student.class.getName()); + static { + logger.setParent(Logger.getGlobal()); + } + /** * A list of partners that the student has said that they would like to be partners with. */ @@ -39,7 +49,12 @@ public class Student extends Person { public StudentTeam getPreferredTeam(Map studentMap) { StudentTeam t = new StudentTeam(); for (int partnerNumber : this.getPreferredPartners()) { - t.addMember(studentMap.get(partnerNumber)); + if (!studentMap.containsKey(partnerNumber)) { + InitializerApp.organization.addError(new Error(Severity.MEDIUM, "Student " + this.getNumber() + " has non-existent preferred partner with id: " + partnerNumber)); + logger.warning("Student has non-existent partner id: " + partnerNumber + '\n' + this); + } else { + t.addMember(studentMap.get(partnerNumber)); + } } t.addMember(this); return t; diff --git a/src/main/java/nl/andrewlalis/ui/control/listeners/ReadStudentsFileListener.java b/src/main/java/nl/andrewlalis/ui/control/listeners/ReadStudentsFileListener.java index 046e78d..e65e7ba 100644 --- a/src/main/java/nl/andrewlalis/ui/control/listeners/ReadStudentsFileListener.java +++ b/src/main/java/nl/andrewlalis/ui/control/listeners/ReadStudentsFileListener.java @@ -44,7 +44,7 @@ public class ReadStudentsFileListener extends ExecutableListener { String teamSizeString = JOptionPane.showInputDialog(this.app, "Enter the student team size.", "Team Size", JOptionPane.QUESTION_MESSAGE); if (teamSizeString != null) { this.executor.executeCommand("read_students", new String[]{ - chooser.getSelectedFile().getName(), + chooser.getSelectedFile().getAbsolutePath(), teamSizeString }); } diff --git a/src/main/java/nl/andrewlalis/util/FileUtils.java b/src/main/java/nl/andrewlalis/util/FileUtils.java index dc76bb9..2d56eab 100644 --- a/src/main/java/nl/andrewlalis/util/FileUtils.java +++ b/src/main/java/nl/andrewlalis/util/FileUtils.java @@ -54,6 +54,7 @@ public class FileUtils { return studentTeams; } catch (IOException | ArrayIndexOutOfBoundsException e) { logger.severe("Unable to generate studentTeams from CSV file, exiting. " + e.getMessage()); + e.printStackTrace(); return null; } } diff --git a/src/main/java/nl/andrewlalis/util/TeamGenerator.java b/src/main/java/nl/andrewlalis/util/TeamGenerator.java index 96800ed..6bc00b5 100644 --- a/src/main/java/nl/andrewlalis/util/TeamGenerator.java +++ b/src/main/java/nl/andrewlalis/util/TeamGenerator.java @@ -155,6 +155,10 @@ public class TeamGenerator { } } Student s = new Student(Integer.parseInt(record.get(3)), record.get(2), record.get(1), record.get(4), preferredIds); + if (studentMap.containsValue(s)) { + InitializerApp.organization.addError(new Error(Severity.HIGH, "Duplicate entries for student:\n" + s + "\nSince records are in chronological order, this more recent duplicate will override the previous value.")); + logger.warning("Duplicate entry found for student: " + s + "\nOverwriting previous value."); + } studentMap.put(s.getNumber(), s); } logger.fine("Read " + studentMap.size() + " students from records."); From 8b69d4881fe313331a153d77420556da2062037b Mon Sep 17 00:00:00 2001 From: andrewlalis Date: Sat, 8 Sep 2018 16:52:37 +0200 Subject: [PATCH 12/14] Better formatting for list_errors command. --- .../andrewlalis/ui/control/command/executables/ListErrors.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/nl/andrewlalis/ui/control/command/executables/ListErrors.java b/src/main/java/nl/andrewlalis/ui/control/command/executables/ListErrors.java index 92804c0..9d3e1c7 100644 --- a/src/main/java/nl/andrewlalis/ui/control/command/executables/ListErrors.java +++ b/src/main/java/nl/andrewlalis/ui/control/command/executables/ListErrors.java @@ -26,7 +26,7 @@ public class ListErrors implements Executable { sb.append("None"); } for (Error error : InitializerApp.organization.getErrors()) { - sb.append(error); + sb.append(error).append('\n'); } logger.info(sb.toString()); return true; From a55fab8d47ab185d5764f4a58907f7550e619b90 Mon Sep 17 00:00:00 2001 From: andrewlalis Date: Sat, 8 Sep 2018 17:20:33 +0200 Subject: [PATCH 13/14] Fixed concurrent modification exception. --- src/main/java/nl/andrewlalis/Main.java | 9 +++------ .../nl/andrewlalis/git_api/GithubManager.java | 19 ++++++++----------- .../java/nl/andrewlalis/model/Student.java | 11 +---------- .../executables/DelegateStudentTeams.java | 6 +++--- .../command/executables/ListErrors.java | 13 +++++++++++-- .../command/executables/ReadStudentsFile.java | 11 ++++++++++- .../executables/SetupStudentRepos.java | 15 ++++++++++++++- .../andrewlalis/ui/view/InitializerApp.java | 7 ++++++- .../DelegateStudentTeamsDialog.java | 2 +- .../nl/andrewlalis/util/TeamGenerator.java | 10 +++++++--- 10 files changed, 64 insertions(+), 39 deletions(-) diff --git a/src/main/java/nl/andrewlalis/Main.java b/src/main/java/nl/andrewlalis/Main.java index f2c82f4..2249e3a 100644 --- a/src/main/java/nl/andrewlalis/Main.java +++ b/src/main/java/nl/andrewlalis/Main.java @@ -28,23 +28,20 @@ public class Main { // Command executor which will be used by all actions the user can do. CommandExecutor executor = new CommandExecutor(); - // Main application model is stored as a static variable that is accessible everywhere. - InitializerApp.organization = new Organization(); - // Initialize User Interface. InitializerApp app = new InitializerApp(executor); app.begin(); app.setAccessToken(userOptions.get("token")); // Initialize executable commands. - executor.registerCommand("read_students", new ReadStudentsFile()); + executor.registerCommand("read_students", new ReadStudentsFile(app)); executor.registerCommand("archive_all", new ArchiveRepos()); executor.registerCommand("generate_assignments", new GenerateAssignmentsRepo()); executor.registerCommand("define_ta_teams", new DefineTaTeams(app)); - executor.registerCommand("list_errors", new ListErrors()); + executor.registerCommand("list_errors", new ListErrors(app)); executor.registerCommand("delete_repos", new DeleteRepos()); executor.registerCommand("delegate_student_teams", new DelegateStudentTeams(app)); - executor.registerCommand("setup_student_repos", new SetupStudentRepos()); + executor.registerCommand("setup_student_repos", new SetupStudentRepos(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/git_api/GithubManager.java b/src/main/java/nl/andrewlalis/git_api/GithubManager.java index 8a4c67a..315bb72 100644 --- a/src/main/java/nl/andrewlalis/git_api/GithubManager.java +++ b/src/main/java/nl/andrewlalis/git_api/GithubManager.java @@ -111,7 +111,6 @@ public class GithubManager { // Check if the repository already exists. GHRepository existingRepo = this.organization.getRepository(assignmentsRepoName); if (existingRepo != null) { - InitializerApp.organization.addError(new Error(Severity.MINOR, "Assignments repository already existed, deleting it.")); existingRepo.delete(); logger.fine("Deleted pre-existing assignments repository."); } @@ -172,7 +171,7 @@ public class GithubManager { repo.delete(); logger.info("Deleted repository: " + repo.getName()); } catch (IOException e) { - InitializerApp.organization.addError(new Error(Severity.HIGH, "Could not delete repository: " + repo.getName())); + logger.severe("Could not delete repository: " + repo.getName()); e.printStackTrace(); } } @@ -212,7 +211,8 @@ public class GithubManager { } logger.info("Archived repository: " + repo.getFullName()); } catch (IOException e) { - InitializerApp.organization.addError(new Error(Severity.HIGH, "Could not archive repository: " + repo.getName())); + logger.severe("Could not archive repository: " + repo.getName()); + e.printStackTrace(); } } @@ -233,8 +233,8 @@ public class GithubManager { repo.addCollaborators(users); this.assignmentsRepo.addCollaborators(users); } catch (IOException e) { - InitializerApp.organization.addError(new Error(Severity.HIGH, "Students in team: " + team + " could not be added as collaborators to assignments repo or their repository.")); - logger.warning("Could not add students as collaborators to assignments or their repo."); + logger.severe("Could not add students as collaborators to assignments or their repo.\n" + team); + e.printStackTrace(); } } @@ -248,8 +248,8 @@ public class GithubManager { taTeam.add(studentRepo, GHOrganization.Permission.ADMIN); logger.fine("Added team " + taTeam.getName() + " as admin to repository: " + studentRepo.getName()); } catch (IOException e) { - InitializerApp.organization.addError(new Error(Severity.HIGH, "Could not add TA Team: " + taTeam.getName() + " as ADMIN to repository: " + studentRepo.getName())); logger.severe("Could not add TA Team: " + taTeam.getName() + " as admins to repository: " + studentRepo.getName()); + e.printStackTrace(); } } @@ -269,7 +269,6 @@ public class GithubManager { protectionBuilder.enable(); logger.fine("Protected master branch of repository: " + repo.getName()); } catch (IOException e) { - InitializerApp.organization.addError(new Error(Severity.HIGH, "Could not protect master branch of repository: " + repo.getName())); logger.severe("Could not protect master branch of repository: " + repo.getName()); e.printStackTrace(); } @@ -285,8 +284,7 @@ public class GithubManager { repo.createRef("refs/heads/development", sha1); logger.fine("Created development branch of repository: " + repo.getName()); } catch (IOException e) { - InitializerApp.organization.addError(new Error(Severity.HIGH, "Could not create development branch of repository: " + repo.getName())); - logger.severe("Could not create development branch for repository: " + repo.getName() + '\n' + e.getMessage()); + logger.severe("Could not create development branch for repository: " + repo.getName()); e.printStackTrace(); } } @@ -314,8 +312,7 @@ public class GithubManager { logger.fine("Created repository: " + repo.getName()); return repo; } catch (IOException e) { - logger.severe("Could not create repository: " + name + '\n' + e.getMessage()); - InitializerApp.organization.addError(new Error(Severity.CRITICAL, "Could not create repository: " + name)); + logger.severe("Could not create repository: " + name); e.printStackTrace(); return null; } diff --git a/src/main/java/nl/andrewlalis/model/Student.java b/src/main/java/nl/andrewlalis/model/Student.java index 67e2f3e..e347b58 100644 --- a/src/main/java/nl/andrewlalis/model/Student.java +++ b/src/main/java/nl/andrewlalis/model/Student.java @@ -1,9 +1,5 @@ package nl.andrewlalis.model; -import nl.andrewlalis.model.error.Error; -import nl.andrewlalis.model.error.Severity; -import nl.andrewlalis.ui.view.InitializerApp; - import java.util.List; import java.util.Map; import java.util.logging.Logger; @@ -49,12 +45,7 @@ public class Student extends Person { public StudentTeam getPreferredTeam(Map studentMap) { StudentTeam t = new StudentTeam(); for (int partnerNumber : this.getPreferredPartners()) { - if (!studentMap.containsKey(partnerNumber)) { - InitializerApp.organization.addError(new Error(Severity.MEDIUM, "Student " + this.getNumber() + " has non-existent preferred partner with id: " + partnerNumber)); - logger.warning("Student has non-existent partner id: " + partnerNumber + '\n' + this); - } else { - t.addMember(studentMap.get(partnerNumber)); - } + t.addMember(studentMap.get(partnerNumber)); } t.addMember(this); return t; 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 index 414f92f..69ed042 100644 --- a/src/main/java/nl/andrewlalis/ui/control/command/executables/DelegateStudentTeams.java +++ b/src/main/java/nl/andrewlalis/ui/control/command/executables/DelegateStudentTeams.java @@ -38,7 +38,7 @@ public class DelegateStudentTeams extends GithubExecutable { @Override protected boolean executeWithManager(GithubManager manager, String[] args) { - if (InitializerApp.organization.getStudentTeams().isEmpty()) { + if (this.app.getOrganization().getStudentTeams().isEmpty()) { JOptionPane.showMessageDialog(this.app, "There are no student teams! Please read some from a CSV file first.", "No Student Teams", JOptionPane.ERROR_MESSAGE); return false; } @@ -47,7 +47,7 @@ public class DelegateStudentTeams extends GithubExecutable { if (dialog.isSuccessful()) { Map results = dialog.getResult(); - List teams = InitializerApp.organization.getStudentTeams(); + List teams = this.app.getOrganization().getStudentTeams(); int initialTeamsSize = teams.size(); Stack teamsStack = new Stack<>(); // Randomize the ordering of the student teams here. @@ -61,7 +61,7 @@ public class DelegateStudentTeams extends GithubExecutable { for (int i = 0; i < entry.getValue(); i++) { team.addStudentTeam(teamsStack.pop()); } - InitializerApp.organization.getTaTeams().add(team); + this.app.getOrganization().getTaTeams().add(team); } } else { return false; diff --git a/src/main/java/nl/andrewlalis/ui/control/command/executables/ListErrors.java b/src/main/java/nl/andrewlalis/ui/control/command/executables/ListErrors.java index 9d3e1c7..b99c07a 100644 --- a/src/main/java/nl/andrewlalis/ui/control/command/executables/ListErrors.java +++ b/src/main/java/nl/andrewlalis/ui/control/command/executables/ListErrors.java @@ -11,6 +11,11 @@ import java.util.logging.Logger; */ public class ListErrors implements Executable { + /** + * A reference to the current application. + */ + private InitializerApp app; + /** * The logger for outputting debug info. */ @@ -19,13 +24,17 @@ public class ListErrors implements Executable { logger.setParent(Logger.getGlobal()); } + public ListErrors(InitializerApp app) { + this.app = app; + } + @Override public boolean execute(String[] args) { StringBuilder sb = new StringBuilder("Runtime Errors:\n"); - if (InitializerApp.organization.getErrors().isEmpty()) { + if (this.app.getOrganization().getErrors().isEmpty()) { sb.append("None"); } - for (Error error : InitializerApp.organization.getErrors()) { + for (Error error : this.app.getOrganization().getErrors()) { sb.append(error).append('\n'); } logger.info(sb.toString()); 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 8d14826..cb0b4a0 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 @@ -17,6 +17,15 @@ import java.util.List; */ public class ReadStudentsFile implements Executable { + /** + * A reference to the current application, which contains the model for storing information. + */ + private InitializerApp app; + + public ReadStudentsFile(InitializerApp app) { + this.app = app; + } + @Override public boolean execute(String[] args) { if (args.length < 2) { @@ -28,7 +37,7 @@ public class ReadStudentsFile implements Executable { if (teams == null) { return false; } - InitializerApp.organization.setStudentTeams(teams); + this.app.getOrganization().setStudentTeams(teams); return true; } } diff --git a/src/main/java/nl/andrewlalis/ui/control/command/executables/SetupStudentRepos.java b/src/main/java/nl/andrewlalis/ui/control/command/executables/SetupStudentRepos.java index 140c6e2..411f255 100644 --- a/src/main/java/nl/andrewlalis/ui/control/command/executables/SetupStudentRepos.java +++ b/src/main/java/nl/andrewlalis/ui/control/command/executables/SetupStudentRepos.java @@ -7,13 +7,26 @@ import nl.andrewlalis.ui.view.InitializerApp; import java.util.List; +/** + * This executable, when run, sets up all student repositories. + */ public class SetupStudentRepos extends GithubExecutable { + + /** + * A reference to the current application. + */ + private InitializerApp app; + + public SetupStudentRepos(InitializerApp app) { + this.app = app; + } + @Override protected boolean executeWithManager(GithubManager manager, String[] args) { if (args.length < 1) { return false; } - List taTeams = InitializerApp.organization.getTaTeams(); + List taTeams = this.app.getOrganization().getTaTeams(); for (TATeam team : taTeams) { for (StudentTeam studentTeam : team.getStudentTeams()) { manager.setupStudentRepo(studentTeam, team, args[0]); diff --git a/src/main/java/nl/andrewlalis/ui/view/InitializerApp.java b/src/main/java/nl/andrewlalis/ui/view/InitializerApp.java index 9aeebad..ab856c9 100644 --- a/src/main/java/nl/andrewlalis/ui/view/InitializerApp.java +++ b/src/main/java/nl/andrewlalis/ui/view/InitializerApp.java @@ -42,7 +42,7 @@ public class InitializerApp extends JFrame { /** * The organization object, which contains all important state information. */ - public static Organization organization; + private Organization organization; /** * Constructs a new instance of the main application frame, with both an executor, and organization model. @@ -52,6 +52,7 @@ public class InitializerApp extends JFrame { */ public InitializerApp(CommandExecutor executor) { this.executor = executor; + this.organization = new Organization(); // UI initialization. ImageIcon icon = new ImageIcon(getClass().getResource("/image/icon.png")); @@ -221,6 +222,10 @@ public class InitializerApp extends JFrame { return this.accessTokenField.getText().trim(); } + public Organization getOrganization() { + return this.organization; + } + public void setAccessToken(String accessToken) { this.accessTokenField.setText(accessToken); } diff --git a/src/main/java/nl/andrewlalis/ui/view/dialogs/delegateStudentTeams/DelegateStudentTeamsDialog.java b/src/main/java/nl/andrewlalis/ui/view/dialogs/delegateStudentTeams/DelegateStudentTeamsDialog.java index a4499de..b2c32d8 100644 --- a/src/main/java/nl/andrewlalis/ui/view/dialogs/delegateStudentTeams/DelegateStudentTeamsDialog.java +++ b/src/main/java/nl/andrewlalis/ui/view/dialogs/delegateStudentTeams/DelegateStudentTeamsDialog.java @@ -48,7 +48,7 @@ public class DelegateStudentTeamsDialog extends JDialog { super(parentApp, "Delegate Student Teams", true); this.manager = manager; this.teamSpinners = new ArrayList<>(); - this.totalStudentTeamsCount = InitializerApp.organization.getStudentTeams().size(); + this.totalStudentTeamsCount = parentApp.getOrganization().getStudentTeams().size(); this.setDefaultCloseOperation(DISPOSE_ON_CLOSE); this.initUI(); diff --git a/src/main/java/nl/andrewlalis/util/TeamGenerator.java b/src/main/java/nl/andrewlalis/util/TeamGenerator.java index 6bc00b5..89b8b35 100644 --- a/src/main/java/nl/andrewlalis/util/TeamGenerator.java +++ b/src/main/java/nl/andrewlalis/util/TeamGenerator.java @@ -39,7 +39,6 @@ public class TeamGenerator { logger.fine("Generating teams of size " + teamSize); if (teamSize < 1) { logger.severe("Invalid team size."); - InitializerApp.organization.addError(new Error(Severity.CRITICAL, "Invalid team size while generating teams from CSV.")); throw new IllegalArgumentException("StudentTeam size must be greater than or equal to 1. Got " + teamSize); } logger.fine("Parsing CSV file."); @@ -51,7 +50,6 @@ public class TeamGenerator { studentMap = readAllStudents(records, teamSize); } catch (ArrayIndexOutOfBoundsException e) { logger.severe("StudentTeam size does not match column count in records."); - InitializerApp.organization.addError(new Error(Severity.CRITICAL, "Team size does not match column count in records.")); throw new IllegalArgumentException("StudentTeam size does not match column count in records."); } @@ -156,11 +154,17 @@ public class TeamGenerator { } Student s = new Student(Integer.parseInt(record.get(3)), record.get(2), record.get(1), record.get(4), preferredIds); if (studentMap.containsValue(s)) { - InitializerApp.organization.addError(new Error(Severity.HIGH, "Duplicate entries for student:\n" + s + "\nSince records are in chronological order, this more recent duplicate will override the previous value.")); logger.warning("Duplicate entry found for student: " + s + "\nOverwriting previous value."); } studentMap.put(s.getNumber(), s); } + + // Perform a safety check to ensure all preferred partners are valid students. + for (Map.Entry entry : studentMap.entrySet()) { + // Remove any ids that don't exist in the whole list of students. + entry.getValue().getPreferredPartners().removeIf(partnerId -> !studentMap.containsKey(partnerId)); + } + // At this point, all students are valid, and all preferred partners are valid. logger.fine("Read " + studentMap.size() + " students from records."); return studentMap; } From b59634f8ed4a3a77afd039311cc0552b00fe7a3c Mon Sep 17 00:00:00 2001 From: andrewlalis Date: Sat, 8 Sep 2018 17:38:09 +0200 Subject: [PATCH 14/14] Changed repo defaults to private in anticipation of first release. --- src/main/java/nl/andrewlalis/Main.java | 1 - .../java/nl/andrewlalis/git_api/GithubManager.java | 7 ++----- .../java/nl/andrewlalis/ui/view/InitializerApp.java | 12 ++++++++++++ 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/src/main/java/nl/andrewlalis/Main.java b/src/main/java/nl/andrewlalis/Main.java index 2249e3a..ed72ebc 100644 --- a/src/main/java/nl/andrewlalis/Main.java +++ b/src/main/java/nl/andrewlalis/Main.java @@ -1,6 +1,5 @@ package nl.andrewlalis; -import nl.andrewlalis.model.Organization; import nl.andrewlalis.ui.control.command.CommandExecutor; import nl.andrewlalis.ui.control.command.executables.*; import nl.andrewlalis.ui.view.InitializerApp; diff --git a/src/main/java/nl/andrewlalis/git_api/GithubManager.java b/src/main/java/nl/andrewlalis/git_api/GithubManager.java index 315bb72..c5b6450 100644 --- a/src/main/java/nl/andrewlalis/git_api/GithubManager.java +++ b/src/main/java/nl/andrewlalis/git_api/GithubManager.java @@ -6,9 +6,6 @@ import nl.andrewlalis.model.Student; import nl.andrewlalis.model.StudentTeam; import nl.andrewlalis.model.TATeam; import nl.andrewlalis.model.TeachingAssistant; -import nl.andrewlalis.model.error.Error; -import nl.andrewlalis.model.error.Severity; -import nl.andrewlalis.ui.view.InitializerApp; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpPatch; import org.apache.http.entity.StringEntity; @@ -115,7 +112,7 @@ public class GithubManager { logger.fine("Deleted pre-existing assignments repository."); } - this.assignmentsRepo = this.createRepository(assignmentsRepoName, team, description, false, true, false); + this.assignmentsRepo = this.createRepository(assignmentsRepoName, team, description, false, true, true); if (this.assignmentsRepo == null) { logger.severe("Could not create assignments repository."); @@ -143,7 +140,7 @@ public class GithubManager { return; } - GHRepository repo = this.createRepository(team.generateUniqueName(prefix), taTeam.getGithubTeam(), team.generateRepoDescription(), false, true, false); + GHRepository repo = this.createRepository(team.generateUniqueName(prefix), taTeam.getGithubTeam(), team.generateRepoDescription(), false, true, true); if (repo == null) { logger.severe("Repository for student team " + team.getId() + " could not be created."); diff --git a/src/main/java/nl/andrewlalis/ui/view/InitializerApp.java b/src/main/java/nl/andrewlalis/ui/view/InitializerApp.java index ab856c9..bf5b353 100644 --- a/src/main/java/nl/andrewlalis/ui/view/InitializerApp.java +++ b/src/main/java/nl/andrewlalis/ui/view/InitializerApp.java @@ -34,6 +34,8 @@ public class InitializerApp extends JFrame { private JTextField organizationField = new JTextField(); private JTextField accessTokenField = new JTextField(); + private JCheckBox privateCheckbox = new JCheckBox("Private"); + /** * The executor responsible for performing meaningful actions. */ @@ -113,6 +115,8 @@ public class InitializerApp extends JFrame { this.organizationField.setText("InitializerTesting"); infoInputPanel.add(generateTextFieldPanel("Access Token", this.accessTokenField)); this.accessTokenField.setText("haha get your own"); + infoInputPanel.add(this.privateCheckbox); + this.privateCheckbox.disable(); githubManagerPanel.add(infoInputPanel, BorderLayout.NORTH); @@ -222,6 +226,14 @@ public class InitializerApp extends JFrame { return this.accessTokenField.getText().trim(); } + /** + * Gets whether or not the 'private' checkbox is selected. + * @return True if the user wishes for repositories to be made private, or false otherwise. + */ + public boolean isPrivateChecked() { + return this.privateCheckbox.isSelected(); + } + public Organization getOrganization() { return this.organization; }