Repository: obsidian-tasks-group/obsidian-tasks
Pull request #733 (2022-06-06 05:19:53)

feat: Add 'group by <start|scheduled|due|done>'
## Description

Add new options to the `group by` command: `start`, `scheduled`, `due` and `done`

## Motivation and Context

Requested in #693

## How has this been tested?

- via new unit tests
- it's been in use in my fork for some months
- downloaded the build from this PR and tested it in the [theme-dev-vault](https://github.com/obsidian-community/theme-dev-vault) to create a screenshot

## Screenshots (if appropriate):

<img src="https://user-images.githubusercontent.com/4840096/172100286-73a73084-c066-4b0e-9ba0-4a7ec351cf94.png" width="500">

## 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 ceedfe49..52bb9478 100644
--- a/docs/queries/grouping.md
+++ b/docs/queries/grouping.md
@@ -49,6 +49,16 @@ Task properties:
 1. `status` (Done or Todo, which is capitalized for visibility in the headings)
     * Note that the Done group is displayed before the Todo group,
       which differs from the Sorting ordering of this property.
+1. `start`
+   * The start date of the task, including the week-day, or `No start date`.
+1. `scheduled`
+    * The scheduled date of the task, including the week-day, or `No scheduled date`.
+1. `due`
+    * 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`.
+
+> `start`, `scheduled`, `due` and `done` grouping options were introduced in Tasks 1.7.0.
 
 ### Multiple groups
 
diff --git a/src/Query.ts b/src/Query.ts
index dc0f823b..95fdde99 100644
--- a/src/Query.ts
+++ b/src/Query.ts
@@ -37,10 +37,14 @@ type Sorting = {
 
 export type GroupingProperty =
     | 'backlink'
+    | 'done'
+    | 'due'
     | 'filename'
     | 'folder'
     | 'heading'
     | 'path'
+    | 'scheduled'
+    | 'start'
     | 'status';
 export type Grouping = { property: GroupingProperty };
 
@@ -72,7 +76,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|filename|folder|heading|path|status)/;
+        /^group by (backlink|done|due|filename|folder|heading|path|scheduled|start|status)/;
 
     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 32bd9efc..fbc0a809 100644
--- a/src/Query/Group.ts
+++ b/src/Query/Group.ts
@@ -11,6 +11,8 @@ type Grouper = (task: Task) => string;
  * Implementation of the 'group by' instruction.
  */
 export class Group {
+    private static readonly groupDateFormat = 'YYYY-MM-DD dddd';
+
     /**
      * Group a list of tasks, according to one or more task properties
      * @param grouping 0 or more Grouping values, one per 'group by' line
@@ -63,13 +65,40 @@ export class Group {
 
     private static groupers: Record<GroupingProperty, Grouper> = {
         backlink: Group.groupByBacklink,
+        done: Group.groupByDoneDate,
+        due: Group.groupByDueDate,
         filename: Group.groupByFileName,
         folder: Group.groupByFolder,
         heading: Group.groupByHeading,
         path: Group.groupByPath,
+        scheduled: Group.groupByScheduledDate,
+        start: Group.groupByStartDate,
         status: Group.groupByStatus,
     };
 
+    private static groupByStartDate(task: Task): string {
+        return Group.groupByDate(task.startDate, 'start');
+    }
+
+    private static groupByScheduledDate(task: Task): string {
+        return Group.groupByDate(task.scheduledDate, 'scheduled');
+    }
+
+    private static groupByDueDate(task: Task): string {
+        return Group.groupByDate(task.dueDate, 'due');
+    }
+
+    private static groupByDoneDate(task: Task): string {
+        return Group.groupByDate(task.doneDate, 'done');
+    }
+
+    private static groupByDate(date: moment.Moment | null, field: string) {
+        if (date === null) {
+            return 'No ' + field + ' date';
+        }
+        return date.format(Group.groupDateFormat);
+    }
+
     private static groupByPath(task: Task): string {
         // Does this need to be made stricter?
         // Is there a better way of getting the file name?


Test patch:
diff --git a/tests/Group.test.ts b/tests/Group.test.ts
index 73093f54..947f8388 100644
--- a/tests/Group.test.ts
+++ b/tests/Group.test.ts
@@ -212,6 +212,32 @@ describe('Group names', () => {
             precedingHeading: 'heading',
         },
 
+        // -----------------------------------------------------------
+        // group by done
+        {
+            groupBy: 'done',
+            taskLine: '- [ ] a ✅ 1970-01-01',
+            expectedGroupName: '1970-01-01 Thursday',
+        },
+        {
+            groupBy: 'done',
+            taskLine: '- [ ] a',
+            expectedGroupName: 'No done date',
+        },
+
+        // -----------------------------------------------------------
+        // group by due
+        {
+            groupBy: 'due',
+            taskLine: '- [ ] a 📅 1970-01-01',
+            expectedGroupName: '1970-01-01 Thursday',
+        },
+        {
+            groupBy: 'due',
+            taskLine: '- [ ] a',
+            expectedGroupName: 'No due date',
+        },
+
         // -----------------------------------------------------------
         // group by filename
         {
@@ -267,6 +293,32 @@ describe('Group names', () => {
             expectedGroupName: 'a/b/c',
         },
 
+        // -----------------------------------------------------------
+        // group by scheduled
+        {
+            groupBy: 'scheduled',
+            taskLine: '- [ ] a ⏳ 1970-01-01',
+            expectedGroupName: '1970-01-01 Thursday',
+        },
+        {
+            groupBy: 'scheduled',
+            taskLine: '- [ ] a',
+            expectedGroupName: 'No scheduled date',
+        },
+
+        // -----------------------------------------------------------
+        // group by start
+        {
+            groupBy: 'start',
+            taskLine: '- [ ] a 🛫 1970-01-01',
+            expectedGroupName: '1970-01-01 Thursday',
+        },
+        {
+            groupBy: 'start',
+            taskLine: '- [ ] a',
+            expectedGroupName: 'No start date',
+        },
+
         // -----------------------------------------------------------
         // group by status
         {