Developer Guide
Introduction
Welcome! We are excited that you want to learn about TBM. TBM is a specialised contact management system targeted at business people with many international clients.
This page contains step-by-step instructions on how to get started in contributing or getting yourself involved in this awesome project! However, before you get too excited, allow us to introduce you to the core concepts that underpins the heart of TBM.
Travelling BusinessMan Design Concepts
-
Lightweight/Portable: TBM has a small footprint and runs fast on any platform.
-
Scalable: Software which does not deteriorate in performance with increasing data sizes.
-
Extensible: A community driven software development process, which encourages innovative inputs and contributions from our users. Features can be easily integrated into the application when needed.
-
Privacy: We do not collect, store or misappropriate our client’s data.
-
CLI Optimised: Built by users for users. Our features in the application cater to users highly accustomed to the CLI.
These concepts are the foundations of TBM, furthermore we believe in the value of having users be active contributors to our project. With that being said, this guide will help orient you eager developers quickly, to things like:
-
Software design choices of TBM.
-
Architecture of TBM.
-
Implementations for the array of features TBM offers.
Table of Contents
- Introduction
- Table of Contents
- Setting up, getting started
- Design
- Implementation
- Documentation, logging, testing, configuration, dev-ops
- Appendix A: Requirements
- Appendix B: Instructions for manual testing
- Appendix C: Effort
Setting up, getting started
Refer to the guide: Setting up and getting started.
Design
Architecture
The Architecture Diagram given above explains the high-level design of the App. Given below is a quick overview of each component.
.puml
files used to create diagrams in this document can be found in the diagrams folder. Refer to the PlantUML Tutorial at se-edu/guides to learn how to create and edit diagrams.
Main
has two classes called Main
and 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 methods where necessary.
Commons
represents a collection of classes used by multiple other components.
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 concrete
{Component Name}Manager
class (which implements the corresponding APIinterface
mentioned in the previous point.
For example, the Logic
component (see the class diagram given below) defines its API in the Logic.java
interface and exposes its functionality using the LogicManager.java
class which implements the Logic
interface.
How the architecture components interact with each other
The Sequence Diagram below shows how the components interact with each other for the scenario where the user issues the command client delete 1
.
The sections below give more details of each component.
UI component
API :
Ui.java
The UI consists of a MainWindow
that is made up of parts e.g.CommandBox
, ResultDisplay
, ClientListPanel
, WidgetViewBox
, StatusBarFooter
, 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. - Listens for changes to
Model
data so that the UI can be updated with the modified data.
Logic component
API :
Logic.java
-
Logic
uses theMainParser
class to parse the user command. - This results in a
Command
object which is executed by theLogicManager
. - The command execution can affect the
Model
(e.g. adding a client). - The result of the command execution is encapsulated as a
CommandResult
object which is passed back to theUi
. - In addition, the
CommandResult
object can also instruct theUi
to perform certain actions, such as displaying help to the user.
Given below is the Sequence Diagram for interactions within the Logic
component for the execute("delete 1")
API call.
DeleteCommandParser
should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline reaches the end of diagram.
Model component
API : Model.java
The Model
,
- stores a
UserPref
object that represents the user’s preferences. - stores the TbmManager data.
- exposes an unmodifiable
ObservableList<Client>
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.
Inner workings
The above class diagram shows the inner workings of the Note, Tag, and Country components, as well as their associations with the Client component.
Storage component
API : Storage.java
The Storage
component,
- can save
UserPref
objects in json format and read it back. - can save the TbmManager data in json format and read it back.
Common classes
Classes used by multiple components are in the seedu.address.commons
package.
Implementation
This section describes some noteworthy details on how certain features are implemented.
Associating Notes and Tags
The proposed association mechanism between Tag
and Note
objects is facilitated by TagNoteMap
.
It is stored internally within the TbmManager
object. Additionally, it implements the following operations:
-
TagNoteMap#getTagsForNote()
— Returns an unmodifiable set of Tags associated to a particular Note. -
TagNoteMap#updateTagsForNote()
— Returns an unmodifiable set of Notes associated to a particular Tag . -
TagNoteMap#initTagNoteMapFromNotes()
— Initialises the TagNoteMap from a set of Notes.
The TagNoteMap#initTagNoteMapFromNotes()
is exposed in the Model
interface as Model#initialiseTagNoteMap()
.
Given below is an example usage scenario and how mapping mechanism behaves at each step.
TagNoteMap
’s storage had two alternative implementations of our choosing:
-
Create JSON adaptations of the association class which holds the relevant many-to-many relationship so that it could simply be loaded upon application start-up.
-
Programatically initialise a single
TagNoteMap
object at application start-up.
Choosing between 1 and 2 has a trade-off between storage-file-size and some overhead operation for initialising at startup. We realised that the file size issue would be a bigger issue and our tests showed that initialising has no visiable impact on the User Experience hence we chose option 2.
The sequence diagram below highlights the key aspects of initialising TagNoteMap
at Application Start-Up:
Associating Notes and Countries
Implementation
The association between Note
and Country
is facilitated by CountryNotesManager
.
Internally, CountryNotesManager
stores a list of country notes using an instance of javafx.collections.ObservableList<CountryNote>
.
By storing the list of country notes as an ObservableList
, the UI would be able to track and immediately reflect any changes to the country notes list.
It implements the following operations:
-
CountryNotesManager#asUnmodifiableObservableList()
— Returns an unmodifiableObservableList<CountryNote>
. -
CountryNotesManager#hasCountryNote(CountryNote countryNote)
— Returns true if the givencountryNote
already exists in the internalObservableList<CountryNote>
. -
CountryNotesManager#addCountryNote(CountryNote countryNote)
— Adds the givencountryNote
to the internalObservableList<CountryNote>
. -
CountryNotesManager#deleteCountryNote(CountryNote countryNote)
— Deletes the givencountryNote
from the internalObservableList<CountryNote>
.
The following class diagram illustrates how the relevant classes in the Model
component are related to CountryNote
.
Given below is a sequence diagram that shows how the country note add
command works within the Logic
component.
For brevity, the full command country note add c/COUNTRY_CODE nt/NOTE_STRING
will be substituted by country note add
.
Note that the MainParser#parseCommand(userInput)
calls CountryNoteAddCommandParser#parse(userInput)
which in turn parses the user input into a CountryNote
object, and returns an instance of a CountryNoteAddCommand
with the CountryNote
instance passed in as an argument to the constructor of CountryNoteAddCommand
.
Hence, CountryNoteAddCommand
stores a CountryNote
object. For brevity, the aforementioned sequence of method calls will be excluded from the following sequence diagram.
Switching between displaying Country Note List Panel, the Client View, and the default view
Implementation
The mechanism to switch between displaying the Country Note List Panel, the Client View, and the default view is facilitated by the state of the CommandResult
after executing the user command.
CommandResult
implements the following operations that are relevant to the Display Panel:
-
CommandResult#shouldDisplayClient()
— Returns true if the UI should display the client view, otherwise returns false. -
CommandResult#shouldDisplayCountryNote()
— Returns true if the UI should display the country notes view, otherwise returns false. -
CommandResult#shouldResetWidget()
— Returns true if the UI should reset the display panel to its default view, otherwise returns false.
The following activity diagram illustrates what happens to the Display Panel when the user inputs a command.
Suggesting clients
Implementation
The suggestion mechanism is facilitated by filteredClients
and sortedFilteredClients
in ModelManager
. They are instances of javafx.collections.transformation.FilteredList<Client>
and javafx.collections.transformation.SortedList<Client>
respectively. The SortedList
wraps around the FilteredList
, which in turn wraps around the ObservableList
of clients. Any change in the underlying ObservableList
or in the FilteredList
will propagate up to the SortedList
. The two lists implement the following relevant operations:
-
FilteredList<Client>#setPredicate(Predicate<? super Client> p)
— Filters out any clients that do not match the predicate in the list. -
SortedList<Client>#setComparator(Comparator<? super Client> p)
— Sets the comparator for the client list, which will automatically sort the clients in theSortedList
using the comparator.
These operations are exposed in the Model
interface as Model#updateFilteredClientList()
and Model#updateSortedFilteredClientList()
respectively.
The following activity diagram summarizes what happens when a user inputs a client suggest
command.
Given below is an example usage scenario and how the suggestion mechanism behaves at each step.
Step 1: The user executes client suggest by/contract
to list the suggested clients sorted by contract expiry dates. At this point, filteredClients
is showing all clients.
Step 2: The client suggest
command calls Model#updateFilteredClientList()
with the contract expiry date predicate (which checks if a client has a contract expiry date). Model
updates the filteredClients
object with the contract expiry date predicate which filters out all clients without an existing contract expiry date.
Step 3: The client suggest
command calls Model#updateSortedFilteredClientList()
with the contract expiry date comparator (which sorts clients by earliest contract expiry date). Model
updates the sortedFilteredClients
object with the contract expiry date comparator which gives us clients in order of increasing contract expiry date.
Step 4: The change is then propagated to Ui
, which updates the displayed clients in ClientListPanel
.
Step 5: The user decides to execute the command client list
, which resets the filteredClients
objects to have all clients, and resets the sortedFilteredClients
to the default sorting order, and this in turn resets the displayed clients in ClientListPanel
as well.
The following sequence diagram shows how the suggest operation works:
Command History
A command history greatly optimises the user’s productivity by removing the need to repetitively type out similar commands. Furthermore, it allows backwards viewing of previous commands given, which is similar to the CLI experience.
However, the difference between the traditional CLI history and TBM’s CLI history is that TBM’s history only accepts valid commands. Invalid commands entered will not be included will not appear in the command history. Furthermore, the modification of previous commands in the history do not override the current command as well. The current input being edited is stored in a separate variable as the user scrolls through the command history. This is proposed upgrade for the CLI history feature.
The implementation of command history is backed by a list of strings history
. Upon the entering of a valid command, the command history, CommandHistory
.
Below is a step-by-step description of what CommandHistory does.
Step 1: User enters a series of valid commands. CommandHistory
saves all the commands.
Step 2: User presses UP button.
Step 3: CommandHistory
tries to shift the pointer upwards, and retrieves the command string at history[pointer]
.
Step 4: User presses DOWN button.
Step 5: CommandHistory
tries to shift the pointer downwards, and retrieves the command string at history[pointer]
.
- If it reaches the end of the history, the pointer in
CommandHistory
will be of value equal to the length of the history. And it will retrieve the current input being typed.
[Proposed] Modification to command history
The current command history does not allow users to edit a previously entered command, and save it as teh current command being edited.
A further, and closer to CLI implementation would be to support this feature. For example:
-
User enters a series of commands:
client view 1
client view 2
client note add 1 nt/Errands
-
User presses up to a previous command (ii).
-
User modifies the command to
client view 5
. -
User continues to navigate the history.
-
User returns to the command he is modifying, which is
client view 5
.
[Proposed] Undo/redo feature
Proposed Implementation
The proposed undo/redo mechanism is facilitated by VersionedTbmManager
. It extends TbmManager
with an undo/redo history, stored internally as an tbmManagerStateList
and currentStatePointer
. Additionally, it implements the following operations:
-
VersionedTbmManager#commit()
— Saves the current TbmManager state in its history. -
VersionedTbmManager#undo()
— Restores the previous TbmManager state from its history. -
VersionedTbmManager#redo()
— Restores a previously undone TbmManager state from its history.
These operations are exposed in the Model
interface as Model#commitTbmManager()
, Model#undoTbmManager()
and Model#redoTbmManager()
respectively.
Given below is an example usage scenario and how the undo/redo mechanism behaves at each step.
Step 1. The user launches the application for the first time. The VersionedTbmManager
will be initialized with the initial TbmManager state, and the currentStatePointer
pointing to that single TbmManager state.
Step 2. The user executes client delete 5
command to delete the 5th client in TbmManager
. The client delete
command calls Model#commitTbmManager()
, causing the modified state of the TbmManager after the client delete 5
command executes to be saved in the tbmManagerStateList
, and the currentStatePointer
is shifted to the newly inserted TbmManager state.
Step 3. The user executes client add n/David …
to add a new client. The client add
command also calls Model#commitTbmManager()
, causing another modified TbmManager state to be saved into the tbmManagerStateList
.
Model#commitTbmManager()
, so the TbmManager state will not be saved into the tbmManagerStateList
.
Step 4. The user now decides that adding the client was a mistake, and decides to undo that action by executing the undo
command. The undo
command will call Model#undoTbmManager()
, which will shift the currentStatePointer
once to the left, pointing it to the previous TbmManager state, and restores the TbmManager to that state.
currentStatePointer
is at index 0, pointing to the initial TbmManager state, then there are no previous TbmManager states to restore. The undo
command uses Model#canUndoTbmManager()
to check if this is the case. If so, it will return an error to the user rather
than attempting to perform the undo.
The following sequence diagram shows how the undo operation works:
UndoCommand
should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline reaches the end of diagram.
The redo
command does the opposite — it calls Model#redoTbmManager()
, which shifts the currentStatePointer
once to the right, pointing to the previously undone state, and restores the TbmManager to that state.
currentStatePointer
is at index tbmManagerStateList.size() - 1
, pointing to the latest TbmManager state, then there are no undone TbmManager states to restore. The redo
command uses Model#canRedoTbmManager()
to check if this is the case. If so, it will return an error to the user rather than attempting to perform the redo.
Step 5. The user then decides to execute the command client list
. Commands that do not modify TbmManager
, such as client list
, will usually not call Model#commitTbmManager()
, Model#undoTbmManager()
or Model#redoTbmManager()
. Thus, the tbmManagerStateList
remains unchanged.
Step 6. The user executes clear
, which calls Model#commitTbmManager()
. Since the currentStatePointer
is not pointing at the end of the tbmManagerStateList
, all TbmManager states after the currentStatePointer
will be purged. Reason: It no longer makes sense to redo the client add n/David …
command. This is the behavior that most modern desktop applications follow.
The following activity diagram summarizes what happens when a user executes a new command:
Design consideration:
Aspect: How undo/redo executes
-
Alternative 1 (current choice): Saves the entire TbmManager.
- 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
client delete
, just save the client being deleted). - Cons: We must ensure that the implementation of each individual command are correct.
- Pros: Will use less memory (e.g. for
[Proposed] Find by Tags
The use of TagNoteMap
as an association class to keep track of the many-to-many relationship between Tag
and Note
allows for easy addition of a “Find by Tags” feature. TagNoteMap
has tagToNotesMap
and noteToTagsMap
which map a single entity to a collection of all the other associated objects of the other entity.
Hence a valid command syntax of find byTag/TAG_NAME
would be a minimal change to the parsing methods that already exist.
The complication lies in the GUI implementation to display the output of the find by tag. This is because the return values will be both country and client notes (which themselves are associated to countries and clients) so while we might reuse the Display Panel area, we would still a modification of cards to indicate the entity type (Client
or Country
).
Documentation, logging, testing, configuration, dev-ops
Appendix A: Requirements
Product scope
Target user profile: Businesspeople who travel a lot
- has a need to manage a significant number of clients that span multiple timezones
- prefers desktop apps over other types
- can type fast
- prefers typing to mouse interactions
- is reasonably comfortable using CLI apps
Value proposition: Manages notes, client contact details and preferences across multiple countries and timezones
User stories
Priorities: High (must have) - * * *
, Medium (nice to have) - * *
, Low (unlikely to have) - *
Priority | As a … | I want to … | So that I can… |
---|---|---|---|
* * * |
potential user | see the App populated with sample data | easily see how the App will look like when it is in use |
* * * |
user ready to start using the App | purge all current data | get rid of sample/experimental data I used to explore the app |
* * * |
new user | see usage instructions | refer to instructions when I forget how to use the App |
* * * |
user | add contact details of business contacts | |
* * * |
user | add personal preferences of existing business contacts | keep track of them |
* * * |
user | add meeting notes after meeting with existing business contacts | keep track of my meetings |
* * * |
user | add information on business deals and contract expiration dates | let TBM keep track of them |
* * * |
user | filter business contacts by country | |
* * * |
user | add cultural or relevant notes regarding a specific country | refer to these notes in the future |
* * * |
user | view my previously added information regarding a country | |
* * * |
user | see and search for all my clients | easily find and view information on my clients without having to go through the entire list |
* * * |
user | edit my clients’ information | |
* * * |
user | save the information I enter | |
* * * |
user | transfer my data across different computers | |
* * * |
user | manage the priority of business aspects regarding my clients (e.g. expiring contracts) | |
* * * |
user | receive suggestions on which clients to catch up on if I have not done so for quite long | maintain my relationship with my clients |
* * * |
user | view who is likely to be free in other timezones currently | contact them and catch up with them |
* * * |
user | delete a client | remove entries that I no longer need |
* * * |
user | note down and be reminded when my clients have their noteworthy moments (dates) | congratulate them in a timely manner |
* * |
long-time user | archive/hide unused data | not be distracted by irrelevant data |
* * |
user | manage my flight bookings across countries | |
* * |
user | manage my hotel bookings in a particular country | |
* * |
user | visualize my flight and hotel bookings on a timeline | |
* * |
user | filter business contacts by country and state | |
* * |
user | undo and redo | correct my mistakes easily |
* |
user | hide private contact details | minimize chance of someone else seeing them by accident |
* |
user | find clients nearest to my current location | find someone near me to meet |
* |
user | customize the GUI | make it more intuitive to use or more visually appealing |
* |
proficient CLI user | type out my emails straight from the application without moving to the browser | have a faster workflow |
Use cases
(For all use cases below, the System is the **_TBM_**
and the Actor is the user
, unless specified otherwise)
UC1 - Adding a Client
MSS
-
User meets secures a new business deal/client
-
User attempts to add the all associated parties and their information into TBM.
-
User successfully adds the all new information into TBM.
Use case ends.
Extensions
-
2a. A party has a prior entry in TBM, which shows that User has a previous professional encounter with the party.
-
2a1. User does not add the new party as it will duplicate entries in the TBM.
-
2a2. User can choose to update/edit the client information instead.
Use case ends.
-
UC2 - Finding Clients
MSS
-
User requests to find a client.
-
TBM shows a list of clients that match user’s query.
Use case ends.
Extensions
-
1a. The list of clients is empty.
Use case ends.
UC3 - Saving data
MSS
-
User enters a valid command that alters data (E.g. `adding a client (UC1)`).
-
Modified data gets stored in the existing data file.
-
TBM shows a message indicating command has been executed successfully.
Use case ends.
Extensions
-
2a. An error occurred while saving the modified data to the existing data file.
-
2a1. TBM reloads the data from the existing data file.
-
2a2. TBM shows an error message.
Use case ends.
-
UC4 - Filtering by country
MSS
-
User inputs a country as filter.
-
TBM shows all clients belonging to that country.
Use case ends.
Extensions
-
1a. Invalid country is given.
-
1a1. TBM shows an error message.
Use case ends.
-
UC5 - Clearing all entries from TBM
MSS
-
User requests to clear all entries.
-
TBM asks the user to confirm.
-
User confirms that they want to clear all entries.
-
TBM clears all entries.
Use case ends.
Extensions
-
1a. There are no entries.
Use case ends.
-
3a. User decides not to clear all entries.
Use case ends.
UC6 - Obtaining client suggestions
MSS
-
User wants to view clients who are available for a quick call.
-
User passes this criteria via a command.
-
TBM filters the clients, only showing clients who are available.
Use case ends.
Extensions
-
1a. User wishes to obtain suggestions based on a different criteria (E.g. clients whose contracts are expiring).
-
1a1. User passes in a different criteria via the command.
-
1a2. TBM filters and sorts the clients, showing those whose contracts are expiring first.
Use case ends.
-
-
2a. User passes in an invalid criteria.
-
2a1. TBM shows an error message.
Use case ends.
-
UC7 - Noting down information about a client
MSS
-
User requests to add a note to a client.
-
TBM adds the note to the client specified.
-
TBM displays that note.
Use case ends.
Extensions
-
1a. The note is not a valid note.
-
1a1. TBM shows an error message.
Use case ends.
-
Non-Functional Requirements
- TBM should work on any mainstream OS as long as it has Java 11 or above installed.
- 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.
- TBM should be able to recover its previous stable state from the data file if it crashes.
- The data file should be stored locally in a human-editable text file.
- TBM should be used by a single user.
- TBM should be able to hold up to 1000 business contacts without a noticeable sluggishness in performance for typical usage.
- TBM should be able to hold up to 5000 total client notes without a noticeable sluggishness in performance for typical usage.
- TBM can handle at most 10000 business contacts and at most 50000 total client notes.
- TBM will only accept countries that are specified by the ISO3166 specification.
- TBM will only accept the UTC offsets defined in this list.
- TBM is not required to validate that the timezone of a business contact correctly matches his/her country.
- TBM should retain all functionalities even when it is not connected to the internet.
- The size of the TBM JAR file should not exceed 100Mb.
Glossary
- Business Contact: Synonymous with Client
- Client: Refers to a person whom the user is conducting his/her business with
- UTC: Coordinated Universal Time
- Mainstream OS: Windows, Linux, Unix, macOS
- TBM: Initialism for Travelling BusinessMan
Appendix B: Instructions for manual testing
Given below are instructions to test the app manually. Additionally, testers can look through our Testing Guide to get started on automated testing.
Launch
-
Initial launch
-
Download the jar file and copy into an empty folder
-
Double-click the jar file Expected: Shows the GUI with a set of sample clients and notes.
-
Deleting a client
-
Deleting a client while all clients are being shown
-
Prerequisites: List all clients using the
client list
command. Multiple clients in the list. -
Test case:
client delete 1
Expected: First contact is deleted from the list. Details of the deleted contact shown in the results display. -
Test case:
client delete 0
Expected: No client is deleted. Error details shown in the display. -
Other incorrect delete commands to try:
client delete
,client delete x
,...
(where x is larger than the list size)
Expected: Similar to previous.
-
Appendix C: Effort
With an estimate value of 100 to be the level of effort required to develop AB3, we would place the effort required to deliver the current version of **TBM** at 205.
Major changes are changes that required a significant amount of effort (an estimate minimum of 68 man-hours per change) from the team to coordinate, plan, implement, review, and optimize, while minor changes are changes that required a moderate amount of effort (an estimated minimum of 20 man-hours per change).
Major Changes
1. Note Package
To give users the ability to write notes capable of being handled in a generic manner for the entities of Client
and Country
, a Note
class was created and a CountryNote
class extended from it. Client notes could be stored easily by allowing clients to contain client-specific notes and country notes had to be managed by the CountryNotesManager
controller class to handle the mapping between unique Country
objects and their associated Notes.
UniqueTag
objects were implemented by having a TagNoteMap
class that keeps track of the association between unique Note
and Tag
objects. Storage of this many-to-many relationship required effort in circumventing the no-DBMS constraint imposed by the module. We chose to programmatically generate the TagNoteMap
object upon TBM’s start-up with the aim of reducing file-size of the JSON data files on the user’s hard-drive.
2. Additional Client Fields
We refactored the codebase as well as the documentation from Person
in AB3 to Client
in TBM with additional fields to fit our target audience. Adding of simple tags was moved to a new note package that allowed more freedom and flexibility in adding data to clients. We added new fields in Country
, Timezone
, ContractExpiryDate
, LastModifiedInstant
. Country
and Timezone
required non-trivial validation and testing to verify their correctness. Timezone
, ContractExpiryDate
and LastModifiedInstant
are necessary for suggestions. The LastModifiedInstant
class serves as metadata that is not directly exposed to the user but is only updated upon any logical modifications to a client object.
3. Client Suggestion
A ClientSuggestionType
class was created which acts as a controller class that generates the combined client predicate and comparator. For the three suggestion criteria, classes were created where necessary to encapsulate the filtering and sorting logic. The non-trivial implementation consisted of accounting for timezone differences, and the sorting and filtering of JavaFX’s ObservableList
. New classes like Timezone
, ContractExpiryDate
, and LastModifiedInstant
were added to Client
to facilitate prioritizing which clients to suggest to the user.
4. GUI
In line with human-centric UX design, TBM’s GUI was changed to be visually appealing, intuitive, informative, smooth, and have a flexible layout. We made effort to ensure that the layout was consistent and eliminated weird behaviours resulting from limitations of JavaFX. One challenge was that the client cards in AB3 had an undefined behaviour where they changed sizes when being clicked on, even though there is no expected response upon clicking. We fixed this by setting ListViewCell
to disabled
.
5. Automated GUI Testing
As recommended by the reference book for CS2103T, we adopted the TestFX library for automated GUI testing. We modified the GUI to listen to the state of the application to achieve a dynamic display. TestFX provided a good testing framework to automate our GUI unit tests. We simulated user interactions with TestFX for our application’s system testing.
However, the implementation encountered many hiccups due to the limited amount of documentation available. Significant effort was taken to troubleshoot the implementation of the framework and to run it in headless mode, so that we could integrate it into our Github Continuous Integration workflow.
6. Non-GUI Testing
We made sure to write rigorous tests for every piece of code we added, expanded coverage, and tested for boundary cases. We also standardized and abstracted the testing code to make it more maintainable and readable. This, together with GUI testing, managed to increase code coverage from 72% (AB3) to 90%.
Minor Changes
1. Command Format
The command format of TBM had to be modified from AB3 to account for client-related, client-note-related and country-note-related commands.
2. Command Parsing
Extending on the original parsing of AB3, we modified the command parsing to accommodate our command format, and further modularized code in MainParser
to make the codebase easier to read and test.
3. Command History
Keeping in line with TBM’s time-conscious target users who prefer using CLIs, we implemented a command history to provide an authentic CLI experience and to increase user-friendliness.
4. Code Clean-up
Code clean up required a moderate amount of effort thanks to our decision to enforce clean coding practices from the get-go: each PR had to reach a minimum of 80% diff line coverage in terms of testing, had to adhere to the module’s code quality guidelines in terms of variable naming and documentation, and had to go through extensive PR reviews. This spread the code clean-up task over the entire project instead of being an issue in its own regard. We also did an in-depth examination of the whole codebase at the end of v1.4 to standardize all JavaDocs and improve code quality.