Repository: obsidian-tasks-group/obsidian-tasks
Pull request #1944 (2023-05-10 10:39:05)

feat: Add 'group by urgency'
<!--- Provide a general summary of your changes in the Title above -->

# Description

Add new 'group by urgency' instruction.


**Note**: Groups are sorted from low priority to high. This order will be reversed in a future release.

<!--- Describe your changes in detail -->

## Motivation and Context

This fixes https://github.com/obsidian-tasks-group/obsidian-tasks/discussions/788.

<!--- 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?

- Adding unit tests
- Testing in the demo 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)

<img width="612" alt="image" src="https://github.com/obsidian-tasks-group/obsidian-tasks/assets/4840096/e8cf9d88-95ca-4017-a7c6-8dcf0fa34589">

## Types of changes

<!--- What types of changes does your code introduce? Put an `x` in all the boxes that apply: -->

Changes visible to users:

- [x] **New feature** (prefix: `feat` - non-breaking change which adds functionality)
- [x] **Documentation** (prefix: `docs` - improvements to any documentation content **for users**)

Internal changes:

- [x] **Tests** (prefix: `test` - additions and improvements to unit tests and the smoke tests)

## 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](https://publish.obsidian.md/tasks-contributing/Documentation/About+Documentation) accordingly.
- [x] My change has adequate [Unit Test coverage](https://publish.obsidian.md/tasks-contributing/Testing/About+Testing).

## Terms

<!--
By submitting this pull request, you must agree to follow our
[contributing guide](https://publish.obsidian.md/tasks-contributing) and
[Code of Conduct](https://github.com/obsidian-tasks-group/obsidian-tasks/blob/main/CODE_OF_CONDUCT.md).
Put an x in the boxes to confirm you agree.
-->

- [x] My contribution follow this project's [contributing guide](https://publish.obsidian.md/tasks-contributing)
- [x] I agree to follow this project's [Code of Conduct](https://github.com/obsidian-tasks-group/obsidian-tasks/blob/main/CODE_OF_CONDUCT.md)



Patch:
diff --git a/docs/Introduction.md b/docs/Introduction.md
index 16212dce..8041443a 100644
--- a/docs/Introduction.md
+++ b/docs/Introduction.md
@@ -6,6 +6,7 @@ publish: true
 
 ## What's New?
 
+- X.Y.Z: 🔥 Add `group by urgency`: See [[Grouping#Other task properties]]
 - 3.5.0: 🔥 New [[Global Query]] facility.
 - 3.4.0: 🔥 Clicking on a [[Backlinks|Backlink]] jumps to the exact task line.
 - 3.4.0: Tasks now requires at least Obsidian 1.1.1.
diff --git a/docs/Queries/Grouping.md b/docs/Queries/Grouping.md
index 44d4702b..e2f83810 100644
--- a/docs/Queries/Grouping.md
+++ b/docs/Queries/Grouping.md
@@ -81,6 +81,9 @@ For more information, including adding your own customised statuses, see [[Statu
         - `Priority 2: Medium`
         - `Priority 3: None`
         - `Priority 4: Low`
+1. `urgency` ([[Urgency|urgency]])
+    - Currently, the groups run from the lowest urgency to highest.
+    - In a future release, the default group order will become from the highest urgency to lowest.
 1. `recurring`
     - Whether the task is recurring: either `Recurring` or `Not Recurring`.
 1. `recurrence`
@@ -90,9 +93,11 @@ For more information, including adding your own customised statuses, see [[Statu
     - The tags of the tasks or `(No tags)`. If the task has multiple tags, it will show up under every tag.
 
 > [!released]
-`start`, `scheduled`, `due` and `done` grouping options were introduced in Tasks 1.7.0.<br>
-`tags` grouping option was introduced in Tasks 1.10.0.<br>
-`priority`, `recurring` and `recurrence` grouping options were introduced in Tasks 1.11.0.
+>
+> - `start`, `scheduled`, `due` and `done` grouping options were introduced in Tasks 1.7.0.<br>
+> - `tags` grouping option was introduced in Tasks 1.10.0.<br>
+> - `priority`, `recurring` and `recurrence` grouping options were introduced in Tasks 1.11.0.
+> - `urgency` grouping option was introduced in Tasks X.Y.Z.
 
 ## Multiple groups
 
diff --git a/docs/Quick Reference.md b/docs/Quick Reference.md
index b9c757eb..0bd668fc 100644
--- a/docs/Quick Reference.md
+++ b/docs/Quick Reference.md
@@ -22,7 +22,7 @@ This table summarizes the filters and other options available inside a `tasks` b
 | `is recurring`<br>`is not recurring`                                                                                                                                                                                                                                                            | `sort by recurring`                         | `group by recurring`   |                        |
 | `recurrence (includes, does not include) <string>`<br>`recurrence (regex matches, regex does not match) /regex/i`                                                                                                                                                                               |                                             | `group by recurrence`  | `hide recurrence rule` |
 | `priority is (above, below, not)? (low, none, medium, high)`                                                                                                                                                                                                                                    | `sort by priority`                          | `group by priority`    | `hide priority`        |
-|                                                                                                                                                                                                                                                                                                 | `sort by urgency`                           |                        | `show urgency`         |
+|                                                                                                                                                                                                                                                                                                 | `sort by urgency`                           | `group by urgency`     | `show urgency`         |
 | `path (includes, does not include) <path>`<br>`path (regex matches, regex does not match) /regex/i`                                                                                                                                                                                             | `sort by path`                              | `group by path`        |                        |
 | `root (includes, does not include) <root>`<br>`root (regex matches, regex does not match) /regex/i`                                                                                                                                                                                             |                                             | `group by root`        |                        |
 | `folder (includes, does not include) <folder>`<br>`folder (regex matches, regex does not match) /regex/i`                                                                                                                                                                                       |                                             | `group by folder`      |                        |
diff --git a/src/Query/Filter/UrgencyField.ts b/src/Query/Filter/UrgencyField.ts
index a9ddb1b0..b60d3ea1 100644
--- a/src/Query/Filter/UrgencyField.ts
+++ b/src/Query/Filter/UrgencyField.ts
@@ -1,5 +1,6 @@
 import type { Comparator } from '../Sorter';
 import type { Task } from '../../Task';
+import type { GrouperFunction } from '../Grouper';
 import { Field } from './Field';
 import { FilterOrErrorMessage } from './Filter';
 
@@ -25,6 +26,10 @@ export class UrgencyField extends Field {
         throw Error(`filterRegExp() unimplemented for ${this.fieldName()}`);
     }
 
+    // -----------------------------------------------------------------------------------------------------------------
+    // Sorting
+    // -----------------------------------------------------------------------------------------------------------------
+
     supportsSorting(): boolean {
         return true;
     }
@@ -35,4 +40,22 @@ export class UrgencyField extends Field {
             return b.urgency - a.urgency;
         };
     }
+
+    // -----------------------------------------------------------------------------------------------------------------
+    // Grouping
+    // -----------------------------------------------------------------------------------------------------------------
+
+    public supportsGrouping(): boolean {
+        return true;
+    }
+
+    public grouper(): GrouperFunction {
+        // Note: Groups are sorted from low priority to high.
+        // This will be improved in a future release, by allowing
+        // the grouping code to take advantage of the comparator()
+        // method above.
+        return (task: Task) => {
+            return [`${task.urgency.toFixed(2)}`];
+        };
+    }
 }


Test patch:
diff --git a/tests/Query.test.ts b/tests/Query.test.ts
index dbc032c5..43a2e03a 100644
--- a/tests/Query.test.ts
+++ b/tests/Query.test.ts
@@ -219,6 +219,7 @@ describe('Query parsing', () => {
             'group by status.name',
             'group by status.type',
             'group by tags',
+            'group by urgency',
         ];
         test.concurrent.each<string>(filters)('recognises %j', (filter) => {
             // Arrange
diff --git a/tests/Query/Filter/UrgencyField.test.ts b/tests/Query/Filter/UrgencyField.test.ts
index 434e495d..946abc3b 100644
--- a/tests/Query/Filter/UrgencyField.test.ts
+++ b/tests/Query/Filter/UrgencyField.test.ts
@@ -11,6 +11,7 @@ import {
     expectTaskComparesBefore,
     expectTaskComparesEqual,
 } from '../../CustomMatchers/CustomMatchersForSorting';
+import { fromLine } from '../../TestHelpers';
 
 window.moment = moment;
 
@@ -75,3 +76,24 @@ describe('sorting by urgency', () => {
         );
     });
 });
+
+describe('grouping by urgency', () => {
+    it('supports grouping methods correctly', () => {
+        expect(new UrgencyField()).toSupportGroupingWithProperty('urgency');
+    });
+
+    // Numbers taken from:
+    // https://publish.obsidian.md/tasks/Advanced/Urgency
+    it.each([
+        ['- [ ] a ⏫', ['6.00']],
+        ['- [ ] a 🔼', ['3.90']],
+        ['- [ ] a', ['1.95']],
+        ['- [ ] a 🔽', ['0.00']],
+    ])('task "%s" should have groups: %s', (taskLine: string, groups: string[]) => {
+        // Arrange
+        const grouper = new UrgencyField().createGrouper().grouper;
+
+        // Assert
+        expect(grouper(fromLine({ line: taskLine }))).toEqual(groups);
+    });
+});