Creating a dataview table Tasks and Subtasks from certain headers

I followed this post on how to organize Tasks into a Dataview table, super helpful. But I’m having a hard time removing Subtasks from the first column.
Obsidian Forum | Create Dataview Table of Multiple Tasks Within Notes

I am using Dataview and Tasks community plugins

What I’m trying to do

  1. Remove Subtasks from being listed in the first column of the Dataview table
  2. Filter the table to tasks from specific pages and headers

Things I have tried

  1. Modifying regex but I couldn’t troubleshoot it very well. I’m familiar with JavaScript but had no luck
TABLE WITHOUT ID 
	regexreplace(Tasks.text, "\[.*$", "") AS Task, 
	choice(Tasks.completed, "🟢", "🔴") AS Status, 
	Tasks.completion AS "Completion Date", 
	Tasks.due AS "Due Date", Tasks.created AS "Created Date", 
	Tasks.priority AS "Priority", 
	regexreplace(Tasks.subtasks.text, "\[.*$", "") AS Subtasks, 
	choice(Tasks.subtasks.completed, "🟢", "🔴") AS "Subtasks status", 
	file.link AS "File"
FROM #task
WHERE file.tasks
FLATTEN file.tasks AS Tasks
SORT Tasks.due desc 
  1. Including WHERE filter to grab the page and header needed according to the docs.
