By: AY1920S2-CS2103T-F11-2
Since: Feb 2020
Licence: MIT
- 1. Setting up
- 2. Design
- 3. Implementation
- 3.1. Logging
- 3.2. Configuration
- 3.3. Command History --- Aaron Choo
- 3.4. Command Autocomplete --- Aaron Choo
- 3.5. Scheduling for a Client --- Ng Ming Liang
- 3.6. Exercise Feature --- Yong Jie
- 3.7. Personal Best --- Li Zi Ying
- 3.8. Graph --- Li Zi Ying
- 3.9. Filtering the list of clients --- Toh Ker Wei
- 3.10. Viewing the information of a client --- Toh Ker Wei
- 4. Documentation
- 5. Testing
- 6. Dev Ops
- Appendix A: Product Scope
- Appendix B: User Stories
- Appendix C: Use Cases
- Appendix D: Non Functional Requirements
- Appendix E: Glossary
- Appendix F: Instructions for Manual Testing
- F.1. Launch and Shutdown
- F.2. Command autocomplete
- F.3. Deleting a client
- F.4. Adding an exercise
- F.5. Saving data
- F.6. Filtering the client list
- F.7. Adding new client into FitBiz
- F.8. Scheduling clients
- F.9. Listing all clients
- F.10. Editing a client
- F.11. Viewing a client
- F.12. Viewing a graph of an exercise
1. Setting up
Refer to the guide here.
2. Design
2.1. Architecture
The Architecture Diagram given above explains the high-level design of the App. Given below is a quick overview of each component.
The .puml files used to create diagrams in this document can be found in the diagrams folder.
Refer to the Using PlantUML guide to learn how to create and edit diagrams.
|
-
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.
The following class plays an important role at the architecture level:
-
LogsCenter
: Used by many classes to write log messages to the App’s log file.
The rest of the App consists of four components.
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.
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 delete-c 1
.
delete-c 1
commandThe sections below give more details of each component.
2.2. UI component
API : Ui.java
The UI consists of a MainWindow
that is made up of parts e.g.CommandBox
, ResultDisplay
, ClientListPanel
, StatusBarFooter
etc. All these, including the MainWindow
, inherit from the abstract UiPart
class.
GraphWindow
is a separate Stage
from MainWindow
. MainWindow
creates a GraphWindow
through a graph
command.
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.
2.3. Logic component
API :
Logic.java
-
Logic
uses theFitBizParser
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 or deleting 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 the list of clients or exercise graphs.
Given below is the sequence diagram for interactions within the Logic
component for the execute("delete-c 1")
API call mentioned previously.
delete-c 1
Command2.4. Model component
API : Model.java
The Model
,
-
stores a
UserPref
object that represents the user’s preferences -
stores a
ClientInView
to represent the current selectedClient
selected by theview-c
command -
stores
FitBiz
-
stores
Client
,Sports
,Tag
,Schedule
andExercise
packages, whereClient
utilises the latter packages as attributes -
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.
As a more OOP model, we can choose to store a Tag list in FitBiz , which Client can reference. This would allow FitBiz to only require one Tag object per unique Tag , instead of each Client needing their own Tag object. An example of how such a model may look like is given below. For simplicity, we have omitted the rest of the attributes that Client has. Refer back to the diagram above for more details. |
2.5. Storage component
API : Storage.java
The Storage
component,
-
can save
UserPref
objects in json format and read it back. -
can save
Client
,Exercise
andSchedule
data in json format and read it back.
2.6. Common classes
Classes used by multiple components are in the seedu.addressbook.commons
package.
3. Implementation
This section describes some noteworthy details on how certain features are implemented.
3.1. 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 3.2, “Configuration”) -
The
Logger
for a class can be obtained usingLogsCenter.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
3.2. Configuration
Certain properties of the application can be controlled (e.g user prefs file location, logging level) through the configuration file (default: config.json
).
3.3. Command History --- Aaron Choo
This feature serves to improve the user experience by allowing users to browse and retrieve their previously entered commands using the ↑ and ↓ arrow keys, similar to what most modern CLIs offer.
3.3.1. Implementation
This command history mechanism is facilitated by the logic class CommandHistory
, which controls both the model class CommandHistoryState
and the storage utility class StorageReaderWriter
.
Behaviour of this feature
The behaviour of this feature has been implemented to mimic most modern CLIs, namely:
-
The empty string,
""
, should not be stored in the history -
Commands that are similar to the most recently stored command in the history should not be stored (ie. duplicate commands will not be stored)
-
All other user input, be it valid or invalid commands, should be stored
-
Number of commands should only be stored up to a well-defined maximum number (100 in this case, for performance reasons discussed in the later section)
-
Pressing the ↑ arrow key should browse backwards towards the least recently entered commands
-
Pressing the ↓ arrow key should browse forwards towards the most recently entered commands
-
The caret position should be at the end of the command string when browsing the history
-
Persistent storage of the command history should be supported (ie. a user can quit the app and come back to the same history as his previous usage of the app)
How this feature works
Since all user inputs, be it valid or invalid commands, should be stored, and since detection of the ↑ and ↓ arrow keys must occur in the JavaFX’s TextField
class found in CommandBox
, we have decided to let CommandBox
directly interact with CommandHistory
. In other words, CommandBox
will be responsible for calling CommandHistory#addToHistory
, CommandHistory#getNextCommand
, and CommandHistory#getPreviousCommand
. A simplified class diagram of the classes involved in this feature is given below:
CommandHistory depends on FileUtil only because it uses the static method FileUtil#writeToFile .
|
In the following sequence diagram, we trace the execution of the classes involved in storing the user command into the command history. For this example, we assume the user is entering the command list-c
:
When the CommandBox#handleCommandEntered
method is called, CommandBox
simply gets and passes the user input text from TextField
to CommandHistory
. CommandHistory
then adds this text to the internal list within CommandHistoryState
, retrieves the full internal list, converts it to a text-based format, and finally requests FileUtil
to save the text-based command history to storage.
How the Command History is persisted on storage
Each command that the user enters is essentially just a normal string. We simply use the utility class FileUtil
to write these lines of text to a text-based file command.txt
. Note that each new line of text in command.txt
represents one single command.
Whenever FitBiz first launches, we will then try to open and read from this same command.txt
file. If no such file exists, an empty new file will be created for use in the future.
Even if the storage component somehow fails to work, the command history will still be guaranteed to work, albeit without the storage features. In other words, the CommandHistoryState model will continue to function since it is not dependent nor have any association with the utility class FileUtil . This ensure that the command history for the current usage can at least be used.
|
3.3.2. Design Considerations
In designing the model CommandHistoryState
, we had to decide on the underlying data structure to store the user’s command history. We currently use the Java native ArrayList<String>
, where each line of command is stored as an individual entry. Another alternative that we have considered is to store the commands in a LinkedList<String>
:
Considerations | ArrayList (chosen) | LinkedList |
---|---|---|
Time Complexity |
Inserting to the list is O(1). Removal of the first item is O(n). Retrieval of any item is always O(1). |
Inserting to the list is O(1). Removal of the first/last item is O(1). Retrieval of an item that is not the first/last item will require traversal of the list and will be more expensive than O(1). |
Ease of Implementation |
Indices are concrete numbers and thus, are much easier to manipulate than pointers. The use of indices are enough to support the retrieval operations needed by this feature and is efficient since retrieval is always O(1). |
Pointers are arguably harder to keep track of and might be more difficult to implement. A custom linked list (as opposed to just using the native Java |
In the interest of saving developement time and better code readability, we decided to use an ArrayList
to store the commands. Since we have decided to cap the maximum size of the list, should this limit be exceeded, we would then need to remove the first item (or the zeroth index) from the list to free up space. Of course, doing a remove(0)
on a n-item ArrayList
will require that all remaining items in the list be reassigned to new indices, and thus incur an O(n) time operation. However, we found out through extensive testing that this causes no observable nor significant lag when the maximum capacity is reached.
Moreover, there is also a need to overwrite the whole storage file command.txt
whenever this maximum size is reached. Before this maximum size is reached, we can easily append to the existing file the new command that the user has just entered. However, after this limit is exceeded, we must remove the first line stored in command.txt
, shift all remaining lines up, and then append that new line. Hard disk operations like writing to storage is many order of magnitudes slower than memory operations like the reassignment of indices as discussed above. Since the much larger bottleneck is in the storage, this effectively nullifies the time complexity comparison that a LinkedList
is faster than an ArrayList
in removing the first item.
In choosing the maximum size of the command history, we have to take note of some important caveats:
-
This number must be small enough to not cause the app to lag when the whole history is being written to storage
-
This number must be big enough to satisfy the user
Ultimately, we felt that 100 is a very generous estimate given that a user really only needs the past few commands at any point of time.
3.4. Command Autocomplete --- Aaron Choo
Similar to the previously mentioned Command History feature, this feature also serves to improve the user experience by allowing users to press the Tab key to autocomplete their partially entered commands.
3.4.1. Implementation
This feature is facilitated by the logic found in the Autocomplete
class. Before we dive into the implementation, let us first define what unambiguous and ambiguous commands are:
Unambiguous Commands | Ambiguous Commands |
---|---|
Can uniquely identify a single command using the sequence of letters that the user has entered |
Cannot uniquely identify a single command using the sequence of letters that the user has entered |
For example, assume we only have 3 commands in our app, add-c , add-e , and edit-c . If the user enters e and tries to autocomplete the command using Tab, we say that this is an unambiguous command since clearly, edit-c can be uniquely identified by e . If instead, the user enters a and presses Tab to autocomplete the command, we say that this is an ambiguous command, since both add-c and add-e are possible choices.
|
Behaviour of this feature
Again, this feature has also been implemented to mimic most modern CLIs, namely:
-
Any unambiguous commands should be immediately completed upon pressing of the Tab key
-
Any ambiguous commands should be completed up till the longest common prefix of all similar commands
-
Using the ambiguous command example in the introduction above, when the user enters
a
and presses Tab, the autocompletion should returnadd-
(the longest common prefix ofadd-e
andadd-c
) to the user
-
-
A list of all similar commands should be presented to the user should he try to autocomplete an ambiguous command
-
Pressing Tab when the command has already been completed will bring the user’s caret to the next prefix delimitter (
/
in our case) with wraparound
How the Trie data structure works
Since Java does not provide a native Trie data structure, we had to implement our own version of it. Moreover, Java also does not allow methods with multiple return values, and thus, we had to create a wrapper class SimilarWordsResult
to store the multiple results returned by Trie#listAllSimilarWords
. In this section, we shall take a more in depth look at the overall implementation of this data structure.
We first look at the Node
class provided in the same package which Trie
relies on. Each Node
object should contain the following attributes:
-
The parent node (
null
if the node is the root of the Trie) -
The current letter it represents
-
The children nodes (if any)
-
A boolean to know whether that node represents a completed word
Since each node stores with it their parent node pointer, we can easily construct the word represented by a node by recursively building the word up letter by letter until the root is reached. This is implemented in Node#constructWord
, as shown here:
public String constructWord() {
if (isRoot()) {
return EMPTY_STRING;
}
return parent.constructWord() + getLetter();
}
Now, let us discuss about how we implemented Trie
to support the behaviours discussed above by first looking at Trie#getLongestPrefixNode
. This method takes in an argument word
and returns in 3 distinct cases:
-
If the argument
word
matches no words currently in theTrie
:null
-
If the argument
word
is unambiguous: theNode
whose constructed word (usingNode#constructWord
) is the longest word contained inTrie
that can be formed fromword
-
If the argument
word
is ambiguous: theNode
whose constructed word is the longest common prefix of all words similar toword
contained inTrie
Refer to Figure 14, “Activity Diagram for the Autocomplete Logic” given in the next section for the complete sequence of the key decisions. |
Let us move on to Trie#listAllSimilarWords
which makes use of the Node
found by Trie#getLongestPrefixNode
. Cases 1 and 2 discussed above are relatively trivial and we shall not discuss about how they are handled in Trie#listAllSimilarWords
. For case 3, in order for us to find all the similar words, we have chosen to use a Depth-First Search (DFS) approach, starting the search from the Node
returned by Trie#getLongestPrefixNode
, as shown here:
Node subtrie = getLongestPrefixNode(word);
ArrayList<String> similarWords = new ArrayList<>();
Stack<Node> stack = new Stack<>();
stack.push(subtrie);
while (!stack.isEmpty()) {
Node current = stack.pop();
if (current.isWordEnd()) {
similarWords.add(current.constructWord());
} else {
stack.addAll(current.getChildren().values());
}
}
The choice of a DFS approach as opposed to a Breadth-First Search (BFS) approach is arbitrary, both should work as expected. |
How this feature works
Similar to Command History, this feature also relies heavily on the UI class CommandBox
, and thus we have decided to let CommandBox
interact with Autocomplete
directly. A simplified class diagram of the classes involved is shown here:
Autocomplete returns an object of type AutocompleteResult to CommandBox when the Autocomplete#execute is called. As such, both Autocomplete and CommandBox depend on, but are not directly associated with, AutocompleteResult . The same reasoning applies for SimilarWordsResult which have been explained in the earlier section.
|
In the following sequence diagram, we follow the execution for when the user tries to autocomplete his partially entered command gra
(which, in the current application, is an unambiguous command, and will result in the full completion of the graph
command as well as its prefixes):
CommandBox
retrieves the user input command and caret position from the TextField
, and calls the execute
method from Autocomplete
with these information. This execute
method (shown and explained in full in the next sequence diagram) creates an AutocompleteResult
object and returns this to CommandBox
, which retrieves all the information required and sets the TextField
and ResultDisplay
accordingly.
Within the execute
method, Autocomplete
calls the listAllSimilarWords
method from Trie
with the user input text. Trie
, which would already have all the commands stored, finds the longest prefix node, calls the constructWord
method from this node, and checks if this node represents the end of a completed word. Since it is indeed a completed word, Trie
immediately creates a SimilarWordsResult
object to store these information and returns it to Autocomplete
. Then, Autocomplete
retrieves these information, realises that it is dealing with an unambiguous command, and constructs the corresponding prefixes. It then creates a AutocompleteResult
object to store all the information that CommandBox
requires, and finally returns this object to CommandBox
.
Lastly, in order to explain the key decisions that this feature does at each step starting from the point where the user presses Tab, we have provided the following activity diagram:
This feature currently only supports autocompletion of commands and prefixes, and not other fields/parameters like names and addresses that have been used by the user before. Implicitly, since all commands defined in FitBiz do not have empty spaces in them, this allows us to easily determine when to allow users to press Tab to get to the next prefix (behaviour 4): by simply checking for the presence of white spaces from the trimmed user input (like shown in the activity diagram).
3.4.2. Design Considerations
As discussed in the implementation section, we have decided to use a Trie data structure. Of course, we have also considered other much simpler alternatives like simply storing all available commands in a native Java List
. A quick summary of the pros and cons is given here:
Considerations | Trie (chosen) | List |
---|---|---|
Time Complexity |
Searching if a word exists is O(n), where n is the number of letters in the word to search for. Finding the longest common prefix of an ambiguous command is O(n), where n is the number of letters in the original word. |
Searching if a word exists is O(nm), where n is the number of letters in the word to search for, and m is the number of words in the list. Finding the longest common prefix of an ambiguous command is not linear with n and m. |
Ease of Implementation |
Initial development might be more difficult; developers might not be familiar with this data structure as it is not as common. Custom class means that additional, custom logic can be easily added. |
Much faster initial development. Custom logic cannot be easily added. |
As such, the choice of implementing our own Trie data structure is obvious. As this app grows bigger in the forseeable future, the number of commands as well as the number of things we would want to autocomplete would increase. Overall, we felt that the Trie data structure will scale much better as compared to a List.
Exchanging some initial development time for future scalability of our app will ensure that we, or future developers, do not end up wasting time refactoring what could have been done in the first place. Moreover, the Trie data structure is much more effective and computationally inexpensive in finding the longest common prefix of all ambiguous commands. The same cannot be said when using a List.
Also, since we have implemented our own Trie data structure, it would also allow more custom logic to be added later, and allow more creative freedom with respect to the features that we, or future developers would want to add. For example, future version of this application might want to also include the autocompletion of frequently used parameters by the user.
3.5. Scheduling for a Client --- Ng Ming Liang
This feature allows for a user to assign a weekly schedule to a client. Timings are represented in the 24-hour format HHmm
. Each client can have none or multiple schedules that do not have overlapping timings. Multiple clients are allowed to have overlapping timings with each other.
3.5.1. Implementation
This scheduling mechanism is facilitated by ScheduleCommand
which extends Command
. The format of the command is given by:
schedule INDEX sch/DAY-STARTTIME-ENDTIME [sch/DAY-STARTTIME-ENDTIME] …
When using this command, at least one valid complete schedule parameter must be specified. The user can follow up with additional optional valid schedule parameters in order to assign more schedules to the same client. The following 3 examples are all valid usages of the schedule
command:
Example Commands
-
schedule 1 sch/MON-1100-1300
-
schedule 2 sch/MON-1100-1300 sch/TUE-1100-1300 sch/SAT-1800-2000
-
schedule 3 sch/
Elaboration on Example Commands
-
This command instance schedules
Client
with index 1 to have a schedule slot on every Monday, 11:00am to 1:00pm. -
This command instance schedules
Client
with index 2 to have schedule slots on every Monday 11:00am to 1:00pm, Tuesday 11:00am to 1:00pm, and Saturday 6:00pm to 8:00pm. -
This command instance schedules
Client
with index 3 to have no schedule slots, that is essentially clearing the schedule ofClient
with index 3. Thesch/
parameter is required when clearing the schedule.
Do note that the schedule parameters given in the schedule
command will entirely overwrite the client’s current list of schedules.
The list of schedules of each client are structured as a ScheduleList
, which is a wrapper class for an ArrayList
of Schedule
objects. Each Client
contains one ScheduleList
attribute to keep track of all Schedule
assigned to it. If there are no assigned Schedule
for the Client
, then the ScheduleList
simply contains an empty ArrayList
of Schedule
.
Schedule
comprises three attributes:
-
Day
-
StartTime
-
EndTime
Day
wraps the enum DayEnum.Weekday
and represents the day of the week the schedule takes place on.
StartTime
and EndTime
represent the start time and end time of the schedule in the "HHmm" format respectively.
The relations between these classes are shown in the class diagram below.
These attributes are bounded by these characteristics:
-
Each
Client
can only contain uniqueSchedule
, that is, there are no overlaps in timings between any twoSchedule
in theScheduleList
. This is ensured byScheduleCommandParser#checkIfOverlaps()
-
Overlapping timings between the
Schedule
of differentClient
is allowed -
The maximum timeframe between
StartTime
andEndTime
is from0000
to2359
-
StartTime
cannot be later thanEndTime
-
Day
can only take up the 7 values of the week (MON/TUE/WED/THU/FRI/SAT/SUN)
Here is an activity diagram displaying the steps taken when FitBiz receives a user input for the schedule
command:
In the following sequence diagram, we trace the execution for when the user decides to enter the command schedule 1 day/mon st/1100 et/1200
into FitBiz. For simplicity, we will refer to this command input as commandText
:
This sequence diagram shows how the schedule command is processed in FitBiz. The LogicManager receives the input commandText and parses it with FitBizParser
to obtain arguments that are then parsed into ScheduleCommandParser
to construct a ScheduleCommand
. This ScheduleCommand
is returned back up to the LogicManager
which then executes it with reference to the model
argument. Subsequently, the Model
is updated with a new Client
with the schedule changes through a series of commands as shown in the right hand side of the sequence diagram, and control is return back to LogicManager.
3.5.2. Design Considerations
In designing this feature, we had to consider the alternative ways in which we can choose to store the information of a schedule. One option of storing the relevant information (day, start, end times) for a schedule was simply to concatenate these values into a single String, for example, monday-1100-1200
. However, we found that this did not exploit the desirable principles of Object-Oriented Programming. As respective sanity checks had to be done for the day
and timing, wrapping each of these properties into their wrapper classes allowed for better modularity and organisation of these attributes. For example, Day#isValidDay
handles the validation of the input for day and Time#isValidTimingFormat
handles the validation of time.
Considerations also then had to be made for how to contain multiple Schedule
. The current implementation uses the ArrayList
data structure to hold multiple Schedule
. Other considered alternative for ScheduleList
was HashSet
.
ArrayList | HashSet | |
---|---|---|
Ensuring no overlaps |
Does not ensure that its elements are unique |
Ensures no duplicate values |
Ensuring order of elements |
Elements can be sorted and retrieved in ascending order |
Does not return elements in order |
Displaying the Schedule Panel
The schedules of all the clients are displayed in a time-sorted manner on the SchedulePanel
of the main FitBiz GUI as shown in the picture below, demarcated by the red rectangle:
The SchedulePanel
extends UiPart<Region>
and takes in a ScheduleDay
class. ScheduleDay
is similar to ScheduleList
, the difference being:
-
ScheduleDay
wraps an ArrayList ofSchedule
for a specificDay
-
ScheduleList
wraps an ArrayList ofSchedule
for a specificClient
As the nature of the SchedulePanel
was to display a sorted collection of Schedule
, we chose ArrayList
as the underlying data structure, due to the ability to sort the ArrayList via a comparator that compares Schedule
according to their Day
and StartTime
. The code snippet below shows how the Schedule
are being sorted using an anonymous comparator in the constructor for ScheduleDay
:
this.scheduleList.sort(Comparator.comparingInt(o -> o.getStartTime().getDirectTimeInt()));
In addition, we also harnessed the capability of the HashSet
to ensure no overlaps between Schedule
within each Client
, which is implemented by ScheduleCommandParser#checkIfOverlaps
. As the ArrayList of Schedule
is being populated in the constructor of ScheduleDay
, we used a HashSet
to check for any overlapping Schedule
. The equals
method of Schedule
was overriden to consider overlapping timeframes between StartTime
and EndTime
to be equal.
3.6. Exercise Feature --- Yong Jie
This feature allows users to record the exercises done by a client. The exercises are displayed in a table form, after the view-c
command is called.
3.6.1. Implementation
Implementation of Exercise class
The Exercise
class is facilitated by the UniqueExerciseList
, which is a wrapper class for an ObservableList
of Exercise
objects. Each Client contains one UniqueExerciseList
attribute to keep track of all Exercises
the client has.
Exercise
comprises five attributes:
-
ExerciseName
-
ExerciseDate
-
ExerciseReps
-
ExerciseWeight
-
ExerciseSets
All instances of Exercise
of a client will be contained in the client’s UniqueExerciseList
. There is an additional class PersonalBest
which is also associated to Exercise
. It is omitted and will be discussed in the Personal Best Section due to its high significance. Below shows a UML class diagram which shows Exercise
class interactions.
An important point to note about our implementation of Exercise
is the method, isSameExercise()
. We will consider two Exercise
as the same if isSameExercise()
returns true.
-
Executes when adding a new
Exercise
to client’sUniqueExerciseList
. This includesadd-e
andedit-e
. -
Checks if the new
Exercise
is the same with an existing instance ofExercise
in the client’sUniqueExerciseList
. -
Two
Exercises
are the same does not mean that they are equal. TwoExercise
are equal only if all attributes are equal.
Below shows an object diagram of two Exercises
that will return true for isSameExercise()
.
The two Exercise
only have different values in ExerciseSets
but equal values in:
-
ExerciseName
-
ExerciseDate
-
ExerciseReps
-
ExerciseWeight
This implementation is chosen as we felt that the user should increment the value in ExerciseSets
in the existing Exercise
. We followed the same idea as in the real life context and want to combine the sets of exercises with the same name, date, reps and weight.
We do consider that the user might want to record the two instances separately as it might be done at different periods of the day. In the future, when adding a same Exercise
, we can implement it such that the sets value of the exising Exercise
gets incremented automatically instead of showing an error. For now, this implementation keeps the exercise table neat and compact for the user.
Execution flow of exercise commands
The exercise commands edits the client’s UniqueExerciseList
. Currently, there are 3 exercise commands.
-
add-e
: Adds an exercise to a client -
edit-e
: Edits a client’s exercise -
delete-e
: Deletes a client’s exercise
The commands follow a similar execution flow as other commands.
-
The
FitBizParser
will create the associated exercise command parser. e.g.AddExerciseCommandParser
-
Using
ParserUtil
, the parser will extract attribute details from the input and create theCommand
. e.g.AddExerciseCommand
-
The exercise
Command
will be executed and modify the client’sUniqueExerciseList
.-
Exceptions like no client being viewed and invalid input are thrown here.
-
-
The change will be reflected in the exercise table in GUI.
-
Result box will display success message for the
Command
.
Adding an exercise to UniqueExerciseList
When adding a new Exercise
to UniqueExerciseList
, it is important which index it is added. This is such that the exercises for the client will be displayed in descending chronological order in the table after a view-c
command. TableView
provides sorting for dates. However, having TableView
to do the sorting would result in mismatch of indexes of the exercises in the UniqueExerciseList
and in the TableView
. This will result in problems when using exercise commands that specifies an index like delete-e
.
To address this problem, a custom insertion sort has been written in UniqueExerciseList, under the addToSorted(Exercise)
method. This method will do a single pass of the internal list to insert the element at the correct position. This assumes that the internal list is initially sorted (which it should be, since reading from storage will do an initial sort on it).
We will use an example of a add-e
command to illustrate the execution of addToSorted(Exercise)
. Consider an instance where user inputs add-e n/pushup d/12-12-2011 reps/20
. The sequence diagram below shows the execution flow when the AddExerciseCommand
is executed. Details of exception thrown are omitted as this is a postive instance and for simplicity.
AddExerciseCommand
checks if there is an client being viewed. For this instance, we will consider the positive case where indeed there is a client being viewed. AddExerciseCommand
will retrieve the client being viewed from the Model
so that details like the existing exercise list of the client can be obtained. The sequence diagram illustrates the execution flow of addToSorted(Exercise)
to obtain details of the Exercise
being added and the existing Exercise
in UniqueExerciseList
. AddCommand
will then check if there is an exercise that we consider as the same in the UniqueExerciseList
.
UniqueExerciseList
loops through the exercises in the list. and calls getExerciseDate()
and getExerciseName()
. The LocalDate
and exercise name in String
type are then used for comparision. The sequence diagram below illustrates the conditional checks during the comparision.
The conditional checks are such that UniqueExerciseList
maintain sorted by descending chronological order, followed by alphabetical order for exercises with the same dates.
Below shows a code snippet of the conditional checks in addToSorted(Exercise)
.
int dateComparision = toAddDate.compareTo(currDate); if (dateComparision > 0) { break; } else if (dateComparision == 0) { if (toAddName.compareTo(currName) <= 0) { break; } else { idx++; } } else { idx++; }
Arrays.sort()
can be used to sort the exercises in UniqueExerciseList
after every addition. However, the worse case time complexity of Arrays.sort()
is O(nlogn). This custom insertion sort will guarantee an O(n) time complexity to insert the new Exercise
correctly. This is as efficient as it gets as any insertion will already incur an O(n) time complexity to first check if the internal list contains the same exercise. It is important to keep the time complexity low as clients can have many exercises.
3.6.2. Design Considerations
This section explains the our design considerations and analysis for the storage of exercises.
Considerations | Store exercises with client and all clients in one JSON file (chosen) | Store all exercises into a separate JSON file | Store exercises with client but one JSON for each client |
---|---|---|---|
Ease of retrieving / storing |
Easy to link the exercises to the client |
Hard to link the exercises to the client |
Hard to identify which JSON file is for which client |
Separation of data |
Does not keep client and exercise data separate |
Keep client data separate from exercise data |
Does not keep client and exercise data separate Might have too many JSON files, one for each client |
Size limit of JSON files |
High chances of having one large JSON file and potentially exceed the size limit of a JSON file |
Low chances of exceeding the size limit of a JSON file |
Low chances of exceeding the size limit of a JSON file |
Separation of concerns |
Non-separate code for reading/storing exercises and clients data |
Separate code for reading/storing exercises and clients data |
Non-separate ode for reading/storing exercises and clients data |
We decided to use the first approach of storing the exercises with the associated client and have all the clients data in one JSON file. Codewise, each JsonAdaptedClient
will have a list of JsonAdaptedExercise
.
We want to keep the implementation of reading and storing of data simple. The first approach is the most simple. When reading the data, it removes the need to associate the exercises to the client. A client might potentially have a large amount of exercises, resulting in the reading process to be extremely slow. Therefore, a bad user experience.
Moreover, storing the exercise data from client data does not provide any performance benefits. Due to time constraints, we decided that the application should store all the data everytime it closes. This is regardless if the particular exercise or client data has been changed. Having to keep track of which data is edited and only overwrite those data would greatly increase the complexity of the application. Therefore, keeping exercises data separate from client data would be unnecessary and provide little additional functionality/benefits to the user.
Lastly, we foresee that it is improbable for the data size of both clients and exercises to exceed the maximum size limit of a JSON file. With the target user in mind, it is unlikely that he will have an enormous amount of clients. The application is meant to be used by a single user and not an organisation. Even though each client might have many exercises, the information of each exercise is relatively small. For now, collectively, the client and exercise data is unlikely to exceed the JSON size limit. We might consider to have multiple JSON files if the data size gets too big in future versions.
Indeed, JsonAdaptedClient
having a list of JsonAdaptedExercise
would violate separation of concerns. JsonAdaptedClient
is now in charge of the client’s information and the exercises. However, we felt that the benefits outweighted the costs and proceeded with the first choice.
3.7. Personal Best --- Li Zi Ying
This feature allows the users (ie. gym managers) to view the personal bests of exercises done by a client. This information is displayed in a table form, after the command view-c INDEX
is called.
3.7.1. Implementation
The personal best feature is facilitated by the model PersonalBest
, and the logic behind it is in PersonalBestFinder
. The behaviour of this feature determines the personal best of each exercise done by the client based on these considerations:
-
If the
ExerciseWeight
attribute is recorded in theExercise
, then theExerciseWeight
is used as comparison -
If there is no
ExerciseWeight
recorded in theExercise
, thenExerciseReps
will be used as comparison -
If neither of
ExerciseWeight
andExerciseReps
are recorded into theExercise
, then this particular exercise will not be put into the Exercise Personal Best table-
However, if the another
Exercise
of the same name is added in the future withExerciseWeight
and/orExerciseReps
specified, then the personal best of this exercise will still be calculated and shown in the Exercise Personal Best table
-
-
Note that
ExerciseSets
, although an attribute of theExercise
model, is not considered when checking forPersonalBest
as the number of sets of an exercise does not contribute to a personal best record
A simplified class diagram of the classes involved in this feature is given below:
In the following sequence diagram, we trace the execution for when the user decides to enter the command view-c
into FitBiz:
The explanation for the sequence diagram is as follows: when the user inputs view-c
, add-e
, edit-c
or delete-c
, PersonalBestFinder#generateAndSetPersonalBest
is called, taking the client currently in view as the parameter. PersonalBestFinder#generateAndSetPersonalBest
then retrieves client’s list of exercises using Client#getExerciseList
and creates a new HashMap
, where the key
is ExerciseName
and the value
is Exercise
. Then the personal bests of each exercise of the client in view are generated using the above considerations. Finally the list of personal bests is set using PersonalBest#setPersonalBest
.
3.7.2. Design Considerations
In designing this feature, we had to decide on the placement of the PersonalBest
class in the model to comply with the OOP standards. Currently, the PersonalBest
model has a whole-part relationship with Client
, with Client
being the whole and PersonalBest
being a part of Client
. The alternative is to consider PersonalBest
as a part of Exercise
instead.
Put PersonalBest as a part of Client (Chosen) |
Put PersonalBest as a part of Exercise |
|
---|---|---|
Adhering to OOP standards (Coupling and Cohesion) |
Increases cohesion as it logically makes more sense, currently each client has a list of exercises to themselves, and thus each client should also have a list of |
Increases coupling between the logic and model as every time the commands |
Ease of Implementation |
Might have significant conflicts as the |
Easier to implemention as methods related to |
We decided to use the first approach of placing PersonalBest
as a part of Client
instead of Exercise
. There are multiple reasons for our choice as mentioned below.
We want to maintain the OOP structure of the program. Logically, the personal best should belong to the client as the list of exercises belongs to the client. As the list of exercises is unique to every client, the personal best should also be so. We also do not want to increase coupling of the program as mentioned in the table above.
Moreover, even though personal best is generated using the list of exercises in the client, it can be instantiated even without an exercise list. Therefore it does not require the exercise class to exist and does not have a whole-part relationship with exercise. Coupling will also be increased as the client will be relying on the exercise class to generate the personal best. Therefore, the final choice was to place the personal best under client, with every client having their own personal best attribute.
This personal best feature also leads into the Graph
feature, which will be discussed in the next section, where we plot a graph of the client’s progress of a specified exercise.
3.8. Graph --- Li Zi Ying
This feature allows users to see the progress graph of the current client in view. The user has to specify the exercise name, the y-axis (either weights or reps), the start date and the end date. There has to be existing exercises in the client’s exercise list for the specified axis and time period for the graph to be plotted, if no graph can be plotted, an error will be thrown.
3.8.1. Implementation
The graph mechanism is faciliated by the model class Graph
, which contains the details of the graph. These include ExerciseName
, Axis
, StartDate
and EndDate
. The figure below is a UML class diagram to illustrate the Graph
model.
These attributes are bounded by these characteristics:
. ExerciseName
can only be alphanumeric characters
. Axis
can either be reps
or weight
only, case insensitive (sets
are not considered due to the same reasoning in the above section)
. Earliest StartDate
possible can only be one year before the current date and cannot be after EndDate
. StartDate
also cannot be a future date
. Earliest EndDate
possible can only be one year before the current date and cannot be before StartDate
. EndDate
also cannot be a future date
Here is an activity diagram displaying the steps taken when FitBiz receives a user input for the graph
command:
The behaviour of this feature determines the graph plotted of the exercise specified based on these considerations:
-
If there is no such exercise with the matching
ExerciseName
in the client’s exercise list from the specifiedStartDate
toEndDate
, then the graph cannot be plotted -
If the
Axis
input isreps
and the exercise specified does not have any reps input withint theStartDate
toEndDate
, then the graph cannot be plot -
If the
Axis
input isweight
and the exercise specified does not have any weight input within theStartDate
toEndDate
, then the graph cannot be plot -
If all of the above are fulfilled (ie. there is at least one valid exercise with the matching
ExerciseName
and has weight/reps input depending on theAxis
specified), then the graph will be plotted, with each exercise in chronological order
The flow of the program is illustrated using the sequence diagram below:
The explanation is as follows: when the user inputs graph
with all relevant arguments input correctly, a new GraphCommand()
is created, taking the newly created Graph
object as parameter.
GraphCommand#execute()
then retrieves the exercise list from the client currently in view and checks if there is at least one exercise with a matching exercise name. If there is no exercise to plot, then an error GraphCommand.MESSAGE_EXERCISE_NOT_IN_LIST
will be thrown. Next, the list of exercises to be plot will be generated using Graph#generateGraphList()
. Once again, there will be a sanity check to see if the list size is zero, which means that no graph cannot be plotted.
3.8.2. Design Considerations
In designing this feature, we had to decide on the implementation of certain classes like Axis
to comply with the OOP standards of Abstraction.
Create enum class Axis Type (Chosen) |
Check for Axis value using raw types |
|
---|---|---|
Adhering to OOP standards (Abstraction) |
Increases level of abstraction as there are only two different types of axis that can be chosen |
Less abstraction and increases complexity as we will have to check for the equality of the axis type using the equality check for the |
Ease of Implementation |
Requires some refactoring to include |
Easier to implemention as no extra classes or methods are needed, so no refactoring is needed |
We decided to use the approach of abstracting the axis types away into AxisType
enum class. As the graph implementation will require a substantial amount of equality checks, especially for the attributes of Graph
to make sure that we are drawing the correct graph for the user. As such equality checks are made, it makes it difficult to keep checking String
equality as regular data types like String
would allow invalid values to be assigned to a variable.
As our axis values can only be REPS
, WEIGHT
or NA
, we can check for each case using the switch case method instead of checking for equality using raw types. This is also much more efficient than using multiple if-else statements. For example, in the code snippet below, the method fillSeries()
uses switch case statements to add data values depending on the AxisType
.
private void fillSeries() {
switch (axisType) {
case REPS:
fillRepsSeries();
yAxis.setLabel("Reps");
break;
case WEIGHT:
fillWeightSeries();
yAxis.setLabel("Weight");
break;
default:
}
}
Moreover, to keep in line with the OOP standards, we decided that it will be better to abstract away data types like AxisType
into its separate class instead of storing it as a raw type in Axis
. This ensures the code quality of our program and reduces complexity (especially in terms of equality checking as mentioned above) by abstracting away the more complex details into classes of a lower level. The consideration of abstracting details away is also used for creating StartDate
and EndDate
classes as attributes of Graph
, instead of using the Java in-built LocalDate
.
By considering the above two factors, despite having to put in the extra effort to create a new AxisType
class and thus requiring extra methods like getters and setters, we decided to move with the approach of creating the AxisType
enum class and refactor to accomodate for the additional data type.
3.9. Filtering the list of clients --- Toh Ker Wei
This feature allows users to filter the list of clients by specifying the Tag
or Sport
of the clients they want to view.
3.9.1. Implementation
This filtering mechanism is facilitated by TagAndSportContainsKeywordsPredicate
, that implements Predicate<Client>
which is a wrapper class for a boolean. FilterCommand
is associated with Model
is responsible for calling Model#updateFilteredClientList
based on TagAndSportContainsKeywordsPredicate
. TagAndSportContainsKeywordsPredicate
will call test
on Client
to check if the clients Tag
and Sport
contains all the keyword. the relations between these classes are shown in the class diagram below.
To further elaborate,
TagAndSportContainsKeywordsPredicate
contains 2 booleans:
-
hasTag
: evaluates if the client has all theTag
specified -
hasSport
: evaluates if the client has all theSport
specified
If there is no keyword specified for either Tag
or Sport
, the corresponding boolean will return true
. There must be at least 1 keyword specified, regardless of whether it is a Tag
or Sport
. TagAndSportContainsKeywordsPredicate
will then evaluate and return the logical addition of hasTag
and hasSport
.
In the following sequence diagram, we will be tracing the execution of the command filter t/obese s/swim
entered by the user.
3.9.2. Design Considerations
Using separate booleans to check for Tag and Sport keywords (Chosen) |
Using one boolean to check for all keywords | |
---|---|---|
Ease of Implementation |
Checks for client’s |
Simpler logic but errors are more difficult to pinpoint to either |
Ease of Expanding Feature |
Easier to add new parameters to filter since a separate check will be done before combining with the result of previous checks |
Boolean conditions can get very complex and logical error will be prone to occur |
We decided to use the first approach of checking if the client contains Tag
specified and Sport
specified separately.
Firstly, by separating the checks for each attributes, a correct implementation of checking Tag
against the keywords will allow us to easily duplicate the logic to be done for Sport
. This makes the code easier to debug as we can simply check the hasAttribute boolean to see if it gives the correct value.
Secondly, separating the checks for each attributes will allow us to add attributes of different types stored in different data structure easier. We could simply add another check on the attribute against the keyword specified then do a logical addition of the result against the others.
Therefore, as we foresee us adding more attributes to be filtered increasing the need to ensure logical correctness, the first approach is the most ideal.
3.10. Viewing the information of a client --- Toh Ker Wei
This feature allows users to view the information of a specific client using his INDEX
in the clients list. Information displayed includes additional information of the client, exercises done and his personal best of exercises done.
3.10.1. Implementation
The view client’s information feature is primarily facilitated by the model Client
. The details for list of exercises done and personal best will be discussed in section 3.7 and not be covered here. The client’s INDEX
in the clients list will be used to identify and retrieve his information. Additionally, only when a client’s information is being viewed, graph of his exercises can be plotted.
In the following sequence diagram, we will be tracing the execution when the user enters the command view-c 3
3.10.2. Design Considerations
Choose client to view based on INDEX (Chosen) |
Choose client to view based on NAME |
|
---|---|---|
Adhering to Single Responsibilities Principle |
|
|
Ease of Implementation |
Easier to implement as |
Harder to implement as |
We decided to use the first approach of using the client’s INDEX
to view his information.
Firstly, as the client’s INDEX
is unique, view-c
will only be responsible for retrieving and displaying the client’s information and will not need to resolve clients with the same names.
Secondly, for clients with the same name, INDEX
qill be used to specify the client to be view. This causes extra work for the implementation. Furthermore, in cases where users manage many clients and some with same names, there are functions like find and filter which allow users to scope the clients list and easily find the desired client’s INDEX
.
Therefore, viewing a client by his INDEX
minimises the responsibility of the command and will not need to resolve conflicting clients and is the most ideal.
4. Documentation
Refer to the guide here.
5. Testing
Refer to the guide here.
6. Dev Ops
Refer to the guide here.
Appendix A: Product Scope
Target user profile:
-
Has a need to manage a significant number of gym clients and their information (clients' details and exercises)
-
Prefer desktop apps over other types
-
Can type fast
-
Prefers typing over mouse input
-
Is reasonably comfortable using CLI apps
-
Wants to book facilities easily [v2.0]
Value proposition: Keep track of your gym training schedule and clients' exercises faster than a typical mouse/GUI driven app
Appendix B: User Stories
Priorities: High (must have) - * * *
, Medium (nice to have) - * *
, Low (unlikely to have) - *
Priority | As a … | I want to … | So that I can… |
---|---|---|---|
|
coach for fitness competitors |
record the cliental bests of my clients |
monitor their progress |
|
coach for fitness competitors |
record the exercise type and intensity my clients have done for the day |
know if they are on track for their competitions |
|
coach for fitness competitors |
record the date and time of my clients’ training sessions and keep track of which day they work out |
|
|
coach with many fitness competitors |
view my overall schedule for the day/week |
|
|
coach that communicates with my clients |
display visualisations (graphs/charts) |
convey the client’s training progress better |
|
coach |
add new profiles to the app to keep track of new clients |
|
|
coach |
list all my clients |
|
|
coach |
edit a client’s details |
change and update an existing client’s details |
|
coach |
delete my client |
|
|
coach |
search my client by typing their name |
find my client’s information easily |
|
coach |
add, edit and delete new exercises that are not found in the application |
|
|
coach |
look for user help |
get help on how to use the features |
|
coach with many clients |
be reminded of my daily schedule at the start of the day |
track my appointments |
|
forgetful coach with many clients |
look at my records on clients |
know what exercises they are weak in or require more assistance |
|
coach with a tight schedule |
display my open slots |
plan for training more effectively |
|
coach with many clients |
set clientalised goals for my clients |
plan a workout routine that is achievable |
|
coach with many different clients |
easily export the data of a client (to a CSV file) |
backup and store that data in another format |
|
coach |
track my clients by using a tag |
easily view the clients I want to |
|
coach with clients all over SG |
find the nearest gym based on where my client stays |
|
|
coach with a tight schedule |
view a summary page to present to me just the important data, configurable by me |
|
|
coach |
track my total earnings from all my clients |
|
|
coach that likes to vary my clients’ training |
choose from a list of different exercises with the same purposes |
|
|
coach for fitness competitors |
view incoming competitions of my clients |
be reminded to focus on them more |
|
coach who wants to visually track the progress of my clients |
store photos to monitor the changes in my client’s physique |
|
|
coach |
check if the gym I am going to is closed |
|
|
coach |
use the timer in the application |
seamlessly execute the time interval of the workout planned |
|
coach |
book the facilities required by the workout |
|
|
coach |
see upcoming competitions or meet |
plan for my clients to attend them |
|
coach for fitness competitors |
record the food intake of my clients |
know if they are following my diet plan for them |
|
coach |
monitor my clients caloric intake |
know he is meeting his dietary requirements |
|
coach |
manage the payment fee/payment day of the clients |
charge them accordingly |
Appendix C: Use Cases
(For all use cases below, the System is the FitBiz
and the Actor is the user
, unless specified otherwise)
Use case 1: Add client
MSS
-
User requests to add a client
-
FitBiz requests for details (eg. name, phone number, address, email)
-
User enters the requested details
-
FitBiz adds client to database
Use case ends.
Extensions
-
3a. The input format is invalid
-
3a1. FitBiz shows an error message
-
3a2. User enters the new details
Steps 3a1 to 3a2 are repeated until the data entered is correct. Use case resumes from step 4
-
Use case 2: View client
MSS
-
User requests to view all the available information of client
-
FitBiz shows a list of clients
-
User requests to view a specific client in the list
-
FitBiz shows all available information of the client
Use case ends.
Extensions
-
2a. The list is empty
-
2a1. FitBiz displays an empty client list
Use case ends.
-
-
3a. The given index is invalid or out of range
-
3a1. FitBiz shows an error message
-
3a2. User enters the new index
Steps 3a1 to 3a2 are repeated until the data entered is correct. Use case resumes at step 4.
-
Use case 3: Edit client
MSS
-
User requests to edit a client’s details
-
FitBiz shows a list of clients
-
User requests to edit a specific client in the list and inputs the attributes and values
-
FitBiz edits client’s details
Use case ends.
Extensions
-
2a. The list is empty
-
2a1. FitBiz displays an empty client list
Use case ends.
-
-
3a. The given input is invalid
-
3a1. FitBiz shows an error message
-
3a2. User enters the new details
Steps 3a1 to 3a2 are repeated until the data entered is correct. Use case resumes at step 4.
-
Use case 4: Delete client
MSS
-
User requests to delete a client
-
FitBiz shows a list of clients
-
User requests to delete a specific client in the list
-
FitBiz deletes the client
Use case ends.
Extensions
-
2a. The list is empty
-
2a1. FitBiz displays an empty client list
Use case ends.
-
-
3a. The given index is invalid or out of range
-
3a1. FitBiz shows an error message
-
3a2. User enters the new index
Steps 3a1 to 3a2 are repeated until the data entered is correct. Use case resumes at step 4.
-
Use case 5: List clients
MSS
-
User requests to list all existing clients
-
FitBiz lists all existing clients
Use case ends.
Extensions
-
1a. The input format is invalid
-
1a1. FitBiz shows an error message
-
1a2. User provides new input
Steps 1a1 to 1a2 are repeated until the input entered is correct.
Use case resumes at step 2.
-
Use case 6: Add exercise
MSS
-
User requests to add an exercise to a client
-
FitBiz shows a list of clients
-
User requests to add exercise to a specific client in the list
-
FitBiz adds exercise to the client
Use case ends.
Extensions
-
2a. The list is empty
-
2a1. FitBiz displays an empty client list
Use case ends.
-
-
3a. The input format is invalid
-
3a1. FitBiz shows an error message
-
3a2. User enters the new details
Steps 3a1 to 3a2 are repeated until the data entered is correct. Use case resumes at step 4.
-
Use case 7: Filter clients
MSS
-
User requests to filter clients based on a keyword in a client’s tag and/or sports
-
FitBiz filters and displays clients based on specified keywords
Use case ends.
Extensions
-
1a. The input format is invalid
-
1a1. FitBiz shows an error message
-
1a2. User provides new input
Steps 1a1 to 1a2 are repeated until the input entered is correct.
Use case resumes at step 2.
-
Use case 8: Add schedule
MSS
-
User requests to add schedule for a client
-
FitBiz displays a list of clients
-
Client inputs the schedule for the day or the time specified for a particular client
-
FitBiz adds and displays the schedule
Use case ends.
Extensions
-
2a. The list is empty
-
2a1. FitBiz displays an empty client list
Use case ends.
-
-
3a. The given input format is incorrect
-
3a1. FitBiz shows an error message
-
3a2. User enters the new input
Steps 3a1 to 3a2 are repeated until the data entered is correct. Use case resumes at step 4.
-
Use case 9: View graph visualisations
MSS
-
User requests to view graph visualisations of a client’s progress
-
FitBiz shows a list of clients
-
User requests to view the specified client in the list by index
-
Fitbiz shows all available information of the client
-
User requests to view the graph of the specified exercise in the client’s exercise list
-
FitBiz displays the graph of the specified exercise
Use case ends.
Extensions
-
2a. The list is empty
-
2a1. FitBiz displays an empty client list
Use case ends.
-
-
3a. The given index is invalid or out of range
-
3a1. FitBiz shows an error message
-
3a2. User enters the new index
Steps 3a1 to 3a2 are repeated until the data entered is correct. Use case resumes at step 4.
-
-
5a. The given input format is incorrect
-
5a1. FitBiz shows an error message
-
5a2. User enters the new input
Steps 5a1 to 5a2 are repeated until the data entered is correct. Use case resumes at step 6.
-
Appendix D: Non Functional Requirements
-
Should work on any mainstream OS as long as it has Java
11
or above installed. -
Should be able to hold up to 1000 clients without a noticeable sluggishness in performance for typical usage.
-
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.
-
Should work mostly without the need for the Internet.
-
Should work reliably.
-
Should be able to store data in a human-readable format.
-
Should be for a single user.
-
Should not use DBMS to store data.
-
Should not exceed 100Mb in file size.
Appendix E: Glossary
- Client
-
The client of the fitness coach.
- Client In View
-
The client currently in view (after the
view-c
command is used). - Client List
-
The GUI section which shows all client currently listed in FitBiz.
- Client View
-
The GUI section which shows all the information of the client in view
- Exercise
-
A workout activity done by a client that can be recorded.
- Exercise Table
-
The GUI section which shows the list of the client in view’s exercises
- Fitness Coach
-
The targer user base of FitBiz.
- Graph
-
The graph of an exercise done by a client (reps/weights vs. date).
- Mainstream OS
-
Any modern OS like Windows, Linux, Unix, or OS-X.
- Personal Best
-
The exercise done by the client with the highest weight (or highest rep if weight does not exist).
- Personal Best Table
-
The GUI section which shows the list of the client in view’s personal best exercises
- Schedule
-
The client’s training schedule per week. Note that one client cannot have overlapping schedules but other client’s schedule can overlap with another client’s.
- Schedule Panel
-
The GUI section which shows the list of all clients' schedules from the current client list
Appendix F: 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. |
F.1. Launch and Shutdown
-
Initial launch
-
Download the jar file and copy into an empty folder
-
Double-click the jar file
Expected: Shows the GUI without the client view populated with a set of sample contacts. The window size may not be optimum.
-
-
Saving window preferences
-
Resize the window to an optimum size. Move the window to a different location. Close the window.
-
Re-launch the app by double-clicking the jar file.
Expected: The most recent window size and location is retained.
-
F.2. Command autocomplete
-
Unambiguous, single command found
-
Test case: type
gr
into the command box and press Tab
Expected: command box shows completedgraph n/ a/ sd/ ed/
command, with the caret position right aftern/
. Result box shows thegraph
command usage. -
Test case: type
add-e
into the command box and press Tab
Expected: command box shows completedadd-e n/ d/ reps/ ew/ sets/
command, with the caret position right aftern/
. Result box shows theadd-e
command usage.
-
-
Ambiguous, multiple commands found
-
Test case: type
a
into the command box and press Tab
Expected: command box shows longest common prefixadd-
, with the caret position at the end of the line. Result box indicates and lists thatadd-e
andadd-c
commands are found.
-
-
No valid commands found
-
Test case: type
Fitbiz!
into the command box and press Tab
Expected: command box and caret positions does not change. Result box indicates that no commands were found.
-
-
Prefix traversal when commands are completed with white spaces
-
Test case: type
graph n/ a/ sd/ ed/
into the command box and press Tab
Expected: caret position travels around all the/
with wraparound. -
Test case: type
///
into the command box and press Tab
Expected: command box and caret positions does not change. Result box indicates that no commands were found. This is similar to 3, no valid commands found.
-
-
Commands with white spaces but without
/
-
Test case: type
i <3 2103
into the command box and press Tab
Expected: command box and caret positions does not change. Result box does not change. Basically nothing happens.
-
F.3. Deleting a client
-
Deleting a client while all clients are listed
-
Prerequisites: At least one client in the list. List all clients using the
list-c
command. -
Test case:
delete-c 1
Expected: First client is deleted from the list. Details of the deleted client shown in the status message. -
Test case:
delete-c 0
Expected: No client is deleted. Error details shown in the status message. -
Other incorrect delete commands to try:
delete-c
,delete-c x
(where x is larger than the list size)
Expected: Similar to previous.
-
F.4. Adding an exercise
-
Adding an exercise to the client in view’s exercise table
-
Prerequisites: View a client using the
view-c
command. A list of exercises done by the client can be seen in the exercise table. It is possible to have no exercises recorded at the moment. -
Test case: Substitute DATE with the current date
add-e n/bench press date/DATE reps/30 sets/4 ew/10
Expected: The added exercise can be seen in the exercise table. A success message stating that there is a new exercise recorded will also be shown in the result box. -
Test case: Substitute DATE with the current date
add-e n/pushup date/DATE
Expected: The added exercise can be seen in the exercise table. A success message stating that there is a new exercise recorded will also be shown in the result box. -
Test case: Substitute DATE with the day after the current date
add-e n/bench press date/DATE reps/30 sets/4 ew/10
Expected: No exercise added. An error message stating that the valid range for date will be shown. -
Test case: Substitute DATE with the current date
add-e n/pushup d/DATE reps/ten
Expected: No exercise added. An error message stating that input for reps have to be a whole number from 1 to 9999 will be shown. -
add-e n/pushup
Expected: No exercise added. An error message stating "invalid command format!" and a description on how to useadd-e
command will be shown. -
Test case: Substitute DATE with the current date
add-e n/pushup d/DATE sets/4
add-e n/pushup d/DATE sets/2
Expected: No exercise added. An error message stating that the exercise already exists and ask user to increment the existing exercise will be shown.
-
F.5. Saving data
-
Dealing with missing/corrupted data files
-
{explain how to simulate a missing/corrupted file and the expected behavior}
-
F.6. Filtering the client list
-
Filtering the client list with different
Tag
andSport
specified-
Test case:
filter-c t/normal s/hockey
Expected: Client withTag
normal andSport
listed. -
Test case:
filter-c s/sumo wrestling
Expected: Client withSport
sumo wrestling
listed. -
Test case:
filter-c t/normal vegetarian
Expected: Error stating "Tags names should be alphanumeric" shown. -
Test case:
filter-c t/normal t/vegetarian
Expected: Client withTag
normal
andvegetarian
listed.
-
F.7. Adding new client into FitBiz
-
Adding a new client into FitBiz with different parameters stated
-
Test case:
add-c n/John Cena p/98765432 e/johnd@example.com a/311, Clementi Ave 2, #02-25 g/Male b/26-01-1980 cw/96 tw/69 h/156 s/Sumo Wrestling t/Vegan t/healthy r/History of back injuries
Expected: New client added. -
Test case:
add-c n/Micheal Jordan p/98254 e/mikeyboii@example.com a/basketball court blk 666 #02-25
Expected: New client added. -
Test case:
add-c n/Simba p/98543456 e/thelionking@gmail.com
Expected: Error "Invalid command format!" shown. -
Test case:
add-c n/Secret Man p/97656561 e/cantseemee@void.com a/here @ dodo coast g/ b/ cw/ tw/ h/ r/ s/ t/
Expected: New client added. -
Test case:
add-c n/Rachel tan p/93214321 e/RachelTan@mail.com a/choa chu kang ave 22 blk 909
followed byadd-c n/Rachellie Tan p/98786110 e/RachelTan@mail.com a/bishan grove 45
Expected: Error "This phone number and/or email already exists in FitBiz." shown.
-
F.8. Scheduling clients
-
Scheduling a valid client from the current Client List to have 1 schedule
-
Prerequisites: At least one client in the Client List
-
Test Case:
schedule 1 sch/MON-1100-1200
Expected:FIRST_CLIENT_NAME
's overall schedule has been changed to: MON Time: 11:00 - 12:00
-
-
Scheduling an invalid client index
-
Test Case:
schedule -1 sch/MON-1100-1200
Expected: Invalid command format!
schedule: Adds the training schedule of the client, identified by the index number used in the displayed client list. The schedule should include the first 3 letters of the day, start and end time in 24 hour format, in 1-minute denominations. Multiple training schedule can be added to a client.
-
-
Multiple scheduling
-
Prerequisite: At least one client in the Client List
-
Test Case:
schedule 1 sch/MON-1300-1400 sch/TUE-1700-1900
Expected: Alex Yeoh’s overall schedule has been changed to:
MON Time: 13:00 - 14:00
TUE Time: 17:00 - 19:00
-
F.9. Listing all clients
-
Using list-c
-
Test Case:
list-c
Expected: Listed all clients
-
F.10. Editing a client
-
Editing a client’s address
-
Prerequisites: At least one client in the Client List
-
Test Case:
edit-c 1 a/New Address
Expected: Edited Client:CLIENT INFORMATION
-
-
Adding multiple sports to a client
-
Prerequisites: At least one client in the Client List
-
Test Case:
edit-c 1 s/Tennis s/Soccer
Expected: Edited Client:CLIENT INFORMATION
-
-
Removing client’s sports
-
Prerequisites: At least one client in the Client List, with existing sports
-
Test Case:
edit-c 1 s/
Expected: Edited Client:CLIENT INFORMATION
-
F.11. Viewing a client
-
Viewing a client while all clients are listed
-
Prerequisites: At least one client in the list. List all clients using the
list-c
command. -
Test case:
view-c 1
Expected: First client is viewed from the list. Success message shows that you are currently viewing the first client. -
Test case:
view-c 0
Expected: No client is viewed. Error details shown in the status message. -
Other incorrect delete commands to try:
view-c
,view-c x
(where x is larger than the list size)
Expected: Similar to previous.
-
F.12. Viewing a graph of an exercise
-
Viewing a graph of the specified exercise while a client is in view
-
Prerequisities: Have a client currently in view using the
view-c
command. Have at least one exercise in the exercise table. -
Test case:
graph n/Push Up a/reps sd/01-01-2020 ed/01-04-2020
command format is correct and no other input issues
Expected: Graph is displayed and success message is shown. -
Test case:
graph
or any other parameters missing
Expected: No graph displayed, error stating "Invalid command format!" with the correct usage is shown. -
Test case: Correct graph command format but no exercise record within the stipulated timeframe
Expected: No graph displayed, error stating "Graph cannot be plotted" as there are no records of this exercise within the given timeframe. -
Test case: Correct graph command format and existing exercise records but no valid inputs for the given axis
Expected: No graph displayed, error stating "There is no graph to be plotted for this axis specified" is shown and the user is asked to pick a different axis or exericse. -
Test case: Correct graph command format but the start date is chronologically later than the end date
Expected: No graph displayed, error stating "Invalid command format!" with the correct usage is shown
-