# pylint:disable=too-many-lines """ Test of Summary tables. This has many test cases, so to keep files smaller, it's split into two files: test_summary.py and test_summary2.py. """ import logging import actions import test_engine from test_engine import Table, Column, View, Section, Field import test_summary import testutil log = logging.getLogger(__name__) class TestSummary2(test_engine.EngineTestCase): sample = test_summary.TestSummary.sample starting_table = test_summary.TestSummary.starting_table starting_table_data = test_summary.TestSummary.starting_table_data @test_engine.test_undo def test_add_summary_formula(self): # Verify that we can add a summary formula; that new sections automatically get columns # matching the source table, and not other columns. Check that group-by columns override # formula columns (if there are any by the same name). # Start as in test_change_summary_formula() test case; see there for what tables and columns # we expect to have at this point. self.load_sample(self.sample) self.apply_user_action(["CreateViewSection", 1, 0, "record", [11,12], None]) self.apply_user_action(["CreateViewSection", 1, 0, "record", [], None]) # Check that we cannot add a non-formula column. with self.assertRaisesRegex(ValueError, r'non-formula column'): 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", "Address_summary", "state", {"formula": "':'.join(sorted(set($group.state)))"}]) 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. self.apply_user_action(["CreateViewSection", 1, 0, "record", [11], None]) self.apply_user_action(["CreateViewSection", 1, 0, "record", [12], None]) # And also a summary table for an existing breakdown. self.apply_user_action(["CreateViewSection", 1, 0, "record", [11,12], None]) # Check the table and columns for all the summary tables. self.assertTables([ self.starting_table, 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), Column(23, "average", "Any", True, "$amount / $count", 0), ]), 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, "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), Column(27, "state", "Any", True, "':'.join(sorted(set($group.state)))", 0), 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, "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), Column(32, "amount", "Numeric", True, "SUM($group.amount)", 0), ]), ]) # We should now have three sections for table 2 (the one with two group-by fields). One for # the raw summary table view, and two for the non-raw views. self.assertTableData('_grist_Views_section', cols="subset", data=[ ["id", "parentId", "tableRef"], [1, 0, 2], [2, 1, 2], [9, 5, 2], ], rows=lambda r: r.tableRef.id == 2) self.assertTableData('_grist_Views_section_field', cols="subset", data=[ ["id", "parentId", "colRef"], [1, 1, 14], [2, 1, 15], [3, 1, 17], [4, 1, 18], [15, 1, 23], [17, 5, 24], [18, 5, 26], [19, 5, 27], [20, 5, 28], # new section doesn't automatically get 'average' column ], rows=lambda r: r.parentId.id in {1,5}) # Check that the data is as we expect. 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. ], [ 3, "Seattle", "WA" , [23], 1, 3. , 3. ], [ 4, "Chicago", "IL" , [24], 1, 4. , 4. ], [ 5, "Bedford", "MA" , [25], 1, 5. , 5. ], [ 6, "Buffalo", "NY" , [27], 1, 7. , 7. ], [ 7, "Bedford", "NY" , [28], 1, 8. , 8. ], [ 8, "Boston", "MA" , [29], 1, 9. , 9. ], [ 9, "Yonkers", "NY" , [30], 1, 10. , 10. ], ]) 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('Address_summary_city', cols="subset", data=[ [ "id", "city", "count", "amount", "state" ], [ 1, "New York", 3, 1.+6+11 , "NY" ], [ 2, "Albany", 1, 2. , "NY" ], [ 3, "Seattle", 1, 3. , "WA" ], [ 4, "Chicago", 1, 4. , "IL" ], [ 5, "Bedford", 2, 5.+8 , "MA:NY"], [ 6, "Buffalo", 1, 7. , "NY" ], [ 7, "Boston", 1, 9. , "MA" ], [ 8, "Yonkers", 1, 10. , "NY" ], ]) 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 ], ]) # Modify a value, and check that various tables got updated correctly. out_actions = self.update_record("Address", 28, state="MA") self.assertPartialOutActions(out_actions, { "stored": [ actions.UpdateRecord("Address", 28, {'state': 'MA'}), 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("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]]}), ] }) #---------------------------------------------------------------------- @test_engine.test_undo def test_summary_col_rename(self): # Verify that renaming a column in a source table causes appropriate renames in the summary # tables, and that renames of group-by columns in summary tables are disallowed. # Start as in test_change_summary_formula() test case; see there for what tables and columns # we expect to have at this point. self.load_sample(self.sample) self.apply_user_action(["CreateViewSection", 1, 0, "record", [11,12], None]) self.apply_user_action(["CreateViewSection", 1, 0, "record", [], None]) # 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", "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('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. ], [ 3, "Seattle", "WA" , [23], 1, 3. ], [ 4, "Chicago", "IL" , [24], 1, 4. ], [ 5, "Bedford", "MA" , [25], 1, 5. ], [ 6, "Buffalo", "NY" , [27], 1, 7. ], [ 7, "Bedford", "NY" , [28], 1, 8. ], [ 8, "Boston", "MA" , [29], 1, 9. ], [ 9, "Yonkers", "NY" , [30], 1, 10. ], ]) 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", "Address_summary_city_state", "count", "xcount"]) # These are the tables and columns we automatically get. self.assertTables([ self.starting_table, 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, "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), ]) ]) # Check that renames in the source table translate to renames in the summary table. self.apply_user_action(["RenameColumn", "Address", "state", "xstate"]) self.apply_user_action(["RenameColumn", "Address", "amount", "xamount"]) self.assertTables([ Table(1, "Address", primaryViewId=0, summarySourceTable=0, columns=[ Column(11, "city", "Text", False, "", 0), Column(12, "xstate", "Text", False, "", 0), Column(13, "xamount", "Numeric", False, "", 0), ]), 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, "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), ]) ]) def replace_col_names(data, **col_renames): return [[col_renames.get(c, c) for c in data[0]]] + data[1:] # Verify actual data to make sure we don't have formula errors. address_table_data = replace_col_names( self.starting_table_data, state='xstate', amount='xamount') self.assertTableData("Address", address_table_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. ], [ 3, "Seattle", "WA" , [23], 1, 3. ], [ 4, "Chicago", "IL" , [24], 1, 4. ], [ 5, "Bedford", "MA" , [25], 1, 5. ], [ 6, "Buffalo", "NY" , [27], 1, 7. ], [ 7, "Bedford", "NY" , [28], 1, 8. ], [ 8, "Boston", "MA" , [29], 1, 9. ], [ 9, "Yonkers", "NY" , [30], 1, 10. ], ]) 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", "Address_summary_city_xstate", "foo", {"formula": "$xamount * 100"}]) self.apply_user_action(["RenameColumn", "Address", "xstate", "foo"]) self.apply_user_action(["RenameColumn", "Address", "xamount", "foo"]) self.apply_user_action(["RenameColumn", "Address", "city", "city"]) self.assertTables([ Table(1, "Address", primaryViewId=0, summarySourceTable=0, columns=[ Column(11, "city", "Text", False, "", 0), Column(12, "foo2", "Text", False, "", 0), Column(13, "foo3", "Numeric", False, "", 0), ]), 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), Column(17, "xcount", "Int", True, "len($group)", 0), Column(18, "foo3", "Numeric", True, "SUM($group.foo3)", 0), Column(22, "foo", "Any", True, "$foo3 * 100", 0), ]), 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), ]) ]) # Verify actual data again to make sure we don't have formula errors. address_table_data = replace_col_names( address_table_data, xstate='foo2', xamount='foo3') self.assertTableData("Address", address_table_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.) ], [ 3, "Seattle", "WA" , [23], 1, 3. , 100*(3.) ], [ 4, "Chicago", "IL" , [24], 1, 4. , 100*(4.) ], [ 5, "Bedford", "MA" , [25], 1, 5. , 100*(5.) ], [ 6, "Buffalo", "NY" , [27], 1, 7. , 100*(7.) ], [ 7, "Bedford", "NY" , [28], 1, 8. , 100*(8.) ], [ 8, "Boston", "MA" , [29], 1, 9. , 100*(9.) ], [ 9, "Yonkers", "NY" , [30], 1, 10. , 100*(10.) ], ]) 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]], ]) # Check that update to widgetOptions in source table affects group-by columns and not formula # columns. (Same should be true for type, but not tested here.) self.apply_user_action(["ModifyColumn", "Address", "foo2", {"widgetOptions": "hello"}]) self.apply_user_action(["ModifyColumn", "Address", "foo3", {"widgetOptions": "world"}]) self.assertTableData('_grist_Tables_column', cols="subset", data=[ ['id', 'colId', 'isFormula', 'widgetOptions'], [12, 'foo2', False, 'hello'], [13, 'foo3', False, 'world'], [15, 'foo2', False, 'hello'], [18, 'foo3', True, 'WidgetOptions2'], [21, 'foo3', True, 'WidgetOptions2'], ], rows=lambda r: r.colId in ('foo2', 'foo3')) @test_engine.test_undo 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) @test_engine.test_undo 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) #---------------------------------------------------------------------- @test_engine.test_undo def test_restrictions(self): # Verify various restrictions on summary tables # (1) no adding/removing/renaming non-formula columns. # (2) no converting between formula/non-formula # (3) no editing values in non-formula columns # (4) no removing rows (this is questionable b/c empty rows might be OK to remove) # (5) no renaming summary tables. self.load_sample(self.sample) self.apply_user_action(["CreateViewSection", 1, 0, "record", [11,12], None]) self.apply_user_action(["CreateViewSection", 1, 0, "record", [], None]) 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. ], [ 3, "Seattle", "WA" , [23], 1, 3. ], [ 4, "Chicago", "IL" , [24], 1, 4. ], [ 5, "Bedford", "MA" , [25], 1, 5. ], [ 6, "Buffalo", "NY" , [27], 1, 7. ], [ 7, "Bedford", "NY" , [28], 1, 8. ], [ 8, "Boston", "MA" , [29], 1, 9. ], [ 9, "Yonkers", "NY" , [30], 1, 10. ], ]) # (1) no adding/removing/renaming non-formula columns. with self.assertRaisesRegex(ValueError, r'non-formula column'): 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", "Address_summary_city_state", "state"]) with self.assertRaisesRegex(ValueError, r'Cannot modify .* group-by'): 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", "Address_summary_city_state", "amount", {"isFormula": False}]) with self.assertRaisesRegex(ValueError, r'Cannot change .* formula and data'): 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", "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", "Address_summary_city_state", 6]) # (5) no renaming summary tables. with self.assertRaisesRegex(ValueError, r'cannot rename .* summary'): 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", "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", "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", "Address_summary_city_state", "foo2", {}]) with self.assertRaisesRegex(ValueError, r'Cannot change .* between formula and data'): self.apply_user_action(["UpdateRecord", "Address_summary_city_state", 1, {"foo2": "hello"}]) 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 ], [ 3, "Seattle", "WA" , [23], 1, 3. , 2 , None ], [ 4, "Chicago", "IL" , [24], 1, 4. , 2 , None ], [ 5, "Bedford", "MA" , [25], 1, 5. , 2 , None ], [ 6, "Buffalo", "NY" , [27], 1, 7. , 2 , None ], [ 7, "Bedford", "NY" , [28], 1, 8. , 2 , None ], [ 8, "Boston", "MA" , [29], 1, 9. , 2 , None ], [ 9, "Yonkers", "NY" , [30], 1, 10. , 2 , None ], ]) #---------------------------------------------------------------------- @test_engine.test_undo def test_update_summary_section(self): # Verify that we can change the group-by for a view section, and that unused tables get # removed. def get_helper_cols(table_id): return [c for c in self.engine.tables[table_id].all_columns if c.startswith('#summary#')] self.load_sample(self.sample) self.apply_user_action(["CreateViewSection", 1, 0, "record", [11,12], None]) # We should have a single summary table, and a single section referring to it. self.assertTables([ self.starting_table, 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), ]), ]) self.assertViews([View(1, sections=[ Section(2, parentKey="record", tableRef=2, fields=[ Field(5, colRef=14), Field(6, colRef=15), Field(7, colRef=17), Field(8, colRef=18), ]) ])]) 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=[ ['id', 'colId', 'type', 'formula', 'widgetOptions', 'label'], [14, 'city', 'Text', '', '', 'City'], [15, 'state', 'Text', '', 'WidgetOptions1', 'State'], [18, 'amount', 'Numeric', 'SUM($group.amount)', 'WidgetOptions2', 'Amount'], ]) # Now change the group-by to just one of the columns ('state') self.apply_user_action(["UpdateSummaryViewSection", 2, [12]]) self.assertTables([ self.starting_table, # Note that Table #2 is gone at this point, since it's unused. 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), Column(22, "amount", "Numeric", True, "SUM($group.amount)", 0), ]), ]) self.assertViews([View(1, sections=[ Section(2, parentKey="record", tableRef=3, fields=[ Field(6, colRef=19), Field(7, colRef=21), Field(8, colRef=22), ]) ])]) 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#Address_summary_state']) # Verify more fields of some of the new column objects. self.assertTableData('_grist_Tables_column', rows="subset", cols="subset", data=[ ['id', 'colId', 'type', 'formula', 'widgetOptions', 'label'], [19, 'state', 'Text', '', 'WidgetOptions1', 'State'], [22, 'amount', 'Numeric', 'SUM($group.amount)', 'WidgetOptions2', 'Amount'], ]) # Change group-by to a different single column ('city') self.apply_user_action(["UpdateSummaryViewSection", 2, [11]]) self.assertTables([ self.starting_table, # Note that Table #3 is gone at this point, since it's unused. 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), Column(26, "amount", "Numeric", True, "SUM($group.amount)", 0), ]), ]) self.assertViews([View(1, sections=[ Section(2, parentKey="record", tableRef=4, fields=[ Field(15, colRef=23), Field(7, colRef=25), Field(8, colRef=26), ]) ])]) self.assertTableData('Address_summary_city', cols="subset", data=[ [ "id", "city", "count", "amount" ], [ 1, "New York", 3, 1.+6+11 ], [ 2, "Albany", 1, 2. ], [ 3, "Seattle", 1, 3. ], [ 4, "Chicago", 1, 4. ], [ 5, "Bedford", 2, 5.+8 ], [ 6, "Buffalo", 1, 7. ], [ 7, "Boston", 1, 9. ], [ 8, "Yonkers", 1, 10. ], ]) 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=[ ['id', 'colId', 'type', 'formula', 'widgetOptions', 'label'], [23, 'city', 'Text', '', '', 'City'], [26, 'amount', 'Numeric', 'SUM($group.amount)', 'WidgetOptions2', 'Amount'], ]) # Change group-by to no columns (totals) self.apply_user_action(["UpdateSummaryViewSection", 2, []]) self.assertTables([ self.starting_table, # Note that Table #4 is gone at this point, since it's unused. 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), ]), ]) self.assertViews([View(1, sections=[ Section(2, parentKey="record", tableRef=5, fields=[ Field(7, colRef=28), Field(8, colRef=29), ]) ])]) self.assertTableData('Address_summary', cols="subset", data=[ [ "id", "count", "amount"], [ 1, 11, 66.0 ], ]) 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, "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), ]), ]) self.assertViews([View(1, sections=[ Section(2, parentKey="record", tableRef=6, fields=[ Field(22, colRef=30), Field(23, colRef=31), Field(7, colRef=33), Field(8, colRef=34), ]) ])]) 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. ], [ 3, "Seattle", "WA" , 1, 3. ], [ 4, "Chicago", "IL" , 1, 4. ], [ 5, "Bedford", "MA" , 1, 5. ], [ 6, "Buffalo", "NY" , 1, 7. ], [ 7, "Bedford", "NY" , 1, 8. ], [ 8, "Boston", "MA" , 1, 9. ], [ 9, "Yonkers", "NY" , 1, 10. ], ]) 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, "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), ]), ]) self.assertViews([View(1, sections=[ Section(2, parentKey="record", tableRef=6, fields=[ Field(22, colRef=30), Field(23, colRef=31), Field(7, colRef=33), Field(8, colRef=34), ]), Section(7, parentKey="record", tableRef=6, fields=[ Field(24, colRef=31), Field(25, colRef=30), Field(26, colRef=33), Field(27, colRef=34), ]) ])]) 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, "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, "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), ]), ]) self.assertViews([View(1, sections=[ Section(2, parentKey="record", tableRef=6, fields=[ Field(22, colRef=30), Field(23, colRef=31), Field(7, colRef=33), Field(8, colRef=34), ]), Section(7, parentKey="record", tableRef=7, fields=[ Field(26, colRef=36), Field(27, colRef=37), ]) ])]) 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, "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), ]) ]) self.assertViews([View(1, sections=[ Section(2, parentKey="record", tableRef=6, fields=[ Field(22, colRef=30), Field(23, colRef=31), Field(7, colRef=33), Field(8, colRef=34), ]) ])]) 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, "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), Column(38, "group", "RefList:Address", True, "table.getSummarySourceGroup(rec)", 0), Column(39, "count", "Int", True, "len($group)", 0), ]), ]) self.assertViews([View(1, sections=[ Section(2, parentKey="record", tableRef=7, fields=[ Field(23, colRef=35), Field(22, colRef=36), Field(28, colRef=37), Field(7, colRef=39), ]) ])]) self.apply_user_action(["UpdateSummaryViewSection", 2, [11,12]]) self.assertTables([ self.starting_table, 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), Column(43, "group", "RefList:Address", True, "table.getSummarySourceGroup(rec)", 0), Column(44, "count", "Int", True, "len($group)", 0), ]), ]) self.assertViews([View(1, sections=[ Section(2, parentKey="record", tableRef=8, fields=[ Field(23, colRef=40), Field(22, colRef=41), Field(28, colRef=42), Field(7, colRef=44), ]) ])]) # Hide a formula and update group by columns; check that the formula columns had not been # deleted self.apply_user_action(['RemoveRecord', '_grist_Views_section_field', 7]) self.apply_user_action(["UpdateSummaryViewSection", 2, [11]]) self.assertTables([ self.starting_table, 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), Column(47, "group", "RefList:Address", True, "table.getSummarySourceGroup(rec)", 0), ]), ]) self.assertViews([View(1, sections=[ Section(2, parentKey="record", tableRef=9, fields=[ Field(23, colRef=45), Field(28, colRef=46), ]) ])]) # Delete source table, and ensure its summary table is also gone. self.apply_user_action(["RemoveTable", "Address"]) self.assertTables([]) self.assertViews([]) #---------------------------------------------------------------------- @test_engine.test_undo def test_update_groupby_override(self): # Verify that if we add a group-by column that conflicts with a formula, group-by column wins. self.load_sample(self.sample) self.apply_user_action(["CreateViewSection", 1, 0, "record", [12], None]) 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, "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), Column(17, "amount", "Numeric", True, "SUM($group.amount)", 0), Column(18, "city", "Any", True, "$state.lower()", 0), ]), ]) self.assertViews([View(1, sections=[ Section(2, parentKey="record", tableRef=2, fields=[ Field(4, colRef=14), Field(5, colRef=16), Field(6, colRef=17), Field(8, colRef=18), ]) ])]) 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" ], [ 3, "IL", 1, 4. , "il" ], [ 4, "MA", 2, 5.+9 , "ma" ], ]) # Change the section to add "city" as a group-by column; check that the formula is gone. self.apply_user_action(["UpdateSummaryViewSection", 2, [11,12]]) self.assertTables([ self.starting_table, 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), Column(22, "count", "Int", True, "len($group)", 0), Column(23, "amount", "Numeric", True, "SUM($group.amount)", 0), ]), ]) self.assertViews([View(1, sections=[ Section(2, parentKey="record", tableRef=3, fields=[ # We requested 'city' to come before 'state', check that this is the case. Field(13, colRef=19), Field(4, colRef=20), Field(5, colRef=22), Field(6, colRef=23), ]) ])]) # TODO We should have more tests on UpdateSummaryViewSection that rearranges columns in # interesting ways (e.g. add new column to middle of existing group-by columns; put group-by # columns in the middle of other fields then UpdateSummary to rearrange them). #---------------------------------------------------------------------- @test_engine.test_undo def test_cleanup_on_view_remove(self): # Verify that if we remove a view, that unused summary tables get cleaned up. # Create one view with one summary section, and another view with three sections. self.load_sample(self.sample) self.apply_user_action(["CreateViewSection", 1, 0, "record", [11,12], None]) # Creates View #1 self.apply_user_action(["CreateViewSection", 1, 0, "record", [], None]) # Creates View #2 self.apply_user_action(["CreateViewSection", 1, 2, "record", [11,12], None]) # Refers to View #2 self.apply_user_action(["CreateViewSection", 1, 2, "record", [12], None]) # Refers to View #2 # We should have a single summary table, and a single section referring to it. self.assertTables([ self.starting_table, 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, "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, "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), Column(25, "amount", "Numeric", True, "SUM($group.amount)", 0), ]), ]) self.assertViews([View(1, sections=[ Section(2, parentKey="record", tableRef=2, fields=[ Field(5, colRef=14), Field(6, colRef=15), Field(7, colRef=17), Field(8, colRef=18), ]) ]), View(2, sections=[ Section(4, parentKey="record", tableRef=3, fields=[ Field(11, colRef=20), Field(12, colRef=21), ]), Section(5, parentKey="record", tableRef=2, fields=[ Field(13, colRef=14), Field(14, colRef=15), Field(15, colRef=17), Field(16, colRef=18), ]), Section(7, parentKey="record", tableRef=4, fields=[ Field(20, colRef=22), Field(21, colRef=24), Field(22, colRef=25), ]) ])]) # Now change the group-by to just one of the columns ('state') self.apply_user_action(["RemoveView", 2]) # Verify that unused summary tables are also gone, but the one used remains. self.assertTables([ self.starting_table, 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), ]), ]) self.assertViews([View(1, sections=[ Section(2, parentKey="record", tableRef=2, fields=[ Field(5, colRef=14), Field(6, colRef=15), Field(7, colRef=17), Field(8, colRef=18), ]) ])]) #---------------------------------------------------------------------- @test_engine.test_undo def test_update_sort_spec(self): # Verify that we correctly update sort spec when we update a summary view section. self.load_sample(self.sample) self.apply_user_action(["CreateViewSection", 1, 0, "record", [11,12], None]) self.apply_user_action(["UpdateRecord", "_grist_Views_section", 2, {"sortColRefs": "[15,14,-17]"}]) # We should have a single summary table, and a single (non-raw) section referring to it. self.assertTables([ self.starting_table, 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), ]), ]) self.assertTableData('_grist_Views_section', cols="subset", data=[ ["id", "tableRef", "sortColRefs"], [1, 2, ""], # This is the raw section. [2, 2, "[15,14,-17]"], ]) # Now change the group-by to just one of the columns ('state') self.apply_user_action(["UpdateSummaryViewSection", 2, [12]]) self.assertTables([ self.starting_table, # Note that Table #2 is gone at this point, since it's unused. 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), Column(22, "amount", "Numeric", True, "SUM($group.amount)", 0), ]), ]) # Verify that sortColRefs refers to new columns. self.assertTableData('_grist_Views_section', cols="subset", data=[ ["id", "tableRef", "sortColRefs"], [2, 3, "[19,-21]"], [3, 3, ""], # This is the raw section. ]) #---------------------------------------------------------------------- @test_engine.test_undo def test_detach_summary_section(self): # Verify that "DetachSummaryViewSection" useraction works correctly. self.load_sample(self.sample) # Add a couple of summary tables. 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", "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, "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), Column(22, "average", "Any", True, "$amount / $count", 0), ]), 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), ]), ]) self.assertTableData('_grist_Views_section', cols="subset", data=[ ["id", "parentId", "tableRef"], [1, 0, 2], [2, 1, 2], [3, 0, 3], [4, 2, 3], ]) self.assertTableData('_grist_Views_section_field', cols="subset", data=[ ["id", "parentId", "colRef"], [1, 1, 14], [2, 1, 15], [3, 1, 17], [4, 1, 18], [13, 1, 22], [5, 2, 14], [6, 2, 15], [7, 2, 17], [8, 2, 18], [14, 2, 22], [9, 3, 20], [10, 3, 21], [11, 4, 20], [12, 4, 21], ], sort=lambda r: (r.parentId, r.id)) # Now save one section as a separate table, i.e. "detach" it from its source. self.apply_user_action(["DetachSummaryViewSection", 2]) # Check the table and columns for all the summary tables. self.assertTables([ self.starting_table, 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, "Table1", primaryViewId=3, summarySourceTable=0, columns=[ Column(23, "manualSort", "ManualSortPos", False, "", 0), Column(24, "city", "Text", False, "", 0), Column(25, "state", "Text", False, "", 0), Column(26, "count", "Int", True, "len($group)", 0), Column(27, "amount", "Numeric", True, "SUM($group.amount)", 0), Column(28, "average", "Any", True, "$amount / $count", 0), Column(29, "group", "RefList:Address", True, "Address.lookupRecords(city=$city, state=$state)", 0), ]), ]) # We should now have two sections for table 2 (the one with two group-by fields). self.assertTableData('_grist_Views_section', cols="subset", rows=lambda r: r.parentId, data=[ ["id", "parentId", "tableRef"], [2, 1, 4], [4, 2, 3], [5, 3, 4], ]) self.assertTableData( '_grist_Views_section_field', cols="subset", rows=lambda r: r.parentId.parentId, data=[ ["id", "parentId", "colRef"], [5, 2, 24], [6, 2, 25], [7, 2, 26], [8, 2, 27], [14, 2, 28], [11, 4, 20], [12, 4, 21], [15, 5, 24], [16, 5, 25], [17, 5, 26], [18, 5, 27], [19, 5, 28], ], sort=lambda r: (r.parentId, r.id)) # Check that the data is as we expect. self.assertTableData('Table1', cols="all", data=[ [ "id", "manualSort", "city", "state", "group", "count", "amount", "average" ], [ 1, 1.0, "New York", "NY" , [21,26,31],3, 1.+6+11 , (1.+6+11)/3 ], [ 2, 2.0, "Albany", "NY" , [22], 1, 2. , 2. ], [ 3, 3.0, "Seattle", "WA" , [23], 1, 3. , 3. ], [ 4, 4.0, "Chicago", "IL" , [24], 1, 4. , 4. ], [ 5, 5.0, "Bedford", "MA" , [25], 1, 5. , 5. ], [ 6, 6.0, "Buffalo", "NY" , [27], 1, 7. , 7. ], [ 7, 7.0, "Bedford", "NY" , [28], 1, 8. , 8. ], [ 8, 8.0, "Boston", "MA" , [29], 1, 9. , 9. ], [ 9, 9.0, "Yonkers", "NY" , [30], 1, 10. , 10. ], ]) 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]], ]) #---------------------------------------------------------------------- @test_engine.test_undo def test_summary_of_detached(self): # Verify that we can make a summary table of a detached table. This is mainly to ensure that # we handle well the presence of columns like 'group' and 'count' in the source table. # Add a summary table and detach it. Then add a summary table of that table. self.load_sample(self.sample) self.apply_user_action(["CreateViewSection", 1, 0, "record", [11,12], None]) self.apply_user_action(["DetachSummaryViewSection", 2]) # Create a summary of the detached table (tableRef 3) by state (colRef 21). self.apply_user_action(["CreateViewSection", 3, 0, "record", [21], None]) # Verify the resulting metadata. self.assertTables([ self.starting_table, Table(3, "Table1", primaryViewId=2, summarySourceTable=0, columns=[ Column(19, "manualSort", "ManualSortPos", False, "", 0), Column(20, "city", "Text", False, "", 0), Column(21, "state", "Text", False, "", 0), Column(22, "count", "Int", True, "len($group)", 0), Column(23, "amount", "Numeric", True, "SUM($group.amount)", 0), Column(24, "group", "RefList:Address", True, "Address.lookupRecords(city=$city, state=$state)", 0), ]), 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), Column(28, "amount", "Numeric", True, "SUM($group.amount)", 0), ]), ]) # Check that the data is as we expect. Table1 is the same as in the previous test case. self.assertTableData('Table1', cols="all", data=[ [ "id", "manualSort", "city", "state", "group", "count", "amount" ], [ 1, 1.0, "New York", "NY" , [21,26,31],3, 1.+6+11 ], [ 2, 2.0, "Albany", "NY" , [22], 1, 2. ], [ 3, 3.0, "Seattle", "WA" , [23], 1, 3. ], [ 4, 4.0, "Chicago", "IL" , [24], 1, 4. ], [ 5, 5.0, "Bedford", "MA" , [25], 1, 5. ], [ 6, 6.0, "Buffalo", "NY" , [27], 1, 7. ], [ 7, 7.0, "Bedford", "NY" , [28], 1, 8. ], [ 8, 8.0, "Boston", "MA" , [29], 1, 9. ], [ 9, 9.0, "Yonkers", "NY" , [30], 1, 10. ], ]) 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. ], [ 3, "IL", [4], 1, 4. ], [ 4, "MA", [5,8], 2, 5.+9 ], ]) #---------------------------------------------------------------------- @test_engine.test_undo def test_update_summary_with_suffixed_colId(self): # Verifies that summary update correctly when one of the formula # columns has a suffixed colId self.load_sample(self.sample) # Let's create two summary table, one with totals (no grouped by columns) and one grouped by # "city". self.apply_user_action(["CreateViewSection", 1, 0, "record", [], None]) self.apply_user_action(["CreateViewSection", 1, 0, "record", [11], None]) # Change type of Amount columns to "Any" only for table Address_summary_city. User actions keep # types consistent across same-named columns for all summary tables with the same source table, # but here we want to test for the case where types are inconsistent. Hence we bypass user # actions and directly use doc actions. self.engine.apply_doc_action(actions.UpdateRecord("_grist_Tables_column", 20, {'type': 'Any'})) self.engine.apply_doc_action(actions.ModifyColumn("Address_summary_city", "amount", {'type': 'Any'})) self.engine.assert_schema_consistent() self.assertTables([ self.starting_table, Table(2, "Address_summary", primaryViewId=0, summarySourceTable=1, columns=[ Column(14, "group", "RefList:Address", isFormula=True, summarySourceCol=0, formula="table.getSummarySourceGroup(rec)"), Column(15, "count", "Int", isFormula=True, summarySourceCol=0, formula="len($group)"), # This column has type Numeric Column(16, "amount", "Numeric", isFormula=True, summarySourceCol=0, formula="SUM($group.amount)"), ]), Table(3, "Address_summary_city", primaryViewId=0, summarySourceTable=1, columns=[ Column(17, "city", "Text", isFormula=False, summarySourceCol=11, formula=""), Column(18, "group", "RefList:Address", isFormula=True, summarySourceCol=0, formula="table.getSummarySourceGroup(rec)"), Column(19, "count", "Int", isFormula=True, summarySourceCol=0, formula="len($group)"), # This column has type Any Column(20, "amount", "Any", isFormula=True, summarySourceCol=0, formula="SUM($group.amount)"), ]), ]) # Now let's add "city" to the summary table with no grouped by column self.apply_user_action(["UpdateSummaryViewSection", 2, [11]]) # Check that summary table now has one column Amount of type Any. self.assertTables([ self.starting_table, Table(3, "Address_summary_city", primaryViewId=0, summarySourceTable=1, columns=[ Column(17, "city", "Text", isFormula=False, summarySourceCol=11, formula=""), Column(18, "group", "RefList:Address", isFormula=True, summarySourceCol=0, formula="table.getSummarySourceGroup(rec)"), Column(19, "count", "Int", isFormula=True, summarySourceCol=0, formula="len($group)"), Column(20, "amount", "Any", isFormula=True, summarySourceCol=0, formula="SUM($group.amount)"), ]) ])