Skip to content

Commit

Permalink
Refactor initial UI and clean code #522
Browse files Browse the repository at this point in the history
Reference: #522
Signed-off-by: John M. Horan <[email protected]>
  • Loading branch information
johnmhoran committed Oct 1, 2024
1 parent fc20a4f commit e680f5e
Show file tree
Hide file tree
Showing 17 changed files with 921 additions and 2,288 deletions.
12 changes: 6 additions & 6 deletions docs/source/index.rst
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
Welcome to PurlDB documentation!
=========================================
Welcome to the PurlDB documentation!
====================================


PurlDB aka. ``Package URL Database`` is a database of software package metadata keyed by Package-URL
or purl that offers information and indentication services about software packages.
`PurlDB <https://github.com/aboutcode-org/purldb>`__ aka. ``Package URL Database`` is a database of software package metadata keyed by a `Package-URL
or purl <https://github.com/package-url>`__ that offers information and indentication services about software packages.

A purl or Package-URL is an attempt to standardize existing approaches to reliably identify and
locate software packages in general and Free and Open Source Software (FOSS) packages in
Expand All @@ -30,8 +30,8 @@ This what PurlDB is all about and it offers:
software supply chain.


What can you do PurlDB?
------------------------
What can you do with PurlDB?
----------------------------

- Build a comprehensive open source software packages knowledge base. This includes the extensive
scan of package code for origin, dependencies, embedded packages and licenses.
Expand Down
86 changes: 58 additions & 28 deletions packagedb/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
from collections import OrderedDict
from urllib.parse import urlencode

import natsort
from dateutil.parser import parse as dateutil_parse
from django.conf import settings
from django.contrib.auth.models import UserManager
from django.contrib.postgres.fields import ArrayField
Expand All @@ -26,9 +28,6 @@
from django.dispatch import receiver
from django.utils import timezone
from django.utils.translation import gettext_lazy as _

import natsort
from dateutil.parser import parse as dateutil_parse
from licensedcode.cache import build_spdx_license_expression
from packagedcode.models import normalize_qualifiers
from packageurl import PackageURL
Expand All @@ -47,6 +46,8 @@
logging.basicConfig(stream=sys.stdout)
logger.setLevel(logging.INFO)

print_to_console = False


def sort_version(packages):
"""Return the packages sorted by version."""
Expand Down Expand Up @@ -85,20 +86,18 @@ def paginated(self, per_page=5000):
page = paginator.page(page_number)
yield from page.object_list

# NOTE Based on class PurlValidateResponseSerializer(Serializer).
class PurlValidateSerializer(Serializer):

# Based on class PurlValidateResponseSerializer(Serializer). Added here because when added to serializers.py and imported raises a circular import error.
class PurlValidateErrorSerializer(Serializer):
valid = BooleanField()
exists = BooleanField(required=False)
message = CharField()
purl = CharField()
error_details = CharField()

def search(self, query: str = None):
"""
Return a Package queryset searching for the ``query``.
- A version is required.
- If only a version is provided, no qualifiers value, and the DB contains both the version alone and the version with a qualifiers value, only the version-only record is returned.
- If a correct qualifiers value is provided, returns an exact match if the record exists, otherwise no match.
- '#' and any characters that follow appear to be ignored, but we have 0 such PURLs in the DB so testing is incomplete.
Return a Package queryset searching for the ``query``. A version is required. Returns an exact match if the record(s) exist(s), otherwise no match.
"""
query = query and query.strip()
if not query:
Expand All @@ -111,37 +110,68 @@ def search(self, query: str = None):
response["purl"] = query
response["valid"] = False
response["message"] = message_not_valid
response["error_details"] = None

# validate purl
try:
package_url = PackageURL.from_string(query)
except ValueError:
serializer = self.PurlValidateSerializer(response)
# print(f"\nserializer.data = {serializer.data}")
except ValueError as e:
if print_to_console:
print(f"\nInput: {query}")
print(f"\nValueError: {e}")
response["error_details"] = e
serializer = self.PurlValidateErrorSerializer(response)
return serializer.data
# return None #throws error AttributeError: 'NoneType' object has no attribute 'prefetch_related'

# NOTE No longer used.
# purl_attributes = utils.simple_validate_from_string(query)

# print(f"\npackage_url = {package_url}")
# print(f"package_url.type = {package_url.type}")
# print(f"package_url.namespace = {package_url.namespace}")
# print(f"package_url.name = {package_url.name}")
# print(f"package_url.version = {package_url.version}")
# print(f"package_url.qualifiers = {package_url.qualifiers}")
# print(f"package_url.subpath = {package_url.subpath}")

