Skip to content

Commit 2164231

Browse files
authored
Merge pull request #2617 from obsidian-tasks-group/expose-query.allTasks
feat: Expose `query.allTasks` in scripting
2 parents 29fced0 + cdb74fd commit 2164231

File tree

9 files changed

+93
-22
lines changed

9 files changed

+93
-22
lines changed

docs/Introduction.md

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,20 +13,22 @@ _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:
17+
- `query.allTasks` is now available in custom searches: see [[Query Properties#Values for Query Search Properties|query search properties]].
1618
- 6.0.0:
1719
- Add [[Custom Sorting|custom sorting]].
1820
- Document the [[Sorting#Default sort order|default sort order]].
1921
- **Warning**: This release contains some **bug-fixes** to **sorting** and to treatment of **invalid dates**.
2022
- The changes are detailed in [[breaking changes#Tasks 6.0.0 (19 January 2024)|breaking changes]], even though they are all improvements to the previous behaviour.
2123
- 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]].
2724

2825
> [!Released]- Earlier Releases
2926
>
27+
> - 5.6.0:
28+
> - The [[Postponing|postpone]] menu now offers `today` and `tomorrow`.
29+
> - 5.5.0:
30+
> - The [[Create or edit Task]] modal can now edit Created, Done and Cancelled dates
31+
> - Add support for [[Dates#Cancelled date|cancelled dates]].
3032
> - 5.4.0:
3133
> - Add [[Layout#Full Mode|'full mode']] to turn off `short mode`.
3234
> - Add any [[Grouping|'group by']] and [[Sorting|'sort by']] instructions to [[Explaining Queries|explain]] output.

docs/Queries/Grouping.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,7 @@ Since Tasks X.Y.Z, **[[Custom Grouping|custom grouping]] by Blocked By** is now
206206
group by function task.blockedBy
207207
```
208208

209-
- Group by the Ids of the tasks that this task depends on, if any.
209+
- Group by the Ids of the tasks that each task depends on, if any.
210210
- If a task depends on more than one other task, it will be listed multiple times.
211211
- Note that currently there is no way to access the tasks being depended on.
212212

docs/Scripting/Query Properties.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,3 +42,19 @@ This page documents all the available pieces of information in Queries that you
4242
1. The presence of `.md` filename extensions is chosen to match the existing conventions in the Tasks filter instructions [[Filters#File Path|path]] and [[Filters#File Name|filename]].
4343
1. `query.file.pathWithoutExtension` was added in Tasks 4.8.0.
4444
1. `query.file.filenameWithoutExtension` was added in Tasks 4.8.0.
45+
46+
## Values for Query Search Properties
47+
48+
<!-- placeholder to force blank line before included text --><!-- include: QueryProperties.test.query_search_properties.approved.md -->
49+
50+
| Field | Type | Example |
51+
| ----- | ----- | ----- |
52+
| `query.allTasks` | `Task[]` | `[... an array with all the Tasks-tracked tasks in the vault ...]` |
53+
54+
<!-- placeholder to force blank line after included text --><!-- endInclude -->
55+
56+
1. `query.allTasks` provides access to all the tasks that Tasks has read from the vault.
57+
- If [[Global Filter|global filter]] is enabled, only tasks containing the global filter are included.
58+
- The [[Global Query|global query]] does not affect `query.allTasks`: all tasks tracked by the Tasks plugin are included.
59+
- See [[Task Properties]] for the available properties on each task.
60+
- `query.allTasks` was added in Tasks X.Y.Z.

src/Query/SearchInfo.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type { Task } from '../Task/Task';
2-
import { type QueryContext, makeQueryContext } from '../Scripting/QueryContext';
2+
import { type QueryContext, makeQueryContextWithTasks } from '../Scripting/QueryContext';
33

44
/**
55
* SearchInfo contains selected data passed in from the {@link Query} being executed.
@@ -30,6 +30,6 @@ export class SearchInfo {
3030
* @return A QueryContext, or undefined if the path to the query file is unknown.
3131
*/
3232
public queryContext(): QueryContext | undefined {
33-
return this.queryPath ? makeQueryContext(this.queryPath) : undefined;
33+
return this.queryPath ? makeQueryContextWithTasks(this.queryPath, this.allTasks) : undefined;
3434
}
3535
}

src/Scripting/QueryContext.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import type { Task } from '../Task/Task';
12
import { TasksFile } from './TasksFile';
23

34
/**
@@ -18,6 +19,7 @@ import { TasksFile } from './TasksFile';
1819
export interface QueryContext {
1920
query: {
2021
file: TasksFile;
22+
allTasks: Readonly<Task[]>;
2123
};
2224
}
2325

@@ -26,12 +28,18 @@ export interface QueryContext {
2628
* @param path
2729
*
2830
* @see SearchInfo.queryContext
31+
* @see makeQueryContextWithTasks
2932
*/
3033
export function makeQueryContext(path: string): QueryContext {
34+
return makeQueryContextWithTasks(path, []);
35+
}
36+
37+
export function makeQueryContextWithTasks(path: string, allTasks: Readonly<Task[]>): QueryContext {
3138
const tasksFile = new TasksFile(path);
3239
return {
3340
query: {
3441
file: tasksFile,
42+
allTasks: allTasks,
3543
},
3644
};
3745
}

tests/Scripting/QueryContext.test.ts

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,35 +4,55 @@ import { FolderField } from '../../src/Query/Filter/FolderField';
44
import { PathField } from '../../src/Query/Filter/PathField';
55
import { RootField } from '../../src/Query/Filter/RootField';
66
import { makeQueryContext } from '../../src/Scripting/QueryContext';
7+
import { FunctionField } from '../../src/Query/Filter/FunctionField';
8+
import { SearchInfo } from '../../src/Query/SearchInfo';
9+
10+
const path = 'a/b/c.md';
11+
const task = new TaskBuilder().path(path).build();
12+
const queryContext = makeQueryContext(path);
713

814
describe('QueryContext', () => {
915
describe('values should all match their corresponding filters', () => {
10-
const path = 'a/b/c.md';
11-
const task = new TaskBuilder().path(path).build();
12-
const queryContext = makeQueryContext(path);
13-
14-
it('root', () => {
16+
it('query.file.root', () => {
1517
const instruction = `root includes ${queryContext.query.file.root}`;
1618
const filter = new RootField().createFilterOrErrorMessage(instruction);
1719
expect(filter).toMatchTask(task);
1820
});
1921

20-
it('path', () => {
22+
it('query.file.path', () => {
2123
const instruction = `path includes ${queryContext.query.file.path}`;
2224
const filter = new PathField().createFilterOrErrorMessage(instruction);
2325
expect(filter).toMatchTask(task);
2426
});
2527

26-
it('folder', () => {
28+
it('query.file.folder', () => {
2729
const instruction = `folder includes ${queryContext.query.file.folder}`;
2830
const filter = new FolderField().createFilterOrErrorMessage(instruction);
2931
expect(filter).toMatchTask(task);
3032
});
3133

32-
it('filename', () => {
34+
it('query.file.filename', () => {
3335
const instruction = `filename includes ${queryContext.query.file.filename}`;
3436
const filter = new FilenameField().createFilterOrErrorMessage(instruction);
3537
expect(filter).toMatchTask(task);
3638
});
3739
});
40+
41+
describe('non-file properties', () => {
42+
it('query.allTasks', () => {
43+
// Arrange
44+
// An artificial example, just to demonstrate that query.allTasks is accessible via scripting,
45+
// when the SearchInfo parameter is converted to a QueryContext.
46+
const instruction = 'group by function query.allTasks.length';
47+
const grouper = new FunctionField().createGrouperFromLine(instruction);
48+
expect(grouper).not.toBeNull();
49+
const searchInfo = new SearchInfo(path, [task]);
50+
51+
// Act
52+
const group: string[] = grouper!.grouper(task, searchInfo);
53+
54+
// Assert
55+
expect(group).toEqual(['1']);
56+
});
57+
});
3858
});
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<!-- placeholder to force blank line before included text -->
2+
3+
| Field | Type | Example |
4+
| ----- | ----- | ----- |
5+
| `query.allTasks` | `Task[]` | `[... an array with all the Tasks-tracked tasks in the vault ...]` |
6+
7+
8+
<!-- placeholder to force blank line after included text -->

tests/Scripting/QueryProperties.test.ts

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,21 @@
1-
import { expandPlaceholders } from '../../src/Scripting/ExpandPlaceholders';
2-
import { makeQueryContext } from '../../src/Scripting/QueryContext';
1+
import { makeQueryContextWithTasks } from '../../src/Scripting/QueryContext';
32

43
import { verifyMarkdownForDocs } from '../TestingTools/VerifyMarkdown';
54
import { MarkdownTable } from '../../src/lib/MarkdownTable';
5+
import { parseAndEvaluateExpression } from '../../src/Scripting/TaskExpression';
6+
import { TaskBuilder } from '../TestingTools/TaskBuilder';
67
import { addBackticks, determineExpressionType, formatToRepresentType } from './ScriptingTestHelpers';
78

89
describe('query', () => {
910
function verifyFieldDataForReferenceDocs(fields: string[]) {
1011
const markdownTable = new MarkdownTable(['Field', 'Type', 'Example']);
1112
const path = 'root/sub-folder/file containing query.md';
12-
const queryContext = makeQueryContext(path);
13+
const task = new TaskBuilder()
14+
.description('... an array with all the Tasks-tracked tasks in the vault ...')
15+
.build();
16+
const queryContext = makeQueryContextWithTasks(path, [task]);
1317
for (const field of fields) {
14-
const value1 = expandPlaceholders('{{' + field + '}}', queryContext);
18+
const value1 = parseAndEvaluateExpression(task, field, queryContext);
1519
const cells = [
1620
addBackticks(field),
1721
addBackticks(determineExpressionType(value1)),
@@ -32,4 +36,8 @@ describe('query', () => {
3236
'query.file.filenameWithoutExtension',
3337
]);
3438
});
39+
40+
it('search properties', () => {
41+
verifyFieldDataForReferenceDocs(['query.allTasks']);
42+
});
3543
});

tests/Scripting/ScriptingTestHelpers.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import moment from 'moment';
22
import { TasksDate } from '../../src/Scripting/TasksDate';
33
import { TaskRegularExpressions } from '../../src/Task/TaskRegularExpressions';
4+
import { Task } from '../../src/Task/Task';
45

56
export function formatToRepresentType(x: any): string {
67
if (typeof x === 'string') {
@@ -11,6 +12,10 @@ export function formatToRepresentType(x: any): string {
1112
return `moment('${x.format(TaskRegularExpressions.dateTimeFormat)}')`;
1213
}
1314

15+
if (x instanceof Task) {
16+
return x.description;
17+
}
18+
1419
if (x instanceof TasksDate) {
1520
return x.formatAsDateAndTime();
1621
}
@@ -33,7 +38,7 @@ export function addBackticks(x: any) {
3338
return quotedText;
3439
}
3540

36-
export function determineExpressionType(value: any) {
41+
export function determineExpressionType(value: any): string {
3742
if (value === null) {
3843
return 'null';
3944
}
@@ -42,13 +47,17 @@ export function determineExpressionType(value: any) {
4247
return 'Moment';
4348
}
4449

50+
if (value instanceof Task) {
51+
return 'Task';
52+
}
53+
4554
if (value instanceof TasksDate) {
4655
return 'TasksDate';
4756
}
4857

4958
if (Array.isArray(value)) {
5059
if (value.length > 0) {
51-
return `${typeof value[0]}[]`;
60+
return `${determineExpressionType(value[0])}[]`;
5261
} else {
5362
return 'any[]';
5463
}

0 commit comments

Comments
 (0)