(core) Download as CSV button on sections

Summary: Adding "Download as CSV" button that exports filtred section data to csv

Test Plan: Browser tests

Reviewers: paulfitz, dsagal

Reviewed By: paulfitz

Differential Revision: https://phab.getgrist.com/D2830
This commit is contained in:
Jarosław Sadziński
2021-05-27 13:06:26 +02:00
parent 5c0494fe29
commit 96fee73b70
14 changed files with 311 additions and 185 deletions

View File

@@ -0,0 +1,18 @@
import { CellValue } from "app/common/DocActions";
import { FilterState, makeFilterState } from "app/common/FilterState";
export type ColumnFilterFunc = (value: CellValue) => boolean;
// Returns a filter function for a particular column: the function takes a cell value and returns
// whether it's accepted according to the given FilterState.
export function makeFilterFunc({ include, values }: FilterState): ColumnFilterFunc {
// NOTE: This logic results in complex values and their stringified JSON representations as equivalent.
// For example, a TypeError in the formula column and the string '["E","TypeError"]' would be seen as the same.
// TODO: This narrow corner case seems acceptable for now, but may be worth revisiting.
return (val: CellValue) => (values.has(Array.isArray(val) ? JSON.stringify(val) : val) === include);
}
// Given a JSON string, returns a ColumnFilterFunc
export function buildColFilter(filterJson: string | undefined): ColumnFilterFunc | null {
return filterJson ? makeFilterFunc(makeFilterState(filterJson)) : null;
}

33
app/common/FilterState.ts Normal file
View File

@@ -0,0 +1,33 @@
import { CellValue } from "app/common/DocActions";
// Filter object as stored in the db
export interface FilterSpec {
included?: CellValue[];
excluded?: CellValue[];
}
// A more efficient representation of filter state for a column than FilterSpec.
export interface FilterState {
include: boolean;
values: Set<CellValue>;
}
// Creates a FilterState. Accepts spec as a json string or a FilterSpec.
export function makeFilterState(spec: string | FilterSpec): FilterState {
if (typeof (spec) === 'string') {
return makeFilterState((spec && JSON.parse(spec)) || {});
}
return {
include: Boolean(spec.included),
values: new Set(spec.included || spec.excluded || []),
};
}
// Returns true if state and spec are equivalent, false otherwise.
export function isEquivalentFilter(state: FilterState, spec: FilterSpec): boolean {
const other = makeFilterState(spec);
if (state.include !== other.include) { return false; }
if (state.values.size !== other.values.size) { return false; }
for (const val of other.values) { if (!state.values.has(val)) { return false; } }
return true;
}

View File

@@ -0,0 +1,16 @@
import { CellValue } from "app/common/DocActions";
import { ColumnFilterFunc } from "app/common/ColumnFilterFunc";
export type RowFilterFunc<T> = (row: T) => boolean;
// Builds RowFilter for a single column
export function buildRowFilter<T>(
getter: RowValueFunc<T> | null,
filterFunc: ColumnFilterFunc | null): RowFilterFunc<T> {
if (!getter || !filterFunc) {
return () => true;
}
return (rowId: T) => filterFunc(getter(rowId));
}
export type RowValueFunc<T> = (rowId: T) => CellValue;