mirror of
https://github.com/gristlabs/grist-core.git
synced 2026-03-02 04:09:24 +00:00
(core) Adding sort options for columns.
Summary: Adding sort options for columns. - Sort menu has a new option "More sort options" that opens up Sort left menu - Each sort entry has an additional menu with 3 options -- Order by choice index (for the Choice column, orders by choice position) -- Empty last (puts empty values last in ascending order, first in descending order) -- Natural sort (for Text column, compares strings with numbers as numbers) Updated also CSV/Excel export and api sorting. Most of the changes in this diff is a sort expression refactoring. Pulling out all the methods that works on sortExpression array into a single namespace. Test Plan: Browser tests Reviewers: alexmojaki Reviewed By: alexmojaki Subscribers: dsagal, alexmojaki Differential Revision: https://phab.getgrist.com/D3077
This commit is contained in:
40
sandbox/grist/sort_specs.py
Normal file
40
sandbox/grist/sort_specs.py
Normal file
@@ -0,0 +1,40 @@
|
||||
COL_SEPARATOR = ":"
|
||||
|
||||
"""
|
||||
Helper module for sort expressions.
|
||||
Sort expressions are encoded as a positive number for ascending column,
|
||||
negative number for descending column. Can also be encoded as strings in a form:
|
||||
'-1:flag' or '1:flag;flag'
|
||||
Flags can be:
|
||||
- emptyLast to put empty values at the end.
|
||||
- orderByChoice: to order column by choice entry index rather then choice value.
|
||||
- naturalSort: to treat strings containing numbers as numbers and sort them accordingly.
|
||||
"""
|
||||
|
||||
def col_ref(col_spec):
|
||||
"""
|
||||
Gets column row id from column expression
|
||||
"""
|
||||
return abs(col_spec if isinstance(col_spec, int) else int(col_spec.split(COL_SEPARATOR)[0]))
|
||||
|
||||
def direction(col_spec):
|
||||
"""
|
||||
Gets direction for column expression (1 for ascending - 1 for descending).
|
||||
"""
|
||||
if isinstance(col_spec, int):
|
||||
return 1 if col_spec >= 0 else -1
|
||||
else:
|
||||
assert col_spec
|
||||
return 1 if col_spec[0] != "-" else -1
|
||||
|
||||
def swap_col_ref(col_spec, new_col_ref):
|
||||
"""
|
||||
Swaps colRef in colSpec preserving direction and options (used for display columns).
|
||||
"""
|
||||
new_spec = direction(col_spec) * new_col_ref
|
||||
if isinstance(col_spec, int):
|
||||
return new_spec
|
||||
else:
|
||||
parts = col_spec.split(COL_SEPARATOR)
|
||||
parts[0] = str(new_spec)
|
||||
return COL_SEPARATOR.join(parts)
|
||||
@@ -5,6 +5,8 @@ import re
|
||||
import six
|
||||
|
||||
from column import is_visible_column
|
||||
import sort_specs
|
||||
|
||||
import logger
|
||||
log = logger.Logger(__name__, logger.INFO)
|
||||
|
||||
@@ -78,13 +80,14 @@ def _update_sort_spec(sort_spec, old_table, new_table):
|
||||
# When adjusting, we take a possibly negated old colRef, and produce a new colRef.
|
||||
# If anything is gone, we return 0, which will be excluded from the new sort spec.
|
||||
def adjust(col_spec):
|
||||
sign = 1 if col_spec >= 0 else -1
|
||||
return sign * new_cols_map.get(old_cols_map.get(abs(col_spec)), 0)
|
||||
old_colref = sort_specs.col_ref(col_spec)
|
||||
new_colref = new_cols_map.get(old_cols_map.get(old_colref), 0)
|
||||
return sort_specs.swap_col_ref(col_spec, new_colref)
|
||||
|
||||
try:
|
||||
old_sort_spec = json.loads(sort_spec)
|
||||
new_sort_spec = [adjust(col_spec) for col_spec in old_sort_spec]
|
||||
new_sort_spec = [col_spec for col_spec in new_sort_spec if col_spec]
|
||||
new_sort_spec = [col_spec for col_spec in new_sort_spec if sort_specs.col_ref(col_spec)]
|
||||
return json.dumps(new_sort_spec, separators=(',', ':'))
|
||||
except Exception:
|
||||
log.warn("update_summary_section: can't parse sortColRefs JSON; clearing sortColRefs")
|
||||
|
||||
36
sandbox/grist/test_sort_spec.py
Normal file
36
sandbox/grist/test_sort_spec.py
Normal file
@@ -0,0 +1,36 @@
|
||||
# coding=utf-8
|
||||
import unittest
|
||||
|
||||
import sort_specs
|
||||
|
||||
class TestSortSpec(unittest.TestCase):
|
||||
def test_direction(self):
|
||||
self.assertEqual(sort_specs.direction(1), 1)
|
||||
self.assertEqual(sort_specs.direction(-1), -1)
|
||||
self.assertEqual(sort_specs.direction('1'), 1)
|
||||
self.assertEqual(sort_specs.direction('-1'), -1)
|
||||
self.assertEqual(sort_specs.direction('1:emptyLast'), 1)
|
||||
self.assertEqual(sort_specs.direction('1:emptyLast;orderByChoice'), 1)
|
||||
self.assertEqual(sort_specs.direction('-1:emptyLast;orderByChoice'), -1)
|
||||
|
||||
def test_col_ref(self):
|
||||
self.assertEqual(sort_specs.col_ref(1), 1)
|
||||
self.assertEqual(sort_specs.col_ref(-1), 1)
|
||||
self.assertEqual(sort_specs.col_ref('1'), 1)
|
||||
self.assertEqual(sort_specs.col_ref('-1'), 1)
|
||||
self.assertEqual(sort_specs.col_ref('1:emptyLast'), 1)
|
||||
self.assertEqual(sort_specs.col_ref('1:emptyLast;orderByChoice'), 1)
|
||||
self.assertEqual(sort_specs.col_ref('-1:emptyLast;orderByChoice'), 1)
|
||||
|
||||
def test_swap_col_ref(self):
|
||||
self.assertEqual(sort_specs.swap_col_ref(1, 2), 2)
|
||||
self.assertEqual(sort_specs.swap_col_ref(-1, 2), -2)
|
||||
self.assertEqual(sort_specs.swap_col_ref('1', 2), '2')
|
||||
self.assertEqual(sort_specs.swap_col_ref('-1', 2), '-2')
|
||||
self.assertEqual(sort_specs.swap_col_ref('1:emptyLast', 2), '2:emptyLast')
|
||||
self.assertEqual(
|
||||
sort_specs.swap_col_ref('1:emptyLast;orderByChoice', 2),
|
||||
'2:emptyLast;orderByChoice')
|
||||
self.assertEqual(
|
||||
sort_specs.swap_col_ref('-1:emptyLast;orderByChoice', 2),
|
||||
'-2:emptyLast;orderByChoice')
|
||||
@@ -11,6 +11,7 @@ import acl
|
||||
from acl_formula import parse_acl_formula_json
|
||||
import actions
|
||||
import column
|
||||
import sort_specs
|
||||
import identifiers
|
||||
from objtypes import strict_equal, encode_object
|
||||
import schema
|
||||
@@ -877,7 +878,8 @@ class UserActions(object):
|
||||
for section in parent_sections:
|
||||
# Only iterates once for each section. Updated sort removes all columns being deleted.
|
||||
sort = json.loads(section.sortColRefs) if section.sortColRefs else []
|
||||
updated_sort = [sort_ref for sort_ref in sort if abs(sort_ref) not in removed_col_refs]
|
||||
updated_sort = [col_spec for col_spec in sort
|
||||
if sort_specs.col_ref(col_spec) not in removed_col_refs]
|
||||
if sort != updated_sort:
|
||||
re_sort_sections.append(section)
|
||||
re_sort_specs.append(json.dumps(updated_sort))
|
||||
|
||||
Reference in New Issue
Block a user