Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
55 changes: 36 additions & 19 deletions apps/application/flow/step_node/form_node/impl/base_form_node.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,18 @@


def get_default_option(option_list, _type, value_field):
if option_list is not None and len(option_list) > 0:
default_value_list = [o.get(value_field) for o in option_list if o.get('default')]
if len(default_value_list) == 0:
return option_list[0].get(value_field)
else:
if _type == 'MultiSelect':
return default_value_list
try:
if option_list is not None and isinstance(option_list, list) and len(option_list) > 0:
default_value_list = [o.get(value_field) for o in option_list if o.get('default')]
if len(default_value_list) == 0:
return option_list[0].get(value_field)
else:
return default_value_list[0]
if _type == 'MultiSelect':
return default_value_list
else:
return default_value_list[0]
except Exception as _:
pass
return []


Expand All @@ -41,6 +44,13 @@ def write_context(step_variable: Dict, global_variable: Dict, node, workflow):
node.context['run_time'] = time.time() - node.context['start_time']


def generate_prompt(workflow_manage, _value):
try:
return workflow_manage.generate_prompt(_value)
except Exception as e:
return _value


class BaseFormNode(IFormNode):
def save_context(self, details, workflow_manage):
form_data = details.get('form_data', None)
Expand All @@ -58,27 +68,34 @@ def save_context(self, details, workflow_manage):
self.context[key] = form_data[key]

def reset_field(self, field):
if ['SingleSelect', 'MultiSelect', 'RadioCard'].__contains__(field.get('input_type')):
if field.get('assignment_method') == 'ref_variables':
option_list = self.workflow_manage.get_reference_field(field.get('option_list')[0],
field.get('option_list')[1:])
field['option_list'] = option_list
field['default_value'] = get_default_option(option_list, field.get('input_type'),
field.get('value_field'))

reset_field = ['field', 'label', 'default_value']
for f in reset_field:
_value = field[f]
if _value is None:
continue
if isinstance(_value, str):
field[f] = self.workflow_manage.generate_prompt(_value)
field[f] = generate_prompt(self.workflow_manage, _value)
elif f == 'label':
_label_value = _value.get('label')
_value['label'] = self.workflow_manage.generate_prompt(_label_value)
_value['label'] = generate_prompt(self.workflow_manage, _label_value)
tooltip = _value.get('attrs').get('tooltip')
if tooltip is not None:
_value.get('attrs')['tooltip'] = self.workflow_manage.generate_prompt(tooltip)
_value.get('attrs')['tooltip'] = generate_prompt(self.workflow_manage, tooltip)

if ['SingleSelect', 'MultiSelect', 'RadioCard', 'RadioRow'].__contains__(field.get('input_type')):
if field.get('assignment_method') == 'ref_variables':
option_list = self.workflow_manage.get_reference_field(field.get('option_list')[0],
field.get('option_list')[1:])
option_list = option_list if isinstance(option_list, list) else []
field['option_list'] = option_list
field['default_value'] = get_default_option(option_list, field.get('input_type'),
field.get('value_field'))

if ['JsonInput'].__contains__(field.get('input_type')):
if field.get('default_value_assignment_method') == 'ref_variables':
field['default_value'] = self.workflow_manage.get_reference_field(field.get('default_value')[0],
field.get('default_value')[1:])

return field

def execute(self, form_field_list, form_content_format, form_data, **kwargs) -> NodeResult:
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

Function get_default_option

  1. Type Checking: The function should use an explicit type check (isinstance) instead of relying on method existence to ensure robustness when handling different input types.
  2. Exception Handling: Although there's a try-except block, it currently uses a generic exception handler, which could catch unrelated errors. It might be beneficial to catch specific exceptions relevant to the operation (e.g., IndexError, ValueError).

Function write_context

  • No issues or optimizations.

Function generate_prompt

  • This function is designed correctly to handle missing prompt generation gracefully by returning the original value.

