Skip to content

Commit be6994a

Browse files
authored
feat: Add 'blocked by' presence and absence instructions (#2615)
* refactor: Create BlockedByField class - will eventually search Task.blockedBy * refactor: BlockedByField.ts now supports 'has blocked by' * feat: Add 'has blocked by' query * feat: Add 'no blocked by' query * docs: Move 'is not blocked' & 'is blocking' to own lines in Quick Reference * docs: Add 'has blocked by' and 'no blocked by' to Quick Reference * docs: Document 'blocked by' field in Filters, Grouping and Sorting. There are no built-in sorting and grouping instructions, but they can be used in custom sorting and grouping.
1 parent 064b7ac commit be6994a

File tree

8 files changed

+120
-3
lines changed

8 files changed

+120
-3
lines changed

docs/Queries/Filters.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -471,6 +471,19 @@ For more information, see [[Task Dependencies]].
471471

472472
Since Tasks X.Y.Z, **[[Custom Filters|custom filtering]] by Id** is now possible, using `task.id`.
473473

474+
### Blocked By
475+
476+
- `has blocked by`
477+
- `no blocked by`
478+
479+
For more information, see [[Task Dependencies]].
480+
481+
> [!released]
482+
>
483+
> - Task Blocked By was introduced in Tasks X.Y.Z.
484+
485+
Since Tasks X.Y.Z, **[[Custom Filters|custom filtering]] by Blocked By** is now possible, using `task.blockedBy`.
486+
474487
## Filters for Dates in Tasks
475488

476489
### Due Date

docs/Queries/Grouping.md

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,19 @@ For more information, see [[Task Dependencies]].
175175
>
176176
> - Task Id was introduced in Tasks X.Y.Z.
177177

178-
Since Tasks X.Y.Z, **[[Custom Grouping|custom grouping]]] by Id** is now possible, using `task.id`.
178+
Since Tasks X.Y.Z, **[[Custom Grouping|custom grouping]] by Id** is now possible, using `task.id`.
179+
180+
### Blocked By
181+
182+
There is no built-in instruction to group by 'Blocked By'.
183+
184+
For more information, see [[Task Dependencies]].
185+
186+
> [!released]
187+
>
188+
> - Task Blocked By was introduced in Tasks X.Y.Z.
189+
190+
Since Tasks X.Y.Z, **[[Custom Grouping|custom grouping]] by Blocked By** is now possible, using `task.blockedBy`.
179191

180192
## Group by Dates in Tasks
181193

docs/Queries/Sorting.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,18 @@ For more information, see [[Task Dependencies]].
172172

173173
Since Tasks X.Y.Z, **[[Custom Sorting|custom sorting]] by Id** is now possible, using `task.id`.
174174

175+
### Blocked By
176+
177+
There is no built-in instruction to sort by 'Blocked By'.
178+
179+
For more information, see [[Task Dependencies]].
180+
181+
> [!released]
182+
>
183+
> - Task Blocked By was introduced in Tasks X.Y.Z.
184+
185+
Since Tasks X.Y.Z, **[[Custom Sorting|custom sorting]] by Blocked By** is now possible, using `task.blockedBy`.
186+
175187
## Sort by Dates in Tasks
176188

177189
### How dates are sorted

