Skip to content

Commit 680e9dd

Browse files
author
Corneliu Tusnea
committed
Added support for yaml inputs and outputs to the playground
1 parent 2589540 commit 680e9dd

File tree

6 files changed

+132
-30
lines changed

6 files changed

+132
-30
lines changed

playground/backend/src/main/kotlin/com/intuit/isl/playground/model/Models.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@ package com.intuit.isl.playground.model
22

33
data class TransformRequest(
44
val isl: String,
5-
val input: String
5+
val input: String,
6+
val inputFormat: String? = "json", // "json" | "yaml"
7+
val outputFormat: String? = "json" // "json" | "yaml"
68
)
79

810
data class ValidationRequest(

playground/backend/src/main/kotlin/com/intuit/isl/playground/service/IslService.kt

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package com.intuit.isl.playground.service
22

3+
import com.fasterxml.jackson.databind.JsonNode
34
import com.fasterxml.jackson.databind.ObjectMapper
5+
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory
46
import com.fasterxml.jackson.module.kotlin.registerKotlinModule
57
import com.intuit.isl.common.OperationContext
68
import com.intuit.isl.playground.model.*
@@ -11,13 +13,34 @@ import org.springframework.stereotype.Service
1113

1214
@Service
1315
class IslService {
14-
15-
private val mapper = ObjectMapper().registerKotlinModule()
16+
17+
private val jsonMapper = ObjectMapper().registerKotlinModule()
18+
private val yamlMapper = ObjectMapper(YAMLFactory()).registerKotlinModule()
19+
20+
private fun parseInput(input: String, format: String): JsonNode {
21+
val normalized = (format ?: "json").lowercase()
22+
return when (normalized) {
23+
"yaml" -> yamlMapper.readTree(input)
24+
else -> jsonMapper.readTree(input)
25+
}
26+
}
27+
28+
private fun writeOutput(result: Any?, format: String): String {
29+
val normalized = (format ?: "json").lowercase()
30+
val value = result ?: jsonMapper.createObjectNode()
31+
return when (normalized) {
32+
"yaml" -> yamlMapper.writerWithDefaultPrettyPrinter().writeValueAsString(value)
33+
else -> jsonMapper.writerWithDefaultPrettyPrinter().writeValueAsString(value)
34+
}
35+
}
1636

1737
fun transform(request: TransformRequest): TransformResponse {
1838
return try {
19-
// Parse the input JSON to JsonNode
20-
val inputJson = mapper.readTree(request.input)
39+
val inputFmt = request.inputFormat?.lowercase() ?: "json"
40+
val outputFmt = request.outputFormat?.lowercase() ?: "json"
41+
42+
// Parse the input (JSON or YAML) to JsonNode
43+
val inputJson = parseInput(request.input, inputFmt)
2144

2245
// Compile the ISL script
2346
val compiler = TransformCompiler()
@@ -41,8 +64,8 @@ class IslService {
4164
// Execute the transformation
4265
val result = transformer.runTransformSync(functionToRun, context)
4366

44-
// Convert result to JSON string
45-
val output = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(result)
67+
// Convert result to requested output format (JSON or YAML)
68+
val output = writeOutput(result, outputFmt)
4669

4770
TransformResponse(
4871
success = true,
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# Use local backend when running npm run dev
2+
VITE_API_URL=http://localhost:8080/api

playground/frontend/package-lock.json

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

playground/frontend/src/App.css

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,39 @@ body {
186186
color: #cccccc;
187187
}
188188

189+
.format-toggle {
190+
display: flex;
191+
gap: 0;
192+
border-radius: 6px;
193+
overflow: hidden;
194+
border: 1px solid #555;
195+
}
196+
197+
.format-toggle .format-btn {
198+
padding: 0.35rem 0.85rem;
199+
font-size: 0.85rem;
200+
font-weight: 600;
201+
background-color: #3c3c3c;
202+
color: #888;
203+
border: none;
204+
border-radius: 0;
205+
cursor: pointer;
206+
transition: background-color 0.15s, color 0.15s;
207+
}
208+
209+
.format-toggle .format-btn:first-child {
210+
border-right: 1px solid #555;
211+
}
212+
213+
.format-toggle .format-btn:hover {
214+
color: #cccccc;
215+
}
216+
217+
.format-toggle .format-btn.active {
218+
background-color: #3c8dd5;
219+
color: white;
220+
}
221+
189222
.button-group {
190223
display: flex;
191224
gap: 0.75rem;

playground/frontend/src/App.tsx

Lines changed: 61 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import type { editor } from 'monaco-editor';
77

88
const API_BASE_URL = import.meta.env.VITE_API_URL || 'https://isl-playground.up.railway.app/api';
99

10+
type DataFormat = 'json' | 'yaml';
11+
1012
interface TransformResponse {
1113
success: boolean;
1214
output?: string;
@@ -108,6 +110,8 @@ function App() {
108110
const [loading, setLoading] = useState(false);
109111
const [validationErrors, setValidationErrors] = useState<string[]>([]);
110112
const [validationSuccess, setValidationSuccess] = useState(false);
113+
const [inputFormat, setInputFormat] = useState<DataFormat>('json');
114+
const [outputFormat, setOutputFormat] = useState<DataFormat>('json');
111115

112116
// Ref to store Monaco editor instance
113117
const editorRef = useRef<editor.IStandaloneCodeEditor | null>(null);
@@ -180,6 +184,8 @@ function App() {
180184
const response = await axios.post<TransformResponse>(`${API_BASE_URL}/transform`, {
181185
isl: codeToRun,
182186
input: inputJson,
187+
inputFormat,
188+
outputFormat,
183189
});
184190

185191
if (response.data.success) {
@@ -201,7 +207,7 @@ function App() {
201207
} finally {
202208
setLoading(false);
203209
}
204-
}, [islCode, inputJson, loading]);
210+
}, [islCode, inputJson, inputFormat, outputFormat, loading]);
205211

206212
const handleValidate = useCallback(async () => {
207213
if (loading) return;
@@ -317,12 +323,30 @@ function App() {
317323
<div className="editor-container">
318324
<div className="editor-panel">
319325
<div className="panel-header">
320-
<h3>Input JSON</h3>
326+
<h3>Input</h3>
327+
<div className="format-toggle">
328+
<button
329+
type="button"
330+
className={inputFormat === 'json' ? 'format-btn active' : 'format-btn'}
331+
onClick={() => setInputFormat('json')}
332+
aria-pressed={inputFormat === 'json'}
333+
>
334+
JSON
335+
</button>
336+
<button
337+
type="button"
338+
className={inputFormat === 'yaml' ? 'format-btn active' : 'format-btn'}
339+
onClick={() => setInputFormat('yaml')}
340+
aria-pressed={inputFormat === 'yaml'}
341+
>
342+
YAML
343+
</button>
344+
</div>
321345
</div>
322346
<div className="editor-wrapper">
323347
<Editor
324348
height="100%"
325-
language="json"
349+
language={inputFormat}
326350
theme="vs-dark"
327351
value={inputJson}
328352
onChange={(value) => setInputJson(value || '')}
@@ -391,23 +415,41 @@ function App() {
391415
<div className="editor-panel">
392416
<div className="panel-header">
393417
<h3>Output</h3>
394-
</div>
395-
<div className="editor-wrapper">
396-
<Editor
397-
height="100%"
398-
language="json"
399-
theme="vs-dark"
400-
value={output}
401-
options={{
402-
minimap: { enabled: false },
403-
fontSize: 14,
404-
lineNumbers: 'on',
405-
scrollBeyondLastLine: false,
406-
automaticLayout: true,
407-
readOnly: true,
408-
}}
409-
/>
418+
<div className="format-toggle">
419+
<button
420+
type="button"
421+
className={outputFormat === 'json' ? 'format-btn active' : 'format-btn'}
422+
onClick={() => setOutputFormat('json')}
423+
aria-pressed={outputFormat === 'json'}
424+
>
425+
JSON
426+
</button>
427+
<button
428+
type="button"
429+
className={outputFormat === 'yaml' ? 'format-btn active' : 'format-btn'}
430+
onClick={() => setOutputFormat('yaml')}
431+
aria-pressed={outputFormat === 'yaml'}
432+
>
433+
YAML
434+
</button>
410435
</div>
436+
</div>
437+
<div className="editor-wrapper">
438+
<Editor
439+
height="100%"
440+
language={outputFormat}
441+
theme="vs-dark"
442+
value={output}
443+
options={{
444+
minimap: { enabled: false },
445+
fontSize: 14,
446+
lineNumbers: 'on',
447+
scrollBeyondLastLine: false,
448+
automaticLayout: true,
449+
readOnly: true,
450+
}}
451+
/>
452+
</div>
411453
</div>
412454
</div>
413455

0 commit comments

Comments
 (0)