diff --git a/dandiapi/api/migrations/0031_applicationstats_asset_count.py b/dandiapi/api/migrations/0031_applicationstats_asset_count.py new file mode 100644 index 000000000..fc45244f7 --- /dev/null +++ b/dandiapi/api/migrations/0031_applicationstats_asset_count.py @@ -0,0 +1,28 @@ +# Generated by Django 5.2.7 on 2026-03-25 14:12 +from __future__ import annotations + +from django.db import migrations, models + + +def populate_asset_count(apps, schema_editor): + Asset = apps.get_model('api.Asset') + ApplicationStats = apps.get_model('api.ApplicationStats') + + asset_count = Asset.objects.filter(versions__isnull=False).distinct().count() + + ApplicationStats.objects.update(asset_count=asset_count) + + +class Migration(migrations.Migration): + dependencies = [ + ('api', '0030_alter_asset_path'), + ] + + operations = [ + migrations.AddField( + model_name='applicationstats', + name='asset_count', + field=models.PositiveBigIntegerField(default=0), + ), + migrations.RunPython(code=populate_asset_count, reverse_code=migrations.RunPython.noop), + ] diff --git a/dandiapi/api/models/asset.py b/dandiapi/api/models/asset.py index d8c2c3646..5f3764f6a 100644 --- a/dandiapi/api/models/asset.py +++ b/dandiapi/api/models/asset.py @@ -353,3 +353,7 @@ def total_size(cls): or 0 for cls in (AssetBlob, ZarrArchive) ) + + @classmethod + def total_count(cls): + return cls.objects.filter(versions__isnull=False).distinct().count() diff --git a/dandiapi/api/models/stats.py b/dandiapi/api/models/stats.py index 80c30ee33..0282e3560 100644 --- a/dandiapi/api/models/stats.py +++ b/dandiapi/api/models/stats.py @@ -11,6 +11,7 @@ class ApplicationStats(models.Model): # noqa: DJ008 published_dandiset_count = models.PositiveIntegerField() user_count = models.PositiveIntegerField() size = models.PositiveBigIntegerField() + asset_count = models.PositiveBigIntegerField(default=0) class Meta: verbose_name_plural = 'Application Stats' diff --git a/dandiapi/api/tasks/scheduled.py b/dandiapi/api/tasks/scheduled.py index 82b369a22..d5dfce620 100644 --- a/dandiapi/api/tasks/scheduled.py +++ b/dandiapi/api/tasks/scheduled.py @@ -153,6 +153,7 @@ def compute_application_stats() -> None: published_dandiset_count=Dandiset.published_count(), user_count=User.objects.filter(metadata__status=UserMetadata.Status.APPROVED).count(), size=Asset.total_size(), + asset_count=Asset.total_count(), ) diff --git a/dandiapi/api/tests/test_stats.py b/dandiapi/api/tests/test_stats.py index 060d8109c..300e41931 100644 --- a/dandiapi/api/tests/test_stats.py +++ b/dandiapi/api/tests/test_stats.py @@ -14,6 +14,7 @@ def test_stats_baseline(api_client): 'published_dandiset_count': 0, 'user_count': 0, 'size': 0, + 'asset_count': 0, } @@ -77,6 +78,20 @@ def test_stats_asset(api_client, version, asset): stats = api_client.get('/api/stats/').data assert stats['size'] == asset.size + assert stats['asset_count'] == 1 + + +@pytest.mark.django_db +def test_stats_asset_count(api_client, version, asset_factory): + """Test that asset_count reflects the number of distinct assets in any version.""" + assets = [asset_factory() for _ in range(3)] + for a in assets: + version.assets.add(a) + + compute_application_stats() + + stats = api_client.get('/api/stats/').data + assert stats['asset_count'] == 3 @pytest.mark.django_db diff --git a/web/src/views/HomeView/StatsBar.vue b/web/src/views/HomeView/StatsBar.vue index 86586e5b0..652f35ca4 100644 --- a/web/src/views/HomeView/StatsBar.vue +++ b/web/src/views/HomeView/StatsBar.vue @@ -10,8 +10,8 @@ > [ { @@ -43,6 +44,7 @@ const stats = computed(() => [ description: 'A DANDI dataset including files and dataset-level metadata', href: '/dandiset', }, + { name: 'files', value: files.value }, { name: 'users', value: users.value }, { name: 'total data size', value: filesize(size.value, { round: 0, base: 10, standard: 'iec' }) }, ]); @@ -52,5 +54,6 @@ dandiRest.stats().then((data) => { dandisets.value = data.dandiset_count; users.value = data.user_count; size.value = data.size; + files.value = data.asset_count; });