diff --git a/apps/analysis_framework/migrations/0041_widget_mapping.py b/apps/analysis_framework/migrations/0041_widget_mapping.py new file mode 100644 index 0000000000..02682b0edc --- /dev/null +++ b/apps/analysis_framework/migrations/0041_widget_mapping.py @@ -0,0 +1,83 @@ +# Generated by Django 3.2.17 on 2024-02-22 05:02 +from django.db import migrations +import copy + + +def analysis_framework_widgets_mapping(apps, schema_editor): + AnalysisFramework = apps.get_model('analysis_framework', 'AnalysisFramework') + af_qs = AnalysisFramework.objects.filter(properties__isnull=False) + for af in af_qs: + if af.properties == {}: + continue + new_widget_config = af.properties + # Remove super legacy config + new_widget_config.pop('old_stats_config', None) + + # For reassurance + new_widget_config = copy.deepcopy(new_widget_config) + + # Migrate legacy config to latest + # -- Widget1D + if 'widget_1d' not in new_widget_config['stats_config']: + new_widget_config['widget_1d'] = [ + {'pk': new_widget_config['stats_config'][key]['pk']} + for key in [ + 'widget1d' + ] + if key in new_widget_config + ] + + # -- Widget2D + if 'widget_2d' not in new_widget_config['stats_config']: + new_widget_config['widget_2d'] = [ + {'pk': new_widget_config['stats_config'][key]['pk']} + for key in [ + 'widget2d' + ] + if key in new_widget_config + ] + + # -- Multiselect + if 'multiselect_widgets' not in new_widget_config['stats_config']: + new_widget_config['multiselect_widgets'] = [ + {'pk': new_widget_config['stats_config'][key]['pk']} + for key in [ + 'specific_needs_groups_widget', + 'demographic_groups_widget' + ] + if key in new_widget_config + ] + + # -- Organigram + if 'organigram_widget' not in new_widget_config['stats_config']: + new_widget_config['organigram_widget'] = [ + {'pk': new_widget_config['stats_config'][key]['pk']} + for key in [ + 'affected_groups_widget' + ] + if key in new_widget_config + ] + + legacy_widget_keys = [ + 'affected_groups_widget', + 'demographic_groups_widget', + 'specific_needs_groups_widget', + ] + for widget_key in legacy_widget_keys: + new_widget_config['stats_config'].pop(widget_key, None) + + af.properties = new_widget_config + af.save(update_fields=('properties',)) + + +class Migration(migrations.Migration): + + dependencies = [ + ('analysis_framework', '0040_auto_20231109_1208'), + ] + operations = [ + migrations.RunPython( + analysis_framework_widgets_mapping, + reverse_code=migrations.RunPython.noop + ) + ] diff --git a/apps/analysis_framework/serializers.py b/apps/analysis_framework/serializers.py index a29f3545a7..a322bc1c85 100644 --- a/apps/analysis_framework/serializers.py +++ b/apps/analysis_framework/serializers.py @@ -423,7 +423,8 @@ class AnalysisFrameworkPropertiesStatsConfigSerializer(serializers.Serializer): @staticmethod def _validate_widget_with_widget_type(data, widget_type, many=False): if not data: - return + if many: + return [] if many: ids = [item['pk'] for item in data] widgets = list(Widget.objects.filter(pk__in=ids)) diff --git a/apps/analysis_framework/tests/snapshots/snap_test_mutations.py b/apps/analysis_framework/tests/snapshots/snap_test_mutations.py index b199f69a33..8af09fe2f7 100644 --- a/apps/analysis_framework/tests/snapshots/snap_test_mutations.py +++ b/apps/analysis_framework/tests/snapshots/snap_test_mutations.py @@ -715,6 +715,17 @@ 'title': 'Scale', 'version': 1, 'widgetId': 'SCALE' + }, + { + 'clientId': 'organigram-widget-104-client-id', + 'id': '9', + 'key': 'organigram-widget-104-key', + 'order': 5, + 'properties': { + }, + 'title': 'Organigram', + 'version': 1, + 'widgetId': 'ORGANIGRAM' } ], 'title': 'AF (TEST)' @@ -780,6 +791,13 @@ 'messages': "Different widget type was provided. Required: matrix2dWidget Provided: ['multiselectWidget']", 'objectErrors': None }, + { + 'arrayErrors': None, + 'clientId': None, + 'field': 'multiselectWidgets', + 'messages': "Different widget type was provided. Required: multiselectWidget Provided: ['organigramWidget']", + 'objectErrors': None + }, { 'arrayErrors': None, 'clientId': None, @@ -843,7 +861,7 @@ { 'clientId': 'section-2-text-101-client-id', 'conditional': None, - 'id': '9', + 'id': '10', 'key': 'section-2-text-101', 'order': 1, 'properties': { @@ -1018,7 +1036,11 @@ 'pk': '6' } ], - 'organigramWidgets': None, + 'organigramWidgets': [ + { + 'pk': '9' + } + ], 'reliabilityWidget': { 'pk': '8' }, @@ -1041,7 +1063,7 @@ { 'clientId': 'multi-select-widget-102-client-id', 'conditional': None, - 'id': '10', + 'id': '11', 'key': 'multi-select-widget-102-key', 'order': 2, 'properties': { @@ -1073,6 +1095,18 @@ 'title': 'Scale', 'version': 1, 'widgetId': 'SCALE' + }, + { + 'clientId': 'organigram-widget-104-client-id', + 'conditional': None, + 'id': '9', + 'key': 'organigram-widget-104-key', + 'order': 5, + 'properties': { + }, + 'title': 'Organigram', + 'version': 1, + 'widgetId': 'ORGANIGRAM' } ], 'title': 'Updated AF (TEST)' @@ -1143,7 +1177,7 @@ { 'clientId': 'section-2-text-101-client-id', 'conditional': None, - 'id': '12', + 'id': '13', 'key': 'section-2-text-101', 'order': 1, 'properties': { @@ -1320,7 +1354,11 @@ }, 'multiselectWidgets': [ ], - 'organigramWidgets': None, + 'organigramWidgets': [ + { + 'pk': '9' + } + ], 'reliabilityWidget': { 'pk': '8' }, @@ -1348,7 +1386,7 @@ 'parentWidget': '1', 'parentWidgetType': 'MATRIX1D' }, - 'id': '13', + 'id': '14', 'key': 'multi-select-widget-102-key', 'order': 2, 'properties': { @@ -1380,6 +1418,18 @@ 'title': 'Scale', 'version': 1, 'widgetId': 'SCALE' + }, + { + 'clientId': 'organigram-widget-104-client-id', + 'conditional': None, + 'id': '9', + 'key': 'organigram-widget-104-key', + 'order': 5, + 'properties': { + }, + 'title': 'Organigram', + 'version': 1, + 'widgetId': 'ORGANIGRAM' } ], 'title': 'Updated AF (TEST)' @@ -1412,7 +1462,7 @@ { 'clientId': 'section-2-text-101-client-id', 'conditional': None, - 'id': '14', + 'id': '15', 'key': 'section-2-text-101', 'order': 1, 'properties': { @@ -1589,7 +1639,11 @@ }, 'multiselectWidgets': [ ], - 'organigramWidgets': None, + 'organigramWidgets': [ + { + 'pk': '9' + } + ], 'reliabilityWidget': { 'pk': '8' }, @@ -1612,7 +1666,7 @@ { 'clientId': 'multi-select-widget-102-client-id', 'conditional': None, - 'id': '15', + 'id': '16', 'key': 'multi-select-widget-102-key', 'order': 2, 'properties': { @@ -1644,6 +1698,18 @@ 'title': 'Scale', 'version': 1, 'widgetId': 'SCALE' + }, + { + 'clientId': 'organigram-widget-104-client-id', + 'conditional': None, + 'id': '9', + 'key': 'organigram-widget-104-key', + 'order': 5, + 'properties': { + }, + 'title': 'Organigram', + 'version': 1, + 'widgetId': 'ORGANIGRAM' } ], 'title': 'Updated AF (TEST)' diff --git a/apps/analysis_framework/tests/test_mutations.py b/apps/analysis_framework/tests/test_mutations.py index 16ec9602a2..b630820fa6 100644 --- a/apps/analysis_framework/tests/test_mutations.py +++ b/apps/analysis_framework/tests/test_mutations.py @@ -430,7 +430,7 @@ def test_analysis_framework_update(self): widget2d { pk } - multiselectWidgets { + multiselectWidgets { pk } organigramWidgets { @@ -492,6 +492,7 @@ def _query_check(id, minput, **kwargs): variables={'id': id}, **kwargs, ) + # ---------- Without login valid_minput = copy.deepcopy(self.valid_minput) new_widgets = [ @@ -513,6 +514,15 @@ def _query_check(id, minput, **kwargs): order=4, properties=dict(), ), + dict( + clientId='organigram-widget-104-client-id', + title='Organigram', + widgetId=self.genum(Widget.WidgetType.ORGANIGRAM), + version=1, + key='organigram-widget-104-key', + order=5, + properties=dict(), + ), ] valid_minput['secondaryTagging'].extend(new_widgets) _query_check(0, valid_minput, assert_for_error=True) diff --git a/apps/entry/stats.py b/apps/entry/stats.py index 759cf5526d..8c0ab7e085 100644 --- a/apps/entry/stats.py +++ b/apps/entry/stats.py @@ -97,7 +97,7 @@ def _return(properties): return _return(w_filter.properties if w_filter else None) properties = widget.properties - if config.get('is_conditional_widget'): + if config.get('is_conditional_widget'): # TODO: Remove this # TODO: Skipping conditional widget, in new this is not needed return default return _return(properties) @@ -180,9 +180,9 @@ def get_project_entries_stats(project, skip_geo_data=False): 'reliability_widget': { 'pk': 2683, }, - 'organigram_widgets': { - 'pk': 2682, - }, + 'organigram_widgets': [ + {'pk': 2682}, + ] 'multiselect_widgets': [ {'pk': 2681}, {'pk': 8703}, @@ -193,21 +193,6 @@ def get_project_entries_stats(project, skip_geo_data=False): af = project.analysis_framework config = af.properties.get('stats_config') - # TODO: REMOVE THIS - if 'multiselect_widgets' not in config: - config['multiselect_widgets'] = [ - {'pk': config[key]['pk']} - for key in [ - 'specific_needs_groups_widget', - 'demographic_groups_widget', - ] - if key in config - ] - if 'organigram_widgets' not in config and 'affected_groups_widget' in config: - config['organigram_widgets'] = [ - config['affected_groups_widget'] - ] - widgets_pk = [ info['pk'] for _info in config.values() @@ -225,15 +210,6 @@ def get_project_entries_stats(project, skip_geo_data=False): for widget in Widget.objects.filter(pk__in=widgets_pk, analysis_framework=af) } - # TODO: Remove this later after all data are updated. - config['widget1d'] = config.get('widget1d') or config['widget_1d'] - config['widget2d'] = config.get('widget2d') or config['widget_2d'] - - # Make sure this are array - for key in ['widget1d', 'widget2d']: - if not isinstance(config[key], list): - config[key] = [config[key]] - w_reliability_default = w_severity_default = w_multiselect_widget_default = w_organigram_widget_default = { 'pk': None, 'properties': { @@ -241,8 +217,8 @@ def get_project_entries_stats(project, skip_geo_data=False): }, } - w1ds = [_get_widget_info(_config, widgets) for _config in config['widget1d']] - w2ds = [_get_widget_info(_config, widgets) for _config in config['widget2d']] + w1ds = [_get_widget_info(_config, widgets) for _config in config['widget_1d'] or []] + w2ds = [_get_widget_info(_config, widgets) for _config in config['widget_2d'] or []] w_multiselect_widgets = [ _get_widget_info( @@ -269,7 +245,7 @@ def get_project_entries_stats(project, skip_geo_data=False): matrix_widgets = [ {'id': w['pk'], 'type': w_type, 'title': w['_widget'].title} - for widgets, w_type in [[w1ds, 'widget1d'], [w2ds, 'widget2d']] + for widgets, w_type in [[w1ds, 'widget_1d'], [w2ds, 'widget_2d']] for w in widgets ] diff --git a/apps/project/management/commands/generate_projects_viz_stats.py b/apps/project/management/commands/generate_projects_viz_stats.py new file mode 100644 index 0000000000..6bd86c4d96 --- /dev/null +++ b/apps/project/management/commands/generate_projects_viz_stats.py @@ -0,0 +1,19 @@ +from django.core.management.base import BaseCommand + +from project.models import Project +from project.tasks import generate_viz_stats + + +class Command(BaseCommand): + help = 'Generate the Project Viz Stats' + + def handle(self, *arg, **options): + generate_project_viz_stats() + + +def generate_project_viz_stats(): + project_qs = Project.objects.filter( + is_visualization_enabled=True + ) + for project in project_qs: + generate_viz_stats(project.id, force=True) diff --git a/apps/project/tests/snapshots/snap_test_mutations.py b/apps/project/tests/snapshots/snap_test_mutations.py index ba9a301d85..6989984c86 100644 --- a/apps/project/tests/snapshots/snap_test_mutations.py +++ b/apps/project/tests/snapshots/snap_test_mutations.py @@ -208,36 +208,6 @@ } } -snapshots['ProjectMutationSnapshotTest::test_project_update_mutation public-project-0:project-change:diff'] = { - 'details': { - 'description': { - 'new': 'Added some description', - 'old': '' - } - }, - 'organizations': { - 'add': [ - { - 'organization': 1, - 'organization_type': 'lead_organization' - } - ], - 'remove': [ - { - 'organization': 2, - 'organization_type': 'national_partner' - } - ] - } -} - -snapshots['ProjectMutationSnapshotTest::test_project_update_mutation public-project-1:project-change:diff'] = { - 'framework': { - 'new': 4, - 'old': 2 - } -} - snapshots['ProjectMutationSnapshotTest::test_project_update_mutation public-project:is-private-change-error'] = { 'data': { '__typename': 'Mutation', diff --git a/apps/project/tests/test_apis.py b/apps/project/tests/test_apis.py index a806c84c2d..bd74ee47a9 100644 --- a/apps/project/tests/test_apis.py +++ b/apps/project/tests/test_apis.py @@ -1304,9 +1304,11 @@ def test_project_stats(self): **config_kwargs, } invalid_stat_config[widget_identifier] = {'pk': 0} + if data_identifier in ['matrix1dWidget', 'matrix2dWidget', 'multiselectWidget']: + valid_stat_config[widget_identifier] = [valid_stat_config[widget_identifier]] + invalid_stat_config[widget_identifier] = [invalid_stat_config[widget_identifier]] url = f'/api/v1/projects/{project.pk}/project-viz/' - # 404 for non project user self.authenticate(non_project_user) response = self.client.get(url)