From df9fdb2ca62e498c8adb1df676778e354de2bdb7 Mon Sep 17 00:00:00 2001 From: sudan Date: Thu, 10 Aug 2023 16:11:54 +0545 Subject: [PATCH] Add query for Assessment registry Count - total assessment count - total stakeholder count - collection technique count - multisector and single sector assessment --- apps/assessment_registry/dashboard_schema.py | 153 +++++++++++++++---- apps/assessment_registry/filter_set.py | 28 ++++ apps/project/schema.py | 5 +- schema.graphql | 20 ++- 4 files changed, 168 insertions(+), 38 deletions(-) create mode 100644 apps/assessment_registry/filter_set.py diff --git a/apps/assessment_registry/dashboard_schema.py b/apps/assessment_registry/dashboard_schema.py index e9a1d47ccd..104af90b31 100644 --- a/apps/assessment_registry/dashboard_schema.py +++ b/apps/assessment_registry/dashboard_schema.py @@ -1,20 +1,45 @@ +import itertools import graphene -from .models import AssessmentRegistry, MethodologyAttribute +from dataclasses import dataclass +from collections import defaultdict +from deep.caches import CacheHelper +from django.db.models import Count +from django.db import models + +from utils.graphene.enums import EnumDescription from .enums import ( AssessmentRegistryCoordinationTypeEnum, AssessmentRegistryDataCollectionTechniqueTypeEnum, ) -from django.db.models import Count +from .models import AssessmentRegistry, MethodologyAttribute + + +def get_global_filters(_filter: dict, date_field="created_at"): + return { + f"{date_field}__gte": _filter["date_from"], + f"{date_field}__lte": _filter["date_to"], + } + + +@dataclass +class AssessmentDashboardStat: + cache_key: str + assessment_registry_qs: models.QuerySet + methodology_attribute_qs: models.QuerySet + + +class AssessmentDashboardFilterInputType(graphene.InputObjectType): + date_from = graphene.Date(required=True) + date_to = graphene.Date(required=True) class AssessmentCountType(graphene.ObjectType): coordinated_joint = graphene.Field(AssessmentRegistryCoordinationTypeEnum) - coordinated_joint_display = graphene.String() + coordinated_joint_display = EnumDescription(required=False) count = graphene.Int() def resolve_coordinated_joint_display(self, info): - return AssessmentRegistry.CoordinationType( - self.coordinated_joint).label + return AssessmentRegistry.CoordinationType(self.coordinated_joint).label class StakeholderCountType(graphene.ObjectType): @@ -24,10 +49,10 @@ class StakeholderCountType(graphene.ObjectType): class CollectionTechniqueCountType(graphene.ObjectType): data_collection_technique = graphene.Field( - AssessmentRegistryDataCollectionTechniqueTypeEnum + AssessmentRegistryDataCollectionTechniqueTypeEnum, required=True ) - data_collection_technique_display = graphene.String() - count = graphene.Int() + data_collection_technique_display = EnumDescription(required=False) + count = graphene.Int(required=True) def resolve_data_collection_technique_display(self, info): return MethodologyAttribute.CollectionTechniqueType( @@ -36,18 +61,64 @@ def resolve_data_collection_technique_display(self, info): class AssessmentDashboardStatisticsType(graphene.ObjectType): + total_assessment = graphene.Int(required=True) + total_stakeholder = graphene.Int(required=True) + total_collection_technique = graphene.Int(required=True) assessment_count = graphene.List(AssessmentCountType) stakeholder_count = graphene.List(StakeholderCountType) collection_technique_count = graphene.List(CollectionTechniqueCountType) - model = AssessmentRegistry + total_multisector_assessment = graphene.Int(required=True) + total_singlesector_assessment = graphene.Int(required=True) + + @staticmethod + def custom_resolver(info, _filter): + assessment_qs = AssessmentRegistry.objects.filter( + project=info.context.active_project, + **get_global_filters(_filter), + ) + methodology_attribute_qs = MethodologyAttribute.objects.filter( + assessment_registry__project_id=info.context.active_project, + **get_global_filters(_filter), + ) + cache_key = CacheHelper.generate_hash(_filter.__dict__) + return AssessmentDashboardStat( + cache_key=cache_key, + assessment_registry_qs=assessment_qs, + methodology_attribute_qs=methodology_attribute_qs, + ) + + @staticmethod + def resolve_total_assessment(root: AssessmentDashboardStat, info) -> int: + return root.assessment_registry_qs.count() - def resolve_assessment_count(self, info): + @staticmethod + def resolve_total_stakeholder(root: AssessmentDashboardStat, info) -> int: + # TODO: when stakeholder manage in a single field + qs = root.assessment_registry_qs.values_list( + "international_partners__title", + "donors__title", + "lead_organizations__title", + "national_partners__title", + "governments__title", + ) + return len(set(list(itertools.chain(*qs)))) + + @staticmethod + def resolve_total_collection_technique(root: AssessmentDashboardStat, info) -> int: + return ( + root.methodology_attribute_qs.values("data_collection_technique") + .distinct() + .count() + ) + + @staticmethod + def resolve_assessment_count(root: AssessmentDashboardStat, info): assessment = ( - self.model.objects.filter(project=info.context.active_project) - .values("coordinated_joint") + root.assessment_registry_qs.values("coordinated_joint") .annotate(count=Count("coordinated_joint")) .order_by("coordinated_joint") ) + return [ AssessmentCountType( coordinated_joint=assessment["coordinated_joint"], @@ -56,27 +127,35 @@ def resolve_assessment_count(self, info): for assessment in assessment ] - def resolve_stakeholder_count(self, info): - stakeholder = ( - self.model.objects.filter(project=info.context.active_project) - .values("lead_organizations", "lead_organizations__title") - .annotate(count=Count("lead_organizations")) - .order_by("lead_organizations") - ) + @staticmethod + def resolve_stakeholder_count(root: AssessmentDashboardStat, info): + # TODO: when stakeholder manage in a single field + stakeholder_counts = defaultdict(int) + organization_type_fields = [ + "lead_organizations__organization_type__title", + "international_partners__organization_type__title", + "donors__organization_type__title", + "national_partners__organization_type__title", + "governments__organization_type__title", + ] + + for field in organization_type_fields: + stakeholders = root.assessment_registry_qs.values(field) + for stakeholder in stakeholders: + if organization_type_title := stakeholder.get(field): + stakeholder_counts[organization_type_title] += 1 return [ StakeholderCountType( - stakeholder=stakeholder["lead_organizations__title"], - count=stakeholder["count"], + stakeholder=org_type_title, + count=count, ) - for stakeholder in stakeholder + for org_type_title, count in stakeholder_counts.items() ] - def resolve_collection_technique_count(self, info): + @staticmethod + def resolve_collection_technique_count(root: AssessmentDashboardStat, info): data_collection_technique = ( - MethodologyAttribute.objects.filter( - assessment_registry__project_id=info.context.active_project - ) - .values("data_collection_technique") + root.methodology_attribute_qs.values("data_collection_technique") .annotate(count=Count("data_collection_technique")) .order_by("data_collection_technique") ) @@ -88,11 +167,25 @@ def resolve_collection_technique_count(self, info): for technique in data_collection_technique ] + @staticmethod + def resolve_total_multisector_assessment( + root: AssessmentDashboardStat, info + ) -> int: + return root.assessment_registry_qs.filter(sectors__len__gte=2).count() + + @staticmethod + def resolve_total_singlesector_assessment( + root: AssessmentDashboardStat, info + ) -> int: + return root.assessment_registry_qs.filter(sectors__len=1).count() + class Query: assessment_dashboard_statistics = graphene.Field( - AssessmentDashboardStatisticsType) + AssessmentDashboardStatisticsType, + filter=AssessmentDashboardFilterInputType(), + ) @staticmethod - def resolve_assessment_dashboard_statistics(root, info, **kwargs): - return AssessmentDashboardStatisticsType + def resolve_assessment_dashboard_statistics(root, info, filter): + return AssessmentDashboardStatisticsType.custom_resolver(info, filter) diff --git a/apps/assessment_registry/filter_set.py b/apps/assessment_registry/filter_set.py new file mode 100644 index 0000000000..9c317a0761 --- /dev/null +++ b/apps/assessment_registry/filter_set.py @@ -0,0 +1,28 @@ +import django_filters + +from user_resource.filters import UserResourceGqlFilterSet +from .models import AssessmentRegistry +from utils.graphene.filters import IDListFilter + + +class AssessmentDashboardFilterSet(UserResourceGqlFilterSet): + search = django_filters.CharFilter(method="filter_title") + stakeholder = IDListFilter(distinct=True) + lead_organization = IDListFilter(distinct=True) + location = IDListFilter(distinct=True) + sector = IDListFilter(distinct=True) + affected_group = IDListFilter(distinct=True) + family = IDListFilter(distinct=True) + frequency = IDListFilter(distinct=True) + coordination_type = IDListFilter(distinct=True) + + class Meta: + model = AssessmentRegistry + fields = () + + def filter_search(self, qs, _, value): + return qs if value is None else qs.filter(project__title=value) + + @property + def qs(self): + return super().qs.distinct() diff --git a/apps/project/schema.py b/apps/project/schema.py index 24c10565ae..7c77779f9b 100644 --- a/apps/project/schema.py +++ b/apps/project/schema.py @@ -37,8 +37,7 @@ from assessment_registry.schema import Query as AssessmentRegistryQuery from unified_connector.schema import UnifiedConnectorQueryType from assisted_tagging.schema import AssistedTaggingQueryType -from assessment_registry import dashboard_schema as assessment_registry_dashboard - +from assessment_registry.dashboard_schema import Query as AssessmentRegistryDashboardQuery from lead.models import Lead from entry.models import Entry from geo.models import Region @@ -399,7 +398,7 @@ class ProjectDetailType( AryQuery, AnalysisQuery, AssessmentRegistryQuery, - assessment_registry_dashboard.Query, + AssessmentRegistryDashboardQuery, # -- End --Project scopped entities ProjectType, ): diff --git a/schema.graphql b/schema.graphql index 8196db6ad1..89192cb0ce 100644 --- a/schema.graphql +++ b/schema.graphql @@ -485,14 +485,24 @@ type AnalyticalStatementType { type AssessmentCountType { coordinatedJoint: AssessmentRegistryCoordinationTypeEnum - coordinatedJointDisplay: String + coordinatedJointDisplay: EnumDescription count: Int } +input AssessmentDashboardFilterInputType { + dateFrom: Date! + dateTo: Date! +} + type AssessmentDashboardStatisticsType { + totalAssessment: Int! + totalStakeholder: Int! + totalCollectionTechnique: Int! assessmentCount: [AssessmentCountType] stakeholderCount: [StakeholderCountType] collectionTechniqueCount: [CollectionTechniqueCountType] + totalMultisectorAssessment: Int! + totalSinglesectorAssessment: Int! } type AssessmentListType { @@ -1099,9 +1109,9 @@ type ChangeUserPassword { } type CollectionTechniqueCountType { - dataCollectionTechnique: AssessmentRegistryDataCollectionTechniqueTypeEnum - dataCollectionTechniqueDisplay: String - count: Int + dataCollectionTechnique: AssessmentRegistryDataCollectionTechniqueTypeEnum! + dataCollectionTechniqueDisplay: EnumDescription + count: Int! } enum ConnectorLeadExtractionStatusEnum { @@ -2731,7 +2741,7 @@ type ProjectDetailType { organizations: [ProjectOrganizationType!] hasAnalysisFramework: Boolean! hasAssessmentTemplate: Boolean! - assessmentDashboardStatistics: AssessmentDashboardStatisticsType + assessmentDashboardStatistics(filter: AssessmentDashboardFilterInputType): AssessmentDashboardStatisticsType assessmentRegistry(id: ID!): AssessmentRegistryType assessmentRegistries(createdAt: DateTime, createdAtGte: DateTime, createdAtLte: DateTime, modifiedAt: DateTime, modifiedAtGte: DateTime, modifiedAtLte: DateTime, createdBy: [ID], modifiedBy: [ID!], project: [ID], lead: [ID], search: String, page: Int = 1, pageSize: Int): AssessmentRegistryListType assessmentRegOptions: AssessmentRegistryOptionsType