You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
gristlabs_grist-core/sandbox/grist/test_summary_choicelist.py

439 lines
16 KiB

# pylint: disable=line-too-long
"""
Test of Summary tables grouped by ChoiceList columns.
"""
import logging
import column
import lookup
import testutil
from test_engine import EngineTestCase, Table, Column, test_undo
log = logging.getLogger(__name__)
class TestSummaryChoiceList(EngineTestCase):
sample = testutil.parse_test_sample({
"SCHEMA": [
[1, "Source", [
[10, "other", "Text", False, "", "other", ""],
[11, "choices1", "ChoiceList", False, "", "choices1", ""],
[12, "choices2", "ChoiceList", False, "", "choices2", ""],
]]
],
"DATA": {
"Source": [
["id", "choices1", "choices2", "other"],
[21, ["a", "b"], ["c", "d"], "foo"],
]
}
})
starting_table = Table(1, "Source", primaryViewId=0, summarySourceTable=0, columns=[
Column(10, "other", "Text", isFormula=False, formula="", summarySourceCol=0),
Column(11, "choices1", "ChoiceList", isFormula=False, formula="", summarySourceCol=0),
Column(12, "choices2", "ChoiceList", isFormula=False, formula="", summarySourceCol=0),
])
# ----------------------------------------------------------------------
@test_undo
def test_summary_by_choice_list(self):
self.load_sample(self.sample)
# Verify the starting table; there should be no views yet.
self.assertTables([self.starting_table])
self.assertViews([])
# Create a summary section, grouped by the "choices1" column.
self.apply_user_action(["CreateViewSection", 1, 0, "record", [11], None])
summary_table1 = Table(
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,
formula="table.getSummarySourceGroup(rec)"),
Column(15, "count", "Int", isFormula=True, summarySourceCol=0,
formula="len($group)"),
],
)
# Create another summary section, grouped by both choicelist columns.
self.apply_user_action(["CreateViewSection", 1, 0, "record", [11, 12], None])
summary_table2 = Table(
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),
Column(18, "group", "RefList:Source", isFormula=True, summarySourceCol=0,
formula="table.getSummarySourceGroup(rec)"),
Column(19, "count", "Int", isFormula=True, summarySourceCol=0,
formula="len($group)"),
],
)
# Create another summary section, grouped by the non-choicelist column
self.apply_user_action(["CreateViewSection", 1, 0, "record", [10], None])
summary_table3 = Table(
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,
formula="table.getSummarySourceGroup(rec)"),
Column(22, "count", "Int", isFormula=True, summarySourceCol=0,
formula="len($group)"),
],
)
# Create another summary section, grouped by the non-choicelist column and choices1
self.apply_user_action(["CreateViewSection", 1, 0, "record", [10, 11], None])
summary_table4 = Table(
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),
Column(25, "group", "RefList:Source", isFormula=True, summarySourceCol=0,
formula="table.getSummarySourceGroup(rec)"),
Column(26, "count", "Int", isFormula=True, summarySourceCol=0,
formula="len($group)"),
],
)
self.assertTables(
[self.starting_table, summary_table1, summary_table2, summary_table3, summary_table4]
)
# Verify the summarized data.
self.assertTableData('Source_summary_choices1', data=[
["id", "choices1", "group", "count"],
[1, "a", [21], 1],
[2, "b", [21], 1],
])
self.assertTableData('Source_summary_choices1_choices2', data=[
["id", "choices1", "choices2", "group", "count"],
[1, "a", "c", [21], 1],
[2, "a", "d", [21], 1],
[3, "b", "c", [21], 1],
[4, "b", "d", [21], 1],
])
self.assertTableData('Source_summary_other', data=[
["id", "other", "group", "count"],
[1, "foo", [21], 1],
])
self.assertTableData('Source_summary_choices1_other', data=[
["id", "other", "choices1", "group", "count"],
[1, "foo", "a", [21], 1],
[2, "foo", "b", [21], 1],
])
# Verify the optimisation works for the table without choicelists
self.assertIs(self.engine.tables["Source"]._summary_simple, None)
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["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#Source_summary_choices1': column.ReferenceListColumn,
"#lookup#_Contains(value='#summary#Source_summary_choices1', match_empty=no_match_empty)":
lookup.ContainsLookupMapColumn,
'#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#Source_summary_other': column.ReferenceColumn,
'#lookup##summary#Source_summary_other': lookup.SimpleLookupMapColumn,
'#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,
}
)
# Remove 'b' from choices1
self.update_record("Source", 21, choices1=["L", "a"])
self.assertTableData('Source', data=[
["id", "choices1", "choices2", "other"],
[21, ["a"], ["c", "d"], "foo"],
])
# Verify that the summary table rows containing 'b' are removed
self.assertTableData('Source_summary_choices1', data=[
["id", "choices1", "group", "count"],
[1, "a", [21], 1],
])
self.assertTableData('Source_summary_choices1_choices2', data=[
["id", "choices1", "choices2", "group", "count"],
[1, "a", "c", [21], 1],
[2, "a", "d", [21], 1],
])
# Add 'e' to choices2
self.update_record("Source", 21, choices2=["L", "c", "d", "e"])
# First summary table unaffected
self.assertTableData('Source_summary_choices1', data=[
["id", "choices1", "group", "count"],
[1, "a", [21], 1],
])
# New row added for 'e'
self.assertTableData('Source_summary_choices1_choices2', data=[
["id", "choices1", "choices2", "group", "count"],
[1, "a", "c", [21], 1],
[2, "a", "d", [21], 1],
[3, "a", "e", [21], 1],
])
# Empty choices1
self.update_record("Source", 21, choices1=None)
self.assertTableData('Source', data=[
["id", "choices1", "choices2", "other"],
[21, None, ["c", "d", "e"], "foo"],
])
self.assertTableData('Source_summary_choices1', data=[
["id", "choices1", "group", "count"],
[2, "", [21], 1],
])
self.assertTableData('Source_summary_choices1_choices2', data=[
["id", "choices1", "choices2", "group", "count"],
[4, "", "c", [21], 1],
[5, "", "d", [21], 1],
[6, "", "e", [21], 1],
])
# Remove record from source
self.remove_record("Source", 21)
# All summary rows are now empty and thus removed
self.assertTableData('Source_summary_choices1', data=[
["id", "choices1", "group", "count"],
])
self.assertTableData('Source_summary_choices1_choices2', data=[
["id", "choices1", "choices2", "group", "count"],
])
# Make rows with every combination of {a,b,ab} and {c,d,cd}
self.add_records(
'Source',
["id", "choices1", "choices2"],
[
[101, ["L", "a"], ["L", "c"]],
[102, ["L", "b"], ["L", "c"]],
[103, ["L", "a", "b"], ["L", "c"]],
[104, ["L", "a"], ["L", "d"]],
[105, ["L", "b"], ["L", "d"]],
[106, ["L", "a", "b"], ["L", "d"]],
[107, ["L", "a"], ["L", "c", "d"]],
[108, ["L", "b"], ["L", "c", "d"]],
[109, ["L", "a", "b"], ["L", "c", "d"]],
# and one row with empty lists
[110, ["L"], ["L"]],
]
)
self.assertTableData('Source', cols="subset", data=[
["id", "choices1", "choices2"],
[101, ["a"], ["c"]],
[102, ["b"], ["c"]],
[103, ["a", "b"], ["c"]],
[104, ["a"], ["d"]],
[105, ["b"], ["d"]],
[106, ["a", "b"], ["d"]],
[107, ["a"], ["c", "d"]],
[108, ["b"], ["c", "d"]],
[109, ["a", "b"], ["c", "d"]],
[110, None, None],
])
# Summary tables now have an even distribution of combinations
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],
[3, "", [110], 1],
])
summary_data = [
["id", "choices1", "choices2", "group", "count"],
[1, "a", "c", [101, 103, 107, 109], 4],
[2, "b", "c", [102, 103, 108, 109], 4],
[3, "a", "d", [104, 106, 107, 109], 4],
[4, "b", "d", [105, 106, 108, 109], 4],
[5, "", "", [110], 1],
]
self.assertTableData('Source_summary_choices1_choices2', data=summary_data)
# Verify that "DetachSummaryViewSection" useraction works correctly.
self.apply_user_action(["DetachSummaryViewSection", 4])
self.assertTables([
self.starting_table, summary_table1, summary_table3, summary_table4,
Table(
6, "Table1", primaryViewId=5, summarySourceTable=0,
columns=[
Column(27, "manualSort", "ManualSortPos", isFormula=False, formula="", summarySourceCol=0),
Column(28, "choices1", "Choice", isFormula=False, formula="", summarySourceCol=0),
Column(29, "choices2", "Choice", isFormula=False, formula="", summarySourceCol=0),
Column(30, "count", "Int", isFormula=True, summarySourceCol=0,
formula="len($group)"),
Column(31, "group", "RefList:Source", isFormula=True, summarySourceCol=0,
formula='Source.lookupRecords('
'choices1=CONTAINS($choices1, match_empty=""), '
'choices2=CONTAINS($choices2, match_empty=""))'),
],
)
])
self.assertTableData('Table1', data=summary_data, cols="subset")
@test_undo
def test_change_choice_to_choicelist(self):
sample = testutil.parse_test_sample({
"SCHEMA": [
[1, "Source", [
[10, "other", "Text", False, "", "other", ""],
[11, "choices1", "Choice", False, "", "choice", ""],
]]
],
"DATA": {
"Source": [
["id", "choices1", "other"],
[21, "a", "foo"],
[22, "b", "bar"],
]
}
})
starting_table = Table(1, "Source", primaryViewId=0, summarySourceTable=0, columns=[
Column(10, "other", "Text", isFormula=False, formula="", summarySourceCol=0),
Column(11, "choices1", "Choice", isFormula=False, formula="", summarySourceCol=0),
])
self.load_sample(sample)
# Verify the starting table; there should be no views yet.
self.assertTables([starting_table])
self.assertViews([])
# Create a summary section, grouped by the "choices1" column.
self.apply_user_action(["CreateViewSection", 1, 0, "record", [11], None])
summary_table = Table(
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,
formula="table.getSummarySourceGroup(rec)"),
Column(14, "count", "Int", isFormula=True, summarySourceCol=0,
formula="len($group)"),
],
)
data = [
["id", "choices1", "group", "count"],
[1, "a", [21], 1],
[2, "b", [22], 1],
]
self.assertTables([starting_table, summary_table])
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"}])
# Changing type in reality is a bit more complex than these actions
# so we put the correct values in place directly
self.apply_user_action(["BulkUpdateRecord", "Source", [21, 22],
{"choices1": [["L", "a"], ["L", "b"]]}])
starting_table.columns[1] = starting_table.columns[1]._replace(type="ChoiceList")
self.assertTables([starting_table, summary_table])
self.assertTableData('Source_summary_choices1', data=data)
@test_undo
def test_rename_choices(self):
self.load_sample(self.sample)
# Create a summary section, grouped by both choicelist columns.
self.apply_user_action(["CreateViewSection", 1, 0, "record", [11, 12], None])
summary_table = Table(
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),
Column(15, "group", "RefList:Source", isFormula=True, summarySourceCol=0,
formula="table.getSummarySourceGroup(rec)"),
Column(16, "count", "Int", isFormula=True, summarySourceCol=0,
formula="len($group)"),
],
)
self.assertTables([self.starting_table, summary_table])
# Rename all the choices
out_actions = self.apply_user_action(
["RenameChoices", "Source", "choices1", {"a": "aa", "b": "bb"}])
self.apply_user_action(
["RenameChoices", "Source", "choices2", {"c": "cc", "d": "dd"}])
# Actions from renaming choices1 only
self.assertPartialOutActions(out_actions, {'stored': [
['UpdateRecord', 'Source', 21, {'choices1': ['L', u'aa', u'bb']}],
['BulkAddRecord',
'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', 'Source_summary_choices1_choices2', [1, 2, 3, 4]],
['BulkUpdateRecord',
'Source_summary_choices1_choices2',
[5, 6, 7, 8],
{'count': [1, 1, 1, 1]}],
['BulkUpdateRecord',
'Source_summary_choices1_choices2',
[5, 6, 7, 8],
{'group': [['L', 21],
['L', 21],
['L', 21],
['L', 21]]}]
]})
# Final Source table is essentially the same as before, just with each letter doubled
self.assertTableData('Source', data=[
["id", "choices1", "choices2", "other"],
[21, ["aa", "bb"], ["cc", "dd"], "foo"],
])
# Final summary table is very similar to before, but with two empty chunks of 4 rows
# left over from each rename
self.assertTableData('Source_summary_choices1_choices2', data=[
["id", "choices1", "choices2", "group", "count"],
[9, "aa", "cc", [21], 1],
[10, "aa", "dd", [21], 1],
[11, "bb", "cc", [21], 1],
[12, "bb", "dd", [21], 1],
])