Aggregating personal details from meeting notes into Person pages

Problem:

When I meet with the same person multiple times, I feel bad when I don’t remember “the little things” about them. I want to have an easy way to extract personal details (e.g. college attended, favorite foods, kids’ names, partner name, etc…) from meeting notes into a Person page for individuals with whom I speak on a regular basis. This will also help me to thoughtful things for them over time that are personal to them.

Solution:

I include the snippet below in the Personal Notes section (header 2) of my Person template:

const personString = dv.current().file.name.split(" ").join("");

dv.list(dv.pages("").where(p => p[personString])[personString])

All of my Person notes in my People folder are titled with just the person’s name. So, the above snippet allows me to type the person’s name (caps sensitive) as inline metadata in any note, extract those details, and aggregate that text in the hub note for that Person. The line gets printed in the Person note as the actual text rather than just the link to the file.

E.g. if I write the below in any note, what will get printed in the Zack Ellis page will be “kids are Pinky and The Brain”

ZackEllis:: kids are Pinky and The Brain

The next thing I want to do is to figure out how to print both the file link and the plain text…e.g.

[[2022-11-23 Zack Ellis]]: kids are Pinky and The Brain

This is how Dataview works if I do something like:

list book
from ""
where book

to aggregate recommended books from different people:

8 Likes

This is cool. I do something similar but exactly the opposite way… that is, I keep ‘people notes’ and show meetings that they were in.

I played with the idea of pulling ‘people note’ facts into a ‘meeting note’. Some assumptions I made were:

  1. The ‘people note’ has the tag ‘#person
  2. The meeting lists who is attending using an outgoing link, e.g. …
# Attendees
- [[John Smith]]
- [[Jane Doe]]

Below is what I came up with…

DataView JS Code…

let facts = ["children", "hobbies"]
displayAttendeeFacts(facts)

function displayAttendeeFacts(facts) {
	let people = getLinkedPeople()   

	let tableColumns = ["person"].concat(facts)
	let tableDataFunction = (page) => {
		let personColumn = [dv.fileLink(page.file.name)]
		let factColumns = facts.map(fact => page[fact])
		return personColumn.concat(factColumns)
	}
	
	if (people.length > 0) {
		dv.table(tableColumns, people.map(tableDataFunction).sort(page => page.file))
	} else {
		dv.paragraph("No people.")
	}
}

function getLinkedPeople() { 
	// Get filenames from outgoing links
	let outlinks = dv.current().file.outlinks || []
	let attendees = outlinks.map((link) => link.path.match(/.*\/(.*)\.md/)[1])

	// Retrieve 'people notes' with the same names as outgoing links
	let linkedPeople = []; 
	if (attendees.length > 0) {
		linkedPeople = dv.pages('#person')
			.filter(page => {
				let fileName = page.file.name
				let isNotATemplate = !fileName.contains("Template")
				let isAnAttendee = attendees.includes(fileName)
				return isNotATemplate && isAnAttendee
			})
	}

	return linkedPeople 
}
4 Likes

Love it. Very clever!

The question for a “non-coding” dummy - is how to achieve this type of outcome through existing tools or plugins, any ideas?

Dear Fyrefyta,

That’s a reasonable question. Not everyone is comfortable writing code. To my knowledge there isn’t a plugin that does what this query does.

If you want help getting this query working on your system, message me directly and I’ll help you out.

As an aside, it’s a lot of code to put into each “person file”. I’d recommend re-using the code by putting it into a “view”. Described here… Obsidian: Dataview Reuse. This article shows visually how to… | by Gareth Stretton | Oct, 2022 | Medium

Best of luck!

1 Like

Thanks for that. As I work on better articulating my use case and need, I may reach out, I appreciate the offer.

1 Like

Topic

Summary
  • How to pull the ‘person note’ metadata into a ‘meeting note’ via Dataview DQL query?

Test

Summary
  • dataview: v0.5.55

Input

Summary

the current note

  • Location: “100_Project/02_dataview/Q23_meeting/Q23_test_data”

folder: Meeting

  • filename : 20221217_meeting