Class BaseFormNode

  1. Field Reset with Prompt Generation:

    • In the reset_field method, especially if you want prompt generation only for certain fields (like label), make sure the condition checks these conditions properly. Also, handle cases where _value might not be a dictionary before attempting to access keys like 'label'.
  2. Handling JSON Input Default Value Assignment Method:

    • Ensure that json_input_default_value_assignment_method is handled consistently regardless of its casing (upper vs. lower). Adjust the comparison accordingly.
  3. Optimization Recommendations

    • If workflow_manage.get_reference_field can return values that are not lists (perhaps due to incorrect logic or external dependencies), consider adding additional checks within the if statement to prevent runtime errors.

Here’s how you might improve those points:

class BaseFormNode(IFormNode):
    def save_context(self, details, workflow_manage):
        form_data = details.get('form_data', None)
        
        # Handle non-dict data appropriately if needed
        if form_data is not None and isinstance(form_data, dict):
            for key in form_data.keys():
                current_val = form_data.get(key)
                
                if current_val is not None:
                    if isinstance(current_val, str):
                        form_data[key] = generate_prompt(workflow_manage, current_val)
                    
                    if f == 'label':
                        label_dict = current_val
                        label_value = label_dict.get('label')
                        
                        if label_value is not None:
                            label_dict['label'] = generate_prompt(workflow_manage, label_value)
                            
                            tooltips = label_dict.get('attrs').get('tooltip')
                            if tooltips is not None:
                                labels_attr = label_dict.get('attrs')
                                labels_attr.update({'tooltip': generate_prompt(workflow_manage, tooltips)})
                                
                            form_data[key] = label_dict
        
            option_list_fields = ['SingleSelect', 'MultiSelect', 'RadioCard', 'RadioRow']
            
            for field in form_field_list:
                if field["input_type"] in option_list_fields:
                    assignment_method = field.get("assignment_method", "none")
                    
                    if assignment_method.lower() != 'ref_variables':
                        continue
                    
                    ref_values = field.get("option_list", [])
                    
                    if isinstance(ref_values[0], str):
                        opt_list = []
                        for ref in ref_values:
                            opt_list.append(generate_prompt(workspace.manage, ref))
                    else:
                        opt_list = [o.get("description") for o in ref_values]
                    
                    field["option_list"] = opt_list if isinstance(opt_list, list) else []
                    field.setdefault("default_value", get_default_option(opt_list, field["input_type"], field["value_field"]))
                    
                if field["input_type"] == 'JsonInput':
                    json_default_value_assign = field.get("default_value_assignment_method", "").lower()
                    
                    if json_default_value_assign != 'ref_variables':
                        continue
                
                    ref_vals_json = [v.strip('{ }') for v in field.pop("default_value")]
                    
                    if all([v.startswith('"') and v.endswith('"') for v in ref_vals_json]):
                        val_lst = []
                        for ref_v in ref_vals_json:
                            val_lst.append(generate_prompt(workspace.manage, ref_v))
                    else:
                        try:
                            lst_of_objs = eval('[%s]' % ', '.join(ref_vals_json))  # Assuming this works based on JSON-like syntax
                        
                        except (SyntaxError, ValueError):
                            print((f"Eval error! Invalid default value format for JsonInput: {ref_vals_json}"))
                            val_lst = []

These improvements address many of the identified potential issues, making your code more robust and maintainable. Always test thoroughly after changes to ensure no unintended behaviors arise.

