Skip to content

Commit 456dd1c

Browse files
authored
#10 Add an ability to choose and apply the resulting estimate. (#11)
- Add a label/badge which shows current story estimate; - Add a session deletion if it doesn't have estimates; - Add logging support;
1 parent f952d9a commit 456dd1c

19 files changed

+584
-71
lines changed

pom.xml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,12 @@
144144
<version>${lombok.version}</version>
145145
<scope>provided</scope>
146146
</dependency>
147+
<dependency>
148+
<groupId>org.slf4j</groupId>
149+
<artifactId>slf4j-api</artifactId>
150+
<version>${slfj.version}</version>
151+
<scope>provided</scope>
152+
</dependency>
147153
</dependencies>
148154
<build>
149155
<plugins>
@@ -222,5 +228,6 @@
222228
<hsql.version>1.8.0.10</hsql.version>
223229
<slf4j-simple.version>1.6.2</slf4j-simple.version>
224230
<ao.version>3.2.9</ao.version>
231+
<slfj.version>1.7.30</slfj.version>
225232
</properties>
226233
</project>

src/main/java/com/aprey/jira/plugin/openpoker/Estimate.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,4 +28,5 @@
2828
public class Estimate {
2929
private final ApplicationUser estimator;
3030
private final String grade;
31+
private final int gradeId;
3132
}

src/main/java/com/aprey/jira/plugin/openpoker/EstimationGrade.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,6 @@ public interface EstimationGrade {
2424
int getId();
2525

2626
String getValue();
27+
28+
boolean isApplicable();
2729
}

src/main/java/com/aprey/jira/plugin/openpoker/FibonacciNumber.java

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -28,29 +28,31 @@
2828
import static java.util.stream.Collectors.toMap;
2929

3030
public enum FibonacciNumber implements EstimationGrade {
31-
ONE(1, "1"),
32-
TWO(2, "2"),
33-
THREE(3, "3"),
34-
FIVE(4, "5"),
35-
EIGHT(5, "8"),
36-
THIRTEEN(6, "13"),
37-
TWENTY_ONE(7, "21"),
38-
INFINITE(8, "Infinite"),
39-
COFFEE(9, "Coffee"),
40-
QUESTION(10, "?");
31+
ONE(1, "1", true),
32+
TWO(2, "2", true),
33+
THREE(3, "3", true),
34+
FIVE(4, "5", true),
35+
EIGHT(5, "8", true),
36+
THIRTEEN(6, "13", true),
37+
TWENTY_ONE(7, "21", true),
38+
INFINITE(8, "Infinite", false),
39+
COFFEE(9, "Coffee", false),
40+
QUESTION(10, "?", false);
4141

4242
private final int id;
4343
private final String value;
44+
private final boolean applicable;
4445

4546
private static final Map<Integer, FibonacciNumber> ID_TO_INSTANCE_MAP = Stream.of(FibonacciNumber.values())
4647
.collect(toMap(
4748
FibonacciNumber::getId,
4849
Function.identity())
49-
);
50+
);
5051

