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

Bug/1255 Duplicating a objective does not bring all properties of the KeyResults #1294

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
68d057b
Refactor duplicating methods to include values of keyResult
ManuelMoeri Jan 13, 2025
7407955
Modify duplicateObjectiveDto to only include ids
ManuelMoeri Jan 13, 2025
667eb41
fix actionlist shared refrence error
nevio18324 Jan 14, 2025
de7820f
fix action being asigned to the wrong action
nevio18324 Jan 14, 2025
0b69b85
run formatter
nevio18324 Jan 14, 2025
39d7d44
fix duplicate tests
nevio18324 Jan 14, 2025
8761c7c
make new method to duplicate Actionlists
nevio18324 Jan 14, 2025
56d64ff
make shouldDupllicateActionList check if every attribute was copied e…
nevio18324 Jan 14, 2025
02ebb53
fix duplicate keyresult tests
nevio18324 Jan 14, 2025
0581fa8
run formatter
nevio18324 Jan 14, 2025
7da0700
write new e2e test testing if all keyresult attributes get duplicated
nevio18324 Jan 14, 2025
f337afb
fix duplicate so you can choose which key results you wanna duplicate
nevio18324 Jan 14, 2025
c4ef623
remove not needed commented line
nevio18324 Jan 14, 2025
b71dcb9
remove id out of pathvariable for duplicate
nevio18324 Jan 14, 2025
290459f
run formatter
nevio18324 Jan 14, 2025
bcd75a0
fix id still being passed as a param in objective form
nevio18324 Jan 14, 2025
b51f091
fix duplicateObjective mapping
nevio18324 Jan 14, 2025
2b99de1
remove unused import
nevio18324 Jan 14, 2025
dfb9714
remove outdated comment
nevio18324 Jan 14, 2025
e5de435
revert formatter changes
nevio18324 Jan 14, 2025
6e04718
remove unnecessary wait
nevio18324 Jan 14, 2025
f915735
remove action list attribute
nevio18324 Jan 14, 2025
a4071d3
Finish logic of duplicating objectives with the action plan
ManuelMoeri Jan 15, 2025
54ea73c
Delete unused test, add two new ones and apply formatters
ManuelMoeri Jan 15, 2025
67a586d
Fix tests and enhance logic
ManuelMoeri Jan 15, 2025
c1afcff
Format code
ManuelMoeri Jan 15, 2025
d9ce1a1
Resolve conversations
ManuelMoeri Jan 15, 2025
1cb243f
remove any from Objective controller duplicate test
nevio18324 Jan 16, 2025
aa034c5
refactor shouldDuplicateMetricKeyResult test
nevio18324 Jan 16, 2025
554ba39
Continue refactoring of logic and fix a couple of tests
ManuelMoeri Jan 16, 2025
b406d97
Fix last backend test
ManuelMoeri Jan 16, 2025
2420339
Add test for ordinal keyResult as well
ManuelMoeri Jan 16, 2025
9f6842d
Remove it.only in cypress tests
ManuelMoeri Jan 16, 2025
12b8a05
Enhance naming of param
ManuelMoeri Jan 16, 2025
7573ec8
Make naming of KeyResultId param more clear
ManuelMoeri Jan 16, 2025
95b113b
Replace else if with switch statement
ManuelMoeri Jan 16, 2025
fe7c39f
Make test paramaterized to check for empty and null
ManuelMoeri Jan 16, 2025
f153414
Format code
ManuelMoeri Jan 16, 2025
69a988c
Improve parameterized test name
MasterEvarior Jan 17, 2025
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
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
import ch.puzzle.okr.mapper.keyresult.KeyResultMapper;
import ch.puzzle.okr.models.Action;
import ch.puzzle.okr.models.Objective;
import ch.puzzle.okr.models.keyresult.KeyResult;
import ch.puzzle.okr.service.authorization.ActionAuthorizationService;
import ch.puzzle.okr.service.authorization.ObjectiveAuthorizationService;
import io.swagger.v3.oas.annotations.Operation;
Expand Down Expand Up @@ -100,17 +99,13 @@ public ResponseEntity<ObjectiveDto> createObjective(@io.swagger.v3.oas.annotatio
@Operation(summary = "Duplicate Objective", description = "Duplicate a given Objective")
@ApiResponses(value = { @ApiResponse(responseCode = "201", description = "Duplicated a given Objective", content = {
@Content(mediaType = "application/json", schema = @Schema(implementation = ObjectiveDto.class)) }) })
@PostMapping("/{id}")
public ResponseEntity<ObjectiveDto> duplicateObjective(@Parameter(description = "The ID for duplicating an Objective.", required = true)
@PathVariable Long id, @io.swagger.v3.oas.annotations.parameters.RequestBody(description = "The Objective which should be duplicated as JSON", required = true) @RequestBody DuplicateObjectiveDto duplicateObjectiveDto) {
@PostMapping("/duplicate")
public ResponseEntity<ObjectiveDto> duplicateObjective(@io.swagger.v3.oas.annotations.parameters.RequestBody(description = "The Objective which should be duplicated as JSON", required = true)
@RequestBody DuplicateObjectiveDto duplicateObjectiveDto) {
Objective objective = objectiveMapper.toObjective(duplicateObjectiveDto.objective());
List<KeyResult> keyResults = duplicateObjectiveDto
.keyResults()
.stream()
.map(keyResultMapper::toKeyResult)
.toList();
List<Long> keyResultIds = duplicateObjectiveDto.keyResultIds();
ObjectiveDto duplicatedObjectiveDto = objectiveMapper
.toDto(objectiveAuthorizationService.duplicateEntity(id, objective, keyResults));
.toDto(objectiveAuthorizationService.duplicateEntity(objective, keyResultIds));
return ResponseEntity.status(HttpStatus.CREATED).body(duplicatedObjectiveDto);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package ch.puzzle.okr.dto;

import ch.puzzle.okr.dto.keyresult.KeyResultDto;
import java.util.List;

public record DuplicateObjectiveDto(ObjectiveDto objective, List<KeyResultDto> keyResults) {
public record DuplicateObjectiveDto(ObjectiveDto objective, List<Long> keyResultIds) {
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
package ch.puzzle.okr.models.keyresult;

import ch.puzzle.okr.models.MessageKey;
import ch.puzzle.okr.models.Objective;
import ch.puzzle.okr.models.User;
import ch.puzzle.okr.models.WriteableInterface;
import ch.puzzle.okr.models.*;
import jakarta.persistence.*;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@ public ObjectiveAuthorizationService(ObjectiveBusinessService objectiveBusinessS
super(objectiveBusinessService, authorizationService);
}

public Objective duplicateEntity(Long id, Objective objective, List<KeyResult> keyResults) {
public Objective duplicateEntity(Objective objective, List<Long> keyResultIds) {
AuthorizationUser authorizationUser = getAuthorizationService().updateOrAddAuthorizationUser();
hasRoleCreateOrUpdate(objective, authorizationUser);
return getBusinessService().duplicateObjective(id, objective, authorizationUser, keyResults);
return getBusinessService().duplicateObjective(objective, authorizationUser, keyResultIds);
}

public List<KeyResult> getAllKeyResultsByObjective(Long objectiveId) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
package ch.puzzle.okr.service.business;

import ch.puzzle.okr.models.Action;
import ch.puzzle.okr.models.keyresult.KeyResult;
import ch.puzzle.okr.service.persistence.ActionPersistenceService;
import ch.puzzle.okr.service.validation.ActionValidationService;
import jakarta.transaction.Transactional;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.springframework.stereotype.Service;

Expand Down Expand Up @@ -74,4 +76,30 @@ public void deleteEntityById(Long id) {
public void deleteEntitiesByKeyResultId(Long keyResultId) {
getActionsByKeyResultId(keyResultId).forEach(action -> deleteEntityById(action.getId()));
}

public List<Action> duplicateActions(KeyResult oldKeyResult, KeyResult newKeyResult) {
List<Action> actionList = actionPersistenceService
.getActionsByKeyResultIdOrderByPriorityAsc(oldKeyResult.getId());
if (actionList == null) {
return Collections.emptyList();
}

List<Action> duplicatedActions = new ArrayList<>();

actionList.forEach(action -> {
Action newAction = Action.Builder
.builder()
.withAction(action.getActionPoint())
.isChecked(action.isChecked())
.withPriority(action.getPriority())
.withVersion(action.getVersion())
.withKeyResult(newKeyResult)
.build();
validator.validate(newAction);
actionPersistenceService.save(newAction);
duplicatedActions.add(newAction);
});

return duplicatedActions;
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
package ch.puzzle.okr.service.business;

import static ch.puzzle.okr.Constants.KEY_RESULT_TYPE_METRIC;
import static ch.puzzle.okr.Constants.KEY_RESULT_TYPE_ORDINAL;

import ch.puzzle.okr.models.Action;
import ch.puzzle.okr.models.Objective;
import ch.puzzle.okr.models.authorization.AuthorizationUser;
import ch.puzzle.okr.models.checkin.CheckIn;
import ch.puzzle.okr.models.keyresult.KeyResult;
import ch.puzzle.okr.models.keyresult.KeyResultMetric;
import ch.puzzle.okr.models.keyresult.KeyResultOrdinal;
import ch.puzzle.okr.models.keyresult.KeyResultWithActionList;
import ch.puzzle.okr.service.persistence.KeyResultPersistenceService;
import ch.puzzle.okr.service.validation.KeyResultValidationService;
Expand Down Expand Up @@ -137,4 +143,45 @@ private boolean isKeyResultTypeChangeable(Long id) {
public List<KeyResult> getKeyResultsOwnedByUser(long id) {
return keyResultPersistenceService.getKeyResultsOwnedByUser(id);
}

@Transactional
public KeyResult duplicateKeyResult(AuthorizationUser authorizationUser, KeyResult keyResult,
Objective duplicatedObjective) {
KeyResult newKeyResult = switch (keyResult.getKeyResultType()) {
case KEY_RESULT_TYPE_METRIC -> makeCopyOfKeyResultMetric(keyResult, duplicatedObjective);
case KEY_RESULT_TYPE_ORDINAL -> makeCopyOfKeyResultOrdinal(keyResult, duplicatedObjective);
default ->
throw new UnsupportedOperationException("Unsupported KeyResultType: " + keyResult.getKeyResultType());
};
newKeyResult = createEntity(newKeyResult, authorizationUser);

actionBusinessService.duplicateActions(keyResult, newKeyResult);
return newKeyResult;
}

private KeyResult makeCopyOfKeyResultMetric(KeyResult keyResult, Objective duplicatedObjective) {
return KeyResultMetric.Builder
.builder() //
.withObjective(duplicatedObjective) //
.withTitle(keyResult.getTitle()) //
.withDescription(keyResult.getDescription()) //
.withOwner(keyResult.getOwner()) //
.withUnit(((KeyResultMetric) keyResult).getUnit()) //
.withBaseline(((KeyResultMetric) keyResult).getBaseline()) //
.withStretchGoal(((KeyResultMetric) keyResult).getStretchGoal()) //
.build();
}

private KeyResult makeCopyOfKeyResultOrdinal(KeyResult keyResult, Objective duplicatedObjective) {
return KeyResultOrdinal.Builder
.builder() //
.withObjective(duplicatedObjective) //
.withTitle(keyResult.getTitle()) //
.withDescription(keyResult.getDescription()) //
.withOwner(keyResult.getOwner()) //
.withCommitZone(((KeyResultOrdinal) keyResult).getCommitZone()) //
.withTargetZone(((KeyResultOrdinal) keyResult).getTargetZone()) //
.withStretchZone(((KeyResultOrdinal) keyResult).getStretchZone()) //
.build();
}
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,8 @@
package ch.puzzle.okr.service.business;

import static ch.puzzle.okr.Constants.KEY_RESULT_TYPE_METRIC;
import static ch.puzzle.okr.Constants.KEY_RESULT_TYPE_ORDINAL;

import ch.puzzle.okr.models.Objective;
import ch.puzzle.okr.models.authorization.AuthorizationUser;
import ch.puzzle.okr.models.keyresult.KeyResult;
import ch.puzzle.okr.models.keyresult.KeyResultMetric;
import ch.puzzle.okr.models.keyresult.KeyResultOrdinal;
import ch.puzzle.okr.service.persistence.ObjectivePersistenceService;
import ch.puzzle.okr.service.validation.ObjectiveValidationService;
import jakarta.transaction.Transactional;
Expand All @@ -26,15 +21,18 @@ public class ObjectiveBusinessService implements BusinessServiceInterface<Long,
private final ObjectiveValidationService validator;
private final KeyResultBusinessService keyResultBusinessService;
private final CompletedBusinessService completedBusinessService;
private final ActionBusinessService actionBusinessService;

public ObjectiveBusinessService(@Lazy KeyResultBusinessService keyResultBusinessService,
ObjectiveValidationService validator,
ObjectivePersistenceService objectivePersistenceService,
CompletedBusinessService completedBusinessService) {
CompletedBusinessService completedBusinessService,
ActionBusinessService actionBusinessService) {
ManuelMoeri marked this conversation as resolved.
Show resolved Hide resolved
this.keyResultBusinessService = keyResultBusinessService;
this.validator = validator;
this.objectivePersistenceService = objectivePersistenceService;
this.completedBusinessService = completedBusinessService;
this.actionBusinessService = actionBusinessService;
}

private static boolean hasQuarterChanged(Objective objective, Objective savedObjective) {
Expand Down Expand Up @@ -107,67 +105,31 @@ public Objective createEntity(Objective objective, AuthorizationUser authorizati
}

/**
* Create a new Objective (with a new ID) and copy from a source Objective
* (identified by id) the KeyResults. The CheckIns are not copied.
* Create a new Objective and copy the KeyResults from the source Objective. The
* CheckIns are not copied.
*
* @param id
* ID of the source Objective
* @param objective
* New Objective with no KeyResults
* Objective to be duplicated
* @param authorizationUser
* AuthorizationUser
* @param keyResults
* KeyResults to copy
*
* @param keyResultIds
* Ids of the keyresults which should be duplicated, the new
* keyresults will be associated with the newly duplicated objective
* @return New Objective with copied KeyResults form the source Objective
*/
@Transactional
public Objective duplicateObjective(Long id, Objective objective, AuthorizationUser authorizationUser,
List<KeyResult> keyResults) {
public Objective duplicateObjective(Objective objective, AuthorizationUser authorizationUser,
List<Long> keyResultIds) {
Objective duplicatedObjective = createEntity(objective, authorizationUser);
for (KeyResult keyResult : keyResults) {
duplicateKeyResult(authorizationUser, keyResult, duplicatedObjective);
for (Long keyResult : keyResultIds) {
keyResultBusinessService
.duplicateKeyResult(authorizationUser,
keyResultBusinessService.getEntityById(keyResult),
duplicatedObjective);
}
return duplicatedObjective;
}

private void duplicateKeyResult(AuthorizationUser authorizationUser, KeyResult keyResult,
Objective duplicatedObjective) {
if (keyResult.getKeyResultType().equals(KEY_RESULT_TYPE_METRIC)) {
KeyResult keyResultMetric = makeCopyOfKeyResultMetric(keyResult, duplicatedObjective);
keyResultBusinessService.createEntity(keyResultMetric, authorizationUser);
} else if (keyResult.getKeyResultType().equals(KEY_RESULT_TYPE_ORDINAL)) {
KeyResult keyResultOrdinal = makeCopyOfKeyResultOrdinal(keyResult, duplicatedObjective);
keyResultBusinessService.createEntity(keyResultOrdinal, authorizationUser);
}
}

private KeyResult makeCopyOfKeyResultMetric(KeyResult keyResult, Objective duplicatedObjective) {
return KeyResultMetric.Builder
.builder() //
.withObjective(duplicatedObjective) //
.withTitle(keyResult.getTitle()) //
.withDescription(keyResult.getDescription()) //
.withOwner(keyResult.getOwner()) //
.withUnit(((KeyResultMetric) keyResult).getUnit()) //
.withBaseline(0D) //
.withStretchGoal(1D) //
.build();
}

private KeyResult makeCopyOfKeyResultOrdinal(KeyResult keyResult, Objective duplicatedObjective) {
return KeyResultOrdinal.Builder
.builder() //
.withObjective(duplicatedObjective) //
.withTitle(keyResult.getTitle()) //
.withDescription(keyResult.getDescription()) //
.withOwner(keyResult.getOwner()) //
.withCommitZone("-") //
.withTargetZone("-") //
.withStretchZone("-") //
.build();
}

@Transactional
public void deleteEntityById(Long id) {
validator.validateOnDelete(id);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package ch.puzzle.okr.controller;

import static ch.puzzle.okr.test.KeyResultTestHelpers.*;
import static ch.puzzle.okr.test.KeyResultTestHelpers.keyResultMetricDto;
import static ch.puzzle.okr.test.KeyResultTestHelpers.keyResultOrdinalDto;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.Mockito.*;
Expand All @@ -18,6 +19,7 @@
import ch.puzzle.okr.service.authorization.AuthorizationService;
import ch.puzzle.okr.service.authorization.ObjectiveAuthorizationService;
import java.time.LocalDateTime;
import java.util.List;
import org.hamcrest.core.Is;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
Expand Down Expand Up @@ -49,7 +51,7 @@ class ObjectiveControllerIT {
private static final String URL_BASE_OBJECTIVE = "/api/v2/objectives";
private static final String URL_OBJECTIVE_5 = "/api/v2/objectives/5";
private static final String URL_OBJECTIVE_10 = "/api/v2/objectives/10";
private static final String URL_DUPLICATE_OBJECTIVE_5 = "/api/v2/objectives/5";
private static final String URL_DUPLICATE_OBJECTIVE_5 = "/api/v2/objectives/duplicate";
private static final String JSON = """
{
"title": "FullObjective", "ownerId": 1, "ownerFirstname": "Bob", "ownerLastname": "Kaufmann",
Expand All @@ -60,9 +62,9 @@ class ObjectiveControllerIT {
private static final String DUPLICATE_OBJECTIVE = """
{
"objective": %s,
"keyResults": [%s,%s]
"keyResultIds": [1,2]
}
""".formatted(JSON, KEY_RESULT_METRIC_JSON, KEY_RESULT_ORDINAL_JSON);
""".formatted(JSON);

private static final String CREATE_NEW_OBJECTIVE = """
{
Expand Down Expand Up @@ -341,15 +343,16 @@ void shouldThrowExceptionWhenObjectiveWithIdCantBeFoundWhileDeleting() throws Ex
.andExpect(MockMvcResultMatchers.status().isNotFound());
}

@DisplayName("Should return is created when an ojective was duplicated")
@DisplayName("Should return is created when an objective was duplicated")
@Test
void shouldReturnIsCreatedWhenObjectiveWasDuplicated() throws Exception {
BDDMockito
.given(objectiveAuthorizationService.duplicateEntity(anyLong(), any(), anyList()))
.willReturn(objective1);
BDDMockito.given(objectiveMapper.toObjective(any(ObjectiveDto.class))).willReturn(objective1);
BDDMockito.given(keyResultMapper.toDto(any(KeyResultMetric.class), any())).willReturn(keyResultMetricDto);
BDDMockito.given(keyResultMapper.toDto(any(KeyResultOrdinal.class), any())).willReturn(keyResultOrdinalDto);
BDDMockito.given(objectiveAuthorizationService.getAuthorizationService()).willReturn(authorizationService);
BDDMockito
.given(objectiveAuthorizationService.duplicateEntity(objective1, List.of(1L, 2L)))
.willReturn(objective1);
BDDMockito.given(objectiveMapper.toDto(objective1)).willReturn(objective1Dto);

mvc
Expand Down
Loading
Loading