Extend filter fields functionality with c-ace editor and autocomplete

This commit is contained in:
Kelani Tolulope
2024-12-11 15:35:16 +01:00
committed by Jože Fortun
parent 4c2afef058
commit d3394a7d9a
25 changed files with 976 additions and 107 deletions
@@ -95,9 +95,11 @@
:label="$t('calendar.recordFeed.prefilterLabel')"
label-class="text-primary"
>
<b-form-textarea
<c-input-expression
v-model="feed.options.prefilter"
:value="true"
height="3.688rem"
lang="javascript"
:suggestion-params="recordAutoCompleteParams"
:placeholder="$t('calendar.recordFeed.prefilterPlaceholder')"
/>
@@ -142,7 +144,10 @@
<script>
import base from './base'
import { components } from '@cortezaproject/corteza-vue'
const { CInputColorPicker } = components
import { compose, NoID } from '@cortezaproject/corteza-js'
import autocomplete from 'corteza-webapp-compose/src/mixins/autocomplete.js'
const { CInputColorPicker, CInputExpression } = components
export default {
i18nOptions: {
@@ -151,10 +156,26 @@ export default {
components: {
CInputColorPicker,
CInputExpression,
},
extends: base,
mixins: [autocomplete],
props: {
record: {
type: compose.Record,
required: false,
default: undefined,
},
page: {
type: compose.Page,
required: true,
},
},
computed: {
/**
* Finds the module, this feed configurator should use
@@ -213,6 +234,14 @@ export default {
themeSettings () {
return this.$Settings.get('ui.studio.themes', [])
},
isRecordPage () {
return this.page && this.page.moduleID !== NoID
},
recordAutoCompleteParams () {
return this.processRecordAutoCompleteParams({ operators: true })
},
},
methods: {
@@ -39,6 +39,9 @@
v-if="feed.resource && configurator(feed)"
:feed="feed"
:modules="modules"
:page="page"
:record="record"
:module="module"
/>
</b-card>
</b-col>
@@ -65,11 +68,15 @@ export default {
namespaces: 'block',
},
components: {
},
extends: base,
props: {
page: {
type: compose.Page,
required: true,
},
},
computed: {
...mapGetters({
modules: 'module/set',
@@ -44,9 +44,11 @@
:label="$t('recordList.record.prefilterLabel')"
label-class="text-primary"
>
<b-form-textarea
<c-input-expression
v-model.trim="options.filter"
:value="true"
height="3.688rem"
lang="javascript"
:suggestion-params="recordAutoCompleteParams"
:placeholder="$t('recordList.record.prefilterPlaceholder')"
/>
@@ -152,8 +154,12 @@
</template>
<script>
import { mapGetters } from 'vuex'
import { components } from '@cortezaproject/corteza-vue'
import autocomplete from 'corteza-webapp-compose/src/mixins/autocomplete.js'
import base from './base'
const { CInputExpression } = components
export default {
i18nOptions: {
namespaces: 'block',
@@ -161,7 +167,14 @@ export default {
name: 'CommentConfigurator',
components: {
CInputExpression,
},
extends: base,
mixins: [autocomplete],
data () {
return {
referenceList: [
@@ -220,6 +233,10 @@ export default {
}
return []
},
recordAutoCompleteParams () {
return this.processRecordAutoCompleteParams({ module: this.selectedModule, operators: true })
},
},
watch: {
@@ -17,10 +17,15 @@
label-class="text-primary"
>
<b-input-group>
<b-form-input
<c-input-expression
id="title"
v-model="block.title"
auto-complete
lang="javascript"
:placeholder="$t('general.titlePlaceholder')"
:suggestion-params="recordAutoCompleteParams"
height="2.375rem"
class="flex-grow-1"
/>
<b-input-group-append>
@@ -53,10 +58,15 @@
label-class="text-primary"
>
<b-input-group>
<b-form-textarea
<c-input-expression
id="description"
v-model="block.description"
auto-complete
lang="javascript"
:placeholder="$t('general.descriptionPlaceholder')"
:suggestion-params="recordAutoCompleteParams"
height="3.448rem"
class="flex-grow-1"
/>
<b-input-group-append>
<page-translator
@@ -257,9 +267,15 @@
ƒ
</b-button>
</b-input-group-prepend>
<b-form-input
<c-input-expression
id="visibility-fields"
v-model="block.meta.visibility.expression"
auto-complete
lang="javascript"
:placeholder="$t('general.visibility.condition.placeholder')"
:suggestion-params="visibilityAutoCompleteParams"
height="2.375rem"
class="flex-grow-1"
/>
<b-input-group-append>
<b-button
@@ -342,12 +358,16 @@
</template>
</b-tabs>
</template>
<script>
import { compose, NoID } from '@cortezaproject/corteza-js'
import { handle } from '@cortezaproject/corteza-vue'
import { handle, components } from '@cortezaproject/corteza-vue'
import PageTranslator from 'corteza-webapp-compose/src/components/Admin/Page/PageTranslator'
import autocomplete from 'corteza-webapp-compose/src/mixins/autocomplete.js'
import PageBlock from './index'
const { CInputExpression } = components
export default {
i18nOptions: {
namespaces: 'block',
@@ -356,18 +376,38 @@ export default {
components: {
PageBlock,
PageTranslator,
CInputExpression,
},
mixins: [autocomplete],
props: {
block: {
type: compose.PageBlock,
required: true,
},
module: {
type: compose.Module,
required: false,
default: undefined,
},
page: {
type: compose.Page,
required: true,
},
record: {
type: [Object, null],
required: false,
default: null,
},
namespace: {
type: compose.Namespace,
required: true,
},
},
data () {
@@ -494,6 +534,7 @@ export default {
},
}
</script>
<style scoped>
.mh-tab {
max-height: calc(100vh - 16rem);
@@ -66,9 +66,11 @@
:label="$t('calendar.recordFeed.prefilterLabel')"
label-class="text-primary"
>
<b-form-textarea
<c-input-expression
v-model="feed.options.prefilter"
:value="true"
height="3.688rem"
lang="javascript"
:suggestion-params="recordAutoCompleteParams"
:placeholder="$t('calendar.recordFeed.prefilterPlaceholder')"
/>
@@ -146,7 +148,9 @@
<script>
import base from './base'
import { components } from '@cortezaproject/corteza-vue'
const { CInputColorPicker } = components
import autocomplete from 'corteza-webapp-compose/src/mixins/autocomplete.js'
const { CInputColorPicker, CInputExpression } = components
export default {
i18nOptions: {
@@ -155,10 +159,13 @@ export default {
components: {
CInputColorPicker,
CInputExpression,
},
extends: base,
mixins: [autocomplete],
data () {
return {
checkboxLabel: {
@@ -25,6 +25,8 @@
v-if="feed.resource && configurator(feed)"
:feed="feed"
:modules="modules"
:page="page"
:record="record"
/>
</b-card>
</b-col>
@@ -18,8 +18,12 @@
:description="enableFromRecordURL ? $t('iframe.srcDesc') : ''"
label-class="text-primary"
>
<b-form-input
<c-input-expression
v-model="options.src"
auto-complete
lang="text"
:suggestion-params="recordAutoCompleteParams"
height="2.375rem"
type="url"
/>
<i18next
@@ -37,6 +41,10 @@
</template>
<script>
import base from './base'
import { components } from '@cortezaproject/corteza-vue'
import autocomplete from 'corteza-webapp-compose/src/mixins/autocomplete.js'
const { CInputExpression } = components
export default {
i18nOptions: {
@@ -45,8 +53,12 @@ export default {
name: 'IFrame',
components: { CInputExpression },
extends: base,
mixins: [autocomplete],
computed: {
fields () {
if (!this.module) {
@@ -147,10 +147,14 @@
:label="$t('metric.edit.filterLabel')"
label-class="text-primary"
>
<b-form-textarea
<c-input-expression
v-model="edit.filter"
auto-complete
lang="javascript"
placeholder="(A > B) OR (A < C)"
class="mb-1"
height="3.448rem"
:suggestion-params="recordAutoCompleteParams"
/>
<i18next
@@ -204,10 +208,14 @@
:label="$t('metric.edit.transformFunctionLabel')"
label-class="text-primary"
>
<b-form-textarea
<c-input-expression
v-model="edit.transformFx"
auto-complete
lang="javascript"
placeholder="v"
class="mb-1"
height="3.448rem"
:suggestion-params="recordAutoCompleteParams"
/>
<small>{{ $t('metric.edit.transformFunctionDescription') }}</small>
@@ -350,7 +358,11 @@ import MStyle from './MStyle'
import { mapGetters } from 'vuex'
import MetricBase from '../MetricBase'
import ColumnPicker from 'corteza-webapp-compose/src/components/Admin/Module/Records/ColumnPicker'
import autocomplete from 'corteza-webapp-compose/src/mixins/autocomplete.js'
import { compose, NoID } from '@cortezaproject/corteza-js'
import { components } from '@cortezaproject/corteza-vue'
const { CInputExpression } = components
export default {
i18nOptions: {
@@ -362,9 +374,12 @@ export default {
MStyle,
MetricBase,
ColumnPicker,
CInputExpression,
},
extends: base,
mixins: [autocomplete],
data () {
return {
edit: undefined,
@@ -436,6 +451,10 @@ export default {
return this.getModuleByID(this.edit.moduleID)
},
recordAutoCompleteParams () {
return this.processRecordAutoCompleteParams({ module: this.selectedMetricModule })
},
},
watch: {
@@ -50,10 +50,13 @@
:label="$t('metric.edit.filterLabel')"
label-class="text-primary"
>
<b-form-textarea
<c-input-expression
v-model="options.value.filter"
height="3.688rem"
lang="javascript"
placeholder="(A > B) OR (A < C)"
class="mb-1"
:suggestion-params="recordAutoCompleteParams.value"
/>
<i18next
path="metric.edit.filterFootnote"
@@ -160,10 +163,13 @@
:label="$t('metric.edit.filterLabel')"
label-class="text-primary"
>
<b-form-textarea
<c-input-expression
v-model="options.minValue.filter"
height="3.688rem"
lang="javascript"
placeholder="(A > B) OR (A < C)"
class="mb-1"
:suggestion-params="recordAutoCompleteParams.min"
/>
<i18next
path="metric.edit.filterFootnote"
@@ -270,10 +276,13 @@
:label="$t('metric.edit.filterLabel')"
label-class="text-primary"
>
<b-form-textarea
<c-input-expression
v-model="options.maxValue.filter"
height="3.688rem"
lang="javascript"
placeholder="(A > B) OR (A < C)"
class="mb-1"
:suggestion-params="recordAutoCompleteParams.max"
/>
<i18next
path="metric.edit.filterFootnote"
@@ -532,9 +541,13 @@
<script>
import base from './base'
import { mapGetters } from 'vuex'
import { components } from '@cortezaproject/corteza-vue'
import { compose, validator } from '@cortezaproject/corteza-js'
import autocomplete from 'corteza-webapp-compose/src/mixins/autocomplete.js'
import FieldViewer from '../ModuleFields/Viewer'
const { CInputExpression } = components
export default {
i18nOptions: {
namespaces: 'block',
@@ -544,10 +557,13 @@ export default {
components: {
FieldViewer,
CInputExpression,
},
extends: base,
mixins: [autocomplete],
data () {
return {
aggregationOperations: [
@@ -606,13 +622,33 @@ export default {
return this.returnValueModuleFields(this.options.value.moduleID)
},
valueModule () {
return this.moduleByID(this.options.value.moduleID)
},
minValueModuleFields () {
return this.returnValueModuleFields(this.options.minValue.moduleID)
},
minValueModule () {
return this.moduleByID(this.options.minValue.moduleID)
},
maxValueModuleFields () {
return this.returnValueModuleFields(this.options.maxValue.moduleID)
},
maxValueModule () {
return this.moduleByID(this.options.maxValue.moduleID)
},
recordAutoCompleteParams () {
return {
value: this.processRecordAutoCompleteParams({ module: this.valueModule, operators: true }),
min: this.processRecordAutoCompleteParams({ module: this.minValueModule, operators: true }),
max: this.processRecordAutoCompleteParams({ module: this.maxValueModule, operators: true }),
}
},
},
watch: {
@@ -0,0 +1,213 @@
<template>
<c-form-table-wrapper hide-add-button>
<b-form-group
:label="$t('recordList.record.prefilterCommand')"
label-class="text-primary"
class="m-0"
>
<b-row v-if="textInput">
<b-col>
<b-form-group label-class="text-primary">
<c-input-expression
v-model="options.prefilter"
height="3.688rem"
lang="javascript"
:suggestion-params="recordAutoCompleteParams"
/>
<i18next
path="recordList.record.prefilterFootnote"
tag="small"
class="text-muted"
>
<code>${record.values.fieldName}</code>
<code>${recordID}</code>
<code>${ownerID}</code>
<span><code>${userID}</code>, <code>${user.name}</code></span>
</i18next>
</b-form-group>
</b-col>
</b-row>
<c-form-table-wrapper
v-else
hide-add-button
>
<filter-toolbox
v-model="filterGroup"
:module="module"
:namespace="namespace"
:mock.sync="mock"
reset-filter-on-created
/>
</c-form-table-wrapper>
<div class="mt-1 d-flex align-items-center">
<b-button
variant="link"
size="sm"
class="ml-auto text-decoration-none"
@click="toggleFilterView"
>
{{ $t('recordList.prefilter.toggleInputType') }}
</b-button>
</div>
</b-form-group>
</c-form-table-wrapper>
</template>
<script>
import { components } from '@cortezaproject/corteza-vue'
import { compose, validator } from '@cortezaproject/corteza-js'
import {
getRecordListFilterSql,
trimChar,
} from 'corteza-webapp-compose/src/lib/record-filter.js'
import FilterToolbox from 'corteza-webapp-compose/src/components/Common/FilterToolbox.vue'
import autocomplete from 'corteza-webapp-compose/src/mixins/autocomplete.js'
const { CInputExpression } = components
export default {
i18nOptions: {
namespaces: 'block',
},
name: 'RecordListConfiguratorPrefilter',
components: {
FilterToolbox,
CInputExpression,
},
mixins: [autocomplete],
props: {
options: {
type: Object,
required: true,
},
namespace: {
type: compose.Namespace,
required: true,
},
module: {
type: compose.Module,
required: true,
},
record: {
type: [Object, null],
required: false,
default: null,
},
},
data () {
return {
textInput: true,
filterGroup: [],
}
},
computed: {
recordAutoCompleteParams () {
return this.processRecordAutoCompleteParams({ operators: true })
},
},
created () {
// Change all module fields to single value to keep multi value fields and single value
const module = JSON.parse(JSON.stringify(this.module || {}))
module.fields = [
...[...module.fields].map((f) => {
f.multi = f.isMulti
f.isMulti = false
// Disable edge case options
if (f.kind === 'DateTime') {
f.options.onlyFutureValues = false
f.options.onlyPastValues = false
}
return f
}),
...this.module.systemFields().map((sf) => {
return { ...sf, label: this.$t(`field:system.${sf.name}`) }
}),
]
this.mock = {
namespace: this.namespace,
module,
errors: new validator.Validated(),
}
},
methods: {
toggleFilterView () {
if (!this.textInput) {
this.options.prefilter = this.parseFilter()
}
this.textInput = !this.textInput
},
getOptionKey ({ name }) {
return name
},
processFilter (filterGroup = this.value) {
return filterGroup.map(({ groupCondition, filter = [], name }) => {
filter = filter.map(({ record, ...f }) => {
if (record) {
f.value = record[f.name] || record.values[f.name]
}
if (this.isBetweenOperator(f.operator)) {
f.value = {
start: this.getField(f.name).isSystem
? record[`${f.name}-start`]
: record.values[`${f.name}-start`],
end: this.getField(f.name).isSystem
? record[`${f.name}-end`]
: record.values[`${f.name}-end`],
}
}
return f
})
return { groupCondition, filter, name }
})
},
isBetweenOperator (op) {
return ['BETWEEN', 'NOT BETWEEN'].includes(op)
},
parseFilter (filterGroup = this.filterGroup) {
const filter = this.processFilter(filterGroup)
const filterSqlArray = filter
.map(({ groupCondition, filter = [] }) => {
groupCondition = groupCondition ? ` ${groupCondition} ` : ''
filter = getRecordListFilterSql(filter)
return filter ? `${filter}${groupCondition}` : ''
})
.filter((filter) => filter)
const filterSql = trimChar(
trimChar(filterSqlArray.join(''), ' AND '),
' OR '
)
return filterSql
},
},
}
</script>
@@ -264,30 +264,13 @@
</b-col>
</b-row>
<b-row>
<b-col>
<b-form-group
:label="$t('recordList.record.prefilterCommand')"
label-class="text-primary"
>
<b-form-textarea
v-model="options.prefilter"
:placeholder="$t('recordList.record.prefilterPlaceholder')"
/>
<i18next
path="recordList.record.prefilterFootnote"
tag="small"
class="text-muted"
>
<code>${record.values.fieldName}</code>
<code>${recordID}</code>
<code>${ownerID}</code>
<span><code>${userID}</code>, <code>${user.name}</code></span>
</i18next>
</b-form-group>
</b-col>
</b-row>
<prefilter
:record="record"
:module="recordListModule"
:namespace="namespace"
:options="options"
:page="page"
/>
<hr>
@@ -45,9 +45,11 @@
:label="$t('recordList.record.prefilterLabel')"
label-class="text-primary"
>
<b-form-textarea
<c-input-expression
v-model.trim="options.filter"
:value="true"
height="3.688rem"
lang="javascript"
:suggestion-params="recordAutoCompleteParams"
:placeholder="$t('recordList.record.prefilterPlaceholder')"
/>
<i18next
@@ -196,8 +198,13 @@
import FieldEditor from '../ModuleFields/Editor'
import { mapGetters } from 'vuex'
import { compose, validator } from '@cortezaproject/corteza-js'
import { components } from '@cortezaproject/corteza-vue'
import autocomplete from 'corteza-webapp-compose/src/mixins/autocomplete.js'
import base from './base'
const { CInputExpression } = components
export default {
i18nOptions: {
namespaces: 'block',
@@ -207,10 +214,13 @@ export default {
components: {
FieldEditor,
CInputExpression,
},
extends: base,
mixins: [autocomplete],
data () {
return {
/*
@@ -275,6 +285,10 @@ export default {
{ value: 'modal', text: this.$t('recordOrganizer.openInModal') },
]
},
recordAutoCompleteParams () {
return this.processRecordAutoCompleteParams({ module: this.selectedModule })
},
},
watch: {
@@ -59,6 +59,8 @@
:button="currentButton"
:script="currentScript"
:trigger="currentTrigger"
:record="record"
:module="module"
@delete="deleteButton(currentButton)"
/>
</b-col>
@@ -8,8 +8,13 @@
:label="$t('buttonLabel')"
label-class="text-primary"
>
<b-form-input
<c-input-expression
v-model="button.label"
auto-complete
lang="javascript"
:suggestion-params="recordAutoCompleteParams"
:page="page"
height="2.375rem"
/>
<i18next
path="block:interpolationFootnote"
@@ -94,6 +99,10 @@
</template>
<script>
import { compose, NoID } from '@cortezaproject/corteza-js'
import { components } from '@cortezaproject/corteza-vue'
import autocomplete from 'corteza-webapp-compose/src/mixins/autocomplete.js'
const { CInputExpression } = components
export default {
i18nOptions: {
@@ -101,6 +110,12 @@ export default {
keyPrefix: 'automation',
},
components: {
CInputExpression,
},
extends: autocomplete,
props: {
button: {
type: Object,
@@ -128,6 +143,12 @@ export default {
type: compose.PageBlock,
required: true,
},
module: {
type: compose.Module,
required: false,
default: undefined,
},
},
computed: {
@@ -1,5 +1,5 @@
<script>
import { compose } from '@cortezaproject/corteza-js'
import { compose, NoID } from '@cortezaproject/corteza-js'
import Wrap from './Wrap'
export default {
@@ -106,6 +106,35 @@ export default {
return !!recordPageID || !!magnifiedBlockID
},
isRecordPage () {
return this.page && this.page.moduleID !== NoID
},
recordAutoCompleteParams () {
const { fields = [] } = this.module || {}
const moduleFields = fields.map(({ name }) => name)
const userProperties = this.$auth.user.properties() || []
const recordSuggestions = this.isRecordPage ? [
...(['ownerID', 'recordID'].map(value => ({ interpolate: true, value }))),
{
interpolate: true,
value: 'record',
properties: [
...(this.record.properties || []),
{ value: 'values', properties: Object.keys(this.record.values) || [] },
],
},
] : []
return [
...moduleFields,
...recordSuggestions,
{ interpolate: true, value: 'userID' },
{ interpolate: true, value: 'user', properties: userProperties },
]
},
},
beforeDestroy () {
@@ -0,0 +1,81 @@
import { compose, NoID } from '@cortezaproject/corteza-js'
export default {
props: {
record: {
type: compose.Record,
required: false,
default: undefined,
},
page: {
type: compose.Page,
required: true,
},
},
computed: {
isRecordPage () {
return this.page && this.page.moduleID !== NoID
},
recordAutoCompleteParams () {
return this.processRecordAutoCompleteParams({})
},
visibilityAutoCompleteParams () {
return this.processVisibilityAutoCompleteParams({})
},
},
methods: {
processRecordAutoCompleteParams ({ module = this.module, operators = false }) {
const { fields = [] } = module || {}
const moduleFields = fields.map(({ name }) => name)
const userProperties = this.$auth.user.properties() || []
const recordSuggestions = this.isRecordPage ? [
...(['ownerID', 'recordID'].map(value => ({ interpolate: true, value }))),
{
interpolate: true,
value: 'record',
properties: [
...(this.record.properties || []),
{ value: 'values', properties: Object.keys(this.record.values) || [] },
],
},
] : []
return [
...moduleFields,
...recordSuggestions,
...(operators ? ['AND', 'OR'] : []),
{ interpolate: true, value: 'userID' },
{ interpolate: true, value: 'user', properties: userProperties },
]
},
processVisibilityAutoCompleteParams ({ module = this.module }) {
const { fields = [] } = module || {}
const moduleFields = fields.map(({ name }) => name)
const userProperties = this.$auth.user.properties() || []
const recordSuggestions = this.isRecordPage ? [
{
value: 'record',
properties: [
...(this.record.properties || []),
{ value: 'values', properties: Object.keys(this.record.values) || [] },
],
},
] : []
return [
...moduleFields,
...recordSuggestions,
{ value: 'user', properties: userProperties },
{ value: 'screen', properties: ['width', 'height', 'userAgent', 'breakpoint'] },
]
},
},
}