Page Menu
Home
Software Heritage
Search
Configure Global Search
Log In
Files
F8394112
D3358.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
23 KB
Subscribers
None
D3358.diff
View Options
diff --git a/cypress/fixtures/save-task-info.json b/cypress/fixtures/save-task-info.json
new file mode 100644
--- /dev/null
+++ b/cypress/fixtures/save-task-info.json
@@ -0,0 +1,16 @@
+{
+ "scheduled": "2020-06-24T11:48:12.561643+00:00",
+ "started": "2020-06-24T12:59:11.103188+00:00",
+ "ended": "2020-06-24T12:59:12.065313+00:00",
+ "status": "eventful",
+ "type": "load-git",
+ "arguments": {
+ "args": [],
+ "kwargs": {
+ "url": "https://gitlab.inria.fr/solverstack/maphys/maphys/"
+ }
+ },
+ "duration": "1.0600971020758152",
+ "message": "[2020-06-24 12:59:12,063: INFO/ForkPoolWorker-161] Task swh.loader.git.tasks.UpdateGitRepository[4ff8b555-9535-4e75-b8ec-8e76165e14ec] succeeded in 1.0600971020758152s: {'status': 'eventful'}",
+ "name": "swh.loader.git.tasks.UpdateGitRepository"
+}
\ No newline at end of file
diff --git a/cypress/integration/origin-save.spec.js b/cypress/integration/origin-save.spec.js
--- a/cypress/integration/origin-save.spec.js
+++ b/cypress/integration/origin-save.spec.js
@@ -159,34 +159,76 @@
});
it('should display origin save info in the requests table', function() {
- cy.fixture('origin-save').then(originSaveJSON => {
- cy.route('GET', '/save/requests/list/**', originSaveJSON);
- cy.get('#swh-origin-save-requests-list-tab').click();
- cy.get('tbody tr').then(rows => {
- let i = 0;
- for (let row of rows) {
- const cells = row.cells;
- const requestDateStr = new Date(originSaveJSON.data[i].save_request_date).toLocaleString();
- const saveStatus = originSaveJSON.data[i].save_task_status;
- assert.equal($(cells[0]).text(), requestDateStr);
- assert.equal($(cells[1]).text(), originSaveJSON.data[i].visit_type);
- let html = '';
- if (saveStatus === 'succeed') {
- let browseOriginUrl = `${this.Urls.browse_origin()}?origin_url=${originSaveJSON.data[i].origin_url}`;
- browseOriginUrl += `&timestamp=${originSaveJSON.data[i].visit_date}`;
- html += `<a href="${browseOriginUrl}">${originSaveJSON.data[i].origin_url}</a>`;
- } else {
- html += originSaveJSON.data[i].origin_url;
- }
- html += ` <a href="${originSaveJSON.data[i].origin_url}">`;
- html += '<i class="mdi mdi-open-in-new" aria-hidden="true"></i></a>';
- assert.equal($(cells[2]).html(), html);
- assert.equal($(cells[3]).text(), originSaveJSON.data[i].save_request_status);
- assert.equal($(cells[4]).text(), saveStatus);
- ++i;
+ cy.fixture('origin-save').as('originSaveJSON');
+ cy.route('GET', '/save/requests/list/**', '@originSaveJSON');
+ cy.get('#swh-origin-save-requests-list-tab').click();
+ cy.get('tbody tr').then(rows => {
+ let i = 0;
+ for (let row of rows) {
+ const cells = row.cells;
+ const requestDateStr = new Date(this.originSaveJSON.data[i].save_request_date).toLocaleString();
+ const saveStatus = this.originSaveJSON.data[i].save_task_status;
+ assert.equal($(cells[0]).text(), requestDateStr);
+ assert.equal($(cells[1]).text(), this.originSaveJSON.data[i].visit_type);
+ let html = '';
+ if (saveStatus === 'succeed') {
+ let browseOriginUrl = `${this.Urls.browse_origin()}?origin_url=${this.originSaveJSON.data[i].origin_url}`;
+ browseOriginUrl += `&timestamp=${this.originSaveJSON.data[i].visit_date}`;
+ html += `<a href="${browseOriginUrl}">${this.originSaveJSON.data[i].origin_url}</a>`;
+ } else {
+ html += this.originSaveJSON.data[i].origin_url;
}
- });
+ html += ` <a href="${this.originSaveJSON.data[i].origin_url}">`;
+ html += '<i class="mdi mdi-open-in-new" aria-hidden="true"></i></a>';
+ assert.equal($(cells[2]).html(), html);
+ assert.equal($(cells[3]).text(), this.originSaveJSON.data[i].save_request_status);
+ assert.equal($(cells[4]).text(), saveStatus);
+ ++i;
+ }
});
});
+ it('should display/close task info popover when clicking on the info button', function() {
+ cy.fixture('origin-save').as('originSaveJSON');
+ cy.fixture('save-task-info').as('saveTaskInfoJSON');
+ cy.route('GET', '/save/requests/list/**', '@originSaveJSON');
+ cy.route('GET', '/save/task/info/**', '@saveTaskInfoJSON');
+
+ cy.get('#swh-origin-save-requests-list-tab').click();
+ cy.get('.swh-save-request-info')
+ .eq(0)
+ .click();
+
+ cy.get('.swh-save-request-info-popover')
+ .should('be.visible');
+
+ cy.get('.swh-save-request-info')
+ .eq(0)
+ .click();
+
+ cy.get('.swh-save-request-info-popover')
+ .should('not.be.visible');
+ });
+
+ it('should hide task info popover when clicking on the close button', function() {
+ cy.fixture('origin-save').as('originSaveJSON');
+ cy.fixture('save-task-info').as('saveTaskInfoJSON');
+ cy.route('GET', '/save/requests/list/**', '@originSaveJSON');
+ cy.route('GET', '/save/task/info/**', '@saveTaskInfoJSON');
+
+ cy.get('#swh-origin-save-requests-list-tab').click();
+ cy.get('.swh-save-request-info')
+ .eq(0)
+ .click();
+
+ cy.get('.swh-save-request-info-popover')
+ .should('be.visible');
+
+ cy.get('.swh-save-request-info-close')
+ .click();
+
+ cy.get('.swh-save-request-info-popover')
+ .should('not.be.visible');
+ });
+
});
diff --git a/swh/web/admin/origin_save.py b/swh/web/admin/origin_save.py
--- a/swh/web/admin/origin_save.py
+++ b/swh/web/admin/origin_save.py
@@ -23,7 +23,6 @@
from swh.web.common.origin_save import (
create_save_origin_request,
- get_save_origin_task_info,
SAVE_REQUEST_PENDING,
SAVE_REQUEST_REJECTED,
)
@@ -214,16 +213,3 @@
entry.delete()
status_code = 200
return HttpResponse(status=status_code)
-
-
-@admin_route(
- r"origin/save/task/info/(?P<save_request_id>.+)/",
- view_name="admin-origin-save-task-info",
-)
-@staff_member_required(view_func=None, login_url=settings.LOGIN_URL)
-def _save_origin_task_info(request, save_request_id):
- request_info = get_save_origin_task_info(save_request_id)
- for date_field in ("scheduled", "started", "ended"):
- if date_field in request_info and request_info[date_field] is not None:
- request_info[date_field] = request_info[date_field].isoformat()
- return HttpResponse(json.dumps(request_info), content_type="application/json")
diff --git a/swh/web/assets/src/bundles/admin/origin-save.js b/swh/web/assets/src/bundles/admin/origin-save.js
--- a/swh/web/assets/src/bundles/admin/origin-save.js
+++ b/swh/web/assets/src/bundles/admin/origin-save.js
@@ -150,7 +150,7 @@
render: (data, type, row) => {
if (row.save_task_status === 'succeed' || row.save_task_status === 'failed') {
return '<i class="mdi mdi-information-outline swh-save-request-info" aria-hidden="true" style="cursor: pointer"' +
- `onclick="swh.admin.displaySaveRequestInfo(event, ${row.id})"></i>`;
+ `onclick="swh.save.displaySaveRequestInfo(event, ${row.id})"></i>`;
} else {
return '';
}
@@ -359,93 +359,3 @@
export function removeRejectedOriginSaveRequest() {
removeOriginSaveRequest(rejectedSaveRequestsTable);
}
-
-export function displaySaveRequestInfo(event, saveRequestId) {
- event.stopPropagation();
- const saveRequestTaskInfoUrl = Urls.admin_origin_save_task_info(saveRequestId);
- $('.swh-save-request-info').popover('dispose');
- $(event.target).popover({
- 'title': 'Save request task information',
- 'content': `<div class="swh-popover">
- <div class="text-center">
- <img src=${swhSpinnerSrc}></img>
- <p>Fetching task information ...</p>
- </div>
- </div>`,
- 'html': true,
- 'placement': 'left',
- 'sanitizeFn': swh.webapp.filterXSS
- });
- $(event.target).popover('show');
- fetch(saveRequestTaskInfoUrl)
- .then(response => response.json())
- .then(saveRequestTaskInfo => {
- let content;
- if ($.isEmptyObject(saveRequestTaskInfo)) {
- content = 'Not available';
- } else {
- let saveRequestInfo = [];
- saveRequestInfo.push({
- key: 'Task type',
- value: saveRequestTaskInfo.type
- });
- if (saveRequestTaskInfo.hasOwnProperty('task_name')) {
- saveRequestInfo.push({
- key: 'Task name',
- value: saveRequestTaskInfo.name
- });
- }
- saveRequestInfo.push({
- key: 'Task arguments',
- value: JSON.stringify(saveRequestTaskInfo.arguments, null, 2)
- });
- saveRequestInfo.push({
- key: 'Task id',
- value: saveRequestTaskInfo.id
- });
- saveRequestInfo.push({
- key: 'Task backend id',
- value: saveRequestTaskInfo.backend_id
- });
- saveRequestInfo.push({
- key: 'Task scheduling date',
- value: new Date(saveRequestTaskInfo.scheduled).toLocaleString()
- });
- saveRequestInfo.push({
- key: 'Task termination date',
- value: new Date(saveRequestTaskInfo.ended).toLocaleString()
- });
- if (saveRequestTaskInfo.hasOwnProperty('duration')) {
- saveRequestInfo.push({
- key: 'Task duration',
- value: saveRequestTaskInfo.duration + ' s'
- });
- }
- if (saveRequestTaskInfo.hasOwnProperty('worker')) {
- saveRequestInfo.push({
- key: 'Task executor',
- value: saveRequestTaskInfo.worker
- });
- }
- if (saveRequestTaskInfo.hasOwnProperty('message')) {
- saveRequestInfo.push({
- key: 'Task log',
- value: saveRequestTaskInfo.message
- });
- }
- content = '<table class="table"><tbody>';
- for (let info of saveRequestInfo) {
- content +=
- `<tr>
- <th class="swh-metadata-table-row swh-metadata-table-key">${info.key}</th>
- <td class="swh-metadata-table-row swh-metadata-table-value">
- <pre>${info.value}</pre>
- </td>
- </tr>`;
- }
- content += '</tbody></table>';
- }
- $('.swh-popover').html(content);
- $(event.target).popover('update');
- });
-}
diff --git a/swh/web/assets/src/bundles/save/index.js b/swh/web/assets/src/bundles/save/index.js
--- a/swh/web/assets/src/bundles/save/index.js
+++ b/swh/web/assets/src/bundles/save/index.js
@@ -108,6 +108,18 @@
{
data: 'save_task_status',
name: 'loading_task_status'
+ },
+ {
+ name: 'info',
+ render: (data, type, row) => {
+ if (row.save_task_status === 'succeed' || row.save_task_status === 'failed') {
+ return `<i class="mdi mdi-information-outline swh-save-request-info" ` +
+ 'aria-hidden="true" style="cursor: pointer"' +
+ `onclick="swh.save.displaySaveRequestInfo(event, ${row.id})"></i>`;
+ } else {
+ return '';
+ }
+ }
}
],
scrollY: '50vh',
@@ -127,8 +139,9 @@
window.location.hash = '#requests';
});
- $('#swh-origin-save-request-create-tab').on('shown.bs.tab', () => {
+ $('#swh-origin-save-request-help-tab').on('shown.bs.tab', () => {
removeUrlFragment();
+ $('.swh-save-request-info').popover('dispose');
});
let saveRequestAcceptedAlert = htmlAlert(
@@ -298,3 +311,123 @@
});
});
}
+
+export function displaySaveRequestInfo(event, saveRequestId) {
+ event.stopPropagation();
+ const saveRequestTaskInfoUrl = Urls.origin_save_task_info(saveRequestId);
+ // close popover when clicking again on the info icon
+ if ($(event.target).data('bs.popover')) {
+ $(event.target).popover('dispose');
+ return;
+ }
+ $('.swh-save-request-info').popover('dispose');
+ $(event.target).popover({
+ animation: false,
+ boundary: 'viewport',
+ container: 'body',
+ title: 'Save request task information ' +
+ '<i style="cursor: pointer; position: absolute; right: 1rem;" ' +
+ `class="mdi mdi-close swh-save-request-info-close"></i>`,
+ content: `<div class="swh-popover swh-save-request-info-popover">
+ <div class="text-center">
+ <img src=${swhSpinnerSrc}></img>
+ <p>Fetching task information ...</p>
+ </div>
+ </div>`,
+ html: true,
+ placement: 'left',
+ sanitizeFn: swh.webapp.filterXSS
+ });
+
+ $(event.target).on('shown.bs.popover', function() {
+ const popoverId = $(this).attr('aria-describedby');
+ $(`#${popoverId} .mdi-close`).click(() => {
+ $(this).popover('dispose');
+ });
+ });
+
+ $(event.target).popover('show');
+ fetch(saveRequestTaskInfoUrl)
+ .then(response => response.json())
+ .then(saveRequestTaskInfo => {
+ let content;
+ if ($.isEmptyObject(saveRequestTaskInfo)) {
+ content = 'Not available';
+ } else {
+ let saveRequestInfo = [];
+ if (saveRequestTaskInfo.type) {
+ saveRequestInfo.push({
+ key: 'Task type',
+ value: saveRequestTaskInfo.type
+ });
+ }
+ if (saveRequestTaskInfo.arguments) {
+ saveRequestInfo.push({
+ key: 'Task arguments',
+ value: JSON.stringify(saveRequestTaskInfo.arguments, null, 2)
+ });
+ }
+ if (saveRequestTaskInfo.id) {
+ saveRequestInfo.push({
+ key: 'Task id',
+ value: saveRequestTaskInfo.id
+ });
+ }
+ if (saveRequestTaskInfo.backend_id) {
+ saveRequestInfo.push({
+ key: 'Task backend id',
+ value: saveRequestTaskInfo.backend_id
+ });
+ }
+ if (saveRequestTaskInfo.scheduled) {
+ saveRequestInfo.push({
+ key: 'Task scheduling date',
+ value: new Date(saveRequestTaskInfo.scheduled).toLocaleString()
+ });
+ }
+ if (saveRequestTaskInfo.started) {
+ saveRequestInfo.push({
+ key: 'Task start date',
+ value: new Date(saveRequestTaskInfo.started).toLocaleString()
+ });
+ }
+ if (saveRequestTaskInfo.ended) {
+ saveRequestInfo.push({
+ key: 'Task termination date',
+ value: new Date(saveRequestTaskInfo.ended).toLocaleString()
+ });
+ }
+ if (saveRequestTaskInfo.duration) {
+ saveRequestInfo.push({
+ key: 'Task duration',
+ value: saveRequestTaskInfo.duration + ' seconds'
+ });
+ }
+ if (saveRequestTaskInfo.worker) {
+ saveRequestInfo.push({
+ key: 'Task executor',
+ value: saveRequestTaskInfo.worker
+ });
+ }
+ if (saveRequestTaskInfo.message) {
+ saveRequestInfo.push({
+ key: 'Task log',
+ value: saveRequestTaskInfo.message
+ });
+ }
+ content = '<table class="table"><tbody>';
+ for (let info of saveRequestInfo) {
+ content +=
+ `<tr>
+ <th class="swh-metadata-table-row swh-metadata-table-key">${info.key}</th>
+ <td class="swh-metadata-table-row swh-metadata-table-value">
+ <pre>${info.value}</pre>
+ </td>
+ </tr>`;
+ }
+ content += '</tbody></table>';
+ }
+ $('.swh-popover').html(content);
+ $(event.target).popover('update');
+ });
+}
diff --git a/swh/web/common/origin_save.py b/swh/web/common/origin_save.py
--- a/swh/web/common/origin_save.py
+++ b/swh/web/common/origin_save.py
@@ -8,6 +8,7 @@
from itertools import product
import json
import logging
+from typing import Any, Dict
from django.core.exceptions import ObjectDoesNotExist
from django.core.exceptions import ValidationError
@@ -424,7 +425,9 @@
return get_save_origin_requests_from_queryset(sors)
-def get_save_origin_task_info(save_request_id):
+def get_save_origin_task_info(
+ save_request_id: int, full_info: bool = True
+) -> Dict[str, Any]:
"""
Get detailed information about an accepted save origin request
and its associated loading task.
@@ -433,10 +436,11 @@
from the scheduler database, returns an empty dictionary.
Args:
- save_request_id (int): identifier of a save origin request
+ save_request_id: identifier of a save origin request
+ full_info: whether to return detailed info for staff users
Returns:
- dict: A dictionary with the following keys:
+ A dictionary with the following keys:
- **type**: loading task type
- **arguments**: loading task arguments
@@ -474,7 +478,6 @@
task_run["id"] = task_run["task"]
del task_run["task"]
del task_run["metadata"]
- del task_run["started"]
es_workers_index_url = config.get_config()["es_workers_index_url"]
if not es_workers_index_url:
@@ -487,8 +490,8 @@
else:
min_ts = save_request.request_date
max_ts = min_ts + timedelta(days=30)
- min_ts = int(min_ts.timestamp()) * 1000
- max_ts = int(max_ts.timestamp()) * 1000
+ min_ts_unix = int(min_ts.timestamp()) * 1000
+ max_ts_unix = int(max_ts.timestamp()) * 1000
save_task_status = _save_task_status[task["status"]]
priority = "3" if save_task_status == SAVE_TASK_FAILED else "6"
@@ -501,8 +504,8 @@
{
"range": {
"@timestamp": {
- "gte": min_ts,
- "lte": max_ts,
+ "gte": min_ts_unix,
+ "lte": max_ts_unix,
"format": "epoch_millis",
}
}
@@ -537,6 +540,21 @@
logger.warning("Request to Elasticsearch failed\n%s", exc)
sentry_sdk.capture_exception(exc)
+ if not full_info:
+ for field in ("id", "backend_id", "worker"):
+ # remove some staff only fields
+ task_run.pop(field, None)
+ if "message" in task_run and "Loading failure" in task_run["message"]:
+ # hide traceback for non staff users, only display exception
+ message_lines = task_run["message"].split("\n")
+ message = ""
+ for line in message_lines:
+ if line.startswith("Traceback"):
+ break
+ message += f"{line}\n"
+ message += message_lines[-1]
+ task_run["message"] = message
+
return task_run
diff --git a/swh/web/misc/origin_save.py b/swh/web/misc/origin_save.py
--- a/swh/web/misc/origin_save.py
+++ b/swh/web/misc/origin_save.py
@@ -19,6 +19,7 @@
create_save_origin_request,
get_savable_visit_types,
get_save_origin_requests_from_queryset,
+ get_save_origin_task_info,
)
from swh.web.common.utils import EnforceCSRFAuthentication
@@ -101,6 +102,16 @@
return HttpResponse(table_data_json, content_type="application/json")
+def _save_origin_task_info(request, save_request_id):
+ request_info = get_save_origin_task_info(
+ save_request_id, full_info=request.user.is_staff
+ )
+ for date_field in ("scheduled", "started", "ended"):
+ if date_field in request_info and request_info[date_field] is not None:
+ request_info[date_field] = request_info[date_field].isoformat()
+ return HttpResponse(json.dumps(request_info), content_type="application/json")
+
+
urlpatterns = [
url(r"^save/$", _origin_save_view, name="origin-save"),
url(
@@ -114,4 +125,9 @@
_origin_save_requests_list,
name="origin-save-requests-list",
),
+ url(
+ r"^save/task/info/(?P<save_request_id>.+)/",
+ _save_origin_task_info,
+ name="origin-save-task-info",
+ ),
]
diff --git a/swh/web/templates/misc/origin-save.html b/swh/web/templates/misc/origin-save.html
--- a/swh/web/templates/misc/origin-save.html
+++ b/swh/web/templates/misc/origin-save.html
@@ -59,7 +59,7 @@
</div>
<ul class="nav nav-tabs" style="padding-left: 5px;">
- <li class="nav-item"><a class="nav-link active" data-toggle="tab" id="swh-origin-save-request-create-tab" href="#swh-origin-save-requests-create">Help</a></li>
+ <li class="nav-item"><a class="nav-link active" data-toggle="tab" id="swh-origin-save-request-help-tab" href="#swh-origin-save-requests-create">Help</a></li>
<li class="nav-item"><a class="nav-link" data-toggle="tab" id="swh-origin-save-requests-list-tab" href="#swh-origin-save-requests-list">Browse save requests</a></li>
</ul>
@@ -109,6 +109,7 @@
<th data-priority="1">Url</th>
<th data-priority="5">Request</th>
<th data-priority="2">Status</th>
+ <th data-priority="6">Info</th>
</tr>
</thead>
</table>
diff --git a/swh/web/tests/common/test_origin_save.py b/swh/web/tests/common/test_origin_save.py
--- a/swh/web/tests/common/test_origin_save.py
+++ b/swh/web/tests/common/test_origin_save.py
@@ -46,10 +46,15 @@
@pytest.mark.django_db
-def test_get_save_origin_task_info_with_es(mocker):
+def test_get_save_origin_task_full_info_with_es(mocker):
_get_save_origin_task_info_test(mocker, es_available=True)
+@pytest.mark.django_db
+def test_get_save_origin_task_info_with_es(mocker):
+ _get_save_origin_task_info_test(mocker, es_available=True, full_info=False)
+
+
@pytest.mark.django_db
def test_get_save_origin_task_info_without_es(mocker):
_get_save_origin_task_info_test(mocker, es_available=False)
@@ -72,7 +77,7 @@
if not task_archived
else None
)
- mock_scheduler.get_tasks.return_value = [task]
+ mock_scheduler.get_tasks.return_value = [dict(task) if task else None]
task_run = {
"backend_id": "f00c712c-e820-41ce-a07c-9bf8df914205",
@@ -84,11 +89,13 @@
"status": task_status,
"task": _task_id,
}
- mock_scheduler.get_task_runs.return_value = [task_run]
+ mock_scheduler.get_task_runs.return_value = [dict(task_run)]
return task, task_run
-def _get_save_origin_task_info_test(mocker, task_archived=False, es_available=True):
+def _get_save_origin_task_info_test(
+ mocker, task_archived=False, es_available=True, full_info=True
+):
swh_web_config = get_config()
if es_available:
@@ -111,7 +118,7 @@
task_exec_data = es_response["hits"]["hits"][-1]["_source"]
- sor_task_info = get_save_origin_task_info(sor.id)
+ sor_task_info = get_save_origin_task_info(sor.id, full_info=full_info)
expected_result = (
{
@@ -120,6 +127,7 @@
"id": task["id"],
"backend_id": task_run["backend_id"],
"scheduled": task_run["scheduled"],
+ "started": task_run["started"],
"ended": task_run["ended"],
"status": task_run["status"],
}
@@ -136,6 +144,20 @@
}
)
+ if not full_info:
+ expected_result.pop("id", None)
+ expected_result.pop("backend_id", None)
+ expected_result.pop("worker", None)
+ if "message" in expected_result:
+ message = ""
+ message_lines = expected_result["message"].split("\n")
+ for line in message_lines:
+ if line.startswith("Traceback"):
+ break
+ message += f"{line}\n"
+ message += message_lines[-1]
+ expected_result["message"] = message
+
assert sor_task_info == expected_result
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Jun 3 2025, 7:21 PM (10 w, 4 h ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
3216217
Attached To
D3358: templates/origin-save: Add a new "Info" column in save requests table
Event Timeline
Log In to Comment