Repository: mathesar-foundation/mathesar

Issue:
Better names for backend default summarization aliases 
Related: #1812

Expectations described here: https://github.com/centerofci/mathesar/issues/1812#issuecomment-1343349433

Below is my summary; note, I replaced mentions of `initial_column` with `input_alias`, also, removed mention of count aggregation, because backend does not create such aliases:

> Grouped columns: <input_alias_display_name> group
> Array aggregation: <input_alias_display_name> list

Some technical details:

Backend currently does not provide default display names (when it's finishing specifying a summarization); it only provides default aliases. So the aliases will stay the same, but backend will create above-specified display names.


Discussion:

Resolving patch:
diff --git a/db/transforms/base.py b/db/transforms/base.py
--- a/db/transforms/base.py
+++ b/db/transforms/base.py
@@ -280,7 +280,7 @@ def _get_grouping_column(col_spec):
                 .to_sa_expression(relation.columns[col_spec['input_alias']])
                 .label(col_spec['output_alias'])
             )
-            for col_spec in self._aggregation_col_specs
+            for col_spec in self.aggregation_col_specs
         ]
 
         executable = (
@@ -304,7 +304,7 @@ def get_unique_constraint_mappings(self, _):
                 output_alias=col_spec['output_alias'],
             )
             for col_spec
-            in self._aggregation_col_specs
+            in self.aggregation_col_specs
         ]
         return (
             mappings_that_carry_uniqueness_over
@@ -347,6 +347,22 @@ def get_col_spec_from_alias(alias):
     def base_grouping_column(self):
         return self.spec['base_grouping_column']
 
+    @property
+    def aggregation_output_aliases(self):
+        return [
+            col_spec['output_alias']
+            for col_spec
+            in self.aggregation_col_specs
+        ]
+
+    @property
+    def grouping_output_aliases(self):
+        return [
+            col_spec['output_alias']
+            for col_spec
+            in self._grouping_col_specs
+        ]
+
     @property
     def grouping_input_aliases(self):
         return [
@@ -360,7 +376,7 @@ def aggregation_input_aliases(self):
         return [
             col_spec['input_alias']
             for col_spec
-            in self._aggregation_col_specs
+            in self.aggregation_col_specs
         ]
 
     @property
@@ -368,7 +384,7 @@ def _grouping_col_specs(self):
         return self.spec.get("grouping_expressions", [])
 
     @property
-    def _aggregation_col_specs(self):
+    def aggregation_col_specs(self):
         return self.spec.get("aggregation_expressions", [])
 
 
diff --git a/mathesar/api/db/viewsets/queries.py b/mathesar/api/db/viewsets/queries.py
--- a/mathesar/api/db/viewsets/queries.py
+++ b/mathesar/api/db/viewsets/queries.py
@@ -100,8 +100,8 @@ def run(self, request):
         input_serializer.is_valid(raise_exception=True)
         query = UIQuery(**input_serializer.validated_data)
         try:
-            # TODO Consider treating the transformation as immutable
             query.replace_transformations_with_processed_transformations()
+            query.add_defaults_to_display_names()
             record_serializer = RecordListParameterSerializer(data=request.GET)
             record_serializer.is_valid(raise_exception=True)
             output_serializer = BaseQuerySerializer(query)
diff --git a/mathesar/models/query.py b/mathesar/models/query.py
--- a/mathesar/models/query.py
+++ b/mathesar/models/query.py
@@ -6,6 +6,7 @@
 from db.queries.operations.process import get_transforms_with_summarizes_speced
 from db.transforms.operations.deserialize import deserialize_transformation
 from db.transforms.operations.serialize import serialize_transformation
+from db.transforms.base import Summarize
 
 from mathesar.api.exceptions.query_exceptions.exceptions import DeletedColumnAccess
 from mathesar.state.cached_property import cached_property
@@ -353,6 +354,92 @@ def _get_display_options_for_alias(self, alias):
                 display_options = self._get_display_options_for_alias(parent_alias)
         return display_options
 
+    @cached_property
+    def _alias_to_display_name(self):
+        alias_to_display_name = {}
+        if self.display_names is not None:
+            alias_to_display_name.update(self.display_names)
+        return alias_to_display_name
+
+    @property
+    def _sa_engine(self):
+        return self.base_table._sa_engine
+
+    def add_defaults_to_display_names(self):
+        """
+        We have some logic for producing default display names. This method fetches those default
+        display names and merges them with previously-stored display names. Previously-stored
+        display names take precedence.
+        """
+        current_display_names = self.display_names or dict()
+        self.display_names = self._default_display_names | current_display_names
+
+    @property
+    def _default_display_names(self):
+        """
+        Returns default display options for initial columns merged with default display options for
+        summarizations. Does not return current display names (as stored in the `display_names`
+        attribute), though they are used when generating some of the default display names.
+        """
+        current_display_names = self.display_names or dict()
+        default_display_names_for_initial_columns = self._default_display_names_for_initial_columns
+        current_display_names = \
+            default_display_names_for_initial_columns \
+            | current_display_names
+        default_display_names_for_summarize_transforms = \
+            self._get_default_display_names_for_summarize_transforms(
+                current_display_names
+            )
+        default_display_names = \
+            default_display_names_for_summarize_transforms \
+            | default_display_names_for_initial_columns
+        return default_display_names
+
+    @property
+    def _default_display_names_for_initial_columns(self):
+        return {
+            alias: dj_col.name
+            for alias, dj_col
+            in self._map_of_initial_col_alias_to_dj_column.items()
+        }
+
+    def _get_default_display_names_for_summarize_transforms(self, current_display_names):
+        default_display_names = dict()
+        if not current_display_names:
+            return default_display_names
+        summarize_transforms = [
+            db_transform
+            for db_transform
+            in self.db_query.transformations
+            if isinstance(db_transform, Summarize)
+        ]
+        for summarize_transform in summarize_transforms:
+            # Find default display names for grouping output aliases
+            for output_alias in summarize_transform.grouping_output_aliases:
+                default_display_name = \
+                    _get_default_display_name_for_group_output_alias(
+                        summarize_transform,
+                        output_alias,
+                        current_display_names,
+                    )
+                if default_display_name:
+                    default_display_names[output_alias] = default_display_name
+            # Find default display names for aggregation output aliases
+            for agg_col_spec in summarize_transform.aggregation_col_specs:
+                input_alias = agg_col_spec.get("input_alias")
+                output_alias = agg_col_spec.get("output_alias")
+                agg_function = agg_col_spec.get("function")
+                default_display_name = \
+                    _get_default_display_name_for_agg_output_alias(
+                        output_alias,
+                        input_alias,
+                        agg_function,
+                        current_display_names,
+                    )
+                if default_display_name:
+                    default_display_names[output_alias] = default_display_name
+        return default_display_names
+
     @property
     def _map_of_initial_col_alias_to_dj_column(self):
         dj_column_ids = [col['id'] for col in self.initial_columns]
@@ -369,17 +456,6 @@ def _map_of_initial_col_alias_to_dj_column(self):
             )
         )
 
