Do not mark grouping tasks invalid when they become leafs

Remove 'Grouping" tasks (tasks with subtasks but no PASS criteria)
from the output if none of their subtasks are applicable.  i.e.
grouping tasks only really apply if at least one of their subtasks
apply. This ensures that grouping tasks which are not functional
aside from being an organizational tool, do not interfere with
task statuses.

Change-Id: Ib0cb931b74e28fa0b3a7f2d194e796b708698031
diff --git a/src/main/java/com/googlesource/gerrit/plugins/task/TaskAttributeFactory.java b/src/main/java/com/googlesource/gerrit/plugins/task/TaskAttributeFactory.java
index 67e90bc..6857203 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/task/TaskAttributeFactory.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/task/TaskAttributeFactory.java
@@ -139,10 +139,12 @@
         }
         task.subTasks = getSubTasks(c, path, def);
         task.status = getStatus(c, def, task);
-        if (task.status == Status.READY) {
-          task.readyHint = def.readyHint;
+        if (task.status != null) { // task still applies
+          if (task.status == Status.READY) {
+            task.readyHint = def.readyHint;
+          }
+          tasks.add(task);
         }
-        tasks.add(task);
       }
     } catch (QueryParseException e) {
       tasks.add(invalid()); // bad applicability query
@@ -248,9 +250,21 @@
   protected Status getStatusWithExceptions(ChangeData c, Task task, TaskAttribute a)
       throws OrmException, QueryParseException {
     if (task.pass == null && a.subTasks == null) {
-      // A leaf without a PASS criteria is likely a missconfiguration.
+      // A leaf task has no defined subtasks.
+      boolean hasDefinedSubtasks =
+          !(task.subTasks.isEmpty()
+              && task.subTasksFiles.isEmpty()
+              && task.subTasksExternals.isEmpty());
+      if (hasDefinedSubtasks) {
+        // Remove 'Grouping" tasks (tasks with subtasks but no PASS
+        // criteria) from the output if none of their subtasks are
+        // applicable.  i.e. grouping tasks only really apply if at
+        // least one of their subtasks apply.
+        return null;
+      }
+      // A leaf configuration without a PASS criteria is a missconfiguration.
       // Either someone forgot to add subtasks, or they forgot to add
-      // the pass criteria.
+      // the PASS criteria.
       return Status.INVALID;
     }
 
diff --git a/src/main/resources/Documentation/task.md b/src/main/resources/Documentation/task.md
index 993b4ee..3f5c606 100644
--- a/src/main/resources/Documentation/task.md
+++ b/src/main/resources/Documentation/task.md
@@ -108,7 +108,9 @@
 
 : This key defines a query that is used to determine whether a task has
 already executed and passed for each change. This key is mandatory for
-leaf tasks.
+leaf tasks. Tasks with no defined pass criteria and with defined subtasks
+are valid, but they are only applicable when at least one subtask is
+applicable.
 
 Example:
 ```
diff --git a/src/main/resources/Documentation/task_states.md b/src/main/resources/Documentation/task_states.md
index 69b306e..b405c19 100644
--- a/src/main/resources/Documentation/task_states.md
+++ b/src/main/resources/Documentation/task_states.md
@@ -31,6 +31,10 @@
   applicable = is:open
   subtask = Subtask FAIL
 
+[root "Root grouping NA (subtask NA)"]
+  applicable = is:open
+  subtask = Subtask NA
+
 [root "Root READY (subtask PASS)"]
   applicable = is:open
   pass = -is:open
@@ -103,6 +107,9 @@
   applicable = is:open
   pass = is:open
 
+[task "Subtask NA"]
+  applicable = NOT is:open
+
 [external "user special"]
   user = current-user
   file = special.config