By: Team JACKHA (W11-B3) Since: Jun 2016 Licence: MIT

1. Introduction

Infinity Book is an Infinity Book application for Tech recruiters who prefer to use a desktop app to keep track candidates' information, job postings, and interview.

Infinity Book provides end-to-end support, from searching for candidates,to adding job postings and saving their resumes and interviews.

More importantly, Infinity Book is optimized for those who prefer to work with a Command Line Interface (CLI) while still having the benefits of a Graphical User Interface (GUI). If you can type fast, Infinity Book can get your candidates management tasks done faster than traditional GUI apps.

2. About

This Developer Guide provides details on setting up this project for development, compatibility, and architecture information about Infinity Book. Design and implementation specifications are specified below, followed by product management specifics.

For information about application usage, refer to the User Guide Interested?
Jump to the Setting up section to get started. Enjoy!

3. Setting up

This section provides step-by-step instructions to set up Infinity Book for development on your computer.

3.1. Prerequisites

To continue development, the following two prerequisites are recommended to be met:

  1. JDK 1.8.0_60 or later

    Having any Java 8 version is not enough.
    This app will not work with earlier versions of Java 8.
  2. IntelliJ IDE

    IntelliJ by default has Gradle and JavaFx plugins installed.
    Do not disable them. If you have disabled them, go to File > Settings > Plugins to re-enable them.

3.2. Setting up the project in your computer

After meeting the prerequisites, follow these steps to set up the project on your computer:

  1. Fork this repo, and clone the fork to your computer

  2. Open IntelliJ (if you are not in the welcome screen, click File > Close Project to close the existing project dialog first)

  3. Set up the correct JDK version for Gradle

    1. Click Configure > Project Defaults > Project Structure

    2. Click New…​ and find the directory of the JDK

  4. Click Import Project

  5. Locate the build.gradle file and select it. Click OK

  6. Click Open as Project

  7. Click OK to accept the default settings

  8. Open a console and run the command gradlew processResources (Mac/Linux: ./gradlew processResources). It should finish with the BUILD SUCCESSFUL message.
    This will generate all resources required by the application and tests.

3.3. Verifying the setup

  1. Run the seedu.address.MainApp and try a few commands

  2. Run the tests to ensure they all pass.

3.4. Configurations to do before writing code

Configuring the coding style, documentation and continuous integration (CI) is recommended to maintain uniformity across contributions.

3.4.1. Configuring the coding style

This project follows oss-generic coding standards. IntelliJ’s default style is mostly compliant with ours but it uses a different import order from ours. To rectify,

  1. Go to File > Settings…​ (Windows/Linux), or IntelliJ IDEA > Preferences…​ (macOS)

  2. Select Editor > Code Style > Java

  3. Click on the Imports tab to set the order

    • For Class count to use import with '*' and Names count to use static import with '*': Set to 999 to prevent IntelliJ from contracting the import statements

    • For Import Layout: The order is import static all other imports, import java.*, import javax.*, import org.*, import com.*, import all other imports. Add a <blank line> between each import

Optionally, you can follow the UsingCheckstyle.adoc document to configure Intellij to check style-compliance as you write code.

3.4.2. Updating documentation to match your fork

After forking the repo, links in the documentation will still point to the se-edu/addressbook-level4 repo. If you plan to develop this as a separate product (i.e. instead of contributing to the se-edu/addressbook-level4) , you should replace the URL in the variable repoURL in DeveloperGuide.adoc and UserGuide.adoc with the URL of your fork.

3.4.3. Setting up CI

Set up Travis to perform Continuous Integration (CI) for your fork. See UsingTravis.adoc to learn how to set it up.

After setting up Travis, you can optionally set up coverage reporting for your team fork (see UsingCoveralls.adoc).

Coverage reporting could be useful for a team repository that hosts the final version but it is not that useful for your personal fork.

Optionally, you can set up AppVeyor as a second CI (see UsingAppVeyor.adoc).

Having both Travis and AppVeyor ensures your App works on both Unix-based platforms and Windows-based platforms (Travis is Unix-based and AppVeyor is Windows-based)

3.4.4. Getting started with coding

When you are ready to start coding,

  1. Get some sense of the overall design by reading [Design-Architecture].

  2. Take a look at Appendix A, Suggested Programming Tasks to Get Started.

4. Design

The following sections describe the architecture of the system, with details about each component.

4.1. Architecture

Architecture
Figure 1. Architecture Diagram

The Architecture Diagram given above explains the high-level design of the App. Given below is a quick overview of each component.

The .pptx files used to create diagrams in this document can be found in the diagrams folder. To update a diagram, modify the diagram in the pptx file, select the objects of the diagram, and choose Save as picture.

Main has only one class called MainApp. It is responsible for,

  • At app launch: Initializes the components in the correct sequence, and connects them up with each other.

  • At shut down: Shuts down the components and invokes cleanup method where necessary.

Commons represents a collection of classes used by multiple other components. Two of those classes play important roles at the architecture level.

  • EventsCenter : This class (written using Google’s Event Bus library) is used by components to communicate with other components using events (i.e. a form of Event Driven design)

  • LogsCenter : Used by many classes to write log messages to the App’s log file.

The rest of the App consists of four components.

  • UI: The UI of the App.

  • Logic: The command executor.

  • Model: Holds the data of the App in-memory.

  • Storage: Reads data from, and writes data to, the hard disk.

Each of the four components

  • Defines its API in an interface with the same name as the Component.

  • Exposes its functionality using a {Component Name}Manager class.

For example, the Logic component (see the class diagram given below) defines it’s API in the Logic.java interface and exposes its functionality using the LogicManager.java class.

LogicClassDiagram
Figure 2. Class Diagram of the Logic Component

Events-Driven nature of the design

The Sequence Diagram below shows how the components interact for the scenario where the user issues the command delete 1.

SDforDeletePerson
Figure 3. Component interactions for delete 1 command (part 1)
Note how the Model simply raises a AddressBookChangedEvent when the Infinity Book data are changed, instead of asking the Storage to save the updates to the hard disk.

The diagram below shows how the EventsCenter reacts to that event, which eventually results in the updates being saved to the hard disk and the status bar of the UI being updated to reflect the 'Last Updated' time.

SDforDeletePersonEventHandling
Figure 4. Component interactions for delete 1 command (part 2)
Note how the event is propagated through the EventsCenter to the Storage and UI without Model having to be coupled to either of them. This is an example of how this Event Driven approach helps us reduce direct coupling between components.

The sections below give more details of each component.

4.2. UI component

UiClassDiagram
Figure 5. Structure of the UI Component

API : Ui.java

The UI consists of a MainWindow that is made up of parts e.g.CommandBox, ResultDisplay, PersonListPanel, StatusBarFooter, BrowserPanel etc. All these, including the MainWindow, inherit from the abstract UiPart class.

The UI component uses JavaFx UI framework. The layout of these UI parts are defined in matching .fxml files that are in the src/main/resources/view folder. For example, the layout of the MainWindow is specified in MainWindow.fxml

The UI component,

  • Executes user commands using the Logic component.

  • Binds itself to some data in the Model so that the UI can auto-update when data in the Model change.

  • Responds to events raised from various parts of the App and updates the UI accordingly.

4.3. Logic component

LogicClassDiagram
Figure 6. Structure of the Logic Component
LogicCommandClassDiagram
Figure 7. Structure of Commands in the Logic Component. This diagram shows finer details concerning XYZCommand and Command in Figure 6, “Structure of the Logic Component”

API : Logic.java

  1. Logic uses the AddressBookParser class to parse the user command.

  2. This results in a Command object which is executed by the LogicManager.

  3. The command execution can affect the Model (e.g. adding a person) and/or raise events.

  4. The result of the command execution is encapsulated as a CommandResult object which is passed back to the Ui.

Given below is the Sequence Diagram for interactions within the Logic component for the execute("delete 1") API call.

DeletePersonSdForLogic
Figure 8. Interactions Inside the Logic Component for the delete 1 Command

4.4. Model component

ModelClassDiagram
Figure 9. Structure of the Model Component

API : Model.java

The Model,

  • stores a UserPref object that represents the user’s preferences.

  • stores the Infinity Book data.

  • exposes an unmodifiable ObservableList<Person> that can be 'observed' e.g. the UI can be bound to this list so that the UI automatically updates when the data in the list change.

  • does not depend on any of the other three components.

4.5. Storage component

StorageClassDiagram
Figure 10. Structure of the Storage Component

API : Storage.java

The Storage component,

  • can save UserPref objects in json format and read it back.

  • can save the Infinity Book data in xml format and read it back.

4.6. Common classes

Classes used by multiple components are in the seedu.addressbook.commons package.

5. Implementation

This section describes some noteworthy details on how certain features are implemented.

5.1. Undo/Redo feature

5.1.1. Current Implementation

The undo/redo mechanism is facilitated by an UndoRedoStack, which resides inside LogicManager. It supports undoing and redoing of commands that modifies the state of the Infinity Book (e.g. add, edit). Such commands will inherit from UndoableCommand.

UndoRedoStack only deals with UndoableCommands. Commands that cannot be undone will inherit from Command instead. The following diagram shows the inheritance diagram for commands:

LogicCommandClassDiagram

As you can see from the diagram, UndoableCommand adds an extra layer between the abstract Command class and concrete commands that can be undone, such as the DeleteCommand. Note that extra tasks need to be done when executing a command in an undoable way, such as saving the state of the Infinity Book before execution. UndoableCommand contains the high-level algorithm for those extra tasks while the child classes implements the details of how to execute the specific command. Note that this technique of putting the high-level algorithm in the parent class and lower-level steps of the algorithm in child classes is also known as the template pattern.

