Skip to content

Commit

Permalink
Error when calling extension (/customenvironments)
Browse files Browse the repository at this point in the history
EventSource does not allow to pass headers. For that reason, a `fetch` promise is used. Its approach is different, however it has the same effect for this manner and no impact from the user's experience.
A dedidacted function for errors was added.
API code was adapted to reply accordingly.
  • Loading branch information
rodrigo-sobral authored and etejedor committed Sep 11, 2024
1 parent 4e727e7 commit 86e64fb
Show file tree
Hide file tree
Showing 2 changed files with 112 additions and 77 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,10 @@ def get(self):
makenv_process = subprocess.Popen([self.makenv_path, *arguments], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)

for line in iter(makenv_process.stdout.readline, b""):
self.write(f"data: {line.decode('utf-8')}\n\n")
self.write(line.decode('utf-8'))
self.flush()

self.finish("data: EOF\n\n")
self.finish()


class SwanCustomEnvironmentsHandler(JupyterHandler):
Expand Down
185 changes: 110 additions & 75 deletions SwanCustomEnvironments/swancustomenvironments/templates/customenvs.html
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,11 @@ <h2 id="modal-title" style="text-align: left;"></h2>
<script type = "text/javascript">
const redirect2Config = () => window.location = `/hub/home?changeconfig`;
const redirect2Lab = (base_url, project_path) => window.location = `${base_url}lab${project_path}`;
const get_jh_auth_header = () => {
const cookie = document.cookie.match("\\b_xsrf=([^;]*)\\b");
const xsrf = cookie ? cookie[1] : undefined;
return xsrf ? {'X-XSRFToken': xsrf} : {}
};

var base_url = document.body.getAttribute('data-base-url');
var project_path = '/';
Expand All @@ -165,92 +170,122 @@ <h2 id="modal-title" style="text-align: left;"></h2>
var modalBackButton = document.getElementById("modal-back-button");
var modalJupyter = document.getElementById("modal-lab-button");

// Create EventSource to listen for updates from the backend
var eventSource = new EventSource(`${base_url}api/customenvs?repo=${encodeURIComponent(repository)}&repo_type=${encodeURIComponent(repo_type)}&builder=${encodeURIComponent(builder)}&builder_version=${encodeURIComponent(builder_version)}`);