qs = qs.filter(
(models.Q(namespace=package_url.namespace) | models.Q(namespace="")),
(models.Q(subpath=package_url.subpath) | models.Q(subpath="")),
models.Q(namespace=package_url.namespace) if package_url.namespace else (models.Q(namespace="")),
models.Q(subpath=package_url.subpath) if package_url.subpath else (models.Q(subpath="")),
type=package_url.type,
name=package_url.name,
version=package_url.version,
qualifiers=urlencode(package_url.qualifiers),
)

if print_to_console:
print(f"\nmodels.py PackageQuerySet search() qs.query = {qs.query}")
print(f"\nlist(qs): {list(qs)}")
for abc in list(qs):
print(f"- abc = {abc}")
print(f"- abc.type = {abc.type}")
print(f"- abc.namespace = {abc.namespace}")
print(f"- abc.name = {abc.name}")
print(f"- abc.version = {abc.version}")
print(f"- abc.qualifiers = {abc.qualifiers}")
print(f"- abc.subpath = {abc.subpath}")

return qs

def get_packageurl_from_string(self, query: str = None):
"""
Vet with packageurl-python __init__.py from_string().
"""
query = query and query.strip()
if not query:
return self.none()

try:
packageurl_from_string = PackageURL.from_string(query)
if print_to_console:
print(f"\n- models.py PackageQuerySet get_packageurl_from_string() query = {packageurl_from_string}")
print(f"\npackageurl_from_string.type = {packageurl_from_string.type}")
print(f"- packageurl_from_string.namespace = {packageurl_from_string.namespace}")
print(f"- packageurl_from_string.name = {packageurl_from_string.name}")
print(f"- packageurl_from_string.version = {packageurl_from_string.version}")
print(f"- packageurl_from_string.qualifiers = {packageurl_from_string.qualifiers}")
print(f"- packageurl_from_string.subpath = {packageurl_from_string.subpath}")

return packageurl_from_string

except ValueError as e:
if print_to_console:
print(f"\nInput: {query}")
print(f"\nValueError: {e}")
return None


VCS_CHOICES = [
("git", "git"),
Expand Down
97 changes: 46 additions & 51 deletions packagedb/templates/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,11 @@
<link rel="stylesheet" href="{% static 'css/bulma-tooltip.css' %}" />

<script src="{% static 'js/clipboard-2.0.0.min.js' %}" integrity="sha384-5tfO0soa+FisnuBhaHP2VmPXQG/JZ8dLcRL43IkJFzbsXTXT6zIX8q8sIT0VSe2G" crossorigin="anonymous"></script>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>

{% block extrahead %}{% endblock %}
</head>
<body class="Site">
<div class="container max-desktop-width is-fullheight">
<div class="container is-max-widescreen">
{% include "navbar.html" %}
{% block content %}{% endblock %}
{% include "footer.html" %}
Expand All @@ -32,7 +31,6 @@
document.addEventListener('DOMContentLoaded', function () {
// Set initial data-tooltip value
document.querySelectorAll('.btn-clipboard').forEach(button => {
// Next line is just for dev.
console.info('span:', button.querySelector('span'));
const span = button.querySelector('span');
if (span && !span.getAttribute('data-tooltip')) {
Expand All @@ -51,18 +49,9 @@
clip.on("success", function (clip) {
const button = clip.trigger;
const span = button.querySelector('span');
// Change tooltip text
span.setAttribute('data-tooltip', 'Copied!');
span.setAttribute('class', 'tooltip-narrow-success');
//span.setAttribute('visibility', 'visible');
//2024-09-02 Monday 12:08:55. Try this instead.
//span.classList.add('tooltip-visible');
//Does not keep tooltip visible. Try this instead.
//span.classList.add('is-active');
span.classList.add('has-tooltip-active');
//how about this?
//span.setAttribute('style', 'visibility: visible; opacity: 1;');
// Try this
button.classList.add('tooltip-visible');
clip.clearSelection()
});
Expand All @@ -71,46 +60,9 @@
console.error('Failed to copy: ', e);
const span = e.trigger.querySelector('span');
span.setAttribute('data-tooltip', 'Failed to copy');
//Keep visible
//span.classList.add('tooltip-visible');
//Does not keep tooltip visible. Try this instead.
//span.classList.add('is-active');
//Try this
//button.classList.add('tooltip-visible');
});

// Reset button/tooltip to default state.
function resetButtonAndTooltip(button) {
button.style.display = 'none';
const span = button.querySelector('span');
if (span) {
const defaultTooltip = span.getAttribute('data-original-tooltip') || 'Copy to clipboard';
span.setAttribute('data-tooltip', defaultTooltip);
span.setAttribute('class', 'tooltip-narrow');
//Reset to not visible
//span.classList.remove('tooltip-visible');
//Does not keep tooltip visible. Try this instead.
//span.classList.remove('is-active');

//Try this
//button.classList.remove('tooltip-visible');
}
}

// Add click event listener to document.
document.addEventListener('click', function(event) {
const clickedButton = event.target.closest('.btn-clipboard');
//Next line is just for dev.
console.info('clickedButton:', clickedButton);
document.querySelectorAll('.btn-clipboard').forEach(button => {
if (button !== clickedButton) {
resetButtonAndTooltip(button);
}
});
});

// Display button when mouse enters the copyable area -- includes "Note" area below textbox/pre.
// TODO Is 'span is null' still an issue?
// Display button when mouse enters the copyable area.
document.querySelectorAll('.btn-clipboard').forEach(button => {
const span = button.querySelector('span');
if (span) {
Expand All @@ -130,8 +82,51 @@
el.classList.add('has-tooltip-left');
}
});
});


//<!-- Tooltips that can contain clickable links. -->
const tooltipTriggers = document.querySelectorAll('.tooltip-trigger');

function hideTooltipWithDelay(tooltipContent) {
setTimeout(() => {
if (!tooltipContent.matches(':hover') && !tooltipContent.previousElementSibling.matches(':hover')) {
tooltipContent.style.display = 'none';
}
}, 200); // Delay to allow the user to hover over the tooltip
}

tooltipTriggers.forEach(trigger => {
trigger.addEventListener('mouseenter', function() {
const tooltipContent = this.nextElementSibling;

if (tooltipContent) {
tooltipContent.style.display = 'block';
}
});

trigger.addEventListener('mouseleave', function() {
const tooltipContent = this.nextElementSibling;

if (tooltipContent) {
hideTooltipWithDelay(tooltipContent);
}
});
});

// Keep the tooltip visible when the user hovers over the tooltip itself
document.querySelectorAll('.tooltip-content').forEach(content => {
content.addEventListener('mouseenter', function() {
this.style.display = 'block';
});

content.addEventListener('mouseleave', function() {
hideTooltipWithDelay(this);
});
});

});
</script>

