Skip to content

Commit

Permalink
Error messages on api requests printed to users
Browse files Browse the repository at this point in the history
Closes #260

We are now dependant on the rest api
  • Loading branch information
costrouc committed Mar 11, 2022
1 parent bd291b6 commit 7347e5e
Show file tree
Hide file tree
Showing 5 changed files with 147 additions and 95 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
{% include 'navigation.html' %}

<div class="container">
<div class="row" id="message"></div>

{% block content %}{% endblock %}
</div>

Expand Down
43 changes: 41 additions & 2 deletions conda-store-server/conda_store_server/server/templates/create.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@
<pre class="alert alert-danger" role="alert">{{ message }}</pre>
{% endif %}

<form action="{{ url_for('ui.ui_create_get_environment') }}" method="post">
<form id="create-environment">
<div>
<label for="namespace">Namespace</label>
<select class="form-select" name="namespace" id="namespace" aria-label="namespace">
{% for namespace in namespaces %}
<option {% if loop.index == 0 %}selected{% endif %} value="{{ namespace.id }}">{{ namespace.name }}</option>
<option {% if loop.index == 0 %}selected{% endif %} value="{{ namespace.name }}">{{ namespace.name }}</option>
{% endfor %}
</select>
</div>
Expand Down Expand Up @@ -52,5 +52,44 @@
});
reader.readAsText(event.target.files[0]);
});

function bannerMessage(message) {
let banner = document.querySelector('#message');
banner.innerHTML = message;
}

async function createEnvironmentHandler(event) {
event.preventDefault();

let url = "{{ url_for('api.api_post_specification') }}";
let namespaceInput = document.querySelector('#namespace');
let specificationInput = document.querySelector('#specification');

let response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
'namespace': namespaceInput.value,
'specification': specificationInput.value,
})
});

if (response.ok) {
window.location = "{{ url_for('ui.ui_list_environments') }}";
} else {
let data = await response.json();
let message = data.message;
if (message.search('[\n\r]') !== -1) {
bannerMessage(`<pre class="alert alert-danger col">${data.message}</pre>`);
} else {
bannerMessage(`<div class="alert alert-danger col">${data.message}</div>`);
}
}
}

let form = document.querySelector('#create-environment');
form.addEventListener('submit', (event) => createEnvironmentHandler(event));
</script>
{% endblock %}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ <h5 class="card-title">{{ environment.namespace.name }}/{{ environment.name }}
</ul>
<div class="card-body">
<a href="{{ url_for('ui.ui_edit_environment', namespace=environment.namespace.name, name=environment.name) }}" class="btn btn-primary btn-block">Edit</a>
<button type="button" onclick="deleteEnvironment()" class="btn btn-primary btn-block">
<button id="delete-environment" type="button" class="btn btn-primary btn-block">
Delete
</button>
</div>
Expand All @@ -31,17 +31,17 @@ <h3>Builds</h3>
<li class="list-group-item d-flex justify-content-between align-items-center {% if build.id == environment.current_build_id %}list-group-item-success{% elif build.deleted_on is not none %}list-group-item-secondary{% endif %}">
<a href="{{ url_for('ui.ui_get_build', build_id=build.id) }}">Build {{ build.id }}</a>
<span>{{ build.status.value }}</span>
<div class="btn-group" role="group" aria-label="Build actions">
<div id="actions-build" class="btn-group" role="group" aria-label="Build actions">
{% if build.id != environment.current_build_id and build.status.value == 'COMPLETED' and build.deleted_on is none %}
<button type="button" onclick="updateEnvironmentBuild('{{ build.id }}')" class="btn btn-primary mb-2">
<button type="button" data-action="set-current" data-id="{{ build.id }}" class="btn btn-primary mb-2">
<ion-icon name="checkmark"></ion-icon>
</button>
{% endif %}
<button type="button" onclick="buildAction('PUT', '{{ build.id }}')" class="btn btn-primary mb-2">
<button type="button" data-action="refresh" data-id="{{ build.id }}" class="btn btn-primary mb-2">
<ion-icon name="refresh"></ion-icon>
</button>
{% if build.status.value in ["COMPLETED", "FAILED"] and build.deleted_on is none %}
<button type="button" onclick="buildAction('DELETE', '{{ build.id }}')" class="btn btn-primary mb-2">
<button type="button" data-action="delete" data-id="{{ build.id }}" class="btn btn-primary mb-2">
<ion-icon name="trash"></ion-icon>
</button>
{% endif %}
Expand All @@ -58,26 +58,90 @@ <h3>Builds</h3>
editor.setFontSize("20px");
editor.setReadOnly(true);

