Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Modernize UI #798 #813

Merged
merged 22 commits into from
Sep 2, 2022
Merged

Modernize UI #798 #813

merged 22 commits into from
Sep 2, 2022

Conversation

johnmhoran
Copy link
Member

@johnmhoran johnmhoran commented Jul 27, 2022

This PR revamps minimally the UI using the same overall look and feel
as ScanCode.io.

Further work beyond the basics including addressing the reviewers
feedback will take place in:
#847

Reference: #798
Signed-off-by: John M. Horan [email protected]

Reference: #798

Signed-off-by: John M. Horan [email protected]
Reference: #798

Signed-off-by: John M. Horan <[email protected]>
Reference: #798

Signed-off-by: John M. Horan <[email protected]>
Reference: #798

Signed-off-by: John M. Horan <[email protected]>
Reference: #798

Signed-off-by: John M. Horan <[email protected]>
Reference: #798

Signed-off-by: John M. Horan <[email protected]>
Reference: #798

Signed-off-by: John M. Horan <[email protected]>
Reference: #798

Signed-off-by: John M. Horan <[email protected]>
Reference: #798

Signed-off-by: John M. Horan [email protected]
Reference: #798

Signed-off-by: John M. Horan <[email protected]>
Reference: #798

Signed-off-by: John M. Horan <[email protected]>
@pombredanne
Copy link
Member

pombredanne commented Aug 9, 2022

Here is some comments on the current state of this PR... The goal would be to have a minimally new UI working so we can release it in verision 30.0:

On the landing page

  • remove template in navbar

JMH: Done -- hidden using debug setting in navbar (affects all 5 primary pages).

third-party JS

JMH: Done -- downloaded and added jquery-3.6.0.js and jquery-3.6.0.min.js to vulnerablecode/vulnerablecode/static/js/ and added <script type="text/javascript" src="{% static 'js/jquery-3.6.0.js' %}"></script> to the head section of base.html.

Package search

  • Replace HTML by proper link in the packages search results list page
  • In the package detail view hide everything after fixing vulnerabilities

JMH: Both are done.

Vulnerabilities search

  • Replace HTML by proper link in the vulnerabilities search results list page

JMH: Done.

  • In the vulnerabilities detail view hide everything after fixed package
    • hide everything after aliases on the essential tabs
    • Populate Affected Packages and Fixed Packages

JMH: Both are done.

for later:

@pombredanne
Copy link
Member

pombredanne commented Aug 10, 2022

Here are a few more suggestions:

  • for the package details, we could have no tabs and everything in one view

vcio-package-details-mock

  • for the vulnerabilities details, a more compact view that would also display affected and fixed packages.
  • The first few affected packages and first few fixed packages would be always listed on the main tab.
  • There would be a ... (dot dot dot) linking to the tab with the full table
    • It seems Bulma tabsets do not enable such indirect links to a tab, so I added the dots via a for loop and above added a parenthetical (see xxx tab)
  • Full details would be in the tabs.
  • The count of references, and other items would be added to the right of the tab title inside parenthesis as in References (23)

vcio-vuln-details-mock

@pombredanne pombredanne added this to the v30.0 milestone Aug 11, 2022
@johnmhoran
Copy link
Member Author

johnmhoran commented Aug 11, 2022

Copy link
Contributor

@tdruez tdruez left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@johnmhoran See my various comments for code improvements.
In general, you are using too many "style" attributes that you could replace with existing CSS classes.
Refer to the Bulma docs, in particular https://bulma.io/documentation/helpers/spacing-helpers/

