PROJECT: ProManage

ProManage is a professional desktop application for companies that specialises in executing projects that undergoes a long and tedious planning phase. ProManage allow Manager and Employees of project teams to manage their team members and events easily. ProManage is optimized for those who prefer to work with a Command Line Interface (CLI) while still having the benefits of a Graphical User Interface (GUI).

Overview

This project portfolio documents my contributions to the development of the CS2113 project, as part of my team T16-2.

Summary of contributions

  • Major enhancement: Extended undo and redo capability of the model via the undo and redo commands to all existing or new commands.

    • Justification: With so many commands with different purposes, it can be very easy to make mistakes with unintended effects. For example, if an employee was wrongly renamed, without the undo command, the employee’s original name would be irreversibly lost. Being able to undo or redo commands improves the flexibility of the app, where users do not have to be worried that their wrongly entered commands have irreversible effects.

    • Highlights: This implementation required an overhaul of the existing Model to enable intelligent undo and redo capability to both the address book and event list, where the model recognises which of them to undo or redo. As they are intertwined with all other modifying commands, extensive testing was required to harden the app against bugs and ensure that the commands worked properly in all scenarios.

  • Minor enhancement:

    • Created EditEvent command which allows the user to edit an event in the event list according to the parameters they entered. Regex was used to ensure that the input parameters were valid.

    • Created DeleteEvent command which allows the user to delete an event specified by its index on the filtered event list.

    • Created SelectEvent command which allows the user to select an event to show its attendees. It filters the person list on the UI to show only the attendees.

    • Implemented VersionedEventList class as a subclass of EventList but with historical record.

    • Implemented EventModel interface and PersonModel interface which are extended by the Model interface.

  • Code contributed: [Functional code]

  • Other contributions:

Contributions to the User Guide

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

Deleting an event : deleteEvent

Priority level: default,manager

Delete the specified event from ProManage.
Format: deleteEvent EVENT_INDEX

  • Deletes the event at the specified EVENT_INDEX.

  • The index refers to the index number shown in the displayed event list.

  • The index must be a positive integer 1, 2, 3, …​

Examples:

  • deleteEvent 2
    Deletes the 2nd event in the address book.

Selecting an event : selectEvent

Priority level: all

Selects the specified event by index and displays all employees attending the event.
Format: selectEvent 1

If any of the attendees are edited or removed, the filtered employee list will not update. The selectEvent command must be input again to view the updated list of attendees.

  • Selects the event at the specified EVENT_INDEX.

  • The index refers to the index number shown in the displayed event list.

  • The index must be a positive integer 1, 2, 3, …​

Examples:

  • selectEvent 2
    Selects the 2nd event in the address book and shows its attendees.

  • invite 1 to/1 (invites an attendee to an event)
    selectEvent 1 (select the event to show the attendee)
    edit 1 n/New Name (edit the name of the attendee)
    selectEvent 1 (re-selects the event to show the updated name of the attendee)

Undoing previous command : undo

Priority level: all

Restores the event schedule or address book to the state before the previous undoable command was executed. Only commands that modify the entries in the event schedule or address book are able to be undone. The unfiltered event schedule and address book will be displayed after undoing the previous command.
Format/Prompts:
Enter a command: undo

Undoable commands: those commands that modify the event schedule’s or address book’s content (add, delete, edit and clear).

Examples:

  • delete 1
    list all
    undo (reverses the delete 1 command)

  • select 1
    list all
    undo
    The undo command fails as there are no undoable commands executed previously.

  • delete 1
    clear
    undo (reverses the clear command)
    undo (reverses the delete 1 command)

Redoing the previously undone command : redo

Priority level: all

Reverses the most recent undo command. Only commands that modify the entries in the event schedule or address book will be reversed. The unfiltered event schedule and address book will be displayed after redoing the command.
Format/Prompts:
Enter a command: redo

Examples:

  • delete 1
    undo (reverses the delete 1 command)
    redo (reapplies the delete 1 command)

  • delete 1
    redo
    The redo command fails as there are no undo commands executed previously.

  • delete 1
    clear
    undo (reverses the clear command)
    undo (reverses the delete 1 command)
    redo (reapplies the delete 1 command)
    redo (reapplies the clear command)

Contributions to the Developer Guide

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

Undo/Redo Feature

Current Implementation

The undo and redo feature allows users to undo and redo their previous commands. The commands that can be undone are only those that modify the contents of the address book or event list, such as adding a person or event within the app. This is managed within ModelManager.

The undo/redo mechanism is implemented separately for the address book and event list by the VersionedAddressBook and VersionedEventList. They are extensions of the superclass AddressBook and EventList respectively with a history of states stored internally as an addressBookStateList for the address book, eventListStateList for the event list, and a currentStatePointer in each of them. To ensure that the address book and event list are undone/redone correctly, a StateHistoryList (sub-class of LinkedList) is kept in the ModelManager to keep track of the list that was modified.

