Repository: obsidian-tasks-group/obsidian-tasks
Pull request #926 (2022-07-23 21:35:44)

feat: Add 'group by root'
<!--- Provide a general summary of your changes in the Title above -->

## Description
Add new `root`  grouping command:

- `group by root`
- update the Grouping docs page
- update the Quick Reference docs page

This groups by the top-level folder of the file that contains the task, that is, the first directory in the path, which will be `/` for files in root of the vault.


## Motivation and Context

I have found this useful in my fork of this project, so am making it available to others.

## How has this been tested?

- Via new unit tests
- At length in my main vault

<!--- 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. -->

## 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 ec77d086..10275921 100644
--- a/docs/queries/grouping.md
+++ b/docs/queries/grouping.md
@@ -35,10 +35,13 @@ You can group by the following properties:
 File locations:
 
 1. `path` (the path to the file that contains the task, that is, the folder and the filename)
+1. `root` (the top-level folder of the file that contains the task, that is, the first directory in the path, which will be `/` for files in root of the vault)
 1. `folder` (the folder to the file that contains the task, which will be `/` for files in root of the vault)
 1. `filename` (the filename of the file that contains the task, without the `.md` extension)
     * Note that tasks from different notes with the same file name will be grouped together in the same group.
 
+> `root` grouping option was introduced in Tasks 1.11.0.
+
 File contents:
 
 1. `backlink` (the text that would be shown in the task's backlink, combining the task's file name and heading, but with no link added)
@@ -57,6 +60,8 @@ Task date properties:
 1. `happens`
     * The earliest of start date, scheduled date, and due date, including the week-day, or `No happens date` if none of those are set.
 
+> `happens` grouping option was introduced in Tasks 1.11.0.
+
 Task properties - other:
 
 1. `status` (Done or Todo, which is capitalized for visibility in the headings)
@@ -80,7 +85,7 @@ Task properties - other:
 >
 > `tags` grouping option was introduced in Tasks 1.10.0.
 >
-> `recurring` and `recurrence` grouping options were introduced in Tasks 1.11.0.
+> `priority`, `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 797ffeef..587d346f 100644
--- a/docs/quick-reference/index.md
+++ b/docs/quick-reference/index.md
@@ -27,6 +27,7 @@ This table summarizes the filters and other options available inside a `tasks` b
 | `priority is (above, below)? (low, none, medium, high)`                                | `sort by priority`                          | `group by priority`   | `hide priority`        |
 |                                                                                        | `sort by urgency`                           |                       |                        |
 | `path (includes, does not include) <path>`                                             | `sort by path`                              | `group by path`       |                        |
+|                                                                                        |                                             | `group by root`       |                        |
 |                                                                                        |                                             | `group by folder`     |                        |
 |                                                                                        |                                             | `group by filename`   |                        |
 | `heading (includes, does not include) <string>`                                        |                                             | `group by heading`    |                        |
diff --git a/src/Query.ts b/src/Query.ts
index 30f1c941..e40d3cdb 100644
--- a/src/Query.ts
+++ b/src/Query.ts
@@ -36,6 +36,7 @@ export type GroupingProperty =
     | 'priority'
     | 'recurrence'
     | 'recurring'
+    | 'root'
     | 'scheduled'
     | 'start'
     | 'status'
@@ -58,7 +59,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|happens|heading|path|priority|recurrence|recurring|scheduled|start|status|tags)/;
+        /^group by (backlink|done|due|filename|folder|happens|heading|path|priority|recurrence|recurring|root|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 9418fba1..3660f9ee 100644
--- a/src/Query/Group.ts
+++ b/src/Query/Group.ts
@@ -49,6 +49,7 @@ export class Group {
         priority: Group.groupByPriority,
         recurrence: Group.groupByRecurrence,
         recurring: Group.groupByRecurring,
+        root: Group.groupByRoot,
         scheduled: Group.groupByScheduledDate,
         start: Group.groupByStartDate,
         status: Group.groupByStatus,
@@ -151,6 +152,15 @@ export class Group {
         return [filename];
     }
 
+    private static groupByRoot(task: Task): string[] {
+        const path = task.path.replace(/\\/g, '/');
+        const separatorIndex = path.indexOf('/');
+        if (separatorIndex == -1) {
+            return ['/'];
+        }
+        return [path.substring(0, separatorIndex + 1)];
+    }
+
     private static groupByBacklink(task: Task): string[] {
         const linkText = task.getLinkText({ isFilenameUnique: true });
         if (linkText === null) {


Test patch:
diff --git a/tests/Group.test.ts b/tests/Group.test.ts
index 18c6fd90..7963edc7 100644
--- a/tests/Group.test.ts
+++ b/tests/Group.test.ts
@@ -432,6 +432,28 @@ describe('Group names', () => {
             expectedGroupNames: ['Recurring'],
         },
 
+        // -----------------------------------------------------------
+        // group by root
+        {
+            groupBy: 'root',
+            taskLine: '- [ ] a',
+            expectedGroupNames: ['a/'],
+            path: 'a/b/c.md',
+        },
+        {
+            groupBy: 'root',
+            taskLine: '- [ ] a',
+            expectedGroupNames: ['a/'],
+            path: 'a\\b\\c.md',
+        },
+        {
+            // file in root of vault:
+            groupBy: 'root',
+            taskLine: '- [ ] a',
+            expectedGroupNames: ['/'],
+            path: 'a.md',
+        },
+
         // -----------------------------------------------------------
         // group by scheduled
         {
diff --git a/tests/Query.test.ts b/tests/Query.test.ts
index d98e0c15..2578ef3a 100644
--- a/tests/Query.test.ts
+++ b/tests/Query.test.ts
@@ -169,7 +169,8 @@ describe('Query parsing', () => {
             'group by path',
             'group by priority',
             'group by recurrence',
-            'group by recurring',
+            'group by recurrence',
+            'group by root',
             'group by scheduled',
             'group by start',
             'group by status',