Expand Down
38 changes: 20 additions & 18 deletions ui/src/components/dynamics-form/constructor/data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,16 @@ const input_type_list = [
value: 'TextInput',
},
{
label: t('dynamicsForm.input_type_list.PasswordInput'),
value: 'PasswordInput',
label: t('dynamicsForm.input_type_list.TextareaInput'),
value: 'TextareaInput',
},
{
label: t('dynamicsForm.input_type_list.Slider'),
value: 'Slider',
label: t('dynamicsForm.input_type_list.JsonInput'),
value: 'JsonInput',
},
{
label: t('dynamicsForm.input_type_list.SwitchInput'),
value: 'SwitchInput',
label: t('dynamicsForm.input_type_list.PasswordInput'),
value: 'PasswordInput',
},
{
label: t('dynamicsForm.input_type_list.SingleSelect'),
Expand All @@ -24,14 +24,6 @@ const input_type_list = [
label: t('dynamicsForm.input_type_list.MultiSelect'),
value: 'MultiSelect',
},
{
label: t('dynamicsForm.input_type_list.DatePicker'),
value: 'DatePicker',
},
{
label: t('dynamicsForm.input_type_list.JsonInput'),
value: 'JsonInput',
},
{
label: t('dynamicsForm.input_type_list.RadioCard'),
value: 'RadioCard',
Expand All @@ -41,12 +33,22 @@ const input_type_list = [
value: 'RadioRow',
},
{
label: t('dynamicsForm.input_type_list.UploadInput'),
value: 'UploadInput',
label: t('dynamicsForm.input_type_list.Slider'),
value: 'Slider',
},
{
label: t('dynamicsForm.input_type_list.TextareaInput'),
value: 'TextareaInput',
label: t('dynamicsForm.input_type_list.SwitchInput'),
value: 'SwitchInput',
},

{
label: t('dynamicsForm.input_type_list.DatePicker'),
value: 'DatePicker',
},

{
label: t('dynamicsForm.input_type_list.UploadInput'),
value: 'UploadInput',
},
]
export { input_type_list }
Original file line number Diff line number Diff line change
@@ -1,8 +1,52 @@
<template>
<el-form-item v-if="getModel">
<template #label>
<div class="flex-between">
{{ $t('dynamicsForm.AssignmentMethod.label', '赋值方式') }}
</div>
</template>

<el-row style="width: 100%" :gutter="10">
<el-radio-group v-model="formValue.default_value_assignment_method">
<el-radio :value="item.value" size="large" v-for="item in assignment_method_option_list"
>{{ item.label }}
<el-popover
width="300px"
v-if="item.value == 'ref_variables'"
class="box-item"
placement="top-start"
>
{{ $t('dynamicsForm.AssignmentMethod.ref_variables.popover') }}:
{{ $t('dynamicsForm.AssignmentMethod.ref_variables.json_format') }}

<template #reference>
<el-icon><InfoFilled /></el-icon>
</template>
</el-popover>
</el-radio>
</el-radio-group>
</el-row>
</el-form-item>
<el-form-item
v-if="formValue.default_value_assignment_method == 'ref_variables'"
:required="true"
prop="default_value"
:rules="[default_ref_variables_value_rule]"
>
<NodeCascader
ref="nodeCascaderRef"
:nodeModel="model"
class="w-full"
:placeholder="$t('views.applicationWorkflow.variable.placeholder')"
v-model="formValue.default_value"
/>
</el-form-item>

<el-form-item
class="defaultValueItem"
:label="$t('dynamicsForm.default.label')"
:required="formValue.required"
v-if="formValue.default_value_assignment_method == 'custom'"
prop="default_value"
:rules="[default_value_rule]"
>
Expand All @@ -16,19 +60,46 @@
</el-form-item>
</template>
<script setup lang="ts">
import { computed, onMounted, ref } from 'vue'
import { computed, onMounted, ref, inject, watch } from 'vue'
import { t } from '@/locales'
import NodeCascader from '@/workflow/common/NodeCascader.vue'
import JsonInput from '@/components/dynamics-form/items/JsonInput.vue'
const props = defineProps<{
modelValue: any
}>()
const getModel = inject('getModel') as any

const assignment_method_option_list = computed(() => {
const option_list = [
{
label: t('dynamicsForm.AssignmentMethod.custom.label', '自定义'),
value: 'custom',
},
]
if (getModel) {
option_list.push({
label: t('dynamicsForm.AssignmentMethod.ref_variables.label', '引用变量'),
value: 'ref_variables',
})
}
return option_list
})

const model = computed(() => {
if (getModel) {
return getModel()
} else {
return null
}
})
const emit = defineEmits(['update:modelValue'])
const formValue = computed({
set: (item) => {
emit('update:modelValue', item)
},
get: () => {
return props.modelValue
}
},
})
const jsonInputRef = ref<InstanceType<typeof JsonInput>>()
const getData = () => {
Expand All @@ -41,14 +112,15 @@ const getData = () => {
required: formValue.value.required,
validator: `validator = (rule, value, callback) => {
return componentFormRef.value?.validate_rules(rule, value, callback);

}`,
trigger: 'blur'
}
]
trigger: 'blur',
},
],
},
default_value: formValue.value.default_value,
show_default_value: formValue.value.show_default_value
show_default_value: formValue.value.show_default_value,
default_value_assignment_method: formValue.value.default_value_assignment_method || 'custom',
}
}

Expand All @@ -58,15 +130,31 @@ const default_value_rule = {
jsonInputRef.value?.validate_rules(rule, value, callback)
return true
},
trigger: 'blur'
trigger: 'blur',
}
const default_ref_variables_value_rule = {
required: true,
validator: (rule: any, value: any, callback: any) => {
if (!(Array.isArray(value) && value.length > 1)) {
callback(
t('dynamicsForm.AssignmentMethod.ref_variables.label', '引用变量') + t('common.required'),
)
}

return true
},
trigger: 'blur',
}

const rander = (form_data: any) => {
formValue.value.default_value = form_data.default_value
formValue.value.default_value_assignment_method =
form_data.default_value_assignment_method || 'custom'
}
defineExpose({ getData, rander })
onMounted(() => {
formValue.value.default_value = {}
formValue.value.default_value_assignment_method = 'custom'
if (formValue.value.show_default_value === undefined) {
formValue.value.show_default_value = true
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The provided code seems clean and well-structured. However, there are a few points of consideration:

  1. Dynamic Content: The content within <el-radio-group> can be more robust when dealing with dynamic data loading asynchronously.

  2. Validation Rules: Ensure that all validation rules are properly defined in default_value_rule to prevent unexpected behavior during updates or deletions of radio values.

  3. Error Messages: Add clear error messages and feedback through Vue's $message API for user interaction when form input requirements are not met.

  4. Testing: Test thoroughly after making changes to ensure no new bugs introduced due to modifications.

  5. Comments: Consider adding comments explaining complex logic or decisions to improve readability.

Overall, the current implementation is good but could be improved by considering these guidelines for better future development adaptability and maintainability.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
</template>

<el-row style="width: 100%" :gutter="10">
<el-radio-group v-model="formValue.assignment_method">
<el-radio-group @change="formValue.option_list = []" v-model="formValue.assignment_method">
<el-radio :value="item.value" size="large" v-for="item in assignment_method_option_list"
>{{ item.label }}
<el-popover
Expand Down Expand Up @@ -37,14 +37,21 @@
</el-radio-group>
</el-row>
</el-form-item>
<NodeCascader
<el-form-item
v-if="formValue.assignment_method == 'ref_variables'"
ref="nodeCascaderRef"
:nodeModel="model"
class="w-full"
:placeholder="$t('views.applicationWorkflow.variable.placeholder')"
v-model="formValue.option_list"
/>
:required="true"
prop="option_list"
:rules="[default_ref_variables_value_rule]"
>
<NodeCascader
ref="nodeCascaderRef"
:nodeModel="model"
class="w-full"
:placeholder="$t('views.applicationWorkflow.variable.placeholder')"
v-model="formValue.option_list"
/>
</el-form-item>

<el-form-item v-if="formValue.assignment_method == 'custom'">
<template #label>
<div class="flex-between">
Expand Down Expand Up @@ -139,7 +146,7 @@
</el-form-item>
</template>
<script setup lang="ts">
import { computed, onMounted, inject } from 'vue'
import { computed, onMounted, inject, watch } from 'vue'
import NodeCascader from '@/workflow/common/NodeCascader.vue'
import { t } from '@/locales'
const getModel = inject('getModel') as any
Expand Down Expand Up @@ -180,6 +187,20 @@ const formValue = computed({
},
})

const default_ref_variables_value_rule = {
required: true,
validator: (rule: any, value: any, callback: any) => {
console.log(value.length)
if (!(Array.isArray(value) && value.length > 1)) {
callback(
t('dynamicsForm.AssignmentMethod.ref_variables.label', '引用变量') + t('common.required'),
)
}

return true
},
trigger: 'blur',
}
const addOption = () => {
formValue.value.option_list.push({ value: '', label: '' })
}
Expand Down
Loading
Loading