Tracing backlinks through a specific note structure

What I’m trying to do

I have a set of Notion-like databases that have been set up with the DB Folder and Dataview plugins.

The databases have relation columns connecting them as illustrated in the picture. Each relation is bidirectional but the picture illustrates the workflow.

I want to set up a template for entries in the Summary database where all of the backlinked books, newspapers, and blogs are listed.

Things I have tried

The workflow is static at the moment, so I began by exploring how to search through each set of Details databases one-by-one. I found that this dataviewjs code can be adapted to list the backlinked entries from any single Details database that are directly linked to a Summary database entry:

let summaryLinks = dv.current().file.inlinks;

let summaryDetailsOne = summaryLinks.filter(summaryLinks => summaryLinks.path.split('/').includes("Details Database #1"));

dv.list(summaryDetailsOne);

However, I don’t know how to search through the backlinks for entries in the new list. This approach for listing the second-order backlinks did not work:

dv.list(dv.summaryDetailsOne.file.inlinks);

So I looked around and found a few threads with similar goals (e.g. here, here, and here). I was pointed toward a similar goal in the dataview documentation by dwuw’s thread. That now has me working on this code that I’ve adapted from the documentation:

let page = dv.current().file.path;
let pages = new Set();

let stack = [page];
while (stack.length > 0) {
    let elem = stack.pop();
    let meta = dv.page(elem);
    if (!meta) continue;

    for (let inlink of meta.file.inlinks.array()) {
        if (pages.has(inlink.path)) continue;
        
        if (
	    pages.path.split('/').includes("Database details #4") ||
	    pages.path.split('/').includes("Database details #3") ||
	    pages.path.split('/').includes("Database details #2") ||
	    pages.path.split('/').includes("Database details #1") ||
	    pages.path.split('/').includes("Content database") ||
	    pages.path.split('/').includes("Books database") ||
	    pages.path.split('/').includes("Newspaper database") ||
            pages.path.split('/').includes("Blog database")
	) continue;
		
	if(
	    pages.path.split('/').includes("Books database") ||
	    pages.path.split('/').includes("Newspaper database") ||
            pages.path.split('/').includes("Blog database")
	)
        pages.add(inlink.path);
        stack.push(inlink.path);
				
    }
}

let data = dv.array(Array.from(pages)).map(p => dv.page(p));

dv.list(data.name)

I found that the original code in the documentation was causing Obsidian to freeze, so I tried focusing the backlinks search to specific folders. But my use of split is throwing an error now.

Any help here is greatly appreciated!

I tried again today using the code from scholarInTraining here and it is grabbing lots of files. It must have overloaded my computer yesterday when I used dv.list instead the dv.table code in that post.

However, the code is grabbing too many items because it is searching through backlinked items that are outside of the illustration.

I am trying to add filters into the while loop to restrict the folders it looks at, but right now the code is working opposite to how I want it to - it is now ignoring the folders where I want it to look.

let page = dv.current().file.path;
let pages = new Set();
let stack = [page];
while (stack.length > 0) {
    let elem = stack.pop();
    let meta = dv.page(elem);
    if (!meta) continue;

    for (let inlink of meta.file.inlinks.array()) {
        if (pages.has(inlink.path)) continue;
        
        if (
        inlink.path.split('/').includes("Database details #4") ||
        inlink.path.split('/').includes("Database details #3") ||
        inlink.path.split('/').includes("Database details #2") ||
        inlink.path.split('/').includes("Database details #1") ||
        inlink.path.split('/').includes("Content Database")
        )
        continue;
        pages.add(inlink.path);
        stack.push(inlink.path);
    }
}

let data = dv.array(Array.from(pages)).map(p => dv.page(p));

dv.table(["File Link", "file.path"], data.map(p => [p.file.link, p.file.path]));

Seems to be working now :slight_smile:. I’m sure it can be optimized though:

let page = dv.current().file.path;
let pages = new Set();
let stack = [page];
while (stack.length > 0) {
    let elem = stack.pop();
    let meta = dv.page(elem);
    if (!meta) continue;

    for (let inlink of meta.file.inlinks.array()) {
        if (pages.has(inlink.path)) continue;

        if (
        inlink.path.includes("Database level 1") ||
        inlink.path.includes("Database level 2") ||
        inlink.path.includes("Database level 3") ||
        inlink.path.includes("Database level 4") ||
        inlink.path.includes("Content database") ||
        inlink.path.includes("Books database") ||
        inlink.path.includes("Newspaper database") ||
        inlink.path.includes("Blog database")
        ) {
        pages.add(inlink.path);
        stack.push(inlink.path);
        }
    }
}