WHERE header = [[Page Name#Header]]

I got empty tables every time. Path, page name, extension, no extension, every variation I could think of. No luck

Thanks in advance, I really appreciate the help

Could you please show some of the tasks you want to visualize, preferably with some subtasks and how you’d like the final result? (Even if that would be just a mockup)

Example

Ideally works with further nesting subtasks (subtasks have subtasks) → only the eldest parent task gets a dedicated row

### Task List
- [ ] #task Test task to do for my internet friends [created:: 2023-04-24] [start:: 2023-05-01] [scheduled:: 2023-04-25] [due:: 2023-04-28]
	- [ ] Update post with this example
	- [ ] Wait for forum Gods to bestow their wisdom

- [ ] #task Return to my Vault and construct a shrine to (username) [priority:: high] [created:: 2023-04-24] [scheduled:: 2023-04-29] [due:: 2023-04-30]

Thanks again in advance, I really appreciate the help


1 Like

It seems you’re wanting to use only the main level of tasks which are tagged with #task, so is it enough if you change the WHERE-clause to:

WHERE file.tasks AND contains(Tasks.tags, "#task")

For this to work you also need to move the WHERE-clause one line down, below the FLATTEN.

Untested, and written on mobile way too early in the morning… :wink:

1 Like

Thanks that works really well, I never thought of just grabbing it with contains() Great idea!

Have any ideas how to grab tasks from certain Heading sections? That way it’s easy to make a dataview table for each Heading section

For example

# Task List 1
- [ ] #task Test task to do for my internet friends [created:: 2023-04-24] [start:: 2023-05-01] [scheduled:: 2023-04-25] [due:: 2023-04-28]
	- [ ] Update post with this example
	- [ ] Wait for forum Gods to bestow their wisdom

- [ ] #task Return to my Vault and construct a shrine to (username) [priority:: high] [created:: 2023-04-24] [scheduled:: 2023-04-29] [due:: 2023-04-30]

# Task List 2
- [ ] #task An entirely different task [created:: 2023-04-24] [start:: 2023-05-01] [scheduled:: 2023-04-25] [due:: 2023-04-28]
	- [ ] Along with it's own subtask

Tasks (and lists) have associated some link variants like link, section and header, where they’re populated with the most appropriate link available related to the item at hand.

These links can be dissected using meta(), and especially for header links, the subpath is an interesting part of this, so try something like the following:

```dataview
TASK
WHERE contains(tags, "#task")
  AND meta(header).subpath = "Task List 1"
```

Or with your FLATTEN ... as Tasks variant:

WHERE contains(Tasks.tags, "#task")
  AND meta(Tasks.header).subpath = "Task List 1"
1 Like

Wow, that works amazingly!

For future internet troubleshooters… You have to target the exact header, nested headings don’t carry over.

Another question…

Example 3

Tasks without subtasks list the fields in the first column for some reason. Is there a way to get rid of that?

Tasks with subtasks are listed perfectly.

1 Like

I don’t see that behaviour when testing with the tasks provide by you. Try restarting Obsidian, or doing a Force reload, or potentially check whether the task has some extra characters or stuff causing your regex’s to misbehave.

1 Like

Force reload worked, it’s a lot better now thanks!

Last question…
How would I do conditional formatting for Task Status vs the boolean currently done.

Example 4

TABLE WITHOUT ID 
	regexreplace(Tasks.text, "\[.*$", "") AS Task, 
	choice(Tasks.completed, "🟢", "🔴") AS Status, 
	Tasks.completion AS "Completion Date", 
	Tasks.due AS "Due Date", 
	Tasks.created AS "Created Date", 
	Tasks.priority AS "Priority", 
	regexreplace(Tasks.subtasks.text, "\[.*$", "") AS Subtasks, 
	choice(Tasks.subtasks.completed, "🟢", "🔴") AS "Subtasks status", 
	Tasks.due - date(today) AS "Time Left"
FROM #task
FLATTEN file.tasks AS Tasks
WHERE file.tasks AND contains(Tasks.tags, "#task")
    AND meta(Tasks.header).subpath = "Task List 1"
SORT default(Tasks.due, "") ASC

Line 3:
choice(Tasks.completed, "🟢", "🔴") AS Status,

Maybe something like this pseudo code
choice(Tasks.status, " " = "🔴", "/" = "🟡", "-" = "⚫️", "x" = "🟢") AS Status,

There are two options I’m currently using for that kind of multiple choice:

  choice(Tasks.status = " ", "🔴",
    choice( Tasks.status = "/", "🟡",
      choice( Tasks.status = "-", "⚫️", 
        choice( Tasks.status = "x", "🟢", "?")))) as Status

But this first variant gets ugly very fast, and I’m mostly using this if I’m not actually matching a given value. In other words, if I need to do larger than or less than comparison stuff.

The other variant is ideal for lookup up a given value, like Tasks.status, and that is to use a dictionary/object lookup:

  default(object(
    " ", "🔴",
    "/", "🟡",
    "-", "⚫️", 
    "x", "🟢")[Tasks.status), "?") as Status

Given proper icons in the second and fourth row, this will look up the various status codes and match to its icon, and if not found it should present you with the question mark.

Either of these variants should be just to insert into your column definitions, but some care needs to be taken with regards to the subtask statuses.

Special handling for the subtask statuses

In your handling of the subtask status you’re actually utilising some deep magic related to how Dataview handles arrays, which can’t be done using this object syntax, so we now need to do a proper map of the status field:

	map(Tasks.subtasks.status, (s) => default(object(
      " ", "🔴",
      "/", "🟡",
      "-", "⚫️", 
      "x", "🟢")[s], "?")) as "Subtask status",

This code will loop through all the items of the subtasks, and map each accordingly to its status.

2 Likes

That’s a great fix, object/dictionary is so much better.

To be comprehensive… I saw another post asking about formatting text based on field values in the table. Maybe we can answer it here.

For example
How could we format the row to strikethrough when the Status is set to Cancelled “-”.

Something like this?
FLATTEN choice(Status = "-", "<s>" + Task + "</s>", Status)

A shrine is under construction for @holroy who graced this Obsidian pleb with their Dataview wisdom. Many thanks.

Here’s the solution I’ve been testing and seems to be working really well. Using community plugin Tasks and Dataview.

Solution

This version allows you to do some nifty things:

  • Create multiple dataview tables by headings — for example here is Sample Tasks. You can use this to separate Work tasks from Personal tasks or have a dedicated table for large complex projects with many tasks, etc.
  • Conditional formatting emoji for task Status
  • Lists Subtasks and their status
  • How many days until its due

Dataview Code Snippet:

```dataview
TABLE WITHOUT ID 
	regexreplace(Tasks.text, "\[.*$", "") AS Task, 
	default(object(
	    " ", "🔴",
	    "/", "🟡",
	    "-", "⚫️", 
	    "x", "🟢")[Tasks.status], "?") AS Status,
	Tasks.scheduled AS "Scheduled Date", 
	Tasks.due AS "Due Date", 
	Tasks.completion AS "Completion Date", 
	Tasks.priority AS "Priority", 
	regexreplace(Tasks.subtasks.text, "\[.*$", "") AS Subtasks, 
	map(Tasks.subtasks.status, (s) => default(object(
	      " ", "🔴",
	      "/", "🟡",
	      "-", "⚫️", 
	      "x", "🟢")[s], "?")) as "Subtask status",
	Tasks.due - date(today) AS "Time Left"
FROM #task
FLATTEN file.tasks AS Tasks
WHERE file.tasks AND contains(Tasks.tags, "#task")
	AND meta(Tasks.header).subpath = "Sample Tasks"
SORT default(Tasks.due, "") ASC 

Hope that’s easy enough to follow along and modify however you need.

1 Like

This topic was automatically closed 7 days after the last reply. New replies are no longer allowed.