function updateEnvironmentBuild(buildId) {
fetch(`{{ url_for('api.api_get_environment', namespace=environment.namespace.name, name=environment.name) }}`, {
function bannerMessage(message) {
let banner = document.querySelector('#message');
banner.innerHTML = message;
}

async function setCurrentEnvironmentBuild(event) {
event.preventDefault();
let buildId = event.currentTarget.getAttribute('data-id');

let url = `{{ url_for('api.api_get_environment', namespace=environment.namespace.name, name=environment.name) }}`;
let response = await fetch(url, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify({"build_id": buildId})
}).then(response => window.location.reload(true));
body: JSON.stringify({ build_id: buildId })
})

if (response.ok) {
window.location.reload(true);
} else {
let data = await response.json();
bannerMessage(`<div class="alert alert-danger col">${data.message}</div>`);
}
}

function deleteEnvironment() {
fetch(`{{ url_for('api.api_get_environment', namespace=environment.namespace.name, name=environment.name) }}`, {
method: 'DELETE',
}).then(response => window.location = '/');
async function refreshBuild(event) {
event.preventDefault();
let buildId = event.currentTarget.getAttribute('data-id');

let url = `{{ url_for('api.api_list_builds') }}${ buildId }/`;
let response = await fetch(url, { method: 'PUT' });

if (response.ok) {
window.location.reload(true);
} else {
let data = await response.json();
bannerMessage(`<div class="alert alert-danger col">${data.message}</div>`);
}
}

function buildAction(method, buildId) {
fetch(`{{ url_for('api.api_list_builds') }}${buildId}/`, {
method: method,
}).then(response => window.location.reload(true));
async function deleteBuild(event) {
event.preventDefault();
let buildId = event.currentTarget.getAttribute('data-id');

let url = `{{ url_for('api.api_list_builds') }}${ buildId }/`;
let response = await fetch(url, { method: 'DELETE' });

if (response.ok) {
window.location.reload(true);
} else {
let data = await response.json();
bannerMessage(`<div class="alert alert-danger col">${data.message}</div>`);
}
}

document.querySelectorAll('button[data-action="set-current"]').forEach(item => {
item.addEventListener('click', (event) => setCurrentEnvironmentBuild(event));
})

document.querySelectorAll('button[data-action="refresh"]').forEach(item => {
item.addEventListener('click', (event) => refreshBuild(event));
})

document.querySelectorAll('button[data-action="delete"]').forEach(item => {
item.addEventListener('click', (event) => deleteBuild(event));
})

async function deleteEnvironment(event) {
event.preventDefault();

let url = "{{ url_for('api.api_get_environment', namespace=environment.namespace.name, name=environment.name) }}";
let response = await fetch(url, { method: 'DELETE' })

if (response.ok) {
window.location = "{{ url_for('ui.ui_list_environments') }}";
} else {
let data = await response.json();
bannerMessage(`<div class="alert alert-danger col">${data.message}</div>`);
}
}

let deleteEnvironmentButton = document.querySelector('#delete-environment');
deleteEnvironmentButton.addEventListener('click', (event) => deleteEnvironment(event));

</script>
{% endblock %}
36 changes: 21 additions & 15 deletions conda-store-server/conda_store_server/server/views/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ def api_get_namespace(namespace):

namespace = api.get_namespace(conda_store.db, namespace)
if namespace is None:
return jsonify({"status": "error", "error": "namespace does not exist"}), 404
return jsonify({"status": "error", "message": "namespace does not exist"}), 404

return jsonify(
{
Expand All @@ -147,7 +147,7 @@ def api_create_namespace(namespace):

namespace_orm = api.get_namespace(conda_store.db, namespace)
if namespace_orm:
return jsonify({"status": "error", "error": "namespace already exists"}), 409
return jsonify({"status": "error", "message": "namespace already exists"}), 409

try:
api.create_namespace(conda_store.db, namespace)
Expand All @@ -166,7 +166,7 @@ def api_delete_namespace(namespace):

namespace_orm = api.get_namespace(conda_store.db, namespace)
if namespace_orm is None:
return jsonify({"status": "error", "error": "namespace does not exist"}), 404
return jsonify({"status": "error", "message": "namespace does not exist"}), 404

conda_store.delete_namespace(namespace)
return jsonify({"status": "ok"})
Expand Down Expand Up @@ -205,7 +205,10 @@ def api_get_environment(namespace, name):

environment = api.get_environment(conda_store.db, namespace=namespace, name=name)
if environment is None:
return jsonify({"status": "error", "error": "environment does not exist"}), 404
return (
jsonify({"status": "error", "message": "environment does not exist"}),
404,
)

return jsonify(
{
Expand Down Expand Up @@ -268,10 +271,13 @@ def api_post_specification():
specification = request.json.get("specification")
specification = yaml.safe_load(specification)
specification = schema.CondaSpecification.parse_obj(specification)
except yaml.error.YAMLError as e:
return jsonify({"status": "error", "error": str(e)}), 400
except yaml.error.YAMLError:
return (
jsonify({"status": "error", "message": "Unable to parse. Invalid YAML"}),
400,
)
except pydantic.ValidationError as e:
return jsonify({"status": "error", "error": e.errors()}), 400
return jsonify({"status": "error", "message": str(e)}), 400

auth.authorize_request(
f"{namespace_name}/{specification.name}",
Expand All @@ -282,7 +288,7 @@ def api_post_specification():
try:
build_id = api.post_specification(conda_store, specification, namespace_name)
except ValueError as e:
return jsonify({"status": "error", "error": str(e.args[0])}), 400
return jsonify({"status": "error", "message": str(e.args[0])}), 400

return jsonify({"status": "ok", "data": {"build_id": build_id}})

Expand Down Expand Up @@ -313,7 +319,7 @@ def api_get_build(build_id):

build = api.get_build(conda_store.db, build_id)
if build is None:
return jsonify({"status": "error", "error": "build id does not exist"}), 404
return jsonify({"status": "error", "message": "build id does not exist"}), 404

auth.authorize_request(
f"{build.environment.namespace.name}/{build.environment.name}",
Expand All @@ -336,11 +342,11 @@ def api_put_build(build_id):

build = api.get_build(conda_store.db, build_id)
if build is None:
return jsonify({"status": "error", "error": "build id does not exist"}), 404
return jsonify({"status": "error", "message": "build id does not exist"}), 404

auth.authorize_request(
f"{build.environment.namespace.name}/{build.environment.name}",
{Permissions.ENVIRONMENT_READ},
{Permissions.ENVIRONMENT_UPDATE},
require=True,
)

Expand All @@ -355,7 +361,7 @@ def api_delete_build(build_id):

build = api.get_build(conda_store.db, build_id)
if build is None:
return jsonify({"status": "error", "error": "build id does not exist"}), 404
return jsonify({"status": "error", "message": "build id does not exist"}), 404

auth.authorize_request(
f"{build.environment.namespace.name}/{build.environment.name}",
Expand All @@ -374,7 +380,7 @@ def api_get_build_packages(build_id):

build = api.get_build(conda_store.db, build_id)
if build is None:
return jsonify({"status": "error", "error": "build id does not exist"}), 404
return jsonify({"status": "error", "message": "build id does not exist"}), 404

auth.authorize_request(
f"{build.environment.namespace.name}/{build.environment.name}",
Expand Down Expand Up @@ -406,7 +412,7 @@ def api_get_build_logs(build_id):

build = api.get_build(conda_store.db, build_id)
if build is None:
return jsonify({"status": "error", "error": "build id does not exist"}), 404
return jsonify({"status": "error", "message": "build id does not exist"}), 404

auth.authorize_request(
f"{build.environment.namespace.name}/{build.environment.name}",
Expand Down Expand Up @@ -468,7 +474,7 @@ def api_get_build_yaml(build_id):

build = api.get_build(conda_store.db, build_id)
if build is None:
return jsonify({"status": "error", "error": "build id does not exist"}), 404
return jsonify({"status": "error", "message": "build id does not exist"}), 404

auth.authorize_request(
f"{build.environment.namespace.name}/{build.environment.name}",
Expand Down
Loading

0 comments on commit 7347e5e

Please sign in to comment.