```md
## Coffee Meeting

### Date
- 2022-12-17T16:00

### Attendees
- [[John Smith]]
- [[Jane Doe]]

### Host
-  [[Tom Felton]]


### Q23_DQL10 (OR Q23_DQL20)



```

dictionary files

  • Location: “100_Project/02_dataview/Q23_meeting/Q23_test_data”

folder: Persons

  • filename : Jane Doe
```yaml
---
tags: [ person ]
children: [ "Nicholas" ]
---

hobbies:: swimming

```

  • filename : John Smith
```yaml
---
tags: [ person ]
children: [ "Emily" ]
---

hobbies:: walking

```

  • filename : Tom Felton
```yaml
---
tags: [ person ]
children: [ "Eliza" ]
---

hobbies:: cycling

```

DQL10_pull_person_note_metadata_into_meeting_note_by_using_file.outlinks

Summary

Main DQL

Code Name Data type Group By Purposes Remark
DQL10
_pull_person_note_metadata
_into_meeting_note
_by_using_file.outlinks
(Meeting notes)
this.file.outlinks:
a list of links

(Person notes)
tags:
a list of strings
no 1.To break up a list this.file.outlinks into each individual element P
1.1 this.file.outlinks: a list of links
1.2 P: a link

2.To filter by P.file.name
3.To filter by P.tags
4.To display the result as a table

Code DQL10_pull_person_note_metadata_into_meeting_note_by_using_file.outlinks

Summary_code
title: DQL10_pull_person_note_metadata_into_meeting_note_by_using_file.outlinks =>1.To break up a list `this.file.outlinks` into each individual element `P` 1.1 `this.file.outlinks`: a list of links 1.2 `P`: a link 2.To filter by `P.file.name` 3.To filter by `P.tags` 4.To display the result as a table
collapse: close
icon: 
color: 
```dataview
TABLE WITHOUT ID
      P AS "person",
      P.children AS "children",
      P.hobbies AS "hobbies"

FROM "100_Project/02_dataview/Q23_meeting/Q23_test_data/Meeting/20221217_meeting.md"

FLATTEN this.file.outlinks AS P

WHERE !contains(P.file.name, "Template")
WHERE contains(P.tags, "person")
```

Screenshots(DQL10): using file.outlinks


DQL20_pull_person_note_metadata_into_meeting_note_by_using_L.outlinks

Summary

Main DQL

Code Name Data type Group By Purposes Remark
DQL20
_pull_person_note_metadata
_into_meeting_note
_by_using_L.outlinks
(Meeting notes)
L.outlinks:
a list of links

(Person notes)
tags:
a list of strings
no 1.To break up a list this.file.lists into each individual element L
1.1 this.file.lists: a list of links
1.2 L: a link

2.To define a field variable F_subpath
3.To define a field variable F_type
4.To filter by F_type
5.To filter by F_subpath
6.To FLATTEN L.outlinks AS P
7.To filter by P.file.name
8.To filter by P.tags
9.To display the result as a table

Code DQL20_pull_person_note_metadata_into_meeting_note_by_using_L.outlinks

Summary_code
title: DQL20_pull_person_note_metadata_into_meeting_note_by_using_L.outlinks =>1.To break up a list `this.file.lists` into each individual element `L` 1.1 `this.file.lists`: a list of links 1.2 `L`: a link 2.To define a field variable `F_subpath` 3.To define a field variable `F_type` 4.To filter by `F_type` 5.To filter by `F_subpath` 6.To FLATTEN L.outlinks AS P 7.To filter by `P.file.name`  8.To filter by `P.tags` 9.To display the result as a table
collapse: close
icon: 
color: 
```dataview
TABLE WITHOUT ID
      P AS "person",
      P.children AS "children",
      P.hobbies AS "hobbies"

FROM "100_Project/02_dataview/Q23_meeting/Q23_test_data/Meeting/20221217_meeting.md"

FLATTEN this.file.lists AS L
FLATTEN meta(L.header).subpath AS F_subpath
FLATTEN meta(L.header).type AS F_type

WHERE F_type = "header" 
WHERE F_subpath = "Attendees" OR F_subpath = "Host"

FLATTEN L.outlinks AS P
WHERE !contains(P.file.name, "Template")
WHERE contains(P.tags, "person")
```

