From b8486dcdba6e8fd30ac66112738c153c1714d677 Mon Sep 17 00:00:00 2001 From: Alex Hall Date: Mon, 11 Jul 2022 20:00:25 +0200 Subject: [PATCH] (core) Nice summary table IDs Summary: Changes auto-generated summary table IDs from e.g. `GristSummary_6_Table1` to `Table1_summary_A_B` (meaning `Table1` grouped by `A` and `B`). This makes it easier to write formulas involving summary tables, make API requests, understand logs, etc. Because these don't encode the source table ID as reliably as before, `decode_summary_table_name` now uses the summary table schema info, not just the summary table ID. Specifically, it looks at the type of the `group` column, which is `RefList:`. Renaming a source table renames the summary table as before, and now renaming a groupby column renames the summary table as well. Conflicting table names are resolved in the usual way by adding a number at the end, e.g. `Table1_summary_A_B2`. These summary tables are not automatically renamed when the disambiguation is no longer needed. A new migration renames all summary tables to the new scheme, and updates formulas using summary tables with a simple regex. Test Plan: Updated many tests to use the new style of name. Added new Python tests to for resolving conflicts when renaming source tables and groupby columns. Added a test for the migration, including renames in formulas. Reviewers: georgegevoian Reviewed By: georgegevoian Differential Revision: https://phab.getgrist.com/D3508 --- app/common/schema.ts | 2 +- app/server/lib/DocPluginManager.ts | 7 +- app/server/lib/initialDocSql.ts | 4 +- sandbox/grist/engine.py | 4 +- sandbox/grist/gencode.py | 11 +- sandbox/grist/migrations.py | 61 ++++- sandbox/grist/schema.py | 2 +- sandbox/grist/summary.py | 46 ++-- sandbox/grist/test_column_actions.py | 10 +- sandbox/grist/test_derived.py | 76 +++--- sandbox/grist/test_summary.py | 117 ++++---- sandbox/grist/test_summary2.py | 326 ++++++++++++++++------- sandbox/grist/test_summary_choicelist.py | 80 +++--- sandbox/grist/test_summary_undo.py | 6 +- sandbox/grist/test_table_actions.py | 30 +-- sandbox/grist/test_useractions.py | 22 +- sandbox/grist/useractions.py | 14 +- test/fixtures/docs/Hello.grist | Bin 59392 -> 59392 bytes 18 files changed, 507 insertions(+), 311 deletions(-) diff --git a/app/common/schema.ts b/app/common/schema.ts index 41f9c086..022a5233 100644 --- a/app/common/schema.ts +++ b/app/common/schema.ts @@ -4,7 +4,7 @@ import { GristObjCode } from "app/plugin/GristData"; // tslint:disable:object-literal-key-quotes -export const SCHEMA_VERSION = 30; +export const SCHEMA_VERSION = 31; export const schema = { diff --git a/app/server/lib/DocPluginManager.ts b/app/server/lib/DocPluginManager.ts index 7be46805..05aacc3c 100644 --- a/app/server/lib/DocPluginManager.ts +++ b/app/server/lib/DocPluginManager.ts @@ -33,9 +33,10 @@ class GristDocAPIImpl implements GristDocAPI { public async getDocName() { return this._activeDoc.docName; } public async listTables(): Promise { - const table = this._activeDoc.docData!.getMetaTable('_grist_Tables'); - return table.getColValues('tableId') - .filter(id => !id.startsWith("GristSummary_")).sort(); + return this._activeDoc.docData!.getMetaTable('_grist_Tables') + .getRecords() + .filter(r => !r.summarySourceTable) + .map(r => r.tableId); } public async fetchTable(tableId: string): Promise { diff --git a/app/server/lib/initialDocSql.ts b/app/server/lib/initialDocSql.ts index b5481017..db8c3f40 100644 --- a/app/server/lib/initialDocSql.ts +++ b/app/server/lib/initialDocSql.ts @@ -6,7 +6,7 @@ export const GRIST_DOC_SQL = ` PRAGMA foreign_keys=OFF; BEGIN TRANSACTION; CREATE TABLE IF NOT EXISTS "_grist_DocInfo" (id INTEGER PRIMARY KEY, "docId" TEXT DEFAULT '', "peers" TEXT DEFAULT '', "basketId" TEXT DEFAULT '', "schemaVersion" INTEGER DEFAULT 0, "timezone" TEXT DEFAULT '', "documentSettings" TEXT DEFAULT ''); -INSERT INTO _grist_DocInfo VALUES(1,'','','',30,'',''); +INSERT INTO _grist_DocInfo VALUES(1,'','','',31,'',''); CREATE TABLE IF NOT EXISTS "_grist_Tables" (id INTEGER PRIMARY KEY, "tableId" TEXT DEFAULT '', "primaryViewId" INTEGER DEFAULT 0, "summarySourceTable" INTEGER DEFAULT 0, "onDemand" BOOLEAN DEFAULT 0, "rawViewSectionRef" INTEGER DEFAULT 0); CREATE TABLE IF NOT EXISTS "_grist_Tables_column" (id INTEGER PRIMARY KEY, "parentId" INTEGER DEFAULT 0, "parentPos" NUMERIC DEFAULT 1e999, "colId" TEXT DEFAULT '', "type" TEXT DEFAULT '', "widgetOptions" TEXT DEFAULT '', "isFormula" BOOLEAN DEFAULT 0, "formula" TEXT DEFAULT '', "label" TEXT DEFAULT '', "untieColIdFromLabel" BOOLEAN DEFAULT 0, "summarySourceCol" INTEGER DEFAULT 0, "displayCol" INTEGER DEFAULT 0, "visibleCol" INTEGER DEFAULT 0, "rules" TEXT DEFAULT NULL, "recalcWhen" INTEGER DEFAULT 0, "recalcDeps" TEXT DEFAULT NULL); CREATE TABLE IF NOT EXISTS "_grist_Imports" (id INTEGER PRIMARY KEY, "tableRef" INTEGER DEFAULT 0, "origFileName" TEXT DEFAULT '', "parseFormula" TEXT DEFAULT '', "delimiter" TEXT DEFAULT '', "doublequote" BOOLEAN DEFAULT 0, "escapechar" TEXT DEFAULT '', "quotechar" TEXT DEFAULT '', "skipinitialspace" BOOLEAN DEFAULT 0, "encoding" TEXT DEFAULT '', "hasHeaders" BOOLEAN DEFAULT 0); @@ -42,7 +42,7 @@ export const GRIST_DOC_WITH_TABLE1_SQL = ` PRAGMA foreign_keys=OFF; BEGIN TRANSACTION; CREATE TABLE IF NOT EXISTS "_grist_DocInfo" (id INTEGER PRIMARY KEY, "docId" TEXT DEFAULT '', "peers" TEXT DEFAULT '', "basketId" TEXT DEFAULT '', "schemaVersion" INTEGER DEFAULT 0, "timezone" TEXT DEFAULT '', "documentSettings" TEXT DEFAULT ''); -INSERT INTO _grist_DocInfo VALUES(1,'','','',30,'',''); +INSERT INTO _grist_DocInfo VALUES(1,'','','',31,'',''); CREATE TABLE IF NOT EXISTS "_grist_Tables" (id INTEGER PRIMARY KEY, "tableId" TEXT DEFAULT '', "primaryViewId" INTEGER DEFAULT 0, "summarySourceTable" INTEGER DEFAULT 0, "onDemand" BOOLEAN DEFAULT 0, "rawViewSectionRef" INTEGER DEFAULT 0); INSERT INTO _grist_Tables VALUES(1,'Table1',1,0,0,2); CREATE TABLE IF NOT EXISTS "_grist_Tables_column" (id INTEGER PRIMARY KEY, "parentId" INTEGER DEFAULT 0, "parentPos" NUMERIC DEFAULT 1e999, "colId" TEXT DEFAULT '', "type" TEXT DEFAULT '', "widgetOptions" TEXT DEFAULT '', "isFormula" BOOLEAN DEFAULT 0, "formula" TEXT DEFAULT '', "label" TEXT DEFAULT '', "untieColIdFromLabel" BOOLEAN DEFAULT 0, "summarySourceCol" INTEGER DEFAULT 0, "displayCol" INTEGER DEFAULT 0, "visibleCol" INTEGER DEFAULT 0, "rules" TEXT DEFAULT NULL, "recalcWhen" INTEGER DEFAULT 0, "recalcDeps" TEXT DEFAULT NULL); diff --git a/sandbox/grist/engine.py b/sandbox/grist/engine.py index 01ab9963..92a92fbf 100644 --- a/sandbox/grist/engine.py +++ b/sandbox/grist/engine.py @@ -436,7 +436,7 @@ class Engine(object): m = match_counter.MatchCounter(sample) # Iterates through each valid column in the document, counting matches. for c in search_cols: - if (not gencode._is_special_table(c.tableId) and + if (not (gencode._is_special_table(c.tableId) or c.parentId.summarySourceTable) and column.is_visible_column(c.colId) and not c.type.startswith('Ref')): table = self.tables[c.tableId] @@ -1265,7 +1265,7 @@ class Engine(object): return sum( table._num_rows() for table_id, table in six.iteritems(self.tables) - if useractions.is_user_table(table_id) + if useractions.is_user_table(table_id) and not table._summary_source_table ) def apply_user_actions(self, user_actions, user=None): diff --git a/sandbox/grist/gencode.py b/sandbox/grist/gencode.py index 9bd661c5..e922afd0 100644 --- a/sandbox/grist/gencode.py +++ b/sandbox/grist/gencode.py @@ -128,7 +128,7 @@ class GenCode(object): If filter_for_user is True, includes only user-visible columns. """ table_id = table_info.tableId - source_table_id = summary.decode_summary_table_name(table_id) + source_table_id = summary.decode_summary_table_name(table_info) # Sort columns by "isFormula" to output all data columns before all formula columns. columns = sorted(six.itervalues(table_info.columns), key=lambda c: c.isFormula) @@ -156,7 +156,7 @@ class GenCode(object): # Collect summary tables to group them by source table. summary_tables = {} for table_info in six.itervalues(schema): - source_table_id = summary.decode_summary_table_name(table_info.tableId) + source_table_id = summary.decode_summary_table_name(table_info) if source_table_id: summary_tables.setdefault(source_table_id, []).append(table_info) @@ -167,7 +167,10 @@ class GenCode(object): for table_info in six.itervalues(schema): fullparts.append("\n\n") fullparts.append(self._make_table_model(table_info, summary_tables.get(table_info.tableId))) - if not _is_special_table(table_info.tableId): + if not ( + _is_special_table(table_info.tableId) or + summary.decode_summary_table_name(table_info) + ): userparts.append("\n\n") userparts.append(self._make_table_model(table_info, summary_tables.get(table_info.tableId), filter_for_user=True)) @@ -193,7 +196,7 @@ class GenCode(object): def _is_special_table(table_id): - return table_id.startswith("_grist_") or bool(summary.decode_summary_table_name(table_id)) + return table_id.startswith("_grist_") def exec_module_text(module_text): diff --git a/sandbox/grist/migrations.py b/sandbox/grist/migrations.py index f693ee05..4c8001dd 100644 --- a/sandbox/grist/migrations.py +++ b/sandbox/grist/migrations.py @@ -1,5 +1,6 @@ import json import re +from collections import defaultdict import six from six.moves import xrange @@ -378,7 +379,8 @@ def migration7(tdset): groupby_colrefs = [int(x) for x in m.group(2).strip("_").split("_")] # Prepare a new-style name for the summary table. Be sure not to conflict with existing tables # or with each other (i.e. don't rename multiple tables to the same name). - new_name = summary.encode_summary_table_name(source_table_name) + groupby_col_ids = [columns_map_by_ref[c].colId for c in groupby_colrefs] + new_name = summary.encode_summary_table_name(source_table_name, groupby_col_ids) new_name = identifiers.pick_table_ident(new_name, avoid=table_name_set) table_name_set.add(new_name) log.warn("Upgrading summary table %s for %s(%s) to %s" % ( @@ -1014,3 +1016,60 @@ def migration30(tdset): new_view_section_id += 1 return tdset.apply_doc_actions(doc_actions) + + +@migration(schema_version=31) +def migration31(tdset): + columns = list(actions.transpose_bulk_action(tdset.all_tables['_grist_Tables_column'])) + tables = list(actions.transpose_bulk_action(tdset.all_tables['_grist_Tables'])) + + tables_by_ref = {t.id: t for t in tables} + columns_by_table_ref = defaultdict(list) + for col in columns: + columns_by_table_ref[col.parentId].append(col) + + table_name_set = {t.tableId for t in tables} + + table_renames = [] # List of (table, new_name) pairs + + for t in six.itervalues(tables_by_ref): + if not t.summarySourceTable: + continue + source_table = tables_by_ref[t.summarySourceTable] + # Prepare a new-style name for the summary table. Be sure not to conflict with existing tables + # or with each other (i.e. don't rename multiple tables to the same name). + groupby_col_ids = [c.colId for c in columns_by_table_ref[t.id] if c.summarySourceCol] + new_name = summary.encode_summary_table_name(source_table.tableId, groupby_col_ids) + if new_name == t.tableId: + continue + new_name = identifiers.pick_table_ident(new_name, avoid=table_name_set) + table_name_set.add(new_name) + log.warn("Upgrading summary table %s for %s(%s) to %s" % ( + t.tableId, source_table.tableId, groupby_col_ids, new_name)) + + # Schedule a rename of the summary table. + table_renames.append((t, new_name)) + + doc_actions = [ + actions.RenameTable(t.tableId, new) + for (t, new) in table_renames + ] + if table_renames: + doc_actions.append( + actions.BulkUpdateRecord( + '_grist_Tables', [t.id for t, new in table_renames], + {'tableId': [new for t, new in table_renames]} + ) + ) + + # Update formulas in all columns containing old-style names like 'GristSummary_' + for col in columns: + if 'GristSummary_' not in col.formula: + continue + formula = col.formula + for table, new_name in table_renames: + # Use regex to only match whole words + formula = re.sub(r'\b%s\b' % table.tableId, new_name, formula) + doc_actions.append(actions.UpdateRecord('_grist_Tables_column', col.id, {'formula': formula})) + + return tdset.apply_doc_actions(doc_actions) diff --git a/sandbox/grist/schema.py b/sandbox/grist/schema.py index a2dfd0c5..3e79e93e 100644 --- a/sandbox/grist/schema.py +++ b/sandbox/grist/schema.py @@ -15,7 +15,7 @@ import six import actions -SCHEMA_VERSION = 30 +SCHEMA_VERSION = 31 def make_column(col_id, col_type, formula='', isFormula=False): return { diff --git a/sandbox/grist/summary.py b/sandbox/grist/summary.py index 6fea7928..4d039c2c 100644 --- a/sandbox/grist/summary.py +++ b/sandbox/grist/summary.py @@ -1,6 +1,5 @@ from collections import namedtuple import json -import re import six @@ -79,38 +78,34 @@ def _copy_widget_options(options): return options return json.dumps({k: v for k, v in options.items() if k != "rulesOptions"}) -# To generate code, we need to know for each summary table, what its source table is. It would be -# easy if we had access to metadata records, but (at least for now) we generate all code based on -# schema only. So we encode the source table name inside of the summary table name. -# -# The encoding includes the length of the source table name, to avoid the possibility of ambiguity -# between the second summary table for "Foo", and the first summary table for "Foo2". -# -# Note that it means we need to rename summary tables when the source table is renamed. -def encode_summary_table_name(source_table_name): +def encode_summary_table_name(source_table_id, groupby_col_ids): """ - Create a summary table name that reliably encodes the source table name. It can be decoded even - if a suffix is added to the returned name. + Create a summary table name based on the source table ID and the groupby column IDs. """ - return "GristSummary_%d_%s" % (len(source_table_name), source_table_name) + result = source_table_id + '_summary' + if groupby_col_ids: + result += '_' + '_'.join(sorted(groupby_col_ids)) + return result -_summary_re = re.compile(r'GristSummary_(\d+)_') - -def decode_summary_table_name(summary_table_name): +def decode_summary_table_name(summary_table_info): """ - Extract the name of the source table from the summary table name. + Extract the name of the source table from the summary table schema info. """ - m = _summary_re.match(summary_table_name) - if m: - start = m.end(0) - length = int(m.group(1)) - source_name = summary_table_name[start : start + length] - if len(source_name) == length: - return source_name + # To generate code, we need to know for each summary table, what its source table is. It would be + # easy if we had access to metadata records, but (at least for now) we generate all code based on + # schema only. So we use the type of special 'group' column in the summary table. + group_col = summary_table_info.columns.get('group') + if ( + group_col + and 'getSummarySourceGroup' in group_col.formula + and group_col.type.startswith('RefList:') + ): + return group_col.type[8:] return None + def _group_colinfo(source_table): """Returns ColInfo() for the 'group' column that must be present in every summary table.""" return _make_col_info(colId='group', type='RefList:%s' % source_table.tableId, @@ -202,8 +197,9 @@ class SummaryActions(object): summary_table = next((t for t in source_table.summaryTables if t.summaryKey == key), None) created = False if not summary_table: + groupby_col_ids = [c.colId for c in groupby_colinfo] result = self.useractions.doAddTable( - encode_summary_table_name(source_table.tableId), + encode_summary_table_name(source_table.tableId, groupby_col_ids), [_get_colinfo_dict(ci, with_id=True) for ci in groupby_colinfo + formula_colinfo], summarySourceTableRef=source_table.id, raw_section=True) diff --git a/sandbox/grist/test_column_actions.py b/sandbox/grist/test_column_actions.py index 4323c933..3416a095 100644 --- a/sandbox/grist/test_column_actions.py +++ b/sandbox/grist/test_column_actions.py @@ -208,7 +208,7 @@ class TestColumnActions(test_engine.EngineTestCase): Column(16, "B", "Text", False, "", 0), Column(17, "C", "Any", True, "", 0), ]), - Table(3, "GristSummary_7_Address", 0, 1, columns=[ + Table(3, "Address_summary_state", 0, 1, columns=[ Column(18, "state", "Text", False, "", summarySourceCol=12), Column(19, "group", "RefList:Address", True, summarySourceCol=0, formula="table.getSummarySourceGroup(rec)"), @@ -249,7 +249,7 @@ class TestColumnActions(test_engine.EngineTestCase): [ 2, "b", "e", None, 2.0], [ 3, "c", "f", None, 3.0], ]) - self.assertTableData("GristSummary_7_Address", cols="subset", data=[ + self.assertTableData("Address_summary_state", cols="subset", data=[ [ "id", "state", "count", "amount" ], [ 1, "NY", 7, 1.+2+6+7+8+10+11 ], [ 2, "WA", 1, 3. ], @@ -297,7 +297,7 @@ class TestColumnActions(test_engine.EngineTestCase): Column(15, "A", "Text", False, "", 0), Column(17, "C", "Any", True, "", 0), ]), - Table(3, "GristSummary_7_Address", 0, 1, columns=[ + Table(3, "Address_summary_state", 0, 1, columns=[ Column(18, "state", "Text", False, "", summarySourceCol=12), Column(19, "group", "RefList:Address", True, summarySourceCol=0, formula="table.getSummarySourceGroup(rec)"), @@ -356,7 +356,7 @@ class TestColumnActions(test_engine.EngineTestCase): Column(17, "C", "Any", True, "", 0), ]), # Note that the summary table here switches to a new one, without the deleted group-by. - Table(4, "GristSummary_7_Address2", 0, 1, columns=[ + Table(4, "Address_summary", 0, 1, columns=[ Column(23, "count", "Int", True, summarySourceCol=0, formula="len($group)"), Column(24, "amount", "Numeric", True, summarySourceCol=0, formula="SUM($group.amount)"), Column(22, "group", "RefList:Address", True, summarySourceCol=0, @@ -406,7 +406,7 @@ class TestColumnActions(test_engine.EngineTestCase): [ 2, "b", None, 2.0], [ 3, "c", None, 3.0], ]) - self.assertTableData("GristSummary_7_Address2", cols="subset", data=[ + self.assertTableData("Address_summary", cols="subset", data=[ [ "id", "count", "amount" ], [ 1, 7+1+1+2, 1.+2+6+7+8+10+11+3+4+5+9 ], ]) diff --git a/sandbox/grist/test_derived.py b/sandbox/grist/test_derived.py index 3ee33090..8c4c0ed3 100644 --- a/sandbox/grist/test_derived.py +++ b/sandbox/grist/test_derived.py @@ -60,7 +60,7 @@ class TestDerived(test_engine.EngineTestCase): self.apply_user_action(["CreateViewSection", 2, 0, 'record', [10], None]) # Check the results. - self.assertPartialData("GristSummary_6_Orders", ["id", "year", "count", "amount", "group" ], [ + self.assertPartialData("Orders_summary_year", ["id", "year", "count", "amount", "group" ], [ [1, 2012, 1, 15, [1]], [2, 2013, 2, 30, [2,3]], [3, 2014, 3, 86, [4,5,6]], @@ -75,9 +75,9 @@ class TestDerived(test_engine.EngineTestCase): self.assertPartialOutActions(out_actions, { "stored": [ actions.BulkUpdateRecord("Orders", [1,2], {'amount': [14, 14]}), - actions.BulkUpdateRecord("GristSummary_6_Orders", [1,2], {'amount': [14, 29]}) + actions.BulkUpdateRecord("Orders_summary_year", [1,2], {'amount': [14, 29]}) ], - "calls": {"GristSummary_6_Orders": {"amount": 2}} + "calls": {"Orders_summary_year": {"amount": 2}} }) # Changing a record from one product to another should cause the two affected lines to change. @@ -85,16 +85,16 @@ class TestDerived(test_engine.EngineTestCase): self.assertPartialOutActions(out_actions, { "stored": [ actions.UpdateRecord("Orders", 10, {"year": 2012}), - actions.BulkUpdateRecord("GristSummary_6_Orders", [1,4], {"amount": [31.0, 89.0]}), - actions.BulkUpdateRecord("GristSummary_6_Orders", [1,4], {"count": [2,3]}), - actions.BulkUpdateRecord("GristSummary_6_Orders", [1,4], {"group": [[1,10], [7,8,9]]}), + actions.BulkUpdateRecord("Orders_summary_year", [1,4], {"amount": [31.0, 89.0]}), + actions.BulkUpdateRecord("Orders_summary_year", [1,4], {"count": [2,3]}), + actions.BulkUpdateRecord("Orders_summary_year", [1,4], {"group": [[1,10], [7,8,9]]}), ], - "calls": {"GristSummary_6_Orders": {"group": 2, "amount": 2, "count": 2}, - "Orders": {"#lookup##summary#GristSummary_6_Orders": 1, - "#summary#GristSummary_6_Orders": 1}} + "calls": {"Orders_summary_year": {"group": 2, "amount": 2, "count": 2}, + "Orders": {"#lookup##summary#Orders_summary_year": 1, + "#summary#Orders_summary_year": 1}} }) - self.assertPartialData("GristSummary_6_Orders", ["id", "year", "count", "amount", "group" ], [ + self.assertPartialData("Orders_summary_year", ["id", "year", "count", "amount", "group" ], [ [1, 2012, 2, 31.0, [1,10]], [2, 2013, 2, 29.0, [2,3]], [3, 2014, 3, 86.0, [4,5,6]], @@ -106,20 +106,20 @@ class TestDerived(test_engine.EngineTestCase): self.assertPartialOutActions(out_actions, { "stored": [ actions.UpdateRecord("Orders", 10, {"year": 1999}), - actions.AddRecord("GristSummary_6_Orders", 5, {'year': 1999}), - actions.BulkUpdateRecord("GristSummary_6_Orders", [1,5], {"amount": [14.0, 17.0]}), - actions.BulkUpdateRecord("GristSummary_6_Orders", [1,5], {"count": [1,1]}), - actions.BulkUpdateRecord("GristSummary_6_Orders", [1,5], {"group": [[1], [10]]}), + actions.AddRecord("Orders_summary_year", 5, {'year': 1999}), + actions.BulkUpdateRecord("Orders_summary_year", [1,5], {"amount": [14.0, 17.0]}), + actions.BulkUpdateRecord("Orders_summary_year", [1,5], {"count": [1,1]}), + actions.BulkUpdateRecord("Orders_summary_year", [1,5], {"group": [[1], [10]]}), ], "calls": { - "GristSummary_6_Orders": { + "Orders_summary_year": { '#lookup#year': 1, "group": 2, "amount": 2, "count": 2, "#lookup#": 1 }, - "Orders": {"#lookup##summary#GristSummary_6_Orders": 1, - "#summary#GristSummary_6_Orders": 1}} + "Orders": {"#lookup##summary#Orders_summary_year": 1, + "#summary#Orders_summary_year": 1}} }) - self.assertPartialData("GristSummary_6_Orders", ["id", "year", "count", "amount", "group" ], [ + self.assertPartialData("Orders_summary_year", ["id", "year", "count", "amount", "group" ], [ [1, 2012, 1, 14.0, [1]], [2, 2013, 2, 29.0, [2,3]], [3, 2014, 3, 86.0, [4,5,6]], @@ -135,7 +135,7 @@ class TestDerived(test_engine.EngineTestCase): self.load_sample(self.sample) self.apply_user_action(["CreateViewSection", 2, 0, 'record', [10, 12], None]) - self.assertPartialData("GristSummary_6_Orders", [ + self.assertPartialData("Orders_summary_product_year", [ "id", "year", "product", "count", "amount", "group" ], [ [1, 2012, "A", 1, 15.0, [1]], @@ -156,23 +156,23 @@ class TestDerived(test_engine.EngineTestCase): self.assertPartialOutActions(out_actions, { "stored": [ actions.BulkUpdateRecord("Orders", [2, 6, 7], {"product": ["B", "B", "C"]}), - actions.AddRecord("GristSummary_6_Orders", 7, {'year': 2013, 'product': 'B'}), - actions.AddRecord("GristSummary_6_Orders", 8, {'year': 2015, 'product': 'C'}), - actions.RemoveRecord("GristSummary_6_Orders", 4), - actions.BulkUpdateRecord("GristSummary_6_Orders", [2,3,5,7,8], { + actions.AddRecord("Orders_summary_product_year", 7, {'year': 2013, 'product': 'B'}), + actions.AddRecord("Orders_summary_product_year", 8, {'year': 2015, 'product': 'C'}), + actions.RemoveRecord("Orders_summary_product_year", 4), + actions.BulkUpdateRecord("Orders_summary_product_year", [2,3,5,7,8], { "amount": [15.0, 86.0, 17.0, 15.0, 17.0] }), - actions.BulkUpdateRecord("GristSummary_6_Orders", [2,3,5,7,8], { + actions.BulkUpdateRecord("Orders_summary_product_year", [2,3,5,7,8], { "count": [1, 3, 1, 1, 1] }), - actions.BulkUpdateRecord("GristSummary_6_Orders", [2,3,5,7,8], { + actions.BulkUpdateRecord("Orders_summary_product_year", [2,3,5,7,8], { "group": [[3], [4,5,6], [10], [2], [7]] }), ], }) # Verify the results. - self.assertPartialData("GristSummary_6_Orders", [ + self.assertPartialData("Orders_summary_product_year", [ "id", "year", "product", "count", "amount", "group" ], [ [1, 2012, "A", 1, 15.0, [1]], @@ -194,10 +194,10 @@ class TestDerived(test_engine.EngineTestCase): # Create a summary on the Customers table. Adding orders involves a lookup for each customer. self.apply_user_action(["CreateViewSection", 1, 0, 'record', [3], None]) - self.add_column("GristSummary_9_Customers", "totalAmount", + self.add_column("Customers_summary_state", "totalAmount", formula="sum(sum(Orders.lookupRecords(customer=c).amount) for c in $group)") - self.assertPartialData("GristSummary_9_Customers", ["id", "state", "count", "totalAmount"], [ + self.assertPartialData("Customers_summary_state", ["id", "state", "count", "totalAmount"], [ [1, "NY", 2, 103.0 ], [2, "CT", 2, 134.0 ], [3, "NJ", 1, 0.0 ], @@ -219,14 +219,14 @@ class TestDerived(test_engine.EngineTestCase): self.assertPartialOutActions(out_actions, { "stored": [ actions.UpdateRecord("Orders", 9, {"amount": 37}), - actions.UpdateRecord("GristSummary_9_Customers", 2, {"totalAmount": 135.0}), + actions.UpdateRecord("Customers_summary_state", 2, {"totalAmount": 135.0}), ] }) # In either case, changing a customer's state should trigger recomputation too. # We are changing a NY customer with $51 in orders to MA. self.update_record('Customers', 2, state="MA") - self.assertPartialData("GristSummary_9_Customers", ["id", "state", "count", "totalAmount"], [ + self.assertPartialData("Customers_summary_state", ["id", "state", "count", "totalAmount"], [ [1, "NY", 1, 52.0 ], [2, "CT", 2, 135.0 ], [3, "NJ", 1, 0.0 ], @@ -246,7 +246,7 @@ class TestDerived(test_engine.EngineTestCase): # actions.AddRecord("Summary4", 4, {"state": "NJ"}), # actions.UpdateRecord("Summary4", 4, {"manualSort": 4.0})] # }) - self.assertPartialData("GristSummary_9_Customers", ["id", "state", "count", "totalAmount"], [ + self.assertPartialData("Customers_summary_state", ["id", "state", "count", "totalAmount"], [ [1, "NY", 1, 35.0 ], [2, "CT", 2, 135.0 ], [3, "NJ", 1, 17.0 ], @@ -264,7 +264,7 @@ class TestDerived(test_engine.EngineTestCase): # Create a summary table summarizing count and total of orders by year. self.apply_user_action(["CreateViewSection", 2, 0, 'record', [10], None]) - self.assertPartialData("GristSummary_6_Orders", ["id", "year", "count", "amount", "group" ], [ + self.assertPartialData("Orders_summary_year", ["id", "year", "count", "amount", "group" ], [ [1, 2012, 1, 15.0, [1]], [2, 2013, 2, 30.0, [2,3]], [3, 2014, 3, 86.0, [4,5,6]], @@ -273,7 +273,7 @@ class TestDerived(test_engine.EngineTestCase): # Update a record so that a new line appears in the summary table. out_actions_update = self.update_record("Orders", 1, year=2007) - self.assertPartialData("GristSummary_6_Orders", ["id", "year", "count", "amount", "group" ], [ + self.assertPartialData("Orders_summary_year", ["id", "year", "count", "amount", "group" ], [ [2, 2013, 2, 30.0, [2,3]], [3, 2014, 3, 86.0, [4,5,6]], [4, 2015, 4, 106.0, [7,8,9,10]], @@ -282,7 +282,7 @@ class TestDerived(test_engine.EngineTestCase): # Undo and ensure that the new line is gone from the summary table. out_actions_undo = self.apply_undo_actions(out_actions_update.undo) - self.assertPartialData("GristSummary_6_Orders", ["id", "year", "count", "amount", "group" ], [ + self.assertPartialData("Orders_summary_year", ["id", "year", "count", "amount", "group" ], [ [1, 2012, 1, 15.0, [1]], [2, 2013, 2, 30.0, [2,3]], [3, 2014, 3, 86.0, [4,5,6]], @@ -290,18 +290,18 @@ class TestDerived(test_engine.EngineTestCase): ]) self.assertPartialOutActions(out_actions_undo, { "stored": [ - actions.AddRecord("GristSummary_6_Orders", 1, { + actions.AddRecord("Orders_summary_year", 1, { "amount": 15.0, "count": 1, "group": [1], "year": 2012 }), - actions.RemoveRecord("GristSummary_6_Orders", 5), + actions.RemoveRecord("Orders_summary_year", 5), actions.UpdateRecord("Orders", 1, {"year": 2012}), ], "calls": { - "GristSummary_6_Orders": { + "Orders_summary_year": { "#lookup#": 1, "#lookup#year": 1, "group": 1, "amount": 1, "count": 1 }, "Orders": { - "#lookup##summary#GristSummary_6_Orders": 1, "#summary#GristSummary_6_Orders": 1, + "#lookup##summary#Orders_summary_year": 1, "#summary#Orders_summary_year": 1, }, }, }) diff --git a/sandbox/grist/test_summary.py b/sandbox/grist/test_summary.py index 06122e57..2a0a58ed 100644 --- a/sandbox/grist/test_summary.py +++ b/sandbox/grist/test_summary.py @@ -66,25 +66,10 @@ class TestSummary(test_engine.EngineTestCase): #---------------------------------------------------------------------- def test_encode_summary_table_name(self): - self.assertEqual(summary.encode_summary_table_name("Foo"), "GristSummary_3_Foo") - self.assertEqual(summary.encode_summary_table_name("Foo2"), "GristSummary_4_Foo2") - self.assertEqual(summary.decode_summary_table_name("GristSummary_3_Foo"), "Foo") - self.assertEqual(summary.decode_summary_table_name("GristSummary_4_Foo2"), "Foo2") - self.assertEqual(summary.decode_summary_table_name("GristSummary_3_Foo2"), "Foo") - self.assertEqual(summary.decode_summary_table_name("GristSummary_4_Foo2_2"), "Foo2") - # Test that underscore in the name is OK. - self.assertEqual(summary.decode_summary_table_name("GristSummary_5_Foo_234"), "Foo_2") - self.assertEqual(summary.decode_summary_table_name("GristSummary_4_Foo_234"), "Foo_") - self.assertEqual(summary.decode_summary_table_name("GristSummary_6__Foo_234"), "_Foo_2") - # Test that we return None for invalid values. - self.assertEqual(summary.decode_summary_table_name("Foo2"), None) - self.assertEqual(summary.decode_summary_table_name("GristSummary_3Foo"), None) - self.assertEqual(summary.decode_summary_table_name("GristSummary_4_Foo"), None) - self.assertEqual(summary.decode_summary_table_name("GristSummary_3X_Foo"), None) - self.assertEqual(summary.decode_summary_table_name("_5_Foo_234"), None) - self.assertEqual(summary.decode_summary_table_name("_GristSummary_3_Foo"), None) - self.assertEqual(summary.decode_summary_table_name("gristsummary_3_Foo"), None) - self.assertEqual(summary.decode_summary_table_name("GristSummary3_Foo"), None) + self.assertEqual(summary.encode_summary_table_name("Foo", []), "Foo_summary") + self.assertEqual(summary.encode_summary_table_name("Foo", ["A"]), "Foo_summary_A") + self.assertEqual(summary.encode_summary_table_name("Foo", ["A", "B"]), "Foo_summary_A_B") + self.assertEqual(summary.encode_summary_table_name("Foo", ["B", "A"]), "Foo_summary_A_B") #---------------------------------------------------------------------- @@ -116,7 +101,7 @@ class TestSummary(test_engine.EngineTestCase): # Verify that a new table gets created, and a new view, with a section for that table, # and some auto-generated summary fields. - summary_table1 = Table(2, "GristSummary_7_Address", primaryViewId=0, summarySourceTable=1, + summary_table1 = Table(2, "Address_summary", primaryViewId=0, summarySourceTable=1, columns=[ Column(14, "group", "RefList:Address", isFormula=True, summarySourceCol=0, formula="table.getSummarySourceGroup(rec)"), @@ -135,7 +120,7 @@ class TestSummary(test_engine.EngineTestCase): self.assertViews([basic_view, summary_view1]) # Verify the summarized data. - self.assertTableData('GristSummary_7_Address', cols="subset", data=[ + self.assertTableData('Address_summary', cols="subset", data=[ [ "id", "count", "amount"], [ 1, 11, 66.0 ], ]) @@ -145,7 +130,7 @@ class TestSummary(test_engine.EngineTestCase): # Verify that a new table gets created again, a new view, and a section for that table. # Note that we also check that summarySourceTable and summarySourceCol fields are correct. - summary_table2 = Table(3, "GristSummary_7_Address2", primaryViewId=0, summarySourceTable=1, + summary_table2 = Table(3, "Address_summary_state", primaryViewId=0, summarySourceTable=1, columns=[ Column(17, "state", "Text", isFormula=False, formula="", summarySourceCol=12), Column(18, "group", "RefList:Address", isFormula=True, summarySourceCol=0, @@ -173,7 +158,7 @@ class TestSummary(test_engine.EngineTestCase): ]) # Verify the summarized data. - self.assertTableData('GristSummary_7_Address2', cols="subset", data=[ + self.assertTableData('Address_summary_state', cols="subset", data=[ [ "id", "state", "count", "amount" ], [ 1, "NY", 7, 1.+2+6+7+8+10+11 ], [ 2, "WA", 1, 3. ], @@ -185,7 +170,7 @@ class TestSummary(test_engine.EngineTestCase): self.apply_user_action(["CreateViewSection", 1, 0, "record", [11,12], None]) # Verify the new table and views. - summary_table3 = Table(4, "GristSummary_7_Address3", primaryViewId=0, summarySourceTable=1, + summary_table3 = Table(4, "Address_summary_city_state", primaryViewId=0, summarySourceTable=1, columns=[ Column(21, "city", "Text", isFormula=False, formula="", summarySourceCol=11), Column(22, "state", "Text", isFormula=False, formula="", summarySourceCol=12), @@ -208,7 +193,7 @@ class TestSummary(test_engine.EngineTestCase): self.assertViews([basic_view, summary_view1, summary_view2, summary_view3]) # Verify the summarized data. - self.assertTableData('GristSummary_7_Address3', cols="subset", data=[ + self.assertTableData('Address_summary_city_state', cols="subset", data=[ [ "id", "city", "state", "count", "amount" ], [ 1, "New York", "NY" , 3, 1.+6+11 ], [ 2, "Albany", "NY" , 1, 2. ], @@ -269,7 +254,7 @@ class Address: self.apply_user_action(["CreateViewSection", 1, 0, "record", [11,12], None]) # Verify the new table and views. - summary_table = Table(2, "GristSummary_7_Address", primaryViewId=0, summarySourceTable=1, + summary_table = Table(2, "Address_summary_city_state", primaryViewId=0, summarySourceTable=1, columns=[ Column(14, "city", "Text", isFormula=False, formula="", summarySourceCol=11), Column(15, "state", "Text", isFormula=False, formula="", summarySourceCol=12), @@ -316,7 +301,7 @@ class Address: self.assertViews([summary_view, summary_view2, summary_view3]) # Verify the summarized data. - self.assertTableData('GristSummary_7_Address', cols="subset", data=[ + self.assertTableData('Address_summary_city_state', cols="subset", data=[ [ "id", "city", "state", "count", "amount" ], [ 1, "New York", "NY" , 3, 1.+6+11 ], [ 2, "Albany", "NY" , 1, 2. ], @@ -342,12 +327,12 @@ class Address: self.assertTables([ self.starting_table, - Table(2, "GristSummary_7_Address", 0, 1, columns=[ + Table(2, "Address_summary", 0, 1, columns=[ Column(14, "group", "RefList:Address", True, "table.getSummarySourceGroup(rec)", 0), Column(15, "count", "Int", True, "len($group)", 0), Column(16, "amount", "Numeric", True, "SUM($group.amount)", 0), ]), - Table(3, "GristSummary_7_Address2", 0, 1, columns=[ + Table(3, "Address_summary_state", 0, 1, columns=[ Column(17, "state", "Text", False, "", 12), Column(18, "group", "RefList:Address", True, "table.getSummarySourceGroup(rec)", 0), Column(19, "count", "Int", True, "len($group)", 0), @@ -370,8 +355,8 @@ class Address: self.assertTableData("_grist_Tables", cols="subset", data=[ ['id', 'tableId', 'summarySourceTable'], [ 1, 'Address', 0], - [ 2, 'GristSummary_7_Address', 1], - [ 3, 'GristSummary_7_Address2', 1], + [ 2, 'Address_summary', 1], + [ 3, 'Address_summary_state', 1], [ 4, 'Address2', 0], ]) @@ -382,12 +367,12 @@ class Address: # Make sure this creates new section rather than reuses similar ones for the wrong table. self.assertTables([ self.starting_table, - Table(2, "GristSummary_7_Address", 0, 1, columns=[ + Table(2, "Address_summary", 0, 1, columns=[ Column(14, "group", "RefList:Address", True, "table.getSummarySourceGroup(rec)", 0), Column(15, "count", "Int", True, "len($group)", 0), Column(16, "amount", "Numeric", True, "SUM($group.amount)", 0), ]), - Table(3, "GristSummary_7_Address2", 0, 1, columns=[ + Table(3, "Address_summary_state", 0, 1, columns=[ Column(17, "state", "Text", False, "", 12), Column(18, "group", "RefList:Address", True, "table.getSummarySourceGroup(rec)", 0), Column(19, "count", "Int", True, "len($group)", 0), @@ -399,12 +384,12 @@ class Address: Column(23, "state", "Text", False, "", 0), Column(24, "amount", "Numeric", False, "", 0), ]), - Table(5, "GristSummary_8_Address2", 0, 4, columns=[ + Table(5, "Address2_summary", 0, 4, columns=[ Column(25, "group", "RefList:Address2", True, "table.getSummarySourceGroup(rec)", 0), Column(26, "count", "Int", True, "len($group)", 0), Column(27, "amount", "Numeric", True, "SUM($group.amount)", 0), ]), - Table(6, "GristSummary_8_Address2_2", 0, 4, columns=[ + Table(6, "Address2_summary_state", 0, 4, columns=[ Column(28, "state", "Text", False, "", 23), Column(29, "group", "RefList:Address2", True, "table.getSummarySourceGroup(rec)", 0), Column(30, "count", "Int", True, "len($group)", 0), @@ -424,7 +409,7 @@ class Address: self.apply_user_action(["CreateViewSection", 1, 0, "record", [11,12], None]) # Verify that the summary table respects all updates to the source table. - self._do_test_updates("Address", "GristSummary_7_Address") + self._do_test_updates("Address", "Address_summary_city_state") def _do_test_updates(self, source_tbl_name, summary_tbl_name): # This is the main part of test_summary_updates(). It's moved to its own method so that @@ -541,7 +526,7 @@ class Address: # Check what tables we have now. self.assertPartialData("_grist_Tables", ["id", "tableId", "summarySourceTable"], [ [1, "Address", 0], - [2, "GristSummary_7_Address", 1], + [2, "Address_summary_city_state", 1], ]) # Rename the table: this is what we are really testing in this test case. @@ -549,11 +534,11 @@ class Address: self.assertPartialData("_grist_Tables", ["id", "tableId", "summarySourceTable"], [ [1, "Location", 0], - [2, "GristSummary_8_Location", 1], + [2, "Location_summary_city_state", 1], ]) # Verify that the bigger summary table respects all updates to the renamed source table. - self._do_test_updates("Location", "GristSummary_8_Location") + self._do_test_updates("Location", "Location_summary_city_state") #---------------------------------------------------------------------- @@ -565,11 +550,11 @@ class Address: self.apply_user_action(["CreateViewSection", 1, 0, "record", [], None]) self.assertPartialData("_grist_Tables", ["id", "tableId", "summarySourceTable"], [ [1, "Address", 0], - [2, "GristSummary_7_Address", 1], - [3, "GristSummary_7_Address2", 1], + [2, "Address_summary_city_state", 1], + [3, "Address_summary", 1], ]) # Verify the data in the simple totals-only summary table. - self.assertTableData('GristSummary_7_Address2', cols="subset", data=[ + self.assertTableData('Address_summary', cols="subset", data=[ [ "id", "count", "amount"], [ 1, 11, 66.0 ], ]) @@ -578,21 +563,21 @@ class Address: self.apply_user_action(["RenameTable", "Address", "Addresses"]) self.assertPartialData("_grist_Tables", ["id", "tableId", "summarySourceTable"], [ [1, "Addresses", 0], - [2, "GristSummary_9_Addresses", 1], - [3, "GristSummary_9_Addresses2", 1], + [2, "Addresses_summary_city_state", 1], + [3, "Addresses_summary", 1], ]) - self.assertTableData('GristSummary_9_Addresses2', cols="subset", data=[ + self.assertTableData('Addresses_summary', cols="subset", data=[ [ "id", "count", "amount"], [ 1, 11, 66.0 ], ]) # Remove one of the tables so that we can use _do_test_updates to verify updates still work. - self.apply_user_action(["RemoveTable", "GristSummary_9_Addresses2"]) + self.apply_user_action(["RemoveTable", "Addresses_summary"]) self.assertPartialData("_grist_Tables", ["id", "tableId", "summarySourceTable"], [ [1, "Addresses", 0], - [2, "GristSummary_9_Addresses", 1], + [2, "Addresses_summary_city_state", 1], ]) - self._do_test_updates("Addresses", "GristSummary_9_Addresses") + self._do_test_updates("Addresses", "Addresses_summary_city_state") #---------------------------------------------------------------------- @@ -610,14 +595,14 @@ class Address: # These are the tables and columns we automatically get. self.assertTables([ self.starting_table, - Table(2, "GristSummary_7_Address", 0, 1, columns=[ + Table(2, "Address_summary_city_state", 0, 1, columns=[ Column(14, "city", "Text", False, "", 11), Column(15, "state", "Text", False, "", 12), Column(16, "group", "RefList:Address", True, "table.getSummarySourceGroup(rec)", 0), Column(17, "count", "Int", True, "len($group)", 0), Column(18, "amount", "Numeric", True, "SUM($group.amount)", 0), ]), - Table(3, "GristSummary_7_Address2", 0, 1, columns=[ + Table(3, "Address_summary", 0, 1, columns=[ Column(19, "group", "RefList:Address", True, "table.getSummarySourceGroup(rec)", 0), Column(20, "count", "Int", True, "len($group)", 0), Column(21, "amount", "Numeric", True, "SUM($group.amount)", 0), @@ -626,7 +611,7 @@ class Address: # Now change a formula using one of the summary tables. It should trigger an equivalent # change in the other. - self.apply_user_action(["ModifyColumn", "GristSummary_7_Address", "amount", + self.apply_user_action(["ModifyColumn", "Address_summary_city_state", "amount", {"formula": "10*sum($group.amount)"}]) self.assertTableData('_grist_Tables_column', rows="subset", cols="subset", data=[ ['id', 'colId', 'type', 'formula', 'widgetOptions', 'label'], @@ -635,7 +620,7 @@ class Address: ]) # Change a formula and a few other fields in the other table, and verify a change to both. - self.apply_user_action(["ModifyColumn", "GristSummary_7_Address2", "amount", + self.apply_user_action(["ModifyColumn", "Address_summary", "amount", {"formula": "100*sum($group.amount)", "type": "Text", "widgetOptions": "hello", @@ -649,7 +634,7 @@ class Address: ]) # Check the values in the summary tables: they should reflect the new formula. - self.assertTableData('GristSummary_7_Address', cols="subset", data=[ + self.assertTableData('Address_summary_city_state', cols="subset", data=[ [ "id", "city", "state", "count", "amount" ], [ 1, "New York", "NY" , 3, str(100*(1+6+11))], [ 2, "Albany", "NY" , 1, "200" ], @@ -661,7 +646,7 @@ class Address: [ 8, "Boston", "MA" , 1, "900" ], [ 9, "Yonkers", "NY" , 1, "1000" ], ]) - self.assertTableData('GristSummary_7_Address2', cols="subset", data=[ + self.assertTableData('Address_summary', cols="subset", data=[ [ "id", "count", "amount"], [ 1, 11, "6600"], ]) @@ -670,19 +655,19 @@ class Address: self.apply_user_action(["CreateViewSection", 1, 0, "record", [12], None]) self.assertTables([ self.starting_table, - Table(2, "GristSummary_7_Address", 0, 1, columns=[ + Table(2, "Address_summary_city_state", 0, 1, columns=[ Column(14, "city", "Text", False, "", 11), Column(15, "state", "Text", False, "", 12), Column(16, "group", "RefList:Address", True, "table.getSummarySourceGroup(rec)", 0), Column(17, "count", "Int", True, "len($group)", 0), Column(18, "amount", "Text", True, "100*sum($group.amount)", 0), ]), - Table(3, "GristSummary_7_Address2", 0, 1, columns=[ + Table(3, "Address_summary", 0, 1, columns=[ Column(19, "group", "RefList:Address", True, "table.getSummarySourceGroup(rec)", 0), Column(20, "count", "Int", True, "len($group)", 0), Column(21, "amount", "Text", True, "100*sum($group.amount)", 0), ]), - Table(4, "GristSummary_7_Address3", 0, 1, columns=[ + Table(4, "Address_summary_state", 0, 1, columns=[ Column(22, "state", "Text", False, "", 12), Column(23, "group", "RefList:Address", True, "table.getSummarySourceGroup(rec)", 0), Column(24, "count", "Int", True, "len($group)", 0), @@ -697,7 +682,7 @@ class Address: ]) # Verify the summarized data. - self.assertTableData('GristSummary_7_Address3', cols="subset", data=[ + self.assertTableData('Address_summary_state', cols="subset", data=[ [ "id", "state", "count", "amount" ], [ 1, "NY", 7, str(int(100*(1.+2+6+7+8+10+11))) ], [ 2, "WA", 1, "300" ], @@ -722,7 +707,7 @@ class Address: Column(3, "B", "Numeric", False, "", 0), Column(4, "C", "Any", True, "", 0), ]), - Table(2, "GristSummary_6_Table1", summarySourceTable=1, primaryViewId=0, columns=[ + Table(2, "Table1_summary_A", summarySourceTable=1, primaryViewId=0, columns=[ Column(5, "A", "Numeric", False, "", 2), Column(6, "group", "RefList:Table1", True, "table.getSummarySourceGroup(rec)", 0), Column(7, "count", "Int", True, "len($group)", 0), @@ -735,7 +720,7 @@ class Address: [ 2, 2.0, 20, 2.0, None ], [ 3, 3.0, 10, 3.0, None ], ]) - self.assertTableData('GristSummary_6_Table1', data=[ + self.assertTableData('Table1_summary_A', data=[ [ "id", "A", "group", "count", "B" ], [ 1, 10, [1,3], 2, 4 ], [ 2, 20, [2], 1, 2 ], @@ -753,7 +738,7 @@ class Address: Column(3, "B", "Numeric", False, "", 0), Column(4, "C", "Any", True, "", 0), ]), - Table(2, "GristSummary_6_Table1", summarySourceTable=1, primaryViewId=0, columns=[ + Table(2, "Table1_summary_A", summarySourceTable=1, primaryViewId=0, columns=[ Column(5, "A", "Text", False, "", 2), Column(6, "group", "RefList:Table1", True, "table.getSummarySourceGroup(rec)", 0), Column(7, "count", "Int", True, "len($group)", 0), @@ -766,7 +751,7 @@ class Address: [ 2, 2.0, "20", 2.0, None ], [ 3, 3.0, "10", 3.0, None ], ]) - self.assertTableData('GristSummary_6_Table1', data=[ + self.assertTableData('Table1_summary_A', data=[ [ "id", "A", "group", "count", "B" ], [ 1, "10", [1,3], 2, 4 ], [ 2, "20", [2], 1, 2 ], @@ -791,7 +776,7 @@ class Address: Column(3, "B", "Numeric", False, "", 0), Column(4, "C", "Numeric", False, "", 0), ]), - Table(2, "GristSummary_6_Table1", summarySourceTable=1, primaryViewId=0, columns=[ + Table(2, "Table1_summary_A_B", summarySourceTable=1, primaryViewId=0, columns=[ Column(5, "A", "Text", False, "", 2), Column(6, "B", "Numeric", False, "", 3), Column(7, "group", "RefList:Table1", True, "table.getSummarySourceGroup(rec)", 0), @@ -805,7 +790,7 @@ class Address: [ 2, 2.0, 'b', 1.0, 5 ], [ 3, 3.0, 'c', 2.0, 6 ], ]) - self.assertTableData('GristSummary_6_Table1', data=[ + self.assertTableData('Table1_summary_A_B', data=[ [ "id", "A", "B", "group", "count", "C" ], [ 1, 'a', 1.0, [1], 1, 4 ], [ 2, 'b', 1.0, [2], 1, 5 ], @@ -822,7 +807,7 @@ class Address: Column(3, "B", "Numeric", False, "", 0), Column(4, "C", "Numeric", False, "", 0), ]), - Table(3, "GristSummary_6_Table1_2", summarySourceTable=1, primaryViewId=0, columns=[ + Table(3, "Table1_summary_B", summarySourceTable=1, primaryViewId=0, columns=[ Column(10, "B", "Numeric", False, "", 3), Column(12, "count", "Int", True, "len($group)", 0), Column(13, "C", "Numeric", True, "SUM($group.C)", 0), @@ -835,7 +820,7 @@ class Address: [ 2, 2.0, 1.0, 5 ], [ 3, 3.0, 2.0, 6 ], ]) - self.assertTableData('GristSummary_6_Table1_2', data=[ + self.assertTableData('Table1_summary_B', data=[ [ "id", "B", "group", "count", "C" ], [ 1, 1.0, [1,2], 2, 9 ], [ 2, 2.0, [3], 1, 6 ], diff --git a/sandbox/grist/test_summary2.py b/sandbox/grist/test_summary2.py index c59767b3..79c8ac00 100644 --- a/sandbox/grist/test_summary2.py +++ b/sandbox/grist/test_summary2.py @@ -4,9 +4,9 @@ files: test_summary.py and test_summary2.py. """ import actions import logger -import objtypes import test_engine import test_summary +import testutil from test_engine import Table, Column, View, Section, Field @@ -32,15 +32,15 @@ class TestSummary2(test_engine.EngineTestCase): # Check that we cannot add a non-formula column. with self.assertRaisesRegex(ValueError, r'non-formula column'): - self.apply_user_action(["AddColumn", "GristSummary_7_Address", "average", + self.apply_user_action(["AddColumn", "Address_summary_city_state", "average", {"type": "Text", "isFormula": False}]) # Add two formula columns: one for 'state' (an existing column name, and a group-by column in # some tables), and one for 'average' (a new column name). - self.apply_user_action(["AddVisibleColumn", "GristSummary_7_Address2", "state", + self.apply_user_action(["AddVisibleColumn", "Address_summary", "state", {"formula": "':'.join(sorted(set($group.state)))"}]) - self.apply_user_action(["AddVisibleColumn", "GristSummary_7_Address", "average", + self.apply_user_action(["AddVisibleColumn", "Address_summary_city_state", "average", {"formula": "$amount / $count"}]) # Add two more summary tables: by 'city', and by 'state', and see what columns they get. @@ -52,7 +52,7 @@ class TestSummary2(test_engine.EngineTestCase): # Check the table and columns for all the summary tables. self.assertTables([ self.starting_table, - Table(2, "GristSummary_7_Address", 0, 1, columns=[ + Table(2, "Address_summary_city_state", 0, 1, columns=[ Column(14, "city", "Text", False, "", 11), Column(15, "state", "Text", False, "", 12), Column(16, "group", "RefList:Address", True, "table.getSummarySourceGroup(rec)", 0), @@ -60,13 +60,13 @@ class TestSummary2(test_engine.EngineTestCase): Column(18, "amount", "Numeric", True, "SUM($group.amount)", 0), Column(23, "average", "Any", True, "$amount / $count", 0), ]), - Table(3, "GristSummary_7_Address2", 0, 1, columns=[ + Table(3, "Address_summary", 0, 1, columns=[ Column(19, "group", "RefList:Address", True, "table.getSummarySourceGroup(rec)", 0), Column(20, "count", "Int", True, "len($group)", 0), Column(21, "amount", "Numeric", True, "SUM($group.amount)", 0), Column(22, "state", "Any", True, "':'.join(sorted(set($group.state)))", 0), ]), - Table(4, "GristSummary_7_Address3", 0, 1, columns=[ + Table(4, "Address_summary_city", 0, 1, columns=[ Column(24, "city", "Text", False, "", 11), Column(25, "group", "RefList:Address", True, "table.getSummarySourceGroup(rec)", 0), Column(26, "count", "Int", True, "len($group)", 0), @@ -74,7 +74,7 @@ class TestSummary2(test_engine.EngineTestCase): Column(28, "amount", "Numeric", True, "SUM($group.amount)", 0), ]), # Note that since 'state' is used as a group-by column here, we skip the 'state' formula. - Table(5, "GristSummary_7_Address4", 0, 1, columns=[ + Table(5, "Address_summary_state", 0, 1, columns=[ Column(29, "state", "Text", False, "", 12), Column(30, "group", "RefList:Address", True, "table.getSummarySourceGroup(rec)", 0), Column(31, "count", "Int", True, "len($group)", 0), @@ -106,7 +106,7 @@ class TestSummary2(test_engine.EngineTestCase): # Check that the data is as we expect. - self.assertTableData('GristSummary_7_Address', cols="all", data=[ + self.assertTableData('Address_summary_city_state', cols="all", data=[ [ "id", "city", "state", "group", "count", "amount", "average" ], [ 1, "New York", "NY" , [21,26,31],3, 1.+6+11 , (1.+6+11)/3 ], [ 2, "Albany", "NY" , [22], 1, 2. , 2. ], @@ -118,11 +118,11 @@ class TestSummary2(test_engine.EngineTestCase): [ 8, "Boston", "MA" , [29], 1, 9. , 9. ], [ 9, "Yonkers", "NY" , [30], 1, 10. , 10. ], ]) - self.assertTableData('GristSummary_7_Address2', cols="all", data=[ + self.assertTableData('Address_summary', cols="all", data=[ [ "id", "count", "amount", "state" , "group" ], [ 1, 11, 66.0 , "IL:MA:NY:WA" , [21,22,23,24,25,26,27,28,29,30,31]], ]) - self.assertTableData('GristSummary_7_Address3', cols="subset", data=[ + self.assertTableData('Address_summary_city', cols="subset", data=[ [ "id", "city", "count", "amount", "state" ], [ 1, "New York", 3, 1.+6+11 , "NY" ], [ 2, "Albany", 1, 2. , "NY" ], @@ -133,7 +133,7 @@ class TestSummary2(test_engine.EngineTestCase): [ 7, "Boston", 1, 9. , "MA" ], [ 8, "Yonkers", 1, 10. , "NY" ], ]) - self.assertTableData('GristSummary_7_Address4', cols="subset", data=[ + self.assertTableData('Address_summary_state', cols="subset", data=[ [ "id", "state", "count", "amount" ], [ 1, "NY", 7, 1.+2+6+7+8+10+11 ], [ 2, "WA", 1, 3. ], @@ -146,16 +146,16 @@ class TestSummary2(test_engine.EngineTestCase): self.assertPartialOutActions(out_actions, { "stored": [ actions.UpdateRecord("Address", 28, {'state': 'MA'}), - actions.RemoveRecord("GristSummary_7_Address", 7), - actions.UpdateRecord("GristSummary_7_Address", 5, {'amount': 5.0 + 8.0}), - actions.UpdateRecord("GristSummary_7_Address", 5, {'average': 6.5}), - actions.UpdateRecord("GristSummary_7_Address", 5, {'count': 2}), - actions.UpdateRecord("GristSummary_7_Address", 5, {'group': [25, 28]}), - actions.UpdateRecord("GristSummary_7_Address3", 5, {'state': "MA"}), - actions.BulkUpdateRecord("GristSummary_7_Address4", [1,4], + actions.RemoveRecord("Address_summary_city_state", 7), + actions.UpdateRecord("Address_summary_city", 5, {'state': "MA"}), + actions.UpdateRecord("Address_summary_city_state", 5, {'amount': 5.0 + 8.0}), + actions.UpdateRecord("Address_summary_city_state", 5, {'average': 6.5}), + actions.UpdateRecord("Address_summary_city_state", 5, {'count': 2}), + actions.UpdateRecord("Address_summary_city_state", 5, {'group': [25, 28]}), + actions.BulkUpdateRecord("Address_summary_state", [1,4], {'amount': [1.+2+6+7+10+11, 5.+8+9]}), - actions.BulkUpdateRecord("GristSummary_7_Address4", [1,4], {'count': [6, 3]}), - actions.BulkUpdateRecord("GristSummary_7_Address4", [1,4], + actions.BulkUpdateRecord("Address_summary_state", [1,4], {'count': [6, 3]}), + actions.BulkUpdateRecord("Address_summary_state", [1,4], {'group': [[21,22,26,27,30,31], [25,28,29]]}), ] }) @@ -175,11 +175,11 @@ class TestSummary2(test_engine.EngineTestCase): # Check that we cannot rename a summary group-by column. (Perhaps it's better to raise an # exception, but currently we translate the invalid request to a no-op.) with self.assertRaisesRegex(ValueError, r'Cannot modify .* group-by'): - self.apply_user_action(["RenameColumn", "GristSummary_7_Address", "state", "s"]) + self.apply_user_action(["RenameColumn", "Address_summary_city_state", "state", "s"]) # Verify all data. We'll repeat this after renamings to make sure there are no errors. self.assertTableData("Address", self.starting_table_data) - self.assertTableData('GristSummary_7_Address', cols="all", data=[ + self.assertTableData('Address_summary_city_state', cols="all", data=[ [ "id", "city", "state", "group", "count", "amount" ], [ 1, "New York", "NY" , [21,26,31],3, 1.+6+11 ], [ 2, "Albany", "NY" , [22], 1, 2. ], @@ -191,25 +191,25 @@ class TestSummary2(test_engine.EngineTestCase): [ 8, "Boston", "MA" , [29], 1, 9. ], [ 9, "Yonkers", "NY" , [30], 1, 10. ], ]) - self.assertTableData('GristSummary_7_Address2', cols="all", data=[ + self.assertTableData('Address_summary', cols="all", data=[ [ "id", "count", "amount", "group" ], [ 1, 11, 66.0 , [21,22,23,24,25,26,27,28,29,30,31]], ]) # This should work fine, and should affect sister tables. - self.apply_user_action(["RenameColumn", "GristSummary_7_Address", "count", "xcount"]) + self.apply_user_action(["RenameColumn", "Address_summary_city_state", "count", "xcount"]) # These are the tables and columns we automatically get. self.assertTables([ self.starting_table, - Table(2, "GristSummary_7_Address", 0, 1, columns=[ + Table(2, "Address_summary_city_state", 0, 1, columns=[ Column(14, "city", "Text", False, "", 11), Column(15, "state", "Text", False, "", 12), Column(16, "group", "RefList:Address", True, "table.getSummarySourceGroup(rec)", 0), Column(17, "xcount", "Int", True, "len($group)", 0), Column(18, "amount", "Numeric", True, "SUM($group.amount)", 0), ]), - Table(3, "GristSummary_7_Address2", 0, 1, columns=[ + Table(3, "Address_summary", 0, 1, columns=[ Column(19, "group", "RefList:Address", True, "table.getSummarySourceGroup(rec)", 0), Column(20, "xcount", "Int", True, "len($group)", 0), Column(21, "amount", "Numeric", True, "SUM($group.amount)", 0), @@ -226,14 +226,14 @@ class TestSummary2(test_engine.EngineTestCase): Column(12, "xstate", "Text", False, "", 0), Column(13, "xamount", "Numeric", False, "", 0), ]), - Table(2, "GristSummary_7_Address", 0, 1, columns=[ + Table(2, "Address_summary_city_xstate", 0, 1, columns=[ Column(14, "city", "Text", False, "", 11), Column(15, "xstate", "Text", False, "", 12), Column(16, "group", "RefList:Address", True, "table.getSummarySourceGroup(rec)", 0), Column(17, "xcount", "Int", True, "len($group)", 0), Column(18, "xamount", "Numeric", True, "SUM($group.xamount)", 0), ]), - Table(3, "GristSummary_7_Address2", 0, 1, columns=[ + Table(3, "Address_summary", 0, 1, columns=[ Column(19, "group", "RefList:Address", True, "table.getSummarySourceGroup(rec)", 0), Column(20, "xcount", "Int", True, "len($group)", 0), Column(21, "xamount", "Numeric", True, "SUM($group.xamount)", 0), @@ -247,7 +247,7 @@ class TestSummary2(test_engine.EngineTestCase): address_table_data = replace_col_names( self.starting_table_data, state='xstate', amount='xamount') self.assertTableData("Address", address_table_data) - self.assertTableData('GristSummary_7_Address', cols="all", data=[ + self.assertTableData('Address_summary_city_xstate', cols="all", data=[ [ "id", "city", "xstate", "group", "xcount", "xamount" ], [ 1, "New York", "NY" , [21,26,31],3, 1.+6+11 ], [ 2, "Albany", "NY" , [22], 1, 2. ], @@ -259,14 +259,14 @@ class TestSummary2(test_engine.EngineTestCase): [ 8, "Boston", "MA" , [29], 1, 9. ], [ 9, "Yonkers", "NY" , [30], 1, 10. ], ]) - self.assertTableData('GristSummary_7_Address2', cols="all", data=[ + self.assertTableData('Address_summary', cols="all", data=[ [ "id", "xcount", "xamount", "group" ], [ 1, 11, 66.0 , [21,22,23,24,25,26,27,28,29,30,31]], ]) # Add a conflicting name to a summary table and see how renames behave. - self.apply_user_action(["AddColumn", "GristSummary_7_Address", "foo", + self.apply_user_action(["AddColumn", "Address_summary_city_xstate", "foo", {"formula": "$xamount * 100"}]) self.apply_user_action(["RenameColumn", "Address", "xstate", "foo"]) self.apply_user_action(["RenameColumn", "Address", "xamount", "foo"]) @@ -278,7 +278,7 @@ class TestSummary2(test_engine.EngineTestCase): Column(12, "foo2", "Text", False, "", 0), Column(13, "foo3", "Numeric", False, "", 0), ]), - Table(2, "GristSummary_7_Address", 0, 1, columns=[ + Table(2, "Address_summary_city_foo2", 0, 1, columns=[ Column(14, "city", "Text", False, "", 11), Column(15, "foo2", "Text", False, "", 12), Column(16, "group", "RefList:Address", True, "table.getSummarySourceGroup(rec)", 0), @@ -286,7 +286,7 @@ class TestSummary2(test_engine.EngineTestCase): Column(18, "foo3", "Numeric", True, "SUM($group.foo3)", 0), Column(22, "foo", "Any", True, "$foo3 * 100", 0), ]), - Table(3, "GristSummary_7_Address2", 0, 1, columns=[ + Table(3, "Address_summary", 0, 1, columns=[ Column(19, "group", "RefList:Address", True, "table.getSummarySourceGroup(rec)", 0), Column(20, "xcount", "Int", True, "len($group)", 0), Column(21, "foo3", "Numeric", True, "SUM($group.foo3)", 0), @@ -297,7 +297,7 @@ class TestSummary2(test_engine.EngineTestCase): address_table_data = replace_col_names( address_table_data, xstate='foo2', xamount='foo3') self.assertTableData("Address", address_table_data) - self.assertTableData('GristSummary_7_Address', cols="all", data=[ + self.assertTableData('Address_summary_city_foo2', cols="all", data=[ [ "id", "city", "foo2" , "group", "xcount", "foo3", "foo" ], [ 1, "New York", "NY" , [21,26,31],3, 1.+6+11, 100*(1.+6+11) ], [ 2, "Albany", "NY" , [22], 1, 2. , 100*(2.) ], @@ -309,7 +309,7 @@ class TestSummary2(test_engine.EngineTestCase): [ 8, "Boston", "MA" , [29], 1, 9. , 100*(9.) ], [ 9, "Yonkers", "NY" , [30], 1, 10. , 100*(10.) ], ]) - self.assertTableData('GristSummary_7_Address2', cols="all", data=[ + self.assertTableData('Address_summary', cols="all", data=[ [ "id", "xcount", "foo3" , "group" ], [ 1, 11, 66.0 , [21,22,23,24,25,26,27,28,29,30,31]], ]) @@ -328,6 +328,145 @@ class TestSummary2(test_engine.EngineTestCase): [21, 'foo3', True, 'WidgetOptions2'], ], rows=lambda r: r.colId in ('foo2', 'foo3')) + def test_summary_col_rename_conflict(self): + sample = testutil.parse_test_sample({ + "SCHEMA": [ + [1, "Table1", [ + [11, "A", "Text", False, "", "A", ""], + [12, "B", "Text", False, "", "B", ""], + ]], + [2, "Table1_summary_A_B", [ + [13, "A", "Text", False, "", "A", ""], + ]], + ], + "DATA": {} + }) + self.load_sample(sample) + + self.apply_user_action(["CreateViewSection", 1, 0, "record", [11, 12], None]) + self.apply_user_action(["CreateViewSection", 1, 0, "record", [11], None]) + + table1 = Table( + 1, "Table1", primaryViewId=0, summarySourceTable=0, columns=[ + Column(11, "A", "Text", False, "", 0), + Column(12, "B", "Text", False, "", 0), + ], + ) + + # Normal table whose name conflicts with the automatically-generated summary table name below + fake_summary = Table( + 2, "Table1_summary_A_B", primaryViewId=0, summarySourceTable=0, columns=[ + Column(13, "A", "Text", False, "", 0), + ], + ) + + # Auto-generated name has to have a '2' to disambiguate from the normal table. + summary_by_a_and_b = Table( + 3, "Table1_summary_A_B2", primaryViewId=0, summarySourceTable=1, columns=[ + Column(14, "A", "Text", False, "", 11), + Column(15, "B", "Text", False, "", 12), + Column(16, "group", "RefList:Table1", True, "table.getSummarySourceGroup(rec)", 0), + Column(17, "count", "Int", True, "len($group)", 0), + ], + ) + + # nothing special here yet + summary_by_a = Table( + 4, "Table1_summary_A", primaryViewId=0, summarySourceTable=1, columns=[ + Column(18, "A", "Text", False, "", 11), + Column(19, "group", "RefList:Table1", True, "table.getSummarySourceGroup(rec)", 0), + Column(20, "count", "Int", True, "len($group)", 0), + ], + ) + + tables = [table1, fake_summary, summary_by_a_and_b, summary_by_a] + self.assertTables(tables) + + # Add some formulas using summary table names that are about to change + self.add_column("Table1", "summary_ref1", + type="RefList:Table1_summary_A_B2", + formula="Table1_summary_A_B2.lookupRecords(A=1)", + isFormula=True) + self.add_column("Table1", "summary_ref2", + type="Ref:Table1_summary_A", + formula="Table1_summary_A.lookupOne(A=23)", + isFormula=True) + + # I got the weirdest heisenbug ever when renaming straight from A to A_B. + # The order of renaming is not deterministic so it may end up with + # 'Table1_summary_A_B3', but asserting that name made it come out as + # 'Table1_summary_A_B2' instead. Seems that file contents play a role in + # order in sets/dictionaries? + self.apply_user_action(["RenameColumn", "Table1", "A", "A2"]) + self.apply_user_action(["RenameColumn", "Table1", "A2", "A_B"]) + + # Summary tables are automatically renamed to match the new column names. + summary_by_a_and_b = summary_by_a_and_b._replace(tableId="Table1_summary_A_B_B") + summary_by_a = summary_by_a._replace(tableId="Table1_summary_A_B2") + + table1.columns[0] = table1.columns[0]._replace(colId="A_B") + summary_by_a_and_b.columns[0] = summary_by_a_and_b.columns[0]._replace(colId="A_B") + summary_by_a.columns[0] = summary_by_a.columns[0]._replace(colId="A_B") + + table1.columns.extend([ + Column(21, "summary_ref1", "RefList:Table1_summary_A_B_B", True, + "Table1_summary_A_B_B.lookupRecords(A_B=1)", 0), + Column(22, "summary_ref2", "Ref:Table1_summary_A_B2", True, + "Table1_summary_A_B2.lookupOne(A_B=23)", 0), + ]) + + tables = [table1, fake_summary, summary_by_a_and_b, summary_by_a] + self.assertTables(tables) + + def test_source_table_rename_conflict(self): + sample = testutil.parse_test_sample({ + "SCHEMA": [ + [1, "Table1", [ + [11, "A", "Text", False, "", "A", ""], + ]], + [2, "Table2_summary", [ + [13, "A", "Text", False, "", "A", ""], + ]], + ], + "DATA": {} + }) + self.load_sample(sample) + + self.apply_user_action(["CreateViewSection", 1, 0, "record", [], None]) + + table1 = Table( + 1, "Table1", primaryViewId=0, summarySourceTable=0, columns=[ + Column(11, "A", "Text", False, "", 0), + ], + ) + + fake_summary = Table( + 2, "Table2_summary", primaryViewId=0, summarySourceTable=0, columns=[ + Column(13, "A", "Text", False, "", 0), + ], + ) + + summary = Table( + 3, "Table1_summary", primaryViewId=0, summarySourceTable=1, columns=[ + Column(14, "group", "RefList:Table1", True, "table.getSummarySourceGroup(rec)", 0), + Column(15, "count", "Int", True, "len($group)", 0), + ], + ) + + tables = [table1, fake_summary, summary] + self.assertTables(tables) + + self.apply_user_action(["RenameTable", "Table1", "Table2"]) + + table1 = table1._replace(tableId="Table2") + # Summary table is automatically renamed to match the new table name. + # Needs a '2' to disambiguate from the fake_summary table. + summary = summary._replace(tableId="Table2_summary2") + summary.columns[0] = summary.columns[0]._replace(type="RefList:Table2") + + tables = [table1, fake_summary, summary] + self.assertTables(tables) + #---------------------------------------------------------------------- def test_restrictions(self): @@ -342,7 +481,7 @@ class TestSummary2(test_engine.EngineTestCase): self.apply_user_action(["CreateViewSection", 1, 0, "record", [11,12], None]) self.apply_user_action(["CreateViewSection", 1, 0, "record", [], None]) - self.assertTableData('GristSummary_7_Address', cols="all", data=[ + self.assertTableData('Address_summary_city_state', cols="all", data=[ [ "id", "city", "state", "group", "count", "amount" ], [ 1, "New York", "NY" , [21,26,31],3, 1.+6+11 ], [ 2, "Albany", "NY" , [22], 1, 2. ], @@ -357,48 +496,49 @@ class TestSummary2(test_engine.EngineTestCase): # (1) no adding/removing/renaming non-formula columns. with self.assertRaisesRegex(ValueError, r'non-formula column'): - self.apply_user_action(["AddColumn", "GristSummary_7_Address", "foo", + self.apply_user_action(["AddColumn", "Address_summary_city_state", "foo", {"type": "Numeric", "isFormula": False}]) with self.assertRaisesRegex(ValueError, r'group-by column'): - self.apply_user_action(["RemoveColumn", "GristSummary_7_Address", "state"]) + self.apply_user_action(["RemoveColumn", "Address_summary_city_state", "state"]) with self.assertRaisesRegex(ValueError, r'Cannot modify .* group-by'): - self.apply_user_action(["RenameColumn", "GristSummary_7_Address", "state", "st"]) + self.apply_user_action(["RenameColumn", "Address_summary_city_state", "state", "st"]) # (2) no converting between formula/non-formula with self.assertRaisesRegex(ValueError, r'Cannot change .* formula and data'): - self.apply_user_action(["ModifyColumn", "GristSummary_7_Address", "amount", + self.apply_user_action(["ModifyColumn", "Address_summary_city_state", "amount", {"isFormula": False}]) with self.assertRaisesRegex(ValueError, r'Cannot change .* formula and data'): - self.apply_user_action(["ModifyColumn", "GristSummary_7_Address", "state", + self.apply_user_action(["ModifyColumn", "Address_summary_city_state", "state", {"isFormula": True}]) # (3) no editing values in non-formula columns with self.assertRaisesRegex(ValueError, r'Cannot enter data .* group-by'): - self.apply_user_action(["UpdateRecord", "GristSummary_7_Address", 6, {"state": "ny"}]) + self.apply_user_action(["UpdateRecord", "Address_summary_city_state", 6, {"state": "ny"}]) # (4) no removing rows (this is questionable b/c empty rows might be OK to remove) with self.assertRaisesRegex(ValueError, r'Cannot remove record .* summary'): - self.apply_user_action(["RemoveRecord", "GristSummary_7_Address", 6]) + self.apply_user_action(["RemoveRecord", "Address_summary_city_state", 6]) # (5) no renaming summary tables. with self.assertRaisesRegex(ValueError, r'cannot rename .* summary'): - self.apply_user_action(["RenameTable", "GristSummary_7_Address", "GristSummary_hello"]) + self.apply_user_action(["RenameTable", "Address_summary_city_state", "Address_summary_X"]) # Check that we can add an empty column, then set a formula for it. - self.apply_user_action(["AddColumn", "GristSummary_7_Address", "foo", {}]) - self.apply_user_action(["ModifyColumn", "GristSummary_7_Address", "foo", {"formula": "1+1"}]) + self.apply_user_action(["AddColumn", "Address_summary_city_state", "foo", {}]) + self.apply_user_action(["ModifyColumn", "Address_summary_city_state", "foo", + {"formula": "1+1"}]) with self.assertRaisesRegex(ValueError, "Can't save .* to formula"): - self.apply_user_action(["UpdateRecord", "GristSummary_7_Address", 1, {"foo": "hello"}]) + self.apply_user_action(["UpdateRecord", "Address_summary_city_state", 1, {"foo": "hello"}]) # But we cannot add an empty column, then add a value to it. - self.apply_user_action(["AddColumn", "GristSummary_7_Address", "foo2", {}]) + self.apply_user_action(["AddColumn", "Address_summary_city_state", "foo2", {}]) with self.assertRaisesRegex(ValueError, r'Cannot change .* between formula and data'): - self.apply_user_action(["UpdateRecord", "GristSummary_7_Address", 1, {"foo2": "hello"}]) + self.apply_user_action(["UpdateRecord", "Address_summary_city_state", 1, {"foo2": "hello"}]) - self.assertTableData('GristSummary_7_Address', cols="all", data=[ + self.assertTableData('Address_summary_city_state', cols="all", data=[ [ "id", "city", "state", "group", "count", "amount", "foo", "foo2" ], [ 1, "New York", "NY" , [21,26,31],3, 1.+6+11 , 2 , None ], [ 2, "Albany", "NY" , [22], 1, 2. , 2 , None ], @@ -426,7 +566,7 @@ class TestSummary2(test_engine.EngineTestCase): # We should have a single summary table, and a single section referring to it. self.assertTables([ self.starting_table, - Table(2, "GristSummary_7_Address", 0, 1, columns=[ + Table(2, "Address_summary_city_state", 0, 1, columns=[ Column(14, "city", "Text", False, "", 11), Column(15, "state", "Text", False, "", 12), Column(16, "group", "RefList:Address", True, "table.getSummarySourceGroup(rec)", 0), @@ -442,7 +582,7 @@ class TestSummary2(test_engine.EngineTestCase): Field(8, colRef=18), ]) ])]) - self.assertEqual(get_helper_cols('Address'), ['#summary#GristSummary_7_Address']) + self.assertEqual(get_helper_cols('Address'), ['#summary#Address_summary_city_state']) # Verify more fields of some of the new column objects. self.assertTableData('_grist_Tables_column', rows="subset", cols="subset", data=[ @@ -457,7 +597,7 @@ class TestSummary2(test_engine.EngineTestCase): self.assertTables([ self.starting_table, # Note that Table #2 is gone at this point, since it's unused. - Table(3, "GristSummary_7_Address2", 0, 1, columns=[ + Table(3, "Address_summary_state", 0, 1, columns=[ Column(19, "state", "Text", False, "", 12), Column(20, "group", "RefList:Address", True, "table.getSummarySourceGroup(rec)", 0), Column(21, "count", "Int", True, "len($group)", 0), @@ -471,14 +611,14 @@ class TestSummary2(test_engine.EngineTestCase): Field(8, colRef=22), ]) ])]) - self.assertTableData('GristSummary_7_Address2', cols="subset", data=[ + self.assertTableData('Address_summary_state', cols="subset", data=[ [ "id", "state", "count", "amount" ], [ 1, "NY", 7, 1.+2+6+7+8+10+11 ], [ 2, "WA", 1, 3. ], [ 3, "IL", 1, 4. ], [ 4, "MA", 2, 5.+9 ], ]) - self.assertEqual(get_helper_cols('Address'), ['#summary#GristSummary_7_Address2']) + self.assertEqual(get_helper_cols('Address'), ['#summary#Address_summary_state']) # Verify more fields of some of the new column objects. self.assertTableData('_grist_Tables_column', rows="subset", cols="subset", data=[ @@ -492,7 +632,7 @@ class TestSummary2(test_engine.EngineTestCase): self.assertTables([ self.starting_table, # Note that Table #3 is gone at this point, since it's unused. - Table(4, "GristSummary_7_Address", 0, 1, columns=[ + Table(4, "Address_summary_city", 0, 1, columns=[ Column(23, "city", "Text", False, "", 11), Column(24, "group", "RefList:Address", True, "table.getSummarySourceGroup(rec)", 0), Column(25, "count", "Int", True, "len($group)", 0), @@ -506,7 +646,7 @@ class TestSummary2(test_engine.EngineTestCase): Field(8, colRef=26), ]) ])]) - self.assertTableData('GristSummary_7_Address', cols="subset", data=[ + self.assertTableData('Address_summary_city', cols="subset", data=[ [ "id", "city", "count", "amount" ], [ 1, "New York", 3, 1.+6+11 ], [ 2, "Albany", 1, 2. ], @@ -517,7 +657,7 @@ class TestSummary2(test_engine.EngineTestCase): [ 7, "Boston", 1, 9. ], [ 8, "Yonkers", 1, 10. ], ]) - self.assertEqual(get_helper_cols('Address'), ['#summary#GristSummary_7_Address']) + self.assertEqual(get_helper_cols('Address'), ['#summary#Address_summary_city']) # Verify more fields of some of the new column objects. self.assertTableData('_grist_Tables_column', rows="subset", cols="subset", data=[ @@ -531,7 +671,7 @@ class TestSummary2(test_engine.EngineTestCase): self.assertTables([ self.starting_table, # Note that Table #4 is gone at this point, since it's unused. - Table(5, "GristSummary_7_Address2", 0, 1, columns=[ + Table(5, "Address_summary", 0, 1, columns=[ Column(27, "group", "RefList:Address", True, "table.getSummarySourceGroup(rec)", 0), Column(28, "count", "Int", True, "len($group)", 0), Column(29, "amount", "Numeric", True, "SUM($group.amount)", 0), @@ -543,18 +683,18 @@ class TestSummary2(test_engine.EngineTestCase): Field(8, colRef=29), ]) ])]) - self.assertTableData('GristSummary_7_Address2', cols="subset", data=[ + self.assertTableData('Address_summary', cols="subset", data=[ [ "id", "count", "amount"], [ 1, 11, 66.0 ], ]) - self.assertEqual(get_helper_cols('Address'), ['#summary#GristSummary_7_Address2']) + self.assertEqual(get_helper_cols('Address'), ['#summary#Address_summary']) # Back to full circle, but with group-by columns differently arranged. self.apply_user_action(["UpdateSummaryViewSection", 2, [12,11]]) self.assertTables([ self.starting_table, # Note that Table #5 is gone at this point, since it's unused. - Table(6, "GristSummary_7_Address", 0, 1, columns=[ + Table(6, "Address_summary_city_state", 0, 1, columns=[ Column(30, "state", "Text", False, "", 12), Column(31, "city", "Text", False, "", 11), Column(32, "group", "RefList:Address", True, "table.getSummarySourceGroup(rec)", 0), @@ -570,7 +710,7 @@ class TestSummary2(test_engine.EngineTestCase): Field(8, colRef=34), ]) ])]) - self.assertTableData('GristSummary_7_Address', cols="subset", data=[ + self.assertTableData('Address_summary_city_state', cols="subset", data=[ [ "id", "city", "state", "count", "amount" ], [ 1, "New York", "NY" , 3, 1.+6+11 ], [ 2, "Albany", "NY" , 1, 2. ], @@ -582,13 +722,13 @@ class TestSummary2(test_engine.EngineTestCase): [ 8, "Boston", "MA" , 1, 9. ], [ 9, "Yonkers", "NY" , 1, 10. ], ]) - self.assertEqual(get_helper_cols('Address'), ['#summary#GristSummary_7_Address']) + self.assertEqual(get_helper_cols('Address'), ['#summary#Address_summary_city_state']) # Now add a different view section with the same group-by columns. self.apply_user_action(["CreateViewSection", 1, 1, "record", [11,12], None]) self.assertTables([ self.starting_table, - Table(6, "GristSummary_7_Address", 0, 1, columns=[ + Table(6, "Address_summary_city_state", 0, 1, columns=[ Column(30, "state", "Text", False, "", 12), Column(31, "city", "Text", False, "", 11), Column(32, "group", "RefList:Address", True, "table.getSummarySourceGroup(rec)", 0), @@ -610,20 +750,20 @@ class TestSummary2(test_engine.EngineTestCase): Field(27, colRef=34), ]) ])]) - self.assertEqual(get_helper_cols('Address'), ['#summary#GristSummary_7_Address']) + self.assertEqual(get_helper_cols('Address'), ['#summary#Address_summary_city_state']) # Change one view section, and ensure there are now two summary tables. self.apply_user_action(["UpdateSummaryViewSection", 7, []]) self.assertTables([ self.starting_table, - Table(6, "GristSummary_7_Address", 0, 1, columns=[ + Table(6, "Address_summary_city_state", 0, 1, columns=[ Column(30, "state", "Text", False, "", 12), Column(31, "city", "Text", False, "", 11), Column(32, "group", "RefList:Address", True, "table.getSummarySourceGroup(rec)", 0), Column(33, "count", "Int", True, "len($group)", 0), Column(34, "amount", "Numeric", True, "SUM($group.amount)", 0), ]), - Table(7, "GristSummary_7_Address2", 0, 1, columns=[ + Table(7, "Address_summary", 0, 1, columns=[ Column(35, "group", "RefList:Address", True, "table.getSummarySourceGroup(rec)", 0), Column(36, "count", "Int", True, "len($group)", 0), Column(37, "amount", "Numeric", True, "SUM($group.amount)", 0), @@ -641,15 +781,15 @@ class TestSummary2(test_engine.EngineTestCase): Field(27, colRef=37), ]) ])]) - self.assertEqual(get_helper_cols('Address'), ['#summary#GristSummary_7_Address', - '#summary#GristSummary_7_Address2']) + self.assertEqual(get_helper_cols('Address'), ['#summary#Address_summary_city_state', + '#summary#Address_summary']) # Delete one view section, and see that the summary table is gone. self.apply_user_action(["RemoveViewSection", 7]) self.assertTables([ self.starting_table, # Note that Table #7 is gone at this point, since it's now unused. - Table(6, "GristSummary_7_Address", 0, 1, columns=[ + Table(6, "Address_summary_city_state", 0, 1, columns=[ Column(30, "state", "Text", False, "", 12), Column(31, "city", "Text", False, "", 11), Column(32, "group", "RefList:Address", True, "table.getSummarySourceGroup(rec)", 0), @@ -665,14 +805,14 @@ class TestSummary2(test_engine.EngineTestCase): Field(8, colRef=34), ]) ])]) - self.assertEqual(get_helper_cols('Address'), ['#summary#GristSummary_7_Address']) + self.assertEqual(get_helper_cols('Address'), ['#summary#Address_summary_city_state']) # Change the section to add and then remove the "amount" to the group-by column; check that # column "amount" was correctly restored self.apply_user_action(["UpdateSummaryViewSection", 2, [11, 12, 13]]) self.assertTables([ self.starting_table, - Table(7, "GristSummary_7_Address2", 0, 1, columns=[ + Table(7, "Address_summary_amount_city_state", 0, 1, columns=[ Column(35, "city", "Text", False, "", 11), Column(36, "state", "Text", False, "", 12), Column(37, "amount", "Numeric", False, "", 13), @@ -691,7 +831,7 @@ class TestSummary2(test_engine.EngineTestCase): self.apply_user_action(["UpdateSummaryViewSection", 2, [11,12]]) self.assertTables([ self.starting_table, - Table(8, "GristSummary_7_Address", 0, 1, columns=[ + Table(8, "Address_summary_city_state", 0, 1, columns=[ Column(40, "city", "Text", False, "", 11), Column(41, "state", "Text", False, "", 12), Column(42, "amount", "Numeric", True, "SUM($group.amount)", 0), @@ -715,7 +855,7 @@ class TestSummary2(test_engine.EngineTestCase): self.apply_user_action(["UpdateSummaryViewSection", 2, [11]]) self.assertTables([ self.starting_table, - Table(9, "GristSummary_7_Address2", 0, 1, columns=[ + Table(9, "Address_summary_city", 0, 1, columns=[ Column(45, "city", "Text", False, "", 11), Column(46, "amount", "Numeric", True, "SUM($group.amount)", 0), Column(48, "count", "Int", True, "len($group)", 0), @@ -741,13 +881,13 @@ class TestSummary2(test_engine.EngineTestCase): self.load_sample(self.sample) self.apply_user_action(["CreateViewSection", 1, 0, "record", [12], None]) - self.apply_user_action(["AddVisibleColumn", "GristSummary_7_Address", "city", + self.apply_user_action(["AddVisibleColumn", "Address_summary_state", "city", {"formula": "$state.lower()"}]) # We should have a single summary table, and a single section referring to it. self.assertTables([ self.starting_table, - Table(2, "GristSummary_7_Address", 0, 1, columns=[ + Table(2, "Address_summary_state", 0, 1, columns=[ Column(14, "state", "Text", False, "", 12), Column(15, "group", "RefList:Address", True, "table.getSummarySourceGroup(rec)", 0), Column(16, "count", "Int", True, "len($group)", 0), @@ -763,7 +903,7 @@ class TestSummary2(test_engine.EngineTestCase): Field(8, colRef=18), ]) ])]) - self.assertTableData('GristSummary_7_Address', cols="subset", data=[ + self.assertTableData('Address_summary_state', cols="subset", data=[ [ "id", "state", "count", "amount" , "city"], [ 1, "NY", 7, 1.+2+6+7+8+10+11 , "ny" ], [ 2, "WA", 1, 3. , "wa" ], @@ -775,7 +915,7 @@ class TestSummary2(test_engine.EngineTestCase): self.apply_user_action(["UpdateSummaryViewSection", 2, [11,12]]) self.assertTables([ self.starting_table, - Table(3, "GristSummary_7_Address2", 0, 1, columns=[ + Table(3, "Address_summary_city_state", 0, 1, columns=[ Column(19, "city", "Text", False, "", 11), Column(20, "state", "Text", False, "", 12), Column(21, "group", "RefList:Address", True, "table.getSummarySourceGroup(rec)", 0), @@ -812,19 +952,19 @@ class TestSummary2(test_engine.EngineTestCase): # We should have a single summary table, and a single section referring to it. self.assertTables([ self.starting_table, - Table(2, "GristSummary_7_Address", 0, 1, columns=[ + Table(2, "Address_summary_city_state", 0, 1, columns=[ Column(14, "city", "Text", False, "", 11), Column(15, "state", "Text", False, "", 12), Column(16, "group", "RefList:Address", True, "table.getSummarySourceGroup(rec)", 0), Column(17, "count", "Int", True, "len($group)", 0), Column(18, "amount", "Numeric", True, "SUM($group.amount)", 0), ]), - Table(3, "GristSummary_7_Address2", 0, 1, columns=[ + Table(3, "Address_summary", 0, 1, columns=[ Column(19, "group", "RefList:Address", True, "table.getSummarySourceGroup(rec)", 0), Column(20, "count", "Int", True, "len($group)", 0), Column(21, "amount", "Numeric", True, "SUM($group.amount)", 0), ]), - Table(4, "GristSummary_7_Address3", 0, 1, columns=[ + Table(4, "Address_summary_state", 0, 1, columns=[ Column(22, "state", "Text", False, "", 12), Column(23, "group", "RefList:Address", True, "table.getSummarySourceGroup(rec)", 0), Column(24, "count", "Int", True, "len($group)", 0), @@ -862,7 +1002,7 @@ class TestSummary2(test_engine.EngineTestCase): # Verify that unused summary tables are also gone, but the one used remains. self.assertTables([ self.starting_table, - Table(2, "GristSummary_7_Address", 0, 1, columns=[ + Table(2, "Address_summary_city_state", 0, 1, columns=[ Column(14, "city", "Text", False, "", 11), Column(15, "state", "Text", False, "", 12), Column(16, "group", "RefList:Address", True, "table.getSummarySourceGroup(rec)", 0), @@ -893,7 +1033,7 @@ class TestSummary2(test_engine.EngineTestCase): # We should have a single summary table, and a single (non-raw) section referring to it. self.assertTables([ self.starting_table, - Table(2, "GristSummary_7_Address", 0, 1, columns=[ + Table(2, "Address_summary_city_state", 0, 1, columns=[ Column(14, "city", "Text", False, "", 11), Column(15, "state", "Text", False, "", 12), Column(16, "group", "RefList:Address", True, "table.getSummarySourceGroup(rec)", 0), @@ -912,7 +1052,7 @@ class TestSummary2(test_engine.EngineTestCase): self.assertTables([ self.starting_table, # Note that Table #2 is gone at this point, since it's unused. - Table(3, "GristSummary_7_Address2", 0, 1, columns=[ + Table(3, "Address_summary_state", 0, 1, columns=[ Column(19, "state", "Text", False, "", 12), Column(20, "group", "RefList:Address", True, "table.getSummarySourceGroup(rec)", 0), Column(21, "count", "Int", True, "len($group)", 0), @@ -935,13 +1075,13 @@ class TestSummary2(test_engine.EngineTestCase): self.apply_user_action(["CreateViewSection", 1, 0, "record", [11,12], None]) self.apply_user_action(["CreateViewSection", 1, 0, "record", [], None]) # Add a formula column - self.apply_user_action(["AddVisibleColumn", "GristSummary_7_Address", "average", + self.apply_user_action(["AddVisibleColumn", "Address_summary_city_state", "average", {"formula": "$amount / $count"}]) # Check the table and columns for all the summary tables. self.assertTables([ self.starting_table, - Table(2, "GristSummary_7_Address", 0, 1, columns=[ + Table(2, "Address_summary_city_state", 0, 1, columns=[ Column(14, "city", "Text", False, "", 11), Column(15, "state", "Text", False, "", 12), Column(16, "group", "RefList:Address", True, "table.getSummarySourceGroup(rec)", 0), @@ -949,7 +1089,7 @@ class TestSummary2(test_engine.EngineTestCase): Column(18, "amount", "Numeric", True, "SUM($group.amount)", 0), Column(22, "average", "Any", True, "$amount / $count", 0), ]), - Table(3, "GristSummary_7_Address2", 0, 1, columns=[ + Table(3, "Address_summary", 0, 1, columns=[ Column(19, "group", "RefList:Address", True, "table.getSummarySourceGroup(rec)", 0), Column(20, "count", "Int", True, "len($group)", 0), Column(21, "amount", "Numeric", True, "SUM($group.amount)", 0), @@ -986,7 +1126,7 @@ class TestSummary2(test_engine.EngineTestCase): # Check the table and columns for all the summary tables. self.assertTables([ self.starting_table, - Table(3, "GristSummary_7_Address2", 0, 1, columns=[ + Table(3, "Address_summary", 0, 1, columns=[ Column(19, "group", "RefList:Address", True, "table.getSummarySourceGroup(rec)", 0), Column(20, "count", "Int", True, "len($group)", 0), Column(21, "amount", "Numeric", True, "SUM($group.amount)", 0), @@ -1039,7 +1179,7 @@ class TestSummary2(test_engine.EngineTestCase): [ 8, 8.0, "Boston", "MA" , [29], 1, 9. , 9. ], [ 9, 9.0, "Yonkers", "NY" , [30], 1, 10. , 10. ], ]) - self.assertTableData('GristSummary_7_Address2', cols="all", data=[ + self.assertTableData('Address_summary', cols="all", data=[ [ "id", "count", "amount", "group" ], [ 1, 11, 66.0 , [21,22,23,24,25,26,27,28,29,30,31]], ]) @@ -1069,7 +1209,7 @@ class TestSummary2(test_engine.EngineTestCase): Column(24, "group", "RefList:Address", True, "Address.lookupRecords(city=$city, state=$state)", 0), ]), - Table(4, "GristSummary_6_Table1", primaryViewId=0, summarySourceTable=3, columns=[ + Table(4, "Table1_summary_state", primaryViewId=0, summarySourceTable=3, columns=[ Column(25, "state", "Text", False, "", 21), Column(26, "group", "RefList:Table1", True, "table.getSummarySourceGroup(rec)", 0), Column(27, "count", "Int", True, "SUM($group.count)", 0), @@ -1090,7 +1230,7 @@ class TestSummary2(test_engine.EngineTestCase): [ 8, 8.0, "Boston", "MA" , [29], 1, 9. ], [ 9, 9.0, "Yonkers", "NY" , [30], 1, 10. ], ]) - self.assertTableData('GristSummary_6_Table1', cols="all", data=[ + self.assertTableData('Table1_summary_state', cols="all", data=[ [ "id", "state", "group", "count", "amount" ], [ 1, "NY", [1,2,6,7,9], 7, 1.+6+11+2+7+8+10 ], [ 2, "WA", [3], 1, 3. ], diff --git a/sandbox/grist/test_summary_choicelist.py b/sandbox/grist/test_summary_choicelist.py index 54cb2c41..1c0914d4 100644 --- a/sandbox/grist/test_summary_choicelist.py +++ b/sandbox/grist/test_summary_choicelist.py @@ -46,7 +46,7 @@ class TestSummaryChoiceList(EngineTestCase): self.apply_user_action(["CreateViewSection", 1, 0, "record", [11], None]) summary_table1 = Table( - 2, "GristSummary_6_Source", primaryViewId=0, summarySourceTable=1, + 2, "Source_summary_choices1", primaryViewId=0, summarySourceTable=1, columns=[ Column(13, "choices1", "Choice", isFormula=False, formula="", summarySourceCol=11), Column(14, "group", "RefList:Source", isFormula=True, summarySourceCol=0, @@ -60,7 +60,7 @@ class TestSummaryChoiceList(EngineTestCase): self.apply_user_action(["CreateViewSection", 1, 0, "record", [11, 12], None]) summary_table2 = Table( - 3, "GristSummary_6_Source2", primaryViewId=0, summarySourceTable=1, + 3, "Source_summary_choices1_choices2", primaryViewId=0, summarySourceTable=1, columns=[ Column(16, "choices1", "Choice", isFormula=False, formula="", summarySourceCol=11), Column(17, "choices2", "Choice", isFormula=False, formula="", summarySourceCol=12), @@ -75,7 +75,7 @@ class TestSummaryChoiceList(EngineTestCase): self.apply_user_action(["CreateViewSection", 1, 0, "record", [10], None]) summary_table3 = Table( - 4, "GristSummary_6_Source3", primaryViewId=0, summarySourceTable=1, + 4, "Source_summary_other", primaryViewId=0, summarySourceTable=1, columns=[ Column(20, "other", "Text", isFormula=False, formula="", summarySourceCol=10), Column(21, "group", "RefList:Source", isFormula=True, summarySourceCol=0, @@ -89,7 +89,7 @@ class TestSummaryChoiceList(EngineTestCase): self.apply_user_action(["CreateViewSection", 1, 0, "record", [10, 11], None]) summary_table4 = Table( - 5, "GristSummary_6_Source4", primaryViewId=0, summarySourceTable=1, + 5, "Source_summary_choices1_other", primaryViewId=0, summarySourceTable=1, columns=[ Column(23, "other", "Text", isFormula=False, formula="", summarySourceCol=10), Column(24, "choices1", "Choice", isFormula=False, formula="", summarySourceCol=11), @@ -105,13 +105,13 @@ class TestSummaryChoiceList(EngineTestCase): ) # Verify the summarized data. - self.assertTableData('GristSummary_6_Source', data=[ + self.assertTableData('Source_summary_choices1', data=[ ["id", "choices1", "group", "count"], [1, "a", [21], 1], [2, "b", [21], 1], ]) - self.assertTableData('GristSummary_6_Source2', data=[ + self.assertTableData('Source_summary_choices1_choices2', data=[ ["id", "choices1", "choices2", "group", "count"], [1, "a", "c", [21], 1], [2, "a", "d", [21], 1], @@ -119,12 +119,12 @@ class TestSummaryChoiceList(EngineTestCase): [4, "b", "d", [21], 1], ]) - self.assertTableData('GristSummary_6_Source3', data=[ + self.assertTableData('Source_summary_other', data=[ ["id", "other", "group", "count"], [1, "foo", [21], 1], ]) - self.assertTableData('GristSummary_6_Source4', data=[ + self.assertTableData('Source_summary_choices1_other', data=[ ["id", "other", "choices1", "group", "count"], [1, "foo", "a", [21], 1], [2, "foo", "b", [21], 1], @@ -132,28 +132,30 @@ class TestSummaryChoiceList(EngineTestCase): # Verify the optimisation works for the table without choicelists self.assertIs(self.engine.tables["Source"]._summary_simple, None) - self.assertIs(self.engine.tables["GristSummary_6_Source"]._summary_simple, False) - self.assertIs(self.engine.tables["GristSummary_6_Source2"]._summary_simple, False) + self.assertIs(self.engine.tables["Source_summary_choices1"]._summary_simple, False) + self.assertIs(self.engine.tables["Source_summary_choices1_choices2"]._summary_simple, False) # simple summary and lookup - self.assertIs(self.engine.tables["GristSummary_6_Source3"]._summary_simple, True) - self.assertIs(self.engine.tables["GristSummary_6_Source4"]._summary_simple, False) + self.assertIs(self.engine.tables["Source_summary_other"]._summary_simple, True) + self.assertIs(self.engine.tables["Source_summary_choices1_other"]._summary_simple, False) self.assertEqual( {k: type(v) for k, v in self.engine.tables["Source"]._special_cols.items()}, { - '#summary#GristSummary_6_Source': column.ReferenceListColumn, - "#lookup#_Contains(value='#summary#GristSummary_6_Source', match_empty=no_match_empty)": + '#summary#Source_summary_choices1': column.ReferenceListColumn, + "#lookup#_Contains(value='#summary#Source_summary_choices1', match_empty=no_match_empty)": lookup.ContainsLookupMapColumn, - '#summary#GristSummary_6_Source2': column.ReferenceListColumn, - "#lookup#_Contains(value='#summary#GristSummary_6_Source2', match_empty=no_match_empty)": + '#summary#Source_summary_choices1_choices2': column.ReferenceListColumn, + "#lookup#_Contains(value='#summary#Source_summary_choices1_choices2', " + "match_empty=no_match_empty)": lookup.ContainsLookupMapColumn, # simple summary and lookup - '#summary#GristSummary_6_Source3': column.ReferenceColumn, - '#lookup##summary#GristSummary_6_Source3': lookup.SimpleLookupMapColumn, + '#summary#Source_summary_other': column.ReferenceColumn, + '#lookup##summary#Source_summary_other': lookup.SimpleLookupMapColumn, - '#summary#GristSummary_6_Source4': column.ReferenceListColumn, - "#lookup#_Contains(value='#summary#GristSummary_6_Source4', match_empty=no_match_empty)": + '#summary#Source_summary_choices1_other': column.ReferenceListColumn, + "#lookup#_Contains(value='#summary#Source_summary_choices1_other', " + "match_empty=no_match_empty)": lookup.ContainsLookupMapColumn, "#lookup#": lookup.SimpleLookupMapColumn, @@ -169,12 +171,12 @@ class TestSummaryChoiceList(EngineTestCase): ]) # Verify that the summary table rows containing 'b' are removed - self.assertTableData('GristSummary_6_Source', data=[ + self.assertTableData('Source_summary_choices1', data=[ ["id", "choices1", "group", "count"], [1, "a", [21], 1], ]) - self.assertTableData('GristSummary_6_Source2', data=[ + self.assertTableData('Source_summary_choices1_choices2', data=[ ["id", "choices1", "choices2", "group", "count"], [1, "a", "c", [21], 1], [2, "a", "d", [21], 1], @@ -184,13 +186,13 @@ class TestSummaryChoiceList(EngineTestCase): self.update_record("Source", 21, choices2=["L", "c", "d", "e"]) # First summary table unaffected - self.assertTableData('GristSummary_6_Source', data=[ + self.assertTableData('Source_summary_choices1', data=[ ["id", "choices1", "group", "count"], [1, "a", [21], 1], ]) # New row added for 'e' - self.assertTableData('GristSummary_6_Source2', data=[ + self.assertTableData('Source_summary_choices1_choices2', data=[ ["id", "choices1", "choices2", "group", "count"], [1, "a", "c", [21], 1], [2, "a", "d", [21], 1], @@ -205,12 +207,12 @@ class TestSummaryChoiceList(EngineTestCase): [21, None, ["c", "d", "e"], "foo"], ]) - self.assertTableData('GristSummary_6_Source', data=[ + self.assertTableData('Source_summary_choices1', data=[ ["id", "choices1", "group", "count"], [2, "", [21], 1], ]) - self.assertTableData('GristSummary_6_Source2', data=[ + self.assertTableData('Source_summary_choices1_choices2', data=[ ["id", "choices1", "choices2", "group", "count"], [4, "", "c", [21], 1], [5, "", "d", [21], 1], @@ -221,11 +223,11 @@ class TestSummaryChoiceList(EngineTestCase): self.remove_record("Source", 21) # All summary rows are now empty and thus removed - self.assertTableData('GristSummary_6_Source', data=[ + self.assertTableData('Source_summary_choices1', data=[ ["id", "choices1", "group", "count"], ]) - self.assertTableData('GristSummary_6_Source2', data=[ + self.assertTableData('Source_summary_choices1_choices2', data=[ ["id", "choices1", "choices2", "group", "count"], ]) @@ -263,7 +265,7 @@ class TestSummaryChoiceList(EngineTestCase): ]) # Summary tables now have an even distribution of combinations - self.assertTableData('GristSummary_6_Source', data=[ + self.assertTableData('Source_summary_choices1', data=[ ["id", "choices1", "group", "count"], [1, "a", [101, 103, 104, 106, 107, 109], 6], [2, "b", [102, 103, 105, 106, 108, 109], 6], @@ -279,7 +281,7 @@ class TestSummaryChoiceList(EngineTestCase): [5, "", "", [110], 1], ] - self.assertTableData('GristSummary_6_Source2', data=summary_data) + self.assertTableData('Source_summary_choices1_choices2', data=summary_data) # Verify that "DetachSummaryViewSection" useraction works correctly. self.apply_user_action(["DetachSummaryViewSection", 4]) @@ -336,7 +338,7 @@ class TestSummaryChoiceList(EngineTestCase): self.apply_user_action(["CreateViewSection", 1, 0, "record", [11], None]) summary_table = Table( - 2, "GristSummary_6_Source", primaryViewId=0, summarySourceTable=1, + 2, "Source_summary_choices1", primaryViewId=0, summarySourceTable=1, columns=[ Column(12, "choices1", "Choice", isFormula=False, formula="", summarySourceCol=11), Column(13, "group", "RefList:Source", isFormula=True, summarySourceCol=0, @@ -353,7 +355,7 @@ class TestSummaryChoiceList(EngineTestCase): ] self.assertTables([starting_table, summary_table]) - self.assertTableData('GristSummary_6_Source', data=data) + self.assertTableData('Source_summary_choices1', data=data) # Change the column from Choice to ChoiceList self.apply_user_action(["UpdateRecord", "_grist_Tables_column", 11, {"type": "ChoiceList"}]) @@ -365,7 +367,7 @@ class TestSummaryChoiceList(EngineTestCase): starting_table.columns[1] = starting_table.columns[1]._replace(type="ChoiceList") self.assertTables([starting_table, summary_table]) - self.assertTableData('GristSummary_6_Source', data=data) + self.assertTableData('Source_summary_choices1', data=data) def test_rename_choices(self): self.load_sample(self.sample) @@ -374,7 +376,7 @@ class TestSummaryChoiceList(EngineTestCase): self.apply_user_action(["CreateViewSection", 1, 0, "record", [11, 12], None]) summary_table = Table( - 2, "GristSummary_6_Source", primaryViewId=0, summarySourceTable=1, + 2, "Source_summary_choices1_choices2", primaryViewId=0, summarySourceTable=1, columns=[ Column(13, "choices1", "Choice", isFormula=False, formula="", summarySourceCol=11), Column(14, "choices2", "Choice", isFormula=False, formula="", summarySourceCol=12), @@ -397,17 +399,17 @@ class TestSummaryChoiceList(EngineTestCase): self.assertPartialOutActions(out_actions, {'stored': [ ['UpdateRecord', 'Source', 21, {'choices1': ['L', u'aa', u'bb']}], ['BulkAddRecord', - 'GristSummary_6_Source', + 'Source_summary_choices1_choices2', [5, 6, 7, 8], {'choices1': [u'aa', u'aa', u'bb', u'bb'], 'choices2': [u'c', u'd', u'c', u'd']}], - ['BulkRemoveRecord', 'GristSummary_6_Source', [1, 2, 3, 4]], + ['BulkRemoveRecord', 'Source_summary_choices1_choices2', [1, 2, 3, 4]], ['BulkUpdateRecord', - 'GristSummary_6_Source', + 'Source_summary_choices1_choices2', [5, 6, 7, 8], {'count': [1, 1, 1, 1]}], ['BulkUpdateRecord', - 'GristSummary_6_Source', + 'Source_summary_choices1_choices2', [5, 6, 7, 8], {'group': [['L', 21], ['L', 21], @@ -423,7 +425,7 @@ class TestSummaryChoiceList(EngineTestCase): # Final summary table is very similar to before, but with two empty chunks of 4 rows # left over from each rename - self.assertTableData('GristSummary_6_Source', data=[ + self.assertTableData('Source_summary_choices1_choices2', data=[ ["id", "choices1", "choices2", "group", "count"], [9, "aa", "cc", [21], 1], [10, "aa", "dd", [21], 1], diff --git a/sandbox/grist/test_summary_undo.py b/sandbox/grist/test_summary_undo.py index ab8ef863..93499df7 100644 --- a/sandbox/grist/test_summary_undo.py +++ b/sandbox/grist/test_summary_undo.py @@ -31,7 +31,7 @@ class TestSummaryUndo(test_engine.EngineTestCase): self.load_sample(self.sample) # Create a summary section, grouped by the "State" column. self.apply_user_action(["CreateViewSection", 1, 0, "record", [1], None]) - self.assertTableData('GristSummary_6_Person', cols="subset", data=[ + self.assertTableData('Person_summary_state', cols="subset", data=[ [ "id", "state", "count"], [ 1, "NY", 2], [ 2, "IL", 2], @@ -39,7 +39,7 @@ class TestSummaryUndo(test_engine.EngineTestCase): ]) out_actions = self.update_record('Person', 4, state='ME') - self.assertTableData('GristSummary_6_Person', cols="subset", data=[ + self.assertTableData('Person_summary_state', cols="subset", data=[ [ "id", "state", "count"], [ 1, "NY", 1], [ 2, "IL", 2], @@ -47,7 +47,7 @@ class TestSummaryUndo(test_engine.EngineTestCase): ]) self.apply_undo_actions(out_actions.undo[0:1]) - self.assertTableData('GristSummary_6_Person', cols="subset", data=[ + self.assertTableData('Person_summary_state', cols="subset", data=[ [ "id", "state", "count"], [ 1, "NY", 2], [ 2, "IL", 2], diff --git a/sandbox/grist/test_table_actions.py b/sandbox/grist/test_table_actions.py index 76e83c32..f6576cf0 100644 --- a/sandbox/grist/test_table_actions.py +++ b/sandbox/grist/test_table_actions.py @@ -71,7 +71,7 @@ class TestTableActions(test_engine.EngineTestCase): Column(7, "address", "Ref:Address", False, "", 0), Column(8, "city", "Any", True, "$address.city", 0), ]), - Table(3, "GristSummary_7_Address", 0, 1, columns=[ + Table(3, "Address_summary_state", 0, 1, columns=[ Column(9, "state", "Text", False, "", summarySourceCol=3), Column(10, "group", "RefList:Address", True, summarySourceCol=0, formula="table.getSummarySourceGroup(rec)"), @@ -116,7 +116,7 @@ class TestTableActions(test_engine.EngineTestCase): # Verify the data we've loaded. self.assertTableData('Address', cols="subset", data=self.address_table_data) self.assertTableData('People', cols="subset", data=self.people_table_data) - self.assertTableData("GristSummary_7_Address", cols="subset", data=[ + self.assertTableData("Address_summary_state", cols="subset", data=[ [ "id", "state", "count", "amount" ], [ 1, "NY", 7, 1.+2+6+7+8+10+11 ], [ 2, "WA", 1, 3. ], @@ -142,7 +142,7 @@ class TestTableActions(test_engine.EngineTestCase): ["id", "tableId"], [1, "Location"], [2, "Persons"], - [3, "GristSummary_8_Location"], + [3, "Location_summary_state"], ]) # Check that reference columns to renamed tables get their type modified. @@ -161,14 +161,14 @@ class TestTableActions(test_engine.EngineTestCase): self.assertPartialOutActions(out_actions, { "stored": [ ["ModifyColumn", "Persons", "address", {"type": "Int"}], - ["ModifyColumn", "GristSummary_8_Location", "group", {"type": "Int"}], + ["ModifyColumn", "Location_summary_state", "group", {"type": "Int"}], ["RenameTable", "Location", "A2"], - ["RenameTable", "GristSummary_8_Location", "GristSummary_2_A2"], + ["RenameTable", "Location_summary_state", "A2_summary_state"], ["RenameTable", "Persons", "A3"], ["BulkUpdateRecord", "_grist_Tables", [1, 3, 2], - {"tableId": ["A2", "GristSummary_2_A2", "A3"]}], + {"tableId": ["A2", "A2_summary_state", "A3"]}], ["ModifyColumn", "A3", "address", {"type": "Ref:A2"}], - ["ModifyColumn", "GristSummary_2_A2", "group", {"type": "RefList:A2"}], + ["ModifyColumn", "A2_summary_state", "group", {"type": "RefList:A2"}], ["BulkUpdateRecord", "_grist_Tables_column", [7, 10], {"type": ["Ref:A2", "RefList:A2"]}], ] }) @@ -178,7 +178,7 @@ class TestTableActions(test_engine.EngineTestCase): ["id", "tableId"], [1, "A2"], [2, "A3"], - [3, "GristSummary_2_A2"], + [3, "A2_summary_state"], [4, "A"], ]) @@ -192,7 +192,7 @@ class TestTableActions(test_engine.EngineTestCase): # Verify the data we've loaded. self.assertTableData('A2', cols="subset", data=self.address_table_data) self.assertTableData('A3', cols="subset", data=self.people_table_data) - self.assertTableData("GristSummary_2_A2", cols="subset", data=[ + self.assertTableData("A2_summary_state", cols="subset", data=[ [ "id", "state", "count", "amount" ], [ 1, "NY", 7, 1.+2+6+7+8+10+11 ], [ 2, "WA", 1, 3. ], @@ -226,15 +226,15 @@ class TestTableActions(test_engine.EngineTestCase): self.assertPartialOutActions(out_actions, { "stored": [ ["ModifyColumn", "People", "address", {"type": "Int"}], - ["ModifyColumn", "GristSummary_7_Address", "group", {"type": "Int"}], - ["ModifyColumn", "GristSummary_6_People", "address", {"type": "Int"}], + ["ModifyColumn", "Address_summary_state", "group", {"type": "Int"}], + ["ModifyColumn", "People_summary_address", "address", {"type": "Int"}], ["RenameTable", "Address", "Location"], - ["RenameTable", "GristSummary_7_Address", "GristSummary_8_Location"], + ["RenameTable", "Address_summary_state", "Location_summary_state"], ["BulkUpdateRecord", "_grist_Tables", [1, 3], - {"tableId": ["Location", "GristSummary_8_Location"]}], + {"tableId": ["Location", "Location_summary_state"]}], ["ModifyColumn", "People", "address", {"type": "Ref:Location"}], - ["ModifyColumn", "GristSummary_8_Location", "group", {"type": "RefList:Location"}], - ["ModifyColumn", "GristSummary_6_People", "address", {"type": "Ref:Location"}], + ["ModifyColumn", "Location_summary_state", "group", {"type": "RefList:Location"}], + ["ModifyColumn", "People_summary_address", "address", {"type": "Ref:Location"}], ["BulkUpdateRecord", "_grist_Tables_column", [7, 10, 13], {"type": ["Ref:Location", "RefList:Location", "Ref:Location"]}], ] diff --git a/sandbox/grist/test_useractions.py b/sandbox/grist/test_useractions.py index f5af53e1..631c93ed 100644 --- a/sandbox/grist/test_useractions.py +++ b/sandbox/grist/test_useractions.py @@ -156,7 +156,7 @@ class TestUserActions(test_engine.EngineTestCase): # Create another section for the same view, this time summarized. self.apply_user_action(["CreateViewSection", 1, 1, "record", [21], None]) - summary_table = Table(2, "GristSummary_7_Address", 0, summarySourceTable=1, columns=[ + summary_table = Table(2, "Address_summary_city", 0, summarySourceTable=1, columns=[ Column(22, "city", "Text", isFormula=False, formula="", summarySourceCol=21), Column(23, "group", "RefList:Address", isFormula=True, formula="table.getSummarySourceGroup(rec)", summarySourceCol=0), @@ -248,7 +248,7 @@ class TestUserActions(test_engine.EngineTestCase): Column(33, "C", "Any", isFormula=True, formula="", summarySourceCol=0), ]) # A summary of it. - summary_table = Table(5, "GristSummary_6_Table3", 0, summarySourceTable=4, columns=[ + summary_table = Table(5, "Table3_summary", 0, summarySourceTable=4, columns=[ Column(34, "group", "RefList:Table3", isFormula=True, formula="table.getSummarySourceGroup(rec)", summarySourceCol=0), Column(35, "count", "Int", isFormula=True, formula="len($group)", summarySourceCol=0), @@ -287,7 +287,7 @@ class TestUserActions(test_engine.EngineTestCase): Column(3, "state", "Text", False, "", 0), Column(4, "size", "Numeric", False, "", 0), ]), - Table(2, "GristSummary_7_Schools", 0, 1, columns=[ + Table(2, "Schools_summary_state", 0, 1, columns=[ Column(5, "state", "Text", False, "", 3), Column(6, "group", "RefList:Schools", True, "table.getSummarySourceGroup(rec)", 0), Column(7, "count", "Int", True, "len($group)", 0), @@ -398,7 +398,7 @@ class TestUserActions(test_engine.EngineTestCase): self.assertTableData('_grist_Tables', cols="subset", data=[ [ 'id', 'tableId', 'primaryViewId' ], [ 1, 'Schools', 1], - [ 2, 'GristSummary_7_Schools', 0], + [ 2, 'Schools_summary_state', 0], [ 3, 'Table1', 0], ]) self.assertTableData('_grist_Views', cols="subset", data=[ @@ -416,7 +416,7 @@ class TestUserActions(test_engine.EngineTestCase): self.assertTableData('_grist_Tables', cols="subset", data=[ [ 'id', 'tableId', 'primaryViewId' ], [ 1, 'Schools', 1], - [ 2, 'GristSummary_7_Schools', 0], + [ 2, 'Schools_summary_state', 0], [ 3, 'Table1', 0], ]) self.assertTableData('_grist_Views', cols="subset", data=[ @@ -434,7 +434,7 @@ class TestUserActions(test_engine.EngineTestCase): self.assertTableData('_grist_Tables', cols="subset", data=[ ['id', 'tableId'], [1, 'Bars', 1], - [2, 'GristSummary_4_Bars', 0], + [2, 'Bars_summary_state', 0], [3, 'Table1', 0], ]) self.assertTableData('_grist_Views', cols="subset", data=[ @@ -452,7 +452,7 @@ class TestUserActions(test_engine.EngineTestCase): self.assertTableData('_grist_Tables', cols="subset", data=[ ['id', 'tableId'], [1, 'A', 1], - [2, 'GristSummary_1_A', 0], + [2, 'A_summary_state', 0], [3, 'Table1', 0], ]) self.assertTableData('_grist_Views', cols="subset", data=[ @@ -468,7 +468,7 @@ class TestUserActions(test_engine.EngineTestCase): self.assertTableData('_grist_Tables', cols="subset", data=[ ['id', 'tableId', 'primaryViewId', 'rawViewSectionRef'], [1, 'Z', 1, 2], - [2, 'GristSummary_1_Z', 0, 4], + [2, 'Z_summary_state', 0, 4], [3, 'Table1', 0, 7], ]) self.assertTableData('_grist_Views', cols="subset", data=[ @@ -485,7 +485,7 @@ class TestUserActions(test_engine.EngineTestCase): self.assertTableData('_grist_Tables', cols="subset", data=[ ['id', 'tableId', 'primaryViewId', 'rawViewSectionRef'], [1, 'Z', 1, 2], - [2, 'GristSummary_1_Z', 0, 4], + [2, 'Z_summary_state', 0, 4], [3, 'Table1', 0, 7], [4, 'Stations', 4, 10], ]) @@ -513,7 +513,7 @@ class TestUserActions(test_engine.EngineTestCase): self.assertTableData('_grist_Tables', cols="subset", data=[ ['id', 'tableId'], [1, 'Schools'], - [2, 'GristSummary_7_Schools'], + [2, 'Schools_summary_state'], [3, 'Table1'], [4, 'Stations'], ]) @@ -618,7 +618,7 @@ class TestUserActions(test_engine.EngineTestCase): self.assertTableData('_grist_Tables', cols="subset", data=[ [ 'id', 'tableId', 'primaryViewId' ], [ 1, 'Schools', 1], - [ 2, 'GristSummary_7_Schools', 0], + [ 2, 'Schools_summary_state', 0], [ 3, 'C', 0], ]) diff --git a/sandbox/grist/useractions.py b/sandbox/grist/useractions.py index a7780d6f..f4021a20 100644 --- a/sandbox/grist/useractions.py +++ b/sandbox/grist/useractions.py @@ -561,7 +561,7 @@ class UserActions(object): update_pairs.append((rec, values)) if has_diff_value(values, 'tableId', rec.tableId): # Disallow renaming of summary tables. - if rec.summarySourceTable: + if rec.summarySourceTable and self._indirection_level == DIRECT_ACTION: raise ValueError("RenameTable: cannot rename a summary table") # Find a non-conflicting name, except that we don't need to avoid the old name. @@ -572,7 +572,8 @@ class UserActions(object): if new_table_id != rec.tableId: # If there are summary tables based on this table, rename them to appropriate names. for st in rec.summaryTables: - st_table_id = summary.encode_summary_table_name(new_table_id) + groupby_col_ids = [c.colId for c in st.columns if c.summarySourceCol] + st_table_id = summary.encode_summary_table_name(new_table_id, groupby_col_ids) st_table_id = identifiers.pick_table_ident(st_table_id, avoid=avoid_tableid_set) avoid_tableid_set.add(st_table_id) update_pairs.append((st, {'tableId': st_table_id})) @@ -697,6 +698,7 @@ class UserActions(object): make_acl_updates = acl.prepare_acl_col_renames(self._docmodel, self, renames) + rename_summary_tables = set() for c, values in update_pairs: # Trigger ModifyColumn and RenameColumn as necessary schema_colinfo = select_keys(values, _modify_col_schema_props) @@ -704,6 +706,8 @@ class UserActions(object): self.doModifyColumn(c.parentId.tableId, c.colId, schema_colinfo) if has_diff_value(values, 'colId', c.colId): self._do_doc_action(actions.RenameColumn(c.parentId.tableId, c.colId, values['colId'])) + if c.summarySourceCol: + rename_summary_tables.add(c.parentId) # If we change a column's type, we should ALSO unset each affected field's displayCol. type_changed = [c for c, values in update_pairs if has_diff_value(values, 'type', c.type)] @@ -718,6 +722,12 @@ class UserActions(object): make_acl_updates() + for table in rename_summary_tables: + groupby_col_ids = [c.colId for c in table.columns if c.summarySourceCol] + new_table_id = summary.encode_summary_table_name(table.summarySourceTable.tableId, + groupby_col_ids) + with self.indirect_actions(): + self.RenameTable(table.tableId, new_table_id) @override_action('BulkUpdateRecord', '_grist_Views_section') def _updateViewSections(self, table_id, row_ids, col_values): diff --git a/test/fixtures/docs/Hello.grist b/test/fixtures/docs/Hello.grist index 67e0821ed5f385f792e783f0c40543f01b6a4ead..23ee72448ae80b47ccd95e2ffd97a21de6ee712c 100644 GIT binary patch delta 26 icmZp;z}#?wd4e<}-$WT_M!t;+wc?EOo9~G$T>t=fE(pH> delta 26 icmZp;z}#?wd4e<}??f4AM&6AHwc?C&o9~G$T>t=f3kbXb