Basic filters + query parsing!!

This commit is contained in:
Chen Asraf
2018-01-22 02:53:42 +02:00
parent 6fa9eba635
commit 7492300d32
8 changed files with 1279 additions and 4 deletions

37
public/test.json Normal file
View File

@@ -0,0 +1,37 @@
[{
"first_name": "Son",
"last_name": "Goku",
"age": null,
"alive": true,
"gender": "male",
"blows stuff up": true,
"comment": "This is what a comment looks like. it should be really long. blah blah blah."
},
{
"first_name": "Gwen",
"last_name": "Stacy",
"age": 25,
"alive": false,
"gender": "female",
"blows stuff up": false,
"comment": "This is what a comment looks like. it should be really long. blah blah blah."
},
{
"first_name": "Harry",
"last_name": "Potter",
"age": 36,
"alive": true,
"gender": "male",
"blows stuff up": true,
"comment": "This is what a comment looks like. it should be really long. blah blah blah."
},
{
"first_name": "Spider",
"last_name": "Man",
"age": 30,
"alive": true,
"gender": "male",
"blows stuff up": null,
"comment": "This is what a comment looks like. it should be really long. blah blah blah."
}
]

View File

@@ -37,3 +37,9 @@
display: none;
}
}
.filter-input {
width: 100%;
padding: 5px 7px;
font-size: 1em;
}

View File

@@ -4,3 +4,4 @@ export const table: string;
export const cell: string;
export const rowStart: string;
export const rowEnd: string;
export const filterInput: string;

View File

