Repository: obsidian-tasks-group/obsidian-tasks
Pull request #917 (2022-07-22 20:51:58)

feat: Add 'group by recurrence' & 'group by recurring'
<!--- Provide a general summary of your changes in the Title above -->

## Description

Add two new grouping commands:

- `group by recurrence`
- `group by recurring`
- update the Grouping docs page
- update the Quick Reference docs page

From the new docs:

1. `recurring`
    * Whether the task is recurring: either `Recurring` or `Not Recurring`.
1. `recurrence`
    * The recurrence rule of the task, for example `every week on Sunday`, or `None` for non-recurring tasks.
    * Note that the text displayed is generated programmatically and standardised, and so may not exactly match the text in any manually typed tasks. For example, a task with `🔁 every Sunday` is grouped in `every week on Sunday`.

## Motivation and Context

I have found these useful in my fork of this project, so am making them available to others.

<!--- Why is this change required? What problem does it solve? -->
<!--- If it fixes an open issue, please link to the issue here. -->

## How has this been tested?
<!--- Please describe in detail how you tested your changes. -->
<!--- Include details of your testing environment, tests ran to see how -->
<!--- your change affects other areas of the code, etc. -->

- Via new unit tests
- At length in my main vault

## Screenshots (if appropriate):

## Types of changes
<!--- What types of changes does your code introduce? Put an `x` in all the boxes that apply: -->
- [ ] Bug fix (non-breaking change which fixes an issue)
- [ ] Refactor (non-breaking change which only improves the design or structure of existing code, and making no changes to its external behaviour)
- [x] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)

## Checklist:
<!--- Go over all the following points, and put an `x` in all the boxes that apply. -->
<!--- If you're unsure about any of these, don't hesitate to ask. We're here to help! -->
- [x] My code follows the code style of this project and passes `yarn run lint`.
- [x] My change requires a change to the documentation.
- [x] I have updated the documentation accordingly.
- [x] My change has adequate Unit Test coverage.

