By: NUS School of Computing AY1819S1 F10-1 Team
Since: Aug 2018
Licence: MIT
- 1. Introduction
- 2. Setting up
- 3. Design
- 4. Implementation
- 4.1. Undo/Redo feature
- 4.2. Command Auto Complete
- 4.3. Permission System
- 4.4. Supporting commands for Permission System
- 4.5. Command Shortcuts
- 4.6. Assignment Management
- 4.7. Archive employee feature
- 4.8. Changes to architecture
- 4.9. Restore employee feature
- 4.10. Login/Logout feature
- 4.11. Leave Application
- 4.12. Logging
- 4.13. Configuration
- 5. Documentation
- 6. Testing
- 7. Dev Ops
- Appendix A: User Stories
- Appendix B: Use Cases
- Appendix C: Non Functional Requirements
- Appendix D: Glossary
- Appendix E: Instructions for Manual Testing
1. Introduction
1.1. What is OASIS?
OASIS is an employee management application that brings your office together. It is an all-in-one tool for office administration and communication that runs on any computer. With the use of primarily textual commands coupled with a beautiful user interface, you can get your administrative jobs done faster than with traditional point-and-click applications. Some of the main features that you can look forward to using are employee records management, authentication and permissions, assignment management, and leave management.
1.2. Architectural Overview
This document describes the design, implemented features and possible features that could be implemented in OASIS. These could serve as guidelines for future developers. Happy Programming!!
2. Setting up
It is time to start, follow the instructions below to get your developing environment ready.
2.1. Prerequisites
-
JDK
9
or laterJDK 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.
2.2. Setting up the project in your computer
-
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
XmlAdaptedPerson.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)
2.3. Verifying the setup
-
Run the
seedu.address.MainApp
and try a few commands -
Run the tests to ensure they all pass.
2.4. Configurations to do before writing code
2.4.1. Configuring the coding style
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.
2.4.2. Updating documentation to match your fork
After forking the repo, the documentation will still have the SE-EDU branding and refer to the se-edu/addressbook-level4
repo.
If you plan to develop this fork as a separate product (i.e. instead of contributing to se-edu/addressbook-level4
), 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.
2.4.3. Setting up CI
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) |
2.4.4. Getting started with coding
When you are ready to start coding,
-
Get some sense of the overall design by reading Section 3.1, “Architecture”.
-
Take a look at Appendix A, User Stories.
3. Design
3.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 .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: 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. 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.
Events-Driven nature of the design
The Sequence Diagram below shows how the components interact for the scenario where the user issues the command delete 1
.
delete 1
command (part 1)
Note how the Model simply raises a AddressBookChangedEvent when the Address Book 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.
delete 1
command (part 2)
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.
3.2. UI component
API : Ui.java
The UI consists of a MainWindow
that is made up of parts e.g.CommandBox
, ResultDisplay
, PersonListPanel
, 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 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.
3.3. Logic component
API :
Logic.java
-
Logic
uses theAddressBookParser
class to parse the user command. -
This results in a
Command
object which is executed by theLogicManager
. -
The command execution can affect the
Model
(e.g. adding a person) 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.
delete 1
Command3.4. Model component
API : Model.java
The Model
,
-
stores a
UserPref
object that represents the user’s preferences. -
stores the Address Book data.
-
exposes an unmodifiable
ObservableList<Person>
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.
3.5. Storage component
API : Storage.java
The Storage
component,
-
can save
UserPref
objects in json format and read it back. -
can save the Address Book data in xml format and read it back.
3.6. Common classes
Classes used by multiple components are in the seedu.addressbook.commons
package.
4. Implementation
This section describes some noteworthy details on how certain features are implemented.
4.1. Undo/Redo feature
4.1.1. Current Implementation
The undo/redo mechanism is facilitated by VersionedAddressBook
.
It extends AddressBook
with an undo/redo history, stored internally as an addressBookStateList
and currentStatePointer
.
Additionally, it implements the following operations:
-
VersionedAddressBook#commit()
— Saves the current address book state in its history. -
VersionedAddressBook#undo()
— Restores the previous address book state from its history. -
VersionedAddressBook#redo()
— Restores a previously undone address book state from its history.
These operations are exposed in the Model
interface as Model#commitAddressBook()
, Model#undoAddressBook()
and Model#redoAddressBook()
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 VersionedAddressBook
will be initialized with the initial address book state, and the currentStatePointer
pointing to that single address book state.
Step 2. The user executes delete 5
command to delete the 5th person in the address book. The delete
command calls Model#commitAddressBook()
, causing the modified state of the address book after the delete 5
command executes to be saved in the addressBookStateList
, and the currentStatePointer
is shifted to the newly inserted address book state.
Step 3. The user executes add n/David …
to add a new person. The add
command also calls Model#commitAddressBook()
, causing another modified address book state to be saved into the addressBookStateList
.
If a command fails its execution, it will not call Model#commitAddressBook() , so the address book state will not be saved into the addressBookStateList .
|
Step 4. The user now decides that adding the person was a mistake, and decides to undo that action by executing the undo
command. The undo
command will call Model#undoAddressBook()
, which will shift the currentStatePointer
once to the left, pointing it to the previous address book state, and restores the address book to that state.
If the currentStatePointer is at index 0, pointing to the initial address book state, then there are no previous address book states to restore. The undo command uses Model#canUndoAddressBook() 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#redoAddressBook()
, which shifts the currentStatePointer
once to the right, pointing to the previously undone state, and restores the address book to that state.
If the currentStatePointer is at index addressBookStateList.size() - 1 , pointing to the latest address book state, then there are no undone address book states to restore. The redo command uses Model#canRedoAddressBook() 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 address book, such as list
, will usually not call Model#commitAddressBook()
, Model#undoAddressBook()
or Model#redoAddressBook()
. Thus, the addressBookStateList
remains unchanged.
Step 6. The user executes clear
, which calls Model#commitAddressBook()
. Since the currentStatePointer
is not pointing at the end of the addressBookStateList
, all address book states after the currentStatePointer
will be purged. We designed it this way because it no longer makes sense to redo the add n/David …
command. This is the behavior that most modern desktop applications follow.
The following activity diagram summarizes what happens when a user executes a new command:
4.1.2. Design Considerations
Aspect: How undo & redo executes
-
Alternative 1 (current choice): Saves the entire address book.
-
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 person being deleted). -
Cons: We must ensure that the implementation of each individual command are correct.
-
Aspect: Data structure to support the undo/redo commands
-
Alternative 1 (current choice): Use a list to store the history of address book 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
andVersionedAddressBook
.
-
-
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.
-
4.2. Command Auto Complete
When user type in the command box, OASIS will predict what commands the user is going to run, and display a drop down list containing all suggestions.
4.2.1. Current Implementation
Auto complete functionality in OASIS is supported by both org.controlsfx.control.textfield.TextFields
API and AutoCompleteCommandHelper
class.
When text is typed into the Command Box (commandTextField
object), it will display a drop down list of possible commands, that is retrieved from AutoCompleteCommandHelper
class.
-
org.controlsfx.control.textfield.TextFields
API is utilised to display the drop down list. -
AutoCompleteCommandHelper
class is used to generate theSet
of possible commands with the input given inCommandBox
.
Aspect: Logic & UI
When CommandBox
class is being constructed, bindAutoCompletion
method provided by TextFields
API will be utilised to create an auto-completion binding between the commandTextField
object and AutoCompleteCommandHelper#autoCompleteWord
method.
With this binding, whenever commandTextField
is updated, it will display a drop down list of possible commands retrieved from AutoCompleteCommandHelper#autoCompleteWord
if it exist.
Design Considerations
-
Alternative 1 (Current Implementation): Make use of
TextFields
API-
Pros: Easy to implement.
-
Pros: Can view ALL possible commands.
-
Cons: There is a slight delay before the drop down list appear. Can potentially slow down users who type fast.
-
-
Alternative 2: Immediately place predicted command as text into
commandTextField
.-
Pros: No delay, predicted command is immediately displayed.
-
Cons: Only able to view 1 possible command.
-
4.3. Permission System
There are several commands in OASIS that should not be executable by every user. E.g. Add and Delete commands should only be usable by user with the power to hire and dismiss other employees. Permission system is used to ensure that each user are only able to perform commands that they are authorised to when using OASIS.
4.3.1. Current implementation
Aspect: Model
Model of a person have been changed to reflect the permission that each user possesses.
The class highlighted in Red in the following diagram represents the classes that have been created to support the Permission system.
-
Permission
class contains the all possible Permissions that are available to a Person. -
All values in
PermissionSet
must be fromPermission
class. -
PresetPermission
is a enumeration class that resides inPermissionSet
.PresetPermission
is utilised byPermissionSet
class to generate aPermissionSet
object that represents the set of permission that a certain type of User will possess.
As of v1.4, PresetPermission only contains the following preset: Admin , Manager and Employee .
|
Aspect: Storage
Permission have to be stored in the addressbook where the information for Person
is stored. This is achieved through creation of XmlAdaptedPermission
, which was utilised by XmlAdaptedPerson
to store the information in an xml file.
The class highlighted in Red in the following diagram represents the classes that have been created to support the Permission system.
Aspect: Logic
Commands will be required to populate a requiredPermission:PermissionSet
object with all Permission
the command requires user to have to execute the command.
The following is an example on how to assign permission to a Command.
public AddCommand(Person person) {
requireNonNull(person);
requiredPermission.addPermissions(Permission.ADD_EMPLOYEE); (1)
toAdd = person;
}
1 | The method to assign permission to a command |
The code to ensure that each command is only executed by user with the correct permission is located in Command#execute
.
When any command executes, the command will first check if the logged in user possess the correct authorization by comparing requiredPermission
with the user’s permissionSet
object, before performing the command.
Given below is an example scenario of how commands will be executed.
Step 1. The user enters a command delete 1
into the CLI.
Step 2. The system retrieves current user’s PermissionSet
Step 3. The system compares user’s PermissionSet
with DeleteCommand
's requiredPermission
.
-
Two different cases
-
User have required permissions, execute command.
-
User don’t have required permissions, show error message.
-
The following activity diagram summarizes what happens when a user executes a command.
4.3.2. Design Considerations
-
Alternative 1 (Current Implementation): Assign permission to each individual user and restrict commands executable by user based on permission assigned.
-
Pros: Easy to control the commands a user can access.
-
Cons: Need to ensure that there is at least 1 user that can assign permissions to other users. Implementation requires knowledge of multiple components of OASIS.
-
-
Alternative 2: Create subclass of
Person
to be used to identify the role of the user. E.g.Employee
andManager
class. The commands executable by the user will depend on their class.-
Pros: Easy to implement. Only require small modification in existing classes.
-
Cons: Commands cannot be freely assigned to users as it is now dependent on which subclass the user is. E.g. we cannot create an
Employee
with a subset of the commands available toManager
.
-
4.4. Supporting commands for Permission System
The following are commands that have been implemented to support the Permission System.
4.4.1. Modify Permissions of employee
This feature allows the user to change the Permission that have been allocated to an employee.
This feature can only be performed by users that have ASSIGN_PERMISSION permission.
|
Current Implementation
This feature allows the user to indicate what permission to add and remove based on the prefix.
-
-a PERMISSION_TO_ADD
to add permission -
-r PERMISSION_TO_REMOVE
to remove permission
Aspect: Logic
To implement this new command syntax, ModifyPermissionCommandParser
utilises ArgumentTokenizer#tokenize
to generate a ArgumentMultiMap
. The ArgumentMultiMap
's key
contains the prefix, and value
contains the list of keywords that succeeded the prefix. There will also be a preamble
which is used to retrieve the index
of the targeted employee.
All the keywords is then added to either permissionToAdd:Set<Permission>
or permissionToRemove:Set<Permission>
depending on their prefix. The 2 sets, together with the index, will be then be used to create ModifyPermissionCommand
.
A ParseException will be thrown if any of the permission names are invalid. |
When ModifyPermissionCommand
is executed, it will then modify the permission of targeted employee, adding permission from permissionToAdd
and removing permissions from permissionToRemove
.
The command will be executed successfully if at least one permission is added or removed. |
The following is a sequence diagram that visualizes how this operation works.
Design Considerations
-
Alternative 1 (Current Implementation): Allow both adding and removing of multiple permissions with one command with the use of prefixes.
-
Pros: Only need to learn how to use 1 command. Can perform both adding and removing of permissions with a single command.
-
Cons: Harder to implement then other alternatives.
-
-
Alternative 2: Create 2 separate commands to handle adding and removing of permission. (E.g.
AddPermissionCommand
andRemovePermissionCommand
)-
Pros: Easy to implement.
-
Cons: User will have to remember 2 commands. In addition to this, User also have to execute at least 2 commands if they wish to both add and remove permission.
-
4.4.2. View Permissions of employee
This feature allows the user to view the permissions that have been allocated to an employee.
This feature can only be performed by users that have ASSIGN_PERMISSION permission.
|
Current Implementation
To use this command, the user only have to give an INDEX
parameter.
Aspect: Logic
To process this command, ViewPermissionCommandParser
simply utilise ParserUtil#parseIndex
to parse the argument into the index
of the target employee.
The index will then be used to create a ViewPermissionCommand
.
When ViewPermissionCommand
is executed, it will retrieve the targeted employee from the Model
, and print out the list of permission that the employee possesses.
The following is a sequence diagram that visualizes how this operation works.
Design Considerations
-
Alternative 1 (Current Implementation): Display permissions in the command box result text field.
-
Pros: Easy to implement.
-
Cons: Can only view permissions of a single employee at a time.
-
-
Alternative 2: Display all permissions in the Person card.
-
Pros: Able to view all permissions, of all employee in one go.
-
Pros: No commands required.
-
Cons: Person card can get very cluttered if employee have many permissions.
-
4.5. Command Shortcuts
With various keyboard combinations user will be able to access command keywords quickly.
4.5.1. Current Implementation
Keyboard shortcuts in OASIS is implemented using KeyEvent handler within the CommandBox
class.
When valid key combination is pressed, the text in the command box will be replaced with the corresponding command word.
4.5.2. Aspect: Logic & UI
Within the CommandBox
class, there is KeyEvent handler method which handles keyboard’s input. If the key pressed is a valid
combination, then the text field in command box will be replaced with the command corresponding to one of the existing commands.
4.5.3. Design Consideration
Current Implementation: Use the KeyEvent handler in the CommandBox
class.
-
Pros: It is easy and simple to implement.
-
Cons: As the number of commands increase, there might not be enough key combination to accommodate all the commands.
4.6. Assignment Management
There are four commands related to assignments. The four commands are addassignment
,
listassignments
, deleteassignment
and editassignment
.
These commands could only be executed by user with the appropriate permission.
4.6.1. Current Implementation
An assignment is represented by Assignment
model object. Assignment
object stores the information
of AssignmentName
, Author
and Description
.
The following diagram shows the classes to model Assignment
:
Add Assignment
The following sequence diagram shows how adding an assignment works:
Below is an example on how addassignment mechanism behaves when a new assignment is added.
-
When user executes
addassignment -au ASSIGNMENTNAME -au AUTHOR -de DESCRIPTION
command, it will be parsed, then newAssignment
object will be initialized. If the current user does not have the permission to add new assignment, error message will be thrown. -
If user has the required permission, then the current
Assignment
object will be compared to the existing assignments in the system withhasAssignment(toAdd)
method. If the new object is unique, then it will be added into the system. However, if there is duplicate, the system will throwDuplicateAssignmentException
. -
The new
Assignment
will then be copied and transformed to becomeXmlAdaptedAssignment
object, which is then saved into a file by the Storage component.
Delete Assignment
Deleting an assignment form the system will remove it permanently! |
The following sequence diagram shows how deleting an assignment works:
Given is an example of how deleting an assignment mechanism behaves when it is executed.
-
When user executes the
deleteassignment INDEX
command,DeleteAssignmentCommand
object will be created. It then checks if the current user has the required permission to execute the command. If the user does not have the required permission, then and error message will be displayed. -
Meanwhile, if the user has the required permission, system gets the
Assignment
object to be deleted from list of assignments and finally delete it. It will then update the list in the Storage component.
List Assignment
The parsing sequence of listing all assignments is similar to adding and deleting an assignment. In addition, there is no permission
required to run listassignment
command.
When user executes listassignment
command, system will retrieve all assignment data in the Storage
component using updateFilteredAssignmentList()
and update the User Interface to list all the assignments.
public CommandResult runBody(Model model, CommandHistory history) {
requireNonNull(model);
model.updateFilteredAssignmentList(PREDICATE_SHOW_ALL_ASSIGNMENTS);(1)
EventsCenter.getInstance().post(new AssignmentListEvent());
return new CommandResult(MESSAGE_SUCCESS);
}
1 | The method to retrieve all assignments. |
Edit Assignment
Parsing the command when executing editassignment
is similar to deleteassignment
command.
-
When user executes
editassignment 1 -an ASSIGNMENTNAME
command, the command will be parsed. The given prefix and field indicate that the corresponding field ofAssignment
should be edited with the new field. To do so,EditAssignmentDescriptor
object will be produce. This object has the newly editedAssignment
object. -
After, the 'Permission' of the user will be check. If the user does not have the appropriate permission, error message will be displayed.
-
While if the user has the appropriate permission, the old
Assignment
object will be replaced with the newAssignment
object. -
The list of the assignments will then be updated in the Storage.
4.6.2. Design Consideration
Aspect: How the Assignment
object is stored
-
Alternative 1 (Current Implementation): store assignment information in Xml file.
-
Pros: Xml file has extensibility, as it has no fixed set of tags. Allowing future developers to enhance the information of the assignment.
-
Cons: Inefficient retrieval of information of the assignment when the storage size gets too big.
-
-
Alternative 2: store the assignment information using database system.
-
Pros: Fast and efficient retrieval of information, even when the amount of data is massive.
-
Cons: Separated system needs to be set up to store information. Thus, might result in additional cost.
-
4.7. Archive employee feature
Employees with the "DELETE_EMPLOYEE" permissions can delete employees in the system - related to firing employees in real life. Deleted employees from the active list will be moved to the archived list. This feature allows an employee to view the archive list of employees.
4.7.1. Proposed implementation
The archive employee feature is facilitated by VersionedArchiveList
, VersionedAddressBook
and UniquePersonList
.
Given below is an example usage scenario and how the VersionedArchiveList
, VersionedAddressBook
and UniquePersonList
behaves at each step.
Step 1. The user launches the application for the first time. The VersionedArchiveList
and VerisionedAddressBook
will be initialized with the initial archive list and address book state. The UniquePersonList
in both classes will be populated with data from read by storage.
Step 2. The user executes delete 3 command on the active list to delete the 3rd person in the active list. The selected Person object from the UniquePersonList
in VersionedAddressBook
will be transferred to the UniquePersonList
in VersionedArchiveList
and store the deleted Person’s data.
Step 3. The user executes archive command to change view from active list to the archive list.
Step 4. OASIS shows delete person in step 2 in the archive list on the left panel.
The following activity diagram summarizes what happens when a user executes delete and restore command:
4.8. Changes to architecture
This section documents the changes made to the architecture and its components.
Aspect: Model
Added a VersionedArchiveList
object. UniquePersonList
in VersionedArchiveList
will store 0 or more Person
objects.
The following diagram shows the class VersionedArchiveList
added to reflect the changes in the Model component:
Aspect: Storage
Added ArchiveListStorage
interface, XmlArchiveListStorage
and XmlSerializableArchiveList
classes to Storage component.
The following diagram shows the interface and classes added to the Storage component:
4.8.3. Design considerations
This section documents the considerations that were carefully studied for implementing this feature. The selected implementation method is denoted by "(current choice)" .
Aspect: Lifetime of objects in Archive list
-
Alternative 1 (current choice): Deleted permanently after being removed by user again.
-
Pros: Guaranteed no loss of data if an employee is accidentally deleted.
-
Cons: May have performance issues in terms of memory usage as employee records stored a few years back could still be stored.
-
-
Alternative 2: Deleted after a certain number of time has passed.
-
Pros: More efficient memory usage wont store old employee records which could cause high memory usage.
-
Cons: Loss of data possible if an employee is accidentally deleted and not restored right away.
-
Aspect: Data structure to support the archive commands
-
Alternative 1 (current choice): Use a list to store the archived employee objects.
-
Pros: Pros: Easy to implement. Only require small modification in existing classes. Faster access to archive list as the user doesn’t need to search every employee in the system to get the employees archived.
-
Cons: We must maintain a separate list for archived objects.
-
-
Alternative 2: Assign an archive attribute to each employee object in the active list and show only in the active list if archived attribute is false. In contrast show in the archive display list if archive attribute is true.
-
Pros: Only need to change 1 attribute when an employee is deleted.
-
Cons: "Archive" is an unusual attribute for a person and it will be time consuming to view the archive list as you must go through all employees to check the archive attribute.
-
4.9. Restore employee feature
Employees with the "RESTORE_EMPLOYEE" permissions can restore employees from the archive list into the active list - related to hiring back old employees in real life. The restored employee will be moved back to the active list and removed from the archive list. Employees with “DELETE_EMPLOYEE” permissions can also completely remove employees in the archive list, permanently removing them from OASIS.
4.9.1. Proposed implementation
The Restore or completely remove employee feature is facilitated by VersionedArchiveList
, VersionedAddressBook
and UniquePersonList
.
Given below is an example usage scenario and how the VersionedArchiveList
, VersionedAddressBook
and UniquePersonList
behaves at each step.
Step 1. The user launches the application for the first time. The VersionedArchiveList
and VersionedAddressBook
will be initialized with the initial archive list and address book state. The UniquePersonList
in both classes will be populated with data from read by storage.
Step 2. The user executes archive command to change view from active list to the archive list.
Step 3a1. The user executes restore 1 command to restore the first person in the archive list. The selected Person object from the UniquePersonList
in VersionedArchiveList
will be transferred to the UniquePersonList
in VersionedAddressBook
and be back in the active list.
Step 3a2. The user executes list command to change view from archive list to active list.
Step 3a3. The employee restored in Step 3a1 is shown in active list
Step 3b1. The user executes delete 1 command to completely remove the first person in the archive list from OASIS. The selected Person object from the UniquePersonList
in VersionedArchiveList
will be deleted.
Step3b2. Archive list updated and deleted employee is removed.
The following activity diagram summarizes what happens when a user executes remove and restore command:
The following sequence diagram shows the method calls when the restore command is executed:
4.9.2. Design considerations
This section documents the considerations that were carefully studied for implementing this feature. The selected implementation method is denoted by "(current choice)" .
Aspect: Data restored from employee
-
Alternative 1 (current choice): All data from the time when the employee was deleted will be restored when the employee object is restored.
-
Pros: Employee being restored wont lose any important information and skips the task of having to key in all the employee’s information again.
-
Cons: Employee may have changed job scopes and permissions assigned may be different, this could lead to big problems if employees have permissions they are not supposed to have.
-
-
Alternative 2: Only certain information like name and phone number will be restored when employee is restored.
-
Pros: More efficient memory usage, don’t have to store other information about the employee. Restored employees will not have the same permissions they had before which could save potential problems of employees having permissions they are not supposed to have.
-
Cons: Could potentially add more workload to the user restoring the employee as the user will need to fill in all necessary details of the employee when the restore command is executed.
-
4.10. Login/Logout feature
4.10.1. Current Implementation
The Login/Logout feature is facilitated through the use of creating a login screen before the application begins, ensuring that the user starts by logging into his account.
These operations are exposed in the MainWindow class through fillLoginParts()
, removeLoginWindow()
, removeInnerElements()
, processLogin(LoginEvent)
and processLogout(LogoutEvent)
While the login screen is displayed, other usual UI elements, such as the browserPanel , PersonListPanel , ResultDisplay , StatusBarFooter , CommandBox are not initialized at all, so they cannot be accessed.
|
To ensure that most tests still work with a login system, the MainWindowHandle , used by all GUI tests, automatically logs the user in right after the UI element loads.
|
The following sequence diagram shows a high level overview between the components when a User begins logging into OASIS.
This situation branches if the user’s input of username and password is invalid. The following activity diagram shows the branching case:
The communication between UI
, Logic
and Model
is mainly from using EventsCenter
and firing events to tell the other components to act.
The following detailed steps show how the program works as the user login.:
-
When the program is started, UIManager creates the MainWindow and tells it to
fillLoginParts()
. -
The user enters his details, and clicks login.
-
This causes the
LoginForm
to fire aLoginEvent
onto the central EventBus, with the username and password saved into theLoginEvent
. -
The Logic Manager catches the LoginEvent. It then checks if the username and password combination matches a person in the system, or the admin user. To do so, it communicates with the
model
to retrieve everyone in the system.-
If there is no successful match, then a
FailedLoginEvent
is fired. The LoginForm catches this Event and displays the error message provided by the FailedLoginEvent.
-
-
If there is a successful match, then a
SuccessfulLoginEvent
is fired. This event contains the person that is currently logging in, wrapped in a User object. -
The
mainWindow
class catches theSuccessfulLoginEvent
and processes it, removing the login UI Elements and replacing it withfillInnerParts()
When the user wishes to logout, he enters logout, which triggers the following:
-
The
LogoutEvent
is fired by theLogoutCommand
. -
The
mainWindow
class catches theLogoutCommand
and processes it, removing the main UI elements and replacing it with thefillLoginParts()
4.10.2. Design Considerations
Aspect: How the login screen is displayed
The login screen needs to be displayed to the user in some fashion.
-
Alternative 1 (current choice): Create a login screen before initializing other UI elements on the fly.
-
Pros: One single window. Clear to the user which window to focus on. Most applications work this way, so it should be familiar to the majority of our users.
-
Cons: Harder to implement. Need to take into account other possible UI elements, preload only those that are required, and ensure that tests stay supported.
-
-
Alternative 2: Build another UI Window just for login. Before logging in, this window will popup. Once the user has logged in, the login window will close and the main window will pop up.
-
Pros: Far easier to implement. Login system abstracted away from other functionality.
-
Cons: It will be hard to maintain the same window size as the login window, if the user resizes it. More coupling would be required to maintain the same window size. Very odd and unfamiliar to most users. No application today opens a login window, then on successful login, closes that login window and opens a new one, meant for the user to use. This can cause a lot of user confusion. They may think that:
-
The new window is representing error message, it should not have opened.
-
The application had an error and unexpectedly shutdown.
-
The new window is from another application that the user has running on his computer.
-
They did something wrong (perhaps they pressed the button to close the window instead?)
-
-
Aspect: UI Elements to build the login system
When OASIS boots up, the login screen needs to be displayed. The UI elements used to build this login screen needs to be decided on where they should go on the screen.
-
Alternative 1 (current choice): Using the same placeholders already available, place the appropriate UI elements on the screen.
-
Pros: Easy to implement. Utilizes the same placeholders currently in the system, so will adapt the same way to window re-sizing.
-
Cons: Looks uglier than if the window was created solely to enter login data
-
-
Alternative 2: Build the window from scratch to show login UI elements.
-
Pros: Nicer, the UI elements are built for login
-
Cons: Harder to implement. Need a good graphic designer to plan out how said nice login screen would look like, otherwise it’d just look bad and you might as well go with Alternative 1.
-
Aspect: How the UI and Logic elements should communicate.
Whenever a login is done by the User, the UI
, Logic
and Model
elements need to communicate to handle the event.
-
The
UI
needs to provide the User Input information. -
The
Logic
needs to perform the check of whether this is a valid Username and Password combination. -
The
Model
needs to provide the data for the logic to do it’s work.
There needs to be a solution to handle this cleanly and without causing unnecessary coupling, as this will likely be required to be extended upon in the long run.
-
Alternative 1 (current choice): Utilize the EventBus to allow
UI
,Model
andLogic
to communicate-
Pros: Reduces coupling, as UI, Model and Logic doesn’t need to know about each other. If required, other classes can also listen for the Event and process accordingly
-
Cons: Needs some work to implement. Requires building up new classes.
-
-
Alternative 2: Let UI, Logic and Model know about each other, allowing them to call the relevant methods and do the relevant checks
-
Pros: Easy to implement.
-
Cons: Grealy increases coupling. Not a good design decision, as it will make it harder to maintain the code in the future.
-
-
Alternative 3: Build a command like system (similar to how Commands are implemented in the system) for UI to talk to Logic. Logic then uses the Command system to reply back to UI.
-
Pros: Reduces the amount of coupling added into the system. Provides a way for UI to get Logic to do things, in case more functionality is added that uses UI input.
-
Cons: Very time consuming to implement. Hard to design as there is no clear functionality that might also need this system in the future.
-
4.10.3. Username and Password storage
To store username and password, the class Person
has been extended to include a Username and Password variable as well. These two variables represent the Username and Password stored in the system for that Person.
4.10.4. Admin account
There is a possibility that the entire system is cleared of all employees (i.e when initializing, or an accidental deleting of all employees). To resolve this issue, an admin account is added that will ensure that there is always a user that can login in. The admin account cannot be removed and always has full access rights. By default, the username of the admin account is Admin
and the password is Pa55w0rd
.
The password of the admin account can be modified through the passwd command.
|
4.10.5. Design Considerations
Aspect: Where the admin account password can be stored.
Since the admin account would cause the system to be very insecure if it’s password couldn’t be changed, the admin account password must be changeable and stored somewhere, so that it persists across sessions. But where?
-
Alternative 1 (current choice): Place the storage in User Preferences
-
Pros: Easy to implement. A nice, centralized place to store general application information.
-
Cons: If the file is deleted, then the password will revert back to the default, which leaves the admin account vulnerable.
-
As we plan to store the system in a central server, the admin account’s information will be stored in the server, and not locally. As this is intended to change before the final release, we went for the option that is the simplest to implement.
-
-
Alternative 2: Add it into the address book.xml file
-
Pros: If the password was attempted to be removed through the deletion of the file, then this will also delete everyone in the system as well, thus rendering the access to the admin account useless.
-
Cons: Since the file is stored in an xml format, it is easy for any dedicated attacker to find and remove the password information, reverting it to the default. Harder to implement, as would require large changes in the address book parser.
-
-
Alternative 3: Store it within environment variables
-
Pros: Somewhat harder to find. Ensures that admin password remains even when the data files are deleted.
-
Cons: The admin password would not transfer over systems for the average user. It would be difficult and require technical knowledge of the user to get it to transfer.
-
4.10.6. passwd Command
To change the password, a passwd command is required. However, this command is very different from the other commands. Specifically, it requires a chain of input, and subsequent inputs from the user should not be stored in history (otherwise the user’s password are easily retrievable).
This is, however, not easy to handle. This command history is automatically populated by LogicManager
, which Command.execute
does not have access to. To complicate things further, CommandParser
doesn’t handle exceptions like storing a command for future use, or redirecting user input to a specific Command.
To resolve this issue, CommandResult
is extended to hold interceptors - a list of ProcessCommand
. ProcessCommand
is a functional interface, similar to Function except that it can throw a specific error as well. To implement passwd, it returns an implementor of ProcessCommand
to CommandResult
, which forwards it to LogicManager
.
LogicManager
now accepts ProcessCommand
from CommandResult
, adding them to a list. As long as there exists at least one ProcessCommand
, further messages are pushed to ProcessCommand
instead of processed normally, and they aren’t added to history.
Other possible design considerations are shown below.
4.10.7. Design Considerations
Aspect: How to implement the passwd Command
-
Alternative 1 (current choice): Build a foundation of
ProcessCommand
that will intercept user input and process it instead. If it does so,LogicManager
doesn’t add the command to history.-
Pros: Doesn’t increase coupling unnecessarily. Allows other functions to utilize this, allowing other commands to also easily extend to a chain of user inputs.
-
Cons: Harder to implement. Somewhat hard to understand, as it requires knowledge of lambdas and functional interfaces.
-
-
Alternative 2: Apply a hack for passwd where
LogicManager
checks that if a passwd command is ongoing, it redirects there instead.-
Pros: Easy to implement.
-
Cons: Greatly increases coupling. If further commands were to require the same functionality, this hack would need to be done again for that function.
-
4.10.8. Password Security
There is a need to have passwords be stored securely as opposed to being stored in plain text. The standard today is to have password be salted and hashed, which is also the standard that OASIS is implemented in.
All of this is handled within the Password
class. API: Password.java
The Password
class stores the password in plaintext (if available), the salt and the hash of the password. The salt and the hash are always available.
If the password object was created this session (i.e. the user changed his password this session), the plaintext will be available. Otherwise, if it was read from the saved XML file, then only the salt and hash are available.
When checking if 2 Passwords are the same, it is better to call isSamePassword to verify if the 2 passwords are the same. isSamePassword checks if both passwords would have had the same plaintext. Utilizing .equals would also require the salt to match as well.
|
4.10.9. Design Considerations
Aspect: How much security is required for the password
-
Alternative 1: Just store and save the password in plaintext.
-
Pros: Very easy to implement. Makes testing significantly easier. Can verify both appropriate input and output.
-
Cons: No security. Anyone who wants to view someone else’s password can just look into the data files and find it easily.
-
-
Alternative 2: Apply a hash to all passwords (i.e. SHA512).
-
Pros: Still somewhat easy to implement.
-
Cons: Testing becomes slightly harder as test code can no longer rely on getting the password in plaintext. Still not very secure, rainbowtables and hash crackers exist that can easily get back the plaintext password.
-
-
Alternative 3: (current choice): Apply a salt + hash to all passwords
-
Pros: Full security suite. Ensures that we aren’t storing our user’s passwords, so if the database is compromised, our user’s passwords aren’t easily broken.
-
Cons: Very hard to implement. Testing becomes significantly harder because calling
.equals
on Passwords that are the same may return false due to differences in salt.
-
4.11. Leave Application
In OASIS, an employee can make a leave application by specifying a description, as well as the dates, that he or she wishes to apply for. Users are also able to view a list of all their own leave applications and their details, while those with the required permissions have the added ability to view all leave applications of all employees, as well as approve or reject leave applications.
4.11.1. Current Implementation
A leave application is represented by a LeaveApplication
model object, as follows:
API: LeaveApplication.java
A leave applications contains a description, status (PENDING
, APPROVED
, or REJECTED
), and one or more dates.
The LeaveApplication constructor removes duplicates, and orders all dates in ascending order.
|
Applying for leave
This section describes and illustrates how an application for leave by a user works in OASIS.
The user issues a leaveapply
command, which includes a description and one or more dates, in the command box UI. The Logic Manager in the Logic Component is then called upon to execute the command. The Model is then updated with the changes, and finally the EventsCenter is notified of this, and it goes on to ask the Storage Component to update the stored file data. This component-level interaction is depicted as follows:
leaveapply -de family holiday -da 2018-11-11
commandThere are further interactions of the EventCenter reacting to the event raised with the Storage Component, but they are ommitted. |
Now, we zoom in on the Logic Component to have a more detailed look as to how the leaveapply
command is handled. The command entered is firstly parsed to ensure validity, and then a new LeaveApplication
is instantiated with the data parsed from the command. This LeaveApplication
is kept inside a newly created LeaveApplyCommand
, which is then executed to update the Person
in the Model, which corresponds to the user who applied for leave. The sequence diagram is as follows:
leaveapply -de family holiday -da 2018-11-11
is executedThere are further interactions within the Model component that are omitted. |
Given below is an example usage scenario and how the leave application mechanism behaves when a new leave application is made by an employee:
-
The user executes the
leaveapply -de family holiday -da 2018-11-11
command. TheLeaveApplication
will be initialized with the specifiedDescription
(family holiday), and one or moreDate
(2018-11-11), and itsLeaveStatus
will be the initial value ofPENDING
. -
The new
LeaveApplication
will then be added to its correspondingPerson
, which represents the employee that applied for the leave. Internally, a duplicatePerson
is created with the newly addedLeaveApplication
, and the originalPerson
in the Model will be replaced with that newPerson
(seeLeaveApplyCommand.java
for more details). -
In the
Storage
, theLeaveApplication
will be copied and transformed to become anXmlAdaptedLeaveApplication
object, which is then added into theXmlAdaptedPerson
representing the person who applied for the leave, and finally saved into a file by the Storage component.
Viewing leave applications
Employees can view their leave applications using the leavelist
command. Given below is an example usage scenario of how OASIS behaves when a user issues this command:
-
The user executes the
leavelist
command. -
The system checks if the user is an Admin, or has the required permissions (
VIEW_EMPLOYEE_LEAVE
orAPPROVE_LEAVE
). If so, all leave applications of all other users will be displayed. If not, only the current logged-in user’s own leave application records will be shown.
The following activity diagram summarizes how the command is executed:
See "Design Considerations - How leave applications are listed" for further explanation of how the list of leave applications are retrieved and displayed. |
Approving or rejecting leave applications
Users with the required permissions can approve or reject leave applications made by other users. Given below is an example usage scenario of when a user issues a leaveapprove 3
command:
-
The user executes the
leaveapprove 3
command (the index specified in the command is based on what is displayed when the user issues aleavelist
command). -
The corresponding
LeaveApplicationWithEmployee
is retrieved from the list. For more details aboutLeaveApplicationWithEmployee
, see "Design Considerations - How leave applications are listed". -
Internally, a duplicate
Person
is created, with the original leave application being replaced with a copy of itself with anAPPROVED
status. The originalPerson
in the Model will then be replaced with that newPerson
, similar to what happens when applying for leave.
The leaveapprove and leavereject commands behave is nearly identical fashion, with the only difference being what the status that the specified LeaveApplication is changed to (either APPROVED or REJECTED ).
|
4.11.2. Design Considerations
Aspect: How leave applications are stored
-
Alternative 1 (current choice): Saved only as a part of
Person
.-
Pros: Easy to implement.
-
Cons: We need to go through every
Person
to retrieve a list of allLeaveApplication
in the system to generate the list of all leaves.
-
-
Alternative 2: Stored only as a part of
AddressBook
.-
Pros: Easy to implement.
-
Cons: We need to go through every
LeaveApplication
in the system when retrieving theLeaveApplication
for a particularPerson
.
-
-
Alternative 3: Stored as a part
Person
as well asAddressBook
.-
Pros: Fast retrieval for a particular
Person
, as well as for the entire list ofLeaveApplication`s from `AddressBook
. -
Cons: Redundant and duplicate storage for each
LeaveApplication
. We need to ensure that when adding, editing, and deleting aLeaveApplication
, it is updated correctly in both parts of the Model as well as Storage.
-
Aspect: How leave applications are listed
-
Alternative 1 (current choice): Leave applications are tagged with the user that applied for them (see
LeaveApplicationWithEmployee.java
), and stored in aLeaveApplicationList.java
inAddressBook
in the Model Component. This additional tagging and storing into a list is done when the application is started where all leave applications are read from eachPerson
, and also when any leave application is created or updated.-
Pros: Leave applications stored are lightweight, as they are kept within the
Person
that applied for them, and do not have to contain fields that uniquely identify thatPerson
. -
Cons: This additional tagging, that only exists while the application is running, is slightly clumsy and not the best way to do it (see the note below Alternative 2 for more details).
-
-
Alternative 2: Leave applications contain the unique identification fields of a
Person
.-
Pros: Leave applications innately store who applied for them, so no additional processing is required when generating the list of leave applications.
-
Cons: Leave applications will have to be stored with 3 additional fields that are used to unique identify a
Person
. Also, if there are any updates to thePerson
, it must be ensured that their corresponding leave applications will also have to be updated correctly.
-
Alternative 2 is actually cleaner to implement and understand. However, Alternative 1 is currently implemented because a Person has 3 identification fields, which means that a lot of unnecessary information would have to be duplicated and stored. In future versions, we suggest that each Person be given a unique ID number that can be used to identify them. This would make it convenient for other entities, like LeaveApplication , to store which Person it is linked to, without too much overhead incurred.
|
4.12. 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 4.13, “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
4.13. Configuration
Certain properties of the application can be controlled (e.g App name, logging level) through the configuration file (default: config.json
).
5. Documentation
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. |
5.1. Editing Documentation
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.
5.2. Publishing Documentation
See UsingTravis.adoc to learn how to deploy GitHub Pages using Travis.
5.3. Converting Documentation to PDF format
We use Google Chrome for converting documentation to PDF format, as Chrome’s PDF engine preserves hyperlinks used in webpages.
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.
5.4. Site-wide Documentation Settings
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 |
5.5. Per-file Documentation Settings
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 |
5.6. Site Template
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 |
6. Testing
6.1. Running Tests
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
)
You may encounter a problem with running Gradle commands on the command line, with the following error message: Cannot find System Java Compiler. Ensure that you have installed a JDK (not just a JRE) and configured your JAVA_HOME system variable to point to the according directory. If you encounter this error, you can apply the fix shown here for Windows. |
6.2. Types of tests
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.address.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.address.commons.StringUtilTest
-
Integration tests that are checking the integration of multiple code units (those code units are assumed to be working).
e.g.seedu.address.storage.StorageManagerTest
-
Hybrids of unit and integration tests. These test are checking multiple code units as well as how the are connected together.
e.g.seedu.address.logic.LogicManagerTest
-
6.3. Troubleshooting Testing
Problem: HelpWindowTest
fails with a NullPointerException
.
-
Reason: One of its dependencies,
HelpWindow.html
insrc/main/resources/docs
is missing. -
Solution: Execute Gradle task
processResources
.
7. Dev Ops
7.1. Build Automation
See UsingGradle.adoc to learn how to use Gradle for build automation.
7.2. Continuous Integration
We use Travis CI and AppVeyor to perform Continuous Integration on our projects. See UsingTravis.adoc and UsingAppVeyor.adoc for more details.
7.3. Coverage Reporting
We use Coveralls to track the code coverage of our projects. See UsingCoveralls.adoc for more details.
7.4. Documentation Previews
When a pull request has 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.
7.5. Making a Release
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.
7.6. Managing Dependencies
A project often depends on third-party libraries. For example, Address Book 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)
Appendix A: User Stories
Priorities: High (must have) - * * *
, Medium (nice to have) - * *
, Low (unlikely to have) - *
Priority | As a/an … | I want to … | So that I can… |
---|---|---|---|
|
Employee |
See usage instructions |
Get help when I forget how to use a feature of the application |
|
Employee |
Log in |
Access the features of the system |
|
Employee |
Change my password |
Ensure that my account will not be compromised |
|
Employee |
View my own profile and personal information |
Check if it is up-to-date |
|
Employee |
Edit my contact information |
Other employees using the application can see my most recent contact information |
|
Employee |
Log out |
Prevent non-authorized users of my computer from accessing the system |
|
Employee with add employee permissions |
Add a new employee into my department |
Have the new hire listed in the system |
|
Employee with delete employee permissions |
Delete an employee from my department |
Remove employees that have left or have been fired |
|
Employee |
View all other employees in the system |
Find out more about employees in the company |
|
Employee |
View the profile of an employee in the system |
Find out more a certain employee |
|
Employee |
Apply for leave |
Get approval for my yearly leave from my manager |
|
Employee with view employee leave permissions |
View leave application of employees in my department |
See who has applied for leave |
|
Employee with approve leave permissions |
Approve or reject employee requests for leave |
Plan out future projects |
|
Employee with add project permissions |
Create a new project |
Have the new project listed in the application |
|
Employee with add project permissions |
Add an employee from any department into a project I created |
Have the employee listed in the project team |
|
Employee with add project permissions |
Remove an employee from a project I created |
Have the employee removed from the project team |
|
Employee with add project permissions |
Assign a task to an employee in one of my projects |
Delegate the required work in a project to the team |
|
Employee |
View all projects and project teams in the company |
Find out more about ongoing projects and employees in the project teams |
|
Employee |
View all projects that I am a part of |
Find out more about my projects and employees in the project team |
|
Employee |
View all tasks assigned to me by a project |
Check what I have to do for a particular project |
|
Employee with "Department Manager" permissions |
See the total manpower strength in each department |
Allocate manpower to department in need for more employees |
|
Employee |
Search for employees by a certain criteria (e.g. name, department, etc.) |
Find a particular employee easily |
|
Employee |
Sort employees in the display list by a certain criteria (e.g. name, department, etc.) |
View the employees sequentially and increasing clarity |
|
Employee |
Filter employees in the display list by a certain criteria |
Find details of a specific employee without going through every employee in the system |
|
Employee with view employee leave permissions |
View summary of approved leaves taken by employees in my department in the upcoming months |
Better plan upcoming projects and manpower distribution |
|
Employee with "Department Manager" permissions |
View workload of employees in my department |
Delegate the work evenly |
|
Employee |
See income summary |
Know my monthly income. |
|
Employee with "Administrative" permissions |
Modify the access permissions of an employee |
Accommodate to the tasks that an employee is allowed to do, possibly in line with promotions or demotions |
|
Employee with "Administrative" permissions |
Change the department of an employee |
Have the system reflect the change of an employee moving to another department |
|
Employee |
Upload a profile picture |
Other users can see my face when they access my profile in the system |
|
Employee |
View the list of past employees that have been removed from the system |
Check the archives for information about previous employees |
|
Employee with "Department Manager" permissions |
See the performance of every department/sector |
Keep track of which departments are not performing |
|
Employee with "Department Manager" permissions |
Record accomplishments of my employees |
Identify the better employees |
|
Employee with "Department Manager" permissions |
See the list of potential candidates for hiring |
Easily rank my top choices on who to hire |
|
Employee |
Submit claim |
Claim money related to company matters |
|
Employee |
Submit overtime claim |
Receive my overtime pay |
|
Employee with "Department Manager" permissions |
See the performance of individual employees |
Identify underperforming employees |
|
Employee |
View current assigned tasks to me |
Track which assignments I have left to finish |
|
Employee |
Write and send email |
Send an email to one or more employees in the company |
|
Employee with "Department Manager" permissions |
Find out how much I am paying my employees in total |
Calculate profit margins for the organization and any bonus for my employees at the end of the year |
Appendix B: Use Cases
(For all use cases below, the System is OASIS
and the Actor is the user
, unless specified otherwise)
Use case: Add a new employee
MSS
-
User login to the system
-
Oasis shows home page
-
User enters add employee with details
-
Oasis request for confirmation
-
User enters confirm
-
Oasis shows success message
Use case ends.
Extensions
-
1a. The credentials are invalid
Use case ends.
-
3a. User enters invalid details
-
3a1. OASIS shows an error message.
Use case resumes at step 2.
-
-
5a. The user chooses to cancel
Use case resumes at step 2.
Use case: Change user details
MSS
-
Employee login to the system
-
Oasis shows home page
-
Employee enters new user details
-
Oasis request for confirmation
-
Employee enters confirm
-
Oasis save the new user details and show success message
Use case ends.
Extensions
-
1a. The credentials are invalid
Use case ends.
-
3a. The details are in an invalid format.
-
3a1. OASIS shows an error message.
Use case resumes at step 2.
-
-
5a. The employee chooses to cancel
Use case resumes at step 2.
Use case: Delete employee
MSS
-
Employee login to the system
-
Oasis shows home page and displays list of employees
-
Employee executes delete command on a selected employee
-
Oasis deletes the employee, moves it to archive list and show success message
Use case ends.
Extensions
-
1a. The credentials are invalid
Use case ends.
-
2a. The list is empty.
Use case ends.
-
2b. Employee moves to archive list.
-
2b1. OASIS shows archive list.
-
2b2. Employee executes delete command on a selected employee
-
2b3. Oasis deletes the employee from the system and shows success message.
Use case ends.
-
-
3a. The chosen employee is invalid.
-
3a1. OASIS shows an error message.
-
-
3b. Employee does not have required permissions.
-
3a1. OASIS shows an error message.
Use case ends.
-
Use case: Apply for leave
MSS
-
User login to the system
-
OASIS shows home page
-
User enters description, start date and end date
-
OASIS displays confirmation message
-
User enters confirm
-
OASIS sends the application to the manager
-
OASIS shows success message
Use case ends.
Extensions
-
1a. The credentials are invalid
Use case ends.
-
3a. The format is invalid
-
3a1. OASIS shows an error message.
Use case resumes at step 2.
-
-
5a. The user chooses to cancel
Use case resumes at step 2.
Use case: List leave applications
Preconditions: user is logged in
MSS
-
User enters command to view leave applications
-
OASIS lists all leave applications of the user
Use case ends.
Extensions
-
1a. The format is invalid
-
1a1. OASIS shows an error message
User case ends.
-
-
2a. The user has permission to be able to view all employee leaves.
-
2a1. OASIS lists all the leaves allplicationsof all users
Use case ends.
-
Use case: Approve a leave application
Preconditions: user is logged in
MSS
-
User lists leave applications (Use case: List leave applications)
-
User enters command to approve a leave, and chooses the leave application to approve
-
OASIS approves the leave application
-
OASIS shows success message
Use case ends.
Extensions
-
2a. The format is invalid
-
2a1. OASIS shows an error message
-
-
2b. The index is does not exist
-
2b1. OASIS shows an error message
-
-
2c. The user does not have the permissions to approve leaves
-
2c1. OASIS shows an error message
Use case resumes at step 1.
-
Use case: Add new assignment
MSS
-
User login to the system
-
Oasis shows home page
-
User enters assignment name, author and description
-
Oasis displays confirmation message
-
User enters confirm
-
Oasis sends the application to the manager
-
Oasis shows success message
Use case ends.
Extensions
-
1a. The credentials are invalid
Use case ends.
-
3a. The format is invalid
-
3a1. OASIS shows an error message.
Use case resumes at step 2.
-
-
5a. The user chooses to cancel
Use case resumes at step 2.
Use case: List assignments
Precondition: user is logged in
MSS
-
User enterscommand to view list of assignments
-
OASIS lists all the assignments in the system
Use case ends.
Extensions
-
1a. The format is invalid
-
1a1. OASIS shows an error message
Use case ends.
-
Use case: Delete assignment
MSS
-
User login to the system
-
Employee executes delete command on selected assignment
-
Oasis deletes the assignment
Use case ends.
Extensions
-
1a. The credentials are invalid
Use case ends.
-
2a. The list is empty
Use case ends.
-
3a. Employee does not have the required permissions
-
3a1. Oasis shows an error message
Use case ends.
-
Use case: Edit assignment
MSS
-
Employee login to the system
-
Emplyee executes edit command on selected assignment
-
OASIS edits the assignment
Use case ends.
Extensions
-
1a. The credentials are invalid
Use case ends.
-
2a. The list is empty
Use case ends.
-
3a. Employee does not have the required permissions
-
3a1. Oasis shows an error message
Use case ends.
-
{More to be added}
Appendix C: Non Functional Requirements
-
Should work on any mainstream OS as long as it has Java
9
or higher installed. -
Should be able to hold up to 1000 persons 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.
-
A user should only be allowed to perform the tasks that he has the credentials to perform based on his login credentials
-
Passwords should be salted and hashed
-
The system should crash no more than 1 time a month.
-
The system should stay responsive even when there are updates to the GUI
Appendix E: 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. |
E.1. Launch and Shutdown
-
Initial launch
-
Download the jar file and copy into an empty folder
-
Double-click the jar file
Expected: Shows the login screen which prompts for a username and password.
-
-
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.
-
E.2. Logging into the system.
-
Logging in as an admin.
-
After opening OASIS, enter in "Admin" and "Pa55w0rd" as the password.
-
Click submit or press enter.
Expected: You should now be logged in as an admin.
-
-
Logging in as a User.
-
Delete the data folder to have OASIS start with the sample data set
-
Open OASIS.
-
Enter in "David Li" for the username and "Pa55w0rd" as the password. (David Li is only of the sample people loaded when no data file is found)
-
Click submit or press enter.
Expected: You should now be logged in as David Li.
-
E.3. Logging out of the system
-
Prerequisities: The user is currently logged in.
-
Type in "Logout" into the command box, then press enter.
Expected: You should now be logged out. OASIS should now show the login screen.
E.4. Editing details of currently logged in user
-
Editing a valid user.
-
Prerequisites: The user is currently logged in as a valid user in the system, not as an admin.
-
Test case:
myself -p 99900999
Expected: The currently logged in user’s phone number is changed to 99900999
-
-
Attempting to edit the admin user
-
Prerequisites: The user is currently logged in as admin.
-
Test cases: `myself -p 999009991
Expected: An error message saying that you cannot edit the admin account.
-
E.5. Deleting a person
-
Deleting a person while active persons are listed
-
Prerequisites: The user is currently logged in as someone with the permission to delete people. List all persons using the
active
command. Multiple persons in the list. -
Test case:
delete 1
Expected: First contact is deleted from the list. Details of the deleted contact shown in the status message. Timestamp in the status bar is updated. -
Test case:
delete 0
Expected: No person is deleted. Error details shown in the status message. Status bar remains the same. -
Other incorrect delete commands to try:
delete
,delete x
(where x is larger than the list size) {give more}
Expected: Similar to previous.
-
-
Deleting a person while archived persons are listed
-
Prerequisites: The user is currently logged in as someone with the permission to delete people. List all persons using the
archive
command. Multiple persons in the list. -
Test case:
delete 1
Expected: First contact is deleted from the archive list. Details of the deleted contact shown in the status message. Timestamp in the status bar is updated. -
Test case:
delete 0
Expected: No person is deleted. Error details shown in the status message. Status bar remains the same. -
Other incorrect delete commands to try:
delete
,delete x
(where x is larger than the list size) {give more}
Expected: Similar to previous.
-
E.6. Modify permission of an employee
-
Add or remove employee’s permission using
modifypermission
command while active persons are listed-
Adding one permission
-
Prerequisites: Logged in as a user with
ASSIGN_PERMISSION
permission. List all persons using theactive
command. At least one person in the list. Employee at index 1 withoutADD_EMPLOYEE
. -
Test case:
modifypermission 1 -a ADD_EMPLOYEE
Expected: ADD_EMPLOYEE permission is granted to employee at index 1. List of updated permission shown in status message. Timestamp in status bar is updated.
-
-
Adding multiple permission
-
Prerequisites: Logged in as a user with
ASSIGN_PERMISSION
permission. List all persons using theactive
command. At least one person in the list. Employee at index 1 withoutDELETE_EMPLOYEE
andEDIT_EMPLOYEE
permission. -
Test case:
modifypermission 1 -a DELETE_EMPLOYEE -a EDIT_EMPLOYEE
Expected: DELETE_EMPLOYEE & EDIT_EMPLOYEE permission is granted to employee at index 1. List of updated permission shown in status message. Timestamp in status bar is updated.
-
-
Removing multiple permission
-
Prerequisites: Logged in as a user with
ASSIGN_PERMISSION
permission. List all persons using theactive
command. At least one person in the list. Employee at index 1 withDELETE_EMPLOYEE
andEDIT_EMPLOYEE
permission. -
Test case:
modifypermission 1 -r DELETE_EMPLOYEE -r EDIT_EMPLOYEE
Expected: DELETE_EMPLOYEE & EDIT_EMPLOYEE permission is removed from employee at index 1. List of updated permission shown in status message. Timestamp in status bar is updated.
-
-
Adding and removing permission
-
Prerequisites: Logged in as a user with
ASSIGN_PERMISSION
permission. List all persons using theactive
command. At least one person in the list. Employee at index 1 with onlyADD_EMPLOYEE
permission. -
Test case:
modifypermission 1 -a DELETE_EMPLOYEE -r ADD_EMPLOYEE
Expected: Employee at index 1 only hasDELETE_EMPLOYEE
permission. List of updated permission shown in status message. Timestamp in status bar is updated.
-
-
Incorrect
modifypermission
commands to try:-
modifypermission 1 -a INVALID_PERMISSION
,modifypermission 1 -a DELETE_EMPLOYEE -r INVALID_PERMISSION
Expected: Status message shows that permission name is invalid. -
modifypermission
,modifypermission -a DELETE_EMPLOYEE -a EDIT_EMPLOYEE
Expected: Status message shows that the command format is invalid.
-
-
E.7. Leave management
-
Applying for leave while leave applications are listed
-
Prerequisites: List all leave applications using the
leavelist
command. There need not be any leave applications in the list. -
Test case:
leaveapply -de Family holiday -da 2018-12-05 -da 2018-12-06
Expected: A new leave application record with the description "Family holiday" and the dates "2018-12-05" and "2018-12-06" appears in the list. Details of the new leave application shown in the status message. Timestamp in the status bar is updated. -
Test case:
leaveapply -de Second family holiday -da 2018-12-16 -da 2018-12-15 -da 2018-12-15
Expected: A new leave application record with the description "Second family holiday" and the dates "2018-12-15" and "2018-12-16" in this order appears in the list. Details of the new leave application shown in the status message. Timestamp in the status bar is updated. -
Incorrect
leaveapply
commands to try:leaveapply -de -da 2018-12-15
(empty description),leaveapply -de Family holiday -da 2018-4-5
(date format not YYYY-MM-DD).
Expected: Status message shows that the command format is invalid.
-
-
Approving or rejecting leave applications as a user without either "VIEW_EMPLOYEE_LEAVE" or "APPROVE_LEAVE" permissions
-
Prerequisites: Login with a user that has neither "VIEW_EMPLOYEE_LEAVE" or "APPROVE_LEAVE" permissions (in a fresh install, the default username "Alex Yeoh" and password "Pa55w0rd" will suffice). List all leave applications using the
leavelist
command. There should be at least one leave application shown. Perform one leave application withleaveapply
if there isn’t. -
Test case:
leaveapprove 1
orleavereject 1
Expected: The status of the leave applications do not change. Error details shown in the status message. Status bar remains the same. -
Incorrect
leaveapply
commands to try:leaveapply -de -da 2018-12-15
(empty description),leaveapply -de Family holiday -da 2018-4-5
(date format not YYYY-MM-DD).
Expected: Status message shows that the command format is invalid.
-
-
Approving or rejecting leave applications as a user with either "VIEW_EMPLOYEE_LEAVE" or "APPROVE_LEAVE" permissions
-
Prerequisites: Login with the Admin account (the default username "Admin" and password "Pa55w0rd"). List all leave applications using the
leavelist
command. There should be at least one leave application shown. Perform one leave application with a non-admin user withleaveapply
if there isn’t. -
Test case:
leaveapprove 1
orleavereject 1
Expected: The status of the first leave application changes to "APPROVED" or "REJECTED", depending on the command issued. Status message indicates that the leave has been approved. Timestamp in status bar is updated. -
Incorrect
leaveapprove
commands to try:leaveapprove 0
(non-positive integer index)
Expected: Status message shows that the command format is invalid. -
Incorrect
leaveapprove
commands to try:leaveapprove 1000
(index larger than displayed list size).
Expected: Status message shows that the leave list index is invalid.
-
E.8. Saving data
-
Dealing with missing/corrupted data files
-
The data files are located under data folder. For example, data/addressbook.xml contains data of everyone stored in the system.
Data is only witten to disk if there is any change in the system. If OASIS was simply opened and closed with no changes made, no data file would be written. -
To modify the data files to simulate missing or corrupted data files, the element’s tag may be modified. Note that information between the tag may also be modified, but it could still remain valid and would not be detected as an error.
Expected: If a missing or corrupted data file is used, OASIS will then begin with an empty system instead.
-