Commands that are not undoable are implemented this way:

public class ListCommand extends Command {
    @Override
    public CommandResult execute() {
        // ... list logic ...
    }
}

With the extra layer, the commands that are undoable are implemented this way:

public abstract class UndoableCommand extends Command {
    @Override
    public CommandResult execute() {
        // ... undo logic ...

        executeUndoableCommand();
    }
}

public class DeleteCommand extends UndoableCommand {
    @Override
    public CommandResult executeUndoableCommand() {
        // ... delete logic ...
    }
}

Suppose that the user has just launched the application. The UndoRedoStack will be empty at the beginning.

The user executes a new UndoableCommand, delete 5, to delete the 5th candidate in the Infinity Book. The current state of the Infinity Book is saved before the delete 5 command executes. The delete 5 command will then be pushed onto the undoStack (the current state is saved together with the command).

UndoRedoStartingStackDiagram
Figure 11. UndoRedo stack at the start.

As the user continues to use the program, more commands are added into the undoStack. For example, the user may execute add n/David …​ to add a new person.

UndoRedoNewCommand1StackDiagram
Figure 12. Undo Redo stack showing add command.
If a command fails its execution, it will not be pushed to the UndoRedoStack at all.

The user now decides that adding the candidate was a mistake, and decides to undo that action using undo.

We will pop the most recent command out of the undoStack and push it back to the redoStack. We will restore the Infinity Book to the state before the add command executed.

UndoRedoExecuteUndoStackDiagram
Figure 13. UndoRedo stack demonstration
If the undoStack is empty, then there are no other commands left to be undone, and an Exception will be thrown when popping the undoStack.

The following sequence diagram shows how the undo operation works:

UndoRedoSequenceDiagram
Figure 14. Sequence diagram for the undo operation.

The redo does the exact opposite (pops from redoStack, push to undoStack, and restores the Infinity Book to the state after the command is executed).

If the redoStack is empty, then there are no other commands left to be redone, and an Exception will be thrown when popping the redoStack.

The user now decides to execute a new command, clear. As before, clear will be pushed into the undoStack. This time the redoStack is no longer empty. It will be purged as it no longer make sense to redo the add n/David command (this is the behavior that most modern desktop applications follow).

UndoRedoNewCommand2StackDiagram
Figure 15. UndoRedo stack showing Redo operation.

Commands that are not undoable are not added into the undoStack. For example, list, which inherits from Command rather than UndoableCommand, will not be added after execution:

UndoRedoNewCommand3StackDiagram
Figure 16. UndoRedo stack showing no changes due to Commands that are not undoable.

The following activity diagram summarize what happens inside the UndoRedoStack when a user executes a new command:

UndoRedoActivityDiagram
Figure 17. Activity Diagram for Undo and Redo operations

5.1.2. Design Considerations

Aspect: Implementation of UndoableCommand
  • Alternative 1 (current choice): Add a new abstract method executeUndoableCommand()

    • Pros: We will not lose any undone/redone functionality as it is now part of the default behaviour. Classes that deal with Command do not have to know that executeUndoableCommand() exist.

    • Cons: Hard for new developers to understand the template pattern.

  • Alternative 2: Just override execute()

    • Pros: Does not involve the template pattern, easier for new developers to understand.

    • Cons: Classes that inherit from UndoableCommand must remember to call super.execute(), or lose the ability to undo/redo.

Aspect: How undo & redo executes
  • Alternative 1 (current choice): Saves the entire Infinity Book.

    • Pros: Easy to implement.

    • Cons: May have performance issues in terms of memory usage.

  • Alternative 2: Individual command knows how to undo/redo by itself.

    • Pros: Will use less memory (e.g. for delete, just save the candidate being deleted).

    • Cons: We must ensure that the implementation of each individual command are correct.

Aspect: Type of commands that can be undone/redone
  • Alternative 1 (current choice): Only include commands that modifies the Infinity Book (add, clear, edit).

    • Pros: We only revert changes that are hard to change back (the view can easily be re-modified as no data are * lost).

    • Cons: User might think that undo also applies when the list is modified (undoing filtering for example), * only to realize that it does not do that, after executing undo.

  • Alternative 2: Include all commands.

    • Pros: Might be more intuitive for the user.

    • Cons: User have no way of skipping such commands if he or she just want to reset the state of the address * book and not the view. Additional Info: See our discussion here.

Aspect: Data structure to support the undo/redo commands
  • Alternative 1 (current choice): Use separate stack for undo and redo

    • Pros: Easy to understand for new Computer Science student undergraduates to understand, who are likely to be * the new incoming developers of our project.

    • Cons: Logic is duplicated twice. For example, when a new command is executed, we must remember to update * both HistoryManager and UndoRedoStack.

  • Alternative 2: Use HistoryManager for undo/redo

    • Pros: We do not need to maintain a separate stack, and just reuse what is already in the codebase.

    • Cons: Requires dealing with commands that have already been undone: We must remember to skip these commands. Violates Single Responsibility Principle and Separation of Concerns as HistoryManager now needs to do two * different things.

5.2. [Proposed] Data Encryption

Data is to be encrypted to protect the Infinity Book data from unauthorized access.

5.3. Logging

We are using java.util.logging package for logging. The LogsCenter class is used to manage the logging levels and logging destinations.

  • The logging level can be controlled using the logLevel setting in the configuration file (See Section 5.4, “Configuration”)

  • The Logger for a class can be obtained using LogsCenter.getLogger(Class) which will log messages according to the specified logging level

  • Currently log messages are output through: Console and to a .log file.

Logging Levels

  • SEVERE : Critical problem detected which may possibly cause the termination of the application

  • WARNING : Can continue, but with caution

  • INFO : Information showing the noteworthy actions by the App

  • FINE : Details that is not usually noteworthy but may be useful in debugging e.g. print the actual list instead of just its size

5.4. Configuration

Certain properties of the application can be controlled (e.g App name, logging level) through the configuration file (default: config.json).

5.5. Search by Name, Tags, Email, and Phone [Since v1.2]

This find command allows the user to search through the list of all candidates and output all whose Name, Tags, Email, and Phone match with the given keywords.

5.5.1. Implementation Details

During execution of this command, Infinity Book will do the following:

  1. FindCommandParser class will extract keywords from user input, form a predicate, then pass it to FindCommand class.

  2. FindCommand will take in the predicate and update the list of Persons accordingly.

The condition for a candidate to be matched with given keywords is defined in PersonContainsKeywordsPredicate.

keywords.stream()
    .anyMatch(keyword -> StringUtil.containsWordIgnoreCase(person.getName().fullName, keyword)
    || StringUtil.containsWordIgnoreCase(person.getEmail().toString(), keyword)
    || StringUtil.containsWordIgnoreCase(person.getPhone().toString(), keyword)
    || person.getTags().stream()
    .anyMatch(tag -> StringUtil.containsWordIgnoreCase(tag.tagName, keyword)));

5.5.2. Design Considerations

Aspect: How user should specify the field to search
Alternative 1 (current choice): Search for all Name, Tags, Email, and Phone
Pros: User does not need to learn additional commands.
Cons: There can be unintended results, but since the set of keywords to search for each field does not overlap with each other, so such unintended results are very rare.

Alternative 2: Allow users to opt searching for one field in Name, Tags, Email, or Phone
Pros: User can narrow down the search result.
Cons: User has to remember additional syntax.


Aspect: The matching condition
Alternative 1 (current choice): Return Persons whose Name, Tags, Email, or Phone have at least one keyword in the input keywords.
Pros: User can do a wider search when he or she does not remember candidates' information clearly.
Cons: User hardly does a detailed search.

Alternative 2: Return Persons whose Name, Tags, Email, or Phone have all input keywords.
Pros: User can narrow down the search results.
Cons: In some cases, user cannot do a more general search.

5.6. Add/Remove Tags (Since v1.3)

The mechanics of EditCommand is that a new Person will be first created with the same information as the Person to edit. Then edited fields of the new Person will be determined based on user inputs, then it will be used to update the persons list of Infinity Book.

personToEdit = lastShownList.get(index.getZeroBased());
editedPerson = editPersonDescriptor.createEditedPerson(personToEdit);

try {
    model.updatePerson(personToEdit, editedPerson);
}

From v1.0, user can overwrite any field of a candidate using edit command. Enhancing on top of this, the creation of the new Person is updated so that user can add new tags by edit -add-tag INDEX [t/TAG]…​ command and remove existing tags by edit -delete-tag [t/TAG]…​ command.

5.6.1. Design Considerations

Aspect: Implementation of removing/adding tags
Alternative 1 (current choice): Modify EditPersonDescriptor in EditCommand
Pros: Prevent overlapping codes.
Cons: Modify other details (e.g. Name, Phone, Address,…​) apart from Tags.

Alternative 2: Write a new EditPersonDescriptor for this command
Pros: Maintain the current edit logic and behaviour.
Cons: Duplicate the exact code, which may cause a lot of hassles in future development.


Aspect: Choosing command to add or remove tags
Alternative 1 (current choice): Add COMMAND_OPTION (-add-tag and -delete-tag) to the current edit command.
Pros: Adding or removing tags is indeed editing a Person’s details, so it makes complete sense to perform this action using edit command and reduces the number of commands that user has to remember.
Cons: Have to modify the parser so that it can extract the option.

Alternative 2: Using two new commands for adding and removing tags
Pros: It is easier to parse the command.
Cons: User has to remember more commands.

5.7. Accept Option for Commands (Since v1.2)

To reduce the number of commands that user needs to learn, functions having similar behaviours can be grouped under one COMMAND_WORD, and each function in the group can be specified by COMMAND_OPTION by the user.