docs/Quick Reference.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,10 @@ This table summarizes the filters and other options available inside a `tasks` b
1919
| | | | | `task.status.symbol` |
2020
| | | | | `task.status.nextSymbol` |
2121
| **[[Task Dependencies]]** | | | | |
22-
| `id (includes, does not include) <string>`<br>`id (regex matches, regex does not match) /regex/i`<br>`has id`<br>`no id`<br>`is blocking` | `sort by id` | `group by id` | `hide id` | `task.id` |
23-
| `is not blocked` | | | `hide blocked by` | `task.blockedBy` |
22+
| `id (includes, does not include) <string>`<br>`id (regex matches, regex does not match) /regex/i`<br>`has id`<br>`no id` | `sort by id` | `group by id` | `hide id` | `task.id` |
23+
| `has blocked by`<br>`no blocked by` | | | `hide blocked by` | `task.blockedBy` |
24+
| `is not blocked` | | | | |
25+
| `is blocking` | | | | |
2426
| **[[Filters#Filters for Dates in Tasks\|Dates]]** | | | | |
2527
| `done (on, before, after, on or before, on or after) <date>`<br>`done (in, before, after, in or before, in or after) ...`<br>`... YYYY-MM-DD YYYY-MM-DD`<br>`... (last, this, next) (week, month, quarter, year)`<br>`... (YYYY-Www,YYYY-mm, YYYY-Qq, YYYY)`<br>`has done date`<br>`no done date`<br>`done date is invalid` | `sort by done` | `group by done` | `hide done date` | `task.done` |
2628
| `created (on, before, after, on or before, on or after) <date>`<br>`created (in, before, after, in or before, in or after) ...`<br>`... YYYY-MM-DD YYYY-MM-DD`<br>`... (last, this, next) (week, month, quarter, year)`<br>`... (YYYY-Www,YYYY-mm, YYYY-Qq, YYYY)`<br>`has created date`<br>`no created date`<br>`created date is invalid` | `sort by created` | `group by created` | `hide created date` | `task.created` |

src/Query/Filter/BlockedByField.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import type { Task } from '../../Task/Task';
2+
import { Field } from './Field';
3+
import { FilterOrErrorMessage } from './FilterOrErrorMessage';
4+
import { FilterInstructions } from './FilterInstructions';
5+
6+
export class BlockedByField extends Field {
7+
private readonly filterInstructions: FilterInstructions = new FilterInstructions();
8+
9+
constructor() {
10+
super();
11+
this.filterInstructions.add('has blocked by', (task: Task) => task.blockedBy.length > 0);
12+
this.filterInstructions.add('no blocked by', (task: Task) => task.blockedBy.length === 0);
13+
}
14+
15+
// -----------------------------------------------------------------------------------------------------------------
16+
// Filtering
17+
// -----------------------------------------------------------------------------------------------------------------
18+
19+
public canCreateFilterForLine(line: string): boolean {
20+
if (this.filterInstructions.canCreateFilterForLine(line)) {
21+
return true;
22+
}
23+
24+
return super.canCreateFilterForLine(line);
25+
}
26+
27+
public createFilterOrErrorMessage(line: string): FilterOrErrorMessage {
28+
const filterResult = this.filterInstructions.createFilterOrErrorMessage(line);
29+
if (filterResult.filter !== undefined) {
30+
return filterResult;
31+
}
32+
33+
return FilterOrErrorMessage.fromError(line, 'Unknown instruction');
34+
}
35+
36+
public fieldName(): string {
37+
return 'blocked by';
38+
}
39+
40+
protected filterRegExp(): RegExp | null {
41+
return null;
42+
}
43+
}

src/Query/FilterParser.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import { BacklinkField } from './Filter/BacklinkField';
3030
import { CancelledDateField } from './Filter/CancelledDateField';
3131
import { BlockingField } from './Filter/BlockingField';
3232
import { IdField } from './Filter/IdField';
33+
import { BlockedByField } from './Filter/BlockedByField';
3334

3435
// When parsing a query the fields are tested one by one according to this order.
3536
// Since BooleanField is a meta-field, which needs to aggregate a few fields together, it is intended to
@@ -63,6 +64,7 @@ export const fieldCreators: EndsWith<BooleanField> = [
6364
() => new RecurrenceField(),
6465
() => new FunctionField(),
6566
() => new IdField(),
67+
() => new BlockedByField(),
6668
() => new BlockingField(),
6769
() => new BooleanField(), // --- Please make sure to keep BooleanField last (see comment above) ---
6870
];
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { testFilter } from '../../TestingTools/FilterTestHelpers';
2+
import { TaskBuilder } from '../../TestingTools/TaskBuilder';
3+
import { BlockedByField } from '../../../src/Query/Filter/BlockedByField';
4+
5+
describe('id', () => {
6+
const blockedByField = new BlockedByField();
7+
8+
it('should supply field name', () => {
9+
expect(blockedByField.fieldName()).toEqual('blocked by');
10+
});
11+
12+
it('by blocked by presence', () => {
13+
// Arrange
14+
const filter = new BlockedByField().createFilterOrErrorMessage('has blocked by');
15+
16+
// Act, Assert
17+
testFilter(filter, new TaskBuilder().blockedBy([]), false);
18+
testFilter(filter, new TaskBuilder().blockedBy(['abcdef']), true);
19+
});
20+
21+
it('by blocked by absence', () => {
22+
// Arrange
23+
const line = 'no blocked by';
24+
const filter = new BlockedByField().createFilterOrErrorMessage(line);
25+
expect(blockedByField.canCreateFilterForLine(line)).toEqual(true);
26+
27+
// Act, Assert
28+
testFilter(filter, new TaskBuilder().blockedBy([]), true);
29+
testFilter(filter, new TaskBuilder().blockedBy(['abcdef']), false);
30+
});
31+
});

tests/Query/Query.test.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ describe('Query parsing', () => {
8282
'happens in 2021-12-27 2021-12-29',
8383
'happens on 2021-12-27',
8484
'happens this week',
85+
'has blocked by',
8586
'has cancelled date',
8687
'has created date',
8788
'has done date',
@@ -102,6 +103,7 @@ describe('Query parsing', () => {
102103
'is not blocked',
103104
'is not recurring',
104105
'is recurring',
106+
'no blocked by',
105107
'no cancelled date',
106108
'no created date',
107109
'no due date',

0 commit comments

Comments
 (0)