By: CS2103-AY1819S1-F11-1
Since: Sep 2018
Licence: MIT
- 1. Setting up
- 2. Design
- 3. Implementation
- 4. Documentation
- 5. Testing
- 6. Dev Ops
- Appendix A: Suggested Programming Tasks to Get Started
- Appendix B: Product Scope
- Appendix C: User Stories
- Appendix D: Use Cases
- Appendix E: Non Functional Requirements
- Appendix F: Glossary
- Appendix G: Product Survey
- Appendix H: Instructions for Manual Testing
-
JDK
9
or later⚠️ JDK 10
on Windows will fail to run tests in headless mode due to a JavaFX bug. Windows developers are highly recommended to use JDK9
. -
IntelliJ IDE
ℹ️IntelliJ by default has Gradle and JavaFX plugins installed.
Do not disable them. If you have disabled them, go toFile
>Settings
>Plugins
to re-enable them.
-
Fork this repo, and clone the fork to your computer
-
Open IntelliJ (if you are not in the welcome screen, click
File
>Close Project
to close the existing project dialog first) -
Set up the correct JDK version for Gradle
-
Click
Configure
>Project Defaults
>Project Structure
-
Click
New…
and find the directory of the JDK
-
-
Click
Import Project
-
Locate the
build.gradle
file and select it. ClickOK
-
Click
Open as Project
-
Click
OK
to accept the default settings -
Open a console and run the command
gradlew processResources
(Mac/Linux:./gradlew processResources
). It should finish with theBUILD SUCCESSFUL
message.
This will generate all resources required by the application and tests. -
Open
XmlAdaptedEvent.java
andMainWindow.java
and check for any code errors-
Due to an ongoing issue with some of the newer versions of IntelliJ, code errors may be detected even if the project can be built and run successfully
-
To resolve this, place your cursor over any of the code section highlighted in red. Press ALT+ENTER, and select
Add '--add-modules=…' to module compiler options
for each error
-
-
Repeat this for the test folder as well (e.g. check
XmlUtilTest.java
andHelpWindowTest.java
for code errors, and if so, resolve it the same way)
-
Run the
seedu.scheduler.MainApp
and try a few commands -
Run the tests to ensure they all pass.
This project follows oss-generic coding standards. IntelliJ’s default style is mostly compliant with ours but it uses a different import order from ours. To rectify,
-
Go to
File
>Settings…
(Windows/Linux), orIntelliJ IDEA
>Preferences…
(macOS) -
Select
Editor
>Code Style
>Java
-
Click on the
Imports
tab to set the order-
For
Class count to use import with '*'
andNames count to use static import with '*'
: Set to999
to prevent IntelliJ from contracting the import statements -
For
Import Layout
: The order isimport static all other imports
,import java.*
,import javax.*
,import org.*
,import com.*
,import all other imports
. Add a<blank line>
between eachimport
-
Optionally, you can follow the UsingCheckstyle.adoc document to configure Intellij to check style-compliance as you write code.
After forking the repo, the documentation will still have the SE-EDU branding and refer to the
CS2103-AY1819S1-F11-1/main
repo.
If you plan to develop this fork as a separate product (i.e. instead of contributing to CS2103-AY1819S1-F11-1/main
),
you should do the following:
-
Configure the site-wide documentation settings in
build.gradle
, such as thesite-name
, to suit your own project. -
Replace the URL in the attribute
repoURL
inDeveloperGuide.adoc
andUserGuide.adoc
with the URL of your fork.
Set up Travis to perform Continuous Integration (CI) for your fork. See UsingTravis.adoc to learn how to set it up.
After setting up Travis, you can optionally set up coverage reporting for your team fork (see UsingCoveralls.adoc).
ℹ️
|
Coverage reporting could be useful for a team repository that hosts the final version but it is not that useful for your personal fork. |
Optionally, you can set up AppVeyor as a second CI (see UsingAppVeyor.adoc).
ℹ️
|
Having both Travis and AppVeyor ensures your App works on both Unix-based platforms and Windows-based platforms (Travis is Unix-based and AppVeyor is Windows-based) |
When you are ready to start coding,
-
Get some sense of the overall design by reading Section 2.1, “Architecture”.
-
Take a look at Appendix A, Suggested Programming Tasks to Get Started.
The Architecture Diagram given above explains the high-level design of the App. Given below is a quick overview of each component.
💡
|
The .pptx files used to create diagrams in this document can be found in the diagrams
folder. To update a diagram, modify the diagram in the pptx file, select the objects of the diagram, and choose
Save as picture .
|
Main
has only one class called MainApp
. It is responsible
for,
-
At app launch: Initializing the components in the correct sequence, and connecting them up with each other.
-
At shut down: Shutting down the components and invokes cleanup method where necessary.
Commons
represents a collection of classes used by multiple other components. Two of those
classes play important roles at the architecture level.
-
EventsCenter
: This class (written using Google’s Event Bus library) is used by components to communicate with other components using events (i.e. a form of Event Driven design) -
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.
The Sequence Diagram below shows how the components interact for the scenario where the user issues the command
delete 1
.
ℹ️
|
Note how the Model simply raises a SchedulerChangedEvent when the Scheduler data are changed, instead of asking the
Storage to save the updates to the hard disk.
|
The diagram below shows how the EventsCenter
reacts to that event, which eventually results in the updates being saved
to the hard disk and the status bar of the UI being updated to reflect the 'Last Updated' time.
ℹ️
|
Note how the event is propagated through the EventsCenter to the Storage and UI without Model having to be
coupled to either of them. This is an example of how this Event Driven approach helps us reduce direct coupling between
components.
|
The sections below give more details of each component.
API : Ui.java
The UI consists of a MainWindow
that is made up of parts e.g.CommandBox
, ResultDisplay
, EventListPanel
,
StatusBarFooter
, BrowserPanel
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 link:https://github.com/CS2103-AY1819S1-F11-1/main/tree/master/src/main/java/seedu/scheduler/ui/
MainWindow.java[MainWindow
] is specified in MainWindow.fxml
The UI
component,
-
Executes user commands using the
Logic
component. -
Binds itself to some data in the
Model
so that the UI can auto-update when data in theModel
change. -
Responds to events raised from various parts of the App and updates the UI accordingly.
API :
Logic.java
-
Logic
uses theSchedulerParser
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 an event) and/or raise events. -
The result of the command execution is encapsulated as a
CommandResult
object which is passed back to theUi
.
Given below is the Sequence Diagram for interactions within the Logic
component for the execute("delete 1")
API
call.
API : Model.java
The Model
,
-
stores a
UserPref
object that represents the user’s preferences. -
stores the Scheduler data.
-
exposes an unmodifiable
ObservableList<Event>
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.
API : Storage.java
The Storage
component,
-
can save
UserPref
objects in JSON format and read it back. -
can save the Scheduler data in XML format and read it back.
This section describes some noteworthy details on how certain features are implemented.
The undo/redo mechanism is facilitated by VersionedScheduler
.
It extends Scheduler
with an undo/redo history, stored internally as a schedulerStateList
and currentStatePointer
.
Additionally, it implements the following operations:
-
VersionedScheduler#commit()
— Saves the current scheduler state in its history. -
VersionedScheduler#undo()
— Restores the previous scheduler state from its history. -
VersionedScheduler#redo()
— Restores a previously undone scheduler state from its history.
These operations are exposed in the Model
interface as Model#commitScheduler()
, Model#undoScheduler()
and
Model#redoScheduler()
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 VersionedScheduler
will be initialized with the
initial scheduler state, and the currentStatePointer
pointing to that single scheduler state.
Step 2. The user executes delete 5
command to delete the 5th event in the scheduler. The delete
command calls
Model#commitScheduler()
, causing the modified state of the scheduler after the delete 5
command executes to be saved
in the schedulerStateList
, and the currentStatePointer
is shifted to the newly inserted scheduler state.
Step 3. The user executes add n/Holiday …
to add a new event. The add
command also calls
Model#commitscheduler()
, causing another modified scheduler state to be saved into the schedulerStateList
.
ℹ️
|
If a command fails its execution, it will not call Model#commitScheduler() , so the scheduler state will not be saved
into the schedulerStateList .
|
Step 4. The user now decides that adding the event was a mistake, and decides to undo that action by executing the
undo
command. The undo
command will call Model#undoScheduler()
, which will shift the currentStatePointer
once to
the left, pointing it to the previous scheduler state, and restores the scheduler to that state.
ℹ️
|
If the currentStatePointer is at index 0, pointing to the initial scheduler state, then there are no previous
scheduler states to restore. The undo command uses Model#canUndoScheduler() 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:
The redo
command does the opposite — it calls Model#redoScheduler()
, which shifts the currentStatePointer
once to
the right, pointing to the previously undone state, and restores the scheduler to that state.
ℹ️
|
If the currentStatePointer is at index schedulerStateList.size() - 1 , pointing to the latest scheduler state, then
there are no undone scheduler states to restore. The redo command uses Model#canRedoScheduler() 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 list
. Commands that do not modify the scheduler, such as list
,
will usually not call Model#commitScheduler()
, Model#undoScheduler()
or Model#redoScheduler()
. Thus, the
schedulerStateList
remains unchanged.
Step 6. The user executes clear
, which calls Model#commitScheduler()
. Since the currentStatePointer
is not
pointing at the end of the schedulerStateList
, all scheduler states after the currentStatePointer
will be purged. We
designed it this way because it no longer makes sense to redo the add n/Party …
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:
-
Alternative 1 (current choice): Saves the entire scheduler.
-
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
delete
, just save the event being deleted). -
Cons: We must ensure that the implementation of each individual command is correct.
-
-
Alternative 1 (current choice): Use a list to store the history of scheduler states.
-
Pros: Easy for new Computer Science student undergraduates to understand, who are likely to be the new incoming developers of our project.
-
Cons: Logic is duplicated twice. For example, when a new command is executed, we must remember to update both
HistoryManager
andVersionedScheduler
.
-
-
Alternative 2: Use
HistoryManager
for undo/redo-
Pros: We do not need to maintain a separate list, and just reuse what is already in the codebase.
-
Cons: Requires dealing with commands that have already been undone: We must remember to skip these commands. Violates Single Responsibility Principle and Separation of Concerns as
HistoryManager
now needs to do two different things.
-
The EnterGoogleCalendarMode mechanism is facilitated by utility method to get Google authentication and enables the Google Calendar feature. It implements the following operations:
-
EnterGoogleCalendarMode
— Enable the Google Calendar feasture and , get (pull) the online Google Calendar events and merge with local events. -
postGoogleCalendarEvents
[coming in v2.0] — Post (push) the local events and merge with online Google Calendar.
It also supports the following existing features, to enhance the with real-time effects on Google Calendar (those created with the app)
* Add
— When the Google Calendar feature is enabled, the app creates the same event(s) in the google calendar.
* Edit
— When the Google Calendar feature is enabled, the app edits the same event(s) in the google calendar.
* Delete
— When the Google Calendar feature is enabled, the app deletes the same event(s) in the google calendar.
* Clear
— When the Google Calendar feature is enabled, the app clears the primary google calendar.
The Event(s) mentioned above refer to those created by the app. The existing ones are not supproted yet. Even they are pulled to local database, commonds have no effect on them, as mentioned in the UserGuide
Below is an example usage scenario and explanation on the EnterGoogleCalendarMode
mechanism behavior behind each step.
Step 1
→ The user has always been using the application locally and never enabled this feasure.
→ All Scheduler Events have been created locally. That is, the Scheduler has only local events (no Google Calendar Events).
→ An empty database is illustrated below as an imaginary array:
Step 2
→ The user executes EnterGoogleCalendarMode
command to enable the feature. download the Google Calendar Events to the local database.
→ A Calendar
object is to be built with method getCalendar()
in ConnectToGoogleCalendar.java
→ A method getNetHttpTransport()
is called to let application establish a connection with Google to initiate an authorization process (for those who use this command for the first time).
→ A pop-up window will appear. The User will be required to log in their Google Account as per other Google Service.
After successful login, the login credential will be saved locally such that no future log-in is required.
Step 3
→ After successful authentication, a success message is shown for the user and instruct the user to close the browser.
+
→ At the same time, the application proceeds automatically for the downloading and merging of Google Calendar’s events
to local events.
→ The flowchart below shows the process of deciding whether to prompt a login page or to proceed with a local
credential file:
ℹ️
|
For developers, if you test the application locally, please do not include this log-in credential file to the |
Step 4
→ The application checks for whether the feature has already been enabled. If it is enabled, the command is rejected. This is because the method is an initialization method. Second initialization is rejected.
Step 5
→ The application will then call method getEvents
to extract a (Google)Events
objects from the user’s online Google Calendar.
ℹ️
|
For current implementation, only events in the primary (default) calendar are extracted. |
Step 6
→ For each event extracted, various ConvertEvent utility methods will be called to convert the Google Extracted Event to local Event.
Step 7
→ The converted events are saved in the local database, together with other locally created Events.
-
Save local login credential (current choice)
-
Pro: Improves the user experience — user won’t have to log in every time they use this command, it would be too troublesome and not user-friendly.
-
Con: Improper protection could cause a leak of personal info.
-
-
Require login everytime (alternative)
-
Pro: Very secured.
-
Con: Not user-friendly.
-
Below is an example usage scenario and explanation on the Add
mechanism behavior behind each step.
After the user enabled the Google Calendar feature, when the command add n/Online Shopping! s/Dec 12, 2018 5pm e/Dec 12, 2018 7pm re/2H
is entered.
Step 1
→ The local event is generated.
Step 2
→ The toAdd
Event is captured and send to pushToGoogleCal
method.
Step 3
→Regardless of whether the Event is a single event or a repeated event. pushToGoogleCal
method sets the common attributes such as EventName
, Venue(Location)
, Description
, StartDateTime
, EndDateTime
and Reminders
.
Step 4
→ If it is a RepeatEvent, set special attributes to it such as googleRecurringEventId
, googleiCalId
, eventRepeatType
,eventUntilDateTime
. A Google specific commandMessage
is constructed and sent to Google API.
Various convertion methods are called to convert the Event format from local Event to Google Event.
A common identifier is needed to uniquelly identify the same Event in local database and in Google Calendar.
-
Use Event Unique Id and EventSet Unique Id (current choice)
-
Pro: It is unique and randomly generated.
-
Con: Very hard to implement and have to find the equivalent unique string in Google’s implementation. Eventually, after checking out Google’s implementation, we set the following equivalent properties:
EventUid == EventId
EventSetUid == iCalId
-
-
By Name (alternative)
-
Pro: Very easy to implement
-
Con: User unable to create events with the same name.
-
Step 1
→Command clear
Cleans up the enviroment
Step 2
→Command add n/Study For next sem s/2018-12-1 9pm e/2018-12-1 10pm rt/WEEKLY ru/2018-12-25 9pm
to create a repeat event, this event will trigger Google to create 4 Event Instances
Step 3
→Command edit 1 n/change1
Only the first event will be edited, its name will become change1.
A command requst will be sent to edit the first event instance.
Step 4
edit 2 n/change2 -u
+
The first event is untouched.
The event instances from the second one onwards in the same EventSet will be edited, their name swill become change2.
A command requst will be sent to edit the various event instances.
Step 5
edit 3 n/change3 -a
+
All event instances in the same EventSet will be edited, their name swill become change3.
A command requst will be sent to edit the underlying Event, instead of editing individual event instances.
We need to carry out integration test on Github.
However various limits are posted on the usage of APIs, but tests are necessary to run.
-
Introduce offline mode (current choice)
-
Pro: Offline mode introduced at the code level to disable Google Calendar methods.
-
Con: Have to write seperate set of tests and helper functions, and control the switch accurately.
enabl()
anddisable()
helper methods are used for tests related to Google Calendar methods.
-
-
No offline mode (alternative)
-
Pro: Easy to implement, no additional tests needed.
-
Con: Running the current set of test cases will certainly sending too many requests to Google, and triggers the IP address/ account to be blocked.
-
Note on Google Calendar Tests
Please ensure only one running PR is running the tests.
All tests will be using the StoredCredential
file to access a test account’s google calendar, running the same set of tests.
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.5, “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
Certain properties of the application can be controlled (e.g App name, logging level) through the configuration file
(default: config.json
).
The add
command allows the user to add new events to the scheduler. Add Command is facilitated by the AddCommand
class.
The format of this command is add event n/EVENT_NAME [s/START_DATETIME] [e/END_DATETIME] [d/DESCRIPTION] [v/VENUE]
[rt/RECURRING_TYPE] [ru/RECURRING_UNTIL_DATETIME] [t/TAG]…
.
The command uses Natty, a natural language date parser written in Java to parse DateTime
given by the user. When given
a user inputted string, Natty will parse the string into a LocalDateTime
class.
AddCommand
requires the interaction between both Logic
and Model
Component. The Logic
component comprises of
AddCommandParser
and RepeatEventGenerator
.
The AddCommandParser
parses the user input and RepeatEventGenerator
generates repeating events (if any). The Model
component is responsible for updating the internal event list.
Given below is the execution flow of Add Command
.
-
When the user inputs a command,
LogicManager
will call theparseCommand
method inSchedulerParser
with the user input as arguments. -
If the user input is valid and contains the keyword
add
,AddCommandParser
will be instantiated. -
SchedulerParser
will then call theparse
method ofAddCommandParser
. -
AddCommandParser
parses the user input with the respective parsing methods. -
AddCommand
will be instantiated with the parsed event. -
generateAllRepeatedEvents
method inRepeatEventGenerator
is invoked to generate a list of repeating events according to the repeat type of the parsed event. -
LogicManager
will then proceed to call theexecute()
method ofAddCommand
. -
Model
Component will then add the list of events to an internal list of events using theaddEvents
method.
The following sequence diagram demonstrates how the add operation works:
The following activity diagram summarizes what happens when a user executes a new Add Command
:
-
Alternative 1 (current choice): Use
RepeatEventGenerator
to generate repeating events.-
Pros: Singleton pattern. Easy to generate repeating events anywhere in the code base. Reduce
RepeatEventGenerator
object creations in testing. Uses the Single Responsibility Principle. -
Cons: May increase coupling across the code base. May increase the difficulty of testing due to the singleton object.
-
-
Alternative 2: Event model knows how to generate its repeating events by itself.
-
Pros: Will use less memory (e.g. generating events do not need to depend on another class to generate the repeating events).
-
Cons: Increase coupling.
-
-
Alternative 1 (current choice): Use Natty, a natural language date parser.
-
Pros: Users do not need to conform to a standard format and able to enter
DateTime
in a more natural way. The user can also specify relativeDateTime
, which provides flexibility inDateTime
input. -
Cons: Parser may not be able to parse all natural language format due to ambiguity in language formats. There may also be a chance of parsing an invalid DateTime.
-
-
Alternative 2: Use standard
DDMMYY
HHMMSS
format for allDateTime
inputs.-
Pros: Users will always be assured of correct
DateTime
being parsed if they provide the correct input. -
Cons: Reduce the flexibility as users have to conform to a standard format.
-
The edit
command allows users to add edit existing events in the scheduler. Edit Command is facilitated by the EditCommand
class.
The format of this command is edit INDEX [n/EVENT_NAME] [s/START_DATETIME] [e/END_DATETIME] [d/DESCRIPTION] [v/VENUE] [rt/REPEAT_TYPE] [ru/REPEAT_UNTIL_DAETIME] [t/TAG]… [re/REMINDER_TIME]… [-a | -u]
EditCommand
requires the interaction between both Logic
and Model
Component. The Logic
component comprises of EditCommandParser
and RepeatEventGenerator
.
The EditCommandParser
parses the user input and RepeatEventGenerator
generates repeating events (if any). The Model
component is responsible for updating the internal event list with the generated event(s).
Given below is the execution flow of Edit Command
.
-
When the user inputs a command,
LogicManager
will call theparseCommand
method inSchedulerParser
with the user input as arguments. -
If the user input is valid and contains the keyword
edit
,EditCommandParser
will be instantiated. -
SchedulerParser
will then call theparse
method ofEditCommandParser
. -
EditCommandParser
parses the user input with the respective parsing methods. -
EditCommand
will be instantiated with details of the edited event. -
generateAllRepeatedEvents
method inRepeatEventGenerator
is invoked to generate edited event(s) if user is trying to edit a set of repeating events with options-a
or-u
. -
LogicManager
will then proceed to call theexecute()
method ofEditCommand
. -
Model
Component will then find the targeted event(s) in internal list according to the parsed option type (no option or-a
or-u
). -
Model
Component will replace the targeted event(s) with the edited event(s).
The following sequence diagram demonstrates how the add operation works:
To find the targeted event(s) in Model
(determined by the parsed option), we filter the internal event list using certain predicates.
The following code blocks shows the predicate used for -a
and -u
respectively.
versionedScheduler.updateEvents(target, editedEvents,
event -> event.getEventSetUid().equals(target.getEventSetUid()));
versionedScheduler.updateEvents(target, editedEvents, event ->
event.getEventSetUid().equals(target.getEventSetUid())
&& event.getStartDateTime().compareTo(target.getStartDateTime()) >= 0);
-
Alternative 1 (current choice): Generating edited events also targets first instance of event.
-
Pros: For
-a
option, we can generate all the repeating events starting from the first event in repeating event set even when the user targets the second event in the repeating event set.E.g. In a set of events:
event A1, event A2 and eventA3
, when we input the commandedit 2 rt/weekly -a
, we can check whetherevent A1
has the same day of week aseventA2
. If yes, we generate new events starting fromeventA1
else, start fromeventA2
. -
Cons: New Events generated will always be based on the targeted event. So, when edit the
eventA1
only first, then edit the whole set (all) of events starting from theeventA2
, the subsequent events generated will be based oneventA2
which overrides any changes that were made individually toeventA1
.
-
-
Alternative 2: Generating edited events based on selected event and delete previous events.
-
Pros: Do not need to be know what is in the model before generating events.
-
Cons: Events generated may not be as intuitive to users because the if we start editing a repeated event from the second event in a set, the first event will always be deleted.
-
The delete
command allows users to delete existing events in the scheduler. Delete Command is facilitated by the DeleteCommand
class.
The format of this command is delete INDEX [-a | -u]
.
DeleteCommand
requires the interaction between both Logic
and Model
Component. The Logic
component comprises of DeleteCommandParser
.
The DeleteCommandParser
parses the user input. The Model
component is responsible for deleting the internal event list(s).
Given below is the execution flow of Delete Command
.
-
When the user inputs a command,
LogicManager
will call theparseCommand
method inSchedulerParser
with the user input as arguments. -
If the user input is valid and contains the keyword
edit
,DeleteCommandParser
will be instantiated. -
SchedulerParser
will then call theparse
method ofEditCommandParser
. -
DeleteCommandParser
parses the user input with the respective parsing methods. -
EditCommand
will be instantiated with index of the event to be deleted. -
LogicManager
will then proceed to call theexecute()
method ofDeleteCommand
. -
Model
Component will then find the targeted event(s) in internal list according to the parsed option type (no option or-a
or-u
). -
Model
Component will delete the targeted event(s).
The input is duration in the following format: [re/xxHxxMxxS]
, meaning a reminder will pop up xxHxxMxxS before the event start time. Multiple unique reminders are allowed for one event.
The reminder operations are first implemented with the add
and edit
command as input fields. Commands addReminder
, deleteReminder
, postponeReminder
are then implemented by inheriting editCommand class for more customised reminder operations. The differences in logic between three reminder commands and edit
are explained below:
-
addReminder INDEX [/re xxHxxMxxS]…[-a|-u]
Add reminders INCREMENTALLY to the event, whereasedit
will replace the reminders of the event by input durations. -
deleteReminder INDEX [/re xxHxxMxxS]…[-a|-u]
Delete reminders if present. If edit is used for delete reminders, user will have to input all the reminders he/she wants to keep. -
postponeReminder INDEX [/re xxHxxMxxS] [-a|-u]
Postpone all reminders of the event by the duration specified.
Pop-up Alert feature requires the interaction between both 'Logic' and 'Model' Component as shown by the class diagram and explanation below. The class diagram only shows relevant classes for clarity.
The main utility responsible for this feature is PopUpManager
, which is implemented in a singleton pattern since it should have no more than just one instance.
Event
has a field ReminderDurationList
which is a set of Duration
objects. A Duration
such as 30m
represents the period before the event start time that reminder will pop up.
Therefore each Event
object can have multiple unique Duration
objects in ReminderDurationList
, and thus multiple reminders.
Each Duration
, together with the Event
object itself, will be used by PopUpManager
to generate 1 EventPopUpInfo
object, which correspond to 1 reminder.
Except for Duration
, EventPopUpInfo
object also has a DateTime
field (PopUpTime) calculated by Start Time - Duration
. It is the time the corresponding reminder should popped up.
The rest of the fields of EventPopUpInfo
is the same as the Event
object. Descriptive fields such as Description
, Venue
, EndTime
are needed for display reminders.
While the two IDs are used for PopUpManager
's various updating methods.
PopUpManager
takes in Event
object to generate EventPopUpInfo
objects and then stores them in a PriorityQueue
(PopUpQueue)
order by PopUpTime. The startRunning() method in PopUpManager will run in parallel
to constantly check when to pop an EventPopUpInfo
and call PopUp
in UI
to display the reminder.
Every time a command is executed and handled by ModelManager
, ModelManager
will call relevant method in PopUpManager
to update PopUpQueue.
The sequence diagram below illustrate a generic case when user key in edit
command. Some parts are omitted for clarity.
Below is an example usage scenario with sequence diagram to illustrate how PopUpManager
is updated
-
The user has a set of recurring events: CS2103 lecture that happen every Friday 4pm - 6pm. He has set reminders to be 1H before the start time.
-
Now is week 7 and the time for the lectures has just changed onwards. So he "edit 7 s/xxx 2pm e/xxx 4pm -u".
-
When updateUpcomingEvents(eventToEdit, editedEvents) in
ModelManager
is called, this method will call editUpcoming(eventToEdit, editedEvents) inPopUpManager
, which runs deleteUpcoming(..) and then add(..) -
deleteUpcoming will delete all
EventPopUpInfo
objects that share the sameSetId
with eventToEdit but have a later start time. Then add method will generate all editedEvents' corresponding 'EventPopUpInfo' and add them to PopUpQueue.
-
At any time, ONLY EventPopUpInfo with PopUpTime in the future will be added to the queue.
-
Any updates to the Scheduler will trigger corresponding updates in PopUpManager.
The updates in PopUpManager
takes place on the main thread. However, the checking of PopUpQueue takes place on a parallel thread as illustrated by the activity diagram.
-
Use Set to store ReminderDurationList (current choice)
-
Store the
Durations
in a Set. Do nothing to the Set whenEventPopUpInfo
is popped out. Every time addingEventPopUpInfo
to PopUpQueue, only those with future PopUpDateTime will be added. -
Pro: Easier implementation
-
Con: Will not be able to keep track of those reminders that have passed when the app is not open. Past reminders cannot pop up when the app opens.
-
-
Use HashMap to store ReminderDurationList (alternative)
-
Store as
Duration
:Boolean
(if the reminder has popped up). Every time a reminder pops up, set toTrue
. -
Pro: Will be able to keep track of those reminders that have passed when the app is not open. When the user open the app,
Duration
withFalse
will generate correspondingEventPopUpInfo
to add to the queue. They will be popped up as past reminders to remind the users they might have miss some important events. -
Con: This design will be complicated and affect
undo
andredo
function.
-
Current tag is implemented as a feature of an event instead of an independent object stored in local storage.
Similar to add event command, all tags are stored in local storage.
The addTag
command allows the user to add new tags to the scheduler. Add Tag Command is facilitated by the
AddTagCommand
class.
The format of this command is add [TAG_NAME]
.
AddTagCommand
requires the interaction between both Logic
and Model
Component. The Logic
component comprises of
AddTagCommandParser
.
The AddTagCommandParser
parses the user input and generates a tag. The Model
component is responsible for updating
the internal tag list.
Given below is the execution flow of Add Tag Command
.
-
When the user inputs a command,
LogicManager
will call theparseCommand
method inSchedulerParser
with the user input as arguments. -
If the user input is valid and contains the keyword
addTag
,AddTagCommandParser
will be instantiated. -
SchedulerParser
will then call theparse
method ofAddTagCommandParser
. -
AddTagCommandParser
parses the user input with the respective parsing methods. -
AddTagCommand
will be instantiated with the list of tags. -
LogicManager
will then proceed to call theexecute
command ofAddTagCommand
. -
Model
Component will then add the list of tags to an internal list of tags using theaddTags
method.
-
For the
add
command, typingadd
,ad
or even simplya
with the appropriate parameters can add an event. -
Similarly, for the 'delete' command, typing one of the following —
delete
,delet
, dele`,del
,de
ord
with the appropriate parameters can delete an event. -
Because of technical restrictions in programming, if there are multiple (2 or more) commands that start with the same alphabetical letter, for e.g.
history
andhelp
, in that case:
→ We have chosen to prioritizehelp
over thehistory
command. It is as follows:history
can be invoked withhistory
,histor
… all the way tohi
. However,help
can be called withhelp
,he
…h
. -
The same logic applies to the
edit
andexit
command. → We have chosen to prioritizeexit
over theedit
command.exit
can be invoked withexit
down toe
whileedit
can be invoked down tilled
with the appropriate parameters specified. -
The alias(es) of each command go through the same logic as the actual command itself. They are parsed through the
parseCommand()
method in theSchedulerParser.java
class.
-
The current implementation may not be the most efficient. We create constants of
public static final String
type and name them asCOMMAND_ALIAS_ONE
,COMMAND_ALIAS_TWO
, etc… in the affected commands' respective classes.
⇒ A suggestion would be to create a suitableCommandAlias
class and/or use data structures likeenums
,HashMap
,TreeMap
etc… for the command aliases. -
An activity diagram (a swimlane diagram) showing how an alias of the
history
command would work is shown below:
Note: The above swimlane diagram is a simplified activity diagram which omits details of interactions with interfaces
such as Model.java
and/or other relevant classes.
This feature is implemented would using the auto-complete feature available in the
ControlsFX library. To be precise, the method
TextFields.bindAutoCompletion()
is invoked in the constructor of the
CommandBox#Command
class.
We use AsciiDoc for writing documentation.
ℹ️
|
We chose AsciiDoc over Markdown because AsciiDoc, although a bit more complex than Markdown, provides more flexibility in formatting. |
See UsingGradle.adoc to learn how to render .adoc
files locally to preview
the end result of your edits.
Alternatively, you can download the AsciiDoc plugin for IntelliJ, which allows you to preview the changes you have made
to your .adoc
files in real-time.
See UsingTravis.adoc to learn how to deploy GitHub Pages using Travis.
We use Google Chrome for converting documentation to PDF format, as Chrome’s PDF engine preserves hyperlinks used in web pages.
Here are the steps to convert the project documentation files to PDF format.
-
Follow the instructions in UsingGradle.adoc to convert the AsciiDoc files in the
docs/
directory to HTML format. -
Go to your generated HTML files in the
build/docs
folder, right click on them and selectOpen with
→Google Chrome
. -
Within Chrome, click on the
Print
option in Chrome’s menu. -
Set the destination to
Save as PDF
, then clickSave
to save a copy of the file in PDF format. For best results, use the settings indicated in the screenshot below.
The build.gradle
file specifies some project-specific
asciidoc attributes which affects how all documentation files
within this project are rendered.
💡
|
Attributes left unset in the build.gradle file will use their default value, if any.
|
Attribute name | Description | Default value |
---|---|---|
|
The name of the website. If set, the name will be displayed near the top of the page. |
not set |
|
URL to the site’s repository on GitHub. Setting this will add a "View on GitHub" link in the navigation bar. |
not set |
|
Define this attribute if the project is an official SE-EDU project. This will render the SE-EDU navigation bar at the top of the page, and add some SE-EDU-specific navigation items. |
not set |
Each .adoc
file may also specify some file-specific asciidoc
attributes which affects how the file is rendered.
Asciidoctor’s built-in attributes may be specified and used as well.
💡
|
Attributes left unset in .adoc files will use their default value, if any.
|
Attribute name | Description | Default value |
---|---|---|
|
Site section that the document belongs to.
This will cause the associated item in the navigation bar to be highlighted.
One of: * Official SE-EDU projects only |
not set |
|
Set this attribute to remove the site navigation bar. |
not set |
The files in docs/stylesheets
are the
CSS stylesheets of the site. You can modify them to change some
properties of the site’s design.
The files in docs/templates
controls the rendering of .adoc
files into HTML5.
These template files are written in a mixture of Ruby and Slim.
|
Modifying the template files in |
There are three ways to run tests.
💡
|
The most reliable way to run tests is the 3rd one. The first two methods might fail some GUI tests due to platform/ resolution-specific idiosyncrasies. |
Method 1: Using IntelliJ JUnit test runner
-
To run all tests, right-click on the
src/test/java
folder and chooseRun 'All Tests'
-
To run a subset of tests, you can right-click on a test package, test class, or a test and choose
Run 'ABC'
Method 2: Using Gradle
-
Open a console and run the command
gradlew clean allTests
(Mac/Linux:./gradlew clean allTests
)
ℹ️
|
See UsingGradle.adoc for more info on how to run tests using Gradle. |
Method 3: Using Gradle (headless)
Thanks to the TestFX library we use, our GUI tests can be run in the headless mode. In the headless mode, GUI tests do not show up on the screen. That means the developer can do other things on the Computer while the tests are running.
To run tests in headless mode, open a console and run the command gradlew clean headless allTests
(Mac/Linux:
./gradlew clean headless allTests
)
We have two types of tests:
-
GUI Tests - These are tests involving the GUI. They include,
-
System Tests that test the entire App by simulating user actions on the GUI. These are in the
systemtests
package. -
Unit tests that test the individual components. These are in
seedu.scheduler.ui
package.
-
-
Non-GUI Tests - These are tests not involving the GUI. They include,
-
Unit tests targeting the lowest level methods/classes.
e.g.seedu.scheduler.commons.StringUtilTest
-
Integration tests that are checking the integration of multiple code units (those code units are assumed to be working).
e.g.seedu.scheduler.storage.StorageManagerTest
-
Hybrids of unit and integration tests. These tests are checking multiple code units as well as how they are connected together.
e.g.seedu.scheduler.logic.LogicManagerTest
-
See UsingGradle.adoc to learn how to use Gradle for build automation.
We use Travis CI and AppVeyor to perform Continuous Integration on our projects. See UsingTravis.adoc and UsingAppVeyor.adoc for more details.
We use Coveralls to track the code coverage of our projects. See UsingCoveralls.adoc for more details.
When a pull request makes changes to AsciiDoc files, you can use Netlify to see a preview of how the HTML version of those AsciiDoc files will look like when the pull request is merged. See UsingNetlify.adoc for more details.
Here are the steps to create a new release.
-
Update the version number in
MainApp.java
. -
Generate a JAR file using Gradle.
-
Tag the repo with the version number. e.g.
v0.1
-
Create a new release using GitHub and upload the JAR file you created.
A project often depends on third-party libraries. For example, Scheduler depends on the Jackson library for XML parsing. Managing these dependencies can be automated using Gradle. For example, Gradle can download the dependencies automatically, which is better than these alternatives.
+
a. Include those libraries in the repo (this bloats the repo size)
b. Require developers to download those libraries manually (this creates extra work for developers)
A suggested path for new programmers:
-
First, add small local-impact (i.e. the impact of the change does not go beyond the component) enhancements to one component at a time. Some suggestions are given in Section A.1, “Improving each component”.
-
Next, add a feature that touches multiple components to learn how to implement an end-to-end feature across all components. Section A.2, “Creating a new command:
remark
” explains how to go about adding such a feature.
Each individual exercise in this section is component-based (i.e. you would not need to modify the other components to get it to work).
Scenario: You are in charge of logic
. During dogfooding, your team realizes that it is troublesome for the user to
type the whole command in order to execute a command. Your team devises some strategies to help cut down the amount of
typing necessary, and one of the suggestions was to implement aliases for the command words. Your job is to implement
such aliases.
💡
|
Do take a look at Section 2.3, “Logic component” before attempting to modify the Logic component.
|
-
Add a shorthand equivalent alias for each of the individual commands. For example, besides typing
clear
, the user can also typec
to remove all events in the list.-
Hints
-
Just like we store each individual command word constant
COMMAND_WORD
inside*Command.java
(e.g.FindCommand#COMMAND_WORD
,DeleteCommand#COMMAND_WORD
), you need a new constant for aliases as well (e.g.FindCommand#COMMAND_ALIAS
). -
SchedulerParser
is responsible for analyzing command words.
-
-
Solution
-
Modify the switch statement in
schedulerParser#parseCommand(String)
such that both the proper command word and alias can be used to execute the same intended command. -
Add new tests for each of the aliases that you have added.
-
Update the user guide to document the new aliases.
-
See this PR for the full solution.
-
-
Scenario: You are in charge of model
. One day, the logic
-in-charge approaches you for help. He wants to implement
a command such that the user is able to remove a particular tag from everyone in the scheduler, but the model API does
not support such a functionality at the moment. Your job is to implement an API method so that your teammate can use
your API to implement his command.
💡
|
Do take a look at Section 2.4, “Model component” before attempting to modify the Model component.
|
-
Add a
removeTag(Tag)
method. The specified tag will be removed from everyone in the scheduler.-
Hints
-
Think about how you can use SLAP to design the method. Where should we place the main logic of deleting tags?
-
Find out which of the existing API methods in
Scheduler
andEvent
classes can be used to implement the tag removal logic.Scheduler
allows you to update an event, andEvent
allows you to update the tags.
-
Solution
-
Implement a
removeTag(Tag)
method inScheduler
. Loop through each event, and remove thetag
from each event. -
Add a new API method
deleteTag(Tag)
inModelManager
. YourModelManager
should callScheduler#removeTag(Tag)
. -
Add new tests for each of the new public methods that you have added.
-
See this PR for the full solution.
-
-
Scenario: You are in charge of ui
. During a beta testing session, your team is observing how the users use your
scheduler application. You realize that one of the users occasionally tries to delete non-existent tags from an event,
because the tags all look the same visually, and the user got confused. Another user made a typing mistake in their
command but did not realize they had done so because the error message wasn’t prominent enough. A third user keeps
scrolling down the list because they keep forgetting the index of the last event in the list. Your job is to
implementimprovements to the UI to solve all these problems.
💡
|
Do take a look at Section 2.2, “UI component” before attempting to modify the UI component.
|
-
Use different colors for different tags inside event cards. For example,
holidays
tags can be all in brown, andappointments
tags can be all in yellow.Before
After
-
Hints
-
The tag labels are created inside the
EventCard
constructor (new Label(tag.tagName)
). JavaFX’sLabel
class allows you to modify the style of each Label, such as changing its color. -
Use the .css attribute
-fx-background-color
to add a color. -
You may wish to modify
DarkTheme.css
to include some pre-defined colors using CSS, especially if you have experience with web-based CSS.
-
-
Solution
-
You can modify the existing test methods for
EventCard
to include testing the tag’s color as well. -
See this PR for the full solution.
-
The PR uses the hash code of the tag names to generate a color. This is deliberately designed to ensure consistent colors each time the application runs. You may wish to expand on this design to include additional features, such as allowing users to set their own tag colors, and directly saving the colors to storage, so that tags retain their colors even if the hash code algorithm changes.
-
-
-
-
Modify
NewResultAvailableEvent
such thatResultDisplay
can show a different style on error (currently it shows the same regardless of errors).Before
After
-
Hints
-
NewResultAvailableEvent
is raised byCommandBox
which also knows whether the result is a success or failure, and is caught byResultDisplay
which is where we want to change the style to. -
Refer to
CommandBox
for an example on how to display an error.
-
-
Solution
-
Modify
NewResultAvailableEvent
's constructor so that users of the event can indicate whether an error has occurred. -
Modify
ResultDisplay#handleNewResultAvailableEvent(NewResultAvailableEvent)
to react to this event appropriately. -
You can write two different kinds of tests to ensure that the functionality works:
-
The unit tests for
ResultDisplay
can be modified to include verification of the color. -
The system tests
schedulerSystemTest#assertCommandBoxShowsDefaultStyle() and schedulerSystemTest#assertCommandBoxShowsErrorStyle()
to include verification forResultDisplay
as well.
-
-
See this PR for the full solution.
-
Do read the commits one at a time if you feel overwhelmed.
-
-
-
-
Modify the
StatusBarFooter
to show the total number of events in the scheduler.Before
After
-
Hints
-
StatusBarFooter.fxml
will need a newStatusBar
. Be sure to set theGridPane.columnIndex
properly for eachStatusBar
to avoid misalignment! -
StatusBarFooter
needs to initialize the status bar on application start, and to update it accordingly whenever the scheduler is updated.
-
-
Solution
-
Modify the constructor of
StatusBarFooter
to take in the number of events when the application just started. -
Use
StatusBarFooter#handleschedulerChangedEvent(schedulerChangedEvent)
to update the number of events whenever there are new changes to the scheduler. -
For tests, modify
StatusBarFooterHandle
by adding a state-saving functionality for the total number of events status, just like what we did for save location and sync status. -
For system tests, modify
SchedulerSystemTest
to also verify the new total number of events status bar. -
See this PR for the full solution.
-
-
Scenario: You are in charge of storage
. For your next project milestone, your team plans to implement a new feature
of saving the scheduler to the cloud. However, the current implementation of the application constantly saves the
scheduler after the execution of each command, which is not ideal if the user is working on a limited internet
connection. Your team decided that the application should instead save the changes to a temporary local backup file
first, and only upload to the cloud after the user closes the application. Your job is to implement a backup API for the
scheduler storage.
💡
|
Do take a look at Section 2.5, “Storage component” before attempting to modify the Storage component.
|
-
Add a new method
backupScheduler(ReadOnlyScheduler)
, so that the scheduler can be saved in a fixed temporary location.-
Hint
-
Add the API method in
SchedulerStorage
interface. -
Implement the logic in
StorageManager
andXmlSchedulerStorage
class.
-
-
Solution
-
See this PR for the full solution.
-
-
By creating this command, you will get a chance to learn how to implement a feature end-to-end, touching all major components of the app.
Scenario: You are a software maintainer for scheduler
, as the former developer team has moved on to new projects.
The current users of your application have a list of new feature requests that they hope the software will eventually
have. The most popular request is to allow adding additional comments/notes about a particular event, by providing a
flexible remark
field for each event, rather than relying on tags alone. After designing the specification for the
remark
command, you are convinced that this feature is worth implementing. Your job is to implement the remark
command.
Edits the remark for an event specified in the INDEX
.
Format: remark INDEX r/[REMARK]
Examples:
-
remark 1 r/Anniversary.
Edits the remark for the first event asAnniversary.
-
remark 1 r/
Removes the remark for the first event
Let’s start by teaching the application how to parse a remark
command. We will add the logic of remark
later.
Main:
-
Add a
RemarkCommand
that extendsCommand
. Upon execution, it should just throw anException
. -
Modify
SchedulerParser
to accept aRemarkCommand
.
Tests:
-
Add
RemarkCommandTest
that tests thatexecute()
throws an Exception. -
Add new test method to
SchedulerParserTest
, which tests that typing "remark" returns an instance ofRemarkCommand
.
Let’s teach the application to parse arguments that our remark
command will accept. E.g. 1 r/Likes to drink coffee.
Main:
-
Modify
RemarkCommand
to take in anIndex
andString
and print those two parameters as the error message. -
Add
RemarkCommandParser
that knows how to parse two arguments, one index and one with prefix 'r/'. -
Modify
SchedulerParser
to use the newly implementedRemarkCommandParser
.
Tests:
-
Modify
RemarkCommandTest
to test theRemarkCommand#equals()
method. -
Add
RemarkCommandParserTest
that tests different boundary values forRemarkCommandParser
. -
Modify
SchedulerParserTest
to test that the correct command is generated according to the user input.
Let’s add a placeholder on all our EventCard
s to
display a remark for each event later.
Main:
-
Add a
Label
with any random text insideEventListCard.fxml
. -
Add FXML annotation in
EventCard
to tie the variable to the actual label.
Tests:
-
Modify
EventCardHandle
so that future tests can read the contents of the remark label.
We have to properly encapsulate the remark in our
Event
class. Instead of just using a String
,
let’s follow the conventional class structure that the codebase already uses by adding a Remark
class.
Main:
-
Add
Remark
to model component (you can copy fromVenue
, remove the regex and change the names accordingly). -
Modify
RemarkCommand
to now take in aRemark
instead of aString
.
Tests:
-
Add test for
Remark
, to test theRemark#equals()
method.
Now we have the Remark
class, we need to actually use it inside
Event
.
Main:
-
Add
getRemark()
inEvent
. -
You may assume that the user will not be able to use the
add
andedit
commands to modify the remarks field (i.e. the event will be created without a remark). -
Modify
SampleSchedulerDataUtil
to add remarks for the sample data (delete yourscheduler.xml
so that the application will load the sample data when you launch it.)
We now have Remark
s for Event
s, but they will be gone when we exit the application. Let’s modify
XmlAdaptedEvent
to include a Remark
field
so that it will be saved.
Main:
-
Add a new XML field for
Remark
.
Tests:
-
Fix
invalidAndValidEventSheduler.xml
,typicalEventsScheduler.xml
,validScheduler.xml
etc., such that the XML tests will not fail due to a missing<remark>
element.
Since Event
can now have a Remark
, we should add a helper method to
EventBuilder
, so that users are able to
create remarks when building a Event
.
Tests:
-
Add a new method
withRemark()
forEventBuilder
. This method will create a newRemark
for the event that it is currently building. -
Try and use the method on any sample
Event
inTypicalEvents
.
Our remark label in EventCard
is still a placeholder.
Let’s bring it to life by binding it with the actual remark
field.
Main:
-
Modify
EventCard
's constructor to bind theRemark
field to the `Event’s remark.
Tests:
-
Modify
GuiTestAssert#assertCardDisplaysEvent(…)
so that it will compare the now-functioning remark label.
We now have everything set up… but we still can’t modify the remarks. Let’s finish it up by adding in actual logic for
our remark
command.
Main:
-
Replace the logic in
RemarkCommand#execute()
(that currently just throws anException
), with the actual logic to modify the remarks of an event.
Tests:
-
Update
RemarkCommandTest
to test that theexecute()
logic works.
See this PR for the step-by-step solution.
Target user profile:
-
has a need to manage a significant number of events
-
prefer desktop apps over other types
-
can type fast
-
prefers typing over mouse input
-
is reasonably comfortable using CLI apps
Value proposition: manage events faster than a typical mouse/GUI driven app
Priorities: High (must have) - * * *
, Medium (nice to have) - * *
, Low (unlikely to have) - *
Priority | As a … | I want to … | So that I can… |
---|---|---|---|
|
university student |
add time and event for an venue |
locate my classroom at specific timings(s) |
|
university student |
search for an event |
see the details for that specific event |
|
university student |
create repeated events |
schedule repeated events easily |
|
university student |
see my academic timetable |
know what modules I am taking and plan accordingly |
|
organized university student |
classify events into different categories |
identify which event belongs to which category |
|
busy university student |
know what task I need to complete by a certain date |
plan my schedule in an efficient manner |
|
university student |
create a new event |
keep track of all my events |
|
university student |
delete an event |
remove a canceled event |
|
university student |
update the event details |
update the event if there is a change in event details |
|
busy university student |
type fewer words when I enter a command |
save some time |
|
forgetful university student |
receive email reminders for my events |
be reminded of events even if I do not check the scheduler |
|
disorganized university student |
notified if I create events that have a time conflict |
know if my events have time clashes |
|
university student |
view a filtered list of my events |
prepare for my upcoming events according to the filter criteria |
|
disorganized university student |
mark events which I attended or did not attend |
keep track of my attended and unattended events |
|
university student |
see attended events |
keep track of events I attended |
|
university student |
monitor and track deadlines |
keep track of my upcoming deadlines |
|
popular university student |
know my friends' contact |
can contact them whenever I want |
|
organized university student |
send event details to my friends |
plan events together with my friends |
|
goal oriented university student |
view statistics of my attended events |
know how many events I attended |
|
university student |
see all the public holidays |
avoid planning certain events on a public holiday |
|
university student |
customize my profile |
keep track of my profile and view it whenever I want |
|
university student |
obtain details of the teaching staff |
clarify my questions with the teaching staff |
|
university student |
obtain details of a speaker easily |
view their credentials |
|
university student with private events |
encrypt certain events |
prevent others from obtaining details of certain events |
|
busy university student |
get the shortest path from current location to next |
reduce my traveling time |
|
university student who love to control things remotely |
add events using email |
schedule my events even when I am not using the application |
|
university student who has a bad sense of direction |
display the location of an event on a map |
locate the venue of my event and not be lost |
|
sleep deprived university student |
record my estimated sleeping hours |
monitor the number of hours I sleep at night |
|
unmotivated university student |
shown a random cat video |
stay motivated to study |
|
university student who likes to exercise |
sync my fitness trackers to the application |
monitor the number of steps I take |
|
lazy university student |
interact with scheduler using syntax closer to natural language |
feel more interactive when using the application |
|
university student |
sync events with social media |
share with my acquaintance about my events |
|
university student who has poor eyesight |
add events by voice |
reduce reading and typing |
|
university student who has poor eyesight |
be able to hear events being read out |
reduce my reliance on what is being displayed on the screen |
|
university student who is used to the Google ecosystem |
sync my scheduler with my own Google Calendar |
be in sync with that calendar |
(For all use cases below, the System is the Scheduler
and the Actor is the user
, unless specified otherwise)
MSS
-
User requests to add a new event
-
iScheduler Xs Max adds the new event(s) into the scheduler
-
iScheduler Xs Max show a list of latest events
Use case ends.
Extensions
-
1a. The event to be added is repeated event
-
1a1. Scheduler generates repeating events
-
MSS
-
User requests to list events
-
iScheduler Xs Max shows a list of events
-
User requests to edit an event in the list
-
iScheduler Xs Max edit(s) the targeted event(s)
Use case ends.
Extensions
-
1a. The user does not input any specific options
-
1a1.iScheduler Xs Max targets only the targeted event
-
-
1b. The user inputs a select all option
-
1b1. iScheduler Xs Max target all associated events of the targeted event
-
-
1c. The user inputs a select upcoming option
-
1c1. iScheduler Xs Max target all upcoming and associated event of the targeted event
-
MSS
-
User requests to list events
-
iScheduler Xs Max shows a list of events
-
User requests to delete an event in the list
-
iScheduler Xs Max delete(s) the targeted event(s)
Use case ends.
Extensions
-
1a. The user does not input any specific options
-
1a1.iScheduler Xs Max targets only the targeted event
-
-
1b. The user inputs a select all option
-
1b1. iScheduler Xs Max target all associated events of the targeted event
-
-
1c. The user inputs a select upcoming option
-
1c1. iScheduler Xs Max target all upcoming and associated event of the targeted event
-
-
Should work on any mainstream OS as long as it has Java
9
or higher installed. -
Should be able to add events that repeat up to 100 times without any problem.
-
Should be able to hold up to 1000 events 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 on both 32-bit and 64-bit environments.
-
Should work without any dependence of other software.
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. |
-
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 events. 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 are retained.
-
-
Adds new event(s)
-
Test case:
add n/Study With Yoko s/tomorrow at 3pm d/Study again and again rt/monthly t/ad-hoc
Expected: Adds an event with event name:'Study with Yoko', start date time:'tomorrow 3pm', description:'study again and again', repeat type:monthly, tag:ad-hoc
-
-
Edits existing events
-
Prerequisites: List all events using the
list
command. Multiple events in the list. -
Test case:
edit 1 n/Dinner with Joy
Expected: Edits the 1st event’s event name to Dinner with Joy.
-
-
Deleting an event while all events are listed
-
Prerequisites: List all events using the
list
command. Multiple events in the list. -
Test case:
delete 1
Expected: The first event is deleted from the list. Event name of the deleted event shown in the status message. -
Test case:
delete 0
Expected: No event is deleted. Error details are shown in the status message. -
Other incorrect delete commands to try:
delete
,delete x
(where x is larger than the list size)
Expected: Similar to previous.
-