Skip to content

Commit ff8cd81

Browse files
authored
Merge pull request #2599 from obsidian-tasks-group/fix-default-sort-order
fix!!: Make default sort order sort first by status type
2 parents 2bed495 + 58b0372 commit ff8cd81

10 files changed

+166
-39
lines changed

docs/Getting Started/Priority.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,8 @@ The following instructions use the priority signifiers in tasks.
4343
- `priority is (above, below)? (lowest, low, none, medium, high, highest)`
4444
- [[Filters#Priority|Documentation]]
4545
- `sort by priority`
46-
- [[Sorting#Basics|Documentation]]
46+
- [[Sorting#Priority|Documentation]]
4747
- `group by priority`
48-
- [[Grouping#Basics|Documentation]]
48+
- [[Grouping#Priority|Documentation]]
4949
- `hide priority`
5050
- [[Layout|Documentation]]

docs/Introduction.md

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,17 @@ _In recent [releases](https://github.com/obsidian-tasks-group/obsidian-tasks/rel
1313
Move the older ones down to the top of the comment block below...
1414
-->
1515

16-
- X.Y.Z: 🔥 Add [[Custom Sorting|custom sorting]].
17-
- 5.6.0: 🔥 The [[Postponing|postpone]] menu now offers `today` and `tomorrow`.
18-
- 5.5.0: 🔥 The [[Create or edit Task]] modal can now edit Created, Done and Cancelled dates
19-
- 5.5.0: 🔥 Add support for [[Dates#Cancelled date|cancelled dates]].
16+
- X.Y.Z:
17+
- Add [[Custom Sorting|custom sorting]].
18+
- Document the [[Sorting#Default sort order|default sort order]].
19+
- **Warning**: This release contains some **bug-fixes** to **sorting** and to treatment of **invalid dates**.
20+
- The changes are detailed in [[breaking changes#Tasks [X.Y.Z](https //github.com/obsidian-tasks-group/obsidian-tasks/releases/tag/X.Y.Z) (21 January 2024)|breaking changes]], even though they are all improvements to the previous behaviour.
21+
- You may need to update any CSS snippets for the Edit or Postpone buttons: see [[How to style buttons]].
22+
- 5.6.0:
23+
- The [[Postponing|postpone]] menu now offers `today` and `tomorrow`.
24+
- 5.5.0:
25+
- The [[Create or edit Task]] modal can now edit Created, Done and Cancelled dates
26+
- Add support for [[Dates#Cancelled date|cancelled dates]].
2027

2128
> [!Released]- Earlier Releases
2229
>

docs/Queries/Sorting.md

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ publish: true
1212

1313
This page is long. Here are some links to the main sections:
1414

15-
- [[#Basics]]
15+
- [[#Default sort order]]
1616
- [[#Sort by Task Statuses]]
1717
- [[#Sort by Dates in Tasks]]
1818
- [[#Sort by Other Task Properties]]
@@ -22,11 +22,32 @@ This page is long. Here are some links to the main sections:
2222
- [[#Reverse sorting]]
2323
- [[#Examples]]
2424

25-
## Basics
25+
## Default sort order
2626

27-
By default Tasks sorts tasks by [[Urgency|a calculated score we call "urgency"]].
27+
The following instructions are the default sort order, and they are **automatically appended to the end of *every* Tasks search**:
2828

29-
To sort the results of a query different from the default, you must add at least one `sort by` line to the query.
29+
<!-- snippet: Sort.test.Sort_save_default_sort_order.approved.text -->
30+
```text
31+
sort by status.type
32+
sort by urgency
33+
sort by due
34+
sort by priority
35+
sort by path
36+
```
37+
<!-- endSnippet -->
38+
39+
It first sorts tasks in the order `IN_PROGRESS`, `TODO`, `DONE`, `CANCELLED` then `NON_TASK` to ensure that actionable tasks appear first, which is important in searches without a filter like `not done`.
40+
41+
Then it sorts by [[Urgency]], which is a calculated score derived from several Task properties.
42+
43+
The above lines are *always* appended to the end of any `sort by` instructions supplied by the user. There is no way to disable this.
44+
45+
However, any `sort by` instructions in queries take precedence over these default ones.
46+
47+
> [!tip]
48+
> To sort the results of a query differently from the default, you must add at least one `sort by` line to the query. The sort instructions you supply will take priority over the appended defaults.
49+
>
50+
> Adding `sort by` lines to the [[Global Query]] provides a way override to the default sort order for **all** searches (except those that [[Global Query#Ignoring the global query|ignore the global query]]).
3051
3152
## Custom Sorting
3253

docs/Support and Help/Breaking Changes.md

Lines changed: 29 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,32 @@ In this case, as we use [semantic versioning](https://semver.org), we will alway
1616

1717
To help users updating across multiple Tasks releases, we collect here links to the few Tasks breaking changes.
1818

19-
- Tasks [2.0.0](https://github.com/obsidian-tasks-group/obsidian-tasks/releases/tag/2.0.0) (22 March 2023):
20-
- The introduction of filtering for date ranges [[Filters#Appendix Tasks 2.0.0 improvements to date filters|improved the results of some date searches]].
21-
- Tasks [3.0.0](https://github.com/obsidian-tasks-group/obsidian-tasks/releases/tag/3.0.0) (2 April 2023):
22-
- Some CSS snippets that use `>` in their selectors [[Styling#Appendix Fixing CSS pre-existing snippets for Tasks 3.0.0|may need updating]].
23-
- For example, see comment mentioning `3.0.0` in the CSS sample in [[How to style backlinks#Using CSS to replace the backlinks with icons|Using CSS to replace the backlinks with icons]].
24-
- Tasks [4.0.0](https://github.com/obsidian-tasks-group/obsidian-tasks/releases/tag/4.0.0) (15 June 2023):
25-
- The order of `group by urgency` [[Grouping#Urgency|was reversed]].
26-
- Tasks [5.0.0](https://github.com/obsidian-tasks-group/obsidian-tasks/releases/tag/5.0.0) (21 October 2023):
27-
- The meaning of final backslash (`\`) characters on query lines [[Line Continuations#Appendix Updating pre-5.0.0 searches with trailing backslashes|has changed]].
28-
- Tasks [X.Y.Z](https://github.com/obsidian-tasks-group/obsidian-tasks/releases/tag/X.Y.Z) (21 January 2024):
29-
- Code snippets used to change the look of the edit and postpone buttons must be changed, as explained in [[How to style buttons]], which gives several examples.
30-
- When sorting tasks, invalid dates are now sorted before valid dates. See [[Sorting#How dates are sorted]].
19+
## Tasks [2.0.0](https://github.com/obsidian-tasks-group/obsidian-tasks/releases/tag/2.0.0) (22 March 2023)
20+
21+
- The introduction of filtering for date ranges [[Filters#Appendix Tasks 2.0.0 improvements to date filters|improved the results of some date searches]].
22+
23+
## Tasks [3.0.0](https://github.com/obsidian-tasks-group/obsidian-tasks/releases/tag/3.0.0) (2 April 2023)
24+
25+
- Some CSS snippets that use `>` in their selectors [[Styling#Appendix Fixing CSS pre-existing snippets for Tasks 3.0.0|may need updating]].
26+
- For example, see comment mentioning `3.0.0` in the CSS sample in [[How to style backlinks#Using CSS to replace the backlinks with icons|Using CSS to replace the backlinks with icons]].
27+
28+
## Tasks [4.0.0](https://github.com/obsidian-tasks-group/obsidian-tasks/releases/tag/4.0.0) (15 June 2023)
29+
30+
- The order of `group by urgency` [[Grouping#Urgency|was reversed]].
31+
32+
## Tasks [5.0.0](https://github.com/obsidian-tasks-group/obsidian-tasks/releases/tag/5.0.0) (21 October 2023)
33+
34+
- The meaning of final backslash (`\`) characters on query lines [[Line Continuations#Appendix Updating pre-5.0.0 searches with trailing backslashes|has changed]].
35+
36+
## Tasks [X.Y.Z](https://github.com/obsidian-tasks-group/obsidian-tasks/releases/tag/X.Y.Z) (21 January 2024)
37+
38+
These are all bug-fixes, improving the default behaviour, and recorded here for transparency.
39+
40+
- Sorting
41+
- The [[Sorting#Default sort order|default sort order]] now sorts first by status type, to greatly improve search results that include completed tasks.
42+
- Invalid dates
43+
- [[Filters#Happens|happens]] date now ignores any invalid dates.
44+
- `sort by [date]` now puts invalid dates before valid dates, as action is required. See [[Sorting#How dates are sorted]].
45+
- `group by [date]` now puts `Invalid [date] date` as the first heading.
46+
- `task.due.category` and `task.due.fromNow` now handle invalid dates as different from future dates. See [[Task Properties#Values in TasksDate Properties|Values in TasksDate Properties]].
47+
- Code snippets used to change the look of the Edit and Postpone buttons must be changed, as explained in [[How to style buttons]], which gives several examples.

src/Query/Sort.ts

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import type { Task } from '../Task';
2-
import type { Comparator } from './Sorter';
3-
import type { Sorter } from './Sorter';
4-
import { StatusField } from './Filter/StatusField';
2+
import type { Comparator, Sorter } from './Sorter';
3+
import { StatusTypeField } from './Filter/StatusTypeField';
54
import { DueDateField } from './Filter/DueDateField';
65
import { PriorityField } from './Filter/PriorityField';
76
import { PathField } from './Filter/PathField';
@@ -12,13 +11,7 @@ type PlainComparator = (a: Task, b: Task) => number;
1211

1312
export class Sort {
1413
public static by(sorters: Sorter[], tasks: Task[], searchInfo: SearchInfo) {
15-
const defaultComparators: Comparator[] = [
16-
new UrgencyField().comparator(),
17-
new StatusField().comparator(),
18-
new DueDateField().comparator(),
19-
new PriorityField().comparator(),
20-
new PathField().comparator(),
21-
];
14+
const defaultComparators: Comparator[] = this.defaultSorters().map((sorter) => sorter.comparator);
2215

2316
const userComparators: Comparator[] = [];
2417

@@ -29,6 +22,16 @@ export class Sort {
2922
return tasks.sort(Sort.makeCompositeComparator([...userComparators, ...defaultComparators], searchInfo));
3023
}
3124

25+
public static defaultSorters() {
26+
return [
27+
new StatusTypeField().createNormalSorter(),
28+
new UrgencyField().createNormalSorter(),
29+
new DueDateField().createNormalSorter(),
30+
new PriorityField().createNormalSorter(),
31+
new PathField().createNormalSorter(),
32+
];
33+
}
34+
3235
private static makeCompositeComparator(comparators: Comparator[], searchInfo: SearchInfo): PlainComparator {
3336
return (a, b) => {
3437
for (const comparator of comparators) {

tests/Scripting/ScriptingReference/CustomSorting/CustomSortingExamples.test.other_properties_task.isDone_results.approved.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,11 @@ sort by function !task.isDone
66
`sort by function` sorts `true` before `false`
77
Hence, we use `!` to negate `task.isDone`, so tasks with [[Status Types|Status Type]] `TODO` and `IN_PROGRESS` tasks are sorted **before** `DONE`, `CANCELLED` and `NON_TASK`.
88
=>
9+
- [/] Status In Progress
910
- [ ] Status Todo
1011
- [] Status EMPTY
11-
- [/] Status In Progress
12-
- [-] Status Cancelled
1312
- [x] Status Done
13+
- [-] Status Cancelled
1414
- [Q] Status Non-Task
1515
====================================================================================
1616

tests/Scripting/ScriptingReference/CustomSorting/CustomSortingExamples.test.statuses_task.status.nextSymbol_results.approved.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,10 @@ sort by function task.status.nextSymbol
66
Sort by the next status symbol.
77
=>
88
- [] Status EMPTY
9-
- [-] Status Cancelled
109
- [x] Status Done
10+
- [-] Status Cancelled
1111
- [Q] Status Non-Task
12-
- [ ] Status Todo
1312
- [/] Status In Progress
13+
- [ ] Status Todo
1414
====================================================================================
1515

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
sort by status.type
2+
sort by urgency
3+
sort by due
4+
sort by priority
5+
sort by path
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
- [/] #task In progress 🔺 📅 1970-01-02
2+
- [/] In progress
3+
- [ ] #task Highest priority 🔺
4+
- [ ] #task High priority ⏫
5+
- [ ] my description 📅 Invalid date
6+
- [ ] my description 📅 2023-05-30
7+
- [ ] my description 📅 2023-05-31
8+
- [ ] my description 📅 2023-06-01
9+
- [ ] #task Medium priority 🔼
10+
- [ ] my description
11+
- [ ] my description 🛫 Invalid date
12+
- [ ] my description
13+
- [ ] my description ⏳ 2023-05-30
14+
- [ ] my description ⏳ 2023-05-31
15+
- [ ] my description ⏳ 2023-06-01
16+
- [ ] my description ⏳ Invalid date
17+
- [ ] my description
18+
- [ ] #task Normal priority
19+
- [ ] Todo
20+
- [%] Unknown
21+
- [ ] xyz in '' in heading 'heading'
22+
- [ ] xyz in 'a_b_c.md' in heading 'a_b_c'
23+
- [ ] xyz in 'a/b.md' in heading 'null'
24+
- [ ] xyz in 'a/b/_c_.md' in heading 'null'
25+
- [ ] xyz in 'a/b/_c_.md' in heading 'heading _italic text_'
26+
- [ ] xyz in 'a/b/c.md' in heading 'null'
27+
- [ ] xyz in 'a/b/c.md' in heading 'c'
28+
- [ ] xyz in 'a/d/c.md' in heading 'heading'
29+
- [ ] xyz in 'e/d/c.md' in heading 'heading'
30+
- [ ] #task Low priority 🔽
31+
- [ ] my description 🛫 2023-05-30
32+
- [ ] my description 🛫 2023-05-31
33+
- [ ] my description 🛫 2023-06-01
34+
- [ ] #task Lowest priority ⏬
35+
- [x] #task Done 🔺 📅 1970-01-02
36+
- [x] Done
37+
- [-] #task Cancelled 🔺 📅 1970-01-02
38+
- [-] Cancelled
39+
- [^] Non-task
40+
- [] Empty task

tests/Sort.test.ts

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,12 @@ import { DueDateField } from '../src/Query/Filter/DueDateField';
1414
import { PathField } from '../src/Query/Filter/PathField';
1515
import { SearchInfo } from '../src/Query/SearchInfo';
1616
import { Sort } from '../src/Query/Sort';
17-
import { fromLine, toLines } from './TestHelpers';
17+
import { StatusRegistry } from '../src/StatusRegistry';
18+
import { StatusConfiguration, StatusType } from '../src/StatusConfiguration';
19+
import { SampleTasks, fromLine, fromLines, toLines } from './TestHelpers';
1820
import { TaskBuilder } from './TestingTools/TaskBuilder';
1921
import { sortBy } from './TestingTools/SortingTestHelpers';
22+
import { verifyWithFileExtension } from './TestingTools/ApprovalTestHelpers';
2023

2124
const longAgo = '2022-01-01';
2225
const yesterday = '2022-01-14';
@@ -34,6 +37,15 @@ afterAll(() => {
3437
jest.useRealTimers();
3538
});
3639

40+
afterEach(() => {
41+
StatusRegistry.getInstance().resetToDefaultStatuses();
42+
});
43+
44+
function verifySortedTasks(tasks: Task[]) {
45+
const sortedTasks = Sort.by([], tasks, SearchInfo.fromAllTasks(tasks));
46+
verify(toLines(sortedTasks).join('\n'));
47+
}
48+
3749
describe('Sort', () => {
3850
it('constructs Sorting both ways from Comparator function', () => {
3951
const comparator: Comparator = (a: Task, b: Task) => {
@@ -126,6 +138,29 @@ describe('Sort', () => {
126138
).toEqual(expectedOrder);
127139
});
128140

141+
it('save default sort order', () => {
142+
const sorters = Sort.defaultSorters();
143+
const defaultSortInstructions = sorters.map((sorter) => sorter.instruction).join('\n');
144+
verifyWithFileExtension(defaultSortInstructions, 'text');
145+
});
146+
147+
it('visualise default sort order', () => {
148+
StatusRegistry.getInstance().add(new StatusConfiguration('^', 'Non-task', ' ', false, StatusType.NON_TASK));
149+
150+
const extraTaskLines = `- [x] #task Done 🔺 📅 1970-01-02
151+
- [/] #task In progress 🔺 📅 1970-01-02
152+
- [-] #task Cancelled 🔺 📅 1970-01-02`;
153+
const extraTasks = fromLines({ lines: extraTaskLines.split('\n') });
154+
const tasks = SampleTasks.withAllRepresentativeDueDates()
155+
.concat(SampleTasks.withAllRepresentativeStartDates())
156+
.concat(SampleTasks.withAllRepresentativeScheduledDates())
157+
.concat(SampleTasks.withAllPriorities())
158+
.concat(SampleTasks.withAllStatusTypes())
159+
.concat(SampleTasks.withAllRootsPathsHeadings())
160+
.concat(extraTasks);
161+
verifySortedTasks(tasks);
162+
});
163+
129164
it('visualise date impact on default sort order', () => {
130165
const dates = [
131166
['long ago', longAgo],
@@ -166,7 +201,6 @@ describe('Sort', () => {
166201
}
167202
}
168203
}
169-
const sortedTasks = Sort.by([], tasks, SearchInfo.fromAllTasks(tasks));
170-
verify(toLines(sortedTasks).join('\n'));
204+
verifySortedTasks(tasks);
171205
});
172206
});

0 commit comments

Comments
 (0)