Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
ed43573
feat: get description of the selected function
shahzeelahmed Jan 3, 2026
aff88d8
feat: define `FunctionSignature` type
shahzeelahmed Jan 6, 2026
0dc14b7
fix: evaluate RHS of an expression instead of LHS
shahzeelahmed Jan 7, 2026
7a79c30
feat: pre-validation for custom expressions
shahzeelahmed Jan 7, 2026
7ebe897
feat: add column highlighting feature in code editor
shahzeelahmed Jan 7, 2026
247ec42
feat: add scoped styles for column highlighting in code editor
shahzeelahmed Jan 7, 2026
9d4676c
feat: function signature handling and add expression validation
shahzeelahmed Jan 7, 2026
eb47fc5
feat: enhance expression validation UI with loading and error indicators
shahzeelahmed Jan 7, 2026
26d969c
fix: dialog border radius
shahzeelahmed Jan 7, 2026
20e4e54
feat: implement expression validation with error handling
shahzeelahmed Jan 8, 2026
9f488bc
chore: cleanup
shahzeelahmed Jan 8, 2026
4a17002
feat: add measure name and data type inputs to NewMeasureSelectorDialog
shahzeelahmed Jan 11, 2026
5aab92c
chore: better colors for code editor
shahzeelahmed Jan 12, 2026
b1b4544
fix: align error text with icon
shahzeelahmed Jan 12, 2026
6ead6b6
feat: enhance function search input with icon and improved layout
shahzeelahmed Jan 12, 2026
4fed398
chore: improve styling
shahzeelahmed Jan 12, 2026
7d8f812
chore: formatting
shahzeelahmed Jan 12, 2026
b6f48cc
feat: improve validation ui
shahzeelahmed Jan 12, 2026
86149ca
chore: remove collapsible section
shahzeelahmed Jan 12, 2026
c54c741
fix: adjust validation error display and increase function section he…
shahzeelahmed Jan 13, 2026
4a69bbd
fix: update validation message styling and adjust layout for better r…
shahzeelahmed Jan 13, 2026
5e0e3c1
fix(style): maintain proper padding
shahzeelahmed Jan 13, 2026
33ee19f
fix(style): decrease code editor height
shahzeelahmed Jan 13, 2026
4053f01
feat: include columns with functions list
shahzeelahmed Jan 13, 2026
4ebb669
Merge remote-tracking branch 'upstream/develop' into custom-measure
shahzeelahmed Jan 17, 2026
6ce3f8e
fix(ui): rounded dialog border
shahzeelahmed Jan 17, 2026
4b3b67d
feat(ui): remove signature element
shahzeelahmed Jan 17, 2026
71f388b
feat(validation): add validation linter for error diagnostics
shahzeelahmed Jan 18, 2026
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
255 changes: 219 additions & 36 deletions frontend/src2/charts/components/NewMeasureSelectorDialog.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ import { COLUMN_TYPES, FIELDTYPES } from '../../helpers/constants'
import ExpressionEditor from '../../query/components/ExpressionEditor.vue'
import { expression } from '../../query/helpers'
import { ColumnOption, ExpressionMeasure, MeasureDataType } from '../../types/query.types'
import { cachedCall } from '../../helpers'
import { TextInput } from 'frappe-ui'
import { SearchIcon } from 'lucide-vue-next'

