Skip to content

Commit b3d5a33

Browse files
authored
fix(Hackathon): Platform Hub: inverted stale counts (#6715)
1 parent 0ac6208 commit b3d5a33

3 files changed

Lines changed: 43 additions & 60 deletions

File tree

api/platform_hub/services.py

Lines changed: 27 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
UsageTrendData,
3131
)
3232
from projects.models import Project
33+
from projects.tags.models import TagType
3334
from users.models import FFAdminUser
3435

3536
logger = structlog.get_logger("platform_hub")
@@ -355,29 +356,34 @@ def get_organisation_metrics(
355356
return results
356357

357358

358-
def _get_stale_flag_counts_by_org(
359+
def _get_stale_flag_counts_by_project(
359360
organisations: QuerySet[Organisation],
360361
) -> dict[int, int]:
361-
"""Return a mapping of organisation_id -> stale flag count."""
362-
projects = Project.objects.filter(
363-
organisation__in=organisations,
364-
).values("id", "organisation_id", "stale_flags_limit_days")
362+
"""Return a mapping of project_id -> stale flag count."""
363+
return dict(
364+
Feature.objects.filter(
365+
project__organisation__in=organisations,
366+
tags__type=TagType.STALE,
367+
)
368+
.values("project_id")
369+
.annotate(count=Count("id", distinct=True))
370+
.values_list("project_id", "count")
371+
)
365372

366-
result: dict[int, int] = defaultdict(int)
367-
for project in projects:
368-
cutoff = timezone.now() - timedelta(days=project["stale_flags_limit_days"])
369373

370-
stale_count = (
371-
Feature.objects.filter(project_id=project["id"])
372-
.exclude(
373-
feature_states__updated_at__gte=cutoff,
374-
)
375-
.distinct()
376-
.count()
374+
def _get_stale_flag_counts_by_org(
375+
organisations: QuerySet[Organisation],
376+
) -> dict[int, int]:
377+
"""Return a mapping of organisation_id -> stale flag count."""
378+
return dict(
379+
Feature.objects.filter(
380+
project__organisation__in=organisations,
381+
tags__type=TagType.STALE,
377382
)
378-
result[project["organisation_id"]] += stale_count
379-
380-
return result
383+
.values("project__organisation_id")
384+
.annotate(count=Count("id", distinct=True))
385+
.values_list("project__organisation_id", "count")
386+
)
381387

382388

383389
def _get_integration_counts_by_org(
@@ -501,27 +507,21 @@ def get_stale_flags_per_project(
501507
.values_list("project_id", "count")
502508
)
503509

510+
stale_counts_by_project = _get_stale_flag_counts_by_project(organisations)
511+
504512
results: list[StaleFlagsPerProjectData] = []
505513
for project in projects:
506514
total_flags = flag_counts_by_project.get(project.id, 0)
507515
if total_flags == 0:
508516
continue
509517

510-
cutoff = timezone.now() - timedelta(days=project.stale_flags_limit_days)
511-
stale_flags = (
512-
Feature.objects.filter(project=project)
513-
.exclude(feature_states__updated_at__gte=cutoff)
514-
.distinct()
515-
.count()
516-
)
517-
518518
results.append(
519519
StaleFlagsPerProjectData(
520520
organisation_id=project.organisation_id,
521521
organisation_name=project.organisation.name,
522522
project_id=project.id,
523523
project_name=project.name,
524-
stale_flags=stale_flags,
524+
stale_flags=stale_counts_by_project.get(project.id, 0),
525525
total_flags=total_flags,
526526
)
527527
)

api/tests/unit/platform_hub/test_services.py

Lines changed: 13 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,14 @@
77
from pytest_mock import MockerFixture
88

99
from environments.models import Environment
10-
from features.models import Feature, FeatureState
10+
from features.models import Feature
1111
from organisations.models import (
1212
Organisation,
1313
OrganisationSubscriptionInformationCache,
1414
)
1515
from platform_hub import services
1616
from projects.models import Project
17+
from projects.tags.models import Tag, TagType
1718
from users.models import FFAdminUser
1819

1920

@@ -418,9 +419,7 @@ def test_get_organisation_metrics__query_count_stable_across_projects(
418419
result = services.get_organisation_metrics(orgs)
419420

420421
# Then — query count should not grow per project.
421-
# The stale flag count still runs one query per project, so we allow
422-
# exactly +1 for the additional project's stale flag query.
423-
assert len(ctx_two) <= baseline + 1
422+
assert len(ctx_two) <= baseline
424423
assert len(result) == 1
425424
assert result[0]["project_count"] == 2
426425

@@ -456,33 +455,29 @@ def test_get_stale_flags_per_project__query_count_stable_across_projects(
456455
with CaptureQueriesContext(connection) as ctx_two:
457456
result = services.get_stale_flags_per_project(orgs)
458457

459-
# Then — the flag_counts_by_project query is batched, so only +1
460-
# for the additional project's stale flag query.
461-
assert len(ctx_two) <= baseline + 1
458+
# Then — query count should not grow per project.
459+
assert len(ctx_two) <= baseline
462460
assert len(result) == 2
463461

464462

465-
def test_get_stale_flags_per_project__different_thresholds__counts_correctly(
463+
def test_get_stale_flags_per_project__stale_tagged_feature__counts_correctly(
466464
platform_hub_organisation: Organisation,
467465
platform_hub_project: Project,
468466
platform_hub_environment: Environment,
469467
platform_hub_admin_user: FFAdminUser,
470468
) -> None:
471-
# Given — create a feature with a feature state updated long ago
469+
# Given — create a feature tagged as stale
472470
feature = Feature.objects.create(
473471
name="stale_feature",
474472
project=platform_hub_project,
475473
)
476-
fs = FeatureState.objects.get(
477-
feature=feature,
478-
environment=platform_hub_environment,
474+
stale_tag = Tag.objects.create(
475+
label="Stale",
476+
project=platform_hub_project,
477+
type=TagType.STALE,
478+
is_system_tag=True,
479479
)
480-
# Make the feature state old
481-
old_date = timezone.now() - timedelta(days=60)
482-
FeatureState.objects.filter(id=fs.id).update(updated_at=old_date)
483-
484-
platform_hub_project.stale_flags_limit_days = 30
485-
platform_hub_project.save()
480+
feature.tags.add(stale_tag)
486481

487482
orgs = Organisation.objects.filter(id=platform_hub_organisation.id)
488483

frontend/web/components/pages/admin-dashboard/components/StaleFlagsTable.tsx

Lines changed: 3 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { FC, useMemo } from 'react'
1+
import { FC } from 'react'
22
import { StaleFlagsPerProject } from 'common/types/responses'
33
import { SortOrder } from 'common/types/requests'
44
import PanelSearch from 'components/PanelSearch'
@@ -8,18 +8,6 @@ interface StaleFlagsTableProps {
88
}
99

1010
const StaleFlagsTable: FC<StaleFlagsTableProps> = ({ data }) => {
11-
// TODO: The backend returns non-stale (active) flag counts in the stale_flags
12-
// field. This should be fixed in the backend (platform_hub/services.py) to
13-
// return the actual stale count, and this inversion removed.
14-
const items = useMemo(
15-
() =>
16-
data.map((row) => ({
17-
...row,
18-
stale_flags: row.total_flags - row.stale_flags,
19-
})),
20-
[data],
21-
)
22-
2311
return (
2412
<PanelSearch
2513
className='no-pad'
@@ -44,8 +32,8 @@ const StaleFlagsTable: FC<StaleFlagsTableProps> = ({ data }) => {
4432
</div>
4533
}
4634
id='stale-flags-table'
47-
items={items}
48-
paging={items.length > 10 ? { goToPage: 1, pageSize: 10 } : undefined}
35+
items={data}
36+
paging={data.length > 10 ? { goToPage: 1, pageSize: 10 } : undefined}
4937
renderRow={(row: StaleFlagsPerProject) => (
5038
<div
5139
className='flex-row list-item'

0 commit comments

Comments
 (0)