gristlabs_grist-core/sandbox/grist/test_column_actions.py
Cyprien P 0829ae67ef (core) Fixing hidden col missing after summary update
Summary:
Updating one summary groupby columns is handle by the
`update_summary_section`. That routine is in charge of creating a new
summary table and transfer all possible columns from old table to the
new one, so that the new table appear to change only the minimum to
users.

The problem that this diff address is that, the logic to decide what columns we need to keep, only applies to the visible
fields and ignore the hidden columns, causing all hidden columns to be removed. This diff, simply
changes that routine to address all columns.

the mutliple fixes on the test_summary2.py result from a change of
ordering of columns: the culprit is the `group` columns, which is by
default a hidden columns and that had to be explicitely added back to
the new summary table. It is now transferred automatically, like other
columns, which does cause a little change of ordering on the db. This
should not result in any display order changes for the users.

Recent related diff: https://phab.getgrist.com/D3351

Test Plan: Includes new test case; both python and nbrowser

Reviewers: alexmojaki

Reviewed By: alexmojaki

Differential Revision: https://phab.getgrist.com/D3393
2022-04-27 14:00:30 +02:00

454 lines
19 KiB
Python

import logger
import testutil
import test_engine
from test_engine import Table, Column, View, Section, Field
log = logger.Logger(__name__, logger.INFO)
class TestColumnActions(test_engine.EngineTestCase):
sample = testutil.parse_test_sample({
"SCHEMA": [
[1, "Address", [
[21, "city", "Text", False, "", "", ""],
]]
],
"DATA": {
"Address": [
["id", "city" ],
[11, "New York" ],
[12, "Colombia" ],
[13, "New Haven" ],
[14, "West Haven" ]],
}
})
@test_engine.test_undo
def test_column_updates(self):
# Verify various automatic adjustments for column updates
# (1) that label gets synced to colId unless untieColIdFromLabel is set.
# (2) that unsetting untieColId syncs the label to colId.
# (3) that a complex BulkUpdateRecord for _grist_Tables_column is processed correctly.
self.load_sample(self.sample)
self.apply_user_action(["AddColumn", "Address", "foo", {"type": "Numeric"}])
self.assertTableData("_grist_Tables_column", cols="subset", data=[
[ "id", "parentId", "colId", "label", "type", "untieColIdFromLabel" ],
[ 21, 1, "city", "", "Text", False ],
[ 22, 1, "foo", "foo", "Numeric", False ],
])
# Check that label is synced to colId, via either ModifyColumn or UpdateRecord useraction.
self.apply_user_action(["ModifyColumn", "Address", "city", {"label": "Hello"}])
self.apply_user_action(["UpdateRecord", "_grist_Tables_column", 22, {"label": "World"}])
self.assertTableData("_grist_Tables_column", cols="subset", data=[
[ "id", "parentId", "colId", "label", "type", "untieColIdFromLabel" ],
[ 21, 1, "Hello", "Hello", "Text", False ],
[ 22, 1, "World", "World", "Numeric", False ],
])
# But check that a rename or an update that includes colId is not affected by label.
self.apply_user_action(["RenameColumn", "Address", "Hello", "Hola"])
self.apply_user_action(["UpdateRecord", "_grist_Tables_column", 22,
{"label": "Foo", "colId": "Bar"}])
self.assertTableData("_grist_Tables_column", cols="subset", data=[
[ "id", "parentId", "colId", "label", "type", "untieColIdFromLabel" ],
[ 21, 1, "Hola", "Hello", "Text", False ],
[ 22, 1, "Bar", "Foo", "Numeric", False ],
])
# Check that setting untieColIdFromLabel doesn't change anything immediately.
self.apply_user_action(["BulkUpdateRecord", "_grist_Tables_column", [21,22],
{"untieColIdFromLabel": [True, True]}])
self.assertTableData("_grist_Tables_column", cols="subset", data=[
[ "id", "parentId", "colId", "label", "type", "untieColIdFromLabel" ],
[ 21, 1, "Hola", "Hello", "Text", True ],
[ 22, 1, "Bar", "Foo", "Numeric", True ],
])
# Check that ModifyColumn and UpdateRecord useractions no longer copy label to colId.
self.apply_user_action(["ModifyColumn", "Address", "Hola", {"label": "Hello"}])
self.apply_user_action(["UpdateRecord", "_grist_Tables_column", 22, {"label": "World"}])
self.assertTableData("_grist_Tables_column", cols="subset", data=[
[ "id", "parentId", "colId", "label", "type", "untieColIdFromLabel" ],
[ 21, 1, "Hola", "Hello", "Text", True ],
[ 22, 1, "Bar", "World", "Numeric", True ],
])
# Check that unsetting untieColIdFromLabel syncs label, whether label is provided or not.
self.apply_user_action(["UpdateRecord", "_grist_Tables_column", 21,
{"untieColIdFromLabel": False, "label": "Alice"}])
self.apply_user_action(["UpdateRecord", "_grist_Tables_column", 22,
{"untieColIdFromLabel": False}])
self.assertTableData("_grist_Tables_column", cols="subset", data=[
[ "id", "parentId", "colId", "label", "type", "untieColIdFromLabel" ],
[ 21, 1, "Alice", "Alice", "Text", False ],
[ 22, 1, "World", "World", "Numeric", False ],
])
# Check that column names still get sanitized and disambiguated.
self.apply_user_action(["UpdateRecord", "_grist_Tables_column", 21, {"label": "Alice M"}])
self.apply_user_action(["UpdateRecord", "_grist_Tables_column", 22, {"label": "Alice-M"}])
self.assertTableData("_grist_Tables_column", cols="subset", data=[
[ "id", "parentId", "colId", "label", "type", "untieColIdFromLabel" ],
[ 21, 1, "Alice_M", "Alice M", "Text", False ],
[ 22, 1, "Alice_M2", "Alice-M", "Numeric", False ],
])
# Check that a column rename doesn't avoid its own name.
self.apply_user_action(["UpdateRecord", "_grist_Tables_column", 21, {"label": "Alice*M"}])
self.assertTableData("_grist_Tables_column", cols="subset", data=[
[ "id", "parentId", "colId", "label", "type", "untieColIdFromLabel" ],
[ 21, 1, "Alice_M", "Alice*M", "Text", False ],
[ 22, 1, "Alice_M2", "Alice-M", "Numeric", False ],
])
# Untie colIds and tie them again, and make sure it doesn't cause unneeded renames.
self.apply_user_action(["BulkUpdateRecord", "_grist_Tables_column", [21,22],
{ "untieColIdFromLabel": [True, True] }])
self.apply_user_action(["BulkUpdateRecord", "_grist_Tables_column", [21,22],
{ "untieColIdFromLabel": [False, False] }])
self.assertTableData("_grist_Tables_column", cols="subset", data=[
[ "id", "parentId", "colId", "label", "type", "untieColIdFromLabel" ],
[ 21, 1, "Alice_M", "Alice*M", "Text", False ],
[ 22, 1, "Alice_M2", "Alice-M", "Numeric", False ],
])
# Check that disambiguating also works correctly for bulk updates.
self.apply_user_action(["BulkUpdateRecord", "_grist_Tables_column", [21,22],
{"label": ["Bob Z", "Bob-Z"]}])
self.assertTableData("_grist_Tables_column", cols="subset", data=[
[ "id", "parentId", "colId", "label", "type", "untieColIdFromLabel" ],
[ 21, 1, "Bob_Z", "Bob Z", "Text", False ],
[ 22, 1, "Bob_Z2", "Bob-Z", "Numeric", False ],
])
# Same for changing colIds directly.
self.apply_user_action(["BulkUpdateRecord", "_grist_Tables_column", [21,22],
{"colId": ["Carol X", "Carol-X"]}])
self.assertTableData("_grist_Tables_column", cols="subset", data=[
[ "id", "parentId", "colId", "label", "type", "untieColIdFromLabel" ],
[ 21, 1, "Carol_X", "Bob Z", "Text", False ],
[ 22, 1, "Carol_X2", "Bob-Z", "Numeric", False ],
])
# Check confusing bulk updates with different keys changing for different records.
out_actions = self.apply_user_action(["BulkUpdateRecord", "_grist_Tables_column", [21,22], {
"label": ["Bob Z", "Bob-Z"], # Unchanged from before.
"untieColIdFromLabel": [True, False]
}])
self.assertPartialOutActions(out_actions, { "stored": [
["RenameColumn", "Address", "Carol_X2", "Bob_Z"],
["BulkUpdateRecord", "_grist_Tables_column", [21, 22],
{"colId": ["Carol_X", "Bob_Z"], # Note that only one column is changing.
"untieColIdFromLabel": [True, False]
# No update to label, they get trimmed as unchanged.
}
],
]})
self.assertTableData("_grist_Tables_column", cols="subset", data=[
[ "id", "parentId", "colId", "label", "type", "untieColIdFromLabel" ],
[ 21, 1, "Carol_X", "Bob Z", "Text", True ],
[ 22, 1, "Bob_Z", "Bob-Z", "Numeric", False ],
])
#----------------------------------------------------------------------
address_table_data = [
["id", "city", "state", "amount" ],
[ 21, "New York", "NY" , 1. ],
[ 22, "Albany", "NY" , 2. ],
[ 23, "Seattle", "WA" , 3. ],
[ 24, "Chicago", "IL" , 4. ],
[ 25, "Bedford", "MA" , 5. ],
[ 26, "New York", "NY" , 6. ],
[ 27, "Buffalo", "NY" , 7. ],
[ 28, "Bedford", "NY" , 8. ],
[ 29, "Boston", "MA" , 9. ],
[ 30, "Yonkers", "NY" , 10. ],
[ 31, "New York", "NY" , 11. ],
]
sample2 = testutil.parse_test_sample({
"SCHEMA": [
[1, "Address", [
[11, "city", "Text", False, "", "", ""],
[12, "state", "Text", False, "", "", ""],
[13, "amount", "Numeric", False, "", "", ""],
]]
],
"DATA": {
"Address": address_table_data
}
})
def init_sample_data(self):
# Add a new view with a section, and a new table to that view, and a summary table.
self.load_sample(self.sample2)
self.apply_user_action(["CreateViewSection", 1, 0, "record", None])
self.apply_user_action(["CreateViewSection", 0, 1, "record", None])
self.apply_user_action(["CreateViewSection", 1, 1, "record", [12]])
self.apply_user_action(["BulkAddRecord", "Table1", [None]*3, {
"A": ["a", "b", "c"],
"B": ["d", "e", "f"],
"C": ["", "", ""]
}])
# Verify the new structure of tables and views.
self.assertTables([
Table(1, "Address", primaryViewId=0, summarySourceTable=0, columns=[
Column(11, "city", "Text", False, "", 0),
Column(12, "state", "Text", False, "", 0),
Column(13, "amount", "Numeric", False, "", 0),
]),
Table(2, "Table1", 2, 0, columns=[
Column(14, "manualSort", "ManualSortPos", False, "", 0),
Column(15, "A", "Text", False, "", 0),
Column(16, "B", "Text", False, "", 0),
Column(17, "C", "Any", True, "", 0),
]),
Table(3, "GristSummary_7_Address", 0, 1, columns=[
Column(18, "state", "Text", False, "", summarySourceCol=12),
Column(19, "group", "RefList:Address", True, summarySourceCol=0,
formula="table.getSummarySourceGroup(rec)"),
Column(20, "count", "Int", True, summarySourceCol=0, formula="len($group)"),
Column(21, "amount", "Numeric", True, summarySourceCol=0, formula="SUM($group.amount)"),
]),
])
self.assertViews([
View(1, sections=[
Section(1, parentKey="record", tableRef=1, fields=[
Field(1, colRef=11),
Field(2, colRef=12),
Field(3, colRef=13),
]),
Section(4, parentKey="record", tableRef=2, fields=[
Field(10, colRef=15),
Field(11, colRef=16),
Field(12, colRef=17),
]),
Section(5, parentKey="record", tableRef=3, fields=[
Field(13, colRef=18),
Field(14, colRef=20),
Field(15, colRef=21),
]),
]),
View(2, sections=[
Section(2, parentKey="record", tableRef=2, fields=[
Field(4, colRef=15),
Field(5, colRef=16),
Field(6, colRef=17),
]),
])
])
self.assertTableData('Address', data=self.address_table_data)
self.assertTableData('Table1', data=[
["id", "A", "B", "C", "manualSort"],
[ 1, "a", "d", None, 1.0],
[ 2, "b", "e", None, 2.0],
[ 3, "c", "f", None, 3.0],
])
self.assertTableData("GristSummary_7_Address", 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 ],
])
#----------------------------------------------------------------------
@test_engine.test_undo
def test_column_removals(self):
# Verify removal of fields when columns are removed.
self.init_sample_data()
# Add link{Src,Target}ColRef to ViewSections. These aren't actually meaningful links, but they
# should still get cleared automatically when columns get removed.
self.apply_user_action(['UpdateRecord', '_grist_Views_section', 2, {
'linkSrcSectionRef': 1,
'linkSrcColRef': 11,
'linkTargetColRef': 16
}])
self.assertTableData('_grist_Views_section', cols="subset", rows="subset", data=[
["id", "linkSrcSectionRef", "linkSrcColRef", "linkTargetColRef"],
[2, 1, 11, 16 ],
])
# Test that we can remove multiple columns using BulkUpdateRecord.
self.apply_user_action(["BulkRemoveRecord", '_grist_Tables_column', [11, 16]])
# Test that link{Src,Target}colRef back-references get unset.
self.assertTableData('_grist_Views_section', cols="subset", rows="subset", data=[
["id", "linkSrcSectionRef", "linkSrcColRef", "linkTargetColRef"],
[2, 1, 0, 0 ],
])
# Test that columns and section fields got removed.
self.assertTables([
Table(1, "Address", primaryViewId=0, summarySourceTable=0, columns=[
Column(12, "state", "Text", False, "", 0),
Column(13, "amount", "Numeric", False, "", 0),
]),
Table(2, "Table1", 2, 0, columns=[
Column(14, "manualSort", "ManualSortPos", False, "", 0),
Column(15, "A", "Text", False, "", 0),
Column(17, "C", "Any", True, "", 0),
]),
Table(3, "GristSummary_7_Address", 0, 1, columns=[
Column(18, "state", "Text", False, "", summarySourceCol=12),
Column(19, "group", "RefList:Address", True, summarySourceCol=0,
formula="table.getSummarySourceGroup(rec)"),
Column(20, "count", "Int", True, summarySourceCol=0, formula="len($group)"),
Column(21, "amount", "Numeric", True, summarySourceCol=0, formula="SUM($group.amount)"),
]),
])
self.assertViews([
View(1, sections=[
Section(1, parentKey="record", tableRef=1, fields=[
Field(2, colRef=12),
Field(3, colRef=13),
]),
Section(4, parentKey="record", tableRef=2, fields=[
Field(10, colRef=15),
Field(12, colRef=17),
]),
Section(5, parentKey="record", tableRef=3, fields=[
Field(13, colRef=18),
Field(14, colRef=20),
Field(15, colRef=21),
]),
]),
View(2, sections=[
Section(2, parentKey="record", tableRef=2, fields=[
Field(4, colRef=15),
Field(6, colRef=17),
]),
])
])
#----------------------------------------------------------------------
@test_engine.test_undo
def test_summary_column_removals(self):
# Verify that when we remove a column used for summary-table group-by, it updates summary
# tables appropriately.
self.init_sample_data()
# Test that we cannot remove group-by columns from summary tables directly.
with self.assertRaisesRegex(ValueError, "cannot remove .* group-by"):
self.apply_user_action(["BulkRemoveRecord", '_grist_Tables_column', [20,18]])
# Test that group-by columns in summary tables get removed.
self.apply_user_action(["BulkRemoveRecord", '_grist_Tables_column', [11,12,16]])
# Verify the new structure of tables and views.
self.assertTables([
Table(1, "Address", primaryViewId=0, summarySourceTable=0, columns=[
Column(13, "amount", "Numeric", False, "", 0),
]),
Table(2, "Table1", 2, 0, columns=[
Column(14, "manualSort", "ManualSortPos", False, "", 0),
Column(15, "A", "Text", False, "", 0),
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=[
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,
formula="table.getSummarySourceGroup(rec)"),
]),
])
self.assertViews([
View(1, sections=[
Section(1, parentKey="record", tableRef=1, fields=[
Field(3, colRef=13),
]),
Section(4, parentKey="record", tableRef=2, fields=[
Field(10, colRef=15),
Field(12, colRef=17),
]),
Section(5, parentKey="record", tableRef=4, fields=[
Field(14, colRef=23),
Field(15, colRef=24),
]),
]),
View(2, sections=[
Section(2, parentKey="record", tableRef=2, fields=[
Field(4, colRef=15),
Field(6, colRef=17),
]),
])
])
# Verify the data itself.
self.assertTableData('Address', data=[
["id", "amount" ],
[ 21, 1. ],
[ 22, 2. ],
[ 23, 3. ],
[ 24, 4. ],
[ 25, 5. ],
[ 26, 6. ],
[ 27, 7. ],
[ 28, 8. ],
[ 29, 9. ],
[ 30, 10. ],
[ 31, 11. ],
])
self.assertTableData('Table1', data=[
["id", "A", "C", "manualSort"],
[ 1, "a", None, 1.0],
[ 2, "b", None, 2.0],
[ 3, "c", None, 3.0],
])
self.assertTableData("GristSummary_7_Address2", cols="subset", data=[
[ "id", "count", "amount" ],
[ 1, 7+1+1+2, 1.+2+6+7+8+10+11+3+4+5+9 ],
])
#----------------------------------------------------------------------
@test_engine.test_undo
def test_column_sort_removals(self):
# Verify removal of sort spec entries when columns are removed.
self.init_sample_data()
# Add sortSpecs to ViewSections.
self.apply_user_action(['BulkUpdateRecord', '_grist_Views_section', [2, 3, 4],
{'sortColRefs': ['[15, -16]', '[-15, 16, 17]', '[19]']}
])
self.assertTableData('_grist_Views_section', cols="subset", rows="subset", data=[
["id", "sortColRefs" ],
[2, '[15, -16]' ],
[3, '[-15, 16, 17]'],
[4, '[19]' ],
])
# Remove column, and check that the correct sortColRefs items are removed.
self.apply_user_action(["RemoveRecord", '_grist_Tables_column', 16])
self.assertTableData('_grist_Views_section', cols="subset", rows="subset", data=[
["id", "sortColRefs"],
[2, '[15]' ],
[3, '[-15, 17]' ],
[4, '[19]' ],
])
# Update sortColRefs for next test.
self.apply_user_action(['UpdateRecord', '_grist_Views_section', 3,
{'sortColRefs': '[-15, -16, 17]'}
])
# Remove multiple columns using BulkUpdateRecord, and check that the sortSpecs are updated.
self.apply_user_action(["BulkRemoveRecord", '_grist_Tables_column', [15, 17, 19]])
self.assertTableData('_grist_Views_section', cols="subset", rows="subset", data=[
["id", "sortColRefs"],
[2, '[]' ],
[3, '[-16]' ],
[4, '[]' ],
])