feat: Replace milestone with iteration (OD-1961)

This commit is contained in:
Robin Shen 2024-06-18 22:01:13 +08:00
parent 088ca2829b
commit cb03254f97
138 changed files with 1642 additions and 1513 deletions

View File

@ -60,7 +60,7 @@ files in certain branches.
Move tasks manually in Kanban, or define rules to move them automatically
when related work is committed/tested/released/deployed.
[**See It In Action**](https://code.onedev.io/onedev/server/~boards/State?milestone=4.2.0&backlog=true)
[**See It In Action**](https://code.onedev.io/onedev/server/~boards/State?iteration=4.2.0&backlog=true)
![issue board](./doc/images/issue-board.png)

View File

@ -253,7 +253,7 @@ public class CoreModule extends AbstractPluginModule {
bind(IssueChangeManager.class).to(DefaultIssueChangeManager.class);
bind(IssueVoteManager.class).to(DefaultIssueVoteManager.class);
bind(IssueWorkManager.class).to(DefaultIssueWorkManager.class);
bind(MilestoneManager.class).to(DefaultMilestoneManager.class);
bind(IterationManager.class).to(DefaultIterationManager.class);
bind(IssueCommentManager.class).to(DefaultIssueCommentManager.class);
bind(IssueQueryPersonalizationManager.class).to(DefaultIssueQueryPersonalizationManager.class);
bind(PullRequestQueryPersonalizationManager.class).to(DefaultPullRequestQueryPersonalizationManager.class);

View File

@ -7,6 +7,6 @@ import java.lang.annotation.Target;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MilestoneChoice {
public @interface IterationChoice {
String value() default "";
}

View File

@ -2075,5 +2075,53 @@ public class BuildSpec implements Serializable, Validatable {
}
}
});
}
}
private void migrateParamSpecs(VersionedYamlDoc doc, Stack<Integer> versions,
Consumer<SequenceNode> paramSpecMigrator) {
for (NodeTuple specTuple: doc.getValue()) {
String specObjectKey = ((ScalarNode)specTuple.getKeyNode()).getValue();
if (specObjectKey.equals("jobs")) {
SequenceNode jobsNode = (SequenceNode) specTuple.getValueNode();
for (Node jobsNodeItem: jobsNode.getValue()) {
MappingNode jobNode = (MappingNode) jobsNodeItem;
for (NodeTuple jobTuple: jobNode.getValue()) {
String jobTupleKey = ((ScalarNode)jobTuple.getKeyNode()).getValue();
if (jobTupleKey.equals("paramSpecs"))
paramSpecMigrator.accept((SequenceNode) jobTuple.getValueNode());
}
}
} else if (specObjectKey.equals("stepTemplates")) {
SequenceNode stepTemplatesNode = (SequenceNode) specTuple.getValueNode();
for (Node stepTemplatesNodeItem: stepTemplatesNode.getValue()) {
MappingNode stepTemplateNode = (MappingNode) stepTemplatesNodeItem;
for (NodeTuple stepTemplateTuple: stepTemplateNode.getValue()) {
String stepTemplateTupleKey = ((ScalarNode)stepTemplateTuple.getKeyNode()).getValue();
if (stepTemplateTupleKey.equals("paramSpecs"))
paramSpecMigrator.accept((SequenceNode)stepTemplateTuple.getValueNode());
}
}
}
}
}
private void migrate34(VersionedYamlDoc doc, Stack<Integer> versions) {
migrateSteps(doc, versions, stepsNode -> {
for (var itStepNode = stepsNode.getValue().iterator(); itStepNode.hasNext();) {
MappingNode stepNode = (MappingNode) itStepNode.next();
var stepType = stepNode.getTag().getValue();
if (stepType.equals("!CloseMilestoneStep"))
stepNode.setTag(new Tag("!CloseIterationStep"));
}
});
migrateParamSpecs(doc, versions, paramSpecsNode -> {
for (var itParamSpecNode = paramSpecsNode.getValue().iterator(); itParamSpecNode.hasNext();) {
MappingNode paramSpecNode = (MappingNode) itParamSpecNode.next();
var paramSpecType = paramSpecNode.getTag().getValue();
if (paramSpecType.equals("!MilestoneChoiceParam"))
paramSpecNode.setTag(new Tag("!IterationChoiceParam"));
}
});
}
}

View File

