Merge branch 'feature/custom_query_language' into develop
# Conflicts: # HISTORY.md
This commit is contained in:
commit
c5c64bcde3
|
@ -3,6 +3,14 @@
|
||||||
https://github.com/josdejong/jsoneditor
|
https://github.com/josdejong/jsoneditor
|
||||||
|
|
||||||
|
|
||||||
|
## not yet published, version 8.5.0
|
||||||
|
|
||||||
|
- Implemented support for customizing the query language used in the
|
||||||
|
Transform modal. New options `createQuery`, `executeQuery`, and
|
||||||
|
`queryDescription` are available for this now. An example is available
|
||||||
|
in `examples/23_custom_query_language.html`. See #857, #871.
|
||||||
|
|
||||||
|
|
||||||
## 2020-01-25, version 8.4.1
|
## 2020-01-25, version 8.4.1
|
||||||
|
|
||||||
- Fix `console.log` in production code. Oopsie.
|
- Fix `console.log` in production code. Oopsie.
|
||||||
|
|
49
docs/api.md
49
docs/api.md
|
@ -589,6 +589,55 @@ Constructs a new JSONEditor.
|
||||||
- `{Number} maxVisibleChilds`
|
- `{Number} maxVisibleChilds`
|
||||||
|
|
||||||
Number of children allowed for a given node before the "show more / show all" message appears (in 'tree', 'view', or 'form' modes). `100` by default.
|
Number of children allowed for a given node before the "show more / show all" message appears (in 'tree', 'view', or 'form' modes). `100` by default.
|
||||||
|
|
||||||
|
- `{ function(json: JSON, queryOptions: QueryOptions) -> string } createQuery`
|
||||||
|
|
||||||
|
Create a query string based on query options filled in the Transform Wizard in the Transform modal.
|
||||||
|
Normally used in combination with `executeQuery`.
|
||||||
|
The input for the function are the entered query options and the current JSON, and the output
|
||||||
|
must be a string containing the query. This query will be executed using `executeQuery`.
|
||||||
|
|
||||||
|
The query options have the following structure:
|
||||||
|
|
||||||
|
```
|
||||||
|
interface QueryOptions {
|
||||||
|
filter?: {
|
||||||
|
field: string | '@'
|
||||||
|
relation: '==' | '!=' | '<' | '<=' | '>' | '>='
|
||||||
|
value: string
|
||||||
|
}
|
||||||
|
sort?: {
|
||||||
|
field: string | '@'
|
||||||
|
direction: 'asc' | 'desc'
|
||||||
|
}
|
||||||
|
projection?: {
|
||||||
|
fields: string[]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Note that there is a special case `'@'` for `filter.field` and `sort.field`.
|
||||||
|
It means that the field itself is selected, for example when having an array containing numbers.
|
||||||
|
|
||||||
|
A usage example can be found in `examples/23_custom_query_language.html`.
|
||||||
|
|
||||||
|
|
||||||
|
- `{ function(json: JSON, query: string) -> JSON } executeQuery`
|
||||||
|
|
||||||
|
Replace the build-in query language used in the Transform modal with a custom language.
|
||||||
|
Normally used in combination with `createQuery`.
|
||||||
|
The input for the function is the current JSON and a query string, and output must be the transformed JSON.
|
||||||
|
|
||||||
|
A usage example can be found in `examples/23_custom_query_language.html`.
|
||||||
|
|
||||||
|
- `{string} queryDescription`
|
||||||
|
|
||||||
|
A text description displayed on top of the Transform modal.
|
||||||
|
Can be used to explain a custom query language implemented via `createQuery` and `executeQuery`.
|
||||||
|
The text can contain HTML code like a link to a web page.
|
||||||
|
|
||||||
|
A usage example can be found in `examples/23_custom_query_language.html`.
|
||||||
|
|
||||||
|
|
||||||
### Methods
|
### Methods
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,123 @@
|
||||||
|
<!DOCTYPE HTML>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<title>JSONEditor | Custom query language</title>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<link href="../dist/jsoneditor.css" rel="stylesheet" type="text/css">
|
||||||
|
<script src="../dist/jsoneditor.js"></script>
|
||||||
|
<script src="https://unpkg.com/lodash@4.17.15/lodash.min.js"></script>
|
||||||
|
|
||||||
|
<style type="text/css">
|
||||||
|
p {
|
||||||
|
max-width: 500px;
|
||||||
|
font-family: sans-serif;
|
||||||
|
font-size: 11pt;
|
||||||
|
}
|
||||||
|
|
||||||
|
code {
|
||||||
|
font-size: 11pt;
|
||||||
|
background: #e5e5e5;
|
||||||
|
}
|
||||||
|
|
||||||
|
#jsoneditor {
|
||||||
|
width: 500px;
|
||||||
|
height: 500px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.warning {
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<p>
|
||||||
|
This demo shows how to configure a custom query language.
|
||||||
|
Click on the "Transform" button and try it out.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
This basic example uses lodash functions <code>filter</code>, <code>sort</code>, and <code>pick</code>,
|
||||||
|
but you can run any JavaScript code.
|
||||||
|
</p>
|
||||||
|
<p class="warning">
|
||||||
|
WARNING: this example uses <code>new Function()</code> which can be dangerous when executed with arbitrary code.
|
||||||
|
Don't use it in production.
|
||||||
|
</p>
|
||||||
|
<div id="jsoneditor"></div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const container = document.getElementById('jsoneditor')
|
||||||
|
const options = {
|
||||||
|
createQuery: function (json, queryOptions) {
|
||||||
|
console.log('createQuery', queryOptions)
|
||||||
|
|
||||||
|
const { filter, sort, projection } = queryOptions
|
||||||
|
let query = 'data'
|
||||||
|
|
||||||
|
if (filter) {
|
||||||
|
// Note that the comparisons embrace type coercion,
|
||||||
|
// so a filter value like '5' (text) will match numbers like 5 too.
|
||||||
|
const getActualValue = filter.field !== '@'
|
||||||
|
? `item => _.get(item, '${filter.field}')`
|
||||||
|
: `item => item`
|
||||||
|
query = `_.filter(${query}, ${getActualValue} ${filter.relation} '${filter.value}')`
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sort) {
|
||||||
|
// The '@' field name is a special case,
|
||||||
|
// which means that the field itself is selected.
|
||||||
|
// For example when we have an array containing numbers.
|
||||||
|
query = sort.field !== '@'
|
||||||
|
? `_.orderBy(${query}, '${sort.field}', '${sort.direction}')`
|
||||||
|
: `_.sortBy(${query}, '${sort.direction}')`
|
||||||
|
}
|
||||||
|
|
||||||
|
if (projection) {
|
||||||
|
// It is possible to make a util function "pickFlat"
|
||||||
|
// and use that when building the query to make it more readable.
|
||||||
|
if (projection.fields.length > 1) {
|
||||||
|
const fields = projection.fields.map(field => {
|
||||||
|
const name = _.last(field.split('.'))
|
||||||
|
return ` '${name}': _.get(item, '${field}')`
|
||||||
|
})
|
||||||
|
query = `_.map(${query}, item => ({\n${fields.join(',\n')}})\n)`
|
||||||
|
} else {
|
||||||
|
const field = projection.fields[0]
|
||||||
|
query = `_.map(${query}, item => _.get(item, '${field}'))`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return query
|
||||||
|
},
|
||||||
|
executeQuery: function (json, query) {
|
||||||
|
console.log('executeQuery', query)
|
||||||
|
|
||||||
|
// WARNING: Using new Function() with arbitrary input can be dangerous! Be careful.
|
||||||
|
const execute = new Function('data', 'return ' + query)
|
||||||
|
|
||||||
|
return execute(json)
|
||||||
|
},
|
||||||
|
queryDescription: 'Enter a JavaScript query to filter, sort, or transform the JSON data.<br/>' +
|
||||||
|
'The <a href="https://lodash.com/" target="_blank">Lodash</a> library is available via <code>_</code> to facilitate this.'
|
||||||
|
}
|
||||||
|
const json = []
|
||||||
|
for (let i = 0; i < 100; i++) {
|
||||||
|
var longitude = 4 + i / 100
|
||||||
|
var latitude = 51 + i / 100
|
||||||
|
|
||||||
|
json.push({
|
||||||
|
name: 'Item ' + i,
|
||||||
|
id: String(i),
|
||||||
|
index: i,
|
||||||
|
time: new Date().toISOString(),
|
||||||
|
location: {
|
||||||
|
latitude: longitude,
|
||||||
|
longitude: latitude,
|
||||||
|
coordinates: [longitude, latitude]
|
||||||
|
},
|
||||||
|
random: Math.random()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
const editor = new JSONEditor(container, options, json)
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -184,7 +184,8 @@ JSONEditor.VALID_OPTIONS = [
|
||||||
'escapeUnicode', 'history', 'search', 'mode', 'modes', 'name', 'indentation',
|
'escapeUnicode', 'history', 'search', 'mode', 'modes', 'name', 'indentation',
|
||||||
'sortObjectKeys', 'navigationBar', 'statusBar', 'mainMenuBar', 'languages', 'language', 'enableSort', 'enableTransform',
|
'sortObjectKeys', 'navigationBar', 'statusBar', 'mainMenuBar', 'languages', 'language', 'enableSort', 'enableTransform',
|
||||||
'maxVisibleChilds', 'onValidationError',
|
'maxVisibleChilds', 'onValidationError',
|
||||||
'modalAnchor', 'popupAnchor'
|
'modalAnchor', 'popupAnchor',
|
||||||
|
'createQuery', 'executeQuery', 'queryDescription'
|
||||||
]
|
]
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
'use strict'
|
'use strict'
|
||||||
|
|
||||||
import jmespath from 'jmespath'
|
|
||||||
import naturalSort from 'javascript-natural-sort'
|
import naturalSort from 'javascript-natural-sort'
|
||||||
import { createAbsoluteAnchor } from './createAbsoluteAnchor'
|
import { createAbsoluteAnchor } from './createAbsoluteAnchor'
|
||||||
import { ContextMenu } from './ContextMenu'
|
import { ContextMenu } from './ContextMenu'
|
||||||
|
@ -3331,7 +3330,7 @@ export class Node {
|
||||||
|
|
||||||
// apply the JMESPath query
|
// apply the JMESPath query
|
||||||
const oldValue = this.getValue()
|
const oldValue = this.getValue()
|
||||||
const newValue = jmespath.search(oldValue, query)
|
const newValue = this.editor.options.executeQuery(oldValue, query)
|
||||||
this.setValue(newValue)
|
this.setValue(newValue)
|
||||||
|
|
||||||
const newInternalValue = this.getInternalValue()
|
const newInternalValue = this.getInternalValue()
|
||||||
|
@ -3883,12 +3882,16 @@ export class Node {
|
||||||
* Show transform modal
|
* Show transform modal
|
||||||
*/
|
*/
|
||||||
showTransformModal () {
|
showTransformModal () {
|
||||||
const node = this
|
const { modalAnchor, createQuery, executeQuery, queryDescription } = this.editor.options
|
||||||
|
const json = this.getValue()
|
||||||
|
|
||||||
const anchor = this.editor.options.modalAnchor || DEFAULT_MODAL_ANCHOR
|
showTransformModal({
|
||||||
const json = node.getValue()
|
anchor: modalAnchor || DEFAULT_MODAL_ANCHOR,
|
||||||
showTransformModal(anchor, json, query => {
|
json,
|
||||||
node.transform(query)
|
queryDescription, // can be undefined
|
||||||
|
createQuery,
|
||||||
|
executeQuery,
|
||||||
|
onTransform: query => { this.transform(query) }
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,75 @@
|
||||||
|
import jmespath from 'jmespath'
|
||||||
|
import { get, parsePath, parseString } from './util'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build a JMESPath query based on query options coming from the wizard
|
||||||
|
* @param {JSON} json The JSON document for which to build the query.
|
||||||
|
* Used for context information like determining
|
||||||
|
* the type of values (string or number)
|
||||||
|
* @param {QueryOptions} queryOptions
|
||||||
|
* @return {string} Returns a query (as string)
|
||||||
|
*/
|
||||||
|
export function createQuery (json, queryOptions) {
|
||||||
|
const { sort, filter, projection } = queryOptions
|
||||||
|
let query = ''
|
||||||
|
|
||||||
|
if (filter) {
|
||||||
|
const examplePath = filter.field !== '@'
|
||||||
|
? ['0'].concat(parsePath('.' + filter.field))
|
||||||
|
: ['0']
|
||||||
|
const exampleValue = get(json, examplePath)
|
||||||
|
const value1 = typeof exampleValue === 'string'
|
||||||
|
? filter.value
|
||||||
|
: parseString(filter.value)
|
||||||
|
|
||||||
|
query += '[? ' +
|
||||||
|
filter.field + ' ' +
|
||||||
|
filter.relation + ' ' +
|
||||||
|
'`' + JSON.stringify(value1) + '`' +
|
||||||
|
']'
|
||||||
|
} else {
|
||||||
|
query += Array.isArray(json)
|
||||||
|
? '[*]'
|
||||||
|
: '@'
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sort) {
|
||||||
|
if (sort.direction === 'desc') {
|
||||||
|
query += ' | reverse(sort_by(@, &' + sort.field + '))'
|
||||||
|
} else {
|
||||||
|
query += ' | sort_by(@, &' + sort.field + ')'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (projection) {
|
||||||
|
if (query[query.length - 1] !== ']') {
|
||||||
|
query += ' | [*]'
|
||||||
|
}
|
||||||
|
|
||||||
|
if (projection.fields.length === 1) {
|
||||||
|
query += '.' + projection.fields[0]
|
||||||
|
} else if (projection.fields.length > 1) {
|
||||||
|
query += '.{' +
|
||||||
|
projection.fields.map(value => {
|
||||||
|
const parts = value.split('.')
|
||||||
|
const last = parts[parts.length - 1]
|
||||||
|
return last + ': ' + value
|
||||||
|
}).join(', ') +
|
||||||
|
'}'
|
||||||
|
} else { // values.length === 0
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return query
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute a JMESPath query
|
||||||
|
* @param {JSON} json
|
||||||
|
* @param {string} query
|
||||||
|
* @return {JSON} Returns the transformed JSON
|
||||||
|
*/
|
||||||
|
export function executeQuery (json, query) {
|
||||||
|
return jmespath.search(json, query)
|
||||||
|
}
|
|
@ -1,6 +1,5 @@
|
||||||
'use strict'
|
'use strict'
|
||||||
|
|
||||||
import jmespath from 'jmespath'
|
|
||||||
import { translate } from './i18n'
|
import { translate } from './i18n'
|
||||||
import { ModeSwitcher } from './ModeSwitcher'
|
import { ModeSwitcher } from './ModeSwitcher'
|
||||||
import { ErrorTable } from './ErrorTable'
|
import { ErrorTable } from './ErrorTable'
|
||||||
|
@ -23,6 +22,7 @@ import {
|
||||||
sortObjectKeys
|
sortObjectKeys
|
||||||
} from './util'
|
} from './util'
|
||||||
import { History } from './History'
|
import { History } from './History'
|
||||||
|
import { createQuery, executeQuery } from './jmespathQuery'
|
||||||
|
|
||||||
const textmode = textModeMixins[0].mixin
|
const textmode = textModeMixins[0].mixin
|
||||||
|
|
||||||
|
@ -44,6 +44,8 @@ previewmode.create = function (container, options = {}) {
|
||||||
options.mainMenuBar = options.mainMenuBar !== false
|
options.mainMenuBar = options.mainMenuBar !== false
|
||||||
options.enableSort = options.enableSort !== false
|
options.enableSort = options.enableSort !== false
|
||||||
options.enableTransform = options.enableTransform !== false
|
options.enableTransform = options.enableTransform !== false
|
||||||
|
options.createQuery = options.createQuery || createQuery
|
||||||
|
options.executeQuery = options.executeQuery || executeQuery
|
||||||
|
|
||||||
this.options = options
|
this.options = options
|
||||||
|
|
||||||
|
@ -385,18 +387,24 @@ previewmode._showSortModal = function () {
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
previewmode._showTransformModal = function () {
|
previewmode._showTransformModal = function () {
|
||||||
const me = this
|
|
||||||
|
|
||||||
this.executeWithBusyMessage(() => {
|
this.executeWithBusyMessage(() => {
|
||||||
const anchor = me.options.modalAnchor || DEFAULT_MODAL_ANCHOR
|
const { createQuery, executeQuery, modalAnchor, queryDescription } = this.options
|
||||||
const json = me.get()
|
const json = this.get()
|
||||||
me._renderPreview() // update array count
|
|
||||||
|
|
||||||
showTransformModal(anchor, json, query => {
|
this._renderPreview() // update array count
|
||||||
me.executeWithBusyMessage(() => {
|
|
||||||
const updatedJson = jmespath.search(json, query)
|
showTransformModal({
|
||||||
me._setAndFireOnChange(updatedJson)
|
anchor: modalAnchor || DEFAULT_MODAL_ANCHOR,
|
||||||
}, 'transforming...')
|
json,
|
||||||
|
queryDescription, // can be undefined
|
||||||
|
createQuery,
|
||||||
|
executeQuery,
|
||||||
|
onTransform: query => {
|
||||||
|
this.executeWithBusyMessage(() => {
|
||||||
|
const updatedJson = executeQuery(json, query)
|
||||||
|
this._setAndFireOnChange(updatedJson)
|
||||||
|
}, 'transforming...')
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}, 'parsing...')
|
}, 'parsing...')
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,28 +1,43 @@
|
||||||
import jmespath from 'jmespath'
|
|
||||||
import picoModal from 'picomodal'
|
import picoModal from 'picomodal'
|
||||||
import Selectr from './assets/selectr/selectr'
|
import Selectr from './assets/selectr/selectr'
|
||||||
import { translate } from './i18n'
|
import { translate } from './i18n'
|
||||||
import { stringifyPartial } from './jsonUtils'
|
import { stringifyPartial } from './jsonUtils'
|
||||||
import { getChildPaths, get, parsePath, parseString, debounce } from './util'
|
import { getChildPaths, debounce } from './util'
|
||||||
import { MAX_PREVIEW_CHARACTERS } from './constants'
|
import { MAX_PREVIEW_CHARACTERS } from './constants'
|
||||||
|
|
||||||
|
const DEFAULT_DESCRIPTION =
|
||||||
|
'Enter a <a href="http://jmespath.org" target="_blank">JMESPath</a> query to filter, sort, or transform the JSON data.<br/>' +
|
||||||
|
'To learn JMESPath, go to <a href="http://jmespath.org/tutorial.html" target="_blank">the interactive tutorial</a>.'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Show advanced filter and transform modal using JMESPath
|
* Show advanced filter and transform modal using JMESPath
|
||||||
* @param {HTMLElement} container The container where to center
|
* @param {Object} params
|
||||||
* the modal and create an overlay
|
* @property {HTMLElement} container The container where to center
|
||||||
* @param {JSON} json The json data to be transformed
|
* the modal and create an overlay
|
||||||
* @param {function} onTransform Callback invoked with the created
|
* @property {JSON} json The json data to be transformed
|
||||||
* query as callback
|
* @property {string} [queryDescription] Optional custom description explaining
|
||||||
|
* the transform functionality
|
||||||
|
* @property {function} createQuery Function called to create a query
|
||||||
|
* from the wizard form
|
||||||
|
* @property {function} executeQuery Execute a query for the preview pane
|
||||||
|
* @property {function} onTransform Callback invoked with the created
|
||||||
|
* query as callback
|
||||||
*/
|
*/
|
||||||
export function showTransformModal (container, json, onTransform) {
|
export function showTransformModal (
|
||||||
|
{
|
||||||
|
container,
|
||||||
|
json,
|
||||||
|
queryDescription = DEFAULT_DESCRIPTION,
|
||||||
|
createQuery,
|
||||||
|
executeQuery,
|
||||||
|
onTransform
|
||||||
|
}
|
||||||
|
) {
|
||||||
const value = json
|
const value = json
|
||||||
|
|
||||||
const content = '<label class="pico-modal-contents">' +
|
const content = '<label class="pico-modal-contents">' +
|
||||||
'<div class="pico-modal-header">' + translate('transform') + '</div>' +
|
'<div class="pico-modal-header">' + translate('transform') + '</div>' +
|
||||||
'<p>' +
|
'<p>' + queryDescription + '</p>' +
|
||||||
'Enter a <a href="http://jmespath.org" target="_blank">JMESPath</a> query to filter, sort, or transform the JSON data.<br/>' +
|
|
||||||
'To learn JMESPath, go to <a href="http://jmespath.org/tutorial.html" target="_blank">the interactive tutorial</a>.' +
|
|
||||||
'</p>' +
|
|
||||||
'<div class="jsoneditor-jmespath-label">' + translate('transformWizardLabel') + ' </div>' +
|
'<div class="jsoneditor-jmespath-label">' + translate('transformWizardLabel') + ' </div>' +
|
||||||
'<div id="wizard" class="jsoneditor-jmespath-block jsoneditor-jmespath-wizard">' +
|
'<div id="wizard" class="jsoneditor-jmespath-block jsoneditor-jmespath-wizard">' +
|
||||||
' <table class="jsoneditor-jmespath-wizard-table">' +
|
' <table class="jsoneditor-jmespath-wizard-table">' +
|
||||||
|
@ -176,8 +191,6 @@ export function showTransformModal (container, json, onTransform) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
query.value = Array.isArray(value) ? '[*]' : '@'
|
|
||||||
|
|
||||||
function preprocessPath (path) {
|
function preprocessPath (path) {
|
||||||
return (path === '')
|
return (path === '')
|
||||||
? '@'
|
? '@'
|
||||||
|
@ -186,69 +199,9 @@ export function showTransformModal (container, json, onTransform) {
|
||||||
: path
|
: path
|
||||||
}
|
}
|
||||||
|
|
||||||
function generateQueryFromWizard () {
|
|
||||||
if (filterField.value && filterRelation.value && filterValue.value) {
|
|
||||||
const field1 = filterField.value
|
|
||||||
const examplePath = field1 !== '@'
|
|
||||||
? ['0'].concat(parsePath('.' + field1))
|
|
||||||
: ['0']
|
|
||||||
const exampleValue = get(value, examplePath)
|
|
||||||
const value1 = typeof exampleValue === 'string'
|
|
||||||
? filterValue.value
|
|
||||||
: parseString(filterValue.value)
|
|
||||||
|
|
||||||
query.value = '[? ' +
|
|
||||||
field1 + ' ' +
|
|
||||||
filterRelation.value + ' ' +
|
|
||||||
'`' + JSON.stringify(value1) + '`' +
|
|
||||||
']'
|
|
||||||
} else {
|
|
||||||
query.value = '[*]'
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sortField.value && sortOrder.value) {
|
|
||||||
const field2 = sortField.value
|
|
||||||
if (sortOrder.value === 'desc') {
|
|
||||||
query.value += ' | reverse(sort_by(@, &' + field2 + '))'
|
|
||||||
} else {
|
|
||||||
query.value += ' | sort_by(@, &' + field2 + ')'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (selectFields.value) {
|
|
||||||
const values = []
|
|
||||||
for (let i = 0; i < selectFields.options.length; i++) {
|
|
||||||
if (selectFields.options[i].selected) {
|
|
||||||
const selectedValue = selectFields.options[i].value
|
|
||||||
values.push(selectedValue)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (query.value[query.value.length - 1] !== ']') {
|
|
||||||
query.value += ' | [*]'
|
|
||||||
}
|
|
||||||
|
|
||||||
if (values.length === 1) {
|
|
||||||
query.value += '.' + values[0]
|
|
||||||
} else if (values.length > 1) {
|
|
||||||
query.value += '.{' +
|
|
||||||
values.map(value => {
|
|
||||||
const parts = value.split('.')
|
|
||||||
const last = parts[parts.length - 1]
|
|
||||||
return last + ': ' + value
|
|
||||||
}).join(', ') +
|
|
||||||
'}'
|
|
||||||
} else { // values.length === 0
|
|
||||||
// ignore
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
debouncedUpdatePreview()
|
|
||||||
}
|
|
||||||
|
|
||||||
function updatePreview () {
|
function updatePreview () {
|
||||||
try {
|
try {
|
||||||
const transformed = jmespath.search(value, query.value)
|
const transformed = executeQuery(value, query.value)
|
||||||
|
|
||||||
preview.className = 'jsoneditor-transform-preview'
|
preview.className = 'jsoneditor-transform-preview'
|
||||||
preview.value = stringifyPartial(transformed, 2, MAX_PREVIEW_CHARACTERS)
|
preview.value = stringifyPartial(transformed, 2, MAX_PREVIEW_CHARACTERS)
|
||||||
|
@ -261,10 +214,61 @@ export function showTransformModal (container, json, onTransform) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var debouncedUpdatePreview = debounce(updatePreview, 300)
|
const debouncedUpdatePreview = debounce(updatePreview, 300)
|
||||||
|
|
||||||
|
function tryCreateQuery (json, queryOptions) {
|
||||||
|
try {
|
||||||
|
query.value = createQuery(json, queryOptions)
|
||||||
|
ok.disabled = false
|
||||||
|
|
||||||
|
debouncedUpdatePreview()
|
||||||
|
} catch (err) {
|
||||||
|
const message = 'Error: an error happened when executing "createQuery": ' + (err.message || err.toString())
|
||||||
|
|
||||||
|
query.value = ''
|
||||||
|
ok.disabled = true
|
||||||
|
|
||||||
|
preview.className = 'jsoneditor-transform-preview jsoneditor-error'
|
||||||
|
preview.value = message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateQueryFromWizard () {
|
||||||
|
const queryOptions = {}
|
||||||
|
|
||||||
|
if (filterField.value && filterRelation.value && filterValue.value) {
|
||||||
|
queryOptions.filter = {
|
||||||
|
field: filterField.value,
|
||||||
|
relation: filterRelation.value,
|
||||||
|
value: filterValue.value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sortField.value && sortOrder.value) {
|
||||||
|
queryOptions.sort = {
|
||||||
|
field: sortField.value,
|
||||||
|
direction: sortOrder.value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selectFields.value) {
|
||||||
|
const fields = []
|
||||||
|
for (let i = 0; i < selectFields.options.length; i++) {
|
||||||
|
if (selectFields.options[i].selected) {
|
||||||
|
const selectedField = selectFields.options[i].value
|
||||||
|
fields.push(selectedField)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
queryOptions.projection = {
|
||||||
|
fields
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tryCreateQuery(json, queryOptions)
|
||||||
|
}
|
||||||
|
|
||||||
query.oninput = debouncedUpdatePreview
|
query.oninput = debouncedUpdatePreview
|
||||||
debouncedUpdatePreview()
|
|
||||||
|
|
||||||
ok.onclick = event => {
|
ok.onclick = event => {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
|
@ -275,6 +279,9 @@ export function showTransformModal (container, json, onTransform) {
|
||||||
onTransform(query.value)
|
onTransform(query.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// initialize with empty query
|
||||||
|
tryCreateQuery(json, {})
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
query.select()
|
query.select()
|
||||||
query.focus()
|
query.focus()
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
'use strict'
|
'use strict'
|
||||||
|
|
||||||
import ace from './ace'
|
import ace from './ace'
|
||||||
import jmespath from 'jmespath'
|
|
||||||
import { translate } from './i18n'
|
import { translate } from './i18n'
|
||||||
import { ModeSwitcher } from './ModeSwitcher'
|
import { ModeSwitcher } from './ModeSwitcher'
|
||||||
import { ErrorTable } from './ErrorTable'
|
import { ErrorTable } from './ErrorTable'
|
||||||
|
@ -26,6 +25,7 @@ import {
|
||||||
} from './util'
|
} from './util'
|
||||||
import { DEFAULT_MODAL_ANCHOR } from './constants'
|
import { DEFAULT_MODAL_ANCHOR } from './constants'
|
||||||
import { tryRequireThemeJsonEditor } from './tryRequireThemeJsonEditor'
|
import { tryRequireThemeJsonEditor } from './tryRequireThemeJsonEditor'
|
||||||
|
import { createQuery, executeQuery } from './jmespathQuery'
|
||||||
|
|
||||||
// create a mixin with the functions for text mode
|
// create a mixin with the functions for text mode
|
||||||
const textmode = {}
|
const textmode = {}
|
||||||
|
@ -47,6 +47,8 @@ textmode.create = function (container, options = {}) {
|
||||||
options.mainMenuBar = options.mainMenuBar !== false
|
options.mainMenuBar = options.mainMenuBar !== false
|
||||||
options.enableSort = options.enableSort !== false
|
options.enableSort = options.enableSort !== false
|
||||||
options.enableTransform = options.enableTransform !== false
|
options.enableTransform = options.enableTransform !== false
|
||||||
|
options.createQuery = options.createQuery || createQuery
|
||||||
|
options.executeQuery = options.executeQuery || executeQuery
|
||||||
|
|
||||||
this.options = options
|
this.options = options
|
||||||
|
|
||||||
|
@ -425,12 +427,19 @@ textmode._showSortModal = function () {
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
textmode._showTransformModal = function () {
|
textmode._showTransformModal = function () {
|
||||||
const me = this
|
const { modalAnchor, createQuery, executeQuery, queryDescription } = this.options
|
||||||
const anchor = this.options.modalAnchor || DEFAULT_MODAL_ANCHOR
|
|
||||||
const json = this.get()
|
const json = this.get()
|
||||||
showTransformModal(anchor, json, query => {
|
|
||||||
const updatedJson = jmespath.search(json, query)
|
showTransformModal({
|
||||||
me.set(updatedJson)
|
anchor: modalAnchor || DEFAULT_MODAL_ANCHOR,
|
||||||
|
json,
|
||||||
|
queryDescription, // can be undefined
|
||||||
|
createQuery,
|
||||||
|
executeQuery,
|
||||||
|
onTransform: query => {
|
||||||
|
const updatedJson = executeQuery(json, query)
|
||||||
|
this.set(updatedJson)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -29,6 +29,7 @@ import {
|
||||||
} from './util'
|
} from './util'
|
||||||
import { autocomplete } from './autocomplete'
|
import { autocomplete } from './autocomplete'
|
||||||
import { setLanguage, setLanguages, translate } from './i18n'
|
import { setLanguage, setLanguages, translate } from './i18n'
|
||||||
|
import { createQuery, executeQuery } from './jmespathQuery'
|
||||||
|
|
||||||
// create a mixin with the functions for tree mode
|
// create a mixin with the functions for tree mode
|
||||||
const treemode = {}
|
const treemode = {}
|
||||||
|
@ -156,6 +157,8 @@ treemode._setOptions = function (options) {
|
||||||
},
|
},
|
||||||
timestampTag: true,
|
timestampTag: true,
|
||||||
timestampFormat: null,
|
timestampFormat: null,
|
||||||
|
createQuery,
|
||||||
|
executeQuery,
|
||||||
onEvent: null,
|
onEvent: null,
|
||||||
enableSort: true,
|
enableSort: true,
|
||||||
enableTransform: true
|
enableTransform: true
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {object} QueryOptions
|
||||||
|
* @property {FilterOptions} [filter]
|
||||||
|
* @property {SortOptions} [sort]
|
||||||
|
* @property {ProjectionOptions} [projection]
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {object} FilterOptions
|
||||||
|
* @property {string} field
|
||||||
|
* @property {string} relation Can be '==', '<', etc
|
||||||
|
* @property {string} value
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {object} SortOptions
|
||||||
|
* @property {string} field
|
||||||
|
* @property {string} direction Can be 'asc' or 'desc'
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {object} ProjectionOptions
|
||||||
|
* @property {string[]} fields
|
||||||
|
*/
|
Loading…
Reference in New Issue