Skip to content
This repository was archived by the owner on Jun 4, 2024. It is now read-only.

Commit 3469aa3

Browse files
Marc-Andre-RivetShammamah Hossain
authored and
Shammamah Hossain
committed
Issue 460 - datestartswith relational operator behavior on number expression (#589)
1 parent f61e94a commit 3469aa3

File tree

5 files changed

+148
-14
lines changed

5 files changed

+148
-14
lines changed

CHANGELOG.md

+6
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,12 @@
22
All notable changes to this project will be documented in this file.
33
This project adheres to [Semantic Versioning](http://semver.org/).
44

5+
## [Unreleased]
6+
### Fixed
7+
[#460](https://github.com/plotly/dash-table/issues/460)
8+
- The `datestartswith` relational operator now supports number comparison
9+
- Fixed a bug where the implicit operator for columns was `equal` instead of the expected default for the column type
10+
511
## [4.3.0] - 2019-09-17
612
### Added
713
[#566](https://github.com/plotly/dash-table/pull/566)

src/dash-table/syntax-tree/SingleColumnSyntaxTree.ts

+15-9
Original file line numberDiff line numberDiff line change
@@ -6,19 +6,28 @@ import { LexemeType, boundLexeme } from 'core/syntax-tree/lexicon';
66
import { ColumnType, IColumn } from 'dash-table/components/Table/props';
77

88
import { fieldExpression } from './lexeme/expression';
9-
import { equal, RelationalOperator } from './lexeme/relational';
9+
import { equal, RelationalOperator, contains, dateStartsWith } from './lexeme/relational';
1010

1111
import columnLexicon from './lexicon/column';
1212

13-
function getDefaultRelationalOperator(type: ColumnType = ColumnType.Any): RelationalOperator {
13+
function getImplicitLexeme(type: ColumnType = ColumnType.Any): ILexemeResult {
1414
switch (type) {
1515
case ColumnType.Any:
1616
case ColumnType.Text:
17-
return RelationalOperator.Contains;
17+
return {
18+
lexeme: boundLexeme(contains),
19+
value: RelationalOperator.Contains
20+
};
1821
case ColumnType.Datetime:
19-
return RelationalOperator.DateStartsWith;
22+
return {
23+
lexeme: boundLexeme(dateStartsWith),
24+
value: RelationalOperator.DateStartsWith
25+
};
2026
case ColumnType.Numeric:
21-
return RelationalOperator.Equal;
27+
return {
28+
lexeme: boundLexeme(equal),
29+
value: RelationalOperator.Equal
30+
};
2231
}
2332
}
2433

@@ -49,10 +58,7 @@ function modifyLex(config: SingleColumnConfig, res: ILexerResult) {
4958
} else if (isExpression(res.lexemes)) {
5059
res.lexemes = [
5160
{ lexeme: boundLexeme(fieldExpression), value: `{${config.id}}` },
52-
{
53-
lexeme: boundLexeme(equal),
54-
value: getDefaultRelationalOperator(config.type)
55-
},
61+
getImplicitLexeme(config.type),
5662
...res.lexemes
5763
];
5864
}

src/dash-table/syntax-tree/lexeme/relational.ts

+3
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,9 @@ const DATE_OPTIONS: IDateValidation = {
8888

8989
export const dateStartsWith: IUnboundedLexeme = R.merge({
9090
evaluate: relationalEvaluator(([op, exp]) => {
91+
op = typeof op === 'number' ? op.toString() : op;
92+
exp = typeof exp === 'number' ? exp.toString() : exp;
93+
9194
const normalizedOp = normalizeDate(op, DATE_OPTIONS);
9295
const normalizedExp = normalizeDate(exp, DATE_OPTIONS);
9396

tests/cypress/tests/unit/dash_table_queries_test.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ describe('Dash Table Queries', () => {
7474

7575
describe('contains', () => {
7676
processCases(c.syntaxer, [
77-
{ name: 'cannot compare "11" to 1', query: `${c.hideOperand ? '' : '{a} '}contains 1`, target: { a: '11' }, valid: true, evaluate: true },
77+
{ name: 'compares "11" to 1', query: `${c.hideOperand ? '' : '{a} '}contains 1`, target: { a: '11' }, valid: true, evaluate: true },
7878
{ name: 'cannot compare 11 to 1', query: `${c.hideOperand ? '' : '{a} '}contains 1`, target: { a: 11 }, valid: true, evaluate: false },
7979
{ name: 'compares "11" to "1"', query: `${c.hideOperand ? '' : '{a} '}contains "1"`, target: { a: '11' }, valid: true, evaluate: true },
8080
{ name: 'compares 11 to "1"', query: `${c.hideOperand ? '' : '{a} '}contains "1"`, target: { a: 11 }, valid: true, evaluate: true },
@@ -84,8 +84,8 @@ describe('Dash Table Queries', () => {
8484
{ name: 'compares "abc" to "b"', query: `${c.hideOperand ? '' : '{a} '}contains "b"`, target: { a: 'abc' }, valid: true, evaluate: true },
8585
{ name: 'compares "abc" to " b"', query: `${c.hideOperand ? '' : '{a} '}contains " b"`, target: { a: 'abc' }, valid: true, evaluate: false },
8686
{ name: 'compares "abc" to "b "', query: `${c.hideOperand ? '' : '{a} '}contains "b "`, target: { a: 'abc' }, valid: true, evaluate: false },
87-
{ name: 'compares "abc" to " b"', query: `${c.hideOperand ? '' : '{a} '}contains " b"`, target: { a: 'a bc' }, valid: true, evaluate: true },
88-
{ name: 'compares "abc" to "b "', query: `${c.hideOperand ? '' : '{a} '}contains "b "`, target: { a: 'ab c' }, valid: true, evaluate: true }
87+
{ name: 'compares "a bc" to " b"', query: `${c.hideOperand ? '' : '{a} '}contains " b"`, target: { a: 'a bc' }, valid: true, evaluate: true },
88+
{ name: 'compares "ab c" to "b "', query: `${c.hideOperand ? '' : '{a} '}contains "b "`, target: { a: 'ab c' }, valid: true, evaluate: true }
8989
]);
9090
});
9191

@@ -96,7 +96,7 @@ describe('Dash Table Queries', () => {
9696
{ name: '0yyy in "0yyy"', query: `${c.hideOperand ? '' : '{a} '}datestartswith "0987"`, target: { a: '0987' }, valid: true, evaluate: true },
9797
{ name: 'yyyy in "yyyy"', query: `${c.hideOperand ? '' : '{a} '}datestartswith "2006"`, target: { a: '2005' }, valid: true, evaluate: false },
9898
{ name: 'yyyy in "yyyy"', query: `${c.hideOperand ? '' : '{a} '}datestartswith "2005"`, target: { a: '2005' }, valid: true, evaluate: true },
99-
{ name: 'yyyy in yyyy', query: `${c.hideOperand ? '' : '{a} '}datestartswith 2005`, target: { a: '2005' }, valid: true, evaluate: false },
99+
{ name: 'yyyy in yyyy', query: `${c.hideOperand ? '' : '{a} '}datestartswith 2005`, target: { a: '2005' }, valid: true, evaluate: true },
100100
{ name: 'yyyy-mm in "yyyy"', query: `${c.hideOperand ? '' : '{a} '}datestartswith "2005"`, target: { a: '2005-01' }, valid: true, evaluate: true },
101101
{ name: 'yyyy-mm-dd in "yyyy"', query: `${c.hideOperand ? '' : '{a} '}datestartswith "2005"`, target: { a: '2005-01-01' }, valid: true, evaluate: true },
102102
{ name: 'yyyy-mm-dd hh in "yyyy"', query: `${c.hideOperand ? '' : '{a} '}datestartswith "2005"`, target: { a: '2005-01-01T10' }, valid: true, evaluate: true },

tests/cypress/tests/unit/single_column_syntactic_tree_test.ts

+120-1
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,19 @@
11
import { SingleColumnSyntaxTree } from 'dash-table/syntax-tree';
22
import { ColumnType } from 'dash-table/components/Table/props';
33
import { SingleColumnConfig } from 'dash-table/syntax-tree/SingleColumnSyntaxTree';
4+
import { RelationalOperator } from 'dash-table/syntax-tree/lexeme/relational';
5+
import { LexemeType } from 'core/syntax-tree/lexicon';
46

57
const COLUMN_ANY: SingleColumnConfig = {
68
id: 'a',
79
type: ColumnType.Any
810
};
911

12+
const COLUMN_DATE: SingleColumnConfig = {
13+
id: 'a',
14+
type: ColumnType.Datetime
15+
};
16+
1017
const COLUMN_NUMERIC: SingleColumnConfig = {
1118
id: 'a',
1219
type: ColumnType.Numeric
@@ -72,7 +79,9 @@ describe('Single Column Syntax Tree', () => {
7279
const tree = new SingleColumnSyntaxTree('1', COLUMN_UNDEFINED);
7380

7481
expect(tree.isValid).to.equal(true);
75-
expect(tree.evaluate({ a: 1 })).to.equal(true);
82+
expect(tree.evaluate({ a: '1' })).to.equal(true);
83+
expect(tree.evaluate({ a: '2' })).to.equal(false);
84+
expect(tree.evaluate({ a: 1 })).to.equal(false);
7685
expect(tree.evaluate({ a: 2 })).to.equal(false);
7786

7887
expect(tree.toQueryString()).to.equal('{a} contains 1');
@@ -110,9 +119,119 @@ describe('Single Column Syntax Tree', () => {
110119
const tree = new SingleColumnSyntaxTree('"1"', COLUMN_TEXT);
111120

112121
expect(tree.isValid).to.equal(true);
122+
expect(tree.evaluate({ a: 1 })).to.equal(true);
123+
expect(tree.evaluate({ a: 2 })).to.equal(false);
113124
expect(tree.evaluate({ a: '1' })).to.equal(true);
114125
expect(tree.evaluate({ a: '2' })).to.equal(false);
115126

116127
expect(tree.toQueryString()).to.equal('{a} contains "1"');
117128
});
129+
130+
['1975', '"1975"'].forEach(value => {
131+
it(`can be expression '${value}' with datetime column type`, () => {
132+
const tree = new SingleColumnSyntaxTree(value, COLUMN_DATE);
133+
134+
expect(tree.evaluate({ a: 1975 })).to.equal(true);
135+
expect(tree.evaluate({ a: '1975' })).to.equal(true);
136+
expect(tree.evaluate({ a: '1975-01' })).to.equal(true);
137+
expect(tree.evaluate({ a: '1975-01-01' })).to.equal(true);
138+
expect(tree.evaluate({ a: '1975-01-01 01:01:01' })).to.equal(true);
139+
140+
expect(tree.evaluate({ a: 1976 })).to.equal(false);
141+
expect(tree.evaluate({ a: '1976' })).to.equal(false);
142+
expect(tree.evaluate({ a: '1976-01' })).to.equal(false);
143+
expect(tree.evaluate({ a: '1976-01-01' })).to.equal(false);
144+
expect(tree.evaluate({ a: '1976-01-01 01:01:01' })).to.equal(false);
145+
});
146+
});
147+
148+
[
149+
{ type: COLUMN_UNDEFINED, name: 'undefined' },
150+
{ type: COLUMN_ANY, name: 'any' },
151+
{ type: COLUMN_TEXT, name: 'text' }
152+
].forEach(({ type, name }) => {
153+
it(`returns the correct relational operator lexeme for '${name}' column type`, () => {
154+
const tree = new SingleColumnSyntaxTree('1', type);
155+
const structure = tree.toStructure();
156+
157+
expect(tree.toQueryString()).to.equal('{a} contains 1');
158+
expect(structure).to.not.equal(null);
159+
160+
if (structure) {
161+
expect(structure.value).to.equal(RelationalOperator.Contains);
162+
expect(structure.subType).to.equal(RelationalOperator.Contains);
163+
expect(structure.type).to.equal(LexemeType.RelationalOperator);
164+
165+
expect(structure.left).to.not.equal(null);
166+
if (structure.left) {
167+
expect(structure.left.type).to.equal(LexemeType.Expression);
168+
expect(structure.left.subType).to.equal('field');
169+
expect(structure.left.value).to.equal('a');
170+
}
171+
172+
expect(structure.right).to.not.equal(null);
173+
if (structure.right) {
174+
expect(structure.right.type).to.equal(LexemeType.Expression);
175+
expect(structure.right.subType).to.equal('value');
176+
expect(structure.right.value).to.equal(1);
177+
}
178+
}
179+
});
180+
});
181+
182+
it(`returns the correct relational operator lexeme for 'date' column type`, () => {
183+
const tree = new SingleColumnSyntaxTree('1975', COLUMN_DATE);
184+
const structure = tree.toStructure();
185+
186+
expect(tree.toQueryString()).to.equal('{a} datestartswith 1975');
187+
expect(structure).to.not.equal(null);
188+
189+
if (structure) {
190+
expect(structure.value).to.equal(RelationalOperator.DateStartsWith);
191+
expect(structure.subType).to.equal(RelationalOperator.DateStartsWith);
192+
expect(structure.type).to.equal(LexemeType.RelationalOperator);
193+
194+
expect(structure.left).to.not.equal(null);
195+
if (structure.left) {
196+
expect(structure.left.type).to.equal(LexemeType.Expression);
197+
expect(structure.left.subType).to.equal('field');
198+
expect(structure.left.value).to.equal('a');
199+
}
200+
201+
expect(structure.right).to.not.equal(null);
202+
if (structure.right) {
203+
expect(structure.right.type).to.equal(LexemeType.Expression);
204+
expect(structure.right.subType).to.equal('value');
205+
expect(structure.right.value).to.equal(1975);
206+
}
207+
}
208+
});
209+
210+
it(`returns the correct relational operator lexeme for 'numeric' column type`, () => {
211+
const tree = new SingleColumnSyntaxTree('1', COLUMN_NUMERIC);
212+
const structure = tree.toStructure();
213+
214+
expect(tree.toQueryString()).to.equal('{a} = 1');
215+
expect(structure).to.not.equal(null);
216+
217+
if (structure) {
218+
expect(structure.value).to.equal(RelationalOperator.Equal);
219+
expect(structure.subType).to.equal(RelationalOperator.Equal);
220+
expect(structure.type).to.equal(LexemeType.RelationalOperator);
221+
222+
expect(structure.left).to.not.equal(null);
223+
if (structure.left) {
224+
expect(structure.left.type).to.equal(LexemeType.Expression);
225+
expect(structure.left.subType).to.equal('field');
226+
expect(structure.left.value).to.equal('a');
227+
}
228+
229+
expect(structure.right).to.not.equal(null);
230+
if (structure.right) {
231+
expect(structure.right.type).to.equal(LexemeType.Expression);
232+
expect(structure.right.subType).to.equal('value');
233+
expect(structure.right.value).to.equal(1);
234+
}
235+
}
236+
});
118237
});

0 commit comments

Comments
 (0)