Skip to content

Commit b982d89

Browse files
authored
Add connector logos and Bloblang interactive features (#359)
* Add connector logos * Add Bloblang syntax highlighting, mini playground, and hover docs Makes Bloblang code blocks more interactive and easier to understand: - Custom syntax highlighting for Prism (static blocks) and Ace (editors) - Hover tooltips on functions and methods showing docs from Connect JSON - "Try It" button opens a mini playground modal to run mappings instantly - Support for # In: comments to provide sample input data - Dark mode support with readable colors throughout - Mini playground links to full playground with encoded state * Use antora.yml as source for Connect version Replace GitHub API calls with fetches from rp-connect-docs antora.yml. This avoids rate limits and uses the same source of truth for both build-time grammar generation and runtime tooltip loading. Runtime version is cached in localStorage for 1 hour. * Add playground at production path for preview link testing * Fix contribution modal centering in viewport Move modal to document.body when opened to escape CSS containment context that was breaking position:fixed relative positioning. Use flexbox centering instead of margin-based centering. * Add Bloblang syntax highlighting in YAML code blocks Detects and highlights Bloblang code embedded in Connect pipeline configs (mapping, check, request_map, etc). Adds hover tooltips for functions/methods and a Skip directive to disable Try It buttons on specific blocks. * Add Bloblang interactive tests with accessibility and reliability fixes - Add comprehensive test suite for mini-playground, YAML detection, and copy safety - Fix accessibility with focus-visible indicators and reduced motion support - Add WASM streaming fallback for better server compatibility - Fix timestamp_unix documentation to match actual function signature - Integrate tests into existing CI/CD workflow * Do not commit test results
1 parent 1626f83 commit b982d89

81 files changed

Lines changed: 6243 additions & 19 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/test-bloblang-playground.yml

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,33 @@
1-
name: Bloblang Playground Tests
1+
name: Bloblang Tests
22
on:
33
push:
4-
branches: [ main, develop ]
4+
branches: [ main ]
55
paths:
66
- 'blobl-editor/**'
77
- 'src/static/blobl.wasm'
88
- 'src/js/vendor/wasm_exec.js'
9+
- 'src/js/16-bloblang-interactive.js'
10+
- 'src/js/17-bloblang-yaml.js'
11+
- 'src/css/bloblang-interactive.css'
12+
- 'src/static/bloblang-docs.json'
913
- 'tests/bloblang-playground/**'
14+
- 'tests/bloblang-interactive/**'
1015
- 'gulpfile.js'
16+
- '.github/workflows/test-bloblang-playground.yml'
1117
pull_request:
12-
branches: [ main, develop ]
18+
branches: [ main ]
1319
paths:
1420
- 'blobl-editor/**'
1521
- 'src/static/blobl.wasm'
1622
- 'src/js/vendor/wasm_exec.js'
23+
- 'src/js/16-bloblang-interactive.js'
24+
- 'src/js/17-bloblang-yaml.js'
25+
- 'src/css/bloblang-interactive.css'
26+
- 'src/static/bloblang-docs.json'
1727
- 'tests/bloblang-playground/**'
28+
- 'tests/bloblang-interactive/**'
1829
- 'gulpfile.js'
30+
- '.github/workflows/test-bloblang-playground.yml'
1931
workflow_dispatch:
2032
jobs:
2133
test-bloblang-playground:
@@ -52,12 +64,20 @@ jobs:
5264
env:
5365
GOOS: js
5466
GOARCH: wasm
55-
- name: Build and Test
67+
- name: Build and Test Playground
5668
run: npx gulp test:build
57-
- name: Upload test results
69+
- name: Test Interactive Features
70+
run: npm run test:interactive
71+
- name: Upload playground test results
5872
uses: actions/upload-artifact@v4
5973
if: always()
6074
with:
61-
name: test-results
75+
name: test-results-playground
6276
path: test-results.json
77+
- name: Upload interactive test results
78+
uses: actions/upload-artifact@v4
79+
if: always()
80+
with:
81+
name: test-results-interactive
82+
path: test-results-interactive.json
6383

.gitignore

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,7 @@
33
/public/
44
.DS_Store
55
blobl.wasm
6-
src/static/
7-
test-results.*
6+
src/static/*
7+
!src/static/bloblang-docs.json
8+
test-results.*
9+
test-results-*.*

gulp.d/tasks/build-preview-pages.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ const map = (transform = () => {}, flush = undefined) => new Transform({ objectM
1212
const vfs = require('vinyl-fs')
1313
const yaml = require('js-yaml')
1414

15-
const ASCIIDOC_ATTRIBUTES = { experimental: '', icons: 'font', sectanchors: '', 'source-highlighter': 'highlight.js' }
15+
const ASCIIDOC_ATTRIBUTES = { experimental: '', icons: 'font', sectanchors: '' }
1616

1717
module.exports = (src, previewSrc, previewDest, sink = () => map()) => (done) =>
1818
Promise.all([
Lines changed: 219 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,219 @@
1+
'use strict'
2+
3+
const fs = require('fs')
4+
const path = require('path')
5+
const log = require('fancy-log')
6+
const https = require('https')
7+
8+
const ANTORA_YML_URL = 'https://raw.githubusercontent.com/redpanda-data/rp-connect-docs/main/antora.yml'
9+
const CONNECT_JSON_BASE = 'https://docs.redpanda.com/redpanda-connect/components/_attachments'
10+
const FALLBACK_VERSIONS = ['4.79.0', '4.78.0', '4.77.0', '4.76.0', '4.75.0']
11+
12+
function fetchText (url) {
13+
return new Promise((resolve, reject) => {
14+
const options = {
15+
headers: { 'User-Agent': 'docs-ui-build' },
16+
}
17+
https.get(url, options, (res) => {
18+
if (res.statusCode !== 200) {
19+
reject(new Error(`HTTP ${res.statusCode}`))
20+
res.resume()
21+
return
22+
}
23+
let data = ''
24+
res.on('data', (chunk) => { data += chunk })
25+
res.on('end', () => resolve(data))
26+
}).on('error', reject)
27+
})
28+
}
29+
30+
function fetchJSON (url) {
31+
return fetchText(url).then((text) => JSON.parse(text))
32+
}
33+
34+
async function getLatestVersion () {
35+
try {
36+
const yaml = await fetchText(ANTORA_YML_URL)
37+
const match = yaml.match(/latest-connect-version:\s*['"]?(\d+\.\d+\.\d+)/)
38+
if (match) {
39+
return match[1]
40+
}
41+
log.warn('Could not parse version from antora.yml')
42+
return null
43+
} catch (err) {
44+
log.warn('Could not fetch Connect version from antora.yml:', err.message)
45+
return null
46+
}
47+
}
48+
49+
async function fetchConnectJSON (version) {
50+
const url = `${CONNECT_JSON_BASE}/connect-${version}.json`
51+
try {
52+
return await fetchJSON(url)
53+
} catch (err) {
54+
log.warn(`Could not fetch connect-${version}.json:`, err.message)
55+
return null
56+
}
57+
}
58+
59+
function extractNames (data) {
60+
const functions = []
61+
const methods = []
62+
63+
if (Array.isArray(data['bloblang-functions'])) {
64+
for (const fn of data['bloblang-functions']) {
65+
if (fn.name) functions.push(fn.name)
66+
}
67+
}
68+
69+
if (Array.isArray(data['bloblang-methods'])) {
70+
for (const method of data['bloblang-methods']) {
71+
if (method.name) methods.push(method.name)
72+
}
73+
}
74+
75+
return { functions: functions.sort(), methods: methods.sort() }
76+
}
77+
78+
function generateGrammar (functions, methods) {
79+
return `/**
80+
* Prism syntax highlighting for Bloblang (blobl)
81+
*
82+
* Bloblang is Redpanda Connect's native mapping language for transforming data.
83+
* @see https://docs.redpanda.com/redpanda-connect/guides/bloblang/about/
84+
*
85+
* AUTO-GENERATED from Connect JSON - do not edit manually.
86+
* Run: gulp generate:bloblang-grammar
87+
*/
88+
89+
(function (Prism) {
90+
var functions = ${JSON.stringify(functions)}.join('|');
91+
var methods = ${JSON.stringify(methods)}.join('|');
92+
93+
Prism.languages.bloblang = {
94+
'comment': {
95+
pattern: /#.*/,
96+
greedy: true
97+
},
98+
99+
'string': [
100+
{
101+
pattern: /"""[\\s\\S]*?"""/,
102+
greedy: true,
103+
alias: 'multiline-string'
104+
},
105+
{
106+
pattern: /(["'])(?:\\\\(?:\\r\\n|[\\s\\S])|(?!\\1)[^\\\\\\r\\n])*\\1/,
107+
greedy: true
108+
}
109+
],
110+
111+
'spread': {
112+
pattern: /\\.\\*/,
113+
alias: 'operator'
114+
},
115+
116+
'keyword': {
117+
pattern: /\\b(?:root|this|let|meta|if|else|match|case|_)\\b/,
118+
lookbehind: false
119+
},
120+
121+
'function': {
122+
pattern: new RegExp('\\\\b(?:' + functions + ')(?=\\\\s*\\\\()', 'i')
123+
},
124+
125+
'method': {
126+
pattern: new RegExp('\\\\.\\\\s*(?:' + methods + ')(?=\\\\s*\\\\()', 'i'),
127+
inside: {
128+
'punctuation': /^\\./
129+
}
130+
},
131+
132+
'boolean': /\\b(?:true|false)\\b/,
133+
134+
'null': {
135+
pattern: /\\bnull\\b/,
136+
alias: 'keyword'
137+
},
138+
139+
'number': /\\b-?(?:0x[\\da-f]+|\\d+(?:\\.\\d*)?(?:e[+-]?\\d+)?)\\b/i,
140+
141+
'metadata': {
142+
pattern: /@(?:[\\w-]+|\\.[\\w-]+|\\()/,
143+
alias: 'variable'
144+
},
145+
146+
'variable': {
147+
pattern: /\\$[\\w-]+/
148+
},
149+
150+
'operator': [
151+
/->/,
152+
{
153+
pattern: /\\|/,
154+
alias: 'coalesce'
155+
},
156+
/[<>]=?|[!=]=?|&&|\\|\\||!(?!=)/,
157+
/=/,
158+
/[+\\-*\\/%]/
159+
],
160+
161+
'punctuation': /[{}[\\]();:,]|\\.(?!\\*)|->|\\.\\.\\.?/,
162+
163+
'property': {
164+
pattern: /\\.[\\w-]+/,
165+
inside: {
166+
'punctuation': /^\\./
167+
}
168+
}
169+
};
170+
171+
Prism.languages.blobl = Prism.languages.bloblang;
172+
173+
}(Prism));
174+
`
175+
}
176+
177+
module.exports = (destPath) => async (done) => {
178+
try {
179+
log('Fetching Connect JSON for Bloblang grammar generation...')
180+
181+
let data = null
182+
183+
// Try latest version first
184+
const latestVersion = await getLatestVersion()
185+
if (latestVersion) {
186+
log(`Trying latest version: ${latestVersion}`)
187+
data = await fetchConnectJSON(latestVersion)
188+
}
189+
190+
// Fall back through known versions
191+
if (!data) {
192+
for (const version of FALLBACK_VERSIONS) {
193+
log(`Trying fallback version: ${version}`)
194+
data = await fetchConnectJSON(version)
195+
if (data) break
196+
}
197+
}
198+
199+
if (!data) {
200+
log.warn('Could not fetch Connect JSON, keeping existing grammar')
201+
done()
202+
return
203+
}
204+
205+
const { functions, methods } = extractNames(data)
206+
log(`Found ${functions.length} functions and ${methods.length} methods`)
207+
208+
const grammar = generateGrammar(functions, methods)
209+
210+
fs.mkdirSync(path.dirname(destPath), { recursive: true })
211+
fs.writeFileSync(destPath, grammar)
212+
log(`Generated: ${destPath}`)
213+
214+
done()
215+
} catch (err) {
216+
log.error('Failed to generate Bloblang grammar:', err)
217+
done(err)
218+
}
219+
}

gulpfile.js

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ const { reload: livereload } = process.env.LIVERELOAD === 'true' ? require('gulp
1818
const serverConfig = { host: '0.0.0.0', port: 5252, livereload }
1919

2020
const task = require('./gulp.d/tasks')
21+
const generateBloblangGrammar = require('./gulp.d/tasks/generate-bloblang-grammar')
2122
const glob = {
2223
all: [srcDir, previewSrcDir],
2324
css: `${srcDir}/css/**/*.css`,
@@ -100,6 +101,12 @@ const buildTask = createTask({
100101
),
101102
})
102103

104+
const generateBloblangGrammarTask = createTask({
105+
name: 'generate:bloblang-grammar',
106+
desc: 'Generate Prism Bloblang grammar from Connect JSON',
107+
call: generateBloblangGrammar(path.join(__dirname, srcDir, 'js', 'vendor', 'prism', 'prism-bloblang.js')),
108+
})
109+
103110
const buildWasmTask = createTask({
104111
name: 'build:wasm',
105112
desc: 'Build the WebAssembly (.wasm) file using Go and the go.mod in blobl-editor/wasm',
@@ -130,7 +137,7 @@ const buildWasmTask = createTask({
130137

131138
const bundleBuildTask = createTask({
132139
name: 'bundle:build',
133-
call: series(cleanTask, lintTask, buildWasmTask, bundleReactTask, compileWidgets, buildTask),
140+
call: series(cleanTask, lintTask, generateBloblangGrammarTask, buildWasmTask, bundleReactTask, compileWidgets, buildTask),
134141
})
135142

136143
const bundlePackTask = createTask({
@@ -212,6 +219,7 @@ module.exports = exportTasks(
212219
cleanTask,
213220
lintTask,
214221
formatTask,
222+
generateBloblangGrammarTask,
215223
buildWasmTask,
216224
bundleReactTask,
217225
buildTask,

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,8 @@
7777
"scripts": {
7878
"test:headless": "node tests/bloblang-playground/test-runner.js",
7979
"test:playground": "npm run build:wasm && npm run test:headless",
80+
"test:interactive": "node tests/bloblang-interactive/test-runner.js",
81+
"test:all": "npm run test:playground && npm run test:interactive",
8082
"build:wasm": "cd blobl-editor/wasm && GOOS=js GOARCH=wasm go build -o ../../src/static/blobl.wasm .",
8183
"copy:wasm-exec": "cp \"$(go env GOROOT)/lib/wasm/wasm_exec.js\" src/js/vendor/",
8284
"serve:playground": "npx serve ."

0 commit comments

Comments
 (0)