From b7215e81d49b6e9fcf19ed35bd88b68ee058aa9b Mon Sep 17 00:00:00 2001 From: Lias Kleisa Date: Thu, 21 Mar 2024 15:24:54 +0100 Subject: [PATCH 01/11] Implement archive for okr tool --- .../java/ch/puzzle/okr/dto/ObjectiveDto.java | 3 +- .../ch/puzzle/okr/mapper/ObjectiveMapper.java | 5 +- .../java/ch/puzzle/okr/models/Objective.java | 32 +++++++--- .../puzzle/okr/models/overview/Overview.java | 30 +++++++--- .../KeyResultAuthorizationService.java | 15 ++++- .../ObjectiveAuthorizationService.java | 11 ++++ .../OverviewAuthorizationService.java | 5 +- .../business/ObjectiveBusinessService.java | 12 ++++ .../business/OverviewBusinessService.java | 10 +++- .../business/QuarterBusinessService.java | 22 ++++++- .../OverviewPersistenceService.java | 11 ++++ .../h2-db/data-test-h2/V100_0_0__TestData.sql | 16 ++--- .../V1_0_0__current-db-schema-for-testing.sql | 5 ++ .../db/migration/V2_1_3__addArchive.sql | 59 +++++++++++++++++++ .../ch/puzzle/okr/KeyResultTestHelpers.java | 6 +- .../okr/controller/ObjectiveControllerIT.java | 12 ++-- .../business/QuarterBusinessServiceTest.java | 9 ++- .../objective-form.component.ts | 3 +- 18 files changed, 224 insertions(+), 42 deletions(-) create mode 100644 backend/src/main/resources/db/migration/V2_1_3__addArchive.sql diff --git a/backend/src/main/java/ch/puzzle/okr/dto/ObjectiveDto.java b/backend/src/main/java/ch/puzzle/okr/dto/ObjectiveDto.java index bf230fdcc8..6afdfedadf 100644 --- a/backend/src/main/java/ch/puzzle/okr/dto/ObjectiveDto.java +++ b/backend/src/main/java/ch/puzzle/okr/dto/ObjectiveDto.java @@ -5,5 +5,6 @@ import java.time.LocalDateTime; public record ObjectiveDto(Long id, int version, String title, Long teamId, Long quarterId, String quarterLabel, - String description, State state, LocalDateTime createdOn, LocalDateTime modifiedOn, boolean writeable) { + String description, State state, LocalDateTime createdOn, LocalDateTime modifiedOn, boolean writeable, + boolean archived) { } diff --git a/backend/src/main/java/ch/puzzle/okr/mapper/ObjectiveMapper.java b/backend/src/main/java/ch/puzzle/okr/mapper/ObjectiveMapper.java index a65574c408..a0541add67 100644 --- a/backend/src/main/java/ch/puzzle/okr/mapper/ObjectiveMapper.java +++ b/backend/src/main/java/ch/puzzle/okr/mapper/ObjectiveMapper.java @@ -23,7 +23,7 @@ public ObjectiveDto toDto(Objective objective) { return new ObjectiveDto(objective.getId(), objective.getVersion(), objective.getTitle(), objective.getTeam().getId(), objective.getQuarter().getId(), objective.getQuarter().getLabel(), objective.getDescription(), objective.getState(), objective.getCreatedOn(), objective.getModifiedOn(), - objective.isWriteable()); + objective.isWriteable(), objective.isArchived()); } public Objective toObjective(ObjectiveDto objectiveDto) { @@ -31,6 +31,7 @@ public Objective toObjective(ObjectiveDto objectiveDto) { .withTitle(objectiveDto.title()).withTeam(teamBusinessService.getTeamById(objectiveDto.teamId())) .withDescription(objectiveDto.description()).withModifiedOn(LocalDateTime.now()) .withState(objectiveDto.state()).withCreatedOn(objectiveDto.createdOn()) - .withQuarter(quarterBusinessService.getQuarterById(objectiveDto.quarterId())).build(); + .withQuarter(quarterBusinessService.getQuarterById(objectiveDto.quarterId())) + .withArchived(objectiveDto.archived()).build(); } } diff --git a/backend/src/main/java/ch/puzzle/okr/models/Objective.java b/backend/src/main/java/ch/puzzle/okr/models/Objective.java index 85a96b59dd..acbbc438cf 100644 --- a/backend/src/main/java/ch/puzzle/okr/models/Objective.java +++ b/backend/src/main/java/ch/puzzle/okr/models/Objective.java @@ -51,6 +51,8 @@ public class Objective implements WriteableInterface { @ManyToOne private User modifiedBy; + private boolean archived; + private transient boolean writeable; public Objective() { @@ -68,6 +70,7 @@ private Objective(Builder builder) { setState(builder.state); setCreatedOn(builder.createdOn); setModifiedBy(builder.modifiedBy); + setArchived(builder.archived); } public Long getId() { @@ -150,6 +153,14 @@ public void setModifiedBy(User modifiedBy) { this.modifiedBy = modifiedBy; } + public boolean isArchived() { + return archived; + } + + public void setArchived(boolean archived) { + this.archived = archived; + } + @Override public boolean isWriteable() { return writeable; @@ -162,10 +173,10 @@ public void setWriteable(boolean writeable) { @Override public String toString() { - return "Objective{" + "id=" + id + ", version=" + version + ", title='" + title + '\'' + ", createdBy=" - + createdBy + ", team=" + team + ", quarter=" + quarter + ", description='" + description + '\'' - + ", modifiedOn=" + modifiedOn + ", state=" + state + ", createdOn=" + createdOn + ", modifiedBy=" - + modifiedBy + ", writeable=" + writeable + '\'' + '}'; + return "Objective{" + "id=" + id + ", version=" + version + ", title='" + title + '\'' + ", state=" + state + + ", description='" + description + '\'' + ", team=" + team + ", quarter=" + quarter + ", createdBy=" + + createdBy + ", createdOn=" + createdOn + ", modifiedOn=" + modifiedOn + ", modifiedBy=" + modifiedBy + + ", archived=" + archived + ", writeable=" + writeable + '}'; } @Override @@ -180,13 +191,14 @@ public boolean equals(Object o) { && Objects.equals(team, objective.team) && Objects.equals(quarter, objective.quarter) && Objects.equals(description, objective.description) && Objects.equals(modifiedOn, objective.modifiedOn) && state == objective.state - && Objects.equals(createdOn, objective.createdOn) && Objects.equals(modifiedBy, objective.modifiedBy); + && Objects.equals(createdOn, objective.createdOn) && Objects.equals(modifiedBy, objective.modifiedBy) + && Objects.equals(archived, objective.archived); } @Override public int hashCode() { - return Objects.hash(id, version, title, createdBy, team, quarter, description, modifiedOn, state, createdOn, - modifiedBy); + return Objects.hash(id, version, title, state, description, team, quarter, createdBy, createdOn, modifiedOn, + modifiedBy, archived); } public static final class Builder { @@ -201,6 +213,7 @@ public static final class Builder { private State state; private LocalDateTime createdOn; private User modifiedBy; + private boolean archived; private Builder() { } @@ -264,6 +277,11 @@ public Builder withModifiedBy(User modifiedBy) { return this; } + public Builder withArchived(boolean archived) { + this.archived = archived; + return this; + } + public Objective build() { return new Objective(this); } diff --git a/backend/src/main/java/ch/puzzle/okr/models/overview/Overview.java b/backend/src/main/java/ch/puzzle/okr/models/overview/Overview.java index b2b3eb6d79..2e21752b04 100644 --- a/backend/src/main/java/ch/puzzle/okr/models/overview/Overview.java +++ b/backend/src/main/java/ch/puzzle/okr/models/overview/Overview.java @@ -22,6 +22,7 @@ public class Overview implements WriteableInterface { @Enumerated(EnumType.STRING) private State objectiveState; private LocalDateTime objectiveCreatedOn; + private boolean objectiveArchived; private Long quarterId; private String quarterLabel; private String keyResultTitle; @@ -49,6 +50,7 @@ private Overview(Builder builder) { objectiveTitle = builder.objectiveTitle; objectiveState = builder.objectiveState; objectiveCreatedOn = builder.objectiveCreatedOn; + objectiveArchived = builder.objectiveArchived; quarterId = builder.quarterId; quarterLabel = builder.quarterLabel; keyResultTitle = builder.keyResultTitle; @@ -89,6 +91,10 @@ public LocalDateTime getObjectiveCreatedOn() { return objectiveCreatedOn; } + public boolean isObjectiveArchived() { + return objectiveArchived; + } + public Long getQuarterId() { return quarterId; } @@ -157,15 +163,15 @@ public void setWriteable(boolean writeable) { @Override public String toString() { - return "Overview{" + "overviewId=" + overviewId + ", teamVersion='" + teamVersion + ", teamName='" + teamName - + '\'' + ", objectiveTitle='" + objectiveTitle + '\'' + ", objectiveState=" + objectiveState - + ", objectiveCreatedOn=" + objectiveCreatedOn + ", quarterId=" + quarterId + ", quarterLabel='" - + quarterLabel + '\'' + ", keyResultTitle='" + keyResultTitle + '\'' + ", keyResultType='" - + keyResultType + '\'' + ", baseline=" + baseline + ", stretchGoal=" + stretchGoal + ", unit='" + unit - + '\'' + ", commitZone='" + commitZone + '\'' + ", targetZone='" + targetZone + '\'' + ", stretchZone='" - + stretchZone + '\'' + ", checkInValue=" + checkInValue + ", checkInZone='" + checkInZone + '\'' - + ", confidence=" + confidence + ", createdOn=" + checkInCreatedOn + ", writeable=" + writeable + '\'' - + '}'; + return "Overview{" + "overviewId=" + overviewId + ", teamName='" + teamName + '\'' + ", teamVersion=" + + teamVersion + ", objectiveTitle='" + objectiveTitle + '\'' + ", objectiveState=" + objectiveState + + ", objectiveCreatedOn=" + objectiveCreatedOn + ", objectiveArchived=" + objectiveArchived + + ", quarterId=" + quarterId + ", quarterLabel='" + quarterLabel + '\'' + ", keyResultTitle='" + + keyResultTitle + '\'' + ", keyResultType='" + keyResultType + '\'' + ", baseline=" + baseline + + ", stretchGoal=" + stretchGoal + ", unit='" + unit + '\'' + ", commitZone='" + commitZone + '\'' + + ", targetZone='" + targetZone + '\'' + ", stretchZone='" + stretchZone + '\'' + ", checkInValue=" + + checkInValue + ", checkInZone='" + checkInZone + '\'' + ", confidence=" + confidence + + ", checkInCreatedOn=" + checkInCreatedOn + ", writeable=" + writeable + '}'; } public static final class Builder { @@ -175,6 +181,7 @@ public static final class Builder { private String objectiveTitle; private State objectiveState; private LocalDateTime objectiveCreatedOn; + private boolean objectiveArchived; private Long quarterId; private String quarterLabel; private String keyResultTitle; @@ -227,6 +234,11 @@ public Builder withObjectiveCreatedOn(LocalDateTime objectiveCreatedOn) { return this; } + public Builder withObjectiveArchived(boolean objectiveArchived) { + this.objectiveArchived = objectiveArchived; + return this; + } + public Builder withQuarterId(Long quarterId) { this.quarterId = quarterId; return this; diff --git a/backend/src/main/java/ch/puzzle/okr/service/authorization/KeyResultAuthorizationService.java b/backend/src/main/java/ch/puzzle/okr/service/authorization/KeyResultAuthorizationService.java index edcced39f2..3f40da4a00 100644 --- a/backend/src/main/java/ch/puzzle/okr/service/authorization/KeyResultAuthorizationService.java +++ b/backend/src/main/java/ch/puzzle/okr/service/authorization/KeyResultAuthorizationService.java @@ -21,6 +21,17 @@ public KeyResultAuthorizationService(KeyResultBusinessService keyResultBusinessS super(keyResultBusinessService, authorizationService); } + @Override + public KeyResult getEntityById(Long id) { + AuthorizationUser authorizationUser = getAuthorizationService().getAuthorizationUser(); + hasRoleReadById(id, authorizationUser); + KeyResult keyResult = getBusinessService().getEntityById(id); + if (!keyResult.getObjective().isArchived()) { + keyResult.setWriteable(isWriteable(keyResult, authorizationUser)); + } + return keyResult; + } + @Override protected void hasRoleReadById(Long id, AuthorizationUser authorizationUser) { getAuthorizationService().hasRoleReadByKeyResultId(id, authorizationUser); @@ -45,7 +56,9 @@ public List getAllCheckInsByKeyResult(Long keyResultId) { AuthorizationUser authorizationUser = getAuthorizationService().getAuthorizationUser(); getAuthorizationService().hasRoleReadByKeyResultId(keyResultId, authorizationUser); List checkIns = getBusinessService().getAllCheckInsByKeyResult(keyResultId); - setRoleCreateOrUpdateCheckIn(checkIns, authorizationUser); + if (!checkIns.get(0).getKeyResult().getObjective().isArchived()) { + setRoleCreateOrUpdateCheckIn(checkIns, authorizationUser); + } return checkIns; } diff --git a/backend/src/main/java/ch/puzzle/okr/service/authorization/ObjectiveAuthorizationService.java b/backend/src/main/java/ch/puzzle/okr/service/authorization/ObjectiveAuthorizationService.java index bb213bf2d8..d3f4ab762e 100644 --- a/backend/src/main/java/ch/puzzle/okr/service/authorization/ObjectiveAuthorizationService.java +++ b/backend/src/main/java/ch/puzzle/okr/service/authorization/ObjectiveAuthorizationService.java @@ -19,6 +19,17 @@ public Objective duplicateEntity(Long id, Objective objective) { return getBusinessService().duplicateObjective(id, objective, authorizationUser); } + @Override + public Objective getEntityById(Long id) { + AuthorizationUser authorizationUser = getAuthorizationService().getAuthorizationUser(); + hasRoleReadById(id, authorizationUser); + Objective objective = getBusinessService().getEntityById(id); + if (!objective.isArchived()) { + objective.setWriteable(isWriteable(objective, authorizationUser)); + } + return objective; + } + @Override protected void hasRoleReadById(Long id, AuthorizationUser authorizationUser) { getAuthorizationService().hasRoleReadByObjectiveId(id, authorizationUser); diff --git a/backend/src/main/java/ch/puzzle/okr/service/authorization/OverviewAuthorizationService.java b/backend/src/main/java/ch/puzzle/okr/service/authorization/OverviewAuthorizationService.java index 5ee61cb4bf..76b82744eb 100644 --- a/backend/src/main/java/ch/puzzle/okr/service/authorization/OverviewAuthorizationService.java +++ b/backend/src/main/java/ch/puzzle/okr/service/authorization/OverviewAuthorizationService.java @@ -9,6 +9,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import static ch.puzzle.okr.service.authorization.AuthorizationService.hasRoleWriteAll; @@ -28,7 +29,9 @@ public List getFilteredOverview(Long quarterId, List teamIds, St AuthorizationUser authorizationUser = authorizationService.getAuthorizationUser(); List overviews = overviewBusinessService.getFilteredOverview(quarterId, teamIds, objectiveQuery, authorizationUser); - setRoleCreateOrUpdateTeam(overviews, authorizationUser); + if (Objects.isNull(quarterId) || quarterId != 998) { + setRoleCreateOrUpdateTeam(overviews, authorizationUser); + } return overviews; } diff --git a/backend/src/main/java/ch/puzzle/okr/service/business/ObjectiveBusinessService.java b/backend/src/main/java/ch/puzzle/okr/service/business/ObjectiveBusinessService.java index b532aba932..ee9d8c53da 100644 --- a/backend/src/main/java/ch/puzzle/okr/service/business/ObjectiveBusinessService.java +++ b/backend/src/main/java/ch/puzzle/okr/service/business/ObjectiveBusinessService.java @@ -38,6 +38,10 @@ public ObjectiveBusinessService(@Lazy KeyResultBusinessService keyResultBusiness this.completedBusinessService = completedBusinessService; } + public List getAllObjectives() { + return objectivePersistenceService.findAll(); + } + public Objective getEntityById(Long id) { validator.validateOnGet(id); return objectivePersistenceService.findById(id); @@ -55,6 +59,7 @@ public Objective updateEntity(Long id, Objective objective, AuthorizationUser au objective.setCreatedOn(savedObjective.getCreatedOn()); objective.setModifiedBy(authorizationUser.user()); objective.setModifiedOn(LocalDateTime.now()); + objective.setArchived(false); String not = " "; if (isImUsed(objective, savedObjective)) { objective.setQuarter(savedObjective.getQuarter()); @@ -87,6 +92,7 @@ private static boolean hasQuarterChanged(Objective objective, Objective savedObj public Objective createEntity(Objective objective, AuthorizationUser authorizationUser) { objective.setCreatedBy(authorizationUser.user()); objective.setCreatedOn(LocalDateTime.now()); + objective.setArchived(false); validator.validateOnCreate(objective); return objectivePersistenceService.save(objective); } @@ -121,4 +127,10 @@ public void deleteEntityById(Long id) { .forEach(keyResult -> keyResultBusinessService.deleteEntityById(keyResult.getId())); objectivePersistenceService.deleteById(id); } + + public void archiveEntity(Long id) { + Objective savedObjective = objectivePersistenceService.findById(id); + savedObjective.setArchived(true); + objectivePersistenceService.save(savedObjective); + } } diff --git a/backend/src/main/java/ch/puzzle/okr/service/business/OverviewBusinessService.java b/backend/src/main/java/ch/puzzle/okr/service/business/OverviewBusinessService.java index 9955505137..4baa5b2dc9 100644 --- a/backend/src/main/java/ch/puzzle/okr/service/business/OverviewBusinessService.java +++ b/backend/src/main/java/ch/puzzle/okr/service/business/OverviewBusinessService.java @@ -35,8 +35,14 @@ public List getFilteredOverview(Long quarterId, List teamIds, St return List.of(); } - List overviews = overviewPersistenceService.getFilteredOverview(quarterId, teamIds, objectiveQuery, - authorizationUser); + List overviews; + if (quarterId == 998) { + overviews = overviewPersistenceService.getArchiveOverview(teamIds, objectiveQuery, authorizationUser); + overviews.forEach(overview -> overview.setWriteable(false)); + } else { + overviews = overviewPersistenceService.getFilteredOverview(quarterId, teamIds, objectiveQuery, + authorizationUser); + } return sortOverview(overviews, authorizationUser); } diff --git a/backend/src/main/java/ch/puzzle/okr/service/business/QuarterBusinessService.java b/backend/src/main/java/ch/puzzle/okr/service/business/QuarterBusinessService.java index 1fbb86f4fd..5302856d50 100644 --- a/backend/src/main/java/ch/puzzle/okr/service/business/QuarterBusinessService.java +++ b/backend/src/main/java/ch/puzzle/okr/service/business/QuarterBusinessService.java @@ -1,5 +1,6 @@ package ch.puzzle.okr.service.business; +import ch.puzzle.okr.models.Objective; import ch.puzzle.okr.models.Quarter; import ch.puzzle.okr.service.persistence.QuarterPersistenceService; import ch.puzzle.okr.service.validation.QuarterValidationService; @@ -21,6 +22,7 @@ public class QuarterBusinessService { private static final Logger logger = LoggerFactory.getLogger(QuarterBusinessService.class); private final QuarterPersistenceService quarterPersistenceService; + private final ObjectiveBusinessService objectiveBusinessService; private final QuarterValidationService validator; @Value("${okr.quarter.business.year.start}") @@ -30,8 +32,9 @@ public class QuarterBusinessService { private String quarterFormat; public QuarterBusinessService(QuarterPersistenceService quarterPersistenceService, - QuarterValidationService validator) { + ObjectiveBusinessService objectiveBusinessService, QuarterValidationService validator) { this.quarterPersistenceService = quarterPersistenceService; + this.objectiveBusinessService = objectiveBusinessService; this.validator = validator; } @@ -43,7 +46,9 @@ public Quarter getQuarterById(Long quarterId) { public List getQuarters() { List mostCurrentQuarterList = quarterPersistenceService.getMostCurrentQuarters(); Quarter backlog = quarterPersistenceService.findByLabel("Backlog"); + Quarter archive = quarterPersistenceService.findByLabel("Archiv"); mostCurrentQuarterList.add(0, backlog); + mostCurrentQuarterList.add(archive); return mostCurrentQuarterList; } @@ -81,6 +86,7 @@ private void generateQuarter(LocalDateTime start, String label) { .withEndDate(yearMonth.plusMonths(2).atEndOfMonth()).build(); validator.validateOnGeneration(quarter); quarterPersistenceService.save(quarter); + handleQuarterArchive(); } private boolean inLastMonthOfQuarter(int currentQuarter, int nextQuarter) { @@ -105,6 +111,20 @@ Map generateQuarters() { return quarters; } + private void handleQuarterArchive() { + List mostCurrentQuarterList = quarterPersistenceService.getMostCurrentQuarters(); + Quarter backlog = quarterPersistenceService.findByLabel("Backlog"); + mostCurrentQuarterList.add(backlog); + List allObjectives = objectiveBusinessService.getAllObjectives(); + + allObjectives.forEach(objective -> { + if (!mostCurrentQuarterList.contains(objective.getQuarter())) { + objectiveBusinessService.archiveEntity(objective.getId()); + } + }); + logger.info("Update archived Objectives"); + } + @Scheduled(cron = "0 59 23 L * ?") // Cron expression for 23:59:00 on the last day of every month public void scheduledGenerationQuarters() { Map quarters = generateQuarters(); diff --git a/backend/src/main/java/ch/puzzle/okr/service/persistence/OverviewPersistenceService.java b/backend/src/main/java/ch/puzzle/okr/service/persistence/OverviewPersistenceService.java index 197cdc5d9d..5f4cae8798 100644 --- a/backend/src/main/java/ch/puzzle/okr/service/persistence/OverviewPersistenceService.java +++ b/backend/src/main/java/ch/puzzle/okr/service/persistence/OverviewPersistenceService.java @@ -15,6 +15,7 @@ public class OverviewPersistenceService { private static final Logger logger = LoggerFactory.getLogger(OverviewPersistenceService.class); private static final String SELECT_OVERVIEW = "SELECT o FROM Overview o WHERE o.quarterId=:quarterId"; + private static final String SELECT_ARCHIVE = "SELECT o FROM Overview o WHERE o.objectiveArchived=true"; private final EntityManager entityManager; private final AuthorizationCriteria authorizationCriteria; @@ -35,4 +36,14 @@ public List getFilteredOverview(Long quarterId, List teamIds, St authorizationCriteria.setParameters(typedQuery, teamIds, objectiveQuery, authorizationUser); return typedQuery.getResultList(); } + + public List getArchiveOverview(List teamIds, String objectiveQuery, + AuthorizationUser authorizationUser) { + String queryString = SELECT_ARCHIVE + + authorizationCriteria.appendOverview(teamIds, objectiveQuery, authorizationUser); + logger.debug("select overview by teamIds={}: {}", teamIds, queryString); + TypedQuery typedQuery = entityManager.createQuery(queryString, Overview.class); + authorizationCriteria.setParameters(typedQuery, teamIds, objectiveQuery, authorizationUser); + return typedQuery.getResultList(); + } } diff --git a/backend/src/main/resources/db/h2-db/data-test-h2/V100_0_0__TestData.sql b/backend/src/main/resources/db/h2-db/data-test-h2/V100_0_0__TestData.sql index 34aec66957..d984ec8596 100644 --- a/backend/src/main/resources/db/h2-db/data-test-h2/V100_0_0__TestData.sql +++ b/backend/src/main/resources/db/h2-db/data-test-h2/V100_0_0__TestData.sql @@ -50,28 +50,28 @@ values (4, 1, '/BBT'), (6, 1, 'LoremIpsum'); insert into objective (id, version, description, modified_on, progress, title, created_by_id, quarter_id, team_id, state, - modified_by_id, created_on) + modified_by_id, created_on, archived) values (4, 1, '', '2023-07-25 08:17:51.309958', 66, 'Build a company culture that kills the competition.', 1, 2, 5, - 'ONGOING', null, '2023-07-25 08:17:51.309958'), + 'ONGOING', null, '2023-07-25 08:17:51.309958', false), (3,1, 'Die Konkurenz nimmt stark zu, um weiterhin einen angenehmen Markt bearbeiten zu können, wollen wir die Kundenzufriedenheit steigern. ', '2023-07-25 08:13:48.768262', 84, 'Wir wollen die Kundenzufriedenheit steigern', 1, 2, 5, 'ONGOING', null, - '2023-07-25 08:13:48.768262'), + '2023-07-25 08:13:48.768262', false), (6,1, '', '2023-07-25 08:26:46.982010', 25, 'Als BBT wollen wir den Arbeitsalltag der Members von Puzzle ITC erleichtern.', 1, 2, 4, 'ONGOING', null, - '2023-07-25 08:26:46.982010'), + '2023-07-25 08:26:46.982010', false), (5,1, 'Damit wir nicht alle anderen Entwickler stören wollen wir so leise wie möglich arbeiten', '2023-07-25 08:20:36.894258', 65, 'Wir wollen das leiseste Team bei Puzzle sein', 1, 2, 4, 'ONGOING', null, - '2023-07-25 08:20:36.894258'), + '2023-07-25 08:20:36.894258', false), (9, 1,'', '2023-07-25 08:39:45.752126', 88, 'At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.', - 1, 2, 6, 'ONGOING', null, '2023-07-25 08:39:45.752126'), + 1, 2, 6, 'ONGOING', null, '2023-07-25 08:39:45.752126', false), (10,1, '', '2023-07-25 08:39:45.772126', 88, 'should not appear on staging, no sea takimata sanctus est Lorem ipsum dolor sit amet.', 1, 2, 6, 'ONGOING', - null, '2023-07-25 08:39:45.772126'), + null, '2023-07-25 08:39:45.772126', false), (8,1, '', '2023-07-25 08:39:28.175703', 40, 'consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua', - 1, 2, 6, 'ONGOING', null, '2023-07-25 08:39:28.175703'); + 1, 2, 6, 'ONGOING', null, '2023-07-25 08:39:28.175703', false); insert into key_result (id, version, baseline, description, modified_on, stretch_goal, title, created_by_id, objective_id, owner_id, key_result_type, created_on, unit, commit_zone, target_zone, stretch_zone) diff --git a/backend/src/main/resources/db/h2-db/database-h2-schema/V1_0_0__current-db-schema-for-testing.sql b/backend/src/main/resources/db/h2-db/database-h2-schema/V1_0_0__current-db-schema-for-testing.sql index 51fbbfc187..353a5107f3 100644 --- a/backend/src/main/resources/db/h2-db/database-h2-schema/V1_0_0__current-db-schema-for-testing.sql +++ b/backend/src/main/resources/db/h2-db/database-h2-schema/V1_0_0__current-db-schema-for-testing.sql @@ -58,6 +58,7 @@ create table if not exists objective state text not null, modified_by_id bigint, created_on timestamp not null, + archived boolean not null, primary key (id), constraint fk_objective_created_by_person foreign key (created_by_id) references person, @@ -169,6 +170,10 @@ SELECT TQ.TEAM_ID AS "TEAM_ID", O.TITLE AS "OBJECTIVE_TITLE", O.STATE AS "OBJECTIVE_STATE", O.CREATED_ON AS "OBJECTIVE_CREATED_ON", + CASE + WHEN O.TITLE IS NOT NULL THEN O.ARCHIVED + ELSE FALSE + END AS "OBJECTIVE_ARCHIVED", COALESCE(KR.ID, -1) AS "KEY_RESULT_ID", KR.TITLE AS "KEY_RESULT_TITLE", KR.KEY_RESULT_TYPE AS "KEY_RESULT_TYPE", diff --git a/backend/src/main/resources/db/migration/V2_1_3__addArchive.sql b/backend/src/main/resources/db/migration/V2_1_3__addArchive.sql new file mode 100644 index 0000000000..8cf9a84763 --- /dev/null +++ b/backend/src/main/resources/db/migration/V2_1_3__addArchive.sql @@ -0,0 +1,59 @@ +alter table objective + add column if not exists archived boolean; + +INSERT INTO quarter (id, label, start_date, end_date) +VALUES (998, 'Archiv', null, null); + +DO $$ + DECLARE + r record; + BEGIN + FOR r IN SELECT * FROM objective + WHERE objective.archived IS NULL + LOOP + UPDATE objective o + SET archived = false + WHERE o.id = r.id; + END LOOP; +END$$; + +alter table objective + alter column archived set not null; + +DROP VIEW IF EXISTS OVERVIEW; +CREATE VIEW OVERVIEW AS +SELECT tq.team_id AS "team_id", + tq.team_version AS "team_version", + tq.name AS "team_name", + tq.quater_id AS "quarter_id", + tq.label AS "quarter_label", + coalesce(o.id, -1) AS "objective_id", + o.title AS "objective_title", + o.state AS "objective_state", + o.created_on AS "objective_created_on", + CASE + WHEN o.title IS NOT NULL THEN o.archived + ELSE FALSE + END AS "objective_archived", + coalesce(kr.id, -1) AS "key_result_id", + kr.title AS "key_result_title", + kr.key_result_type AS "key_result_type", + kr.unit, + kr.baseline, + kr.stretch_goal, + kr.commit_zone, + kr.target_zone, + kr.stretch_zone, + coalesce(c.id, -1) AS "check_in_id", + c.value_metric AS "check_in_value", + c.zone AS "check_in_zone", + c.confidence, + c.created_on AS "check_in_created_on" +FROM (select t.id as team_id, t.version as team_version, t.name, q.id as quater_id, q.label + from team t, + quarter q) tq + LEFT JOIN OBJECTIVE O ON TQ.TEAM_ID = O.TEAM_ID AND TQ.QUATER_ID = O.QUARTER_ID + LEFT JOIN KEY_RESULT KR ON O.ID = KR.OBJECTIVE_ID + LEFT JOIN CHECK_IN C ON KR.ID = C.KEY_RESULT_ID AND C.MODIFIED_ON = (SELECT MAX(CC.MODIFIED_ON) + FROM CHECK_IN CC + WHERE CC.KEY_RESULT_ID = C.KEY_RESULT_ID); \ No newline at end of file diff --git a/backend/src/test/java/ch/puzzle/okr/KeyResultTestHelpers.java b/backend/src/test/java/ch/puzzle/okr/KeyResultTestHelpers.java index 41310cfe32..ee8807c606 100644 --- a/backend/src/test/java/ch/puzzle/okr/KeyResultTestHelpers.java +++ b/backend/src/test/java/ch/puzzle/okr/KeyResultTestHelpers.java @@ -80,8 +80,11 @@ public class KeyResultTestHelpers { static final String CHANGE_INFO_2 = "Changeinfo2"; static final User user = User.Builder.builder().withId(1L).withFirstname("Bob").withLastname("Kaufmann") .withUsername("bkaufmann").withEmail("kaufmann@puzzle.ch").build(); + public static final Objective objective = Objective.Builder.builder().withId(5L).withTitle("Objective 1") + .withArchived(false).build(); + public static final KeyResult metricKeyResult = KeyResultMetric.Builder.builder().withId(5L).withTitle(TITLE) - .build(); + .withObjective(objective).build(); public static final CheckIn checkIn1 = CheckInMetric.Builder.builder().withValue(23D).withId(1L) .withKeyResult(metricKeyResult).withCreatedBy(user).withCreatedOn(LocalDateTime.MAX) .withChangeInfo(CHANGE_INFO_1).withInitiatives(INITIATIVES_1).build(); @@ -110,7 +113,6 @@ public class KeyResultTestHelpers { KEY_RESULT_TYPE_ORDINAL, TITLE, DESCRIPTION, COMMIT_ZONE, TARGET_ZONE, STRETCH_ZONE, keyResultUserDto, keyResultObjectiveDto, keyResultLastCheckInOrdinalDto, LocalDateTime.MIN, LocalDateTime.MAX, true, List.of()); - public static final Objective objective = Objective.Builder.builder().withId(5L).withTitle("Objective 1").build(); public static final KeyResult ordinalKeyResult = KeyResultOrdinal.Builder.builder().withId(3L) .withTitle("Keyresult 2").withOwner(user).withObjective(objective).build(); diff --git a/backend/src/test/java/ch/puzzle/okr/controller/ObjectiveControllerIT.java b/backend/src/test/java/ch/puzzle/okr/controller/ObjectiveControllerIT.java index f4fbc5d68a..7e937dcc18 100644 --- a/backend/src/test/java/ch/puzzle/okr/controller/ObjectiveControllerIT.java +++ b/backend/src/test/java/ch/puzzle/okr/controller/ObjectiveControllerIT.java @@ -65,7 +65,7 @@ class ObjectiveControllerIT { } """; private static final String RESPONSE_NEW_OBJECTIVE = """ - {"id":null,"version":1,"title":"Program Faster","teamId":1,"quarterId":1,"quarterLabel":"GJ 22/23-Q2","description":"Just be faster","state":"DRAFT","createdOn":null,"modifiedOn":null,"writeable":true}"""; + {"id":null,"version":1,"title":"Program Faster","teamId":1,"quarterId":1,"quarterLabel":"GJ 22/23-Q2","description":"Just be faster","state":"DRAFT","createdOn":null,"modifiedOn":null,"writeable":true,"archived":false}"""; private static final String JSON_PATH_TITLE = "$.title"; private static final Objective objective1 = Objective.Builder.builder().withId(5L).withTitle(OBJECTIVE_TITLE_1) .build(); @@ -79,9 +79,9 @@ class ObjectiveControllerIT { .withCreatedBy(user).withTeam(team).withQuarter(quarter).withDescription(DESCRIPTION) .withModifiedOn(LocalDateTime.MAX).build(); private static final ObjectiveDto objective1Dto = new ObjectiveDto(5L, 1, OBJECTIVE_TITLE_1, 1L, 1L, "GJ 22/23-Q2", - DESCRIPTION, State.DRAFT, LocalDateTime.MAX, LocalDateTime.MAX, true); + DESCRIPTION, State.DRAFT, LocalDateTime.MAX, LocalDateTime.MAX, true, false); private static final ObjectiveDto objective2Dto = new ObjectiveDto(7L, 1, OBJECTIVE_TITLE_2, 1L, 1L, "GJ 22/23-Q2", - DESCRIPTION, State.DRAFT, LocalDateTime.MIN, LocalDateTime.MIN, true); + DESCRIPTION, State.DRAFT, LocalDateTime.MIN, LocalDateTime.MIN, true, false); @Autowired private MockMvc mvc; @@ -119,7 +119,7 @@ void getObjectiveByIdFail() throws Exception { @Test void shouldReturnObjectiveWhenCreatingNewObjective() throws Exception { ObjectiveDto testObjective = new ObjectiveDto(null, 1, "Program Faster", 1L, 1L, "GJ 22/23-Q2", - "Just be faster", State.DRAFT, null, null, true); + "Just be faster", State.DRAFT, null, null, true, false); BDDMockito.given(objectiveMapper.toDto(any())).willReturn(testObjective); BDDMockito.given(objectiveAuthorizationService.createEntity(any())).willReturn(fullObjective); @@ -144,7 +144,7 @@ void shouldReturnResponseStatusExceptionWhenCreatingObjectiveWithNullValues() th @Test void shouldReturnUpdatedObjective() throws Exception { ObjectiveDto testObjective = new ObjectiveDto(1L, 1, TITLE, 1L, 1L, "GJ 22/23-Q2", EVERYTHING_FINE_DESCRIPTION, - State.NOTSUCCESSFUL, LocalDateTime.MIN, LocalDateTime.MAX, true); + State.NOTSUCCESSFUL, LocalDateTime.MIN, LocalDateTime.MAX, true, false); Objective objective = Objective.Builder.builder().withId(1L).withDescription(EVERYTHING_FINE_DESCRIPTION) .withTitle(TITLE).build(); @@ -162,7 +162,7 @@ void shouldReturnUpdatedObjective() throws Exception { @Test void shouldReturnImUsed() throws Exception { ObjectiveDto testObjectiveDto = new ObjectiveDto(1L, 1, TITLE, 1L, 1L, "GJ 22/23-Q2", - EVERYTHING_FINE_DESCRIPTION, State.SUCCESSFUL, LocalDateTime.MAX, LocalDateTime.MAX, true); + EVERYTHING_FINE_DESCRIPTION, State.SUCCESSFUL, LocalDateTime.MAX, LocalDateTime.MAX, true, false); Objective objectiveImUsed = Objective.Builder.builder().withId(1L).withDescription(EVERYTHING_FINE_DESCRIPTION) .withQuarter(Quarter.Builder.builder().withId(1L).withLabel("GJ 22/23-Q2").build()).withTitle(TITLE) .build(); diff --git a/backend/src/test/java/ch/puzzle/okr/service/business/QuarterBusinessServiceTest.java b/backend/src/test/java/ch/puzzle/okr/service/business/QuarterBusinessServiceTest.java index c931679c21..6d9dc35f72 100644 --- a/backend/src/test/java/ch/puzzle/okr/service/business/QuarterBusinessServiceTest.java +++ b/backend/src/test/java/ch/puzzle/okr/service/business/QuarterBusinessServiceTest.java @@ -1,6 +1,9 @@ package ch.puzzle.okr.service.business; +import ch.puzzle.okr.models.Objective; import ch.puzzle.okr.models.Quarter; +import ch.puzzle.okr.models.Team; +import ch.puzzle.okr.models.User; import ch.puzzle.okr.service.persistence.QuarterPersistenceService; import ch.puzzle.okr.service.validation.QuarterValidationService; import org.junit.jupiter.api.Test; @@ -14,6 +17,7 @@ import org.springframework.test.util.ReflectionTestUtils; import java.time.LocalDate; +import java.time.LocalDateTime; import java.time.YearMonth; import java.util.*; import java.util.stream.Stream; @@ -29,6 +33,9 @@ class QuarterBusinessServiceTest { @Mock QuarterValidationService quarterValidationService; + @Mock + ObjectiveBusinessService objectiveBusinessService; + @InjectMocks @Spy private QuarterBusinessService quarterBusinessService; @@ -76,7 +83,7 @@ void shouldGetBacklogQuarter() { when(quarterPersistenceService.findByLabel("Backlog")).thenReturn(backlogQuarter); quarterList = quarterBusinessService.getQuarters(); - assertEquals(3, quarterList.size()); + assertEquals(4, quarterList.size()); assertEquals("Backlog", quarterList.get(0).getLabel()); assertNull(quarterList.get(0).getStartDate()); assertNull(quarterList.get(0).getEndDate()); diff --git a/frontend/src/app/shared/dialog/objective-dialog/objective-form.component.ts b/frontend/src/app/shared/dialog/objective-dialog/objective-form.component.ts index 2b967cabd8..a5d6ccdeb9 100644 --- a/frontend/src/app/shared/dialog/objective-dialog/objective-form.component.ts +++ b/frontend/src/app/shared/dialog/objective-dialog/objective-form.component.ts @@ -87,7 +87,7 @@ export class ObjectiveFormComponent implements OnInit { : of(this.getDefaultObjective()); forkJoin([objective$, this.quarters$]).subscribe(([objective, quarters]) => { - this.quarters = quarters; + this.quarters = quarters.filter((quarter) => quarter.label !== 'Archiv'); const teamId = isCreating ? objective.teamId : this.data.objective.teamId; let quarterId = getValueFromQuery(this.route.snapshot.queryParams['quarter'], quarters[1].id)[0]; @@ -98,6 +98,7 @@ export class ObjectiveFormComponent implements OnInit { this.state = objective.state; this.version = objective.version; + this.quarters$ = of(this.quarters); this.teams$.subscribe((value) => { this.currentTeam.next(value.filter((team) => team.id == teamId)[0]); }); From 93fb0150969410327f2f8dc2409f2b454321f611 Mon Sep 17 00:00:00 2001 From: Lias Kleisa Date: Mon, 25 Mar 2024 13:55:11 +0100 Subject: [PATCH 02/11] Update testdata with archiv --- .../resources/db/h2-db/data-test-h2/V100_0_0__TestData.sql | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/backend/src/main/resources/db/h2-db/data-test-h2/V100_0_0__TestData.sql b/backend/src/main/resources/db/h2-db/data-test-h2/V100_0_0__TestData.sql index d984ec8596..96f43b50a3 100644 --- a/backend/src/main/resources/db/h2-db/data-test-h2/V100_0_0__TestData.sql +++ b/backend/src/main/resources/db/h2-db/data-test-h2/V100_0_0__TestData.sql @@ -41,7 +41,8 @@ values (1, 'GJ 22/23-Q4', '2023-04-01', '2023-06-30'), (7, 'GJ 23/24-Q2', '2023-10-01', '2023-12-31'), (8, 'GJ 23/24-Q3', '2024-01-01', '2024-03-31'), (9, 'GJ 23/24-Q4', '2024-04-01', '2024-06-30'), - (199, 'Backlog', null, null); + (199, 'Backlog', null, null), + (998, 'Archiv', null, null); insert into team (id, version, name) values (4, 1, '/BBT'), From 16d485d4978c85ef0bdbf2234bfc60f1d9c6a528 Mon Sep 17 00:00:00 2001 From: Lias Kleisa Date: Thu, 6 Jun 2024 07:39:11 +0200 Subject: [PATCH 03/11] Clean up imports --- .../business/QuarterBusinessServiceTest.java | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/backend/src/test/java/ch/puzzle/okr/service/business/QuarterBusinessServiceTest.java b/backend/src/test/java/ch/puzzle/okr/service/business/QuarterBusinessServiceTest.java index 6d9dc35f72..b5f1b78142 100644 --- a/backend/src/test/java/ch/puzzle/okr/service/business/QuarterBusinessServiceTest.java +++ b/backend/src/test/java/ch/puzzle/okr/service/business/QuarterBusinessServiceTest.java @@ -1,9 +1,6 @@ package ch.puzzle.okr.service.business; -import ch.puzzle.okr.models.Objective; import ch.puzzle.okr.models.Quarter; -import ch.puzzle.okr.models.Team; -import ch.puzzle.okr.models.User; import ch.puzzle.okr.service.persistence.QuarterPersistenceService; import ch.puzzle.okr.service.validation.QuarterValidationService; import org.junit.jupiter.api.Test; @@ -12,17 +9,23 @@ import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; import org.junit.jupiter.params.provider.ValueSource; -import org.mockito.*; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.Spy; import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.test.util.ReflectionTestUtils; import java.time.LocalDate; -import java.time.LocalDateTime; import java.time.YearMonth; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; import java.util.stream.Stream; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; import static org.mockito.Mockito.*; @ExtendWith(MockitoExtension.class) From ffbe233828b7b5fce5415451c384d75cc1f4d5ca Mon Sep 17 00:00:00 2001 From: Lias Kleisa Date: Fri, 7 Jun 2024 15:32:25 +0200 Subject: [PATCH 04/11] Make archive work again after quarter changes --- .../okr/service/business/QuarterBusinessService.java | 4 +--- .../db/callback/afterMigrate__1_quarterGenerateLabels.sql | 7 ++++--- .../src/main/resources/db/migration/V2_1_3__addArchive.sql | 5 +---- frontend/src/app/overview/overview.component.html | 3 ++- frontend/src/app/overview/overview.component.ts | 2 ++ 5 files changed, 10 insertions(+), 11 deletions(-) diff --git a/backend/src/main/java/ch/puzzle/okr/service/business/QuarterBusinessService.java b/backend/src/main/java/ch/puzzle/okr/service/business/QuarterBusinessService.java index 5302856d50..029c653f7d 100644 --- a/backend/src/main/java/ch/puzzle/okr/service/business/QuarterBusinessService.java +++ b/backend/src/main/java/ch/puzzle/okr/service/business/QuarterBusinessService.java @@ -113,12 +113,10 @@ Map generateQuarters() { private void handleQuarterArchive() { List mostCurrentQuarterList = quarterPersistenceService.getMostCurrentQuarters(); - Quarter backlog = quarterPersistenceService.findByLabel("Backlog"); - mostCurrentQuarterList.add(backlog); List allObjectives = objectiveBusinessService.getAllObjectives(); allObjectives.forEach(objective -> { - if (!mostCurrentQuarterList.contains(objective.getQuarter())) { + if (!mostCurrentQuarterList.contains(objective.getQuarter()) && objective.getQuarter().getId() != 999) { objectiveBusinessService.archiveEntity(objective.getId()); } }); diff --git a/backend/src/main/resources/db/callback/afterMigrate__1_quarterGenerateLabels.sql b/backend/src/main/resources/db/callback/afterMigrate__1_quarterGenerateLabels.sql index 88eaa12895..0678df0664 100644 --- a/backend/src/main/resources/db/callback/afterMigrate__1_quarterGenerateLabels.sql +++ b/backend/src/main/resources/db/callback/afterMigrate__1_quarterGenerateLabels.sql @@ -32,15 +32,16 @@ INSERT INTO quarter_meta(id, start_month, end_month, quarter, start_year_offset, VALUES (4, 10, 12, 4, 0, '-10-01', '-12-31'); */ --- delete quarters outside [1..8, 999] +-- delete quarters outside [1..8, 999, 998] DELETE FROM quarter WHERE id NOT BETWEEN 1 AND 8 - AND id != 999; + AND id != 999 + AND id != 998; -- "empty" label and start/end date of quarter table UPDATE quarter SET start_date = null; UPDATE quarter SET end_date = null; -UPDATE quarter q SET label = 'label-' || q.id WHERE id < 999; +UPDATE quarter q SET label = 'label-' || q.id WHERE id < 998; -- utility functions CREATE OR REPLACE FUNCTION next_quarter(current_quarter integer) diff --git a/backend/src/main/resources/db/migration/V2_1_3__addArchive.sql b/backend/src/main/resources/db/migration/V2_1_3__addArchive.sql index 8cf9a84763..258adcd20c 100644 --- a/backend/src/main/resources/db/migration/V2_1_3__addArchive.sql +++ b/backend/src/main/resources/db/migration/V2_1_3__addArchive.sql @@ -31,10 +31,7 @@ SELECT tq.team_id AS "team_id", o.title AS "objective_title", o.state AS "objective_state", o.created_on AS "objective_created_on", - CASE - WHEN o.title IS NOT NULL THEN o.archived - ELSE FALSE - END AS "objective_archived", + o.archived AS "objective_archived", coalesce(kr.id, -1) AS "key_result_id", kr.title AS "key_result_title", kr.key_result_type AS "key_result_type", diff --git a/frontend/src/app/overview/overview.component.html b/frontend/src/app/overview/overview.component.html index a8e1e40d7f..c4f47616e7 100644 --- a/frontend/src/app/overview/overview.component.html +++ b/frontend/src/app/overview/overview.component.html @@ -10,7 +10,8 @@ >
-

Kein Team ausgewählt

+

Kein Team ausgewählt

+

Keine Daten im Archiv

diff --git a/frontend/src/app/overview/overview.component.ts b/frontend/src/app/overview/overview.component.ts index 674346df80..a72e2aa26d 100644 --- a/frontend/src/app/overview/overview.component.ts +++ b/frontend/src/app/overview/overview.component.ts @@ -18,6 +18,7 @@ export class OverviewComponent implements OnInit, OnDestroy { private destroyed$: ReplaySubject = new ReplaySubject(1); hasAdminAccess: ReplaySubject = new ReplaySubject(1); overviewPadding: Subject = new Subject(); + isArchiveQuarter: boolean = false; constructor( private overviewService: OverviewService, @@ -58,6 +59,7 @@ export class OverviewComponent implements OnInit, OnDestroy { const teamIds = getValueFromQuery(teamQuery); const quarterId = getValueFromQuery(quarterQuery)[0]; + this.isArchiveQuarter = quarterId == 998; const objectiveQueryString = getQueryString(objectiveQuery); this.loadOverview(quarterId, teamIds, objectiveQueryString); } From abc0043c6948d84c950c50256683a43a4888dcb6 Mon Sep 17 00:00:00 2001 From: Lias Kleisa Date: Mon, 10 Jun 2024 10:30:33 +0200 Subject: [PATCH 05/11] Fix exception while archive set --- .../okr/service/business/ObjectiveBusinessService.java | 7 +++++-- .../okr/service/business/QuarterBusinessService.java | 8 ++++++-- .../main/resources/db/migration/V2_1_3__addArchive.sql | 5 ++++- 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/backend/src/main/java/ch/puzzle/okr/service/business/ObjectiveBusinessService.java b/backend/src/main/java/ch/puzzle/okr/service/business/ObjectiveBusinessService.java index ee9d8c53da..4a558eca66 100644 --- a/backend/src/main/java/ch/puzzle/okr/service/business/ObjectiveBusinessService.java +++ b/backend/src/main/java/ch/puzzle/okr/service/business/ObjectiveBusinessService.java @@ -128,9 +128,12 @@ public void deleteEntityById(Long id) { objectivePersistenceService.deleteById(id); } + @Transactional public void archiveEntity(Long id) { Objective savedObjective = objectivePersistenceService.findById(id); - savedObjective.setArchived(true); - objectivePersistenceService.save(savedObjective); + if (savedObjective != null) { + savedObjective.setArchived(true); + objectivePersistenceService.save(savedObjective); + } } } diff --git a/backend/src/main/java/ch/puzzle/okr/service/business/QuarterBusinessService.java b/backend/src/main/java/ch/puzzle/okr/service/business/QuarterBusinessService.java index 029c653f7d..c6a450ff29 100644 --- a/backend/src/main/java/ch/puzzle/okr/service/business/QuarterBusinessService.java +++ b/backend/src/main/java/ch/puzzle/okr/service/business/QuarterBusinessService.java @@ -4,6 +4,7 @@ import ch.puzzle.okr.models.Quarter; import ch.puzzle.okr.service.persistence.QuarterPersistenceService; import ch.puzzle.okr.service.validation.QuarterValidationService; +import jakarta.transaction.Transactional; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -80,7 +81,8 @@ private int getStartOfBusinessYear(YearMonth startOfQuarter, int quarter) { return startOfQuarter.minusMonths((quarter - 1) * 3L).getYear(); } - private void generateQuarter(LocalDateTime start, String label) { + @Transactional + protected void generateQuarter(LocalDateTime start, String label) { YearMonth yearMonth = YearMonth.from(start); Quarter quarter = Quarter.Builder.builder().withLabel(label).withStartDate(start.toLocalDate()) .withEndDate(yearMonth.plusMonths(2).atEndOfMonth()).build(); @@ -111,7 +113,8 @@ Map generateQuarters() { return quarters; } - private void handleQuarterArchive() { + @Transactional + protected void handleQuarterArchive() { List mostCurrentQuarterList = quarterPersistenceService.getMostCurrentQuarters(); List allObjectives = objectiveBusinessService.getAllObjectives(); @@ -123,6 +126,7 @@ private void handleQuarterArchive() { logger.info("Update archived Objectives"); } + @Transactional @Scheduled(cron = "0 59 23 L * ?") // Cron expression for 23:59:00 on the last day of every month public void scheduledGenerationQuarters() { Map quarters = generateQuarters(); diff --git a/backend/src/main/resources/db/migration/V2_1_3__addArchive.sql b/backend/src/main/resources/db/migration/V2_1_3__addArchive.sql index 258adcd20c..43ca1baeb8 100644 --- a/backend/src/main/resources/db/migration/V2_1_3__addArchive.sql +++ b/backend/src/main/resources/db/migration/V2_1_3__addArchive.sql @@ -31,7 +31,10 @@ SELECT tq.team_id AS "team_id", o.title AS "objective_title", o.state AS "objective_state", o.created_on AS "objective_created_on", - o.archived AS "objective_archived", + CASE + WHEN o.archived IS NOT NULL THEN o.archived + ELSE FALSE + END AS "objective_archived", coalesce(kr.id, -1) AS "key_result_id", kr.title AS "key_result_title", kr.key_result_type AS "key_result_type", From 05c58f645bf8992daa3a6c4fddf0e840671d54fb Mon Sep 17 00:00:00 2001 From: Lias Kleisa Date: Mon, 10 Jun 2024 15:26:14 +0200 Subject: [PATCH 06/11] Refactor code --- .../main/java/ch/puzzle/okr/Constants.java | 4 ++ .../ch/puzzle/okr/mapper/ObjectiveMapper.java | 2 + .../java/ch/puzzle/okr/models/Objective.java | 8 ++-- .../OverviewAuthorizationService.java | 4 +- .../business/OverviewBusinessService.java | 10 +---- .../business/QuarterBusinessService.java | 14 ++++--- .../OverviewPersistenceService.java | 38 ++++++++++++------- .../ObjectiveValidationService.java | 2 +- .../validation/QuarterValidationService.java | 4 +- .../okr/controller/QuarterControllerIT.java | 5 ++- .../business/QuarterBusinessServiceTest.java | 7 ++-- .../ObjectiveValidationServiceTest.java | 7 ++-- .../src/app/overview/overview.component.ts | 3 +- 13 files changed, 66 insertions(+), 42 deletions(-) diff --git a/backend/src/main/java/ch/puzzle/okr/Constants.java b/backend/src/main/java/ch/puzzle/okr/Constants.java index 38220a0e91..8e85267ef5 100644 --- a/backend/src/main/java/ch/puzzle/okr/Constants.java +++ b/backend/src/main/java/ch/puzzle/okr/Constants.java @@ -17,4 +17,8 @@ private Constants() { public static final String QUARTER = "Quarter"; public static final String TEAM = "Team"; public static final String USER = "User"; + public static final String BACKLOG = "Backlog"; + public static final Long BACKLOG_QUARTER_ID = 999L; + public static final String ARCHIVE = "Archiv"; + public static final Long ARCHIVE_QUARTER_ID = 998L; } diff --git a/backend/src/main/java/ch/puzzle/okr/mapper/ObjectiveMapper.java b/backend/src/main/java/ch/puzzle/okr/mapper/ObjectiveMapper.java index a0541add67..25d6a87c5b 100644 --- a/backend/src/main/java/ch/puzzle/okr/mapper/ObjectiveMapper.java +++ b/backend/src/main/java/ch/puzzle/okr/mapper/ObjectiveMapper.java @@ -19,6 +19,8 @@ public ObjectiveMapper(TeamBusinessService teamBusinessService, QuarterBusinessS this.quarterBusinessService = quarterBusinessService; } + // TODO: Adjust Unit Tests of ObjectiveMapper after merge of multitenancy-main + public ObjectiveDto toDto(Objective objective) { return new ObjectiveDto(objective.getId(), objective.getVersion(), objective.getTitle(), objective.getTeam().getId(), objective.getQuarter().getId(), objective.getQuarter().getLabel(), diff --git a/backend/src/main/java/ch/puzzle/okr/models/Objective.java b/backend/src/main/java/ch/puzzle/okr/models/Objective.java index acbbc438cf..08493480be 100644 --- a/backend/src/main/java/ch/puzzle/okr/models/Objective.java +++ b/backend/src/main/java/ch/puzzle/okr/models/Objective.java @@ -173,10 +173,10 @@ public void setWriteable(boolean writeable) { @Override public String toString() { - return "Objective{" + "id=" + id + ", version=" + version + ", title='" + title + '\'' + ", state=" + state - + ", description='" + description + '\'' + ", team=" + team + ", quarter=" + quarter + ", createdBy=" - + createdBy + ", createdOn=" + createdOn + ", modifiedOn=" + modifiedOn + ", modifiedBy=" + modifiedBy - + ", archived=" + archived + ", writeable=" + writeable + '}'; + return "Objective{" + "id=" + id + ", version=" + version + ", title='" + title + '\'' + ", createdBy=" + + createdBy + ", team=" + team + ", quarter=" + quarter + ", description='" + description + '\'' + + ", modifiedOn=" + modifiedOn + ", state=" + state + ", createdOn=" + createdOn + ", modifiedBy=" + + modifiedBy + ", archived=" + archived + ", writeable=" + writeable + '\'' + '}'; } @Override diff --git a/backend/src/main/java/ch/puzzle/okr/service/authorization/OverviewAuthorizationService.java b/backend/src/main/java/ch/puzzle/okr/service/authorization/OverviewAuthorizationService.java index 76b82744eb..b28850db0f 100644 --- a/backend/src/main/java/ch/puzzle/okr/service/authorization/OverviewAuthorizationService.java +++ b/backend/src/main/java/ch/puzzle/okr/service/authorization/OverviewAuthorizationService.java @@ -11,6 +11,7 @@ import java.util.Map; import java.util.Objects; +import static ch.puzzle.okr.Constants.ARCHIVE_QUARTER_ID; import static ch.puzzle.okr.service.authorization.AuthorizationService.hasRoleWriteAll; @Service @@ -29,7 +30,8 @@ public List getFilteredOverview(Long quarterId, List teamIds, St AuthorizationUser authorizationUser = authorizationService.getAuthorizationUser(); List overviews = overviewBusinessService.getFilteredOverview(quarterId, teamIds, objectiveQuery, authorizationUser); - if (Objects.isNull(quarterId) || quarterId != 998) { + // TODO Search for 998 in all Flyway/SQL Scripts and check that it is everywhere considered + if (Objects.isNull(quarterId) || !quarterId.equals(ARCHIVE_QUARTER_ID)) { setRoleCreateOrUpdateTeam(overviews, authorizationUser); } return overviews; diff --git a/backend/src/main/java/ch/puzzle/okr/service/business/OverviewBusinessService.java b/backend/src/main/java/ch/puzzle/okr/service/business/OverviewBusinessService.java index 4baa5b2dc9..9955505137 100644 --- a/backend/src/main/java/ch/puzzle/okr/service/business/OverviewBusinessService.java +++ b/backend/src/main/java/ch/puzzle/okr/service/business/OverviewBusinessService.java @@ -35,14 +35,8 @@ public List getFilteredOverview(Long quarterId, List teamIds, St return List.of(); } - List overviews; - if (quarterId == 998) { - overviews = overviewPersistenceService.getArchiveOverview(teamIds, objectiveQuery, authorizationUser); - overviews.forEach(overview -> overview.setWriteable(false)); - } else { - overviews = overviewPersistenceService.getFilteredOverview(quarterId, teamIds, objectiveQuery, - authorizationUser); - } + List overviews = overviewPersistenceService.getFilteredOverview(quarterId, teamIds, objectiveQuery, + authorizationUser); return sortOverview(overviews, authorizationUser); } diff --git a/backend/src/main/java/ch/puzzle/okr/service/business/QuarterBusinessService.java b/backend/src/main/java/ch/puzzle/okr/service/business/QuarterBusinessService.java index c6a450ff29..a00870b0fd 100644 --- a/backend/src/main/java/ch/puzzle/okr/service/business/QuarterBusinessService.java +++ b/backend/src/main/java/ch/puzzle/okr/service/business/QuarterBusinessService.java @@ -17,6 +17,9 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Objects; + +import static ch.puzzle.okr.Constants.*; @Service public class QuarterBusinessService { @@ -46,8 +49,8 @@ public Quarter getQuarterById(Long quarterId) { public List getQuarters() { List mostCurrentQuarterList = quarterPersistenceService.getMostCurrentQuarters(); - Quarter backlog = quarterPersistenceService.findByLabel("Backlog"); - Quarter archive = quarterPersistenceService.findByLabel("Archiv"); + Quarter backlog = quarterPersistenceService.findByLabel(BACKLOG); + Quarter archive = quarterPersistenceService.findByLabel(ARCHIVE); mostCurrentQuarterList.add(0, backlog); mostCurrentQuarterList.add(archive); return mostCurrentQuarterList; @@ -88,7 +91,7 @@ protected void generateQuarter(LocalDateTime start, String label) { .withEndDate(yearMonth.plusMonths(2).atEndOfMonth()).build(); validator.validateOnGeneration(quarter); quarterPersistenceService.save(quarter); - handleQuarterArchive(); + moveObjectsFromNonMostCurrentQuartersIntoArchive(); } private boolean inLastMonthOfQuarter(int currentQuarter, int nextQuarter) { @@ -114,12 +117,13 @@ Map generateQuarters() { } @Transactional - protected void handleQuarterArchive() { + protected void moveObjectsFromNonMostCurrentQuartersIntoArchive() { List mostCurrentQuarterList = quarterPersistenceService.getMostCurrentQuarters(); List allObjectives = objectiveBusinessService.getAllObjectives(); allObjectives.forEach(objective -> { - if (!mostCurrentQuarterList.contains(objective.getQuarter()) && objective.getQuarter().getId() != 999) { + if (!mostCurrentQuarterList.contains(objective.getQuarter()) + && !Objects.equals(objective.getQuarter().getId(), BACKLOG_QUARTER_ID)) { objectiveBusinessService.archiveEntity(objective.getId()); } }); diff --git a/backend/src/main/java/ch/puzzle/okr/service/persistence/OverviewPersistenceService.java b/backend/src/main/java/ch/puzzle/okr/service/persistence/OverviewPersistenceService.java index 5f4cae8798..5733d44256 100644 --- a/backend/src/main/java/ch/puzzle/okr/service/persistence/OverviewPersistenceService.java +++ b/backend/src/main/java/ch/puzzle/okr/service/persistence/OverviewPersistenceService.java @@ -9,13 +9,16 @@ import org.springframework.stereotype.Service; import java.util.List; +import java.util.Objects; + +import static ch.puzzle.okr.Constants.ARCHIVE_QUARTER_ID; @Service public class OverviewPersistenceService { private static final Logger logger = LoggerFactory.getLogger(OverviewPersistenceService.class); private static final String SELECT_OVERVIEW = "SELECT o FROM Overview o WHERE o.quarterId=:quarterId"; - private static final String SELECT_ARCHIVE = "SELECT o FROM Overview o WHERE o.objectiveArchived=true"; + private static final String SELECT_OVERVIEW_FROM_ARCHIVE = "SELECT o FROM Overview o WHERE o.objectiveArchived=true"; private final EntityManager entityManager; private final AuthorizationCriteria authorizationCriteria; @@ -28,21 +31,30 @@ public OverviewPersistenceService(EntityManager entityManager, public List getFilteredOverview(Long quarterId, List teamIds, String objectiveQuery, AuthorizationUser authorizationUser) { - String queryString = SELECT_OVERVIEW - + authorizationCriteria.appendOverview(teamIds, objectiveQuery, authorizationUser); - logger.debug("select overview by quarterId={} and teamIds={}: {}", quarterId, teamIds, queryString); - TypedQuery typedQuery = entityManager.createQuery(queryString, Overview.class); - typedQuery.setParameter("quarterId", quarterId); - authorizationCriteria.setParameters(typedQuery, teamIds, objectiveQuery, authorizationUser); - return typedQuery.getResultList(); + boolean isArchive = Objects.equals(quarterId, ARCHIVE_QUARTER_ID); + String queryString = createQueryString(teamIds, objectiveQuery, authorizationUser, isArchive); + return createTypedQuery(quarterId, teamIds, objectiveQuery, authorizationUser, queryString, isArchive); } - public List getArchiveOverview(List teamIds, String objectiveQuery, - AuthorizationUser authorizationUser) { - String queryString = SELECT_ARCHIVE - + authorizationCriteria.appendOverview(teamIds, objectiveQuery, authorizationUser); - logger.debug("select overview by teamIds={}: {}", teamIds, queryString); + private String createQueryString(List teamIds, String objectiveQuery, AuthorizationUser authorizationUser, + boolean isArchive) { + if (isArchive) { + return SELECT_OVERVIEW_FROM_ARCHIVE + + authorizationCriteria.appendOverview(teamIds, objectiveQuery, authorizationUser); + } else { + return SELECT_OVERVIEW + authorizationCriteria.appendOverview(teamIds, objectiveQuery, authorizationUser); + } + } + + private List createTypedQuery(Long quarterId, List teamIds, String objectiveQuery, + AuthorizationUser authorizationUser, String queryString, boolean isArchive) { TypedQuery typedQuery = entityManager.createQuery(queryString, Overview.class); + if (isArchive) { + logger.debug("select overview by teamIds={}: {}", teamIds, queryString); + } else { + logger.debug("select overview by quarterId={} and teamIds={}: {}", quarterId, teamIds, queryString); + typedQuery.setParameter("quarterId", quarterId); + } authorizationCriteria.setParameters(typedQuery, teamIds, objectiveQuery, authorizationUser); return typedQuery.getResultList(); } diff --git a/backend/src/main/java/ch/puzzle/okr/service/validation/ObjectiveValidationService.java b/backend/src/main/java/ch/puzzle/okr/service/validation/ObjectiveValidationService.java index c302db645d..1c01ffa592 100644 --- a/backend/src/main/java/ch/puzzle/okr/service/validation/ObjectiveValidationService.java +++ b/backend/src/main/java/ch/puzzle/okr/service/validation/ObjectiveValidationService.java @@ -71,7 +71,7 @@ private static void throwExceptionWhenTeamHasChanged(Team team, Team savedTeam) private static void throwExceptionWhenNotDraftInBacklogQuarter(Objective model) { if (model.getQuarter().getStartDate() == null && model.getQuarter().getEndDate() == null - && model.getQuarter().getLabel().equals("Backlog") && (model.getState() != State.DRAFT)) { + && model.getQuarter().getLabel().equals(BACKLOG) && (model.getState() != State.DRAFT)) { throw new OkrResponseStatusException(HttpStatus.BAD_REQUEST, ErrorKey.ATTRIBUTE_MUST_BE_DRAFT, List.of(OBJECTIVE, STATE_DRAFT, model.getState())); } diff --git a/backend/src/main/java/ch/puzzle/okr/service/validation/QuarterValidationService.java b/backend/src/main/java/ch/puzzle/okr/service/validation/QuarterValidationService.java index e129abac70..bc6aefd59f 100644 --- a/backend/src/main/java/ch/puzzle/okr/service/validation/QuarterValidationService.java +++ b/backend/src/main/java/ch/puzzle/okr/service/validation/QuarterValidationService.java @@ -10,6 +10,8 @@ import java.util.List; +import static ch.puzzle.okr.Constants.BACKLOG; + @Service public class QuarterValidationService extends ValidationBase { @@ -29,7 +31,7 @@ public void validateOnUpdate(Long id, Quarter model) { } public static void throwExceptionWhenStartEndDateQuarterIsNull(Quarter model) { - if (!model.getLabel().equals("Backlog")) { + if (!model.getLabel().equals(BACKLOG)) { if (model.getStartDate() == null) { throw new OkrResponseStatusException(HttpStatus.BAD_REQUEST, ErrorKey.ATTRIBUTE_NULL, List.of("StartDate", model.getLabel())); diff --git a/backend/src/test/java/ch/puzzle/okr/controller/QuarterControllerIT.java b/backend/src/test/java/ch/puzzle/okr/controller/QuarterControllerIT.java index 76f9f27da6..12a57df8e8 100644 --- a/backend/src/test/java/ch/puzzle/okr/controller/QuarterControllerIT.java +++ b/backend/src/test/java/ch/puzzle/okr/controller/QuarterControllerIT.java @@ -21,6 +21,7 @@ import java.util.Collections; import java.util.List; +import static ch.puzzle.okr.Constants.BACKLOG; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; @@ -33,7 +34,7 @@ class QuarterControllerIT { .withStartDate(LocalDate.of(2022, 9, 1)).withEndDate(LocalDate.of(2022, 12, 31)).build(); static Quarter quarter2 = Quarter.Builder.builder().withId(2L).withLabel("GJ 22/23-Q3") .withStartDate(LocalDate.of(2023, 1, 1)).withEndDate(LocalDate.of(2023, 3, 31)).build(); - static Quarter backlogQuarter = Quarter.Builder.builder().withId(199L).withLabel("Backlog").withStartDate(null) + static Quarter backlogQuarter = Quarter.Builder.builder().withId(199L).withLabel(BACKLOG).withStartDate(null) .withEndDate(null).build(); static List quaterList = Arrays.asList(quarter1, quarter2, backlogQuarter); @@ -54,7 +55,7 @@ void shouldGetAllQuarters() throws Exception { .andExpect(jsonPath("$[1].id", Is.is(2))).andExpect(jsonPath("$[1].label", Is.is("GJ 22/23-Q3"))) .andExpect(jsonPath("$[1].startDate", Is.is(LocalDate.of(2023, 1, 1).toString()))) .andExpect(jsonPath("$[1].endDate", Is.is(LocalDate.of(2023, 3, 31).toString()))) - .andExpect(jsonPath("$[2].id", Is.is(199))).andExpect(jsonPath("$[2].label", Is.is("Backlog"))); + .andExpect(jsonPath("$[2].id", Is.is(199))).andExpect(jsonPath("$[2].label", Is.is(BACKLOG))); } @Test diff --git a/backend/src/test/java/ch/puzzle/okr/service/business/QuarterBusinessServiceTest.java b/backend/src/test/java/ch/puzzle/okr/service/business/QuarterBusinessServiceTest.java index b5f1b78142..2dd0e0b52c 100644 --- a/backend/src/test/java/ch/puzzle/okr/service/business/QuarterBusinessServiceTest.java +++ b/backend/src/test/java/ch/puzzle/okr/service/business/QuarterBusinessServiceTest.java @@ -24,6 +24,7 @@ import java.util.Map; import java.util.stream.Stream; +import static ch.puzzle.okr.Constants.BACKLOG; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; import static org.mockito.Mockito.*; @@ -81,13 +82,13 @@ void shouldGetBacklogQuarter() { .withStartDate(LocalDate.of(2022, 8, 1)).withEndDate(LocalDate.of(2022, 11, 30)).build(); List quarterList = new ArrayList<>(Arrays.asList(realQuarter1, realQuarter2)); - Quarter backlogQuarter = Quarter.Builder.builder().withId(199L).withLabel("Backlog").build(); + Quarter backlogQuarter = Quarter.Builder.builder().withId(199L).withLabel(BACKLOG).build(); when(quarterPersistenceService.getMostCurrentQuarters()).thenReturn(quarterList); - when(quarterPersistenceService.findByLabel("Backlog")).thenReturn(backlogQuarter); + when(quarterPersistenceService.findByLabel(BACKLOG)).thenReturn(backlogQuarter); quarterList = quarterBusinessService.getQuarters(); assertEquals(4, quarterList.size()); - assertEquals("Backlog", quarterList.get(0).getLabel()); + assertEquals(BACKLOG, quarterList.get(0).getLabel()); assertNull(quarterList.get(0).getStartDate()); assertNull(quarterList.get(0).getEndDate()); } diff --git a/backend/src/test/java/ch/puzzle/okr/service/validation/ObjectiveValidationServiceTest.java b/backend/src/test/java/ch/puzzle/okr/service/validation/ObjectiveValidationServiceTest.java index 172095b4ee..2eca77a820 100644 --- a/backend/src/test/java/ch/puzzle/okr/service/validation/ObjectiveValidationServiceTest.java +++ b/backend/src/test/java/ch/puzzle/okr/service/validation/ObjectiveValidationServiceTest.java @@ -26,6 +26,7 @@ import java.util.List; import java.util.stream.Stream; +import static ch.puzzle.okr.Constants.BACKLOG; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.*; import static org.junit.jupiter.params.provider.Arguments.arguments; @@ -384,7 +385,7 @@ void validateOnUpdateShouldThrowExceptionWheTeamHasChanged() { @ParameterizedTest @EnumSource(value = State.class, names = { "DRAFT" }, mode = EnumSource.Mode.EXCLUDE) void validateOnCreateShouldThrowExceptionWhenQuarterIsBacklogAndStateIsNotDraft(State state) { - Quarter backlogQuarter = Quarter.Builder.builder().withId(199L).withLabel("Backlog").withStartDate(null) + Quarter backlogQuarter = Quarter.Builder.builder().withId(199L).withLabel(BACKLOG).withStartDate(null) .withEndDate(null).build(); Objective invalidObjective = Objective.Builder.builder().withTitle("Invalid Objective").withCreatedBy(user) @@ -403,7 +404,7 @@ void validateOnCreateShouldThrowExceptionWhenQuarterIsBacklogAndStateIsNotDraft( @ParameterizedTest @EnumSource(value = State.class, names = { "DRAFT" }, mode = EnumSource.Mode.EXCLUDE) void validateOnUpdateShouldThrowExceptionWhenQuarterIsBacklogAndStateIsNotDraft(State state) { - Quarter backlogQuarter = Quarter.Builder.builder().withId(199L).withLabel("Backlog").withStartDate(null) + Quarter backlogQuarter = Quarter.Builder.builder().withId(199L).withLabel(BACKLOG).withStartDate(null) .withEndDate(null).build(); Objective invalidObjective = Objective.Builder.builder().withId(1L).withTitle("Invalid Objective") @@ -422,7 +423,7 @@ void validateOnUpdateShouldThrowExceptionWhenQuarterIsBacklogAndStateIsNotDraft( @Test void validateOnUpdateShouldPassWhenQuarterIsBacklogAndStateIsDraft() { - Quarter backlogQuarter = Quarter.Builder.builder().withId(199L).withLabel("Backlog").withStartDate(null) + Quarter backlogQuarter = Quarter.Builder.builder().withId(199L).withLabel(BACKLOG).withStartDate(null) .withEndDate(null).build(); Objective validObjective = Objective.Builder.builder().withId(1L).withTitle("Invalid Objective") diff --git a/frontend/src/app/overview/overview.component.ts b/frontend/src/app/overview/overview.component.ts index a72e2aa26d..cf6727c07b 100644 --- a/frontend/src/app/overview/overview.component.ts +++ b/frontend/src/app/overview/overview.component.ts @@ -19,6 +19,7 @@ export class OverviewComponent implements OnInit, OnDestroy { hasAdminAccess: ReplaySubject = new ReplaySubject(1); overviewPadding: Subject = new Subject(); isArchiveQuarter: boolean = false; + archiveQuarterId: number = 998; constructor( private overviewService: OverviewService, @@ -59,7 +60,7 @@ export class OverviewComponent implements OnInit, OnDestroy { const teamIds = getValueFromQuery(teamQuery); const quarterId = getValueFromQuery(quarterQuery)[0]; - this.isArchiveQuarter = quarterId == 998; + this.isArchiveQuarter = quarterId == this.archiveQuarterId; const objectiveQueryString = getQueryString(objectiveQuery); this.loadOverview(quarterId, teamIds, objectiveQueryString); } From 65c5675b1e400153e80bc834436bedda405e4a06 Mon Sep 17 00:00:00 2001 From: Lias Kleisa Date: Thu, 13 Jun 2024 14:02:18 +0200 Subject: [PATCH 07/11] Write backend tests --- .../h2-db/data-test-h2/V100_0_0__TestData.sql | 5 +- .../ch/puzzle/okr/KeyResultTestHelpers.java | 7 ++ .../KeyResultAuthorizationServiceTest.java | 81 ++++++++++++- .../ObjectiveAuthorizationServiceTest.java | 73 +++++++++++- .../OverviewAuthorizationServiceTest.java | 34 +++++- .../ObjectiveBusinessServiceTest.java | 97 ++++++++++++++-- .../business/QuarterBusinessServiceTest.java | 106 +++++++++++++++++- .../ObjectivePersistenceServiceIT.java | 2 +- .../OverviewPersistenceServiceIT.java | 12 +- 9 files changed, 395 insertions(+), 22 deletions(-) diff --git a/backend/src/main/resources/db/h2-db/data-test-h2/V100_0_0__TestData.sql b/backend/src/main/resources/db/h2-db/data-test-h2/V100_0_0__TestData.sql index 96f43b50a3..4289503af9 100644 --- a/backend/src/main/resources/db/h2-db/data-test-h2/V100_0_0__TestData.sql +++ b/backend/src/main/resources/db/h2-db/data-test-h2/V100_0_0__TestData.sql @@ -72,7 +72,10 @@ values (4, 1, '', '2023-07-25 08:17:51.309958', 66, 'Build a company culture tha null, '2023-07-25 08:39:45.772126', false), (8,1, '', '2023-07-25 08:39:28.175703', 40, 'consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua', - 1, 2, 6, 'ONGOING', null, '2023-07-25 08:39:28.175703', false); + 1, 2, 6, 'ONGOING', null, '2023-07-25 08:39:28.175703', false), + (998,1, '', '2023-07-25 08:39:28.175703', 40, + 'consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua', + 1, 6, 6, 'ONGOING', null, '2023-07-25 08:39:28.175703', true); insert into key_result (id, version, baseline, description, modified_on, stretch_goal, title, created_by_id, objective_id, owner_id, key_result_type, created_on, unit, commit_zone, target_zone, stretch_zone) diff --git a/backend/src/test/java/ch/puzzle/okr/KeyResultTestHelpers.java b/backend/src/test/java/ch/puzzle/okr/KeyResultTestHelpers.java index ee8807c606..bab4c7771c 100644 --- a/backend/src/test/java/ch/puzzle/okr/KeyResultTestHelpers.java +++ b/backend/src/test/java/ch/puzzle/okr/KeyResultTestHelpers.java @@ -82,12 +82,19 @@ public class KeyResultTestHelpers { .withUsername("bkaufmann").withEmail("kaufmann@puzzle.ch").build(); public static final Objective objective = Objective.Builder.builder().withId(5L).withTitle("Objective 1") .withArchived(false).build(); + public static final Objective archivedObjective = Objective.Builder.builder().withId(12L).withTitle("Objective 12") + .withArchived(true).build(); public static final KeyResult metricKeyResult = KeyResultMetric.Builder.builder().withId(5L).withTitle(TITLE) .withObjective(objective).build(); + public static final KeyResult archivedKeyResult = KeyResultMetric.Builder.builder().withId(1L).withTitle(TITLE) + .withObjective(archivedObjective).build(); public static final CheckIn checkIn1 = CheckInMetric.Builder.builder().withValue(23D).withId(1L) .withKeyResult(metricKeyResult).withCreatedBy(user).withCreatedOn(LocalDateTime.MAX) .withChangeInfo(CHANGE_INFO_1).withInitiatives(INITIATIVES_1).build(); + public static final CheckIn archivedCheckin = CheckInMetric.Builder.builder().withValue(23D).withId(13L) + .withKeyResult(archivedKeyResult).withCreatedBy(user).withCreatedOn(LocalDateTime.MAX) + .withChangeInfo(CHANGE_INFO_1).withInitiatives(INITIATIVES_1).build(); public static final CheckIn checkIn2 = CheckInMetric.Builder.builder().withValue(12D).withId(4L) .withKeyResult(metricKeyResult).withCreatedBy(user).withCreatedOn(LocalDateTime.MAX) .withChangeInfo(CHANGE_INFO_2).withInitiatives(INITIATIVES_2).build(); diff --git a/backend/src/test/java/ch/puzzle/okr/service/authorization/KeyResultAuthorizationServiceTest.java b/backend/src/test/java/ch/puzzle/okr/service/authorization/KeyResultAuthorizationServiceTest.java index 0e712b94a9..fb79f41529 100644 --- a/backend/src/test/java/ch/puzzle/okr/service/authorization/KeyResultAuthorizationServiceTest.java +++ b/backend/src/test/java/ch/puzzle/okr/service/authorization/KeyResultAuthorizationServiceTest.java @@ -15,13 +15,12 @@ import java.util.List; -import static ch.puzzle.okr.KeyResultTestHelpers.checkIn1; -import static ch.puzzle.okr.KeyResultTestHelpers.metricKeyResult; +import static ch.puzzle.okr.KeyResultTestHelpers.*; import static ch.puzzle.okr.TestHelper.defaultAuthorizationUser; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.Mockito.doThrow; -import static org.mockito.Mockito.when; +import static org.mockito.Mockito.*; +import static org.mockito.Mockito.times; import static org.springframework.http.HttpStatus.BAD_REQUEST; import static org.springframework.http.HttpStatus.UNAUTHORIZED; @@ -37,91 +36,136 @@ class KeyResultAuthorizationServiceTest { @Test void createEntityShouldReturnKeyResultWhenAuthorized() { + // arrange when(authorizationService.getAuthorizationUser()).thenReturn(authorizationUser); when(keyResultBusinessService.createEntity(metricKeyResult, authorizationUser)).thenReturn(metricKeyResult); + // act KeyResult keyResult = keyResultAuthorizationService.createEntity(metricKeyResult); + + // assert assertEquals(metricKeyResult, keyResult); } @Test void createEntityShouldThrowExceptionWhenNotAuthorized() { + // arrange String reason = "junit test reason"; when(authorizationService.getAuthorizationUser()).thenReturn(authorizationUser); doThrow(new ResponseStatusException(HttpStatus.UNAUTHORIZED, reason)).when(authorizationService) .hasRoleCreateOrUpdate(metricKeyResult, authorizationUser); + // act ResponseStatusException exception = assertThrows(ResponseStatusException.class, () -> keyResultAuthorizationService.createEntity(metricKeyResult)); + + // assert assertEquals(UNAUTHORIZED, exception.getStatusCode()); assertEquals(reason, exception.getReason()); } @Test void getEntityByIdShouldReturnKeyResultWhenAuthorized() { + // arrange Long id = 13L; when(authorizationService.getAuthorizationUser()).thenReturn(authorizationUser); when(keyResultBusinessService.getEntityById(id)).thenReturn(metricKeyResult); + // act KeyResult keyResult = keyResultAuthorizationService.getEntityById(id); + + // assert assertEquals(metricKeyResult, keyResult); } @Test void getEntityByIdShouldReturnKeyResultWritableWhenAuthorized() { + // arrange Long id = 13L; when(authorizationService.getAuthorizationUser()).thenReturn(authorizationUser); when(authorizationService.isWriteable(metricKeyResult, authorizationUser)).thenReturn(true); when(keyResultBusinessService.getEntityById(id)).thenReturn(metricKeyResult); + // act KeyResult keyResult = keyResultAuthorizationService.getEntityById(id); + + // assert assertTrue(keyResult.isWriteable()); } + @Test + void getEntityByIdShouldReturnKeyResultNotWritableWhenArchived() { + // arrange + Long id = 1L; + when(authorizationService.getAuthorizationUser()).thenReturn(authorizationUser); + when(keyResultBusinessService.getEntityById(id)).thenReturn(archivedKeyResult); + + // act + KeyResult keyResult = keyResultAuthorizationService.getEntityById(id); + + // assert + assertFalse(keyResult.isWriteable()); + } + @Test void getEntityByIdShouldThrowExceptionWhenNotAuthorized() { + // arrange Long id = 13L; String reason = "junit test reason"; when(authorizationService.getAuthorizationUser()).thenReturn(authorizationUser); doThrow(new ResponseStatusException(HttpStatus.UNAUTHORIZED, reason)).when(authorizationService) .hasRoleReadByKeyResultId(id, authorizationUser); + // act ResponseStatusException exception = assertThrows(ResponseStatusException.class, () -> keyResultAuthorizationService.getEntityById(id)); + + // assert assertEquals(UNAUTHORIZED, exception.getStatusCode()); assertEquals(reason, exception.getReason()); } @Test void updateEntitiesShouldReturnUpdatedKeyResultWhenAuthorized() { + // arrange Long id = 13L; when(authorizationService.getAuthorizationUser()).thenReturn(authorizationUser); when(keyResultBusinessService.updateEntities(id, metricKeyResult, List.of())) .thenReturn(new KeyResultWithActionList(metricKeyResult, List.of())); + // act KeyResultWithActionList KeyResult = keyResultAuthorizationService.updateEntities(id, metricKeyResult, List.of()); + + // assert assertEquals(metricKeyResult, KeyResult.keyResult()); } @Test void updateEntitiesShouldThrowExceptionWhenNotAuthorized() { + // arrange Long id = 13L; String reason = "junit test reason"; when(authorizationService.getAuthorizationUser()).thenReturn(authorizationUser); doThrow(new ResponseStatusException(HttpStatus.UNAUTHORIZED, reason)).when(authorizationService) .hasRoleCreateOrUpdate(metricKeyResult, authorizationUser); + // act ResponseStatusException exception = assertThrows(ResponseStatusException.class, () -> keyResultAuthorizationService.updateEntities(id, metricKeyResult, List.of())); + + // assert assertEquals(UNAUTHORIZED, exception.getStatusCode()); assertEquals(reason, exception.getReason()); } @Test void updateEntityShouldThrowException() { + // act ResponseStatusException exception = assertThrows(ResponseStatusException.class, () -> keyResultAuthorizationService.updateEntity(1L, metricKeyResult)); + + // assert assertEquals(BAD_REQUEST, exception.getStatusCode()); assertEquals("unsupported method in class " + KeyResultAuthorizationService.class.getSimpleName() + ", use updateEntities() instead", exception.getReason()); @@ -129,41 +173,70 @@ void updateEntityShouldThrowException() { @Test void deleteEntityByIdShouldPassThroughWhenAuthorized() { + // arrange Long id = 13L; when(authorizationService.getAuthorizationUser()).thenReturn(authorizationUser); + // act keyResultAuthorizationService.deleteEntityById(id); } @Test void deleteEntityByIdShouldThrowExceptionWhenNotAuthorized() { + // arrange Long id = 13L; String reason = "junit test reason"; when(authorizationService.getAuthorizationUser()).thenReturn(authorizationUser); doThrow(new ResponseStatusException(HttpStatus.UNAUTHORIZED, reason)).when(authorizationService) .hasRoleDeleteByKeyResultId(id, authorizationUser); + // act ResponseStatusException exception = assertThrows(ResponseStatusException.class, () -> keyResultAuthorizationService.deleteEntityById(id)); + + // assert assertEquals(UNAUTHORIZED, exception.getStatusCode()); assertEquals(reason, exception.getReason()); } @Test void getAllCheckInsByKeyResultShouldReturnListOfCheckInsWhenAuthorized() { + // arrange long id = 13L; when(authorizationService.getAuthorizationUser()).thenReturn(authorizationUser); when(keyResultBusinessService.getAllCheckInsByKeyResult(id)).thenReturn(List.of(checkIn1, checkIn1)); + // act List checkIns = keyResultAuthorizationService.getAllCheckInsByKeyResult(id); + + // assert + verify(authorizationService, times(1)).isWriteable(checkIn1, authorizationUser); assertThat(List.of(checkIn1, checkIn1)).hasSameElementsAs(checkIns); } + @Test + void getAllCheckInsByKeyResultShouldReturnListOfCheckInsNotWritableWhenArchived() { + // arrange + Long id = 13L; + when(authorizationService.getAuthorizationUser()).thenReturn(authorizationUser); + when(keyResultBusinessService.getAllCheckInsByKeyResult(id)).thenReturn(List.of(archivedCheckin)); + + // act + List checkIns = keyResultAuthorizationService.getAllCheckInsByKeyResult(id); + + // assert + verify(authorizationService, times(0)).isWriteable(archivedCheckin, authorizationUser); + assertThat(List.of(archivedCheckin)).hasSameElementsAs(checkIns); + assertFalse(checkIns.get(0).isWriteable()); + } + @Test void isImUsedShouldReturnTrueWhenImUsed() { + // arrange Long id = 13L; when(keyResultBusinessService.isImUsed(id, metricKeyResult)).thenReturn(true); + // assert assertTrue(keyResultAuthorizationService.isImUsed(id, metricKeyResult)); } } diff --git a/backend/src/test/java/ch/puzzle/okr/service/authorization/ObjectiveAuthorizationServiceTest.java b/backend/src/test/java/ch/puzzle/okr/service/authorization/ObjectiveAuthorizationServiceTest.java index 26f452d0a5..378ff1bc8d 100644 --- a/backend/src/test/java/ch/puzzle/okr/service/authorization/ObjectiveAuthorizationServiceTest.java +++ b/backend/src/test/java/ch/puzzle/okr/service/authorization/ObjectiveAuthorizationServiceTest.java @@ -13,8 +13,8 @@ import static ch.puzzle.okr.TestHelper.defaultAuthorizationUser; import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.Mockito.doThrow; -import static org.mockito.Mockito.when; +import static org.mockito.Mockito.*; +import static org.mockito.Mockito.times; import static org.springframework.http.HttpStatus.UNAUTHORIZED; @ExtendWith(MockitoExtension.class) @@ -28,113 +28,182 @@ class ObjectiveAuthorizationServiceTest { private final AuthorizationUser authorizationUser = defaultAuthorizationUser(); private final Objective newObjective = Objective.Builder.builder().withId(5L).withTitle("Objective 1").build(); + private final Objective archivedObjective = Objective.Builder.builder().withId(12L).withTitle("Objective 12") + .withArchived(true).build(); @Test void createEntityShouldReturnObjectiveWhenAuthorized() { + // arrange when(authorizationService.getAuthorizationUser()).thenReturn(authorizationUser); when(objectiveBusinessService.createEntity(newObjective, authorizationUser)).thenReturn(newObjective); + // act Objective objective = objectiveAuthorizationService.createEntity(newObjective); + + // assert assertEquals(newObjective, objective); } @Test void createEntityShouldThrowExceptionWhenNotAuthorized() { + // arrange String reason = "junit test reason"; when(authorizationService.getAuthorizationUser()).thenReturn(authorizationUser); doThrow(new ResponseStatusException(HttpStatus.UNAUTHORIZED, reason)).when(authorizationService) .hasRoleCreateOrUpdate(newObjective, authorizationUser); + // act ResponseStatusException exception = assertThrows(ResponseStatusException.class, () -> objectiveAuthorizationService.createEntity(newObjective)); + + // assert assertEquals(UNAUTHORIZED, exception.getStatusCode()); assertEquals(reason, exception.getReason()); } @Test void getEntityByIdShouldReturnObjectiveWhenAuthorized() { + // arrange Long id = 13L; when(authorizationService.getAuthorizationUser()).thenReturn(authorizationUser); when(objectiveBusinessService.getEntityById(id)).thenReturn(newObjective); + // act Objective objective = objectiveAuthorizationService.getEntityById(id); + + // assert assertEquals(newObjective, objective); } + @Test + void getEntityByIdShouldReturnObjectiveWritable() { + // arrange + Long id = 13L; + when(authorizationService.getAuthorizationUser()).thenReturn(authorizationUser); + when(authorizationService.isWriteable(newObjective, authorizationUser)).thenReturn(true); + when(objectiveBusinessService.getEntityById(id)).thenReturn(newObjective); + + // act + Objective objective = objectiveAuthorizationService.getEntityById(id); + + // assert + verify(authorizationService, times(1)).isWriteable(newObjective, authorizationUser); + assertTrue(objective.isWriteable()); + } + + @Test + void getEntityByIdShouldReturnObjectiveNotWritableWhenArchived() { + // arrange + Long id = 12L; + when(authorizationService.getAuthorizationUser()).thenReturn(authorizationUser); + when(objectiveBusinessService.getEntityById(id)).thenReturn(archivedObjective); + + // act + Objective objective = objectiveAuthorizationService.getEntityById(id); + + // assert + verify(authorizationService, times(0)).isWriteable(archivedObjective, authorizationUser); + assertFalse(objective.isWriteable()); + } + @Test void getEntityByIdShouldReturnObjectiveWritableWhenAuthorized() { + // arrange Long id = 13L; when(authorizationService.getAuthorizationUser()).thenReturn(authorizationUser); when(authorizationService.isWriteable(newObjective, authorizationUser)).thenReturn(true); when(objectiveBusinessService.getEntityById(id)).thenReturn(newObjective); + // act Objective objective = objectiveAuthorizationService.getEntityById(id); + + // assert assertTrue(objective.isWriteable()); } @Test void getEntityByIdShouldThrowExceptionWhenNotAuthorized() { + // arrange Long id = 13L; String reason = "junit test reason"; when(authorizationService.getAuthorizationUser()).thenReturn(authorizationUser); doThrow(new ResponseStatusException(HttpStatus.UNAUTHORIZED, reason)).when(authorizationService) .hasRoleReadByObjectiveId(id, authorizationUser); + // act ResponseStatusException exception = assertThrows(ResponseStatusException.class, () -> objectiveAuthorizationService.getEntityById(id)); + + // assert assertEquals(UNAUTHORIZED, exception.getStatusCode()); assertEquals(reason, exception.getReason()); } @Test void updateEntityShouldReturnUpdatedObjectiveWhenAuthorized() { + // arrange Long id = 13L; when(authorizationService.getAuthorizationUser()).thenReturn(authorizationUser); when(objectiveBusinessService.updateEntity(id, newObjective, authorizationUser)).thenReturn(newObjective); + // act Objective Objective = objectiveAuthorizationService.updateEntity(id, newObjective); + + // assert assertEquals(newObjective, Objective); } @Test void updateEntityShouldThrowExceptionWhenNotAuthorized() { + // arrange Long id = 13L; String reason = "junit test reason"; when(authorizationService.getAuthorizationUser()).thenReturn(authorizationUser); doThrow(new ResponseStatusException(HttpStatus.UNAUTHORIZED, reason)).when(authorizationService) .hasRoleCreateOrUpdate(newObjective, authorizationUser); + // act ResponseStatusException exception = assertThrows(ResponseStatusException.class, () -> objectiveAuthorizationService.updateEntity(id, newObjective)); + + // assert assertEquals(UNAUTHORIZED, exception.getStatusCode()); assertEquals(reason, exception.getReason()); } @Test void isImUsedShouldReturnTrueWhenQuarterChanged() { + // arrange when(objectiveBusinessService.isImUsed(newObjective)).thenReturn(true); + // assert assertTrue(objectiveAuthorizationService.isImUsed(newObjective)); } @Test void deleteEntityByIdShouldPassThroughWhenAuthorized() { + // arrange Long id = 13L; when(authorizationService.getAuthorizationUser()).thenReturn(authorizationUser); + // act objectiveAuthorizationService.deleteEntityById(id); } @Test void deleteEntityByIdShouldThrowExceptionWhenNotAuthorized() { + // arrange Long id = 13L; String reason = "junit test reason"; when(authorizationService.getAuthorizationUser()).thenReturn(authorizationUser); doThrow(new ResponseStatusException(HttpStatus.UNAUTHORIZED, reason)).when(authorizationService) .hasRoleDeleteByObjectiveId(id, authorizationUser); + // act ResponseStatusException exception = assertThrows(ResponseStatusException.class, () -> objectiveAuthorizationService.deleteEntityById(id)); + + // assert assertEquals(UNAUTHORIZED, exception.getStatusCode()); assertEquals(reason, exception.getReason()); } diff --git a/backend/src/test/java/ch/puzzle/okr/service/authorization/OverviewAuthorizationServiceTest.java b/backend/src/test/java/ch/puzzle/okr/service/authorization/OverviewAuthorizationServiceTest.java index 12090b33c3..f7ef9c365d 100644 --- a/backend/src/test/java/ch/puzzle/okr/service/authorization/OverviewAuthorizationServiceTest.java +++ b/backend/src/test/java/ch/puzzle/okr/service/authorization/OverviewAuthorizationServiceTest.java @@ -17,10 +17,10 @@ import static ch.puzzle.okr.TestHelper.*; import static ch.puzzle.okr.models.authorization.AuthorizationRole.READ_ALL_PUBLISHED; import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.*; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.when; +import static org.mockito.Mockito.*; @ExtendWith(MockitoExtension.class) class OverviewAuthorizationServiceTest { @@ -37,40 +37,69 @@ class OverviewAuthorizationServiceTest { @Test void getFilteredOverviewShouldReturnOverviewsWhenAuthorized() { + // arrange when(authorizationService.getAuthorizationUser()).thenReturn(authorizationUser); when(overviewBusinessService.getFilteredOverview(any(), any(), any(), eq(authorizationUser))) .thenReturn(List.of(overview)); + when(authorizationService.isWriteable(authorizationUser, 5L)).thenReturn(true); + // act List overviews = overviewAuthorizationService.getFilteredOverview(1L, List.of(5L), ""); + // assert + verify(authorizationService, times(1)).isWriteable(authorizationUser, 5L); assertThat(List.of(overview)).hasSameElementsAs(overviews); + assertTrue(overviews.get(0).isWriteable()); + } + + @Test + void getFilteredOverviewShouldReturnNotWriteableOverviewsWhenArchived() { + // arrange + when(authorizationService.getAuthorizationUser()).thenReturn(authorizationUser); + when(overviewBusinessService.getFilteredOverview(any(), any(), any(), eq(authorizationUser))) + .thenReturn(List.of(overview)); + + // act + List overviews = overviewAuthorizationService.getFilteredOverview(998L, List.of(5L), ""); + + // assert + verify(authorizationService, times(0)).isWriteable(authorizationUser, 5L); + assertFalse(overviews.get(0).isWriteable()); } @ParameterizedTest @ValueSource(booleans = { true, false }) void getFilteredOverviewShouldSetWritableProperly(boolean isWritable) { + // arrange when(authorizationService.getAuthorizationUser()).thenReturn(authorizationUser); when(authorizationService.isWriteable(authorizationUser, 5L)).thenReturn(isWritable); when(overviewBusinessService.getFilteredOverview(any(), any(), any(), eq(authorizationUser))) .thenReturn(List.of(overview)); + // act List overviews = overviewAuthorizationService.getFilteredOverview(1L, List.of(5L), ""); + // assert assertEquals(isWritable, overviews.get(0).isWriteable()); } @Test void getFilteredOverviewShouldReturnEmptyListWhenNotAuthorized() { + // arrange when(authorizationService.getAuthorizationUser()).thenReturn(authorizationUser); when(overviewBusinessService.getFilteredOverview(1L, List.of(5L), "", authorizationUser)).thenReturn(List.of()); + // act List overviews = overviewAuthorizationService.getFilteredOverview(1L, List.of(5L), ""); + + // assert assertThat(List.of()).hasSameElementsAs(overviews); } @ParameterizedTest @ValueSource(booleans = { true, false }) void hasWriteAllAccessShouldReturnHasRoleWriteAll(boolean hasRoleWriteAll) { + // arrange if (hasRoleWriteAll) { when(authorizationService.getAuthorizationUser()).thenReturn(authorizationUser); } else { @@ -78,6 +107,7 @@ void hasWriteAllAccessShouldReturnHasRoleWriteAll(boolean hasRoleWriteAll) { .thenReturn(mockAuthorizationUser(defaultUser(5L), List.of(), 7L, List.of(READ_ALL_PUBLISHED))); } + // assert assertEquals(hasRoleWriteAll, overviewAuthorizationService.hasWriteAllAccess()); } } diff --git a/backend/src/test/java/ch/puzzle/okr/service/business/ObjectiveBusinessServiceTest.java b/backend/src/test/java/ch/puzzle/okr/service/business/ObjectiveBusinessServiceTest.java index f7360742c3..9743af63dc 100644 --- a/backend/src/test/java/ch/puzzle/okr/service/business/ObjectiveBusinessServiceTest.java +++ b/backend/src/test/java/ch/puzzle/okr/service/business/ObjectiveBusinessServiceTest.java @@ -51,63 +51,109 @@ class ObjectiveBusinessServiceTest { private final Objective objective = Objective.Builder.builder().withId(5L).withTitle("Objective 1").build(); private final Objective fullObjective = Objective.Builder.builder().withTitle("FullObjective1").withCreatedBy(user) .withTeam(team1).withQuarter(quarter).withDescription("This is our description") - .withModifiedOn(LocalDateTime.MAX).build(); + .withModifiedOn(LocalDateTime.MAX).withArchived(false).build(); private final KeyResult ordinalKeyResult = KeyResultOrdinal.Builder.builder().withCommitZone("Baum") .withStretchZone("Wald").withId(5L).withTitle("Keyresult Ordinal").withObjective(objective).build(); private final List keyResultList = List.of(ordinalKeyResult, ordinalKeyResult, ordinalKeyResult); @Test void getOneObjective() { + // arrange when(objectivePersistenceService.findById(5L)).thenReturn(objective); + // act Objective realObjective = objectiveBusinessService.getEntityById(5L); + // assert assertEquals("Objective 1", realObjective.getTitle()); } + @Test + void getAllObjectives() { + // arrange + when(objectivePersistenceService.findAll()).thenReturn(List.of(fullObjective)); + + // act + List objectiveList = objectiveBusinessService.getAllObjectives(); + + // assert + assertEquals(List.of(fullObjective), objectiveList); + } + @Test void getEntitiesByTeamId() { + // arrange when(objectivePersistenceService.findObjectiveByTeamId(anyLong())).thenReturn(List.of(objective)); + // act List entities = objectiveBusinessService.getEntitiesByTeamId(5L); + // assert assertThat(entities).hasSameElementsAs(List.of(objective)); } @Test void shouldNotFindTheObjective() { + // arrange when(objectivePersistenceService.findById(6L)) .thenThrow(new ResponseStatusException(NOT_FOUND, "Objective with id 6 not found")); + // act ResponseStatusException exception = assertThrows(ResponseStatusException.class, () -> objectiveBusinessService.getEntityById(6L)); + + // assert assertEquals(NOT_FOUND, exception.getStatusCode()); assertEquals("Objective with id 6 not found", exception.getReason()); } @Test void shouldSaveANewObjective() { + // arrange Objective objective = spy(Objective.Builder.builder().withTitle("Received Objective").withTeam(team1) .withQuarter(quarter).withDescription("The description").withModifiedOn(null).withModifiedBy(null) - .withState(DRAFT).build()); - + .withState(DRAFT).withArchived(false).build()); doNothing().when(objective).setCreatedOn(any()); + // act objectiveBusinessService.createEntity(objective, authorizationUser); + // assert verify(objectivePersistenceService, times(1)).save(objective); assertEquals(DRAFT, objective.getState()); assertEquals(user, objective.getCreatedBy()); assertNull(objective.getCreatedOn()); } + @Test + void shouldSetIsArchivedToFalseOnCreate() { + // arrange + Objective objective = spy(Objective.Builder.builder().withTitle("Received Objective").withTeam(team1) + .withQuarter(quarter).withDescription("The description").withModifiedOn(null).withModifiedBy(null) + .withState(DRAFT).withArchived(true).build()); + Objective savedObjective = Objective.Builder.builder().withTitle("Received Objective").withTeam(team1) + .withQuarter(quarter).withDescription("The description").withModifiedOn(null).withModifiedBy(null) + .withState(DRAFT).withArchived(false).withCreatedBy(user).build(); + doNothing().when(objective).setCreatedOn(any()); + + // act + objectiveBusinessService.createEntity(objective, authorizationUser); + + // assert + verify(objectivePersistenceService, times(1)).save(savedObjective); + } + @Test void shouldNotThrowResponseStatusExceptionWhenPuttingNullId() { + // arrange Objective objective1 = Objective.Builder.builder().withId(null).withTitle("Title") .withDescription("Description").withModifiedOn(LocalDateTime.now()).build(); when(objectiveBusinessService.createEntity(objective1, authorizationUser)).thenReturn(fullObjective); + // act Objective savedObjective = objectiveBusinessService.createEntity(objective1, authorizationUser); + + // assert assertNull(savedObjective.getId()); assertEquals("FullObjective1", savedObjective.getTitle()); assertEquals("Bob", savedObjective.getCreatedBy().getFirstname()); @@ -116,58 +162,95 @@ void shouldNotThrowResponseStatusExceptionWhenPuttingNullId() { @ParameterizedTest @ValueSource(booleans = { false, true }) void updateEntityShouldHandleQuarterCorrectly(boolean hasKeyResultAnyCheckIns) { + // arrange Long id = 27L; String title = "Received Objective"; String description = "The description"; Quarter changedQuarter = Quarter.Builder.builder().withId(2L).withLabel("another quarter").build(); Objective savedObjective = Objective.Builder.builder().withId(id).withTitle(title).withTeam(team1) - .withQuarter(quarter).withDescription(null).withModifiedOn(null).withModifiedBy(null).build(); + .withQuarter(quarter).withDescription(null).withModifiedOn(null).withModifiedBy(null) + .withArchived(false).build(); Objective changedObjective = Objective.Builder.builder().withId(id).withTitle(title).withTeam(team1) .withQuarter(changedQuarter).withDescription(description).withModifiedOn(null).withModifiedBy(null) .build(); Objective updatedObjective = Objective.Builder.builder().withId(id).withTitle(title).withTeam(team1) .withQuarter(hasKeyResultAnyCheckIns ? quarter : changedQuarter).withDescription(description) .withModifiedOn(null).withModifiedBy(null).build(); - when(objectivePersistenceService.findById(any())).thenReturn(savedObjective); when(keyResultBusinessService.getAllKeyResultsByObjective(savedObjective.getId())).thenReturn(keyResultList); when(keyResultBusinessService.hasKeyResultAnyCheckIns(any())).thenReturn(hasKeyResultAnyCheckIns); when(objectivePersistenceService.save(changedObjective)).thenReturn(updatedObjective); - boolean isImUsed = objectiveBusinessService.isImUsed(changedObjective); + + // act Objective updatedEntity = objectiveBusinessService.updateEntity(changedObjective.getId(), changedObjective, authorizationUser); + // assert assertEquals(hasKeyResultAnyCheckIns, isImUsed); assertEquals(hasKeyResultAnyCheckIns ? savedObjective.getQuarter() : changedObjective.getQuarter(), updatedEntity.getQuarter()); assertEquals(changedObjective.getDescription(), updatedEntity.getDescription()); assertEquals(changedObjective.getTitle(), updatedEntity.getTitle()); + assertFalse(updatedEntity.isArchived()); } @Test void shouldDeleteObjectiveAndAssociatedKeyResults() { + // arrange when(keyResultBusinessService.getAllKeyResultsByObjective(1L)).thenReturn(keyResultList); + // act objectiveBusinessService.deleteEntityById(1L); + // assert verify(keyResultBusinessService, times(3)).deleteEntityById(5L); verify(objectiveBusinessService, times(1)).deleteEntityById(1L); } @Test void shouldDuplicateObjective() { + // arrange KeyResult keyResultOrdinal = KeyResultOrdinal.Builder.builder().withTitle("Ordinal").build(); KeyResult keyResultOrdinal2 = KeyResultOrdinal.Builder.builder().withTitle("Ordinal2").build(); KeyResult keyResultMetric = KeyResultMetric.Builder.builder().withTitle("Metric").withUnit(Unit.FTE).build(); KeyResult keyResultMetric2 = KeyResultMetric.Builder.builder().withTitle("Metric2").withUnit(Unit.CHF).build(); List keyResults = List.of(keyResultOrdinal, keyResultOrdinal2, keyResultMetric, keyResultMetric2); - when(objectivePersistenceService.save(any())).thenReturn(objective); when(keyResultBusinessService.getAllKeyResultsByObjective(anyLong())).thenReturn(keyResults); + // act objectiveBusinessService.duplicateObjective(objective.getId(), objective, authorizationUser); + + // assert verify(keyResultBusinessService, times(4)).createEntity(any(), any()); verify(objectiveBusinessService, times(1)).createEntity(any(), any()); } + + @Test + void shouldCorrectArchiveObjective() { + // arrange + Objective savedObjective = Objective.Builder.builder().withTitle("FullObjective1").withCreatedBy(user) + .withTeam(team1).withQuarter(quarter).withDescription("This is our description") + .withModifiedOn(LocalDateTime.MAX).withArchived(true).build(); + when(objectivePersistenceService.findById(2L)).thenReturn(fullObjective); + + // act + objectiveBusinessService.archiveEntity(2L); + + // assert + verify(objectivePersistenceService, times(1)).save(savedObjective); + } + + @Test + void shouldNotThrowErrorWhenObjectiveToArchiveIsNotExisting() { + // arrange + when(objectivePersistenceService.findById(2L)).thenReturn(null); + + // act + objectiveBusinessService.archiveEntity(2L); + + // assert + verify(objectivePersistenceService, times(0)).save(any()); + } } diff --git a/backend/src/test/java/ch/puzzle/okr/service/business/QuarterBusinessServiceTest.java b/backend/src/test/java/ch/puzzle/okr/service/business/QuarterBusinessServiceTest.java index 2dd0e0b52c..66999d7597 100644 --- a/backend/src/test/java/ch/puzzle/okr/service/business/QuarterBusinessServiceTest.java +++ b/backend/src/test/java/ch/puzzle/okr/service/business/QuarterBusinessServiceTest.java @@ -1,6 +1,9 @@ package ch.puzzle.okr.service.business; +import ch.puzzle.okr.models.Objective; import ch.puzzle.okr.models.Quarter; +import ch.puzzle.okr.models.Team; +import ch.puzzle.okr.models.User; import ch.puzzle.okr.service.persistence.QuarterPersistenceService; import ch.puzzle.okr.service.validation.QuarterValidationService; import org.junit.jupiter.api.Test; @@ -17,6 +20,7 @@ import org.springframework.test.util.ReflectionTestUtils; import java.time.LocalDate; +import java.time.LocalDateTime; import java.time.YearMonth; import java.util.ArrayList; import java.util.Arrays; @@ -24,6 +28,7 @@ import java.util.Map; import java.util.stream.Stream; +import static ch.puzzle.okr.Constants.ARCHIVE; import static ch.puzzle.okr.Constants.BACKLOG; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; @@ -44,13 +49,25 @@ class QuarterBusinessServiceTest { @Spy private QuarterBusinessService quarterBusinessService; + private final Team team1 = Team.Builder.builder().withId(1L).withName("Team1").build(); + private final Quarter quarter1 = Quarter.Builder.builder().withId(1L).withLabel("GJ 22/23-Q2").build(); + private final Quarter quarter2 = Quarter.Builder.builder().withId(2L).withLabel("GJ 22/23-Q3").build(); + private final User user = User.Builder.builder().withId(1L).withFirstname("Bob").withLastname("Kaufmann") + .withUsername("bkaufmann").withEmail("kaufmann@puzzle.ch").build(); + private final Objective fullObjective = Objective.Builder.builder().withId(3L).withTitle("FullObjective1") + .withCreatedBy(user).withTeam(team1).withQuarter(quarter1).withDescription("This is our description") + .withModifiedOn(LocalDateTime.MAX).withArchived(false).build(); + private static Stream shouldGetFirstMonthFromQuarter() { return Stream.of(Arguments.of(1, 1), Arguments.of(2, 4), Arguments.of(3, 7), Arguments.of(4, 10)); } @Test void shouldReturnProperQuarter() { + // act quarterBusinessService.getQuarterById(3L); + + // assert verify(quarterValidationService, times(1)).validateOnGet(3L); verify(quarterPersistenceService, times(1)).findById(3L); @@ -58,37 +75,52 @@ void shouldReturnProperQuarter() { @Test void shouldReturnExceptionWhenIdIsNullOnGetQuarter() { + // act quarterBusinessService.getQuarterById(null); + + // assert verify(quarterValidationService, times(1)).validateOnGet(null); } @Test void shouldCallGetCurrentQuarterOnGetCurrentQuarter() { + // act quarterBusinessService.getCurrentQuarter(); + + // assert verify(quarterPersistenceService, times(1)).getCurrentQuarter(); } @Test void shouldCallGetQuarters() { + // act quarterBusinessService.getQuarters(); + + // assert verify(quarterPersistenceService).getMostCurrentQuarters(); } @Test - void shouldGetBacklogQuarter() { + void shouldGetBacklogAndArchiveQuarter() { + // arrange Quarter realQuarter1 = Quarter.Builder.builder().withId(1L).withLabel("GJ-22/23-Q3") .withStartDate(LocalDate.of(2022, 4, 1)).withEndDate(LocalDate.of(2022, 7, 31)).build(); Quarter realQuarter2 = Quarter.Builder.builder().withId(2L).withLabel("GJ-22/23-Q4") .withStartDate(LocalDate.of(2022, 8, 1)).withEndDate(LocalDate.of(2022, 11, 30)).build(); List quarterList = new ArrayList<>(Arrays.asList(realQuarter1, realQuarter2)); - Quarter backlogQuarter = Quarter.Builder.builder().withId(199L).withLabel(BACKLOG).build(); + Quarter archiveQuarter = Quarter.Builder.builder().withId(198L).withLabel(ARCHIVE).build(); when(quarterPersistenceService.getMostCurrentQuarters()).thenReturn(quarterList); when(quarterPersistenceService.findByLabel(BACKLOG)).thenReturn(backlogQuarter); + when(quarterPersistenceService.findByLabel(ARCHIVE)).thenReturn(archiveQuarter); + // act quarterList = quarterBusinessService.getQuarters(); + + // assert assertEquals(4, quarterList.size()); assertEquals(BACKLOG, quarterList.get(0).getLabel()); + assertEquals(ARCHIVE, quarterList.get(3).getLabel()); assertNull(quarterList.get(0).getStartDate()); assertNull(quarterList.get(0).getEndDate()); } @@ -96,21 +128,31 @@ void shouldGetBacklogQuarter() { @ParameterizedTest @ValueSource(ints = { 1, 2, 4, 5, 7, 8, 10, 11 }) void shouldNotGenerateQuarterIfNotLastMonth(int month) { + // arrange ReflectionTestUtils.setField(quarterBusinessService, "quarterStart", 7); - Mockito.when(quarterBusinessService.getCurrentYearMonth()).thenReturn(YearMonth.of(2030, month)); + + // act quarterBusinessService.scheduledGenerationQuarters(); + + // assert verify(quarterPersistenceService, never()).save(any()); } @ParameterizedTest @ValueSource(ints = { 3, 6, 9, 12 }) void shouldGenerateQuarterIfLastMonth(int month) { + // arrange ReflectionTestUtils.setField(quarterBusinessService, "quarterStart", 7); - Mockito.when(quarterBusinessService.getCurrentYearMonth()).thenReturn(YearMonth.of(2030, month)); + + // act quarterBusinessService.scheduledGenerationQuarters(); + + // assert verify(quarterPersistenceService, times(1)).save(any()); + verify(objectiveBusinessService, times(1)).getAllObjectives(); + verify(quarterPersistenceService, times(1)).getMostCurrentQuarters(); } private static Stream generateQuarterParams() { @@ -126,6 +168,7 @@ private static Stream generateQuarterParams() { @MethodSource("generateQuarterParams") void shouldGenerateCorrectQuarter(int quarterStart, String quarterFormat, YearMonth currentYearMonth, String expectedLabel) { + // arrange ReflectionTestUtils.setField(quarterBusinessService, "quarterStart", quarterStart); ReflectionTestUtils.setField(quarterBusinessService, "quarterFormat", quarterFormat); @@ -140,9 +183,13 @@ void shouldGenerateCorrectQuarter(int quarterStart, String quarterFormat, YearMo Mockito.when(quarterBusinessService.getCurrentYearMonth()).thenReturn(currentYearMonth); + // act quarterBusinessService.scheduledGenerationQuarters(); + // assert verify(quarterPersistenceService).save(expectedQuarter); + verify(objectiveBusinessService, times(1)).getAllObjectives(); + verify(quarterPersistenceService, times(1)).getMostCurrentQuarters(); } private static Stream getQuartersParams() { @@ -157,15 +204,66 @@ private static Stream getQuartersParams() { @ParameterizedTest(name = "Start month={0}, current month={1} => quarter={2}") @MethodSource("getQuartersParams") void shouldGetQuartersBasedOnStart(int start, int month, int quarter) { + // arrange ReflectionTestUtils.setField(quarterBusinessService, "quarterStart", start); + + // act Map quarters = quarterBusinessService.generateQuarters(); + + // assert assertEquals(quarter, quarters.get(month)); } @Test void shouldReturnNullWhenNoQuarterGenerationNeeded() { + // arrange Mockito.when(quarterBusinessService.getCurrentYearMonth()).thenReturn(YearMonth.of(2030, 4)); + + // act quarterBusinessService.scheduledGenerationQuarters(); + + // assert verify(quarterPersistenceService, times(0)).save(any()); } + + @Test + void shouldCorrectMoveObjectsToArchive() { + // arrange + when(quarterPersistenceService.getMostCurrentQuarters()).thenReturn(List.of(quarter2)); + when(objectiveBusinessService.getAllObjectives()).thenReturn(List.of(fullObjective, fullObjective, fullObjective)); + + // act + quarterBusinessService.moveObjectsFromNonMostCurrentQuartersIntoArchive(); + + // assert + verify(objectiveBusinessService, times(3)).archiveEntity(3L); + } + + @Test + void shouldNotMoveObjectsToArchiveWhenInMostCurrentQuarters() { + // arrange + when(quarterPersistenceService.getMostCurrentQuarters()).thenReturn(List.of(quarter1, quarter2)); + when(objectiveBusinessService.getAllObjectives()).thenReturn(List.of(fullObjective)); + + // act + quarterBusinessService.moveObjectsFromNonMostCurrentQuartersIntoArchive(); + + // assert + verify(objectiveBusinessService, times(0)).archiveEntity(anyLong()); + } + + @Test + void shouldNotMoveObjectsToArchiveWhenInBacklog() { + // arrange + Objective objective = Objective.Builder.builder().withId(5L).withTitle("Objective 1") + .withQuarter(Quarter.Builder.builder().withId(999L).build()).build(); + when(quarterPersistenceService.getMostCurrentQuarters()).thenReturn(List.of(quarter2)); + when(objectiveBusinessService.getAllObjectives()).thenReturn(List.of(objective)); + + // act + quarterBusinessService.moveObjectsFromNonMostCurrentQuartersIntoArchive(); + + // assert + verify(objectiveBusinessService, times(0)).archiveEntity(anyLong()); + } } diff --git a/backend/src/test/java/ch/puzzle/okr/service/persistence/ObjectivePersistenceServiceIT.java b/backend/src/test/java/ch/puzzle/okr/service/persistence/ObjectivePersistenceServiceIT.java index 8bb744a9aa..02cd265eb1 100644 --- a/backend/src/test/java/ch/puzzle/okr/service/persistence/ObjectivePersistenceServiceIT.java +++ b/backend/src/test/java/ch/puzzle/okr/service/persistence/ObjectivePersistenceServiceIT.java @@ -68,7 +68,7 @@ void tearDown() { void findAllShouldReturnListOfObjectives() { List objectives = objectivePersistenceService.findAll(); - assertEquals(7, objectives.size()); + assertEquals(8, objectives.size()); } @Test diff --git a/backend/src/test/java/ch/puzzle/okr/service/persistence/OverviewPersistenceServiceIT.java b/backend/src/test/java/ch/puzzle/okr/service/persistence/OverviewPersistenceServiceIT.java index a8165644bb..cb97b31b80 100644 --- a/backend/src/test/java/ch/puzzle/okr/service/persistence/OverviewPersistenceServiceIT.java +++ b/backend/src/test/java/ch/puzzle/okr/service/persistence/OverviewPersistenceServiceIT.java @@ -11,7 +11,7 @@ import static ch.puzzle.okr.TestHelper.defaultAuthorizationUser; import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.*; @SpringIntegrationTest class OverviewPersistenceServiceIT { @@ -81,6 +81,16 @@ void getFilteredOverviewShouldReturnOverviewsWhenQuarterWithoutObjectivesAndObje assertTrue(overviews.isEmpty()); } + @Test + void getFilteredOverviewShouldReturnCorrectOverviewForArchive() { + List overviews = overviewPersistenceService.getFilteredOverview(998L, List.of(5L, 6L, 8L), "", + authorizationUser); + + assertTrue(overviews.get(0).isObjectiveArchived()); + assertEquals(6, overviews.get(0).getQuarterId()); + assertFalse(overviews.get(0).isWriteable()); + } + private List getOverviewIds(List overviewList) { return overviewList.stream().map(Overview::getOverviewId).toList(); } From e59ff02a5d427072a05cf15cc7626e1ce23af15f Mon Sep 17 00:00:00 2001 From: Lias Kleisa Date: Thu, 13 Jun 2024 21:55:48 +0200 Subject: [PATCH 08/11] Add frontend tests --- .../src/app/overview/overview.component.spec.ts | 13 +++++++++++++ .../objective-form.component.spec.ts | 11 +++++++++++ 2 files changed, 24 insertions(+) diff --git a/frontend/src/app/overview/overview.component.spec.ts b/frontend/src/app/overview/overview.component.spec.ts index 4599cb1e60..6961c77c74 100644 --- a/frontend/src/app/overview/overview.component.spec.ts +++ b/frontend/src/app/overview/overview.component.spec.ts @@ -132,6 +132,19 @@ describe('OverviewComponent', () => { expect(component.loadOverview).toHaveBeenLastCalledWith(); }); + it('should correct set isArchiveQuarter', async () => { + let routerHarness = await RouterTestingHarness.create(); + await routerHarness.navigateByUrl('/' + ['?quarter=998', 7, [], '']); + routerHarness.detectChanges(); + component.loadOverviewWithParams(); + expect(component.isArchiveQuarter).toBeTruthy(); + + await routerHarness.navigateByUrl('/' + ['?quarter=98', 7, [], '']); + routerHarness.detectChanges(); + component.loadOverviewWithParams(); + expect(component.isArchiveQuarter).toBeFalsy(); + }); + function markFiltersAsReady() { refreshDataServiceMock.quarterFilterReady.next(null); refreshDataServiceMock.teamFilterReady.next(null); diff --git a/frontend/src/app/shared/dialog/objective-dialog/objective-form.component.spec.ts b/frontend/src/app/shared/dialog/objective-dialog/objective-form.component.spec.ts index bae1fc2c25..e4cd0de42b 100644 --- a/frontend/src/app/shared/dialog/objective-dialog/objective-form.component.spec.ts +++ b/frontend/src/app/shared/dialog/objective-dialog/objective-form.component.spec.ts @@ -41,6 +41,7 @@ const quarterService = { { id: 1, startDate: quarter.startDate, endDate: quarter.endDate, label: quarter.label }, { id: 2, startDate: quarter.startDate, endDate: quarter.endDate, label: quarter.label }, { id: 199, startDate: null, endDate: null, label: 'Backlog' }, + { id: 998, startDate: null, endDate: null, label: 'Archiv' }, ]); }, }; @@ -254,6 +255,16 @@ describe('ObjectiveDialogComponent', () => { expect(rawFormValue.quarter).toBe(objective.quarterId); }); + it('should remove archive as option in quarter list', async () => { + matDataMock.objective.objectiveId = 1; + const routerHarness = await RouterTestingHarness.create(); + await routerHarness.navigateByUrl('/?quarter=2'); + objectiveService.getFullObjective.mockReturnValue(of(objective)); + component.ngOnInit(); + expect(component.quarters.length).toBe(3); + expect(component.quarters[2].label).toBe('Backlog'); + }); + it('should return correct value if allowed to save to backlog', async () => { component.quarters = quarterList; const isBacklogQuarterSpy = jest.spyOn(component, 'isBacklogQuarter'); From 18121f1b7cb92e54e17e126d808bf2dfc2e199d6 Mon Sep 17 00:00:00 2001 From: Lias Kleisa Date: Thu, 13 Jun 2024 21:56:20 +0200 Subject: [PATCH 09/11] Add e2e tests --- .../h2-db/data-test-h2/V100_0_0__TestData.sql | 5 ++-- frontend/cypress/e2e/archive-quarter.cy.ts | 29 +++++++++++++++++++ 2 files changed, 32 insertions(+), 2 deletions(-) create mode 100644 frontend/cypress/e2e/archive-quarter.cy.ts diff --git a/backend/src/main/resources/db/h2-db/data-test-h2/V100_0_0__TestData.sql b/backend/src/main/resources/db/h2-db/data-test-h2/V100_0_0__TestData.sql index 4289503af9..0b5b643230 100644 --- a/backend/src/main/resources/db/h2-db/data-test-h2/V100_0_0__TestData.sql +++ b/backend/src/main/resources/db/h2-db/data-test-h2/V100_0_0__TestData.sql @@ -74,7 +74,7 @@ values (4, 1, '', '2023-07-25 08:17:51.309958', 66, 'Build a company culture tha 'consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua', 1, 2, 6, 'ONGOING', null, '2023-07-25 08:39:28.175703', false), (998,1, '', '2023-07-25 08:39:28.175703', 40, - 'consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua', + 'This objective is in backlog', 1, 6, 6, 'ONGOING', null, '2023-07-25 08:39:28.175703', true); insert into key_result (id, version, baseline, description, modified_on, stretch_goal, title, created_by_id, @@ -94,7 +94,8 @@ values (10,1, 465, '', '2023-07-25 08:23:02.273028', 60, 'Im Durchschnitt soll (19,1, 50, 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lore', '2023-07-25 08:42:56.407125', 1, 'nsetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At ', 1, 8, 1, 'metric', '2023-07-25 08:42:56.407125', 'PERCENT', null, null, null), (17,1, 525, 'asdf', '2023-07-25 08:41:52.844903', 20000000, 'vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lore', 1, 9, 1, 'metric', '2023-07-25 08:41:52.844903', 'PERCENT', null, null, null), (9,1, 100, '', '2023-07-25 08:48:45.825328', 80, 'Die Member des BBT reduzieren Ihre Lautstärke um 20%', 1, 5, 1, 'metric', '2023-07-25 08:48:45.825328', 'PERCENT', null, null, null), - (18,1, 0, 'consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lore', '2023-07-25 08:42:24.779721', 1, 'Lorem', 1, 8, 1, 'metric', '2023-07-25 08:42:24.779721', 'PERCENT', null, null, null); + (18,1, 0, 'consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lore', '2023-07-25 08:42:24.779721', 1, 'Lorem', 1, 8, 1, 'metric', '2023-07-25 08:42:24.779721', 'PERCENT', null, null, null), + (998,1, 0, 'Description is here', '2023-07-25 08:42:24.779721', 1, 'KeyResult in archive', 1, 998, 1, 'metric', '2023-07-25 08:42:24.779721', 'PERCENT', null, null, null); insert into check_in (id, version, change_info, created_on, initiatives, modified_on, value_metric, created_by_id, key_result_id, confidence, check_in_type, zone) values (1,1, 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam', '2023-07-25 08:44:13.865976', '', '2023-07-24 22:00:00.000000', 77, 1, 8, 5, 'metric', null), diff --git a/frontend/cypress/e2e/archive-quarter.cy.ts b/frontend/cypress/e2e/archive-quarter.cy.ts new file mode 100644 index 0000000000..a8287a15a8 --- /dev/null +++ b/frontend/cypress/e2e/archive-quarter.cy.ts @@ -0,0 +1,29 @@ +import * as users from '../fixtures/users.json'; +import { onlyOn } from '@cypress/skip-test'; + +describe('OKR Archive Quarter e2e tests', () => { + describe('tests via click', () => { + beforeEach(() => { + cy.loginAsUser(users.gl); + cy.visit('/?quarter=2'); + onlyOn('chrome'); + }); + + it(`Should display past objects in archive without possibility to edit`, () => { + cy.visit('/?quarter=998'); + cy.contains('Keine Daten im Archiv'); + cy.visit('/?quarter=998&teams=5,4,6,8'); + cy.contains('This objective is in backlog'); + cy.contains('KeyResult in archive'); + cy.contains('LoremIpsum'); + cy.contains('Objective hinzufügen').should('not.exist'); + cy.contains('Key Result hinzufügen').should('not.exist'); + + cy.getByTestId('objective').first().getByTestId('three-dot-menu').should('not.exist'); + + cy.getByTestId('objective').first().click(); + cy.contains('Key Result bearbeiten').should('not.exist'); + cy.contains('Check-in erfassen').should('not.exist'); + }); + }); +}); From c6f23b341f7a5bc92ff9fc592ccdb4d57bd1a907 Mon Sep 17 00:00:00 2001 From: Lias Kleisa Date: Mon, 24 Jun 2024 11:03:42 +0200 Subject: [PATCH 10/11] Add default value for objective archived --- .../java/ch/puzzle/okr/models/Objective.java | 2 +- .../V1_0_0__current-db-schema-for-testing.sql | 5 +--- .../db/migration/V2_1_3__addArchive.sql | 23 ++----------------- 3 files changed, 4 insertions(+), 26 deletions(-) diff --git a/backend/src/main/java/ch/puzzle/okr/models/Objective.java b/backend/src/main/java/ch/puzzle/okr/models/Objective.java index 08493480be..a002e45f4e 100644 --- a/backend/src/main/java/ch/puzzle/okr/models/Objective.java +++ b/backend/src/main/java/ch/puzzle/okr/models/Objective.java @@ -51,7 +51,7 @@ public class Objective implements WriteableInterface { @ManyToOne private User modifiedBy; - private boolean archived; + private boolean archived = false; private transient boolean writeable; diff --git a/backend/src/main/resources/db/h2-db/database-h2-schema/V1_0_0__current-db-schema-for-testing.sql b/backend/src/main/resources/db/h2-db/database-h2-schema/V1_0_0__current-db-schema-for-testing.sql index 353a5107f3..2294d5abc9 100644 --- a/backend/src/main/resources/db/h2-db/database-h2-schema/V1_0_0__current-db-schema-for-testing.sql +++ b/backend/src/main/resources/db/h2-db/database-h2-schema/V1_0_0__current-db-schema-for-testing.sql @@ -170,10 +170,7 @@ SELECT TQ.TEAM_ID AS "TEAM_ID", O.TITLE AS "OBJECTIVE_TITLE", O.STATE AS "OBJECTIVE_STATE", O.CREATED_ON AS "OBJECTIVE_CREATED_ON", - CASE - WHEN O.TITLE IS NOT NULL THEN O.ARCHIVED - ELSE FALSE - END AS "OBJECTIVE_ARCHIVED", + O.ARCHIVED AS "OBJECTIVE_ARCHIVED", COALESCE(KR.ID, -1) AS "KEY_RESULT_ID", KR.TITLE AS "KEY_RESULT_TITLE", KR.KEY_RESULT_TYPE AS "KEY_RESULT_TYPE", diff --git a/backend/src/main/resources/db/migration/V2_1_3__addArchive.sql b/backend/src/main/resources/db/migration/V2_1_3__addArchive.sql index 43ca1baeb8..57eadb20d0 100644 --- a/backend/src/main/resources/db/migration/V2_1_3__addArchive.sql +++ b/backend/src/main/resources/db/migration/V2_1_3__addArchive.sql @@ -1,25 +1,9 @@ alter table objective - add column if not exists archived boolean; + add column if not exists archived boolean default false not null; INSERT INTO quarter (id, label, start_date, end_date) VALUES (998, 'Archiv', null, null); -DO $$ - DECLARE - r record; - BEGIN - FOR r IN SELECT * FROM objective - WHERE objective.archived IS NULL - LOOP - UPDATE objective o - SET archived = false - WHERE o.id = r.id; - END LOOP; -END$$; - -alter table objective - alter column archived set not null; - DROP VIEW IF EXISTS OVERVIEW; CREATE VIEW OVERVIEW AS SELECT tq.team_id AS "team_id", @@ -31,10 +15,7 @@ SELECT tq.team_id AS "team_id", o.title AS "objective_title", o.state AS "objective_state", o.created_on AS "objective_created_on", - CASE - WHEN o.archived IS NOT NULL THEN o.archived - ELSE FALSE - END AS "objective_archived", + o.archived AS "objective_archived", coalesce(kr.id, -1) AS "key_result_id", kr.title AS "key_result_title", kr.key_result_type AS "key_result_type", From 32dd1bbecda7c2419a72b4ae333a939341c615c3 Mon Sep 17 00:00:00 2001 From: Lias Kleisa Date: Mon, 24 Jun 2024 13:23:29 +0200 Subject: [PATCH 11/11] Use default value when null in overview --- .../V1_0_0__current-db-schema-for-testing.sql | 34 +++++++++---------- .../db/migration/V2_1_3__addArchive.sql | 34 +++++++++---------- 2 files changed, 34 insertions(+), 34 deletions(-) diff --git a/backend/src/main/resources/db/h2-db/database-h2-schema/V1_0_0__current-db-schema-for-testing.sql b/backend/src/main/resources/db/h2-db/database-h2-schema/V1_0_0__current-db-schema-for-testing.sql index 2294d5abc9..723106c40b 100644 --- a/backend/src/main/resources/db/h2-db/database-h2-schema/V1_0_0__current-db-schema-for-testing.sql +++ b/backend/src/main/resources/db/h2-db/database-h2-schema/V1_0_0__current-db-schema-for-testing.sql @@ -161,30 +161,30 @@ create index if not exists idx_completed_objective DROP VIEW IF EXISTS OVERVIEW; CREATE VIEW OVERVIEW AS -SELECT TQ.TEAM_ID AS "TEAM_ID", - TQ.TEAM_VERSION AS "TEAM_VERSION", - TQ.NAME AS "TEAM_NAME", - TQ.QUARTER_ID AS "QUARTER_ID", - TQ.LABEL AS "QUARTER_LABEL", - COALESCE(O.ID, -1) AS "OBJECTIVE_ID", - O.TITLE AS "OBJECTIVE_TITLE", - O.STATE AS "OBJECTIVE_STATE", - O.CREATED_ON AS "OBJECTIVE_CREATED_ON", - O.ARCHIVED AS "OBJECTIVE_ARCHIVED", - COALESCE(KR.ID, -1) AS "KEY_RESULT_ID", - KR.TITLE AS "KEY_RESULT_TITLE", - KR.KEY_RESULT_TYPE AS "KEY_RESULT_TYPE", +SELECT TQ.TEAM_ID AS "TEAM_ID", + TQ.TEAM_VERSION AS "TEAM_VERSION", + TQ.NAME AS "TEAM_NAME", + TQ.QUARTER_ID AS "QUARTER_ID", + TQ.LABEL AS "QUARTER_LABEL", + COALESCE(O.ID, -1) AS "OBJECTIVE_ID", + O.TITLE AS "OBJECTIVE_TITLE", + O.STATE AS "OBJECTIVE_STATE", + O.CREATED_ON AS "OBJECTIVE_CREATED_ON", + COALESCE(O.ARCHIVED, FALSE) AS "OBJECTIVE_ARCHIVED", + COALESCE(KR.ID, -1) AS "KEY_RESULT_ID", + KR.TITLE AS "KEY_RESULT_TITLE", + KR.KEY_RESULT_TYPE AS "KEY_RESULT_TYPE", KR.UNIT, KR.BASELINE, KR.STRETCH_GOAL, KR.COMMIT_ZONE, KR.TARGET_ZONE, KR.STRETCH_ZONE, - COALESCE(C.ID, -1) AS "CHECK_IN_ID", - C.VALUE_METRIC AS "CHECK_IN_VALUE", - C.ZONE AS "CHECK_IN_ZONE", + COALESCE(C.ID, -1) AS "CHECK_IN_ID", + C.VALUE_METRIC AS "CHECK_IN_VALUE", + C.ZONE AS "CHECK_IN_ZONE", C.CONFIDENCE, - C.CREATED_ON AS "CHECK_IN_CREATED_ON" + C.CREATED_ON AS "CHECK_IN_CREATED_ON" FROM (SELECT T.ID AS TEAM_ID, T.VERSION AS TEAM_VERSION, T.NAME, Q.ID AS QUARTER_ID, Q.LABEL FROM TEAM T, QUARTER Q) TQ diff --git a/backend/src/main/resources/db/migration/V2_1_3__addArchive.sql b/backend/src/main/resources/db/migration/V2_1_3__addArchive.sql index 57eadb20d0..da9d055765 100644 --- a/backend/src/main/resources/db/migration/V2_1_3__addArchive.sql +++ b/backend/src/main/resources/db/migration/V2_1_3__addArchive.sql @@ -6,30 +6,30 @@ VALUES (998, 'Archiv', null, null); DROP VIEW IF EXISTS OVERVIEW; CREATE VIEW OVERVIEW AS -SELECT tq.team_id AS "team_id", - tq.team_version AS "team_version", - tq.name AS "team_name", - tq.quater_id AS "quarter_id", - tq.label AS "quarter_label", - coalesce(o.id, -1) AS "objective_id", - o.title AS "objective_title", - o.state AS "objective_state", - o.created_on AS "objective_created_on", - o.archived AS "objective_archived", - coalesce(kr.id, -1) AS "key_result_id", - kr.title AS "key_result_title", - kr.key_result_type AS "key_result_type", +SELECT tq.team_id AS "team_id", + tq.team_version AS "team_version", + tq.name AS "team_name", + tq.quater_id AS "quarter_id", + tq.label AS "quarter_label", + coalesce(o.id, -1) AS "objective_id", + o.title AS "objective_title", + o.state AS "objective_state", + o.created_on AS "objective_created_on", + coalesce(o.archived, false) AS "objective_archived", + coalesce(kr.id, -1) AS "key_result_id", + kr.title AS "key_result_title", + kr.key_result_type AS "key_result_type", kr.unit, kr.baseline, kr.stretch_goal, kr.commit_zone, kr.target_zone, kr.stretch_zone, - coalesce(c.id, -1) AS "check_in_id", - c.value_metric AS "check_in_value", - c.zone AS "check_in_zone", + coalesce(c.id, -1) AS "check_in_id", + c.value_metric AS "check_in_value", + c.zone AS "check_in_zone", c.confidence, - c.created_on AS "check_in_created_on" + c.created_on AS "check_in_created_on" FROM (select t.id as team_id, t.version as team_version, t.name, q.id as quater_id, q.label from team t, quarter q) tq