From 68bc207e651670016a7e423cac732cb827b53dcf 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/comments.js | 108 +++++++++++++++-------------- assets/javascripts/index.js | 27 ++++---- assets/javascripts/openqa.js | 32 ++++++--- assets/javascripts/overview.js | 17 ++--- assets/javascripts/test_result.js | 51 +++++++++----- assets/javascripts/tests.js | 17 +++-- 7 files changed, 162 insertions(+), 139 deletions(-) diff --git a/assets/javascripts/admin_assets.js b/assets/javascripts/admin_assets.js index cd60962e672..8a0c7c42f7c 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/comments.js b/assets/javascripts/comments.js index 8121045f8f3..ea7ef40d1f2 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 ec475518c0d..2526c423d26 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/openqa.js b/assets/javascripts/openqa.js index dd4de2be52c..4d686a425ad 100644 --- a/assets/javascripts/openqa.js +++ b/assets/javascripts/openqa.js @@ -23,6 +23,16 @@ function setupForAll() { }); } +function getCSRFToken() { + return document.querySelector('meta[name="csrf-token"]').content; +} + +function fetchWithCSRF(resource, options) { + options.headers ??= {}; + options.headers['X-CSRF-TOKEN'] ??= getCSRFToken(); + return fetch(resource, options); +} + function makeFlashElement(text) { return typeof text === 'string' ? '' + text + '' : text; } @@ -238,11 +248,12 @@ function restartJob(ajaxUrl, jobId) { addFlash('danger', errorMessage); }; - return $.ajax({ - type: 'POST', - url: ajaxUrl, - success: function (data, res, xhr) { - var responseJSON = xhr.responseJSON; + return fetchWithCSRF(ajaxUrl, {method: 'POST'}) + .then(response => { + if (!response.ok) throw `Server returned ${response.status}: ${response.statusText}`; + return response.json(); + }) + .then(responseJSON => { var newJobUrl; try { newJobUrl = responseJSON.test_url[0][jobId]; @@ -261,13 +272,12 @@ function restartJob(ajaxUrl, jobId) { if (newJobUrl) { window.location.replace(newJobUrl); } else { - showError('URL for new job not available'); + throw 'URL for new job not available'; } - }, - error: function (xhr, ajaxOptions, thrownError) { - showError(xhr.responseJSON ? xhr.responseJSON.error : undefined); - } - }); + }) + .catch(error => { + showError(error); + }); } function htmlEscape(str) { diff --git a/assets/javascripts/overview.js b/assets/javascripts/overview.js index 0e64463ab41..003f65f6cbc 100644 --- a/assets/javascripts/overview.js +++ b/assets/javascripts/overview.js @@ -301,20 +301,17 @@ function addComments(form) { controls.style.display = 'inline'; window.addCommentsModal.hide(); }; - $.ajax({ - url: form.action, - method: 'POST', - data: $(form).serialize(), - success: response => { + fetchWithCSRF(form.action, {method: 'POST', body: new FormData(form)}) + .then(response => { + if (!response.ok) throw `Server returned ${response.status}: ${response.statusText}`; addFlash( 'info', 'The comments have been created. Reload the page to show changes.' ); done(); - }, - error: (jqXHR, textStatus, errorThrown) => { - addFlash('danger', 'The comments could not be added: ' + getXhrError(jqXHR, textStatus, errorThrown)); + }) + .catch(error => { + addFlash('danger', `The comments could not be added: ${error}`); done(); - } - }); + }); } diff --git a/assets/javascripts/test_result.js b/assets/javascripts/test_result.js index 4d7e79fd335..db67bb979f3 100644 --- a/assets/javascripts/test_result.js +++ b/assets/javascripts/test_result.js @@ -497,27 +497,30 @@ function loadTabPanelElement(tabName, tabConfig) { return false; } tabConfig.panelElement = tabPanelElement; // for easier access in custom renderers - $.ajax({ - url: ajaxUrl, - method: 'GET', - success: function (response) { + fetch(ajaxUrl, {method: 'GET'}) + .then(response => { + if (!response.ok) throw `Server returned ${response.status}: ${response.statusText}`; + if (response.headers.get('Content-Type').includes('application/json')) return response.json(); + return response.text(); + }) + .then(response => { const customRenderer = tabConfig.renderContents; if (customRenderer) { return customRenderer.call(tabConfig, response); } tabPanelElement.innerHTML = response; - }, - error: function (xhr, ajaxOptions, thrownError) { + }) + .catch(error => { + console.error(error); const customRenderer = tabConfig.renderError; if (customRenderer) { - return customRenderer.call(tabConfig, xhr, ajaxOptions, thrownError); + return customRenderer.call(tabConfig, error); } tabPanelElement.innerHTML = ''; tabPanelElement.appendChild( - document.createTextNode('Unable to load ' + (tabConfig.descriptiveName || tabName) + '.') + document.createTextNode('Unable to load ' + (tabConfig.descriptiveName || tabName) + `: ${error}`) ); - } - }); + }); tabPanelElement.innerHTML = '

Loading ' + (tabConfig.descriptiveName || tabName) + @@ -707,14 +710,19 @@ function loadEmbeddedLogFiles(filter) { if (logFileElement.dataset.contentsLoaded) { return; } - $.ajax(logFileElement.dataset.src) - .done(function (response) { + fetch(logFileElement.dataset.src) + .then(response => { + if (!response.ok) throw `Server returned ${response.status}: ${response.statusText}`; + return response.text(); + }) + .then(response => { const lines = (logFileElement.content = response.split(/\r?\n/)); filter ? filter() : showLogLines(logFileElement, lines, false); logFileElement.dataset.contentsLoaded = true; }) - .fail(function (jqXHR, textStatus, errorThrown) { - logFileElement.appendChild(document.createTextNode('Unable to load logfile: ' + errorThrown)); + .catch(error => { + log.error(error); + logFileElement.appendChild(document.createTextNode(`Unable to load logfile: ${error}`)); }); }); } @@ -922,20 +930,25 @@ function renderCommentsTab(response) { } const id = found[1]; const url = urlWithBase('/api/v1/experimental/jobs/' + id + '/status'); - $.ajax(url) - .done(function (response) { + fetch(url) + .then(response => { + if (!response.ok) throw `Server returned ${response.status}: ${response.statusText}`; + return response.text(); + }) + .then(comments => { const span = document.createElement('span'); span.className = 'openqa-testref'; const i = document.createElement('i'); - const job = response; - const stateHTML = testStateHTML(job); + const stateHTML = testStateHTML(comments); i.className = stateHTML[0]; span.title = stateHTML[1]; span.appendChild(i); element.parentNode.replaceChild(span, element); span.appendChild(element); }) - .fail(function (jqXHR, textStatus, errorThrown) {}); + .catch(error => { + console.error(error); + }); }); } diff --git a/assets/javascripts/tests.js b/assets/javascripts/tests.js index 72457253315..6591562fc11 100644 --- a/assets/javascripts/tests.js +++ b/assets/javascripts/tests.js @@ -191,16 +191,15 @@ function changeJobPrio(jobId, delta, linkElement) { } var newPrio = currentPrio + delta; - $.ajax({ - url: urlWithBase('/api/v1/jobs/' + jobId + '/prio?prio=' + newPrio), - method: 'POST', - success: function (result) { + + fetchWithCSRF(urlWithBase(`/api/v1/jobs/${jobId}/prio?prio=${newPrio}`), {method: 'POST'}) + .then(response => { + if (!response.ok) throw `Server returned ${response.status}: ${response.statusText}`; prioValueElement.text(newPrio); - }, - error: function (xhr, ajaxOptions, thrownError) { - addFlash('danger', 'Unable to set the priority value of job ' + jobId + '.'); - } - }); + }) + .catch(error => { + addFlash('danger', `Unable to set the priority value of job ${jobId}: ${error}`); + }); } function renderTestSummary(data) {