mirror of
https://github.com/gristlabs/grist-core.git
synced 2026-03-02 04:09:24 +00:00
(core) Filter rows based on linked widgets when exporting view
Summary: Fixes a problem reported here: https://community.getgrist.com/t/exporting-the-records-in-a-linked-view/2556/4 The download CSV/Excel link now contains an additional `linkingFilter` URL parameter containing JSON-encoded `filters` and `operations`. This object is originally created in the frontend in `LinkingState`, and previously it was only used internally in the frontend. It would make its way via `QuerySetManager` to `QuerySet.getFilterFunc` where the actual filtering logic happened. Now most of that logic has been moved to a similar function in `common`. The new function works with a new interface `ColumnGettersByColId` which abstract over the different ways data is accessed in the client and server in this context. There's no significant new logic in the diff, just refactoring and wiring. Test Plan: Expanded two `nbrowser/SelectBy*.ts` test suites to also check the contents of a downloaded CSV in different linking scenarios. Reviewers: paulfitz Reviewed By: paulfitz Differential Revision: https://phab.getgrist.com/D3961
This commit is contained in:
@@ -57,7 +57,7 @@ import {invokePrompt} from 'app/client/ui2018/modals';
|
||||
import {DiscussionPanel} from 'app/client/widgets/DiscussionEditor';
|
||||
import {FieldEditor} from "app/client/widgets/FieldEditor";
|
||||
import {MinimalActionGroup} from 'app/common/ActionGroup';
|
||||
import {ClientQuery} from "app/common/ActiveDocAPI";
|
||||
import {ClientQuery, FilterColValues} from "app/common/ActiveDocAPI";
|
||||
import {CommDocChatter, CommDocUsage, CommDocUserAction} from 'app/common/CommTypes';
|
||||
import {delay} from 'app/common/delay';
|
||||
import {DisposableWithEvents} from 'app/common/DisposableWithEvents';
|
||||
@@ -1518,18 +1518,20 @@ export class GristDoc extends DisposableWithEvents {
|
||||
}
|
||||
|
||||
private _getDocApiDownloadParams() {
|
||||
const filters = this.viewModel.activeSection.peek().activeFilters.get().map(filterInfo => ({
|
||||
const activeSection = this.viewModel.activeSection();
|
||||
const filters = activeSection.activeFilters.get().map(filterInfo => ({
|
||||
colRef: filterInfo.fieldOrColumn.origCol().origColRef(),
|
||||
filter: filterInfo.filter()
|
||||
}));
|
||||
const linkingFilter: FilterColValues = activeSection.linkingFilter();
|
||||
|
||||
const params = {
|
||||
return {
|
||||
viewSection: this.viewModel.activeSectionId(),
|
||||
tableId: this.viewModel.activeSection().table().tableId(),
|
||||
activeSortSpec: JSON.stringify(this.viewModel.activeSection().activeSortSpec()),
|
||||
tableId: activeSection.table().tableId(),
|
||||
activeSortSpec: JSON.stringify(activeSection.activeSortSpec()),
|
||||
filters: JSON.stringify(filters),
|
||||
linkingFilter: JSON.stringify(linkingFilter),
|
||||
};
|
||||
return params;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -6,7 +6,7 @@ import {TableRec} from "app/client/models/entities/TableRec";
|
||||
import {ViewSectionRec} from "app/client/models/entities/ViewSectionRec";
|
||||
import {UIRowId} from "app/common/TableData";
|
||||
import {LinkConfig} from "app/client/ui/selectBy";
|
||||
import {ClientQuery, QueryOperation} from "app/common/ActiveDocAPI";
|
||||
import {FilterColValues, QueryOperation} from "app/common/ActiveDocAPI";
|
||||
import {isList, isListType, isRefListType} from "app/common/gristTypes";
|
||||
import * as gutil from "app/common/gutil";
|
||||
import {encodeObject} from 'app/plugin/objtypes';
|
||||
@@ -34,8 +34,6 @@ function isSummaryOf(summary: TableRec, detail: TableRec): boolean {
|
||||
gutil.isSubset(summary.summarySourceColRefs(), detail.summarySourceColRefs()));
|
||||
}
|
||||
|
||||
export type FilterColValues = Pick<ClientQuery, "filters" | "operations">;
|
||||
|
||||
/**
|
||||
* Maintains state useful for linking sections, i.e. auto-filtering and auto-scrolling.
|
||||
* Exposes .filterColValues, which is either null or a computed evaluating to a filtering object;
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import DataTableModel from 'app/client/models/DataTableModel';
|
||||
import { ColumnGetter, ColumnGetters } from 'app/common/ColumnGetters';
|
||||
import {ColumnGetter, ColumnGetters, ColumnGettersByColId} from 'app/common/ColumnGetters';
|
||||
import * as gristTypes from 'app/common/gristTypes';
|
||||
import { choiceGetter } from 'app/common/SortFunc';
|
||||
import { Sort } from 'app/common/SortSpec';
|
||||
import {choiceGetter} from 'app/common/SortFunc';
|
||||
import {Sort} from 'app/common/SortSpec';
|
||||
import {TableData} from 'app/common/TableData';
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -56,3 +57,13 @@ export class ClientColumnGetters implements ColumnGetters {
|
||||
return this.getColGetter(manualSortCol.getRowId());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export class ClientColumnGettersByColId implements ColumnGettersByColId {
|
||||
constructor(private _tableData: TableData) {
|
||||
}
|
||||
|
||||
public getColGetterByColId(colId: string): ColumnGetter {
|
||||
return this._tableData.getRowPropFunc(colId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,17 +31,16 @@ import {DocModel} from 'app/client/models/DocModel';
|
||||
import {BaseFilteredRowSource, RowList, RowSource} from 'app/client/models/rowset';
|
||||
import {TableData} from 'app/client/models/TableData';
|
||||
import {ActiveDocAPI, ClientQuery, QueryOperation} from 'app/common/ActiveDocAPI';
|
||||
import {CellValue, TableDataAction} from 'app/common/DocActions';
|
||||
import {TableDataAction} from 'app/common/DocActions';
|
||||
import {DocData} from 'app/common/DocData';
|
||||
import {isList} from "app/common/gristTypes";
|
||||
import {nativeCompare} from 'app/common/gutil';
|
||||
import {IRefCountSub, RefCountMap} from 'app/common/RefCountMap';
|
||||
import {RowFilterFunc} from 'app/common/RowFilterFunc';
|
||||
import {getLinkingFilterFunc, RowFilterFunc} from 'app/common/RowFilterFunc';
|
||||
import {TableData as BaseTableData, UIRowId} from 'app/common/TableData';
|
||||
import {tbind} from 'app/common/tbind';
|
||||
import {decodeObject} from "app/plugin/objtypes";
|
||||
import {Disposable, Holder, IDisposableOwnerT} from 'grainjs';
|
||||
import * as ko from 'knockout';
|
||||
import {ClientColumnGettersByColId} from 'app/client/models/ClientColumnGetters';
|
||||
import debounce = require('lodash/debounce');
|
||||
|
||||
// Limit on the how many rows to request for OnDemand tables.
|
||||
@@ -306,28 +305,9 @@ export class TableQuerySets {
|
||||
export function getFilterFunc(docData: DocData, query: ClientQuery): RowFilterFunc<UIRowId> {
|
||||
// NOTE we rely without checking on tableId and colIds being valid.
|
||||
const tableData: BaseTableData = docData.getTable(query.tableId)!;
|
||||
const colFuncs = Object.keys(query.filters).sort().map(
|
||||
(colId) => {
|
||||
const getter = tableData.getRowPropFunc(colId)!;
|
||||
const values = new Set(query.filters[colId]);
|
||||
switch (query.operations[colId]) {
|
||||
case "intersects":
|
||||
return (rowId: UIRowId) => {
|
||||
const value = getter(rowId) as CellValue;
|
||||
return isList(value) &&
|
||||
(decodeObject(value) as unknown[]).some(v => values.has(v));
|
||||
};
|
||||
case "empty":
|
||||
return (rowId: UIRowId) => {
|
||||
const value = getter(rowId);
|
||||
// `isList(value) && value.length === 1` means `value == ['L']` i.e. an empty list
|
||||
return !value || isList(value) && value.length === 1;
|
||||
};
|
||||
case "in":
|
||||
return (rowId: UIRowId) => values.has(getter(rowId));
|
||||
}
|
||||
});
|
||||
return (rowId: UIRowId) => colFuncs.every(f => f(rowId));
|
||||
const colGetters = new ClientColumnGettersByColId(tableData);
|
||||
const rowFilterFunc = getLinkingFilterFunc(colGetters, query);
|
||||
return (rowId: UIRowId) => rowId !== "new" && rowFilterFunc(rowId);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import BaseView from 'app/client/components/BaseView';
|
||||
import {CursorPos} from 'app/client/components/Cursor';
|
||||
import {FilterColValues, LinkingState} from 'app/client/components/LinkingState';
|
||||
import {LinkingState} from 'app/client/components/LinkingState';
|
||||
import {KoArray} from 'app/client/lib/koArray';
|
||||
import {
|
||||
ColumnRec,
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
import * as modelUtil from 'app/client/models/modelUtil';
|
||||
import {LinkConfig} from 'app/client/ui/selectBy';
|
||||
import {getWidgetTypes} from 'app/client/ui/widgetTypes';
|
||||
import {FilterColValues} from "app/common/ActiveDocAPI";
|
||||
import {AccessLevel, ICustomWidget} from 'app/common/CustomWidget';
|
||||
import {UserAction} from 'app/common/DocActions';
|
||||
import {arrayRepeat} from 'app/common/gutil';
|
||||
|
||||
Reference in New Issue
Block a user