From ed0be92c04fd42bf8da9e035755fd193ab137ced Mon Sep 17 00:00:00 2001 From: Patrick Date: Mon, 15 Apr 2024 16:52:31 +0800 Subject: [PATCH] Modify the DG Key changes: * More Features are added under Implementation * UML diagrams are added --- docs/DeveloperGuide.md | 119 +++++------------- .../FindEmployeeNameActivityDiagram.puml | 17 +++ .../FindEmployeeNameSequenceDiagram.puml | 69 ++++++++++ .../FindEmployeeNameActivityDiagram.png | Bin 0 -> 25721 bytes .../FindEmployeeNameSequenceDiagram.png | Bin 0 -> 21094 bytes 5 files changed, 117 insertions(+), 88 deletions(-) create mode 100644 docs/diagrams/FindEmployeeNameActivityDiagram.puml create mode 100644 docs/diagrams/FindEmployeeNameSequenceDiagram.puml create mode 100644 docs/images/FindEmployeeNameActivityDiagram.png create mode 100644 docs/images/FindEmployeeNameSequenceDiagram.png diff --git a/docs/DeveloperGuide.md b/docs/DeveloperGuide.md index 0c739a1f344..08024cfe5d6 100644 --- a/docs/DeveloperGuide.md +++ b/docs/DeveloperGuide.md @@ -13,9 +13,16 @@ title: Developer Guide 5. [Storage Component](#storage-component) 6. [Common Classes](#common-classes) 4. [Implementations](#implementation) - 1. [Editing a specific tag](#editing-tag) + 1. [Add new employee](#adding-new-employee) + 1. [Implementation](#implementation-add-new-employee) + 2. [Design considerations](#design-consideration-add-employee) + 2. [Editing a specific tag](#editing-tag) 1. [Design considerations](#design-consideration-edit-tag) 2. [Implementation](#implementation-edit-tag) + 3. [Deleting a specific employee](#deleting-employee) + 1. [Implementation](#implementation-delete-employee) + 4. [Finding a specific employee with name](#finding-employee-name) + 1. [Implementation](#implementation-fiding-employee-name) 5. [Documentation, logging, testing, configuration, dev-ops](#documentation) 6. [Appendix: Requirements](#requirements) 1. [Product scope](#product-scope) @@ -179,9 +186,9 @@ Classes used by multiple components are in the `seedu.address.commons` package. This section describes some noteworthy details on how certain features are implemented. -### \[Implemented\] Add new employee +### \[Implemented\] Add new employee -#### Implementation +#### Implementation The proposed implementation of adding a new employee is facilitated by `AddCommand` and `AddCommandParser`. The `AddCommand` class encapsulates the logic for adding a new employee, while the `AddCommandParser` class is responsible for parsing the arguments and returning an `AddCommand` object. @@ -218,7 +225,7 @@ The following activity diagram summarizes what happens when a user executes an ` The `AddCommand` class is designed to be easily extensible. For example, if a new field is added to the `Person` class, the `AddCommand` class can be easily modified to accommodate the new field. -#### Design considerations: +#### Design considerations: **Aspect: How `/add` executes:** @@ -230,90 +237,6 @@ The `AddCommand` class is designed to be easily extensible. For example, if a ne * Pros: Provides a more guided experience. * Cons: May be slower for users who are comfortable with the system. -### \[Proposed\] Undo/redo feature - -#### Proposed Implementation - -The proposed undo/redo mechanism is facilitated by `VersionedPayBack`. It extends `PayBack` with an undo/redo history, stored internally as an `payBackStateList` and `currentStatePointer`. Additionally, it implements the following operations: - -* `VersionedPayBack#commit()` — Saves the current employee state in its history. -* `VersionedPayBack#undo()` — Restores the previous employee state from its history. -* `VersionedPayBack#redo()` — Restores a previously undone employee state from its history. - -These operations are exposed in the `Model` interface as `Model#commitPayBack()`, `Model#undoPayBack()` and `Model#redoPayBack()` 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 `VersionedPayBack` will be initialized with the initial employee state, and the `currentStatePointer` pointing to that single employee state. - -![UndoRedoState0](images/UndoRedoState0.png) - -Step 2. The user executes `delete 5` command to delete the 5th person in the employee. The `delete` command calls `Model#commitPayBack()`, causing the modified state of the employee after the `delete 5` command executes to be saved in the `payBackStateList`, and the `currentStatePointer` is shifted to the newly inserted employee state. - -![UndoRedoState1](images/UndoRedoState1.png) - -Step 3. The user executes `add n/David …​` to add a new person. The `add` command also calls `Model#commitPayBack()`, causing another modified employee state to be saved into the `payBackStateList`. - -![UndoRedoState2](images/UndoRedoState2.png) - -
:information_source: **Note:** If a command fails its execution, it will not call `Model#commitPayBack()`, so the employee state will not be saved into the `PayBackStateList`. - -
- -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#undoPayBack()`, which will shift the `currentStatePointer` once to the left, pointing it to the previous employee state, and restores the employee to that state. - -![UndoRedoState3](images/UndoRedoState3.png) - -
:information_source: **Note:** If the `currentStatePointer` is at index 0, pointing to the initial PayBack state, then there are no previous PayBack states to restore. The `undo` command uses `Model#canUndoPayBack()` 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 an undo operation goes through the `Logic` component: - -![UndoSequenceDiagram](images/UndoSequenceDiagram-Logic.png) - -
:information_source: **Note:** The lifeline for `UndoCommand` should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline reaches the end of diagram. - -
- -Similarly, how an undo operation goes through the `Model` component is shown below: - -![UndoSequenceDiagram](images/UndoSequenceDiagram-Model.png) - -The `redo` command does the opposite — it calls `Model#redoPayBack()`, which shifts the `currentStatePointer` once to the right, pointing to the previously undone state, and restores the employee to that state. - -
:information_source: **Note:** If the `currentStatePointer` is at index `payBackStateList.size() - 1`, pointing to the latest employee state, then there are no undone PayBack states to restore. The `redo` command uses `Model#canRedoPayBack()` 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 employee, such as `list`, will usually not call `Model#commitPayBack()`, `Model#undoPayBack()` or `Model#redoPayBack()`. Thus, the `payBackStateList` remains unchanged. - -![UndoRedoState4](images/UndoRedoState4.png) - -Step 6. The user executes `clear`, which calls `Model#commitPayBack()`. Since the `currentStatePointer` is not pointing at the end of the `payBackStateList`, all employee states after the `currentStatePointer` will be purged. Reason: It no longer makes sense to redo the `add n/David …​` command. This is the behavior that most modern desktop applications follow. - -![UndoRedoState5](images/UndoRedoState5.png) - -The following activity diagram summarizes what happens when a user executes a new command: - - - -#### Design considerations: - -**Aspect: How undo & redo executes:** - -* **Alternative 1 (current choice):** Saves the entire employee list. - * 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. - -_{more aspects and alternatives to be added}_ - ### Editing a specific tag #### Design considerations: @@ -370,6 +293,26 @@ The following activity diagram summarizes what happens when a user executes a `/ ![DeleteActivityDiagram](images/DeleteActivityDiagram.png) +### Finding a specific emplyoee + +#### Implementation + +Given below is an example usage scenario and how the `/find :name KEYWORD` mechanism behaves at each step. + +Step 1. The user launches the application for the first time. + +Step 2. The user executes `/add NAME; PHONE; EMAIL; ADDRESS; YEAR_JOINED[; TAG]…` command to add a new employee. + +Step 3: The user executes `/find :name KEYWORD` command to find the employee who contains the `KEYWORD`. + +The following steps will show the updated employee panel list containing the employee who matches the `KEYWORD`. + +![FindEmployeeNameSequenceDiagram](images/FindEmployeeNameSequenceDiagram.png) + +The following activity diagram what happens when a user executes a `/find :name KEYWORD`. + +![FindEmployeeNameActivityDiagram](images/FindEmployeeNameActivityDiagram.png) + -------------------------------------------------------------------------------------------------------------------- ## **Documentation, logging, testing, configuration, dev-ops** diff --git a/docs/diagrams/FindEmployeeNameActivityDiagram.puml b/docs/diagrams/FindEmployeeNameActivityDiagram.puml new file mode 100644 index 00000000000..d546c2650ec --- /dev/null +++ b/docs/diagrams/FindEmployeeNameActivityDiagram.puml @@ -0,0 +1,17 @@ +@startuml +skin rose +skinparam ActivityFontSize 15 +skinparam ArrowFontSize 12 + +start +repeat + :User enters the /find command and the + :name with a keyword to find the employee.; + :PayBack reads user input; +repeat while () is (Incorrect [Command Format]) +->[else]; +:PayBack creates a NameContainsKeywordsPredicate object; +:PayBack updates filtered list of employees based on the predicate; +:PayBack displays updated list of employees; +stop +@enduml diff --git a/docs/diagrams/FindEmployeeNameSequenceDiagram.puml b/docs/diagrams/FindEmployeeNameSequenceDiagram.puml new file mode 100644 index 00000000000..d2c95385a21 --- /dev/null +++ b/docs/diagrams/FindEmployeeNameSequenceDiagram.puml @@ -0,0 +1,69 @@ +@startuml +!include style.puml +skinparam ArrowFontStyle plain +box Logic LOGIC_COLOR_T1 +participant ":LogicManager" as LogicManager LOGIC_COLOR +participant ":PayBackParser" as PayBackParser LOGIC_COLOR +participant ":FindCommandParser" as FindCommandParser LOGIC_COLOR +participant "f:FindCommand" as FindCommand LOGIC_COLOR +participant "r:CommandResult" as CommandResult LOGIC_COLOR +end box + +box Model MODEL_COLOR_T1 +participant "m:Model" as Model MODEL_COLOR +end box + +[-> LogicManager : execute("/find :name Patrick") +activate LogicManager + +LogicManager -> PayBackParser : parseCommand("/find :name Patrick") +activate PayBackParser + +create FindCommandParser +PayBackParser -> FindCommandParser +activate FindCommandParser + +FindCommandParser --> PayBackParser +deactivate FindCommandParser + +PayBackParser -> FindCommandParser : parse(":name Patrick") +activate FindCommandParser + +create FindCommand +FindCommandParser -> FindCommand +activate FindCommand + +FindCommand --> FindCommandParser : +deactivate FindCommand + +FindCommandParser --> PayBackParser : f +deactivate FindCommandParser + +PayBackParser -[hidden]-> PayBackParser +destroy FindCommandParser + +PayBackParser --> LogicManager : f +deactivate PayBackParser + +LogicManager -> FindCommand : execute(m) +activate FindCommand + +FindCommand -> Model : updateFilteredPersonList(predicate) +activate Model + +Model --> FindCommand +deactivate Model + +create CommandResult +FindCommand -> CommandResult +activate CommandResult + +CommandResult --> FindCommand +deactivate CommandResult + +FindCommand --> LogicManager : r +deactivate FindCommand + +[<--LogicManager +deactivate LogicManager +@enduml \ No newline at end of file diff --git a/docs/images/FindEmployeeNameActivityDiagram.png b/docs/images/FindEmployeeNameActivityDiagram.png new file mode 100644 index 0000000000000000000000000000000000000000..9aaa847d9e0ddb09c1dd4ef56bb53ac66eea1a38 GIT binary patch literal 25721 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 z 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