PROJECT: U-Schedule


Overview

U-Schedule is a desktop calendar application for university students written for the project for the module CS2103 Software Engineering. The user interacts with it using a CLI, and it has a GUI created with JavaFX for user feedback. It is written in Java, and has about 10 kLoC.

Summary of contributions

  • Major enhancement: added fuzzy search with filters

    • What it does: Allows the user to search for commands without having to type the exact matching words - e.g. "find event proj" to find a calendar event with "Project" in its name. Also allows the user to filter their search by date and time, and by tags, for more precise searching. Finally, it displays the most relevant results first.

    • Justification: This feature improves the product significantly because it allows users to search for events more conveniently and more precisely. This would improve the overall user experience as well as the ease of navigation for users of U-Schedule, since the search function also makes the edit and select functions more convenient.

    • Highlights: This feature expands upon the existing find command in Address Book (Level 4), by revamping the Parser and Model to allow extra functionality (filtering by multiple Predicates and Sorting). This feature has the potential to be extended further, to allow users to filter the search by even more criteria or to incorporate more fine-grained filtering with boolean logic.

    • Credits: Uses third party library fuzzywuzzy for the fuzzy search

  • Minor enhancement: added feature that switches the GUI to the appropriate tab - either To Do List or Calendar Events - when a todo or event command is entered respectively

  • Code Contributed

  • Other contributions:

    • Project management:

      • Set up Travis and AppVeyor CI, Coveralls and Netlify for our team repo (Pull requests: #5, #49)

    • Enhancements to existing features:

      • Modified classes in the original AddressBook app to help transform it into our U-Schedule app. e.g. renaming AddressBook to Scheduler, modifying CalendarEvent and the various parsers to accept and store events' start and end date/time (Pull Requests: #11, #39)

      • Updated the GUI color scheme (Pull request #111)

      • Wrote additional tests and test cases for existing features, increasing coverage to 91% (Pull request #131)

    • Documentation:

      • Modified the Developer Guide (Architecture, Logic, Model and Storage sections), including the class and sequence diagrams, to reflect the modifications we made to the original AddressBook (Level 4) (Pull Requests: #119, #122)

    • Community:

      • PRs reviewed (with non-trivial review comments): #17, #60, #67, #78

      • Reported bugs and suggestions for other teams in the class (examples: 1, 2)

Contributions to the User Guide

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

Searching for events: find event

Finds calendar events whose title, venue or description match any of the given keywords. Also optionally filters the search by the specified date & time interval as well as the specified tags.
Format: find event [KEYWORD MORE_KEYWORDS…​] [from/DATE & TIME] [to/DATE & TIME] [tag/TAG] [tag/MORE_TAGS]…​

  • Shows list of events that match the keywords and fulfil the filter rules.

  • The most relevant results are shown at the top.

  • At least one of the optional fields must be provided.

  • Fuzzy Keyword Search

    • The search is case insensitive for both keywords. e.g lecture will match Lecture

    • The order of the keywords does not matter. e.g. CS2103 Lecture will match Lecture CS2103

    • The fuzzy search allows for partial matches for keywords. e.g. 2103 lec will match CS2103 Lecture.

      • Slight differences and typos will also usually be matched. e.g. s2130 Lecutre will match 'CS2103 Lecture'

  • Filter by Date & Time

    • Shows events that occur in the interval from the from date & time, to the to date & time.

      • i.e. the event must be ongoing at some point in the time interval starting at the from date and ending at the to date

    • You can choose to include only one of the 2 (from or to) date & times

    • Date & time are parsed using natural language parsing, thus they can be input in most human-readable formats, e.g. 16 nov 8am

      • Note: sometimes date & time inputs may be parsed in an unexpected manner - see Question 3 in the FAQ

  • Filter by Tags

    • In order for an event to be shown, it must have a tag that is exactly matching (although case-insensitive) with the input tag.

    • If multiple tags are input, the filter will only show the events with ALL the input tags.

Examples:

  • find event lecture
    Returns list of events whose title, venue or description matches the keyword lecture.

  • find event cs2103 assignment tag/Project
    Returns list of events matching the keywords cs2103 and assignment, and having the tag Project

  • find event exam from/29 nov 8am to/7 dec 5pm Returns list of events matching the keyword exam, that is ongoing at some point between 8am on 29th November and 5pm on 7th December.

  • find event project tag/CS2103 from/15 nov 10am ` Returns list of events matching the keyword `project, having the tag CS2103, that is ongoing at some point after 10am on 15th November.

  • find event tag/cs2103 tag/lecture Returns list of events having both the tags 'CS2103' and 'Lecture' (i.e. events with only the CS2103 tag but not the Lecture tag would be excluded).

Contributions to the Developer Guide

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

Fuzzy Search and Filter by Date/Time and Tags

Current Implementation

Overview

The Fuzzy Search enhancement for the find event command is achieved by modifying the Predicate used to filter the FilteredList already present in the ModelManager, by using a fuzzy match algorithm to check the similarity of the event’s Title, Description and Venue text with each input keywords, and seeing if it exceeds a given threshold.

The Filter by Date/Time and Tags feature is achieved by allowing the aforementioned FilteredList to accept multiple predicates at a time, by taking the logical AND of all the input predicates.

The feature to show the most relevant search results first is achieved by sorting the FilteredList, by wrapping it with a SortedList, and changing the Comparator used based on the input command.

The FindEventCommandParser and the ArgumentTokenizer were modified to accept commands that may or may not have keywords (i.e. preamble), and handle having varying numbers of prefixes in each command.

Finally, the GUI CalendarPanel was modified to access the new SortedList instead of the FilteredList so as to display the most relevant results first.

Model - Predicates, Comparators and SortedList

To allow for fuzzy matching, the fuzzywuzzy API was used. This allows us to compare strings using using a fuzzy algorithm based on Levenshtein distance between the strings. It assigns an integer match score from 0-100 for each pair of strings. If this score was above the threshold of 70, the retooled FuzzySearchFilterPredicate would allow it to be seen. This score was also used to compare events in our FuzzySearchComparator.

To implement the extra filters in conjunction with the fuzzy search, the UpdateFilteredCalendarEventList method was modified to allow for variable number (at least 1) of input Predicate<CalendarEvent> arguments. The method would then combine the input Predicates into a single Predicate by taking their logical AND. The combined Predicate would then be used to filter the FilteredList.

To allow users to filter by date/time, a new predicate, DateTimePredicate was created with 2 DateTime objects to set the dateFrom and dateTo for the filter. The DateTime objects were allowed to be null - this allowed for only 1 DateTime (either from or to) to be specified, so as to reduce the restrictions on users. If both DateTime objects were null, it would always return true - allowing it to be AND-ed with the other Predicates by the ModelManager without interfering with the filters.

To allow users to filter by tags, a new predicate, TagsPredicate was created. This stored a Set of input tag Strings, and if all of them had a full (but case-insensitive) match with at least one of the event’s Tags, then the event would be shown. The Set is allowed to be empty - similar to the implementation of the DatePredicate, this case would always return true.

The existing ModelManager attributes and methods were insufficient to show the most relevant search results first, as this would require sorting. As a result, we created a new FuzzySearchComparator to sort the CalendarEvents based on the aforementioned fuzzy match score, in descending order. In order to let users see this, we created a new SortedList to the ModelManager, to sort the existing FilteredList based on the FuzzySearchComparator.

Logic - ArgumentTokenizer and Parser

The new FindEventCommand required modifications to the parser to allow for commands with varying levels of inputs - it could have preamble and prefixes, only preamble with no prefixes or no preamble and only prefixes. In the latter case, we noticed that the ArgumentTokenizer would end up capturing the initial prefix as the "preamble". In order to correctly detect the absence of a preamble, the ArgumentTokenizer was modified, so as to correctly detect the absence of a preamble (i.e. when a prefix is at index 0 of the input arguments), and accordingly not add the 'preamble' (actually the first prefix) to the ArgumentMultimap.

After modifying the ArgumentTokenizer, the FindEventCommandParser also needed to be modified to generate the correct FindEventCommand from the inputs. As mentioned earlier, for each of the Predicates and Comparators, we implemented a 'default case', when the Predicate always returned true and the Comparator always returned zero - e.g. when both the DateTime for the DatePredicate are null, or when the List of keywords for the FuzzySearchFilterPredicate and FuzzySearchComparator are empty.

This allowed us to deal with the cases where some of the inputs were missing. If the user did not enter any tag/ prefix, then the FindEventCommand created by the FindEventCommandParser will have a TagPredicate containing an empty Set of tag strings.

GUI

In order for the new search features to be visible to the user, the getFilteredAndSortedCalendarEventList method was modified to allow the GUI CalendarPanel to access the SortedList rather than the FilteredList when initializing its ListView.

Tabbed Interface

Since the left panel would be shared between the TaskListPanel and the CalendarPanel, we realised that sometimes the panel would be showing the wrong list when a command is entered. Hence, after updating the FilteredList and SortedList, we made the execute method of the FindEventCommand post a SwitchToSearchTabEvent to the EventsCenter. The MainWindow handles the SwitchToSearchTabEvent and switches to the Calendar Events tab, so that the user can see the search results.

Design Considerations

Aspect: How Predicates are Managed and Applied to the FilteredList
  • Alternative 1 (current choice): Use a simple rule (AND) to combine all the Predicates into a single one, then apply them to the FilteredList as normal.

    • Pros: Easy to implement, since it mostly leverages existing functionality (only need to modify 1 method). As such it is also less likely to lead to regression errors.

    • Cons: Only capable of limited functionality

  • Alternative 2: Use a separate class to store and manage the Predicates, combine them in the desired fashion and apply the resulting Predicate to the FilteredList.

    • Pros: Will be capable of combining multiple Predicates in various different ways, allowing for more complex boolean expressions (see the next section for possible implementations)

    • Cons: More difficult to implement, since it would add an extra layer of integration between the new class and the ModelManager. Would also need to ensure that the list of predicates is appropriately reset when each new command is entered.

Aspect: How Best to Show the User the Most Relevant Search Result
  • Alternative 1 (current choice): Sort the FilteredList of CalendarEvents in descending order of fuzzy match score

    • Pros: More reliable, has high chance of achieving the desired goal

    • Cons: Slower (since it needs to sort), more complicated to implement

  • Alternative 2: Apply a more aggressive filter (with higher threshold for acceptance) to hide less relevant results

    • Pros: Faster than sorting

    • Cons: Can be difficult to calibrate the filters properly to achieve the desired result. May end up being too aggressive, and could remove correct/desired results in some scenarios.

Further Extensions

Additional Filters

The FindEventCommand could be enhanced by including more Predicates to further filter the events based on other properties. For instance, we could allow users to specifically filter based on whether Venue or Description matched an input keyword, e.g. find event v/COM2 or find event d/Wear formal clothes.

Custom Filters using Boolean Logic

The FindEventCommand could be modified to allow users to specify custom filters using boolean logic, e.g. a possible command could be find event t/exam && v/MPSH5 or find event t/lecture || d/lecture. This would allow users to perform much more fine-grained searches. It is likely to appeal to our users, since they are more tech-savvy university students who are comfortable with CLI, and thus are probably somewhat familiar with boolean logic.

In order to implement this feature, we could create a new class specifically to take in the list of input Predicates as well as the specific rules by which to apply the AND/OR, and then to generate the appropriate predicate based on those instructions. This could then be fed to the ModelManager to update the FilteredList.