Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement Undo command #147

Merged
merged 3 commits into from
Nov 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions docs/UserGuide.md
Original file line number Diff line number Diff line change
Expand Up @@ -185,8 +185,9 @@ Here is a reference table that briefly summarizes available commands:
| `list` | Displays all clients currently stored in the system. |
| `filter` | Filters clients based on specified criteria |
| `view` | Opens a split view showing detailed client information |
| `close` | Closes the split view of client details |
| `close` | Closes the split view of client details |
| `clear` | Deletes all clients from the system. |
| `undo` | Undoes latest command. |
| `help` | Displays a list of available commands and their usage. |
| `exit` | Exits the AgentAssist application. |

Expand Down Expand Up @@ -726,8 +727,9 @@ Each credit card tier is visually distinguished in the UI: Gold is marked with a
| **Edit Existing Client** | `edit <INDEX> n/<NAME> p/<PHONE> e/<EMAIL> a/<ADDRESS> j/<JOB> i/<INCOME> [t/<TIER>] [rn/<NEW REMARK>] [ra/<APPEND REMARK>]` | `edit 69 n/ GORDON MOORE p/ 77337733 e/ [email protected] a/ COM3 j/ doctor i/ 1000000000 ra/ added info` |
| **List All Clients** | `list` | `list` |
| **Filter Client List** | `filter [n/<NAME>] [p/<PHONE>] [e/<EMAIL>] [a/<ADDRESS>] [j/<JOB>] [r/<REMARK>] [t/<TIER>] [i/<INCOME>]` | `filter n/ GORDON MOORE j/ doctor t/ gold` |
| **View Client Details** | `view <INDEX>` | `view 1` |
| **View Client Details** | `view <INDEX>` | `view 1` |
| **Close Client Details** | `close` | `close` |
| **View Help** | `help` | `help` |
| **Undo Command** | `undo` | `undo` |
| **Exit Application** | `exit` | `exit` |
| **Clear All Data** | `clear` | `clear` |
28 changes: 28 additions & 0 deletions src/main/java/seedu/address/logic/commands/UndoCommand.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package seedu.address.logic.commands;

import static java.util.Objects.requireNonNull;

import seedu.address.logic.commands.exceptions.CommandException;
import seedu.address.model.Model;