Screenshots(DQL20): using L.outlinks


2 Likes

I love this, thanks! However, I’m getting a weird error now, though a bit ago I had it working perfectly. Any chance you know what’s going wrong here? See below for the error i’m getting:

I have the person linked to the meeting page like this:
attendee:: [[Person Link]]

That person’s linked page has this in their page:

___
## Interesting details:
notable details:: wow an interesting detail
hobbies:: golf
___
## Relations
spouse:: [[Persons Wife]]
children:: Child1 Name, Child2 Name
colleagues:: 

and my meeting page dataviewjs code is just like yours:

let facts = ["children", "spouse", "notable details", "hobbies"]
displayAttendeeFacts(facts)

function displayAttendeeFacts(facts) {
	let people = getLinkedPeople()

	let tableColumns = ["person"].concat(facts)
	let tableDataFunction = (page) => {
		let personColumn = [dv.fileLink(page.file.name)]
		let factColumns = facts.map(fact => page[fact])
		return personColumn.concat(factColumns)
	}

	if (people.length > 0) {
		dv.table(tableColumns, people.map(tableDataFunction).sort(page => page.file))
	} else {
		dv.paragraph("No people.")
	}
}

function getLinkedPeople() {
	// Get filenames from outgoing links
	let outlinks = dv.current().file.outlinks || []
	let attendees = outlinks.map((link) => link.path.match(/.*\/(.*)\.md/)[1])

	// Retrieve 'people notes' with the same names as outgoing links
	let linkedPeople = [];
	if (attendees.length > 0) {
		linkedPeople = dv.pages('#person')
			.filter(page => {
				let fileName = page.file.name
				let isNotATemplate = !fileName.contains("Template")
				let isAnAttendee = attendees.includes(fileName)
				return isNotATemplate && isAnAttendee
			})
	}

	return linkedPeople
}

Think you can crack the case why I might be getting that “Evaluation Error: TypeError: Cannot read properties of null (reading ‘1’)” ?

I recreated this scenario in a new vault and it worked. Nothing jumps out at me as to why there is a problem. ¯\(ツ)

My advice is:

  1. Restart Obsidian and see if the query works. I’ve noticed that sometimes the Dataview plugin will “glitch out” and only a restart “fixes it”.

  2. If it is still broken, try to isolate the issue…
    a. Recreate this scenario in a new vault
    b. Add one meeting at a time until the problem occurs.
    c. Compare the bad meeting with a good meeting.
    d. You may need to replace values in the bad meeting with those from the good one to identify bad data. Even the frontmatter.

Let me know how you get on. I suspect it is a “bad meeting” file.

Good luck!

Btw Great problem description :+1:

Thanks for getting back to me! I’ll try out those things in a new vault and see what happens.

and yeah, i figure make it as easy as possible for someone to help me!

1 Like

Hey @Gahrae, great script. I was not aware how “mighty” Dataview could be. I have implemented this into my template for meeting notes.

I just stumbled across a similar problem like @Dutchnesss

Evaluation Error: TypeError: Cannot read properties of null (reading '1')
    at eval (eval at <anonymous> (plugin:dataview), <anonymous>:25:72)
    at Array.map (<anonymous>)
    at Proxy.map (plugin:dataview:8038:39)
    at getLinkedPeople (eval at <anonymous> (plugin:dataview), <anonymous>:25:27)
    at displayAttendeeFacts (eval at <anonymous> (plugin:dataview), <anonymous>:6:15)
    at eval (eval at <anonymous> (plugin:dataview), <anonymous>:3:1)
    at DataviewInlineApi.eval (plugin:dataview:18404:16)
    at evalInContext (plugin:dataview:18405:7)
    at asyncEvalInContext (plugin:dataview:18415:32)
    at DataviewJSRenderer.render (plugin:dataview:18436:19)