51-
FibonacciNumber(int id, String value) {
52+
FibonacciNumber(int id, String value, boolean applicable) {
5253
this.id = id;
5354
this.value = value;
55+
this.applicable = applicable;
5456
}
5557

5658
@Override
@@ -63,6 +65,11 @@ public String getValue() {
6365
return value;
6466
}
6567

68+
@Override
69+
public boolean isApplicable() {
70+
return applicable;
71+
}
72+
6673
public static EstimationGrade findById(int id) {
6774
return ID_TO_INSTANCE_MAP.get(id);
6875
}
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
/*
2+
* Copyright (C) 2021 Andriy Preizner
3+
*
4+
* This file is a part of Open Poker jira plugin
5+
*
6+
* This program is free software: you can redistribute it and/or modify
7+
* it under the terms of the GNU General Public License as published by
8+
* the Free Software Foundation, either version 3 of the License, or
9+
* (at your option) any later version.
10+
*
11+
* This program is distributed in the hope that it will be useful,
12+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+
* GNU General Public License for more details.
15+
*
16+
* You should have received a copy of the GNU General Public License
17+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
18+
*/
19+
20+
package com.aprey.jira.plugin.openpoker;
21+
22+
import com.atlassian.jira.bc.issue.IssueService;
23+
import com.atlassian.jira.issue.CustomFieldManager;
24+
import com.atlassian.jira.issue.Issue;
25+
import com.atlassian.jira.issue.IssueInputParameters;
26+
import com.atlassian.jira.issue.IssueManager;
27+
import com.atlassian.jira.issue.fields.CustomField;
28+
import com.atlassian.jira.user.ApplicationUser;
29+
import com.atlassian.plugin.spring.scanner.annotation.component.Scanned;
30+
import com.atlassian.plugin.spring.scanner.annotation.imports.ComponentImport;
31+
import java.text.DecimalFormat;
32+
import java.util.Optional;
33+
import javax.inject.Inject;
34+
import javax.inject.Named;
35+
import lombok.extern.slf4j.Slf4j;
36+
37+
@Slf4j
38+
@Scanned
39+
@Named
40+
public class IssueServiceFacade {
41+
42+
private static final String STORY_POINT_FILED_NAME = "Story Points";
43+
44+
@ComponentImport
45+
private final CustomFieldManager customFieldManager;
46+
@ComponentImport
47+
private final IssueService issueService;
48+
@ComponentImport
49+
private final IssueManager issueManager;
50+
51+
@Inject
52+
public IssueServiceFacade(CustomFieldManager customFieldManager, IssueService issueService,
53+
IssueManager issueManager) {
54+
this.customFieldManager = customFieldManager;
55+
this.issueService = issueService;
56+
this.issueManager = issueManager;
57+
}
58+
59+
public void applyEstimate(int estimate, ApplicationUser user, String issueId) {
60+
Issue issue = issueManager.getIssueObject(issueId);
61+
Optional<CustomField> field = getField(issue);
62+
if (!field.isPresent()) {
63+
log.error("'Story Point' custom field does not exist");
64+
return;
65+
}
66+
IssueInputParameters inputParameters = buildInputParams(field.get().getIdAsLong(), estimate);
67+
IssueService.UpdateValidationResult updateValidationResult = issueService
68+
.validateUpdate(user, issue.getId(), inputParameters);
69+
70+
if (!updateValidationResult.isValid()) {
71+
log.error("Validation for updating story point is failed, errors: {}", updateValidationResult
72+
.getErrorCollection().toString());
73+
return;
74+
}
75+
76+
IssueService.IssueResult updateResult = issueService.update(user, updateValidationResult);
77+
if (!updateResult.isValid()) {
78+
log.error("ISSUE has NOT been updated. Errors: {}\n" + updateResult.getErrorCollection().toString());
79+
return;
80+
}
81+
82+
log.info("The issue {} has been updated with a new story point value", issueId);
83+
}
84+
85+
private IssueInputParameters buildInputParams(long fieldId, int estimate) {
86+
IssueInputParameters issueInputParameters = issueService.newIssueInputParameters();
87+
issueInputParameters.addCustomFieldValue(fieldId, String.valueOf(estimate));
88+
issueInputParameters.setSkipScreenCheck(true);
89+
issueInputParameters.setRetainExistingValuesWhenParameterNotProvided(true, true);
90+
91+
return issueInputParameters;
92+
}
93+
94+
private Optional<CustomField> getField(Issue issue) {
95+
return customFieldManager.getCustomFieldObjects(issue)
96+
.stream()
97+
.filter(f -> f.getFieldName().equals(STORY_POINT_FILED_NAME))
98+
.findFirst();
99+
}
100+
101+
public Optional<String> getStoryPoints(String issueId) {
102+
Issue issue = issueManager.getIssueObject(issueId);
103+
Optional<Object> value = customFieldManager.getCustomFieldObjects(issue)
104+
.stream()
105+
.filter(f -> f.getFieldName().equals(STORY_POINT_FILED_NAME))
106+
.map(f -> f.getValue(issue))
107+
.findFirst();
108+
if (!value.isPresent()) {
109+
return Optional.empty();
110+
}
111+
112+
return format(value.get());
113+
}
114+
115+
private Optional<String> format(Object value) {
116+
if (value instanceof Double) {
117+
DecimalFormat format = new DecimalFormat("0.#");
118+
return Optional.of(format.format(value));
119+
}
120+
121+
if (value instanceof Integer) {
122+
return Optional.of(value.toString());
123+
}
124+
125+
return Optional.empty();
126+
}
127+
}

src/main/java/com/aprey/jira/plugin/openpoker/PokerSession.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,5 +31,6 @@ public class PokerSession {
3131
private final String issueId;
3232
private final SessionStatus status;
3333
private final long completionDate;
34+
private final List<EstimationGrade> estimationGrades;
3435
private final List<Estimate> estimates;
3536
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
/*
2+
* Copyright (C) 2021 Andriy Preizner
3+
*
4+
* This file is a part of Open Poker jira plugin
5+
*
6+
* This program is free software: you can redistribute it and/or modify
7+
* it under the terms of the GNU General Public License as published by
8+
* the Free Software Foundation, either version 3 of the License, or
9+
* (at your option) any later version.
10+
*
11+
* This program is distributed in the hope that it will be useful,
12+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+
* GNU General Public License for more details.
15+
*
16+
* You should have received a copy of the GNU General Public License
17+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
18+
*/
19+
20+
package com.aprey.jira.plugin.openpoker.api;
21+
22+
import com.aprey.jira.plugin.openpoker.IssueServiceFacade;
23+
import com.aprey.jira.plugin.openpoker.persistence.PersistenceService;
24+
import com.atlassian.jira.user.ApplicationUser;
25+
import com.atlassian.jira.user.util.UserManager;
26+
import com.atlassian.plugin.spring.scanner.annotation.component.Scanned;
27+
import com.atlassian.plugin.spring.scanner.annotation.imports.ComponentImport;
28+
import java.util.Optional;
29+
import javax.inject.Inject;
30+
import javax.inject.Named;
31+
import javax.servlet.http.HttpServletRequest;
32+
import lombok.extern.slf4j.Slf4j;
33+
34+
@Named
35+
@Scanned
36+
@Slf4j
37+
public class ApplyVoteProcessor implements ActionProcessor {
38+
private static final String ESTIMATE_FILED = "resultingEstimate";
39+
private static final String USER_ID_FIELD = "userId";
40+
41+
@ComponentImport
42+
private final UserManager userManager;
43+
private final IssueServiceFacade issueServiceFacade;
44+
45+
@Inject
46+
public ApplyVoteProcessor(UserManager userManager,
47+
IssueServiceFacade issueServiceFacade) {
48+
this.userManager = userManager;
49+
this.issueServiceFacade = issueServiceFacade;
50+
}
51+
52+
@Override
53+
public void process(PersistenceService persistenceService, HttpServletRequest request, String issueId) {
54+
final long userId = Long.parseLong(request.getParameter(USER_ID_FIELD));
55+
final int estimate = Integer.parseInt(request.getParameter(ESTIMATE_FILED));
56+
Optional<ApplicationUser> applicationUser = userManager.getUserById(userId);
57+
if (!applicationUser.isPresent()) {
58+
log.error("Application user is not found by {} id", userId);
59+
return;
60+
}
61+
62+
issueServiceFacade.applyEstimate(estimate, applicationUser.get(), issueId);
63+
persistenceService.deleteSessions(issueId);
64+
}
65+
}

src/main/java/com/aprey/jira/plugin/openpoker/api/EstimateDTO.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,4 +27,5 @@
2727
public class EstimateDTO {
2828
private final UserDTO estimator;
2929
private final String grade;
30+
private final int gradeId;
3031
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/*
2+
* Copyright (C) 2021 Andriy Preizner
3+
*
4+
* This file is a part of Open Poker jira plugin
5+
*
6+
* This program is free software: you can redistribute it and/or modify
7+
* it under the terms of the GNU General Public License as published by
8+
* the Free Software Foundation, either version 3 of the License, or
9+
* (at your option) any later version.
10+
*
11+
* This program is distributed in the hope that it will be useful,
12+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+
* GNU General Public License for more details.
15+
*
16+
* You should have received a copy of the GNU General Public License
17+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
18+
*/
19+
20+
package com.aprey.jira.plugin.openpoker.api;
21+
22+
import com.aprey.jira.plugin.openpoker.EstimationGrade;
23+
import java.util.List;
24+
import java.util.Map;
25+
import java.util.function.Function;
26+
import lombok.Builder;
27+
import lombok.Value;
28+
29+
import static java.util.stream.Collectors.counting;
30+
import static java.util.stream.Collectors.groupingBy;
31+
32+
@Value
33+
@Builder
34+
public class EstimationViewDTO {
35+
private final List<EstimateDTO> estimates;
36+
private final List<EstimationGrade> gradesToChoose;
37+
private final List<EstimationGrade> gradesToApply;
38+
private final boolean alreadyVoted;
39+
private final boolean applicableGrades;
40+
41+
public int getAverageEstimateId() {
42+
return estimates.stream().map(EstimateDTO::getGradeId)
43+
.collect(groupingBy(Function.identity(), counting()))
44+
.entrySet()
45+
.stream()
46+
.max(Map.Entry.comparingByValue())
47+
.map(Map.Entry::getKey).orElse(1);
48+
}
49+
}

0 commit comments

Comments
 (0)