@@ -5,4 +5,7 @@ export interface IProps {
export interface IState {
response: any
sortKey: string
sortDesc: boolean
filter: string
}

View File

@@ -4,13 +4,19 @@ import * as I from './ResponseRepr.module'
import * as D from 'common/Dispatcher'
import RObject from 'components/RObject/RObject'
import * as classNames from 'classnames'
import { parse } from '../../filter-parser/parser'
class ResponseRepr extends React.Component<I.IProps, I.IState> {
private listeners: string[]
constructor(props: I.IProps) {
super(props)
const response = props.store.get(D.StoreKeys.Response, {})
this.state = {
response: props.store.get(D.StoreKeys.Response, {})
response,
sortKey: Object.keys(response[0] || {})[0],
sortDesc: false,
filter: '',
}
}
@@ -36,6 +42,94 @@ class ResponseRepr extends React.Component<I.IProps, I.IState> {
this.listeners.forEach(listener => D.AppDispatcher.unregister(listener))
}
private sortBy(key: string) {
this.setState((cur) => ({
sortKey: key,
sortDesc: Boolean(cur.sortKey === key && !cur.sortDesc)
}))
}
private columns() {
const keys = Object.keys(this.state.response[0] || {})
return keys.map((key) => {
return (
<h3 key={`col-th-${key}`}
onClick={() => this.sortBy(key)}>
{key}
</h3>
)
})
}
private processedResponse() {
let response = this.state.response || []
if (!response.length) {
return []
}
if (response[0].hasOwnProperty(this.state.sortKey)) {
const desc = this.state.sortDesc
const key = this.state.sortKey
response = response.sort((a, b) => {
// numbers are matching
if (typeof a[key] === 'number' && typeof b === 'number' ||
isFinite(a[key]) && isFinite(b[key])) {
const out = parseFloat(a[key]) - parseFloat(b[key])
return desc ? -out : out
}
if (a[key] > b[key]) {
return desc ? -1 : 1
// tslint:disable-next-line:triple-equals
} else if (a[key] == b[key]) {
return 0
} else if (a[key] < b[key]) {
return desc ? 1 : -1
}
})
}
if (this.state.filter.trim().length) {
const filter = this.state.filter.trim().replace(/\s{2,}/g, ' ')
try {
const filterOps = parse(filter)
console.debug({filterOps})
response = response.filter((row) => {
for (const key in filterOps) {
if (!filterOps.hasOwnProperty(key)) {
continue
}
const { oper, value } = filterOps[key]
switch (oper) {
case '>':
return row[key] > value
case '>=':
return row[key] >= value
case '<':
return row[key] < value
case '<=':
return row[key] <= value
case '=':
// tslint:disable-next-line:triple-equals
return row[key] == value
case '!=':
// tslint:disable-next-line:triple-equals
return row[key] != value
default:
return true
}
}
})
} catch (e) {
console.warn(e)
}
}
return response
}
private getRObjectList() {
const { response } = this.state
let colAmt
@@ -46,8 +140,8 @@ class ResponseRepr extends React.Component<I.IProps, I.IState> {
return (
<div className={css.table}
style={{gridTemplateColumns: `repeat(${colAmt}, auto)`}}>
{keys.map((key) => <h3 key={`col-th-${key}`}>{key}</h3>)}
{response.map((row, i) => {
{this.columns()}
{this.processedResponse().map((row, i) => {
const cls = (j) => {
return classNames(css.cell, {
[css.rowStart]: j % colAmt === 0,
@@ -69,7 +163,7 @@ class ResponseRepr extends React.Component<I.IProps, I.IState> {
return (
<div className={css.table}
style={{gridTemplateRows: `repeat(${colAmt}, auto)`}}>
style={{gridTemplateRows: `repeat(${colAmt}, auto)`}}>}
<RObject data={response} />
</div>
)
@@ -85,6 +179,10 @@ class ResponseRepr extends React.Component<I.IProps, I.IState> {
return (
<div className={className}>
<input className={css.filterInput}
type="text" value={this.state.filter}
placeholder={`Filter data (e.g.: first_name="John" age>=32)`}
onChange={(e) => this.setState({ filter: e.target.value })} />
{repr}
</div>
)

View File

@@ -0,0 +1,55 @@
Chain
= all:Equals+ {
let filters = {}
all.forEach(i => filters[i[0]] = { oper: i[1], value: i[2] })
return filters
}
Equals
= _ key:Filter _ operator:Operator _ value:Value _ {
return [key, operator, value]
}
Operator "comparison operator =, !=, >, >=, <, or <="
= eq:"="
/ neq1:[\<\>!]"=" { return neq1 + '=' }
/ neq2:[\<\>]
Filter "field name"
= $ [a-z0-9_.]i+
Value "string or number"
= Number
/ StringValue
Number
= int:([0-9]+) "." dec:([0-9]+) {
return parseFloat([int.join(''), dec.join('') || '0'].join('.'))
}
/ int:([0-9]+) { return parseInt(int.join('')) }
StringValue
= '"' chars:DoubleStringCharacter* '"' { return chars.join(''); }
/ "'" chars:SingleStringCharacter* "'" { return chars.join(''); }
DoubleStringCharacter
= !('"' / "\\") char:. { return char; }
/ "\\" sequence:EscapeSequence { return sequence; }
SingleStringCharacter
= !("'" / "\\") char:. { return char; }
/ "\\" sequence:EscapeSequence { return sequence; }
EscapeSequence
= "'"
/ '"'
/ "\\"
/ "b" { return "\b"; }
/ "f" { return "\f"; }
/ "n" { return "\n"; }
/ "r" { return "\r"; }
/ "t" { return "\t"; }
/ "v" { return "\x0B"; }
_ "whitespace"
= $ [ \t]*

12
src/filter-parser/parser.d.ts vendored Normal file
View File

@@ -0,0 +1,12 @@
export function parse<T extends FilterResultValue = FilterResultValue>(input: string): FilterResult<T>
export type Oper = '=' | '>' | '<' | '>=' | '<=' | '!='
export type Value = string | number
export interface FilterResultValue {
oper: Oper
value: Value
}
export interface FilterResult<T extends FilterResultValue = FilterResultValue> {
[filter: string]: FilterResultValue
}

1063
src/filter-parser/parser.js Normal file

File diff suppressed because it is too large Load Diff