I could trace this back to an image that I embedded - ![[Pasted image 20230808090346.png]] - into my notes. When I remove it the table comes back.

Any idea how to exclude images during the eval of the document and finding people?

Regards, Nils

Hi @N1L5 (and @Dutchnesss),

This problem can be solved by…

Replace this line

let attendees = outlinks.map((link) => link.path.match(/.*\/(.*)\.md/)[1])

with

let attendees = outlinks
  .filter((link) => link.path.endsWith('.md'))
  .map((link) => link.path.match(/.*\/(.*)\.md/)[1])

Explanation: The match function tried to extract out part of the file name. This would fail when the file name did not end with ‘.md’, that is, it was not a link to a markdown file. The solution is to first filter for only those links which are to markdown files.

Pro-tip: It can be helpful to open the Chrome developer console and use debugging statements to identify and resolve javascript issues. For example, add console.log(outlinks) to see what the contents are.

Enjoy!

1 Like

Thanks a lot! Very helpful with the details and explanations. I’m new to Python and JS - however coming from PHP, Java, C++, Obj C and SWIFT - I need to learn a new “dialect”

I found the Codeblock Reference for DataviewJS - this helps a lot. I’m currently trying to understand:

dv.table(tableColumns, people.map(tableDataFunction).sort(page => page.file))

Obviously this is the way to sort the Table, I have a column “org” - and I would like to use this to sort the table. So consulting the Codeblock Reference and your code, my column are in “let factColumns = facts.map(fact => page[fact])” so I thought:

dv.table(tableColumns, people.map(tableDataFunction).sort(page => page["org"]))

But this only gives me the same old table. So some more digging - try and error.

Regards, Nils

You’re welcome.

The code fragment sort(page => page.file) is actually broken. I’ve viewed it with the developer console and can see that the property doesn’t exist.

Attached is an image that shows using the developer console to examine the data. This image shows the correct way to sort by the person’s name. This happens to be in the property named path.

IMAGE: Sort by persons name (AKA path property)

However, you wanted to sort by an attribute, that is ‘org’.

A messy way to do this, is to use the index of the attribute:
.sort(page=> page[1], "asc"))

A cleaner way is to obtain this value directly from the associated markdown file given it’s name. Below is an updated code fragment to sort by a provided field and in a certain sort order (i.e. ‘asc’ or ‘desc’).

let facts = ["org"]
displayAttendeeFacts(facts, "desc", "org")
 
function displayAttendeeFacts(facts, sortOrder = "asc", sortField = "path") {
	let people = getLinkedPeople()

	let tableColumns = ["person"].concat(facts)

	let tableDataFunction = (page) => {
		let personColumn = [dv.fileLink(page.file.name)]
		let factColumns = facts.map(fact => page[fact])
		return personColumn.concat(factColumns)
	}

	if (people.length > 0) {
		dv.table(tableColumns, 
		people
			.map(tableDataFunction)
			.sort(item => dv.page(item[0])[sortField], sortOrder))
	} else {
		dv.paragraph("No people.")
	}
}

Overall, this code could be reworked to be cleaner, but it’ll get the result you are after.

2 Likes

Thanks a lot. I got it solved with the Index. I needed to figure out how “arrays” are working in JS. Well still not sure if it’s an array or struct, but you never know in some languages you can use an index, other use a name…

Your code sample is much better as it allows more flexibility in case for reuse!

Thanks again! Much more help than expected. I did some “stalking” and found your ko-fi page and used it! Great help! - I will take a deeper dive into debugging JS in the next couple of days when I have time.

1 Like

Adding a quick update here that I modified my code snipped that I include in the Person Note links back automatically to the Meeting Note where I added personal details:

const personString = dv.current().file.name.split(" ").join("");

dv.list(dv.pages("").where(p => p[personString]).map(p => {
	if (typeof p[personString] === "string") {
		return p.file.link + ": " + p[personString]
	} else {
		return p[personString].map((item, index) => {
			return p.file.link + ": " + p[personString][index]
		})
	}
}))

Looks like this in the Personal Note:

when I typed this in the Meeting Note:

2 Likes