The command format is thus: COMMAND_WORD [-COMMAND_OPTION] [PARAMETERS]…​

5.7.1. Implementation Details

Arguments including option are wrapped with the ArgumentWithOption class shown in the below code snippet.

/**
 *  ArgumentWithOption class encapsulates an argument in the format: [OPTION] ARGS, and handles the parsing, extracting
 *  option from the argument.
 */

public class ArgumentWithOption {

    private static final Pattern ARGUMENT_FORMAT =
            Pattern.compile("(?<commandOption>" + PREFIX_OPTION.getPrefix() + "\\S+)?(?<arguments>.*)");
    private String rawArgs;
    private final String option;
    private String args;

    public ArgumentWithOption(String rawArgs) throws ParseException {
        this.rawArgs = rawArgs.trim();

        final Matcher matcher = ARGUMENT_FORMAT.matcher(this.rawArgs);
        if (!matcher.matches()) {
            throw new ParseException(String.format(MESSAGE_INVALID_COMMAND_FORMAT, EditCommand.MESSAGE_USAGE));
        }
        this.args = matcher.group("arguments");

        String rawOption = matcher.group("commandOption");
        this.option = (rawOption != null) ?  rawOption.substring(PREFIX_OPTION.getPrefix().length()) : "";
    }

    public boolean isOption(String toCheck) {
        return toCheck.equals(option);
    }

    public String getArgs() {
        return args;
    }
}

The following snippet shows how ArgumentWithOption may be used

ArgumentWithOption argWithOption = new ArgumentWithOption(args);
// Get arguments
args = argWithOption.getArgs();
// Check for option
if (argWithOption.isOption(EditCommand.COMMAND_OPTION_ADD_TAG)) {
    parseTagsForEdit(argMultimap.getAllValues(PREFIX_TAG)).ifPresent(editPersonDescriptor::setNewTags);
}

5.8. Report (Since v1.4)

Each Report is identified with a Tag population:

Represented with the prefix pop/ followed by the tagName of the Tag population. Example: pop/SEIntern.

In the report, you will find statistics of the group of all Persons tagged with population:

  1. A bar chart showing all other Tags owned by this group and number of owners for each Tag.

  2. A pie chart illustrating the numerical proportion of each Tag.

  3. A history panel listing statistics of this group at earlier points of time.

User can view a report using viewreport command and save a report for future reference using savereport command.

5.8.1. Implementation Details

Model Component

For each report, all statistics details are encapsulated within the Report class. We use one UniqueReportList to keep all Report that user has saved.

report model
Figure 18. Report as a part in the structure of Model Component of Infinity Book

Calculating statistics:

Given the idea we use UniqueReportList to keep history, all Report instances in this list are immutable. There is no method to update instances in this list. We keep one Report instance in Infinity Book so that we can show to the user. This instance can be re-calculated via method updateReport() (implementation shown in the below snippet) in Model interface.

public void updateReport(Tag population) {

    // Get the list of `Person` tagged with `population`.

    FilteredList<Person> allPersonList = new FilteredList<>(this.addressBook.getPersonList());
    Predicate<Person> personContainsPopulationTagPredicate =
        new Predicate<Person>() {
            @Override
            public boolean test(Person person) {
                return person.getTags().stream()
                        .anyMatch(tag -> StringUtil.containsWordIgnoreCase(tag.tagName, population.tagName));
            }
        };
    allPersonList.setPredicate(personContainsPopulationTagPredicate);

    // Calculate statistics of other `Tag` owned by `Person` in this list

    Map<String, Pair<Integer, Integer>> counts = new HashMap<>();
    allPersonList.forEach((p) -> {
        Set<Tag> allTags = p.getTags();
        for (Tag t : allTags) {
            if (!t.tagName.equalsIgnoreCase(population.tagName)) {
                counts.merge(t.tagName, new Pair<>(1, 1), (a, b) ->
                        new Pair(a.getKey() + b.getKey(), a.getValue() + b.getValue()));
            }
        }
    });

    // Encapsulate statistics of each `Tag` in a `Proportion` instance

    List<Proportion> allProportions = new ArrayList<>();
    for (Map.Entry<String, Pair<Integer, Integer>> entry : counts.entrySet()) {
        allProportions.add(new Proportion(entry.getKey(), entry.getValue().getKey(), entry.getValue().getValue()));
    }

    report = new Report(population, allProportions, allPersonList.size());
}
Storage Component

We keep UniqueReportList persistently in XML files.

ReportStorageDiagram
Figure 19. XmlAdaptedReport in the structure of Storage Component of Infinity Book.
Logic Component

The below sequence diagram show interactions within Logic component when user executes viewreport pop/CS.

ViewReportDiagram
Figure 20. Interactions Inside the Logic Component for the viewreport pop/CS Command
UI Component

'ReportPanel' extends UiPart<Region> and shares the same region with BrowserPanel. When viewreport or savereport is executed, MainWindow class will swap out the BrowserPanel for the ReportPanel. We achieve this by posting ToggleReportPanelEvent (showing in the below sequence diagram).

ComponentInteractions
Figure 21. Posting event when we execute Report commands.

The EventsCenters then handles this Event and then triggers the update of Report instance (as shown in the below seqence diagram).

EventInteraction
Figure 22. Update Report instance after the ToggleReportPanelEvent is posted

5.8.2. Design Considerations

Aspect: Where calculation of statistics in the Report should be placed
Alternative 1 (current choice): in ModelManager
Pros: Since the calculation requires fetching the list of all Persons in Infinity Book, Defining it in the ModelManager to reduce dependency among components.
Cons: Have to modify multiple components such as LogicManager, ModelManager, Event.
Alternative 2: in Report class itself
Pros: Do not require a Report instance in Infinity Book, which can be redundant when user does not use.
Cons: Have to write setter and getter to get the list of Persons from Model.


Aspect: Choosing the region to show ReportPanel
Alternative 1 (current choice): ReportPanel shares the same region with BrowserPanel
Pros: Do not take additional area
Cons: Have to implement Event handling to swap panel

Alternative 2: Use a new panel
Pros: User can view reports and the browser at the same time.
Cons: Add one more panel to the interface.


Aspect: Choosing the chart color in Report
Alternative 1 (current choice): Define a new color palettes
Pros: We are able to use color palettes that are vary in both hue and brightness so that they are accessible by people who are color blind and obvious for everyone.
Cons: Have to write code to overwrite the default color palettes.

Alternative 2: Use a the default JavaFX chart color palettes
Pros: Do not need to implement.
Cons: Do not have the control over visual effect.

5.9. Tag colors [since v1.0]

5.9.1. Current Implementation

Use different colors for different tags inside person cards. For example, friends tags can be all in red and colleagues tags can be all in blue. Pre-defined colors are included in DarkTheme.css, LightTheme.css and MorningTheme.css. We use hash code of the tag names to generate a color. This is deliberately designed to ensure consistent colors each time the application runs.

5.9.2. Design considerations

Aspect: Implementation of color for tags

Alternative 1 (current choice): Have tags to be colored using hashcode instead of randomization
Pros: The color stay consistent each time the app is launched
Cons: Limited number of colors (the limiting factor is the size of pre-define color array)

Alternative 2: Have tag colors to be a random hexadecimal color value
Pros: Number of different color is very big (i.e. there is less repetitive color used)
Cons: The color for each tag value might be different between app launchs

5.10. Display relevant Github or resume page. (Since v1.3)

As a candidate may have linked online resume or github page. The recruiter may want to consider these pages.

5.10.1. Design Considerations

Aspect: Aspects: Implementation of removing/adding a linked page.

Alternative 1:(current choice) Each person has a page, which is resume or github. Add the view command to view a specific person, and view the linked page on the browser panel.
Pros: The page can be linked to the person, and it is easier to view by person’s email
Cons: Need to modify current implementation of person model
Alternative 2: Add a new command to view specific page, which is hard code URL. Pros: No need to modify current person
Cons: Hard to maintain the hard code URL

5.10.2. Implementation details

  • ViewCommandParser class will extract emails ID from user input, form a predicate, then pass it to ViewCommand class.

  • ViewCommand will take in the predicate and update the list of Persons by email ID, and change the browser panel accordingly. source, java]

public class EmailFilter implements Predicate<Person> {
    private final String email;

    public EmailFilter (Email email) {
        this.email = email.toString();
    }

    @Override
    public boolean test(Person person) {
        return person.getEmail().toString().equals(this.email);
    }

    @Override
    public boolean equals(Object other) {
        return other == this // short circuit if same object
                || (other instanceof EmailFilter // instanceof handles nulls
                && this.email.equals(((EmailFilter) other).email)); // state check
    }

}

5.11. Auto-correct and Auto-complete [since v1.4]

Infinity Book enhances the user’s interaction with the application, with built in auto-correct and auto-complete. Command words that are typed in are examined for spelling errors and completions upon SPACE and TAB press respectively.

To include completion and correction support for a new command, simply add it into commandDictionary in CommandCorrection#createDictionary() as shown in code snippet.

public static void createDictionary() {
        commandDictionary = new HashSet<>();

        // .. add other command words
        commandDictionary.add(PostJobCommand.COMMAND_WORD);
}

5.11.1. Auto-complete Implementation

The user input is recorded in CommandBox, and upon TAB keypress, the CommandBox#navigateToCompletedCommand() method looks for command words containing the entered string as the first substring.

AutocompleteActivityDiagram
Figure 23. This activity diagram demonstrates the high level behavior of the autocomplete feature.

When CommandCorrection#updateSuggestionsList() method is called, it searches through all command words for suitable matches.

When multiple matches are available, the TAB keypress is counted, and the command box cycles through all suggestions. When no suggestions are available, the existing text is left unchanged.

