Page MenuHomeSoftware Heritage

D6186.id22498.diff
No OneTemporary

D6186.id22498.diff

diff --git a/assets/src/bundles/browse/swhid-utils.js b/assets/src/bundles/browse/swhid-utils.js
--- a/assets/src/bundles/browse/swhid-utils.js
+++ b/assets/src/bundles/browse/swhid-utils.js
@@ -112,7 +112,7 @@
$('#swh-identifiers').tabSlideOut(tabSlideOptions);
// set the tab visible once the close animation is terminated
- $('#swh-identifiers').css('display', 'block');
+ $('#swh-identifiers').addClass('d-none d-sm-block');
$('.swhid-context-option').trigger('click');
// highlighted code lines changed
diff --git a/assets/src/bundles/webapp/code-highlighting.js b/assets/src/bundles/webapp/code-highlighting.js
--- a/assets/src/bundles/webapp/code-highlighting.js
+++ b/assets/src/bundles/webapp/code-highlighting.js
@@ -1,5 +1,5 @@
/**
- * Copyright (C) 2018-2019 The Software Heritage developers
+ * Copyright (C) 2018-2021 The Software Heritage developers
* See the AUTHORS file at the top-level directory of this distribution
* License: GNU Affero General Public License version 3, or any later version
* See top-level LICENSE file for more information
@@ -22,21 +22,35 @@
return lineTd;
}
+// function to highlight a range of lines
+export function highlightLines(first, last) {
+ if (!first) {
+ return;
+ }
+ if (!last) {
+ last = first;
+ }
+ for (let i = first; i <= last; ++i) {
+ highlightLine(i);
+ }
+}
+
// function to reset highlighting
export function resetHighlightedLines() {
firstHighlightedLine = null;
$('.hljs-ln-line[data-line-number]').css('background-color', 'inherit');
}
-export function scrollToLine(lineDomElt) {
+export function scrollToLine(lineDomElt, offset = 70) {
if ($(lineDomElt).closest('.swh-content').length > 0) {
$('html, body').animate({
- scrollTop: $(lineDomElt).offset().top - 70
+ scrollTop: $(lineDomElt).offset().top - offset
}, 500);
}
}
-export async function highlightCode(showLineNumbers = true, selector = 'code') {
+export async function highlightCode(showLineNumbers = true, selector = 'code',
+ enableLinesSelection = true) {
await import(/* webpackChunkName: "highlightjs" */ 'utils/highlightjs');
@@ -60,9 +74,7 @@
} else if (lines[0] < lines[lines.length - 1]) {
firstHighlightedLine = parseInt(lines[0]);
scrollToLine(highlightLine(lines[0]));
- for (let i = lines[0] + 1; i <= lines[lines.length - 1]; ++i) {
- highlightLine(i);
- }
+ highlightLines(lines[0] + 1, lines[lines.length - 1]);
}
}
@@ -75,7 +87,7 @@
}
});
- if (!showLineNumbers) {
+ if (!showLineNumbers || !enableLinesSelection) {
return;
}
@@ -88,9 +100,7 @@
if (evt.shiftKey && firstHighlightedLine && line > firstHighlightedLine) {
const firstLine = firstHighlightedLine;
resetHighlightedLines();
- for (let i = firstLine; i <= line; ++i) {
- highlightLine(i);
- }
+ highlightLines(firstLine, line);
firstHighlightedLine = firstLine;
window.location.hash = `#L${firstLine}-L${line}`;
} else {
diff --git a/assets/src/bundles/webapp/iframes.js b/assets/src/bundles/webapp/iframes.js
new file mode 100644
--- /dev/null
+++ b/assets/src/bundles/webapp/iframes.js
@@ -0,0 +1,23 @@
+/**
+ * Copyright (C) 2021 The Software Heritage developers
+ * See the AUTHORS file at the top-level directory of this distribution
+ * License: GNU Affero General Public License version 3, or any later version
+ * See top-level LICENSE file for more information
+ */
+
+export function showIframeInfoModal(objectType, objectSWHID) {
+ const html = `
+ <p>
+ You can embed that ${objectType} view in an external website
+ through the use of an iframe. Use the following HTML code
+ to do so.
+ </p>
+ <pre><code class="swh-iframe-html html">&lt;iframe style="width: 100%; height: 500px; border: 1px solid rgba(0, 0, 0, 0.125);"
+ src="${window.location.origin}${Urls.swhid_iframe(objectSWHID.replaceAll('\n', ''))}"&gt;
+&lt;/iframe&gt;</code></pre>
+ <iframe style="width: 100%; height: 500px; border: 1px solid rgba(0, 0, 0, 0.125);"
+ src="${window.location.origin}${Urls.swhid_iframe(objectSWHID.replaceAll('\n', ''))}">
+ </iframe>`;
+ swh.webapp.showModalHtml(`Software Heritage ${objectType} iframe`, html, '1000px');
+ swh.webapp.highlightCode(false, '.swh-iframe-html');
+}
diff --git a/assets/src/bundles/webapp/index.js b/assets/src/bundles/webapp/index.js
--- a/assets/src/bundles/webapp/index.js
+++ b/assets/src/bundles/webapp/index.js
@@ -26,3 +26,4 @@
export * from './sentry';
export * from './math-typesetting';
export * from './status-widget';
+export * from './iframes';
diff --git a/assets/src/bundles/webapp/webapp-utils.js b/assets/src/bundles/webapp/webapp-utils.js
--- a/assets/src/bundles/webapp/webapp-utils.js
+++ b/assets/src/bundles/webapp/webapp-utils.js
@@ -251,9 +251,11 @@
$('#swh-web-modal-confirm').modal('show');
}
-export function showModalHtml(title, html) {
+export function showModalHtml(title, html, width = '500px') {
$('#swh-web-modal-html .modal-title').text(title);
$('#swh-web-modal-html .modal-body').html(html);
+ $('#swh-web-modal-html .modal-dialog').css('max-width', width);
+ $('#swh-web-modal-html .modal-dialog').css('width', width);
$('#swh-web-modal-html').modal('show');
}
diff --git a/assets/src/bundles/webapp/webapp.css b/assets/src/bundles/webapp/webapp.css
--- a/assets/src/bundles/webapp/webapp.css
+++ b/assets/src/bundles/webapp/webapp.css
@@ -662,6 +662,7 @@
}
.swh-badge-html,
+.swh-iframe-html,
.swh-badge-md,
.swh-badge-rst {
white-space: pre-wrap !important;
diff --git a/docs/index.rst b/docs/index.rst
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -15,6 +15,7 @@
uri-scheme-api
uri-scheme-browse
uri-scheme-identifiers
+ uri-scheme-misc
diff --git a/docs/uri-scheme-misc.rst b/docs/uri-scheme-misc.rst
new file mode 100644
--- /dev/null
+++ b/docs/uri-scheme-misc.rst
@@ -0,0 +1,41 @@
+Miscellaneous URLs
+^^^^^^^^^^^^^^^^^^
+
+Iframe view for contents and directories
+----------------------------------------
+
+A subset of Software Heritage objects (contents and directories) can be embedded
+in external websites through the use of iframes. A dedicated endpoint serving
+a minimalist Web UI is available for that use case.
+
+.. http:get:: /embed/(swhid)/
+
+ Endpoint to embed Software Heritage objects in external websites using
+ an iframe.
+
+ :param string swhid: a SoftWare Heritage persistent IDentifier
+ object, or SWHID (see :ref:`persistent-identifiers` to learn more about its syntax)
+
+ :statuscode 200: no error
+ :statuscode 400: the provided identifier is malformed or
+ the object type is not supported by the view
+ :statuscode 404: requested object cannot be found in the archive
+
+ **Example:**
+
+ By adding HTML code similar to the one below in a web page,
+
+ .. code-block:: html
+
+ <iframe style="width: 100%; height: 500px; border: 1px solid rgba(0, 0, 0, 0.125);"
+ src="https://archive.softwareheritage.org/embed/swh:1:cnt:edc043a59197bcebc1d44fb70bf1b84cde3db791;origin=https://github.com/rdicosmo/parmap;visit=swh:1:snp:2d869aa00591d2ac8ec8e7abacdda563d413189d;anchor=swh:1:rev:f140dbc8b05aa3d341c70436a1920a06df9a0ed4;path=/src/parmap.ml">
+ </iframe>
+
+ you will obtain the following rendering.
+
+ .. raw:: html
+
+ <iframe style="width: 100%; height: 500px; border: 1px solid rgba(0, 0, 0, 0.125);"
+ src="https://archive.softwareheritage.org/embed/swh:1:cnt:edc043a59197bcebc1d44fb70bf1b84cde3db791;origin=https://github.com/rdicosmo/parmap;visit=swh:1:snp:2d869aa00591d2ac8ec8e7abacdda563d413189d;anchor=swh:1:rev:f140dbc8b05aa3d341c70436a1920a06df9a0ed4;path=/src/parmap.ml">
+ </iframe>
+
diff --git a/swh/web/common/utils.py b/swh/web/common/utils.py
--- a/swh/web/common/utils.py
+++ b/swh/web/common/utils.py
@@ -285,6 +285,7 @@
),
"swh_web_version": get_distribution("swh.web").version,
"visit_types": ORIGIN_VISIT_TYPES,
+ "iframe_mode": False,
}
diff --git a/swh/web/misc/iframe.py b/swh/web/misc/iframe.py
new file mode 100644
--- /dev/null
+++ b/swh/web/misc/iframe.py
@@ -0,0 +1,310 @@
+# Copyright (C) 2021 The Software Heritage developers
+# See the AUTHORS file at the top-level directory of this distribution
+# License: GNU Affero General Public License version 3, or any later version
+# See top-level LICENSE file for more information
+
+from typing import Any, Dict, List, Optional
+
+from django.conf.urls import url
+from django.shortcuts import render
+from django.views.decorators.clickjacking import xframe_options_exempt
+
+from swh.model.hashutil import hash_to_bytes
+from swh.model.identifiers import (
+ CONTENT,
+ DIRECTORY,
+ REVISION,
+ SNAPSHOT,
+ ObjectType,
+ QualifiedSWHID,
+)
+from swh.web.browse.snapshot_context import get_snapshot_context
+from swh.web.browse.utils import (
+ content_display_max_size,
+ get_directory_entries,
+ prepare_content_for_display,
+ request_content,
+)
+from swh.web.common import archive
+from swh.web.common.exc import BadInputExc, NotFoundExc, http_status_code_message
+from swh.web.common.identifiers import get_swhid, get_swhids_info
+from swh.web.common.typing import SnapshotContext, SWHObjectInfo
+from swh.web.common.utils import gen_path_info, reverse
+
+
+def _get_content_rendering_data(cnt_swhid: QualifiedSWHID, path: str) -> Dict[str, Any]:
+ content_data = request_content(f"sha1_git:{cnt_swhid.object_id.hex()}")
+ content = None
+ language = None
+ mimetype = None
+ if content_data.get("raw_data") is not None:
+ content_display_data = prepare_content_for_display(
+ content_data["raw_data"], content_data["mimetype"], path
+ )
+ content = content_display_data["content_data"]
+ language = content_display_data["language"]
+ mimetype = content_display_data["mimetype"]
+
+ return {
+ "content": content,
+ "content_size": content_data.get("length"),
+ "max_content_size": content_display_max_size,
+ "filename": path.split("/")[-1],
+ "encoding": content_data.get("encoding"),
+ "mimetype": mimetype,
+ "language": language,
+ }
+
+
+def _get_directory_rendering_data(
+ dir_swhid: QualifiedSWHID, focus_swhid: QualifiedSWHID, path: str,
+) -> Dict[str, Any]:
+ dirs, files = get_directory_entries(dir_swhid.object_id.hex())
+ for d in dirs:
+ if d["type"] == "rev":
+ d["url"] = None
+ else:
+ dir_swhid = QualifiedSWHID(
+ object_type=ObjectType.DIRECTORY,
+ object_id=hash_to_bytes(d["target"]),
+ origin=dir_swhid.origin,
+ visit=dir_swhid.visit,
+ anchor=dir_swhid.anchor,
+ path=(path or "/") + d["name"] + "/",
+ )
+ d["url"] = reverse(
+ "swhid-iframe",
+ url_args={"swhid": str(dir_swhid)},
+ query_params={"focus_swhid": str(focus_swhid)},
+ )
+
+ for f in files:
+ object_id = hash_to_bytes(f["target"])
+ cnt_swhid = QualifiedSWHID(
+ object_type=ObjectType.CONTENT,
+ object_id=object_id,
+ origin=dir_swhid.origin,
+ visit=dir_swhid.visit,
+ anchor=dir_swhid.anchor,
+ path=(path or "/") + f["name"],
+ lines=(focus_swhid.lines if object_id == focus_swhid.object_id else None),
+ )
+ f["url"] = reverse(
+ "swhid-iframe",
+ url_args={"swhid": str(cnt_swhid)},
+ query_params={"focus_swhid": str(focus_swhid)},
+ )
+
+ return {"dirs": dirs, "files": files}
+
+
+def _get_breacrumbs_data(
+ swhid: QualifiedSWHID,
+ focus_swhid: QualifiedSWHID,
+ path: str,
+ snapshot_context: Optional[SnapshotContext] = None,
+) -> List[Dict[str, Any]]:
+ breadcrumbs = []
+ filename = None
+ # strip any leading or trailing slash from path qualifier of SWHID
+ if path and path[0] == "/":
+ path = path[1:]
+ if path and path[-1] == "/":
+ path = path[:-1]
+ if swhid.object_type == ObjectType.CONTENT:
+ split_path = path.split("/")
+ filename = split_path[-1]
+ path = path[: -len(filename)]
+
+ path_info = gen_path_info(path) if path != "/" else []
+
+ root_dir = None
+ if snapshot_context and snapshot_context["root_directory"]:
+ root_dir = snapshot_context["root_directory"]
+ elif focus_swhid.object_type == ObjectType.DIRECTORY:
+ root_dir = focus_swhid.object_id.hex()
+
+ if root_dir:
+ root_dir_swhid = QualifiedSWHID(
+ object_type=ObjectType.DIRECTORY,
+ object_id=hash_to_bytes(root_dir),
+ origin=swhid.origin,
+ visit=swhid.visit,
+ anchor=swhid.anchor,
+ )
+ breadcrumbs.append(
+ {
+ "name": root_dir[:7],
+ "object_id": root_dir_swhid.object_id.hex(),
+ "path": "/",
+ "url": reverse(
+ "swhid-iframe",
+ url_args={"swhid": str(root_dir_swhid)},
+ query_params={"focus_swhid": focus_swhid},
+ ),
+ }
+ )
+
+ for pi in path_info:
+ dir_info = archive.lookup_directory_with_path(root_dir, pi["path"])
+ dir_swhid = QualifiedSWHID(
+ object_type=ObjectType.DIRECTORY,
+ object_id=hash_to_bytes(dir_info["target"]),
+ origin=swhid.origin,
+ visit=swhid.visit,
+ anchor=swhid.anchor,
+ path="/" + pi["path"] + "/",
+ )
+ breadcrumbs.append(
+ {
+ "name": pi["name"],
+ "object_id": dir_swhid.object_id.hex(),
+ "path": dir_swhid.path.decode("utf-8") if dir_swhid.path else "",
+ "url": reverse(
+ "swhid-iframe",
+ url_args={"swhid": str(dir_swhid)},
+ query_params={"focus_swhid": focus_swhid},
+ ),
+ }
+ )
+ if filename:
+ breadcrumbs.append(
+ {
+ "name": filename,
+ "object_id": swhid.object_id.hex(),
+ "path": path,
+ "url": "",
+ }
+ )
+
+ return breadcrumbs
+
+
+@xframe_options_exempt
+def swhid_iframe(request, swhid: str):
+ """Django view that can be embedded in an iframe to display objects archived
+ by Software Heritage (currently contents and directories) in a minimalist
+ Web UI.
+ """
+ focus_swhid = request.GET.get("focus_swhid", swhid)
+ parsed_swhid = None
+ view_data = {}
+ breadcrumbs = []
+ swh_objects = []
+ snapshot_context = None
+ swhids_info_extra_context = {}
+ try:
+ parsed_swhid = get_swhid(swhid)
+ parsed_focus_swhid = get_swhid(focus_swhid)
+ path = parsed_swhid.path.decode("utf-8") if parsed_swhid.path else ""
+
+ snapshot_context = None
+ revision_id = None
+ if (
+ parsed_swhid.anchor
+ and parsed_swhid.anchor.object_type == ObjectType.REVISION
+ ):
+ revision_id = parsed_swhid.anchor.object_id.hex()
+ if parsed_swhid.origin or parsed_swhid.visit:
+ snapshot_context = get_snapshot_context(
+ origin_url=parsed_swhid.origin,
+ snapshot_id=parsed_swhid.visit.object_id.hex()
+ if parsed_swhid.visit
+ else None,
+ revision_id=revision_id,
+ )
+
+ error_info: Dict[str, Any] = {"status_code": 200, "description": ""}
+
+ if parsed_swhid and parsed_swhid.object_type == ObjectType.CONTENT:
+ view_data = _get_content_rendering_data(parsed_swhid, path)
+ swh_objects.append(
+ SWHObjectInfo(
+ object_type=CONTENT, object_id=parsed_swhid.object_id.hex()
+ )
+ )
+
+ elif parsed_swhid and parsed_swhid.object_type == ObjectType.DIRECTORY:
+ view_data = _get_directory_rendering_data(
+ parsed_swhid, parsed_focus_swhid, path
+ )
+ swh_objects.append(
+ SWHObjectInfo(
+ object_type=DIRECTORY, object_id=parsed_swhid.object_id.hex()
+ )
+ )
+
+ elif parsed_swhid:
+ error_info = {
+ "status_code": 400,
+ "description": (
+ f"Objects of type {parsed_swhid.object_type} are not supported"
+ ),
+ }
+
+ swhids_info_extra_context["path"] = path
+ if parsed_swhid and view_data:
+ breadcrumbs = _get_breacrumbs_data(
+ parsed_swhid, parsed_focus_swhid, path, snapshot_context
+ )
+
+ if parsed_swhid.object_type == ObjectType.CONTENT and len(breadcrumbs) > 1:
+ swh_objects.append(
+ SWHObjectInfo(
+ object_type=DIRECTORY, object_id=breadcrumbs[-2]["object_id"]
+ )
+ )
+ swhids_info_extra_context["path"] = breadcrumbs[-2]["path"]
+ swhids_info_extra_context["filename"] = breadcrumbs[-1]["name"]
+
+ if snapshot_context:
+ swh_objects.append(
+ SWHObjectInfo(
+ object_type=REVISION,
+ object_id=snapshot_context["revision_id"] or "",
+ )
+ )
+ swh_objects.append(
+ SWHObjectInfo(
+ object_type=SNAPSHOT,
+ object_id=snapshot_context["snapshot_id"] or "",
+ )
+ )
+
+ except BadInputExc as e:
+ error_info = {"status_code": 400, "description": f"BadInputExc: {str(e)}"}
+ except NotFoundExc as e:
+ error_info = {"status_code": 404, "description": f"NotFoundExc: {str(e)}"}
+ except Exception as e:
+ error_info = {"status_code": 500, "description": str(e)}
+
+ return render(
+ request,
+ "misc/iframe.html",
+ {
+ **view_data,
+ "iframe_mode": True,
+ "object_type": parsed_swhid.object_type.value if parsed_swhid else None,
+ "lines": parsed_swhid.lines if parsed_swhid else None,
+ "breadcrumbs": breadcrumbs,
+ "swhid": swhid,
+ "focus_swhid": focus_swhid,
+ "error_code": error_info["status_code"],
+ "error_message": http_status_code_message.get(error_info["status_code"]),
+ "error_description": error_info["description"],
+ "snapshot_context": None,
+ "swhids_info": get_swhids_info(
+ swh_objects, snapshot_context, swhids_info_extra_context
+ ),
+ },
+ status=error_info["status_code"],
+ )
+
+
+urlpatterns = [
+ url(
+ r"^embed/(?P<swhid>swh:[0-9]+:[a-z]+:[0-9a-f]+.*)$",
+ swhid_iframe,
+ name="swhid-iframe",
+ ),
+]
diff --git a/swh/web/misc/urls.py b/swh/web/misc/urls.py
--- a/swh/web/misc/urls.py
+++ b/swh/web/misc/urls.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2019 The Software Heritage developers
+# Copyright (C) 2019-2021 The Software Heritage developers
# See the AUTHORS file at the top-level directory of this distribution
# License: GNU Affero General Public License version 3, or any later version
# See top-level LICENSE file for more information
@@ -51,6 +51,7 @@
url(r"^stat_counters/", _stat_counters, name="stat-counters"),
url(r"^", include("swh.web.misc.badges")),
url(r"^metrics/prometheus/$", prometheus_metrics, name="metrics-prometheus"),
+ url(r"^", include("swh.web.misc.iframe")),
]
diff --git a/swh/web/templates/includes/breadcrumbs.html b/swh/web/templates/includes/breadcrumbs.html
--- a/swh/web/templates/includes/breadcrumbs.html
+++ b/swh/web/templates/includes/breadcrumbs.html
@@ -1,24 +1,23 @@
{% comment %}
-Copyright (C) 2017-2018 The Software Heritage developers
+Copyright (C) 2017-2021 The Software Heritage developers
See the AUTHORS file at the top-level directory of this distribution
License: GNU Affero General Public License version 3, or any later version
See top-level LICENSE file for more information
{% endcomment %}
{% if breadcrumbs %}
- {% if breadcrumbs|length > 1 or breadcrumbs.0.url %}
- <nav class="bread-crumbs swh-browse-bread-crumbs">
- <ul>
- {% for bc in breadcrumbs %}
- {% if bc.url %}
- <li class="swh-path"><a href="{{ bc.url | safe }}">{{ bc.name }}</a></li>
- <li>/</li>
- {% else %}
- <li>{{ bc.name }}</li>
- {% endif %}
- {% endfor %}
- </ul>
- </nav>
- {% endif %}
+ {% if iframe_mode or breadcrumbs|length > 1 or breadcrumbs.0.url %}
+ <nav class="bread-crumbs swh-browse-bread-crumbs">
+ <ul>
+ {% for bc in breadcrumbs %}
+ {% if bc.url %}
+ <li class="swh-path"><a href="{{ bc.url | safe }}">{{ bc.name }}</a></li>
+ <li>/</li>
+ {% else %}
+ <li>{{ bc.name }}</li>
+ {% endif %}
+ {% endfor %}
+ </ul>
+ </nav>
+ {% endif %}
{% endif %}
-
diff --git a/swh/web/templates/includes/content-display.html b/swh/web/templates/includes/content-display.html
--- a/swh/web/templates/includes/content-display.html
+++ b/swh/web/templates/includes/content-display.html
@@ -1,5 +1,5 @@
{% comment %}
-Copyright (C) 2017-2020 The Software Heritage developers
+Copyright (C) 2017-2021 The Software Heritage developers
See the AUTHORS file at the top-level directory of this distribution
License: GNU Affero General Public License version 3, or any later version
See top-level LICENSE file for more information
@@ -12,12 +12,14 @@
{% if snapshot_context and snapshot_context.is_empty %}
{% include "includes/empty-snapshot.html" %}
{% else %}
- <div class="card">
- {% if filename %}
- <div class="swh-content-filename card-header bg-gray-light swh-heading-color">
- {{ filename }}
- </div>
- {% endif %}
+ {% if not iframe_mode %}
+ <div class="card">
+ {% if filename %}
+ <div class="swh-content-filename card-header bg-gray-light swh-heading-color">
+ {{ filename }}
+ </div>
+ {% endif %}
+ {% endif %}
<div class="swh-content">
{% if content_size > max_content_size %}
Content is too large to be displayed (size is greater than {{ max_content_size|filesizeformat }}).
@@ -47,7 +49,9 @@
{% include "includes/http-error.html" %}
{% endif %}
</div>
- </div>
+ {% if not iframe_mode %}
+ </div>
+ {% endif %}
{% if content %}
<script>
@@ -59,7 +63,7 @@
let codeContainer = $('code');
let content = codeContainer.text();
- swh.webapp.highlightCode();
+ swh.webapp.highlightCode(true, 'code', !{{ iframe_mode|jsonify }});
function updateLanguage(language) {
codeContainer.text(content);
@@ -72,7 +76,7 @@
const newUrl = window.location.pathname + '?' + urlParams.toString() + window.location.hash;
window.history.replaceState('', document.title, newUrl);
- swh.webapp.highlightCode();
+ swh.webapp.highlightCode(true, 'code', !{{ iframe_mode|jsonify }});
}
{% endif %}
diff --git a/swh/web/templates/includes/directory-display.html b/swh/web/templates/includes/directory-display.html
--- a/swh/web/templates/includes/directory-display.html
+++ b/swh/web/templates/includes/directory-display.html
@@ -1,11 +1,13 @@
{% comment %}
-Copyright (C) 2017-2020 The Software Heritage developers
+Copyright (C) 2017-2021 The Software Heritage developers
See the AUTHORS file at the top-level directory of this distribution
License: GNU Affero General Public License version 3, or any later version
See top-level LICENSE file for more information
{% endcomment %}
-{% include "includes/revision-info.html" %}
+{% if not iframe_mode %}
+ {% include "includes/revision-info.html" %}
+{% endif %}
{% if snapshot_context and snapshot_context.is_empty %}
{% include "includes/empty-snapshot.html" %}
@@ -53,7 +55,9 @@
</tbody>
</table>
</div>
- <hr class="mt-0 mb-2">
+ {% if not iframe_mode %}
+ <hr class="mt-0 mb-2">
+ {% endif %}
{% elif "revision_found" in swh_object_metadata and swh_object_metadata.revision_found is False %}
<i>Revision {{ swh_object_metadata.revision }} could not be found in the archive.</i>
<br/>
diff --git a/swh/web/templates/includes/http-error.html b/swh/web/templates/includes/http-error.html
--- a/swh/web/templates/includes/http-error.html
+++ b/swh/web/templates/includes/http-error.html
@@ -1,5 +1,5 @@
{% comment %}
-Copyright (C) 2018 The Software Heritage developers
+Copyright (C) 2018-2021 The Software Heritage developers
See the AUTHORS file at the top-level directory of this distribution
License: GNU Affero General Public License version 3, or any later version
See top-level LICENSE file for more information
@@ -18,16 +18,18 @@
<div class="swh-http-error-desc">
<pre>{{ error_description }}</pre>
- <div>
- <a class="btn" onclick="window.history.back();">
- <i class="mdi mdi-arrow-left" aria-hidden="true"></i>
- Go back to previous page
- </a>
- or
- <a class="btn" href="{% url 'swh-web-homepage' %}">
- <i class="mdi mdi-arrow-left" aria-hidden="true"></i>
- Go back to homepage
- </a>
- </div>
+ {% if not iframe_mode %}
+ <div>
+ <a class="btn" onclick="window.history.back();">
+ <i class="mdi mdi-arrow-left" aria-hidden="true"></i>
+ Go back to previous page
+ </a>
+ or
+ <a class="btn" href="{% url 'swh-web-homepage' %}">
+ <i class="mdi mdi-arrow-left" aria-hidden="true"></i>
+ Go back to homepage
+ </a>
+ </div>
+ {% endif %}
</div>
</div>
\ No newline at end of file
diff --git a/swh/web/templates/includes/show-swhids.html b/swh/web/templates/includes/show-swhids.html
--- a/swh/web/templates/includes/show-swhids.html
+++ b/swh/web/templates/includes/show-swhids.html
@@ -18,7 +18,8 @@
<div id="swh-identifiers-content">
<p>
To reference or cite the objects present in the Software Heritage archive, permalinks based on
- <a href="https://docs.softwareheritage.org/devel/swh-model/persistent-identifiers.html">
+ <a target="_blank" rel="noopener noreferrer"
+ href="https://docs.softwahreheritage.org/devel/swh-model/persistent-identifiers.html">
SoftWare Heritage persistent IDentifiers (SWHIDs)
</a>
must be used instead of copying and pasting the url from the address bar of the browser (as there is no guarantee the current URI
@@ -55,22 +56,31 @@
{% endif %}
<div class="card">
<div class="card-body swhid-ui">
- <div class="swh-badges">
- {% if snapshot_context and snapshot_context.origin_info %}
- <img class="swh-badge swh-badge-origin"
- src="{% url 'swh-badge' 'origin' snapshot_context.origin_info.url %}"
- onclick="swh.webapp.showBadgeInfoModal('origin', '{{ snapshot_context.origin_info.url|urlencode:"/?:@&" }}')"
- title="Click to display badge integration info">
- {% endif %}
- {% if swhid_info.object_id %}
- <img class="swh-badge swh-badge-{{ swhid_info.object_type }}"
- src="{% url 'swh-badge' swhid_info.object_type swhid_info.object_id %}"
- onclick="swh.webapp.showBadgeInfoModal('{{ swhid_info.object_type }}', $(this).parent().parent().find('.swhid').text())"
- title="Click to display badge integration info">
- {% endif %}
- </div>
+ {% if not iframe_mode %}
+ <div class="swh-badges">
+ {% if snapshot_context and snapshot_context.origin_info %}
+ <img class="swh-badge swh-badge-origin"
+ src="{% url 'swh-badge' 'origin' snapshot_context.origin_info.url %}"
+ onclick="swh.webapp.showBadgeInfoModal('origin', '{{ snapshot_context.origin_info.url|urlencode:"/?:@&" }}')"
+ title="Click to display badge integration info">
+ {% endif %}
+ {% if swhid_info.object_id %}
+ <img class="swh-badge swh-badge-{{ swhid_info.object_type }}"
+ src="{% url 'swh-badge' swhid_info.object_type swhid_info.object_id %}"
+ onclick="swh.webapp.showBadgeInfoModal('{{ swhid_info.object_type }}', $(this).parent().parent().find('.swhid').text())"
+ title="Click to display badge integration info">
+ {% endif %}
+ {% if swhid_info.object_type == "content" or swhid_info.object_type == "directory" %}
+ <a class="float-right" style="cursor: pointer;"
+ onclick="swh.webapp.showIframeInfoModal('{{ swhid_info.object_type }}', $(this).parent().parent().find('.swhid').text())">
+ Iframe embedding
+ </a>
+ {% endif %}
+ </div>
+
+ {% endif %}
{% if swhid_info.object_id %}
- <pre><a class="swhid" id="{{ swhid_info.swhid }}" href="{{ swhid_info.swhid_url }}">{{ swhid_info.swhid }}</a></pre>
+ <pre><a class="swhid" target="_blank" rel="noopener noreferrer" id="{{ swhid_info.swhid }}" href="{{ swhid_info.swhid_url }}">{{ swhid_info.swhid }}</a></pre>
{% endif %}
{% if swhid_info.swhid_with_context is not None %}
<div class="float-left">
diff --git a/swh/web/templates/misc/iframe.html b/swh/web/templates/misc/iframe.html
new file mode 100644
--- /dev/null
+++ b/swh/web/templates/misc/iframe.html
@@ -0,0 +1,182 @@
+{% comment %}
+Copyright (C) 2021 The Software Heritage developers
+See the AUTHORS file at the top-level directory of this distribution
+License: GNU Affero General Public License version 3, or any later version
+See top-level LICENSE file for more information
+{% endcomment %}
+
+<!DOCTYPE html>
+
+{% load static %}
+{% load render_bundle from webpack_loader %}
+{% load swh_templatetags %}
+
+<html lang="en">
+ <head>
+ <meta charset="utf-8">
+ <meta http-equiv="X-UA-Compatible" content="IE=edge">
+ <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
+ <title>Software Heritage archived object</title>
+ <script src="{% url 'js_reverse' %}" type="text/javascript"></script>
+ {% render_bundle 'vendors' %}
+ {% render_bundle 'webapp' %}
+ {% render_bundle 'browse' %}
+ <script>
+/*
+@licstart The following is the entire license notice for the JavaScript code in this page.
+
+Copyright (C) 2021 The Software Heritage developers
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as
+published by the Free Software Foundation, either version 3 of the
+License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Affero General Public License for more details.
+
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+@licend The above is the entire license notice for the JavaScript code in this page.
+*/
+ </script>
+ <style>
+ .card {
+ border: none;
+ margin-bottom: 0 !important;
+ }
+
+ .card-header {
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 80px;
+ border-bottom: 1px solid rgba(0, 0, 0, 0.125);
+ }
+
+ .breadcrumbs-container {
+ border-top: 1px solid rgba(0, 0, 0, 0.125);
+ border-bottom: 1px solid rgba(0, 0, 0, 0.125);
+ background: white;
+ margin-left: -20px;
+ margin-right: -20px;
+ margin-bottom: 5px;
+ }
+
+ .breadcrumbs-container ul {
+ padding-left: 25px;
+ }
+
+ .card-block {
+ margin-top: 78px;
+ overflow: auto;
+ }
+
+ .swh-content pre {
+ margin-top: 5px;
+ margin-bottom: 0;
+ padding: 0;
+ }
+
+ .swh-directory-table {
+ margin-top: 3px;
+ }
+
+ .bread-crumbs {
+ font-size: large;
+ }
+
+ .hljs-ln-numbers {
+ cursor: default !important;
+ }
+
+ #swh-identifiers {
+ top: 60px !important;
+ }
+
+ #swh-identifiers .card-body {
+ padding-top: 0;
+ padding-bottom: 0;
+ padding-left: 8px;
+ }
+
+ </style>
+ </head>
+
+ <body style="padding-bottom: 0;">
+ <div class="wrapper" style="margin-left: 0;">
+ <div class="content">
+ <div class="container-fluid">
+ {% include "includes/show-swhids.html" %}
+ <div class="card">
+ <div class="card-header bg-gray-light">
+ <div class="d-flex align-items-center">
+ <a class="mr-auto" href="https://www.softwareheritage.org"
+ target="_blank" rel="noopener noreferrer">
+ <div class="brand-text sitename">
+ <img src="{% static 'img/swh-logo.svg' %}" style="height: 40px;"/>
+ <span class="first-word pl-1">Software</span>
+ <span class="second-word">Heritage</span>
+ </div>
+ </a>
+ <div class="d-none d-md-block">
+ Navigating in
+ <img src="{% url 'swh-badge-swhid' swhid %}">
+ </div>
+ <div class="ml-auto d-flex align-items-center">
+ {% if swhid != focus_swhid %}
+ <a class="d-flex align-items-center pr-2" href="{% url 'swhid-iframe' focus_swhid %}"
+ title="Reset view to its original state">
+ <div class="d-none d-lg-block">Reset view</div>
+ <i class="mdi mdi-refresh" aria-hidden="true"></i>
+ </a>
+ {% endif %}
+ <a class="d-none d-lg-block" href="{% url 'browse-swhid' swhid %}"
+ target="_blank" rel="noopener noreferrer">
+ View in the archive
+ <i class="mdi mdi-open-in-new" aria-hidden="true"></i>
+ </a>
+ <a class="d-lg-none" href="{% url 'browse-swhid' swhid %}"
+ target="_blank" rel="noopener noreferrer"
+ title="Go to archive">
+ <i class="mdi mdi-open-in-new" aria-hidden="true"></i>
+ </a>
+ </div>
+ </div>
+ <div class="d-flex align-items-center breadcrumbs-container mt-1">
+ {% include "includes/breadcrumbs.html" %}
+ </div>
+ </div>
+ <div class="card-block">
+ {% if error_code != 200 %}
+ {% include "includes/http-error.html" %}
+ {% elif object_type == "cnt" %}
+ {% include "includes/content-display.html" %}
+ {% elif object_type == "dir" %}
+ {% include "includes/directory-display.html" %}
+ {% endif %}
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ <a href="{% url 'jslicenses' %}" rel="jslicense" style="display: none;">
+ JavaScript license information
+ </a>
+ {% if object_type == "cnt" %}
+ <script>
+ setTimeout(function() {
+ {% if lines %}
+ swh.webapp.scrollToLine(swh.webapp.highlightLine({{ lines.0|jsonify }}), 100);
+ swh.webapp.highlightLines({{ lines.0|jsonify }}, {{ lines.1|jsonify }});
+ {% endif %}
+ }, 500);
+ </script>
+ {% endif %}
+ </body>
+
+</html>
diff --git a/swh/web/tests/data.py b/swh/web/tests/data.py
--- a/swh/web/tests/data.py
+++ b/swh/web/tests/data.py
@@ -20,6 +20,7 @@
from swh.indexer.storage.model import OriginIntrinsicMetadataRow
from swh.loader.git.from_disk import GitLoaderFromArchive
from swh.model.hashutil import DEFAULT_ALGORITHMS, hash_to_hex
+from swh.model.identifiers import CoreSWHID, ObjectType, QualifiedSWHID
from swh.model.model import (
Content,
Directory,
@@ -277,18 +278,35 @@
revisions = set()
releases = set()
snapshots = set()
+ swhids = []
content_path = {}
# Get all objects loaded into the test archive
common_metadata = {ORIGIN_METADATA_KEY: ORIGIN_METADATA_VALUE}
for origin in _TEST_ORIGINS:
+ origin_revisions = set()
snp = snapshot_get_latest(storage, origin["url"])
+ swhids.append(
+ QualifiedSWHID(
+ object_type=ObjectType.SNAPSHOT, object_id=snp.id, origin=origin["url"]
+ )
+ )
snapshots.add(hash_to_hex(snp.id))
for branch_name, branch_data in snp.branches.items():
target_type = branch_data.target_type.value
if target_type == "revision":
- revisions.add(branch_data.target)
+ origin_revisions.add(branch_data.target)
+ swhids.append(
+ QualifiedSWHID(
+ object_type=ObjectType.REVISION,
+ object_id=branch_data.target,
+ origin=origin["url"],
+ visit=CoreSWHID(
+ object_type=ObjectType.SNAPSHOT, object_id=snp.id
+ ),
+ )
+ )
if b"master" in branch_name:
# Add some origin intrinsic metadata for tests
metadata = common_metadata
@@ -310,14 +328,24 @@
)
elif target_type == "release":
release = storage.release_get([branch_data.target])[0]
- revisions.add(release.target)
+ origin_revisions.add(release.target)
releases.add(hash_to_hex(branch_data.target))
+ swhids.append(
+ QualifiedSWHID(
+ object_type=ObjectType.RELEASE,
+ object_id=branch_data.target,
+ origin=origin["url"],
+ visit=CoreSWHID(
+ object_type=ObjectType.SNAPSHOT, object_id=snp.id
+ ),
+ )
+ )
- for rev_log in storage.revision_shortlog(set(revisions)):
+ for rev_log in storage.revision_shortlog(origin_revisions):
rev_id = rev_log[0]
revisions.add(rev_id)
- for rev in storage.revision_get(revisions):
+ for rev in storage.revision_get(origin_revisions):
if rev is None:
continue
dir_id = rev.directory
@@ -328,8 +356,36 @@
content_path[entry["sha1"]] = "/".join(
[hash_to_hex(dir_id), entry["path"].decode("utf-8")]
)
+ swhids.append(
+ QualifiedSWHID(
+ object_type=ObjectType.CONTENT,
+ object_id=entry["sha1_git"],
+ origin=origin["url"],
+ visit=CoreSWHID(
+ object_type=ObjectType.SNAPSHOT, object_id=snp.id
+ ),
+ anchor=CoreSWHID(
+ object_type=ObjectType.REVISION, object_id=rev.id
+ ),
+ path=b"/" + entry["path"],
+ )
+ )
elif entry["type"] == "dir":
directories.add(hash_to_hex(entry["target"]))
+ swhids.append(
+ QualifiedSWHID(
+ object_type=ObjectType.DIRECTORY,
+ object_id=entry["target"],
+ origin=origin["url"],
+ visit=CoreSWHID(
+ object_type=ObjectType.SNAPSHOT, object_id=snp.id
+ ),
+ anchor=CoreSWHID(
+ object_type=ObjectType.REVISION, object_id=rev.id
+ ),
+ path=b"/" + entry["path"] + b"/",
+ )
+ )
_add_extra_contents(storage, sha1s)
@@ -413,7 +469,7 @@
"releases": list(releases),
"revisions": list(map(hash_to_hex, revisions)),
"snapshots": list(snapshots),
- "generated_checksums": set(),
+ "swhids": swhids,
}
diff --git a/swh/web/tests/misc/test_iframe.py b/swh/web/tests/misc/test_iframe.py
new file mode 100644
--- /dev/null
+++ b/swh/web/tests/misc/test_iframe.py
@@ -0,0 +1,83 @@
+# Copyright (C) 2021 The Software Heritage developers
+# See the AUTHORS file at the top-level directory of this distribution
+# License: GNU Affero General Public License version 3, or any later version
+# See top-level LICENSE file for more information
+
+from hypothesis import given
+
+from swh.model.hashutil import hash_to_bytes
+from swh.model.identifiers import CoreSWHID, ObjectType
+from swh.web.common.utils import reverse
+from swh.web.tests.strategies import (
+ content_swhid,
+ directory_swhid,
+ revision_swhid,
+ unknown_directory,
+)
+from swh.web.tests.utils import check_html_get_response
+
+
+@given(content_swhid())
+def test_content_swhid_iframe(client, content_swhid):
+ url = reverse("swhid-iframe", url_args={"swhid": str(content_swhid)})
+ check_html_get_response(
+ client, url, status_code=200, template_used="misc/iframe.html"
+ )
+
+
+@given(content_swhid())
+def test_content_core_swhid_iframe(client, content_swhid):
+ content_core_swhid = CoreSWHID(
+ object_type=content_swhid.object_type, object_id=content_swhid.object_id
+ )
+ url = reverse("swhid-iframe", url_args={"swhid": str(content_core_swhid)})
+ check_html_get_response(
+ client, url, status_code=200, template_used="misc/iframe.html"
+ )
+
+
+@given(directory_swhid())
+def test_directory_swhid_iframe(client, directory_swhid):
+ url = reverse("swhid-iframe", url_args={"swhid": str(directory_swhid)})
+ check_html_get_response(
+ client, url, status_code=200, template_used="misc/iframe.html"
+ )
+
+
+@given(directory_swhid())
+def test_directory_core_swhid_iframe(client, directory_swhid):
+ directory_core_swhid = CoreSWHID(
+ object_type=directory_swhid.object_type, object_id=directory_swhid.object_id
+ )
+ url = reverse("swhid-iframe", url_args={"swhid": str(directory_core_swhid)})
+ check_html_get_response(
+ client, url, status_code=200, template_used="misc/iframe.html"
+ )
+
+
+@given(revision_swhid())
+def test_iframe_unsupported_object(client, revision_swhid):
+ url = reverse("swhid-iframe", url_args={"swhid": str(revision_swhid)})
+ check_html_get_response(
+ client, url, status_code=400, template_used="misc/iframe.html"
+ )
+
+
+@given(unknown_directory())
+def test_iframe_object_not_found(client, unknown_directory):
+ swhid = CoreSWHID(
+ object_type=ObjectType.DIRECTORY, object_id=hash_to_bytes(unknown_directory)
+ )
+ url = reverse("swhid-iframe", url_args={"swhid": str(swhid)})
+ check_html_get_response(
+ client, url, status_code=404, template_used="misc/iframe.html"
+ )
+
+
+@given(content_swhid())
+def test_swhid_iframe_unknown_error(client, mocker, content_swhid):
+ mocker.patch("swh.web.misc.iframe.get_swhid").side_effect = Exception("Error")
+ url = reverse("swhid-iframe", url_args={"swhid": str(content_swhid)})
+ check_html_get_response(
+ client, url, status_code=500, template_used="misc/iframe.html"
+ )
diff --git a/swh/web/tests/strategies.py b/swh/web/tests/strategies.py
--- a/swh/web/tests/strategies.py
+++ b/swh/web/tests/strategies.py
@@ -23,6 +23,7 @@
from swh.model.hashutil import DEFAULT_ALGORITHMS, hash_to_bytes, hash_to_hex
from swh.model.hypothesis_strategies import origins as new_origin_strategy
from swh.model.hypothesis_strategies import snapshots as new_snapshot
+from swh.model.identifiers import ObjectType
from swh.model.model import (
Content,
Directory,
@@ -646,3 +647,51 @@
"rev_dir_rev_path": "libtess2",
}
)
+
+
+def swhid():
+ """
+ Hypothesis strategy returning a qualified SWHID for any object
+ ingested into the test archive.
+ """
+ return _known_swh_object("swhids")
+
+
+def content_swhid():
+ """
+ Hypothesis strategy returning a qualified SWHID for a content object
+ ingested into the test archive.
+ """
+ return swhid().filter(lambda swhid: swhid.object_type == ObjectType.CONTENT)
+
+
+def directory_swhid():
+ """
+ Hypothesis strategy returning a qualified SWHID for a directory object
+ ingested into the test archive.
+ """
+ return swhid().filter(lambda swhid: swhid.object_type == ObjectType.DIRECTORY)
+
+
+def release_swhid():
+ """
+ Hypothesis strategy returning a qualified SWHID for a release object
+ ingested into the test archive.
+ """
+ return swhid().filter(lambda swhid: swhid.object_type == ObjectType.RELEASE)
+
+
+def revision_swhid():
+ """
+ Hypothesis strategy returning a qualified SWHID for a revision object
+ ingested into the test archive.
+ """
+ return swhid().filter(lambda swhid: swhid.object_type == ObjectType.REVISION)
+
+
+def snapshot_swhid():
+ """
+ Hypothesis strategy returning a qualified SWHID for a snapshot object
+ ingested into the test archive.
+ """
+ return swhid().filter(lambda swhid: swhid.object_type == ObjectType.SNAPSHOT)

File Metadata

Mime Type
text/plain
Expires
Nov 5 2024, 8:00 AM (8 w, 2 d ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
3216579

Event Timeline