Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions templates/store/snap-details/_details.html
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,15 @@ <h3 class="p-muted-heading">Last updated</h3>
<li>{{ updates[1]["released-at-display"] }} - <small>{{ updates[1]["track"] }}/{{ updates[1]["risk"] }}</small></li>
{% endif %}
</ul>
{% if old_snap_info %}
<div class="p-notification--caution is-borderless">
<div class="p-notification__content">
<p class="p-notification__message">
This snap hasn't been updated in a while. It might be unmaintained and have stability or security issues.
</p>
</div>
</div>
{% endif %}
<hr class="p-rule--muted">
{% endif %}

Expand Down
69 changes: 69 additions & 0 deletions tests/store/tests_public_logic.py
Original file line number Diff line number Diff line change
Expand Up @@ -482,3 +482,72 @@ def test_get_last_updated_versions(self):
self.assertEqual(
result, [latest_edge["channel"], latest_stable["channel"]]
)

def test_is_snap_old_empty_date(self):
"""Test that empty or None date returns not old"""
result = logic.is_snap_old(None)
self.assertFalse(result)

result = logic.is_snap_old("")
self.assertFalse(result)

def test_is_snap_old_invalid_date(self):
"""Test that invalid date returns not old"""
result = logic.is_snap_old("invalid-date")
self.assertFalse(result)

@freeze_time("2024-01-01")
def test_is_snap_old_recent_snap(self):
"""Test that a recently updated snap is not considered old"""
recent_date = "2023-06-01T10:00:00Z"
result = logic.is_snap_old(recent_date)
self.assertFalse(result)

@freeze_time("2024-01-02T10:00:00Z")
def test_is_snap_old_exactly_two_years(self):
Comment thread
gajeshbhat marked this conversation as resolved.
"""Test that a snap updated exactly 2 years ago is considered old"""
old_date = "2022-01-02T10:00:00Z" # Exactly 2 years ago
result = logic.is_snap_old(old_date)
self.assertTrue(result)

@freeze_time("2024-01-02")
def test_is_snap_old_very_old_snap(self):
"""Test that a very old snap is considered old"""
very_old_date = "2020-01-01T10:00:00Z"
result = logic.is_snap_old(very_old_date)
self.assertTrue(result)

@freeze_time("2024-01-01")
def test_is_snap_old_just_under_two_years(self):
"""Test that a snap updated just under 2 years ago is not old"""
almost_old_date = "2022-02-01T10:00:00Z"
result = logic.is_snap_old(almost_old_date)
self.assertFalse(result)

@freeze_time("2024-01-02")
def test_is_snap_old_custom_threshold(self):
"""Test that custom threshold works correctly"""
date_one_year_ago = "2022-12-01T10:00:00Z" # Over 1 year ago

# With default threshold (2 years), should not be old
result = logic.is_snap_old(date_one_year_ago)
self.assertFalse(result)

# With custom threshold (1 year), should be old
result = logic.is_snap_old(date_one_year_ago, old_threshold_years=1)
self.assertTrue(result)

def test_is_snap_old_different_date_formats(self):
"""Test that different ISO date formats are handled correctly"""
# Test with different timezone formats
dates_to_test = [
"2020-01-01T10:00:00Z",
"2020-01-01T10:00:00+00:00",
"2020-01-01T10:00:00.000Z",
"2020-01-01T10:00:00.123456+00:00",
]

for date_str in dates_to_test:
with freeze_time("2024-01-02"):
result = logic.is_snap_old(date_str)
self.assertTrue(result, f"Failed for date format: {date_str}")
31 changes: 31 additions & 0 deletions webapp/store/logic.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

import humanize
from dateutil import parser
from dateutil.relativedelta import relativedelta
from webapp import helpers


Expand Down Expand Up @@ -203,6 +204,36 @@ def convert_date(date_to_convert):
return date_parsed.strftime("%-d %B %Y")


def is_snap_old(last_updated_date, old_threshold_years=2.0):
Comment thread
edisile marked this conversation as resolved.
"""Check if a snap is considered 'old' based on its last update date

A snap is considered old if it hasn't been updated in the specified
number of years (default: 2 years).

:param last_updated_date: The last updated date string in ISO format
:param old_threshold_years: Number of years to consider a snap old
(default: 2)
:returns: True if snap is old, False otherwise
"""
if not last_updated_date:
return False

try:
date_parsed = parser.parse(last_updated_date)
if date_parsed.tzinfo is None:
date_parsed = date_parsed.replace(tzinfo=datetime.timezone.utc)

now = datetime.datetime.now(datetime.timezone.utc)

delta = relativedelta(now, date_parsed)
years_since_update = delta.years

return years_since_update >= old_threshold_years
except (ValueError, TypeError):
# If we can't parse the date, assume it's not old
return False


categories_list = [
"development",
"games",
Expand Down
19 changes: 19 additions & 0 deletions webapp/store/snap_details_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,24 @@ def _get_context_snap_details(snap_name, supported_architectures=None):
lowest_risk_available,
supported_architectures,
)

# Determine the most recent update date from updates tuple
# updates[0] is the stable channel, updates[1] is the most
# recent non-stable
most_recent_update = None
if updates[0] and updates[1]:
# Compare both and use the most recent
date_0 = updates[0].get("released-at")
date_1 = updates[1].get("released-at")
if date_0 and date_1:
most_recent_update = max(date_0, date_1)
else:
most_recent_update = date_0 or date_1
elif updates[0]:
most_recent_update = updates[0].get("released-at")
elif updates[1]:
most_recent_update = updates[1].get("released-at")

binary_filesize = latest_channel["download"]["size"]

# filter out banner and banner-icon images from screenshots
Expand Down Expand Up @@ -191,6 +209,7 @@ def _get_context_snap_details(snap_name, supported_architectures=None):
"filesize": humanize.naturalsize(binary_filesize),
"last_updated": logic.convert_date(last_updated),
"last_updated_raw": last_updated,
"old_snap_info": logic.is_snap_old(most_recent_update),
"is_users_snap": is_users_snap,
"unlisted": details.get("snap", {}).get("unlisted", False),
"developer": developer,
Expand Down