Merge "Chip 'passed' and 'duplicate' navigate to 'All' task in primary tab" into stable-3.5
diff --git a/src/main/java/com/googlesource/gerrit/plugins/task/TaskPluginDefinedInfoFactory.java b/src/main/java/com/googlesource/gerrit/plugins/task/TaskPluginDefinedInfoFactory.java
index 2e3dfa8..7cb083f 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/task/TaskPluginDefinedInfoFactory.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/task/TaskPluginDefinedInfoFactory.java
@@ -175,17 +175,24 @@
public Node node;
protected Task task;
protected TaskAttribute attribute;
+ protected boolean isDuplicate;
protected AttributeFactory(Node node) {
this.node = node;
this.task = node.task;
attribute = new TaskAttribute(task.name());
+
+ isDuplicate =
+ node.isDuplicate
+ || (node.isChange()
+ && node.getNodeSetByBaseTasksFactory().get(task.subSection).contains(node.key()));
+
if (options.includeStatistics) {
statistics.numberOfNodes++;
if (node.isChange()) {
statistics.numberOfChangeNodes++;
}
- if (node.isDuplicate) {
+ if (isDuplicate) {
statistics.numberOfDuplicates++;
}
attribute.statistics = new TaskAttribute.Statistics();
@@ -215,8 +222,8 @@
if (node.isChange()) {
attribute.change = node.getChangeData().getId().get();
}
- attribute.hasPass = !node.isDuplicate && (task.pass != null || task.fail != null);
- if (!node.isDuplicate) {
+ attribute.hasPass = !(isDuplicate || isAllNull(task.pass, task.fail));
+ if (!isDuplicate) {
attribute.subTasks = getSubTasks();
}
attribute.status = getStatus();
@@ -239,7 +246,7 @@
if (!options.onlyApplicable) {
attribute.applicable = applicable;
}
- if (!node.isDuplicate) {
+ if (!isDuplicate) {
if (task.inProgress != null) {
attribute.inProgress = node.matchOrNull(task.inProgress);
}
@@ -251,6 +258,9 @@
attribute.evaluationMilliSeconds = millis() - attribute.evaluationMilliSeconds;
}
addStatistics(attribute.statistics);
+ if (node.isChange()) {
+ node.getNodeSetByBaseTasksFactory().get(task.subSection).add(node.key());
+ }
return Optional.of(attribute);
}
}
@@ -284,7 +294,7 @@
}
protected Status getStatusWithExceptions() throws StorageException, QueryParseException {
- if (node.isDuplicate) {
+ if (isDuplicate) {
return Status.DUPLICATE;
}
if (isAllNull(task.pass, task.fail, attribute.subTasks)) {
diff --git a/src/main/java/com/googlesource/gerrit/plugins/task/TaskTree.java b/src/main/java/com/googlesource/gerrit/plugins/task/TaskTree.java
index d7e4b7d..4785885 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/task/TaskTree.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/task/TaskTree.java
@@ -89,6 +89,32 @@
public transient int summaryCount;
}
+ public class NodeSet {
+ protected ChangeData changeData;
+ protected Set<String> nodeKeys = new HashSet<>();
+
+ public NodeSet() {
+ changeData = TaskTree.this.changeData;
+ }
+
+ public boolean contains(String nodeKey) {
+ clearIfNeeded();
+ return nodeKeys.contains(nodeKey);
+ }
+
+ public boolean add(String nodeKey) {
+ clearIfNeeded();
+ return nodeKeys.add(nodeKey);
+ }
+
+ protected void clearIfNeeded() {
+ if (changeData != TaskTree.this.changeData) {
+ nodeKeys.clear();
+ changeData = TaskTree.this.changeData;
+ }
+ }
+ }
+
protected static final String TASK_DIR = "task";
protected final AccountResolver accountResolver;
@@ -105,6 +131,7 @@
protected final Provider<ChangeQueryProcessor> changeQueryProcessorProvider;
protected final StatisticsMap<String, List<ChangeData>> changesByNamesFactoryQuery =
new HitHashMap<>();
+ protected final Map<SubSectionKey, NodeSet> nodeSetByBaseTasksFactory = new HashMap<>();
protected final StatisticsMap<SubSectionKey, List<Task>> definitionsBySubSection =
new HitHashMapOfCollection<>();
protected final StatisticsMap<SubSectionKey, Map<BranchNameKey, List<Task>>>
@@ -144,6 +171,7 @@
this.changeData = changeData;
root.path = Collections.emptyList();
root.duplicateKeys = Collections.emptyList();
+ nodeSetByBaseTasksFactory.clear();
return root.getSubNodes();
}
@@ -171,6 +199,10 @@
return TaskTree.this.changeData;
}
+ protected Map<SubSectionKey, NodeSet> getNodeSetByBaseTasksFactory() {
+ return TaskTree.this.nodeSetByBaseTasksFactory;
+ }
+
protected boolean isTrusted() {
return true;
}
@@ -190,7 +222,8 @@
return createFromPreloaded(def, (parent, definition) -> new Node(parent, definition));
}
- public Node createFromPreloaded(Task def, ChangeData changeData) {
+ public Node createFromPreloaded(
+ Task def, ChangeData changeData, Map<SubSectionKey, NodeSet> nodeSetByBaseTasksFactory) {
return createFromPreloaded(
def,
(parent, definition) ->
@@ -204,6 +237,11 @@
public boolean isChange() {
return true;
}
+
+ @Override
+ protected Map<SubSectionKey, NodeSet> getNodeSetByBaseTasksFactory() {
+ return nodeSetByBaseTasksFactory;
+ }
});
}
@@ -338,6 +376,20 @@
}
@Override
+ protected Map<SubSectionKey, NodeSet> getNodeSetByBaseTasksFactory() {
+ return parent.getNodeSetByBaseTasksFactory();
+ }
+
+ protected Map<SubSectionKey, NodeSet> getNodeSetByBaseTasksFactory(SubSectionKey subSection) {
+ Map<SubSectionKey, NodeSet> nodeSetByBaseTasksFactory = getNodeSetByBaseTasksFactory();
+ if (!nodeSetByBaseTasksFactory.containsKey(subSection)) {
+ nodeSetByBaseTasksFactory = new HashMap<>(nodeSetByBaseTasksFactory);
+ nodeSetByBaseTasksFactory.put(subSection, new NodeSet());
+ }
+ return nodeSetByBaseTasksFactory;
+ }
+
+ @Override
protected boolean isTrusted() {
return parent.isTrusted() && !task.isMasqueraded;
}
@@ -473,11 +525,14 @@
throws IOException {
try {
if (namesFactory.changes != null) {
+ Map<SubSectionKey, NodeSet> nodeSetByBaseTasksFactory =
+ getNodeSetByBaseTasksFactory(tasksFactory.subSection);
for (ChangeData changeData : query(namesFactory.changes, task.isVisible)) {
addPreloaded(
preloader.preload(
task.config.new Task(tasksFactory, changeData.getId().toString())),
- changeData);
+ changeData,
+ nodeSetByBaseTasksFactory);
}
return;
}
@@ -519,8 +574,9 @@
nodes.addAll(factory.createFromPreloaded(defs));
}
- public void addPreloaded(Task def, ChangeData changeData) {
- nodes.add(factory.createFromPreloaded(def, changeData));
+ public void addPreloaded(
+ Task def, ChangeData changeData, Map<SubSectionKey, NodeSet> nodeSetByBaseTasksFactory) {
+ nodes.add(factory.createFromPreloaded(def, changeData, nodeSetByBaseTasksFactory));
}
public void addPreloaded(Task def) {
diff --git a/src/main/resources/Documentation/task.md b/src/main/resources/Documentation/task.md
index 4cf8c71..931b3f9 100644
--- a/src/main/resources/Documentation/task.md
+++ b/src/main/resources/Documentation/task.md
@@ -57,10 +57,17 @@
A task with a `READY` status is ready to be executed. All of its subtasks are
in the `PASS` state.
-A task with a `DUPLICATE` status has the same task key as one of its ancestors.
+A task can have `DUPLICATE` status in one of the following scenarios:
+
+- It has the same task key as one of its ancestors
+- If the task is generated using a task-factory with change type names-factory,
+ and it has the same task key as another change task descendant of the same
+ ancestor task-factory
+
Task keys are generally made up of the canonical task name and the change to
-which it applies. To avoid infinite loops, subtasks are ignored on duplicate
-tasks.
+which it applies. To avoid infinite loops, and to potentially reduce needless
+duplication, subtasks are ignored on duplicate tasks.
+Also see [duplicate-key](#duplicate-key).
A task with a `PASS` status meets all the criteria for `READY`, and has
executed and was successful.
@@ -238,6 +245,8 @@
subtasks-file = common.config # references the file named task/common.config
```
+<a id="duplicate-key"></a>
+
`duplicate-key`
: This key defines an identifier to help identify tasks which should be
diff --git a/src/main/resources/Documentation/test/task_states.md b/src/main/resources/Documentation/test/task_states.md
index 2e21fa8..aea074e 100644
--- a/src/main/resources/Documentation/test/task_states.md
+++ b/src/main/resources/Documentation/test/task_states.md
@@ -2284,60 +2284,10 @@
{
"applicable" : true,
"change" : _change2_number,
- "hasPass" : true,
+ "hasPass" : false,
+ "hint" : "Duplicate task is non blocking and empty to break the loop",
"name" : "_change2_number",
- "status" : "FAIL",
- "subTasks" : [
- {
- "applicable" : true,
- "hasPass" : false,
- "name" : "task (tasks-factory changes loop)",
- "status" : "WAITING",
- "subTasks" : [
- {
- "applicable" : true,
- "change" : _change1_number,
- "hasPass" : true,
- "name" : "_change1_number",
- "status" : "FAIL",
- "subTasks" : [
- {
- "applicable" : true,
- "hasPass" : false,
- "name" : "task (tasks-factory changes loop)",
- "status" : "PASS",
- "subTasks" : [
- {
- "applicable" : true,
- "change" : _change1_number,
- "hasPass" : false,
- "hint" : "Duplicate task is non blocking and empty to break the loop",
- "name" : "_change1_number",
- "status" : "DUPLICATE"
- },
- {
- "applicable" : true,
- "change" : _change2_number,
- "hasPass" : false,
- "hint" : "Duplicate task is non blocking and empty to break the loop",
- "name" : "_change2_number",
- "status" : "DUPLICATE"
- }
- ]
- }
- ]
- },
- {
- "applicable" : true,
- "change" : _change2_number,
- "hasPass" : false,
- "hint" : "Duplicate task is non blocking and empty to break the loop",
- "name" : "_change2_number",
- "status" : "DUPLICATE"
- }
- ]
- }
- ]
+ "status" : "DUPLICATE"
}
]
}