The following operations were implemented:

  • VersionedAddressBook#commit() — Saves the current address book state in its internal history.

  • VersionedAddressBook#undo() — Restores the previous address book state from its internal history.

  • VersionedAddressBook#redo() — Restores a previously undone address book state from its internal history.

  • VersionedEventList#commit() — Saves the current event list in its internal history.

  • VersionedEventList#undo() — Restores the previous event list state from its internal history.

  • VersionedEventList#redo() — Restores a previously undone event list state from its internal history.

These operations are exposed in the Model interface as Model#commitAddressBook(), Model#undoAddressBook() and Model#redoAddressBook() for the address book, and Model#commitEventList(), Model#undoEventList() and Model#redoEventList() for the event list. Two wrapper methods, Model#undo() and Model#redo() are also implemented. These will be the methods called by the commands undo and redo.

The StateHistoryList implements the following operations for the Model to decide whether to undo/redo the address book or event list:

  • StateHistoryList#getCurrentState() - Retrieves the latest record state.

  • StateHistoryList#getNextState() - Retrieves the next (previously undone) record state.

  • StateHistoryList#decrementPointer() - Shifts record state pointer back. Called during undo.

  • StateHistoryList#incrementPointer() - Shifts record state pointer forward. Called during redo.

A command can either modify the address book, event list, or both. Within the model, the StateHistoryList keeps track of all model methods which modify the address book or event list, and stores a history record value within itself for each method called.

When undoing a command, the ModelManager requests for the latest record by StateHistoryList#getCurrentState() and calls the corresponding VersionedAddressBook#undo() and/or VersionedEventList#undo(). The ModelManager also informs the StateHistoryList that an undo command was issued to shift the pointer back.

When redoing a command,the ModelManager requests for the next record from StateHistoryList and calls the corresponding VersionedAddressBook#redo() and/or VersionedEventList#redo(). The ModelManager also informs the StateHistoryList that an undo command was issued to shift the pointer forward.

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 and VersionedEventList will be initialized with the initial address book and event list states, and the currentStatePointers will point to their single state respectively. The StateHistoryList is instantiated as an empty list.

UndoRedoModelInitializationStateListDiagram

Step 2. The user executes add (with valid input arguments) to add a person to the address book. The add command calls Model#addPerson(), which will add a state record index integer STATE_ADDRESSBOOK to the StateHistoryList. This command is the last entered command, therefore the pointer in StateHistoryList shifts forward. The add command also calls Model#commitAddressBook(), causing the modified state of the address book after the add command executes to be saved in the addressBookStateList, and the address book currentStatePointer is shifted to the newly inserted address book state. The currentStatePointer for VersionedEventList remains unchanged as only the VersionedAddressBook was modified..

UndoRedoNewCommandAB1StateListDiagram
If a command fails its execution, it will not call Model#commitAddressBook() or Model#commitEventList(), so the address book or event list state will not be saved into addressBookStateList or eventListStateList. The StateHistoryList will also not save a record state.

Step 3. The user realises that they have accidentally added the wrong person, and decides to undo the action by executing the undo command. The undo command calls Model#undo(), which gets the latest record state from StateHistoryList and recognises that the VersionedAddressBook was modified in the previous command. The Model#undoAddressBook() is then called, which shift the currentStatePointer once to the left, pointing it to the previous address book state, and restores the address book to that state. Also, the pointer in StateHistoryList is shifted to the left by StateHistoryList#decrementPointer.

UndoRedoNewCommandAB2StateListDiagram
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#canUndo() 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:

UndoRedoSequenceDiagram

Step 4. The redo command does the opposite — it calls Model#redo(), which shifts the currentStatePointer in either the VersionedAddressBook and/or VersionedEventList once to the right, pointing to the previously undone state, and restores it to that state.

If the pointer is at index stateHistoryList.size() - 1, pointing to the latest memory state, then there are no undone states to restore. The redo command uses Model#canRedo() to check if this is the case. If so, it will return an error to the user rather than attempting to perform the redo.
UndoRedoNewCommandAB3StateListDiagram

Step 5. The user then decides to add a new event to the event list, by the addEvent command. The addEvent command calls Model#addEvent(), which will add a state record index integer STATE_EVENTLIST to the StateHistoryList. This command is the last entered command, therefore the pointer in StateHistoryList shifts forward. The addEvent command also calls Model#commitEventList(), causing the modified state of the event list after the addEvent command executes to be saved in the eventListStateList, and the event list currentStatePointer is shifted to the newly inserted address book state. The currentStatePointer for VersionedAddressBook remains unchanged as only the VersionedEventList was modified.

UndoRedoNewCommandELStateListDiagram

Step 6. The user then decides to execute clear. Since this command must clear both the address book and event list, the clear command calls both Model#commitAddressBook() and Model#commitEventList(). It adds a state record index integer STATE_BOTH to the StateHistoryList. The currentStatePointer in both VersionedAddressBook and VersionedEventList shifts to point to the newly added states respectively.

UndoRedoNewCommandBothStateListDiagram

Future Improvements

Façade Class for VersionedAddressBook and VersionedEventList

To improve abstraction and shift the responsibility of deciding which list to undo or redo away from the Model, a façade class can be created, e.g. VersionedProManage, to hold the StateHistoryList, VersionedAddressBook and VersionedEventList. The model will then only have to call the VersionedProManage#undo() or VersionedProManage#redo() methods.