Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Expose query.allTasks in scripting #2617

Merged
merged 7 commits into from
Jan 25, 2024
12 changes: 7 additions & 5 deletions docs/Introduction.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,22 @@ _In recent [releases](https://github.com/obsidian-tasks-group/obsidian-tasks/rel
Move the older ones down to the top of the comment block below...
-->

- X.Y.Z:
- `query.allTasks` is now available in custom searches: see [[Query Properties#Values for Query Search Properties|query search properties]].
- 6.0.0:
- Add [[Custom Sorting|custom sorting]].
- Document the [[Sorting#Default sort order|default sort order]].
- **Warning**: This release contains some **bug-fixes** to **sorting** and to treatment of **invalid dates**.
- 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.
- You may need to update any CSS snippets for the Edit or Postpone buttons: see [[How to style buttons]].
- 5.6.0:
- The [[Postponing|postpone]] menu now offers `today` and `tomorrow`.
- 5.5.0:
- The [[Create or edit Task]] modal can now edit Created, Done and Cancelled dates
- Add support for [[Dates#Cancelled date|cancelled dates]].

> [!Released]- Earlier Releases
>
> - 5.6.0:
> - The [[Postponing|postpone]] menu now offers `today` and `tomorrow`.
> - 5.5.0:
> - The [[Create or edit Task]] modal can now edit Created, Done and Cancelled dates
> - Add support for [[Dates#Cancelled date|cancelled dates]].
> - 5.4.0:
> - Add [[Layout#Full Mode|'full mode']] to turn off `short mode`.
> - Add any [[Grouping|'group by']] and [[Sorting|'sort by']] instructions to [[Explaining Queries|explain]] output.
Expand Down
2 changes: 1 addition & 1 deletion docs/Queries/Grouping.md
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ Since Tasks X.Y.Z, **[[Custom Grouping|custom grouping]] by Blocked By** is now
group by function task.blockedBy
```

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

Expand Down
16 changes: 16 additions & 0 deletions docs/Scripting/Query Properties.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,19 @@ This page documents all the available pieces of information in Queries that you
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]].
1. `query.file.pathWithoutExtension` was added in Tasks 4.8.0.
1. `query.file.filenameWithoutExtension` was added in Tasks 4.8.0.

## Values for Query Search Properties

<!-- placeholder to force blank line before included text --><!-- include: QueryProperties.test.query_search_properties.approved.md -->

| Field | Type | Example |
| ----- | ----- | ----- |
| `query.allTasks` | `Task[]` | `[... an array with all the Tasks-tracked tasks in the vault ...]` |

<!-- placeholder to force blank line after included text --><!-- endInclude -->

1. `query.allTasks` provides access to all the tasks that Tasks has read from the vault.
- If [[Global Filter|global filter]] is enabled, only tasks containing the global filter are included.
- The [[Global Query|global query]] does not affect `query.allTasks`: all tasks tracked by the Tasks plugin are included.
- See [[Task Properties]] for the available properties on each task.
- `query.allTasks` was added in Tasks X.Y.Z.
4 changes: 2 additions & 2 deletions src/Query/SearchInfo.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { Task } from '../Task/Task';
import { type QueryContext, makeQueryContext } from '../Scripting/QueryContext';
import { type QueryContext, makeQueryContextWithTasks } from '../Scripting/QueryContext';

/**
* SearchInfo contains selected data passed in from the {@link Query} being executed.
Expand Down Expand Up @@ -30,6 +30,6 @@ export class SearchInfo {
* @return A QueryContext, or undefined if the path to the query file is unknown.
*/
public queryContext(): QueryContext | undefined {
return this.queryPath ? makeQueryContext(this.queryPath) : undefined;
return this.queryPath ? makeQueryContextWithTasks(this.queryPath, this.allTasks) : undefined;
}
}
8 changes: 8 additions & 0 deletions src/Scripting/QueryContext.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { Task } from '../Task/Task';
import { TasksFile } from './TasksFile';

/**
Expand All @@ -18,6 +19,7 @@ import { TasksFile } from './TasksFile';
export interface QueryContext {
query: {
file: TasksFile;
allTasks: Readonly<Task[]>;
};
}

Expand All @@ -26,12 +28,18 @@ export interface QueryContext {
* @param path
*
* @see SearchInfo.queryContext
* @see makeQueryContextWithTasks
*/
export function makeQueryContext(path: string): QueryContext {
return makeQueryContextWithTasks(path, []);
}

export function makeQueryContextWithTasks(path: string, allTasks: Readonly<Task[]>): QueryContext {
const tasksFile = new TasksFile(path);
return {
query: {
file: tasksFile,
allTasks: allTasks,
},
};
}
36 changes: 28 additions & 8 deletions tests/Scripting/QueryContext.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,35 +4,55 @@ import { FolderField } from '../../src/Query/Filter/FolderField';
import { PathField } from '../../src/Query/Filter/PathField';
import { RootField } from '../../src/Query/Filter/RootField';
import { makeQueryContext } from '../../src/Scripting/QueryContext';
import { FunctionField } from '../../src/Query/Filter/FunctionField';
import { SearchInfo } from '../../src/Query/SearchInfo';

const path = 'a/b/c.md';
const task = new TaskBuilder().path(path).build();
const queryContext = makeQueryContext(path);

describe('QueryContext', () => {
describe('values should all match their corresponding filters', () => {
const path = 'a/b/c.md';
const task = new TaskBuilder().path(path).build();
const queryContext = makeQueryContext(path);

it('root', () => {
it('query.file.root', () => {
const instruction = `root includes ${queryContext.query.file.root}`;
const filter = new RootField().createFilterOrErrorMessage(instruction);
expect(filter).toMatchTask(task);
});

it('path', () => {
it('query.file.path', () => {
const instruction = `path includes ${queryContext.query.file.path}`;
const filter = new PathField().createFilterOrErrorMessage(instruction);
expect(filter).toMatchTask(task);
});

it('folder', () => {
it('query.file.folder', () => {
const instruction = `folder includes ${queryContext.query.file.folder}`;
const filter = new FolderField().createFilterOrErrorMessage(instruction);
expect(filter).toMatchTask(task);
});

it('filename', () => {
it('query.file.filename', () => {
const instruction = `filename includes ${queryContext.query.file.filename}`;
const filter = new FilenameField().createFilterOrErrorMessage(instruction);
expect(filter).toMatchTask(task);
});
});

describe('non-file properties', () => {
it('query.allTasks', () => {
// Arrange
// An artificial example, just to demonstrate that query.allTasks is accessible via scripting,
// when the SearchInfo parameter is converted to a QueryContext.
const instruction = 'group by function query.allTasks.length';
const grouper = new FunctionField().createGrouperFromLine(instruction);
expect(grouper).not.toBeNull();
const searchInfo = new SearchInfo(path, [task]);

// Act
const group: string[] = grouper!.grouper(task, searchInfo);

// Assert
expect(group).toEqual(['1']);
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<!-- placeholder to force blank line before included text -->

| Field | Type | Example |
| ----- | ----- | ----- |
| `query.allTasks` | `Task[]` | `[... an array with all the Tasks-tracked tasks in the vault ...]` |


<!-- placeholder to force blank line after included text -->
16 changes: 12 additions & 4 deletions tests/Scripting/QueryProperties.test.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,21 @@
import { expandPlaceholders } from '../../src/Scripting/ExpandPlaceholders';
import { makeQueryContext } from '../../src/Scripting/QueryContext';
import { makeQueryContextWithTasks } from '../../src/Scripting/QueryContext';

import { verifyMarkdownForDocs } from '../TestingTools/VerifyMarkdown';
import { MarkdownTable } from '../../src/lib/MarkdownTable';
import { parseAndEvaluateExpression } from '../../src/Scripting/TaskExpression';
import { TaskBuilder } from '../TestingTools/TaskBuilder';
import { addBackticks, determineExpressionType, formatToRepresentType } from './ScriptingTestHelpers';

describe('query', () => {
function verifyFieldDataForReferenceDocs(fields: string[]) {
const markdownTable = new MarkdownTable(['Field', 'Type', 'Example']);
const path = 'root/sub-folder/file containing query.md';
const queryContext = makeQueryContext(path);
const task = new TaskBuilder()
.description('... an array with all the Tasks-tracked tasks in the vault ...')
.build();
const queryContext = makeQueryContextWithTasks(path, [task]);
for (const field of fields) {
const value1 = expandPlaceholders('{{' + field + '}}', queryContext);
const value1 = parseAndEvaluateExpression(task, field, queryContext);
const cells = [
addBackticks(field),
addBackticks(determineExpressionType(value1)),
Expand All @@ -32,4 +36,8 @@ describe('query', () => {
'query.file.filenameWithoutExtension',
]);
});

it('search properties', () => {
verifyFieldDataForReferenceDocs(['query.allTasks']);
});
});
13 changes: 11 additions & 2 deletions tests/Scripting/ScriptingTestHelpers.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import moment from 'moment';
import { TasksDate } from '../../src/Scripting/TasksDate';
import { TaskRegularExpressions } from '../../src/Task/TaskRegularExpressions';
import { Task } from '../../src/Task/Task';

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

if (x instanceof Task) {
return x.description;
}

if (x instanceof TasksDate) {
return x.formatAsDateAndTime();
}
Expand All @@ -33,7 +38,7 @@ export function addBackticks(x: any) {
return quotedText;
}

export function determineExpressionType(value: any) {
export function determineExpressionType(value: any): string {
if (value === null) {
return 'null';
}
Expand All @@ -42,13 +47,17 @@ export function determineExpressionType(value: any) {
return 'Moment';
}

if (value instanceof Task) {
return 'Task';
}

if (value instanceof TasksDate) {
return 'TasksDate';
}

if (Array.isArray(value)) {
if (value.length > 0) {
return `${typeof value[0]}[]`;
return `${determineExpressionType(value[0])}[]`;
} else {
return 'any[]';
}
Expand Down