From 7f3e626108dfa162b93a85870d1145b4765d2cd1 Mon Sep 17 00:00:00 2001 From: Lias Kleisa Date: Thu, 21 Mar 2024 15:24:54 +0100 Subject: [PATCH] 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 | 25 ++++++-- .../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 | 15 ++++- .../objective-form.component.ts | 3 +- 18 files changed, 230 insertions(+), 45 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 9d39d47eb5..957406c157 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,13 +1,11 @@ package ch.puzzle.okr.service.business; -import ch.puzzle.okr.ErrorKey; -import ch.puzzle.okr.exception.OkrResponseStatusException; +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; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.http.HttpStatus; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; @@ -20,11 +18,13 @@ public class QuarterBusinessService { private static final Logger logger = LoggerFactory.getLogger(QuarterBusinessService.class); private final QuarterPersistenceService quarterPersistenceService; + private final ObjectiveBusinessService objectiveBusinessService; private final QuarterValidationService validator; public QuarterBusinessService(QuarterPersistenceService quarterPersistenceService, - QuarterValidationService validator) { + ObjectiveBusinessService objectiveBusinessService, QuarterValidationService validator) { this.quarterPersistenceService = quarterPersistenceService; + this.objectiveBusinessService = objectiveBusinessService; this.validator = validator; } @@ -36,7 +36,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; } @@ -66,12 +68,27 @@ private void generateQuarter(YearMonth yearMonth) { .build(); validator.validateOnGeneration(quarter); quarterPersistenceService.save(quarter); + handleQuarterArchive(); } public YearMonth getCurrentYearMonth() { return YearMonth.now(); } + 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() { YearMonth yearMonth = getCurrentYearMonth(); 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 8939474bdf..839afb41f1 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 @@ -49,28 +49,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 1a569a158e..3e974d701e 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; @@ -10,6 +13,7 @@ import org.mockito.junit.jupiter.MockitoExtension; import java.time.LocalDate; +import java.time.LocalDateTime; import java.time.YearMonth; import java.util.ArrayList; import java.util.Arrays; @@ -28,6 +32,9 @@ class QuarterBusinessServiceTest { @Mock QuarterValidationService quarterValidationService; + @Mock + ObjectiveBusinessService objectiveBusinessService; + @InjectMocks @Spy private QuarterBusinessService quarterBusinessService; @@ -75,7 +82,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()); @@ -89,7 +96,13 @@ void shouldGenerateCorrectQuarter() { Quarter quarterAfterMidYear = Quarter.Builder.builder().withId(null).withLabel("GJ 30/31-Q3") .withStartDate(LocalDate.of(2031, 1, 1)).withEndDate(LocalDate.of(2031, 3, 31)).build(); + Objective fullObjective = Objective.Builder.builder().withId(42L).withTitle("FullObjective") + .withCreatedBy(User.Builder.builder().withId(2L).withFirstname("Robert").build()) + .withTeam(Team.Builder.builder().withId(7L).withName("OKR").build()).withQuarter(quarterStandard) + .withDescription("Description").withModifiedOn(LocalDateTime.MAX).withArchived(false).build(); + Mockito.when(quarterBusinessService.getCurrentYearMonth()).thenReturn(YearMonth.of(2030, 3)); + Mockito.when(objectiveBusinessService.getAllObjectives()).thenReturn(List.of(fullObjective)); quarterBusinessService.scheduledGenerationQuarters(); verify(quarterPersistenceService).save(quarterStandard); 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]); });