vulnerabilities/templates/index.html Show resolved Hide resolved
vulnerabilities/templates/index.html Show resolved Hide resolved
vulnerabilities/templates/index.html Show resolved Hide resolved
vulnerabilities/templates/base.html Show resolved Hide resolved
vulnerabilities/templates/packages.html Show resolved Hide resolved
vulnerabilities/templates/packages.html Show resolved Hide resolved
vulnerabilities/views.py Show resolved Hide resolved
# Check whether the input value is a syntactically-correct purl
try:
purl = PackageURL.from_string(package_name)
return list(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The QS should live outside the try block.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tdruez I don't understand -- what's the reason to move the Qs outside the try block, and how would that be done?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps you're suggesting that I move the entire return() into a new method, leaving only purl = PackageURL.from_string(package_name), which tests whether the input is a syntactically-correct purl? Pass purl to the new method, keeping the try focused on its sole purpose: the purl test?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what's the reason to move the Qs outside the try block

In general, you want to keep only one piece of logic that could fail within a try block.
An exception raised by converting a PURL has nothing to do with an exception filtering a QuerySet.

Also, using generic except: is bad practice. You should always try to be explicit about the exception you want to catch. Since you want to "Check whether the input value is a syntactically-correct purl", let's catch that specific Exception:

>>> PackageURL.from_string('wrong syntax')
Traceback (most recent call last):
  File "<console>", line 1, in <module>
  File "/Volumes/nexB/repos/scancode.io/lib/python3.9/site-packages/packageurl/__init__.py", line 354, in from_string
    raise ValueError(
ValueError: purl is missing the required "pkg" scheme component: {repr(purl)}.

The packageurl raise a ValueError in that case, that's the exception we want to catch.
You code could become:

try:
    purl = PackageURL.from_string(package_name)
except ValueError:
    purl = None

if purl:
    packages = ...
else:
    packages = ...

return list(packages)

This way, this are properly grouped, the code is more readable:

  1. we deal with the purl
  2. we prepare a queryset based on the available data
  3. we return the fetched objects

keeping the try focused on its sole purpose: the purl test?

Yes, bottom line is to always try to keep you try block as focused as possible.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Far out. Thank you for the detailed explanation @tdruez . I'll tackle this in the A.M. 👍

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See new issue #876

return resolved_vuln, unresolved_vuln

def _related_packages(self):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be moved to the models.py as a method on the Package QuerySet.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tdruez What's the reason to do this, and how would I implement?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've not yet had a chance to give this a careful look. If our schedule permits (we don't have much overlap, and it might be another day before I could followup again if need be), I'd prefer to do that first and give it a try, although my familiarity with Django models is still rather shallow.

Re the rationale -- I modelled def _related_packages(self) on def _package_vulnerabilities(self), which is just above it inside class PackageUpdate(UpdateView). Why does one of these belong in models.py as a method on the Package QuerySet while the other belongs in the view?

And another newb question: does adding it as a QuerySet method just mean adding it as a method to the model, e.g, def related_packages(self, package_url) -- no preceding _ -- and figuring out how to pass this to the view/template?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the reason to do this, and how would I implement?

To keep thing properly organized, re-usable, and easier to test.

In a Django app, code related to the database transaction belongs in the models.

Why does one of these belong in models.py as a method on the Package QuerySet while the other belongs in the view?

I don't see any QuerySet logic in _package_vulnerabilities.

And another newb question: does adding it as a QuerySet method just mean adding it as a method to the model, e.g, def related_packages(self, package_url) -- no preceding _ -- and figuring out how to pass this to the view/template?

Not exactly, it means adding this on the QuerySet class associated with a model.

Some examples:
https://github.com/nexB/scancode.io/blob/main/scanpipe/models.py#L1215
https://github.com/nexB/scancode.io/blob/main/scanpipe/models.py#L960

The first step is to add a QuerySet class to your Package model:

class PackageQuerySet(models.QuerySet):    

You can then start to break your various filters and annotation into reusable methods on the PackageQuerySet class:

return list(
            models.Package.objects.all()
            .filter(
                Q(
                    type=purl.type,
                    namespace=purl.namespace,
                    name=purl.name,
                    subpath=purl.subpath,
                    qualifiers=purl.qualifiers,
                )
            )
            .order_by("version")
            .annotate(
                vulnerability_count=Count(
                    "vulnerabilities",
                    filter=Q(packagerelatedvulnerability__fix=False),
                ),
                # TODO: consider renaming to fixed in the future
                patched_vulnerability_count=Count(
                    "vulnerabilities",
                    filter=Q(packagerelatedvulnerability__fix=True),
                ),
            )
            .prefetch_related()
        )

Could become:

class PackageQuerySet(models.QuerySet): 
    def for_package_url(self, purl):
        return self.filter(
                type=purl.type,
                namespace=purl.namespace,
                name=purl.name,
                subpath=purl.subpath,
                qualifiers=purl.qualifiers,
        )

    def with_vulnerability_counts(self):
        return self.annotate(
                vulnerability_count=Count(
                    "vulnerabilities",
                    filter=Q(packagerelatedvulnerability__fix=False),
                ),
                patched_vulnerability_count=Count(
                    "vulnerabilities",
                    filter=Q(packagerelatedvulnerability__fix=True),
                ),
            )

Then on the view side, you have something much more readable such as:

return list(
    models.Package.objects
    .for_package_url(purl)
    .order_by("version")
    .with_vulnerability_counts()
    .prefetch_related()
)

Add this on your Package model before the "Meta" class and after all fields, such as https://github.com/nexB/scancode.io/blob/main/scanpipe/models.py#L1469

objects = PackageQuerySet.as_manager()

You now have the database logic organized in the models, you can re-use the count annotations without copy pasting the code into other views, and you view logic is more focused and readable.

Note: The code above is not tested, simply here to illustrate, it'll have to be adapted and refined.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See new issue #880

Comment on lines +67 to +69
"package_search": "The VCIO DB does not contain a record of the package you entered -- "
+ request.GET["name"]
+ ".",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use f-strings for better readability:

search_string = request.GET["name"]
"package_search": f"The VCIO DB does not contain a record of the package you entered: {search_string}."

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See new issue #877

Reference: #798

Signed-off-by: John M. Horan [email protected]
@johnmhoran
Copy link
Member Author

@tdruez Re my using too many style attributes, I'll do my best to replace these with Bulma classes as you requested, but it will take a fair amount of time to figure out what the Bulma version is of a specific display style I've fine-tuned, and not all likely are covered by the class choices provided by Bulma. Working on this now.

purl = PackageURL.from_string(package_name)
return list(
models.Package.objects.all()
.filter(Q(type=purl.type, name=purl.name, version=purl.version))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Q object is not needed here, it is only required for complex query such as applying OR between filters such as https://github.com/nexB/vulnerablecode/pull/813/files#diff-3e8fc96d993d0bd8585642f68239732a30a19b7ec100a36ee5d21e209625ff81R202.
This comment applies to all the place you have a single Q in your filter().

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See new issue #878

try:
purl = PackageURL.from_string(package_name)
return list(
models.Package.objects.all()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

.all() is not needed if you call .filter() after.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See new issue #879

.filter(name__icontains=package_name, type__icontains=package_type)
.order_by("type", "namespace", "name", "version", "subpath", "qualifiers")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is also a good candidate to move on the PackageQuerySet class:

def order_by_purl(self):
    self.order_by("type", "namespace", "name", "version", "subpath", "qualifiers")

Then on the view side:

models.Package.objects.[...].order_by_purl()

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See new issue #880

@tdruez
Copy link
Contributor

tdruez commented Aug 17, 2022

  • Fix the labels and wording case consistency

johnmhoran added a commit that referenced this pull request Aug 17, 2022
Reference: #798
Reference: #851
Reference: #813

Signed-off-by: John M. Horan <[email protected]>
@pombredanne pombredanne changed the title Start new UI, add session variables for temporary API portion Modernize UI #798 Aug 18, 2022
johnmhoran added a commit that referenced this pull request Aug 20, 2022
johnmhoran added a commit that referenced this pull request Aug 25, 2022
Reference: #813

- Also deleted several model-diagram PNGs.

Signed-off-by: John M. Horan <[email protected]>
@pombredanne pombredanne removed this from the v30.0 milestone Aug 30, 2022
@pombredanne pombredanne merged commit 773064e into main Sep 2, 2022
@pombredanne pombredanne deleted the 798-new-ui branch September 2, 2022 08:13
TG1999 pushed a commit to TG1999/vulnerablecode that referenced this pull request Sep 5, 2022
TG1999 pushed a commit to TG1999/vulnerablecode that referenced this pull request Sep 5, 2022
TG1999 pushed a commit to TG1999/vulnerablecode that referenced this pull request Sep 5, 2022


Reference: aboutcode-org#813

- Also deleted several model-diagram PNGs.

Signed-off-by: John M. Horan <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants