Implement transform wizard (WIP)
This commit is contained in:
parent
d67fffc7d5
commit
22f429e01b
|
@ -10,6 +10,7 @@
|
|||
|
||||
font-family: $font-family;
|
||||
font-size: $font-size;
|
||||
color: $black;
|
||||
|
||||
.contents {
|
||||
padding: 20px;
|
||||
|
@ -27,5 +28,5 @@
|
|||
// custom styling for the modal.
|
||||
// FIXME: not neat to override global styles!
|
||||
:global(.bg .window-wrap) {
|
||||
margin-top: 10rem;
|
||||
margin-top: 8rem;
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
.jsoneditor-modal.transform {
|
||||
|
||||
.description {
|
||||
padding-bottom: $padding;
|
||||
color: $dark-gray;
|
||||
|
||||
code {
|
||||
background: $background-gray;
|
||||
|
@ -15,7 +15,8 @@
|
|||
|
||||
label {
|
||||
font-weight: bold;
|
||||
padding: $padding/2 0;
|
||||
padding-top: $padding * 2;
|
||||
padding-bottom: $padding / 2;
|
||||
display: block;
|
||||
}
|
||||
|
||||
|
@ -31,6 +32,7 @@
|
|||
padding: $padding / 2;
|
||||
font-family: $font-family-mono;
|
||||
font-size: $font-size-mono;
|
||||
color: $black;
|
||||
}
|
||||
|
||||
textarea.preview {
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
import { transformModalState } from './transformModalState.js'
|
||||
import { DEBOUNCE_DELAY, MAX_PREVIEW_CHARACTERS } from '../../constants.js'
|
||||
import { truncate } from '../../utils/stringUtils.js'
|
||||
import TransformWizard from './TransformWizard.svelte'
|
||||
import * as _ from 'lodash-es'
|
||||
import { getIn } from '../../utils/immutabilityHelpers.js'
|
||||
|
||||
|
@ -34,6 +35,11 @@
|
|||
return queryFn(json)
|
||||
}
|
||||
|
||||
function updateQuery (newQuery) {
|
||||
console.log('updated query by wizard', newQuery)
|
||||
query = newQuery
|
||||
}
|
||||
|
||||
function previewTransform(json, query) {
|
||||
try {
|
||||
const jsonTransformed = evalTransform(json, query)
|
||||
|
@ -96,6 +102,13 @@
|
|||
<code>_.pick</code>, <code>_.uniq</code>, <code>_.get</code>, etcetera.
|
||||
</div>
|
||||
|
||||
<label>Wizard</label>
|
||||
{#if Array.isArray(json)}
|
||||
<TransformWizard json={json} onQuery={updateQuery} />
|
||||
{:else}
|
||||
(Only available for arrays, not for objects)
|
||||
{/if}
|
||||
|
||||
<label>Query</label>
|
||||
<textarea class="query" bind:value={query} />
|
||||
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
@import '../../styles.scss';
|
||||
|
||||
table.transform-wizard {
|
||||
border-collapse: collapse;
|
||||
border-spacing: 0;
|
||||
width: 100%;
|
||||
|
||||
tr {
|
||||
th {
|
||||
font-weight: normal;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
td {
|
||||
.horizontal {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
||||
.selectContainer.filter-field {
|
||||
flex: 4;
|
||||
margin-right: $padding;
|
||||
}
|
||||
|
||||
.selectContainer.filter-relation {
|
||||
flex: 1;
|
||||
margin-right: $padding;
|
||||
}
|
||||
|
||||
.filter-value {
|
||||
flex: 4;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,133 @@
|
|||
<svelte:options immutable={true} />
|
||||
|
||||
<script>
|
||||
import Select from 'svelte-select'
|
||||
import { getNestedPaths } from '../../utils/arrayUtils.js'
|
||||
import { stringifyPath } from '../../utils/pathUtils.js'
|
||||
import { createQuery } from '../../logic/jsCreateQuery.js'
|
||||
import { isEqual } from 'lodash-es'
|
||||
|
||||
export let json
|
||||
export let onQuery
|
||||
|
||||
// fields
|
||||
let filterField = undefined
|
||||
let filterRelation = undefined
|
||||
let filterValue = undefined
|
||||
let sortField = undefined
|
||||
let sortDirection = undefined
|
||||
let pickFields = undefined
|
||||
|
||||
// options
|
||||
$: jsonIsArray = Array.isArray(json)
|
||||
$: paths = jsonIsArray ? getNestedPaths(json) : undefined
|
||||
$: fieldOptions = paths ? paths.map(pathToOption) : undefined
|
||||
|
||||
const filterRelationOptions = ['==', '!=', '<', '<=', '>', '>='].map(relation => ({
|
||||
value: relation,
|
||||
label: relation
|
||||
}))
|
||||
|
||||
const sortDirectionOptions = [
|
||||
{ value: 'asc', label: 'ascending' },
|
||||
{ value: 'desc', label: 'descending' },
|
||||
]
|
||||
|
||||
function pathToOption (path) {
|
||||
return {
|
||||
value: path,
|
||||
label: stringifyPath(path)
|
||||
}
|
||||
}
|
||||
|
||||
let queryOptions = {}
|
||||
$: {
|
||||
const newQueryOptions = {}
|
||||
|
||||
if (filterField && filterRelation && filterValue) {
|
||||
newQueryOptions.filter = {
|
||||
field: filterField.value,
|
||||
relation: filterRelation.value,
|
||||
value: filterValue
|
||||
}
|
||||
}
|
||||
|
||||
if (sortField && sortDirection) {
|
||||
newQueryOptions.sort = {
|
||||
field: sortField.value,
|
||||
direction: sortDirection.value
|
||||
}
|
||||
}
|
||||
|
||||
if (pickFields) {
|
||||
newQueryOptions.projection = {
|
||||
fields: pickFields.map(item => item.value)
|
||||
}
|
||||
}
|
||||
|
||||
if (!isEqual(newQueryOptions, queryOptions)) {
|
||||
queryOptions = newQueryOptions
|
||||
const query = createQuery(json, queryOptions)
|
||||
|
||||
console.log('query updated', query, queryOptions)
|
||||
|
||||
onQuery(query)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<table class="transform-wizard">
|
||||
<tr>
|
||||
<th>Filter</th>
|
||||
<td>
|
||||
<div class='horizontal'>
|
||||
<Select
|
||||
containerClasses='filter-field'
|
||||
items={fieldOptions}
|
||||
bind:selectedValue={filterField}
|
||||
/>
|
||||
<Select
|
||||
containerClasses='filter-relation'
|
||||
items={filterRelationOptions}
|
||||
bind:selectedValue={filterRelation}
|
||||
/>
|
||||
<input
|
||||
class='filter-value'
|
||||
bind:value={filterValue}
|
||||
/>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Sort</th>
|
||||
<td>
|
||||
<div class='horizontal'>
|
||||
<Select
|
||||
containerClasses='sort-field'
|
||||
items={fieldOptions}
|
||||
bind:selectedValue={sortField}
|
||||
/>
|
||||
<Select
|
||||
containerClasses='sort-direction'
|
||||
items={sortDirectionOptions}
|
||||
bind:selectedValue={sortDirection}
|
||||
/>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Pick</th>
|
||||
<td>
|
||||
<div class='horizontal'>
|
||||
<Select
|
||||
containerClasses='pick-fields'
|
||||
items={fieldOptions}
|
||||
isMulti
|
||||
bind:selectedValue={pickFields}
|
||||
/>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<style src="./TransformWizard.scss"></style>
|
|
@ -0,0 +1,47 @@
|
|||
import { last } from 'lodash-es'
|
||||
|
||||
export function createQuery (json, queryOptions) {
|
||||
console.log('createQuery', queryOptions)
|
||||
|
||||
const { filter, sort, projection } = queryOptions
|
||||
const queryParts = []
|
||||
|
||||
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.length > 0
|
||||
? `item => _.get(item, ${JSON.stringify(filter.field)})`
|
||||
: 'item => item'
|
||||
queryParts.push(` data = data.filter(${getActualValue} ${filter.relation} '${filter.value}')\n`)
|
||||
}
|
||||
|
||||
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.
|
||||
if (sort.field !== '@') {
|
||||
queryParts.push(` data = _.orderBy(data, '${sort.field}', '${sort.direction}')\n`)
|
||||
} else {
|
||||
queryParts.push(` data = _.sortBy(data, '${sort.direction}')\n`)
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
return ` ${JSON.stringify(name)}: _.get(item, ${JSON.stringify(field)})`
|
||||
})
|
||||
queryParts.push(` data = data.map(item => ({\n${fields.join(',\n')}})\n )\n`)
|
||||
} else {
|
||||
const field = projection.fields[0]
|
||||
queryParts.push(` data = data.map(item => _.get(item, ${JSON.stringify(field)}))\n`)
|
||||
}
|
||||
}
|
||||
|
||||
queryParts.push(' return data\n')
|
||||
|
||||
return `function query (data) {\n${queryParts.join('')}}`
|
||||
}
|
Loading…
Reference in New Issue