Skip to content

Commit b9cae02

Browse files
authored
feat: Explain view with the engine's execution plan and the SPARQL algebra (#237)
* feat(yasr): pass extra request parameters and an endpoint to plugin queries Host API 2: `yasr.executeQuery` now honours `extraArgs` and `endpoint`, so a result plugin can ask an engine for its execution plan (Blazegraph and Neptune need an `explain` parameter, Stardog its `/explain` path) with the authentication and settings of the tab. Plugins feature-detect it with `hostApi.queryRequest`. Documents the Explain plugin in the user guide. * docs: point the Explain plugin links to yasgui-explain-plugin * feat(yasgui): bundle the Explain plugin Registers @matdata/yasgui-explain-plugin 1.0.0 as the Explain view and adds it to the default plugin order after Chart.
1 parent f188b5a commit b9cae02

9 files changed

Lines changed: 280 additions & 7 deletions

File tree

‎docs/developer-guide.md‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3435,7 +3435,7 @@ export default class ChartPlugin implements Plugin {
34353435
}
34363436
```
34373437
3438-
### Host API for Result Plugins (yasr hostApi v1)
3438+
### Host API for Result Plugins (yasr hostApi v2)
34393439
34403440
YASR exposes a small host API so plugins share one describe panel and one toolbar style. Feature-detect it with `yasr.hostApi`, never with version strings, and keep your own fallback for older hosts:
34413441
@@ -3452,6 +3452,7 @@ const bar = host?.toolbar && typeof this.yasr.getPluginToolbar === "function" ?
34523452
- `yasr.describe(iri, { direction?, source?, newHistory? }): Promise<void>` opens the shared describe panel. It runs a CONSTRUCT query through `yasr.executeQuery` with `Accept: text/turtle` and never rejects; errors show in the panel. `newHistory: false` adds to the panel's back/forward history instead of starting a new one. `yasr.closeDescribe()` closes it, `yasr.isDescribeOpen()` reports its state, and YASR emits `"describe"` with `(yasr, iri, direction)` each time a resource is shown.
34533453
- `yasr.getPluginToolbar()` returns `div.yasr_pluginToolbar`, a full-width bar under the YASR header. YASR empties it before every `draw()` and hides it while it is empty. A plugin that redraws itself must remove its own items first. Build controls inside it with `.yasr_toolbarButton` (toggle with `aria-pressed="true"` or `.is-active`), `.yasr_toolbarSelect` (`<label><span>Label</span><select>`), `.yasr_toolbarSearch` (`<input type="search">`), `.yasr_toolbarGroup` and `.yasr_toolbarSpacer` (a direct child of the slot). Put "View settings" last, as a `.yasr_toolbarButton` with the Lucide `sliders-horizontal` icon.
34543454
- Hosts can set the YASR config hook `openQueryInNewTab(query)` to enable the panel's "Open as query" button. YASGUI sets it to open a new tab on the same endpoint.
3455+
- `yasr.executeQuery(query, options)` runs a background request with the endpoint, method, headers and authentication of the tab and resolves with the raw response (`status`, `headers`, `content`). Since host API 2 (`hostApi.queryRequest`), `options.extraArgs` adds request parameters next to the query (for example `{ explain: "details" }`) and `options.endpoint` sends the request to another URL with the same settings (for example Stardog's `/{db}/explain`). Older hosts ignore both, so a plugin that needs them checks `hostApi.queryRequest` first.
34553456
- `yasr.hostApi.assistant` is true when the host has an AI assistant (MatGUI unless `ai.enabled` is false). A plugin can then hand a question to it with `yasr.askAssistant({ title, prompt, context? })`, for example "Explain this chart" with the chart settings as `context`. MatGUI opens its AI view and handles the service, privacy notice and "Copy for AI"; the context is shown to the user under "What was sent", so keep it small. `askAssistant()` returns false when there is no assistant, so hide your AI button in that case:
34563457
34573458
```js

