From 79dc1216f1d1f4459bb8b60c2f00e99dc333a5a8 Mon Sep 17 00:00:00 2001 From: Dominik Heidler Date: Fri, 13 Sep 2024 14:12:02 +0200 Subject: [PATCH] Replace $.ajax() with fetch() Ticket: https://progress.opensuse.org/issues/166310 Co-authored-by: Martchus --- assets/javascripts/admin_assets.js | 49 +++++---- assets/javascripts/admin_groups.js | 98 +++++++++-------- assets/javascripts/admin_needle.js | 20 ++-- assets/javascripts/admin_user.js | 51 +++++---- assets/javascripts/admin_worker.js | 23 ++-- assets/javascripts/admintable.js | 94 ++++++++-------- assets/javascripts/audit_log.js | 27 +++-- assets/javascripts/comments.js | 108 ++++++++++--------- assets/javascripts/index.js | 27 +++-- assets/javascripts/job_templates.js | 88 +++++++-------- assets/javascripts/needleeditor.js | 96 ++++++++--------- assets/javascripts/obs_rsync.js | 66 ++++++------ assets/javascripts/openqa.js | 33 ++++-- assets/javascripts/overview.js | 17 ++- assets/javascripts/running.js | 42 +++++--- assets/javascripts/test_result.js | 51 +++++---- assets/javascripts/tests.js | 17 ++- lib/OpenQA/WebAPI/Controller/API/V1/Table.pm | 20 +++- t/ui/27-plugin_obs_rsync_status_details.t | 13 ++- 19 files changed, 504 insertions(+), 436 deletions(-) diff --git a/assets/javascripts/admin_assets.js b/assets/javascripts/admin_assets.js index cd60962e6722..8a0c7c42f7cc 100644 --- a/assets/javascripts/admin_assets.js +++ b/assets/javascripts/admin_assets.js @@ -121,37 +121,42 @@ function reloadAssetsTable() { } function deleteAsset(assetId) { - $.ajax({ - url: urlWithBase('/api/v1/assets/' + assetId), - method: 'DELETE', - dataType: 'json', - success: function () { + fetchWithCSRF(urlWithBase(`/api/v1/assets/${assetId}`), {method: 'DELETE'}) + .then(response => { + // not checking for status code as 404 case also returns proper json + return response.json(); + }) + .then(response => { + if (response.error) throw response.error; addFlash( 'info', - 'The asset was deleted successfully. The asset table\'s contents are cached. Hence the removal is not immediately visible. To update the view use the "Trigger asset cleanup" button. Note that this is an expensive operation which might take a while.' + "The asset was deleted successfully. The asset table's contents are cached." + + 'Hence the removal is not immediately visible. To update the view use the "Trigger asset cleanup" button.' + + 'Note that this is an expensive operation which might take a while.' ); - }, - error: function (xhr, ajaxOptions, thrownError) { - var error_message = xhr.responseJSON.error; - addFlash('danger', error_message); - } - }); + }) + .catch(error => { + console.error(error); + addFlash('danger', `Error deleting asset: ${error}`); + }); } function triggerAssetCleanup(form) { - $.ajax({ - url: form.action, - method: form.method, - success: function () { + fetchWithCSRF(form.action, {method: form.method}) + .then(response => { + if (!response.ok) throw `Server returned ${response.status}: ${response.statusText}`; + return response.json(); + }) + .then(response => { addFlash( 'info', - 'Asset cleanup has been triggered. Open the Minion dashboard to keep track of the task.' + `Asset cleanup has been triggered. Open the Minion dashboard to keep track of the task (gru_id #${response.gru_id}).` ); - }, - error: function (xhr, ajaxOptions, thrownError) { - addFlash('danger', 'Unable to trigger the asset cleanup: ' + thrownError); - } - }); + }) + .catch(error => { + console.error(error); + addFlash('danger', `Unable to trigger the asset cleanup: ${error}`); + }); } function showLastAssetStatusUpdate(assetStatus) { diff --git a/assets/javascripts/admin_groups.js b/assets/javascripts/admin_groups.js index 342939f18594..4e8f4cdba541 100644 --- a/assets/javascripts/admin_groups.js +++ b/assets/javascripts/admin_groups.js @@ -37,21 +37,23 @@ function showError(message) { } function fetchHtmlEntry(url, targetElement) { - $.ajax({ - url: url, - method: 'GET', - success: function (response) { + fetch(url) + .then(response => { + if (!response.ok) throw `Server returned ${response.status}: ${response.statusText}`; + return response.text(); + }) + .then(response => { var element = $(response); element.hide(); targetElement.prepend(element); $('#new_group_creating').hide(); $('#add_group_modal').modal('hide'); element.fadeIn('slow'); - }, - error: function (xhr, ajaxOptions, thrownError) { - showError(thrownError + ' (requesting entry HTML, group probably added though! - reload page to find out)'); - } - }); + }) + .catch(error => { + console.error(error); + showError(`${error} (requesting entry HTML, group probably added though! - reload page to find out)`); + }); } function countEmptyInputs(form) { @@ -87,7 +89,7 @@ function createGroup(form) { $('#new_group_error').hide(); $('#new_group_creating').show(); - let data = $(form).serialize(); + let data = new FormData(form); let postUrl, rowUrl, targetElement; if (form.dataset.createParent !== 'false') { postUrl = form.dataset.postParentGroupUrl; @@ -99,36 +101,26 @@ function createGroup(form) { const parentId = form.dataset.parentId; if (parentId !== 'none') { targetElement = $('#parent_group_' + parentId).find('ul'); - data += '&parent_id=' + parentId; + data.set('parent_id', parentId); } else { targetElement = $('#job_group_list'); } } - $.ajax({ - url: postUrl, - method: 'POST', - data: data, - success: function (response) { - if (!response) { - showError('Server returned no response'); - return; - } - var id = response.id; - if (!id) { - showError('Server returned no ID'); - return; - } + fetchWithCSRF(postUrl, {method: 'POST', body: data}) + .then(response => { + if (!response.ok) throw `Server returned ${response.status}: ${response.statusText}`; + return response.json(); + }) + .then(response => { + if (!response) throw 'Server returned no response'; + if (!response.id) throw 'Server returned no ID'; fetchHtmlEntry(rowUrl + response.id, targetElement); - }, - error: function (xhr, ajaxOptions, thrownError) { - if (xhr.responseJSON.error) { - showError(xhr.responseJSON.error); - } else { - showError(thrownError); - } - } - }); + }) + .catch(error => { + console.error(error); + showError(error); + }); return false; } @@ -262,24 +254,25 @@ function saveReorganizedGroups() { var updateJobGroupUrl = jobGroupList.data('put-job-group-url'); // event handlers for AJAX queries - var handleError = function (xhr, ajaxOptions, thrownError) { + var handleError = function (error) { + console.error(error); $('#reorganize_groups_panel').show(); $('#reorganize_groups_error').show(); $('#reorganize_groups_progress').hide(); - $('#reorganize_groups_error_message').text(thrownError ? thrownError : 'something went wrong'); + $('#reorganize_groups_error_message').text(error ? error : 'something went wrong'); $('html, body').animate({scrollTop: 0}, 1000); }; var handleSuccess = function (response, groupLi, index, parentId) { if (!response) { - handleError(undefined, undefined, 'Server returned nothing'); + handleError('Server returned nothing'); return; } if (!response.nothingToDo) { var id = response.id; if (!id) { - handleError(undefined, undefined, 'Server returned no ID'); + handleError('Server returned no ID'); return; } @@ -292,7 +285,7 @@ function saveReorganizedGroups() { if (ajaxQueries.length) { // do next query - $.ajax(ajaxQueries.shift()); + handleQuery(ajaxQueries.shift()); } else { // all queries done if (showPanelTimeout) { @@ -325,7 +318,7 @@ function saveReorganizedGroups() { ajaxQueries.push({ url: updateGroupUrl + groupId, method: 'PUT', - data: { + body: { sort_order: groupIndex, parent_id: 'none', drag: 1 @@ -350,7 +343,7 @@ function saveReorganizedGroups() { ajaxQueries.push({ url: updateJobGroupUrl + jobGroupId, method: 'PUT', - data: { + body: { sort_order: childGroupIndex, parent_id: groupId, drag: 1 @@ -366,9 +359,30 @@ function saveReorganizedGroups() { }); if (ajaxQueries.length) { - $.ajax(ajaxQueries.shift()); + handleQuery(ajaxQueries.shift()); } else { handleSuccess({nothingToDo: true}); } return false; } + +function handleQuery(query) { + const url = query.url; + delete query.url; + const success = query.success; + delete query.success; + const error = query.error; + delete query.error; + var body = new FormData(); + for (const key in query.body) { + body.append(key, query.body[key]); + } + query.body = body; + fetchWithCSRF(url, query) + .then(response => { + if (!response.ok) throw `Server returned ${response.status}: ${response.statusText}`; + return response.json(); + }) + .then(success) + .catch(error); +} diff --git a/assets/javascripts/admin_needle.js b/assets/javascripts/admin_needle.js index f85e366a162b..9178c0b6756c 100644 --- a/assets/javascripts/admin_needle.js +++ b/assets/javascripts/admin_needle.js @@ -158,10 +158,12 @@ function setupAdminNeedles() { deleteBunchOfNeedles(); }; - $.ajax({ - url: url + nextIDs.join('&id='), - type: 'DELETE', - success: function (response) { + fetchWithCSRF(url + nextIDs.join('&id='), {method: 'DELETE'}) + .then(response => { + if (!response.ok) throw `Server returned ${response.status}: ${response.statusText}`; + return response.json(); + }) + .then(response => { // add error affecting all deletions var singleError = response.error; if (singleError) { @@ -191,11 +193,11 @@ function setupAdminNeedles() { }); deleteBunchOfNeedles(); - }, - error: function (xhr, ajaxOptions, thrownError) { - handleSingleError(thrownError); - } - }); + }) + .catch(error => { + console.error(error); + handleSingleError(error); + }); return true; }; diff --git a/assets/javascripts/admin_user.js b/assets/javascripts/admin_user.js index d05240bc31af..8da704ee1586 100644 --- a/assets/javascripts/admin_user.js +++ b/assets/javascripts/admin_user.js @@ -22,46 +22,43 @@ function setup_admin_user() { return; } - var data = form.serializeArray(); - var newRole = data[1].value; + var data = new FormData(form[0]); + var newRole = data.get('role'); - $.ajax({ - type: 'POST', - url: form.attr('action'), - data: jQuery.param(data), - success: function (data) { + fetch(form.attr('action'), {method: 'POST', body: data}) + .then(response => { + if (!response.ok) throw `Server returned ${response.status}: ${response.statusText}`; findDefault(form).removeClass('default'); form.find('input[value="' + newRole + '"]').addClass('default'); - }, - error: function (err) { + }) + .catch(error => { + console.error(error); rollback(form); - addFlash('danger', 'An error occurred when changing the user role'); - } - }); + addFlash('danger', `An error occurred when changing the user role: ${error}`); + }); }); window.deleteUser = function (id) { if (!confirm('Are you sure you want to delete this user?')) return; - $.ajax({ - url: urlWithBase('/api/v1/user/' + id), - method: 'DELETE', - dataType: 'json', - success: function () { + fetchWithCSRF(urlWithBase('/api/v1/user/' + id), {method: 'DELETE'}) + .then(response => { + if (response.status == 500) + throw 'An internal server error has occurred. Maybe there are unsatisfied foreign key restrictions in the DB for this user.'; + if (!response.ok) throw `Server returned ${response.status}: ${response.statusText}`; + return response.json(); + }) + .then(response => { + if (response.error) throw response.error; addFlash('info', 'The user was deleted successfully.'); window.admin_user_table .row($('#user_' + id)) .remove() .draw(); - }, - error: function (xhr, ajaxOptions, thrownError) { - if (xhr.responseJSON && xhr.responseJSON.error) addFlash('danger', xhr.responseJSON.error); - else - addFlash( - 'danger', - 'An error has occurred. Maybe there are unsatisfied foreign key restrictions in the DB for this user.' - ); - } - }); + }) + .catch(error => { + console.error(error); + addFlash('danger', error); + }); }; } diff --git a/assets/javascripts/admin_worker.js b/assets/javascripts/admin_worker.js index bf8c9e543f54..2d587fb840d8 100644 --- a/assets/javascripts/admin_worker.js +++ b/assets/javascripts/admin_worker.js @@ -61,18 +61,17 @@ function loadWorkerTable() { function deleteWorker(deleteBtn) { var post_url = $(deleteBtn).attr('post_delete_url'); - $.ajax({ - url: post_url, - method: 'DELETE', - dataType: 'json', - success: function (data) { + fetchWithCSRF(post_url, {method: 'DELETE'}) + .then(response => { + return response.json(); + }) + .then(response => { + if (response.error) throw response.error; var table = $('#workers').DataTable(); table.row($(deleteBtn).parents('tr')).remove().draw(); - addFlash('info', data.message); - }, - error: function (xhr, ajaxOptions, thrownError) { - var message = xhr.responseJSON.error; - addFlash('danger', "The worker couldn't be deleted: " + message); - } - }); + addFlash('info', response.message); + }) + .catch(error => { + addFlash('danger', "The worker couldn't be deleted: " + error); + }); } diff --git a/assets/javascripts/admintable.js b/assets/javascripts/admintable.js index cd2ec38ddd17..aa0c4766f858 100644 --- a/assets/javascripts/admintable.js +++ b/assets/javascripts/admintable.js @@ -83,13 +83,6 @@ function refreshAdminTableRow(tdElement) { window.adminTable.row(tdElement.parentElement).invalidate().draw(); } -function handleAdminTableApiError(request, status, error) { - if (request.responseJSON.error) { - error += ': ' + request.responseJSON.error; - } - addFlash('danger', error); -} - function adminTableApiUrl() { return urlWithBase(document.getElementById('admintable_api_url').value); } @@ -99,12 +92,13 @@ function handleAdminTableSubmit(tdElement, response, id) { setEditingAdminTableRow(tdElement, false, true); // query affected row again so changes applied by the server (removing invalid chars from settings keys) are visible - $.ajax({ - url: adminTableApiUrl() + '/' + id, - type: 'GET', - dataType: 'json', - success: function (resp) { - var rowData = resp[Object.keys(resp)[0]]; + fetch(adminTableApiUrl() + '/' + id) + .then(response => { + return response.json(); + }) + .then(response => { + if (response.error) throw response.error; + var rowData = response[Object.keys(response)[0]]; if (rowData) { rowData = rowData[0]; } @@ -121,9 +115,10 @@ function handleAdminTableSubmit(tdElement, response, id) { } row.data((adminTable.rowData[rowIndex] = rowData)).draw(false); showAdminTableRow(row); - }, - error: handleAdminTableApiError - }); + }) + .catch(error => { + addFlash('danger', error); + }); } function getAdminTableRowData(trElement, dataToSubmit, internalRowData) { @@ -223,31 +218,38 @@ function submitAdminTableRow(tdElement, id) { const url = adminTableApiUrl(); if (id) { // update - $.ajax({ - url: url + '/' + id, - type: 'POST', - dataType: 'json', - data: dataToSubmit, - headers: { - 'X-HTTP-Method-Override': 'PUT' - }, - success: function (response) { + fetchWithCSRF(url + '/' + id, { + method: 'PUT', + body: JSON.stringify(dataToSubmit), + headers: {'Content-Type': 'application/json'} + }) + .then(response => { + return response.json(); + }) + .then(response => { + if (response.error) throw response.error; handleAdminTableSubmit(tdElement, response, id); - }, - error: handleAdminTableApiError - }); + }) + .catch(error => { + addFlash('danger', error); + }); } else { // create new - $.ajax({ - url: url, - type: 'POST', - dataType: 'json', - data: dataToSubmit, - success: function (response) { + fetchWithCSRF(url, { + method: 'POST', + body: JSON.stringify(dataToSubmit), + headers: {'Content-Type': 'application/json'} + }) + .then(response => { + return response.json(); + }) + .then(response => { + if (response.error) throw response.error; handleAdminTableSubmit(tdElement, response, response.id); - }, - error: handleAdminTableApiError - }); + }) + .catch(error => { + addFlash('danger', error); + }); } } @@ -272,15 +274,17 @@ function deleteTableRow(tdElement, id) { return; } - $.ajax({ - url: adminTableApiUrl() + '/' + id, - type: 'DELETE', - dataType: 'json', - success: function () { + fetchWithCSRF(adminTableApiUrl() + '/' + id, {method: 'DELETE'}) + .then(response => { + return response.json(); + }) + .then(response => { + if (response.error) throw response.error; removeAdminTableRow(tdElement); - }, - error: handleAdminTableApiError - }); + }) + .catch(error => { + addFlash('danger', error); + }); } function renderAdminTableValue(data, type, row, meta) { diff --git a/assets/javascripts/audit_log.js b/assets/javascripts/audit_log.js index 7771813fdd46..4a4b4c2441bb 100644 --- a/assets/javascripts/audit_log.js +++ b/assets/javascripts/audit_log.js @@ -56,16 +56,25 @@ function undoComments(undoButton) { return; } undoButton.style.display = 'none'; - $.ajax({ - url: urlWithBase('/api/v1/comments'), - method: 'DELETE', - data: ids.map(id => `id=${id}`).join('&'), - success: () => addFlash('info', 'The comments have been deleted.'), - error: (jqXHR, textStatus, errorThrown) => { + var data = new FormData(); + console.log('ids:'); + console.log(ids); + for (const id of ids) { + data.append('id', id); + } + fetchWithCSRF(urlWithBase('/api/v1/comments'), {method: 'DELETE', body: data}) + .then(response => { + return response.json(); + }) + .then(response => { + if (response.error) throw response.error; + addFlash('info', 'The comments have been deleted.'); + }) + .catch(error => { + console.error(error); undoButton.style.display = 'inline'; - addFlash('danger', 'The comments could not be deleted: ' + getXhrError(jqXHR, textStatus, errorThrown)); - } - }); + addFlash('danger', `The comments could not be deleted: ${error}`); + }); } function getElementForEventType(type, eventData) { diff --git a/assets/javascripts/comments.js b/assets/javascripts/comments.js index 8121045f8f3c..ea7ef40d1f22 100644 --- a/assets/javascripts/comments.js +++ b/assets/javascripts/comments.js @@ -34,10 +34,6 @@ function renderCommentHeading(comment, commentId) { return heading; } -function showXhrError(context, jqXHR, textStatus, errorThrown) { - window.alert(context + getXhrError(jqXHR, textStatus, errorThrown)); -} - function updateNumerOfComments() { const commentsLink = document.querySelector('a[href="#comments"]'); if (commentsLink) { @@ -51,15 +47,15 @@ function deleteComment(deleteButton) { if (!window.confirm('Do you really want to delete the comment written by ' + author + '?')) { return; } - $.ajax({ - url: deleteButton.dataset.deleteUrl, - method: 'DELETE', - success: () => { + fetchWithCSRF(deleteButton.dataset.deleteUrl, {method: 'DELETE'}) + .then(response => { + if (!response.ok) throw `Server returned ${response.status}: ${response.statusText}`; $(deleteButton).parents('.comment-row, .pinned-comment-row').remove(); updateNumerOfComments(); - }, - error: showXhrError.bind(undefined, "The comment couldn't be deleted: ") - }); + }) + .catch(error => { + window.alert(`The comment couldn't be deleted: ${error}`); + }); } function updateComment(form) { @@ -75,31 +71,30 @@ function updateComment(form) { displayElements([textElement, form.applyChanges, form.discardChanges], 'none'); markdownElement.style.display = ''; markdownElement.innerHTML = 'Loading…'; - $.ajax({ - url: url, - method: 'PUT', - data: $(form).serialize(), - success: () => { - $.ajax({ - url: url, - method: 'GET', - success: response => { + fetchWithCSRF(url, {method: 'PUT', body: new FormData(form)}) + .then(response => { + if (!response.ok) throw `Server returned ${response.status}: ${response.statusText}`; + // get rendered markdown + fetch(url) + .then(response => { + if (!response.ok) throw `Server returned ${response.status}: ${response.statusText}`; + return response.json(); + }) + .then(comment => { const commentId = headingElement.querySelector('.comment-anchor').href.split('#comment-')[1]; - headingElement.replaceWith(renderCommentHeading(response, commentId)); - textElement.value = response.text; - markdownElement.innerHTML = response.renderedMarkdown; + headingElement.replaceWith(renderCommentHeading(comment, commentId)); + textElement.value = comment.text; + markdownElement.innerHTML = comment.renderedMarkdown; hideCommentEditor(form); - }, - error: () => location.reload() - }); - }, - error: (jqXHR, textStatus, errorThrown) => { - textElement.value = text; - markdownElement.innerHTML = markdown; - showCommentEditor(form); - window.alert("The comment couldn't be updated: " + getXhrError(jqXHR, textStatus, errorThrown)); - } - }); + }) + .catch(error => { + console.error(error); + location.reload(); + }); + }) + .catch(error => { + window.alert(`The comment couldn't be updated : ${error}`); + }); } function addComment(form, insertAtBottom) { @@ -109,22 +104,26 @@ function addComment(form, insertAtBottom) { return window.alert("The comment text mustn't be empty."); } const url = form.action; - $.ajax({ - url: url, - method: 'POST', - data: $(form).serialize(), - success: response => { - const commentId = response.id; + fetch(url, {method: 'POST', body: new FormData(form)}) + .then(response => { + if (!response.ok) throw `Server returned ${response.status}: ${response.statusText}`; + return response.json(); + }) + .then(data => { + const commentId = data.id; + console.log(`Created comment #${commentId}`); // get rendered markdown - $.ajax({ - url: url + '/' + commentId, - method: 'GET', - success: response => { + fetch(`${url}/${commentId}`) + .then(response => { + if (!response.ok) throw `Server returned ${response.status}: ${response.statusText}`; + return response.json(); + }) + .then(comment => { const templateElement = document.getElementById('comment-row-template'); const commentRow = $(templateElement.innerHTML.replace(/@comment_id@/g, commentId))[0]; - commentRow.querySelector('[name="text"]').value = response.text; - commentRow.querySelector('h4').replaceWith(renderCommentHeading(response, commentId)); - commentRow.querySelector('.markdown').innerHTML = response.renderedMarkdown; + commentRow.querySelector('[name="text"]').value = comment.text; + commentRow.querySelector('h4').replaceWith(renderCommentHeading(comment, commentId)); + commentRow.querySelector('.markdown').innerHTML = comment.renderedMarkdown; let nextElement; if (!insertAtBottom) { nextElement = document.querySelectorAll('.comment-row')[0]; @@ -136,12 +135,15 @@ function addComment(form, insertAtBottom) { $('html, body').animate({scrollTop: commentRow.offsetTop}, 1000); textElement.value = ''; updateNumerOfComments(); - }, - error: () => location.reload() - }); - }, - error: showXhrError.bind(undefined, "The comment couldn't be added: ") - }); + }) + .catch(error => { + console.error(error); + location.reload(); + }); + }) + .catch(error => { + window.alert(`The comment couldn't be added: ${error}`); + }); } function insertTemplate(button) { diff --git a/assets/javascripts/index.js b/assets/javascripts/index.js index ec475518c0d5..2526c423d264 100644 --- a/assets/javascripts/index.js +++ b/assets/javascripts/index.js @@ -93,28 +93,25 @@ function loadBuildResults(queryParams) { }; // query build results via AJAX using parameters from filter form - $.ajax({ - url: buildResultsElement.data('build-results-url'), - data: queryParams ? queryParams : window.location.search.substr(1), - success: function (response) { - showBuildResults(response); + var url = new URL(buildResultsElement.data('build-results-url'), window.location.href); + url.search = queryParams ? queryParams : window.location.search.substr(1); + fetch(url) + .then(response => { + if (!response.ok) throw `Server returned ${response.status}: ${response.statusText}`; + return response.text(); + }) + .then(responsetext => { + showBuildResults(responsetext); window.buildResultStatus = 'success'; - }, - error: function (xhr, textStatus, thrownError) { - // ignore error if just navigating away - if (textStatus !== 'timeout' && !xhr.getAllResponseHeaders()) { - return; - } - const error = xhr.responseJSON?.error; + }) + .catch(error => { const message = error ? htmlEscape(error) : 'Unable to fetch build results.'; showBuildResults( '' ); - window.buildResultStatus = 'error: ' + thrownError; - } - }); + }); } function autoRefreshRestart() { diff --git a/assets/javascripts/job_templates.js b/assets/javascripts/job_templates.js index 362a2f2b5027..92ca367c6ef0 100644 --- a/assets/javascripts/job_templates.js +++ b/assets/javascripts/job_templates.js @@ -421,19 +421,35 @@ function submitTemplateEditor(button) { editor.setValue(template, -1); } - $.ajax({ - url: form.data('put-url'), - type: 'POST', - dataType: 'json', - data: { + var data = fetchWithCSRF(form.data('put-url'), { + method: 'POST', + headers: {Accept: 'application/json'}, + body: new URLSearchParams({ schema: 'JobTemplates-01.yaml', preview: button !== 'save' ? 1 : 0, expand: button === 'expand' ? 1 : 0, template: template, reference: form.data('reference') - } + }) }) - .done(function (data) { + .then(response => { + return response.json(); + }) + .then(data => { + // handle errors with YAML syntax + if (Object.prototype.hasOwnProperty.call(data, 'error')) { + result.text('There was a problem applying the changes:'); + var errors = data.error; + var list = $('