-
Notifications
You must be signed in to change notification settings - Fork 21
Add value plugin for inline JSON and YAML data in markdown fence blocks, schema definitions for inline data elements, and compiler support #130
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 6 commits
Commits
Show all changes
47 commits
Select commit
Hold shift + click to select a range
036d221
Initial plan
Copilot e05d3af
Add JSON plugin for inline JSON data
Copilot 0ab0f42
Update compiler to handle JSON format in dataLoaders
Copilot 1a429b2
Add comprehensive JSON plugin example and documentation
Copilot ab43fa5
Update JSON plugin to use 'json data variableId' syntax and script tag
Copilot e38425f
Rename json plugin to json-data and fix loader.ts JSON handling
Copilot a67e6f8
Fix JSON dataLoaders to always output as fence blocks and rename types
Copilot fb01c3a
Rename plugin from json-data to data, remove special case logic
Copilot 7f22663
Fix plugin name extraction to use infoWords instead of slice
Copilot 7bede40
Preserve signal bus state during reset by carrying over logLevel and …
danmarshall 8156077
handle nullref
danmarshall 194451d
Fix plugin name extraction to check for 'json' keyword directly in in…
danmarshall 99e01cf
receive initial batches without running
danmarshall 687d3b4
Optimize initial batch handling in SignalBus by reusing the construct…
danmarshall f93a424
remove json data examples
danmarshall 69ab583
Add YAML support to data plugin
Copilot 58fbd35
Refactor Mermaid diagram rendering to use MermaidInstance for improve…
danmarshall 78ba3d5
Improve batch handling in Vega plugin by setting needToRun flag based…
danmarshall 0a92c75
Revert UMD build artifacts to exclude from PR
Copilot 06f6174
Revert loader.ts changes and add UMD file guidance to copilot instruc…
Copilot 065dd50
Rename data plugin to value plugin
Copilot 80501c1
Add value plugin documentation to schema
Copilot fe002f9
Revert built schema file and add value plugin docs to source schema
Copilot dc5700e
Add ValueElement, CsvElement, TsvElement, DsvElement to schema
Copilot 3e83113
Fix schema: revert built files, update types, move inline data to pag…
Copilot e70d759
Polish schema and compiler: minimal comments, arrays for content, val…
Copilot 8ae7b85
Revert built files (UMD bundles and schema files) from PR
Copilot c661a6e
Remove built schema files and interactive.ts from PR
Copilot 7724b46
Remove docs/schema/idoc_v1.json from PR
Copilot cef5728
Refactor DSV parsing functions to consolidate variable ID and delimit…
danmarshall ac90041
Update ignoredSignals to replace 'origins' with 'value'
danmarshall 6af707d
Add parseFenceInfo function to extract metadata from fence info strings
danmarshall 1e21dc2
Add default 'value' plugin fallback for JSON and YAML data in create …
danmarshall 5e89f66
Refactor value plugin to use parseFenceInfo for improved fence info p…
danmarshall df9d27c
Refactor parseDsvInfo to utilize parseFenceInfo for improved paramete…
danmarshall e7b1087
Refactor JSON and YAML element types to remove '-value' suffix for co…
danmarshall 7028155
alphabetize
danmarshall 62b2706
common header
danmarshall 06ca7ae
remove comments
danmarshall c6a1334
remove csv etc
danmarshall 510ed09
convert to data loaders
danmarshall 7d93eb4
fix: update type from "yaml-value" to "yaml" in YAML Value Plugin test
danmarshall 94fe2e6
refactor: remove YAML Value Plugin test JSON file
danmarshall 1a12e45
refactor: remove InlineDataElement and related exports
danmarshall 82edd3c
refactor: remove JSON and YAML handling from groupMarkdown function
danmarshall 9fa8a84
refactor: remove JSON and YAML element validation from validateElemen…
danmarshall 2169e7b
remove json
danmarshall File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,216 @@ | ||
| /** | ||
| * Copyright (c) Microsoft Corporation. | ||
| * Licensed under the MIT License. | ||
| */ | ||
|
|
||
| import { Batch, IInstance, Plugin, RawFlaggableSpec } from '../factory.js'; | ||
| import { sanitizedScriptTag, sanitizeHtmlComment } from '../sanitize.js'; | ||
| import { pluginClassName } from './util.js'; | ||
| import { PluginNames } from './interfaces.js'; | ||
| import { SpecReview } from 'common'; | ||
| import { parseVariableId } from './dsv.js'; | ||
|
|
||
| interface JsonInstance { | ||
| id: string; | ||
| spec: JsonSpec; | ||
| data: object[]; | ||
| } | ||
|
|
||
| export interface JsonSpec { | ||
| variableId: string; | ||
| wasDefaultId?: boolean; | ||
| } | ||
|
|
||
| function inspectJsonSpec(spec: JsonSpec): RawFlaggableSpec<JsonSpec> { | ||
| const result: RawFlaggableSpec<JsonSpec> = { | ||
| spec, | ||
| hasFlags: false, | ||
| reasons: [] | ||
| }; | ||
|
|
||
| // Flag if we had to use defaults | ||
| if (spec.wasDefaultId) { | ||
| result.hasFlags = true; | ||
| result.reasons.push('No variable ID specified - using default'); | ||
| } | ||
|
|
||
| return result; | ||
| } | ||
|
|
||
| const pluginName: PluginNames = 'json-data'; | ||
| const className = pluginClassName(pluginName); | ||
|
|
||
| export const jsonDataPlugin: Plugin<JsonSpec> = { | ||
| name: pluginName, | ||
| fence: (token, index) => { | ||
| const content = token.content.trim(); | ||
| const info = token.info.trim(); | ||
|
|
||
| // Parse the fence info - expect "json data variableId" format | ||
| const parts = info.split(/\s+/); | ||
|
|
||
| // Require "json data" prefix | ||
| if (parts.length < 2 || parts[0] !== 'json' || parts[1] !== 'data') { | ||
| // This fence is not for json-data plugin | ||
| return ''; | ||
| } | ||
|
|
||
| // Check for variable ID | ||
| let variableId: string; | ||
| let wasDefaultId = false; | ||
|
|
||
| if (parts.length >= 3) { | ||
| // Format: json data variableId | ||
| variableId = parts[2]; | ||
| } else { | ||
| // Default variable ID if not provided | ||
| variableId = `jsonData${index}`; | ||
| wasDefaultId = true; | ||
| } | ||
|
|
||
| // Use script tag with application/json type instead of pre tag | ||
| const scriptElement = sanitizedScriptTag(content, { | ||
| id: `${pluginName}-${index}`, | ||
| class: className, | ||
| 'data-variable-id': variableId, | ||
| 'data-was-default-id': wasDefaultId.toString() | ||
| }); | ||
|
|
||
| return scriptElement.outerHTML; | ||
| }, | ||
| hydrateSpecs: (renderer, errorHandler) => { | ||
| const flagged: SpecReview<JsonSpec>[] = []; | ||
| const containers = renderer.element.querySelectorAll(`.${className}`); | ||
|
|
||
| for (const [index, container] of Array.from(containers).entries()) { | ||
| try { | ||
| const variableId = container.getAttribute('data-variable-id'); | ||
| const wasDefaultId = container.getAttribute('data-was-default-id') === 'true'; | ||
|
|
||
| if (!variableId) { | ||
| errorHandler(new Error('No variable ID found'), pluginName, index, 'parse', container); | ||
| continue; | ||
| } | ||
|
|
||
| const spec: JsonSpec = { variableId, wasDefaultId }; | ||
| const flaggableSpec = inspectJsonSpec(spec); | ||
|
|
||
| const f: SpecReview<JsonSpec> = { | ||
| approvedSpec: null, | ||
| pluginName, | ||
| containerId: container.id | ||
| }; | ||
|
|
||
| if (flaggableSpec.hasFlags) { | ||
| f.blockedSpec = flaggableSpec.spec; | ||
| f.reason = flaggableSpec.reasons?.join(', ') || 'Unknown reason'; | ||
| } else { | ||
| f.approvedSpec = flaggableSpec.spec; | ||
| } | ||
|
|
||
| flagged.push(f); | ||
| } catch (e) { | ||
| errorHandler(e instanceof Error ? e : new Error(String(e)), pluginName, index, 'parse', container); | ||
| } | ||
| } | ||
|
|
||
| return flagged; | ||
| }, | ||
| hydrateComponent: async (renderer, errorHandler, specs) => { | ||
| const { signalBus } = renderer; | ||
| const jsonInstances: JsonInstance[] = []; | ||
|
|
||
| for (let index = 0; index < specs.length; index++) { | ||
| const specReview = specs[index]; | ||
| if (!specReview.approvedSpec) { | ||
| continue; | ||
| } | ||
|
|
||
| const container = renderer.element.querySelector(`#${specReview.containerId}`); | ||
| if (!container) { | ||
| errorHandler(new Error('Container not found'), pluginName, index, 'init', null); | ||
| continue; | ||
| } | ||
|
|
||
| try { | ||
| const content = container.textContent?.trim(); | ||
| if (!content) { | ||
| errorHandler(new Error('No JSON content found'), pluginName, index, 'parse', container); | ||
| continue; | ||
| } | ||
|
|
||
| const spec: JsonSpec = specReview.approvedSpec; | ||
|
|
||
| // Parse JSON content | ||
| let data: object[]; | ||
| try { | ||
| const parsed = JSON.parse(content); | ||
| // Ensure data is an array | ||
| if (Array.isArray(parsed)) { | ||
| data = parsed; | ||
| } else { | ||
| // If it's a single object, wrap it in an array | ||
| data = [parsed]; | ||
| } | ||
| } catch (jsonError) { | ||
| errorHandler( | ||
| new Error(`Invalid JSON: ${jsonError instanceof Error ? jsonError.message : String(jsonError)}`), | ||
| pluginName, | ||
| index, | ||
| 'parse', | ||
| container | ||
| ); | ||
| continue; | ||
| } | ||
|
|
||
| const jsonInstance: JsonInstance = { | ||
| id: `${pluginName}-${index}`, | ||
| spec, | ||
| data | ||
| }; | ||
| jsonInstances.push(jsonInstance); | ||
|
|
||
| // Add a safe comment before the container to show that data was loaded | ||
| const comment = sanitizeHtmlComment(`JSON data loaded: ${data.length} rows for variable '${spec.variableId}'`); | ||
| container.insertAdjacentHTML('beforebegin', comment); | ||
|
|
||
| } catch (e) { | ||
| errorHandler(e instanceof Error ? e : new Error(String(e)), pluginName, index, 'parse', container); | ||
| } | ||
| } | ||
|
|
||
| const instances = jsonInstances.map((jsonInstance): IInstance => { | ||
| const { spec, data } = jsonInstance; | ||
|
|
||
| const initialSignals = [{ | ||
| name: spec.variableId, | ||
| value: data, | ||
| priority: 1, | ||
| isData: true, | ||
| }]; | ||
|
|
||
| return { | ||
| ...jsonInstance, | ||
| initialSignals, | ||
| beginListening() { | ||
| // JSON data is static, but we broadcast it when listening begins | ||
| const batch: Batch = { | ||
| [spec.variableId]: { | ||
| value: data, | ||
| isData: true, | ||
| }, | ||
| }; | ||
| signalBus.broadcast(jsonInstance.id, batch); | ||
| }, | ||
| getCurrentSignalValue: () => { | ||
| return data; | ||
| }, | ||
| destroy: () => { | ||
| // No cleanup needed for JSON data | ||
| }, | ||
| }; | ||
| }); | ||
|
|
||
| return instances; | ||
| }, | ||
| }; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@copilot but we don't want it embedded with the spec, we want it output as tickWrap(
json data ${ds_raw}, content); here tooThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed in commit a67e6f8:
JSON.stringify(content, null, 2)and output astickWrap('json data ${ds_raw}', content)Also renamed types per new requirement:
JsonInstance→JsonDataInstanceJsonSpec→JsonDataSpec