From cd2166d4f600836a93586bd12c176bc6d6c6de8d Mon Sep 17 00:00:00 2001 From: Jan Baykara Date: Mon, 18 Mar 2024 15:59:20 +0000 Subject: [PATCH 01/18] When importing data, store the full postcodes.io result for aggregation purposes --- .../0087_genericdata_postcode_data.py | 19 +++++++++++++++++++ hub/models.py | 11 +++++++++-- 2 files changed, 28 insertions(+), 2 deletions(-) create mode 100644 hub/migrations/0087_genericdata_postcode_data.py diff --git a/hub/migrations/0087_genericdata_postcode_data.py b/hub/migrations/0087_genericdata_postcode_data.py new file mode 100644 index 000000000..091d3a99a --- /dev/null +++ b/hub/migrations/0087_genericdata_postcode_data.py @@ -0,0 +1,19 @@ +# Generated by Django 4.2.10 on 2024-03-18 15:58 + +from django.db import migrations +import django_jsonform.models.fields + + +class Migration(migrations.Migration): + + dependencies = [ + ("hub", "0086_genericdata_point_genericdata_polygon"), + ] + + operations = [ + migrations.AddField( + model_name="genericdata", + name="postcode_data", + field=django_jsonform.models.fields.JSONField(blank=True, null=True), + ), + ] diff --git a/hub/models.py b/hub/models.py index d991e4c13..fc38fb0e5 100644 --- a/hub/models.py +++ b/hub/models.py @@ -658,6 +658,13 @@ class Meta: class GenericData(CommonData): point = PointField(blank=True, null=True) polygon = PolygonField(blank=True, null=True) + postcode_data = JSONField(blank=True, null=True) + + def get_postcode_data(self) -> Optional[PostcodesIOResult]: + if self.postcode_data is None: + return None + + return self.postcode_data class Area(models.Model): @@ -950,11 +957,11 @@ async def create_import_record(record): data=self.get_record_id(record), defaults={ "json": self.get_record_dict(record), + "postcode_data": postcode_data, "point": Point( postcode_data["longitude"], postcode_data["latitude"], - ) - if ( + ) if ( postcode_data is not None and "latitude" in postcode_data and "longitude" in postcode_data From 2370c5551b1ff1e28f8892848a083db312015ea7 Mon Sep 17 00:00:00 2001 From: Jan Baykara Date: Mon, 18 Mar 2024 18:46:21 +0000 Subject: [PATCH 02/18] GraphQL fields to count imported data by different political geographies --- hub/graphql/types/model_types.py | 16 ++++++++- hub/graphql/utils.py | 20 ++++++++--- hub/models.py | 62 +++++++++++++++++++++++++++++++- utils/py.py | 12 +++++++ 4 files changed, 103 insertions(+), 7 deletions(-) diff --git a/hub/graphql/types/model_types.py b/hub/graphql/types/model_types.py index f859ee259..51c11ed6d 100644 --- a/hub/graphql/types/model_types.py +++ b/hub/graphql/types/model_types.py @@ -7,10 +7,11 @@ from strawberry import auto from strawberry.types.info import Info from strawberry_django.auth.utils import get_current_user +from strawberry.scalars import JSON from hub import models from hub.graphql.types.geojson import PointFeature, PointGeometry -from hub.graphql.utils import dict_key_field +from hub.graphql.utils import dict_key_field, fn_field @strawberry_django.filters.filter( @@ -136,6 +137,13 @@ class ExternalDataSourceFilter: geography_column_type: auto +@strawberry.type +class GroupedDataCount: + label: Optional[str] = dict_key_field() + area_id: Optional[str] = dict_key_field() + count: int = dict_key_field() + + @strawberry_django.type(models.ExternalDataSource, filters=ExternalDataSourceFilter) class ExternalDataSource: id: auto @@ -220,6 +228,12 @@ def geojson_point_features( for generic_datum in data if generic_datum.point is not None ] + + imported_data_count_by_region: List[GroupedDataCount] = fn_field() + imported_data_count_by_constituency: List[GroupedDataCount] = fn_field() + imported_data_count_by_constituency_2024: List[GroupedDataCount] = fn_field() + imported_data_count_by_council: List[GroupedDataCount] = fn_field() + imported_data_count_by_ward: List[GroupedDataCount] = fn_field() @strawberry_django.field def is_importing(self: models.ExternalDataSource, info: Info) -> bool: diff --git a/hub/graphql/utils.py b/hub/graphql/utils.py index d91e89c2d..c7c46199e 100644 --- a/hub/graphql/utils.py +++ b/hub/graphql/utils.py @@ -1,10 +1,20 @@ -import strawberry +import strawberry_django from strawberry.types.info import Info +def attr_resolver(root, info: Info): + return getattr(root, info.python_name, None) -def dict_key(root, info: Info) -> str: - return root.get(info.python_name, None) +def attr_field(**kwargs): + return strawberry_django.field(resolver=attr_resolver, **kwargs) + +def fn_resolver(root, info: Info): + return getattr(root, info.python_name, lambda: None)() +def fn_field(**kwargs): + return strawberry_django.field(resolver=fn_resolver, **kwargs) + +def dict_resolver(root, info: Info): + return root.get(info.python_name, None) -def dict_key_field(): - return strawberry.field(resolver=dict_key) +def dict_key_field(**kwargs): + return strawberry_django.field(resolver=dict_resolver, **kwargs) diff --git a/hub/models.py b/hub/models.py index fc38fb0e5..9d259d857 100644 --- a/hub/models.py +++ b/hub/models.py @@ -17,6 +17,7 @@ from django.urls import reverse from django.utils.functional import cached_property from django.utils.text import slugify +from django.db.models import Count, F import pandas as pd from asgiref.sync import sync_to_async @@ -1087,10 +1088,69 @@ def get_import_data(self): def get_import_dataframe(self): enrichment_data = self.get_import_data() - json_list = [d.json for d in enrichment_data] + json_list = [{**d.postcode_data, **d.json} for d in enrichment_data] enrichment_df = pd.DataFrame.from_records(json_list) return enrichment_df + class RegionCount(TypedDict): + label: str + area_id: Optional[str] + count: int + + def imported_data_count_by_region(self) -> List[RegionCount]: + return self.get_import_data()\ + .annotate( + label=F('postcode_data__european_electoral_region') + )\ + .values('label')\ + .annotate(count=Count('label'))\ + .order_by('-count')\ + + + def imported_data_count_by_constituency(self) -> List[RegionCount]: + return self.get_import_data()\ + .annotate( + label=F('postcode_data__parliamentary_constituency'), + area_id=F('postcode_data__codes__parliamentary_constituency') + )\ + .values('label', 'area_id')\ + .annotate(count=Count('label'))\ + .order_by('-count')\ + + + def imported_data_count_by_constituency_2024(self) -> List[RegionCount]: + return self.get_import_data()\ + .annotate( + label=F('postcode_data__parliamentary_constituency_2025'), + area_id=F('postcode_data__codes__parliamentary_constituency_2025') + )\ + .values('label', 'area_id')\ + .annotate(count=Count('label'))\ + .order_by('-count')\ + + + def imported_data_count_by_council(self) -> List[RegionCount]: + return self.get_import_data()\ + .annotate( + label=F('postcode_data__admin_district'), + area_id=F('postcode_data__codes__admin_district') + )\ + .values('label', 'area_id')\ + .annotate(count=Count('label'))\ + .order_by('-count')\ + + + def imported_data_count_by_ward(self) -> List[RegionCount]: + return self.get_import_data()\ + .annotate( + label=F('postcode_data__admin_ward'), + area_id=F('postcode_data__codes__admin_ward') + )\ + .values('label', 'area_id')\ + .annotate(count=Count('label'))\ + .order_by('-count')\ + + def imported_data_count(self) -> int: count = self.get_import_data().all().count() if isinstance(count, int): diff --git a/utils/py.py b/utils/py.py index 7be3a5e18..9b05354f5 100644 --- a/utils/py.py +++ b/utils/py.py @@ -2,6 +2,18 @@ from benedict import benedict +from types import SimpleNamespace + + +class DictWithDotNotation(SimpleNamespace): + def __init__(self, dictionary, **kwargs): + super().__init__(**kwargs) + for key, value in dictionary.items(): + if isinstance(value, dict): + self.__setattr__(key, DictWithDotNotation(value)) + else: + self.__setattr__(key, value) + def get(d, path, default=None): if isinstance(d, benedict): From d470a65314223f1874ec0cefbd6862ab9ce34692 Mon Sep 17 00:00:00 2001 From: Jan Baykara Date: Tue, 19 Mar 2024 06:57:56 +0000 Subject: [PATCH 03/18] Add European Electoral Region codes x --- hub/models.py | 5 +++-- utils/geo.py | 40 ++++++++++++++++++++++++++++++++++++++++ utils/postcodesIO.py | 17 +++++++++++++++-- 3 files changed, 58 insertions(+), 4 deletions(-) diff --git a/hub/models.py b/hub/models.py index 9d259d857..e869422d5 100644 --- a/hub/models.py +++ b/hub/models.py @@ -1100,9 +1100,10 @@ class RegionCount(TypedDict): def imported_data_count_by_region(self) -> List[RegionCount]: return self.get_import_data()\ .annotate( - label=F('postcode_data__european_electoral_region') + label=F('postcode_data__european_electoral_region'), + area_id=F('postcode_data__codes__european_electoral_region') )\ - .values('label')\ + .values('label', 'area_id')\ .annotate(count=Count('label'))\ .order_by('-count')\ diff --git a/utils/geo.py b/utils/geo.py index ada0df83a..6fcd4b83b 100644 --- a/utils/geo.py +++ b/utils/geo.py @@ -3,3 +3,43 @@ def create_point(latitude: float = 0, longitude: float = 0): return Point(x=float(longitude), y=float(latitude), srid=4326) + +EERs = [ + { + "code": "E15000001", + "label": "North East" + }, { + "code": "E15000002", + "label": "North West" + }, { + "code": "E15000003", + "label": "Yorkshire and The Humber" + }, { + "code": "E15000004", + "label": "East Midlands" + }, { + "code": "E15000005", + "label": "West Midlands" + }, { + "code": "E15000006", + "label": "Eastern" + }, { + "code": "E15000007", + "label": "London" + }, { + "code": "E15000008", + "label": "South East" + }, { + "code": "E15000009", + "label": "South West" + }, { + "code": "N07000001", + "label": "Northern Ireland" + }, { + "code": "S15000001", + "label": "Scotland" + }, { + "code": "W08000001", + "label": "Wales" + } +] \ No newline at end of file diff --git a/utils/postcodesIO.py b/utils/postcodesIO.py index baa2cf0f9..4434c6b75 100644 --- a/utils/postcodesIO.py +++ b/utils/postcodesIO.py @@ -6,7 +6,7 @@ import httpx import requests -from utils.geo import create_point +from utils.geo import create_point, EERs from utils.py import batch_and_aggregate, get, get_path @@ -85,6 +85,10 @@ def get_postcode_geo(postcode: str) -> PostcodesIOResult: if status != 200 or result is None: raise Exception(f"Failed to geocode postcode: {postcode}.") + + result['codes']['european_electoral_region'] = next( + filter(lambda eer: eer['label'] == result['european_electoral_region'], EERs), {} + ).get('code', None) return result @@ -106,7 +110,7 @@ async def get_bulk_postcode_geo(postcodes) -> PostcodesIOBulkResult: if status != 200 or result is None: raise Exception(f"Failed to bulk geocode postcodes: {postcodes}.") - return [ + results = [ next( ( geo.get("result") if geo.get("result") else None @@ -118,6 +122,15 @@ async def get_bulk_postcode_geo(postcodes) -> PostcodesIOBulkResult: for postcode in postcodes ] + # add EER codes + for index, result in enumerate(results): + if result is not None: + results[index]['codes']['european_electoral_region'] = next( + filter(lambda eer: eer['label'] == result['european_electoral_region'], EERs), {} + ).get('code', None) + + return results + @batch_and_aggregate(25) async def bulk_coordinate_geo(coordinates): From 7919cb06890e37539b3531e2627dbabec0a20eb7 Mon Sep 17 00:00:00 2001 From: Jan Baykara Date: Tue, 19 Mar 2024 07:26:20 +0000 Subject: [PATCH 04/18] Add report loading spinner --- nextjs/src/app/reports/[id]/page.tsx | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/nextjs/src/app/reports/[id]/page.tsx b/nextjs/src/app/reports/[id]/page.tsx index 2b37162e5..75e0fa865 100644 --- a/nextjs/src/app/reports/[id]/page.tsx +++ b/nextjs/src/app/reports/[id]/page.tsx @@ -44,6 +44,7 @@ import { toastPromise } from "@/lib/toast"; import { ReportMap } from "@/components/report/ReportMap"; import { MapReportPageFragmentStr } from "./lib"; import { ReportContext } from "./context"; +import { LoadingIcon } from "@/components/ui/loadingIcon"; type Params = { id: string @@ -84,6 +85,16 @@ export default function Page({ params: { id } }: { params: Params }) { ) } + if (report.loading && !report.data?.mapReport) { + return ( +
+
+ +
+
+ ) + } + return ( Date: Tue, 19 Mar 2024 13:07:01 +0000 Subject: [PATCH 05/18] Query aggregate counts across all source layers in a report --- hub/analytics.py | 77 ++++++++++++++ hub/graphql/types/model_types.py | 12 ++- hub/models.py | 111 +++++++++------------ nextjs/src/__generated__/gql.ts | 4 +- nextjs/src/__generated__/graphql.ts | 35 ++++++- nextjs/src/app/reports/[id]/lib.tsx | 4 +- nextjs/src/components/dataConfig.tsx | 34 ++++++- nextjs/src/components/report/ReportMap.tsx | 4 +- 8 files changed, 201 insertions(+), 80 deletions(-) create mode 100644 hub/analytics.py diff --git a/hub/analytics.py b/hub/analytics.py new file mode 100644 index 000000000..3d37fe665 --- /dev/null +++ b/hub/analytics.py @@ -0,0 +1,77 @@ +from django.db.models import Count, F +import pandas as pd +from typing import List, Optional, TypedDict + +class Analytics: + def __init__(self, qs) -> None: + self.qs = qs + + def get_dataframe(self, qs): + json_list = [{**d.postcode_data, **d.json} for d in self.qs] + enrichment_df = pd.DataFrame.from_records(json_list) + return enrichment_df + + class RegionCount(TypedDict): + label: str + area_id: Optional[str] + count: int + + def imported_data_count_by_region(self) -> List[RegionCount]: + return self.qs()\ + .annotate( + label=F('postcode_data__european_electoral_region'), + area_id=F('postcode_data__codes__european_electoral_region') + )\ + .values('label', 'area_id')\ + .annotate(count=Count('label'))\ + .order_by('-count')\ + + + def imported_data_count_by_constituency(self) -> List[RegionCount]: + return self.qs()\ + .annotate( + label=F('postcode_data__parliamentary_constituency'), + area_id=F('postcode_data__codes__parliamentary_constituency') + )\ + .values('label', 'area_id')\ + .annotate(count=Count('label'))\ + .order_by('-count')\ + + + def imported_data_count_by_constituency_2024(self) -> List[RegionCount]: + return self.qs()\ + .annotate( + label=F('postcode_data__parliamentary_constituency_2025'), + area_id=F('postcode_data__codes__parliamentary_constituency_2025') + )\ + .values('label', 'area_id')\ + .annotate(count=Count('label'))\ + .order_by('-count')\ + + + def imported_data_count_by_council(self) -> List[RegionCount]: + return self.qs()\ + .annotate( + label=F('postcode_data__admin_district'), + area_id=F('postcode_data__codes__admin_district') + )\ + .values('label', 'area_id')\ + .annotate(count=Count('label'))\ + .order_by('-count')\ + + + def imported_data_count_by_ward(self) -> List[RegionCount]: + return self.qs()\ + .annotate( + label=F('postcode_data__admin_ward'), + area_id=F('postcode_data__codes__admin_ward') + )\ + .values('label', 'area_id')\ + .annotate(count=Count('label'))\ + .order_by('-count')\ + + def imported_data_count(self) -> int: + count = self.qs().all().count() + if isinstance(count, int): + return count + return 0 \ No newline at end of file diff --git a/hub/graphql/types/model_types.py b/hub/graphql/types/model_types.py index 51c11ed6d..361f4da4d 100644 --- a/hub/graphql/types/model_types.py +++ b/hub/graphql/types/model_types.py @@ -208,10 +208,6 @@ def auto_update_webhook_url(self: models.ExternalDataSource, info) -> str: def webhook_healthcheck(self: models.ExternalDataSource, info) -> bool: return self.webhook_healthcheck() - @strawberry_django.field - def imported_data_count(self: models.ExternalDataSource, info: Info) -> int: - return self.imported_data_count() - @strawberry_django.field def geojson_point_features( self: models.ExternalDataSource, info: Info @@ -229,6 +225,7 @@ def geojson_point_features( if generic_datum.point is not None ] + imported_data_count: int = fn_field() imported_data_count_by_region: List[GroupedDataCount] = fn_field() imported_data_count_by_constituency: List[GroupedDataCount] = fn_field() imported_data_count_by_constituency_2024: List[GroupedDataCount] = fn_field() @@ -287,3 +284,10 @@ def source(self, info: Info) -> ExternalDataSource: @strawberry_django.type(models.MapReport) class MapReport(Report): layers: Optional[List[MapLayer]] + + imported_data_count: int = fn_field() + imported_data_count_by_region: List[GroupedDataCount] = fn_field() + imported_data_count_by_constituency: List[GroupedDataCount] = fn_field() + imported_data_count_by_constituency_2024: List[GroupedDataCount] = fn_field() + imported_data_count_by_council: List[GroupedDataCount] = fn_field() + imported_data_count_by_ward: List[GroupedDataCount] = fn_field() \ No newline at end of file diff --git a/hub/models.py b/hub/models.py index e869422d5..364e6dcfc 100644 --- a/hub/models.py +++ b/hub/models.py @@ -44,6 +44,7 @@ from hub.views.mapped import ExternalDataSourceAutoUpdateWebhook from utils.postcodesIO import PostcodesIOResult, get_bulk_postcode_geo from utils.py import ensure_list, get +from hub.analytics import Analytics User = get_user_model() @@ -1085,78 +1086,31 @@ def get_import_data(self): return GenericData.objects.filter( data_type__data_set__external_data_source_id=self.id ) + + @cached_property + def analytics(self): + return Analytics(qs=self.get_import_data) def get_import_dataframe(self): - enrichment_data = self.get_import_data() - json_list = [{**d.postcode_data, **d.json} for d in enrichment_data] - enrichment_df = pd.DataFrame.from_records(json_list) - return enrichment_df - - class RegionCount(TypedDict): - label: str - area_id: Optional[str] - count: int + return self.analytics.get_dataframe() - def imported_data_count_by_region(self) -> List[RegionCount]: - return self.get_import_data()\ - .annotate( - label=F('postcode_data__european_electoral_region'), - area_id=F('postcode_data__codes__european_electoral_region') - )\ - .values('label', 'area_id')\ - .annotate(count=Count('label'))\ - .order_by('-count')\ - + def imported_data_count_by_region(self) -> List[Analytics.RegionCount]: + return self.analytics.imported_data_count_by_region() - def imported_data_count_by_constituency(self) -> List[RegionCount]: - return self.get_import_data()\ - .annotate( - label=F('postcode_data__parliamentary_constituency'), - area_id=F('postcode_data__codes__parliamentary_constituency') - )\ - .values('label', 'area_id')\ - .annotate(count=Count('label'))\ - .order_by('-count')\ - + def imported_data_count_by_constituency(self) -> List[Analytics.RegionCount]: + return self.analytics.imported_data_count_by_constituency() - def imported_data_count_by_constituency_2024(self) -> List[RegionCount]: - return self.get_import_data()\ - .annotate( - label=F('postcode_data__parliamentary_constituency_2025'), - area_id=F('postcode_data__codes__parliamentary_constituency_2025') - )\ - .values('label', 'area_id')\ - .annotate(count=Count('label'))\ - .order_by('-count')\ - + def imported_data_count_by_constituency_2024(self) -> List[Analytics.RegionCount]: + return self.analytics.imported_data_count_by_constituency_2024() - def imported_data_count_by_council(self) -> List[RegionCount]: - return self.get_import_data()\ - .annotate( - label=F('postcode_data__admin_district'), - area_id=F('postcode_data__codes__admin_district') - )\ - .values('label', 'area_id')\ - .annotate(count=Count('label'))\ - .order_by('-count')\ - + def imported_data_count_by_council(self) -> List[Analytics.RegionCount]: + return self.analytics.imported_data_count_by_council() - def imported_data_count_by_ward(self) -> List[RegionCount]: - return self.get_import_data()\ - .annotate( - label=F('postcode_data__admin_ward'), - area_id=F('postcode_data__codes__admin_ward') - )\ - .values('label', 'area_id')\ - .annotate(count=Count('label'))\ - .order_by('-count')\ - + def imported_data_count_by_ward(self) -> List[Analytics.RegionCount]: + return self.analytics.imported_data_count_by_ward() def imported_data_count(self) -> int: - count = self.get_import_data().all().count() - if isinstance(count, int): - return count - return 0 + return self.analytics.imported_data_count() def data_loader_factory(self): async def fetch_enrichment_data(keys: List[self.EnrichmentLookup]) -> list[str]: @@ -1665,3 +1619,34 @@ class MapLayer(TypedDict): def get_layers(self) -> list[MapLayer]: return self.layers + + def get_import_data(self): + visible_layer_ids = [layer["source"] for layer in self.get_layers() if layer.get("visible", True)] + return GenericData.objects.filter( + data_type__data_set__external_data_source_id__in=visible_layer_ids + ) + + @cached_property + def analytics(self): + return Analytics(qs=self.get_import_data) + + def get_import_dataframe(self): + return self.analytics.get_dataframe() + + def imported_data_count_by_region(self) -> List[Analytics.RegionCount]: + return self.analytics.imported_data_count_by_region() + + def imported_data_count_by_constituency(self) -> List[Analytics.RegionCount]: + return self.analytics.imported_data_count_by_constituency() + + def imported_data_count_by_constituency_2024(self) -> List[Analytics.RegionCount]: + return self.analytics.imported_data_count_by_constituency_2024() + + def imported_data_count_by_council(self) -> List[Analytics.RegionCount]: + return self.analytics.imported_data_count_by_council() + + def imported_data_count_by_ward(self) -> List[Analytics.RegionCount]: + return self.analytics.imported_data_count_by_ward() + + def imported_data_count(self) -> int: + return self.analytics.imported_data_count() \ No newline at end of file diff --git a/nextjs/src/__generated__/gql.ts b/nextjs/src/__generated__/gql.ts index c0bd9702c..54095185a 100644 --- a/nextjs/src/__generated__/gql.ts +++ b/nextjs/src/__generated__/gql.ts @@ -43,7 +43,7 @@ const documents = { "\n fragment ExternalDataSourceCardFields on ExternalDataSource {\n id\n name\n connectionDetails {\n crmType: __typename\n }\n }\n": types.ExternalDataSourceCardFieldsFragmentDoc, "\n query ExternalDataSourceCard($ID: ID!) {\n externalDataSource(pk: $ID) {\n ...ExternalDataSourceCardFields\n }\n }\n \n": types.ExternalDataSourceCardDocument, "\n query EnrichmentLayers {\n externalDataSources {\n id\n name\n geographyColumn\n geographyColumnType\n dataType\n connectionDetails {\n __typename\n }\n fieldDefinitions {\n label\n value\n description\n }\n }\n }\n": types.EnrichmentLayersDocument, - "\n fragment MapReportLayersSummary on MapReport {\n layers {\n name\n source {\n id\n name\n isImporting\n importedDataCount\n }\n }\n }\n": types.MapReportLayersSummaryFragmentDoc, + "\n fragment MapReportLayersSummary on MapReport {\n importedDataCountByRegion {\n label\n areaId\n count\n }\n importedDataCountByConstituency {\n label\n areaId\n count\n }\n importedDataCountByWard {\n label\n areaId\n count\n }\n layers {\n name\n source {\n id\n name\n isImporting\n importedDataCount\n importedDataCountByRegion {\n label\n areaId\n count\n }\n importedDataCountByConstituency {\n label\n areaId\n count\n }\n importedDataCountByWard {\n label\n areaId\n count\n }\n }\n }\n }\n": types.MapReportLayersSummaryFragmentDoc, "\n query GetMemberList {\n externalDataSources(filters: { dataType: MEMBER }) {\n id\n name\n importedDataCount\n }\n }\n": types.GetMemberListDocument, "\n query GetSourceGeoJSON($externalDataSourceId: ID!) {\n externalDataSource(pk: $externalDataSourceId) {\n geojsonPointFeatures {\n id\n type\n geometry {\n type\n coordinates\n }\n }\n }\n }\n": types.GetSourceGeoJsonDocument, "\n mutation UpdateExternalDataSource($input: ExternalDataSourceInput!) {\n updateExternalDataSource(data: $input) {\n id\n name\n geographyColumn\n geographyColumnType\n autoUpdateEnabled\n updateMapping {\n source\n sourcePath\n destinationColumn\n }\n }\n }\n": types.UpdateExternalDataSourceDocument, @@ -187,7 +187,7 @@ export function gql(source: "\n query EnrichmentLayers {\n externalDataSourc /** * The gql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ -export function gql(source: "\n fragment MapReportLayersSummary on MapReport {\n layers {\n name\n source {\n id\n name\n isImporting\n importedDataCount\n }\n }\n }\n"): (typeof documents)["\n fragment MapReportLayersSummary on MapReport {\n layers {\n name\n source {\n id\n name\n isImporting\n importedDataCount\n }\n }\n }\n"]; +export function gql(source: "\n fragment MapReportLayersSummary on MapReport {\n importedDataCountByRegion {\n label\n areaId\n count\n }\n importedDataCountByConstituency {\n label\n areaId\n count\n }\n importedDataCountByWard {\n label\n areaId\n count\n }\n layers {\n name\n source {\n id\n name\n isImporting\n importedDataCount\n importedDataCountByRegion {\n label\n areaId\n count\n }\n importedDataCountByConstituency {\n label\n areaId\n count\n }\n importedDataCountByWard {\n label\n areaId\n count\n }\n }\n }\n }\n"): (typeof documents)["\n fragment MapReportLayersSummary on MapReport {\n importedDataCountByRegion {\n label\n areaId\n count\n }\n importedDataCountByConstituency {\n label\n areaId\n count\n }\n importedDataCountByWard {\n label\n areaId\n count\n }\n layers {\n name\n source {\n id\n name\n isImporting\n importedDataCount\n importedDataCountByRegion {\n label\n areaId\n count\n }\n importedDataCountByConstituency {\n label\n areaId\n count\n }\n importedDataCountByWard {\n label\n areaId\n count\n }\n }\n }\n }\n"]; /** * The gql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ diff --git a/nextjs/src/__generated__/graphql.ts b/nextjs/src/__generated__/graphql.ts index 9e59e8258..a81e46de0 100644 --- a/nextjs/src/__generated__/graphql.ts +++ b/nextjs/src/__generated__/graphql.ts @@ -69,6 +69,11 @@ export type AirtableSource = { healthcheck: Scalars['Boolean']['output']; id: Scalars['UUID']['output']; importedDataCount: Scalars['Int']['output']; + importedDataCountByConstituency: Array; + importedDataCountByConstituency2024: Array; + importedDataCountByCouncil: Array; + importedDataCountByRegion: Array; + importedDataCountByWard: Array; isImporting: Scalars['Boolean']['output']; jobs: Array; lastUpdate: Scalars['DateTime']['output']; @@ -166,6 +171,11 @@ export type ExternalDataSource = { healthcheck: Scalars['Boolean']['output']; id: Scalars['UUID']['output']; importedDataCount: Scalars['Int']['output']; + importedDataCountByConstituency: Array; + importedDataCountByConstituency2024: Array; + importedDataCountByCouncil: Array; + importedDataCountByRegion: Array; + importedDataCountByWard: Array; isImporting: Scalars['Boolean']['output']; jobs: Array; lastUpdate: Scalars['DateTime']['output']; @@ -234,6 +244,13 @@ export type Geometry = { type: GeoJsonTypes; }; +export type GroupedDataCount = { + __typename?: 'GroupedDataCount'; + areaId?: Maybe; + count: Scalars['Int']['output']; + label?: Maybe; +}; + export type IdFilterLookup = { contains?: InputMaybe; endsWith?: InputMaybe; @@ -282,11 +299,13 @@ export type MapLayer = { __typename?: 'MapLayer'; name: Scalars['String']['output']; source: ExternalDataSource; + visible?: Maybe; }; export type MapLayerInput = { name: Scalars['String']['input']; source: Scalars['String']['input']; + visible?: InputMaybe; }; /** MapReport(polymorphic_ctype, id, organisation, name, slug, description, created_at, last_update, report_ptr, layers) */ @@ -295,6 +314,12 @@ export type MapReport = { createdAt: Scalars['DateTime']['output']; description?: Maybe; id: Scalars['UUID']['output']; + importedDataCount: Scalars['Int']['output']; + importedDataCountByConstituency: Array; + importedDataCountByConstituency2024: Array; + importedDataCountByCouncil: Array; + importedDataCountByRegion: Array; + importedDataCountByWard: Array; lastUpdate: Scalars['DateTime']['output']; layers?: Maybe>; name: Scalars['String']['output']; @@ -1055,7 +1080,7 @@ export type EnrichmentLayersQueryVariables = Exact<{ [key: string]: never; }>; export type EnrichmentLayersQuery = { __typename?: 'Query', externalDataSources: Array<{ __typename?: 'ExternalDataSource', id: any, name: string, geographyColumn?: string | null, geographyColumnType: PostcodesIoGeographyTypes, dataType: DataSourceType, connectionDetails: { __typename: 'AirtableSource' }, fieldDefinitions?: Array<{ __typename?: 'FieldDefinition', label?: string | null, value: string, description?: string | null }> | null }> }; -export type MapReportLayersSummaryFragment = { __typename?: 'MapReport', layers?: Array<{ __typename?: 'MapLayer', name: string, source: { __typename?: 'ExternalDataSource', id: any, name: string, isImporting: boolean, importedDataCount: number } }> | null } & { ' $fragmentName'?: 'MapReportLayersSummaryFragment' }; +export type MapReportLayersSummaryFragment = { __typename?: 'MapReport', importedDataCountByRegion: Array<{ __typename?: 'GroupedDataCount', label?: string | null, areaId?: string | null, count: number }>, importedDataCountByConstituency: Array<{ __typename?: 'GroupedDataCount', label?: string | null, areaId?: string | null, count: number }>, importedDataCountByWard: Array<{ __typename?: 'GroupedDataCount', label?: string | null, areaId?: string | null, count: number }>, layers?: Array<{ __typename?: 'MapLayer', name: string, source: { __typename?: 'ExternalDataSource', id: any, name: string, isImporting: boolean, importedDataCount: number, importedDataCountByRegion: Array<{ __typename?: 'GroupedDataCount', label?: string | null, areaId?: string | null, count: number }>, importedDataCountByConstituency: Array<{ __typename?: 'GroupedDataCount', label?: string | null, areaId?: string | null, count: number }>, importedDataCountByWard: Array<{ __typename?: 'GroupedDataCount', label?: string | null, areaId?: string | null, count: number }> } }> | null } & { ' $fragmentName'?: 'MapReportLayersSummaryFragment' }; export type GetMemberListQueryVariables = Exact<{ [key: string]: never; }>; @@ -1081,8 +1106,8 @@ export type PublicUserQueryVariables = Exact<{ [key: string]: never; }>; export type PublicUserQuery = { __typename?: 'Query', publicUser?: { __typename?: 'UserType', id: string, username: string, email: string } | null }; -export const MapReportLayersSummaryFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"MapReportLayersSummary"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"MapReport"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"layers"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"source"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"isImporting"}},{"kind":"Field","name":{"kind":"Name","value":"importedDataCount"}}]}}]}}]}}]} as unknown as DocumentNode; -export const MapReportPageFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"MapReportPage"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"MapReport"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"MapReportLayersSummary"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"MapReportLayersSummary"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"MapReport"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"layers"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"source"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"isImporting"}},{"kind":"Field","name":{"kind":"Name","value":"importedDataCount"}}]}}]}}]}}]} as unknown as DocumentNode; +export const MapReportLayersSummaryFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"MapReportLayersSummary"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"MapReport"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"importedDataCountByRegion"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"label"}},{"kind":"Field","name":{"kind":"Name","value":"areaId"}},{"kind":"Field","name":{"kind":"Name","value":"count"}}]}},{"kind":"Field","name":{"kind":"Name","value":"importedDataCountByConstituency"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"label"}},{"kind":"Field","name":{"kind":"Name","value":"areaId"}},{"kind":"Field","name":{"kind":"Name","value":"count"}}]}},{"kind":"Field","name":{"kind":"Name","value":"importedDataCountByWard"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"label"}},{"kind":"Field","name":{"kind":"Name","value":"areaId"}},{"kind":"Field","name":{"kind":"Name","value":"count"}}]}},{"kind":"Field","name":{"kind":"Name","value":"layers"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"source"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"isImporting"}},{"kind":"Field","name":{"kind":"Name","value":"importedDataCount"}},{"kind":"Field","name":{"kind":"Name","value":"importedDataCountByRegion"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"label"}},{"kind":"Field","name":{"kind":"Name","value":"areaId"}},{"kind":"Field","name":{"kind":"Name","value":"count"}}]}},{"kind":"Field","name":{"kind":"Name","value":"importedDataCountByConstituency"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"label"}},{"kind":"Field","name":{"kind":"Name","value":"areaId"}},{"kind":"Field","name":{"kind":"Name","value":"count"}}]}},{"kind":"Field","name":{"kind":"Name","value":"importedDataCountByWard"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"label"}},{"kind":"Field","name":{"kind":"Name","value":"areaId"}},{"kind":"Field","name":{"kind":"Name","value":"count"}}]}}]}}]}}]}}]} as unknown as DocumentNode; +export const MapReportPageFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"MapReportPage"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"MapReport"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"MapReportLayersSummary"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"MapReportLayersSummary"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"MapReport"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"importedDataCountByRegion"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"label"}},{"kind":"Field","name":{"kind":"Name","value":"areaId"}},{"kind":"Field","name":{"kind":"Name","value":"count"}}]}},{"kind":"Field","name":{"kind":"Name","value":"importedDataCountByConstituency"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"label"}},{"kind":"Field","name":{"kind":"Name","value":"areaId"}},{"kind":"Field","name":{"kind":"Name","value":"count"}}]}},{"kind":"Field","name":{"kind":"Name","value":"importedDataCountByWard"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"label"}},{"kind":"Field","name":{"kind":"Name","value":"areaId"}},{"kind":"Field","name":{"kind":"Name","value":"count"}}]}},{"kind":"Field","name":{"kind":"Name","value":"layers"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"source"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"isImporting"}},{"kind":"Field","name":{"kind":"Name","value":"importedDataCount"}},{"kind":"Field","name":{"kind":"Name","value":"importedDataCountByRegion"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"label"}},{"kind":"Field","name":{"kind":"Name","value":"areaId"}},{"kind":"Field","name":{"kind":"Name","value":"count"}}]}},{"kind":"Field","name":{"kind":"Name","value":"importedDataCountByConstituency"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"label"}},{"kind":"Field","name":{"kind":"Name","value":"areaId"}},{"kind":"Field","name":{"kind":"Name","value":"count"}}]}},{"kind":"Field","name":{"kind":"Name","value":"importedDataCountByWard"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"label"}},{"kind":"Field","name":{"kind":"Name","value":"areaId"}},{"kind":"Field","name":{"kind":"Name","value":"count"}}]}}]}}]}}]}}]} as unknown as DocumentNode; export const DataSourceCardFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"DataSourceCard"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ExternalDataSource"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"dataType"}},{"kind":"Field","name":{"kind":"Name","value":"connectionDetails"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","alias":{"kind":"Name","value":"crmType"},"name":{"kind":"Name","value":"__typename"}}]}},{"kind":"Field","name":{"kind":"Name","value":"autoUpdateEnabled"}},{"kind":"Field","name":{"kind":"Name","value":"updateMapping"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"source"}},{"kind":"Field","name":{"kind":"Name","value":"sourcePath"}},{"kind":"Field","name":{"kind":"Name","value":"destinationColumn"}}]}},{"kind":"Field","name":{"kind":"Name","value":"jobs"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"lastEventAt"}},{"kind":"Field","name":{"kind":"Name","value":"status"}}]}}]}}]} as unknown as DocumentNode; export const ExternalDataSourceCardFieldsFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ExternalDataSourceCardFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ExternalDataSource"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"connectionDetails"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","alias":{"kind":"Name","value":"crmType"},"name":{"kind":"Name","value":"__typename"}}]}}]}}]} as unknown as DocumentNode; export const ListExternalDataSourcesDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"ListExternalDataSources"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"externalDataSources"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"dataType"}},{"kind":"Field","name":{"kind":"Name","value":"connectionDetails"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","alias":{"kind":"Name","value":"crmType"},"name":{"kind":"Name","value":"__typename"}}]}},{"kind":"Field","name":{"kind":"Name","value":"autoUpdateEnabled"}},{"kind":"Field","name":{"kind":"Name","value":"jobs"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"lastEventAt"}},{"kind":"Field","name":{"kind":"Name","value":"status"}}]}},{"kind":"Field","name":{"kind":"Name","value":"updateMapping"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"source"}},{"kind":"Field","name":{"kind":"Name","value":"sourcePath"}},{"kind":"Field","name":{"kind":"Name","value":"destinationColumn"}}]}}]}}]}}]} as unknown as DocumentNode; @@ -1102,8 +1127,8 @@ export const ExampleDocument = {"kind":"Document","definitions":[{"kind":"Operat export const LoginDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"Login"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"username"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"password"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"tokenAuth"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"username"},"value":{"kind":"Variable","name":{"kind":"Name","value":"username"}}},{"kind":"Argument","name":{"kind":"Name","value":"password"},"value":{"kind":"Variable","name":{"kind":"Name","value":"password"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"errors"}},{"kind":"Field","name":{"kind":"Name","value":"success"}},{"kind":"Field","name":{"kind":"Name","value":"token"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"token"}},{"kind":"Field","name":{"kind":"Name","value":"payload"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"exp"}}]}}]}}]}}]}}]} as unknown as DocumentNode; export const RegisterDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"Register"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"email"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"password1"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"password2"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"username"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"register"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"email"},"value":{"kind":"Variable","name":{"kind":"Name","value":"email"}}},{"kind":"Argument","name":{"kind":"Name","value":"password1"},"value":{"kind":"Variable","name":{"kind":"Name","value":"password1"}}},{"kind":"Argument","name":{"kind":"Name","value":"password2"},"value":{"kind":"Variable","name":{"kind":"Name","value":"password2"}}},{"kind":"Argument","name":{"kind":"Name","value":"username"},"value":{"kind":"Variable","name":{"kind":"Name","value":"username"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"errors"}},{"kind":"Field","name":{"kind":"Name","value":"success"}}]}}]}}]} as unknown as DocumentNode; export const GetMapReportNameDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetMapReportName"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ID"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"mapReport"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"pk"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}}]}}]} as unknown as DocumentNode; -export const GetMapReportDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetMapReport"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ID"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"mapReport"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"pk"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"MapReportPage"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"MapReportLayersSummary"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"MapReport"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"layers"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"source"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"isImporting"}},{"kind":"Field","name":{"kind":"Name","value":"importedDataCount"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"MapReportPage"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"MapReport"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"MapReportLayersSummary"}}]}}]} as unknown as DocumentNode; -export const UpdateMapReportDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"UpdateMapReport"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"MapReportInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"updateMapReport"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"data"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"MapReportPage"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"MapReportLayersSummary"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"MapReport"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"layers"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"source"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"isImporting"}},{"kind":"Field","name":{"kind":"Name","value":"importedDataCount"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"MapReportPage"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"MapReport"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"MapReportLayersSummary"}}]}}]} as unknown as DocumentNode; +export const GetMapReportDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetMapReport"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ID"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"mapReport"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"pk"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"MapReportPage"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"MapReportLayersSummary"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"MapReport"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"importedDataCountByRegion"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"label"}},{"kind":"Field","name":{"kind":"Name","value":"areaId"}},{"kind":"Field","name":{"kind":"Name","value":"count"}}]}},{"kind":"Field","name":{"kind":"Name","value":"importedDataCountByConstituency"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"label"}},{"kind":"Field","name":{"kind":"Name","value":"areaId"}},{"kind":"Field","name":{"kind":"Name","value":"count"}}]}},{"kind":"Field","name":{"kind":"Name","value":"importedDataCountByWard"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"label"}},{"kind":"Field","name":{"kind":"Name","value":"areaId"}},{"kind":"Field","name":{"kind":"Name","value":"count"}}]}},{"kind":"Field","name":{"kind":"Name","value":"layers"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"source"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"isImporting"}},{"kind":"Field","name":{"kind":"Name","value":"importedDataCount"}},{"kind":"Field","name":{"kind":"Name","value":"importedDataCountByRegion"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"label"}},{"kind":"Field","name":{"kind":"Name","value":"areaId"}},{"kind":"Field","name":{"kind":"Name","value":"count"}}]}},{"kind":"Field","name":{"kind":"Name","value":"importedDataCountByConstituency"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"label"}},{"kind":"Field","name":{"kind":"Name","value":"areaId"}},{"kind":"Field","name":{"kind":"Name","value":"count"}}]}},{"kind":"Field","name":{"kind":"Name","value":"importedDataCountByWard"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"label"}},{"kind":"Field","name":{"kind":"Name","value":"areaId"}},{"kind":"Field","name":{"kind":"Name","value":"count"}}]}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"MapReportPage"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"MapReport"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"MapReportLayersSummary"}}]}}]} as unknown as DocumentNode; +export const UpdateMapReportDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"UpdateMapReport"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"MapReportInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"updateMapReport"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"data"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"MapReportPage"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"MapReportLayersSummary"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"MapReport"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"importedDataCountByRegion"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"label"}},{"kind":"Field","name":{"kind":"Name","value":"areaId"}},{"kind":"Field","name":{"kind":"Name","value":"count"}}]}},{"kind":"Field","name":{"kind":"Name","value":"importedDataCountByConstituency"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"label"}},{"kind":"Field","name":{"kind":"Name","value":"areaId"}},{"kind":"Field","name":{"kind":"Name","value":"count"}}]}},{"kind":"Field","name":{"kind":"Name","value":"importedDataCountByWard"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"label"}},{"kind":"Field","name":{"kind":"Name","value":"areaId"}},{"kind":"Field","name":{"kind":"Name","value":"count"}}]}},{"kind":"Field","name":{"kind":"Name","value":"layers"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"source"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"isImporting"}},{"kind":"Field","name":{"kind":"Name","value":"importedDataCount"}},{"kind":"Field","name":{"kind":"Name","value":"importedDataCountByRegion"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"label"}},{"kind":"Field","name":{"kind":"Name","value":"areaId"}},{"kind":"Field","name":{"kind":"Name","value":"count"}}]}},{"kind":"Field","name":{"kind":"Name","value":"importedDataCountByConstituency"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"label"}},{"kind":"Field","name":{"kind":"Name","value":"areaId"}},{"kind":"Field","name":{"kind":"Name","value":"count"}}]}},{"kind":"Field","name":{"kind":"Name","value":"importedDataCountByWard"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"label"}},{"kind":"Field","name":{"kind":"Name","value":"areaId"}},{"kind":"Field","name":{"kind":"Name","value":"count"}}]}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"MapReportPage"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"MapReport"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"MapReportLayersSummary"}}]}}]} as unknown as DocumentNode; export const DeleteMapReportDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"DeleteMapReport"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"IDObject"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"deleteMapReport"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"data"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}}]}}]} as unknown as DocumentNode; export const AutoUpdateWebhookRefreshDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"AutoUpdateWebhookRefresh"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"ID"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"refreshWebhooks"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"externalDataSourceId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"ID"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"webhookHealthcheck"}}]}}]}}]} as unknown as DocumentNode; export const ExternalDataSourceAutoUpdateCardDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"ExternalDataSourceAutoUpdateCard"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"ID"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ID"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"externalDataSource"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"pk"},"value":{"kind":"Variable","name":{"kind":"Name","value":"ID"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"DataSourceCard"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"DataSourceCard"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ExternalDataSource"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"dataType"}},{"kind":"Field","name":{"kind":"Name","value":"connectionDetails"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","alias":{"kind":"Name","value":"crmType"},"name":{"kind":"Name","value":"__typename"}}]}},{"kind":"Field","name":{"kind":"Name","value":"autoUpdateEnabled"}},{"kind":"Field","name":{"kind":"Name","value":"updateMapping"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"source"}},{"kind":"Field","name":{"kind":"Name","value":"sourcePath"}},{"kind":"Field","name":{"kind":"Name","value":"destinationColumn"}}]}},{"kind":"Field","name":{"kind":"Name","value":"jobs"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"lastEventAt"}},{"kind":"Field","name":{"kind":"Name","value":"status"}}]}}]}}]} as unknown as DocumentNode; diff --git a/nextjs/src/app/reports/[id]/lib.tsx b/nextjs/src/app/reports/[id]/lib.tsx index 9d8622ec1..e6fc433a5 100644 --- a/nextjs/src/app/reports/[id]/lib.tsx +++ b/nextjs/src/app/reports/[id]/lib.tsx @@ -1,6 +1,6 @@ "use client" -import { MapReportLayersSummaryFragmentStr } from "@/components/dataConfig"; +import { MAP_REPORT_LAYERS_SUMMARY } from "@/components/dataConfig" import { gql } from "@apollo/client" export const MapReportPageFragmentStr = gql` @@ -9,5 +9,5 @@ export const MapReportPageFragmentStr = gql` name ... MapReportLayersSummary } - ${MapReportLayersSummaryFragmentStr} + ${MAP_REPORT_LAYERS_SUMMARY} ` \ No newline at end of file diff --git a/nextjs/src/components/dataConfig.tsx b/nextjs/src/components/dataConfig.tsx index 93e6e89b9..00abb6754 100644 --- a/nextjs/src/components/dataConfig.tsx +++ b/nextjs/src/components/dataConfig.tsx @@ -35,7 +35,7 @@ export default function DataConfigPanel () { const { id, update } = useContext(ReportContext) const client = useApolloClient() const layers = useFragment({ - fragment: MapReportLayersSummaryFragmentStr, + fragment: MAP_REPORT_LAYERS_SUMMARY, fragmentName: "MapReportLayersSummary", from: { __typename: "MapReport", @@ -199,8 +199,23 @@ export default function DataConfigPanel () { } }; -export const MapReportLayersSummaryFragmentStr = gql` +export const MAP_REPORT_LAYERS_SUMMARY = gql` fragment MapReportLayersSummary on MapReport { + importedDataCountByRegion { + label + areaId + count + } + importedDataCountByConstituency { + label + areaId + count + } + importedDataCountByWard { + label + areaId + count + } layers { name source { @@ -208,6 +223,21 @@ export const MapReportLayersSummaryFragmentStr = gql` name isImporting importedDataCount + importedDataCountByRegion { + label + areaId + count + } + importedDataCountByConstituency { + label + areaId + count + } + importedDataCountByWard { + label + areaId + count + } } } } diff --git a/nextjs/src/components/report/ReportMap.tsx b/nextjs/src/components/report/ReportMap.tsx index c5989a0a7..2099953f1 100644 --- a/nextjs/src/components/report/ReportMap.tsx +++ b/nextjs/src/components/report/ReportMap.tsx @@ -3,14 +3,14 @@ import { GetSourceGeoJsonQuery, GetSourceGeoJsonQueryVariables, MapReportLayersSummaryFragment } from "@/__generated__/graphql"; import { useContext, useId } from "react"; import Map, { Layer, Marker, Popup, Source } from "react-map-gl"; -import { MapReportLayersSummaryFragmentStr } from "../dataConfig"; +import { MAP_REPORT_LAYERS_SUMMARY } from "../dataConfig"; import { gql, useFragment, useQuery } from "@apollo/client"; import { ReportContext } from "@/app/reports/[id]/context"; export function ReportMap () { const { id, update } = useContext(ReportContext) const layers = useFragment({ - fragment: MapReportLayersSummaryFragmentStr, + fragment: MAP_REPORT_LAYERS_SUMMARY, fragmentName: "MapReportLayersSummary", from: { __typename: "MapReport", From 299148ce2743fa6de340249e058494046f8d5320 Mon Sep 17 00:00:00 2001 From: Jan Baykara Date: Tue, 19 Mar 2024 13:07:36 +0000 Subject: [PATCH 06/18] Default visibility switch --- hub/graphql/mutations.py | 1 + hub/graphql/types/model_types.py | 1 + 2 files changed, 2 insertions(+) diff --git a/hub/graphql/mutations.py b/hub/graphql/mutations.py index a682a0851..5912cbd43 100644 --- a/hub/graphql/mutations.py +++ b/hub/graphql/mutations.py @@ -50,6 +50,7 @@ class AirtableSourceInput(ExternalDataSourceInput): class MapLayerInput: name: str source: str + visible: Optional[bool] = True @strawberry_django.input(models.MapReport, partial=True) diff --git a/hub/graphql/types/model_types.py b/hub/graphql/types/model_types.py index 361f4da4d..a451d5004 100644 --- a/hub/graphql/types/model_types.py +++ b/hub/graphql/types/model_types.py @@ -274,6 +274,7 @@ def get_queryset(cls, queryset, info, **kwargs): @strawberry.type class MapLayer: name: str = dict_key_field() + visible: Optional[bool] = dict_key_field() @strawberry_django.field def source(self, info: Info) -> ExternalDataSource: From 6b1b2c6d36f5b4ce89c4961005a42d5cef4b44bf Mon Sep 17 00:00:00 2001 From: Jan Baykara Date: Tue, 19 Mar 2024 13:23:27 +0000 Subject: [PATCH 07/18] Import PostGIS centroids and polygons for regions, constituencies, wards --- hub/management/commands/import_areas.py | 15 ++++-- .../commands/import_new_constituencies.py | 12 ++++- hub/management/commands/import_regions.py | 52 +++++++++++++++++++ hub/management/commands/import_wards.py | 52 +++++++++++++++++++ .../commands/run_all_import_scripts.py | 2 +- ..._area_polygon_alter_genericdata_polygon.py | 35 +++++++++++++ hub/models.py | 7 ++- 7 files changed, 166 insertions(+), 9 deletions(-) create mode 100644 hub/management/commands/import_regions.py create mode 100644 hub/management/commands/import_wards.py create mode 100644 hub/migrations/0088_area_point_area_polygon_alter_genericdata_polygon.py diff --git a/hub/management/commands/import_areas.py b/hub/management/commands/import_areas.py index 982f55742..b1fdb80c0 100644 --- a/hub/management/commands/import_areas.py +++ b/hub/management/commands/import_areas.py @@ -1,6 +1,8 @@ import json from django.core.management.base import BaseCommand +# from django postgis +from django.contrib.gis.geos import MultiPolygon, Polygon, GEOSGeometry from tqdm import tqdm @@ -9,7 +11,7 @@ class Command(BaseCommand): - help = "Import basic area information from MaPit" + help = "Import basic area information from Mapit" def add_arguments(self, parser): parser.add_argument( @@ -27,7 +29,7 @@ def handle(self, quiet: bool = False, *args, **options): ) if not quiet: - print("Importing Areas") + print("Importing 2010 Constituencies") for area in tqdm(areas, disable=quiet): try: geom = mapit_client.area_geometry(area["id"]) @@ -40,7 +42,6 @@ def handle(self, quiet: bool = False, *args, **options): "type": "WMC", }, } - geom = json.dumps(geom) except mapit.NotFoundException: # pragma: no cover print(f"could not find mapit area for {area['name']}") geom = None @@ -52,5 +53,11 @@ def handle(self, quiet: bool = False, *args, **options): area_type=area_type, ) - a.geometry = geom + geom_str = json.dumps(geom) + geom = GEOSGeometry(json.dumps(geom['geometry'])) + if isinstance(geom, Polygon): + geom = MultiPolygon([geom]) + a.geometry = geom_str + a.polygon = geom + a.point = a.polygon.centroid a.save() diff --git a/hub/management/commands/import_new_constituencies.py b/hub/management/commands/import_new_constituencies.py index a9e6ec1c0..f7bdde6a2 100644 --- a/hub/management/commands/import_new_constituencies.py +++ b/hub/management/commands/import_new_constituencies.py @@ -3,6 +3,7 @@ from django.conf import settings from django.core.management.base import BaseCommand from django.db.utils import IntegrityError +from django.contrib.gis.geos import MultiPolygon, GEOSGeometry, Polygon from mysoc_dataset import get_dataset_df from tqdm import tqdm @@ -30,7 +31,7 @@ def add_arguments(self, parser): def handle(self, quiet: bool = False, *args, **options): if not quiet: - print("Importing Areas") + print("Importing 2024 Constituencies") with open(self.data_file) as f: cons = json.load(f) @@ -57,7 +58,14 @@ def handle(self, quiet: bool = False, *args, **options): con["properties"]["PCON13CD"] = area["gss_code"] con["properties"]["type"] = "WMC23" - a.geometry = json.dumps(con) + + geom_str = json.dumps(con) + geom = GEOSGeometry(json.dumps(con['geometry'])) + if isinstance(geom, Polygon): + geom = MultiPolygon([geom]) + a.geometry = geom_str + a.polygon = geom + a.point = a.polygon.centroid a.save() constituency_lookup = ( diff --git a/hub/management/commands/import_regions.py b/hub/management/commands/import_regions.py new file mode 100644 index 000000000..a50556980 --- /dev/null +++ b/hub/management/commands/import_regions.py @@ -0,0 +1,52 @@ +import json + +from django.core.management.base import BaseCommand +from django.contrib.gis.geos import GEOSGeometry, Polygon, MultiPolygon + +from tqdm import tqdm + +from hub.models import Area, AreaType +from utils import mapit +import requests + + +class Command(BaseCommand): + help = "Import historical European regions for high-level aggregation" + + def add_arguments(self, parser): + parser.add_argument( + "-q", "--quiet", action="store_true", help="Silence progress bars." + ) + + def handle(self, quiet: bool = False, *args, **options): + # https://www.data.gov.uk/dataset/636da066-7044-42d7-8f80-f6631f394f83/european-electoral-regions-december-2018-boundaries-uk-bfe + download_url = "https://open-geography-portalx-ons.hub.arcgis.com/api/download/v1/items/932f769148bb4753989e55b6703b7add/geojson?layers=0" + + data = requests.get(download_url).json() + areas = data['features'] + + area_type, created = AreaType.objects.get_or_create( + name="2018 European Electoral Regions", + code="EER", + area_type="European Electoral Region", + description="European Electoral Region boundaries, as at December 2018", + ) + + if not quiet: + print("Importing Regions") + for area in tqdm(areas, disable=quiet): + a, created = Area.objects.get_or_create( + mapit_id=area["properties"]["eer18cd"], + gss=area["properties"]["eer18cd"], + name=area["properties"]["eer18nm"], + area_type=area_type, + ) + + geom_str = json.dumps(area) + geom = GEOSGeometry(json.dumps(area['geometry'])) + if isinstance(geom, Polygon): + geom = MultiPolygon([geom]) + a.geometry = geom_str + a.polygon = geom + a.point = a.polygon.centroid + a.save() diff --git a/hub/management/commands/import_wards.py b/hub/management/commands/import_wards.py new file mode 100644 index 000000000..c4881cc34 --- /dev/null +++ b/hub/management/commands/import_wards.py @@ -0,0 +1,52 @@ +import json + +from django.core.management.base import BaseCommand +from django.contrib.gis.geos import GEOSGeometry, Polygon, MultiPolygon + +from tqdm import tqdm + +from hub.models import Area, AreaType +from utils import mapit +import requests + + +class Command(BaseCommand): + help = "Import Electoral Wards" + + def add_arguments(self, parser): + parser.add_argument( + "-q", "--quiet", action="store_true", help="Silence progress bars." + ) + + def handle(self, quiet: bool = False, *args, **options): + # https://www.data.gov.uk/dataset/f8a5e1bb-b2c1-4b01-b9c0-f4d6fa29d65f/wards-may-2023-boundaries-uk-bgc + download_url = "https://open-geography-portalx-ons.hub.arcgis.com/api/download/v1/items/67c88ea8027244e3b2313c69e3fad503/geojson?layers=0" + + data = requests.get(download_url).json() + areas = data['features'] + + area_type, created = AreaType.objects.get_or_create( + name="May 2023 Electoral Wards", + code="WD23", + area_type="Electoral Ward", + description="Electoral Ward boundaries, as at May 2023", + ) + + if not quiet: + print("Importing Electoral Wards") + for area in tqdm(areas, disable=quiet): + a, created = Area.objects.get_or_create( + mapit_id=area["properties"]["WD23CD"], + gss=area["properties"]["WD23CD"], + name=area["properties"]["WD23NM"], + area_type=area_type, + ) + + geom_str = json.dumps(area) + geom = GEOSGeometry(json.dumps(area['geometry'])) + if isinstance(geom, Polygon): + geom = MultiPolygon([geom]) + a.geometry = geom_str + a.polygon = geom + a.point = a.polygon.centroid + a.save() diff --git a/hub/management/commands/run_all_import_scripts.py b/hub/management/commands/run_all_import_scripts.py index f63a40a81..f8d3e32c0 100644 --- a/hub/management/commands/run_all_import_scripts.py +++ b/hub/management/commands/run_all_import_scripts.py @@ -56,7 +56,7 @@ def run_importer_scripts(self, imports, *args, **options): total = str(len(imports)) i = 1 failed_imports = {} - priority_imports = ["import_areas", "import_mps", "import_mps_election_results"] + priority_imports = ["import_regions", "import_areas", "import_mps", "import_mps_election_results"] for importer in priority_imports: imports.remove(importer) print(f"Running command: {importer} ({str(i)}/{total})") diff --git a/hub/migrations/0088_area_point_area_polygon_alter_genericdata_polygon.py b/hub/migrations/0088_area_point_area_polygon_alter_genericdata_polygon.py new file mode 100644 index 000000000..43394eb96 --- /dev/null +++ b/hub/migrations/0088_area_point_area_polygon_alter_genericdata_polygon.py @@ -0,0 +1,35 @@ +# Generated by Django 4.2.10 on 2024-03-19 12:42 + +import django.contrib.gis.db.models.fields +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ("hub", "0087_genericdata_postcode_data"), + ] + + operations = [ + migrations.AddField( + model_name="area", + name="point", + field=django.contrib.gis.db.models.fields.PointField( + blank=True, null=True, srid=4326 + ), + ), + migrations.AddField( + model_name="area", + name="polygon", + field=django.contrib.gis.db.models.fields.MultiPolygonField( + blank=True, null=True, srid=4326 + ), + ), + migrations.AlterField( + model_name="genericdata", + name="polygon", + field=django.contrib.gis.db.models.fields.MultiPolygonField( + blank=True, null=True, srid=4326 + ), + ), + ] diff --git a/hub/models.py b/hub/models.py index 364e6dcfc..cc1132d6f 100644 --- a/hub/models.py +++ b/hub/models.py @@ -7,7 +7,7 @@ from django.conf import settings from django.contrib.auth import get_user_model -from django.contrib.gis.db.models import PointField, PolygonField +from django.contrib.gis.db.models import PointField, MultiPolygonField from django.contrib.gis.geos import Point from django.db import models from django.db.models import Avg, IntegerField, Max, Min @@ -659,7 +659,7 @@ class Meta: class GenericData(CommonData): point = PointField(blank=True, null=True) - polygon = PolygonField(blank=True, null=True) + polygon = MultiPolygonField(blank=True, null=True) postcode_data = JSONField(blank=True, null=True) def get_postcode_data(self) -> Optional[PostcodesIOResult]: @@ -675,6 +675,8 @@ class Area(models.Model): name = models.CharField(max_length=200) area_type = models.ForeignKey(AreaType, on_delete=models.CASCADE) geometry = models.TextField(blank=True, null=True) + polygon = MultiPolygonField(blank=True, null=True) + point = PointField(blank=True, null=True) overlaps = models.ManyToManyField("self", through="AreaOverlap") def __str__(self): @@ -1616,6 +1618,7 @@ class MapReport(Report): class MapLayer(TypedDict): name: str source: str + visible: Optional[bool] def get_layers(self) -> list[MapLayer]: return self.layers From 6435e0fe641fa78538fcd0941ee1738742cae2b3 Mon Sep 17 00:00:00 2001 From: Jan Baykara Date: Tue, 19 Mar 2024 13:24:05 +0000 Subject: [PATCH 08/18] Preload map whilst loading data for reports --- nextjs/src/app/reports/[id]/page.tsx | 168 +++++++++++++-------------- 1 file changed, 83 insertions(+), 85 deletions(-) diff --git a/nextjs/src/app/reports/[id]/page.tsx b/nextjs/src/app/reports/[id]/page.tsx index 75e0fa865..080298467 100644 --- a/nextjs/src/app/reports/[id]/page.tsx +++ b/nextjs/src/app/reports/[id]/page.tsx @@ -85,16 +85,6 @@ export default function Page({ params: { id } }: { params: Params }) { ) } - if (report.loading && !report.data?.mapReport) { - return ( -
-
- -
-
- ) - } - return ( -
-
- - - { - updateMutation({ - name: document.getElementById("nickname")?.textContent?.trim() - }) - }}> - {report.data?.mapReport.name} - - - - - - - Report Settings - - Share - Invite - setDeleteOpen(true)}>Delete - - - - - - {/* @ts-ignore */} - - Data Configuration - - {/* @ts-ignore */} - - Constituency Data - - - - - {isDataConfigOpen && ( - - )} - {isConsDataOpen && ( - - - - - All Constituencies - Bury North - - - - - - - Change your password here. - + {report.loading && !report.data?.mapReport ? ( +
+
+ +
+
+ ) : ( +
+
+ + + { + updateMutation({ + name: document.getElementById("nickname")?.textContent?.trim() + }) + }}> + {report.data?.mapReport.name} + + + + + + + Report Settings + + Share + Invite + setDeleteOpen(true)}>Delete + + + + + {/* @ts-ignore */} + + Data Configuration + + {/* @ts-ignore */} + + Constituency Data + + + - )} + {isDataConfigOpen && ( + + )} + {isConsDataOpen && ( + + + + + All Constituencies + Bury North + + + + + + + Change your password here. + + + + )} +
-
+ )} setDeleteOpen(false)}> From 7a42fb0f640cad1b8309500c0bdd220767afdd61 Mon Sep 17 00:00:00 2001 From: Jan Baykara Date: Tue, 19 Mar 2024 13:25:02 +0000 Subject: [PATCH 09/18] Ugly first draft at aggregated data view --- nextjs/package-lock.json | 118 +++++++++ nextjs/package.json | 4 + nextjs/src/components/report/ReportMap.tsx | 271 ++++++++++++++++++++- 3 files changed, 384 insertions(+), 9 deletions(-) diff --git a/nextjs/package-lock.json b/nextjs/package-lock.json index 6f2fcf73d..c30134c5b 100644 --- a/nextjs/package-lock.json +++ b/nextjs/package-lock.json @@ -29,10 +29,13 @@ "@radix-ui/react-toggle-group": "^1.0.4", "@radix-ui/react-tooltip": "^1.0.7", "@tanstack/react-table": "^8.13.2", + "@types/d3-scale": "^4.0.8", "@types/date-fns": "^2.6.0", "class-variance-authority": "^0.7.0", "clsx": "^2.1.0", "cmdk": "^0.2.1", + "d3-scale": "^4.0.2", + "d3-scale-chromatic": "^3.1.0", "date-fns": "^3.5.0", "graphiql": "^3.1.1", "graphql": "^16.8.1", @@ -60,6 +63,7 @@ "@graphql-codegen/typescript-resolvers": "^4.0.6", "@graphql-typed-document-node/core": "^3.2.0", "@tailwindcss/forms": "^0.5.7", + "@types/d3-scale-chromatic": "^3.0.3", "@types/node": "^20", "@types/react": "^18", "@types/react-dom": "^18", @@ -4493,6 +4497,25 @@ "@types/tern": "*" } }, + "node_modules/@types/d3-scale": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.8.tgz", + "integrity": "sha512-gkK1VVTr5iNiYJ7vWDI+yUFFlszhNMtVeneJ6lUTKPjprsvLLI9/tgEGiXJOnlINJA8FyA88gfnQsHbybVZrYQ==", + "dependencies": { + "@types/d3-time": "*" + } + }, + "node_modules/@types/d3-scale-chromatic": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-scale-chromatic/-/d3-scale-chromatic-3.0.3.tgz", + "integrity": "sha512-laXM4+1o5ImZv3RpFAsTRn3TEkzqkytiOY0Dz0sq5cnd1dtNlk6sHLon4OvqaiJb28T0S/TdsBI3Sjsy+keJrw==", + "dev": true + }, + "node_modules/@types/d3-time": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.3.tgz", + "integrity": "sha512-2p6olUZ4w3s+07q3Tm2dbiMZy5pCDfYwtLXXHUnVzXgQlZ/OyPtUz6OL382BkOuGlLXqfT+wqv8Fw2v8/0geBw==" + }, "node_modules/@types/date-fns": { "version": "2.6.0", "resolved": "https://registry.npmjs.org/@types/date-fns/-/date-fns-2.6.0.tgz", @@ -6433,6 +6456,93 @@ "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", "devOptional": true }, + "node_modules/d3-array": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", + "dependencies": { + "internmap": "1 - 2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-format": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz", + "integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "dependencies": { + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", + "dependencies": { + "d3-array": "2.10.0 - 3", + "d3-format": "1 - 3", + "d3-interpolate": "1.2.0 - 3", + "d3-time": "2.1.1 - 3", + "d3-time-format": "2 - 4" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale-chromatic": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", + "integrity": "sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==", + "dependencies": { + "d3-color": "1 - 3", + "d3-interpolate": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", + "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", + "dependencies": { + "d3-array": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time-format": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", + "dependencies": { + "d3-time": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/damerau-levenshtein": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", @@ -9563,6 +9673,14 @@ "node": ">= 0.4" } }, + "node_modules/internmap": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", + "engines": { + "node": ">=12" + } + }, "node_modules/invariant": { "version": "2.2.4", "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", diff --git a/nextjs/package.json b/nextjs/package.json index 4d914d1fc..781f17138 100644 --- a/nextjs/package.json +++ b/nextjs/package.json @@ -33,10 +33,13 @@ "@radix-ui/react-toggle-group": "^1.0.4", "@radix-ui/react-tooltip": "^1.0.7", "@tanstack/react-table": "^8.13.2", + "@types/d3-scale": "^4.0.8", "@types/date-fns": "^2.6.0", "class-variance-authority": "^0.7.0", "clsx": "^2.1.0", "cmdk": "^0.2.1", + "d3-scale": "^4.0.2", + "d3-scale-chromatic": "^3.1.0", "date-fns": "^3.5.0", "graphiql": "^3.1.1", "graphql": "^16.8.1", @@ -64,6 +67,7 @@ "@graphql-codegen/typescript-resolvers": "^4.0.6", "@graphql-typed-document-node/core": "^3.2.0", "@tailwindcss/forms": "^0.5.7", + "@types/d3-scale-chromatic": "^3.0.3", "@types/node": "^20", "@types/react": "^18", "@types/react-dom": "^18", diff --git a/nextjs/src/components/report/ReportMap.tsx b/nextjs/src/components/report/ReportMap.tsx index 2099953f1..a0610a970 100644 --- a/nextjs/src/components/report/ReportMap.tsx +++ b/nextjs/src/components/report/ReportMap.tsx @@ -1,11 +1,14 @@ "use client" -import { GetSourceGeoJsonQuery, GetSourceGeoJsonQueryVariables, MapReportLayersSummaryFragment } from "@/__generated__/graphql"; -import { useContext, useId } from "react"; -import Map, { Layer, Marker, Popup, Source } from "react-map-gl"; +import { GetSourceGeoJsonQuery, GetSourceGeoJsonQueryVariables, GroupedDataCount, MapReportLayersSummaryFragment } from "@/__generated__/graphql"; +import { useContext, useEffect, useId, useRef } from "react"; +import Map, { Layer, MapRef, Marker, Popup, Source, LayerProps, VectorSourceRaw } from "react-map-gl"; import { MAP_REPORT_LAYERS_SUMMARY } from "../dataConfig"; import { gql, useFragment, useQuery } from "@apollo/client"; import { ReportContext } from "@/app/reports/[id]/context"; +import { Expression } from "mapbox-gl"; +import { scaleSequential } from 'd3-scale' +import { interpolatePlasma } from 'd3-scale-chromatic' export function ReportMap () { const { id, update } = useContext(ReportContext) @@ -18,6 +21,123 @@ export function ReportMap () { }, }); + const mapboxRef = useRef(null) + + const TILESETS: Record, + mapboxLayerProps?: Omit, + data: Array, + downloadUrl?: string + }> = { + EERs: { + name: "regions", + singular: "region", + sourceId: "commonknowledge.awsfhx20", + downloadUrl: "https://ckan.publishing.service.gov.uk/dataset/european-electoral-regions-december-2018-boundaries-uk-buc1/resource/b268c97f-2507-4477-9149-0a0c5d2bfbca", + sourceLayerId: "European_Electoral_Regions_De-bxyqod", + promoteId: "eer18cd", + labelId: "eer18nm", + // @ts-ignore + data: layers.data.importedDataCountByRegion || [], + mapboxSourceProps: { + maxzoom: 9.5 + }, + mapboxLayerProps: { + maxzoom: 9.5 + } + }, + constituencies: { + name: "GE2019 constituencies", + singular: "constituency", + sourceId: "commonknowledge.4xqg91lc", + sourceLayerId: "Westminster_Parliamentary_Con-6i1rlq", + promoteId: "pcon16cd", + labelId: "pcon16nm", + // @ts-ignore + data: layers.data.importedDataCountByConstituency || [], + mapboxSourceProps: { + minzoom: 9.5, + maxzoom: 13, + }, + mapboxLayerProps: { + minzoom: 9.5, + maxzoom: 13, + } + }, + // constituencies2024: { + // name: "GE2024 constituencies", + // singular: "constituency", + // sourceId: "commonknowledge.b5t8td4w" + // promoteId: "PCON25CD", + // labelId: "PCON25NM" + // }, + // councils: { + // name: "councils", + // singular: "council", + // sourceId: "commonknowledge.9zcvsx9l", + // promoteId: "ctyua_code", + // labelId: "ctyua_name", + // }, + wards: { + name: "wards", + singular: "ward", + sourceId: "commonknowledge.0rzbo365", + promoteId: "WD23CD", + labelId: "WD23NM", + // @ts-ignore + data: layers.data.importedDataCountByWard || [], + mapboxSourceProps: { + minzoom: 13, + maxzoom: 18, + }, + mapboxLayerProps: { + minzoom: 13, + maxzoom: 18, + } + } + } + + useEffect(function setFeatureState() { + layers.data.importedDataCountByRegion?.forEach((region) => { + if (region?.areaId && region?.count) { + mapboxRef.current?.setFeatureState({ + source: TILESETS.EERs.sourceId, + sourceLayer: TILESETS.EERs.sourceLayerId, + id: region.areaId, + }, { + count: region.count, + // ...layers.data.layers?.map((layer) => { + // return { + // [layer?.source!.id]: layer?.source?.importedDataCountByRegion?.find(r => r?.areaId === region.areaId)?.count + // } + // }) + }) + console.log("setFeatureState", region.areaId, region.count) + } + }) + // layers.data.layers?.forEach((layer) => { + // if (layer?.source) { + // layer.source.importedDataCountByRegion?.forEach((region) => { + // if (region?.areaId && region?.count) { + // mapboxRef.current?.setFeatureState({ + // source: TILESETS.EERs.sourceId, + // id: region.areaId, + // }, { + // totalCount: + // [layer.source!.id]: region.count, + // }) + // } + // }) + // } + // }) + }, [layers, mapboxRef]) + return ( + {Object.entries(TILESETS).map(([key, tileset]) => { + const min = tileset.data.reduce( + (min, p) => p?.count! < min ? p?.count! : min, + tileset.data?.[0]?.count! + ) || 0 + const max = tileset.data.reduce( + (max, p) => p?.count! > max ? p?.count! : max, + tileset.data?.[0]?.count! + ) || 1 + const scale = scaleSequential() + .domain([min, max]) + .interpolator(interpolatePlasma) + console.log(scale) + + return ( + + {/* Shade area by count */} + + {/* Border of the boundary */} + + {/* Display count as a number in the centre of the area by a symbol layer */} + d.count?.toString() || "", + "?" + ), + "text-size": 25, + "symbol-placement": "point", + "text-offset": [0, -0.5], + "text-allow-overlap": true, + // "text-variable-anchor": ["center"], + // "text-ignore-placement": true, + }} + paint={{ + "text-color": "white", + }} + {...tileset.mapboxLayerProps || {}} + /> + d.label || "", + "?" + ), + "text-size": 13, + "symbol-placement": "point", + "text-offset": [0, 0.6], + "text-allow-overlap": true, + // "text-variable-anchor": ["center"], + // "text-ignore-placement": true, + }} + paint={{ + "text-color": "white", + "text-opacity": 0.7 + }} + {...tileset.mapboxLayerProps || {}} + /> + + ) + })} {layers.data.layers?.map((layer, index) => { return ( >(data: Array, mapboxIdField: string, dataIdField: string, value: (d: T) => string, defaultValue: any): Expression { + // For each fromId value, find the corresponding toId value and return the valueId + const expression: Expression = [ + "match", + ["get", mapboxIdField], + ...data.map((d) => { + return [ + // If + d[dataIdField], + // Then + value(d), + ] + }).filter((d) => !!d[0]).flat(), + defaultValue + ] + return expression +} + function MapboxGLClusteredPointsLayer ({ externalDataSourceId }: { externalDataSourceId: string }) { const { data, error } = useQuery(GET_SOURCE_DATA, { variables: { @@ -51,11 +299,15 @@ function MapboxGLClusteredPointsLayer ({ externalDataSourceId }: { externalDataS return ( <> - + From 306672aa47bd0b82fdec2b1ee012d58fcfcf498e Mon Sep 17 00:00:00 2001 From: Jan Baykara Date: Tue, 19 Mar 2024 14:36:55 +0000 Subject: [PATCH 10/18] Supply area geojson for analytical counts --- hub/graphql/types/geojson.py | 68 +++++++++++++++++++++++--------- hub/graphql/types/model_types.py | 60 +++++++++++++++++++++++++--- 2 files changed, 104 insertions(+), 24 deletions(-) diff --git a/hub/graphql/types/geojson.py b/hub/graphql/types/geojson.py index cd1e69581..891a23f8b 100644 --- a/hub/graphql/types/geojson.py +++ b/hub/graphql/types/geojson.py @@ -1,9 +1,11 @@ from enum import Enum from typing import List, Optional, Union +from django.contrib.gis.geos import Point, Polygon, MultiPolygon import strawberry from strawberry.scalars import JSON +# @strawberry.enum class GeoJSONTypes(Enum): @@ -13,45 +15,75 @@ class GeoJSONTypes(Enum): Polygon = "Polygon" MultiPolygon = "MultiPolygon" +# @strawberry.type class FeatureCollection: type: GeoJSONTypes.FeatureCollection = GeoJSONTypes.FeatureCollection features: List["Feature"] +# @strawberry.interface class Feature: - id: Optional[str] type: GeoJSONTypes.Feature = GeoJSONTypes.Feature - properties: JSON - geometry: Union["PointGeometry", "PolygonGeometry", "MultiPolygonGeometry"] - - -@strawberry.type -class PointFeature(Feature): - geometry: "PointGeometry" - - -@strawberry.interface -class Geometry: - type: GeoJSONTypes + id: Optional[str] + properties: Optional[JSON] +# @strawberry.type -class PointGeometry(Geometry): +class PointGeometry: type: GeoJSONTypes.Point = GeoJSONTypes.Point # lng, lat coordinates: List[float] - @strawberry.type -class PolygonGeometry(Geometry): +class PointFeature(Feature): + geometry: PointGeometry + + @classmethod + def from_geodjango(cls, point: Point, properties: dict = {}, id: str = None) -> "PointFeature": + return PointFeature( + id=str(id), + geometry=PointGeometry(coordinates=point), + properties=properties, + ) + +# + +@strawberry.type +class PolygonGeometry: type: GeoJSONTypes.Polygon = GeoJSONTypes.Polygon coordinates: List[List[List[float]]] - @strawberry.type -class MultiPolygonGeometry(Geometry): +class PolygonFeature(Feature): + geometry: PolygonGeometry + + @classmethod + def from_geodjango(cls, polygon: Polygon, properties: dict = {}, id: str = None) -> "PolygonFeature": + return PolygonFeature( + id=str(id), + geometry=PolygonGeometry(coordinates=polygon), + properties=properties, + ) + +# + +@strawberry.type +class MultiPolygonGeometry: type: GeoJSONTypes.MultiPolygon = GeoJSONTypes.MultiPolygon coordinates: List[List[List[List[float]]]] + +@strawberry.type +class MultiPolygonFeature(Feature): + geometry: MultiPolygonGeometry + + @classmethod + def from_geodjango(cls, multipolygon: MultiPolygon, properties: dict = {}, id: str = None) -> "MultiPolygonFeature": + return MultiPolygonFeature( + id=str(id), + geometry=MultiPolygonGeometry(coordinates=multipolygon), + properties=properties, + ) \ No newline at end of file diff --git a/hub/graphql/types/model_types.py b/hub/graphql/types/model_types.py index a451d5004..d4aca6a3f 100644 --- a/hub/graphql/types/model_types.py +++ b/hub/graphql/types/model_types.py @@ -10,7 +10,7 @@ from strawberry.scalars import JSON from hub import models -from hub.graphql.types.geojson import PointFeature, PointGeometry +from hub.graphql.types.geojson import PointFeature, MultiPolygonFeature from hub.graphql.utils import dict_key_field, fn_field @@ -137,12 +137,62 @@ class ExternalDataSourceFilter: geography_column_type: auto +@strawberry_django.type(models.Area) +class Area: + mapit_id: auto + gss: auto + name: auto + area_type: auto + geometry: auto + overlaps: auto + # So that we can pass in properties to the geojson Feature objects + extra_geojson_properties: strawberry.Private[object] + + @strawberry_django.field + def polygon(self, info: Info, with_parent_data: bool = False) -> Optional[MultiPolygonFeature]: + props = { + "name": self.name, + "gss": self.gss + } + if with_parent_data and hasattr(self, "extra_geojson_properties"): + props["extra_geojson_properties"] = self.extra_geojson_properties + + return MultiPolygonFeature.from_geodjango( + multipolygon=self.polygon, + id=self.gss, + properties=props + ) + + @strawberry_django.field + def point(self, info: Info, with_parent_data: bool = False) -> Optional[PointFeature]: + props = { + "name": self.name, + "gss": self.gss + } + if with_parent_data and hasattr(self, "extra_geojson_properties"): + props["extra_geojson_properties"] = self.extra_geojson_properties + + return PointFeature.from_geodjango( + point=self.point, + id=self.gss, + properties=props + ) + + @strawberry.type class GroupedDataCount: label: Optional[str] = dict_key_field() area_id: Optional[str] = dict_key_field() count: int = dict_key_field() + @strawberry_django.field + def gss_area(self, info: Info) -> Optional[Area]: + if self.get('area_id', None): + area = models.Area.objects.get(gss=self['area_id']) + area.extra_geojson_properties = self + return area + return None + @strawberry_django.type(models.ExternalDataSource, filters=ExternalDataSourceFilter) class ExternalDataSource: @@ -214,11 +264,9 @@ def geojson_point_features( ) -> List[PointFeature]: data = self.get_import_data() return [ - PointFeature( - id=str(generic_datum.data), - geometry=PointGeometry( - coordinates=[generic_datum.point.x, generic_datum.point.y] - ), + PointFeature.from_geodjango( + point=generic_datum, + id=generic_datum.data, properties=generic_datum.json, ) for generic_datum in data From 97e463edc3d0f08cf31f72281fa73a7e56951cc9 Mon Sep 17 00:00:00 2001 From: Jan Baykara Date: Tue, 19 Mar 2024 18:24:11 +0000 Subject: [PATCH 11/18] Chloropleths of region/constituency/ward and member markers --- hub/graphql/types/model_types.py | 4 +- hub/management/commands/import_regions.py | 2 +- hub/management/commands/import_wards.py | 1 + hub/models.py | 8 +- nextjs/package-lock.json | 21 + nextjs/package.json | 1 + nextjs/public/markers/default.png | Bin 0 -> 710 bytes nextjs/public/markers/default.svg | 6 + nextjs/public/markers/selected.png | Bin 0 -> 562 bytes nextjs/public/markers/selected.svg | 18 + nextjs/src/__generated__/gql.ts | 8 +- nextjs/src/__generated__/graphql.ts | 84 ++-- nextjs/src/app/graphiql/layout.tsx | 2 + nextjs/src/components/dataConfig.tsx | 30 ++ nextjs/src/components/report/ReportMap.tsx | 436 ++++++++++++--------- 15 files changed, 391 insertions(+), 230 deletions(-) create mode 100644 nextjs/public/markers/default.png create mode 100644 nextjs/public/markers/default.svg create mode 100644 nextjs/public/markers/selected.png create mode 100644 nextjs/public/markers/selected.svg diff --git a/hub/graphql/types/model_types.py b/hub/graphql/types/model_types.py index d4aca6a3f..49b610fb6 100644 --- a/hub/graphql/types/model_types.py +++ b/hub/graphql/types/model_types.py @@ -259,13 +259,13 @@ def webhook_healthcheck(self: models.ExternalDataSource, info) -> bool: return self.webhook_healthcheck() @strawberry_django.field - def geojson_point_features( + def imported_data_geojson_points( self: models.ExternalDataSource, info: Info ) -> List[PointFeature]: data = self.get_import_data() return [ PointFeature.from_geodjango( - point=generic_datum, + point=generic_datum.point, id=generic_datum.data, properties=generic_datum.json, ) diff --git a/hub/management/commands/import_regions.py b/hub/management/commands/import_regions.py index a50556980..6fe00c1b9 100644 --- a/hub/management/commands/import_regions.py +++ b/hub/management/commands/import_regions.py @@ -9,7 +9,6 @@ from utils import mapit import requests - class Command(BaseCommand): help = "Import historical European regions for high-level aggregation" @@ -46,6 +45,7 @@ def handle(self, quiet: bool = False, *args, **options): geom = GEOSGeometry(json.dumps(area['geometry'])) if isinstance(geom, Polygon): geom = MultiPolygon([geom]) + geom.srid = 27700 a.geometry = geom_str a.polygon = geom a.point = a.polygon.centroid diff --git a/hub/management/commands/import_wards.py b/hub/management/commands/import_wards.py index c4881cc34..af0be1c69 100644 --- a/hub/management/commands/import_wards.py +++ b/hub/management/commands/import_wards.py @@ -46,6 +46,7 @@ def handle(self, quiet: bool = False, *args, **options): geom = GEOSGeometry(json.dumps(area['geometry'])) if isinstance(geom, Polygon): geom = MultiPolygon([geom]) + geom.srid = 27700 a.geometry = geom_str a.polygon = geom a.point = a.polygon.centroid diff --git a/hub/models.py b/hub/models.py index cc1132d6f..836343ff1 100644 --- a/hub/models.py +++ b/hub/models.py @@ -658,8 +658,8 @@ class Meta: class GenericData(CommonData): - point = PointField(blank=True, null=True) - polygon = MultiPolygonField(blank=True, null=True) + point = PointField(srid=4326, blank=True, null=True) + polygon = MultiPolygonField(srid=4326, blank=True, null=True) postcode_data = JSONField(blank=True, null=True) def get_postcode_data(self) -> Optional[PostcodesIOResult]: @@ -675,8 +675,8 @@ class Area(models.Model): name = models.CharField(max_length=200) area_type = models.ForeignKey(AreaType, on_delete=models.CASCADE) geometry = models.TextField(blank=True, null=True) - polygon = MultiPolygonField(blank=True, null=True) - point = PointField(blank=True, null=True) + polygon = MultiPolygonField(srid=4326, blank=True, null=True) + point = PointField(srid=4326, blank=True, null=True) overlaps = models.ManyToManyField("self", through="AreaOverlap") def __str__(self): diff --git a/nextjs/package-lock.json b/nextjs/package-lock.json index c30134c5b..9ced3c25f 100644 --- a/nextjs/package-lock.json +++ b/nextjs/package-lock.json @@ -39,6 +39,7 @@ "date-fns": "^3.5.0", "graphiql": "^3.1.1", "graphql": "^16.8.1", + "jotai": "^2.7.1", "lucide-react": "^0.344.0", "mapbox-gl": "^3.2.0", "next": "14.1.0", @@ -10299,6 +10300,26 @@ "url": "https://github.com/sponsors/panva" } }, + "node_modules/jotai": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/jotai/-/jotai-2.7.1.tgz", + "integrity": "sha512-bsaTPn02nFgWNP6cBtg/htZhCu4s0wxqoklRHePp6l/vlsypR9eLn7diRliwXYWMXDpPvW/LLA2afI8vwgFFaw==", + "engines": { + "node": ">=12.20.0" + }, + "peerDependencies": { + "@types/react": ">=17.0.0", + "react": ">=17.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "react": { + "optional": true + } + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", diff --git a/nextjs/package.json b/nextjs/package.json index 781f17138..1db80d118 100644 --- a/nextjs/package.json +++ b/nextjs/package.json @@ -43,6 +43,7 @@ "date-fns": "^3.5.0", "graphiql": "^3.1.1", "graphql": "^16.8.1", + "jotai": "^2.7.1", "lucide-react": "^0.344.0", "mapbox-gl": "^3.2.0", "next": "14.1.0", diff --git a/nextjs/public/markers/default.png b/nextjs/public/markers/default.png new file mode 100644 index 0000000000000000000000000000000000000000..aab73ef729bda6898b2e76dc4ceec82af1347617 GIT binary patch literal 710 zcmV;%0y+JOP)9xlIJJ+Nc;0}_oF?FL;k1x=X%_~D3 zx|fb%>mfPU)^hwuR-;b+ZBd^6^ONlqFOyIVR^s6H&5(vGoUGE5Nr7sUAK5_gcLpGoh6k$R;!~KV+Wf81suzfh` z5NF5Dg$nfKwcmrm*1q~oV)*E-%dX>_4f4apGzQN)kL^;*D_k1M{ zKK$~q)v}z@O3vxu-_7ycM}IB{uHR|ve&9f;ScRqKAUlvsWtOW5;u*L>&aeoSLz9XY z_Z?+E1`=PK+33_t&e1go1kD29hnduRTNdN2ebh%ct^T zkswY+-Sy4vkXAAb`D<7##0j3~2Q}%J-L)xEGKNJNNgRFNU7uD;Q{vts%7Gjzs?%N3UU^5B|6K0j>(NpRJOBUy07*qoM6N<$g5_^KLI3~& literal 0 HcmV?d00001 diff --git a/nextjs/public/markers/default.svg b/nextjs/public/markers/default.svg new file mode 100644 index 000000000..1fd56709e --- /dev/null +++ b/nextjs/public/markers/default.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/nextjs/public/markers/selected.png b/nextjs/public/markers/selected.png new file mode 100644 index 0000000000000000000000000000000000000000..e52e76f318276d8b87addbd7a5c644d43a0cfe30 GIT binary patch literal 562 zcmV-20?qx2P)qr}tV8vJq6fdET zj3t*?*ChfIBo0s-S<6s#j30BXA+&>J5BeomC880FY}1%nVVxI+N)vTWbRb08nQVC4 z!9GQVj{%+zWVrLbXOVD)bC36A@cZ+2#a%8#Gl^WJ62{a@l57g@nTX6~D!~_-5AGRy z+Jn_4**)9AAK~%i#CnA0`6IfEC%KZrTq4EiU`9mT!;zvB~Y0R0eQh#T41TRgevUZ6jM;)Yi;fG*`4xx56tl=h-+XRKRQ98`!skF%{ue zoypg|Nh{n|9jufw)CxB1UBs8Zy^LESgnELy0XE4 + + + + + + + + + + + + + + + + + diff --git a/nextjs/src/__generated__/gql.ts b/nextjs/src/__generated__/gql.ts index 54095185a..fb1d977fa 100644 --- a/nextjs/src/__generated__/gql.ts +++ b/nextjs/src/__generated__/gql.ts @@ -43,9 +43,9 @@ const documents = { "\n fragment ExternalDataSourceCardFields on ExternalDataSource {\n id\n name\n connectionDetails {\n crmType: __typename\n }\n }\n": types.ExternalDataSourceCardFieldsFragmentDoc, "\n query ExternalDataSourceCard($ID: ID!) {\n externalDataSource(pk: $ID) {\n ...ExternalDataSourceCardFields\n }\n }\n \n": types.ExternalDataSourceCardDocument, "\n query EnrichmentLayers {\n externalDataSources {\n id\n name\n geographyColumn\n geographyColumnType\n dataType\n connectionDetails {\n __typename\n }\n fieldDefinitions {\n label\n value\n description\n }\n }\n }\n": types.EnrichmentLayersDocument, - "\n fragment MapReportLayersSummary on MapReport {\n importedDataCountByRegion {\n label\n areaId\n count\n }\n importedDataCountByConstituency {\n label\n areaId\n count\n }\n importedDataCountByWard {\n label\n areaId\n count\n }\n layers {\n name\n source {\n id\n name\n isImporting\n importedDataCount\n importedDataCountByRegion {\n label\n areaId\n count\n }\n importedDataCountByConstituency {\n label\n areaId\n count\n }\n importedDataCountByWard {\n label\n areaId\n count\n }\n }\n }\n }\n": types.MapReportLayersSummaryFragmentDoc, + "\n fragment MapReportLayersSummary on MapReport {\n importedDataCountByRegion {\n label\n areaId\n count\n gssArea {\n point {\n id\n type\n geometry {\n type\n coordinates\n }\n }\n }\n }\n importedDataCountByConstituency {\n label\n areaId\n count\n gssArea {\n point {\n id\n type\n geometry {\n type\n coordinates\n }\n }\n }\n }\n importedDataCountByWard {\n label\n areaId\n count\n gssArea {\n point {\n id\n type\n geometry {\n type\n coordinates\n }\n }\n }\n }\n layers {\n name\n source {\n id\n name\n isImporting\n importedDataCount\n importedDataCountByRegion {\n label\n areaId\n count\n }\n importedDataCountByConstituency {\n label\n areaId\n count\n }\n importedDataCountByWard {\n label\n areaId\n count\n }\n }\n }\n }\n": types.MapReportLayersSummaryFragmentDoc, "\n query GetMemberList {\n externalDataSources(filters: { dataType: MEMBER }) {\n id\n name\n importedDataCount\n }\n }\n": types.GetMemberListDocument, - "\n query GetSourceGeoJSON($externalDataSourceId: ID!) {\n externalDataSource(pk: $externalDataSourceId) {\n geojsonPointFeatures {\n id\n type\n geometry {\n type\n coordinates\n }\n }\n }\n }\n": types.GetSourceGeoJsonDocument, + "\n query GetSourceGeoJSON($externalDataSourceId: ID!) {\n externalDataSource(pk: $externalDataSourceId) {\n importedDataGeojsonPoints {\n id\n type\n geometry {\n type\n coordinates\n }\n }\n }\n }\n": types.GetSourceGeoJsonDocument, "\n mutation UpdateExternalDataSource($input: ExternalDataSourceInput!) {\n updateExternalDataSource(data: $input) {\n id\n name\n geographyColumn\n geographyColumnType\n autoUpdateEnabled\n updateMapping {\n source\n sourcePath\n destinationColumn\n }\n }\n }\n": types.UpdateExternalDataSourceDocument, "\n query PublicUser {\n publicUser {\n id\n username\n email\n }\n }\n": types.PublicUserDocument, }; @@ -187,7 +187,7 @@ export function gql(source: "\n query EnrichmentLayers {\n externalDataSourc /** * The gql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ -export function gql(source: "\n fragment MapReportLayersSummary on MapReport {\n importedDataCountByRegion {\n label\n areaId\n count\n }\n importedDataCountByConstituency {\n label\n areaId\n count\n }\n importedDataCountByWard {\n label\n areaId\n count\n }\n layers {\n name\n source {\n id\n name\n isImporting\n importedDataCount\n importedDataCountByRegion {\n label\n areaId\n count\n }\n importedDataCountByConstituency {\n label\n areaId\n count\n }\n importedDataCountByWard {\n label\n areaId\n count\n }\n }\n }\n }\n"): (typeof documents)["\n fragment MapReportLayersSummary on MapReport {\n importedDataCountByRegion {\n label\n areaId\n count\n }\n importedDataCountByConstituency {\n label\n areaId\n count\n }\n importedDataCountByWard {\n label\n areaId\n count\n }\n layers {\n name\n source {\n id\n name\n isImporting\n importedDataCount\n importedDataCountByRegion {\n label\n areaId\n count\n }\n importedDataCountByConstituency {\n label\n areaId\n count\n }\n importedDataCountByWard {\n label\n areaId\n count\n }\n }\n }\n }\n"]; +export function gql(source: "\n fragment MapReportLayersSummary on MapReport {\n importedDataCountByRegion {\n label\n areaId\n count\n gssArea {\n point {\n id\n type\n geometry {\n type\n coordinates\n }\n }\n }\n }\n importedDataCountByConstituency {\n label\n areaId\n count\n gssArea {\n point {\n id\n type\n geometry {\n type\n coordinates\n }\n }\n }\n }\n importedDataCountByWard {\n label\n areaId\n count\n gssArea {\n point {\n id\n type\n geometry {\n type\n coordinates\n }\n }\n }\n }\n layers {\n name\n source {\n id\n name\n isImporting\n importedDataCount\n importedDataCountByRegion {\n label\n areaId\n count\n }\n importedDataCountByConstituency {\n label\n areaId\n count\n }\n importedDataCountByWard {\n label\n areaId\n count\n }\n }\n }\n }\n"): (typeof documents)["\n fragment MapReportLayersSummary on MapReport {\n importedDataCountByRegion {\n label\n areaId\n count\n gssArea {\n point {\n id\n type\n geometry {\n type\n coordinates\n }\n }\n }\n }\n importedDataCountByConstituency {\n label\n areaId\n count\n gssArea {\n point {\n id\n type\n geometry {\n type\n coordinates\n }\n }\n }\n }\n importedDataCountByWard {\n label\n areaId\n count\n gssArea {\n point {\n id\n type\n geometry {\n type\n coordinates\n }\n }\n }\n }\n layers {\n name\n source {\n id\n name\n isImporting\n importedDataCount\n importedDataCountByRegion {\n label\n areaId\n count\n }\n importedDataCountByConstituency {\n label\n areaId\n count\n }\n importedDataCountByWard {\n label\n areaId\n count\n }\n }\n }\n }\n"]; /** * The gql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ @@ -195,7 +195,7 @@ export function gql(source: "\n query GetMemberList {\n externalDataSources( /** * The gql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ -export function gql(source: "\n query GetSourceGeoJSON($externalDataSourceId: ID!) {\n externalDataSource(pk: $externalDataSourceId) {\n geojsonPointFeatures {\n id\n type\n geometry {\n type\n coordinates\n }\n }\n }\n }\n"): (typeof documents)["\n query GetSourceGeoJSON($externalDataSourceId: ID!) {\n externalDataSource(pk: $externalDataSourceId) {\n geojsonPointFeatures {\n id\n type\n geometry {\n type\n coordinates\n }\n }\n }\n }\n"]; +export function gql(source: "\n query GetSourceGeoJSON($externalDataSourceId: ID!) {\n externalDataSource(pk: $externalDataSourceId) {\n importedDataGeojsonPoints {\n id\n type\n geometry {\n type\n coordinates\n }\n }\n }\n }\n"): (typeof documents)["\n query GetSourceGeoJSON($externalDataSourceId: ID!) {\n externalDataSource(pk: $externalDataSourceId) {\n importedDataGeojsonPoints {\n id\n type\n geometry {\n type\n coordinates\n }\n }\n }\n }\n"]; /** * The gql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ diff --git a/nextjs/src/__generated__/graphql.ts b/nextjs/src/__generated__/graphql.ts index a81e46de0..427da5f5a 100644 --- a/nextjs/src/__generated__/graphql.ts +++ b/nextjs/src/__generated__/graphql.ts @@ -65,7 +65,6 @@ export type AirtableSource = { fieldDefinitions?: Maybe>; geographyColumn?: Maybe; geographyColumnType: PostcodesIoGeographyTypes; - geojsonPointFeatures: Array; healthcheck: Scalars['Boolean']['output']; id: Scalars['UUID']['output']; importedDataCount: Scalars['Int']['output']; @@ -74,6 +73,7 @@ export type AirtableSource = { importedDataCountByCouncil: Array; importedDataCountByRegion: Array; importedDataCountByWard: Array; + importedDataGeojsonPoints: Array; isImporting: Scalars['Boolean']['output']; jobs: Array; lastUpdate: Scalars['DateTime']['output']; @@ -104,6 +104,31 @@ export type AirtableSourceInput = { updateMapping?: InputMaybe>; }; +/** Area(id, mapit_id, gss, name, area_type, geometry, polygon, point) */ +export type Area = { + __typename?: 'Area'; + areaType: DjangoModelType; + geometry?: Maybe; + gss: Scalars['String']['output']; + mapitId: Scalars['String']['output']; + name: Scalars['String']['output']; + overlaps: Array; + point?: Maybe; + polygon?: Maybe; +}; + + +/** Area(id, mapit_id, gss, name, area_type, geometry, polygon, point) */ +export type AreaPointArgs = { + withParentData?: Scalars['Boolean']['input']; +}; + + +/** Area(id, mapit_id, gss, name, area_type, geometry, polygon, point) */ +export type AreaPolygonArgs = { + withParentData?: Scalars['Boolean']['input']; +}; + export type AutoUpdateConfig = { __typename?: 'AutoUpdateConfig'; destinationColumn: Scalars['String']['output']; @@ -167,7 +192,6 @@ export type ExternalDataSource = { fieldDefinitions?: Maybe>; geographyColumn?: Maybe; geographyColumnType: PostcodesIoGeographyTypes; - geojsonPointFeatures: Array; healthcheck: Scalars['Boolean']['output']; id: Scalars['UUID']['output']; importedDataCount: Scalars['Int']['output']; @@ -176,6 +200,7 @@ export type ExternalDataSource = { importedDataCountByCouncil: Array; importedDataCountByRegion: Array; importedDataCountByWard: Array; + importedDataGeojsonPoints: Array; isImporting: Scalars['Boolean']['output']; jobs: Array; lastUpdate: Scalars['DateTime']['output']; @@ -219,9 +244,8 @@ export type ExternalDataSourceInput = { }; export type Feature = { - geometry: PointGeometryPolygonGeometryMultiPolygonGeometry; id?: Maybe; - properties: Scalars['JSON']['output']; + properties?: Maybe; type: GeoJsonTypes; }; @@ -240,14 +264,11 @@ export enum GeoJsonTypes { Polygon = 'Polygon' } -export type Geometry = { - type: GeoJsonTypes; -}; - export type GroupedDataCount = { __typename?: 'GroupedDataCount'; areaId?: Maybe; count: Scalars['Int']['output']; + gssArea?: Maybe; label?: Maybe; }; @@ -348,7 +369,15 @@ export type Membership = { user: User; }; -export type MultiPolygonGeometry = Geometry & { +export type MultiPolygonFeature = Feature & { + __typename?: 'MultiPolygonFeature'; + geometry: MultiPolygonGeometry; + id?: Maybe; + properties?: Maybe; + type: GeoJsonTypes; +}; + +export type MultiPolygonGeometry = { __typename?: 'MultiPolygonGeometry'; coordinates: Array>>>; type: GeoJsonTypes; @@ -613,24 +642,16 @@ export type PointFeature = Feature & { __typename?: 'PointFeature'; geometry: PointGeometry; id?: Maybe; - properties: Scalars['JSON']['output']; + properties?: Maybe; type: GeoJsonTypes; }; -export type PointGeometry = Geometry & { +export type PointGeometry = { __typename?: 'PointGeometry'; coordinates: Array; type: GeoJsonTypes; }; -export type PointGeometryPolygonGeometryMultiPolygonGeometry = MultiPolygonGeometry | PointGeometry | PolygonGeometry; - -export type PolygonGeometry = Geometry & { - __typename?: 'PolygonGeometry'; - coordinates: Array>>; - type: GeoJsonTypes; -}; - export enum PostcodesIoGeographyTypes { Constituency = 'CONSTITUENCY', Constituency_2025 = 'CONSTITUENCY_2025', @@ -1080,7 +1101,7 @@ export type EnrichmentLayersQueryVariables = Exact<{ [key: string]: never; }>; export type EnrichmentLayersQuery = { __typename?: 'Query', externalDataSources: Array<{ __typename?: 'ExternalDataSource', id: any, name: string, geographyColumn?: string | null, geographyColumnType: PostcodesIoGeographyTypes, dataType: DataSourceType, connectionDetails: { __typename: 'AirtableSource' }, fieldDefinitions?: Array<{ __typename?: 'FieldDefinition', label?: string | null, value: string, description?: string | null }> | null }> }; -export type MapReportLayersSummaryFragment = { __typename?: 'MapReport', importedDataCountByRegion: Array<{ __typename?: 'GroupedDataCount', label?: string | null, areaId?: string | null, count: number }>, importedDataCountByConstituency: Array<{ __typename?: 'GroupedDataCount', label?: string | null, areaId?: string | null, count: number }>, importedDataCountByWard: Array<{ __typename?: 'GroupedDataCount', label?: string | null, areaId?: string | null, count: number }>, layers?: Array<{ __typename?: 'MapLayer', name: string, source: { __typename?: 'ExternalDataSource', id: any, name: string, isImporting: boolean, importedDataCount: number, importedDataCountByRegion: Array<{ __typename?: 'GroupedDataCount', label?: string | null, areaId?: string | null, count: number }>, importedDataCountByConstituency: Array<{ __typename?: 'GroupedDataCount', label?: string | null, areaId?: string | null, count: number }>, importedDataCountByWard: Array<{ __typename?: 'GroupedDataCount', label?: string | null, areaId?: string | null, count: number }> } }> | null } & { ' $fragmentName'?: 'MapReportLayersSummaryFragment' }; +export type MapReportLayersSummaryFragment = { __typename?: 'MapReport', importedDataCountByRegion: Array<{ __typename?: 'GroupedDataCount', label?: string | null, areaId?: string | null, count: number, gssArea?: { __typename?: 'Area', point?: { __typename?: 'PointFeature', id?: string | null, type: GeoJsonTypes, geometry: { __typename?: 'PointGeometry', type: GeoJsonTypes, coordinates: Array } } | null } | null }>, importedDataCountByConstituency: Array<{ __typename?: 'GroupedDataCount', label?: string | null, areaId?: string | null, count: number, gssArea?: { __typename?: 'Area', point?: { __typename?: 'PointFeature', id?: string | null, type: GeoJsonTypes, geometry: { __typename?: 'PointGeometry', type: GeoJsonTypes, coordinates: Array } } | null } | null }>, importedDataCountByWard: Array<{ __typename?: 'GroupedDataCount', label?: string | null, areaId?: string | null, count: number, gssArea?: { __typename?: 'Area', point?: { __typename?: 'PointFeature', id?: string | null, type: GeoJsonTypes, geometry: { __typename?: 'PointGeometry', type: GeoJsonTypes, coordinates: Array } } | null } | null }>, layers?: Array<{ __typename?: 'MapLayer', name: string, source: { __typename?: 'ExternalDataSource', id: any, name: string, isImporting: boolean, importedDataCount: number, importedDataCountByRegion: Array<{ __typename?: 'GroupedDataCount', label?: string | null, areaId?: string | null, count: number }>, importedDataCountByConstituency: Array<{ __typename?: 'GroupedDataCount', label?: string | null, areaId?: string | null, count: number }>, importedDataCountByWard: Array<{ __typename?: 'GroupedDataCount', label?: string | null, areaId?: string | null, count: number }> } }> | null } & { ' $fragmentName'?: 'MapReportLayersSummaryFragment' }; export type GetMemberListQueryVariables = Exact<{ [key: string]: never; }>; @@ -1092,7 +1113,7 @@ export type GetSourceGeoJsonQueryVariables = Exact<{ }>; -export type GetSourceGeoJsonQuery = { __typename?: 'Query', externalDataSource: { __typename?: 'ExternalDataSource', geojsonPointFeatures: Array<{ __typename?: 'PointFeature', id?: string | null, type: GeoJsonTypes, geometry: { __typename?: 'PointGeometry', type: GeoJsonTypes, coordinates: Array } }> } }; +export type GetSourceGeoJsonQuery = { __typename?: 'Query', externalDataSource: { __typename?: 'ExternalDataSource', importedDataGeojsonPoints: Array<{ __typename?: 'PointFeature', id?: string | null, type: GeoJsonTypes, geometry: { __typename?: 'PointGeometry', type: GeoJsonTypes, coordinates: Array } }> } }; export type UpdateExternalDataSourceMutationVariables = Exact<{ input: ExternalDataSourceInput; @@ -1106,8 +1127,8 @@ export type PublicUserQueryVariables = Exact<{ [key: string]: never; }>; export type PublicUserQuery = { __typename?: 'Query', publicUser?: { __typename?: 'UserType', id: string, username: string, email: string } | null }; -export const MapReportLayersSummaryFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"MapReportLayersSummary"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"MapReport"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"importedDataCountByRegion"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"label"}},{"kind":"Field","name":{"kind":"Name","value":"areaId"}},{"kind":"Field","name":{"kind":"Name","value":"count"}}]}},{"kind":"Field","name":{"kind":"Name","value":"importedDataCountByConstituency"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"label"}},{"kind":"Field","name":{"kind":"Name","value":"areaId"}},{"kind":"Field","name":{"kind":"Name","value":"count"}}]}},{"kind":"Field","name":{"kind":"Name","value":"importedDataCountByWard"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"label"}},{"kind":"Field","name":{"kind":"Name","value":"areaId"}},{"kind":"Field","name":{"kind":"Name","value":"count"}}]}},{"kind":"Field","name":{"kind":"Name","value":"layers"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"source"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"isImporting"}},{"kind":"Field","name":{"kind":"Name","value":"importedDataCount"}},{"kind":"Field","name":{"kind":"Name","value":"importedDataCountByRegion"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"label"}},{"kind":"Field","name":{"kind":"Name","value":"areaId"}},{"kind":"Field","name":{"kind":"Name","value":"count"}}]}},{"kind":"Field","name":{"kind":"Name","value":"importedDataCountByConstituency"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"label"}},{"kind":"Field","name":{"kind":"Name","value":"areaId"}},{"kind":"Field","name":{"kind":"Name","value":"count"}}]}},{"kind":"Field","name":{"kind":"Name","value":"importedDataCountByWard"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"label"}},{"kind":"Field","name":{"kind":"Name","value":"areaId"}},{"kind":"Field","name":{"kind":"Name","value":"count"}}]}}]}}]}}]}}]} as unknown as DocumentNode; -export const MapReportPageFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"MapReportPage"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"MapReport"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"MapReportLayersSummary"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"MapReportLayersSummary"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"MapReport"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"importedDataCountByRegion"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"label"}},{"kind":"Field","name":{"kind":"Name","value":"areaId"}},{"kind":"Field","name":{"kind":"Name","value":"count"}}]}},{"kind":"Field","name":{"kind":"Name","value":"importedDataCountByConstituency"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"label"}},{"kind":"Field","name":{"kind":"Name","value":"areaId"}},{"kind":"Field","name":{"kind":"Name","value":"count"}}]}},{"kind":"Field","name":{"kind":"Name","value":"importedDataCountByWard"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"label"}},{"kind":"Field","name":{"kind":"Name","value":"areaId"}},{"kind":"Field","name":{"kind":"Name","value":"count"}}]}},{"kind":"Field","name":{"kind":"Name","value":"layers"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"source"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"isImporting"}},{"kind":"Field","name":{"kind":"Name","value":"importedDataCount"}},{"kind":"Field","name":{"kind":"Name","value":"importedDataCountByRegion"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"label"}},{"kind":"Field","name":{"kind":"Name","value":"areaId"}},{"kind":"Field","name":{"kind":"Name","value":"count"}}]}},{"kind":"Field","name":{"kind":"Name","value":"importedDataCountByConstituency"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"label"}},{"kind":"Field","name":{"kind":"Name","value":"areaId"}},{"kind":"Field","name":{"kind":"Name","value":"count"}}]}},{"kind":"Field","name":{"kind":"Name","value":"importedDataCountByWard"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"label"}},{"kind":"Field","name":{"kind":"Name","value":"areaId"}},{"kind":"Field","name":{"kind":"Name","value":"count"}}]}}]}}]}}]}}]} as unknown as DocumentNode; +export const MapReportLayersSummaryFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"MapReportLayersSummary"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"MapReport"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"importedDataCountByRegion"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"label"}},{"kind":"Field","name":{"kind":"Name","value":"areaId"}},{"kind":"Field","name":{"kind":"Name","value":"count"}},{"kind":"Field","name":{"kind":"Name","value":"gssArea"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"point"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"geometry"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"coordinates"}}]}}]}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"importedDataCountByConstituency"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"label"}},{"kind":"Field","name":{"kind":"Name","value":"areaId"}},{"kind":"Field","name":{"kind":"Name","value":"count"}},{"kind":"Field","name":{"kind":"Name","value":"gssArea"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"point"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"geometry"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"coordinates"}}]}}]}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"importedDataCountByWard"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"label"}},{"kind":"Field","name":{"kind":"Name","value":"areaId"}},{"kind":"Field","name":{"kind":"Name","value":"count"}},{"kind":"Field","name":{"kind":"Name","value":"gssArea"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"point"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"geometry"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"coordinates"}}]}}]}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"layers"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"source"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"isImporting"}},{"kind":"Field","name":{"kind":"Name","value":"importedDataCount"}},{"kind":"Field","name":{"kind":"Name","value":"importedDataCountByRegion"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"label"}},{"kind":"Field","name":{"kind":"Name","value":"areaId"}},{"kind":"Field","name":{"kind":"Name","value":"count"}}]}},{"kind":"Field","name":{"kind":"Name","value":"importedDataCountByConstituency"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"label"}},{"kind":"Field","name":{"kind":"Name","value":"areaId"}},{"kind":"Field","name":{"kind":"Name","value":"count"}}]}},{"kind":"Field","name":{"kind":"Name","value":"importedDataCountByWard"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"label"}},{"kind":"Field","name":{"kind":"Name","value":"areaId"}},{"kind":"Field","name":{"kind":"Name","value":"count"}}]}}]}}]}}]}}]} as unknown as DocumentNode; +export const MapReportPageFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"MapReportPage"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"MapReport"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"MapReportLayersSummary"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"MapReportLayersSummary"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"MapReport"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"importedDataCountByRegion"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"label"}},{"kind":"Field","name":{"kind":"Name","value":"areaId"}},{"kind":"Field","name":{"kind":"Name","value":"count"}},{"kind":"Field","name":{"kind":"Name","value":"gssArea"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"point"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"geometry"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"coordinates"}}]}}]}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"importedDataCountByConstituency"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"label"}},{"kind":"Field","name":{"kind":"Name","value":"areaId"}},{"kind":"Field","name":{"kind":"Name","value":"count"}},{"kind":"Field","name":{"kind":"Name","value":"gssArea"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"point"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"geometry"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"coordinates"}}]}}]}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"importedDataCountByWard"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"label"}},{"kind":"Field","name":{"kind":"Name","value":"areaId"}},{"kind":"Field","name":{"kind":"Name","value":"count"}},{"kind":"Field","name":{"kind":"Name","value":"gssArea"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"point"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"geometry"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"coordinates"}}]}}]}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"layers"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"source"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"isImporting"}},{"kind":"Field","name":{"kind":"Name","value":"importedDataCount"}},{"kind":"Field","name":{"kind":"Name","value":"importedDataCountByRegion"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"label"}},{"kind":"Field","name":{"kind":"Name","value":"areaId"}},{"kind":"Field","name":{"kind":"Name","value":"count"}}]}},{"kind":"Field","name":{"kind":"Name","value":"importedDataCountByConstituency"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"label"}},{"kind":"Field","name":{"kind":"Name","value":"areaId"}},{"kind":"Field","name":{"kind":"Name","value":"count"}}]}},{"kind":"Field","name":{"kind":"Name","value":"importedDataCountByWard"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"label"}},{"kind":"Field","name":{"kind":"Name","value":"areaId"}},{"kind":"Field","name":{"kind":"Name","value":"count"}}]}}]}}]}}]}}]} as unknown as DocumentNode; export const DataSourceCardFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"DataSourceCard"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ExternalDataSource"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"dataType"}},{"kind":"Field","name":{"kind":"Name","value":"connectionDetails"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","alias":{"kind":"Name","value":"crmType"},"name":{"kind":"Name","value":"__typename"}}]}},{"kind":"Field","name":{"kind":"Name","value":"autoUpdateEnabled"}},{"kind":"Field","name":{"kind":"Name","value":"updateMapping"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"source"}},{"kind":"Field","name":{"kind":"Name","value":"sourcePath"}},{"kind":"Field","name":{"kind":"Name","value":"destinationColumn"}}]}},{"kind":"Field","name":{"kind":"Name","value":"jobs"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"lastEventAt"}},{"kind":"Field","name":{"kind":"Name","value":"status"}}]}}]}}]} as unknown as DocumentNode; export const ExternalDataSourceCardFieldsFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ExternalDataSourceCardFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ExternalDataSource"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"connectionDetails"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","alias":{"kind":"Name","value":"crmType"},"name":{"kind":"Name","value":"__typename"}}]}}]}}]} as unknown as DocumentNode; export const ListExternalDataSourcesDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"ListExternalDataSources"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"externalDataSources"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"dataType"}},{"kind":"Field","name":{"kind":"Name","value":"connectionDetails"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","alias":{"kind":"Name","value":"crmType"},"name":{"kind":"Name","value":"__typename"}}]}},{"kind":"Field","name":{"kind":"Name","value":"autoUpdateEnabled"}},{"kind":"Field","name":{"kind":"Name","value":"jobs"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"lastEventAt"}},{"kind":"Field","name":{"kind":"Name","value":"status"}}]}},{"kind":"Field","name":{"kind":"Name","value":"updateMapping"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"source"}},{"kind":"Field","name":{"kind":"Name","value":"sourcePath"}},{"kind":"Field","name":{"kind":"Name","value":"destinationColumn"}}]}}]}}]}}]} as unknown as DocumentNode; @@ -1127,8 +1148,8 @@ export const ExampleDocument = {"kind":"Document","definitions":[{"kind":"Operat export const LoginDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"Login"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"username"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"password"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"tokenAuth"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"username"},"value":{"kind":"Variable","name":{"kind":"Name","value":"username"}}},{"kind":"Argument","name":{"kind":"Name","value":"password"},"value":{"kind":"Variable","name":{"kind":"Name","value":"password"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"errors"}},{"kind":"Field","name":{"kind":"Name","value":"success"}},{"kind":"Field","name":{"kind":"Name","value":"token"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"token"}},{"kind":"Field","name":{"kind":"Name","value":"payload"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"exp"}}]}}]}}]}}]}}]} as unknown as DocumentNode; export const RegisterDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"Register"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"email"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"password1"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"password2"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"username"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"register"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"email"},"value":{"kind":"Variable","name":{"kind":"Name","value":"email"}}},{"kind":"Argument","name":{"kind":"Name","value":"password1"},"value":{"kind":"Variable","name":{"kind":"Name","value":"password1"}}},{"kind":"Argument","name":{"kind":"Name","value":"password2"},"value":{"kind":"Variable","name":{"kind":"Name","value":"password2"}}},{"kind":"Argument","name":{"kind":"Name","value":"username"},"value":{"kind":"Variable","name":{"kind":"Name","value":"username"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"errors"}},{"kind":"Field","name":{"kind":"Name","value":"success"}}]}}]}}]} as unknown as DocumentNode; export const GetMapReportNameDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetMapReportName"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ID"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"mapReport"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"pk"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}}]}}]} as unknown as DocumentNode; -export const GetMapReportDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetMapReport"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ID"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"mapReport"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"pk"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"MapReportPage"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"MapReportLayersSummary"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"MapReport"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"importedDataCountByRegion"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"label"}},{"kind":"Field","name":{"kind":"Name","value":"areaId"}},{"kind":"Field","name":{"kind":"Name","value":"count"}}]}},{"kind":"Field","name":{"kind":"Name","value":"importedDataCountByConstituency"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"label"}},{"kind":"Field","name":{"kind":"Name","value":"areaId"}},{"kind":"Field","name":{"kind":"Name","value":"count"}}]}},{"kind":"Field","name":{"kind":"Name","value":"importedDataCountByWard"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"label"}},{"kind":"Field","name":{"kind":"Name","value":"areaId"}},{"kind":"Field","name":{"kind":"Name","value":"count"}}]}},{"kind":"Field","name":{"kind":"Name","value":"layers"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"source"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"isImporting"}},{"kind":"Field","name":{"kind":"Name","value":"importedDataCount"}},{"kind":"Field","name":{"kind":"Name","value":"importedDataCountByRegion"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"label"}},{"kind":"Field","name":{"kind":"Name","value":"areaId"}},{"kind":"Field","name":{"kind":"Name","value":"count"}}]}},{"kind":"Field","name":{"kind":"Name","value":"importedDataCountByConstituency"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"label"}},{"kind":"Field","name":{"kind":"Name","value":"areaId"}},{"kind":"Field","name":{"kind":"Name","value":"count"}}]}},{"kind":"Field","name":{"kind":"Name","value":"importedDataCountByWard"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"label"}},{"kind":"Field","name":{"kind":"Name","value":"areaId"}},{"kind":"Field","name":{"kind":"Name","value":"count"}}]}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"MapReportPage"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"MapReport"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"MapReportLayersSummary"}}]}}]} as unknown as DocumentNode; -export const UpdateMapReportDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"UpdateMapReport"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"MapReportInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"updateMapReport"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"data"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"MapReportPage"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"MapReportLayersSummary"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"MapReport"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"importedDataCountByRegion"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"label"}},{"kind":"Field","name":{"kind":"Name","value":"areaId"}},{"kind":"Field","name":{"kind":"Name","value":"count"}}]}},{"kind":"Field","name":{"kind":"Name","value":"importedDataCountByConstituency"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"label"}},{"kind":"Field","name":{"kind":"Name","value":"areaId"}},{"kind":"Field","name":{"kind":"Name","value":"count"}}]}},{"kind":"Field","name":{"kind":"Name","value":"importedDataCountByWard"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"label"}},{"kind":"Field","name":{"kind":"Name","value":"areaId"}},{"kind":"Field","name":{"kind":"Name","value":"count"}}]}},{"kind":"Field","name":{"kind":"Name","value":"layers"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"source"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"isImporting"}},{"kind":"Field","name":{"kind":"Name","value":"importedDataCount"}},{"kind":"Field","name":{"kind":"Name","value":"importedDataCountByRegion"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"label"}},{"kind":"Field","name":{"kind":"Name","value":"areaId"}},{"kind":"Field","name":{"kind":"Name","value":"count"}}]}},{"kind":"Field","name":{"kind":"Name","value":"importedDataCountByConstituency"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"label"}},{"kind":"Field","name":{"kind":"Name","value":"areaId"}},{"kind":"Field","name":{"kind":"Name","value":"count"}}]}},{"kind":"Field","name":{"kind":"Name","value":"importedDataCountByWard"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"label"}},{"kind":"Field","name":{"kind":"Name","value":"areaId"}},{"kind":"Field","name":{"kind":"Name","value":"count"}}]}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"MapReportPage"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"MapReport"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"MapReportLayersSummary"}}]}}]} as unknown as DocumentNode; +export const GetMapReportDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetMapReport"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ID"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"mapReport"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"pk"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"MapReportPage"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"MapReportLayersSummary"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"MapReport"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"importedDataCountByRegion"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"label"}},{"kind":"Field","name":{"kind":"Name","value":"areaId"}},{"kind":"Field","name":{"kind":"Name","value":"count"}},{"kind":"Field","name":{"kind":"Name","value":"gssArea"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"point"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"geometry"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"coordinates"}}]}}]}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"importedDataCountByConstituency"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"label"}},{"kind":"Field","name":{"kind":"Name","value":"areaId"}},{"kind":"Field","name":{"kind":"Name","value":"count"}},{"kind":"Field","name":{"kind":"Name","value":"gssArea"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"point"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"geometry"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"coordinates"}}]}}]}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"importedDataCountByWard"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"label"}},{"kind":"Field","name":{"kind":"Name","value":"areaId"}},{"kind":"Field","name":{"kind":"Name","value":"count"}},{"kind":"Field","name":{"kind":"Name","value":"gssArea"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"point"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"geometry"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"coordinates"}}]}}]}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"layers"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"source"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"isImporting"}},{"kind":"Field","name":{"kind":"Name","value":"importedDataCount"}},{"kind":"Field","name":{"kind":"Name","value":"importedDataCountByRegion"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"label"}},{"kind":"Field","name":{"kind":"Name","value":"areaId"}},{"kind":"Field","name":{"kind":"Name","value":"count"}}]}},{"kind":"Field","name":{"kind":"Name","value":"importedDataCountByConstituency"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"label"}},{"kind":"Field","name":{"kind":"Name","value":"areaId"}},{"kind":"Field","name":{"kind":"Name","value":"count"}}]}},{"kind":"Field","name":{"kind":"Name","value":"importedDataCountByWard"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"label"}},{"kind":"Field","name":{"kind":"Name","value":"areaId"}},{"kind":"Field","name":{"kind":"Name","value":"count"}}]}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"MapReportPage"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"MapReport"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"MapReportLayersSummary"}}]}}]} as unknown as DocumentNode; +export const UpdateMapReportDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"UpdateMapReport"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"MapReportInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"updateMapReport"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"data"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"MapReportPage"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"MapReportLayersSummary"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"MapReport"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"importedDataCountByRegion"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"label"}},{"kind":"Field","name":{"kind":"Name","value":"areaId"}},{"kind":"Field","name":{"kind":"Name","value":"count"}},{"kind":"Field","name":{"kind":"Name","value":"gssArea"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"point"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"geometry"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"coordinates"}}]}}]}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"importedDataCountByConstituency"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"label"}},{"kind":"Field","name":{"kind":"Name","value":"areaId"}},{"kind":"Field","name":{"kind":"Name","value":"count"}},{"kind":"Field","name":{"kind":"Name","value":"gssArea"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"point"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"geometry"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"coordinates"}}]}}]}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"importedDataCountByWard"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"label"}},{"kind":"Field","name":{"kind":"Name","value":"areaId"}},{"kind":"Field","name":{"kind":"Name","value":"count"}},{"kind":"Field","name":{"kind":"Name","value":"gssArea"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"point"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"geometry"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"coordinates"}}]}}]}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"layers"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"source"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"isImporting"}},{"kind":"Field","name":{"kind":"Name","value":"importedDataCount"}},{"kind":"Field","name":{"kind":"Name","value":"importedDataCountByRegion"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"label"}},{"kind":"Field","name":{"kind":"Name","value":"areaId"}},{"kind":"Field","name":{"kind":"Name","value":"count"}}]}},{"kind":"Field","name":{"kind":"Name","value":"importedDataCountByConstituency"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"label"}},{"kind":"Field","name":{"kind":"Name","value":"areaId"}},{"kind":"Field","name":{"kind":"Name","value":"count"}}]}},{"kind":"Field","name":{"kind":"Name","value":"importedDataCountByWard"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"label"}},{"kind":"Field","name":{"kind":"Name","value":"areaId"}},{"kind":"Field","name":{"kind":"Name","value":"count"}}]}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"MapReportPage"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"MapReport"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"MapReportLayersSummary"}}]}}]} as unknown as DocumentNode; export const DeleteMapReportDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"DeleteMapReport"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"IDObject"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"deleteMapReport"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"data"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}}]}}]} as unknown as DocumentNode; export const AutoUpdateWebhookRefreshDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"AutoUpdateWebhookRefresh"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"ID"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"refreshWebhooks"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"externalDataSourceId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"ID"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"webhookHealthcheck"}}]}}]}}]} as unknown as DocumentNode; export const ExternalDataSourceAutoUpdateCardDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"ExternalDataSourceAutoUpdateCard"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"ID"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ID"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"externalDataSource"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"pk"},"value":{"kind":"Variable","name":{"kind":"Name","value":"ID"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"DataSourceCard"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"DataSourceCard"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ExternalDataSource"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"dataType"}},{"kind":"Field","name":{"kind":"Name","value":"connectionDetails"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","alias":{"kind":"Name","value":"crmType"},"name":{"kind":"Name","value":"__typename"}}]}},{"kind":"Field","name":{"kind":"Name","value":"autoUpdateEnabled"}},{"kind":"Field","name":{"kind":"Name","value":"updateMapping"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"source"}},{"kind":"Field","name":{"kind":"Name","value":"sourcePath"}},{"kind":"Field","name":{"kind":"Name","value":"destinationColumn"}}]}},{"kind":"Field","name":{"kind":"Name","value":"jobs"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"lastEventAt"}},{"kind":"Field","name":{"kind":"Name","value":"status"}}]}}]}}]} as unknown as DocumentNode; @@ -1138,7 +1159,7 @@ export const TriggerFullUpdateDocument = {"kind":"Document","definitions":[{"kin export const ExternalDataSourceCardDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"ExternalDataSourceCard"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"ID"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ID"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"externalDataSource"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"pk"},"value":{"kind":"Variable","name":{"kind":"Name","value":"ID"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"ExternalDataSourceCardFields"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"ExternalDataSourceCardFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ExternalDataSource"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"connectionDetails"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","alias":{"kind":"Name","value":"crmType"},"name":{"kind":"Name","value":"__typename"}}]}}]}}]} as unknown as DocumentNode; export const EnrichmentLayersDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"EnrichmentLayers"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"externalDataSources"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"geographyColumn"}},{"kind":"Field","name":{"kind":"Name","value":"geographyColumnType"}},{"kind":"Field","name":{"kind":"Name","value":"dataType"}},{"kind":"Field","name":{"kind":"Name","value":"connectionDetails"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"__typename"}}]}},{"kind":"Field","name":{"kind":"Name","value":"fieldDefinitions"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"label"}},{"kind":"Field","name":{"kind":"Name","value":"value"}},{"kind":"Field","name":{"kind":"Name","value":"description"}}]}}]}}]}}]} as unknown as DocumentNode; export const GetMemberListDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetMemberList"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"externalDataSources"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"filters"},"value":{"kind":"ObjectValue","fields":[{"kind":"ObjectField","name":{"kind":"Name","value":"dataType"},"value":{"kind":"EnumValue","value":"MEMBER"}}]}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"importedDataCount"}}]}}]}}]} as unknown as DocumentNode; -export const GetSourceGeoJsonDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetSourceGeoJSON"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"externalDataSourceId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ID"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"externalDataSource"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"pk"},"value":{"kind":"Variable","name":{"kind":"Name","value":"externalDataSourceId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"geojsonPointFeatures"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"geometry"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"coordinates"}}]}}]}}]}}]}}]} as unknown as DocumentNode; +export const GetSourceGeoJsonDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetSourceGeoJSON"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"externalDataSourceId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ID"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"externalDataSource"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"pk"},"value":{"kind":"Variable","name":{"kind":"Name","value":"externalDataSourceId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"importedDataGeojsonPoints"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"geometry"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"coordinates"}}]}}]}}]}}]}}]} as unknown as DocumentNode; export const UpdateExternalDataSourceDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"UpdateExternalDataSource"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ExternalDataSourceInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"updateExternalDataSource"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"data"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"geographyColumn"}},{"kind":"Field","name":{"kind":"Name","value":"geographyColumnType"}},{"kind":"Field","name":{"kind":"Name","value":"autoUpdateEnabled"}},{"kind":"Field","name":{"kind":"Name","value":"updateMapping"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"source"}},{"kind":"Field","name":{"kind":"Name","value":"sourcePath"}},{"kind":"Field","name":{"kind":"Name","value":"destinationColumn"}}]}}]}}]}}]} as unknown as DocumentNode; export const PublicUserDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"PublicUser"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"publicUser"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"username"}},{"kind":"Field","name":{"kind":"Name","value":"email"}}]}}]}}]} as unknown as DocumentNode; @@ -1154,20 +1175,11 @@ export const PublicUserDocument = {"kind":"Document","definitions":[{"kind":"Ope "OperationInfo" ], "Feature": [ + "MultiPolygonFeature", "PointFeature" ], - "Geometry": [ - "MultiPolygonGeometry", - "PointGeometry", - "PolygonGeometry" - ], "OutputInterface": [ "ObtainJSONWebTokenType" - ], - "PointGeometryPolygonGeometryMultiPolygonGeometry": [ - "MultiPolygonGeometry", - "PointGeometry", - "PolygonGeometry" ] } }; diff --git a/nextjs/src/app/graphiql/layout.tsx b/nextjs/src/app/graphiql/layout.tsx index 48b625c23..812dd3720 100644 --- a/nextjs/src/app/graphiql/layout.tsx +++ b/nextjs/src/app/graphiql/layout.tsx @@ -1,6 +1,8 @@ +import { useRequireAuth } from "@/hooks/auth"; import { Metadata } from "next"; export default function GraphiQL ({ children }: { children: React.ReactNode }) { + useRequireAuth() return
{children}
} diff --git a/nextjs/src/components/dataConfig.tsx b/nextjs/src/components/dataConfig.tsx index 00abb6754..9b35b6e7f 100644 --- a/nextjs/src/components/dataConfig.tsx +++ b/nextjs/src/components/dataConfig.tsx @@ -205,16 +205,46 @@ export const MAP_REPORT_LAYERS_SUMMARY = gql` label areaId count + gssArea { + point { + id + type + geometry { + type + coordinates + } + } + } } importedDataCountByConstituency { label areaId count + gssArea { + point { + id + type + geometry { + type + coordinates + } + } + } } importedDataCountByWard { label areaId count + gssArea { + point { + id + type + geometry { + type + coordinates + } + } + } } layers { name diff --git a/nextjs/src/components/report/ReportMap.tsx b/nextjs/src/components/report/ReportMap.tsx index a0610a970..d50d5b009 100644 --- a/nextjs/src/components/report/ReportMap.tsx +++ b/nextjs/src/components/report/ReportMap.tsx @@ -1,17 +1,21 @@ "use client" import { GetSourceGeoJsonQuery, GetSourceGeoJsonQueryVariables, GroupedDataCount, MapReportLayersSummaryFragment } from "@/__generated__/graphql"; -import { useContext, useEffect, useId, useRef } from "react"; -import Map, { Layer, MapRef, Marker, Popup, Source, LayerProps, VectorSourceRaw } from "react-map-gl"; +import { Fragment, useContext, useEffect, useRef, useState } from "react"; +import Map, { Layer, MapRef, Source, LayerProps } from "react-map-gl"; import { MAP_REPORT_LAYERS_SUMMARY } from "../dataConfig"; import { gql, useFragment, useQuery } from "@apollo/client"; import { ReportContext } from "@/app/reports/[id]/context"; import { Expression } from "mapbox-gl"; -import { scaleSequential } from 'd3-scale' -import { interpolatePlasma } from 'd3-scale-chromatic' +import { scaleLinear, scaleSequential } from 'd3-scale' +import { interpolateInferno } from 'd3-scale-chromatic' + +const MAX_REGION_ZOOM = 8 +const MAX_CONSTITUENCY_ZOOM = 11.5 +const MIN_MEMBERS_ZOOM = MAX_CONSTITUENCY_ZOOM export function ReportMap () { - const { id, update } = useContext(ReportContext) + const { id } = useContext(ReportContext) const layers = useFragment({ fragment: MAP_REPORT_LAYERS_SUMMARY, fragmentName: "MapReportLayersSummary", @@ -30,7 +34,7 @@ export function ReportMap () { sourceLayerId?: string, promoteId: string, labelId: string, - mapboxSourceProps: Omit, + mapboxSourceProps?: { maxzoom?: number }, mapboxLayerProps?: Omit, data: Array, downloadUrl?: string @@ -46,10 +50,10 @@ export function ReportMap () { // @ts-ignore data: layers.data.importedDataCountByRegion || [], mapboxSourceProps: { - maxzoom: 9.5 + maxzoom: MAX_REGION_ZOOM }, mapboxLayerProps: { - maxzoom: 9.5 + maxzoom: MAX_REGION_ZOOM } }, constituencies: { @@ -62,14 +66,27 @@ export function ReportMap () { // @ts-ignore data: layers.data.importedDataCountByConstituency || [], mapboxSourceProps: { - minzoom: 9.5, - maxzoom: 13, + maxzoom: MAX_CONSTITUENCY_ZOOM, }, mapboxLayerProps: { - minzoom: 9.5, - maxzoom: 13, + minzoom: MAX_REGION_ZOOM, + maxzoom: MAX_CONSTITUENCY_ZOOM, } }, + wards: { + name: "wards", + singular: "ward", + sourceId: "commonknowledge.0rzbo365", + sourceLayerId: "Wards_Dec_2023_UK_Boundaries_-7wzb6g", + promoteId: "WD23CD", + labelId: "WD23NM", + // @ts-ignore + data: layers.data.importedDataCountByWard || [], + mapboxSourceProps: {}, + mapboxLayerProps: { + minzoom: MAX_CONSTITUENCY_ZOOM, + } + } // constituencies2024: { // name: "GE2024 constituencies", // singular: "constituency", @@ -84,59 +101,67 @@ export function ReportMap () { // promoteId: "ctyua_code", // labelId: "ctyua_name", // }, - wards: { - name: "wards", - singular: "ward", - sourceId: "commonknowledge.0rzbo365", - promoteId: "WD23CD", - labelId: "WD23NM", - // @ts-ignore - data: layers.data.importedDataCountByWard || [], - mapboxSourceProps: { - minzoom: 13, - maxzoom: 18, - }, - mapboxLayerProps: { - minzoom: 13, - maxzoom: 18, - } - } } useEffect(function setFeatureState() { - layers.data.importedDataCountByRegion?.forEach((region) => { - if (region?.areaId && region?.count) { - mapboxRef.current?.setFeatureState({ - source: TILESETS.EERs.sourceId, - sourceLayer: TILESETS.EERs.sourceLayerId, - id: region.areaId, - }, { - count: region.count, - // ...layers.data.layers?.map((layer) => { - // return { - // [layer?.source!.id]: layer?.source?.importedDataCountByRegion?.find(r => r?.areaId === region.areaId)?.count - // } - // }) - }) - console.log("setFeatureState", region.areaId, region.count) - } + if (!mapboxRef.current) return + if (!layers.data) return + Object.values(TILESETS)?.forEach((tileset) => { + tileset.data?.forEach((area) => { + if (area?.areaId && area?.count) { + mapboxRef.current?.setFeatureState({ + source: tileset.sourceId, + sourceLayer: tileset.sourceLayerId, + id: area.areaId, + }, { + count: area.count, + // Per-layer data for choloropleths also + // ...layers.data.layers?.map((layer) => { + // return { + // [layer?.source!.id]: layer?.source?.importedDataCountByRegion?.find(r => r?.areaId === area.areaId)?.count + // } + // }) + }) + } + }) + }) + }, [layers, TILESETS, mapboxRef]) + + const requiredImages = [ + { + url: () => new URL('/markers/default.png', window.location.href).toString(), + name: 'meep-marker' + }, + { + url: () => new URL('/markers/selected.png', window.location.href).toString(), + name: 'meep-marker-selected' + } + ] + + const [loadedImages, setLoadedImages] = useState([]) + + useEffect(function loadIcons() { + if (!mapboxRef.current) return + requiredImages.forEach((requiredImage) => { + console.log("Loading", requiredImage.url()) + // Load an image from an external URL. + mapboxRef.current!.loadImage( + requiredImage.url(), + (error, image) => { + console.log("Loaded image", requiredImage.name, image, error) + try { + if (error) throw error; + if (!image) throw new Error('Marker icon did not load') + mapboxRef.current!.addImage(requiredImage.name, image); + setLoadedImages(loadedImages => [...loadedImages, requiredImage.name]) + console.log("Loaded image", requiredImage.name, image) + } catch (e) { + console.error("Failed to load image", e) + } + } + ) }) - // layers.data.layers?.forEach((layer) => { - // if (layer?.source) { - // layer.source.importedDataCountByRegion?.forEach((region) => { - // if (region?.areaId && region?.count) { - // mapboxRef.current?.setFeatureState({ - // source: TILESETS.EERs.sourceId, - // id: region.areaId, - // }, { - // totalCount: - // [layer.source!.id]: region.count, - // }) - // } - // }) - // } - // }) - }, [layers, mapboxRef]) + }, [mapboxRef.current, setLoadedImages]) return ( - {Object.entries(TILESETS).map(([key, tileset]) => { + {!layers.data && null} + {!!layers.data && Object.entries(TILESETS).map(([key, tileset]) => { const min = tileset.data.reduce( (min, p) => p?.count! < min ? p?.count! : min, tileset.data?.[0]?.count! @@ -158,106 +184,167 @@ export function ReportMap () { (max, p) => p?.count! > max ? p?.count! : max, tileset.data?.[0]?.count! ) || 1 - const scale = scaleSequential() + + // Uses 0-1 for easy interpolation + // go from 0-100% and return real numbers + const legendScale = scaleLinear() + .domain([0, 1]) + .range([min, max]) + + // Map real numbers to colours + const colourScale = scaleSequential() + .domain([min, max]) + .interpolator(interpolateInferno) + + // Text scale + const textScale = scaleLinear() .domain([min, max]) - .interpolator(interpolatePlasma) - console.log(scale) + .range([1, 1.5]) + + const inDataFilter = [ + "in", + ["get", tileset.promoteId], + ["literal", tileset.data.map(d => d.areaId)], + ] + + const steps = Math.min(max, 30) + const colourStops = (new Array(steps)).fill(0).map((_, i) => i / steps).map( + (n) => [ + Math.floor(legendScale(n)), + colourScale(legendScale(n)) + ] + ).flat() return ( - - {/* Shade area by count */} - - {/* Border of the boundary */} - - {/* Display count as a number in the centre of the area by a symbol layer */} - d.count?.toString() || "", - "?" - ), - "text-size": 25, - "symbol-placement": "point", - "text-offset": [0, -0.5], - "text-allow-overlap": true, - // "text-variable-anchor": ["center"], - // "text-ignore-placement": true, + + + {/* Shade area by count */} + + {/* Border of the boundary */} + + + { + return { + type: "Feature", + geometry: d.gssArea?.point?.geometry, + properties: { + count: d.count, + label: d.label, + } + } + }) }} - paint={{ - "text-color": "white", - }} - {...tileset.mapboxLayerProps || {}} - /> - d.label || "", - "?" - ), - "text-size": 13, - "symbol-placement": "point", - "text-offset": [0, 0.6], - "text-allow-overlap": true, - // "text-variable-anchor": ["center"], - // "text-ignore-placement": true, - }} - paint={{ - "text-color": "white", - "text-opacity": 0.7 - }} - {...tileset.mapboxLayerProps || {}} - /> - + {...tileset.mapboxSourceProps || {}} + > + + + + ) })} + {/* Wait for all icons to load */} {layers.data.layers?.map((layer, index) => { return ( >(data: Array, mapboxIdField: string, dataIdField: string, value: (d: T) => string, defaultValue: any): Expression { - // For each fromId value, find the corresponding toId value and return the valueId - const expression: Expression = [ - "match", - ["get", mapboxIdField], - ...data.map((d) => { - return [ - // If - d[dataIdField], - // Then - value(d), - ] - }).filter((d) => !!d[0]).flat(), - defaultValue - ] - return expression -} - function MapboxGLClusteredPointsLayer ({ externalDataSourceId }: { externalDataSourceId: string }) { const { data, error } = useQuery(GET_SOURCE_DATA, { variables: { @@ -295,7 +364,7 @@ function MapboxGLClusteredPointsLayer ({ externalDataSourceId }: { externalDataS }, }); - const id = useId() + const id = externalDataSourceId return ( <> @@ -305,18 +374,19 @@ function MapboxGLClusteredPointsLayer ({ externalDataSourceId }: { externalDataS data={{ type: "FeatureCollection", // @ts-ignore TODO: Fix types - features: data?.externalDataSource?.geojsonPointFeatures || [] + features: data?.externalDataSource?.importedDataGeojsonPoints || [] }} > @@ -326,7 +396,7 @@ function MapboxGLClusteredPointsLayer ({ externalDataSourceId }: { externalDataS const GET_SOURCE_DATA = gql` query GetSourceGeoJSON($externalDataSourceId: ID!) { externalDataSource(pk: $externalDataSourceId) { - geojsonPointFeatures { + importedDataGeojsonPoints { id type geometry { From 14f9c06ebb4dbb5d83ee1b6dc94caaab0fab2bbf Mon Sep 17 00:00:00 2001 From: Jan Baykara Date: Wed, 20 Mar 2024 00:06:32 +0000 Subject: [PATCH 12/18] Refactor analytical queries - subclass to reduce boilerplate code --- hub/analytics.py | 18 ++++++++-------- hub/models.py | 56 ++++++------------------------------------------ 2 files changed, 15 insertions(+), 59 deletions(-) diff --git a/hub/analytics.py b/hub/analytics.py index 3d37fe665..6dcb1f1dc 100644 --- a/hub/analytics.py +++ b/hub/analytics.py @@ -1,13 +1,13 @@ from django.db.models import Count, F import pandas as pd from typing import List, Optional, TypedDict +from django.db.models.manager import BaseManager class Analytics: - def __init__(self, qs) -> None: - self.qs = qs + get_analytics_queryset: BaseManager def get_dataframe(self, qs): - json_list = [{**d.postcode_data, **d.json} for d in self.qs] + json_list = [{**d.postcode_data, **d.json} for d in self.get_analytics_queryset()] enrichment_df = pd.DataFrame.from_records(json_list) return enrichment_df @@ -17,7 +17,7 @@ class RegionCount(TypedDict): count: int def imported_data_count_by_region(self) -> List[RegionCount]: - return self.qs()\ + return self.get_analytics_queryset()\ .annotate( label=F('postcode_data__european_electoral_region'), area_id=F('postcode_data__codes__european_electoral_region') @@ -28,7 +28,7 @@ def imported_data_count_by_region(self) -> List[RegionCount]: def imported_data_count_by_constituency(self) -> List[RegionCount]: - return self.qs()\ + return self.get_analytics_queryset()\ .annotate( label=F('postcode_data__parliamentary_constituency'), area_id=F('postcode_data__codes__parliamentary_constituency') @@ -39,7 +39,7 @@ def imported_data_count_by_constituency(self) -> List[RegionCount]: def imported_data_count_by_constituency_2024(self) -> List[RegionCount]: - return self.qs()\ + return self.get_analytics_queryset()\ .annotate( label=F('postcode_data__parliamentary_constituency_2025'), area_id=F('postcode_data__codes__parliamentary_constituency_2025') @@ -50,7 +50,7 @@ def imported_data_count_by_constituency_2024(self) -> List[RegionCount]: def imported_data_count_by_council(self) -> List[RegionCount]: - return self.qs()\ + return self.get_analytics_queryset()\ .annotate( label=F('postcode_data__admin_district'), area_id=F('postcode_data__codes__admin_district') @@ -61,7 +61,7 @@ def imported_data_count_by_council(self) -> List[RegionCount]: def imported_data_count_by_ward(self) -> List[RegionCount]: - return self.qs()\ + return self.get_analytics_queryset()\ .annotate( label=F('postcode_data__admin_ward'), area_id=F('postcode_data__codes__admin_ward') @@ -71,7 +71,7 @@ def imported_data_count_by_ward(self) -> List[RegionCount]: .order_by('-count')\ def imported_data_count(self) -> int: - count = self.qs().all().count() + count = self.get_analytics_queryset().all().count() if isinstance(count, int): return count return 0 \ No newline at end of file diff --git a/hub/models.py b/hub/models.py index 836343ff1..82b1d08c1 100644 --- a/hub/models.py +++ b/hub/models.py @@ -791,7 +791,7 @@ def cast_data(sender, instance, *args, **kwargs): instance.data = "" -class ExternalDataSource(PolymorphicModel): +class ExternalDataSource(PolymorphicModel, Analytics): """ A third-party data source that can be read and optionally written back to. E.g. Google Sheet or an Action Network table. @@ -1089,30 +1089,8 @@ def get_import_data(self): data_type__data_set__external_data_source_id=self.id ) - @cached_property - def analytics(self): - return Analytics(qs=self.get_import_data) - - def get_import_dataframe(self): - return self.analytics.get_dataframe() - - def imported_data_count_by_region(self) -> List[Analytics.RegionCount]: - return self.analytics.imported_data_count_by_region() - - def imported_data_count_by_constituency(self) -> List[Analytics.RegionCount]: - return self.analytics.imported_data_count_by_constituency() - - def imported_data_count_by_constituency_2024(self) -> List[Analytics.RegionCount]: - return self.analytics.imported_data_count_by_constituency_2024() - - def imported_data_count_by_council(self) -> List[Analytics.RegionCount]: - return self.analytics.imported_data_count_by_council() - - def imported_data_count_by_ward(self) -> List[Analytics.RegionCount]: - return self.analytics.imported_data_count_by_ward() - - def imported_data_count(self) -> int: - return self.analytics.imported_data_count() + def get_analytics_queryset(self): + return self.get_import_data() def data_loader_factory(self): async def fetch_enrichment_data(keys: List[self.EnrichmentLookup]) -> list[str]: @@ -1612,7 +1590,7 @@ def __str__(self): return self.name -class MapReport(Report): +class MapReport(Report, Analytics): layers = models.JSONField(blank=True, null=True, default=list) class MapLayer(TypedDict): @@ -1629,27 +1607,5 @@ def get_import_data(self): data_type__data_set__external_data_source_id__in=visible_layer_ids ) - @cached_property - def analytics(self): - return Analytics(qs=self.get_import_data) - - def get_import_dataframe(self): - return self.analytics.get_dataframe() - - def imported_data_count_by_region(self) -> List[Analytics.RegionCount]: - return self.analytics.imported_data_count_by_region() - - def imported_data_count_by_constituency(self) -> List[Analytics.RegionCount]: - return self.analytics.imported_data_count_by_constituency() - - def imported_data_count_by_constituency_2024(self) -> List[Analytics.RegionCount]: - return self.analytics.imported_data_count_by_constituency_2024() - - def imported_data_count_by_council(self) -> List[Analytics.RegionCount]: - return self.analytics.imported_data_count_by_council() - - def imported_data_count_by_ward(self) -> List[Analytics.RegionCount]: - return self.analytics.imported_data_count_by_ward() - - def imported_data_count(self) -> int: - return self.analytics.imported_data_count() \ No newline at end of file + def get_analytics_queryset(self): + return self.get_import_data() \ No newline at end of file From a296bed26dcb15da22fdfea86844c42251178127 Mon Sep 17 00:00:00 2001 From: Jan Baykara Date: Wed, 20 Mar 2024 00:07:06 +0000 Subject: [PATCH 13/18] Remove unused code comments --- nextjs/src/components/report/ReportMap.tsx | 30 +++------------------- 1 file changed, 3 insertions(+), 27 deletions(-) diff --git a/nextjs/src/components/report/ReportMap.tsx b/nextjs/src/components/report/ReportMap.tsx index d50d5b009..204fa0338 100644 --- a/nextjs/src/components/report/ReportMap.tsx +++ b/nextjs/src/components/report/ReportMap.tsx @@ -6,7 +6,6 @@ import Map, { Layer, MapRef, Source, LayerProps } from "react-map-gl"; import { MAP_REPORT_LAYERS_SUMMARY } from "../dataConfig"; import { gql, useFragment, useQuery } from "@apollo/client"; import { ReportContext } from "@/app/reports/[id]/context"; -import { Expression } from "mapbox-gl"; import { scaleLinear, scaleSequential } from 'd3-scale' import { interpolateInferno } from 'd3-scale-chromatic' @@ -87,20 +86,6 @@ export function ReportMap () { minzoom: MAX_CONSTITUENCY_ZOOM, } } - // constituencies2024: { - // name: "GE2024 constituencies", - // singular: "constituency", - // sourceId: "commonknowledge.b5t8td4w" - // promoteId: "PCON25CD", - // labelId: "PCON25NM" - // }, - // councils: { - // name: "councils", - // singular: "council", - // sourceId: "commonknowledge.9zcvsx9l", - // promoteId: "ctyua_code", - // labelId: "ctyua_name", - // }, } useEffect(function setFeatureState() { @@ -114,14 +99,8 @@ export function ReportMap () { sourceLayer: tileset.sourceLayerId, id: area.areaId, }, { - count: area.count, - // Per-layer data for choloropleths also - // ...layers.data.layers?.map((layer) => { - // return { - // [layer?.source!.id]: layer?.source?.importedDataCountByRegion?.find(r => r?.areaId === area.areaId)?.count - // } - // }) - }) + count: area.count + }) } }) }) @@ -238,7 +217,6 @@ export function ReportMap () { "interpolate", ["linear"], ['to-number', ["feature-state", "count"], 0], - // 10 stops ...colourStops ], "fill-opacity": [ @@ -329,8 +307,6 @@ export function ReportMap () { "text-font": ["DIN Offc Pro Medium", "Arial Unicode MS Bold"], "symbol-placement": "point", "text-offset": [0, 0.6], - // "text-allow-overlap": true, - // "text-ignore-placement": true, }} paint={{ "text-color": "white", @@ -373,7 +349,7 @@ function MapboxGLClusteredPointsLayer ({ externalDataSourceId }: { externalDataS type="geojson" data={{ type: "FeatureCollection", - // @ts-ignore TODO: Fix types + // @ts-ignore features: data?.externalDataSource?.importedDataGeojsonPoints || [] }} > From fea71e83488d872e79e80defc55104c3030b7be5 Mon Sep 17 00:00:00 2001 From: Jan Baykara Date: Wed, 20 Mar 2024 00:15:12 +0000 Subject: [PATCH 14/18] Speed up report updates mutation by separating analytical queries --- nextjs/src/__generated__/gql.ts | 4 ++-- nextjs/src/__generated__/graphql.ts | 7 ++---- nextjs/src/app/reports/[id]/lib.tsx | 2 +- nextjs/src/app/reports/[id]/page.tsx | 33 +++++++++++++++++++++++----- 4 files changed, 33 insertions(+), 13 deletions(-) diff --git a/nextjs/src/__generated__/gql.ts b/nextjs/src/__generated__/gql.ts index fb1d977fa..f935c8e3a 100644 --- a/nextjs/src/__generated__/gql.ts +++ b/nextjs/src/__generated__/gql.ts @@ -32,7 +32,7 @@ const documents = { "\n query GetMapReportName($id: ID!) {\n mapReport(pk: $id) {\n id\n name\n }\n }\n": types.GetMapReportNameDocument, "\n fragment MapReportPage on MapReport {\n id\n name\n ... MapReportLayersSummary\n }\n \n": types.MapReportPageFragmentDoc, "\n query GetMapReport($id: ID!) {\n mapReport(pk: $id) {\n id\n name\n ... MapReportPage\n }\n }\n \n": types.GetMapReportDocument, - "\n mutation UpdateMapReport($input: MapReportInput!) {\n updateMapReport(data: $input) {\n ... MapReportPage\n }\n }\n \n": types.UpdateMapReportDocument, + "\n mutation UpdateMapReport($input: MapReportInput!) {\n updateMapReport(data: $input) {\n id\n name\n layers {\n name\n source {\n id\n name\n }\n }\n }\n }\n": types.UpdateMapReportDocument, "\n mutation DeleteMapReport($id: IDObject!) {\n deleteMapReport(data: $id) {\n id\n }\n }\n": types.DeleteMapReportDocument, "\n mutation AutoUpdateWebhookRefresh($ID: String!) {\n refreshWebhooks(externalDataSourceId: $ID) {\n id\n webhookHealthcheck\n }\n }\n": types.AutoUpdateWebhookRefreshDocument, "\n fragment DataSourceCard on ExternalDataSource {\n id\n name\n dataType\n connectionDetails {\n crmType: __typename\n }\n autoUpdateEnabled\n updateMapping {\n source\n sourcePath\n destinationColumn\n }\n jobs {\n lastEventAt\n status\n }\n }\n": types.DataSourceCardFragmentDoc, @@ -143,7 +143,7 @@ export function gql(source: "\n query GetMapReport($id: ID!) {\n mapReport(p /** * The gql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ -export function gql(source: "\n mutation UpdateMapReport($input: MapReportInput!) {\n updateMapReport(data: $input) {\n ... MapReportPage\n }\n }\n \n"): (typeof documents)["\n mutation UpdateMapReport($input: MapReportInput!) {\n updateMapReport(data: $input) {\n ... MapReportPage\n }\n }\n \n"]; +export function gql(source: "\n mutation UpdateMapReport($input: MapReportInput!) {\n updateMapReport(data: $input) {\n id\n name\n layers {\n name\n source {\n id\n name\n }\n }\n }\n }\n"): (typeof documents)["\n mutation UpdateMapReport($input: MapReportInput!) {\n updateMapReport(data: $input) {\n id\n name\n layers {\n name\n source {\n id\n name\n }\n }\n }\n }\n"]; /** * The gql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. */ diff --git a/nextjs/src/__generated__/graphql.ts b/nextjs/src/__generated__/graphql.ts index 427da5f5a..4afc09d4b 100644 --- a/nextjs/src/__generated__/graphql.ts +++ b/nextjs/src/__generated__/graphql.ts @@ -1032,10 +1032,7 @@ export type UpdateMapReportMutationVariables = Exact<{ }>; -export type UpdateMapReportMutation = { __typename?: 'Mutation', updateMapReport: ( - { __typename?: 'MapReport' } - & { ' $fragmentRefs'?: { 'MapReportPageFragment': MapReportPageFragment } } - ) }; +export type UpdateMapReportMutation = { __typename?: 'Mutation', updateMapReport: { __typename?: 'MapReport', id: any, name: string, layers?: Array<{ __typename?: 'MapLayer', name: string, source: { __typename?: 'ExternalDataSource', id: any, name: string } }> | null } }; export type DeleteMapReportMutationVariables = Exact<{ id: IdObject; @@ -1149,7 +1146,7 @@ export const LoginDocument = {"kind":"Document","definitions":[{"kind":"Operatio export const RegisterDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"Register"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"email"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"password1"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"password2"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"username"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"register"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"email"},"value":{"kind":"Variable","name":{"kind":"Name","value":"email"}}},{"kind":"Argument","name":{"kind":"Name","value":"password1"},"value":{"kind":"Variable","name":{"kind":"Name","value":"password1"}}},{"kind":"Argument","name":{"kind":"Name","value":"password2"},"value":{"kind":"Variable","name":{"kind":"Name","value":"password2"}}},{"kind":"Argument","name":{"kind":"Name","value":"username"},"value":{"kind":"Variable","name":{"kind":"Name","value":"username"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"errors"}},{"kind":"Field","name":{"kind":"Name","value":"success"}}]}}]}}]} as unknown as DocumentNode; export const GetMapReportNameDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetMapReportName"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ID"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"mapReport"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"pk"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}}]}}]} as unknown as DocumentNode; export const GetMapReportDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetMapReport"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ID"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"mapReport"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"pk"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"MapReportPage"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"MapReportLayersSummary"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"MapReport"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"importedDataCountByRegion"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"label"}},{"kind":"Field","name":{"kind":"Name","value":"areaId"}},{"kind":"Field","name":{"kind":"Name","value":"count"}},{"kind":"Field","name":{"kind":"Name","value":"gssArea"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"point"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"geometry"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"coordinates"}}]}}]}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"importedDataCountByConstituency"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"label"}},{"kind":"Field","name":{"kind":"Name","value":"areaId"}},{"kind":"Field","name":{"kind":"Name","value":"count"}},{"kind":"Field","name":{"kind":"Name","value":"gssArea"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"point"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"geometry"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"coordinates"}}]}}]}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"importedDataCountByWard"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"label"}},{"kind":"Field","name":{"kind":"Name","value":"areaId"}},{"kind":"Field","name":{"kind":"Name","value":"count"}},{"kind":"Field","name":{"kind":"Name","value":"gssArea"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"point"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"geometry"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"coordinates"}}]}}]}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"layers"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"source"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"isImporting"}},{"kind":"Field","name":{"kind":"Name","value":"importedDataCount"}},{"kind":"Field","name":{"kind":"Name","value":"importedDataCountByRegion"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"label"}},{"kind":"Field","name":{"kind":"Name","value":"areaId"}},{"kind":"Field","name":{"kind":"Name","value":"count"}}]}},{"kind":"Field","name":{"kind":"Name","value":"importedDataCountByConstituency"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"label"}},{"kind":"Field","name":{"kind":"Name","value":"areaId"}},{"kind":"Field","name":{"kind":"Name","value":"count"}}]}},{"kind":"Field","name":{"kind":"Name","value":"importedDataCountByWard"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"label"}},{"kind":"Field","name":{"kind":"Name","value":"areaId"}},{"kind":"Field","name":{"kind":"Name","value":"count"}}]}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"MapReportPage"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"MapReport"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"MapReportLayersSummary"}}]}}]} as unknown as DocumentNode; -export const UpdateMapReportDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"UpdateMapReport"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"MapReportInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"updateMapReport"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"data"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"MapReportPage"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"MapReportLayersSummary"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"MapReport"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"importedDataCountByRegion"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"label"}},{"kind":"Field","name":{"kind":"Name","value":"areaId"}},{"kind":"Field","name":{"kind":"Name","value":"count"}},{"kind":"Field","name":{"kind":"Name","value":"gssArea"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"point"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"geometry"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"coordinates"}}]}}]}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"importedDataCountByConstituency"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"label"}},{"kind":"Field","name":{"kind":"Name","value":"areaId"}},{"kind":"Field","name":{"kind":"Name","value":"count"}},{"kind":"Field","name":{"kind":"Name","value":"gssArea"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"point"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"geometry"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"coordinates"}}]}}]}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"importedDataCountByWard"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"label"}},{"kind":"Field","name":{"kind":"Name","value":"areaId"}},{"kind":"Field","name":{"kind":"Name","value":"count"}},{"kind":"Field","name":{"kind":"Name","value":"gssArea"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"point"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"geometry"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"coordinates"}}]}}]}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"layers"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"source"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"isImporting"}},{"kind":"Field","name":{"kind":"Name","value":"importedDataCount"}},{"kind":"Field","name":{"kind":"Name","value":"importedDataCountByRegion"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"label"}},{"kind":"Field","name":{"kind":"Name","value":"areaId"}},{"kind":"Field","name":{"kind":"Name","value":"count"}}]}},{"kind":"Field","name":{"kind":"Name","value":"importedDataCountByConstituency"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"label"}},{"kind":"Field","name":{"kind":"Name","value":"areaId"}},{"kind":"Field","name":{"kind":"Name","value":"count"}}]}},{"kind":"Field","name":{"kind":"Name","value":"importedDataCountByWard"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"label"}},{"kind":"Field","name":{"kind":"Name","value":"areaId"}},{"kind":"Field","name":{"kind":"Name","value":"count"}}]}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"MapReportPage"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"MapReport"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"MapReportLayersSummary"}}]}}]} as unknown as DocumentNode; +export const UpdateMapReportDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"UpdateMapReport"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"MapReportInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"updateMapReport"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"data"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"layers"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"source"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}}]}}]}}]}}]}}]} as unknown as DocumentNode; export const DeleteMapReportDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"DeleteMapReport"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"IDObject"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"deleteMapReport"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"data"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}}]}}]}}]} as unknown as DocumentNode; export const AutoUpdateWebhookRefreshDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"AutoUpdateWebhookRefresh"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"ID"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"refreshWebhooks"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"externalDataSourceId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"ID"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"webhookHealthcheck"}}]}}]}}]} as unknown as DocumentNode; export const ExternalDataSourceAutoUpdateCardDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"ExternalDataSourceAutoUpdateCard"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"ID"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ID"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"externalDataSource"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"pk"},"value":{"kind":"Variable","name":{"kind":"Name","value":"ID"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"DataSourceCard"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"DataSourceCard"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ExternalDataSource"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"dataType"}},{"kind":"Field","name":{"kind":"Name","value":"connectionDetails"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","alias":{"kind":"Name","value":"crmType"},"name":{"kind":"Name","value":"__typename"}}]}},{"kind":"Field","name":{"kind":"Name","value":"autoUpdateEnabled"}},{"kind":"Field","name":{"kind":"Name","value":"updateMapping"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"source"}},{"kind":"Field","name":{"kind":"Name","value":"sourcePath"}},{"kind":"Field","name":{"kind":"Name","value":"destinationColumn"}}]}},{"kind":"Field","name":{"kind":"Name","value":"jobs"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"lastEventAt"}},{"kind":"Field","name":{"kind":"Name","value":"status"}}]}}]}}]} as unknown as DocumentNode; diff --git a/nextjs/src/app/reports/[id]/lib.tsx b/nextjs/src/app/reports/[id]/lib.tsx index e6fc433a5..bab10b673 100644 --- a/nextjs/src/app/reports/[id]/lib.tsx +++ b/nextjs/src/app/reports/[id]/lib.tsx @@ -3,7 +3,7 @@ import { MAP_REPORT_LAYERS_SUMMARY } from "@/components/dataConfig" import { gql } from "@apollo/client" -export const MapReportPageFragmentStr = gql` +export const MAP_REPORT_FRAGMENT = gql` fragment MapReportPage on MapReport { id name diff --git a/nextjs/src/app/reports/[id]/page.tsx b/nextjs/src/app/reports/[id]/page.tsx index 080298467..cf0d60de0 100644 --- a/nextjs/src/app/reports/[id]/page.tsx +++ b/nextjs/src/app/reports/[id]/page.tsx @@ -42,7 +42,7 @@ import { useRouter } from "next/navigation"; import spaceCase from 'to-space-case' import { toastPromise } from "@/lib/toast"; import { ReportMap } from "@/components/report/ReportMap"; -import { MapReportPageFragmentStr } from "./lib"; +import { MAP_REPORT_FRAGMENT } from "./lib"; import { ReportContext } from "./context"; import { LoadingIcon } from "@/components/ui/loadingIcon"; @@ -210,6 +210,16 @@ export default function Page({ params: { id } }: { params: Params }) { ); + function refreshStatistics () { + toastPromise(report.refetch(), + { + loading: "Refreshing statistics...", + success: "Statistics updated", + error: `Couldn't update statistics`, + } + ) + } + function updateMutation (input: MapReportInput) { const update = client.mutate({ mutation: UPDATE_MAP_REPORT, @@ -224,7 +234,11 @@ export default function Page({ params: { id } }: { params: Params }) { loading: "Saving...", success: (d) => { if (!d.errors && d.data) { - console.log(input, Object.keys(input)) + if (input.layers) { + // If layers changed, that means + // all the member numbers will have changed too. + refreshStatistics() + } return { title: "Report saved", description: `Updated ${Object.keys(input).map(spaceCase).join(", ")}` @@ -265,16 +279,25 @@ const GET_MAP_REPORT = gql` ... MapReportPage } } - ${MapReportPageFragmentStr} + ${MAP_REPORT_FRAGMENT} ` +// Keep this fragment trim +// so that updates return fast const UPDATE_MAP_REPORT = gql` mutation UpdateMapReport($input: MapReportInput!) { updateMapReport(data: $input) { - ... MapReportPage + id + name + layers { + name + source { + id + name + } + } } } - ${MapReportPageFragmentStr} ` const DELETE_MAP_REPORT = gql` From a989e9cd0da8c1dea322728f50a90edbd589198c Mon Sep 17 00:00:00 2001 From: Jan Baykara Date: Wed, 20 Mar 2024 23:32:27 +0000 Subject: [PATCH 15/18] Fix tests --- hub/analytics.py | 2 +- hub/management/commands/import_areas.py | 4 ++-- hub/models.py | 2 +- hub/tests/test_import_areas.py | 8 +++----- hub/tests/test_sources.py | 4 ++-- 5 files changed, 9 insertions(+), 11 deletions(-) diff --git a/hub/analytics.py b/hub/analytics.py index 6dcb1f1dc..ba42a7e14 100644 --- a/hub/analytics.py +++ b/hub/analytics.py @@ -6,7 +6,7 @@ class Analytics: get_analytics_queryset: BaseManager - def get_dataframe(self, qs): + def get_imported_dataframe(self): json_list = [{**d.postcode_data, **d.json} for d in self.get_analytics_queryset()] enrichment_df = pd.DataFrame.from_records(json_list) return enrichment_df diff --git a/hub/management/commands/import_areas.py b/hub/management/commands/import_areas.py index b1fdb80c0..8f2fc781f 100644 --- a/hub/management/commands/import_areas.py +++ b/hub/management/commands/import_areas.py @@ -42,6 +42,7 @@ def handle(self, quiet: bool = False, *args, **options): "type": "WMC", }, } + geom_str = json.dumps(geom) except mapit.NotFoundException: # pragma: no cover print(f"could not find mapit area for {area['name']}") geom = None @@ -53,11 +54,10 @@ def handle(self, quiet: bool = False, *args, **options): area_type=area_type, ) - geom_str = json.dumps(geom) + a.geometry = geom_str geom = GEOSGeometry(json.dumps(geom['geometry'])) if isinstance(geom, Polygon): geom = MultiPolygon([geom]) - a.geometry = geom_str a.polygon = geom a.point = a.polygon.centroid a.save() diff --git a/hub/models.py b/hub/models.py index 82b1d08c1..aa3afc007 100644 --- a/hub/models.py +++ b/hub/models.py @@ -1095,7 +1095,7 @@ def get_analytics_queryset(self): def data_loader_factory(self): async def fetch_enrichment_data(keys: List[self.EnrichmentLookup]) -> list[str]: return_data = [] - enrichment_df = await sync_to_async(self.get_import_dataframe)() + enrichment_df = await sync_to_async(self.get_imported_dataframe)() for key in keys: try: relevant_member_geography = get( diff --git a/hub/tests/test_import_areas.py b/hub/tests/test_import_areas.py index 303ac7896..acc24687b 100644 --- a/hub/tests/test_import_areas.py +++ b/hub/tests/test_import_areas.py @@ -13,10 +13,8 @@ class ImportAreasTestCase(TestCase): @mock.patch.object(MapIt, "areas_of_type") @mock.patch.object(MapIt, "area_geometry") def test_import(self, mapit_geom, mapit_areas): - mapit_geom.return_value = { - "type": "Polygon", - "coordinates": [[1, 2], [2, 1]], - } + mapit_geom.return_value = { "type": "Polygon", "coordinates": [[[355272.7037, 214366.797499999], [355288.5266, 214298.2019], [355302.7834, 214258.4015], [355308.0008, 214245.1022], [355297.4966, 214168.5042], [355287.0007, 214143.101399999], [355030.0021, 214093.3957], [354547.699, 213998.402799999], [354681.4009, 213668.6972], [354622.0035, 213539.5043], [354526.7978, 213482.8004], [354531.8025, 213458.997300001], [354554.1645, 213396.302200001], [354568.7458, 213360.897299999], [354679.0016, 212973.6962], [354677.1959, 212900.497099999], [354678.3008, 212856.299799999], [354674.7966, 212740.303099999], [354627.404, 212654.997500001], [354437.8006, 212591.195699999], [354276.0984, 212607.5011], [354147.6981, 212561.204299999], [353920.2004, 212464.502], [353724.3967, 212465.7017], [353663.3997, 212296.200099999], [353663.3997, 212271.7972], [353606.4016, 212269.797700001], [353530.4976, 212227.899700001], [353510.0993, 212183.702299999], [353511.8967, 212134.196599999], [353449.9021, 212047.101500001], [353454.0987, 212046.7015], [353417.3011, 211982.9998], [353413.2033, 211956.7973], [353309.497, 211795.4035], [353304.6983, 211769.800799999], [353309.802, 211738.0999], [353354.1096, 211661.005100001], [353363.5021, 211637.4987], [353377.4033, 211561.100500001], [353372.4975, 211531.399], [353376.6035, 211512.5044], [353372.2996, 211495.599300001], [353373.5034, 211470.4965], [353381.4788, 211448.9058], [353399.6513, 211405.151699999], [353403.7773, 211392.591600001], [353417.1165, 211351.503699999], [353494.4996, 211251.099300001], [353577.2965, 211143.300100001], [353578.8409, 211141.5089], [353722.6395, 210997.7005], [353736.7354, 210978.397399999], [353895.2935, 210848.2974], [353857.8018, 210780.604], [353739.9963, 210758.3004], [353700.9972, 210705.295499999], [353706.9007, 210651.101], [353681.4976, 210580.001399999], [353673.4999, 210521.7981], [353692.3976, 210445], [353684.6967, 210414.0988], [353685.4965, 210341.899599999], [353626.8988, 210191.4027], [353479.9966, 210150.304400001], [353434.4014, 210246.297], [353247.2962, 210453.197699999], [352832.0997, 210848.5046], [352653.1983, 211318.9999], [352579.8997, 211412.7031], [352468.7975, 211455.001], [352288.0988, 211465.797900001], [351890.2004, 211406.9047], [351793.6012, 211446.3035], [351611.6987, 211638.498500001], [351469.1993, 211852.7971], [351367.2986, 212081.001800001], [351234.3965, 212151.3017], [351120.5, 212207.6], [351043.8, 212350.800000001], [351059.7991, 212540.500299999], [351110.3001, 212685.398800001], [351317.8035, 212941.995300001], [351600.8977, 213142.697899999], [351948.0726, 213452.1622], [352569.7006, 213946.797700001], [352646.7012, 214054.4969], [352796.5965, 214632.501399999], [352757.4984, 215160.9002], [352799.5976, 215467.9023], [352815.9951, 215493.3969], [352776.899, 215530.304400001], [353049.2004, 215758.799000001], [353064.7011, 215681.801100001], [353272.5014, 215656.1984], [353363.197, 215589.6974], [353461.2968, 215564.9045], [353462.5006, 215520.6972], [353826.9982, 215505.9014], [354249.4999, 215404.500399999], [354288.598, 215379.5976], [354609.001, 215215.3046], [354746.0999, 215097.7982], [354781.2981, 214972.404100001], [354756.9999, 214598.801100001], [354828.5011, 214454.9023], [355033.1022, 214364.4981], [355272.7037, 214366.797499999]]]} + mapit_areas.return_value = [ { "id": 1, @@ -44,7 +42,7 @@ def test_import(self, mapit_geom, mapit_areas): self.assertEqual(first.gss, "E10000001") self.assertEqual( first.geometry, - '{"type": "Feature", "geometry": {"type": "Polygon", "coordinates": [[1, 2], [2, 1]]}, "properties": {"PCON13CD": "E10000001", "name": "South Borsetshire", "type": "WMC"}}', + '{"type": "Feature", "geometry": {"type": "Polygon", "coordinates": [[[355272.7037, 214366.797499999], [355288.5266, 214298.2019], [355302.7834, 214258.4015], [355308.0008, 214245.1022], [355297.4966, 214168.5042], [355287.0007, 214143.101399999], [355030.0021, 214093.3957], [354547.699, 213998.402799999], [354681.4009, 213668.6972], [354622.0035, 213539.5043], [354526.7978, 213482.8004], [354531.8025, 213458.997300001], [354554.1645, 213396.302200001], [354568.7458, 213360.897299999], [354679.0016, 212973.6962], [354677.1959, 212900.497099999], [354678.3008, 212856.299799999], [354674.7966, 212740.303099999], [354627.404, 212654.997500001], [354437.8006, 212591.195699999], [354276.0984, 212607.5011], [354147.6981, 212561.204299999], [353920.2004, 212464.502], [353724.3967, 212465.7017], [353663.3997, 212296.200099999], [353663.3997, 212271.7972], [353606.4016, 212269.797700001], [353530.4976, 212227.899700001], [353510.0993, 212183.702299999], [353511.8967, 212134.196599999], [353449.9021, 212047.101500001], [353454.0987, 212046.7015], [353417.3011, 211982.9998], [353413.2033, 211956.7973], [353309.497, 211795.4035], [353304.6983, 211769.800799999], [353309.802, 211738.0999], [353354.1096, 211661.005100001], [353363.5021, 211637.4987], [353377.4033, 211561.100500001], [353372.4975, 211531.399], [353376.6035, 211512.5044], [353372.2996, 211495.599300001], [353373.5034, 211470.4965], [353381.4788, 211448.9058], [353399.6513, 211405.151699999], [353403.7773, 211392.591600001], [353417.1165, 211351.503699999], [353494.4996, 211251.099300001], [353577.2965, 211143.300100001], [353578.8409, 211141.5089], [353722.6395, 210997.7005], [353736.7354, 210978.397399999], [353895.2935, 210848.2974], [353857.8018, 210780.604], [353739.9963, 210758.3004], [353700.9972, 210705.295499999], [353706.9007, 210651.101], [353681.4976, 210580.001399999], [353673.4999, 210521.7981], [353692.3976, 210445], [353684.6967, 210414.0988], [353685.4965, 210341.899599999], [353626.8988, 210191.4027], [353479.9966, 210150.304400001], [353434.4014, 210246.297], [353247.2962, 210453.197699999], [352832.0997, 210848.5046], [352653.1983, 211318.9999], [352579.8997, 211412.7031], [352468.7975, 211455.001], [352288.0988, 211465.797900001], [351890.2004, 211406.9047], [351793.6012, 211446.3035], [351611.6987, 211638.498500001], [351469.1993, 211852.7971], [351367.2986, 212081.001800001], [351234.3965, 212151.3017], [351120.5, 212207.6], [351043.8, 212350.800000001], [351059.7991, 212540.500299999], [351110.3001, 212685.398800001], [351317.8035, 212941.995300001], [351600.8977, 213142.697899999], [351948.0726, 213452.1622], [352569.7006, 213946.797700001], [352646.7012, 214054.4969], [352796.5965, 214632.501399999], [352757.4984, 215160.9002], [352799.5976, 215467.9023], [352815.9951, 215493.3969], [352776.899, 215530.304400001], [353049.2004, 215758.799000001], [353064.7011, 215681.801100001], [353272.5014, 215656.1984], [353363.197, 215589.6974], [353461.2968, 215564.9045], [353462.5006, 215520.6972], [353826.9982, 215505.9014], [354249.4999, 215404.500399999], [354288.598, 215379.5976], [354609.001, 215215.3046], [354746.0999, 215097.7982], [354781.2981, 214972.404100001], [354756.9999, 214598.801100001], [354828.5011, 214454.9023], [355033.1022, 214364.4981], [355272.7037, 214366.797499999]]]}, "properties": {"PCON13CD": "E10000001", "name": "South Borsetshire", "type": "WMC"}}', ) diff --git a/hub/tests/test_sources.py b/hub/tests/test_sources.py index 71aa11194..ed8cdc371 100644 --- a/hub/tests/test_sources.py +++ b/hub/tests/test_sources.py @@ -100,7 +100,7 @@ async def test_import_async(self): ) await self.custom_data_layer.import_all() enrichment_df = await sync_to_async( - self.custom_data_layer.get_import_dataframe + self.custom_data_layer.get_imported_dataframe )() self.assertGreaterEqual(len(enrichment_df.index), 2) @@ -150,7 +150,7 @@ def test_import_all(self): import_data[0].json["mayoral region"], ["North East Mayoral Combined Authority"], ) - df = self.custom_data_layer.get_import_dataframe() + df = self.custom_data_layer.get_imported_dataframe() # assert len(df.index) == import_count self.assertIn("council district", list(df.columns.values)) self.assertIn("mayoral region", list(df.columns.values)) From 5733689d7a5a182f5580bfd46d3248ba6b15dcc1 Mon Sep 17 00:00:00 2001 From: Jan Baykara Date: Wed, 20 Mar 2024 23:33:49 +0000 Subject: [PATCH 16/18] Fix linting --- hub/analytics.py | 109 ++++++++-------- hub/graphql/types/geojson.py | 37 ++++-- hub/graphql/types/model_types.py | 39 +++--- hub/graphql/utils.py | 6 + hub/management/commands/import_areas.py | 6 +- .../commands/import_new_constituencies.py | 4 +- hub/management/commands/import_regions.py | 10 +- hub/management/commands/import_wards.py | 9 +- .../commands/run_all_import_scripts.py | 7 +- hub/models.py | 19 +-- hub/tests/test_import_areas.py | 116 +++++++++++++++++- utils/geo.py | 52 +++----- utils/postcodesIO.py | 21 ++-- utils/py.py | 3 +- 14 files changed, 284 insertions(+), 154 deletions(-) diff --git a/hub/analytics.py b/hub/analytics.py index ba42a7e14..887f58938 100644 --- a/hub/analytics.py +++ b/hub/analytics.py @@ -1,13 +1,18 @@ -from django.db.models import Count, F -import pandas as pd from typing import List, Optional, TypedDict + +from django.db.models import Count, F from django.db.models.manager import BaseManager +import pandas as pd + + class Analytics: get_analytics_queryset: BaseManager def get_imported_dataframe(self): - json_list = [{**d.postcode_data, **d.json} for d in self.get_analytics_queryset()] + json_list = [ + {**d.postcode_data, **d.json} for d in self.get_analytics_queryset() + ] enrichment_df = pd.DataFrame.from_records(json_list) return enrichment_df @@ -15,63 +20,69 @@ class RegionCount(TypedDict): label: str area_id: Optional[str] count: int - + def imported_data_count_by_region(self) -> List[RegionCount]: - return self.get_analytics_queryset()\ - .annotate( - label=F('postcode_data__european_electoral_region'), - area_id=F('postcode_data__codes__european_electoral_region') - )\ - .values('label', 'area_id')\ - .annotate(count=Count('label'))\ - .order_by('-count')\ + return ( + self.get_analytics_queryset() + .annotate( + label=F("postcode_data__european_electoral_region"), + area_id=F("postcode_data__codes__european_electoral_region"), + ) + .values("label", "area_id") + .annotate(count=Count("label")) + .order_by("-count") + ) - def imported_data_count_by_constituency(self) -> List[RegionCount]: - return self.get_analytics_queryset()\ - .annotate( - label=F('postcode_data__parliamentary_constituency'), - area_id=F('postcode_data__codes__parliamentary_constituency') - )\ - .values('label', 'area_id')\ - .annotate(count=Count('label'))\ - .order_by('-count')\ + return ( + self.get_analytics_queryset() + .annotate( + label=F("postcode_data__parliamentary_constituency"), + area_id=F("postcode_data__codes__parliamentary_constituency"), + ) + .values("label", "area_id") + .annotate(count=Count("label")) + .order_by("-count") + ) - def imported_data_count_by_constituency_2024(self) -> List[RegionCount]: - return self.get_analytics_queryset()\ - .annotate( - label=F('postcode_data__parliamentary_constituency_2025'), - area_id=F('postcode_data__codes__parliamentary_constituency_2025') - )\ - .values('label', 'area_id')\ - .annotate(count=Count('label'))\ - .order_by('-count')\ + return ( + self.get_analytics_queryset() + .annotate( + label=F("postcode_data__parliamentary_constituency_2025"), + area_id=F("postcode_data__codes__parliamentary_constituency_2025"), + ) + .values("label", "area_id") + .annotate(count=Count("label")) + .order_by("-count") + ) - def imported_data_count_by_council(self) -> List[RegionCount]: - return self.get_analytics_queryset()\ - .annotate( - label=F('postcode_data__admin_district'), - area_id=F('postcode_data__codes__admin_district') - )\ - .values('label', 'area_id')\ - .annotate(count=Count('label'))\ - .order_by('-count')\ + return ( + self.get_analytics_queryset() + .annotate( + label=F("postcode_data__admin_district"), + area_id=F("postcode_data__codes__admin_district"), + ) + .values("label", "area_id") + .annotate(count=Count("label")) + .order_by("-count") + ) - def imported_data_count_by_ward(self) -> List[RegionCount]: - return self.get_analytics_queryset()\ - .annotate( - label=F('postcode_data__admin_ward'), - area_id=F('postcode_data__codes__admin_ward') - )\ - .values('label', 'area_id')\ - .annotate(count=Count('label'))\ - .order_by('-count')\ + return ( + self.get_analytics_queryset() + .annotate( + label=F("postcode_data__admin_ward"), + area_id=F("postcode_data__codes__admin_ward"), + ) + .values("label", "area_id") + .annotate(count=Count("label")) + .order_by("-count") + ) def imported_data_count(self) -> int: count = self.get_analytics_queryset().all().count() if isinstance(count, int): return count - return 0 \ No newline at end of file + return 0 diff --git a/hub/graphql/types/geojson.py b/hub/graphql/types/geojson.py index 891a23f8b..c58590875 100644 --- a/hub/graphql/types/geojson.py +++ b/hub/graphql/types/geojson.py @@ -1,12 +1,14 @@ from enum import Enum -from typing import List, Optional, Union -from django.contrib.gis.geos import Point, Polygon, MultiPolygon +from typing import List, Optional + +from django.contrib.gis.geos import MultiPolygon, Point, Polygon import strawberry from strawberry.scalars import JSON # + @strawberry.enum class GeoJSONTypes(Enum): Feature = "Feature" @@ -15,75 +17,94 @@ class GeoJSONTypes(Enum): Polygon = "Polygon" MultiPolygon = "MultiPolygon" + # + @strawberry.type class FeatureCollection: type: GeoJSONTypes.FeatureCollection = GeoJSONTypes.FeatureCollection features: List["Feature"] + # + @strawberry.interface class Feature: type: GeoJSONTypes.Feature = GeoJSONTypes.Feature id: Optional[str] properties: Optional[JSON] + # + @strawberry.type class PointGeometry: type: GeoJSONTypes.Point = GeoJSONTypes.Point # lng, lat coordinates: List[float] + @strawberry.type class PointFeature(Feature): geometry: PointGeometry @classmethod - def from_geodjango(cls, point: Point, properties: dict = {}, id: str = None) -> "PointFeature": + def from_geodjango( + cls, point: Point, properties: dict = {}, id: str = None + ) -> "PointFeature": return PointFeature( id=str(id), geometry=PointGeometry(coordinates=point), properties=properties, ) + # - + + @strawberry.type class PolygonGeometry: type: GeoJSONTypes.Polygon = GeoJSONTypes.Polygon coordinates: List[List[List[float]]] + @strawberry.type class PolygonFeature(Feature): geometry: PolygonGeometry @classmethod - def from_geodjango(cls, polygon: Polygon, properties: dict = {}, id: str = None) -> "PolygonFeature": + def from_geodjango( + cls, polygon: Polygon, properties: dict = {}, id: str = None + ) -> "PolygonFeature": return PolygonFeature( id=str(id), geometry=PolygonGeometry(coordinates=polygon), properties=properties, ) + # - + + @strawberry.type class MultiPolygonGeometry: type: GeoJSONTypes.MultiPolygon = GeoJSONTypes.MultiPolygon coordinates: List[List[List[List[float]]]] + @strawberry.type class MultiPolygonFeature(Feature): geometry: MultiPolygonGeometry @classmethod - def from_geodjango(cls, multipolygon: MultiPolygon, properties: dict = {}, id: str = None) -> "MultiPolygonFeature": + def from_geodjango( + cls, multipolygon: MultiPolygon, properties: dict = {}, id: str = None + ) -> "MultiPolygonFeature": return MultiPolygonFeature( id=str(id), geometry=MultiPolygonGeometry(coordinates=multipolygon), properties=properties, - ) \ No newline at end of file + ) diff --git a/hub/graphql/types/model_types.py b/hub/graphql/types/model_types.py index 49b610fb6..34616f8e0 100644 --- a/hub/graphql/types/model_types.py +++ b/hub/graphql/types/model_types.py @@ -7,10 +7,9 @@ from strawberry import auto from strawberry.types.info import Info from strawberry_django.auth.utils import get_current_user -from strawberry.scalars import JSON from hub import models -from hub.graphql.types.geojson import PointFeature, MultiPolygonFeature +from hub.graphql.types.geojson import MultiPolygonFeature, PointFeature from hub.graphql.utils import dict_key_field, fn_field @@ -149,33 +148,27 @@ class Area: extra_geojson_properties: strawberry.Private[object] @strawberry_django.field - def polygon(self, info: Info, with_parent_data: bool = False) -> Optional[MultiPolygonFeature]: - props = { - "name": self.name, - "gss": self.gss - } + def polygon( + self, info: Info, with_parent_data: bool = False + ) -> Optional[MultiPolygonFeature]: + props = {"name": self.name, "gss": self.gss} if with_parent_data and hasattr(self, "extra_geojson_properties"): props["extra_geojson_properties"] = self.extra_geojson_properties return MultiPolygonFeature.from_geodjango( - multipolygon=self.polygon, - id=self.gss, - properties=props + multipolygon=self.polygon, id=self.gss, properties=props ) @strawberry_django.field - def point(self, info: Info, with_parent_data: bool = False) -> Optional[PointFeature]: - props = { - "name": self.name, - "gss": self.gss - } + def point( + self, info: Info, with_parent_data: bool = False + ) -> Optional[PointFeature]: + props = {"name": self.name, "gss": self.gss} if with_parent_data and hasattr(self, "extra_geojson_properties"): props["extra_geojson_properties"] = self.extra_geojson_properties return PointFeature.from_geodjango( - point=self.point, - id=self.gss, - properties=props + point=self.point, id=self.gss, properties=props ) @@ -187,8 +180,8 @@ class GroupedDataCount: @strawberry_django.field def gss_area(self, info: Info) -> Optional[Area]: - if self.get('area_id', None): - area = models.Area.objects.get(gss=self['area_id']) + if self.get("area_id", None): + area = models.Area.objects.get(gss=self["area_id"]) area.extra_geojson_properties = self return area return None @@ -272,7 +265,7 @@ def imported_data_geojson_points( for generic_datum in data if generic_datum.point is not None ] - + imported_data_count: int = fn_field() imported_data_count_by_region: List[GroupedDataCount] = fn_field() imported_data_count_by_constituency: List[GroupedDataCount] = fn_field() @@ -333,10 +326,10 @@ def source(self, info: Info) -> ExternalDataSource: @strawberry_django.type(models.MapReport) class MapReport(Report): layers: Optional[List[MapLayer]] - + imported_data_count: int = fn_field() imported_data_count_by_region: List[GroupedDataCount] = fn_field() imported_data_count_by_constituency: List[GroupedDataCount] = fn_field() imported_data_count_by_constituency_2024: List[GroupedDataCount] = fn_field() imported_data_count_by_council: List[GroupedDataCount] = fn_field() - imported_data_count_by_ward: List[GroupedDataCount] = fn_field() \ No newline at end of file + imported_data_count_by_ward: List[GroupedDataCount] = fn_field() diff --git a/hub/graphql/utils.py b/hub/graphql/utils.py index c7c46199e..a13bbefd6 100644 --- a/hub/graphql/utils.py +++ b/hub/graphql/utils.py @@ -1,20 +1,26 @@ import strawberry_django from strawberry.types.info import Info + def attr_resolver(root, info: Info): return getattr(root, info.python_name, None) + def attr_field(**kwargs): return strawberry_django.field(resolver=attr_resolver, **kwargs) + def fn_resolver(root, info: Info): return getattr(root, info.python_name, lambda: None)() + def fn_field(**kwargs): return strawberry_django.field(resolver=fn_resolver, **kwargs) + def dict_resolver(root, info: Info): return root.get(info.python_name, None) + def dict_key_field(**kwargs): return strawberry_django.field(resolver=dict_resolver, **kwargs) diff --git a/hub/management/commands/import_areas.py b/hub/management/commands/import_areas.py index 8f2fc781f..6d4a70185 100644 --- a/hub/management/commands/import_areas.py +++ b/hub/management/commands/import_areas.py @@ -1,8 +1,8 @@ import json -from django.core.management.base import BaseCommand # from django postgis -from django.contrib.gis.geos import MultiPolygon, Polygon, GEOSGeometry +from django.contrib.gis.geos import GEOSGeometry, MultiPolygon, Polygon +from django.core.management.base import BaseCommand from tqdm import tqdm @@ -55,7 +55,7 @@ def handle(self, quiet: bool = False, *args, **options): ) a.geometry = geom_str - geom = GEOSGeometry(json.dumps(geom['geometry'])) + geom = GEOSGeometry(json.dumps(geom["geometry"])) if isinstance(geom, Polygon): geom = MultiPolygon([geom]) a.polygon = geom diff --git a/hub/management/commands/import_new_constituencies.py b/hub/management/commands/import_new_constituencies.py index f7bdde6a2..07b98d124 100644 --- a/hub/management/commands/import_new_constituencies.py +++ b/hub/management/commands/import_new_constituencies.py @@ -1,9 +1,9 @@ import json from django.conf import settings +from django.contrib.gis.geos import GEOSGeometry, MultiPolygon, Polygon from django.core.management.base import BaseCommand from django.db.utils import IntegrityError -from django.contrib.gis.geos import MultiPolygon, GEOSGeometry, Polygon from mysoc_dataset import get_dataset_df from tqdm import tqdm @@ -60,7 +60,7 @@ def handle(self, quiet: bool = False, *args, **options): con["properties"]["type"] = "WMC23" geom_str = json.dumps(con) - geom = GEOSGeometry(json.dumps(con['geometry'])) + geom = GEOSGeometry(json.dumps(con["geometry"])) if isinstance(geom, Polygon): geom = MultiPolygon([geom]) a.geometry = geom_str diff --git a/hub/management/commands/import_regions.py b/hub/management/commands/import_regions.py index 6fe00c1b9..0aa43d082 100644 --- a/hub/management/commands/import_regions.py +++ b/hub/management/commands/import_regions.py @@ -1,13 +1,13 @@ import json +from django.contrib.gis.geos import GEOSGeometry, MultiPolygon, Polygon from django.core.management.base import BaseCommand -from django.contrib.gis.geos import GEOSGeometry, Polygon, MultiPolygon +import requests from tqdm import tqdm from hub.models import Area, AreaType -from utils import mapit -import requests + class Command(BaseCommand): help = "Import historical European regions for high-level aggregation" @@ -22,7 +22,7 @@ def handle(self, quiet: bool = False, *args, **options): download_url = "https://open-geography-portalx-ons.hub.arcgis.com/api/download/v1/items/932f769148bb4753989e55b6703b7add/geojson?layers=0" data = requests.get(download_url).json() - areas = data['features'] + areas = data["features"] area_type, created = AreaType.objects.get_or_create( name="2018 European Electoral Regions", @@ -42,7 +42,7 @@ def handle(self, quiet: bool = False, *args, **options): ) geom_str = json.dumps(area) - geom = GEOSGeometry(json.dumps(area['geometry'])) + geom = GEOSGeometry(json.dumps(area["geometry"])) if isinstance(geom, Polygon): geom = MultiPolygon([geom]) geom.srid = 27700 diff --git a/hub/management/commands/import_wards.py b/hub/management/commands/import_wards.py index af0be1c69..2fce918ed 100644 --- a/hub/management/commands/import_wards.py +++ b/hub/management/commands/import_wards.py @@ -1,13 +1,12 @@ import json +from django.contrib.gis.geos import GEOSGeometry, MultiPolygon, Polygon from django.core.management.base import BaseCommand -from django.contrib.gis.geos import GEOSGeometry, Polygon, MultiPolygon +import requests from tqdm import tqdm from hub.models import Area, AreaType -from utils import mapit -import requests class Command(BaseCommand): @@ -23,7 +22,7 @@ def handle(self, quiet: bool = False, *args, **options): download_url = "https://open-geography-portalx-ons.hub.arcgis.com/api/download/v1/items/67c88ea8027244e3b2313c69e3fad503/geojson?layers=0" data = requests.get(download_url).json() - areas = data['features'] + areas = data["features"] area_type, created = AreaType.objects.get_or_create( name="May 2023 Electoral Wards", @@ -43,7 +42,7 @@ def handle(self, quiet: bool = False, *args, **options): ) geom_str = json.dumps(area) - geom = GEOSGeometry(json.dumps(area['geometry'])) + geom = GEOSGeometry(json.dumps(area["geometry"])) if isinstance(geom, Polygon): geom = MultiPolygon([geom]) geom.srid = 27700 diff --git a/hub/management/commands/run_all_import_scripts.py b/hub/management/commands/run_all_import_scripts.py index f8d3e32c0..69643b0f4 100644 --- a/hub/management/commands/run_all_import_scripts.py +++ b/hub/management/commands/run_all_import_scripts.py @@ -56,7 +56,12 @@ def run_importer_scripts(self, imports, *args, **options): total = str(len(imports)) i = 1 failed_imports = {} - priority_imports = ["import_regions", "import_areas", "import_mps", "import_mps_election_results"] + priority_imports = [ + "import_regions", + "import_areas", + "import_mps", + "import_mps_election_results", + ] for importer in priority_imports: imports.remove(importer) print(f"Running command: {importer} ({str(i)}/{total})") diff --git a/hub/models.py b/hub/models.py index aa3afc007..0d0a6828c 100644 --- a/hub/models.py +++ b/hub/models.py @@ -7,7 +7,7 @@ from django.conf import settings from django.contrib.auth import get_user_model -from django.contrib.gis.db.models import PointField, MultiPolygonField +from django.contrib.gis.db.models import MultiPolygonField, PointField from django.contrib.gis.geos import Point from django.db import models from django.db.models import Avg, IntegerField, Max, Min @@ -17,9 +17,7 @@ from django.urls import reverse from django.utils.functional import cached_property from django.utils.text import slugify -from django.db.models import Count, F -import pandas as pd from asgiref.sync import sync_to_async from django_choices_field import TextChoicesField from django_jsonform.models.fields import JSONField @@ -33,6 +31,7 @@ from strawberry.dataloader import DataLoader import utils as lih_utils +from hub.analytics import Analytics from hub.filters import Filter from hub.tasks import ( import_all, @@ -44,7 +43,6 @@ from hub.views.mapped import ExternalDataSourceAutoUpdateWebhook from utils.postcodesIO import PostcodesIOResult, get_bulk_postcode_geo from utils.py import ensure_list, get -from hub.analytics import Analytics User = get_user_model() @@ -965,7 +963,8 @@ async def create_import_record(record): "point": Point( postcode_data["longitude"], postcode_data["latitude"], - ) if ( + ) + if ( postcode_data is not None and "latitude" in postcode_data and "longitude" in postcode_data @@ -1088,7 +1087,7 @@ def get_import_data(self): return GenericData.objects.filter( data_type__data_set__external_data_source_id=self.id ) - + def get_analytics_queryset(self): return self.get_import_data() @@ -1602,10 +1601,12 @@ def get_layers(self) -> list[MapLayer]: return self.layers def get_import_data(self): - visible_layer_ids = [layer["source"] for layer in self.get_layers() if layer.get("visible", True)] + visible_layer_ids = [ + layer["source"] for layer in self.get_layers() if layer.get("visible", True) + ] return GenericData.objects.filter( data_type__data_set__external_data_source_id__in=visible_layer_ids ) - + def get_analytics_queryset(self): - return self.get_import_data() \ No newline at end of file + return self.get_import_data() diff --git a/hub/tests/test_import_areas.py b/hub/tests/test_import_areas.py index acc24687b..48d82b39c 100644 --- a/hub/tests/test_import_areas.py +++ b/hub/tests/test_import_areas.py @@ -13,7 +13,121 @@ class ImportAreasTestCase(TestCase): @mock.patch.object(MapIt, "areas_of_type") @mock.patch.object(MapIt, "area_geometry") def test_import(self, mapit_geom, mapit_areas): - mapit_geom.return_value = { "type": "Polygon", "coordinates": [[[355272.7037, 214366.797499999], [355288.5266, 214298.2019], [355302.7834, 214258.4015], [355308.0008, 214245.1022], [355297.4966, 214168.5042], [355287.0007, 214143.101399999], [355030.0021, 214093.3957], [354547.699, 213998.402799999], [354681.4009, 213668.6972], [354622.0035, 213539.5043], [354526.7978, 213482.8004], [354531.8025, 213458.997300001], [354554.1645, 213396.302200001], [354568.7458, 213360.897299999], [354679.0016, 212973.6962], [354677.1959, 212900.497099999], [354678.3008, 212856.299799999], [354674.7966, 212740.303099999], [354627.404, 212654.997500001], [354437.8006, 212591.195699999], [354276.0984, 212607.5011], [354147.6981, 212561.204299999], [353920.2004, 212464.502], [353724.3967, 212465.7017], [353663.3997, 212296.200099999], [353663.3997, 212271.7972], [353606.4016, 212269.797700001], [353530.4976, 212227.899700001], [353510.0993, 212183.702299999], [353511.8967, 212134.196599999], [353449.9021, 212047.101500001], [353454.0987, 212046.7015], [353417.3011, 211982.9998], [353413.2033, 211956.7973], [353309.497, 211795.4035], [353304.6983, 211769.800799999], [353309.802, 211738.0999], [353354.1096, 211661.005100001], [353363.5021, 211637.4987], [353377.4033, 211561.100500001], [353372.4975, 211531.399], [353376.6035, 211512.5044], [353372.2996, 211495.599300001], [353373.5034, 211470.4965], [353381.4788, 211448.9058], [353399.6513, 211405.151699999], [353403.7773, 211392.591600001], [353417.1165, 211351.503699999], [353494.4996, 211251.099300001], [353577.2965, 211143.300100001], [353578.8409, 211141.5089], [353722.6395, 210997.7005], [353736.7354, 210978.397399999], [353895.2935, 210848.2974], [353857.8018, 210780.604], [353739.9963, 210758.3004], [353700.9972, 210705.295499999], [353706.9007, 210651.101], [353681.4976, 210580.001399999], [353673.4999, 210521.7981], [353692.3976, 210445], [353684.6967, 210414.0988], [353685.4965, 210341.899599999], [353626.8988, 210191.4027], [353479.9966, 210150.304400001], [353434.4014, 210246.297], [353247.2962, 210453.197699999], [352832.0997, 210848.5046], [352653.1983, 211318.9999], [352579.8997, 211412.7031], [352468.7975, 211455.001], [352288.0988, 211465.797900001], [351890.2004, 211406.9047], [351793.6012, 211446.3035], [351611.6987, 211638.498500001], [351469.1993, 211852.7971], [351367.2986, 212081.001800001], [351234.3965, 212151.3017], [351120.5, 212207.6], [351043.8, 212350.800000001], [351059.7991, 212540.500299999], [351110.3001, 212685.398800001], [351317.8035, 212941.995300001], [351600.8977, 213142.697899999], [351948.0726, 213452.1622], [352569.7006, 213946.797700001], [352646.7012, 214054.4969], [352796.5965, 214632.501399999], [352757.4984, 215160.9002], [352799.5976, 215467.9023], [352815.9951, 215493.3969], [352776.899, 215530.304400001], [353049.2004, 215758.799000001], [353064.7011, 215681.801100001], [353272.5014, 215656.1984], [353363.197, 215589.6974], [353461.2968, 215564.9045], [353462.5006, 215520.6972], [353826.9982, 215505.9014], [354249.4999, 215404.500399999], [354288.598, 215379.5976], [354609.001, 215215.3046], [354746.0999, 215097.7982], [354781.2981, 214972.404100001], [354756.9999, 214598.801100001], [354828.5011, 214454.9023], [355033.1022, 214364.4981], [355272.7037, 214366.797499999]]]} + mapit_geom.return_value = { + "type": "Polygon", + "coordinates": [ + [ + [355272.7037, 214366.797499999], + [355288.5266, 214298.2019], + [355302.7834, 214258.4015], + [355308.0008, 214245.1022], + [355297.4966, 214168.5042], + [355287.0007, 214143.101399999], + [355030.0021, 214093.3957], + [354547.699, 213998.402799999], + [354681.4009, 213668.6972], + [354622.0035, 213539.5043], + [354526.7978, 213482.8004], + [354531.8025, 213458.997300001], + [354554.1645, 213396.302200001], + [354568.7458, 213360.897299999], + [354679.0016, 212973.6962], + [354677.1959, 212900.497099999], + [354678.3008, 212856.299799999], + [354674.7966, 212740.303099999], + [354627.404, 212654.997500001], + [354437.8006, 212591.195699999], + [354276.0984, 212607.5011], + [354147.6981, 212561.204299999], + [353920.2004, 212464.502], + [353724.3967, 212465.7017], + [353663.3997, 212296.200099999], + [353663.3997, 212271.7972], + [353606.4016, 212269.797700001], + [353530.4976, 212227.899700001], + [353510.0993, 212183.702299999], + [353511.8967, 212134.196599999], + [353449.9021, 212047.101500001], + [353454.0987, 212046.7015], + [353417.3011, 211982.9998], + [353413.2033, 211956.7973], + [353309.497, 211795.4035], + [353304.6983, 211769.800799999], + [353309.802, 211738.0999], + [353354.1096, 211661.005100001], + [353363.5021, 211637.4987], + [353377.4033, 211561.100500001], + [353372.4975, 211531.399], + [353376.6035, 211512.5044], + [353372.2996, 211495.599300001], + [353373.5034, 211470.4965], + [353381.4788, 211448.9058], + [353399.6513, 211405.151699999], + [353403.7773, 211392.591600001], + [353417.1165, 211351.503699999], + [353494.4996, 211251.099300001], + [353577.2965, 211143.300100001], + [353578.8409, 211141.5089], + [353722.6395, 210997.7005], + [353736.7354, 210978.397399999], + [353895.2935, 210848.2974], + [353857.8018, 210780.604], + [353739.9963, 210758.3004], + [353700.9972, 210705.295499999], + [353706.9007, 210651.101], + [353681.4976, 210580.001399999], + [353673.4999, 210521.7981], + [353692.3976, 210445], + [353684.6967, 210414.0988], + [353685.4965, 210341.899599999], + [353626.8988, 210191.4027], + [353479.9966, 210150.304400001], + [353434.4014, 210246.297], + [353247.2962, 210453.197699999], + [352832.0997, 210848.5046], + [352653.1983, 211318.9999], + [352579.8997, 211412.7031], + [352468.7975, 211455.001], + [352288.0988, 211465.797900001], + [351890.2004, 211406.9047], + [351793.6012, 211446.3035], + [351611.6987, 211638.498500001], + [351469.1993, 211852.7971], + [351367.2986, 212081.001800001], + [351234.3965, 212151.3017], + [351120.5, 212207.6], + [351043.8, 212350.800000001], + [351059.7991, 212540.500299999], + [351110.3001, 212685.398800001], + [351317.8035, 212941.995300001], + [351600.8977, 213142.697899999], + [351948.0726, 213452.1622], + [352569.7006, 213946.797700001], + [352646.7012, 214054.4969], + [352796.5965, 214632.501399999], + [352757.4984, 215160.9002], + [352799.5976, 215467.9023], + [352815.9951, 215493.3969], + [352776.899, 215530.304400001], + [353049.2004, 215758.799000001], + [353064.7011, 215681.801100001], + [353272.5014, 215656.1984], + [353363.197, 215589.6974], + [353461.2968, 215564.9045], + [353462.5006, 215520.6972], + [353826.9982, 215505.9014], + [354249.4999, 215404.500399999], + [354288.598, 215379.5976], + [354609.001, 215215.3046], + [354746.0999, 215097.7982], + [354781.2981, 214972.404100001], + [354756.9999, 214598.801100001], + [354828.5011, 214454.9023], + [355033.1022, 214364.4981], + [355272.7037, 214366.797499999], + ] + ], + } mapit_areas.return_value = [ { diff --git a/utils/geo.py b/utils/geo.py index 6fcd4b83b..72d94b5d5 100644 --- a/utils/geo.py +++ b/utils/geo.py @@ -4,42 +4,18 @@ def create_point(latitude: float = 0, longitude: float = 0): return Point(x=float(longitude), y=float(latitude), srid=4326) + EERs = [ - { - "code": "E15000001", - "label": "North East" - }, { - "code": "E15000002", - "label": "North West" - }, { - "code": "E15000003", - "label": "Yorkshire and The Humber" - }, { - "code": "E15000004", - "label": "East Midlands" - }, { - "code": "E15000005", - "label": "West Midlands" - }, { - "code": "E15000006", - "label": "Eastern" - }, { - "code": "E15000007", - "label": "London" - }, { - "code": "E15000008", - "label": "South East" - }, { - "code": "E15000009", - "label": "South West" - }, { - "code": "N07000001", - "label": "Northern Ireland" - }, { - "code": "S15000001", - "label": "Scotland" - }, { - "code": "W08000001", - "label": "Wales" - } -] \ No newline at end of file + {"code": "E15000001", "label": "North East"}, + {"code": "E15000002", "label": "North West"}, + {"code": "E15000003", "label": "Yorkshire and The Humber"}, + {"code": "E15000004", "label": "East Midlands"}, + {"code": "E15000005", "label": "West Midlands"}, + {"code": "E15000006", "label": "Eastern"}, + {"code": "E15000007", "label": "London"}, + {"code": "E15000008", "label": "South East"}, + {"code": "E15000009", "label": "South West"}, + {"code": "N07000001", "label": "Northern Ireland"}, + {"code": "S15000001", "label": "Scotland"}, + {"code": "W08000001", "label": "Wales"}, +] diff --git a/utils/postcodesIO.py b/utils/postcodesIO.py index 4434c6b75..1519e0920 100644 --- a/utils/postcodesIO.py +++ b/utils/postcodesIO.py @@ -6,7 +6,7 @@ import httpx import requests -from utils.geo import create_point, EERs +from utils.geo import EERs, create_point from utils.py import batch_and_aggregate, get, get_path @@ -85,10 +85,11 @@ def get_postcode_geo(postcode: str) -> PostcodesIOResult: if status != 200 or result is None: raise Exception(f"Failed to geocode postcode: {postcode}.") - - result['codes']['european_electoral_region'] = next( - filter(lambda eer: eer['label'] == result['european_electoral_region'], EERs), {} - ).get('code', None) + + result["codes"]["european_electoral_region"] = next( + filter(lambda eer: eer["label"] == result["european_electoral_region"], EERs), + {}, + ).get("code", None) return result @@ -125,9 +126,13 @@ async def get_bulk_postcode_geo(postcodes) -> PostcodesIOBulkResult: # add EER codes for index, result in enumerate(results): if result is not None: - results[index]['codes']['european_electoral_region'] = next( - filter(lambda eer: eer['label'] == result['european_electoral_region'], EERs), {} - ).get('code', None) + results[index]["codes"]["european_electoral_region"] = next( + filter( + lambda eer: eer["label"] == result["european_electoral_region"], + EERs, + ), + {}, + ).get("code", None) return results diff --git a/utils/py.py b/utils/py.py index 9b05354f5..e2a78066d 100644 --- a/utils/py.py +++ b/utils/py.py @@ -1,9 +1,8 @@ import pprint +from types import SimpleNamespace from benedict import benedict -from types import SimpleNamespace - class DictWithDotNotation(SimpleNamespace): def __init__(self, dictionary, **kwargs): From 8610abb0a9fde632bf869e686a425ac737b12669 Mon Sep 17 00:00:00 2001 From: Jan Baykara Date: Wed, 20 Mar 2024 23:45:01 +0000 Subject: [PATCH 17/18] Fix tests --- hub/analytics.py | 15 +++++---------- hub/models.py | 12 ++++++++++++ 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/hub/analytics.py b/hub/analytics.py index 887f58938..cceac524a 100644 --- a/hub/analytics.py +++ b/hub/analytics.py @@ -1,20 +1,15 @@ -from typing import List, Optional, TypedDict +from typing import TYPE_CHECKING, List, Optional, TypedDict from django.db.models import Count, F from django.db.models.manager import BaseManager -import pandas as pd +if TYPE_CHECKING: + from hub.models import GenericData class Analytics: - get_analytics_queryset: BaseManager - - def get_imported_dataframe(self): - json_list = [ - {**d.postcode_data, **d.json} for d in self.get_analytics_queryset() - ] - enrichment_df = pd.DataFrame.from_records(json_list) - return enrichment_df + def get_analytics_queryset(self) -> BaseManager["GenericData"]: + raise NotImplementedError("Subclasses must implement this method") class RegionCount(TypedDict): label: str diff --git a/hub/models.py b/hub/models.py index 0d0a6828c..a9d9bfb36 100644 --- a/hub/models.py +++ b/hub/models.py @@ -18,6 +18,7 @@ from django.utils.functional import cached_property from django.utils.text import slugify +import pandas as pd from asgiref.sync import sync_to_async from django_choices_field import TextChoicesField from django_jsonform.models.fields import JSONField @@ -1091,6 +1092,17 @@ def get_import_data(self): def get_analytics_queryset(self): return self.get_import_data() + def get_imported_dataframe(self): + json_list = [ + { + **(d.postcode_data if d.postcode_data else {}), + **(d.json if d.json else {}), + } + for d in self.get_analytics_queryset() + ] + enrichment_df = pd.DataFrame.from_records(json_list) + return enrichment_df + def data_loader_factory(self): async def fetch_enrichment_data(keys: List[self.EnrichmentLookup]) -> list[str]: return_data = [] From 37c2e48fde0a0ece9ce602b4742126a42e054d02 Mon Sep 17 00:00:00 2001 From: Jan Baykara Date: Wed, 20 Mar 2024 23:59:43 +0000 Subject: [PATCH 18/18] Add a basic test for analytics x --- hub/tests/test_sources.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/hub/tests/test_sources.py b/hub/tests/test_sources.py index ed8cdc371..f409db99b 100644 --- a/hub/tests/test_sources.py +++ b/hub/tests/test_sources.py @@ -247,3 +247,23 @@ def test_airtable_filter(self): self.assertEqual( self.source.get_record_field(records[0], "Postcode"), date + "11111" ) + + def test_analytics(self): + """ + This is testing the ability to get analytics from the data source + """ + # Add some test data + self.create_many_test_records([{"Postcode": "E5 0AA"}, {"Postcode": "E10 6EF"}]) + # import + async_to_sync(self.source.import_all)() + # check analytics + analytics = self.source.imported_data_count_by_constituency() + constituencies_in_report = list(map(lambda a: a["label"], analytics)) + self.assertGreaterEqual(len(analytics), 2) + self.assertIn("Hackney North and Stoke Newington", constituencies_in_report) + self.assertIn("Leyton and Wanstead", constituencies_in_report) + for a in analytics: + if a["label"] == "Hackney North and Stoke Newington": + self.assertGreaterEqual(a["count"], 1) + elif a["label"] == "Leyton and Wanstead": + self.assertGreaterEqual(a["count"], 1)