Skip to content

Commit 7628dba

Browse files
committed
Quick example supporting multiple library execution
1 parent 0849d7e commit 7628dba

4 files changed

Lines changed: 291 additions & 7 deletions

File tree

cmd/cqlplay/main.go

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import (
2727
"time"
2828

2929
"flag"
30+
3031
log "github.com/golang/glog"
3132
"github.com/google/cql"
3233
"github.com/google/cql/retriever/local"
@@ -107,7 +108,11 @@ func handleEvalCQL(w http.ResponseWriter, req *http.Request) {
107108
return
108109
}
109110

110-
elm, err := cql.Parse(req.Context(), []string{evalCQLReq.CQL, fhirHelpers}, cql.ParseConfig{DataModels: [][]byte{fhirDM}})
111+
// Combine main CQL with additional libraries and FHIRHelpers
112+
cqlInputs := append([]string{evalCQLReq.CQL}, evalCQLReq.Libraries...)
113+
cqlInputs = append(cqlInputs, fhirHelpers)
114+
115+
elm, err := cql.Parse(req.Context(), cqlInputs, cql.ParseConfig{DataModels: [][]byte{fhirDM}})
111116
if err != nil {
112117
sendError(w, fmt.Errorf("failed to parse: %w", err), http.StatusInternalServerError)
113118
return
@@ -147,8 +152,9 @@ func sendError(w http.ResponseWriter, err error, code int) {
147152
}
148153

149154
type evalCQLRequest struct {
150-
CQL string `json:"cql"`
151-
Data string `json:"data"`
155+
CQL string `json:"cql"`
156+
Data string `json:"data"`
157+
Libraries []string `json:"libraries"`
152158
}
153159

154160
func getTerminologyProvider() (*terminology.LocalFHIRProvider, error) {

cmd/cqlplay/static/cqlPlay.js

Lines changed: 186 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@ context Patient`;
3939

4040
let data = syntheticPatient;
4141

42+
// Libraries array to store additional CQL libraries
43+
let libraries = [];
44+
4245
let results = '';
4346

4447
// Helper functions:
@@ -81,6 +84,16 @@ function bindButtonActions() {
8184
.addEventListener('click', function(e) {
8285
showDataTab();
8386
});
87+
document.getElementById('librariesTabButton')
88+
.addEventListener('click', function(e) {
89+
showLibrariesTab();
90+
});
91+
document.getElementById('addLibrary')
92+
.addEventListener('click', function(e) {
93+
addLibrary();
94+
});
95+
document.getElementById('uploadLibrary')
96+
.addEventListener('change', handleFileUpload);
8497
}
8598

8699
/**
@@ -98,7 +111,15 @@ function runCQL() {
98111
};
99112
xhr.open('POST', '/eval_cql', true);
100113
xhr.setRequestHeader('Content-Type', 'text/json');
101-
xhr.send(JSON.stringify({'cql': code, 'data': data}));
114+
115+
// Collect library content to send with request
116+
const libraryContents = libraries.map(lib => lib.content);
117+
118+
xhr.send(JSON.stringify({
119+
'cql': code,
120+
'data': data,
121+
'libraries': libraryContents
122+
}));
102123
}
103124

104125
/**
@@ -107,9 +128,11 @@ function runCQL() {
107128
function showDataTab() {
108129
document.getElementById('cqlEntry').style.display = 'none';
109130
document.getElementById('dataEntry').style.display = 'block';
131+
document.getElementById('librariesEntry').style.display = 'none';
110132

111-
document.getElementById('dataTabButton').className += 'active';
133+
document.getElementById('dataTabButton').className = 'active';
112134
document.getElementById('cqlTabButton').className = '';
135+
document.getElementById('librariesTabButton').className = '';
113136
}
114137

115138
/**
@@ -118,9 +141,134 @@ function showDataTab() {
118141
function showCQLTab() {
119142
document.getElementById('cqlEntry').style.display = 'block';
120143
document.getElementById('dataEntry').style.display = 'none';
144+
document.getElementById('librariesEntry').style.display = 'none';
121145

122-
document.getElementById('cqlTabButton').className += 'active';
146+
document.getElementById('cqlTabButton').className = 'active';
123147
document.getElementById('dataTabButton').className = '';
148+
document.getElementById('librariesTabButton').className = '';
149+
}
150+
151+
/**
152+
* showLibrariesTab shows the Libraries tab and hides other tabs.
153+
*/
154+
function showLibrariesTab() {
155+
document.getElementById('cqlEntry').style.display = 'none';
156+
document.getElementById('dataEntry').style.display = 'none';
157+
document.getElementById('librariesEntry').style.display = 'block';
158+
159+
document.getElementById('librariesTabButton').className = 'active';
160+
document.getElementById('cqlTabButton').className = '';
161+
document.getElementById('dataTabButton').className = '';
162+
}
163+
164+
/**
165+
* addLibrary adds a new library to the libraries list and updates the UI.
166+
*/
167+
function addLibrary(name = '', content = '') {
168+
const libraryId = Date.now(); // Unique ID for the library
169+
170+
// Add to libraries array
171+
libraries.push({
172+
id: libraryId,
173+
name: name,
174+
content: content
175+
});
176+
177+
// Update the UI
178+
renderLibraries();
179+
180+
// Save to localStorage
181+
saveLibrariesToLocalStorage();
182+
}
183+
184+
/**
185+
* removeLibrary removes a library from the libraries list and updates the UI.
186+
*/
187+
function removeLibrary(libraryId) {
188+
libraries = libraries.filter(lib => lib.id !== libraryId);
189+
renderLibraries();
190+
saveLibrariesToLocalStorage();
191+
}
192+
193+
/**
194+
* renderLibraries updates the libraries UI with the current libraries.
195+
*/
196+
function renderLibraries() {
197+
const container = document.getElementById('librariesContainer');
198+
container.innerHTML = '';
199+
200+
libraries.forEach(library => {
201+
const libraryContainer = document.createElement('div');
202+
libraryContainer.className = 'libraryContainer';
203+
204+
const headerDiv = document.createElement('div');
205+
headerDiv.className = 'libraryHeader';
206+
207+
// Create label for the library name input
208+
const nameLabel = document.createElement('div');
209+
nameLabel.className = 'libraryNameLabel';
210+
211+
const nameInput = document.createElement('input');
212+
nameInput.type = 'text';
213+
nameInput.value = library.name;
214+
nameInput.placeholder = 'Library Name';
215+
nameInput.className = 'libraryNameInput';
216+
nameInput.oninput = function(e) {
217+
library.name = e.target.value;
218+
saveLibrariesToLocalStorage();
219+
};
220+
221+
nameLabel.appendChild(document.createTextNode('Library Name:'));
222+
nameLabel.appendChild(nameInput);
223+
224+
const removeButton = document.createElement('button');
225+
removeButton.className = 'removeLibraryButton';
226+
removeButton.textContent = 'Remove';
227+
removeButton.onclick = function() {
228+
removeLibrary(library.id);
229+
};
230+
231+
headerDiv.appendChild(nameLabel);
232+
headerDiv.appendChild(removeButton);
233+
234+
const editorDiv = document.createElement('div');
235+
editorDiv.className = 'codeInputContainer';
236+
237+
const codeInput = document.createElement('code-input');
238+
codeInput.setAttribute('lang', 'cql');
239+
codeInput.setAttribute('placeholder', 'Type CQL Library Here');
240+
codeInput.className = 'codeInput';
241+
codeInput.value = library.content;
242+
codeInput.onchange = function(e) {
243+
library.content = e.target.value;
244+
saveLibrariesToLocalStorage();
245+
};
246+
247+
editorDiv.appendChild(codeInput);
248+
249+
libraryContainer.appendChild(headerDiv);
250+
libraryContainer.appendChild(editorDiv);
251+
252+
container.appendChild(libraryContainer);
253+
});
254+
}
255+
256+
/**
257+
* saveLibrariesToLocalStorage saves the libraries to localStorage.
258+
*/
259+
function saveLibrariesToLocalStorage() {
260+
localStorage.setItem('cqlLibraries', JSON.stringify(libraries));
261+
}
262+
263+
/**
264+
* loadLibrariesFromLocalStorage loads the libraries from localStorage.
265+
*/
266+
function loadLibrariesFromLocalStorage() {
267+
const storedLibraries = localStorage.getItem('cqlLibraries');
268+
if (storedLibraries) {
269+
libraries = JSON.parse(storedLibraries);
270+
renderLibraries();
271+
}
124272
}
125273

126274
/**
@@ -150,6 +298,33 @@ function setupPrism() {
150298
'syntax-highlighted', codeInput.templates.prism(Prism, []));
151299
}
152300

301+
/**
302+
* handleFileUpload processes uploaded CQL library files
303+
*/
304+
function handleFileUpload(event) {
305+
const fileList = event.target.files;
306+
if (fileList.length === 0) {
307+
return; // No file selected
308+
}
309+
310+
const file = fileList[0];
311+
const reader = new FileReader();
312+
313+
reader.onload = function(e) {
314+
const content = e.target.result;
315+
// Extract library name from filename (remove .cql extension)
316+
const fileName = file.name.replace(/\.cql$/i, '');
317+
318+
// Add the library with the file content
319+
addLibrary(fileName, content);
320+
};
321+
322+
reader.readAsText(file);
323+
324+
// Reset the file input so the same file can be selected again
325+
event.target.value = '';
326+
}
327+
153328
/**
154329
* main is the entrypoint for the script.
155330
*/
@@ -159,8 +334,15 @@ function main() {
159334
bindInputsOnChange();
160335
bindButtonActions();
161336

162-
// Initially hide dataEntry tab:
337+
// Load libraries from localStorage
338+
loadLibrariesFromLocalStorage();
339+
340+
// Initially hide non-CQL tabs
163341
document.getElementById('dataEntry').style.display = 'none';
342+
document.getElementById('librariesEntry').style.display = 'none';
343+
344+
// Set CQL tab as active
345+
document.getElementById('cqlTabButton').className = 'active';
164346
}
165347

166348
main(); // All code actually executed when the script is loaded by the HTML.

cmd/cqlplay/static/index.html

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ <h2> ⚡ CQL Playground </h2>
4343
<div class="tabholder">
4444
<button id="cqlTabButton"> CQL </button>
4545
<button id="dataTabButton"> Data </button>
46+
<button id="librariesTabButton"> Libraries </button>
4647
</div>
4748

4849
<div id="cqlEntry" class="tabContent">
@@ -57,6 +58,22 @@ <h3>Data Editor</h3>
5758
<code-input lang="json" placeholder="Enter synthetic JSON FHIR Bundle here." class="codeInput" id="dataInput"></code-input>
5859
</div>
5960
</div>
61+
<div id="librariesEntry" class="tabContent">
62+
<h3>CQL Libraries</h3>
63+
<p class="instructions">
64+
Add additional CQL libraries to be included in your CQL execution.
65+
Libraries can be created directly or uploaded from files.
66+
Each library should have a name and contain valid CQL code.
67+
</p>
68+
<div id="librariesContainer">
69+
<!-- Libraries will be added here dynamically -->
70+
</div>
71+
<div class="libraryButtons">
72+
<button id="addLibrary" class="addButton">Add Library</button>
73+
<label for="uploadLibrary" class="uploadButton">Add Library from File</label>
74+
<input type="file" id="uploadLibrary" accept=".cql" style="display:none">
75+
</div>
76+
</div>
6077
<button id="submit" class="submitButton">
6178
Run!
6279
</button>

cmd/cqlplay/static/styles.css

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,4 +91,83 @@ code-input {
9191

9292
.submitButton {
9393
margin: 10px;
94+
font-family: Roboto, Helvetica;
95+
font-size: 1em;
96+
font-weight: normal;
97+
}
98+
99+
.addButton {
100+
margin: 10px;
101+
background-color: #4CAF50;
102+
color: white;
103+
border: none;
104+
padding: 8px 16px;
105+
cursor: pointer;
106+
font-family: Roboto, Helvetica;
107+
font-size: 1em;
108+
font-weight: normal;
109+
}
110+
111+
.uploadButton {
112+
margin: 10px;
113+
background-color: #2196F3;
114+
color: white;
115+
border: none;
116+
padding: 8px 16px;
117+
cursor: pointer;
118+
display: inline-block;
119+
font-family: Roboto, Helvetica;
120+
font-size: 1em;
121+
font-weight: normal;
122+
}
123+
124+
.libraryButtons {
125+
display: flex;
126+
flex-direction: row;
127+
}
128+
129+
.libraryContainer {
130+
margin-bottom: 20px;
131+
border: 1px solid #ddd;
132+
padding: 10px;
133+
background-color: #f9f9f9;
134+
}
135+
136+
.libraryHeader {
137+
display: flex;
138+
justify-content: space-between;
139+
align-items: center;
140+
margin-bottom: 10px;
141+
}
142+
143+
.libraryNameLabel {
144+
display: flex;
145+
align-items: center;
146+
font-family: Roboto, Helvetica;
147+
font-weight: bold;
148+
}
149+
150+
.libraryNameInput {
151+
padding: 5px;
152+
width: 300px;
153+
margin-left: 10px;
154+
}
155+
156+
.removeLibraryButton {
157+
background-color: #f44336;
158+
color: white;
159+
border: none;
160+
padding: 5px 10px;
161+
cursor: pointer;
162+
font-family: Roboto, Helvetica;
163+
font-size: 1em;
164+
font-weight: normal;
165+
}
166+
167+
.instructions {
168+
font-family: Roboto, Helvetica;
169+
font-size: 14px;
170+
color: #555;
171+
margin-bottom: 15px;
172+
line-height: 1.4;
94173
}

0 commit comments

Comments
 (0)