/**
* Undoes the latest command. Throws an error if there is no previous command/ if previous command does
* not make changes to the Model.
*/
public class UndoCommand extends Command {

public static final String COMMAND_WORD = "undo";
public static final String MESSAGE_SUCCESS = "Your latest command has been undone.";
public static final String MESSAGE_NO_COMMAND_TO_UNDO = "Please input a command first in order to undo it.";

@Override
protected CommandResult execute(Model model) throws CommandException {
requireNonNull(model);
if (!model.hasPreviousCommand()) {
throw new CommandException(MESSAGE_NO_COMMAND_TO_UNDO);
}
model.undoCommand();
return new CommandResult(MESSAGE_SUCCESS);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import seedu.address.logic.commands.FilterCommand;
import seedu.address.logic.commands.HelpCommand;
import seedu.address.logic.commands.ListCommand;
import seedu.address.logic.commands.UndoCommand;
import seedu.address.logic.commands.ViewCommand;
import seedu.address.logic.parser.exceptions.ParseException;

Expand Down Expand Up @@ -86,6 +87,9 @@ public Command parseCommand(String userInput) throws ParseException {
case CloseCommand.COMMAND_WORD:
return new CloseCommand();

case UndoCommand.COMMAND_WORD:
return new UndoCommand();

default:
logger.finer("This user input caused a ParseException: " + userInput);
throw new ParseException(MESSAGE_UNKNOWN_COMMAND);
Expand Down
9 changes: 9 additions & 0 deletions src/main/java/seedu/address/model/AgentAssist.java
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,15 @@ public void removePerson(Person key) {
persons.remove(key);
}

/**
* Gets a copy of the current AgentAssist.
*/
public AgentAssist getCopy() {
AgentAssist newAgentAssist = new AgentAssist();
newAgentAssist.setPersons(this.getPersonList());
return newAgentAssist;
}

//// util methods

@Override
Expand Down
10 changes: 10 additions & 0 deletions src/main/java/seedu/address/model/Model.java
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,16 @@ public interface Model {
*/
void setPerson(Person target, Person editedPerson);

/**
* Undos previous command by setting currentAddressBook to historyAddressBook.
*/
void undoCommand();

/**
* Returns true if a command has been executed before.
*/
boolean hasPreviousCommand();

/** Returns an unmodifiable view of the filtered person list */
ObservableList<Person> getFilteredPersonList();

Expand Down
42 changes: 32 additions & 10 deletions src/main/java/seedu/address/model/ModelManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@
public class ModelManager implements Model {
private static final Logger logger = LogsCenter.getLogger(ModelManager.class);

private final AgentAssist agentAssist;
private AgentAssist historyAgentAssist = null;
private AgentAssist currentAgentAssist;
private final UserPrefs userPrefs;
private final FilteredList<Person> filteredPersons;
private final SimpleObjectProperty<Person> selectedPerson = new SimpleObjectProperty<>();
Expand All @@ -37,15 +38,22 @@ public ModelManager(ReadOnlyAgentAssist agentAssist, ReadOnlyUserPrefs userPrefs

logger.fine("Initializing with address book: " + agentAssist + " and user prefs " + userPrefs);

this.agentAssist = new AgentAssist(agentAssist);
this.currentAgentAssist = new AgentAssist(agentAssist);
this.userPrefs = new UserPrefs(userPrefs);
filteredPersons = new FilteredList<>(this.agentAssist.getPersonList());
filteredPersons = new FilteredList<>(this.currentAgentAssist.getPersonList());
}

public ModelManager() {
this(new AgentAssist(), new UserPrefs());
}

/**
* Save the history of the AgentAssist.
*/
private void saveHistory() {
historyAgentAssist = currentAgentAssist.getCopy();
}


//=========== UserPrefs ==================================================================================

Expand Down Expand Up @@ -86,36 +94,50 @@ public void setAgentAssistFilePath(Path agentAssistFilePath) {

@Override
public void setAgentAssist(ReadOnlyAgentAssist agentAssist) {
this.agentAssist.resetData(agentAssist);
saveHistory();
this.currentAgentAssist.resetData(agentAssist);
}

@Override
public ReadOnlyAgentAssist getAgentAssist() {
return agentAssist;
return currentAgentAssist;
}

@Override
public boolean hasPerson(Person person) {
requireNonNull(person);
return agentAssist.hasPerson(person);
return currentAgentAssist.hasPerson(person);
}

@Override
public void deletePerson(Person target) {
agentAssist.removePerson(target);
saveHistory();
currentAgentAssist.removePerson(target);
}

@Override
public void addPerson(Person person) {
agentAssist.addPerson(person);
saveHistory();
currentAgentAssist.addPerson(person);
updateFilteredPersonList(PREDICATE_SHOW_ALL_PERSONS);
}

@Override
public void setPerson(Person target, Person editedPerson) {
requireAllNonNull(target, editedPerson);
saveHistory();
currentAgentAssist.setPerson(target, editedPerson);
}

agentAssist.setPerson(target, editedPerson);
@Override
public void undoCommand() {
requireNonNull(this.historyAgentAssist);
this.setAgentAssist(this.historyAgentAssist);
}

@Override
public boolean hasPreviousCommand() {
return this.historyAgentAssist != null;
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great use of abstraction! Liked how clean this is with just adding saveHistory into each command works.


//=========== Selected Person ===========================================================================
Expand Down Expand Up @@ -185,7 +207,7 @@ public boolean equals(Object other) {
}

ModelManager otherModelManager = (ModelManager) other;
return agentAssist.equals(otherModelManager.agentAssist)
return currentAgentAssist.equals(otherModelManager.currentAgentAssist)
&& userPrefs.equals(otherModelManager.userPrefs)
&& filteredPersons.equals(otherModelManager.filteredPersons);
}
Expand Down
10 changes: 10 additions & 0 deletions src/test/java/seedu/address/logic/commands/AddCommandTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,16 @@ public void setPerson(Person target, Person editedPerson) {
throw new AssertionError("This method should not be called.");
}

@Override
public void undoCommand() {
throw new AssertionError("This method should not be called.");
}

@Override
public boolean hasPreviousCommand() {
throw new AssertionError("This method should not be called.");
}

@Override
public ObservableList<Person> getFilteredPersonList() {
throw new AssertionError("This method should not be called.");
Expand Down
45 changes: 45 additions & 0 deletions src/test/java/seedu/address/logic/commands/UndoCommandTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package seedu.address.logic.commands;

import static seedu.address.logic.commands.CommandTestUtil.assertCommandFailure;
import static seedu.address.logic.commands.CommandTestUtil.assertCommandSuccess;
import static seedu.address.testutil.TypicalIndexes.INDEX_FIRST_PERSON;
import static seedu.address.testutil.TypicalPersons.getTypicalAgentAssist;

import org.junit.jupiter.api.Test;

import seedu.address.logic.commands.exceptions.CommandException;
import seedu.address.model.Model;
import seedu.address.model.ModelManager;
import seedu.address.model.UserPrefs;
import seedu.address.model.person.Person;
import seedu.address.testutil.EditPersonDescriptorBuilder;
import seedu.address.testutil.PersonBuilder;

public class UndoCommandTest {

private Model model = new ModelManager(getTypicalAgentAssist(), new UserPrefs());

@Test
public void execute_undo_success() {
Person editedPerson = new PersonBuilder().build();
EditCommand.EditPersonDescriptor descriptor = new EditPersonDescriptorBuilder(editedPerson).build();
EditCommand editCommand = new EditCommand(INDEX_FIRST_PERSON, descriptor);
try {
CommandResult result = editCommand.execute(model);
} catch (CommandException ce) {
throw new AssertionError("Execution of command should not fail.", ce);
}

UndoCommand undoCommand = new UndoCommand();
Model expectedModel = new ModelManager(getTypicalAgentAssist(), new UserPrefs());

assertCommandSuccess(undoCommand, model, UndoCommand.MESSAGE_SUCCESS, expectedModel);
}

@Test
public void execute_noPreviousCommand_failure() {
Model model = new ModelManager();
assertCommandFailure(new UndoCommand(), model, UndoCommand.MESSAGE_NO_COMMAND_TO_UNDO);
}

}