Skip to content

Commit

Permalink
Enhancement/aggregate functions expression editor (#1545)
Browse files Browse the repository at this point in the history
* aggregate function: use AceEditor to edit sql expression

* use ScriptEditor component in analysis props editor

Co-authored-by: Stefano Ricci <[email protected]>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
  • Loading branch information
3 people authored Jun 29, 2021
1 parent 8bdade8 commit 1de1b8a
Show file tree
Hide file tree
Showing 15 changed files with 240 additions and 125 deletions.
6 changes: 5 additions & 1 deletion common/model/db/views/dataNodeDef/view.js
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,11 @@ export default class ViewDataNodeDef extends TableDataNodeDef {
return columns
}

get columnNodeDefNamesRead() {
get columnNodeDefNames() {
return this.columnNodeDefs.flatMap((columnNodeDef) => new ColumnNodeDef(this, columnNodeDef.nodeDef).names)
}

get columnNodeDefNamesFull() {
return this.columnNodeDefs.flatMap((columnNodeDef) => new ColumnNodeDef(this, columnNodeDef.nodeDef).namesFull)
}

Expand Down
2 changes: 1 addition & 1 deletion server/modules/surveyRdb/repository/dataView/read.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ const _prepareSelectFields = ({ queryBuilder, viewDataNodeDef, columnNodeDefs, n
_selectsByNodeDefType({ viewDataNodeDef, streamMode })(columnNodeDef.nodeDef)
)
)
// queryBuilder.select(viewDataNodeDef.columnRecordUuid, ...viewDataNodeDef.columnNodeDefNamesRead)
// queryBuilder.select(viewDataNodeDef.columnRecordUuid, ...viewDataNodeDef.columnNodeDefNamesFull)
} else if (R.isEmpty(nodeDefCols)) {
queryBuilder.select('*')
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,13 @@ import React from 'react'
import PropTypes from 'prop-types'
import classNames from 'classnames'

import * as Survey from '@core/survey/survey'
import * as NodeDef from '@core/survey/nodeDef'
import { Query } from '@common/model/query'

import PanelRight from '@webapp/components/PanelRight'

import { useSurvey } from '@webapp/store/survey'
import { useI18n, useLang } from '@webapp/store/system'

import { CustomAggregateFunctionsEditor } from './CustomAggregateFunctionsEditor'
Expand All @@ -15,11 +18,15 @@ export const AggregateFunctionsPanel = (props) => {

const lang = useLang()
const i18n = useI18n()
const survey = useSurvey()

const customAggregateFunctionUuids = aggregateFunctions.filter(
(fn) => !Object.values(Query.DEFAULT_AGGREGATE_FUNCTIONS).includes(fn)
)

const entityDefUuid = Query.getEntityDefUuid(query)
const entityDef = Survey.getNodeDefByUuid(entityDefUuid)(survey)

const nodeDefUuid = NodeDef.getUuid(nodeDef)
const nodeDefLabel = NodeDef.getLabel(nodeDef, lang)

Expand All @@ -42,6 +49,7 @@ export const AggregateFunctionsPanel = (props) => {
</button>
))}
<CustomAggregateFunctionsEditor
entityDef={entityDef}
nodeDef={nodeDef}
selectedUuids={customAggregateFunctionUuids}
onSelectionChange={(selectedAggregateFunctionsByUuid) => {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import React from 'react'
import PropTypes from 'prop-types'

import * as NodeDef from '@core/survey/nodeDef'
import { ViewDataNodeDef } from '@common/model/db'

import { useSurvey } from '@webapp/store/survey'
import { ScriptEditor } from '@webapp/components/ScriptEditor'

export const AggregateFunctionExpressionEditor = (props) => {
const { expression, entityDef, onChange } = props

const survey = useSurvey()

// create entity view column names completer
const viewDataNodeDef = new ViewDataNodeDef(survey, entityDef)

const columnItems = viewDataNodeDef.columnNodeDefNames.map((columnName) => ({
caption: columnName,
value: columnName,
meta: `${NodeDef.getName(entityDef)} - Column`,
}))

const columnNamesCompleter = {
getCompletions: (_editor, _session, _pos, _prefix, callback) => {
callback(null, columnItems)
},
}

return (
<ScriptEditor
name="custom_aggregate_function_editor"
mode="sql"
script={expression}
onChange={onChange}
completer={columnNamesCompleter}
height="150px"
/>
)
}

AggregateFunctionExpressionEditor.propTypes = {
expression: PropTypes.string.isRequired,
entityDef: PropTypes.object.isRequired,
onChange: PropTypes.func.isRequired,
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,17 @@ import { useI18n } from '@webapp/store/system'
import { FormItem, Input } from '@webapp/components/form/Input'
import { ButtonCancel, ButtonDelete, ButtonSave } from '@webapp/components'
import { NotificationActions } from '@webapp/store/ui'
import { AggregateFunctionExpressionEditor } from './AggregateFunctionExpressionEditor'

export const CustomAggregateFunctionEditor = (props) => {
const { aggregateFunction: aggregateFunctionParam, nodeDef, onCancel, onDelete, onSave: onSaveParam } = props
const {
aggregateFunction: aggregateFunctionParam,
entityDef,
nodeDef,
onCancel,
onDelete,
onSave: onSaveParam,
} = props

const dispatch = useDispatch()
const i18n = useI18n()
Expand Down Expand Up @@ -59,13 +67,14 @@ export const CustomAggregateFunctionEditor = (props) => {
/>
</FormItem>
<FormItem label={i18n.t('dataExplorerView.customAggregateFunction.sqlExpression')}>
<textarea
rows="4"
value={expression}
onChange={(e) =>
<AggregateFunctionExpressionEditor
entityDef={entityDef}
nodeDef={nodeDef}
expression={expression}
onChange={(value) =>
updateAggregateFunction({
propName: AggregateFunction.keys.expression,
value: e.target.value,
value,
})
}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { CustomAggregateFunctionViewer } from './CustomAggregateFunctionViewer'
import { useCustomAggregateFunctionsEditor } from './useCustomAggregateFunctionsEditor'

export const CustomAggregateFunctionsEditor = (props) => {
const { nodeDef, selectedUuids, onSelectionChange } = props
const { entityDef, nodeDef, selectedUuids, onSelectionChange } = props

const i18n = useI18n()
const { customAggregateFunctionsArray, editedUuid, setEditedUuid, onDelete, onNew, onSave, onEditCancel } =
Expand All @@ -37,6 +37,7 @@ export const CustomAggregateFunctionsEditor = (props) => {
{editing ? (
<CustomAggregateFunctionEditor
aggregateFunction={aggregateFunction}
entityDef={entityDef}
nodeDef={nodeDef}
onCancel={onEditCancel}
onDelete={onDelete}
Expand All @@ -62,6 +63,7 @@ export const CustomAggregateFunctionsEditor = (props) => {
}

CustomAggregateFunctionsEditor.propTypes = {
entityDef: PropTypes.object.isRequired,
nodeDef: PropTypes.object.isRequired,
selectedUuids: PropTypes.array,
onSelectionChange: PropTypes.func,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,6 @@

.form-item {
height: auto;

textarea {
height: 120px;
}
}

.button-bar {
Expand Down
78 changes: 78 additions & 0 deletions webapp/components/ScriptEditor/ScriptEditor.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import './ScriptEditor.scss'

import React, { useEffect, useRef } from 'react'
import PropTypes from 'prop-types'

import AceEditor from 'react-ace'
import * as ace from 'ace-builds'

import 'ace-builds/webpack-resolver'
import 'ace-builds/src-noconflict/mode-r'
import 'ace-builds/src-noconflict/theme-github'
import 'ace-builds/src-noconflict/ext-searchbox'
import 'ace-builds/src-noconflict/ext-language_tools'

const aceLangTools = ace.require('ace/ext/language_tools')
const { snippetCompleter, textCompleter, keyWordCompleter } = aceLangTools
const defaultCompleters = [snippetCompleter, textCompleter, keyWordCompleter]

export const ScriptEditor = (props) => {
const { name, mode, script, completer, height, width, onChange } = props

const editorRef = useRef()

useEffect(() => {
const { editor } = editorRef.current

// reset completers
editor.completers = [...defaultCompleters]

// add custom completer (if any)
if (completer) {
editor.completers.push(completer)
}
}, [completer])

return (
<AceEditor
ref={editorRef}
mode={mode}
theme="github"
onChange={onChange}
name={name}
editorProps={{ $blockScrolling: true }}
fontSize={16}
showPrintMargin
showGutter
highlightActiveLine
setOptions={{
enableBasicAutocompletion: true,
enableLiveAutocompletion: true,
showLineNumbers: true,
useWorker: true,
tabSize: 2,
}}
defaultValue={script}
height={height}
width={width}
/>
)
}

ScriptEditor.propTypes = {
name: PropTypes.string.isRequired,
mode: PropTypes.oneOf(['r', 'sql']).isRequired,
completer: PropTypes.shape({
getCompletions: PropTypes.func.isRequired,
}),
height: PropTypes.string,
width: PropTypes.string,
script: PropTypes.string.isRequired,
onChange: PropTypes.func.isRequired,
}

ScriptEditor.defaultProps = {
completer: null,
height: '200px',
width: 'inherit',
}
5 changes: 5 additions & 0 deletions webapp/components/ScriptEditor/ScriptEditor.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
@import '~@webapp/style/vars';

.ace_editor {
border: 1px solid $greyBorder;
}
1 change: 1 addition & 0 deletions webapp/components/ScriptEditor/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { ScriptEditor } from './ScriptEditor'

This file was deleted.

99 changes: 0 additions & 99 deletions webapp/components/survey/NodeDefDetails/AnalysisCodeProps/index.js

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
@import '~@webapp/style/vars';

.script-form {
align-items: normal;
}
Loading

0 comments on commit 1de1b8a

Please sign in to comment.