let data = dv.array(Array.from(pages)).map(p => dv.page(p));

data = data.filter(data => 
	data.file.path.split('/').includes("Books database") ||
	data.file.path.split('/').includes("Newspaper database") ||
	data.file.path.split('/').includes("Blog database")
	);

dv.table(["Content Sources"], data.map(p => [p.file.link]));

Is there any particular reason you’re using dataviewjs to do this? It can be done using Dataview too, although it can look a little confusing.

I’ve not setup the correct structure, but let me try to illustrate how it possible can be done:

```dataview
TABLE databaseLinks, contentLinks
WHERE file = this.file
FLATTEN list(filter(file.inlinks, (i) => contains(i.file.path, "Details database"))) as databaseLinks
FLATTEN list(filter( databaseLinks.file.inlinks, (i2) => endswith(i2.file.path, "database"))) as contentLinks
```

What criteria you would want for the second and/or third order inlinks I leave up to you, but hopefully you get the gist of the idea.

1 Like

Thanks for the idea @holroy. I’m new to adding scripts in Obsidian in general and the path for dataviewjs in the documentation just happened to work.

1 Like

I’m adding an update here because the script does not do exactly what I need. Since I have my databases connected using bidirectional links, the script ends up grabbing links that are irrelevant to the lineage I’m trying to trace.

I want to trace back from one entry in Takeaways to all of the relevant entries in Studies and Patents. That way I can know exactly which studies and patents were used to as evidence for a takeaway. Here is a more specific illustration of what I’m trying to do:

Here is an example scenario of what the script currently does:

In this example, one set of links is correctly traced from a takeaway to the original studies. But one of the links in “Level 1 Insights” is connected to multiple entries in “Level 2 Insights”, causing the script to trace a separate lineage that ends up grabbing irrelevant links and thus irrelevant studies.

I assume that switching over to unidirectional links could fix this. But as I understand it, there would be no going back after that without a lot of manual work.

Is there a way to trace links the way I want without switching to unidirectional links?

I managed to get this working the way I want using this code:

