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
23 changes: 21 additions & 2 deletions apps/reports/graphql/filters.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
from concurrent.futures import ThreadPoolExecutor
from uuid import UUID

import strawberry
import strawberry_django
from django.db.models import Q

from apps.reports.ai_features.search_docs import SearchReports
from apps.reports.models import (
DocumentExtraction,
DocumentExtractionStatus,
Expand All @@ -15,6 +19,11 @@
from apps.reports.models import ReportType as ReportTypeEnum


def _get_ranked_ids(value: str) -> list[UUID]:
ranked_reports = SearchReports(query=value).rank_reports()
return [r.id for r in ranked_reports]


@strawberry_django.filters.filter(Link, lookups=True)
class LinkFilter:
id: strawberry.ID | None = strawberry.UNSET
Expand Down Expand Up @@ -43,10 +52,20 @@ class ReportFilter:
thematic_area_id: strawberry.ID | None = strawberry.UNSET
disaster_type: str | None = strawberry.UNSET
report_type: ReportTypeEnum | None = strawberry.UNSET
title: strawberry.auto

@strawberry_django.filter_field
def search(self, value: str, prefix: str) -> Q:
return Q(title__icontains=value)
def search(self, queryset, value: str, prefix: str):
if not value or not value.strip():
return Q()

with ThreadPoolExecutor(max_workers=1) as executor:
ids = executor.submit(_get_ranked_ids, value).result()

if not ids:
return Q(pk__in=[]) # if nothing matches

return Q(id__in=ids)

@strawberry_django.filter_field
def regions(self, queryset, value: list[strawberry.ID], prefix: str) -> Q:
Expand Down
2 changes: 1 addition & 1 deletion apps/reports/graphql/inputs.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ class ReportCreateInput:
iframe_url: str | None = strawberry.UNSET
visibility: ReportVisibility = ReportVisibility.PUBLIC
report_type: ReportType = ReportType.REPORT
thematic_area: strawberry.ID
thematic_area: strawberry.ID | None = strawberry.UNSET
region: strawberry.ID | None = strawberry.UNSET
disaster_type: str | None = strawberry.UNSET
owner: str | None = strawberry.UNSET
Expand Down
3 changes: 1 addition & 2 deletions apps/reports/graphql/queries.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from main.graphql.permissions import IsAuthenticated

from .filters import LinkFilter, ReportFilter, ReportSummaryFilter, ThematicAreaFilter
from .orders import LinkOrder, ReportOrder
from .orders import LinkOrder
from .types import LinkType, ReportSummaryType, ReportType, ThematicAreaType


Expand All @@ -17,7 +17,6 @@ class Query:

reports: OffsetPaginated[ReportType] = strawberry_django.offset_paginated(
filters=ReportFilter,
order=ReportOrder,
)

report: ReportType = strawberry_django.field()
Expand Down
19 changes: 19 additions & 0 deletions apps/reports/migrations/0010_alter_report_thematic_area.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Generated by Django 6.0.3 on 2026-06-30 08:17

import django.db.models.deletion
from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('reports', '0009_create_hnsw_index'),
]

operations = [
migrations.AlterField(
model_name='report',
name='thematic_area',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='reports', to='reports.thematicarea'),
),
]
2 changes: 2 additions & 0 deletions apps/reports/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,8 @@ class Report(BaseModel):
ThematicArea,
on_delete=models.PROTECT,
related_name="reports",
blank=True,
null=True,
)
region = models.ForeignKey(
"geo.AdminArea",
Expand Down
2 changes: 1 addition & 1 deletion apps/reports/tests/queries_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ class TestReportQueries(TestCase):
class Query:
REPORTS = """
query Reports($pagination: OffsetPaginationInput, $filters: ReportFilter) {
reports(pagination: $pagination, filters: $filters, order: {createdAt: DESC}) {
reports(pagination: $pagination, filters: $filters) {
totalCount
pageInfo { offset limit }
results {
Expand Down
59 changes: 51 additions & 8 deletions schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -892,7 +892,7 @@ type Query {
user(id: ID!): UserType!
me: UserMeType
thematicAreas(pagination: OffsetPaginationInput, filters: ThematicAreaFilter): ThematicAreaTypeOffsetPaginated!
reports(pagination: OffsetPaginationInput, filters: ReportFilter, order: ReportOrder): ReportTypeOffsetPaginated!
reports(pagination: OffsetPaginationInput, filters: ReportFilter): ReportTypeOffsetPaginated!
report(id: ID!): ReportType!
publicLinks(pagination: OffsetPaginationInput, filters: LinkFilter, order: LinkOrder): LinkTypeOffsetPaginated!
internalLinks(pagination: OffsetPaginationInput, filters: LinkFilter, order: LinkOrder): LinkTypeOffsetPaginated!
Expand Down Expand Up @@ -933,7 +933,7 @@ input ReportCreateInput {
iframeUrl: String
visibility: ReportVisibility! = PUBLIC
reportType: ReportTypeEnum! = REPORT
thematicArea: ID!
thematicArea: ID
region: ID
disasterType: String
owner: String
Expand All @@ -958,6 +958,7 @@ input ReportFilter {
thematicAreaId: ID
disasterType: String
reportType: ReportTypeEnum
title: StrFilterLookup
AND: ReportFilter
OR: ReportFilter
NOT: ReportFilter
Expand All @@ -966,12 +967,6 @@ input ReportFilter {
regions: [ID!]
}

input ReportOrder @oneOf {
title: Ordering
createdAt: Ordering
publishedAt: Ordering
}

"""
Tracks the AI extraction lifecycle for a FILE-type Report.

Expand Down Expand Up @@ -1095,6 +1090,54 @@ enum ReportVisibility {

union ResetUserPasswordPayload = UserTypeMutationResponseType | OperationInfo

input StrFilterLookup {
"""Exact match. Filter will be skipped on `null` value"""
exact: String

"""Assignment test. Filter will be skipped on `null` value"""
isNull: Boolean

"""
Exact match of items in a given list. Filter will be skipped on `null` value
"""
inList: [String!]

"""Case-insensitive exact match. Filter will be skipped on `null` value"""
iExact: String

"""
Case-sensitive containment test. Filter will be skipped on `null` value
"""
contains: String

"""
Case-insensitive containment test. Filter will be skipped on `null` value
"""
iContains: String

"""Case-sensitive starts-with. Filter will be skipped on `null` value"""
startsWith: String

"""Case-insensitive starts-with. Filter will be skipped on `null` value"""
iStartsWith: String

"""Case-sensitive ends-with. Filter will be skipped on `null` value"""
endsWith: String

"""Case-insensitive ends-with. Filter will be skipped on `null` value"""
iEndsWith: String

"""
Case-sensitive regular expression match. Filter will be skipped on `null` value
"""
regex: String

"""
Case-insensitive regular expression match. Filter will be skipped on `null` value
"""
iRegex: String
}

input TeamCreateInput {
name: String!
description: String
Expand Down