mirror of
https://github.com/gristlabs/grist-core.git
synced 2026-03-02 04:09:24 +00:00
(core) Linking summary tables grouped by list columns
Summary:
Prefix keys of `LinkingState.filterColValues` with `_contains:` when the source column is a ChoiceList or ReferenceList.
This is parsed out to make a boolean `isContainsFilter` which is kept in each value of `QueryRefs.filterTuples` (previously `filterPairs`).
Then when converting back in `convertQueryFromRefs` we construct `Query.contains: {[colId: string]: boolean}`.
Finally `getFilterFunc` uses `Query.contains` to decide what kind of filtering to do.
This is not pretty, but the existing code is already very complex and it was hard to find something that wouldn't require touching loads of code just to make things compile.
Test Plan: Added a new nbrowser test and fixture, tests that selecting a source table by summary tables grouped by a choicelist column, non-list column, and both all filter the correct data.
Reviewers: dsagal
Reviewed By: dsagal
Differential Revision: https://phab.getgrist.com/D2940
This commit is contained in:
@@ -26,6 +26,7 @@ const {copyToClipboard} = require('app/client/lib/copyToClipboard');
|
||||
const {setTestState} = require('app/client/lib/testState');
|
||||
const {ExtraRows} = require('app/client/models/DataTableModelWithDiff');
|
||||
const {createFilterMenu} = require('app/client/ui/ColumnFilterMenu');
|
||||
const {encodeObject} = require("app/plugin/objtypes");
|
||||
|
||||
/**
|
||||
* BaseView forms the basis for ViewSection classes.
|
||||
@@ -140,7 +141,12 @@ function BaseView(gristDoc, viewSectionModel, options) {
|
||||
|
||||
this._linkingFilter = this.autoDispose(ko.computed(() => {
|
||||
const linking = this._linkingState();
|
||||
return linking && linking.filterColValues ? linking.filterColValues() : {};
|
||||
const result = linking && linking.filterColValues ? linking.filterColValues() : {filters: {}};
|
||||
result.operations = result.operations || {};
|
||||
for (const key in result.filters) {
|
||||
result.operations[key] = result.operations[key] || 'in';
|
||||
}
|
||||
return result;
|
||||
}));
|
||||
|
||||
// A computed for the rowId of the row selected by section linking.
|
||||
@@ -204,7 +210,8 @@ function BaseView(gristDoc, viewSectionModel, options) {
|
||||
// dependency changes.
|
||||
this.autoDispose(ko.computed(() => {
|
||||
this._isLoading(true);
|
||||
this._queryRowSource.makeQuery(this._linkingFilter(), (err) => {
|
||||
const linkingFilter = this._linkingFilter();
|
||||
this._queryRowSource.makeQuery(linkingFilter.filters, linkingFilter.operations, (err) => {
|
||||
if (this.isDisposed()) { return; }
|
||||
if (err) { window.gristNotify(`Query error: ${err.message}`); }
|
||||
this.onTableLoaded();
|
||||
@@ -373,8 +380,11 @@ BaseView.prototype._parsePasteForView = function(data, cols) {
|
||||
};
|
||||
|
||||
BaseView.prototype._getDefaultColValues = function() {
|
||||
const filterValues = this._linkingFilter.peek();
|
||||
return _.mapObject(_.pick(filterValues, v => (v.length > 0)), v => v[0]);
|
||||
const {filters, operations} = this._linkingFilter.peek();
|
||||
return _.mapObject(
|
||||
_.pick(filters, v => (v.length > 0)),
|
||||
(value, key) => operations[key] === "intersects" ? encodeObject(value) : value[0]
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -77,7 +77,7 @@ function LinkingState(gristDoc, srcSection, srcColId, tgtSection, tgtColId, byAl
|
||||
}
|
||||
}
|
||||
}
|
||||
return {[tgtColId]: Array.from(srcValues)};
|
||||
return {filters: {[tgtColId]: Array.from(srcValues)}};
|
||||
}));
|
||||
} else if (srcColId) {
|
||||
let srcRowModel = this.autoDispose(srcTableModel.createFloatingRowModel());
|
||||
@@ -88,13 +88,13 @@ function LinkingState(gristDoc, srcSection, srcColId, tgtSection, tgtColId, byAl
|
||||
this.filterColValues = this.autoDispose(ko.computed(() => {
|
||||
const srcRowId = srcSection.activeRowId();
|
||||
srcRowModel.assign(srcRowId);
|
||||
return {[tgtColId]: [srcCell()]};
|
||||
return {filters: {[tgtColId]: [srcCell()]}};
|
||||
}));
|
||||
}
|
||||
} else {
|
||||
this.filterColValues = this.autoDispose(ko.computed(() => {
|
||||
const srcRowId = srcSection.activeRowId();
|
||||
return {[tgtColId]: [srcRowId]};
|
||||
return {filters: {[tgtColId]: [srcRowId]}};
|
||||
}));
|
||||
}
|
||||
} else if (isSummaryOf(srcSection.table(), tgtSection.table())) {
|
||||
@@ -103,17 +103,24 @@ function LinkingState(gristDoc, srcSection, srcColId, tgtSection, tgtColId, byAl
|
||||
// those in the srcSection).
|
||||
// TODO: This approach doesn't help cursor-linking (the other direction). If we have the
|
||||
// inverse of summary-table's 'group' column, we could implement both, and more efficiently.
|
||||
const isDirectSummary = srcSection.table().summarySourceTable() === tgtSection.table().getRowId();
|
||||
this.filterColValues = this.autoDispose(ko.computed(() => {
|
||||
const srcRowId = srcSection.activeRowId();
|
||||
const filter = {};
|
||||
for (const c of srcSection.table().columns().all()) {
|
||||
if (c.summarySourceCol()) {
|
||||
const colId = c.summarySource().colId();
|
||||
const srcValue = srcTableData.getValue(srcRowId, colId);
|
||||
filter[colId] = [srcValue];
|
||||
const filters = {};
|
||||
const operations = {};
|
||||
for (const c of srcSection.table().groupByColumns()) {
|
||||
const col = c.summarySource();
|
||||
const colId = col.colId();
|
||||
const srcValue = srcTableData.getValue(srcRowId, colId);
|
||||
filters[colId] = [srcValue];
|
||||
if (isDirectSummary) {
|
||||
const tgtColType = col.type();
|
||||
if (tgtColType === 'ChoiceList' || tgtColType.startsWith('RefList:')) {
|
||||
operations[colId] = 'intersects';
|
||||
}
|
||||
}
|
||||
}
|
||||
return filter;
|
||||
return {filters, operations};
|
||||
}));
|
||||
} else if (isSummaryOf(tgtSection.table(), srcSection.table())) {
|
||||
// TODO: We should move the cursor, but don't currently it for summaries. For that, we need a
|
||||
|
||||
Reference in New Issue
Block a user