diff --git a/lattice/src/App/MoleculaTables/MoleculaTable/MoleculaTable.tsx b/lattice/src/App/MoleculaTables/MoleculaTable/MoleculaTable.tsx index cfe2d6dc8..0946b3d0e 100644 --- a/lattice/src/App/MoleculaTables/MoleculaTable/MoleculaTable.tsx +++ b/lattice/src/App/MoleculaTables/MoleculaTable/MoleculaTable.tsx @@ -203,7 +203,7 @@ export const MoleculaTable: FC = ({ .map((field) => { const { name, options, cardinality } = field; const { type, keys, bitDepth, ...rest } = options; - const showKeys = ['set', 'time'].includes(type); + const showKeys = ['set', 'time', 'mutex'].includes(type); return ( diff --git a/lattice/src/App/QueryBuilder/QueryBuilder.tsx b/lattice/src/App/QueryBuilder/QueryBuilder.tsx index ef83a787b..7f948b1ca 100644 --- a/lattice/src/App/QueryBuilder/QueryBuilder.tsx +++ b/lattice/src/App/QueryBuilder/QueryBuilder.tsx @@ -147,7 +147,8 @@ export const QueryBuilder: FC = ({ field: '', rowOperator: '=', value: '', - type: 'set' + type: 'set', + keys: true } ]; diff --git a/lattice/src/App/QueryBuilder/RowCall/RowCall.tsx b/lattice/src/App/QueryBuilder/RowCall/RowCall.tsx index 1b0279097..cd0501060 100644 --- a/lattice/src/App/QueryBuilder/RowCall/RowCall.tsx +++ b/lattice/src/App/QueryBuilder/RowCall/RowCall.tsx @@ -40,9 +40,8 @@ export const RowCall: FC = ({ onRemoveGroup }) => { const [operatorEl, setOperatorEl] = useState(null); - const [activeOperatorEl, setActiveOperatorEl] = useState( - null - ); + const [activeOperatorEl, setActiveOperatorEl] = + useState(null); const [anchorEl, setAnchorEl] = useState(null); const [activeAnchor, setActiveAnchor] = useState(); @@ -51,7 +50,8 @@ export const RowCall: FC = ({ field: '', rowOperator: '=', value: '', - type: 'set' + type: 'set', + keys: true }; if (op) { @@ -106,7 +106,10 @@ export const RowCall: FC = ({ {rowData.map((row, idx) => { - const { field, rowOperator, value, type } = row; + const { field, rowOperator, value, keys } = row; + const type = ['set', 'time', 'mutex'].includes(row.type) + ? `${row.type}-${keys ? 'keys' : 'id'}` + : row.type; let isInvalidValue = !value; if (rowOperator === 'cidr') { try { @@ -156,21 +159,24 @@ export const RowCall: FC = ({ options={fields.map((field) => { return { label: `${field.name} (${field.options.type})`, - value: field.name, - disabled: - !Object.keys(operators).includes(field.options.type) || - (field.options.type === 'set' && !field.options.keys) + value: field.name }; })} onChange={(value) => { const updatedField = fields.find((f) => f.name === value); const rowUpdate = row.type === updatedField.options.type - ? { ...row, field: value, value: row.value } + ? { + ...row, + field: value, + value: row.value, + keys: updatedField.options.keys + } : { field: value, rowOperator: '=', type: updatedField.options.type, + keys: updatedField.options.keys, value: updatedField.options.type === 'timestamp' ? moment.utc().format() @@ -217,19 +223,7 @@ export const RowCall: FC = ({ - {['int', 'set'].includes(type) ? ( - - onRowUpdate(idx, { ...row, value: event.target.value }) - } - /> - ) : type === 'timestamp' ? ( + {type === 'timestamp' ? ( = ({ fullWidth /> - ) : null} + ) : ( + + onRowUpdate(idx, { ...row, value: event.target.value }) + } + /> + )} {rowData.length > 1 ? ( ', value: '>' }, + { label: '<', value: '<' }, + { label: '>=', value: '>=' }, + { label: '<=', value: '<=' }, + { label: '==', value: '=' }, + { label: '!=', value: '!=' }, + ], int: [ { label: '>', value: '>' }, { label: '<', value: '<' }, @@ -7,12 +15,35 @@ export const operators = { { label: '==', value: '=' }, { label: '!=', value: '!=' }, ], - set: [ + 'mutex-id': [ + { label: 'is', value: '=' }, + { label: 'is not', value: '!=' } + ], + 'mutex-keys': [ { label: 'is', value: '=' }, { label: 'is not', value: '!=' }, { label: 'like', value: 'like' }, { label: 'CIDR', value: 'cidr' } ], + "set-id": [ + { label: 'is', value: '=' }, + { label: 'is not', value: '!=' } + ], + "set-keys": [ + { label: 'is', value: '=' }, + { label: 'is not', value: '!=' }, + { label: 'like', value: 'like' }, + { label: 'CIDR', value: 'cidr' } + ], + "time-id": [ + { label: 'is', value: '=' }, + { label: 'is not', value: '!=' } + ], + "time-keys": [ + { label: 'is', value: '=' }, + { label: 'is not', value: '!=' }, + { label: 'like', value: 'like' } + ], timestamp: [ { label: 'is before', value: '<' }, { label: 'is after', value: '>' }, diff --git a/lattice/src/App/QueryBuilder/rowTypes.ts b/lattice/src/App/QueryBuilder/rowTypes.ts index 066da68ad..b4808cd0d 100644 --- a/lattice/src/App/QueryBuilder/rowTypes.ts +++ b/lattice/src/App/QueryBuilder/rowTypes.ts @@ -12,7 +12,8 @@ export type RowCallType = { field: string; rowOperator: string; value: string; - type: 'set' | 'int' | 'timestamp'; + type: string; + keys: boolean; }; export type RowsCallType = { diff --git a/lattice/src/App/QueryBuilder/stringifyRowData.test.ts b/lattice/src/App/QueryBuilder/stringifyRowData.test.ts index 4ace4d7a9..d6cb4b586 100644 --- a/lattice/src/App/QueryBuilder/stringifyRowData.test.ts +++ b/lattice/src/App/QueryBuilder/stringifyRowData.test.ts @@ -1,13 +1,14 @@ import { stringifyRowData } from './stringifyRowData'; -describe('int types', () => { +describe('decimal types', () => { it('stringifies = operator', () => { expect(stringifyRowData([{ row: [{ "field": "fieldName", "rowOperator": "=", "value": "0", - "type": "int" + "type": "decimal", + "keys": undefined }] }])).toEqual({ error: false, query: 'Row(fieldName=0)' }) }); @@ -18,7 +19,8 @@ describe('int types', () => { "field": "fieldName", "rowOperator": "!=", "value": "0", - "type": "int" + "type": "decimal", + "keys": undefined }] }])).toEqual({ error: false, query: 'Not(Row(fieldName=0))' }) }); @@ -29,7 +31,8 @@ describe('int types', () => { "field": "fieldName", "rowOperator": ">", "value": "0", - "type": "int" + "type": "decimal", + "keys": undefined }] }])).toEqual({ error: false, query: 'Row(fieldName>0)' }) }); @@ -40,32 +43,148 @@ describe('int types', () => { "field": "fieldName", "rowOperator": ">=", "value": "0", - "type": "int" + "type": "decimal", + "keys": undefined }] }])).toEqual({ error: false, query: 'Row(fieldName>=0)' }) }); - it('stringifies negated operatorations', () => { + it('stringifies negated operations', () => { expect(stringifyRowData([{ row: [{ "field": "fieldName", "rowOperator": ">=", "value": "0", - "type": "int" + "type": "decimal", + "keys": undefined }], isNot: true }])).toEqual({ error: false, query: 'Not(Row(fieldName>=0))' }) }); -}) +}); -describe('set types', () => { +describe('int types', () => { + it('stringifies = operator', () => { + expect(stringifyRowData([{ + row: [{ + "field": "fieldName", + "rowOperator": "=", + "value": "0", + "type": "int", + "keys": undefined + }] + }])).toEqual({ error: false, query: 'Row(fieldName=0)' }) + }); + + it('stringifies != operator', () => { + expect(stringifyRowData([{ + row: [{ + "field": "fieldName", + "rowOperator": "!=", + "value": "0", + "type": "int", + "keys": undefined + }] + }])).toEqual({ error: false, query: 'Not(Row(fieldName=0))' }) + }); + + it('stringifies > operator', () => { + expect(stringifyRowData([{ + row: [{ + "field": "fieldName", + "rowOperator": ">", + "value": "0", + "type": "int", + "keys": undefined + }] + }])).toEqual({ error: false, query: 'Row(fieldName>0)' }) + }); + + it('stringifies >= operator', () => { + expect(stringifyRowData([{ + row: [{ + "field": "fieldName", + "rowOperator": ">=", + "value": "0", + "type": "int", + "keys": undefined + }] + }])).toEqual({ error: false, query: 'Row(fieldName>=0)' }) + }); + + it('stringifies negated operations', () => { + expect(stringifyRowData([{ + row: [{ + "field": "fieldName", + "rowOperator": ">=", + "value": "0", + "type": "int", + "keys": undefined + }], + isNot: true + }])).toEqual({ error: false, query: 'Not(Row(fieldName>=0))' }) + }); +}); + +describe('mutex (id) types', () => { + it('stringifies is operator', () => { + expect(stringifyRowData([{ + row: [{ + "field": "fieldName", + "rowOperator": "=", + "value": "1234", + "type": "mutex", + "keys": undefined + }] + }])).toEqual({ error: false, query: 'Row(fieldName=1234)' }) + }); + + it('stringifies is operator for multiple values', () => { + expect(stringifyRowData([{ + row: [{ + "field": "fieldName", + "rowOperator": "=", + "value": "1234, 5678", + "type": "mutex", + "keys": undefined + }] + }])).toEqual({ error: false, query: 'Union(Row(fieldName=1234), Row(fieldName=5678))' }) + }); + + it('stringifies is not operator', () => { + expect(stringifyRowData([{ + row: [{ + "field": "fieldName", + "rowOperator": "!=", + "value": "1234", + "type": "mutex", + "keys": undefined + }] + }])).toEqual({ error: false, query: 'Not(Row(fieldName=1234))' }) + }); + + it('stringifies is not operator for multiple values', () => { + expect(stringifyRowData([{ + row: [{ + "field": "fieldName", + "rowOperator": "!=", + "value": "1234, 5678", + "type": "mutex", + "keys": undefined + }] + }])).toEqual({ error: false, query: 'Not(Union(Row(fieldName=1234), Row(fieldName=5678)))' }) + }); +}); + +describe('mutex (keys) types', () => { it('stringifies is operator', () => { expect(stringifyRowData([{ row: [{ "field": "fieldName", "rowOperator": "=", "value": "value", - "type": "set" + "type": "mutex", + "keys": true }] }])).toEqual({ error: false, query: 'Row(fieldName="value")' }) }); @@ -76,7 +195,8 @@ describe('set types', () => { "field": "fieldName", "rowOperator": "=", "value": "one, two", - "type": "set" + "type": "mutex", + "keys": true }] }])).toEqual({ error: false, query: 'Union(Row(fieldName="one"), Row(fieldName="two"))' }) }); @@ -87,7 +207,8 @@ describe('set types', () => { "field": "fieldName", "rowOperator": "!=", "value": "value", - "type": "set" + "type": "mutex", + "keys": true }] }])).toEqual({ error: false, query: 'Not(Row(fieldName="value"))' }) }); @@ -98,7 +219,8 @@ describe('set types', () => { "field": "fieldName", "rowOperator": "!=", "value": "one, two", - "type": "set" + "type": "mutex", + "keys": true }] }])).toEqual({ error: false, query: 'Not(Union(Row(fieldName="one"), Row(fieldName="two")))' }) }); @@ -109,7 +231,8 @@ describe('set types', () => { "field": "fieldName", "rowOperator": "like", "value": "value%", - "type": "set" + "type": "mutex", + "keys": true }] }])).toEqual({ error: false, query: 'UnionRows(Rows(field=fieldName, like="value%"))' }) }); @@ -120,7 +243,8 @@ describe('set types', () => { "field": "fieldName", "rowOperator": "like", "value": "value", - "type": "set" + "type": "mutex", + "keys": true }] }])).toEqual({ error: false, query: 'UnionRows(Rows(field=fieldName, like="%value%"))' }) }); @@ -131,24 +255,328 @@ describe('set types', () => { "field": "fieldName", "rowOperator": "cidr", "value": "10.164.124.33/30", - "type": "set" + "type": "mutex", + "keys": true }] }])).toEqual({ error: false, query: 'Union(Row(fieldName="10.164.124.32"), Row(fieldName="10.164.124.33"), Row(fieldName="10.164.124.34"), Row(fieldName="10.164.124.35"))' }) }); - it('stringifies negated operatorations', () => { + it('stringifies negated operations', () => { expect(stringifyRowData([{ row: [{ "field": "fieldName", "rowOperator": "like", "value": "value", - "type": "set" + "type": "mutex", + "keys": true }], isNot: true }])).toEqual({ error: false, query: 'Not(UnionRows(Rows(field=fieldName, like="%value%")))' }) }); }); +describe('set (id) types', () => { + it('stringifies is operator', () => { + expect(stringifyRowData([{ + row: [{ + "field": "fieldName", + "rowOperator": "=", + "value": "1234", + "type": "set", + "keys": undefined + }] + }])).toEqual({ error: false, query: 'Row(fieldName=1234)' }) + }); + + it('stringifies is operator for multiple values', () => { + expect(stringifyRowData([{ + row: [{ + "field": "fieldName", + "rowOperator": "=", + "value": "1234, 5678", + "type": "set", + "keys": undefined + }] + }])).toEqual({ error: false, query: 'Union(Row(fieldName=1234), Row(fieldName=5678))' }) + }); + + it('stringifies is not operator', () => { + expect(stringifyRowData([{ + row: [{ + "field": "fieldName", + "rowOperator": "!=", + "value": "1234", + "type": "set", + "keys": undefined + }] + }])).toEqual({ error: false, query: 'Not(Row(fieldName=1234))' }) + }); + + it('stringifies is not operator for multiple values', () => { + expect(stringifyRowData([{ + row: [{ + "field": "fieldName", + "rowOperator": "!=", + "value": "1234, 5678", + "type": "set", + "keys": undefined + }] + }])).toEqual({ error: false, query: 'Not(Union(Row(fieldName=1234), Row(fieldName=5678)))' }) + }); +}); + +describe('set (keys) types', () => { + it('stringifies is operator', () => { + expect(stringifyRowData([{ + row: [{ + "field": "fieldName", + "rowOperator": "=", + "value": "value", + "type": "set", + "keys": true + }] + }])).toEqual({ error: false, query: 'Row(fieldName="value")' }) + }); + + it('stringifies is operator for multiple values', () => { + expect(stringifyRowData([{ + row: [{ + "field": "fieldName", + "rowOperator": "=", + "value": "one, two", + "type": "set", + "keys": true + }] + }])).toEqual({ error: false, query: 'Union(Row(fieldName="one"), Row(fieldName="two"))' }) + }); + + it('stringifies is not operator', () => { + expect(stringifyRowData([{ + row: [{ + "field": "fieldName", + "rowOperator": "!=", + "value": "value", + "type": "set", + "keys": true + }] + }])).toEqual({ error: false, query: 'Not(Row(fieldName="value"))' }) + }); + + it('stringifies is not operator for multiple values', () => { + expect(stringifyRowData([{ + row: [{ + "field": "fieldName", + "rowOperator": "!=", + "value": "one, two", + "type": "set", + "keys": true + }] + }])).toEqual({ error: false, query: 'Not(Union(Row(fieldName="one"), Row(fieldName="two")))' }) + }); + + it('stringifies like operator with a wildcard', () => { + expect(stringifyRowData([{ + row: [{ + "field": "fieldName", + "rowOperator": "like", + "value": "value%", + "type": "set", + "keys": true + }] + }])).toEqual({ error: false, query: 'UnionRows(Rows(field=fieldName, like="value%"))' }) + }); + + it('stringifies like operator without a wildcard', () => { + expect(stringifyRowData([{ + row: [{ + "field": "fieldName", + "rowOperator": "like", + "value": "value", + "type": "set", + "keys": true + }] + }])).toEqual({ error: false, query: 'UnionRows(Rows(field=fieldName, like="%value%"))' }) + }); + + it('stringifies CIDR operator', () => { + expect(stringifyRowData([{ + row: [{ + "field": "fieldName", + "rowOperator": "cidr", + "value": "10.164.124.33/30", + "type": "set", + "keys": true + }] + }])).toEqual({ error: false, query: 'Union(Row(fieldName="10.164.124.32"), Row(fieldName="10.164.124.33"), Row(fieldName="10.164.124.34"), Row(fieldName="10.164.124.35"))' }) + }); + + it('stringifies negated operations', () => { + expect(stringifyRowData([{ + row: [{ + "field": "fieldName", + "rowOperator": "like", + "value": "value", + "type": "set", + "keys": true + }], + isNot: true + }])).toEqual({ error: false, query: 'Not(UnionRows(Rows(field=fieldName, like="%value%")))' }) + }); +}); + +describe('time (ID) types', () => { + it('stringifies is operators', () => { + expect(stringifyRowData([{ + row: [{ + "field": "fieldName", + "rowOperator": "=", + "value": "1234", + "type": "time", + "keys": undefined + }] + }])).toEqual({ error: false, query: 'Row(fieldName=1234)' }) + }); + + it('stringifies is not operators', () => { + expect(stringifyRowData([{ + row: [{ + "field": "fieldName", + "rowOperator": "!=", + "value": "1234", + "type": "time", + "keys": undefined + }] + }])).toEqual({ error: false, query: 'Not(Row(fieldName=1234))' }) + }); + + it('stringifies negated operators', () => { + expect(stringifyRowData([{ + row: [{ + "field": "fieldName", + "rowOperator": "=", + "value": "1234", + "type": "time", + "keys": undefined + }], + isNot: true + }])).toEqual({ error: false, query: 'Not(Row(fieldName=1234))' }) + }); +}); + +describe('time (keys) types', () => { + it('stringifies is operators', () => { + expect(stringifyRowData([{ + row: [{ + "field": "fieldName", + "rowOperator": "=", + "value": "1234", + "type": "time", + "keys": true + }] + }])).toEqual({ error: false, query: 'Row(fieldName="1234")' }) + }); + + it('stringifies is not operators', () => { + expect(stringifyRowData([{ + row: [{ + "field": "fieldName", + "rowOperator": "!=", + "value": "1234", + "type": "time", + "keys": true + }] + }])).toEqual({ error: false, query: 'Not(Row(fieldName="1234"))' }) + }); + + it('stringifies like operators with a wildcard', () => { + expect(stringifyRowData([{ + row: [{ + "field": "fieldName", + "rowOperator": "like", + "value": "%1234", + "type": "time", + "keys": true + }] + }])).toEqual({ error: false, query: 'UnionRows(Rows(field=fieldName, like="%1234"))' }) + }); + + it('stringifies like operators without a wildcard', () => { + expect(stringifyRowData([{ + row: [{ + "field": "fieldName", + "rowOperator": "like", + "value": "1234", + "type": "time", + "keys": true + }] + }])).toEqual({ error: false, query: 'UnionRows(Rows(field=fieldName, like="%1234%"))' }) + }); + + it('stringifies negated operators', () => { + expect(stringifyRowData([{ + row: [{ + "field": "fieldName", + "rowOperator": "=", + "value": "1234", + "type": "time", + "keys": true + }], + isNot: true + }])).toEqual({ error: false, query: 'Not(Row(fieldName="1234"))' }) + }); +}); + +describe('timestamp types', () => { + it('stringifies is operators', () => { + expect(stringifyRowData([{ + row: [{ + "field": "fieldName", + "rowOperator": "=", + "value": "2021-06-04T12:00:00Z", + "type": "timestamp", + "keys": undefined + }], + }])).toEqual({ error: false, query: 'Row(fieldName="2021-06-04T12:00:00Z")' }) + }); + + it('stringifies is before operators', () => { + expect(stringifyRowData([{ + row: [{ + "field": "fieldName", + "rowOperator": "<", + "value": "2021-06-04T12:00:00Z", + "type": "timestamp", + "keys": undefined + }], + }])).toEqual({ error: false, query: 'Row(fieldName<"2021-06-04T12:00:00Z")' }) + }); + + it('stringifies is after operators', () => { + expect(stringifyRowData([{ + row: [{ + "field": "fieldName", + "rowOperator": ">", + "value": "2021-06-04T12:00:00Z", + "type": "timestamp", + "keys": undefined + }], + }])).toEqual({ error: false, query: 'Row(fieldName>"2021-06-04T12:00:00Z")' }) + }); + + it('stringifies negated operators', () => { + expect(stringifyRowData([{ + row: [{ + "field": "fieldName", + "rowOperator": "=", + "value": "2021-06-04T12:00:00Z", + "type": "timestamp", + "keys": undefined + }], + isNot: true + }])).toEqual({ error: false, query: 'Not(Row(fieldName="2021-06-04T12:00:00Z"))' }) + }); +}); + describe('mixed types', () => { it('stringifies multiple and-ed rows', () => { expect(stringifyRowData([{ @@ -156,12 +584,14 @@ describe('mixed types', () => { "field": "stringType", "rowOperator": "like", "value": "value", - "type": "set" + "type": "set", + "keys": true }, { "field": "intType", "rowOperator": "=", "value": "0", - "type": "int" + "type": "int", + "keys": undefined }], operator: "and" }])).toEqual({ error: false, query: 'Intersect(UnionRows(Rows(field=stringType, like="%value%")), Row(intType=0))' }) @@ -173,12 +603,14 @@ describe('mixed types', () => { "field": "stringType", "rowOperator": "like", "value": "value", - "type": "set" + "type": "set", + "keys": true }, { "field": "intType", "rowOperator": "=", "value": "0", - "type": "int" + "type": "int", + "keys": undefined }], operator: "or" }])).toEqual({ error: false, query: 'Union(UnionRows(Rows(field=stringType, like="%value%")), Row(intType=0))' }) @@ -190,12 +622,14 @@ describe('mixed types', () => { "field": "stringType", "rowOperator": "like", "value": "value", - "type": "set" + "type": "set", + "keys": true }, { "field": "intType", "rowOperator": "=", "value": "0", - "type": "int" + "type": "int", + "keys": undefined }], operator: "or", isNot: true diff --git a/lattice/src/App/QueryBuilder/stringifyRowData.ts b/lattice/src/App/QueryBuilder/stringifyRowData.ts index c03ea9da4..5b06c88c9 100644 --- a/lattice/src/App/QueryBuilder/stringifyRowData.ts +++ b/lattice/src/App/QueryBuilder/stringifyRowData.ts @@ -8,7 +8,7 @@ export const stringifyRowData = (rowData: RowGrouping[], operator?: Operator) => rowsMap.push([]); group.row.forEach((row) => { let rowString = ''; - const { field, rowOperator, value, type } = row; + const { field, rowOperator, value, type, keys } = row; const isNegatory = rowOperator === '!='; const isUnion = ['=', '!='].includes(rowOperator) && value.split(',').length > 1; @@ -16,7 +16,7 @@ export const stringifyRowData = (rowData: RowGrouping[], operator?: Operator) => if (isUnion) { const values = value.split(','); const unionRows = values - .map((v) => `Row(${field}="${v.trim()}")`) + .map((v) => keys ? `Row(${field}="${v.trim()}")` : `Row(${field}=${v.trim()})`) .join(', '); rowString = `Union(${unionRows})`; } else if (rowOperator === 'cidr') { @@ -36,7 +36,7 @@ export const stringifyRowData = (rowData: RowGrouping[], operator?: Operator) => rowString = `UnionRows(Rows(field=${field}, like="%${value}%"))`; } } else { - rowString = ['set', 'timestamp'].includes(type) + rowString = keys || type === 'timestamp' ? `Row(${field}${operator}"${value}")` : `Row(${field}${operator}${value})`; }