@ -3,33 +3,33 @@ package io.onedev.server.buildspec.param.spec;
import java.util.List;
import java.util.Map;
import io.onedev.server.buildspecmodel.inputspec.MilestoneChoiceInput;
import io.onedev.server.buildspecmodel.inputspec.IterationChoiceInput;
import io.onedev.server.annotation.Editable;
@Editable(order=1110, name=ParamSpec.MILESTONE)
public class MilestoneChoiceParam extends ParamSpec {
@Editable(order=1110, name=ParamSpec.ITERATION)
public class IterationChoiceParam extends ParamSpec {
private static final long serialVersionUID = 1L;
@Override
public String getPropertyDef(Map<String, Integer> indexes) {
return MilestoneChoiceInput.getPropertyDef(this, indexes);
return IterationChoiceInput.getPropertyDef(this, indexes);
}
@Override
public Object convertToObject(List<String> strings) {
return MilestoneChoiceInput.convertToObject(this, strings);
return IterationChoiceInput.convertToObject(this, strings);
}
@Override
public List<String> convertToStrings(Object value) {
return MilestoneChoiceInput.convertToStrings(this, value);
return IterationChoiceInput.convertToStrings(this, value);
}
@Override
public long getOrdinal(String fieldValue) {
if (fieldValue != null)
return MilestoneChoiceInput.getOrdinal(fieldValue);
return IterationChoiceInput.getOrdinal(fieldValue);
else
return super.getOrdinal(fieldValue);
}

View File

@ -9,8 +9,8 @@ import io.onedev.server.annotation.Editable;
import io.onedev.server.annotation.Interpolative;
import io.onedev.server.buildspec.BuildSpec;
import io.onedev.server.entitymanager.BuildManager;
import io.onedev.server.entitymanager.MilestoneManager;
import io.onedev.server.model.Milestone;
import io.onedev.server.entitymanager.IterationManager;
import io.onedev.server.model.Iteration;
import io.onedev.server.model.Project;
import io.onedev.server.persistence.TransactionManager;
@ -19,24 +19,24 @@ import java.io.File;
import java.util.List;
import java.util.stream.Collectors;
@Editable(name="Close Milestone", order=400)
public class CloseMilestoneStep extends ServerSideStep {
@Editable(name="Close Iteration", order=400)
public class CloseIterationStep extends ServerSideStep {
private static final long serialVersionUID = 1L;
private String milestoneName;
private String iterationName;
private String accessTokenSecret;
@Editable(order=1000, description="Specify name of the milestone")
@Editable(order=1000, description="Specify name of the iteration")
@Interpolative(variableSuggester="suggestVariables")
@NotEmpty
public String getMilestoneName() {
return milestoneName;
public String getIterationName() {
return iterationName;
}
public void setMilestoneName(String milestoneName) {
this.milestoneName = milestoneName;
public void setIterationName(String iterationName) {
this.iterationName = iterationName;
}
@SuppressWarnings("unused")
@ -66,19 +66,19 @@ public class CloseMilestoneStep extends ServerSideStep {
return OneDev.getInstance(TransactionManager.class).call(() -> {
var build = OneDev.getInstance(BuildManager.class).load(buildId);
Project project = build.getProject();
String milestoneName = getMilestoneName();
MilestoneManager milestoneManager = OneDev.getInstance(MilestoneManager.class);
Milestone milestone = milestoneManager.findInHierarchy(project, milestoneName);
if (milestone != null) {
if (build.canCloseMilestone(getAccessTokenSecret())) {
milestone.setClosed(true);
milestoneManager.createOrUpdate(milestone);
String iterationName = getIterationName();
IterationManager iterationManager = OneDev.getInstance(IterationManager.class);
Iteration iteration = iterationManager.findInHierarchy(project, iterationName);
if (iteration != null) {
if (build.canCloseIteration(getAccessTokenSecret())) {
iteration.setClosed(true);
iterationManager.createOrUpdate(iteration);
} else {
logger.error("This build is not authorized to close milestone '" + milestoneName + "'");
logger.error("This build is not authorized to close iteration '" + iterationName + "'");
return new ServerStepResult(false);
}
} else {
logger.warning("Unable to find milestone '" + milestoneName + "' to close. Ignored.");
logger.warning("Unable to find iteration '" + iterationName + "' to close. Ignored.");
}
return new ServerStepResult(true);
});

View File

@ -54,7 +54,7 @@ public abstract class InputSpec implements Serializable {
public static final String ISSUE = "Issue";
public static final String MILESTONE = "Milestone";
public static final String ITERATION = "Iteration";
public static final String BUILD = "Build";

View File

@ -7,10 +7,10 @@ import java.util.Map;
import javax.validation.ValidationException;
import edu.emory.mathcs.backport.java.util.Collections;
import io.onedev.server.model.Milestone;
import io.onedev.server.model.Iteration;
import io.onedev.server.model.Project;
public class MilestoneChoiceInput {
public class IterationChoiceInput {
public static String getPropertyDef(InputSpec inputSpec, Map<String, Integer> indexes) {
int index = indexes.get(inputSpec.getName());
@ -19,12 +19,12 @@ public class MilestoneChoiceInput {
inputSpec.appendCommonAnnotations(buffer, index);
if (!inputSpec.isAllowEmpty()) {
if (inputSpec.isAllowMultiple())
buffer.append(" @Size(min=1, message=\"At least one milestone needs to be selected\")\n");
buffer.append(" @Size(min=1, message=\"At least one iteration needs to be selected\")\n");
else
buffer.append(" @NotEmpty\n");
}
buffer.append(" @MilestoneChoice\n");
buffer.append(" @IterationChoice\n");
if (inputSpec.isAllowMultiple())
inputSpec.appendMethods(buffer, index, "List<String>", null, null);
@ -62,11 +62,11 @@ public class MilestoneChoiceInput {
int ordinal = -1;
Project project = Project.get();
if (project != null) {
List<Milestone> milestones = new ArrayList<>(project.getHierarchyMilestones());
Collections.sort(milestones);
for (Milestone milestone: milestones) {
List<Iteration> iterations = new ArrayList<>(project.getHierarchyIterations());
Collections.sort(iterations);
for (Iteration iteration: iterations) {
ordinal++;
if (milestone.getName().equals(fieldValue))
if (iteration.getName().equals(fieldValue))
break;
}
}

View File

@ -6443,6 +6443,107 @@ public class DataMigrator {
}
}
}
private void migrate167(File dataDir, Stack<Integer> versions) {
for (File file : dataDir.listFiles()) {
if (file.getName().startsWith("Milestones.xml")) {
File renamedFile = new File(dataDir, file.getName().replace("Milestones.xml", "Iterations.xml"));
try {
FileUtils.moveFile(file, renamedFile);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
for (File file : dataDir.listFiles()) {
if (file.getName().startsWith("Iterations.xml")) {
VersionedXmlDoc dom = VersionedXmlDoc.fromFile(file);
for (Element element : dom.getRootElement().elements())
element.setName("io.onedev.server.model.Iteration");
dom.writeToFile(file, false);
} else if (file.getName().startsWith("Settings.xml")) {
VersionedXmlDoc dom = VersionedXmlDoc.fromFile(file);
for (Element element : dom.getRootElement().elements()) {
String key = element.elementTextTrim("key");
if (key.equals("ISSUE")) {
for (var fieldSpecElement: element.element("value").element("fieldSpecs").elements()) {
if (fieldSpecElement.getName().contains("MilestoneChoiceField"))
fieldSpecElement.setName("io.onedev.server.model.support.issue.field.spec.IterationChoiceField");
}
}
}
dom.writeToFile(file, false);
} else if (file.getName().startsWith("IssueSchedules.xml")) {
VersionedXmlDoc dom = VersionedXmlDoc.fromFile(file);
for (Element element : dom.getRootElement().elements())
element.element("milestone").setName("iteration");
dom.writeToFile(file, false);
} else if (file.getName().startsWith("Dashboards.xml")) {
VersionedXmlDoc dom = VersionedXmlDoc.fromFile(file);
for (Node node : dom.selectNodes("//io.onedev.server.ee.dashboard.widgets.MilestoneListWidget")) {
if (node instanceof Element) {
Element element = (Element) node;
element.setName("io.onedev.server.ee.dashboard.widgets.IterationListWidget");
}
}
for (Node node : dom.selectNodes("//io.onedev.server.ee.dashboard.widgets.BurnDownChartWidget")) {
if (node instanceof Element) {
Element element = (Element) node;
var milestoneNameElement = element.element("milestoneName");
if (milestoneNameElement != null)
milestoneNameElement.setName("iterationName");
}
}
dom.writeToFile(file, false);
} else if (file.getName().startsWith("IssueFields.xml")) {
VersionedXmlDoc dom = VersionedXmlDoc.fromFile(file);
for (Element element : dom.getRootElement().elements()) {
var typeElement = element.element("type");
if (typeElement.getTextTrim().equals("Milestone"))
typeElement.setText("Iteration");
}
dom.writeToFile(file, false);
} else if (file.getName().startsWith("IssueChanges.xml")) {
VersionedXmlDoc dom = VersionedXmlDoc.fromFile(file);
for (Element element : dom.getRootElement().elements()) {
var dataElement = element.element("data");
if (dataElement.attributeValue("class").contains("IssueMilestoneAddData"))
dataElement.addAttribute("class", "io.onedev.server.model.support.issue.changedata.IssueIterationAddData");
else if (dataElement.attributeValue("class").contains("IssueMilestoneRemoveData"))
dataElement.addAttribute("class", "io.onedev.server.model.support.issue.changedata.IssueIterationRemoveData");
else if (dataElement.attributeValue("class").contains("IssueMilestoneChangeData"))
dataElement.addAttribute("class", "io.onedev.server.model.support.issue.changedata.IssueIterationChangeData");
var milestoneElement = dataElement.element("milestone");
if (milestoneElement != null)
milestoneElement.setName("iteration");
var oldMilestonesElement = dataElement.element("oldMilestones");
if (oldMilestonesElement != null)
oldMilestonesElement.setName("oldIterations");
var newMilestonesElement = dataElement.element("newMilestones");
if (newMilestonesElement != null)
newMilestonesElement.setName("newIterations");
}
dom.writeToFile(file, false);
}
}
for (File file : dataDir.listFiles()) {
try {
String content = FileUtils.readFileToString(file, UTF_8);
content = StringUtils.replace(content,
"\"Milestone\" is", "\"Iteration\" is");
content = StringUtils.replace(content,
"\"Milestone\" is", "\"Iteration\" is");
content = StringUtils.replace(content,
"\"Milestone\" is", "\"Iteration\" is");
content = StringUtils.replace(content,
"\"Milestone\" is", "\"Iteration\" is");
FileUtils.writeStringToFile(file, content, UTF_8);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}

View File

@ -3,7 +3,7 @@ package io.onedev.server.entitymanager;
import io.onedev.server.model.Issue;
import io.onedev.server.model.IssueChange;
import io.onedev.server.model.LinkSpec;
import io.onedev.server.model.Milestone;
import io.onedev.server.model.Iteration;
import io.onedev.server.persistence.dao.EntityManager;
import javax.annotation.Nullable;
@ -36,19 +36,19 @@ public interface IssueChangeManager extends EntityManager<IssueChange> {
void changeFields(Issue issue, Map<String, Object> fieldValues);
void changeMilestones(Issue issue, Collection<Milestone> milestones);
void changeIterations(Issue issue, Collection<Iteration> iterations);
void create(IssueChange change, @Nullable String note);
void addSchedule(Issue issue, Milestone milestone);
void addSchedule(Issue issue, Iteration iteration);
void removeSchedule(Issue issue, Milestone milestone);
void removeSchedule(Issue issue, Iteration iteration);
void changeState(Issue issue, String state, Map<String, Object> fieldValues,
Collection<String> removeFields, @Nullable String comment);
void batchUpdate(Iterator<? extends Issue> issues, @Nullable String state, @Nullable Boolean confidential,
@Nullable Collection<Milestone> milestone, Map<String, Object> fieldValues, @Nullable String comment);
@Nullable Collection<Iteration> iterations, Map<String, Object> fieldValues, @Nullable String comment);
List<IssueChange> queryAfter(Long projectId, Long afterChangeId, int count);

View File

@ -1,13 +1,13 @@
package io.onedev.server.entitymanager;
import io.onedev.server.model.Issue;
import io.onedev.server.model.Milestone;
import io.onedev.server.model.Iteration;
import io.onedev.server.model.Project;
import io.onedev.server.persistence.dao.EntityManager;
import io.onedev.server.search.entity.EntityQuery;
import io.onedev.server.search.entity.EntitySort;
import io.onedev.server.util.IssueTimes;
import io.onedev.server.util.MilestoneAndIssueState;
import io.onedev.server.util.IterationAndIssueState;
import io.onedev.server.util.ProjectIssueStats;
import io.onedev.server.util.ProjectScope;
import io.onedev.server.util.criteria.Criteria;
@ -81,13 +81,13 @@ public interface IssueManager extends EntityManager<Issue> {
void delete(Collection<Issue> issues, Project project);
Collection<MilestoneAndIssueState> queryMilestoneAndIssueStates(Project project, Collection<Milestone> milestones);
Collection<IterationAndIssueState> queryIterationAndIssueStates(Project project, Collection<Iteration> iterations);
List<ProjectIssueStats> queryStats(Collection<Project> projects);
Collection<Milestone> queryUsedMilestones(Project project);
Collection<Iteration> queryUsedIterations(Project project);
void clearSchedules(Project project, Collection<Milestone> milestones);
void clearSchedules(Project project, Collection<Iteration> iterations);
List<Issue> queryAfter(Long projectId, Long afterIssueId, int count);

View File

@ -4,12 +4,12 @@ import java.util.Collection;
import io.onedev.server.model.Issue;
import io.onedev.server.model.IssueSchedule;
import io.onedev.server.model.Milestone;
import io.onedev.server.model.Iteration;
import io.onedev.server.persistence.dao.EntityManager;
public interface IssueScheduleManager extends EntityManager<IssueSchedule> {
void syncMilestones(Issue issue, Collection<Milestone> milestones);
void syncIterations(Issue issue, Collection<Iteration> iterations);
void create(IssueSchedule schedule);
}

View File

@ -0,0 +1,20 @@
package io.onedev.server.entitymanager;
import io.onedev.server.model.Iteration;
import io.onedev.server.model.Project;
import io.onedev.server.persistence.dao.EntityManager;
import javax.annotation.Nullable;
public interface IterationManager extends EntityManager<Iteration> {
@Nullable
Iteration findInHierarchy(Project project, String name);
void delete(Iteration iteration);
Iteration findInHierarchy(String iterationFQN);
void createOrUpdate(Iteration iteration);
}

View File

@ -1,20 +0,0 @@
package io.onedev.server.entitymanager;
import io.onedev.server.model.Milestone;
import io.onedev.server.model.Project;
import io.onedev.server.persistence.dao.EntityManager;
import javax.annotation.Nullable;
public interface MilestoneManager extends EntityManager<Milestone> {
@Nullable
Milestone findInHierarchy(Project project, String name);
void delete(Milestone milestone);
Milestone findInHierarchy(String milestoneFQN);
void createOrUpdate(Milestone milestone);
}

View File

@ -243,25 +243,25 @@ public class DefaultIssueChangeManager extends BaseEntityManager<IssueChange>
@Transactional
@Override
public void addSchedule(Issue issue, Milestone milestone) {
issueScheduleManager.create(issue.addSchedule(milestone));
public void addSchedule(Issue issue, Iteration iteration) {
issueScheduleManager.create(issue.addSchedule(iteration));
IssueChange change = new IssueChange();
change.setIssue(issue);
change.setData(new IssueMilestoneAddData(milestone.getName()));
change.setData(new IssueIterationAddData(iteration.getName()));
change.setUser(SecurityUtils.getUser());
create(change, null);
}
@Transactional
@Override
public void removeSchedule(Issue issue, Milestone milestone) {
IssueSchedule schedule = issue.removeSchedule(milestone);
public void removeSchedule(Issue issue, Iteration iteration) {
IssueSchedule schedule = issue.removeSchedule(iteration);
if (schedule != null) {
issueScheduleManager.delete(schedule);
IssueChange change = new IssueChange();
change.setIssue(issue);
change.setData(new IssueMilestoneRemoveData(milestone.getName()));
change.setData(new IssueIterationRemoveData(iteration.getName()));
change.setUser(SecurityUtils.getUser());
create(change, null);
}
@ -307,12 +307,12 @@ public class DefaultIssueChangeManager extends BaseEntityManager<IssueChange>
@Transactional
@Override
public void batchUpdate(Iterator<? extends Issue> issues, @Nullable String state, @Nullable Boolean confidential,
@Nullable Collection<Milestone> milestones, Map<String, Object> fieldValues, @Nullable String comment) {
@Nullable Collection<Iteration> iterations, Map<String, Object> fieldValues, @Nullable String comment) {
while (issues.hasNext()) {
Issue issue = issues.next();
String prevState = issue.getState();
boolean prevConfidential = issue.isConfidential();
Collection<Milestone> prevMilestones = issue.getMilestones();
Collection<Iteration> prevIterations = issue.getIterations();
Map<String, Input> prevFields = issue.getFieldInputs();
if (state != null)
issue.setState(state);
@ -320,8 +320,8 @@ public class DefaultIssueChangeManager extends BaseEntityManager<IssueChange>
if (confidential != null)
issue.setConfidential(confidential);
if (milestones != null)
issueScheduleManager.syncMilestones(issue, milestones);
if (iterations != null)
issueScheduleManager.syncIterations(issue, iterations);
issue.setFieldValues(fieldValues);
issueFieldManager.saveFields(issue);
@ -329,18 +329,18 @@ public class DefaultIssueChangeManager extends BaseEntityManager<IssueChange>
if (!prevState.equals(issue.getState())
|| prevConfidential != issue.isConfidential()
|| !prevFields.equals(issue.getFieldInputs())
|| !new HashSet<>(prevMilestones).equals(new HashSet<>(issue.getMilestones()))) {
|| !new HashSet<>(prevIterations).equals(new HashSet<>(issue.getIterations()))) {
IssueChange change = new IssueChange();
change.setIssue(issue);
change.setUser(SecurityUtils.getUser());
List<Milestone> prevMilestoneList = new ArrayList<>(prevMilestones);
prevMilestoneList.sort(new Milestone.DatesAndStatusComparator());
List<Milestone> currentMilestoneList = new ArrayList<>(issue.getMilestones());
currentMilestoneList.sort(new Milestone.DatesAndStatusComparator());
List<Iteration> prevIterationList = new ArrayList<>(prevIterations);
prevIterationList.sort(new Iteration.DatesAndStatusComparator());
List<Iteration> currentIterationList = new ArrayList<>(issue.getIterations());
currentIterationList.sort(new Iteration.DatesAndStatusComparator());
change.setData(new IssueBatchUpdateData(prevState, issue.getState(),
prevConfidential, issue.isConfidential(), prevMilestoneList,
currentMilestoneList, prevFields, issue.getFieldInputs()));
prevConfidential, issue.isConfidential(), prevIterationList,
currentIterationList, prevFields, issue.getFieldInputs()));
create(change, comment);
}
}
@ -660,19 +660,19 @@ public class DefaultIssueChangeManager extends BaseEntityManager<IssueChange>
@Transactional
@Override
public void changeMilestones(Issue issue, Collection<Milestone> milestones) {
Collection<Milestone> prevMilestones = new HashSet<>(issue.getMilestones());
if (!prevMilestones.equals(new HashSet<>(milestones))) {
issueScheduleManager.syncMilestones(issue, milestones);
public void changeIterations(Issue issue, Collection<Iteration> iterations) {
Collection<Iteration> prevIterations = new HashSet<>(issue.getIterations());
if (!prevIterations.equals(new HashSet<>(iterations))) {
issueScheduleManager.syncIterations(issue, iterations);
IssueChange change = new IssueChange();
change.setIssue(issue);
change.setUser(SecurityUtils.getUser());
List<Milestone> prevMilestoneList = new ArrayList<>(prevMilestones);
prevMilestoneList.sort(new Milestone.DatesAndStatusComparator());
List<Milestone> currentMilestoneList = new ArrayList<>(milestones);
currentMilestoneList.sort(new Milestone.DatesAndStatusComparator());
change.setData(new IssueMilestoneChangeData(prevMilestoneList, currentMilestoneList));
List<Iteration> prevIterationList = new ArrayList<>(prevIterations);
prevIterationList.sort(new Iteration.DatesAndStatusComparator());
List<Iteration> currentIterationList = new ArrayList<>(iterations);
currentIterationList.sort(new Iteration.DatesAndStatusComparator());
change.setData(new IssueIterationChangeData(prevIterationList, currentIterationList));
create(change, null);
}

View File

@ -39,7 +39,7 @@ import io.onedev.server.search.entity.issue.IssueQueryUpdater;
import io.onedev.server.security.SecurityUtils;
import io.onedev.server.security.permission.AccessProject;
import io.onedev.server.util.IssueTimes;
import io.onedev.server.util.MilestoneAndIssueState;
import io.onedev.server.util.IterationAndIssueState;
import io.onedev.server.util.ProjectIssueStats;
import io.onedev.server.util.ProjectScope;
import io.onedev.server.util.criteria.Criteria;
@ -885,33 +885,33 @@ public class DefaultIssueManager extends BaseEntityManager<Issue> implements Iss
@Sessional
@Override
public Collection<MilestoneAndIssueState> queryMilestoneAndIssueStates(Project project, Collection<Milestone> milestones) {
public Collection<IterationAndIssueState> queryIterationAndIssueStates(Project project, Collection<Iteration> iterations) {
CriteriaBuilder builder = getSession().getCriteriaBuilder();
CriteriaQuery<MilestoneAndIssueState> criteriaQuery = builder.createQuery(MilestoneAndIssueState.class);
CriteriaQuery<IterationAndIssueState> criteriaQuery = builder.createQuery(IterationAndIssueState.class);
Root<IssueSchedule> root = criteriaQuery.from(IssueSchedule.class);
Join<Issue, Issue> issueJoin = root.join(IssueSchedule.PROP_ISSUE, JoinType.INNER);
criteriaQuery.multiselect(
root.get(IssueSchedule.PROP_MILESTONE).get(Milestone.PROP_ID),
root.get(IssueSchedule.PROP_ITERATION).get(Iteration.PROP_ID),
issueJoin.get(Issue.PROP_STATE));
List<Predicate> milestonePredicates = new ArrayList<>();
for (Milestone milestone: milestones)
milestonePredicates.add(builder.equal(root.get(IssueSchedule.PROP_MILESTONE), milestone));
List<Predicate> iterationPredicates = new ArrayList<>();
for (Iteration iteration: iterations)
iterationPredicates.add(builder.equal(root.get(IssueSchedule.PROP_ITERATION), iteration));
criteriaQuery.where(builder.and(
buildSubtreePredicate(builder, issueJoin.get(Issue.PROP_PROJECT), project),
builder.or(milestonePredicates.toArray(new Predicate[0]))));
builder.or(iterationPredicates.toArray(new Predicate[0]))));
return getSession().createQuery(criteriaQuery).getResultList();
}
@Sessional
@Override
public Collection<Milestone> queryUsedMilestones(Project project) {
public Collection<Iteration> queryUsedIterations(Project project) {
CriteriaBuilder builder = getSession().getCriteriaBuilder();
CriteriaQuery<Milestone> criteriaQuery = builder.createQuery(Milestone.class);
CriteriaQuery<Iteration> criteriaQuery = builder.createQuery(Iteration.class);
Root<IssueSchedule> root = criteriaQuery.from(IssueSchedule.class);
criteriaQuery.select(root.get(IssueSchedule.PROP_MILESTONE));
criteriaQuery.select(root.get(IssueSchedule.PROP_ITERATION));
Path<Project> projectPath = root
.join(IssueSchedule.PROP_ISSUE, JoinType.INNER)
@ -1044,8 +1044,8 @@ public class DefaultIssueManager extends BaseEntityManager<Issue> implements Iss
numberMapping.put(oldNumber, nextNumber);
for (IssueSchedule schedule: issue.getSchedules()) {
if (schedule.getMilestone() != null
&& !schedule.getMilestone().getProject().isSelfOrAncestorOf(targetProject)) {
if (schedule.getIteration() != null
&& !schedule.getIteration().getProject().isSelfOrAncestorOf(targetProject)) {
dao.remove(schedule);
}
}
@ -1086,21 +1086,21 @@ public class DefaultIssueManager extends BaseEntityManager<Issue> implements Iss
@Transactional
@Override
public void clearSchedules(Project project, Collection<Milestone> milestones) {
public void clearSchedules(Project project, Collection<Iteration> iterations) {
CriteriaBuilder builder = getSession().getCriteriaBuilder();
CriteriaQuery<IssueSchedule> criteriaQuery = builder.createQuery(IssueSchedule.class);
Root<IssueSchedule> root = criteriaQuery.from(IssueSchedule.class);
List<Predicate> milestonePredicates = new ArrayList<>();
for (Milestone milestone: milestones)
milestonePredicates.add(builder.equal(root.get(IssueSchedule.PROP_MILESTONE), milestone));
List<Predicate> iterationPredicates = new ArrayList<>();
for (Iteration iteration: iterations)
iterationPredicates.add(builder.equal(root.get(IssueSchedule.PROP_ITERATION), iteration));
Path<Project> projectPath = root
.join(IssueSchedule.PROP_ISSUE, JoinType.INNER)
.get(Issue.PROP_PROJECT);
criteriaQuery.where(builder.and(
buildSubtreePredicate(builder, projectPath, project),
builder.or(milestonePredicates.toArray(new Predicate[0]))));
builder.or(iterationPredicates.toArray(new Predicate[0]))));
for (IssueSchedule schedule: getSession().createQuery(criteriaQuery).getResultList())
dao.remove(schedule);

View File

@ -10,7 +10,7 @@ import com.google.common.base.Preconditions;
import io.onedev.server.entitymanager.IssueScheduleManager;
import io.onedev.server.model.Issue;
import io.onedev.server.model.IssueSchedule;
import io.onedev.server.model.Milestone;
import io.onedev.server.model.Iteration;
import io.onedev.server.persistence.annotation.Transactional;
import io.onedev.server.persistence.dao.BaseEntityManager;
import io.onedev.server.persistence.dao.Dao;
@ -26,19 +26,19 @@ public class DefaultIssueScheduleManager extends BaseEntityManager<IssueSchedule
@Transactional
@Override
public void syncMilestones(Issue issue, Collection<Milestone> milestones) {
public void syncIterations(Issue issue, Collection<Iteration> iterations) {
for (Iterator<IssueSchedule> it = issue.getSchedules().iterator(); it.hasNext();) {
IssueSchedule schedule = it.next();
if (!milestones.contains(schedule.getMilestone())) {
if (!iterations.contains(schedule.getIteration())) {
dao.remove(schedule);
it.remove();
}
}
for (Milestone milestone: milestones) {
if (!issue.getMilestones().contains(milestone)) {
for (Iteration iteration: iterations) {
if (!issue.getIterations().contains(iteration)) {
IssueSchedule schedule = new IssueSchedule();
schedule.setIssue(issue);
schedule.setMilestone(milestone);
schedule.setIteration(iteration);
issue.getSchedules().add(schedule);
dao.persist(schedule);
}

View File

@ -1,9 +1,9 @@
package io.onedev.server.entitymanager.impl;
import io.onedev.commons.utils.StringUtils;
import io.onedev.server.entitymanager.MilestoneManager;
import io.onedev.server.entitymanager.IterationManager;
import io.onedev.server.entitymanager.ProjectManager;
import io.onedev.server.model.Milestone;
import io.onedev.server.model.Iteration;
import io.onedev.server.model.Project;
import io.onedev.server.persistence.annotation.Sessional;
import io.onedev.server.persistence.annotation.Transactional;
@ -17,26 +17,26 @@ import javax.inject.Singleton;
import java.util.List;
@Singleton
public class DefaultMilestoneManager extends BaseEntityManager<Milestone> implements MilestoneManager {
public class DefaultIterationManager extends BaseEntityManager<Iteration> implements IterationManager {
private final ProjectManager projectManager;
@Inject
public DefaultMilestoneManager(Dao dao, ProjectManager projectManager) {
public DefaultIterationManager(Dao dao, ProjectManager projectManager) {
super(dao);
this.projectManager = projectManager;
}
@Sessional
@Override
public Milestone findInHierarchy(String milestoneFQN) {
String projectName = StringUtils.substringBefore(milestoneFQN, ":");
public Iteration findInHierarchy(String iterationFQN) {
String projectName = StringUtils.substringBefore(iterationFQN, ":");
Project project = projectManager.findByPath(projectName);
if (project != null) {
String milestoneName = StringUtils.substringAfter(milestoneFQN, ":");
EntityCriteria<Milestone> criteria = EntityCriteria.of(Milestone.class);
String iterationName = StringUtils.substringAfter(iterationFQN, ":");
EntityCriteria<Iteration> criteria = EntityCriteria.of(Iteration.class);
criteria.add(Restrictions.in("project", project.getSelfAndAncestors()));
criteria.add(Restrictions.eq("name", milestoneName));
criteria.add(Restrictions.eq("name", iterationName));
criteria.setCacheable(true);
return find(criteria);
} else {
@ -46,8 +46,8 @@ public class DefaultMilestoneManager extends BaseEntityManager<Milestone> implem
@Sessional
@Override
public Milestone findInHierarchy(Project project, String name) {
EntityCriteria<Milestone> criteria = EntityCriteria.of(Milestone.class);
public Iteration findInHierarchy(Project project, String name) {
EntityCriteria<Iteration> criteria = EntityCriteria.of(Iteration.class);
criteria.add(Restrictions.in("project", project.getSelfAndAncestors()));
criteria.add(Restrictions.eq("name", name));
criteria.setCacheable(true);
@ -55,7 +55,7 @@ public class DefaultMilestoneManager extends BaseEntityManager<Milestone> implem
}
@Override
public List<Milestone> query() {
public List<Iteration> query() {
return query(true);
}
@ -66,8 +66,8 @@ public class DefaultMilestoneManager extends BaseEntityManager<Milestone> implem
@Transactional
@Override
public void createOrUpdate(Milestone milestone) {
dao.persist(milestone);
public void createOrUpdate(Iteration iteration) {
dao.persist(iteration);
}
}

View File

@ -268,14 +268,14 @@ public class DefaultProjectManager extends BaseEntityManager<Project>
}
dao.persist(project);
if (oldPath != null && !oldPath.equals(project.getPath())) {
Collection<Milestone> milestones = new ArrayList<>();
for (Milestone milestone : issueManager.queryUsedMilestones(project)) {
if (!project.isSelfOrAncestorOf(milestone.getProject())
&& !milestone.getProject().isSelfOrAncestorOf(project)) {
milestones.add(milestone);
Collection<Iteration> iterations = new ArrayList<>();
for (Iteration iteration : issueManager.queryUsedIterations(project)) {
if (!project.isSelfOrAncestorOf(iteration.getProject())
&& !iteration.getProject().isSelfOrAncestorOf(project)) {
iterations.add(iteration);
}
}
issueManager.clearSchedules(project, milestones);
issueManager.clearSchedules(project, iterations);
settingManager.onMoveProject(oldPath, project.getPath());
for (LinkSpec link : linkSpecManager.query()) {

View File

@ -945,7 +945,7 @@ public class Build extends ProjectBelonging
return accessToken;
}
public boolean canCloseMilestone(@Nullable String accessTokenSecret) {
public boolean canCloseIteration(@Nullable String accessTokenSecret) {
var project = getProject();
return project.isCommitOnBranch(getCommitId(), project.getDefaultBranch())
|| accessTokenSecret != null && SecurityUtils.canManageIssues(getAccessToken(accessTokenSecret).asSubject(), project);

View File

@ -29,7 +29,7 @@ import io.onedev.server.util.ProjectScopedCommit;
import io.onedev.server.util.facade.IssueFacade;
import io.onedev.server.web.UrlManager;
import io.onedev.server.web.asset.emoji.Emojis;
import io.onedev.server.web.component.milestone.burndown.BurndownIndicators;
import io.onedev.server.web.component.iteration.burndown.BurndownIndicators;
import io.onedev.server.web.editable.BeanDescriptor;
import io.onedev.server.web.editable.PropertyDescriptor;
import io.onedev.server.web.util.IssueAware;
@ -52,7 +52,7 @@ import java.util.stream.Collectors;
import static io.onedev.server.model.AbstractEntity.PROP_NUMBER;
import static io.onedev.server.model.Issue.*;
import static io.onedev.server.model.IssueSchedule.NAME_MILESTONE;
import static io.onedev.server.model.IssueSchedule.NAME_ITERATION;
import static java.util.Comparator.comparing;
import static java.util.Comparator.comparingInt;
@ -153,7 +153,7 @@ public class Issue extends ProjectBelonging implements AttachmentStorageSupport
public static final Set<String> ALL_FIELDS = Sets.newHashSet(
NAME_PROJECT, NAME_NUMBER, NAME_STATE, NAME_TITLE, NAME_SUBMITTER,
NAME_DESCRIPTION, NAME_COMMENT, NAME_SUBMIT_DATE, NAME_LAST_ACTIVITY_DATE,
NAME_VOTE_COUNT, NAME_COMMENT_COUNT, NAME_MILESTONE,
NAME_VOTE_COUNT, NAME_COMMENT_COUNT, NAME_ITERATION,
NAME_ESTIMATED_TIME, NAME_SPENT_TIME, NAME_PROGRESS,
BurndownIndicators.ISSUE_COUNT, BurndownIndicators.REMAINING_TIME);
@ -161,7 +161,7 @@ public class Issue extends ProjectBelonging implements AttachmentStorageSupport
NAME_PROJECT, NAME_NUMBER, NAME_STATE, NAME_TITLE, NAME_DESCRIPTION,
NAME_ESTIMATED_TIME, NAME_SPENT_TIME, NAME_PROGRESS,
NAME_COMMENT, NAME_SUBMIT_DATE, NAME_LAST_ACTIVITY_DATE, NAME_VOTE_COUNT,
NAME_COMMENT_COUNT, NAME_MILESTONE);
NAME_COMMENT_COUNT, NAME_ITERATION);
public static final Map<String, SortField<Issue>> ORDER_FIELDS = new LinkedHashMap<>();
@ -924,23 +924,23 @@ public class Issue extends ProjectBelonging implements AttachmentStorageSupport
return OneDev.getInstance(UrlManager.class).urlFor(this);
}
public Collection<Milestone> getMilestones() {
return getSchedules().stream().map(it->it.getMilestone()).collect(Collectors.toList());
public Collection<Iteration> getIterations() {
return getSchedules().stream().map(it->it.getIteration()).collect(Collectors.toList());
}
public IssueSchedule addSchedule(Milestone milestone) {
public IssueSchedule addSchedule(Iteration iteration) {
IssueSchedule schedule = new IssueSchedule();
schedule.setIssue(this);
schedule.setMilestone(milestone);
schedule.setIteration(iteration);
getSchedules().add(schedule);
return schedule;
}
@Nullable
public IssueSchedule removeSchedule(Milestone milestone) {
public IssueSchedule removeSchedule(Iteration iteration) {
for (Iterator<IssueSchedule> it = getSchedules().iterator(); it.hasNext();) {
IssueSchedule schedule = it.next();
if (schedule.getMilestone().equals(milestone)) {
if (schedule.getIteration().equals(iteration)) {
it.remove();
return schedule;
}

View File

@ -15,8 +15,8 @@ import org.hibernate.annotations.CacheConcurrencyStrategy;
@Entity
@Table(
indexes={@Index(columnList="o_issue_id"), @Index(columnList="o_milestone_id")},
uniqueConstraints={@UniqueConstraint(columnNames={"o_issue_id", "o_milestone_id"})
indexes={@Index(columnList="o_issue_id"), @Index(columnList="o_iteration_id")},
uniqueConstraints={@UniqueConstraint(columnNames={"o_issue_id", "o_iteration_id"})
})
@Cache(usage=CacheConcurrencyStrategy.READ_WRITE)
public class IssueSchedule extends AbstractEntity {
@ -25,9 +25,9 @@ public class IssueSchedule extends AbstractEntity {
public static String PROP_ISSUE = "issue";
public static String NAME_MILESTONE = "Milestone";
public static String NAME_ITERATION = "Iteration";
public static String PROP_MILESTONE = "milestone";
public static String PROP_ITERATION = "iteration";
public static String PROP_DATE = "date";
@ -37,7 +37,7 @@ public class IssueSchedule extends AbstractEntity {
@ManyToOne(fetch=FetchType.LAZY)
@JoinColumn(nullable=false)
private Milestone milestone;
private Iteration iteration;
private Date date = new Date();
@ -49,12 +49,12 @@ public class IssueSchedule extends AbstractEntity {
this.issue = issue;
}
public Milestone getMilestone() {
return milestone;
public Iteration getIteration() {
return iteration;
}
public void setMilestone(Milestone milestone) {
this.milestone = milestone;
public void setIteration(Iteration iteration) {
this.iteration = iteration;
}
public Date getDate() {

View File

@ -12,10 +12,10 @@ import java.util.*;
@Entity
@Table(
indexes={@Index(columnList="o_project_id")},
uniqueConstraints={@UniqueConstraint(columnNames={"o_project_id", Milestone.PROP_NAME})}
uniqueConstraints={@UniqueConstraint(columnNames={"o_project_id", Iteration.PROP_NAME})}
)
@Cache(usage=CacheConcurrencyStrategy.READ_WRITE)
public class Milestone extends AbstractEntity {
public class Iteration extends AbstractEntity {
private static final long serialVersionUID = 1L;
@ -48,7 +48,7 @@ public class Milestone extends AbstractEntity {
private boolean closed;
@OneToMany(mappedBy="milestone", cascade=CascadeType.REMOVE)
@OneToMany(mappedBy= "iteration", cascade=CascadeType.REMOVE)
private Collection<IssueSchedule> schedules = new ArrayList<>();
public Project getProject() {
@ -131,7 +131,7 @@ public class Milestone extends AbstractEntity {
public static class DatesAndStatusComparator extends DatesComparator {
@Override
public int compare(Milestone o1, Milestone o2) {
public int compare(Iteration o1, Iteration o2) {
if (o1.isClosed()) {
if (o2.isClosed())
return super.compare(o1, o2) * -1;
@ -146,10 +146,10 @@ public class Milestone extends AbstractEntity {
}
public static class DatesComparator implements Comparator<Milestone> {
public static class DatesComparator implements Comparator<Iteration> {
@Override
public int compare(Milestone o1, Milestone o2) {
public int compare(Iteration o1, Iteration o2) {
if (o1.getStartDate() != null) {
if (o2.getStartDate() != null)
return o1.getStartDate().compareTo(o2.getStartDate());

View File

@ -326,7 +326,7 @@ public class Project extends AbstractEntity implements LabelSupport<ProjectLabel
@OneToMany(mappedBy="project", cascade=CascadeType.REMOVE)
@Cache(usage=CacheConcurrencyStrategy.READ_WRITE)
private Collection<Milestone> milestones = new ArrayList<>();
private Collection<Iteration> iterations = new ArrayList<>();
private boolean codeManagement = true;
@ -411,7 +411,7 @@ public class Project extends AbstractEntity implements LabelSupport<ProjectLabel
private transient Optional<CommitQueryPersonalization> commitQueryPersonalizationOfCurrentUserHolder;
private transient List<Milestone> sortedHierarchyMilestones;
private transient List<Iteration> sortedHierarchyIterations;
private transient Optional<String> activeServer;
@ -1283,27 +1283,27 @@ public class Project extends AbstractEntity implements LabelSupport<ProjectLabel
return getGitService().getMode(this, getObjectId(revision, true), path);
}
public Collection<Milestone> getMilestones() {
return milestones;
public Collection<Iteration> getIterations() {
return iterations;
}
public Collection<Milestone> getHierarchyMilestones() {
Collection<Milestone> milestones = new ArrayList<>(getMilestones());
public Collection<Iteration> getHierarchyIterations() {
Collection<Iteration> iterations = new ArrayList<>(getIterations());
if (getParent() != null)
milestones.addAll(getParent().getHierarchyMilestones());
return milestones;
iterations.addAll(getParent().getHierarchyIterations());
return iterations;
}
public void setMilestones(Collection<Milestone> milestones) {
this.milestones = milestones;
public void setIterations(Collection<Iteration> iterations) {
this.iterations = iterations;
}
public List<Milestone> getSortedHierarchyMilestones() {
if (sortedHierarchyMilestones == null) {
sortedHierarchyMilestones = new ArrayList<>(getHierarchyMilestones());
sortedHierarchyMilestones.sort(new Milestone.DatesAndStatusComparator());
public List<Iteration> getSortedHierarchyIterations() {
if (sortedHierarchyIterations == null) {
sortedHierarchyIterations = new ArrayList<>(getHierarchyIterations());
sortedHierarchyIterations.sort(new Iteration.DatesAndStatusComparator());
}
return sortedHierarchyMilestones;
return sortedHierarchyIterations;
}
@Editable
@ -1494,19 +1494,19 @@ public class Project extends AbstractEntity implements LabelSupport<ProjectLabel
}
@Nullable
public Milestone getHierarchyMilestone(@Nullable String milestoneName) {
for (Milestone milestone: getHierarchyMilestones()) {
if (milestone.getName().equals(milestoneName))
return milestone;
public Iteration getHierarchyIteration(@Nullable String iterationName) {
for (Iteration iteration: getHierarchyIterations()) {
if (iteration.getName().equals(iterationName))
return iteration;
}
return null;
}
@Nullable
public Milestone getMilestone(String milestoneName) {
for (Milestone milestone: getMilestones()) {
if (milestone.getName().equals(milestoneName))
return milestone;
public Iteration getIteration(String iterationName) {
for (Iteration iteration: getIterations()) {
if (iteration.getName().equals(iterationName))
return iteration;
}
return null;
}

View File

@ -203,7 +203,7 @@ public class Role extends AbstractEntity implements BasePermission {
this.accessConfidentialIssues = accessConfidentialIssues;
}
@Editable(order=500, description = "This permission enables one to schedule issues into milestones")
@Editable(order=500, description = "This permission enables one to schedule issues into iterations")
@ShowCondition("isManageIssuesDisabled")
public boolean isScheduleIssues() {
return scheduleIssues;

View File

@ -212,7 +212,7 @@ public class GlobalIssueSetting implements Serializable {
board.setName(Issue.NAME_STATE);
board.setIdentifyField(Issue.NAME_STATE);
board.setColumns(Lists.newArrayList("Open", "Closed"));
board.setDisplayFields(Lists.newArrayList(Issue.NAME_STATE, "Type", "Priority", "Assignees", IssueSchedule.NAME_MILESTONE));
board.setDisplayFields(Lists.newArrayList(Issue.NAME_STATE, "Type", "Priority", "Assignees", IssueSchedule.NAME_ITERATION));
board.setDisplayLinks(Lists.newArrayList("Child Issue", "Blocked By"));
boardSpecs.add(board);
@ -220,7 +220,7 @@ public class GlobalIssueSetting implements Serializable {
listFields.add("Type");
listFields.add("Priority");
listFields.add("Assignees");
listFields.add(IssueSchedule.NAME_MILESTONE);
listFields.add(IssueSchedule.NAME_ITERATION);
listLinks.add("Child Issue");
listLinks.add("Blocked By");
@ -236,7 +236,7 @@ public class GlobalIssueSetting implements Serializable {
namedQueries.add(new NamedIssueQuery("Has activity recently", "\"Last Activity Date\" is since \"last week\""));
namedQueries.add(new NamedIssueQuery("Open & Critical", "\"State\" is \"Open\" and \"Priority\" is \"Critical\""));
namedQueries.add(new NamedIssueQuery("Open & Unassigned", "\"State\" is \"Open\" and \"Assignees\" is empty"));
namedQueries.add(new NamedIssueQuery("Open & Unscheduled", "\"State\" is \"Open\" and \"Milestone\" is empty"));
namedQueries.add(new NamedIssueQuery("Open & Unscheduled", "\"State\" is \"Open\" and \"Iteration\" is empty"));
namedQueries.add(new NamedIssueQuery("Closed", "\"State\" is \"Closed\""));
namedQueries.add(new NamedIssueQuery("All", null));
@ -354,7 +354,7 @@ public class GlobalIssueSetting implements Serializable {
Collection<String> undefinedFields = new HashSet<>();
for (String fieldName: getListFields()) {
if (!fieldName.equals(Issue.NAME_STATE)
&& !fieldName.equals(IssueSchedule.NAME_MILESTONE)
&& !fieldName.equals(IssueSchedule.NAME_ITERATION)
&& getFieldSpec(fieldName) == null) {
undefinedFields.add(fieldName);
}

View File

@ -61,9 +61,9 @@ public class BoardSpec implements Serializable {
private List<String> columns = new ArrayList<>();
private String milestonePrefix;
private String iterationPrefix;
private List<String> displayFields = Lists.newArrayList(Issue.NAME_STATE, IssueSchedule.NAME_MILESTONE);
private List<String> displayFields = Lists.newArrayList(Issue.NAME_STATE, IssueSchedule.NAME_ITERATION);
private List<String> displayLinks = new ArrayList<>();
@ -92,7 +92,7 @@ public class BoardSpec implements Serializable {
}
@Editable(order=250, placeholder="Not specified", description="Optionally specify a base query to filter/order issues in backlog. "
+ "Backlog issues are those not associating with current milestone")
+ "Backlog issues are those not associating with current iteration")
@IssueQuery(withCurrentUserCriteria = true, withCurrentProjectCriteria = true, withOrder = false)
@Nullable
public String getBacklogBaseQuery() {
@ -152,15 +152,13 @@ public class BoardSpec implements Serializable {
return displayColumns;
}
@Editable(order=450, description = "" +
"If specified, OneDev will only display milestones with this prefix, and the prefix will be stripped " +
"for brevity. Also milestones created from this board will get this prefix automatically")
public String getMilestonePrefix() {
return milestonePrefix;
@Editable(order=450, description = "If specified, OneDev will only display iterations with this prefix")
public String getIterationPrefix() {
return iterationPrefix;
}
public void setMilestonePrefix(String milestonePrefix) {
this.milestonePrefix = milestonePrefix;
public void setIterationPrefix(String iterationPrefix) {
this.iterationPrefix = iterationPrefix;
}
@Editable(order=500, placeholder="Not displaying any fields", description="Specify fields to display in board card")
@ -213,7 +211,7 @@ public class BoardSpec implements Serializable {
for (FieldSpec fieldSpec: getIssueSetting().getFieldSpecs()) {
choices.add(fieldSpec.getName());
}
choices.add(IssueSchedule.NAME_MILESTONE);
choices.add(IssueSchedule.NAME_ITERATION);
return choices;
}
@ -282,7 +280,7 @@ public class BoardSpec implements Serializable {
undefinedFields.add(getIdentifyField());
}
for (String displayField: getDisplayFields()) {
if (!Issue.NAME_STATE.equals(displayField) && !IssueSchedule.NAME_MILESTONE.equals(displayField)) {
if (!Issue.NAME_STATE.equals(displayField) && !IssueSchedule.NAME_ITERATION.equals(displayField)) {
FieldSpec fieldSpec = getIssueSetting().getFieldSpec(displayField);
if (fieldSpec == null)
undefinedFields.add(displayField);

View File

@ -150,7 +150,7 @@ public class ProjectIssueSetting implements Serializable {
if (listFields != null) {
for (String fieldName: listFields) {
if (!fieldName.equals(Issue.NAME_STATE)
&& !fieldName.equals(IssueSchedule.NAME_MILESTONE)
&& !fieldName.equals(IssueSchedule.NAME_ITERATION)
&& getGlobalSetting().getFieldSpec(fieldName) == null) {
undefinedFields.add(fieldName);
}

View File

@ -6,7 +6,7 @@ import java.util.Map;
import java.util.stream.Collectors;
import io.onedev.commons.utils.StringUtils;
import io.onedev.server.model.Milestone;
import io.onedev.server.model.Iteration;
import io.onedev.server.util.Input;
public class IssueBatchUpdateData extends IssueFieldChangeData {
@ -21,21 +21,21 @@ public class IssueBatchUpdateData extends IssueFieldChangeData {
private final boolean newConfidential;
private final List<String> oldMilestones;
private final List<String> oldIterations;
private final List<String> newMilestones;
private final List<String> newIterations;
public IssueBatchUpdateData(String oldState, String newState,
boolean oldConfidential, boolean newConfidential,
List<Milestone> oldMilestones, List<Milestone> newMilestones,
Map<String, Input> oldFields, Map<String, Input> newFields) {
public IssueBatchUpdateData(String oldState, String newState,
boolean oldConfidential, boolean newConfidential,
List<Iteration> oldIterations, List<Iteration> newIterations,
Map<String, Input> oldFields, Map<String, Input> newFields) {
super(oldFields, newFields);
this.oldState = oldState;
this.newState = newState;
this.oldConfidential = oldConfidential;
this.newConfidential = newConfidential;
this.oldMilestones = oldMilestones.stream().map(it->it.getName()).collect(Collectors.toList());
this.newMilestones = newMilestones.stream().map(it->it.getName()).collect(Collectors.toList());
this.oldIterations = oldIterations.stream().map(it->it.getName()).collect(Collectors.toList());
this.newIterations = newIterations.stream().map(it->it.getName()).collect(Collectors.toList());
}
@Override
@ -43,8 +43,8 @@ public class IssueBatchUpdateData extends IssueFieldChangeData {
Map<String, String> oldFieldValues = new LinkedHashMap<>();
oldFieldValues.put("State", oldState);
oldFieldValues.put("Confidential", String.valueOf(oldConfidential));
if (!oldMilestones.isEmpty())
oldFieldValues.put("Milestones", StringUtils.join(oldMilestones));
if (!oldIterations.isEmpty())
oldFieldValues.put("Iterations", StringUtils.join(oldIterations));
oldFieldValues.putAll(super.getOldFieldValues());
return oldFieldValues;
}
@ -54,8 +54,8 @@ public class IssueBatchUpdateData extends IssueFieldChangeData {
Map<String, String> newFieldValues = new LinkedHashMap<>();
newFieldValues.put("State", newState);
newFieldValues.put("Confidential", String.valueOf(newConfidential));
if (!newMilestones.isEmpty())
newFieldValues.put("Milestones", StringUtils.join(newMilestones));
if (!newIterations.isEmpty())
newFieldValues.put("Iterations", StringUtils.join(newIterations));
newFieldValues.putAll(super.getNewFieldValues());
return newFieldValues;
}
@ -76,12 +76,12 @@ public class IssueBatchUpdateData extends IssueFieldChangeData {
return newConfidential;
}
public List<String> getOldMilestones() {
return oldMilestones;
public List<String> getOldIterations() {
return oldIterations;
}
public List<String> getNewMilestones() {
return newMilestones;
public List<String> getNewIterations() {
return newIterations;
}
@Override

View File

@ -7,19 +7,19 @@ import java.util.Map;
import io.onedev.server.model.Group;
import io.onedev.server.model.User;
public class IssueMilestoneAddData extends IssueChangeData {
public class IssueIterationAddData extends IssueChangeData {
private static final long serialVersionUID = 1L;
private final String milestone;
private final String iteration;
public IssueMilestoneAddData(String milestone) {
this.milestone = milestone;
public IssueIterationAddData(String iteration) {
this.iteration = iteration;
}
@Override
public String getActivity() {
return "added to milestone \"" + milestone + "\"";
return "added to iteration \"" + iteration + "\"";
}
@Override

View File

@ -8,34 +8,34 @@ import java.util.stream.Collectors;
import io.onedev.commons.utils.StringUtils;
import io.onedev.server.model.Group;
import io.onedev.server.model.Milestone;
import io.onedev.server.model.Iteration;
import io.onedev.server.model.User;
import io.onedev.server.notification.ActivityDetail;
public class IssueMilestoneChangeData extends IssueChangeData {
public class IssueIterationChangeData extends IssueChangeData {
private static final long serialVersionUID = 1L;
private final List<String> oldMilestones;
private final List<String> oldIterations;
private final List<String> newMilestones;
private final List<String> newIterations;
public IssueMilestoneChangeData(List<Milestone> oldMilestones, List<Milestone> newMilestones) {
this.oldMilestones = oldMilestones.stream().map(it->it.getName()).collect(Collectors.toList());
this.newMilestones = newMilestones.stream().map(it->it.getName()).collect(Collectors.toList());
public IssueIterationChangeData(List<Iteration> oldIterations, List<Iteration> newIterations) {
this.oldIterations = oldIterations.stream().map(it->it.getName()).collect(Collectors.toList());
this.newIterations = newIterations.stream().map(it->it.getName()).collect(Collectors.toList());
}
public List<String> getOldMilestones() {
return oldMilestones;
public List<String> getOldIterations() {
return oldIterations;
}
public List<String> getNewMilestones() {
return newMilestones;
public List<String> getNewIterations() {
return newIterations;
}
@Override
public String getActivity() {
return "changed milestones";
return "changed iterations";
}
@Override
@ -56,9 +56,9 @@ public class IssueMilestoneChangeData extends IssueChangeData {
@Override
public ActivityDetail getActivityDetail() {
Map<String, String> oldFieldValues = new HashMap<>();
oldFieldValues.put("Milestones", StringUtils.join(oldMilestones));
oldFieldValues.put("Iterations", StringUtils.join(oldIterations));
Map<String, String> newFieldValues = new HashMap<>();
newFieldValues.put("Milestones", StringUtils.join(newMilestones));
newFieldValues.put("Iterations", StringUtils.join(newIterations));
return ActivityDetail.compare(oldFieldValues, newFieldValues, true);
}

View File

@ -7,19 +7,19 @@ import java.util.Map;
import io.onedev.server.model.Group;
import io.onedev.server.model.User;
public class IssueMilestoneRemoveData extends IssueChangeData {
public class IssueIterationRemoveData extends IssueChangeData {
private static final long serialVersionUID = 1L;
private final String milestone;
private final String iteration;
public IssueMilestoneRemoveData(String milestone) {
this.milestone = milestone;
public IssueIterationRemoveData(String iteration) {
this.iteration = iteration;
}
@Override
public String getActivity() {
return "removed from milestone \"" + milestone + "\"";
return "removed from iteration \"" + iteration + "\"";
}
@Override

View File

@ -3,33 +3,33 @@ package io.onedev.server.model.support.issue.field.spec;
import java.util.List;
import java.util.Map;
import io.onedev.server.buildspecmodel.inputspec.MilestoneChoiceInput;
import io.onedev.server.buildspecmodel.inputspec.IterationChoiceInput;
import io.onedev.server.annotation.Editable;
@Editable(order=1110, name=FieldSpec.MILESTONE)
public class MilestoneChoiceField extends FieldSpec {
@Editable(order=1110, name=FieldSpec.ITERATION)
public class IterationChoiceField extends FieldSpec {
private static final long serialVersionUID = 1L;
@Override
public String getPropertyDef(Map<String, Integer> indexes) {
return MilestoneChoiceInput.getPropertyDef(this, indexes);
return IterationChoiceInput.getPropertyDef(this, indexes);
}
@Override
public Object convertToObject(List<String> strings) {
return MilestoneChoiceInput.convertToObject(this, strings);
return IterationChoiceInput.convertToObject(this, strings);
}
@Override
public List<String> convertToStrings(Object value) {
return MilestoneChoiceInput.convertToStrings(this, value);
return IterationChoiceInput.convertToStrings(this, value);
}
@Override
public long getOrdinal(String fieldValue) {
if (fieldValue != null)
return MilestoneChoiceInput.getOrdinal(fieldValue);
return IterationChoiceInput.getOrdinal(fieldValue);
else
return super.getOrdinal(fieldValue);
}

View File

@ -36,8 +36,8 @@ public abstract class BaseEntityManager<T extends AbstractEntity> implements Ent
}
@Override
public void delete(T entity) {
dao.remove(entity);
public void delete(T iteration) {
dao.remove(iteration);
}
@Override

View File

@ -44,7 +44,7 @@ public class IssueResource {
private final IssueChangeManager issueChangeManager;
private final MilestoneManager milestoneManager;
private final IterationManager iterationManager;
private final ProjectManager projectManager;
@ -52,12 +52,12 @@ public class IssueResource {
@Inject
public IssueResource(SettingManager settingManager, IssueManager issueManager,
IssueChangeManager issueChangeManager, MilestoneManager milestoneManager,
IssueChangeManager issueChangeManager, IterationManager iterationManager,
ProjectManager projectManager, ObjectMapper objectMapper) {
this.settingManager = settingManager;
this.issueManager = issueManager;
this.issueChangeManager = issueChangeManager;
this.milestoneManager = milestoneManager;
this.iterationManager = iterationManager;
this.projectManager = projectManager;
this.objectMapper = objectMapper;
}
@ -123,13 +123,13 @@ public class IssueResource {
}
@Api(order=450)
@Path("/{issueId}/milestones")
@Path("/{issueId}/iterations")
@GET
public Collection<Milestone> getMilestones(@PathParam("issueId") Long issueId) {
public Collection<Iteration> getIterations(@PathParam("issueId") Long issueId) {
Issue issue = issueManager.load(issueId);
if (!SecurityUtils.canAccessIssue(issue))
throw new UnauthorizedException();
return issue.getMilestones();
return issue.getIterations();
}
@Api(order=500)
@ -237,7 +237,7 @@ public class IssueResource {
if (!SecurityUtils.canAccessProject(project))
throw new UnauthorizedException();
if (!data.getMilestoneIds().isEmpty() && !SecurityUtils.canScheduleIssues(project))
if (!data.getIterationIds().isEmpty() && !SecurityUtils.canScheduleIssues(project))
throw new UnauthorizedException("No permission to schedule issue");
var issueSetting = settingManager.getIssueSetting();
@ -252,13 +252,13 @@ public class IssueResource {
issue.setState(issueSetting.getInitialStateSpec().getName());
issue.setOwnEstimatedTime(data.getOwnEstimatedTime());
for (Long milestoneId : data.getMilestoneIds()) {
Milestone milestone = milestoneManager.load(milestoneId);
if (!milestone.getProject().isSelfOrAncestorOf(project))
throw new BadRequestException("Milestone is not defined in project hierarchy of the issue");
for (Long iterationId : data.getIterationIds()) {
Iteration iteration = iterationManager.load(iterationId);
if (!iteration.getProject().isSelfOrAncestorOf(project))
throw new BadRequestException("Iteration is not defined in project hierarchy of the issue");
IssueSchedule schedule = new IssueSchedule();
schedule.setIssue(issue);
schedule.setMilestone(milestone);
schedule.setIteration(iteration);
issue.getSchedules().add(schedule);
}
@ -311,23 +311,23 @@ public class IssueResource {
return Response.ok().build();
}
@Api(order=1300, description="Schedule issue into specified milestones with list of milestone id")
@Path("/{issueId}/milestones")
@Api(order=1300, description="Schedule issue into specified iterations with list of iteration id")
@Path("/{issueId}/iterations")
@POST
public Response setMilestones(@PathParam("issueId") Long issueId, List<Long> milestoneIds) {
public Response setIterations(@PathParam("issueId") Long issueId, List<Long> iterationIds) {
Issue issue = issueManager.load(issueId);
if (!SecurityUtils.canScheduleIssues(issue.getProject()))
throw new UnauthorizedException("No permission to schedule issue");
Collection<Milestone> milestones = new HashSet<>();
for (Long milestoneId: milestoneIds) {
Milestone milestone = milestoneManager.load(milestoneId);
if (!milestone.getProject().isSelfOrAncestorOf(issue.getProject()))
throw new InvalidParamException("Milestone is not defined in project hierarchy of the issue");
milestones.add(milestone);
Collection<Iteration> iterations = new HashSet<>();
for (Long iterationId: iterationIds) {
Iteration iteration = iterationManager.load(iterationId);
if (!iteration.getProject().isSelfOrAncestorOf(issue.getProject()))
throw new InvalidParamException("Iteration is not defined in project hierarchy of the issue");
iterations.add(iteration);
}
issueChangeManager.changeMilestones(issue, milestones);
issueChangeManager.changeIterations(issue, iterations);
return Response.ok().build();
}
@ -434,7 +434,7 @@ public class IssueResource {
private int ownEstimatedTime;
@Api(order=500)
private List<Long> milestoneIds = new ArrayList<>();
private List<Long> iterationIds = new ArrayList<>();
@Api(order=600, exampleProvider = "getFieldsExample")
private Map<String, Serializable> fields = new HashMap<>();
@ -481,12 +481,12 @@ public class IssueResource {
this.ownEstimatedTime = ownEstimatedTime;
}
public List<Long> getMilestoneIds() {
return milestoneIds;
public List<Long> getIterationIds() {
return iterationIds;
}
public void setMilestoneIds(List<Long> milestoneIds) {
this.milestoneIds = milestoneIds;
public void setIterationIds(List<Long> iterationIds) {
this.iterationIds = iterationIds;
}
@NotNull

View File

@ -0,0 +1,77 @@
package io.onedev.server.rest.resource;
import javax.inject.Inject;
import javax.inject.Singleton;
import javax.validation.constraints.NotNull;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import org.apache.shiro.authz.UnauthorizedException;
import io.onedev.server.entitymanager.IterationManager;
import io.onedev.server.model.Iteration;
import io.onedev.server.rest.annotation.Api;
import io.onedev.server.security.SecurityUtils;
@Api(order=2500)
@Path("/iterations")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
@Singleton
public class IterationResource {
private final IterationManager iterationManager;
@Inject
public IterationResource(IterationManager iterationManager) {
this.iterationManager = iterationManager;
}
@Api(order=100)
@Path("/{iterationId}")
@GET
public Iteration get(@PathParam("iterationId") Long iterationId) {
Iteration iteration = iterationManager.load(iterationId);
if (!SecurityUtils.canAccessProject(iteration.getProject()))
throw new UnauthorizedException();
return iteration;
}
@Api(order=200, description="Create new iteration")
@POST
public Long create(@NotNull Iteration iteration) {
if (!SecurityUtils.canManageIssues(iteration.getProject()))
throw new UnauthorizedException();
iterationManager.createOrUpdate(iteration);
return iteration.getId();
}
@Api(order=250, description="Update iteration of specified id")
@Path("/{iterationId}")
@POST
public Long update(@PathParam("iterationId") Long iterationId, @NotNull Iteration iteration) {
if (!SecurityUtils.canManageIssues(iteration.getProject()))
throw new UnauthorizedException();
iterationManager.createOrUpdate(iteration);
return iteration.getId();
}
@Api(order=300)
@Path("/{iterationId}")
@DELETE
public Response delete(@PathParam("iterationId") Long iterationId) {
Iteration iteration = iterationManager.load(iterationId);
if (!SecurityUtils.canManageIssues(iteration.getProject()))
throw new UnauthorizedException();
iterationManager.delete(iteration);
return Response.ok().build();
}
}

View File

@ -1,77 +0,0 @@
package io.onedev.server.rest.resource;
import javax.inject.Inject;
import javax.inject.Singleton;
import javax.validation.constraints.NotNull;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import org.apache.shiro.authz.UnauthorizedException;
import io.onedev.server.entitymanager.MilestoneManager;
import io.onedev.server.model.Milestone;
import io.onedev.server.rest.annotation.Api;
import io.onedev.server.security.SecurityUtils;
@Api(order=2500)
@Path("/milestones")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
@Singleton
public class MilestoneResource {
private final MilestoneManager milestoneManager;
@Inject
public MilestoneResource(MilestoneManager milestoneManager) {
this.milestoneManager = milestoneManager;
}
@Api(order=100)
@Path("/{milestoneId}")
@GET
public Milestone get(@PathParam("milestoneId") Long milestoneId) {
Milestone milestone = milestoneManager.load(milestoneId);
if (!SecurityUtils.canAccessProject(milestone.getProject()))
throw new UnauthorizedException();
return milestone;
}
@Api(order=200, description="Create new milestone")
@POST
public Long create(@NotNull Milestone milestone) {
if (!SecurityUtils.canManageIssues(milestone.getProject()))
throw new UnauthorizedException();
milestoneManager.createOrUpdate(milestone);
return milestone.getId();
}
@Api(order=250, description="Update milestone of specified id")
@Path("/{milestoneId}")
@POST
public Long update(@PathParam("milestoneId") Long milestoneId, @NotNull Milestone milestone) {
if (!SecurityUtils.canManageIssues(milestone.getProject()))
throw new UnauthorizedException();
milestoneManager.createOrUpdate(milestone);
return milestone.getId();
}
@Api(order=300)
@Path("/{milestoneId}")
@DELETE
public Response delete(@PathParam("milestoneId") Long milestoneId) {
Milestone milestone = milestoneManager.load(milestoneId);
if (!SecurityUtils.canManageIssues(milestone.getProject()))
throw new UnauthorizedException();
milestoneManager.delete(milestone);
return Response.ok().build();
}
}

View File

@ -2,7 +2,7 @@ package io.onedev.server.rest.resource;
import com.google.common.collect.Sets;
import io.onedev.commons.utils.ExplicitException;
import io.onedev.server.entitymanager.MilestoneManager;
import io.onedev.server.entitymanager.IterationManager;
import io.onedev.server.entitymanager.ProjectManager;
import io.onedev.server.model.support.pack.ProjectPackSetting;
import io.onedev.server.web.UrlManager;
@ -53,17 +53,17 @@ public class ProjectResource {
private final ProjectManager projectManager;
private final MilestoneManager milestoneManager;
private final IterationManager iterationManager;
private final CommitInfoManager commitInfoManager;
private final UrlManager urlManager;
@Inject
public ProjectResource(ProjectManager projectManager, MilestoneManager milestoneManager,
public ProjectResource(ProjectManager projectManager, IterationManager iterationManager,
CommitInfoManager commitInfoManager, UrlManager urlManager) {
this.projectManager = projectManager;
this.milestoneManager = milestoneManager;
this.iterationManager = iterationManager;
this.commitInfoManager = commitInfoManager;
this.urlManager = urlManager;
}
@ -186,15 +186,15 @@ public class ProjectResource {
}
@Api(order=750)
@Path("/{projectId}/milestones")
@Path("/{projectId}/iterations")
@GET
public List<Milestone> queryMilestones(@PathParam("projectId") Long projectId, @QueryParam("name") String name,
@QueryParam("startBefore") @Api(exampleProvider="getDateExample", description="ISO 8601 date") String startBefore,
@QueryParam("startAfter") @Api(exampleProvider="getDateExample", description="ISO 8601 date") String startAfter,
@QueryParam("dueBefore") @Api(exampleProvider="getDateExample", description="ISO 8601 date") String dueBefore,
@QueryParam("dueAfter") @Api(exampleProvider="getDateExample", description="ISO 8601 date") String dueAfter,
@QueryParam("closed") Boolean closed, @QueryParam("offset") @Api(example="0") int offset,
@QueryParam("count") @Api(example="100") int count) {
public List<Iteration> queryIterations(@PathParam("projectId") Long projectId, @QueryParam("name") String name,
@QueryParam("startBefore") @Api(exampleProvider="getDateExample", description="ISO 8601 date") String startBefore,
@QueryParam("startAfter") @Api(exampleProvider="getDateExample", description="ISO 8601 date") String startAfter,
@QueryParam("dueBefore") @Api(exampleProvider="getDateExample", description="ISO 8601 date") String dueBefore,
@QueryParam("dueAfter") @Api(exampleProvider="getDateExample", description="ISO 8601 date") String dueAfter,
@QueryParam("closed") Boolean closed, @QueryParam("offset") @Api(example="0") int offset,
@QueryParam("count") @Api(example="100") int count) {
Project project = projectManager.load(projectId);
if (!SecurityUtils.canAccessProject(project))
throw new UnauthorizedException();
@ -202,22 +202,22 @@ public class ProjectResource {
if (count > RestConstants.MAX_PAGE_SIZE)
throw new InvalidParamException("Count should not be greater than " + RestConstants.MAX_PAGE_SIZE);
EntityCriteria<Milestone> criteria = EntityCriteria.of(Milestone.class);
criteria.add(Restrictions.in(Milestone.PROP_PROJECT, project.getSelfAndAncestors()));
EntityCriteria<Iteration> criteria = EntityCriteria.of(Iteration.class);
criteria.add(Restrictions.in(Iteration.PROP_PROJECT, project.getSelfAndAncestors()));
if (name != null)
criteria.add(Restrictions.ilike(Milestone.PROP_NAME, name.replace('%', '*')));
criteria.add(Restrictions.ilike(Iteration.PROP_NAME, name.replace('%', '*')));
if (startBefore != null)
criteria.add(Restrictions.le(Milestone.PROP_START_DATE, DateUtils.parseISO8601Date(startBefore)));
criteria.add(Restrictions.le(Iteration.PROP_START_DATE, DateUtils.parseISO8601Date(startBefore)));
if (startAfter != null)
criteria.add(Restrictions.ge(Milestone.PROP_START_DATE, DateUtils.parseISO8601Date(startAfter)));
criteria.add(Restrictions.ge(Iteration.PROP_START_DATE, DateUtils.parseISO8601Date(startAfter)));
if (dueBefore != null)
criteria.add(Restrictions.le(Milestone.PROP_DUE_DATE, DateUtils.parseISO8601Date(dueBefore)));
criteria.add(Restrictions.le(Iteration.PROP_DUE_DATE, DateUtils.parseISO8601Date(dueBefore)));
if (dueAfter != null)
criteria.add(Restrictions.ge(Milestone.PROP_DUE_DATE, DateUtils.parseISO8601Date(dueAfter)));
criteria.add(Restrictions.ge(Iteration.PROP_DUE_DATE, DateUtils.parseISO8601Date(dueAfter)));
if (closed != null)
criteria.add(Restrictions.eq(Milestone.PROP_CLOSED, closed));
criteria.add(Restrictions.eq(Iteration.PROP_CLOSED, closed));
return milestoneManager.query(criteria, offset, count);
return iterationManager.query(criteria, offset, count);
}
@Api(order=760, description="Get top contributors on default branch")

View File

@ -156,14 +156,14 @@ public abstract class EntityQuery<T extends AbstractEntity> implements Serializa
throw new ExplicitException("Unable to find build: " + value);
}
public static Milestone getMilestone(@Nullable Project project, String value) {
public static Iteration getIteration(@Nullable Project project, String value) {
if (project != null && !value.contains(":"))
value = project.getPath() + ":" + value;
Milestone milestone = OneDev.getInstance(MilestoneManager.class).findInHierarchy(value);
if (milestone != null)
return milestone;
Iteration iteration = OneDev.getInstance(IterationManager.class).findInHierarchy(value);
if (iteration != null)
return iteration;
else
throw new ExplicitException("Unable to find milestone: " + value);
throw new ExplicitException("Unable to find iteration: " + value);
}
public boolean matches(T entity) {

View File

@ -154,8 +154,8 @@ public class IssueQuery extends EntityQuery<Issue> implements Comparator<Issue>
checkField(fieldName, operator, option);
if (fieldName.equals(NAME_PROJECT)) {
return new ProjectIsCurrentCriteria();
} else if (fieldName.equals(IssueSchedule.NAME_MILESTONE)) {
return new MilestoneEmptyCriteria(operator);
} else if (fieldName.equals(IssueSchedule.NAME_ITERATION)) {
return new IterationEmptyCriteria(operator);
} else {
FieldSpec fieldSpec = getGlobalIssueSetting().getFieldSpec(fieldName);
if (fieldSpec != null)
@ -246,8 +246,8 @@ public class IssueQuery extends EntityQuery<Issue> implements Comparator<Issue>
case IssueQueryLexer.IsNot:
if (fieldName.equals(NAME_PROJECT)) {
return new ProjectCriteria(value, operator);
} else if (fieldName.equals(IssueSchedule.NAME_MILESTONE)) {
return new MilestoneCriteria(value, operator);
} else if (fieldName.equals(IssueSchedule.NAME_ITERATION)) {
return new IterationCriteria(value, operator);
} else if (fieldName.equals(NAME_STATE)) {
return new StateCriteria(value, operator);
} else if (fieldName.equals(NAME_VOTE_COUNT)) {
@ -357,7 +357,7 @@ public class IssueQuery extends EntityQuery<Issue> implements Comparator<Issue>
FieldSpec fieldSpec = getGlobalIssueSetting().getFieldSpec(fieldName);
if (validate && !(fieldSpec instanceof ChoiceField) && !(fieldSpec instanceof DateField)
&& !(fieldSpec instanceof DateTimeField) && !(fieldSpec instanceof IntegerField)
&& !(fieldSpec instanceof MilestoneChoiceField)) {
&& !(fieldSpec instanceof IterationChoiceField)) {
throw new ExplicitException("Can not order by field: " + fieldName);
}
}
@ -396,7 +396,7 @@ public class IssueQuery extends EntityQuery<Issue> implements Comparator<Issue>
case IssueQueryLexer.IsEmpty:
case IssueQueryLexer.IsNotEmpty:
if (Issue.QUERY_FIELDS.contains(fieldName)
&& !fieldName.equals(IssueSchedule.NAME_MILESTONE)) {
&& !fieldName.equals(IssueSchedule.NAME_ITERATION)) {
throw newOperatorException(fieldName, operator);
}
break;
@ -442,7 +442,7 @@ public class IssueQuery extends EntityQuery<Issue> implements Comparator<Issue>
&& !fieldName.equals(Issue.NAME_VOTE_COUNT)
&& !fieldName.equals(Issue.NAME_COMMENT_COUNT)
&& !fieldName.equals(Issue.NAME_NUMBER)
&& !fieldName.equals(IssueSchedule.NAME_MILESTONE)
&& !fieldName.equals(IssueSchedule.NAME_ITERATION)
&& !(fieldSpec instanceof IssueChoiceField)
&& !(fieldSpec instanceof PullRequestChoiceField)
&& !(fieldSpec instanceof BuildChoiceField)

View File

@ -11,20 +11,20 @@ import javax.persistence.criteria.Subquery;
import io.onedev.server.model.Issue;
import io.onedev.server.model.IssueSchedule;
import io.onedev.server.model.Milestone;
import io.onedev.server.model.Iteration;
import io.onedev.server.util.criteria.Criteria;
import io.onedev.commons.utils.match.WildcardUtils;
public class MilestoneCriteria extends Criteria<Issue> {
public class IterationCriteria extends Criteria<Issue> {
private static final long serialVersionUID = 1L;
private final String milestoneName;
private final String iterationName;
private final int operator;
public MilestoneCriteria(String milestoneName, int operator) {
this.milestoneName = milestoneName;
public IterationCriteria(String iterationName, int operator) {
this.iterationName = iterationName;
this.operator = operator;
}
@ -33,10 +33,10 @@ public class MilestoneCriteria extends Criteria<Issue> {
Subquery<IssueSchedule> scheduleQuery = query.subquery(IssueSchedule.class);
Root<IssueSchedule> schedule = scheduleQuery.from(IssueSchedule.class);
scheduleQuery.select(schedule);
Join<?, ?> milestoneJoin = schedule.join(IssueSchedule.PROP_MILESTONE, JoinType.INNER);
Join<?, ?> iterationJoin = schedule.join(IssueSchedule.PROP_ITERATION, JoinType.INNER);
scheduleQuery.where(builder.and(
builder.equal(schedule.get(IssueSchedule.PROP_ISSUE), from),
builder.like(milestoneJoin.get(Milestone.PROP_NAME), milestoneName.replace("*", "%"))));
builder.like(iterationJoin.get(Iteration.PROP_NAME), iterationName.replace("*", "%"))));
var predicate = builder.exists(scheduleQuery);
if (operator == IssueQueryLexer.IsNot)
predicate = builder.not(predicate);
@ -46,7 +46,7 @@ public class MilestoneCriteria extends Criteria<Issue> {
@Override
public boolean matches(Issue issue) {
var matches = issue.getSchedules().stream()
.anyMatch(it->WildcardUtils.matchString(milestoneName, it.getMilestone().getName()));
.anyMatch(it->WildcardUtils.matchString(iterationName, it.getIteration().getName()));
if (operator == IssueQueryLexer.IsNot)
matches = !matches;
return matches;
@ -54,19 +54,19 @@ public class MilestoneCriteria extends Criteria<Issue> {
@Override
public String toStringWithoutParens() {
return quote(IssueSchedule.NAME_MILESTONE) + " "
return quote(IssueSchedule.NAME_ITERATION) + " "
+ IssueQuery.getRuleName(operator) + " "
+ quote(milestoneName);
+ quote(iterationName);
}
@Override
public void fill(Issue issue) {
if (operator == IssueQueryLexer.Is) {
Milestone milestone = issue.getProject().getHierarchyMilestone(milestoneName);
if (milestone != null) {
Iteration iteration = issue.getProject().getHierarchyIteration(iterationName);
if (iteration != null) {
IssueSchedule schedule = new IssueSchedule();
schedule.setIssue(issue);
schedule.setMilestone(milestone);
schedule.setIteration(iteration);
issue.getSchedules().add(schedule);
}
}

View File

@ -9,13 +9,13 @@ import io.onedev.server.model.Issue;
import io.onedev.server.model.IssueSchedule;
import io.onedev.server.util.criteria.Criteria;
public class MilestoneEmptyCriteria extends Criteria<Issue> {
public class IterationEmptyCriteria extends Criteria<Issue> {
private static final long serialVersionUID = 1L;
private final int operator;
public MilestoneEmptyCriteria(int operator) {
public IterationEmptyCriteria(int operator) {
this.operator = operator;
}
@ -37,7 +37,7 @@ public class MilestoneEmptyCriteria extends Criteria<Issue> {
@Override
public String toStringWithoutParens() {
return quote(IssueSchedule.NAME_MILESTONE) + " " + IssueQuery.getRuleName(operator);
return quote(IssueSchedule.NAME_ITERATION) + " " + IssueQuery.getRuleName(operator);
}
}

View File

@ -2,21 +2,21 @@ package io.onedev.server.util;
import java.io.Serializable;
public class MilestoneAndIssueState implements Serializable {
public class IterationAndIssueState implements Serializable {
private static final long serialVersionUID = 1L;
private final Long milestoneId;
private final Long iterationId;
private final String issueState;
public MilestoneAndIssueState(Long milestoneId, String issueState) {
this.milestoneId = milestoneId;
public IterationAndIssueState(Long iterationId, String issueState) {
this.iterationId = iterationId;
this.issueState = issueState;
}
public Long getMilestoneId() {
return milestoneId;
public Long getIterationId() {
return iterationId;
}
public String getIssueState() {

View File

@ -3,14 +3,14 @@ package io.onedev.server.util;
import org.eclipse.jgit.util.StringUtils;
import org.hibernate.criterion.Order;
import io.onedev.server.model.Milestone;
import io.onedev.server.model.Iteration;
public enum MilestoneSort {
public enum IterationSort {
CLOSEST_DUE_DATE {
@Override
public Order getOrder(boolean closed) {
return closed?Order.desc(Milestone.PROP_DUE_DATE):Order.asc(Milestone.PROP_DUE_DATE);
return closed?Order.desc(Iteration.PROP_DUE_DATE):Order.asc(Iteration.PROP_DUE_DATE);
}
},
@ -18,7 +18,7 @@ public enum MilestoneSort {
@Override
public Order getOrder(boolean closed) {
return closed?Order.asc(Milestone.PROP_DUE_DATE):Order.desc(Milestone.PROP_DUE_DATE);
return closed?Order.asc(Iteration.PROP_DUE_DATE):Order.desc(Iteration.PROP_DUE_DATE);
}
},

View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1718635970649" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="7534" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M460.356 215.04c0 62.976-51.052 114.03-114.038 114.03-62.964 0-114.018-51.052-114.018-114.03 0-62.982 51.052-114.036 114.018-114.036C409.302 101.004 460.356 152.056 460.356 215.04zM346.328 694.924c-62.986 0-114.038 51.068-114.038 114.038 0 62.984 51.05 114.036 114.038 114.036 62.984 0 114.038-51.05 114.038-114.036C460.366 745.992 409.312 694.924 346.328 694.924zM520.258 220.554c91.046 8.744 170.018 59.456 217.28 132.514 34.118-15.68 71.982-19.584 107.642-12.224-63.526-131.058-197.91-221.646-353.062-221.646-0.202 0-0.402 0.008-0.604 0.008C510.898 148.472 521.406 183.404 520.258 220.554zM245.304 667.328c-60.176-94.848-60.428-216.006-0.608-311.108-28.804-20.792-51.018-50.17-62.858-84.354-109.222 141.222-109.128 338.154 0.338 479.282C194.188 717.142 216.476 687.95 245.304 667.328zM737.542 669.504c-47.272 73.076-126.26 123.792-217.328 132.526 1.436 36.774-8.56 71.744-27.758 101.35 155.012-0.132 289.25-90.684 352.728-221.652C809.616 689.064 771.76 685.23 737.542 669.504zM809.964 397.26c-62.966 0-114.02 51.05-114.02 114.028 0 62.974 51.052 114.026 114.02 114.026 62.982 0 114.036-51.05 114.036-114.026C924 448.31 872.948 397.26 809.964 397.26z" p-id="7535"></path></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -1 +0,0 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg class="icon" width="200px" height="200.00px" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="M540 175.8H428v-112h112v112z m224 280H204c-30.8 0-56-25.2-56-56v-112c0-30.8 25.2-56 56-56h560l112 112-112 112z m-224-168H428v112h112v-112zM428 959.7h112V511.8H428v447.9z" /></svg>

Before

Width:  |  Height:  |  Size: 446 B

View File

@ -141,7 +141,7 @@ public class IssueQueryBehavior extends ANTLRAssistBehavior {
for (FieldSpec field: issueSetting.getFieldSpecs()) {
if (field instanceof IntegerField || field instanceof ChoiceField
|| field instanceof DateField || field instanceof DateTimeField
|| field instanceof MilestoneChoiceField) {
|| field instanceof IterationChoiceField) {
candidates.put(field.getName(), null);
}
}
@ -227,9 +227,9 @@ public class IssueQueryBehavior extends ANTLRAssistBehavior {
}
} else if (fieldName.equals(NAME_NUMBER)) {
return SuggestionUtils.suggestIssues(project, matchWith, InputAssistBehavior.MAX_SUGGESTIONS);
} else if (fieldName.equals(IssueSchedule.NAME_MILESTONE)) {
} else if (fieldName.equals(IssueSchedule.NAME_ITERATION)) {
if (project != null && !matchWith.contains("*"))
return SuggestionUtils.suggestMilestones(project, matchWith);
return SuggestionUtils.suggestIterations(project, matchWith);
else
return null;
} else if (fieldName.equals(NAME_ESTIMATED_TIME) || fieldName.equals(NAME_SPENT_TIME)) {
@ -328,7 +328,7 @@ public class IssueQueryBehavior extends ANTLRAssistBehavior {
} else if (fieldName.equals(Issue.NAME_TITLE)
|| fieldName.equals(Issue.NAME_DESCRIPTION)
|| fieldName.equals(Issue.NAME_COMMENT)
|| fieldName.equals(IssueSchedule.NAME_MILESTONE)) {
|| fieldName.equals(IssueSchedule.NAME_ITERATION)) {
hints.add("Use '*' for wildcard match");
hints.add("Use '\\' to escape quotes");
}

View File

@ -59,7 +59,7 @@ public abstract class CommandPalettePanel extends Panel {
"~test/** ~errors/** ~sso/** ~oauth/** ~verify-email-address/** ~create-user-from-invitation/** " +
"~reset-password/** ~signup/** ~logout/** ~login/** ~loading/** ~init/** ~help/** **/invalid " +
"**/${issue}/** -**/${issue} **/${request}/** -**/${request} **/${build}/** -**/${build} " +
"**/${milestone}/** -**/${milestone} **/${agent}/** -**/${agent} **/${group}/** -**/${group} " +
"**/${iteration}/** -**/${iteration} **/${agent}/** -**/${agent} **/${group}/** -**/${group} " +
"projects/**");
private static final PatternSet eeUrlPatterns = PatternSet.parse("" +

View File

@ -0,0 +1,43 @@
package io.onedev.server.web.component.commandpalette;
import java.util.LinkedHashMap;
import java.util.Map;
import io.onedev.server.OneDev;
import io.onedev.server.entitymanager.IterationManager;
import io.onedev.server.model.Iteration;
import io.onedev.server.web.page.project.issues.iteration.IterationDetailPage;
public class IterationParam extends ParamSegment {
private static final long serialVersionUID = 1L;
public IterationParam(boolean optional) {
super(IterationDetailPage.PARAM_ITERATION, optional);
}
@Override
public Map<String, String> suggest(String matchWith, Map<String, String> paramValues, int count) {
Map<String, String> suggestions = new LinkedHashMap<>();
for (Iteration iteration: ParsedUrl.getProject(paramValues).getSortedHierarchyIterations()) {
if (iteration.getName().toLowerCase().contains(matchWith)) {
suggestions.put(iteration.getName(), String.valueOf(iteration.getId()));
if (--count == 0)
break;
}
}
return suggestions;
}
@Override
public boolean isExactMatch(String matchWith, Map<String, String> paramValues) {
try {
Long iterationId = Long.valueOf(matchWith);
if (OneDev.getInstance(IterationManager.class).get(iterationId) != null)
return true;
} catch (NumberFormatException e) {
}
return false;
}
}

View File

@ -1,43 +0,0 @@
package io.onedev.server.web.component.commandpalette;
import java.util.LinkedHashMap;
import java.util.Map;
import io.onedev.server.OneDev;
import io.onedev.server.entitymanager.MilestoneManager;
import io.onedev.server.model.Milestone;
import io.onedev.server.web.page.project.issues.milestones.MilestoneDetailPage;
public class MilestoneParam extends ParamSegment {
private static final long serialVersionUID = 1L;
public MilestoneParam(boolean optional) {
super(MilestoneDetailPage.PARAM_MILESTONE, optional);
}
@Override
public Map<String, String> suggest(String matchWith, Map<String, String> paramValues, int count) {
Map<String, String> suggestions = new LinkedHashMap<>();
for (Milestone milestone: ParsedUrl.getProject(paramValues).getSortedHierarchyMilestones()) {
if (milestone.getName().toLowerCase().contains(matchWith)) {
suggestions.put(milestone.getName(), String.valueOf(milestone.getId()));
if (--count == 0)
break;
}
}
return suggestions;
}
@Override
public boolean isExactMatch(String matchWith, Map<String, String> paramValues) {
try {
Long milestoneId = Long.valueOf(matchWith);
if (OneDev.getInstance(MilestoneManager.class).get(milestoneId) != null)
return true;
} catch (NumberFormatException e) {
}
return false;
}
}

View File

@ -33,7 +33,7 @@ import io.onedev.server.web.page.project.builds.detail.BuildDetailPage;
import io.onedev.server.web.page.project.commits.CommitDetailPage;
import io.onedev.server.web.page.project.issues.boards.IssueBoardsPage;
import io.onedev.server.web.page.project.issues.detail.IssueDetailPage;
import io.onedev.server.web.page.project.issues.milestones.MilestoneDetailPage;
import io.onedev.server.web.page.project.issues.iteration.IterationDetailPage;
import io.onedev.server.web.page.project.pullrequests.detail.PullRequestDetailPage;
public abstract class ParsedUrl implements Serializable {
@ -88,8 +88,8 @@ public abstract class ParsedUrl implements Serializable {
case AgentDetailPage.PARAM_AGENT:
parsedSegments.add(new AgentParam(optional));
break;
case MilestoneDetailPage.PARAM_MILESTONE:
parsedSegments.add(new MilestoneParam(optional));
case IterationDetailPage.PARAM_ITERATION:
parsedSegments.add(new IterationParam(optional));
break;
case ServerDetailPage.PARAM_SERVER:
if (getServers().size() > 1)
@ -147,7 +147,7 @@ public abstract class ParsedUrl implements Serializable {
case "~boards":
case "~issues":
return project.isIssueManagement();
case "~milestones":
case "~iterations":
if (project.isIssueManagement()) {
if (segment2.equals("new"))
return SecurityUtils.canManageIssues(project);

View File

@ -27,10 +27,10 @@
</label>
</span>
</div>
<wicket:enclosure child="milestones">
<wicket:enclosure child="iterations">
<div class="form-group">
<label class="control-label">Milestones</label>
<input wicket:id="milestones" type="hidden" class="form-control">
<label class="control-label">Iterations</label>
<input wicket:id="iterations" type="hidden" class="form-control">
</div>
</wicket:enclosure>
<div wicket:id="fields"></div>

View File

@ -9,7 +9,7 @@ import io.onedev.server.buildspecmodel.inputspec.InputSpec;
import io.onedev.server.entitymanager.SettingManager;
import io.onedev.server.model.Issue;
import io.onedev.server.model.IssueSchedule;
import io.onedev.server.model.Milestone;
import io.onedev.server.model.Iteration;
import io.onedev.server.model.Project;
import io.onedev.server.model.support.administration.GlobalIssueSetting;
import io.onedev.server.model.support.issue.IssueTemplate;
@ -27,7 +27,7 @@ import io.onedev.server.web.behavior.ReferenceInputBehavior;
import io.onedev.server.web.component.comment.CommentInput;
import io.onedev.server.web.component.issue.IssueStateBadge;
import io.onedev.server.web.component.issue.title.IssueTitlePanel;
import io.onedev.server.web.component.milestone.choice.MilestoneMultiChoice;
import io.onedev.server.web.component.iteration.choice.IterationMultiChoice;
import io.onedev.server.web.component.modal.confirm.ConfirmModalPanel;
import io.onedev.server.web.editable.BeanContext;
import io.onedev.server.web.editable.BeanEditor;
@ -76,7 +76,7 @@ public abstract class NewIssueEditor extends FormComponentPanel<Issue> implement
private CheckBox confidentialInput;
private MilestoneMultiChoice milestoneChoice;
private IterationMultiChoice iterationChoice;
private BeanEditor fieldEditor;
@ -211,20 +211,20 @@ public abstract class NewIssueEditor extends FormComponentPanel<Issue> implement
add(confidentialInput = new CheckBox("confidential", Model.of(false)));
Collection<Milestone> milestones = issue.getMilestones();
milestoneChoice = new MilestoneMultiChoice("milestones", Model.of(milestones),
new LoadableDetachableModel<Collection<Milestone>>() {
Collection<Iteration> iterations = issue.getIterations();
iterationChoice = new IterationMultiChoice("iterations", Model.of(iterations),
new LoadableDetachableModel<Collection<Iteration>>() {
@Override
protected Collection<Milestone> load() {
return getProject().getSortedHierarchyMilestones();
protected Collection<Iteration> load() {
return getProject().getSortedHierarchyIterations();
}
});
milestoneChoice.setVisible(SecurityUtils.canScheduleIssues(getProject()));
milestoneChoice.setRequired(false);
iterationChoice.setVisible(SecurityUtils.canScheduleIssues(getProject()));
iterationChoice.setRequired(false);
add(milestoneChoice);
add(iterationChoice);
Collection<String> properties = FieldUtils.getEditablePropertyNames(getProject(),
fieldBeanClass, fieldNames);
@ -411,12 +411,12 @@ public abstract class NewIssueEditor extends FormComponentPanel<Issue> implement
issue.setFieldValues(FieldUtils.getFieldValues(fieldEditor.newComponentContext(),
fieldEditor.getConvertedInput(), fieldNames));
milestoneChoice.convertInput();
iterationChoice.convertInput();
issue.getSchedules().clear();
for (Milestone milestone: milestoneChoice.getConvertedInput()) {
for (Iteration iteration: iterationChoice.getConvertedInput()) {
IssueSchedule schedule = new IssueSchedule();
schedule.setIssue(issue);
schedule.setMilestone(milestone);
schedule.setIteration(iteration);
issue.getSchedules().add(schedule);
}

View File

@ -34,7 +34,7 @@ import io.onedev.server.web.page.base.BasePage;
import io.onedev.server.web.page.project.builds.detail.dashboard.BuildDashboardPage;
import io.onedev.server.web.page.project.commits.CommitDetailPage;
import io.onedev.server.web.page.project.issues.detail.IssueActivitiesPage;
import io.onedev.server.web.page.project.issues.milestones.MilestoneIssuesPage;
import io.onedev.server.web.page.project.issues.iteration.IterationIssuesPage;
import io.onedev.server.web.page.project.pullrequests.detail.activities.PullRequestActivitiesPage;
import io.onedev.server.web.util.ProjectAware;
import org.apache.wicket.Component;
@ -280,14 +280,14 @@ public abstract class FieldValuesPanel extends Panel implements EditContext, Pro
} else {
valueContainer.add(new Label("value", "<i>Not Found</i>").setEscapeModelStrings(false));
}
} else if (getField().getType().equals(FieldSpec.MILESTONE)) {
Milestone milestone = OneDev.getInstance(MilestoneManager.class).findInHierarchy(getIssue().getProject(), value);
if (milestone != null) {
} else if (getField().getType().equals(FieldSpec.ITERATION)) {
Iteration iteration = OneDev.getInstance(IterationManager.class).findInHierarchy(getIssue().getProject(), value);
if (iteration != null) {
Fragment linkFrag = new Fragment("value", "linkFrag", FieldValuesPanel.this);
Link<Void> milestoneLink = new BookmarkablePageLink<Void>("link", MilestoneIssuesPage.class,
MilestoneIssuesPage.paramsOf(getIssue().getProject(), milestone));
milestoneLink.add(new Label("label", milestone.getName()));
linkFrag.add(milestoneLink);
Link<Void> iterationLink = new BookmarkablePageLink<Void>("link", IterationIssuesPage.class,
IterationIssuesPage.paramsOf(getIssue().getProject(), iteration));
iterationLink.add(new Label("label", iteration.getName()));
linkFrag.add(iterationLink);
valueContainer.add(linkFrag);
} else {
valueContainer.add(new Label("value", "<i>Not Found</i>").setEscapeModelStrings(false));

View File

@ -0,0 +1,13 @@
package io.onedev.server.web.component.issue.iteration;
import io.onedev.server.web.page.base.BaseDependentCssResourceReference;
public class IterationCrumbCssResourceReference extends BaseDependentCssResourceReference {
private static final long serialVersionUID = 1L;
public IterationCrumbCssResourceReference() {
super(IterationCrumbCssResourceReference.class, "iteration-crumb.css");
}
}

View File

@ -0,0 +1,8 @@
<wicket:panel>
<div wicket:id="edit" class="iteration-crumb">
<a wicket:id="iterations" onclick="event.stopPropagation();">
<wicket:svg href="iteration" class="icon"/>
<span wicket:id="name"></span>
</a>
</div>
</wicket:panel>

View File

@ -1,4 +1,4 @@
package io.onedev.server.web.component.issue.milestone;
package io.onedev.server.web.component.issue.iteration;
import io.onedev.server.OneDev;
import io.onedev.server.entitymanager.IssueChangeManager;
@ -8,7 +8,7 @@ import io.onedev.server.security.SecurityUtils;
import io.onedev.server.web.component.floating.FloatingPanel;
import io.onedev.server.web.editable.InplacePropertyEditLink;
import io.onedev.server.web.page.base.BasePage;
import io.onedev.server.web.page.project.issues.milestones.MilestoneIssuesPage;
import io.onedev.server.web.page.project.issues.iteration.IterationIssuesPage;
import org.apache.wicket.Component;
import org.apache.wicket.core.request.handler.IPartialPageRequestHandler;
import org.apache.wicket.markup.head.CssHeaderItem;
@ -22,9 +22,9 @@ import java.io.Serializable;
import static java.util.stream.Collectors.toList;
public abstract class MilestoneCrumbPanel extends Panel {
public abstract class IterationCrumbPanel extends Panel {
public MilestoneCrumbPanel(String id) {
public IterationCrumbPanel(String id) {
super(id);
}
@ -45,25 +45,25 @@ public abstract class MilestoneCrumbPanel extends Panel {
@Override
protected Serializable getBean() {
var bean = new MilestonesBean();
bean.setMilestoneNames(getIssue().getSchedules().stream()
.map(it -> it.getMilestone().getName()).collect(toList()));
var bean = new IterationsBean();
bean.setIterationNames(getIssue().getSchedules().stream()
.map(it -> it.getIteration().getName()).collect(toList()));
return bean;
}
@Override
protected String getPropertyName() {
return "milestoneNames";
return "iterationNames";
}
@Override
protected void onUpdated(IPartialPageRequestHandler handler, Serializable bean, String propertyName) {
var milestonesBean = (MilestonesBean) bean;
var iterationsBean = (IterationsBean) bean;
var issue = getIssue();
var milestones = milestonesBean.getMilestoneNames().stream()
.map(it -> issue.getProject().getHierarchyMilestone(it))
var iterations = iterationsBean.getIterationNames().stream()
.map(it -> issue.getProject().getHierarchyIteration(it))
.collect(toList());
OneDev.getInstance(IssueChangeManager.class).changeMilestones(issue, milestones);
OneDev.getInstance(IssueChangeManager.class).changeIterations(issue, iterations);
((BasePage) getPage()).notifyObservablesChange(handler, issue.getChangeObservables(true));
}
@ -75,15 +75,15 @@ public abstract class MilestoneCrumbPanel extends Panel {
};
add(editLink);
var milestonesView = new RepeatingView("milestones");
var iterationsView = new RepeatingView("iterations");
for (var schedule: getIssue().getSchedules()) {
var child = new BookmarkablePageLink<Void>(milestonesView.newChildId(),
MilestoneIssuesPage.class,
MilestoneIssuesPage.paramsOf(getIssue().getProject(), schedule.getMilestone()));
milestonesView.add(child);
child.add(new Label("name", schedule.getMilestone().getName()));
var child = new BookmarkablePageLink<Void>(iterationsView.newChildId(),
IterationIssuesPage.class,
IterationIssuesPage.paramsOf(getIssue().getProject(), schedule.getIteration()));
iterationsView.add(child);
child.add(new Label("name", schedule.getIteration().getName()));
}
editLink.add(milestonesView);
editLink.add(iterationsView);
}
@Override
@ -95,7 +95,7 @@ public abstract class MilestoneCrumbPanel extends Panel {
@Override
public void renderHead(IHeaderResponse response) {
super.renderHead(response);
response.render(CssHeaderItem.forReference(new MilestoneCrumbCssResourceReference()));
response.render(CssHeaderItem.forReference(new IterationCrumbCssResourceReference()));
}
protected abstract Issue getIssue();

View File

@ -0,0 +1,24 @@
package io.onedev.server.web.component.issue.iteration;
import io.onedev.server.annotation.Editable;
import io.onedev.server.annotation.IterationChoice;
import java.io.Serializable;
import java.util.List;
@Editable
public class IterationsBean implements Serializable {
private List<String> iterationNames;
@Editable
@IterationChoice
public List<String> getIterationNames() {
return iterationNames;
}
public void setIterationNames(List<String> iterationNames) {
this.iterationNames = iterationNames;
}
}

View File

@ -1,8 +1,8 @@
.milestone-crumb {
.iteration-crumb {
cursor: pointer;
border-bottom: 2px dotted transparent;
padding: 0 0.5rem 0.5rem 0;
}
.milestone-crumb:hover {
.iteration-crumb:hover {
border-color: var(--gray);
}

View File

@ -20,7 +20,7 @@
<input wicket:id="confidentialCheck" type="checkbox"> Confidential
</label>
<label class="checkbox flex-shrink-0 mr-3 mb-4">
<input wicket:id="milestoneCheck" type="checkbox"> Milestone
<input wicket:id="iterationCheck" type="checkbox"> Iteration
</label>
<label wicket:id="customFields" class="checkbox flex-shrink-0 mr-3 mb-4">
<input wicket:id="customFieldCheck" type="checkbox"> <wicket:container wicket:id="name"></wicket:container>

View File

@ -36,7 +36,7 @@ import io.onedev.server.OneDev;
import io.onedev.server.entitymanager.IssueChangeManager;
import io.onedev.server.entitymanager.SettingManager;
import io.onedev.server.model.Issue;
import io.onedev.server.model.Milestone;
import io.onedev.server.model.Iteration;
import io.onedev.server.model.Project;
import io.onedev.server.model.support.administration.GlobalIssueSetting;
import io.onedev.server.buildspecmodel.inputspec.InputContext;
@ -145,7 +145,7 @@ abstract class BatchEditPanel extends Panel implements InputContext {
}).add(newOnChangeBehavior(form)));
form.add(new CheckBox("milestoneCheck", new IModel<Boolean>() {
form.add(new CheckBox("iterationCheck", new IModel<Boolean>() {
@Override
public void detach() {
@ -153,15 +153,15 @@ abstract class BatchEditPanel extends Panel implements InputContext {
@Override
public Boolean getObject() {
return selectedFields.contains(NAME_MILESTONES);
return selectedFields.contains(NAME_ITERATION);
}
@Override
public void setObject(Boolean object) {
if (object)
selectedFields.add(NAME_MILESTONES);
selectedFields.add(NAME_ITERATION);
else
selectedFields.remove(NAME_MILESTONES);
selectedFields.remove(NAME_ITERATION);
}
}).add(newOnChangeBehavior(form)));
@ -207,7 +207,7 @@ abstract class BatchEditPanel extends Panel implements InputContext {
issue.setProject(getProject());
if (getIssueQuery() != null && getIssueQuery().getCriteria() != null) {
getIssueQuery().getCriteria().fill(issue);
builtInFieldsBean.setMilestones(issue.getMilestones().stream()
builtInFieldsBean.setIterations(issue.getIterations().stream()
.map(it->it.getName()).collect(Collectors.toList()));
customFieldsBean = issue.getFieldBean(fieldBeanClass, false);
} else {
@ -225,8 +225,8 @@ abstract class BatchEditPanel extends Panel implements InputContext {
excludedProperties.add(PROP_STATE);
if (!selectedFields.contains(NAME_CONFIDENTIAL))
excludedProperties.add(PROP_CONFIDENTIAL);
if (!selectedFields.contains(NAME_MILESTONES))
excludedProperties.add(PROP_MILESTONES);
if (!selectedFields.contains(NAME_ITERATION))
excludedProperties.add(PROP_ITERATIONS);
builtInFieldsEditor = BeanContext.edit("builtInFieldsEditor", builtInFieldsBean, excludedProperties, true);
form.add(builtInFieldsEditor);
@ -284,22 +284,22 @@ abstract class BatchEditPanel extends Panel implements InputContext {
else
confidential = null;
Collection<Milestone> milestones;
if (selectedFields.contains(NAME_MILESTONES)) {
milestones = new ArrayList<>();
for (String each: builtInFieldsBean.getMilestones()) {
Milestone milestone = getProject().getHierarchyMilestone(each);
if (milestone != null)
milestones.add(milestone);
Collection<Iteration> iterations;
if (selectedFields.contains(NAME_ITERATION)) {
iterations = new ArrayList<>();
for (String each: builtInFieldsBean.getIterations()) {
Iteration iteration = getProject().getHierarchyIteration(each);
if (iteration != null)
iterations.add(iteration);
}
} else {
milestones = null;
iterations = null;
}
Map<String, Object> fieldValues = FieldUtils.getFieldValues(customFieldsEditor.newComponentContext(),
customFieldsBean, selectedFields);
OneDev.getInstance(IssueChangeManager.class).batchUpdate(
getIssueIterator(), state, confidential, milestones, fieldValues, comment);
getIssueIterator(), state, confidential, iterations, fieldValues, comment);
onUpdated(target);
}

View File

@ -11,7 +11,7 @@ import io.onedev.server.entitymanager.SettingManager;
import io.onedev.server.model.support.administration.GlobalIssueSetting;
import io.onedev.server.annotation.ChoiceProvider;
import io.onedev.server.annotation.Editable;
import io.onedev.server.annotation.MilestoneChoice;
import io.onedev.server.annotation.IterationChoice;
@Editable
public class BuiltInFieldsBean implements Serializable {
@ -22,19 +22,19 @@ public class BuiltInFieldsBean implements Serializable {
public static final String NAME_CONFIDENTIAL = "Confidential";
public static final String NAME_MILESTONES = "Milestones";
public static final String NAME_ITERATION = "Iterations";
public static final String PROP_STATE = "state";
public static final String PROP_CONFIDENTIAL = "confidential";
public static final String PROP_MILESTONES = "milestones";
public static final String PROP_ITERATIONS = "iterations";
private String state;
private boolean confidential;
private List<String> milestones;
private List<String> iterations;
@Editable(order=100)
@ChoiceProvider("getStateChoices")
@ -57,13 +57,13 @@ public class BuiltInFieldsBean implements Serializable {
}
@Editable(order=200)
@MilestoneChoice
public List<String> getMilestones() {
return milestones;
@IterationChoice
public List<String> getIterations() {
return iterations;
}
public void setMilestones(List<String> milestones) {
this.milestones = milestones;
public void setIterations(List<String> iterations) {
this.iterations = iterations;
}
@SuppressWarnings("unused")

View File

@ -39,7 +39,7 @@ public class FieldsAndLinksBean implements Serializable {
choices.add(Issue.NAME_STATE);
for (String fieldName: OneDev.getInstance(SettingManager.class).getIssueSetting().getFieldNames())
choices.add(fieldName);
choices.add(IssueSchedule.NAME_MILESTONE);
choices.add(IssueSchedule.NAME_ITERATION);
return choices;
}

View File

@ -39,7 +39,7 @@ import io.onedev.server.web.component.floating.FloatingPanel;
import io.onedev.server.web.component.issue.IssueStateBadge;
import io.onedev.server.web.component.issue.fieldvalues.FieldValuesPanel;
import io.onedev.server.web.component.issue.link.IssueLinksPanel;
import io.onedev.server.web.component.issue.milestone.MilestoneCrumbPanel;
import io.onedev.server.web.component.issue.iteration.IterationCrumbPanel;
import io.onedev.server.web.component.issue.operation.TransitionMenuLink;
import io.onedev.server.web.component.issue.progress.IssueProgressPanel;
import io.onedev.server.web.component.issue.progress.QueriedIssuesProgressPanel;
@ -1668,8 +1668,8 @@ public abstract class IssueListPanel extends Panel {
stateFragment.add(transitLink);
fieldsView.add(stateFragment.setOutputMarkupId(true));
} else if (field.equals(IssueSchedule.NAME_MILESTONE)) {
fieldsView.add(new MilestoneCrumbPanel(fieldsView.newChildId()) {
} else if (field.equals(IssueSchedule.NAME_ITERATION)) {
fieldsView.add(new IterationCrumbPanel(fieldsView.newChildId()) {
@Override
protected Issue getIssue() {
return (Issue) fragment.getDefaultModelObject();

View File

@ -1,13 +0,0 @@
package io.onedev.server.web.component.issue.milestone;
import io.onedev.server.web.page.base.BaseDependentCssResourceReference;
public class MilestoneCrumbCssResourceReference extends BaseDependentCssResourceReference {
private static final long serialVersionUID = 1L;
public MilestoneCrumbCssResourceReference() {
super(MilestoneCrumbCssResourceReference.class, "milestone-crumb.css");
}
}

View File

@ -1,8 +0,0 @@
<wicket:panel>
<div wicket:id="edit" class="milestone-crumb">
<a wicket:id="milestones" onclick="event.stopPropagation();">
<wicket:svg href="milestone" class="icon"/>
<span wicket:id="name"></span>
</a>
</div>
</wicket:panel>

View File

@ -1,24 +0,0 @@
package io.onedev.server.web.component.issue.milestone;
import io.onedev.server.annotation.Editable;
import io.onedev.server.annotation.MilestoneChoice;
import java.io.Serializable;
import java.util.List;
@Editable
public class MilestonesBean implements Serializable {
private List<String> milestoneNames;
@Editable
@MilestoneChoice
public List<String> getMilestoneNames() {
return milestoneNames;
}
public void setMilestoneNames(List<String> milestoneNames) {
this.milestoneNames = milestoneNames;
}
}

View File

@ -20,13 +20,13 @@
</div>
</div>
</wicket:enclosure>
<div wicket:id="milestones" class="milestones">
<div class="head">Milestones</div>
<div wicket:id="iterations" class="iterations">
<div class="head">Iterations</div>
<ul class="body list-unstyled mb-0">
<li wicket:id="milestones" class="bg-light rounded p-3">
<li wicket:id="iterations" class="bg-light rounded p-3">
<div class="d-flex">
<a wicket:id="link" class="mr-2"><span wicket:id="label"></span></a>
<a wicket:id="delete" class="mr-2 flex-shrink-0" title="Remove issue from this milestone"><wicket:svg href="trash" class="icon icon-sm"></wicket:svg></a>
<a wicket:id="delete" class="mr-2 flex-shrink-0" title="Remove issue from this iteration"><wicket:svg href="trash" class="icon icon-sm"></wicket:svg></a>
<span wicket:id="status" class="status ml-auto"></span>
</div>
<div wicket:id="progress" class="mt-3"></div>

View File

@ -31,9 +31,9 @@ import io.onedev.server.web.component.issue.fieldvalues.FieldValuesPanel;
import io.onedev.server.web.component.issue.operation.TransitionMenuLink;
import io.onedev.server.web.component.issue.statestats.StateStatsBar;
import io.onedev.server.web.component.link.ViewStateAwarePageLink;
import io.onedev.server.web.component.milestone.MilestoneStatusLabel;
import io.onedev.server.web.component.milestone.choice.AbstractMilestoneChoiceProvider;
import io.onedev.server.web.component.milestone.choice.MilestoneChoiceResourceReference;
import io.onedev.server.web.component.iteration.IterationStatusLabel;
import io.onedev.server.web.component.iteration.choice.AbstractIterationChoiceProvider;
import io.onedev.server.web.component.iteration.choice.IterationChoiceResourceReference;
import io.onedev.server.web.component.modal.ModalLink;
import io.onedev.server.web.component.modal.ModalPanel;
import io.onedev.server.web.component.select2.Response;
@ -45,7 +45,7 @@ import io.onedev.server.web.component.user.list.SimpleUserListLink;
import io.onedev.server.web.editable.InplacePropertyEditLink;
import io.onedev.server.web.page.base.BasePage;
import io.onedev.server.web.page.project.issues.detail.IssueActivitiesPage;
import io.onedev.server.web.page.project.issues.milestones.MilestoneIssuesPage;
import io.onedev.server.web.page.project.issues.iteration.IterationIssuesPage;
import io.onedev.server.web.page.simple.security.LoginPage;
import org.apache.wicket.Component;
import org.apache.wicket.RestartResponseAtInterceptPageException;
@ -111,7 +111,7 @@ public abstract class IssueSidePanel extends Panel {
protected void onBeforeRender() {
addOrReplace(newFieldsContainer());
addOrReplace(newConfidentialContainer());
addOrReplace(newMilestonesContainer());
addOrReplace(newIterationsContainer());
addOrReplace(newLinksContainer());
addOrReplace(newVotesContainer());
@ -593,8 +593,8 @@ public abstract class IssueSidePanel extends Panel {
}
}
private Component newMilestonesContainer() {
WebMarkupContainer container = new WebMarkupContainer("milestones") {
private Component newIterationsContainer() {
WebMarkupContainer container = new WebMarkupContainer("iterations") {
@Override
protected void onConfigure() {
@ -604,24 +604,24 @@ public abstract class IssueSidePanel extends Panel {
};
container.add(new ListView<Milestone>("milestones", new AbstractReadOnlyModel<List<Milestone>>() {
container.add(new ListView<Iteration>("iterations", new AbstractReadOnlyModel<List<Iteration>>() {
@Override
public List<Milestone> getObject() {
return getIssue().getMilestones().stream()
.sorted(new Milestone.DatesAndStatusComparator())
public List<Iteration> getObject() {
return getIssue().getIterations().stream()
.sorted(new Iteration.DatesAndStatusComparator())
.collect(Collectors.toList());
}
}) {
@Override
protected void populateItem(ListItem<Milestone> item) {
Milestone milestone = item.getModelObject();
protected void populateItem(ListItem<Iteration> item) {
Iteration iteration = item.getModelObject();
Link<Void> link = new BookmarkablePageLink<Void>("link", MilestoneIssuesPage.class,
MilestoneIssuesPage.paramsOf(getIssue().getProject(), milestone, null));
link.add(new Label("label", milestone.getName()));
Link<Void> link = new BookmarkablePageLink<Void>("link", IterationIssuesPage.class,
IterationIssuesPage.paramsOf(getIssue().getProject(), iteration, null));
link.add(new Label("label", iteration.getName()));
item.add(link);
item.add(new StateStatsBar("progress", new AbstractReadOnlyModel<Map<String, Integer>>() {
@ -636,16 +636,16 @@ public abstract class IssueSidePanel extends Panel {
@Override
protected Link<Void> newStateLink(String componentId, String state) {
String query = new IssueQuery(new StateCriteria(state, IssueQueryLexer.Is)).toString();
PageParameters params = MilestoneIssuesPage.paramsOf(getIssue().getProject(),
PageParameters params = IterationIssuesPage.paramsOf(getIssue().getProject(),
item.getModelObject(), query);
return new ViewStateAwarePageLink<Void>(componentId, MilestoneIssuesPage.class, params);
return new ViewStateAwarePageLink<Void>(componentId, IterationIssuesPage.class, params);
}
});
item.add(new MilestoneStatusLabel("status", new AbstractReadOnlyModel<Milestone>() {
item.add(new IterationStatusLabel("status", new AbstractReadOnlyModel<Iteration>() {
@Override
public Milestone getObject() {
public Iteration getObject() {
return item.getModelObject();
}
@ -658,7 +658,7 @@ public abstract class IssueSidePanel extends Panel {
super.updateAjaxAttributes(attributes);
if (!getIssue().isNew()) {
attributes.getAjaxCallListeners().add(new ConfirmClickListener("Do you really want to "
+ "remove the issue from milestone '" + item.getModelObject().getName() + "'?"));
+ "remove the issue from iteration '" + item.getModelObject().getName() + "'?"));
}
}
@ -679,22 +679,22 @@ public abstract class IssueSidePanel extends Panel {
});
container.add(new SelectToActChoice<Milestone>("add", new AbstractMilestoneChoiceProvider() {
container.add(new SelectToActChoice<Iteration>("add", new AbstractIterationChoiceProvider() {
@Override
public void query(String term, int page, Response<Milestone> response) {
List<Milestone> milestones = getProject().getSortedHierarchyMilestones();
milestones.removeAll(getIssue().getMilestones());
public void query(String term, int page, Response<Iteration> response) {
List<Iteration> iterations = getProject().getSortedHierarchyIterations();
iterations.removeAll(getIssue().getIterations());
milestones = new Similarities<Milestone>(milestones) {
iterations = new Similarities<Iteration>(iterations) {
@Override
public double getSimilarScore(Milestone object) {
public double getSimilarScore(Iteration object) {
return Similarities.getSimilarScore(object.getName(), term);
}
};
new ResponseFiller<>(response).fill(milestones, page, WebConstants.PAGE_SIZE);
new ResponseFiller<>(response).fill(iterations, page, WebConstants.PAGE_SIZE);
}
}) {
@ -703,10 +703,10 @@ public abstract class IssueSidePanel extends Panel {
protected void onInitialize() {
super.onInitialize();
getSettings().setPlaceholder("Add to milestone...");
getSettings().setFormatResult("onedev.server.milestoneChoiceFormatter.formatResult");
getSettings().setFormatSelection("onedev.server.milestoneChoiceFormatter.formatSelection");
getSettings().setEscapeMarkup("onedev.server.milestoneChoiceFormatter.escapeMarkup");
getSettings().setPlaceholder("Add to iteration...");
getSettings().setFormatResult("onedev.server.iterationChoiceFormatter.formatResult");
getSettings().setFormatSelection("onedev.server.iterationChoiceFormatter.formatSelection");
getSettings().setEscapeMarkup("onedev.server.iterationChoiceFormatter.escapeMarkup");
}
@Override
@ -718,12 +718,12 @@ public abstract class IssueSidePanel extends Panel {
@Override
public void renderHead(IHeaderResponse response) {
super.renderHead(response);
response.render(JavaScriptHeaderItem.forReference(new MilestoneChoiceResourceReference()));
response.render(JavaScriptHeaderItem.forReference(new IterationChoiceResourceReference()));
}
@Override
protected void onSelect(AjaxRequestTarget target, Milestone milestone) {
getIssueChangeManager().addSchedule(getIssue(), milestone);
protected void onSelect(AjaxRequestTarget target, Iteration iteration) {
getIssueChangeManager().addSchedule(getIssue(), iteration);
notifyIssueChange(target, getIssue());
}

View File

@ -58,7 +58,7 @@
.issue-side>.fields .field-values.editable {
border-color: transparent;
}
.issue-side>.milestones li+li, .issue-side>.links li+li {
.issue-side>.iterations li+li, .issue-side>.links li+li {
margin-top: 1.2rem;
}

View File

@ -56,7 +56,7 @@ public abstract class StateStatsBar extends GenericPanel<Map<String, Integer>> {
}.setEscapeModelStrings(false));
add(AttributeAppender.append("title", "No issues in milestone"));
add(AttributeAppender.append("title", "No issues in iteration"));
}
setOutputMarkupId(true);

View File

@ -1,4 +1,4 @@
package io.onedev.server.web.component.milestone;
package io.onedev.server.web.component.iteration;
import java.util.Date;
@ -7,43 +7,43 @@ import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.model.IModel;
import org.apache.wicket.model.LoadableDetachableModel;
import io.onedev.server.model.Milestone;
import io.onedev.server.model.Iteration;
import io.onedev.server.util.DateUtils;
import io.onedev.server.web.component.svg.SpriteImage;
@SuppressWarnings("serial")
public class MilestoneDateLabel extends Label {
public class IterationDateLabel extends Label {
private final IModel<Milestone> milestoneModel;
private final IModel<Iteration> iterationIModel;
public MilestoneDateLabel(String id, IModel<Milestone> milestoneModel) {
public IterationDateLabel(String id, IModel<Iteration> iterationIModel) {
super(id, new LoadableDetachableModel<String>() {
@Override
protected String load() {
String arrow = "<svg class='icon'><use xlink:href='" + SpriteImage.getVersionedHref("arrow3") + "'/></svg>";
Milestone milestone = milestoneModel.getObject();
if (milestone.getStartDate() != null && milestone.getDueDate() != null) {
Iteration iteration = iterationIModel.getObject();
if (iteration.getStartDate() != null && iteration.getDueDate() != null) {
return ""
+ "<span title='Start date'>" + DateUtils.formatDate(milestone.getStartDate()) + "</span>"
+ "<span title='Start date'>" + DateUtils.formatDate(iteration.getStartDate()) + "</span>"
+ " " + arrow + " "
+ "<span title='Due date'>" + DateUtils.formatDate(milestone.getDueDate()) + "</span>";
} else if (milestone.getStartDate() != null) {
return "<span title='Start date'>" + DateUtils.formatDate(milestone.getStartDate()) + "</span> " + arrow;
} else if (milestone.getDueDate() != null) {
return arrow + " <span title='Due date'>" + DateUtils.formatDate(milestone.getDueDate()) + "</span>";
+ "<span title='Due date'>" + DateUtils.formatDate(iteration.getDueDate()) + "</span>";
} else if (iteration.getStartDate() != null) {
return "<span title='Start date'>" + DateUtils.formatDate(iteration.getStartDate()) + "</span> " + arrow;
} else if (iteration.getDueDate() != null) {
return arrow + " <span title='Due date'>" + DateUtils.formatDate(iteration.getDueDate()) + "</span>";
} else {
return "<i> No Start/Due Date</i>";
}
}
});
this.milestoneModel = milestoneModel;
this.iterationIModel = iterationIModel;
}
@Override
protected void onDetach() {
milestoneModel.detach();
iterationIModel.detach();
super.onDetach();
}
@ -55,23 +55,23 @@ public class MilestoneDateLabel extends Label {
@Override
protected String load() {
Milestone milestone = milestoneModel.getObject();
if (!milestone.isClosed()) {
Iteration iteration = iterationIModel.getObject();
if (!iteration.isClosed()) {
Date now = new Date();
if (milestone.getStartDate() != null && milestone.getDueDate() != null) {
if (now.before(milestone.getStartDate()))
if (iteration.getStartDate() != null && iteration.getDueDate() != null) {
if (now.before(iteration.getStartDate()))
return "text-info";
else if (now.after(milestone.getStartDate()) && now.before(milestone.getDueDate()))
else if (now.after(iteration.getStartDate()) && now.before(iteration.getDueDate()))
return "text-warning";
else
return "text-danger";
} else if (milestone.getStartDate() != null) {
if (now.before(milestone.getStartDate()))
} else if (iteration.getStartDate() != null) {
if (now.before(iteration.getStartDate()))
return "text-info";
else
return "text-warning";
} else if (milestone.getDueDate() != null) {
if (now.before(milestone.getDueDate()))
} else if (iteration.getDueDate() != null) {
if (now.before(iteration.getDueDate()))
return "text-info";
else
return "text-danger";

View File

@ -1,4 +1,4 @@
package io.onedev.server.web.component.milestone;
package io.onedev.server.web.component.iteration;
import org.apache.wicket.behavior.AttributeAppender;
import org.apache.wicket.markup.html.basic.Label;
@ -6,28 +6,28 @@ import org.apache.wicket.model.AbstractReadOnlyModel;
import org.apache.wicket.model.IModel;
import org.apache.wicket.model.LoadableDetachableModel;
import io.onedev.server.model.Milestone;
import io.onedev.server.model.Iteration;
@SuppressWarnings("serial")
public class MilestoneStatusLabel extends Label {
public class IterationStatusLabel extends Label {
private final IModel<Milestone> milestoneModel;
private final IModel<Iteration> iterationModel;
public MilestoneStatusLabel(String id, IModel<Milestone> milestoneModel) {
public IterationStatusLabel(String id, IModel<Iteration> iterationModel) {
super(id, new LoadableDetachableModel<String>() {
@Override
protected String load() {
return milestoneModel.getObject().getStatusName();
return iterationModel.getObject().getStatusName();
}
});
this.milestoneModel = milestoneModel;
this.iterationModel = iterationModel;
}
@Override
protected void onDetach() {
milestoneModel.detach();
iterationModel.detach();
super.onDetach();
}
@ -38,7 +38,7 @@ public class MilestoneStatusLabel extends Label {
@Override
public String getObject() {
return "badge badge-" + (milestoneModel.getObject().isClosed()? "success": "warning");
return "badge badge-" + (iterationModel.getObject().isClosed()? "success": "warning");
}
}));

View File

@ -0,0 +1,10 @@
<wicket:panel>
<span class="actions d-inline-flex align-items-center">
<a wicket:id="reopen" title="Reopen this iteration">Reopen</a>
<a wicket:id="close" title="Close this iteration">Close</a>
<span class="dot mx-2"></span>
<a wicket:id="edit" title="Edit this iteration">Edit</a>
<span class="dot mx-2"></span>
<a wicket:id="delete" title="Delete this iteration">Delete</a>
</span>
</wicket:panel>

View File

@ -1,4 +1,4 @@
package io.onedev.server.web.component.milestone.actions;
package io.onedev.server.web.component.iteration.actions;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.ajax.attributes.AjaxRequestAttributes;
@ -8,19 +8,19 @@ import org.apache.wicket.markup.html.panel.GenericPanel;
import org.apache.wicket.model.IModel;
import io.onedev.server.OneDev;
import io.onedev.server.entitymanager.MilestoneManager;
import io.onedev.server.model.Milestone;
import io.onedev.server.entitymanager.IterationManager;
import io.onedev.server.model.Iteration;
import io.onedev.server.web.ajaxlistener.ConfirmClickListener;
import io.onedev.server.web.page.project.issues.milestones.MilestoneEditPage;
import io.onedev.server.web.page.project.issues.iteration.IterationEditPage;
@SuppressWarnings("serial")
public abstract class MilestoneActionsPanel extends GenericPanel<Milestone> {
public abstract class IterationActionsPanel extends GenericPanel<Iteration> {
public MilestoneActionsPanel(String id, IModel<Milestone> model) {
public IterationActionsPanel(String id, IModel<Iteration> model) {
super(id, model);
}
private Milestone getMilestone() {
private Iteration getIteration() {
return getModelObject();
}
@ -32,17 +32,17 @@ public abstract class MilestoneActionsPanel extends GenericPanel<Milestone> {
@Override
public void onClick(AjaxRequestTarget target) {
getMilestone().setClosed(false);
getMilestoneManager().createOrUpdate(getMilestone());
target.add(MilestoneActionsPanel.this);
getIteration().setClosed(false);
getIterationManager().createOrUpdate(getIteration());
target.add(IterationActionsPanel.this);
onUpdated(target);
getSession().success("Milestone '" + getMilestone().getName() + "' reopened");
getSession().success("Iteratioin '" + getIteration().getName() + "' reopened");
}
@Override
protected void onConfigure() {
super.onConfigure();
setVisible(getMilestone().isClosed());
setVisible(getIteration().isClosed());
}
});
@ -52,22 +52,22 @@ public abstract class MilestoneActionsPanel extends GenericPanel<Milestone> {
@Override
protected void onConfigure() {
super.onConfigure();
setVisible(!getMilestone().isClosed());
setVisible(!getIteration().isClosed());
}
@Override
public void onClick(AjaxRequestTarget target) {
getMilestone().setClosed(true);
getMilestoneManager().createOrUpdate(getMilestone());
target.add(MilestoneActionsPanel.this);
getIteration().setClosed(true);
getIterationManager().createOrUpdate(getIteration());
target.add(IterationActionsPanel.this);
onUpdated(target);
getSession().success("Milestone '" + getMilestone().getName() + "' closed");
getSession().success("Iteration '" + getIteration().getName() + "' closed");
}
});
add(new BookmarkablePageLink<Void>("edit", MilestoneEditPage.class,
MilestoneEditPage.paramsOf(getMilestone())));
add(new BookmarkablePageLink<Void>("edit", IterationEditPage.class,
IterationEditPage.paramsOf(getIteration())));
add(new AjaxLink<Void>("delete") {
@ -75,15 +75,15 @@ public abstract class MilestoneActionsPanel extends GenericPanel<Milestone> {
protected void updateAjaxAttributes(AjaxRequestAttributes attributes) {
super.updateAjaxAttributes(attributes);
attributes.getAjaxCallListeners().add(new ConfirmClickListener(
"Do you really want to delete milestone '" + getMilestone().getName() + "'?"));
"Do you really want to delete iteration '" + getIteration().getName() + "'?"));
}
@Override
public void onClick(AjaxRequestTarget target) {
getMilestoneManager().delete(getMilestone());
target.add(MilestoneActionsPanel.this);
getIterationManager().delete(getIteration());
target.add(IterationActionsPanel.this);
onDeleted(target);
getSession().success("Milestone '" + getMilestone().getName() + "' deleted");
getSession().success("Iteration '" + getIteration().getName() + "' deleted");
}
});
@ -91,8 +91,8 @@ public abstract class MilestoneActionsPanel extends GenericPanel<Milestone> {
setOutputMarkupId(true);
}
private MilestoneManager getMilestoneManager() {
return OneDev.getInstance(MilestoneManager.class);
private IterationManager getIterationManager() {
return OneDev.getInstance(IterationManager.class);
}
protected abstract void onDeleted(AjaxRequestTarget target);

View File

@ -1,4 +1,4 @@
package io.onedev.server.web.component.milestone.burndown;
package io.onedev.server.web.component.iteration.burndown;
import io.onedev.server.OneDev;
import io.onedev.server.buildspecmodel.inputspec.InputSpec;

View File

@ -1,18 +1,17 @@
package io.onedev.server.web.component.milestone.burndown;
package io.onedev.server.web.component.iteration.burndown;
import io.onedev.server.OneDev;
import io.onedev.server.xodus.IssueInfoManager;
import io.onedev.server.entitymanager.SettingManager;
import io.onedev.server.model.Issue;
import io.onedev.server.model.IssueSchedule;
import io.onedev.server.model.Milestone;
import io.onedev.server.model.Iteration;
import io.onedev.server.model.support.administration.GlobalIssueSetting;
import io.onedev.server.model.support.issue.StateSpec;
import io.onedev.server.model.support.issue.field.spec.WorkingPeriodField;
import io.onedev.server.web.component.chart.line.Line;
import io.onedev.server.web.component.chart.line.LineChartPanel;
import io.onedev.server.web.component.chart.line.LineSeries;
import org.apache.wicket.behavior.AttributeAppender;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.panel.Fragment;
import org.apache.wicket.markup.html.panel.GenericPanel;
@ -25,16 +24,16 @@ import java.time.LocalDate;
import java.util.*;
import static io.onedev.server.util.DateUtils.toLocalDate;
import static io.onedev.server.web.component.milestone.burndown.BurndownIndicators.*;
import static io.onedev.server.web.component.iteration.burndown.BurndownIndicators.*;
public class MilestoneBurndownPanel extends GenericPanel<Milestone> {
public class IterationBurndownPanel extends GenericPanel<Iteration> {
private static final int MAX_DAYS = 365;
private final String indicator;
public MilestoneBurndownPanel(String id, IModel<Milestone> milestoneModel, @Nullable String indicator) {
super(id, milestoneModel);
public IterationBurndownPanel(String id, IModel<Iteration> iterationModel, @Nullable String indicator) {
super(id, iterationModel);
this.indicator = indicator;
}
@ -47,17 +46,17 @@ public class MilestoneBurndownPanel extends GenericPanel<Milestone> {
super.onInitialize();
String message = null;
if (getMilestone().getStartDate() != null && getMilestone().getDueDate() != null) {
if (getMilestone().getStartDate().before(getMilestone().getDueDate())) {
long startDay = toLocalDate(getMilestone().getStartDate()).toEpochDay();
long dueDay = toLocalDate(getMilestone().getDueDate()).toEpochDay();
if (getIteration().getStartDate() != null && getIteration().getDueDate() != null) {
if (getIteration().getStartDate().before(getIteration().getDueDate())) {
long startDay = toLocalDate(getIteration().getStartDate()).toEpochDay();
long dueDay = toLocalDate(getIteration().getDueDate()).toEpochDay();
if (dueDay - startDay >= MAX_DAYS)
message = "Milestone spans too long to show burndown chart";
message = "Iteration spans too long to show burndown chart";
} else {
message = "Milestone start date should be before due date";
message = "Iteration start date should be before due date";
}
} else {
message = "Milestone start and due date should be specified to show burndown chart";
message = "Iteration start and due date should be specified to show burndown chart";
}
if (message != null) {
var fragment = new Fragment("content", "messageFrag", this);
@ -73,11 +72,11 @@ public class MilestoneBurndownPanel extends GenericPanel<Milestone> {
@Override
protected LineSeries load() {
long startDay = toLocalDate(getMilestone().getStartDate()).toEpochDay();
long dueDay = toLocalDate(getMilestone().getDueDate()).toEpochDay();
long startDay = toLocalDate(getIteration().getStartDate()).toEpochDay();
long dueDay = toLocalDate(getIteration().getDueDate()).toEpochDay();
Map<Long, Map<String, Integer>> dailyStateMetrics = new LinkedHashMap<>();
for (IssueSchedule schedule : getMilestone().getSchedules()) {
for (IssueSchedule schedule : getIteration().getSchedules()) {
Issue issue = schedule.getIssue();
long scheduleDay = toLocalDate(schedule.getDate()).toEpochDay();
@ -186,7 +185,7 @@ public class MilestoneBurndownPanel extends GenericPanel<Milestone> {
if (indicator != null)
return indicator;
else
return getDefault(getMilestone().getProject());
return getDefault(getIteration().getProject());
}
private int getIndicatorValue(Issue issue) {
@ -207,7 +206,7 @@ public class MilestoneBurndownPanel extends GenericPanel<Milestone> {
}
}
private Milestone getMilestone() {
private Iteration getIteration() {
return getModelObject();
}

View File

@ -1,4 +1,4 @@
package io.onedev.server.web.component.milestone.choice;
package io.onedev.server.web.component.iteration.choice;
import java.util.Collection;
import java.util.List;
@ -11,16 +11,16 @@ import org.unbescape.html.HtmlEscape;
import com.google.common.collect.Lists;
import io.onedev.server.OneDev;
import io.onedev.server.entitymanager.MilestoneManager;
import io.onedev.server.model.Milestone;
import io.onedev.server.entitymanager.IterationManager;
import io.onedev.server.model.Iteration;
import io.onedev.server.web.component.select2.ChoiceProvider;
public abstract class AbstractMilestoneChoiceProvider extends ChoiceProvider<Milestone> {
public abstract class AbstractIterationChoiceProvider extends ChoiceProvider<Iteration> {
private static final long serialVersionUID = 1L;
@Override
public void toJson(Milestone choice, JSONWriter writer) throws JSONException {
public void toJson(Iteration choice, JSONWriter writer) throws JSONException {
writer.key("id").value(choice.getId()).key("name").value(HtmlEscape.escapeHtml5(choice.getName()));
writer.key("statusName").value(choice.getStatusName());
if (choice.isClosed())
@ -30,16 +30,16 @@ public abstract class AbstractMilestoneChoiceProvider extends ChoiceProvider<Mil
}
@Override
public Collection<Milestone> toChoices(Collection<String> ids) {
List<Milestone> milestones = Lists.newArrayList();
MilestoneManager milestoneManager = OneDev.getInstance(MilestoneManager.class);
public Collection<Iteration> toChoices(Collection<String> ids) {
List<Iteration> iterations = Lists.newArrayList();
IterationManager iterationManager = OneDev.getInstance(IterationManager.class);
for (String each : ids) {
Milestone milestone = milestoneManager.load(Long.valueOf(each));
Hibernate.initialize(milestone);
milestones.add(milestone);
Iteration iteration = iterationManager.load(Long.valueOf(each));
Hibernate.initialize(iteration);
iterations.add(iteration);
}
return milestones;
return iterations;
}
}

View File

@ -1,23 +1,23 @@
package io.onedev.server.web.component.milestone.choice;
package io.onedev.server.web.component.iteration.choice;
import java.util.Collection;
import java.util.List;
import org.apache.wicket.model.IModel;
import io.onedev.server.model.Milestone;
import io.onedev.server.model.Iteration;
import io.onedev.server.util.Similarities;
import io.onedev.server.web.WebConstants;
import io.onedev.server.web.component.select2.Response;
import io.onedev.server.web.component.select2.ResponseFiller;
public class MilestoneChoiceProvider extends AbstractMilestoneChoiceProvider {
public class IterationChoiceProvider extends AbstractIterationChoiceProvider {
private static final long serialVersionUID = 1L;
private final IModel<Collection<Milestone>> choicesModel;
private final IModel<Collection<Iteration>> choicesModel;
public MilestoneChoiceProvider(IModel<Collection<Milestone>> choicesModel) {
public IterationChoiceProvider(IModel<Collection<Iteration>> choicesModel) {
this.choicesModel = choicesModel;
}
@ -28,18 +28,18 @@ public class MilestoneChoiceProvider extends AbstractMilestoneChoiceProvider {
}
@Override
public void query(String term, int page, Response<Milestone> response) {
List<Milestone> milestones = new Similarities<Milestone>(choicesModel.getObject()) {
public void query(String term, int page, Response<Iteration> response) {
List<Iteration> iterations = new Similarities<Iteration>(choicesModel.getObject()) {
private static final long serialVersionUID = 1L;
@Override
public double getSimilarScore(Milestone object) {
public double getSimilarScore(Iteration object) {
return Similarities.getSimilarScore(object.getName(), term);
}
};
new ResponseFiller<Milestone>(response).fill(milestones, page, WebConstants.PAGE_SIZE);
new ResponseFiller<Iteration>(response).fill(iterations, page, WebConstants.PAGE_SIZE);
}
}

View File

@ -1,4 +1,4 @@
package io.onedev.server.web.component.milestone.choice;
package io.onedev.server.web.component.iteration.choice;
import java.util.List;
@ -8,19 +8,19 @@ import org.apache.wicket.request.resource.CssResourceReference;
import io.onedev.server.web.page.base.BaseDependentResourceReference;
public class MilestoneChoiceResourceReference extends BaseDependentResourceReference {
public class IterationChoiceResourceReference extends BaseDependentResourceReference {
private static final long serialVersionUID = 1L;
public MilestoneChoiceResourceReference() {
super(MilestoneChoiceResourceReference.class, "milestone-choice.js");
public IterationChoiceResourceReference() {
super(IterationChoiceResourceReference.class, "iteration-choice.js");
}
@Override
public List<HeaderItem> getDependencies() {
List<HeaderItem> dependencies = super.getDependencies();
dependencies.add(CssHeaderItem.forReference(
new CssResourceReference(MilestoneChoiceResourceReference.class, "milestone-choice.css")));
new CssResourceReference(IterationChoiceResourceReference.class, "iteration-choice.css")));
return dependencies;
}

View File

@ -0,0 +1,40 @@
package io.onedev.server.web.component.iteration.choice;
import java.util.Collection;
import org.apache.wicket.markup.head.IHeaderResponse;
import org.apache.wicket.markup.head.JavaScriptHeaderItem;
import org.apache.wicket.model.IModel;
import io.onedev.server.model.Iteration;
import io.onedev.server.web.component.select2.Select2MultiChoice;
public class IterationMultiChoice extends Select2MultiChoice<Iteration> {
private static final long serialVersionUID = 1L;
public IterationMultiChoice(String id, IModel<Collection<Iteration>> selectionsModel, IModel<Collection<Iteration>>choicesModel) {
super(id, selectionsModel, new IterationChoiceProvider(choicesModel));
}
@Override
protected void onInitialize() {
super.onInitialize();
if (isRequired())
getSettings().setPlaceholder("Choose iterations...");
else
getSettings().setPlaceholder("Not specified");
getSettings().setFormatResult("onedev.server.iterationChoiceFormatter.formatResult");
getSettings().setFormatSelection("onedev.server.iterationChoiceFormatter.formatSelection");
getSettings().setEscapeMarkup("onedev.server.iterationChoiceFormatter.escapeMarkup");
setConvertEmptyInputStringToNull(true);
}
@Override
public void renderHead(IHeaderResponse response) {
super.renderHead(response);
response.render(JavaScriptHeaderItem.forReference(new IterationChoiceResourceReference()));
}
}

View File

@ -0,0 +1,41 @@
package io.onedev.server.web.component.iteration.choice;
import java.util.Collection;
import org.apache.wicket.markup.head.IHeaderResponse;
import org.apache.wicket.markup.head.JavaScriptHeaderItem;
import org.apache.wicket.model.IModel;
import io.onedev.server.model.Iteration;
import io.onedev.server.web.component.select2.Select2Choice;
@SuppressWarnings("serial")
public class IterationSingleChoice extends Select2Choice<Iteration> {
public IterationSingleChoice(String id, IModel<Iteration> selectionModel, IModel<Collection<Iteration>> iterationsModel) {
super(id, selectionModel, new IterationChoiceProvider(iterationsModel));
}
@Override
protected void onInitialize() {
super.onInitialize();
getSettings().setAllowClear(!isRequired());
if (isRequired())
getSettings().setPlaceholder("Choose iteration...");
else
getSettings().setPlaceholder("Not specified");
getSettings().setFormatResult("onedev.server.iterationChoiceFormatter.formatResult");
getSettings().setFormatSelection("onedev.server.iterationChoiceFormatter.formatSelection");
getSettings().setEscapeMarkup("onedev.server.iterationChoiceFormatter.escapeMarkup");
setConvertEmptyInputStringToNull(true);
}
@Override
public void renderHead(IHeaderResponse response) {
super.renderHead(response);
response.render(JavaScriptHeaderItem.forReference(new IterationChoiceResourceReference()));
}
}

View File

@ -0,0 +1,3 @@
.select2-results .iteration .label {
margin-left: 6px;
}

View File

@ -0,0 +1,14 @@
onedev.server.iterationChoiceFormatter = {
formatSelection: function(iteration) {
return iteration.name;
},
formatResult: function(iteration) {
return "<span class='iteration'>" + iteration.name + " <span class='ml-2 badge " + iteration.statusClass + "'>" + iteration.statusName + "</span></span>";
},
escapeMarkup: function(m) {
return m;
},
};

View File

@ -1,5 +1,5 @@
<wicket:panel>
<div class="card milestone-list">
<div class="card iteration-list">
<div class="card-body">
<div class="d-flex align-items-center flex-wrap">
<span wicket:id="states" class="btn-group mr-4 mb-4">
@ -9,9 +9,9 @@
<div class="mr-4 mb-4">
<a wicket:id="sort" class="btn btn-outline-secondary">Sort <wicket:svg href="arrow" class="icon rotate-90"></wicket:svg></a>
</div>
<a wicket:id="newMilestone" class="btn btn-primary btn-icon mb-4"><wicket:svg href="plus" class="icon mr-1"></wicket:svg></a>
<a wicket:id="newIteration" class="btn btn-primary btn-icon mb-4"><wicket:svg href="plus" class="icon mr-1"></wicket:svg></a>
</div>
<table wicket:id="milestones" class="table"></table>
<table wicket:id="iterations" class="table"></table>
</div>
</div>
<wicket:fragment wicket:id="issueStatsFrag">

View File

@ -1,9 +1,9 @@
package io.onedev.server.web.component.milestone.list;
package io.onedev.server.web.component.iteration.list;
import io.onedev.server.OneDev;
import io.onedev.server.entitymanager.IssueManager;
import io.onedev.server.entitymanager.MilestoneManager;
import io.onedev.server.model.Milestone;
import io.onedev.server.entitymanager.IterationManager;
import io.onedev.server.model.Iteration;
import io.onedev.server.model.Project;
import io.onedev.server.persistence.dao.Dao;
import io.onedev.server.persistence.dao.EntityCriteria;
@ -11,8 +11,8 @@ import io.onedev.server.search.entity.issue.IssueQuery;
import io.onedev.server.search.entity.issue.IssueQueryLexer;
import io.onedev.server.search.entity.issue.StateCriteria;
import io.onedev.server.security.SecurityUtils;
import io.onedev.server.util.MilestoneAndIssueState;
import io.onedev.server.util.MilestoneSort;
import io.onedev.server.util.IterationAndIssueState;
import io.onedev.server.util.IterationSort;
import io.onedev.server.web.WebConstants;
import io.onedev.server.web.WebSession;
import io.onedev.server.web.component.datatable.DefaultDataTable;
@ -22,10 +22,10 @@ import io.onedev.server.web.component.link.ActionablePageLink;
import io.onedev.server.web.component.link.ViewStateAwarePageLink;
import io.onedev.server.web.component.menu.MenuItem;
import io.onedev.server.web.component.menu.MenuLink;
import io.onedev.server.web.component.milestone.MilestoneDateLabel;
import io.onedev.server.web.component.milestone.actions.MilestoneActionsPanel;
import io.onedev.server.web.page.project.issues.milestones.MilestoneIssuesPage;
import io.onedev.server.web.page.project.issues.milestones.NewMilestonePage;
import io.onedev.server.web.component.iteration.IterationDateLabel;
import io.onedev.server.web.component.iteration.actions.IterationActionsPanel;
import io.onedev.server.web.page.project.issues.iteration.IterationIssuesPage;
import io.onedev.server.web.page.project.issues.iteration.NewIterationPage;
import io.onedev.server.web.util.LoadableDetachableDataProvider;
import io.onedev.server.web.util.PagingHistorySupport;
import org.apache.wicket.Component;
@ -56,40 +56,40 @@ import javax.annotation.Nullable;
import java.util.*;
@SuppressWarnings("serial")
public class MilestoneListPanel extends GenericPanel<Project> {
public class IterationListPanel extends GenericPanel<Project> {
private boolean closed;
private MilestoneSort sort;
private IterationSort sort;
private final PagingHistorySupport pagingHistorySupport;
private final IModel<Collection<MilestoneAndIssueState>> milestoneAndStatesModel =
private final IModel<Collection<IterationAndIssueState>> iterationAndStatesModel =
new LoadableDetachableModel<>() {
@Override
protected Collection<MilestoneAndIssueState> load() {
List<Milestone> milestones = new ArrayList<>();
for (Component row : (WebMarkupContainer) milestonesTable.get("body").get("rows")) {
Milestone milestone = (Milestone) row.getDefaultModelObject();
milestones.add(milestone);
protected Collection<IterationAndIssueState> load() {
List<Iteration> iterations = new ArrayList<>();
for (Component row : (WebMarkupContainer) iterationsTable.get("body").get("rows")) {
Iteration iteration = (Iteration) row.getDefaultModelObject();
iterations.add(iteration);
}
return OneDev.getInstance(IssueManager.class).queryMilestoneAndIssueStates(getProject(), milestones);
return OneDev.getInstance(IssueManager.class).queryIterationAndIssueStates(getProject(), iterations);
}
};
private DataTable<Milestone, Void> milestonesTable;
private DataTable<Iteration, Void> iterationsTable;
private EntityCriteria<Milestone> getCriteria(boolean closed) {
EntityCriteria<Milestone> criteria = EntityCriteria.of(Milestone.class);
private EntityCriteria<Iteration> getCriteria(boolean closed) {
EntityCriteria<Iteration> criteria = EntityCriteria.of(Iteration.class);
criteria.add(Restrictions.in("project", getProject().getSelfAndAncestors()));
criteria.add(Restrictions.eq("closed", closed));
return criteria;
}
public MilestoneListPanel(String id, IModel<Project> model, boolean closed, MilestoneSort sort,
@Nullable PagingHistorySupport pagingHistorySupport) {
public IterationListPanel(String id, IModel<Project> model, boolean closed, IterationSort sort,
@Nullable PagingHistorySupport pagingHistorySupport) {
super(id, model);
this.closed = closed;
@ -111,7 +111,7 @@ public class MilestoneListPanel extends GenericPanel<Project> {
public void onClick(AjaxRequestTarget target) {
closed = false;
target.add(statesContainer);
target.add(milestonesTable);
target.add(iterationsTable);
onStateChanged(target, closed);
}
@ -132,7 +132,7 @@ public class MilestoneListPanel extends GenericPanel<Project> {
public void onClick(AjaxRequestTarget target) {
closed = true;
target.add(statesContainer);
target.add(milestonesTable);
target.add(iterationsTable);
onStateChanged(target, closed);
}
@ -152,7 +152,7 @@ public class MilestoneListPanel extends GenericPanel<Project> {
@Override
protected List<MenuItem> getMenuItems(FloatingPanel dropdown) {
List<MenuItem> menuItems = new ArrayList<>();
for (MilestoneSort sort: MilestoneSort.values()) {
for (IterationSort sort: IterationSort.values()) {
menuItems.add(new MenuItem() {
@Override
@ -162,7 +162,7 @@ public class MilestoneListPanel extends GenericPanel<Project> {
@Override
public String getIconHref() {
if (sort == MilestoneListPanel.this.sort)
if (sort == IterationListPanel.this.sort)
return "tick";
else
return null;
@ -175,14 +175,14 @@ public class MilestoneListPanel extends GenericPanel<Project> {
@Override
public void onClick(AjaxRequestTarget target) {
dropdown.close();
target.add(milestonesTable);
MilestoneListPanel.this.sort = sort;
target.add(iterationsTable);
IterationListPanel.this.sort = sort;
onSortChanged(target, sort);
}
};
link.add(AttributeAppender.append("class", "milestone-sort"));
if (sort == MilestoneListPanel.this.sort)
link.add(AttributeAppender.append("class", "iteration-sort"));
if (sort == IterationListPanel.this.sort)
link.add(AttributeAppender.append("class", "active"));
return link;
}
@ -194,7 +194,7 @@ public class MilestoneListPanel extends GenericPanel<Project> {
});
add(new BookmarkablePageLink<Void>("newMilestone", NewMilestonePage.class, NewMilestonePage.paramsOf(getProject())) {
add(new BookmarkablePageLink<Void>("newIteration", NewIterationPage.class, NewIterationPage.paramsOf(getProject())) {
@Override
protected void onConfigure() {
@ -204,7 +204,7 @@ public class MilestoneListPanel extends GenericPanel<Project> {
});
List<IColumn<Milestone, Void>> columns = new ArrayList<>();
List<IColumn<Iteration, Void>> columns = new ArrayList<>();
columns.add(new AbstractColumn<>(Model.of("Name")) {
@ -214,22 +214,22 @@ public class MilestoneListPanel extends GenericPanel<Project> {
}
@Override
public void populateItem(Item<ICellPopulator<Milestone>> cellItem, String componentId,
IModel<Milestone> rowModel) {
Milestone milestone = rowModel.getObject();
Fragment fragment = new Fragment(componentId, "nameFrag", MilestoneListPanel.this);
WebMarkupContainer link = new ActionablePageLink("link", MilestoneIssuesPage.class,
MilestoneIssuesPage.paramsOf(getProject(), milestone, null)) {
public void populateItem(Item<ICellPopulator<Iteration>> cellItem, String componentId,
IModel<Iteration> rowModel) {
Iteration iteration = rowModel.getObject();
Fragment fragment = new Fragment(componentId, "nameFrag", IterationListPanel.this);
WebMarkupContainer link = new ActionablePageLink("link", IterationIssuesPage.class,
IterationIssuesPage.paramsOf(getProject(), iteration, null)) {
@Override
protected void doBeforeNav(AjaxRequestTarget target) {
String redirectUrlAfterDelete = RequestCycle.get().urlFor(
getPage().getClass(), getPage().getPageParameters()).toString();
WebSession.get().setRedirectUrlAfterDelete(Milestone.class, redirectUrlAfterDelete);
WebSession.get().setRedirectUrlAfterDelete(Iteration.class, redirectUrlAfterDelete);
}
};
link.add(new Label("label", milestone.getName()));
link.add(new Label("label", iteration.getName()));
fragment.add(link);
fragment.add(new WebMarkupContainer("inherited") {
@ -253,9 +253,9 @@ public class MilestoneListPanel extends GenericPanel<Project> {
}
@Override
public void populateItem(Item<ICellPopulator<Milestone>> cellItem, String componentId,
IModel<Milestone> rowModel) {
cellItem.add(new MilestoneDateLabel(componentId, rowModel));
public void populateItem(Item<ICellPopulator<Iteration>> cellItem, String componentId,
IModel<Iteration> rowModel) {
cellItem.add(new IterationDateLabel(componentId, rowModel));
}
});
@ -268,29 +268,29 @@ public class MilestoneListPanel extends GenericPanel<Project> {
}
@Override
public void populateItem(Item<ICellPopulator<Milestone>> cellItem, String componentId,
IModel<Milestone> rowModel) {
Fragment fragment = new Fragment(componentId, "issueStatsFrag", MilestoneListPanel.this) {
public void populateItem(Item<ICellPopulator<Iteration>> cellItem, String componentId,
IModel<Iteration> rowModel) {
Fragment fragment = new Fragment(componentId, "issueStatsFrag", IterationListPanel.this) {
@Override
protected void onBeforeRender() {
/*
* Create StateStatsBar here as it requires to access the milestoneAndStatsModel which can
* only be calculated correctly after the milestone table is initialized
* Create StateStatsBar here as it requires to access the iterationAndStatsModel which can
* only be calculated correctly after the iteration table is initialized
*/
addOrReplace(new StateStatsBar("content", new LoadableDetachableModel<Map<String, Integer>>() {
@Override
protected Map<String, Integer> load() {
Map<String, Integer> stateStats = new HashMap<>();
for (MilestoneAndIssueState milestoneAndState : milestoneAndStatesModel.getObject()) {
if (milestoneAndState.getMilestoneId().equals(rowModel.getObject().getId())) {
Integer count = stateStats.get(milestoneAndState.getIssueState());
for (IterationAndIssueState iterationAndState : iterationAndStatesModel.getObject()) {
if (iterationAndState.getIterationId().equals(rowModel.getObject().getId())) {
Integer count = stateStats.get(iterationAndState.getIssueState());
if (count != null)
count++;
else
count = 1;
stateStats.put(milestoneAndState.getIssueState(), count);
stateStats.put(iterationAndState.getIssueState(), count);
}
}
return stateStats;
@ -301,8 +301,8 @@ public class MilestoneListPanel extends GenericPanel<Project> {
@Override
protected Link<Void> newStateLink(String componentId, String state) {
String query = new IssueQuery(new StateCriteria(state, IssueQueryLexer.Is)).toString();
PageParameters params = MilestoneIssuesPage.paramsOf(getProject(), rowModel.getObject(), query);
return new ViewStateAwarePageLink<Void>(componentId, MilestoneIssuesPage.class, params);
PageParameters params = IterationIssuesPage.paramsOf(getProject(), rowModel.getObject(), query);
return new ViewStateAwarePageLink<Void>(componentId, IterationIssuesPage.class, params);
}
});
@ -324,19 +324,19 @@ public class MilestoneListPanel extends GenericPanel<Project> {
}
@Override
public void populateItem(Item<ICellPopulator<Milestone>> cellItem, String componentId,
IModel<Milestone> rowModel) {
public void populateItem(Item<ICellPopulator<Iteration>> cellItem, String componentId,
IModel<Iteration> rowModel) {
if (rowModel.getObject().getProject().equals(getProject())) {
cellItem.add(new MilestoneActionsPanel(componentId, rowModel) {
cellItem.add(new IterationActionsPanel(componentId, rowModel) {
@Override
protected void onUpdated(AjaxRequestTarget target) {
target.add(milestonesTable);
target.add(iterationsTable);
}
@Override
protected void onDeleted(AjaxRequestTarget target) {
target.add(milestonesTable);
target.add(iterationsTable);
}
});
@ -348,11 +348,11 @@ public class MilestoneListPanel extends GenericPanel<Project> {
});
}
SortableDataProvider<Milestone, Void> dataProvider = new LoadableDetachableDataProvider<>() {
SortableDataProvider<Iteration, Void> dataProvider = new LoadableDetachableDataProvider<>() {
@Override
public Iterator<? extends Milestone> iterator(long first, long count) {
EntityCriteria<Milestone> criteria = getCriteria(closed);
public Iterator<? extends Iteration> iterator(long first, long count) {
EntityCriteria<Iteration> criteria = getCriteria(closed);
criteria.addOrder(sort.getOrder(closed));
return OneDev.getInstance(Dao.class).query(criteria, (int) first, (int) count).iterator();
}
@ -363,27 +363,27 @@ public class MilestoneListPanel extends GenericPanel<Project> {
}
@Override
public IModel<Milestone> model(Milestone object) {
public IModel<Iteration> model(Iteration object) {
Long id = object.getId();
return new LoadableDetachableModel<>() {
@Override
protected Milestone load() {
return OneDev.getInstance(MilestoneManager.class).load(id);
protected Iteration load() {
return OneDev.getInstance(IterationManager.class).load(id);
}
};
}
};
add(milestonesTable = new DefaultDataTable<>("milestones", columns, dataProvider,
add(iterationsTable = new DefaultDataTable<>("iterations", columns, dataProvider,
WebConstants.PAGE_SIZE, pagingHistorySupport));
milestonesTable.setOutputMarkupId(true);
iterationsTable.setOutputMarkupId(true);
}
@Override
protected void onDetach() {
milestoneAndStatesModel.detach();
iterationAndStatesModel.detach();
super.onDetach();
}
@ -391,7 +391,7 @@ public class MilestoneListPanel extends GenericPanel<Project> {
return getModelObject();
}
protected void onSortChanged(AjaxRequestTarget target, MilestoneSort sort) {
protected void onSortChanged(AjaxRequestTarget target, IterationSort sort) {
}
protected void onStateChanged(AjaxRequestTarget target, boolean closed) {

View File

@ -1,10 +0,0 @@
<wicket:panel>
<span class="actions d-inline-flex align-items-center">
<a wicket:id="reopen" title="Reopen this milestone">Reopen</a>
<a wicket:id="close" title="Close this milestone">Close</a>
<span class="dot mx-2"></span>
<a wicket:id="edit" title="Edit this milestone">Edit</a>
<span class="dot mx-2"></span>
<a wicket:id="delete" title="Delete this milestone">Delete</a>
</span>
</wicket:panel>

View File

@ -1,40 +0,0 @@
package io.onedev.server.web.component.milestone.choice;
import java.util.Collection;
import org.apache.wicket.markup.head.IHeaderResponse;
import org.apache.wicket.markup.head.JavaScriptHeaderItem;
import org.apache.wicket.model.IModel;
import io.onedev.server.model.Milestone;
import io.onedev.server.web.component.select2.Select2MultiChoice;
public class MilestoneMultiChoice extends Select2MultiChoice<Milestone> {
private static final long serialVersionUID = 1L;
public MilestoneMultiChoice(String id, IModel<Collection<Milestone>> selectionsModel, IModel<Collection<Milestone>>choicesModel) {
super(id, selectionsModel, new MilestoneChoiceProvider(choicesModel));
}
@Override
protected void onInitialize() {
super.onInitialize();
if (isRequired())
getSettings().setPlaceholder("Choose milestones...");
else
getSettings().setPlaceholder("Not specified");
getSettings().setFormatResult("onedev.server.milestoneChoiceFormatter.formatResult");
getSettings().setFormatSelection("onedev.server.milestoneChoiceFormatter.formatSelection");
getSettings().setEscapeMarkup("onedev.server.milestoneChoiceFormatter.escapeMarkup");
setConvertEmptyInputStringToNull(true);
}
@Override
public void renderHead(IHeaderResponse response) {
super.renderHead(response);
response.render(JavaScriptHeaderItem.forReference(new MilestoneChoiceResourceReference()));
}
}

View File

@ -1,41 +0,0 @@
package io.onedev.server.web.component.milestone.choice;
import java.util.Collection;
import org.apache.wicket.markup.head.IHeaderResponse;
import org.apache.wicket.markup.head.JavaScriptHeaderItem;
import org.apache.wicket.model.IModel;
import io.onedev.server.model.Milestone;
import io.onedev.server.web.component.select2.Select2Choice;
@SuppressWarnings("serial")
public class MilestoneSingleChoice extends Select2Choice<Milestone> {
public MilestoneSingleChoice(String id, IModel<Milestone> selectionModel, IModel<Collection<Milestone>> milestonesModel) {
super(id, selectionModel, new MilestoneChoiceProvider(milestonesModel));
}
@Override
protected void onInitialize() {
super.onInitialize();
getSettings().setAllowClear(!isRequired());
if (isRequired())
getSettings().setPlaceholder("Choose milestone...");
else
getSettings().setPlaceholder("Not specified");
getSettings().setFormatResult("onedev.server.milestoneChoiceFormatter.formatResult");
getSettings().setFormatSelection("onedev.server.milestoneChoiceFormatter.formatSelection");
getSettings().setEscapeMarkup("onedev.server.milestoneChoiceFormatter.escapeMarkup");
setConvertEmptyInputStringToNull(true);
}
@Override
public void renderHead(IHeaderResponse response) {
super.renderHead(response);
response.render(JavaScriptHeaderItem.forReference(new MilestoneChoiceResourceReference()));
}
}

View File

@ -1,3 +0,0 @@
.select2-results .milestone .label {
margin-left: 6px;
}

View File

@ -1,14 +0,0 @@
onedev.server.milestoneChoiceFormatter = {
formatSelection: function(milestone) {
return milestone.name;
},
formatResult: function(milestone) {
return "<span class='milestone'>" + milestone.name + " <span class='ml-2 badge " + milestone.statusClass + "'>" + milestone.statusName + "</span></span>";
},
escapeMarkup: function(m) {
return m;
},
};

View File

@ -1,4 +1,4 @@
package io.onedev.server.web.editable.milestonechoice;
package io.onedev.server.web.editable.iterationchoice;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Method;
@ -16,16 +16,16 @@ import io.onedev.server.web.editable.PropertyContext;
import io.onedev.server.web.editable.PropertyDescriptor;
import io.onedev.server.web.editable.PropertyEditor;
import io.onedev.server.web.editable.PropertyViewer;
import io.onedev.server.annotation.MilestoneChoice;
import io.onedev.server.annotation.IterationChoice;
@SuppressWarnings("serial")
public class MilestoneChoiceEditSupport implements EditSupport {
public class IterationChoiceEditSupport implements EditSupport {
@Override
public PropertyContext<?> getEditContext(PropertyDescriptor descriptor) {
Method propertyGetter = descriptor.getPropertyGetter();
MilestoneChoice milestoneChoice = propertyGetter.getAnnotation(MilestoneChoice.class);
if (milestoneChoice != null) {
IterationChoice iterationChoice = propertyGetter.getAnnotation(IterationChoice.class);
if (iterationChoice != null) {
if (List.class.isAssignableFrom(propertyGetter.getReturnType())
&& ReflectionUtils.getCollectionElementClass(propertyGetter.getGenericReturnType()) == String.class) {
return new PropertyContext<List<String>>(descriptor) {
@ -36,9 +36,9 @@ public class MilestoneChoiceEditSupport implements EditSupport {
@Override
protected Component newContent(String id, PropertyDescriptor propertyDescriptor) {
List<String> milestoneNames = model.getObject();
if (milestoneNames != null && !milestoneNames.isEmpty()) {
return new Label(id, StringUtils.join(milestoneNames, ", " ));
List<String> iterationNames = model.getObject();
if (iterationNames != null && !iterationNames.isEmpty()) {
return new Label(id, StringUtils.join(iterationNames, ", " ));
} else {
return new EmptyValueLabel(id) {
@ -56,7 +56,7 @@ public class MilestoneChoiceEditSupport implements EditSupport {
@Override
public PropertyEditor<List<String>> renderForEdit(String componentId, IModel<List<String>> model) {
return new MilestoneMultiChoiceEditor(componentId, descriptor, model);
return new IterationMultiChoiceEditor(componentId, descriptor, model);
}
};
@ -88,12 +88,12 @@ public class MilestoneChoiceEditSupport implements EditSupport {
@Override
public PropertyEditor<String> renderForEdit(String componentId, IModel<String> model) {
return new MilestoneSingleChoiceEditor(componentId, descriptor, model);
return new IterationSingleChoiceEditor(componentId, descriptor, model);
}
};
} else {
throw new RuntimeException("Annotation 'MilestoneChoice' should be applied to property with type 'String' or 'List<String>'");
throw new RuntimeException("Annotation 'IterationChoice' should be applied to property with type 'String' or 'List<String>'");
}
} else {
return null;

View File

@ -1,36 +1,32 @@
package io.onedev.server.web.editable.milestonechoice;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;
package io.onedev.server.web.editable.iterationchoice;
import com.google.common.base.Preconditions;
import io.onedev.server.annotation.IterationChoice;
import io.onedev.server.model.Iteration;
import io.onedev.server.model.Project;
import io.onedev.server.util.ComponentContext;
import io.onedev.server.util.ReflectionUtils;
import io.onedev.server.web.component.iteration.choice.IterationMultiChoice;
import io.onedev.server.web.editable.PropertyDescriptor;
import io.onedev.server.web.editable.PropertyEditor;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.ajax.form.AjaxFormComponentUpdatingBehavior;
import org.apache.wicket.model.IModel;
import org.apache.wicket.model.Model;
import org.apache.wicket.util.convert.ConversionException;
import com.google.common.base.Preconditions;
import io.onedev.server.OneDev;
import io.onedev.server.entitymanager.MilestoneManager;
import io.onedev.server.model.Milestone;
import io.onedev.server.model.Project;
import io.onedev.server.util.ComponentContext;
import io.onedev.server.util.ReflectionUtils;
import io.onedev.server.web.component.milestone.choice.MilestoneMultiChoice;
import io.onedev.server.web.editable.PropertyDescriptor;
import io.onedev.server.web.editable.PropertyEditor;
import io.onedev.server.annotation.MilestoneChoice;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;
@SuppressWarnings("serial")
public class MilestoneMultiChoiceEditor extends PropertyEditor<List<String>> {
public class IterationMultiChoiceEditor extends PropertyEditor<List<String>> {
private MilestoneMultiChoice input;
private IterationMultiChoice input;
public MilestoneMultiChoiceEditor(String id, PropertyDescriptor propertyDescriptor,
IModel<List<String>> propertyModel) {
public IterationMultiChoiceEditor(String id, PropertyDescriptor propertyDescriptor,
IModel<List<String>> propertyModel) {
super(id, propertyDescriptor, propertyModel);
}
@ -39,34 +35,30 @@ public class MilestoneMultiChoiceEditor extends PropertyEditor<List<String>> {
protected void onInitialize() {
super.onInitialize();
List<Milestone> choices = new ArrayList<>();
List<Milestone> selections = new ArrayList<>();
List<Iteration> choices = new ArrayList<>();
List<Iteration> selections = new ArrayList<>();
ComponentContext componentContext = new ComponentContext(this);
ComponentContext.push(componentContext);
try {
MilestoneChoice milestoneChoice = descriptor.getPropertyGetter().getAnnotation(MilestoneChoice.class);
Preconditions.checkNotNull(milestoneChoice);
if (milestoneChoice.value().length() != 0) {
choices.addAll((List<Milestone>)ReflectionUtils
.invokeStaticMethod(descriptor.getBeanClass(), milestoneChoice.value()));
IterationChoice iterationChoice = descriptor.getPropertyGetter().getAnnotation(IterationChoice.class);
Preconditions.checkNotNull(iterationChoice);
if (iterationChoice.value().length() != 0) {
choices.addAll((List<Iteration>)ReflectionUtils
.invokeStaticMethod(descriptor.getBeanClass(), iterationChoice.value()));
} else if (Project.get() != null) {
choices.addAll(Project.get().getSortedHierarchyMilestones());
choices.addAll(Project.get().getSortedHierarchyIterations());
}
if (getModelObject() != null && Project.get() != null) {
MilestoneManager milestoneManager = OneDev.getInstance(MilestoneManager.class);
for (String milestoneName: getModelObject()) {
Milestone milestone = milestoneManager.findInHierarchy(Project.get(), milestoneName);
if (milestone != null && choices.contains(milestone))
selections.add(milestone);
}
}
for (var choice: choices) {
if (getModelObject() != null && getModelObject().contains(choice.getName()))
selections.add(choice);
}
} finally {
ComponentContext.pop();
}
input = new MilestoneMultiChoice("input", Model.of(selections), Model.of(choices)) {
input = new IterationMultiChoice("input", Model.of(selections), Model.of(choices)) {
@Override
protected void onInitialize() {
@ -92,9 +84,9 @@ public class MilestoneMultiChoiceEditor extends PropertyEditor<List<String>> {
@Override
protected List<String> convertInputToValue() throws ConversionException {
Collection<Milestone> milestones = input.getConvertedInput();
if (milestones != null)
return milestones.stream().map(it->it.getName()).collect(Collectors.toList());
Collection<Iteration> iterations = input.getConvertedInput();
if (iterations != null)
return iterations.stream().map(it->it.getName()).collect(Collectors.toList());
else
return new ArrayList<>();
}

View File

@ -1,32 +1,30 @@
package io.onedev.server.web.editable.milestonechoice;
import java.util.ArrayList;
import java.util.List;
package io.onedev.server.web.editable.iterationchoice;
import com.google.common.base.Preconditions;
import io.onedev.server.annotation.IterationChoice;
import io.onedev.server.model.Iteration;
import io.onedev.server.model.Project;
import io.onedev.server.util.ComponentContext;
import io.onedev.server.util.ReflectionUtils;
import io.onedev.server.web.component.iteration.choice.IterationSingleChoice;
import io.onedev.server.web.editable.PropertyDescriptor;
import io.onedev.server.web.editable.PropertyEditor;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.ajax.form.AjaxFormComponentUpdatingBehavior;
import org.apache.wicket.model.IModel;
import org.apache.wicket.model.Model;
import org.apache.wicket.util.convert.ConversionException;
import com.google.common.base.Preconditions;
import io.onedev.server.model.Milestone;
import io.onedev.server.model.Project;
import io.onedev.server.util.ComponentContext;
import io.onedev.server.util.ReflectionUtils;
import io.onedev.server.web.component.milestone.choice.MilestoneSingleChoice;
import io.onedev.server.web.editable.PropertyDescriptor;
import io.onedev.server.web.editable.PropertyEditor;
import io.onedev.server.annotation.MilestoneChoice;
import java.util.ArrayList;
import java.util.List;
@SuppressWarnings("serial")
public class MilestoneSingleChoiceEditor extends PropertyEditor<String> {
public class IterationSingleChoiceEditor extends PropertyEditor<String> {
private MilestoneSingleChoice input;
private IterationSingleChoice input;
public MilestoneSingleChoiceEditor(String id, PropertyDescriptor propertyDescriptor,
IModel<String> propertyModel) {
public IterationSingleChoiceEditor(String id, PropertyDescriptor propertyDescriptor,
IModel<String> propertyModel) {
super(id, propertyDescriptor, propertyModel);
}
@ -35,33 +33,32 @@ public class MilestoneSingleChoiceEditor extends PropertyEditor<String> {
protected void onInitialize() {
super.onInitialize();
List<Milestone> choices = new ArrayList<>();
Milestone selection;
List<Iteration> choices = new ArrayList<>();
Iteration selection = null;
ComponentContext componentContext = new ComponentContext(this);
ComponentContext.push(componentContext);
try {
MilestoneChoice milestoneChoice = descriptor.getPropertyGetter().getAnnotation(MilestoneChoice.class);
Preconditions.checkNotNull(milestoneChoice);
if (milestoneChoice.value().length() != 0) {
choices.addAll((List<Milestone>)ReflectionUtils
.invokeStaticMethod(descriptor.getBeanClass(), milestoneChoice.value()));
IterationChoice iterationChoice = descriptor.getPropertyGetter().getAnnotation(IterationChoice.class);
Preconditions.checkNotNull(iterationChoice);
if (iterationChoice.value().length() != 0) {
choices.addAll((List<Iteration>)ReflectionUtils
.invokeStaticMethod(descriptor.getBeanClass(), iterationChoice.value()));
} else if (Project.get() != null) {
choices.addAll(Project.get().getSortedHierarchyMilestones());
choices.addAll(Project.get().getSortedHierarchyIterations());
}
for (var choice: choices) {
if (choice.getName().equals(getModelObject())) {
selection = choice;
break;
}
}
if (Project.get() != null && getModelObject() != null)
selection = Project.get().getHierarchyMilestone(getModelObject());
else
selection = null;
if (selection != null && !choices.contains(selection))
selection = null;
} finally {
ComponentContext.pop();
}
input = new MilestoneSingleChoice("input", Model.of(selection), Model.of(choices)) {
input = new IterationSingleChoice("input", Model.of(selection), Model.of(choices)) {
@Override
protected void onInitialize() {
@ -87,9 +84,9 @@ public class MilestoneSingleChoiceEditor extends PropertyEditor<String> {
@Override
protected String convertInputToValue() throws ConversionException {
Milestone milestone = input.getConvertedInput();
if (milestone != null)
return milestone.getName();
Iteration iteration = input.getConvertedInput();
if (iteration != null)
return iteration.getName();
else
return null;
}

View File

@ -97,7 +97,7 @@ import io.onedev.server.web.page.project.issues.create.NewIssuePage;
import io.onedev.server.web.page.project.issues.detail.*;
import io.onedev.server.web.page.project.issues.imports.IssueImportPage;
import io.onedev.server.web.page.project.issues.list.ProjectIssueListPage;
import io.onedev.server.web.page.project.issues.milestones.*;
import io.onedev.server.web.page.project.issues.iteration.*;
import io.onedev.server.web.page.project.packs.ProjectPacksPage;
import io.onedev.server.web.page.project.packs.detail.PackDetailPage;
import io.onedev.server.web.page.project.pullrequests.InvalidPullRequestPage;
@ -344,11 +344,11 @@ public class BaseUrlMapper extends CompoundRequestMapper {
add(new ProjectPageMapper("${project}/~issues/${issue}/authorizations", IssueAuthorizationsPage.class));
add(new ProjectPageMapper("${project}/~issues/new", NewIssuePage.class));
add(new ProjectPageMapper("${project}/~issues/import/${importer}", IssueImportPage.class));
add(new ProjectPageMapper("${project}/~milestones", MilestoneListPage.class));
add(new ProjectPageMapper("${project}/~milestones/${milestone}", MilestoneIssuesPage.class));
add(new ProjectPageMapper("${project}/~milestones/${milestone}/burndown", MilestoneBurndownPage.class));
add(new ProjectPageMapper("${project}/~milestones/${milestone}/edit", MilestoneEditPage.class));
add(new ProjectPageMapper("${project}/~milestones/new", NewMilestonePage.class));
add(new ProjectPageMapper("${project}/~iterations", IterationListPage.class));
add(new ProjectPageMapper("${project}/~iterations/${iteration}", IterationIssuesPage.class));
add(new ProjectPageMapper("${project}/~iterations/${iteration}/burndown", IterationBurndownPage.class));
add(new ProjectPageMapper("${project}/~iterations/${iteration}/edit", IterationEditPage.class));
add(new ProjectPageMapper("${project}/~iterations/new", NewIterationPage.class));
add(new ProjectPageMapper("${project}/~builds", ProjectBuildsPage.class));
add(new ProjectPageMapper("${project}/~builds/${build}", BuildDashboardPage.class));

View File

@ -48,12 +48,6 @@
display: none;
}
.milestone-type-management .drag-indicator,
.milestone-type-management th.property-id,
.milestone-type-management td.property-id {
display: none;
}
.branding-setting .logo-preview>div {
background-color: white !important;
}

View File

@ -12,7 +12,7 @@
<tbody>
<tr wicket:id="methods" class="method">
<td>
<a wicket:id="link" class="title`"><span wicket:id="label"></span></a>
<a wicket:id="link" class="title"><span wicket:id="label"></span></a>
</td>
<td wicket:id="httpMethod"></td>
<td wicket:id="path"></td>

Some files were not shown because too many files have changed in this diff Show More