Developer Guide
- Acknowledgements
- Setting up, getting started
- Design
- Implementation
- Documentation, logging, testing, configuration, dev-ops
- Appendix: Requirements
- Appendix: Instructions for manual testing
- Appendix: Effort
Acknowledgements
- This project is based on the AddressBook-Level3 project created by the SE-EDU initiative.
- Borrowed from AddressBook-Level4 for the
undo
feature. - Libraries used: JavaFX, Jackson, JUnit5
Setting up, getting started
Refer to the guide Setting up and getting started.
Design
.puml
files used to create diagrams in this document can be found in the diagrams folder. Refer to the PlantUML Tutorial at se-edu/guides to learn how to create and edit diagrams.
Architecture
The Architecture Diagram given above explains the high-level design of the App.
Given below is a quick overview of main components and how they interact with each other.
Main components of the architecture
Main
has two classes called Main
and 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 methods where necessary.
Commons
represents a collection of classes used by multiple other components.
The rest of the App consists of four components.
-
UI
: The UI of the App. -
Logic
: The command executor. -
Model
: Holds the data of the App in memory. -
Storage
: Reads data from, and writes data to, the hard disk.
How the architecture components interact with each other
The Sequence Diagram below shows how the components interact with each other for the scenario where the user issues the command delete 1
.
Each of the four main components (also shown in the diagram above),
- defines its API in an
interface
with the same name as the Component. - implements its functionality using a concrete
{Component Name}Manager
class (which follows the corresponding APIinterface
mentioned in the previous point.
For example, the Logic
component defines its API in the Logic.java
interface and implements its functionality using the LogicManager.java
class which follows the Logic
interface. Other components interact with a given component through its interface rather than the concrete class (reason: to prevent outside component’s being coupled to the implementation of a component), as illustrated in the (partial) class diagram below.
The sections below give more details of each component.
UI component
The API of this component is specified in Ui.java
The UI consists of a MainWindow
that is made up of parts e.g.CommandBox
, ResultDisplay
, PersonListPanel
, StatusBarFooter
etc. All these, including the MainWindow
, inherit from the abstract UiPart
class which captures the commonalities between classes that represent parts of the visible GUI.
The UI
component uses the 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. - listens for changes to
Model
data so that the UI can be updated with the modified data. - keeps a reference to the
Logic
component, because theUI
relies on theLogic
to execute commands. - depends on some classes in the
Model
component, as it displaysPerson
object residing in theModel
.
Logic component
API : Logic.java
Here’s a (partial) class diagram of the Logic
component:
How the Logic
component works:
- When
Logic
is called upon to execute a command, it uses theSurvinParser
class to parse the user command. - This results in a
Command
object (more precisely, an object of one of its subclasses e.g.,AddCommand
) which is executed by theLogicManager
. - The command can communicate with the
Model
when it is executed (e.g. to add a person). - The result of the command execution is encapsulated as a
CommandResult
object which is returned back fromLogic
.
The Sequence Diagram below illustrates the interactions within the Logic
component for the execute("delete 1")
API call.
DeleteCommandParser
should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline reaches the end of diagram.
Here are the other classes in Logic
(omitted from the class diagram above) that are used for parsing a user command:
How the parsing works:
- When called upon to parse a user command, the
SurvinParser
class creates anXYZCommandParser
(XYZ
is a placeholder for the specific command name e.g.,AddCommandParser
) which uses the other classes shown above to parse the user command and create aXYZCommand
object (e.g.,AddCommand
) which theSurvinParser
returns back as aCommand
object. - All
XYZCommandParser
classes (e.g.,AddCommandParser
,DeleteCommandParser
, …) inherit from theParser
interface so that they can be treated similarly where possible e.g, during testing.
Model component
API : Model.java
The Model
component,
- stores Survin data i.e., all
Person
objects (which are contained in aUniquePersonList
object). - stores the currently ‘selected’
Person
objects (e.g., results of a search query) as a separate filtered list which is exposed to outsiders as an unmodifiableObservableList<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. - stores a
UserPref
object that represents the user’s preferences. This is exposed to the outside as aReadOnlyUserPref
objects. - does not depend on any of the other three components (as the
Model
represents data entities of the domain, they should make sense on their own without depending on other components)
Tag
list in the Survin
, which Person
references. This allows Survin
to only require one Tag
object per unique tag, instead of each Person
needing their own Tag
objects.Storage component
API : Storage.java
The Storage
component,
- can save both Survin data and user preference data in json format, and read them back into corresponding objects.
- inherits from both
SurvinStorage
andUserPrefStorage
, which means it can be treated as either one (if only the functionality of only one is needed). - depends on some classes in the
Model
component (because theStorage
component’s job is to save/retrieve objects that belong to theModel
)
Common classes
Classes used by multiple components are in the seedu.address.commons
package.
Implementation
This section describes some noteworthy details on how certain features are implemented.
Undo/redo feature
Implementation
Note that we only implement the undo feature in this iteration. The undo/redo mechanism is facilitated by VersionedSurvin
. It extends Survin
with an undo/redo history, stored internally as an survinStateList
and currentStatePointer
. Additionally, it implements the following operations:
-
VersionedSurvin#commit()
— Saves the current Survin state in its history. -
VersionedSurvin#undo()
— Restores the previous Survin state from its history. -
VersionedSurvin#redo()
— Restores a previously undone Survin state from its history.
These operations are exposed in the Model
interface as Model#commitSurvin()
, Model#undoSurvin()
and Model#redoSurvin()
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 VersionedSurvin
will be initialized with the initial Survin state, and the currentStatePointer
pointing to that single Survin state.
Step 2. The user executes delete 5
command to delete the 5th person in Survin. The delete
command calls Model#commitSurvin()
, causing the modified state of Survin after the delete 5
command executes to be saved in the survinStateList
, and the currentStatePointer
is shifted to the newly inserted Survin state.
Step 3. The user executes add n/David …
to add a new person. The add
command also calls Model#commitSurvin()
, causing another modified Survin state to be saved into the survinStateList
.
Model#commitSurvin()
, so the Survin state will not be saved into the survinStateList
.
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#undoSurvin()
, which will shift the currentStatePointer
once to the left, pointing it to the previous Survin state, and restores Survin to that state.
currentStatePointer
is at index 0, pointing to the initial Survin state, then there are no previous Survin states to restore. The undo
command uses Model#canUndoSurvin()
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:
UndoCommand
should end at the destroy marker (X) but due to a limitation of PlantUML, the lifeline reaches the end of diagram.
The redo
command does the opposite — it calls Model#redoSurvin()
, which shifts the currentStatePointer
once to the right, pointing to the previously undone state, and restores Survin to that state.
currentStatePointer
is at index survinStateList.size() - 1
, pointing to the latest Survin state, then there are no undone Survin states to restore. The redo
command uses Model#canRedoSurvin()
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 Survin, such as list
, will usually not call Model#commitSurvin()
, Model#undoSurvin()
or Model#redoSurvin()
. Thus, the survinStateList
remains unchanged.
Step 6. The user executes clear
, which calls Model#commitSurvin()
. Since the currentStatePointer
is not pointing at the end of the survinStateList
, all Survin 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.
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 Survin state.
- 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.
- Pros: Will use less memory (e.g. for
Changing themes
Implementation
Implementing this feature will require the MainWindow
to keep track of the current theme of Survin. This is done with an enum called Theme
which is kept in a container class called Themes
. Themes
also stores static String variables to represent what users should type to swap to that theme.
Currently, since there are only 2 themes, there is a toggle button in GUI to toggle between the 2 themes. The method that is called when the button is pressed is called handleLightDarkTheme()
in MainWindow
. When called it simply checks what the current theme is and sets the theme to the other one. To keep things simple, the theme
command also uses this handleLightDarkTheme()
command to change the themes. A user invoking the theme
command will result in the call of the handeThemeCommand(Theme)
method in MainWindow
which will only call handleLightDarkTheme()
if the specified theme to change to is different from the current theme. This gives the expected behavior of the theme
command using the toggling nature of handeLightDarkTheme()
.
Below is the sequence diagram that explains how the theme
command works:
(Note: Details of the creation of CommandResult is similar to the Delete Command just without the update to model. You can find the sequence diagram for that command under the Logic Component section.)
Adding more themes (In the future)
The theme
command was implemented with the addition of more themes in mind. First, add the names of the new themes in the container class Themes
and their stylesheets in resources/view
. Then just edit the handleThemeCommand(Theme)
method with a simple switch case to change the stylesheet based on the specified theme. And since there is more than 2 themes, a toggle button would no longer work so you might want to replace that with a ComboBox or something similar. This also means that then handleLightDarkTheme()
command is obsolete and can be removed.
Compactable and expandable PersonCard
This feature is accomplished by recording the current state of the PersonCard
with a boolean variable called isExpanded
. isExpanded
is true if PersonCard
is expanded and false if PersonCard
is compacted. isExpanded
is set to false
by default so that the application starts with all PersonCard
compacted.
There is also a listener that listens to mouse clicks on each PersonCard
. Clicking on a PersonCard
calls one of two methods depending on its isExpanded
value. If isExpanded
is true
, it calls hideLabels()
. If isExpanded
is false
, it calls showLabels()
.
showLabels()
calls setManaged(true)
and setVisible(true)
on all elements that should be shown and managed for when expanding a PersonCard
. hideLabels()
likewise calls setManage(false)
and setVisible(false)
on all elements that should be hidden and not managed for when compacting a PersonCard
.
setManaged(true)
or setManaged(false)
when showing or hiding respectively, if not PersonCard
will not account for space for the shown elements or will account for space for hidden elements.
The sequence diagram below shows how the process works:
Due to the way the updateItem
method works in the ListCell
class. There were some undesirable effects in the UI. Whenever the user clicks on a PersonCard
, two things would happen, handleMouseClicked
and updateItem
would be called. updateItem
would create a new PersonCard
to replace the old PersonCard
. This is good if there was an update to the PersonCard
but in this case there was no update. This results in the handleMouseClicked
call to be useless since it was called in the PersonCard
that was replaced. The newly created PersonCard
would default to its original state of being compacted and effectively nothing would happen from the click the user made.
The sequence diagram below demonstrates the problem:
To stop this from happening, updateItem
has to create a new PersonCard
only if the PersonCard
to be created is not the same as the current PersonCard
. Now even if updateItem
is called, no new PersonCard
is created unless it is a different PersonCard
. This results in the expected behavior of resetting the state of a PersonCard
to its default state if it changes and keeping its state, since it is not replaced, if it is the same PersonCard
.
The activity diagram below explains what happens when the user clicks on a PersonCard
:
Improved DeleteCommand
To accomodate the new functionality of delete
, we make modifications in both DeleteCommandParser
and DeleteCommand
classes.
In the DeleteCommandParser
class, we made use of a regex string to check if the user specified a digit or attributes. If the user provided a number, then the parse
method would parse the input as an index. Else, the method would attempt to parse for the attributes. If none is provided, then we will throw an exception.
Since the user can provide any number of the three attributes (from 1 to 3 inclusive), it is natural to make use of the Optional
container object, which is rather helpful in avoiding the usage of null
in the implementation.
For the DeleteCommand
class, the idea is rather straightforward where we make use of Predicate
so that we can make use of the updateFilteredPersonList
method in the ModelManager
class. Note that if the user specified a survey, we would only remove the person if that was the only survey that the person is involved in, or else, we would just remove the survey from the person.
ViewCommand
feature
This feature allows users to view Person
objects with specified attributes. The current implementation supports viewing keywords in any of the following fields
Name
Phone
Email
Address
Gender
Birthdate
Race
Religion
Survey
Tag
This is a new enhancement in v1.3, as older iterations did not support viewing by Survey
or Tag
. We hope that this implementation allows the user to filter and view specific demographics more easily, for example, viewing all chinese males that has participated in a Food survey.
This is achieved in the ViewCommand
and ViewCommandParser
classes.
In ViewCommandParser
, we make use of the ArugmenetMultimap
class to parse the user input and interprets them to get the values of the suffixes input by the user. Note that there is a possibility that the values to said suffixes are null
, but this is handled by wrapping them in an Optional
by ArgumentMultimap
.
With this list of values, we make use of Predicate
to define a predicate which returns true whenever a person contains the attributes as specified by the list of values, and returns false otherwise.
The predicate is then passed into ViewCommand
, where we call updateFilteredPersonList
in the ModelManager
class to get a filtered list of people satisfying the predicate.
The sequence diagram below shows how the process works:
CloneCommand
feature
This feature allows the user to clone all details of an existing surveyee with updated attributes. It is accomplished mainly by using the CloneCommandParser
and CloneCommand
classes.
In the CloneCommandParser
class, we parse the arguments using ArgumentMultimap
class and check which attributes are present in the arguments. We will then create a ClonePersonDescriptor
object to store these attributes.
In the CloneCommand
class, we will retrieve the person we want to clone by getFilteredPersonList
method, then call createClonedPerson
method to compare the person we retrieved with the ClonePersonDescriptor
object, get the updated attributes and return a new person. Then call addPerson
method in ModelManager
to add this newly created person in.
Note that if the new person we cloned is already in Survin, it will trigger a duplicate cloned person exception.
Documentation, logging, testing, configuration, dev-ops
Appendix: Requirements
Product scope
Target user profile:
- has a need to manage a significant number of surveyees
- prefer desktop apps over other types
- can type fast
- prefers typing to mouse interactions
- is reasonably comfortable using CLI apps
Value proposition: manage surveyees faster than a typical mouse/GUI driven app
User stories
Priorities: High (must have) - * * *
, Medium (nice to have) - * *
, Low (unlikely to have) - *
Priority | As a … | I want to … | So that I can… |
---|---|---|---|
* * * |
user | use ‘add’ in the command line | add a new surveyee quickly |
* * * |
user | use ‘edit’ in the command line | edit a surveyee quickly |
* * * |
user | use ‘delete’ in the command line | delete a surveyee quickly |
* * * |
user | use ‘view’ in the command line | view my surveyees’ information quickly |
* * |
user | use ‘clone’ in the command line | use an existing surveyee as a template for a new surveyee to save time |
* * |
user | add on surveys or tags to existing surveyees without the need to retype everything again | save time and effort when adding on surveys and tags |
* * |
user | remove existing surveys or tags from surveyees without clearing everything | save time and effort re-adding the ones I did not want to delete |
* * |
user | hide more detailed information on a surveyee | protect their information and have a less cluttered GUI |
* * |
user | use ‘view |
save time when looking for surveyees with a specific attribute |
* * |
user | use ‘delete |
save time when deleting surveyees with a specific attribute |
* |
user | change between a ‘light’ and ‘dark’ theme | have a GUI that is comfortable to use in the day and at night |
* |
user | use CTRL + N | add a new surveyee qucickly |
* |
user | use CTRL + R | clear all the texts in command box qucikly |
* |
user | use CTRL + E | edit an existing surveyee qucickly |
* |
user | use CTRL + D | delete an existing surveyee qucickly |
* |
user | use CTRL + M | mark an existing surveyee qucickly |
* |
user | use CTRL + U | unmark an existing surveyee qucickly |
* |
user | use CTRL + Z | undo a command qucikly |
* |
user | use CTRL + L | clone a new surveyee qucickly |
* |
user | use CTRL + I | view an existing surveyee qucickly |
Use cases
(For all use cases below, the System is the Survin
and the Actor is the user
, unless specified otherwise)
Use case: Delete a surveyee
MSS
-
User requests to list surveyees
-
Survin shows a list of surveyees
-
User requests to delete a specific surveyee in the list
-
Survin deletes the surveyee
Use case ends.
Extensions
-
2a. The list is empty.
Use case ends.
-
3a. The given index is invalid.
-
3a1. Survin shows an error message.
Use case resumes at step 2.
-
Use case: view surveyees
Preconditions: User can recall the part of a survey name
MSS
-
User would like to search for a specific demographic for a survey
-
User views such surveyees in the desired demographic with
view
-
Survin displays all surveyees that matches the search and shows whether the survey has been completed
Extensions
-
2a. The survey name is not found.
- 2a1. Survin returns an empty list.
- 2a2. The user tries again with another survey keyword.
Steps 2a1-2a2 are repeated until some keywords are matched
Use case resumes from step 3.
Use case: Append surveys/tags to a surveyee
MSS
-
User requests to list surveyees
-
Survin shows a list of surveyees
-
User requests to append surveys and/or tags to a specific surveyee in the list
-
Survin appends requested surveys and/or tags to the surveyee
Use case ends.
Extensions
-
2a. The list is empty.
Use case ends.
-
4a. The requested surveys and/or tags already exist on the surveyee
-
4a1. Survin shows an error message.
Use case resumes at step 2.
-
Use case: Unappend surveys/tags to a surveyee
MSS
-
User requests to list surveyees
-
Survin shows a list of surveyees
-
User requests to unappend surveys and/or tags from a specific surveyee in the list
-
Survin unappends requested surveys and/or tags from the surveyee
Use case ends.
Extensions
-
2a. The list is empty.
Use case ends.
-
4a. The requested surveys and/or tags does not exist on the surveyee
-
4a1. Survin shows an error message.
Use case resumes at step 2.
-
Use case: Change theme
MSS
-
User requests to change the theme of Survin
-
Survin’s theme changes
Use case ends.
Use case: Show/Hide more detailed information
MSS
-
User requests to show/hide more detailed information of surveyees
-
Survin shows/hides more detailed information of surveyees
Use case ends.
Use case: Clone an existing surveyee
MSS
-
User wants to add a new person that is the son of an existing surveyee, both of them sharing alot similar information and took part in the same survey.
-
User finds the index of the dad in the list of surveyees.
-
User uses clone command to clone the dad with updated details of the son.
-
A new surveyee which is the son has been added.
Use case ends.
Non-Functional Requirements
- Should work on any mainstream OS as long as it has Java
11
or above installed. - 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.
- No backend server required since local storage is used.
Glossary
- Mainstream OS: Windows, Linux, Unix, OS-X
- Private contact detail: A contact detail that is not meant to be shared with others
Appendix: Instructions for manual testing
Given below are instructions to test the app manually.
Launch and shutdown
-
Initial launch
-
Download the jar file and copy into an empty folder
-
Double-click the jar file Expected: Shows the GUI with a set of sample contacts. The window size may not be optimum.
-
-
Saving window preferences
-
Resize the window to an optimum size. Move the window to a different location. Close the window.
-
Re-launch the app by double-clicking the jar file.
Expected: The most recent window size and location is retained.
-
Deleting a person
-
Deleting a person while all surveyees are being shown
-
Prerequisites: List all surveyees using the
list
command. Multiple surveyees 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. -
Test case:
delete s/Enviromental Survey
Expected: Surveyees with survey namedEnvironmental Survey
would only be deleted if that was the only survey that the surveyee has, otherwise,Environmental Survey
would be removed from the list of surveys the surveyee has (surveyee is not deleted in this case). -
Other incorrect delete commands to try:
delete
,delete x
,...
(where x is larger than the list size)
Expected: Similar to previous.
-
Mark a survey as completed
-
Mark a survey completed while all surveyees are shown
-
Prerequisites: List all surveyees using the
list
command. Multiple surveyees in the list. -
Test case:
mark 1 s/Environmental Survey
Expected: If the first surveyee has the surveyEnvironmental Survey
, then the survey would be marked as done. Otherwise, error is shown.
-
Mark a survey as completed
- Omitted as it is similar to
Mark a survey as completed
.
Appending surveys/tags to a surveyee
-
Appending surveys/tags to a surveyee while all surveyees are shown
-
Prerequisites: List all persons using the
list
command. Multiple persons in the list. -
Test case:
append 1 s/New Survey
Expected 1: First surveyee in the list has ‘New Survey’ added to their list of surveys
Expected 2: Error shown if first surveyee in the list already has ‘New Survey’ in their list of surveys before the command. -
Test case:
append 1 s/New Survey s/New Survey2 t/newTag t/newTag2
Expected 1: First surveyee in the list has ‘New Survey’ and ‘New Survey2’ added to their list of surveys and has ‘newTag’ and ‘newTag2’ added to their list of tags.
Expected 2: Error shown if first surveyee in the list already has any of the surveys or tags specified in the command before the command.
-
Unappending surveys/tags from a surveyee
- Omitted as it is similar to ‘Appending surveys/tags to a surveyee’
Changing theme
-
Changing theme while in light theme
- Test case:
theme dark
Expected: Survin changes to the dark theme. - Test case:
theme light
Expected: Survin shows an error message. - Test case: Click on the
Appearance
tab and selectDark Theme
Expected: Survin changes to the dark theme.
- Test case:
-
Changing theme while in dark theme
- Test case:
theme light
Expected: Survin changes to the light theme. - Test case:
theme dark
Expected: Survin changes to the dark theme. - Test case: Click on the
Appearance
tab and selectLight Theme
Expected: Survin changes to the light theme.
- Test case:
Showing/Hiding more detailed information
-
Showing more detailed information
-
Single surveyee
- Test case: Click on the surveyee you wish to show more details (Note: surveyee should be in the hiding more detailed information state) Expected: Surveyee will have their more detailed information shown.
-
All surveyees
-
Test case:
toggle-list-mode
Expected: All surveyees have their more detailed information shown. (Note: The mode of the list will change, this means that newPersonCard
will be created in this showing more detailed information state) -
Test case: Click on the
Appearance
tab and selectExpanded Cards
Expected: All surveyees have their more detailed information shown. (Note: The mode of the list will change, this means that newPersonCard
will be created in this showing more detailed information state)
-
-
-
Hiding more detailed information
- Omitted as it is similar to ‘Showing more detailed information’.
Clone an existing surveyee
-
Clone an existing surveyee while all surveyees are shown
-
Prerequisites: List all persons using the
list
command. Multiple persons in the list. -
Test case:
clone 1 n/James Lee p/91234567 e/jameslee@example.com b/1990-10-22
Expected 1: A new person with name James Lee, phone number 91234567, email jameslee@example.com and birthdate 1990-10-22 and other details same as the first person will be added to the end of the list.
Expected 2: Error shown if this person is already exist in the Survin.
-
Appendix: Effort
Challenges faced
- Finding out that the
updateItem()
method was the reason for the buggy implementation of the compacted and expanded PersonCard took some time. There was a need to read a lot of documentation to find the fix. - Finding out how to change the stylesheet took some time. AB3 did not seem to use
Scene
due to the use ofFXMLLoader
and I needed to get the scene to get the stylesheet. Took a few hours to find out that theStage
class had agetScene()
method. - Given that we want
delete
to support both deleting by index and deleting by attributes, it is rather tricky to settle on an implementation as there were many alternative choices that we could have taken. At the end, we settled on implementing all the necessary details inDeleteCommand
itself since increasing the amount of abstraction would actually increase coupling. So, we opted to contain all the code in the Command class itself. This is a recurring theme throughout the different commands that we chose to add. - Since we wanted to support a surveyee having multiple surveys, we had to change the definition of a unique surveyee in the app and change the corresponding code to adapt to this change. For instance, in the add command, we had to check whether the new surveyee to be added differs only by the survey name. If it was the case that only the survey name was different, then, we had to get existing person in the app and set it to a new copy with the new survey name added to the list of surveys. So, it became rather nuanced and involved.