// Update UI every time a message is received
eventSource.onmessage = (event) => {
const message_line = event.data;
// Function to trigger a visual error in the loader
const trigger_visual_error = (message) => {
loader.getElementsByClassName('loader-line-mask')[0].style.display = 'none';
loader.getElementsByClassName('text')[0].innerHTML = 'Error creating environment';
back_button.onclick = () => redirect2Config();
back_button.style.display = 'block';
console.error(message);
};

// Close SSE connection -> update UI when done
if (message_line === 'EOF') {
eventSource.close();
loader.getElementsByClassName('loader-line-mask')[0].style.display = 'none';
// Function to process each chunk of data iteratively
async function render_data_chunks(reader, decoder) {
try {
// Read chunks until the stream is done
while (true) {
// Wait for the next chunk of data
const { done, value } = await reader.read();

// Exit the loop if no more data is available
if (done) {
loader.getElementsByClassName('loader-line-mask')[0].style.display = 'none';

// Output has errors -> change title and show button for going back to spawner
if (has_failed) {
loader.getElementsByClassName('text')[0].innerHTML = 'Error creating environment';
back_button.onclick = () => redirect2Config();
back_button.style.display = 'block';
// Output has errors -> change title and show button for going back to spawner
if (has_failed) {
trigger_visual_error("");
}

// Environment exists already -> ask user if they want to reconfigure it
} else if (is_created) {
modalTitle.innerHTML = `This session has already an environment named ${env_name}`;
modalBackButton.onclick = () => redirect2Config();
modalJupyter.onclick = () => redirect2Lab(base_url, project_path);
modal.style.display = "block";
// Environment exists already -> ask user if they want to reconfigure it
else if (is_created) {
modalTitle.innerHTML = `This session already has an environment named ${env_name}`;
modalBackButton.onclick = () => redirect2Config();
modalJupyter.onclick = () => redirect2Lab(base_url, project_path);
modal.style.display = "block";
}

// Output is fine -> change title if successful (and Next button to go to lab)
} else {
let stop_clock = false, seconds = 5;
loader.getElementsByClassName('text')[0].innerHTML = 'Environment created';
outputBox.innerHTML += `Environment ${env_name} created successfully.<br>Redirecting to JupyterLab in `;
outputBox.scrollTop = outputBox.scrollHeight;
back_button.onclick = () => redirect2Config();
back_button.style.display = 'block';
pause_button.style.display = 'block';
pause_button.onclick = () => {
stop_clock = true
lab_button.style.display = 'block';
pause_button.style.display = 'none';
lab_button.onclick = () => redirect2Lab(base_url, project_path);
};
const countdown = setInterval(() => {
if (stop_clock) {
clearInterval(countdown);
} else if (seconds <= 0) {
clearInterval(countdown);
redirect2Lab(base_url, project_path);
} else {
outputBox.innerHTML += `${seconds}...`;
seconds--;
// Output is fine -> change title if successful (and Next button to go to lab)
else {
let stop_clock = false, seconds = 5;
loader.getElementsByClassName('text')[0].innerHTML = 'Environment created';
outputBox.innerHTML += `Environment ${env_name} created successfully.<br>Redirecting to JupyterLab in `;
outputBox.scrollTop = outputBox.scrollHeight;
back_button.onclick = () => redirect2Config();
back_button.style.display = 'block';
pause_button.style.display = 'block';
pause_button.onclick = () => {
stop_clock = true;
lab_button.style.display = 'block';
pause_button.style.display = 'none';
lab_button.onclick = () => redirect2Lab(base_url, project_path);
};
const countdown = setInterval(() => {
if (stop_clock) {
clearInterval(countdown);
} else if (seconds <= 0) {
clearInterval(countdown);
redirect2Lab(base_url, project_path);
} else {
outputBox.innerHTML += `${seconds}...`;
seconds--;
}
}, 1000);
}
}, 1000);
}
break; // Exit the loop after processing all data
}

// Get the project path from the output
} else if (message_line.startsWith('REPO_PATH')) {
const repo_path = message_line.split(':')[1];
if (repo_path && repo_path !== '/') {
project_path = `/tree${repo_path}${file}`;
// Decode the chunk into text and split by newlines
const chunked_lines = decoder.decode(value, { stream: true });
chunked_lines.split('\n').forEach(message_line => {
if (message_line.startsWith('REPO_PATH')) {
const repo_path = message_line.split(':')[1];
if (repo_path && repo_path !== '/') {
project_path = `/tree${repo_path}${file}`;
}
}
// Get the environment name from the output
else if (message_line.startsWith('ENV_NAME')) {
env_name = message_line.split(':')[1];
}
// Environment already exists -> pops up a modal
else if (message_line.startsWith('ENVIRONMENT_ALREADY_EXISTS')) {
is_created = true;
env_name = message_line.split(':')[1];
}
// Usual case -> update output box with the message received
else {
outputBox.innerHTML += message_line;
outputBox.scrollTop = outputBox.scrollHeight;
if (message_line.includes('ERROR')) {
has_failed = true;
}
}
});
outputBox.innerHTML += '<br>';
}
} catch (error) {
trigger_visual_error(`Error reading stream: ${error}`);
}
}

// Get the environment name from the output
} else if (message_line.startsWith('ENV_NAME')) {
env_name = message_line.split(':')[1];

// Environment already exists -> pops up a modal
} else if (message_line.startsWith('ENVIRONMENT_ALREADY_EXISTS')) {
is_created = true;
env_name = message_line.split(':')[1];

// Usual case -> update output box with the message received
fetch(`${base_url}api/customenvs?repo=${encodeURIComponent(repository)}&repo_type=${encodeURIComponent(repo_type)}&builder=${encodeURIComponent(builder)}&builder_version=${encodeURIComponent(builder_version)}`, {
method: 'GET',
headers: get_jh_auth_header(),
})
.then(response => {
if (!response.ok) {
trigger_visual_error(`Network response was not ok: ${response.statusText}`);
} else {
outputBox.innerHTML += message_line + '<br>';
outputBox.scrollTop = outputBox.scrollHeight;
if (message_line.includes('ERROR')) {
has_failed = true;
}
const reader = response.body.getReader();
const decoder = new TextDecoder("utf-8");
// Start reading the first chunk
render_data_chunks(reader, decoder);
}
};

// Handle any errors that occur with the EventSource connection
eventSource.onerror = (err) => {
loader.getElementsByClassName('loader-line-mask')[0].style.display = 'none';
loader.getElementsByClassName('text')[0].innerHTML = 'Error creating environment';
back_button.style.display = 'block';
};

})
.catch(error => {
trigger_visual_error(`Error fetching stream: ${error}`);
});

</script>
{% endblock %}

0 comments on commit 86e64fb

Please sign in to comment.