Skip to content

Commit adb961a

Browse files
authored
NAS-133590 / 25.10 / Allow sorting apps by popularity (#16496)
This PR adds another attribute to `app.available` / `app.latest` which shows how popular an app is. Higher the value of this attribute means it is more popular in comparison to apps which have a lower value or no value present.
1 parent ec28def commit adb961a

File tree

3 files changed

+28
-0
lines changed

3 files changed

+28
-0
lines changed

src/middlewared/middlewared/api/v25_10_0/app.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -365,6 +365,7 @@ class AppAvailableResponse(CatalogAppInfo):
365365
catalog: NonEmptyString
366366
installed: bool
367367
train: NonEmptyString
368+
popularity_rank: int | None
368369

369370

370371
class AppCategoriesArgs(BaseModel):

src/middlewared/middlewared/plugins/catalog/apps.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ def available(self, filters, options):
3535
if not self.middleware.call_sync('catalog.synced'):
3636
self.middleware.call_sync('catalog.sync').wait_sync()
3737

38+
apps_popularity = self.middleware.call_sync('catalog.popularity_cache')
3839
results = []
3940
installed_apps = [
4041
(app['metadata']['name'], app['metadata']['train'])
@@ -51,6 +52,7 @@ def available(self, filters, options):
5152
'catalog': catalog['label'],
5253
'installed': (app_data['name'], train) in installed_apps,
5354
'train': train,
55+
'popularity_rank': apps_popularity.get(train, {}).get(app_data['name']),
5456
**app_data,
5557
})
5658

src/middlewared/middlewared/plugins/catalog/sync.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import aiohttp
2+
13
from middlewared.api import api_method
24
from middlewared.api.current import CatalogSyncArgs, CatalogSyncResult
35
from middlewared.service import job, private, Service
@@ -6,10 +8,32 @@
68
from .utils import OFFICIAL_LABEL, OFFICIAL_CATALOG_REPO, OFFICIAL_CATALOG_BRANCH
79

810

11+
STATS_URL = 'https://telemetry.sys.truenas.net/apps/truenas-apps-stats.json'
12+
13+
914
class CatalogService(Service):
1015

16+
POPULARITY_INFO = {}
1117
SYNCED = False
1218

19+
@private
20+
async def update_popularity_cache(self):
21+
async with aiohttp.ClientSession(timeout=aiohttp.ClientTimeout(total=10)) as session:
22+
try:
23+
async with session.get(STATS_URL) as response:
24+
response.raise_for_status()
25+
self.POPULARITY_INFO = {
26+
k.lower(): v for k, v in (await response.json()).items()
27+
# Making sure we have a consistent format as for trains we see capitalized
28+
# entries in the file
29+
}
30+
except Exception as e:
31+
self.logger.error('Failed to fetch popularity stats for apps: %r', e)
32+
33+
@private
34+
async def popularity_cache(self):
35+
return self.POPULARITY_INFO
36+
1337
@private
1438
async def synced(self):
1539
return self.SYNCED
@@ -38,6 +62,7 @@ async def sync(self, job):
3862
'retrieve_all_trains': True,
3963
'trains': [],
4064
})
65+
await self.update_popularity_cache()
4166
except Exception as e:
4267
await self.middleware.call(
4368
'alert.oneshot_create', 'CatalogSyncFailed', {'catalog': OFFICIAL_LABEL, 'error': str(e)}

0 commit comments

Comments
 (0)