{% endblock %}
</body>
</html>
4 changes: 3 additions & 1 deletion packagedb/templates/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@

{% block content %}
<section class="section pt-0">
{% include "package_search_box.html" %}
{% include "validate_purl_input.html" %}
</section>

<div></div>
{% endblock %}
41 changes: 0 additions & 41 deletions packagedb/templates/navbar.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,6 @@
</div>
<div class="navbar-menu">
<div class="navbar-start">
<a class="navbar-item" href="{% url 'package_search' %}">
Packages
</a>
<a class="navbar-item" href="{% url 'package_search_test_tabset' %}">
test_tabset
</a>
<a class="navbar-item" href="https://aboutcode.readthedocs.io/projects/PURLdb/" target="_blank">
Documentation
</a>
Expand All @@ -21,41 +15,6 @@
<a class="navbar-item" href="/api" target="_blank">
API
</a>
<div class="navbar-item navbar-item is-cursor-help">
<div class="dropdown is-right is-hoverable ">
<div class="dropdown-trigger has-text-grey-light">About</div>
<div class="dropdown-menu navbar-hover-div" role="menu">
<div class="dropdown-content">
<div class="dropdown-item about-hover-div">

PurlDB is a free and open database of <strong>purls</strong>
(<a href="https://github.com/package-url/purl-spec" target="_blank">Package URLs</a>).
<ul>
<li>
Live chat at <a href="https://gitter.im/aboutcode-org/discuss" target="_blank">
https://gitter.im/aboutcode-org/discuss</a>
</li>
<li>
Source code and support at <a href="https://github.com/aboutcode-org/purldb" target="_blank">https://github.com/aboutcode-org/purldb</a>
</li>
<li>
Review and add issues at <a href="https://github.com/aboutcode-org/purldb/issues" target="_blank">https://github.com/aboutcode-org/purldb/issues</a>
</li>
<li>
Docs at <a href="https://aboutcode.readthedocs.io/projects/PURLdb/" target="_blank">
https://aboutcode.readthedocs.io/projects/PURLdb/</a>
</li>
<li>
Sponsored by NLnet <a href="https://nlnet.nl/project/vulnerabilitydatabase/" target="_blank">
https://nlnet.nl/project/vulnerabilitydatabase/</a> for
<a href="https://www.aboutcode.org/" target="_blank">https://www.aboutcode.org/</a>
</li>
</ul>
</div>
</div>
</div>
</div>
</div>
<div class="navbar-item navbar-item is-cursor-help">
<div class="dropdown-trigger has-text-grey-light">
v{{ PURLDB_VERSION }}
Expand Down
Loading

0 comments on commit e680f5e

Please sign in to comment.