Skip to content

Commit 735dad1

Browse files
authored
feat(vrl playground): add runnable examples and returned values (#26708)
1 parent 9ea96a1 commit 735dad1

5 files changed

Lines changed: 259 additions & 52 deletions

File tree

‎lib/vector-vrl/web-playground/public/index.css‎

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,11 @@ div#toolbar-section {
206206
overflow: hidden;
207207
}
208208

209+
/* Reveal the panels once Split.js and Monaco agree on their initial dimensions. */
210+
#main-split[aria-busy="true"] {
211+
visibility: hidden;
212+
}
213+
209214
div#input-section,
210215
div#output-section {
211216
border: 1px solid var(--datadog-gray-lighter);
@@ -214,6 +219,8 @@ div#output-section {
214219
min-height: 0;
215220
display: flex;
216221
flex-direction: column;
222+
box-sizing: border-box;
223+
width: calc(50% - 3px);
217224
}
218225

219226
#input-section #cell {
@@ -226,16 +233,27 @@ div#output-section {
226233

227234
/* Split.js writes inline flex-basis for vertical sizing — don't collapse to 0 */
228235
#output-section #event-cell,
229-
#output-section #output-cell {
236+
#output-section #output-cell,
237+
#output-section #returned-value-cell {
230238
display: flex;
231239
flex-direction: column;
232240
min-height: 0;
233241
overflow: hidden;
234242
}
235243

244+
#event-cell,
245+
#output-cell {
246+
height: calc(40% - 4px);
247+
}
248+
249+
#returned-value-cell {
250+
height: calc(20% - 4px);
251+
}
252+
236253
#container-program,
237254
#container-event,
238-
#container-output {
255+
#container-output,
256+
#container-returned-value {
239257
flex: 1 1 0;
240258
min-height: 0;
241259
width: 100%;
@@ -417,7 +435,8 @@ div#output-section {
417435
}
418436

419437
#output-section #event-cell,
420-
#output-section #output-cell {
438+
#output-section #output-cell,
439+
#output-section #returned-value-cell {
421440
min-height: 30vh;
422441
}
423442
}

‎lib/vector-vrl/web-playground/public/index.html‎

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,9 @@ <h2>VRL Playground</h2>
8888
</datalist>
8989
</div>
9090
<div class="toolbar-group">
91+
<select id="examples-select" title="Load an example program and input" aria-label="Examples">
92+
<option value="" disabled selected>Examples</option>
93+
</select>
9194
<select id="history-select" title="Run history (stored locally in your browser)">
9295
<option value="" disabled selected>History</option>
9396
</select>
@@ -97,7 +100,7 @@ <h2>VRL Playground</h2>
97100
</div>
98101
</div>
99102
</div>
100-
<div id="main-split">
103+
<div id="main-split" aria-busy="true">
101104
<div id="input-section">
102105
<div id="cell">
103106
<div id="input-cell-title">
@@ -124,6 +127,13 @@ <h2>VRL Playground</h2>
124127
</div>
125128
<div id="container-output"></div>
126129
</div>
130+
131+
<div id="returned-value-cell">
132+
<div id="returned-value-cell-title">
133+
<p class="cell-title" title="The value of the program's last expression">Returned value</p>
134+
</div>
135+
<div id="container-returned-value"></div>
136+
</div>
127137
</div>
128138
</div>
129139
</div>

‎lib/vector-vrl/web-playground/public/index.js‎

Lines changed: 102 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -7,31 +7,9 @@ import init, {
77
vrl_version
88
} from "./pkg/vector_vrl_web_playground.js";
99
import { registerVrlCompletion } from "./vrl-completion.js";
10+
import { DEFAULT_EXAMPLE, VRL_EXAMPLES } from "./vrl-examples.js";
1011
import { vrlLanguageDefinition, vrlThemeDefinition } from "./vrl-highlighter.js";
1112

12-
const PROGRAM_EDITOR_DEFAULT_VALUE = `# Remove some fields
13-
del(.foo)
14-
15-
# Add a timestamp
16-
.timestamp = now()
17-
18-
# Parse HTTP status code into local variable
19-
http_status_code = parse_int!(.http_status)
20-
del(.http_status)
21-
22-
# Add status
23-
if http_status_code >= 200 && http_status_code <= 299 {
24-
.status = "success"
25-
} else {
26-
.status = "error"
27-
}`;
28-
29-
const EVENT_EDITOR_DEFAULT_VALUE = `{
30-
"message": "Hello VRL",
31-
"foo": "delete me",
32-
"http_status": "200"
33-
}`;
34-
3513
const OUTPUT_EDITOR_DEFAULT_VALUE = `{}`;
3614

