Added basic dialog view of members and teams.

This commit is contained in:
Andrew Lalis 2018-08-30 15:38:22 +02:00
parent 667a11d69b
commit 7f9a44d0ad
17 changed files with 302 additions and 66 deletions

View File

@ -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.");
}

View File

@ -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<TATeam> getTeams() {
List<TATeam> teams = new ArrayList<>();
try {
for (Map.Entry<String, GHTeam> 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<TeachingAssistant> getMembers() {
List<TeachingAssistant> 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.
*

View File

@ -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.

View File

@ -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<TeachingAssistant> 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<TeachingAssistant>();
}
/**
* 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<TeachingAssistant> 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() {

View File

@ -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.
*

View File

@ -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;
}
}

View File

@ -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 {

View File

@ -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

View File

@ -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()
});
}
}

View File

@ -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,

View File

@ -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
});

View File

@ -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;
}
}

View File

@ -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();
}

View File

@ -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<TATeam> teams;
public TATeamListModel(List<TATeam> taTeams) {
this.teams = taTeams;
}
@Override
public int getSize() {
return this.teams.size();
}
@Override
public Object getElementAt(int i) {
return this.teams.get(i);
}
}

View File

@ -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;
}
}

View File

@ -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<TeachingAssistant> teachingAssistants;
public TeachingAssistantsListModel(List<TeachingAssistant> teachingAssistants) {
this.teachingAssistants = teachingAssistants;
}
@Override
public int getSize() {
return this.teachingAssistants.size();
}
@Override
public Object getElementAt(int i) {
return this.teachingAssistants.get(i);
}
}

View File

@ -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,
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
);