‎docs/user-guide.md‎

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@
6565
- [Graph Plugin](#graph-plugin)
6666
- [Geo Plugin](#geo-plugin)
6767
- [Chart Plugin](#chart-plugin)
68+
- [Explain Plugin](#explain-plugin)
6869
- [Error Plugin](#error-plugin)
6970
- [Plugin Selection](#plugin-selection)
7071
- [Describing a Resource](#describing-a-resource)
@@ -2163,6 +2164,58 @@ Your choices are saved per tab. When the query returns other variables, the char
21632164

21642165
Visit the [Chart Plugin repository](https://github.com/Matdata-eu/yasgui-chart-plugin) for full documentation and the [online demo](https://matdata-eu.github.io/yasgui-chart-plugin/).
21652166

2167+
### Explain Plugin
2168+
2169+
Shows how your query is executed. The **Explain** tab has two parts:
2170+
2171+
- **Execution plan**: the operations the triple store ran for your query, with the rows and the time of each. MatGUI asks the endpoint for it with one extra request after your query completed, using the authentication and request settings of the tab. The slowest operation is highlighted, which is usually the answer to "why is this query slow".
2172+
- **Algebra**: your query translated to SPARQL algebra, the form in which engines plan it (`project`, `join`, `leftjoin`, `filter`, `union`, ...). It is computed in MatGUI itself and works for any endpoint. Syntax errors are reported here with the parser's message.
2173+
2174+
**Best For:**
2175+
2176+
- Finding the join or scan that takes the time in a slow query
2177+
- Checking that a `FILTER` or `OPTIONAL` is evaluated where you think it is
2178+
- Learning how a query reads in SPARQL algebra
2179+
2180+
---
2181+
2182+
**Supported engines:**
2183+
2184+
| Engine | What the plan shows |
2185+
|---|---|
2186+
| QLever | The operation tree with time, rows and cache status per operation |
2187+
| GraphDB | The chosen order of the patterns with estimated sizes (no timings) |
2188+
| Stardog | The profiled plan with rows, wall time and memory per operator |
2189+
| Blazegraph | Blazegraph's explain page (syntax tree, operator plan, statistics) |
2190+
| Amazon Neptune | Neptune's explain table, one row per operator |
2191+
2192+
Virtuoso, Jena Fuseki, Oxigraph, RDF4J and Comunica publish no plan over the SPARQL protocol; for those endpoints only the algebra is shown.
2193+
2194+
---
2195+
2196+
**Getting Started:**
2197+
2198+
1. Run your query.
2199+
2. Open the **Explain** tab. The algebra appears at once.
2200+
3. MatGUI recognises the engine from the endpoint URL (QLever, GraphDB, Blazegraph, Stardog and Neptune have recognisable URLs). When it does not, pick the engine in the **Engine** list; the choice is remembered for that endpoint. **No plan** switches the request off for an endpoint.
2201+
4. **Refresh plan** asks the engine again, for example after the endpoint's cache warmed up.
2202+
2203+
Each operation of the plan can be folded. **Expand all**, **Collapse all** and **Copy** act on the whole tree; **Download** in the results header saves the plan and the algebra as a text file. The algebra's **Copy** gives the s-expression notation used by Apache Jena ARQ and the SPARQL specification, handy for issues and documentation.
2204+
2205+
---
2206+
2207+
**Good to know:**
2208+
2209+
- The plan request is a second, separate request. Your results are never changed by it, and when it fails (unsupported engine, authentication, CORS) only the plan section says so.
2210+
- QLever answers the plan request from its result cache, so operations that were already cached show their original times in their details.
2211+
- The GraphDB plan is asked with the `onto:explain` pseudo graph. RDF4J shares GraphDB's URL layout but has no explain, so the request fails there.
2212+
2213+
---
2214+
2215+
**More Information:**
2216+
2217+
Visit the [Explain Plugin repository](https://github.com/Matdata-eu/yasgui-explain-plugin) for full documentation and the [online demo](https://matdata-eu.github.io/yasgui-explain-plugin/).
2218+
21662219
### Error Plugin
21672220

21682221
Displays error messages and diagnostic information when queries fail.

‎package-lock.json‎

Lines changed: 193 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎package.json‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@
5454
"dependencies": {
5555
"@fortawesome/fontawesome-free": "^7.1.0",
5656
"@matdata/yasgui-chart-plugin": "^1.2.0",
57+
"@matdata/yasgui-explain-plugin": "^1.0.0",
5758
"@matdata/yasgui-geo-plugin": "^1.3.0",
5859
"@matdata/yasgui-graph-plugin": "^1.7.0",
5960
"@matdata/yasgui-table-plugin": "^1.7.1",

‎packages/yasgui/src/Tab.ts‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1851,6 +1851,8 @@ export class Tab extends EventEmitter {
18511851
customQuery: query,
18521852
customAccept: options?.acceptHeader,
18531853
signal: options?.signal,
1854+
extraArgs: options?.extraArgs,
1855+
customEndpoint: options?.endpoint,
18541856
silent: true,
18551857
});
18561858
return response;

‎packages/yasgui/src/index.ts‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,13 @@ import { default as Yasqe, PartialConfig as YasqeConfig, RequestConfig } from "@
1111
import { default as Yasr, Config as YasrConfig } from "@matdata/yasr";
1212
import { addClass, removeClass } from "@matdata/yasgui-utils";
1313
import ChartPlugin from "@matdata/yasgui-chart-plugin";
14+
import ExplainPlugin from "@matdata/yasgui-explain-plugin";
1415
import GeoPlugin from "@matdata/yasgui-geo-plugin";
1516
import GraphPlugin from "@matdata/yasgui-graph-plugin";
1617
import TablePlugin from "@matdata/yasgui-table-plugin";
1718
import { moveTabIdToFront, removeTabId } from "./tabNavigationHistory";
1819
import "@matdata/yasgui-chart-plugin/dist/yasgui-chart-plugin.min.css";
20+
import "@matdata/yasgui-explain-plugin/dist/yasgui-explain-plugin.min.css";
1921
import "@matdata/yasgui-graph-plugin/dist/yasgui-graph-plugin.min.css";
2022
import "@matdata/yasgui-table-plugin/dist/yasgui-table-plugin.min.css";
2123
import { ThemeManager, Theme } from "./ThemeManager";
@@ -37,6 +39,7 @@ Yasr.registerPlugin("Table", TablePlugin);
3739
Yasr.registerPlugin("Geo", GeoPlugin as any);
3840
Yasr.registerPlugin("Graph", GraphPlugin);
3941
Yasr.registerPlugin("Chart", ChartPlugin as any);
42+
Yasr.registerPlugin("Explain", ExplainPlugin as any);
4043
if (window) {
4144
//We're storing yasqe and yasr as a member of Yasgui, but _also_ in the window
4245
//That way, we dont have to tweak e.g. pro plugins to register themselves to both

0 commit comments

Comments
 (0)