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:
-
Did a UI overhaul to provide a sleek and visual appeal look that optimize for users' satisfaction and enjoyment. Infinity Book v1.5 uses this design as the default theme.
-
Managed to wrapped up project properly on GitHub from v1.1 to v1.5
-
Was responsible for updating Readme, UI screenshot, User Guide, Developer Guide to match with the actual product.
-
Community:
-
PRs reviewed (with non-trivial review comments): https://github.com/CS2103JAN2018-W11-B3/main/pull/58, https://github.com/CS2103JAN2018-W11-B3/main/pull/140
-
Reported bugs and suggestions for other teams in the class: https://github.com/CS2103JAN2018-F11-B2/main/issues?utf8=%E2%9C%93&q=is%3Aissue+is%3Aclosed+anh2111+
-
-
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
.
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. |
|
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.
SEIntern
. In the right-most column, the history list includes all statistics you have saved.
|
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]…
|
|
Examples:
* edit -add-tag 1 t/Year2 t/SpeakVietnamese
Adds tag Year2
and SpeakVietnamese
to the first candidate.
-
edit -add-tag 1 t/Year2
Adds a new tagYear2
to the first candidate, but it is not successful since this candidate already has tagYear2
.
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
:
-
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.
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.
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.
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
.
viewreport pop/CS
CommandUI 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).
The EventsCenters
then handles this Event
and then triggers the update of Report
instance (as shown in the below seqence diagram).
Report
instance after the ToggleReportPanelEvent
is postedDesign 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.
SEIntern
reportExplanation: 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.
SEIntern
reportExplanation: 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.
SEIntern
reportExample 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]…
|
Examples:
-
edit -delete-tag 1 t/computing
Deletes tagcomputing
from the first candidate.
-
edit -delete-tag 1 t/computing
Removes tagcomputing
from the first candidate, but it is not successful since this candidate does not have tagcomputing
.
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]
Examples:
- find Alex 93210283
Returns Alex Yeoh
, Charlotte Oliveiro
, and Alex Smith
whose names and phone numbers match with the given keywords.
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:
-
FindCommandParser
class will extract keywords from user input, form a predicate, then pass it toFindCommand
class. -
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);
}