3715
const HISTORY_STORAGE_KEY = "vrl_playground_history";
@@ -114,25 +92,33 @@ export class VrlWebPlayground {
11492
getSampleEvent: () => this._sampleEvent()
11593
});
11694

95+
// Size the panels before Monaco measures its containers.
96+
this.addVersions();
97+
this._initSplitPanels();
98+
11799
// Editors
118-
this.eventEditor = this.createDefaultEditor("container-event", EVENT_EDITOR_DEFAULT_VALUE, "json", "vs-light");
119-
this.outputEditor = this.createDefaultEditor("container-output", OUTPUT_EDITOR_DEFAULT_VALUE, "json", "vs-light");
120-
this.programEditor = this.createDefaultEditor(
121-
"container-program",
122-
PROGRAM_EDITOR_DEFAULT_VALUE,
123-
"vrl",
124-
"vrl-theme"
100+
this.eventEditor = this.createDefaultEditor(
101+
"container-event",
102+
JSON.stringify(DEFAULT_EXAMPLE.event, null, 2),
103+
"json",
104+
"vs-light"
125105
);
126-
127-
// Resizable panels
128-
this._initSplitPanels();
106+
this.outputEditor = this.createDefaultEditor("container-output", OUTPUT_EDITOR_DEFAULT_VALUE, "json", "vs-light");
107+
this.returnedValueEditor = this.createDefaultEditor("container-returned-value", "", "json", "vs-light");
108+
this.returnedValueEditor.updateOptions({ readOnly: true });
109+
this.programEditor = this.createDefaultEditor("container-program", DEFAULT_EXAMPLE.program, "vrl", "vrl-theme");
110+
this.programEditor.addAction({
111+
id: "vrl.find",
112+
label: "Find",
113+
contextMenuGroupId: "navigation",
114+
contextMenuOrder: 0,
115+
run: (editor) => editor.getAction("actions.find").run()
116+
});
117+
this._initChangeAllAction();
129118

130119
// Theme
131120
this._initTheme();
132121

133-
// Versions
134-
this.addVersions();
135-
136122
// Keyboard shortcut: Shift+Enter runs the program
137123
window.addEventListener("keydown", (e) => {
138124
if (e.shiftKey && e.key === "Enter") {
@@ -141,6 +127,8 @@ export class VrlWebPlayground {
141127
}
142128
});
143129

130+
this._initExamples();
131+
144132
// Populate history dropdown from localStorage
145133
this._populateHistoryDropdown();
146134

@@ -155,6 +143,64 @@ export class VrlWebPlayground {
155143

156144
// Handle shared state from URL (if present)
157145
this._maybeLoadFromUrl();
146+
147+
for (const editor of [this.programEditor, this.eventEditor, this.outputEditor, this.returnedValueEditor]) {
148+
editor.layout();
149+
}
150+
document.getElementById("main-split").setAttribute("aria-busy", "false");
151+
}
152+
153+
_initExamples() {
154+
const select = document.getElementById("examples-select");
155+
if (!select) return;
156+
157+
for (const example of VRL_EXAMPLES) {
158+
const option = document.createElement("option");
159+
option.value = example.id;
160+
option.textContent = example.name;
161+
select.appendChild(option);
162+
}
163+
164+
select.addEventListener("change", () => {
165+
const example = VRL_EXAMPLES.find((entry) => entry.id === select.value);
166+
if (!example) return;
167+
168+
this.programEditor.setValue(example.program);
169+
this.eventEditor.setValue(JSON.stringify(example.event, null, 2));
170+
this.enableJsonLinting();
171+
this.handleRunCode();
172+
});
173+
174+
const updateSelection = () => {
175+
const example = VRL_EXAMPLES.find(
176+
(entry) =>
177+
entry.program === this._safeGet(this.programEditor) &&
178+
JSON.stringify(entry.event, null, 2) === this._safeGet(this.eventEditor)
179+
);
180+
select.value = example?.id ?? "";
181+
};
182+
this.programEditor.onDidChangeModelContent(updateSelection);
183+
this.eventEditor.onDidChangeModelContent(updateSelection);
184+
}
185+
186+
_initChangeAllAction() {
187+
// The context menu takes focus, so remember which editor opened it.
188+
let focusedEditor = this.programEditor;
189+
for (const editor of [this.programEditor, this.eventEditor, this.outputEditor, this.returnedValueEditor]) {
190+
editor.onDidFocusEditorText(() => {
191+
focusedEditor = editor;
192+
});
193+
}
194+
195+
this.monaco.editor.registerCommand("editor.action.changeAll", async () => {
196+
const editor = focusedEditor;
197+
if (editor !== this.programEditor) {
198+
return editor.getAction("editor.action.changeAll").run();
199+
}
200+
// Find seeds the query from the selection or the word under the cursor.
201+
await editor.getAction("actions.find").run();
202+
return editor.getAction("editor.action.startFindReplaceAction").run();
203+
});
158204
}
159205

160206
_maybeLoadFromUrl() {
@@ -256,10 +302,10 @@ export class VrlWebPlayground {
256302
cursor: "col-resize"
257303
});
258304

259-
window.Split(["#event-cell", "#output-cell"], {
305+
window.Split(["#event-cell", "#output-cell", "#returned-value-cell"], {
260306
direction: "vertical",
261-
sizes: [50, 50],
262-
minSize: 100,
307+
sizes: [40, 40, 20],
308+
minSize: [80, 80, 60],
263309
gutterSize: 6,
264310
cursor: "row-resize"
265311
});
@@ -304,6 +350,7 @@ export class VrlWebPlayground {
304350
}
305351

306352
_clearOutput() {
353+
this.returnedValueEditor?.setValue("");
307354
if (this.outputEditor) {
308355
// wipe the buffer so stale values never linger
309356
this.outputEditor.setValue("");
@@ -341,6 +388,15 @@ export class VrlWebPlayground {
341388
}
342389
}
343390

391+
_formatReturnedValue(runResult) {
392+
if (typeof runResult?.runtime_output === "string") return runResult.runtime_output;
393+
// Also support older runtimes, where only the deserialized value is available.
394+
if (runResult && Object.prototype.hasOwnProperty.call(runResult, "runtime_result")) {
395+
return JSON.stringify(runResult.runtime_result, null, "\t");
396+
}
397+
return "";
398+
}
399+
344400
_safeGet(editor, fallback = "") {
345401
return editor?.getValue?.() ?? fallback;
346402
}
@@ -496,6 +552,7 @@ export class VrlWebPlayground {
496552
if (entry.outputIsJson) this.enableJsonLinting();
497553
else this.disableJsonLinting();
498554
this.outputEditor?.setValue(entry.output ?? "");
555+
this.returnedValueEditor?.setValue(entry.returnedValue ?? "");
499556
this._setElapsed(entry.elapsedTime);
500557
}
501558

@@ -527,13 +584,16 @@ export class VrlWebPlayground {
527584
if (isJson) this.enableJsonLinting();
528585
else this.disableJsonLinting();
529586
this.outputEditor.setValue(text);
587+
const returnedValue = this._formatReturnedValue(runResult);
588+
this.returnedValueEditor.setValue(returnedValue);
530589

531590
this._setElapsed(runResult?.elapsed_time);
532591
this._saveToHistory({
533592
program: this._safeGet(this.programEditor),
534593
event: this._safeGet(this.eventEditor),
535594
output: text,
536595
outputIsJson: isJson,
596+
returnedValue,
537597
elapsedTime: runResult?.elapsed_time,
538598
timezone
539599
});
@@ -566,6 +626,8 @@ export class VrlWebPlayground {
566626
// Output is not pure JSON (multiple objects / possible errors)
567627
this.disableJsonLinting();
568628
this.outputEditor.setValue(outputText);
629+
const returnedValue = results.map((r) => this._formatReturnedValue(r)).join("\n");
630+
this.returnedValueEditor.setValue(returnedValue);
569631

570632
// Aggregate elapsed time, rounded
571633
const total = results.reduce((sum, r) => sum + (typeof r?.elapsed_time === "number" ? r.elapsed_time : 0), 0);
@@ -575,6 +637,7 @@ export class VrlWebPlayground {
575637
event: this._safeGet(this.eventEditor),
576638
output: outputText,
577639
outputIsJson: false,
640+
returnedValue,
578641
elapsedTime: total,
579642
timezone
580643
});

0 commit comments

Comments
 (0)