Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions apps/roam/src/utils/__tests__/fireQuery.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,34 @@ describe("getDatalogQuery", () => {
]);
expect(formatted).toMatchObject({ text: "A", uid: "u1", Created: "123" });
});

it("adds internal find variables to the query output", async () => {
const built = getDatalogQuery({
conditions: [],
selections: [],
findVariables: [
{ label: "relationUid", variable: "condition-relSchema" },
{ label: "effectiveSource", variable: "?condition-relSource" },
],
});

expect(built.query).toContain("?condition-relSchema");
expect(built.query).toContain("?condition-relSource");

const formatted = await built.formatResult([
{ ":node/title": "A", ":block/uid": "u1" },
{ ":block/uid": "u1" },
"rel-1",
"source-1",
]);

expect(formatted).toMatchObject({
text: "A",
uid: "u1",
relationUid: "rel-1",
effectiveSource: "source-1",
});
});
});

describe("fireQuery", () => {
Expand Down
163 changes: 163 additions & 0 deletions apps/roam/src/utils/__tests__/getDiscourseContextResults.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import type { DiscourseNode } from "~/utils/getDiscourseNodes";
import type { DiscourseRelation } from "~/utils/getDiscourseRelations";

const mocks = vi.hoisted(() => ({
findDiscourseNode: vi.fn(),
fireQuery: vi.fn(),
generateUID: vi.fn(),
getSetting: vi.fn(),
}));

vi.mock("~/utils/deriveDiscourseNodeAttribute", () => ({
ANY_RELATION_NAME: "Has Any Relation To",
ANY_RELATION_REGEX: /Has Any Relation To/i,
}));

vi.mock("~/utils/extensionSettings", () => ({
getSetting: mocks.getSetting,
}));

vi.mock("~/utils/findDiscourseNode", () => ({
default: mocks.findDiscourseNode,
}));

vi.mock("~/utils/fireQuery", () => ({
default: mocks.fireQuery,
}));

vi.mock("~/utils/getDiscourseNodes", () => ({
default: () => [],
}));

vi.mock("~/utils/getDiscourseRelations", () => ({
default: () => [],
}));

import getDiscourseContextResults from "~/utils/getDiscourseContextResults";

const makeNode = ({
type,
text,
}: {
type: string;
text: string;
}): DiscourseNode => ({
type,
text,
shortcut: "",
specification: [],
backedBy: "user",
canvasSettings: {},
format: "{content}",
});

describe("getDiscourseContextResults", () => {
beforeEach(() => {
vi.clearAllMocks();
(globalThis as { window: unknown }).window = {
roamAlphaAPI: {
util: {
generateUID: mocks.generateUID,
},
},
};

mocks.generateUID.mockReturnValue("condition");
mocks.getSetting.mockReturnValue(true);
mocks.findDiscourseNode.mockReturnValue({ type: "CLM" });
});

it("regroups all-relation reified query results by schema order", async () => {
const onResult = vi.fn();
const nodes: DiscourseNode[] = [
makeNode({ type: "CLM", text: "Claim" }),
makeNode({ type: "QUE", text: "Question" }),
makeNode({ type: "EVD", text: "Evidence" }),
];
const relations: DiscourseRelation[] = [
{
id: "supports",
label: "Supports",
complement: "Supported By",
source: "CLM",
destination: "QUE",
triples: [],
},
{
id: "informs",
label: "Informs",
complement: "Informed By",
source: "EVD",
destination: "CLM",
triples: [],
},
];

mocks.fireQuery.mockResolvedValue([
{
text: "Evidence A",
uid: "evidence-a",
relationUid: "informs",
effectiveSource: "evidence-a",
},
{
text: "Question A",
uid: "question-a",
relationUid: "supports",
effectiveSource: "claim-a",
},
]);

const results = await getDiscourseContextResults({
uid: "claim-a",
nodes,
relations,
onResult,
});

expect(mocks.fireQuery).toHaveBeenCalledWith(
expect.objectContaining({
returnNode: "Any",
selections: [],
findVariables: [
{ label: "relationUid", variable: "condition-relSchema" },
{ label: "effectiveSource", variable: "condition-relSource" },
],
}),
);

expect(results[0]).toEqual({
label: "Supports",
results: {
"question-a": {
text: "Question A",
uid: "question-a",
relationUid: "supports",
effectiveSource: "claim-a",
target: "Question",
complement: 0,
id: "supports",
ctxTargetUid: "claim-a",
},
},
});
expect(results[1]).toEqual({
label: "Informed By",
results: {
"evidence-a": {
text: "Evidence A",
uid: "evidence-a",
relationUid: "informs",
effectiveSource: "evidence-a",
target: "Evidence",
complement: 1,
id: "informs",
ctxTargetUid: "claim-a",
},
},
});
expect(onResult).toHaveBeenNthCalledWith(1, results[0]);
expect(onResult).toHaveBeenNthCalledWith(2, results[1]);
});
});
3 changes: 2 additions & 1 deletion apps/roam/src/utils/deriveDiscourseNodeAttribute.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ import findDiscourseNode from "./findDiscourseNode";
import getDiscourseNodes from "./getDiscourseNodes";
import getDiscourseRelations from "./getDiscourseRelations";

export const ANY_RELATION_REGEX = /Has Any Relation To/i;
export const ANY_RELATION_NAME = "Has Any Relation To";
export const ANY_RELATION_REGEX = RegExp(ANY_RELATION_NAME, "i");

const evaluate = async (s: string) =>
window.RoamLazy
Expand Down
69 changes: 43 additions & 26 deletions apps/roam/src/utils/fireQuery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ export type QueryArgs = {
conditions: Condition[];
selections: Selection[];
inputs?: Record<string, string | number>;
findVariables?: {
label: string;
variable: string;
}[];
};
type RelationInQuery = {
id: string;
Expand All @@ -39,6 +43,12 @@ export type FireQueryArgs = QueryArgs & {
};

type FireQuery = (query: FireQueryArgs) => Promise<QueryResult[]>;
type DefinedSelection = {
mapper?: PredefinedSelection["mapper"];
pull: string;
label: string;
key: string;
};

const firstVariable = (
clause: DatalogClause | DatalogAndClause,
Expand Down Expand Up @@ -200,6 +210,7 @@ export const getDatalogQuery = ({
selections,
returnNode = DEFAULT_RETURN_NODE,
inputs = {},
findVariables = [],
}: FireQueryArgs) => {
const expectedInputs = getConditionTargets(conditions)
.filter((c) => /^:in /.test(c))
Expand All @@ -211,12 +222,7 @@ export const getDatalogQuery = ({
new Set([]),
);

const defaultSelections: {
mapper: PredefinedSelection["mapper"];
pull: string;
label: string;
key: string;
}[] = [
const defaultSelections: DefinedSelection[] = [
{
mapper: (r) => {
return {
Expand All @@ -237,26 +243,35 @@ export const getDatalogQuery = ({
key: "",
},
];
const userSelections: DefinedSelection[] = selections
.map((s) => ({
defined: predefinedSelections.find((p) => p.test.test(s.text)),
s,
}))
.filter(
(p): p is { defined: PredefinedSelection; s: Selection } => !!p.defined,
)
.map((p) => ({
mapper: p.defined.mapper,
pull: p.defined.pull({
where: whereClauses,
returnNode,
match: p.defined.test.exec(p.s.text),
}),
label: p.s.label || p.s.text,
key: p.s.text,
}))
.filter((p) => !!p.pull);
const internalFindVariables: DefinedSelection[] = findVariables.map(
({ label, variable }) => ({
pull: variable.startsWith("?") ? variable : `?${variable}`,
label,
key: variable,
}),
);
const definedSelections = defaultSelections.concat(
selections
.map((s) => ({
defined: predefinedSelections.find((p) => p.test.test(s.text)),
s,
}))
.filter(
(p): p is { defined: PredefinedSelection; s: Selection } => !!p.defined,
)
.map((p) => ({
mapper: p.defined.mapper,
pull: p.defined.pull({
where: whereClauses,
returnNode,
match: p.defined.test.exec(p.s.text),
}),
label: p.s.label || p.s.text,
key: p.s.text,
}))
.filter((p) => !!p.pull),
userSelections,
internalFindVariables,
);
const find = definedSelections.map((p) => p.pull).join("\n ");
const where = whereClauses.map((c) => compileDatalog(c, 1)).join("\n");
Expand All @@ -274,7 +289,9 @@ export const getDatalogQuery = ({
definedSelections
.map((c, i) => (prev: QueryResult) => {
const pullResult = result[i];
return typeof pullResult === "object" && pullResult !== null
return c.mapper &&
typeof pullResult === "object" &&
pullResult !== null
? Promise.resolve(
c.mapper(pullResult as PullBlock, c.key, prev),
).then((output) => ({
Expand Down
Loading