const props = defineProps<{
measure?: ExpressionMeasure
Expand Down Expand Up @@ -34,68 +37,248 @@ const isValid = computed(() => {
return newMeasure.value.name && newMeasure.value.type && newMeasure.value.expression.trim()
})

function confirmCalculation() {
const validationState = ref<'unknown' | 'validating' | 'valid' | 'invalid'>('unknown')
const validationErrors = ref<Array<{ line?: number; column?: number; message: string }>>([])

async function confirmCalculation() {
if (!isValid.value) return
emit('select', {
measure_name: newMeasure.value.name,
data_type: newMeasure.value.type,
expression: expression(newMeasure.value.expression),
})
resetNewMeasure()
showDialog.value = false
validationState.value = 'validating'
validationErrors.value = []
try {
const res: any = await cachedCall(
'insights.insights.doctype.insights_data_source_v3.ibis.utils.validate_expression',
{
expression: newMeasure.value.expression,
column_options: JSON.stringify(props.columnOptions),
}
)

if (!res || !res.is_valid) {
validationState.value = 'invalid'
validationErrors.value = res?.errors || [{ message: 'Validation failed' }]
return
}

validationState.value = 'valid'
emit('select', {
measure_name: newMeasure.value.name,
data_type: newMeasure.value.type,
expression: expression(newMeasure.value.expression),
})
resetNewMeasure()
showDialog.value = false
} catch (e) {
console.error(e)
validationState.value = 'unknown'
validationErrors.value = [{ message: 'Unexpected validation error' }]
}
}

function resetNewMeasure() {
newMeasure.value = {
name: 'new_measure',
type: columnTypes[0],
expression: '',
}
}

const functionList = ref<string[]>([])
const selectedFunction = ref<string>('')

const searchTerm = ref('')
const filteredFunctions = computed(() => {
const searchQuery = searchTerm.value.trim().toLowerCase()
if (!searchQuery) return functionList.value
return functionList.value.filter((fn) => fn.toLowerCase().includes(searchQuery))
})

type FunctionSignature = {
name: string
definition: string
description: string
current_param: string
current_param_description: string
params: { name: string; description: string }[]
}
const functionDoc = ref<FunctionSignature | null>(null)
const columns = props.columnOptions.map((c) => c.label)
cachedCall('insights.insights.doctype.insights_data_source_v3.ibis.utils.get_function_list').then(
(res: any) => {
const result = [...res,...columns]
functionList.value = result
}
)

function selectFunction(funcName: string) {
selectedFunction.value = funcName

cachedCall(
'insights.insights.doctype.insights_data_source_v3.ibis.utils.get_function_description',
{ funcName }
)
.then((res: any) => {
if (res) {
functionDoc.value = res
}
})
.catch(console.error)
}

function updateDocumentationFromEditor(currentFunction: any) {
if (currentFunction) {
functionDoc.value = currentFunction
selectedFunction.value = currentFunction.name
}
}
</script>

<template>
<Dialog
:modelValue="Boolean(showDialog)"
:options="{ title: 'Create Measure' }"
:options="{ title: 'Create Measure',size:'2xl'
}"
@after-leave="resetNewMeasure"
@close="!newMeasure.expression && (showDialog = false)"
>
<template #body-content>
<div class="flex flex-col gap-2">
<ExpressionEditor
v-model="newMeasure.expression"
:column-options="props.columnOptions"
/>
<div class="flex gap-2">
<FormControl
type="text"
class="flex-1"
label="Measure Name"
autocomplete="off"
placeholder="Measure Name"
v-model="newMeasure.name"
/>
<FormControl
type="select"
class="flex-1"
label="Data Type"
autocomplete="off"
:options="columnTypes"
v-model="newMeasure.type"
<template #body>
<div class="min-w-[30rem] flex flex-col px-4 pb-4 pt-3">
<div class="flex justify-between pb-2">
<h3 class="text-2xl font-semibold leading-6 text-gray-900">Create Measure</h3>
<Button variant="ghost" @click="showDialog = false" icon="x" size="md" />
</div>

<div class="flex flex-col gap-3">
<div class="flex gap-2">
<FormControl
type="text"
class="flex-1"
label="Measure Name"
autocomplete="off"
placeholder="Measure Name"
v-model="newMeasure.name"
/>
<FormControl
type="select"
class="flex-1"
label="Data Type"
autocomplete="off"
:options="columnTypes"
v-model="newMeasure.type"
/>
</div>
<ExpressionEditor
v-model="newMeasure.expression"
:column-options="props.columnOptions"
@function-signature-update="updateDocumentationFromEditor"
/>
<div
v-if="validationErrors.length"
class="mt-2 rounded border border-red-200 bg-red-50 p-3 text-sm text-red-800"
>
<div class="font-medium">Validation Errors</div>
<ul class="mt-1 pl-5">
<li v-for="(err, idx) in validationErrors" :key="idx">
<span v-if="err.line !== undefined"
>Lin {{ err.line }}, Col {{ err.column }}: </span
>{{ err.message }}
</li>
</ul>
</div>
<div class="flex h-[12rem] gap-4">
<div class="w-[33%] flex flex-col border-r pr-4">
<TextInput
v-model="searchTerm"
type="text"
placeholder="Search"
class="w-full text-sm mb-1"
>
<template #prefix>
<SearchIcon class="h-4 w-4 text-gray-600" />
</template>
</TextInput>
<div class="flex-1 overflow-y-auto">
<div
v-if="filteredFunctions.length === 0"
class="flex h-full w-full items-center justify-center"
>
<p class="text-sm text-gray-500">No functions found</p>
</div>
<div
v-for="fn in filteredFunctions"
:key="fn"
@click="selectFunction(fn)"
:class="[
'cursor-pointer rounded px-2 py-1.5 text-sm',
selectedFunction === fn
? 'bg-blue-50 text-blue-700'
: 'hover:bg-gray-50 text-gray-700',
]"
>
{{ fn }}
</div>
</div>
</div>
<div class="flex-1 overflow-y-auto">
<div
v-if="!functionDoc"
class="flex h-full w-full items-center justify-center"
>
<p class="text-sm text-gray-500">
Select a function to see details
</p>
</div>
<div v-if="functionDoc" class="flex flex-col gap-3">
<div
v-if="functionDoc.definition"
v-html="functionDoc.definition"
class="font-mono text-sm text-gray-800 bg-gray-50 rounded p-2"
></div>
<div
v-if="functionDoc.description"
class="whitespace-pre-wrap text-sm text-gray-700"
>
{{ functionDoc.description }}
</div>
<div
v-if="functionDoc.params?.length"
class="flex flex-col gap-2"
>
<h5 class="text-sm font-medium text-gray-700">
Parameters:
</h5>
<div
v-for="param in functionDoc.params"
:key="param.name"
class="ml-2 text-sm"
>
<span class="font-mono font-medium text-gray-800">{{
param.name
}}</span>
<span class="text-gray-600"
>: {{ param.description }}</span
>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="mt-2 flex items-center justify-between gap-2">
<div></div>
<div class="flex items-center gap-2">

<div class="mt-4 flex items-center justify-end gap-2">
<Button
label="Confirm"
variant="solid"
:disabled="!isValid"
:disabled="!isValid || validationState === 'validating'"
@click="confirmCalculation"
/>
</div>
</div>
</template>
</Dialog>
</template>

<style>
div[data-dismissable-layer] {
border-radius: 0.75rem;
}
</style>
Loading