Since the text in the command box is altered with each TAB press, a brief history is maintained containing the most recent suggestion made by auto-complete and original user input. Here, the user has pressed the TAB key multiple times, but expects completions based on his input, not the recent suggestion that fills the command box.

The following code snipped demonstrates the use of the suggestion history to choose the text to complete.

    public static String updateTextToComplete(String textToComplete) {
        if (textToComplete.compareTo(recentSuggestion.trim()) == 0) {
            return recentInput;
        } else {
            return textToComplete;
        }
    }

5.11.2. Auto-correct Implementation

Auto-correct searches command word input for potential one edit distance errors, including missed,swapped and stray characters.

Two details about the implementation are noteworthy:

  1. For seamless background operation, command words that are not recognized are automatically analysed for spelling errors on SPACE key press.

  2. In cases where commands with spell errors are copy-pasted into the command box, SPACE key press separates the command word from arguments, and corrects the command word without altering the rest of the command.

Upon being triggered, CommandBox#navigateToLikelyInput() method returns the likely correction using the mentioned heuristics.

When multiple suggestions are available, corrections obtained by removing stray character, and adding missing character are prioritized, in that order. This is in line with belief that these are the most common kinds of errors.

The following code snippet demonstrates this ability clearly.

   public static String extractCommandWord(String commandText) {
           String trimmedCommandText = commandText.trim();
           String[] wordsInCommandText = trimmedCommandText.split(" ");
           commandParameters = trimmedCommandText.replace(wordsInCommandText[0], "");
           return wordsInCommandText[0];
       }

5.12. Job Postings [Since v1.3]

Recruiters may have several job openings available with varying required skills, location and tags. These are represented in the Infinity Book as Job Postings. Job postings may be added or removed at any time. Existing job postings can be listed, searched, edited and deleted. Any existing job postings may be matched with the list of candidates to filter potential matches.

5.12.1. Implementation Specifics

Model Component

Job postings are maintained in the Model in a UniqueJobList, enforcing uniqueness among the stored individual job postings.

UniqueJobListUML
Figure 24. UML diagram showing representation of UniqueJobList in Infinity Book.

Individual job postings are represented by the Job object with attributes Job Title, Location, Skills and Tags. The class relationship is shown in Figure 2 below.

JobUMLDiagram
Figure 25. UML diagram showing representation of Job in Infinity Book.

In addition, the Person and Job class have been modified to maintain Skills as an additional field to offer better functionality during candidate-job matching. The add and edit commands reflect these changes as well.

Together, this modifies the Model to the state shown in the following diagram.

ModelWithJobUML
Figure 26. A complete picture of the model as maintained by Infinity Book.
Storage Component

Job postings are stored in XmlAdaptedJob objects, which maintain a class relationship as shown in this diagram.

JobStorageDiagram
Figure 27. Job objects are saved using XmlAdaptedJob objects as shown above.

An example job stored in this format is reproduced below:

<jobs>
        <jobTitle>Backend Engineer</jobTitle>
        <location>Geylang</location>
        <skills>Java,SQL</skills>
        <tagged>SEIntern</tagged>
        <tagged>ATAP</tagged>
</jobs>
UI Component

The main window contains a JobListPanel, which contains individual jobs represented using JobListCards. JobListCards represent the fields in each Job using Labels and flowPanes.

Job Operations and Logic

Operations performed on jobs follow the sequence of operations described in the diagram below.

  1. Command input is received by the UI component.

  2. The command execution is handled by the Logic component.

  3. The changes to data are effected by the Model component.

  4. The new Infinity Book data is saved by the Storage component.

This sequence of events is summarized in the following sequence diagram.

SequenceDiagramJobPosting
Figure 28. A high level sequence diagram showing events triggered by the postjob command.

The following job posting commands are implemented extending UndoableCommand and Command:

extends UndoableCommand extends Command

PostJobCommand
EditJobCommand
DeleteJobCommand
MatchJobCommand

ListJobCommand
FindJobCommand

The following diagram details the class relationship for Commands.

CommandsStructure
Figure 29. UML Diagram depicting the class relationship between command classes.

Adding Job Postings

Adding job postings to Infinity Book is performed by the PostJobCommand. The sequence of events triggered while adding a job posting are shown in the diagram below:

PostJobCommandSequenceDiagram
Figure 30. Adding a job posting is executed as shown above.

The PostJobCommand#executeUndoableCommand triggers addition of the Job to UniqueJobList in AddressBook#add The following code snippet demonstrates the prevention of duplicates in the UniqueJobList

    public void add(Job toAdd) throws DuplicateJobException {
        requireNonNull(toAdd);
        if (contains(toAdd)) {
            throw new DuplicateJobException();
        }
        internalList.add(toAdd);
    }

Editing Job Postings

Editing job postings in the Infinity Book is performed by the EditJobCommand. The sequence of events triggered while editing a job posting are shown in the diagram below:

EditJobCommandSequenceDiagram
Figure 31. Editing a job posting is executed as shown above.

The EditJobCommand#executeUndoableCommand triggers editing of a existing Job in UniqueJobList in AddressBook#add The EditJobCommand ensures that the index specified refers to a valid index in the most recent listing of jobs. Editing jobs to cause duplicates in the UniqueJobList is prevented by throwing a DuplicateJobException.

The following code snippet demonstrates the prevention of duplicates and index checking in the UniqueJobList after editing

    public void setJob(Job target, Job editedJob) throws DuplicateJobException, JobNotFoundException {
        requireNonNull(editedJob);
        int index = internalList.indexOf(target);
        if (index == -1) {
            throw new JobNotFoundException();
        }
        if (!target.equals(editedJob) && internalList.contains(editedJob)) {
            throw new DuplicateJobException();
        }
        internalList.set(index, editedJob);
    }

To completely specify the changes to the Job object specified by EditJobCommand, an EditJobCommandDescriptor is parsed from the command, and used to update changes in the Model. Completing these operations ensures the new Job object is updated in the Infinity Book.


Delete Job Postings

Deleting job postings in the Infinity Book is performed by the DeleteJobCommand. The sequence of events triggered while deleting a job posting are shown in the diagram below:

DeleteJobCommandSequenceDiagram
Figure 32. Deleting a job posting is executed as shown above.

