-
Notifications
You must be signed in to change notification settings - Fork 210
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
Modernize UI #798 #813
Conversation
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]>
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]>
…lts pages Reference: #798 Signed-off-by: John M. Horan <[email protected]>
Reference: #798 Signed-off-by: John M. Horan [email protected]
References: #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]>
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
JMH: Done -- hidden using debug setting in navbar (affects all 5 primary pages). third-party JS
JMH: Done -- downloaded and added Package search
JMH: Both are done. Vulnerabilities search
JMH: Done.
JMH: Both are done. for later:
|
Here are a few more suggestions:
|
Reference: #798 Signed-off-by: John M. Horan <[email protected]>
|
Reference: #798 Signed-off-by: John M. Horan <[email protected]>
There was a problem hiding this 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/
# Check whether the input value is a syntactically-correct purl | ||
try: | ||
purl = PackageURL.from_string(package_name) | ||
return list( |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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:
- we deal with the purl
- we prepare a queryset based on the available data
- 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.
There was a problem hiding this comment.
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. 👍
There was a problem hiding this comment.
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): |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See new issue #880
"package_search": "The VCIO DB does not contain a record of the package you entered -- " | ||
+ request.GET["name"] | ||
+ ".", |
There was a problem hiding this comment.
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}."
There was a problem hiding this comment.
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]
@tdruez Re my using too many |
purl = PackageURL.from_string(package_name) | ||
return list( | ||
models.Package.objects.all() | ||
.filter(Q(type=purl.type, name=purl.name, version=purl.version)) |
There was a problem hiding this comment.
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().
There was a problem hiding this comment.
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() |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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") |
There was a problem hiding this comment.
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()
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See new issue #880
|
Reference: #798 Reference: #851 Reference: #813 Signed-off-by: John M. Horan <[email protected]>
Reference: #798 Reference: #813 Signed-off-by: John M. Horan <[email protected]>
Reference: #813 - Also deleted several model-diagram PNGs. Signed-off-by: John M. Horan <[email protected]>
Reference: aboutcode-org#798 Reference: aboutcode-org#851 Reference: aboutcode-org#813 Signed-off-by: John M. Horan <[email protected]>
Reference: aboutcode-org#798 Reference: aboutcode-org#813 Signed-off-by: John M. Horan <[email protected]>
Reference: aboutcode-org#813 - Also deleted several model-diagram PNGs. Signed-off-by: John M. Horan <[email protected]>
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]