PROJECT: Infinity Book


Overview

Infinity Book (IB) is an address book application for Tech recruiters, providing end-to-end support, from searching for candidates,to adding job postings and saving their resumes and interviews.

Summary of contributions

  • Major enhancement: Implementation of Dashboard Reporting and Tagging features.

    • Recruiters may have to screen through one candidate’s profile many times, which costs time. Thus, I improved edit command so that recruiters can add new tags or remove current tags from candidates. This enables them to summarize each candidate in a few keywords to save time for their future reference.

    • Recruiters need to make decisions based on candidates' information in their contact list. But their list is overwhelmingly long, so I implemented Report feature showing them statistics from Tags they have added so that recruiters can have an idea of what is happening without going through each record.

    • Highlights: This enhancement touches multiple components: Model, Logic, Storage, and UI.

  • Minor enhancement: Improvement of Find command.

    • Since Infinity Book v1.2, recruiters can find candidates by their name, email, phone, and tags.

    • Justification: Given the huge number of contacts that a recruiter has, it is not possible for them to remember candidates' name. Hence, recruiters must be able to search by other fields so that it is easier for them to locate a person.

  • Code contributed: [Functional code][Test code]

  • Other contributions:

Contributions to the User Guide

Given below are sections I contributed to the User Guide, which showcase my ability to write documentation targeting end-users.

Dashboard Reporting

Infinity Book is tailored to optimize recruiters' speed. Aligning with this vision, Report feature offers you an easy way to get statistics of the group of candidates tagged with a specific tag (which we call Population Tag) in few keystrokes.

Each report is identified with a Population Tag:

  • Represented with the prefix pop/ followed by the tag name of the Population Tag. Example: pop/SEIntern.

  • The name of Population Tag must be an alphanumeric string.

In the report, you will find statistics of the group of all candidates tagged with Population Tag:

  • A bar chart showing all other tags owned by this group and number of owners for each tag.

  • A pie chart illustrating the numerical proportion of each tag.

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

An example usage can be found in Appendices.

Viewing a report : viewreport or vr [Since v1.4]

Want to have bird’s view statistics of a certain group of candidates? View a report using viewreport or vr command.

Format: viewreport pop/POPULATION_TAG or vr pop/POPULATION_TAG

Example:
viewreport pop/SEIntern
Displays statistics for candidates tagged with SEIntern.

viewreport
Figure 1. Infinity Book has displayed Report with Population Tag SEIntern in the right-most column.

When you execute a Report command, Infinity Book will show you a bar chart, a pie chart, and a history list in the right-most column.

  • If there are no candidates tagged with the Population Tag you provide, Inifinity Book will not show you the pie chart.

  • If you provide multiple POPULATION_TAG, Infinity Book will take the last one. For example, when you execute savereport pop/SEIntern pop/Computing, Infinity Book will display the report for Computing.

Saving a report : savereport or sr [Since v1.4]

Wish to save the current statistics for future reference? Saving the report using savereport or sr command.

Format: savereport pop/REPORT_NAME or sr pop/REPORT_NAME

Example:
savereport pop/SEIntern
Saves the current statistics for candidates tagged with SEIntern at this current time. Infinity Book will add a new entry to the history list in the report.

savereport
Figure 2. Infinity Book has saved Report with population Tag SEIntern. In the right-most column, the history list includes all statistics you have saved.
  • Date format is in "hh/mm/ss mm/dd/yyyy"".

  • To keep transparency, saved reports in Inifnity Book are immutable, which means you cannot edit or delete saved reports. You also cannot undo savereport command.

If you provide multiple POPULATION_TAG, Infinity Book will take the last one. For example, when you execute viewreport pop/SEIntern pop/Computing, Infinity Book will save the report for Computing.

Add new tags to a person : edit -add-tag

Infinity Book helps you process candidate’s information faster. Summarize each candidate in a few keywords using the edit -add-tag command to save time for your future reference.

Format: edit -add-tag INDEX [t/TAG]…​

  • Each candidate:

    1. Can have any number (including zero) of tags.

    2. Cannot have duplicated tags. Tag name comparision is case sensitive, which means the list of tags of a candidate can be Java JAVA JAVa, for example, but cannot be JAVA JAVA.

    3. Cannot have tags with empty tag name.

  • Ensure that the specified INDEX is a positive integer that is smaller or equal to the total number of candidates shown in the list.

  • When you provide invalid tags, Infinity Book will ignore them. To verify, you can look at all tags shown under candidate’s name in the list.

Examples:
* edit -add-tag 1 t/Year2 t/SpeakVietnamese
Adds tag Year2 and SpeakVietnamese to the first candidate.

canaddtag
Figure 3. Added new tags to the first candidate.
  • edit -add-tag 1 t/Year2
    Adds a new tag Year2 to the first candidate, but it is not successful since this candidate already has tag Year2.

cannotaddtag
Figure 4. 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.

You can find my other contributions to User Guide: Delete Tags and Improved Find command in Appendices.

Contributions to the Developer Guide

Given below are sections I contributed to the Developer Guide, which showcase my ability to write technical documentation and the technical depth of my contributions to the project.

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.

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 5. 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 6. 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 7. 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 8. 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 9. Update Report instance after the ToggleReportPanelEvent is posted

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.

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.

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.

You can find my other contributions to Developer Guide.

Appendices

Other contributions to the User Guide

An example usage of Report

An example usage:

You are recruiting Software Engineering interns, and you want to keep track of the number of candidates in each stage: Screening, Interviewing, Offered, or Rejected. Given the a significant pool of applicants, this task is tedious, and our Report feature is a solution.

Using our Report with Population Tag SEIntern,

  • To ensure your team keeps up with deadlines, you can monitor the whole recruiting pipeline using Report bar chart.

barchart
Figure 10. Bar chart in SEIntern report

Explanation: there are 11 candidates tagged with SEIntern in which 5 of them are tagged with Screening, 3 of them are tagged with Interviewing, 1 of them is tagged with Rejected, and 2 of them are tagged with Offered.

  • To see the progress of your interns recruitment, you can view percentages of candidates in each stage using Report pie chart.

piechart
Figure 11. Pie chart in SEIntern report

Explanation: there are 11 tags that candidates tagged with SEIntern has in total, and 45% of them are Screening, 27% are Interviewing, 18% are Offered, and 9% are Rejected.

  • To analyze your team’s productivity, you can track the speed at which tasks are done using the history list.

history copy
Figure 12. History list in SEIntern report

Example in the firgure: At 14:10:24 04/06/2018, there are 9 candidates tagged with Screening in Infinity Book, then at 16:36:55 04/12/2018, there are only 5 Screening candidates left.

Date format is in "hh/mm/ss mm/dd/yyyy"".

Delete tags from a person : edit -delete-tag

Some tags are no longer seem to be relevant to the candidate? Remove them using the edit -delete-tag command.

Format: edit -delete-tag INDEX [t/TAG]…​

  1. Tag name comparision is case sensitive, which means when the first candidate has tags COMPUTING computing, for example, edit -delete-tag 1 t/computing will only remove tag computing.

  2. Ensure that the specified INDEX is a positive integer that is smaller or equal to the total number of candidates shown in the list.

Examples:

  • edit -delete-tag 1 t/computing
    Deletes tag computing from the first candidate.

candeletetag
Figure 13. Remove tags from the first candidate.
  • edit -delete-tag 1 t/computing
    Removes tag computing from the first candidate, but it is not successful since this candidate does not have tag computing.

cannotdeletetag
Figure 14. Cannot delete tag computing from the first candidate. The candidate’s details will not be updated, and Infinity Book will not show any warning.

Finding candidates by name, phone, email, and tags: find

Finds candidates whose names, phone numbers, emails, or tags contain any of the given keywords.

Format: find KEYWORD [MORE_KEYWORDS]

  • The search is case insensitive. e.g hans will match Hans.

  • The order of the keywords does not matter. e.g. Hans Bo will match Bo Hans.

  • Only candidate’s name, phone number, email, and tags are searched.

  • Only full words will be matched e.g. Han will not match Hans.

  • Candidates matching at least one keyword will be returned (i.e. OR search). e.g. Hans Bo will return Hans Gruber, Bo Yang

Examples: - find Alex 93210283
Returns Alex Yeoh, Charlotte Oliveiro, and Alex Smith whose names and phone numbers match with the given keywords.

Find

Figure. Person list after finding with keywords Alex and 93210283.

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.

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

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.

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]…​

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