The DeleteJobCommand selects a Job object by matching the index with the last shown job list, and checks the index as shown in the following code snippet.

 protected void preprocessUndoableCommand() throws CommandException {
        List<Job> lastShownList = model.getFilteredJobList();
        if (targetIndex.getZeroBased() >= lastShownList.size()) {
            throw new CommandException(Messages.MESSAGE_INVALID_JOB_DISPLAYED_INDEX);
        }

        jobToDelete = lastShownList.get(targetIndex.getZeroBased());

The changes to the UniqueJobList are enacted by AddressBook#removeJob, which completes the removal of the object and triggers Storage to update the data.


Matching Job Postings

Matching job postings to candidates in the Infinity Book is performed by the MatchJobCommand.

The sequence of events triggered while matching a job posting are shown in the diagram below:

matchJobSequenceDiagram
Figure 33. matchjob uses predicate based filtering similar to findjob and listjobs

The MatchJobCommand selects a Job object by matching the index with the last shown job list. A PersonMatchesJobPredicate is used to find matches, by looking for matches between tags, location and skills fields of the Job and Person.

Search parameters can be excluded from search when they are set to 'ALL'. Under these circumstances only the other parameters will influence the search.

The following code snippet shows how Location can be excluded from the search. Other two parameters are handles similarly.

public boolean test(Person person) {

        // .. code to set up and assign locationMatch, skillsMatch and tagsMatch
        locationMatch = isLocationMatchSatisfied(locationMatch);
        //.. similarly for skillsMatch and tagsMatch

        return locationMatch && skillsMatch && tagsMatch;
 }
 private boolean isLocationMatchSatisfied(boolean locationMatch) {
         if (notLocationBound) {
             locationMatch = true;
         }
         return locationMatch;
 }

The PersonListPanel is populated with a list of candidates who match the job according to the set parameters.


Listing Job Postings

Listing all job postings in the Infinity Book is performed by the FindJobCommand. The sequence of events triggered while listing all job postings are shown in the diagram below:

listjobcommandSequenceDiagram
Figure 34. Sequence diagram above shows the sequence of events triggered on execution of listjobs

The ListJobCommand selects displays all Job objects from UniqueJobList in the JobListPanel.


Finding Job Postings

Finding job postings by keywords in the Infinity Book is performed by the FindJobCommand. The sequence of events triggered while finding a job posting are extremely similar to listing all job postings as in Figure above, differing only in the specifics of the predicate used.

The FindJobCommand selects Job objects to display by matching the keyword with the Job Title, Location or Tags, and lists all matches in the JobListPanel.

5.12.2. Design Considerations

Aspect: Implementation of additional Skill parameter for Person and Job

Alternative 1 (current choice): Maintain skills as list of strings.
Pros: Allows recruiter freedom in creating a skills list as they need.
Cons: Searching requires complex string operations. Duplicates of the same skill will be found under different persons.

Alternative 2: Maintain a UniqueSkillsList similar to tags.
Pros: Different candidates with similar skills will utilize the same object.
Cons: The list of skills must be synchronized with both person list and job list, removing any skills that are not used by both.


Aspect: Implementation of Job-Person Matching

Alternative 1 (current choice): Match as many keywords in an OR search as possible
Pros: Maintains existing search predicates with minor modifications.
Cons: May not provide effective filtering.

Alternative 2: Indicate required and optional matches in job posting.
Pros: Search can be customized to be as accurate as required.
Cons: Spelling differences may still cause diminished effectiveness.

5.13. Theming Mechanism [since v1.3]

The theme mechanism is facilitated by a singleton class UiTheme which will be called inside the MainWindow constructor where it passes its scene into the UiTheme class. The scene is necessary for changing the CSS at runtime.

The class UiTheme requires the scene to be set at MainWindow class so that it can be used to edit themes.

public class UiTheme {
    ...
    public static void setScene(Scene s) {
        scene = s;
        setDefaultTheme();
    }

    private static void setDefaultTheme() {
        setToMorningTheme();
    }

    public static void setToLightTheme() {
        scene.getStylesheets().setAll(LIGHT_THEME_STYLE);
    }

    public static void setToDarkTheme() {
        scene.getStylesheets().setAll(DARK_THEME_STYLE);
    }

    public static void setToMorningTheme() {
        scene.getStylesheets().setAll(MORNING_THEME_STYLE);
    }
    ...
}

Example of calling the application to change to light theme:

UiTheme.getInstance().setToLightTheme();

5.13.1. Design Considerations

Aspect: Ways to change the application theme
Alternative 1 (current choice): Use a dedicated singleton class UiTheme to change the theme
Pros: Provide a dedicated class that manages all the changing of themes. UiTheme can contain many CSS theme file paths and thus the theme mechanism is more scalable . It is easier for ThemeCommand class to call UiTheme instance and use its methods directly.
Cons: Increases coupling
Alternative 2: Change the themes in MainWindow class directly
Pros: Decreases coupling
Cons: Violates the single responsibility principle since MainWindow class also controls the changing of theme. It is harder for ThemeCommand class to access the methods from MainWindow to change theme

5.14. Interview Management [Since v1.5rc]

Interviewing is one of the important steps in recruiting process. HR may need a tool to manage interview efficiently. The Infinity Book will need interview model with basic features including adding interview, listing interview, and deleting interview.

  • Model Component:

InterviewModel
Figure 35. Add Interview Model to Model Component
  • Storage Component:

InterviewStorage
Figure 36. add XmlAdaptedInterview to Storage Component

5.14.1. Adding an interview

The recruiter may want to conduct an interview with candidates, and maintain an interview lists.

Aspects: Implementation of removing/add interview with candidates.

Alternative 1: (current choice): Add a new model interview including many sub fields such as Date, Location, List of Questions.
Pros: It is easier to implement other commands such as find Interview, delete Interview, add questions.
Cons: It takes time to create new model.

Alternative 2: Add a new field interview to each candidates and maintain according to each candidates.
Pros: It is easier to implement.
Cons: It is difficult to search specific interview effectively.

  • Current implementation details

    • AddInterviewCommandParser class will extract interview title, interviewee name, date, and interview location from user input, form a new interview, then pass it to AddInterviewCommand class.

    • AddInterviewCommand will take in the interview and call add new interview to storage in model management.

addInterviewSequence
Figure 37. Adding an interview sequence diagram
public synchronized void addInterview(Interview interview) throws DuplicateInterviewException {
        addressBook.addInterview(interview);
        updateFilteredInterviewList(PREDICATE_SHOW_ALL_INTERVIEWS);
        indicateAddressBookChanged();
}

5.14.2. List interview

The recruiter may want to take a look at all of the interviews.

Aspects: Implementation of listing all the interviews.

Alternative 1: (current choice) create a new command to listing all the interview.
Pros: It is easier to use and modify.
Cons: New command is needed.

Alternative 2: each candidate has been linked to an interview, listing all the interview when listing all candidates.
Pros: No need to create new command, just need to edit current list comamnd
Cons: It increases coupling and it is harder to implement.

  • Current implementation details

    • AddressBookParser class will parse the command listInterview from CLI and call ListInterviewCommand.

    • ListInterviewCommand will call updateFilteredList in model and list all the interviews in storage.

ListInterviewSequence
Figure 38. Listing an interview sequence diagram
public void updateFilteredInterviewList(Predicate<Interview> predicate) {
        requireNonNull(predicate);
        filteredInterviews.setPredicate(predicate);
}

5.14.3. Deleting Interview

After conducted interview, the HR may want to delete the interview from the Infinity Book.

Aspects: Implementation of deleting an interview:

Alternative 1: (current choice) create deleting command for deleting interview by index
Pros: It can be easily to use follow the index of listing interview command
Cons: It requires users to use two commands

Alternative 2: create deleting command for deleting interview by name.
Pros: It requires addtional tools to match interview
Cons: It is easier for user to use.

DeleteInterviewSequence
Figure 39. Delete an interview sequence diagram
  • Current implementation details

    • DeletingInterviewParser class will extract the index of the interview needed to be deleted and pass in to DeleteInterviewCommand.

    • DeleteInterviewCommand will take in the index and update the interview list accordingly.

public synchronized void deleteInterview(Interview target) throws InterviewNotFoundException {
        addressBook.removeInterview(target);
        indicateAddressBookChanged();
}

5.15. Facebook Integration

The Facebook features (facebooklogin and facebookpost) are facilitated by Facebook Graph API and RestFB API. The Graph API is low-level HTTP-based API used to get data into and out of the Facebook platform while RestAPI is a high level third-party Graph API client.

5.15.1. Preparation

Firstly, we need to build a Graddle dependency in the graddle.properties file for RestFB API

compile group: 'com.restfb', name: 'restfb', version: '2.4.0'

To use Facebook Graph API, we followed the instruction here to create a Facebook Application on the Facebook for developer site

Finishing the steps, we reached this page

FacebookAppCreated
Figure 40. Facebook Application page

5.15.2. Implementation

Facebooklogin

We allow users to log in by loading the authentication URL associated with our Facebook Application to the FacebookPanel (to be explained later) specified by Graph API

private static final String FACEBOOK_AUTH_URL =
    "https://graph.facebook.com/oauth/authorize?type=user_agent&client_id=" + FACEBOOK_APP_ID
        + "&redirect_uri=" + FACEBOOK_DOMAIN + "&scope" + FACEBOOK_PERMISSIONS;
Platform.runLater(() -> webEngine.load(FACEBOOK_AUTH_URL));

If login credentials are accepted, facebook will return an access_token in its redirected url. An example of a redirected url

https://www.facebook.com/?#access_token=EAAC15Ydo408BANN5h1MoSHWhLATnte3EchlxqsXA7x2WASMlZBJzlZC3rZCmlu4Wib3hi1bXR2r2sx2ZCgIJvJCrXaTAnQVN4f0ksFLSvCuezLhG43myLJtxT3Qtn0PpcIXXZBjgYUI4LwGvv837KGC2V4ifctNaBnHqRX6e7qAZDZD&expires_in=5184000

Using this access_token, we 'capture' this login session by making use of RestFB API. We set up the session in FacebookLoginCommand as followed

fbClient = new DefaultFacebookClient(accessToken, Version.LATEST);
user = fbClient.fetchObject("me", User.class);

'fbClient' is the Facebook Graph API client with the supplied token.

'user' is the authenticated user object.

Facebookpost

We will check if user is authenticated via the boolean variable isAuthenticated before allowing posting on facebook. If user is not authenticated, he will be redirected to log in

if (!FacebookLoginCommand.getAuthenticateState()) {
    FacebookLoginCommand fbLoginCommand = new FacebookLoginCommand();
    fbLoginCommand.execute();
} else {
    completePost();
}

We simply post the message to Facebook using the authenticated fbClient in FacebookLoginCommand

DefaultFacebookClient fbClient = FacebookLoginCommand.getFbClient();
fbClient.publish("me/feed", FacebookType.class, Parameter.with("message", toPost));

5.15.3. Implementation details

'FacebookPanel' extends UiPart<Region> and shares the same region with BrowserPanel or ReportPanel. When facebooklogin is executed, MainWindow class will swap out the BrowserPanel or ReportPanel and replace by FacebookPanel. We achieve this by posting ToggleFacebookPanelEvent (showing in the below sequence diagram).

'FacebookPanel' is used mainly as a browser to authorize a user to Facebook. It is also used to display the Facebook page after the user is authorized.

For capturing the access_token, we register FacebookPanel as an event handler. If the url (of FacebookPanel) changes to a new one that contains the access_token, we raise a BrowserUrlChangedEvent. Also, our FacebookPanel is subscribed to handle BrowserUrlChangedEvent by passing the url (with access_token) back to FacebookLoginCommand to complete the authentication.

The below sequence diagram show interactions within Logic component when user executes facebookpost hello.

FbSequenceDiagram
Figure 41. Sequence diagram detailing Logic component when executing facebookpost

5.15.4. Design considerations

Aspect: Choice of client API for Facebook Graph API
Alternative 1 (current choice): Use RestFB
Pros: RestFB is simple and easy to use with well-documented API Cons: Features are limited
Alternative 2: Facebook4j
Pros: Facebook4j provides more extensive features
Cons: It is harder to use and we do not really need advanced features for now


Aspect: Authentication
Alternative 1 (current choice): Authenticate using the built-in web browser (in FacebookPanel)
Pros: Convenient for user as authentication is done inside the app itself
Cons: The built-in Java web-engine might not support certain features of a webpage
Alternative 2: Authenticate by opening a full-pledged web browser such as Chrome/ Safari
Pros: The Facebook authorization page will be better supported by such a browser
Cons: User needs to switch between different applications

6. Documentation

This project maintains documentation in the docs folder, also accessible through the readme. This documentation is written and maintained using asciidoc.

We chose asciidoc over Markdown because asciidoc, although a bit more complex than Markdown, provides more flexibility in formatting.

6.1. Editing Documentation

See UsingGradle.adoc to learn how to render .adoc files locally to preview the end result of your edits. Alternatively, you can download the AsciiDoc plugin for IntelliJ, which allows you to preview the changes you have made to your .adoc files in real-time.

6.2. Publishing Documentation

See UsingTravis.adoc to learn how to deploy GitHub Pages using Travis.

6.3. Converting Documentation to PDF format

We use Google Chrome for converting documentation to PDF format, as Chrome’s PDF engine preserves hyperlinks used in webpages.

Here are the steps to convert the project documentation files to PDF format.

  1. Follow the instructions in UsingGradle.adoc to convert the AsciiDoc files in the docs/ directory to HTML format.

  2. Go to your generated HTML files in the build/docs folder, right click on them and select Open withGoogle Chrome.

  3. Within Chrome, click on the Print option in Chrome’s menu.

  4. Set the destination to Save as PDF, then click Save to save a copy of the file in PDF format. For best results, use the settings indicated in the screenshot below.

chrome save as pdf
Figure 42. Saving documentation as PDF files in Chrome

7. Testing

The Infinity Book project maintains GUI and non-GUI tests. Instructions on writing and running tests, and details about the nature of these tests are provided in this section.

7.1. Running Tests

There are three ways to run tests.

The most reliable way to run tests is the 3rd one. The first two methods might fail some GUI tests due to platform/resolution-specific idiosyncrasies.

Method 1: Using IntelliJ JUnit test runner

  • To run all tests, right-click on the src/test/java folder and choose Run 'All Tests'

  • To run a subset of tests, you can right-click on a test package, test class, or a test and choose Run 'ABC'

Method 2: Using Gradle

  • Open a console and run the command gradlew clean allTests (Mac/Linux: ./gradlew clean allTests)

See UsingGradle.adoc for more info on how to run tests using Gradle.

Method 3: Using Gradle (headless)

Thanks to the TestFX library we use, our GUI tests can be run in the headless mode. In the headless mode, GUI tests do not show up on the screen. That means the developer can do other things on the Computer while the tests are running.

To run tests in headless mode, open a console and run the command gradlew clean headless allTests (Mac/Linux: ./gradlew clean headless allTests)

7.2. Types of tests

We have two types of tests:

  1. GUI Tests - These are tests involving the GUI. They include,

    1. System Tests that test the entire App by simulating user actions on the GUI. These are in the systemtests package.

    2. Unit tests that test the individual components. These are in seedu.address.ui package.

  2. Non-GUI Tests - These are tests not involving the GUI. They include,

    1. Unit tests targeting the lowest level methods/classes.
      e.g. seedu.address.commons.StringUtilTest

    2. Integration tests that are checking the integration of multiple code units (those code units are assumed to be working).
      e.g. seedu.address.storage.StorageManagerTest

    3. Hybrids of unit and integration tests. These test are checking multiple code units as well as how the are connected together.
      e.g. seedu.address.logic.LogicManagerTest

7.3. Troubleshooting Testing

Problem: HelpWindowTest fails with a NullPointerException.

  • Reason: One of its dependencies, UserGuide.html in src/main/resources/docs is missing.

  • Solution: Execute Gradle task processResources.

8. Dev Ops

8.1. Build Automation

See UsingGradle.adoc to learn how to use Gradle for build automation.

8.2. Continuous Integration

We use Travis CI and AppVeyor to perform Continuous Integration on our projects. See UsingTravis.adoc and UsingAppVeyor.adoc for more details.

8.3. Coverage Reporting

We use Coveralls to track the code coverage of our projects. See UsingCoveralls.adoc for more details.

8.4. Documentation Previews

When a pull request has changes to asciidoc files, you can use Netlify to see a preview of how the HTML version of those asciidoc files will look like when the pull request is merged. See UsingNetlify.adoc for more details.

8.5. Making a Release

Here are the steps to create a new release.

  1. Update the version number in MainApp.java.

  2. Generate a JAR file using Gradle.

  3. Tag the repo with the version number. e.g. v0.1

  4. Create a new release using GitHub and upload the JAR file you created.

8.6. Managing Dependencies

A project often depends on third-party libraries. For example, Infinity Book depends on the Jackson library for XML parsing. Managing these dependencies can be automated using Gradle. For example, Gradle can download the dependencies automatically, which is better than these alternatives.
a. Include those libraries in the repo (this bloats the repo size)
b. Require developers to download those libraries manually (this creates extra work for developers)

Appendix A: Suggested Programming Tasks to Get Started

Suggested path for new programmers:

  1. First, add small local-impact (i.e. the impact of the change does not go beyond the component) enhancements to one component at a time. Some suggestions are given in Section A.1, “Improving each component”.

  2. Next, add a feature that touches multiple components to learn how to implement an end-to-end feature across all components. Section A.2, “Creating a new command: remark explains how to go about adding such a feature.

A.1. Improving each component

Each individual exercise in this section is component-based (i.e. you would not need to modify the other components to get it to work).

Logic component

Scenario: You are in charge of logic. During dog-fooding, your team realize that it is troublesome for the user to type the whole command in order to execute a command. Your team devise some strategies to help cut down the amount of typing necessary, and one of the suggestions was to implement aliases for the command words. Your job is to implement such aliases.

Do take a look at Section 4.3, “Logic component” before attempting to modify the Logic component.
  1. Add a shorthand equivalent alias for each of the individual commands. For example, besides typing clear, the user can also type c to remove all persons in the list.

    • Hints

    • Solution

      • Modify the switch statement in AddressBookParser#parseCommand(String) such that both the proper command word and alias can be used to execute the same intended command.

      • Add new tests for each of the aliases that you have added.

      • Update the user guide to document the new aliases.

      • See this PR for the full solution.

Model component

Scenario: You are in charge of model. One day, the logic-in-charge approaches you for help. He wants to implement a command such that the user is able to remove a particular tag from everyone in the Infinity Book, but the model API does not support such a functionality at the moment. Your job is to implement an API method, so that your teammate can use your API to implement his command.

Do take a look at Section 4.4, “Model component” before attempting to modify the Model component.
  1. Add a removeTag(Tag) method. The specified tag will be removed from everyone in the Infinity Book.

    • Hints

      • The Model and the AddressBook API need to be updated.

      • Think about how you can use SLAP to design the method. Where should we place the main logic of deleting tags?

      • Find out which of the existing API methods in AddressBook and Person classes can be used to implement the tag removal logic. AddressBook allows you to update a person, and Person allows you to update the tags.

    • Solution

      • Implement a removeTag(Tag) method in AddressBook. Loop through each person, and remove the tag from each person.

      • Add a new API method deleteTag(Tag) in ModelManager. Your ModelManager should call AddressBook#removeTag(Tag).

      • Add new tests for each of the new public methods that you have added.

      • See this PR for the full solution.

        • The current codebase has a flaw in tags management. Tags no longer in use by anyone may still exist on the AddressBook. This may cause some tests to fail. See issue #753 for more information about this flaw.

        • The solution PR has a temporary fix for the flaw mentioned above in its first commit.

Ui component

Scenario: You are in charge of ui. During a beta testing session, your team is observing how the users use your Infinity Book application. You realize that one of the users occasionally tries to delete non-existent tags from a contact, because the tags all look the same visually, and the user got confused. Another user made a typing mistake in his command, but did not realize he had done so because the error message wasn’t prominent enough. A third user keeps scrolling down the list, because he keeps forgetting the index of the last candidate in the list. Your job is to implement improvements to the UI to solve all these problems.

Do take a look at Section 4.2, “UI component” before attempting to modify the UI component.
  1. Use different colors for different tags inside candidate cards. For example, friends tags can be all in brown, and colleagues tags can be all in yellow.

    Before

    getting started ui tag before

    After

    getting started ui tag after
    • Hints

      • The tag labels are created inside the PersonCard constructor (new Label(tag.tagName)). JavaFX’s Label class allows you to modify the style of each Label, such as changing its color.

      • Use the .css attribute -fx-background-color to add a color.

      • You may wish to modify DarkTheme.css to include some pre-defined colors using css, especially if you have experience with web-based css.

    • Solution

      • You can modify the existing test methods for PersonCard 's to include testing the tag’s color as well.

      • See this PR for the full solution.

        • The PR uses the hash code of the tag names to generate a color. This is deliberately designed to ensure consistent colors each time the application runs. You may wish to expand on this design to include additional features, such as allowing users to set their own tag colors, and directly saving the colors to storage, so that tags retain their colors even if the hash code algorithm changes.

  2. Modify NewResultAvailableEvent such that ResultDisplay can show a different style on error (currently it shows the same regardless of errors).

    Before

    getting started ui result before

    After

    getting started ui result after
  3. Modify the StatusBarFooter to show the total number of people in the Infinity Book.

    Before

    getting started ui status before

    After

    getting started ui status after
    • Hints

      • StatusBarFooter.fxml will need a new StatusBar. Be sure to set the GridPane.columnIndex properly for each StatusBar to avoid misalignment!

      • StatusBarFooter needs to initialize the status bar on application start, and to update it accordingly whenever the Infinity Book is updated.

    • Solution

Storage component

Scenario: You are in charge of storage. For your next project milestone, your team plans to implement a new feature of saving the Infinity Book to the cloud. However, the current implementation of the application constantly saves the Infinity Book after the execution of each command, which is not ideal if the user is working on limited internet connection. Your team decided that the application should instead save the changes to a temporary local backup file first, and only upload to the cloud after the user closes the application. Your job is to implement a backup API for the Infinity Book storage.

Do take a look at Section 4.5, “Storage component” before attempting to modify the Storage component.
  1. Add a new method backupAddressBook(ReadOnlyAddressBook), so that the Infinity Book can be saved in a fixed temporary location.

A.2. Creating a new command: remark

By creating this command, you will get a chance to learn how to implement a feature end-to-end, touching all major components of the app.

Scenario: You are a software maintainer for addressbook, as the former developer team has moved on to new projects. The current users of your application have a list of new feature requests that they hope the software will eventually have. The most popular request is to allow adding additional comments/notes about a particular contact, by providing a flexible remark field for each contact, rather than relying on tags alone. After designing the specification for the remark command, you are convinced that this feature is worth implementing. Your job is to implement the remark command.

A.2.1. Description

Edits the remark for a candidate specified in the INDEX.
Format: remark INDEX r/[REMARK]

Examples:

  • remark 1 r/Likes to drink coffee.
    Edits the remark for the first candidate to Likes to drink coffee.

  • remark 1 r/
    Removes the remark for the first person.

A.2.2. Step-by-step Instructions

[Step 1] Logic: Teach the app to accept 'remark' which does nothing

Let’s start by teaching the application how to parse a remark command. We will add the logic of remark later.

Main:

  1. Add a RemarkCommand that extends UndoableCommand. Upon execution, it should just throw an Exception.

  2. Modify AddressBookParser to accept a RemarkCommand.

Tests:

  1. Add RemarkCommandTest that tests that executeUndoableCommand() throws an Exception.

  2. Add new test method to AddressBookParserTest, which tests that typing "remark" returns an instance of RemarkCommand.

[Step 2] Logic: Teach the app to accept 'remark' arguments

Let’s teach the application to parse arguments that our remark command will accept. E.g. 1 r/Likes to drink coffee.

Main:

  1. Modify RemarkCommand to take in an Index and String and print those two parameters as the error message.

  2. Add RemarkCommandParser that knows how to parse two arguments, one index and one with prefix 'r/'.

  3. Modify AddressBookParser to use the newly implemented RemarkCommandParser.

Tests:

  1. Modify RemarkCommandTest to test the RemarkCommand#equals() method.

  2. Add RemarkCommandParserTest that tests different boundary values for RemarkCommandParser.

  3. Modify AddressBookParserTest to test that the correct command is generated according to the user input.

[Step 3] Ui: Add a placeholder for remark in PersonCard

Let’s add a placeholder on all our PersonCard s to display a remark for each candidate later.

Main:

  1. Add a Label with any random text inside PersonListCard.fxml.

  2. Add FXML annotation in PersonCard to tie the variable to the actual label.

Tests:

  1. Modify PersonCardHandle so that future tests can read the contents of the remark label.

[Step 4] Model: Add Remark class

We have to properly encapsulate the remark in our Person class. Instead of just using a String, let’s follow the conventional class structure that the codebase already uses by adding a Remark class.

Main:

  1. Add Remark to model component (you can copy from Address, remove the regex and change the names accordingly).

  2. Modify RemarkCommand to now take in a Remark instead of a String.

Tests:

  1. Add test for Remark, to test the Remark#equals() method.

[Step 5] Model: Modify Person to support a Remark field

Now we have the Remark class, we need to actually use it inside Person.

Main:

  1. Add getRemark() in Person.

  2. You may assume that the user will not be able to use the add and edit commands to modify the remarks field (i.e. the candidate will be created without a remark).

  3. Modify SampleDataUtil to add remarks for the sample data (delete your addressBook.xml so that the application will load the sample data when you launch it.)

[Step 6] Storage: Add Remark field to XmlAdaptedPerson class

We now have Remark s for Person s, but they will be gone when we exit the application. Let’s modify XmlAdaptedPerson to include a Remark field so that it will be saved.

Main:

  1. Add a new Xml field for Remark.

Tests:

  1. Fix invalidAndValidPersonAddressBook.xml, typicalPersonsAddressBook.xml, validAddressBook.xml etc., such that the XML tests will not fail due to a missing <remark> element.

[Step 6b] Test: Add withRemark() for PersonBuilder

Since Person can now have a Remark, we should add a helper method to PersonBuilder, so that users are able to create remarks when building a Person.

Tests:

  1. Add a new method withRemark() for PersonBuilder. This method will create a new Remark for the candidate that it is currently building.

  2. Try and use the method on any sample Person in TypicalPersons.

[Step 7] Ui: Connect Remark field to PersonCard

Our remark label in PersonCard is still a placeholder. Let’s bring it to life by binding it with the actual remark field.

Main:

  1. Modify PersonCard's constructor to bind the Remark field to the Person 's remark.

Tests:

  1. Modify GuiTestAssert#assertCardDisplaysPerson(…​) so that it will compare the now-functioning remark label.

[Step 8] Logic: Implement RemarkCommand#execute() logic

We now have everything set up…​ but we still can’t modify the remarks. Let’s finish it up by adding in actual logic for our remark command.

Main:

  1. Replace the logic in RemarkCommand#execute() (that currently just throws an Exception), with the actual logic to modify the remarks of a person.

Tests:

  1. Update RemarkCommandTest to test that the execute() logic works.

A.2.3. Full Solution

See this PR for the step-by-step solution.

Appendix B: Product Scope

Target user profile: Tech recruiters who have the need for finding and acquiring skilled Computing students for tech companies.

  • manage a significant number of student contacts

  • store student’s basic information, contacts, expected graduation year, familiar frameworks, languages.

  • find students whose skill set is suitable for available positions

  • Keep track of student’s past interactions with the company: internship, coding challenges, interview,…​

B.1. Feature contributions

B.1.1. Sashankh Chengavalli Kumar (@ChengSashankh)

Major Enhancement: Job postings

This allows the recruiter to add and manage job postings and receive automated suggestions about most suitable candidates based on various parameters.

For example, when the need arises to hire a new fresh graduate for a backend software development role with proficiency in SQL and Javascript, with location restrictions as well, the recruiter creates a job posting. As candidates are added and removed, the application displays an updated list of matching candidates, until removed from Infinity Book.

Implementations needed:

  1. Creating command such as postjob, editjob, deletejob, findjob and listjob to manage jobposting.

  2. Creating a matchjob command that displays the list of matched candidates in the Infinity Book.

  3. Display pane for showing the list of jobs currently active.

  4. Adding skills field to Person and Job to enhance matching.

Minor Enhancement: Auto-correct and auto-complete

This allows users to interact with Infinity Book with enhanced speed and accuracy. This implementation provides users the ability to auto-correct trivial spelling errors and complete partial command words.

For example, when li is typed, the command enables completion to either list , listinterview or listjob. Similarly lsit is corrected to list by the command.

Implementations needed:

  1. Complete list of command words used by Infinity Book has to be updated and maintained when features are added.

  2. Command box has to be modified to consume some keypresses such as TAB and SPACE for completion and correction.

  3. To support multiple suggestions for auto-complete, a brief history of the last suggestion and input has to be maintained. This is further explained in Section 5.11.1

B.1.2. Huynh Thanh Duc Anh (@anh2111)

Major Enhancement: Data visualization

This allows users to make data-driven decisions with the visualization of recruiting data.

graph

In the above example, Screening, Interviewing, Accepted, SoftwareEngineerIntern are tags that can be managed conveniently by the user. Given this chart, the recruiter can help a better high-level insights of candidates, which helps them significantly in making decisions.

Implementations needed:

  1. Making Command accepts OPTION. Example: tag add, tag remove.

  2. Allowing add/remove one tag.

  3. Drawing chart.

Minor Enhancement: Enhance Find command
  • Allow users to Find Person by name, phone, email, and tags.

  • Given the huge number of contacts that a recruiter may have, it is not possible for them to remember candidates' name. Hence, users should be able to search by other fields so that it’s easier for them to locate a person.

B.1.3. Nguyen Thi Viet Ha (@deeheenguyen)

Major Enhancement: Adding Interview

This allows the recruiters maintain a list of interview. Each Interview is linked to a person. The recruiters can search about the interview based on the candidates.

Implementation needed:

  1. Make a interview model

  2. Add a interview command

  3. Make the interview searchable.

Minor Enhancement: View Command
  1. Given a list of candidates, the recruiters can view a specific person by emailID.

  2. Display all the information in the person card.

  3. If there is a linked page to the person, it will be displayed on the brower panel.

B.1.4. Nguyen Dinh Dung (@tiny767)

Major Enhancement (coming in v2.0): Make several enhancements to the UI so that it is much more user-friendly.

This allows tech recruiters who need to manage big Infinity Books to save time by using the app more efficiently and feel more comfortable.

Implementations needed:

  • Making the color for tags the same for tags with same name and the choice of color stays the same.

  • For the user’s command input, coloring keywords such as name, p/.

  • Provide usage syntax or syntax error in real time (e.g. display syntax for add when the users finish typing 'add'

Major Enhancement (since v1.5): Facebook integration

Implementations needed:

  • Allow user to log in to their facebook account and connect the address book with that facebook account

  • Allow for some features such as posting to wall, saving friends to the address book

Minor Enhancement: remark command
  • Allow users to add remarks for people in their Infinity Books.

  • This allows tech recruiters to have a more flexible field to use for their candidates

Minor Enhancement: theme command
  • Allow users to change between some pre-defined theme.

Minor Enhancement: Tag color
  • Use different colors for different tags inside person cards

Appendix C: User Stories

Priorities: High (must have) - * * *, Medium (nice to have) - * *, Low (unlikely to have) - *

Priority As a …​ I want to …​ So that I can…​

* * *

User

Add a new candidate

* * *

User

Delete a candidate

Remove entries that I no longer need

* * *

User

Find a candidate by name

Locate details of persons without having to go through the entire list

* * *

User who wants to record notes

Add a note entry for a candidate

Record more information about the person

* * *

User interested in a contact’s skills

Get the technical skills of a candidate

to find suitable candidates

* * *

User who saves many resumes

Open candidates' resumes from commandline

to quickly view their profiles

* * *

User

Delete a tag from Infinity Book

To remove tags I am no longer interested in

* * *

Recruiter

Log in to Facebook on Infinity Book

Share job posting to facebook faster

* * *

new user

see usage instructions

refer to instructions when I forget how to use the App

* *

User

Hide private contact details by default

Minimize chance of someone else seeing them by accident

* *

User who views contacts

List frequently viewed candidates

To select the most probable candidates

* *

User with interest in recruitment

View candidates' LinkedIn profiles from commandline

To know about their work profiles

* *

User

View my search history

To keep track of my previous searches

* *

User interested in specific skills

Search candidates by skill

To find suitable candidates easily

* *

User who is recruiting

Search by graduation year

To find suitable full/part time candidates

* *

User interested in checking projects

Access contacts' Github repositories

easily view and evaluate projects

* *

With many contacts

Set profile pictures for contacts

identify and remember them

* *

User

read all notes about a contact

Remember my past interactions and research about the contact

* *

User with multiple Infinity Books

Export my contacts

Import into another Infinity Book application

* *

User with multiple devices

Back up my Infinity Book in the cloud

Data is accessible from other devices

*

User with many persons in the Infinity Book

Sort persons by name

Locate a candidate easily

*

User concerned about confidentiality

Set a password for the AddressBook

My Infinity Book will be secure

*

User with frequently changing contacts

Clear the entire Infinity Book

To start with new candidates when required

*

User who frequently emails contacts

Open an email link to the contact directly

Quickly email candidates with copying their addresses

*

User who types many commands

Get autocomplete suggestion

Quickly complete command

Appendix D: Use Cases

(For all use cases below, the System is the InfinityBook and the Actor is the user, unless specified otherwise)

Use case: Delete person

MSS

  1. User requests to list persons

  2. Infinity Book shows a list of persons

  3. User requests to delete a specific candidate in the list

  4. InfinityBook deletes the person

    Use case ends.

Extensions

  • 2a. The list is empty.

    Use case ends.

  • 3a. The given index is invalid.

    • 3a1. Infinity Book shows an error message.

      Use case resumes at step 2.

D.1. Use case: Search a candidate by tag

MSS

  1. User requests to search and provides search params

  2. Infinity Book shows a list of candidate fitting the given criteria

    Use case ends.

D.2. Use case: View Linkedin profile of a person

MSS

  1. User requests to search the candidate by name

  2. Infinity Book shows a list of person

  3. User chooses a candidate by index in the list

  4. Infinity Book shows the linkedin page of that person

    Use case ends.

Appendix E: Non Functional Requirements

  1. Should work on any mainstream OS as long as it has Java 1.8.0_60 or higher installed.

  2. Should be able to hold up to 1000 persons with a reponse time of less than 2 seconds for a command.

  3. Commands should ideally be less than 12 characters long to enable users with reasonable typing speed to use commands effectively.

  4. Should not use more than 11% CPU resources for executing commands

  5. Data storage for Infinity Book with 1000 contacts should not exceed 500 Gb

  6. Should be able to work even on older 32-bit system

  7. Project should not require updates to remain functional.

  8. A user with above average typing speed for regular English text (i.e. not code, not system admin commands) should be able to accomplish most of the tasks faster using commands than using the mouse.

Appendix F: Glossary

Mainstream OS

Windows, Linux, Unix, OS-X

Private contact detail

A contact detail that is not meant to be shared with others

Appendix G: Product Survey

Infinity Book

Author: Sashankh Chengavalli Kumar

Pros:

  • Allows recruiters to collate all candidate details in one location

  • Allows for quick management of data using a Desktop Application

  • End-to-end support for recruiters from viewing candidates to inviting to interviews.

Cons:

  • Synchronization across multiple devices/cloud not possible.

  • Mobile client not available to enable remote access to Infinity Book.

Appendix H: Instructions for Manual Testing

Given below are instructions to test the app manually.

These instructions only provide a starting point for testers to work on; testers are expected to do more exploratory testing.

H.1. Launch and Shutdown

  1. Initial launch

    1. Download the jar file and copy into an empty folder

    2. Double-click the jar file
      Expected: Shows the GUI with a set of sample contacts. The window size may not be optimum.

  2. Saving window preferences

    1. Resize the window to an optimum size. Move the window to a different location. Close the window.

    2. Re-launch the app by double-clicking the jar file.
      Expected: The most recent window size and location is retained.

H.2. Add tags to a candidate

  1. Add new tags to a candidate in the list.

    1. Prerequisites: List all persons using the list command. Multiple persons in the list.

    2. Test case: edit -add-tag 1 t/Year2 t/SpeakVietnamese
      Expected: Added new tags to the first candidate.

    3. Test case: edit -add-tag 1 t/Year2
      Expected: Cannot add a new duplicated tag to the first candidate. The candidate’s details will not be updated, and Infinity Book will not show any warning.

H.3. Delete tags from a candidate

  1. Delete tags from a candidate in the list.

    1. Prerequisites: List all persons using the list command. Multiple persons in the list.

    2. Test case: edit -delete-tag 1 t/computing
      Expected: Cannot delete tag computing from the first candidate. The candidate’s details will not be updated, and Infinity Book will not show any warning

    3. Test case: edit -delete-tag 1 t/Computing
      Expected: Removes tag Computing from the first candidate.

H.4. View a report

  1. View report based on candidates in IB.

    1. Test case: viewreport pop/SEIntern
      Expected: Infinity Book has displayed Report with Population Tag SEIntern in the right-most column.

    2. Test case: viewreport pop/SEIntern pop/Computing
      Expected: Infinity Book will display the report for Computing.

Report explanation can be found in User Guide.

H.5. Save a report

  1. Save report based on candidates in IB.

    1. Test case: savereport pop/SEIntern
      Expected: Infinity Book has saved Report with population Tag SEIntern. In the right-most column, the history list includes all statistics you have saved.

    2. Test case: savereport pop/SEIntern pop/Computing
      Expected: Infinity Book will save the report for Computing.

Report explanation can be found in User Guide.

H.6. View a person

  1. Viewing a person by emailID

    1. Test case: view notexisted@example.com
      Expected: person found and no new url display on browser panel

    2. Test case: view a@example.com
      Prerequisites: the person with email a@example.com exists on Infinity Book.
      Expected: the person with email a@example.com returned, and the corresponding linking page has been displayed on the browser panel.

H.7. Interview Management

H.7.1. Add interview

  1. Add an interview to current infinity book.

    1. Test case: addInterview
      Expected: Invalid format showing in the status message

    2. Test case: addInterview i/SE INTERVIEW n/DAVID d/04.05.2018 l/NUS SOC
      Expected: new interview added in the status message. New interview added in the browser panel.

H.7.2. List interview

  1. List all interviews of infinity book

    1. Test case: listInterview Expected: Showing all the interviews in the interview panel

H.7.3. Delete Interview

  1. Prerequisites: List all interviews using the listInterview command. Multiple interviews in the list.

  2. Deleting an interview while all interviews are listed

    1. Test case: deleteInterview 1 Expected: First interview deleted from the interview list. Details of the deleted contact shown in the status message.

    2. Test case: deleteInterview 0 Expected: No interview is deleted. Error details shown in the status message.

    3. Other incorrect delete commands to try: deleteInterview, deleteInterview x (where x is larger than the list size)
      Expected: Similar to previous.

H.8. Job Management

H.8.1. Add Job

  1. Post a job to infinity book.

    1. Test case: postjob
      Expected: Invalid format showing in the status message

    2. Test case: postjob j/JobTitle l/location s/skill t/NewTag
      Expected: new job posted in the status message. New job card displauyed in the JobListPanel.

H.8.2. List Jobs

  1. List all jobs of infinity book

    1. Test case: listjobs Expected: Showing all jobs in the job panel.

H.8.3. Delete Job

  1. Prerequisites: List all jobs using the listjobs command. All entries are displayed in JobListPanel.

  2. Deleting a job while all jobs are listed

    1. Test case: deletejob 1 Expected: First job deleted from the job list. Details of the deleted job shown in the status message.

    2. Test case: deletejob 0 Expected: No interview is deleted. Error details shown in the status message.

    3. Other incorrect delete commands to try: deletejob, deletejob x (where x is larger than the list size)
      Expected: Similar to previous.

H.8.4. Edit Job

  1. Prerequisites: List all jobs using the listjobs command. All entries are displayed in JobListPanel.

  2. Editing a job while all jobs are listed

    1. Test case: editjob 1 j/newTitle l/newLocation s/newSkill t/NewTag Expected: First job edited to contain new job title. Details of the deleted job shown in the status message.

    2. Test case: editjob 1 j/newTitle l/newLocation s/newSkill t/NewTag + editjob 2 j/newTitle l/newLocation s/newSkill t/NewTag Expected: Second job is not edited, but first is. Error details shown in the status message.

    3. Other incorrect delete commands to try: editjob 1, deletejob x (where x is larger than the list size)
      Expected: Similar to previous.

H.8.5. Find Job

  1. Find a job by keyword[s].

    1. Test case: findjob keyword Expected: Showing all jobs matching keyword in the job panel.

    2. Test case: findjob Expected: No job list change is made. Error details shown in the status message.

H.8.6. Match Job

  1. Prerequisites: List all jobs using the listjobs command. All entries are displayed in JobListPanel.

  2. Matching a job to candidates while all jobs are listed

    1. Test case: matchjob 1 Expected: All candidates matching the first job are displayed in the PersonListPanel.

    2. Test case: matchjob 0 Expected: No change is seen in the panels. Error message shown in the status message.

    3. Other incorrect delete commands to try: matchjob, matchjob x (where x is larger than the list size)
      Expected: Similar to previous.