mirror of
https://github.com/chenasraf/redar-browser.git
synced 2026-05-18 01:59:00 +00:00
Basic filters + query parsing!!
This commit is contained in:
37
public/test.json
Normal file
37
public/test.json
Normal 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."
|
||||
}
|
||||
]
|
||||
@@ -37,3 +37,9 @@
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.filter-input {
|
||||
width: 100%;
|
||||
padding: 5px 7px;
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
@@ -4,3 +4,4 @@ export const table: string;
|
||||
export const cell: string;
|
||||
export const rowStart: string;
|
||||
export const rowEnd: string;
|
||||
export const filterInput: string;
|
||||
|
||||
@@ -5,4 +5,7 @@ export interface IProps {
|
||||
|
||||
export interface IState {
|
||||
response: any
|
||||
sortKey: string
|
||||
sortDesc: boolean
|
||||
filter: string
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
|
||||
55
src/filter-parser/filter.pegjs
Normal file
55
src/filter-parser/filter.pegjs
Normal 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
12
src/filter-parser/parser.d.ts
vendored
Normal 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
1063
src/filter-parser/parser.js
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user