By creating a Pull Request you agree to our [Code of Conduct](https://github.com/obsidian-tasks-group/obsidian-tasks/blob/main/CODE_OF_CONDUCT.md). For further guidance on contributing please see [contributing guide](https://github.com/obsidian-tasks-group/obsidian-tasks/blob/main/CONTRIBUTING.md)



Patch:
diff --git a/docs/queries/grouping.md b/docs/queries/grouping.md
index 399f2759..2a3b3c3e 100644
--- a/docs/queries/grouping.md
+++ b/docs/queries/grouping.md
@@ -57,12 +57,19 @@ Task properties:
     * The due date of the task, including the week-day, or `No due date`.
 1. `done`
     * The done date of the task, including the week-day, or `No done date`.
+1. `recurring`
+    * Whether the task is recurring: either `Recurring` or `Not Recurring`.
+1. `recurrence`
+    * The recurrence rule of the task, for example `every week on Sunday`, or `None` for non-recurring tasks.
+    * Note that the text displayed is generated programmatically and standardised, and so may not exactly match the text in any manually typed tasks. For example, a task with `🔁 every Sunday` is grouped in `every week on Sunday`.
 1. `tags`
     * The tags of the tasks or `(No tags)`. If the task has multiple tags, it will show up under every tag.
 
 > `start`, `scheduled`, `due` and `done` grouping options were introduced in Tasks 1.7.0.
 >
 > `tags` grouping option was introduced in Tasks 1.10.0.
+>
+> `recurring` and `recurrence` grouping options were introduced in Tasks 1.11.0.
 
 ### Multiple groups
 
diff --git a/docs/quick-reference/index.md b/docs/quick-reference/index.md
index 3258f467..71ce29aa 100644
--- a/docs/quick-reference/index.md
+++ b/docs/quick-reference/index.md
@@ -14,33 +14,34 @@ has_toc: false
 
 This table summarizes the filters and other options available inside a `tasks` block.
 
-| [Filters][1]                                                                           | [Sort][2]                                   |  [Group][3]         | [Display][4]           |
-| ---------------------------------------------------------------------------------------| ------------------------------------------- | --------------------| -----------------------|
-| `done`<br>`not done`                                                                   | `sort by status`                            | `group by status`   |                        |
-| `done (before, after, on) <date>`<br>`has done date`<br>`no  done date`                | `sort by done`                              | `group by done`     | `hide done date`       |
-| `starts (before, after, on) <date>`<br>`has start date`<br>`no  start date`            | `sort by start`                             | `group by start`    | `hide start date`      |
-| `scheduled (before, after, on) <date>`<br>`has scheduled date`<br>`no  scheduled date` | `sort by scheduled`                         | `group by scheduled`| `hide scheduled date`  |
-| `due (before, after, on) <date>`<br>`has due date`<br>`no  due date`                   | `sort by due`                               | `group by due`      | `hide due date`        |
-| `happens (before, after, on) <date>`<br>`has happens date`<br>`no  happens date`       |                                             |                     |                        |
-| `is recurring`<br>`is not recurring`                                                   |                                             |                     | `hide recurrence rule` |
-| `priority is (above, below)? (low, none, medium, high)`                                | `sort by priority`                          |                     | `hide priority`        |
-|                                                                                        | `sort by urgency`                           |                     |                        |
-| `path (includes, does not include) <path>`                                             | `sort by path`                              | `group by path`     |                        |
-|                                                                                        |                                             | `group by folder`   |                        |
-|                                                                                        |                                             | `group by filename` |                        |
-| `heading (includes, does not include) <string>`                                        |                                             | `group by heading`  |                        |
-|                                                                                        |                                             | `group by backlink` | `hide backlink`        |
-| `description (includes, does not include) <string>`                                    | `sort by description`                       |                     |                        |
-| `tag (includes, does not include) <tag>`<br>`tags (include, do not include) <tag>`     | `sort by tag`<br>`sort by tag <tag_number>` | `group by tags`     |                        |
-| `(filter 1) AND (filter 2)`                                                            |                                             |                     |                        |
-| `(filter 1) OR (filter 2)`                                                             |                                             |                     |                        |
-| `NOT (filter 1)`                                                                       |                                             |                     |                        |
-| `(filter 1) XOR (filter 2)`                                                            |                                             |                     |                        |
-| `(filter 1) AND NOT (filter 2)`                                                        |                                             |                     |                        |
-| `(filter 1) OR NOT (filter 2)`                                                         |                                             |                     |                        |
-| `(filter 1) AND ((filter 2) OR (filter 3))`                                            |                                             |                     |                        |
-| `exclude sub-items`                                                                    |                                             |                     |                        |
-| `limit to <number> tasks`<br>`limit <number>`                                          |                                             |                     |                        |
+| [Filters][1]                                                                           | [Sort][2]                                   | [Group][3]            | [Display][4]           |
+| -------------------------------------------------------------------------------------- | ------------------------------------------- | --------------------- | ---------------------- |
+| `done`<br>`not done`                                                                   | `sort by status`                            | `group by status`     |                        |
+| `done (before, after, on) <date>`<br>`has done date`<br>`no  done date`                | `sort by done`                              | `group by done`       | `hide done date`       |
+| `starts (before, after, on) <date>`<br>`has start date`<br>`no  start date`            | `sort by start`                             | `group by start`      | `hide start date`      |
+| `scheduled (before, after, on) <date>`<br>`has scheduled date`<br>`no  scheduled date` | `sort by scheduled`                         | `group by scheduled`  | `hide scheduled date`  |
+| `due (before, after, on) <date>`<br>`has due date`<br>`no  due date`                   | `sort by due`                               | `group by due`        | `hide due date`        |
+| `happens (before, after, on) <date>`<br>`has happens date`<br>`no  happens date`       |                                             |                       |                        |
+| `is recurring`<br>`is not recurring`                                                   |                                             | `group by recurring`  |                        |
+|                                                                                        |                                             | `group by recurrence` | `hide recurrence rule` |
+| `priority is (above, below)? (low, none, medium, high)`                                | `sort by priority`                          |                       | `hide priority`        |
+|                                                                                        | `sort by urgency`                           |                       |                        |
+| `path (includes, does not include) <path>`                                             | `sort by path`                              | `group by path`       |                        |
+|                                                                                        |                                             | `group by folder`     |                        |
+|                                                                                        |                                             | `group by filename`   |                        |
+| `heading (includes, does not include) <string>`                                        |                                             | `group by heading`    |                        |
+|                                                                                        |                                             | `group by backlink`   | `hide backlink`        |
+| `description (includes, does not include) <string>`                                    | `sort by description`                       |                       |                        |
+| `tag (includes, does not include) <tag>`<br>`tags (include, do not include) <tag>`     | `sort by tag`<br>`sort by tag <tag_number>` | `group by tags`       |                        |
+| `(filter 1) AND (filter 2)`                                                            |                                             |                       |                        |
+| `(filter 1) OR (filter 2)`                                                             |                                             |                       |                        |
+| `NOT (filter 1)`                                                                       |                                             |                       |                        |
+| `(filter 1) XOR (filter 2)`                                                            |                                             |                       |                        |
+| `(filter 1) AND NOT (filter 2)`                                                        |                                             |                       |                        |
+| `(filter 1) OR NOT (filter 2)`                                                         |                                             |                       |                        |
+| `(filter 1) AND ((filter 2) OR (filter 3))`                                            |                                             |                       |                        |
+| `exclude sub-items`                                                                    |                                             |                       |                        |
+| `limit to <number> tasks`<br>`limit <number>`                                          |                                             |                       |                        |
 
 Other layout options:
 
diff --git a/src/Query.ts b/src/Query.ts
index d87ebeb2..5be70217 100644
--- a/src/Query.ts
+++ b/src/Query.ts
@@ -32,6 +32,8 @@ export type GroupingProperty =
     | 'folder'
     | 'heading'
     | 'path'
+    | 'recurrence'
+    | 'recurring'
     | 'scheduled'
     | 'start'
     | 'status'
@@ -54,7 +56,7 @@ export class Query implements IQuery {
         /^sort by (urgency|status|priority|start|scheduled|due|done|path|description|tag)( reverse)?[\s]*(\d+)?/;
 
     private readonly groupByRegexp =
-        /^group by (backlink|done|due|filename|folder|heading|path|scheduled|start|status|tags)/;
+        /^group by (backlink|done|due|filename|folder|heading|path|recurrence|recurring|scheduled|start|status|tags)/;
 
     private readonly hideOptionsRegexp =
         /^hide (task count|backlink|priority|start date|scheduled date|done date|due date|recurrence rule|edit button)/;
diff --git a/src/Query/Group.ts b/src/Query/Group.ts
index 4283ce0c..9f83c5e6 100644
--- a/src/Query/Group.ts
+++ b/src/Query/Group.ts
@@ -43,12 +43,30 @@ export class Group {
         folder: Group.groupByFolder,
         heading: Group.groupByHeading,
         path: Group.groupByPath,
+        recurrence: Group.groupByRecurrence,
+        recurring: Group.groupByRecurring,
         scheduled: Group.groupByScheduledDate,
         start: Group.groupByStartDate,
         status: Group.groupByStatus,
         tags: Group.groupByTags,
     };
 
+    private static groupByRecurrence(task: Task): string[] {
+        if (task.recurrence !== null) {
+            return [task.recurrence!.toText()];
+        } else {
+            return ['None'];
+        }
+    }
+
+    private static groupByRecurring(task: Task): string[] {
+        if (task.recurrence !== null) {
+            return ['Recurring'];
+        } else {
+            return ['Not Recurring'];
+        }
+    }
+
     private static groupByStartDate(task: Task): string[] {
         return [Group.stringFromDate(task.startDate, 'start')];
     }


Test patch:
diff --git a/tests/Group.test.ts b/tests/Group.test.ts
index 2006d062..0c921afb 100644
--- a/tests/Group.test.ts
+++ b/tests/Group.test.ts
@@ -329,6 +329,42 @@ describe('Group names', () => {
             expectedGroupNames: ['a/b/c'],
         },
 
+        // -----------------------------------------------------------
+        // group by recurrence
+        {
+            groupBy: 'recurrence',
+            taskLine: '- [ ] a',
+            expectedGroupNames: ['None'],
+        },
+        {
+            groupBy: 'recurrence',
+            taskLine: '- [ ] a 🔁 every Sunday',
+            expectedGroupNames: ['every week on Sunday'],
+        },
+        {
+            groupBy: 'recurrence',
+            taskLine: '- [ ] a 🔁 every Sunday when done',
+            expectedGroupNames: ['every week on Sunday when done'],
+        },
+        {
+            groupBy: 'recurrence',
+            taskLine: '- [ ] a 🔁 every 6 months on the 2nd Wednesday',
+            expectedGroupNames: ['every 6 months on the 2nd Wednesday'],
+        },
+
+        // -----------------------------------------------------------
+        // group by recurring
+        {
+            groupBy: 'recurring',
+            taskLine: '- [ ] a',
+            expectedGroupNames: ['Not Recurring'],
+        },
+        {
+            groupBy: 'recurring',
+            taskLine: '- [ ] a 🔁 every Sunday',
+            expectedGroupNames: ['Recurring'],
+        },
+
         // -----------------------------------------------------------
         // group by scheduled
         {
diff --git a/tests/Query.test.ts b/tests/Query.test.ts
index cf595449..c5b1cf02 100644
--- a/tests/Query.test.ts
+++ b/tests/Query.test.ts
@@ -166,6 +166,8 @@ describe('Query parsing', () => {
             'group by folder',
             'group by heading',
             'group by path',
+            'group by recurrence',
+            'group by recurring',
             'group by scheduled',
             'group by start',
             'group by status',