Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enhancement/aggregate functions expression editor #1545

Merged
merged 5 commits into from
Jun 29, 2021
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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