Implement transform wizard (WIP)
This commit is contained in:
parent
d67fffc7d5
commit
22f429e01b
|
@ -10,6 +10,7 @@
|
||||||
|
|
||||||
font-family: $font-family;
|
font-family: $font-family;
|
||||||
font-size: $font-size;
|
font-size: $font-size;
|
||||||
|
color: $black;
|
||||||
|
|
||||||
.contents {
|
.contents {
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
|
@ -27,5 +28,5 @@
|
||||||
// custom styling for the modal.
|
// custom styling for the modal.
|
||||||
// FIXME: not neat to override global styles!
|
// FIXME: not neat to override global styles!
|
||||||
:global(.bg .window-wrap) {
|
:global(.bg .window-wrap) {
|
||||||
margin-top: 10rem;
|
margin-top: 8rem;
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
.jsoneditor-modal.transform {
|
.jsoneditor-modal.transform {
|
||||||
|
|
||||||
.description {
|
.description {
|
||||||
padding-bottom: $padding;
|
color: $dark-gray;
|
||||||
|
|
||||||
code {
|
code {
|
||||||
background: $background-gray;
|
background: $background-gray;
|
||||||
|
@ -15,7 +15,8 @@
|
||||||
|
|
||||||
label {
|
label {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
padding: $padding/2 0;
|
padding-top: $padding * 2;
|
||||||
|
padding-bottom: $padding / 2;
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -31,6 +32,7 @@
|
||||||
padding: $padding / 2;
|
padding: $padding / 2;
|
||||||
font-family: $font-family-mono;
|
font-family: $font-family-mono;
|
||||||
font-size: $font-size-mono;
|
font-size: $font-size-mono;
|
||||||
|
color: $black;
|
||||||
}
|
}
|
||||||
|
|
||||||
textarea.preview {
|
textarea.preview {
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
import { transformModalState } from './transformModalState.js'
|
import { transformModalState } from './transformModalState.js'
|
||||||
import { DEBOUNCE_DELAY, MAX_PREVIEW_CHARACTERS } from '../../constants.js'
|
import { DEBOUNCE_DELAY, MAX_PREVIEW_CHARACTERS } from '../../constants.js'
|
||||||
import { truncate } from '../../utils/stringUtils.js'
|
import { truncate } from '../../utils/stringUtils.js'
|
||||||
|
import TransformWizard from './TransformWizard.svelte'
|
||||||
import * as _ from 'lodash-es'
|
import * as _ from 'lodash-es'
|
||||||
import { getIn } from '../../utils/immutabilityHelpers.js'
|
import { getIn } from '../../utils/immutabilityHelpers.js'
|
||||||
|
|
||||||
|
@ -34,6 +35,11 @@
|
||||||
return queryFn(json)
|
return queryFn(json)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function updateQuery (newQuery) {
|
||||||
|
console.log('updated query by wizard', newQuery)
|
||||||
|
query = newQuery
|
||||||
|
}
|
||||||
|
|
||||||
function previewTransform(json, query) {
|
function previewTransform(json, query) {
|
||||||
try {
|
try {
|
||||||
const jsonTransformed = evalTransform(json, query)
|
const jsonTransformed = evalTransform(json, query)
|
||||||
|
@ -96,6 +102,13 @@
|
||||||
<code>_.pick</code>, <code>_.uniq</code>, <code>_.get</code>, etcetera.
|
<code>_.pick</code>, <code>_.uniq</code>, <code>_.get</code>, etcetera.
|
||||||
</div>
|
</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>
|
<label>Query</label>
|
||||||
<textarea class="query" bind:value={query} />
|
<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