```dataviewjs
// ALL PAGE LINKS
// Grab all links from the current page
let takeawayLinks = dv.array(dv.current().file.path).flatMap(p => dv.page(p).file.inlinks);

// Filter out links from the takeaway that are specific to each type of insight
let takeawayLinksLevelL1Insights = Array.from(takeawayLinks.filter(takeawayLinks => takeawayLinks.path.includes("Level 1 Insights")));

let takeawayLinksLevelL2Insights = Array.from(takeawayLinks.filter(takeawayLinks => takeawayLinks.path.includes("Level 2 Insights")));

let takeawayLinksLevelL3Insights = Array.from(takeawayLinks.filter(takeawayLinks => takeawayLinks.path.includes("Level 3 Insights")));

let takeawayLinksLevelL4Insights = Array.from(takeawayLinks.filter(takeawayLinks => takeawayLinks.path.includes("Level 4 Insights")));



// LEVEL 4 INSIGHTS
// Grab all directly-linked and indirectly-linked Level 4 Insights
let level4Insights = takeawayLinksLevelL4Insights;

// Filter out duplicates from level4Insights
level4Insights = dv.array(level4Insights).groupBy(p => dv.page(p).file.link).key.distinct();


// LEVEL 4 INSIGHTS - ALL LINKS
// Grab all links from Level 4 Insights
let level4InsightsLinks = dv.array(level4Insights).flatMap(p => dv.page(p).file.inlinks);


// LEVEL 4 INSIGHTS - LINKS TO OTHER INSIGHTS
// Filter out links from Level 4 Insights that are specific to other types of insights
let level4InsightsLinksLevel3Insights = Array.from(level4InsightsLinks.filter(level4InsightsLinks => level4InsightsLinks.path.includes("Level 3 Insights")));

let level4InsightsLinksLevel2Insights = Array.from(level4InsightsLinks.filter(level4InsightsLinks => level4InsightsLinks.path.includes("Level 2 Insights")));

let level4InsightsLinksLevel1Insights = Array.from(level4InsightsLinks.filter(level4InsightsLinks => level4InsightsLinks.path.includes("Level 1 Insights")));


// LEVEL 4 INSIGHTS - LINKS TO ASSUMPTIONS
// Grab all links to Assumptions from Level 4 Insights
let level4InsightsAssumptions = Array.from(level4InsightsLinks.filter(level4InsightsLinks => level4InsightsLinks.path.includes("Assumptions")));

// Filter out duplicates from Assumptions linked to Level 4 Insights
level4InsightsAssumptions = dv.array(level4InsightsAssumptions).groupBy(p => dv.page(p).file.link).key.distinct();



// LEVEL 3 INSIGHTS
// Grab all directly-linked and indirectly-linked Level 3 Insights
let level3Insights = takeawayLinksLevelL3Insights.concat(level4InsightsLinksLevel3Insights);

// Filter out duplicates from level3Insights
level3Insights = dv.array(level3Insights).groupBy(p => dv.page(p).file.link).key.distinct();


// LEVEL 3 INSIGHTS - ALL LINKS
// Grab all links from Level 3 Insights
let level3InsightsLinks = dv.array(level3Insights).flatMap(p => dv.page(p).file.inlinks);


// LEVEL 3 INSIGHTS - LINKS TO OTHER INSIGHTS
// Filter out links from Level 3 Insights that are specific to other types of insights
let level3InsightsLinksLevel2Insights = Array.from(level3InsightsLinks.filter(level3InsightsLinks => level3InsightsLinks.path.includes("Level 2 Insights")));

let level3InsightsLinksLevel1Insights = Array.from(level3InsightsLinks.filter(level3InsightsLinks => level3InsightsLinks.path.includes("Level 1 Insights")));


// LEVEL 3 INSIGHTS - LINKS TO ASSUMPTIONS
// Grab all links to Assumptions from Level 3 Insights
let level3InsightsAssumptions = Array.from(level3InsightsLinks.filter(level3InsightsLinks => level3InsightsLinks.path.includes("Assumptions")));

// Filter out duplicates from Assumptions linked to Level 3 Insights
level3InsightsAssumptions = dv.array(level3InsightsAssumptions).groupBy(p => dv.page(p).file.link).key.distinct();



// LEVEL 2 INSIGHTS
// Grab all directly-linked and indirectly-linked Level 2 Insights
let level2Insights = takeawayLinksLevelL2Insights.concat(level4InsightsLinksLevel2Insights, level3InsightsLinksLevel2Insights);

// Filter out duplicates from level2Insights
level2Insights = dv.array(level2Insights).groupBy(p => dv.page(p).file.link).key.distinct();


// LEVEL 2 INSIGHTS - ALL LINKS
// Grab all links from Level 2 Insights
let level2InsightsLinks = dv.array(level2Insights).flatMap(p => dv.page(p).file.inlinks);


// LEVEL 2 INSIGHTS - LINKS TO OTHER INSIGHTS
// Filter out links from Level 2 Insights that are specific to other types of insights
let level2InsightsLinksLevel1Insights = Array.from(level2InsightsLinks.filter(level2InsightsLinks => level2InsightsLinks.path.includes("Level 1 Insights")));


// LEVEL 2 INSIGHTS - LINKS TO ASSUMPTIONS
// Grab all links to Assumptions from Level 2 Insights
let level2InsightsAssumptions = Array.from(level2InsightsLinks.filter(level2InsightsLinks => level2InsightsLinks.path.includes("Assumptions")));

// Filter out duplicates from Assumptions linked to Level 2 Insights
level2InsightsAssumptions = dv.array(level2InsightsAssumptions).groupBy(p => dv.page(p).file.link).key.distinct();



// LEVEL 1 INSIGHTS
// Grab all directly-linked and indirectly-linked Level 1 Insights
let level1Insights = takeawayLinksLevelL1Insights.concat(level4InsightsLinksLevel1Insights, level3InsightsLinksLevel1Insights, level2InsightsLinksLevel1Insights);

// Filter out duplicates from level1Insights
level1Insights = dv.array(level1Insights).groupBy(p => dv.page(p).file.link).key.distinct();


// LEVEL 1 INSIGHTS - ALL LINKS
// Grab all links from Level 1 Insights
let level1InsightsLinks = dv.array(level1Insights).flatMap(p => dv.page(p).file.inlinks);


// LEVEL 1 INSIGHTS - LINKS TO ASSUMPTIONS
// Grab all links to Assumptions from Level 1 Insights
let level1InsightsAssumptions = Array.from(level1InsightsLinks.filter(level1InsightsLinks => level1InsightsLinks.path.includes("Assumptions")));

// Filter out duplicates from Assumptions linked to Level 1 Insights
level1InsightsAssumptions = dv.array(level1InsightsAssumptions).groupBy(p => dv.page(p).file.link).key.distinct();



// SOURCE CONTENT
// Grab all links to Source Content from Level 1 Insights
let sourceContent = Array.from(level1InsightsLinks.filter(level1InsightsLinks => level1InsightsLinks.path.includes("Source Content")));

// Filter out duplicates from Source Content
sourceContent = dv.array(sourceContent).groupBy(p => dv.page(p).file.link).key.distinct();


// SOURCE CONTENT - ALL LINKS
// Grab all links from indirectly-linked Source Content
let sourceContentLinks = dv.array(sourceContent).flatMap(p => dv.page(p).file.inlinks);

// Filter out duplicates from sourceContentLinks
sourceContentLinks = dv.array(sourceContentLinks).groupBy(p => dv.page(p).file.link).key.distinct();



// STUDIES
// Grab all links to Studies from Source Content
let studies = Array.from(sourceContentLinks.filter(sourceContentLinks => sourceContentLinks.path.includes("Studies")));

// Filter out duplicates from studies
studies = dv.array(studies).groupBy(p => dv.page(p).file.link).key.distinct();



// PATENTS
// Grab all links to Patents from Source Content
let patents = Array.from(sourceContentLinks.filter(sourceContentLinks => sourceContentLinks.path.includes("Patents")));

// Filter out duplicates from studies
patents = dv.array(patents).groupBy(p => dv.page(p).file.link).key.distinct();



// PREPARATION FOR TABLES
level1Insights = dv.array(Array.from(level1Insights)).map(p => dv.page(p));

level1InsightsAssumptions = dv.array(Array.from(level1InsightsAssumptions)).map(p => dv.page(p));

level2Insights = dv.array(Array.from(level2Insights)).map(p => dv.page(p));

level2InsightsAssumptions = dv.array(Array.from(level2InsightsAssumptions)).map(p => dv.page(p));

level3Insights = dv.array(Array.from(level3Insights)).map(p => dv.page(p));

level3InsightsAssumptions = dv.array(Array.from(level3InsightsAssumptions)).map(p => dv.page(p));

level4Insights = dv.array(Array.from(level4Insights)).map(p => dv.page(p));

level4InsightsAssumptions = dv.array(Array.from(level4InsightsAssumptions)).map(p => dv.page(p));

sourceContent = dv.array(Array.from(sourceContent)).map(p => dv.page(p));

studies = dv.array(Array.from(studies)).map(p => dv.page(p));

patents = dv.array(Array.from(patents)).map(p => dv.page(p));



// TABLES
dv.table(["Studies", "Citation Key"], studies.map(p => [p.file.frontmatter.Citation, p.file.name]));

dv.table(["Patents", "Citation Key"], patents.map(p => [p.file.frontmatter.Citation, p.file.name]));

dv.table(["Level 1 Insights"], level1Insights.map(p => [p.file.name]));

dv.table(["Level 1 Insights - Assumptions"], level1InsightsAssumptions.map(p => [p.file.name]));

dv.table(["Level 2 Insights"], level2Insights.map(p => [p.file.name]));

dv.table(["Level 2 Insights - Assumptions"], level2InsightsAssumptions.map(p => [p.file.name]));

dv.table(["Level 3 Insights"], level3Insights.map(p => [p.file.name]));

dv.table(["Level 3 Insights - Assumptions"], level3InsightsAssumptions.map(p => [p.file.name]));

dv.table(["Level 4 Insights"], level4Insights.map(p => [p.file.name]));

dv.table(["Level 4 Insights - Assumptions"], level4InsightsAssumptions.map(p => [p.file.name]));
```

The code will grab links step-wise starting at Takeaways and ending at Studies/ Patents. It’s also set up to grab Assumptions specifically from where they are linked in the structure. Then it creates tables for everything at the end so I can call a single script and generate everything for a takeaway. This is a current illustration of how the hierarchy is set up right now:

I’m sure the code can be made much more efficient. But at least it seems to be working now :slight_smile:

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