diff --git a/backend/src/main/java/ch/puzzle/okr/dto/keyresult/KeyResultMetricDto.java b/backend/src/main/java/ch/puzzle/okr/dto/keyresult/KeyResultMetricDto.java index 0841a1ffca..24b87194e4 100644 --- a/backend/src/main/java/ch/puzzle/okr/dto/keyresult/KeyResultMetricDto.java +++ b/backend/src/main/java/ch/puzzle/okr/dto/keyresult/KeyResultMetricDto.java @@ -8,9 +8,10 @@ @JsonDeserialize(as = KeyResultMetricDto.class) public record KeyResultMetricDto(Long id, int version, String keyResultType, String title, String description, - Double baseline, Double stretchGoal, UnitDto unit, KeyResultUserDto owner, KeyResultObjectiveDto objective, - KeyResultLastCheckInMetricDto lastCheckIn, LocalDateTime createdOn, LocalDateTime modifiedOn, - boolean isWriteable, List actionList) implements KeyResultDto { + Double baseline, Double commitValue, Double targetValue, Double stretchGoal, UnitDto unit, + KeyResultUserDto owner, KeyResultObjectiveDto objective, KeyResultLastCheckInMetricDto lastCheckIn, + LocalDateTime createdOn, LocalDateTime modifiedOn, boolean isWriteable, + List actionList) implements KeyResultDto { @Override public List getActionList() { return actionList; diff --git a/backend/src/main/java/ch/puzzle/okr/dto/overview/OverviewKeyResultMetricDto.java b/backend/src/main/java/ch/puzzle/okr/dto/overview/OverviewKeyResultMetricDto.java index 2b397339ad..f49c2630ee 100644 --- a/backend/src/main/java/ch/puzzle/okr/dto/overview/OverviewKeyResultMetricDto.java +++ b/backend/src/main/java/ch/puzzle/okr/dto/overview/OverviewKeyResultMetricDto.java @@ -1,5 +1,6 @@ package ch.puzzle.okr.dto.overview; -public record OverviewKeyResultMetricDto(Long id, String title, String keyResultType, String unit, Double baseline, - Double stretchGoal, OverviewLastCheckInMetricDto lastCheckIn) implements OverviewKeyResultDto { +public record OverviewKeyResultMetricDto(Long id, String title, String keyResultType, Double baseline, + Double commitValue, Double targetValue, Double stretchGoal, + OverviewLastCheckInMetricDto lastCheckIn) implements OverviewKeyResultDto { } diff --git a/backend/src/main/java/ch/puzzle/okr/mapper/OverviewMapper.java b/backend/src/main/java/ch/puzzle/okr/mapper/OverviewMapper.java index 7f0ef5b198..ace81adfc9 100644 --- a/backend/src/main/java/ch/puzzle/okr/mapper/OverviewMapper.java +++ b/backend/src/main/java/ch/puzzle/okr/mapper/OverviewMapper.java @@ -103,8 +103,9 @@ private OverviewKeyResultMetricDto createKeyResultMetricDto(Overview overview) { return new OverviewKeyResultMetricDto(overview.getOverviewId().getKeyResultId(), overview.getKeyResultTitle(), overview.getKeyResultType(), - overview.getUnit(), overview.getBaseline(), + overview.getCommitValue(), + overview.getTargetValue(), overview.getStretchGoal(), lastCheckIn); } diff --git a/backend/src/main/java/ch/puzzle/okr/mapper/keyresult/KeyResultMetricMapper.java b/backend/src/main/java/ch/puzzle/okr/mapper/keyresult/KeyResultMetricMapper.java index 5ed8adf55e..1d5f9d5f10 100644 --- a/backend/src/main/java/ch/puzzle/okr/mapper/keyresult/KeyResultMetricMapper.java +++ b/backend/src/main/java/ch/puzzle/okr/mapper/keyresult/KeyResultMetricMapper.java @@ -63,6 +63,8 @@ public KeyResultDto toDto(KeyResultMetric keyResult, List actionList) { keyResult.getTitle(), // keyResult.getDescription(), // keyResult.getBaseline(), // + keyResult.getCommitValue(), + keyResult.getTargetValue(), keyResult.getStretchGoal(), // unitMapper.toDto(keyResult.getUnit()), // ownerDto, @@ -79,6 +81,8 @@ public KeyResult toKeyResultMetric(KeyResultMetricDto keyResultMetricDto) { .builder() // .withBaseline(keyResultMetricDto.baseline()) // .withStretchGoal(keyResultMetricDto.stretchGoal()) // + .withCommitValue(keyResultMetricDto.commitValue()) // + .withTargetValue(keyResultMetricDto.targetValue()) // .withUnit(unitBusinessService.findUnitByName(keyResultMetricDto.unit().unitName())) .withId(keyResultMetricDto.id()) // .withVersion(keyResultMetricDto.version()) // diff --git a/backend/src/main/java/ch/puzzle/okr/models/keyresult/KeyResultMetric.java b/backend/src/main/java/ch/puzzle/okr/models/keyresult/KeyResultMetric.java index a0a1ca4b34..b8f6139898 100644 --- a/backend/src/main/java/ch/puzzle/okr/models/keyresult/KeyResultMetric.java +++ b/backend/src/main/java/ch/puzzle/okr/models/keyresult/KeyResultMetric.java @@ -14,6 +14,10 @@ public class KeyResultMetric extends KeyResult { @NotNull(message = MessageKey.ATTRIBUTE_NOT_NULL) private Double baseline; + private Double commitValue; + + private Double targetValue; + @NotNull(message = MessageKey.ATTRIBUTE_NOT_NULL) private Double stretchGoal; @@ -33,6 +37,22 @@ public Double getStretchGoal() { return stretchGoal; } + public Double getCommitValue() { + return commitValue; + } + + public void setCommitValue(Double commitValue) { + this.commitValue = commitValue; + } + + public Double getTargetValue() { + return targetValue; + } + + public void setTargetValue(Double targetValue) { + this.targetValue = targetValue; + } + public void setStretchGoal(Double stretchGoal) { this.stretchGoal = stretchGoal; } @@ -54,6 +74,8 @@ public boolean equals(Object o) { if (o instanceof KeyResultMetric keyResultMetric) { return super.equals(o) && Objects.equals(baseline, keyResultMetric.baseline) && Objects.equals(stretchGoal, keyResultMetric.stretchGoal) + && Objects.equals(commitValue, keyResultMetric.commitValue) + && Objects.equals(targetValue, keyResultMetric.targetValue) && Objects.equals(unit, keyResultMetric.unit); } return false; @@ -61,24 +83,28 @@ public boolean equals(Object o) { @Override public int hashCode() { - return Objects.hash(super.hashCode(), baseline, stretchGoal, unit); + return Objects.hash(super.hashCode(), baseline, commitValue, targetValue, stretchGoal, unit); } @Override public String toString() { - return super.toString() + "KeyResultMetric{" + "baseline=" + baseline + ", stretchGoal=" + stretchGoal - + ", unit='" + unit + '\'' + '}'; + return "KeyResultMetric{" + "baseline=" + baseline + ", commitValue=" + commitValue + ", targetValue=" + + targetValue + ", stretchGoal=" + stretchGoal + ", unit=" + unit + '}'; } private KeyResultMetric(Builder builder) { super(builder); setBaseline(builder.baseline); + setCommitValue(builder.commitValue); + setTargetValue(builder.targetValue); setStretchGoal(builder.stretchGoal); setUnit(builder.unit); } public static class Builder extends KeyResult.Builder { private Double baseline; + private Double commitValue; + private Double targetValue; private Double stretchGoal; private Unit unit; @@ -105,6 +131,16 @@ public Builder withUnit(Unit unit) { return this; } + public Builder withCommitValue(Double commitValue) { + this.commitValue = commitValue; + return this; + } + + public Builder withTargetValue(Double targetValue) { + this.targetValue = targetValue; + return this; + } + @Override public KeyResult build() { return new KeyResultMetric(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 8f45505807..a538aeee02 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 @@ -23,8 +23,9 @@ public class Overview implements WriteableInterface { private String keyResultTitle; private String keyResultType; private Double baseline; + private Double commitValue; + private Double targetValue; private Double stretchGoal; - private String unit; private String commitZone; private String targetZone; private String stretchZone; @@ -54,8 +55,9 @@ private Overview(Builder builder) { keyResultTitle = builder.keyResultTitle; keyResultType = builder.keyResultType; baseline = builder.baseline; + commitValue = builder.commitValue; + targetValue = builder.targetValue; stretchGoal = builder.stretchGoal; - unit = builder.unit; commitZone = builder.commitZone; targetZone = builder.targetZone; stretchZone = builder.stretchZone; @@ -113,10 +115,6 @@ public Double getStretchGoal() { return stretchGoal; } - public String getUnit() { - return unit; - } - public String getCommitZone() { return commitZone; } @@ -145,6 +143,14 @@ public LocalDateTime getCheckInCreatedOn() { return checkInCreatedOn; } + public Double getCommitValue() { + return commitValue; + } + + public Double getTargetValue() { + return targetValue; + } + @Override public boolean isWriteable() { return writeable; @@ -165,15 +171,15 @@ public void setBacklogQuarter(boolean backlogQuarter) { @Override public String toString() { - return "Overview{" + "overviewId=" + overviewId + ", teamVersion='" + teamVersion + ", teamName='" + teamName - + '\'' + ", objectiveTitle='" + objectiveTitle + '\'' + ", objectiveState=" + objectiveState + return "Overview{" + "overviewId=" + overviewId + ", teamName='" + teamName + '\'' + ", teamVersion=" + + teamVersion + ", 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 + '\'' - + '}'; + + keyResultType + '\'' + ", baseline=" + baseline + ", commitValue=" + commitValue + ", targetValue=" + + targetValue + ", stretchGoal=" + stretchGoal + ", commitZone='" + commitZone + '\'' + ", targetZone='" + + targetZone + '\'' + ", stretchZone='" + stretchZone + '\'' + ", checkInValue=" + checkInValue + + ", checkInZone='" + checkInZone + '\'' + ", confidence=" + confidence + ", checkInCreatedOn=" + + checkInCreatedOn + ", writeable=" + writeable + ", backlogQuarter=" + backlogQuarter + '}'; } public static final class Builder { @@ -188,8 +194,9 @@ public static final class Builder { private String keyResultTitle; private String keyResultType; private Double baseline; + private Double commitValue; + private Double targetValue; private Double stretchGoal; - private String unit; private String commitZone; private String targetZone; private String stretchZone; @@ -260,13 +267,18 @@ public Builder withBaseline(Double baseline) { return this; } - public Builder withStretchGoal(Double stretchGoal) { - this.stretchGoal = stretchGoal; + public Builder withCommitValue(Double commitValue) { + this.commitValue = commitValue; + return this; + } + + public Builder withTargetValue(Double targetValue) { + this.targetValue = targetValue; return this; } - public Builder withUnit(String unit) { - this.unit = unit; + public Builder withStretchGoal(Double stretchGoal) { + this.stretchGoal = stretchGoal; return this; } 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 f23e94d906..caa1279de3 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 @@ -115,23 +115,23 @@ values (4, 1, '', '2023-07-25 08:17:51.309958', 66, 'Build a company culture tha 1, 2, 6, 'ONGOING', null, '2023-07-25 08:39:28.175703'); 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_id, commit_zone, target_zone, stretch_zone) -VALUES (10,1, 465, '', '2023-07-25 08:23:02.273028', 60, 'Im Durchschnitt soll die Lautstärke 60dB nicht überschreiten', 1, 5, 1, 'metric', '2023-07-25 08:23:02.273028', 1, null, null, null), - (8,1, 213425, '', '2023-07-25 08:19:44.351252', 80, 'High employee satisfaction scores (80%+) throughout the year.', 1, 4, 1, 'metric', '2023-07-25 08:19:44.351252', 1, null, null, null), - (7,1, 84, '', '2023-07-25 08:19:13.569300', 4, 'Monthly town halls between our people and leadership teams over the next four months.', 1, 4, 1, 'metric', '2023-07-25 08:19:13.569300', 1, null, null, null), - (6,1, 5, '', '2023-07-25 08:18:44.087674', 1, 'New structure that rewards funny guys and innovation before the end of Q1.', 1, 4, 1, 'metric', '2023-07-25 08:18:44.087674', 1, null, null, null), - (15,1, 0, 'asf', '2023-07-25 08:40:49.412684', 1, ' Lorem ipsum dolor sit amet', 1, 9, 1, 'metric', '2023-07-25 08:40:49.412684', 1, null, null, null), - (5,1, null, '', '2023-07-25 08:16:24.466383', null, 'Kundenzufriedenheitsumfrage soll mindestens einmal pro 2 Wochen durchgeführt werden. ', 1, 3, 1, 'ordinal', '2023-07-25 08:16:24.466383', 1, 'Fisch', 'Hai', 'MEG'), - (12,1, 0, '', '2023-07-25 08:28:45.110759', 80, 'Wir wollen bereits nach Q1 rund 80% des Redesigns vom OKR-Tool abgeschlossen haben. ', 1, 6, 1, 'metric', '2023-07-25 08:28:45.110759', 1, null, null, null), - (4,1, null, '', '2023-07-25 08:15:18.244565', null, 'Antwortzeit für Supportanfragen um 33% verkürzen.', 1, 3, 1, 'ordinal', '2023-07-25 08:15:18.244565', 1, 'Pflanze', 'Baum', 'Wald'), - (13,1, 43, '', '2023-07-25 08:29:57.709926', 15, 'Jedes Woche wird ein Kuchen vom BBT für Puzzle Organisiert', 1, 6, 1, 'metric', '2023-07-25 08:29:57.709926', 1, null, null, null), - (3,1, 0, '', '2023-07-25 08:14:31.964115', 25, 'Steigern der URS um 25%', 1, 3, 1, 'metric', '2023-07-25 08:14:31.964115', 1, null, null, null), - (14,1, 0, '', '2023-07-25 08:31:39.249943', 20, 'Das BBT hilft den Membern 20% mehr beim Töggelen', 1, 6, 1, 'metric', '2023-07-25 08:31:39.249943', 1, null, null, null), - (16,1, 200, '', '2023-07-25 08:41:24.592007', 1, 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.', 1, 9, 1, 'metric', '2023-07-25 08:41:24.592007', 1, null, null, null), - (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', 1, 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', 1, 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', 1, 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', 1, null, null, null); + objective_id, owner_id, key_result_type, created_on, unit_id, commit_zone, target_zone, stretch_zone, commit_value, target_value) +VALUES (10,1, 465, '', '2023-07-25 08:23:02.273028', 60, 'Im Durchschnitt soll die Lautstärke 60dB nicht überschreiten', 1, 5, 1, 'metric', '2023-07-25 08:23:02.273028', 1, null, null, null, 321, 186), + (8,1, 213425, '', '2023-07-25 08:19:44.351252', 80, 'High employee satisfaction scores (80%+) throughout the year.', 1, 4, 1, 'metric', '2023-07-25 08:19:44.351252', 1, null, null, null, 149303.5, 71550.5), + (7,1, 84, '', '2023-07-25 08:19:13.569300', 4, 'Monthly town halls between our people and leadership teams over the next four months.', 1, 4, 1, 'metric', '2023-07-25 08:19:13.569300', 1, null, null, null, 60.0, 28.0), + (6,1, 5, '', '2023-07-25 08:18:44.087674', 1, 'New structure that rewards funny guys and innovation before the end of Q1.', 1, 4, 1, 'metric', '2023-07-25 08:18:44.087674', 1, null, null, null, 3.8, 2.2), + (15,1, 0, 'asf', '2023-07-25 08:40:49.412684', 1, ' Lorem ipsum dolor sit amet', 1, 9, 1, 'metric', '2023-07-25 08:40:49.412684', 1, null, null, null, 0.3, 0.7), + (5,1, null, '', '2023-07-25 08:16:24.466383', null, 'Kundenzufriedenheitsumfrage soll mindestens einmal pro 2 Wochen durchgeführt werden. ', 1, 3, 1, 'ordinal', '2023-07-25 08:16:24.466383', 1, 'Fisch', 'Hai', 'MEG',null,null), + (12,1, 0, '', '2023-07-25 08:28:45.110759', 80, 'Wir wollen bereits nach Q1 rund 80% des Redesigns vom OKR-Tool abgeschlossen haben. ', 1, 6, 1, 'metric', '2023-07-25 08:28:45.110759', 1, null, null, null, 24.0, 56.0), + (4,1, null, '', '2023-07-25 08:15:18.244565', null, 'Antwortzeit für Supportanfragen um 33% verkürzen.', 1, 3, 1, 'ordinal', '2023-07-25 08:15:18.244565', 1, 'Pflanze', 'Baum', 'Wald',null,null), + (13,1, 43, '', '2023-07-25 08:29:57.709926', 15, 'Jedes Woche wird ein Kuchen vom BBT für Puzzle Organisiert', 1, 6, 1, 'metric', '2023-07-25 08:29:57.709926', 1, null, null, null, 35.6, 22.4), + (3,1, 0, '', '2023-07-25 08:14:31.964115', 25, 'Steigern der URS um 25%', 1, 3, 1, 'metric', '2023-07-25 08:14:31.964115', 1, null, null, null, 7.5, 17.5), + (14,1, 0, '', '2023-07-25 08:31:39.249943', 20, 'Das BBT hilft den Membern 20% mehr beim Töggelen', 1, 6, 1, 'metric', '2023-07-25 08:31:39.249943', 1, null, null, null, 6.0, 14.0), + (16,1, 200, '', '2023-07-25 08:41:24.592007', 1, 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.', 1, 9, 1, 'metric', '2023-07-25 08:41:24.592007', 1, null, null, null, 140.3, 80.7), + (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', 1, null, null, null, 35.3, 15.7), + (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', 1, null, null, null, 6000000.08, 14000000.08), + (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', 1, null, null, null, 94.0, 86.0), + (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', 1, null, null, null, 0.3, 0.7); 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 5f560db8ba..921dade574 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 @@ -107,6 +107,8 @@ create table if not exists key_result commit_zone varchar(1024), target_zone varchar(1024), stretch_zone varchar(1024), + commit_value DOUBLE PRECISION, + target_value DOUBLE PRECISION, primary key (id), constraint fk4ba6rgbr8mrkc8vvyqd5il4v9 foreign key (created_by_id) references person, @@ -187,7 +189,8 @@ SELECT TQ.TEAM_ID AS "TEAM_ID", COALESCE(KR.ID, -1) AS "KEY_RESULT_ID", KR.TITLE AS "KEY_RESULT_TITLE", KR.KEY_RESULT_TYPE AS "KEY_RESULT_TYPE", - U.unit_name as "UNIT", + KR.COMMIT_VALUE, + KR.TARGET_VALUE, KR.BASELINE, KR.STRETCH_GOAL, KR.COMMIT_ZONE, @@ -203,7 +206,6 @@ FROM (SELECT T.ID AS TEAM_ID, T.VERSION AS TEAM_VERSION, T.NAME, Q.ID AS QUARTER QUARTER Q) TQ LEFT JOIN OBJECTIVE O ON TQ.TEAM_ID = O.TEAM_ID AND TQ.QUARTER_ID = O.QUARTER_ID LEFT JOIN KEY_RESULT KR ON O.ID = KR.OBJECTIVE_ID - LEFT JOIN unit U ON U.ID = KR.unit_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); diff --git a/backend/src/main/resources/db/migration/V3_6_1__addMetricGoalsToKeyResult.sql b/backend/src/main/resources/db/migration/V3_6_1__addMetricGoalsToKeyResult.sql new file mode 100644 index 0000000000..8c9abcc11f --- /dev/null +++ b/backend/src/main/resources/db/migration/V3_6_1__addMetricGoalsToKeyResult.sql @@ -0,0 +1,9 @@ +ALTER TABLE key_result + ADD COLUMN IF NOT EXISTS commit_value DOUBLE PRECISION, + ADD COLUMN IF NOT EXISTS target_value DOUBLE PRECISION; + +UPDATE key_result +SET commit_value = ROUND(((COALESCE(stretch_goal, 0) - COALESCE(baseline, 0)) * 0.3 + COALESCE(baseline, 0))::numeric, 2), + target_value = ROUND(((COALESCE(stretch_goal, 0) - COALESCE(baseline, 0)) * 0.7 + COALESCE(baseline, 0))::numeric, 2) +WHERE commit_value IS NULL + AND target_value IS NULL; diff --git a/backend/src/main/resources/db/migration/V3_6_2__addMetricKeyResultGoalsToOverview.sql b/backend/src/main/resources/db/migration/V3_6_2__addMetricKeyResultGoalsToOverview.sql new file mode 100644 index 0000000000..fccf23fb8c --- /dev/null +++ b/backend/src/main/resources/db/migration/V3_6_2__addMetricKeyResultGoalsToOverview.sql @@ -0,0 +1,34 @@ +DROP VIEW IF EXISTS OVERVIEW RESTRICT ; +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", + coalesce(kr.id, -1) AS "key_result_id", + kr.title AS "key_result_title", + kr.key_result_type AS "key_result_type", + kr.baseline, + kr.commit_value, + kr.target_value, + 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/controller/OverviewControllerIT.java b/backend/src/test/java/ch/puzzle/okr/controller/OverviewControllerIT.java index 4f7acfe7d3..2611586cd2 100644 --- a/backend/src/test/java/ch/puzzle/okr/controller/OverviewControllerIT.java +++ b/backend/src/test/java/ch/puzzle/okr/controller/OverviewControllerIT.java @@ -63,7 +63,6 @@ class OverviewControllerIT { .withQuarterLabel(QUARTER_LABEL) .withKeyResultTitle(DESCRIPTION) .withKeyResultType(KEY_RESULT_TYPE_METRIC) - .withUnit(CHF) .withBaseline(5.0) .withStretchGoal(20.0) .withCheckInValue(15.0) @@ -80,7 +79,6 @@ class OverviewControllerIT { .withQuarterLabel(QUARTER_LABEL) .withKeyResultTitle(DESCRIPTION) .withKeyResultType(KEY_RESULT_TYPE_METRIC) - .withUnit(EUR) .withBaseline(5.0) .withStretchGoal(20.0) .withCheckInValue(15.0) @@ -98,7 +96,6 @@ class OverviewControllerIT { .withQuarterLabel(QUARTER_LABEL) .withKeyResultTitle(DESCRIPTION) .withKeyResultType(KEY_RESULT_TYPE_METRIC) - .withUnit(CHF) .withBaseline(5.0) .withStretchGoal(20.0) .withCheckInValue(15.0) @@ -115,7 +112,6 @@ class OverviewControllerIT { .withQuarterLabel(QUARTER_LABEL) .withKeyResultTitle(DESCRIPTION) .withKeyResultType(KEY_RESULT_TYPE_METRIC) - .withUnit(FTE) .withBaseline(5.0) .withStretchGoal(20.0) .withCheckInValue(15.0) @@ -133,7 +129,6 @@ class OverviewControllerIT { .withQuarterLabel(QUARTER_LABEL) .withKeyResultTitle(DESCRIPTION) .withKeyResultType(KEY_RESULT_TYPE_METRIC) - .withUnit(EUR) .withBaseline(5.0) .withStretchGoal(20.0) .withCheckInValue(15.0) diff --git a/backend/src/test/java/ch/puzzle/okr/mapper/OverviewMapperTest.java b/backend/src/test/java/ch/puzzle/okr/mapper/OverviewMapperTest.java index c394e63f8e..51ee05e715 100644 --- a/backend/src/test/java/ch/puzzle/okr/mapper/OverviewMapperTest.java +++ b/backend/src/test/java/ch/puzzle/okr/mapper/OverviewMapperTest.java @@ -179,7 +179,6 @@ void shouldReturnOneElementWhenToDtoIsCalledWithMultipleObjectivesKeyResultsAndC .withKeyResultType(KEY_RESULT_TYPE_METRIC) .withBaseline(20.0) .withStretchGoal(37.0) - .withUnit("TCHF") .withCheckInValue(27.5) .build(), Overview.Builder diff --git a/backend/src/test/java/ch/puzzle/okr/mapper/keyresult/helper/TestDataConstants.java b/backend/src/test/java/ch/puzzle/okr/mapper/keyresult/helper/TestDataConstants.java index 50bf3bd6b8..657f8ab503 100644 --- a/backend/src/test/java/ch/puzzle/okr/mapper/keyresult/helper/TestDataConstants.java +++ b/backend/src/test/java/ch/puzzle/okr/mapper/keyresult/helper/TestDataConstants.java @@ -38,6 +38,8 @@ public class TestDataConstants { public static final String KEY_RESULT_TITLE = "keyresult title"; public static final String KEY_RESULT_DESCRIPTION = "keyresult description"; public static final double KEY_RESULT_BASELINE = 55D; + public static final double KEY_RESULT_COMMIT_GOAL = 62.5D; + public static final double KEY_RESULT_TARGET_GOAL = 72.5D; public static final double KEY_RESULT_STRETCH_GOAL = 80D; public static final Unit KEY_RESULT_UNIT = TestHelper.NUMBER_UNIT; diff --git a/backend/src/test/java/ch/puzzle/okr/mapper/keyresult/helper/TestDataDtoHelper.java b/backend/src/test/java/ch/puzzle/okr/mapper/keyresult/helper/TestDataDtoHelper.java index 4bf310acf0..34e64c164a 100644 --- a/backend/src/test/java/ch/puzzle/okr/mapper/keyresult/helper/TestDataDtoHelper.java +++ b/backend/src/test/java/ch/puzzle/okr/mapper/keyresult/helper/TestDataDtoHelper.java @@ -69,7 +69,9 @@ public static KeyResultLastCheckInOrdinalDto lastCheckInOrdinalDto() { KEY_RESULT_TYPE_METRIC, // KEY_RESULT_TITLE, // KEY_RESULT_DESCRIPTION, // - KEY_RESULT_BASELINE, // + KEY_RESULT_BASELINE, + KEY_RESULT_COMMIT_GOAL, + KEY_RESULT_TARGET_GOAL, // KEY_RESULT_STRETCH_GOAL, // KEY_RESULT_UNIT_DTO, // ownerDto, // diff --git a/backend/src/test/java/ch/puzzle/okr/service/business/KeyResultBusinessServiceIT.java b/backend/src/test/java/ch/puzzle/okr/service/business/KeyResultBusinessServiceIT.java index b2c14d4e8c..a6ff808db5 100644 --- a/backend/src/test/java/ch/puzzle/okr/service/business/KeyResultBusinessServiceIT.java +++ b/backend/src/test/java/ch/puzzle/okr/service/business/KeyResultBusinessServiceIT.java @@ -64,8 +64,10 @@ class KeyResultBusinessServiceIT { private KeyResult createKeyResultMetric(Long id) { return KeyResultMetric.Builder .builder() - .withBaseline(3.0) - .withStretchGoal(5.0) + .withBaseline(0.0) + .withStretchGoal(10.0) + .withTargetValue(7.0) + .withCommitValue(3.0) .withUnit(this.UNIT) .withId(id) .withTitle("Title") diff --git a/backend/src/test/java/ch/puzzle/okr/test/KeyResultTestHelpers.java b/backend/src/test/java/ch/puzzle/okr/test/KeyResultTestHelpers.java index b0525cfae0..1eca51c1dc 100644 --- a/backend/src/test/java/ch/puzzle/okr/test/KeyResultTestHelpers.java +++ b/backend/src/test/java/ch/puzzle/okr/test/KeyResultTestHelpers.java @@ -28,7 +28,7 @@ public class KeyResultTestHelpers { public static final Number OBJECTIVE_ID = 1; public static final Number LAST_CHECK_IN_ID = 1; public static final Double BASELINE_VALUE = 1.0; - public static final Double STRETCH_GOAL_VALUE = 5.0; + public static final Double STRETCH_GOAL_VALUE = 10.0; public static final String TARGET_ZONE = "Ein Baum"; public static final String STRETCH_ZONE = "Ein Wald"; public static final String QUARTER_LABEL = "GJ 22/23-Q4"; @@ -384,7 +384,9 @@ public class KeyResultTestHelpers { TITLE, DESCRIPTION, 1.0, - 5.0, + 3.0, + 7.0, + 10.0, KEY_RESULT_UNIT_DTO, keyResultUserDto, keyResultObjectiveDto, diff --git a/frontend/cypress/e2e/scoring.cy.ts b/frontend/cypress/e2e/scoring.cy.ts index 48374da764..84330c3078 100644 --- a/frontend/cypress/e2e/scoring.cy.ts +++ b/frontend/cypress/e2e/scoring.cy.ts @@ -1,5 +1,5 @@ import * as users from '../fixtures/users.json'; -import { getPercentageMetric, getPercentageOrdinal } from 'cypress/support/helper/scoringSupport'; +import { CheckInValue, getPercentageMetric, getPercentageOrdinal } from 'cypress/support/helper/scoringSupport'; import CyOverviewPage from '../support/helper/dom-helper/pages/overviewPage'; import KeyResultDetailPage from '../support/helper/dom-helper/pages/keyResultDetailPage'; import { UNIT_PERCENT } from '../../src/app/shared/test-data'; @@ -15,21 +15,41 @@ describe('okr scoring', () => { }); [ - [0, + [ + 0, + 30, + 70, 100, - 10], - [0, + 10 + ], + [ + 0, + 30, + 70, 100, - 31], - [0, + 31 + ], + [ + 0, + 30, + 70, 100, - 71], - [0, + 71 + ], + [ + 0, + 30, + 70, 100, - 100] - ].forEach(([baseline, + 100 + ] + ].forEach(([ + baseline, + commitValue, + targetValue, stretchGoal, - value]) => { + value + ]) => { it('should display correct value on scoring component after creating metric check-in', () => { setupMetricKr( @@ -37,7 +57,15 @@ describe('okr scoring', () => { ); const percentage = getPercentageMetric(baseline, stretchGoal, value); - cy.validateScoring(false, percentage); + const keyResult: CheckInValue = { + baseline: baseline, + commitValue: commitValue, + targetValue: targetValue, + stretchGoal: stretchGoal, + lastCheckIn: value + }; + + cy.validateScoringMetric(false, keyResult, percentage); cy.get('.key-result-detail-attribute-show') .contains('Aktuell') .parent() @@ -46,27 +74,47 @@ describe('okr scoring', () => { .and('not.equal', 'rgb(186, 56, 56)'); keyResultDetailPage.close(); - cy.validateScoring(true, percentage); + cy.validateScoringMetric(true, keyResult, percentage); overviewPage .getKeyResultByName(`Metric kr with check-in value ${value}`) .not(':contains(*[class="scoring-error-badge"])'); + deleteKeyResult(`Metric kr with check-in value ${value}`, keyResultDetailPage); }); }); - [[0, + [[ + 0, + 30, + 70, 100, - -1], - [200, + -1 + ], + [ + 200, + 170, + 130, 100, - 250]].forEach(([baseline, + 250 + ]].forEach(([ + baseline, + commitValue, + targetValue, stretchGoal, - value]) => { + value + ]) => { it('show indicator that value is negative', () => { setupMetricKr( `Check indicator with value ${value}`, baseline, stretchGoal, value ); - cy.validateScoring(false, 0); + const keyResult: CheckInValue = { + baseline: baseline, + commitValue: commitValue, + targetValue: targetValue, + stretchGoal: stretchGoal, + lastCheckIn: value + }; + cy.validateScoringMetric(false, keyResult, 0); cy.get('.key-result-detail-attribute-show') .contains('Aktuell') .parent() @@ -75,10 +123,10 @@ describe('okr scoring', () => { .and('equal', 'rgb(186, 56, 56)'); keyResultDetailPage.close(); - cy.validateScoring(true, 0); overviewPage.getKeyResultByName(`Check indicator with value ${value}`) .get('.scoring-error-badge'); + deleteKeyResult(`Check indicator with value ${value}`, keyResultDetailPage); }); }); @@ -106,13 +154,25 @@ describe('okr scoring', () => { .fillCheckInInitiatives('Testmassnahmen') .submit(); const percentage = getPercentageOrdinal(zoneName); - cy.validateScoring(false, percentage); + cy.validateScoringOrdinal(false, percentage); keyResultDetailPage.close(); - cy.validateScoring(true, percentage); + cy.validateScoringOrdinal(true, percentage); + deleteKeyResult('Ordinal scoring keyresult', keyResultDetailPage); }); }); }); +function deleteKeyResult(keyResultName: string, keyResultDetailPage: KeyResultDetailPage) { + keyResultDetailPage + .visit(keyResultName) + .editKeyResult() + .deleteKeyResult() + .checkForContentOnDialog('Key Result löschen') + .checkForContentOnDialog('Möchtest du dieses Key Result wirklich löschen? Zugehörige Check-ins werden dadurch ebenfalls gelöscht!') + .submit(); + keyResultDetailPage.close(); +} + function setupMetricKr( name: string, baseline: number, stretchGoal: number, value: number ) { diff --git a/frontend/cypress/e2e/tab.cy.ts b/frontend/cypress/e2e/tab.cy.ts index c7a86c9f66..d8cd0122b3 100644 --- a/frontend/cypress/e2e/tab.cy.ts +++ b/frontend/cypress/e2e/tab.cy.ts @@ -173,6 +173,7 @@ describe('tabbing workflows', () => { tabAndCheck('stretch-goal'); tabAndCheck('owner-input'); tabAndCheck('description-input'); + tabAndCheck('manage-units'); tabAndCheck('action-input'); tabAndCheck('add-action-plan-line', 'Weitere Action hinzufügen'); tabAndCheck('ordinal-tab', 'Ordinal'); diff --git a/frontend/cypress/support/commands.ts b/frontend/cypress/support/commands.ts index 8877963ee4..5a0dada483 100644 --- a/frontend/cypress/support/commands.ts +++ b/frontend/cypress/support/commands.ts @@ -1,4 +1,4 @@ -import { validateScoring } from './helper/scoringSupport'; +import { CheckInValue, validateScoringMetric, validateScoringOrdinal } from './helper/scoringSupport'; import { keyCodeDefinitions } from 'cypress-real-events/keyCodeDefinitions'; import { pressUntilContains, doUntilSelector } from './helper/utils'; import Chainable = Cypress.Chainable; @@ -71,8 +71,12 @@ Cypress.Commands.add('getZone', (zone: string, onOverview: boolean) => { return (onOverview ? cy.focused() : cy.getByTestId('side-panel')).findByTestId(zone); }); -Cypress.Commands.add('validateScoring', (isOverview: boolean, percentage: number) => { - validateScoring(isOverview, percentage); +Cypress.Commands.add('validateScoringMetric', (isOverview: boolean, keyResult: CheckInValue, percentage: number) => { + validateScoringMetric(isOverview, keyResult, percentage); +}); + +Cypress.Commands.add('validateScoringOrdinal', (isOverview: boolean, percentage: number) => { + validateScoringOrdinal(isOverview, percentage); }); Cypress.Commands.add('buttonShouldBePrimary', (buttonId: string) => { diff --git a/frontend/cypress/support/component.ts b/frontend/cypress/support/component.ts index caf1fad3da..ea753e1a85 100644 --- a/frontend/cypress/support/component.ts +++ b/frontend/cypress/support/component.ts @@ -1,5 +1,6 @@ import './commands'; import { keyCodeDefinitions } from 'cypress-real-events/keyCodeDefinitions'; +import { CheckInValue } from './helper/scoringSupport'; declare global { export namespace Cypress { @@ -15,7 +16,8 @@ declare global { tabForwardUntil(selector: string, limit?: number): void; tabBackwardUntil(selector: string, limit?: number): void; getZone(zone: string, onOverview: boolean): Chainable; - validateScoring(isOverview: boolean, percentage: number): Chainable; + validateScoringMetric(isOverview: boolean, keyResult: CheckInValue, percentage: number): Chainable; + validateScoringOrdinal(isOverview: boolean, percentage: number): Chainable; } } } diff --git a/frontend/cypress/support/helper/scoringSupport.ts b/frontend/cypress/support/helper/scoringSupport.ts index cfa2c75aca..b8bcba4a16 100644 --- a/frontend/cypress/support/helper/scoringSupport.ts +++ b/frontend/cypress/support/helper/scoringSupport.ts @@ -6,9 +6,31 @@ interface ScoringValue { targetPercent: number; } -export function validateScoring(isOverview: boolean, percentage: number) { - const rgbCode = colorFromPercentage(percentage); - const scoringValue = scoringValueFromPercentage(percentage); +export interface CheckInValue { + baseline: number; + commitValue: number; + targetValue: number; + stretchGoal: number; + lastCheckIn: number; +} +export function validateScoringMetric(isOverview: boolean, keyResult: CheckInValue, percentage: number) { + const rgbCode = colorFromPercentageMetric(keyResult); + const scoringValue = scoringValueFromPercentageMetric(keyResult, percentage); + + if (percentage >= 100) { + cy.getZone('stretch', isOverview) + .should('have.attr', 'src') + .should('include', 'star-filled-icon.svg'); + } + + validateScoring( + isOverview, rgbCode, scoringValue, percentage + ); +} + +export function validateScoringOrdinal(isOverview: boolean, percentage: number) { + const rgbCode = colorFromPercentageOrdinal(percentage); + const scoringValue = scoringValueFromPercentageOrdinal(percentage); if (percentage >= 100) { cy.getZone('stretch', isOverview) @@ -16,6 +38,14 @@ export function validateScoring(isOverview: boolean, percentage: number) { .should('include', 'star-filled-icon.svg'); } + validateScoring( + isOverview, rgbCode, scoringValue, percentage + ); +} + +export function validateScoring( + isOverview: boolean, rgbCode: string, scoringValue: ScoringValue, percentage: number +) { validateScoringWidth('fail', scoringValue.failPercent, isOverview); validateScoringWidth('commit', scoringValue.commitPercent, isOverview); validateScoringWidth('target', scoringValue.targetPercent, isOverview); @@ -85,7 +115,7 @@ function checkVisibilityOfScoringComponent(isOverview: boolean, displayProperty: .should('equal', displayProperty); } -function colorFromPercentage(percentage: number) { +function colorFromPercentageOrdinal(percentage: number) { switch (true) { case percentage >= 100: return 'rgba(0, 0, 0, 0)'; @@ -98,7 +128,7 @@ function colorFromPercentage(percentage: number) { } } -function scoringValueFromPercentage(percentage: number): ScoringValue { +function scoringValueFromPercentageOrdinal(percentage: number): ScoringValue { switch (true) { case percentage >= 100: return { @@ -126,3 +156,43 @@ function scoringValueFromPercentage(percentage: number): ScoringValue { }; } } + +function colorFromPercentageMetric(keyResult: CheckInValue) { + if (keyResult.lastCheckIn < keyResult.commitValue) { + return 'rgb(186, 56, 56)'; + } else if (keyResult.lastCheckIn < keyResult.targetValue) { + return 'rgb(255, 214, 0)'; + } else if (keyResult.lastCheckIn < keyResult.stretchGoal) { + return 'rgb(30, 138, 41)'; + } else if (keyResult.lastCheckIn >= keyResult.stretchGoal) { + return 'rgba(0, 0, 0, 0)'; + } +} + +function scoringValueFromPercentageMetric(keyResult: CheckInValue, percentage: number): ScoringValue { + if (keyResult.lastCheckIn < keyResult.commitValue) { + return { + failPercent: percentage * (100 / 30), + commitPercent: -1, + targetPercent: -1 + }; + } else if (keyResult.lastCheckIn < keyResult.targetValue) { + return { + failPercent: 100, + commitPercent: (percentage - 30) * (100 / 40), + targetPercent: -1 + }; + } else if (keyResult.lastCheckIn < keyResult.stretchGoal) { + return { + failPercent: 100, + commitPercent: 100, + targetPercent: (percentage - 70) * (100 / 30) + }; + } else if (keyResult.lastCheckIn >= keyResult.stretchGoal) { + return { + failPercent: 0, + commitPercent: 0, + targetPercent: 0 + }; + } +} diff --git a/frontend/src/app/components/key-result-form/key-result-form.component.spec.ts b/frontend/src/app/components/key-result-form/key-result-form.component.spec.ts index 3214dba519..a8dd410ff5 100644 --- a/frontend/src/app/components/key-result-form/key-result-form.component.spec.ts +++ b/frontend/src/app/components/key-result-form/key-result-form.component.spec.ts @@ -157,6 +157,8 @@ describe('KeyResultFormComponent', () => { lastCheckIn: null, actionList: [], stretchGoal: 25, + commitValue: 9.6, + targetValue: 18.4, unit: UNIT_CHF, createdOn: new Date(), modifiedOn: new Date(), diff --git a/frontend/src/app/components/key-result-form/key-result-form.component.ts b/frontend/src/app/components/key-result-form/key-result-form.component.ts index e74692809f..888f07e203 100644 --- a/frontend/src/app/components/key-result-form/key-result-form.component.ts +++ b/frontend/src/app/components/key-result-form/key-result-form.component.ts @@ -6,7 +6,6 @@ import { KeyResultMetric } from '../../shared/types/model/key-result-metric'; import { KeyResultOrdinal } from '../../shared/types/model/key-result-ordinal'; import { filter, map, Observable, of, startWith, switchMap } from 'rxjs'; import { UserService } from '../../services/user.service'; -import { ActionService } from '../../services/action.service'; import { actionListToItemList, formInputCheck } from '../../shared/common'; import { initFormGroupFromItem, Item } from '../action-plan/action-plan.component'; @@ -29,7 +28,7 @@ export class KeyResultFormComponent implements OnInit, AfterContentInit { @Input() keyResult?: KeyResult; - constructor(public userService: UserService, public actionService: ActionService) { + constructor(public userService: UserService) { } ngOnInit(): void { diff --git a/frontend/src/app/components/key-result-type/key-result-type.component.html b/frontend/src/app/components/key-result-type/key-result-type.component.html index 7655bc3dc1..8142a63694 100644 --- a/frontend/src/app/components/key-result-type/key-result-type.component.html +++ b/frontend/src/app/components/key-result-type/key-result-type.component.html @@ -133,11 +133,11 @@ - + diff --git a/frontend/src/app/components/key-result-type/key-result-type.component.spec.ts b/frontend/src/app/components/key-result-type/key-result-type.component.spec.ts index 4b82dedd3a..937724ad25 100644 --- a/frontend/src/app/components/key-result-type/key-result-type.component.spec.ts +++ b/frontend/src/app/components/key-result-type/key-result-type.component.spec.ts @@ -214,19 +214,19 @@ describe('KeyResultTypeComponent', () => { }); it.each([[{ baseline: 0, - targetGoal: 0, + targetValue: 0, stretchGoal: 5 }, KeyResultMetricField.BASELINE, - KeyResultMetricField.TARGET_GOAL], + KeyResultMetricField.TARGET_VALUE], [{ baseline: 0, - targetGoal: 0, + targetValue: 0, stretchGoal: 5 }, KeyResultMetricField.STRETCH_GOAL, - KeyResultMetricField.TARGET_GOAL], + KeyResultMetricField.TARGET_VALUE], [{ baseline: 0, - targetGoal: 0, + targetValue: 0, stretchGoal: 5 }, - KeyResultMetricField.TARGET_GOAL, + KeyResultMetricField.TARGET_VALUE, KeyResultMetricField.STRETCH_GOAL]])('Should call calculateValueForField with the correct enum', (values: MetricValue, changed: KeyResultMetricField, result: KeyResultMetricField) => { const spyInstance = jest.spyOn(component, 'calculateValueForField'); @@ -237,92 +237,109 @@ describe('KeyResultTypeComponent', () => { it.each([ [{ baseline: NaN, - targetGoal: 8.5, + targetValue: 8.5, stretchGoal: 10 }, KeyResultMetricField.BASELINE, - { baseline: 5 }], + { baseline: 5, + commitValue: 6.5 }], [{ baseline: NaN, - targetGoal: 6.5, + targetValue: 6.5, stretchGoal: 5 }, KeyResultMetricField.BASELINE, - { baseline: 10 }], + { baseline: 10, + commitValue: 8.5 }], [{ baseline: NaN, - targetGoal: -8.5, + targetValue: -8.5, stretchGoal: -10 }, KeyResultMetricField.BASELINE, - { baseline: -5 }], + { baseline: -5, + commitValue: -6.5 }], [{ baseline: NaN, - targetGoal: -6.5, + targetValue: -6.5, stretchGoal: -5 }, KeyResultMetricField.BASELINE, - { baseline: -10 }], + { baseline: -10, + commitValue: -8.5 }], [{ baseline: NaN, - targetGoal: 2, + targetValue: 2, stretchGoal: 5 }, KeyResultMetricField.BASELINE, - { baseline: -5 }], + { baseline: -5, + commitValue: -2 }], [{ baseline: NaN, - targetGoal: 5, + targetValue: 5, stretchGoal: 10 }, KeyResultMetricField.BASELINE, - { baseline: -6.67 }], + { baseline: -6.67, + commitValue: -1.67 }], [{ baseline: 5, - targetGoal: NaN, + targetValue: NaN, stretchGoal: 10 }, - KeyResultMetricField.TARGET_GOAL, - { targetGoal: 8.5 }], + KeyResultMetricField.TARGET_VALUE, + { targetValue: 8.5, + commitValue: 6.5 }], [{ baseline: 10, - targetGoal: NaN, + targetValue: NaN, stretchGoal: 5 }, - KeyResultMetricField.TARGET_GOAL, - { targetGoal: 6.5 }], + KeyResultMetricField.TARGET_VALUE, + { targetValue: 6.5, + commitValue: 8.5 }], [{ baseline: -5, - targetGoal: NaN, + targetValue: NaN, stretchGoal: -10 }, - KeyResultMetricField.TARGET_GOAL, - { targetGoal: -8.5 }], + KeyResultMetricField.TARGET_VALUE, + { targetValue: -8.5, + commitValue: -6.5 }], [{ baseline: -10, - targetGoal: NaN, + targetValue: NaN, stretchGoal: -5 }, - KeyResultMetricField.TARGET_GOAL, - { targetGoal: -6.5 }], + KeyResultMetricField.TARGET_VALUE, + { targetValue: -6.5, + commitValue: -8.5 }], [{ baseline: -5, - targetGoal: NaN, + targetValue: NaN, stretchGoal: 5 }, - KeyResultMetricField.TARGET_GOAL, - { targetGoal: 2 }], + KeyResultMetricField.TARGET_VALUE, + { targetValue: 2, + commitValue: -2 }], [{ baseline: 0, - targetGoal: NaN, + targetValue: NaN, stretchGoal: 5.34 }, - KeyResultMetricField.TARGET_GOAL, - { targetGoal: 3.74 }], + KeyResultMetricField.TARGET_VALUE, + { targetValue: 3.74, + commitValue: 1.6 }], [{ baseline: 5, - targetGoal: 8.5, + targetValue: 8.5, stretchGoal: NaN }, KeyResultMetricField.STRETCH_GOAL, - { stretchGoal: 10 }], + { stretchGoal: 10, + commitValue: 6.5 }], [{ baseline: 10, - targetGoal: 6.5, + targetValue: 6.5, stretchGoal: NaN }, KeyResultMetricField.STRETCH_GOAL, - { stretchGoal: 5 }], + { stretchGoal: 5, + commitValue: 8.5 }], [{ baseline: -5, - targetGoal: -8.5, + targetValue: -8.5, stretchGoal: NaN }, KeyResultMetricField.STRETCH_GOAL, - { stretchGoal: -10 }], + { stretchGoal: -10, + commitValue: -6.5 }], [{ baseline: -10, - targetGoal: -6.5, + targetValue: -6.5, stretchGoal: NaN }, KeyResultMetricField.STRETCH_GOAL, - { stretchGoal: -5 }], + { stretchGoal: -5, + commitValue: -8.5 }], [{ baseline: -5, - targetGoal: 2, + targetValue: 2, stretchGoal: NaN }, KeyResultMetricField.STRETCH_GOAL, - { stretchGoal: 5 }] + { stretchGoal: 5, + commitValue: -2 }] ])('calculateValueForField should calculate correct', (values: MetricValue, targetField: KeyResultMetricField, result: any) => { expect(component.calculateValueForField(values, targetField)) .toEqual(result); @@ -338,7 +355,7 @@ describe('KeyResultTypeComponent', () => { .toEqual(0); expect(getValueOfForm(component.keyResultForm, ['metric', - 'targetGoal'])) + 'targetValue'])) .toEqual(0); expect(getValueOfForm(component.keyResultForm, ['metric', diff --git a/frontend/src/app/components/key-result-type/key-result-type.component.ts b/frontend/src/app/components/key-result-type/key-result-type.component.ts index 2643320340..dde26c1fb0 100644 --- a/frontend/src/app/components/key-result-type/key-result-type.component.ts +++ b/frontend/src/app/components/key-result-type/key-result-type.component.ts @@ -13,14 +13,14 @@ import { ManageUnitsDialogComponent } from '../manage-units-dialog/manage-units- export enum KeyResultMetricField { BASELINE, - TARGET_GOAL, + TARGET_VALUE, STRETCH_GOAL, NONE } export interface MetricValue { baseline: number; - targetGoal: number; + targetValue: number; stretchGoal: number; } @@ -91,7 +91,8 @@ export class KeyResultTypeComponent implements AfterContentInit { formGroupValue = { ...formGroupValue, ...fieldValue }; return { baseline: +formGroupValue.baseline, - targetGoal: +formGroupValue.targetGoal, + targetValue: +formGroupValue.targetValue, + commitValue: +formGroupValue.commitValue, stretchGoal: +formGroupValue.stretchGoal } as MetricValue; } @@ -99,11 +100,12 @@ export class KeyResultTypeComponent implements AfterContentInit { switch (changed) { case KeyResultMetricField.STRETCH_GOAL: case KeyResultMetricField.BASELINE: { - return this.calculateValueForField(values, KeyResultMetricField.TARGET_GOAL); + return this.calculateValueForField(values, KeyResultMetricField.TARGET_VALUE); } - case KeyResultMetricField.TARGET_GOAL: { + case KeyResultMetricField.TARGET_VALUE: { return this.calculateValueForField(values, KeyResultMetricField.STRETCH_GOAL); } + case KeyResultMetricField.NONE: { return {}; } @@ -115,15 +117,20 @@ export class KeyResultTypeComponent implements AfterContentInit { switch (field) { case KeyResultMetricField.BASELINE: { - return { baseline: roundToTwoDecimals((values.targetGoal - values.stretchGoal * 0.7) / 0.3) }; + const baseline = roundToTwoDecimals((values.targetValue - values.stretchGoal * 0.7) / 0.3); + return { baseline: baseline, + commitValue: roundToTwoDecimals((values.stretchGoal - baseline) * 0.3 + baseline) }; } - case KeyResultMetricField.TARGET_GOAL: { - return { targetGoal: roundToTwoDecimals((values.stretchGoal - values.baseline) * 0.7 + values.baseline) }; + case KeyResultMetricField.TARGET_VALUE: { + return { targetValue: roundToTwoDecimals((values.stretchGoal - values.baseline) * 0.7 + values.baseline), + commitValue: roundToTwoDecimals((values.stretchGoal - values.baseline) * 0.3 + values.baseline) }; } case KeyResultMetricField.STRETCH_GOAL: { - return { stretchGoal: roundToTwoDecimals((values.targetGoal - values.baseline) / 0.7 + values.baseline) }; + const stretchGoal = roundToTwoDecimals((values.targetValue - values.baseline) / 0.7 + values.baseline); + return { stretchGoal: stretchGoal, + commitValue: roundToTwoDecimals((stretchGoal - values.baseline) * 0.3 + values.baseline) }; } case KeyResultMetricField.NONE: { @@ -149,8 +156,8 @@ export class KeyResultTypeComponent implements AfterContentInit { formGroupMetric?.get('baseline')?.valueChanges .subscribe((value: any) => this.updateMetricValue(KeyResultMetricField.BASELINE, { baseline: value })); - formGroupMetric?.get('targetGoal')?.valueChanges - .subscribe((value) => this.updateMetricValue(KeyResultMetricField.TARGET_GOAL, { targetGoal: value })); + formGroupMetric?.get('targetValue')?.valueChanges + .subscribe((value) => this.updateMetricValue(KeyResultMetricField.TARGET_VALUE, { targetValue: value })); formGroupMetric?.get('stretchGoal')?.valueChanges .subscribe((value) => this.updateMetricValue(KeyResultMetricField.STRETCH_GOAL, { stretchGoal: value })); diff --git a/frontend/src/app/shared/constant-library.ts b/frontend/src/app/shared/constant-library.ts index 6ada396ef3..b744cb9868 100644 --- a/frontend/src/app/shared/constant-library.ts +++ b/frontend/src/app/shared/constant-library.ts @@ -104,7 +104,10 @@ export function getKeyResultForm(): FormGroup { baseline: new FormControl(0, [Validators.required, numberValidator(), Validators.maxLength(20)]), - targetGoal: new FormControl(0, [Validators.required, + commitValue: new FormControl(0, [Validators.required, + numberValidator(), + Validators.maxLength(20)]), + targetValue: new FormControl(0, [Validators.required, numberValidator(), Validators.maxLength(20)]), stretchGoal: new FormControl(0, [Validators.required, diff --git a/frontend/src/app/shared/custom/scoring/scoring.component.ts b/frontend/src/app/shared/custom/scoring/scoring.component.ts index 550d62d2aa..7f4058f7a3 100644 --- a/frontend/src/app/shared/custom/scoring/scoring.component.ts +++ b/frontend/src/app/shared/custom/scoring/scoring.component.ts @@ -12,7 +12,7 @@ import { } from '@angular/core'; import { Zone } from '../../types/enums/zone'; import { KeyResultMetricMin } from '../../types/model/key-result-metric-min'; -import { Observable, of } from 'rxjs'; +import { Observable } from 'rxjs'; import { calculateCurrentPercentage } from '../../common'; import { KeyResultOrdinalMin } from '../../types/model/key-result-ordinal-min'; import { CheckInOrdinalMin } from '../../types/model/check-in-ordinal-min'; @@ -111,22 +111,22 @@ export class ScoringComponent implements OnInit, AfterViewInit, OnChanges { calculatePercentageMetric() { if (this.keyResult.lastCheckIn !== null) { - const keyResultMetric: KeyResultMetricMin = this.castToMetric(); - const percentage = calculateCurrentPercentage(keyResultMetric); - this.labelPercentage = of(percentage); - if (percentage < 30) { + const keyResult = this.keyResult as KeyResultMetricMin; + const lastCheckIn = keyResult.lastCheckIn?.value!; + + if (lastCheckIn < keyResult.commitValue) { this.stretched = false; - this.failPercent = 100 / 30 * percentage; - } else if (percentage < 70) { + this.failPercent = (lastCheckIn - keyResult.baseline) / (keyResult.commitValue - keyResult.baseline) * 100; + } else if (lastCheckIn < keyResult.targetValue) { this.stretched = false; this.failPercent = 100; - this.commitPercent = 100 / 40 * (percentage - 30); - } else if (percentage < 100) { + this.commitPercent = (lastCheckIn - keyResult.commitValue) / (keyResult.targetValue - keyResult.commitValue) * 100; + } else if (lastCheckIn < keyResult.stretchGoal) { this.stretched = false; this.failPercent = 100; this.commitPercent = 100; - this.targetPercent = 100 / 30 * (percentage - 70); - } else if (percentage >= 100) { + this.targetPercent = (lastCheckIn - keyResult.targetValue) / (keyResult.stretchGoal - keyResult.targetValue) * 100; + } else if (lastCheckIn >= keyResult.stretchGoal) { this.stretched = true; } } diff --git a/frontend/src/app/shared/test-data.ts b/frontend/src/app/shared/test-data.ts index 83120392c2..d486478407 100644 --- a/frontend/src/app/shared/test-data.ts +++ b/frontend/src/app/shared/test-data.ts @@ -220,6 +220,8 @@ export const keyResultMetricMin: KeyResultMetricMin = { unit: UNIT_PERCENT, baseline: 10.0, stretchGoal: 25.0, + commitValue: 14.5, + targetValue: 20.5, lastCheckIn: checkInMetric, type: 'keyResult' } as KeyResultMetricMin; @@ -232,6 +234,8 @@ export const keyResultMetricMinScoring: KeyResultMetricMin = { unit: UNIT_PERCENT, baseline: 25.0, stretchGoal: 75.0, + commitValue: 40, + targetValue: 60, lastCheckIn: { id: 800, version: 1, @@ -255,6 +259,8 @@ export const keyResultMetricMinScoringInversion: KeyResultMetricMin = { unit: UNIT_PERCENT, baseline: 50.0, stretchGoal: 0.0, + commitValue: 35, + targetValue: 15, lastCheckIn: { id: 800, version: 1, @@ -579,6 +585,8 @@ export const keyResultMetric: KeyResultMetric = { description: 'Puzzle ITC erledigt die IT-Aufträge für 100% aller Unternehmen.', baseline: 30, stretchGoal: 100, + commitValue: 51, + targetValue: 79, unit: UNIT_PERCENT, owner: users[3], keyResultType: 'metric', @@ -616,6 +624,8 @@ export const keyResultActions: KeyResultMetric = { description: 'Puzzle ITC hat schöne Büros, wo es alles hat.', baseline: 10, stretchGoal: 30, + commitValue: 16, + targetValue: 24, unit: UNIT_PERCENT, owner: users[3], keyResultType: 'metric', diff --git a/frontend/src/app/shared/types/DTOs/key-result-metric-dto.ts b/frontend/src/app/shared/types/DTOs/key-result-metric-dto.ts index 4bbcda4ff4..b71960161c 100644 --- a/frontend/src/app/shared/types/DTOs/key-result-metric-dto.ts +++ b/frontend/src/app/shared/types/DTOs/key-result-metric-dto.ts @@ -3,5 +3,7 @@ import { KeyResultDto } from './key-result-dto'; export interface KeyResultMetricDto extends KeyResultDto { unit: string | null; baseline: number | null; + commitValue: number | null; + targetValue: number | null; stretchGoal: number | null; } diff --git a/frontend/src/app/shared/types/model/key-result-metric-min.ts b/frontend/src/app/shared/types/model/key-result-metric-min.ts index 8cd09bea6d..04f60fd6c1 100644 --- a/frontend/src/app/shared/types/model/key-result-metric-min.ts +++ b/frontend/src/app/shared/types/model/key-result-metric-min.ts @@ -4,6 +4,8 @@ import { Unit } from '../enums/unit'; export interface KeyResultMetricMin extends KeyResultMin { baseline: number; + commitValue: number; + targetValue: number; stretchGoal: number; unit: Unit; lastCheckIn: CheckInMetricMin | null; diff --git a/frontend/src/app/shared/types/model/key-result-metric.ts b/frontend/src/app/shared/types/model/key-result-metric.ts index 924a9f9896..10eb587fa8 100644 --- a/frontend/src/app/shared/types/model/key-result-metric.ts +++ b/frontend/src/app/shared/types/model/key-result-metric.ts @@ -5,6 +5,8 @@ import { Unit } from '../enums/unit'; export interface KeyResultMetric extends KeyResult { lastCheckIn: CheckInMetric | null; baseline: number; + commitValue: number; + targetValue: number; stretchGoal: number; unit: Unit; }