-    @cached_property
-    def _alias_to_display_name(self):
-        alias_to_display_name = {}
-        if self.display_names is not None:
-            alias_to_display_name.update(self.display_names)
-        return alias_to_display_name
-
-    @property
-    def _sa_engine(self):
-        return self.base_table._sa_engine
-
 
 def _get_dj_column_for_initial_db_column(initial_column):
     oid = initial_column.reloid
@@ -410,3 +486,35 @@ def _db_initial_column_from_json(col_json):
         alias=alias,
         jp_path=jp_path if jp_path else None,
     )
+
+
+def _get_default_display_name_for_agg_output_alias(
+    output_alias,
+    input_alias,
+    agg_function,
+    current_display_names,
+):
+    if output_alias and input_alias and agg_function:
+        map_of_agg_function_to_suffix = dict(
+            aggregate_to_array=" list",
+            count=" count",
+        )
+        suffix_to_add = map_of_agg_function_to_suffix.get(agg_function)
+        if suffix_to_add:
+            input_alias_display_name = current_display_names.get(input_alias)
+            if input_alias_display_name:
+                return input_alias_display_name + suffix_to_add
+
+
+def _get_default_display_name_for_group_output_alias(
+    summarize_transform,
+    output_alias,
+    current_display_names,
+):
+    input_alias = \
+        summarize_transform\
+        .map_of_output_alias_to_input_alias[output_alias]
+    input_alias_display_name = current_display_names.get(input_alias)
+    if input_alias_display_name:
+        suffix_to_add = " group"
+        return input_alias_display_name + suffix_to_add


Test patch:
diff --git a/mathesar/tests/api/query/test_query_run_partial_transforms.py b/mathesar/tests/api/query/test_query_run_partial_transforms.py
--- a/mathesar/tests/api/query/test_query_run_partial_transforms.py
+++ b/mathesar/tests/api/query/test_query_run_partial_transforms.py
@@ -111,13 +111,19 @@ def test_partial_summarize_transform(
         | {
             'schema': base_table.schema.id,
             'transformations': output_transformations,
+            'display_names': {
+                'col1': 'Center',
+                'col1_grouped': 'Center group',
+                'col2': 'Case Number',
+                'col2_agged': 'Case Number list',
+            },
         }
     )
     expect_response_json = {
         'column_metadata': {
             'col1': {
                 'alias': 'col1',
-                'display_name': None,
+                'display_name': 'Center',
                 'display_options': None,
                 'input_alias': None,
                 'input_column_name': 'Center',
@@ -128,7 +134,7 @@ def test_partial_summarize_transform(
             },
             'col1_grouped': {
                 'alias': 'col1_grouped',
-                'display_name': None,
+                'display_name': 'Center group',
                 'display_options': None,
                 'input_alias': 'col1',
                 'input_column_name': None,
@@ -139,7 +145,7 @@ def test_partial_summarize_transform(
             },
             'col2': {
                 'alias': 'col2',
-                'display_name': None,
+                'display_name': 'Case Number',
                 'display_options': None,
                 'input_alias': None,
                 'input_column_name': 'Case Number',
@@ -150,7 +156,7 @@ def test_partial_summarize_transform(
             },
             'col2_agged': {
                 'alias': 'col2_agged',
-                'display_name': None,
+                'display_name': 'Case Number list',
                 'display_options': None,
                 'input_alias': 'col2',
                 'input_column_name': None,