[PR #23323] retention of data filled on tab switch for tool plugin #30237

Closed
opened 2026-02-21 20:47:06 -05:00 by yindo · 0 comments
Owner

Original Pull Request: https://github.com/langgenius/dify/pull/23323

State: closed
Merged: Yes


Important

  1. Make sure you have read our contribution guidelines
  2. Ensure there is an associated issue and you have been assigned to it
  3. Use the correct syntax to link this PR: Fixes #<issue number>.

Summary

Screenshots

Before After
... ...

script to test function schemaWithDynamicSelect

// Mock FormTypeEnum
const FormTypeEnum = {
  dynamicSelect: 'dynamic-select'
};

// BEFORE: Original problematic implementation
function schemaWithDynamicSelectBefore(schema, dynamicOptions) {
  if (schema?.type !== FormTypeEnum.dynamicSelect)
    return schema;

  if (dynamicOptions) {
    return {
      ...schema,
      options: dynamicOptions,
    };
  }

  return schema;
}

function schemaWithDynamicSelectAfter(schema, dynamicOptions, isLoading, value) {
  if (schema?.type !== FormTypeEnum.dynamicSelect)
    return schema
  // rewrite schema.options with dynamicOptions
  if (dynamicOptions) {
    return {
      ...schema,
      options: dynamicOptions,
    }
  }

  // If we don't have dynamic options but we have a selected value, create a temporary option to preserve the selection during loading
  if (isLoading && value && typeof value === 'string') {
    const preservedOptions = [{
      value,
      label: { en_US: value, zh_Hans: value },
      show_on: [],
    }]
    return {
      ...schema,
      options: preservedOptions,
    }
  }

  // Default case: return schema with empty options
  return {
    ...schema,
    options: [],
  }
}



// Test cases
function runTests() {
  console.log('🧪 Testing schemaWithDynamicSelect Before vs After Fix');
  console.log('='.repeat(60));

  // Test data
  const testSchema = {
    type: 'dynamic-select',
    name: 'test_parameter',
    options: [] // Initially empty
  };

  const mockDynamicOptions = [
    { value: 'option1', label: { en_US: 'Option 1', zh_Hans: '选项1' }, show_on: [] },
    { value: 'option2', label: { en_US: 'Option 2', zh_Hans: '选项2' }, show_on: [] }
  ];

  console.log('\n📋 Test Cases:');
  console.log('-'.repeat(40));

  // Test Case 1: Normal case with dynamic options available
  console.log('\n1️⃣  Case 1: Dynamic options available');
  const result1Before = schemaWithDynamicSelectBefore(testSchema, mockDynamicOptions);
  const result1After = schemaWithDynamicSelectAfter(testSchema, mockDynamicOptions, false, null);

  console.log('   BEFORE:', result1Before.options?.length || 0, 'options');
  console.log('   AFTER: ', result1After.options?.length || 0, 'options');
  console.log('   ✅ Both should work the same (have options)');

  // Test Case 2: Critical case - no dynamic options, but user has selected value
  console.log('\n2️⃣  Case 2: No dynamic options, user has selected value, loading=true (THE BUG)');
  const selectedValue = 'previously_selected_value';
  const result2Before = schemaWithDynamicSelectBefore(testSchema, null);
  const result2After = schemaWithDynamicSelectAfter(testSchema, null, true, selectedValue);

  console.log('   BEFORE:', result2Before.options?.length || 0, 'options');
  console.log('   AFTER: ', result2After.options?.length || 0, 'options');
  console.log('   🐛 BEFORE: User loses their selection!');
  console.log('   ✅ AFTER:  Selection preserved during loading');

  // Test Case 3: No dynamic options, no selected value
  console.log('\n3️⃣  Case 3: No dynamic options, no selected value');
  const result3Before = schemaWithDynamicSelectBefore(testSchema, null);
  const result3After = schemaWithDynamicSelectAfter(testSchema, null, false, null);

  console.log('   BEFORE:', result3Before.options?.length || 0, 'options');
  console.log('   AFTER: ', result3After.options?.length || 0, 'options');
  console.log('   ✅ Both should be empty (no options to show)');

  // Test Case 4: Non-dynamic-select schema (should pass through unchanged)
  console.log('\n4️⃣  Case 4: Non-dynamic-select schema');
  const nonDynamicSchema = { type: 'string', name: 'regular_param' };
  const result4Before = schemaWithDynamicSelectBefore(nonDynamicSchema, null);
  const result4After = schemaWithDynamicSelectAfter(nonDynamicSchema, null, false, null);

  console.log('   BEFORE:', result4Before.type);
  console.log('   AFTER: ', result4After.type);
  console.log('   ✅ Both should pass through unchanged');

  // Detailed comparison for the critical bug case
  console.log('\n🔍 Detailed Analysis of Critical Case:');
  console.log('-'.repeat(40));
  console.log('Scenario: User switches between nodes, causing dynamicOptions reset');
  console.log('State: isLoading=true, dynamicOptions=null, value="user_selection"');

  const criticalBefore = schemaWithDynamicSelectBefore(testSchema, null);
  const criticalAfter = schemaWithDynamicSelectAfter(testSchema, null, true, 'user_selection');

  console.log('\nBEFORE (Buggy):');
  console.log('  Options:', JSON.stringify(criticalBefore.options || [], null, 2));
  console.log('  Result: User sees their selection as "invalid" ❌');

  console.log('\nAFTER (Fixed):');
  console.log('  Options:', JSON.stringify(criticalAfter.options, null, 2));
  console.log('  Result: User sees their selection preserved ✅');

  console.log('\n🎯 Summary:');
  console.log('The fix prevents data loss by creating temporary options during loading');
  console.log('when a user has a selected value but dynamic options are being reloaded.');
}

// Run the tests
runTests();

logs of above script

🧪 Testing schemaWithDynamicSelect Before vs After Fix
============================================================

📋 Test Cases:
----------------------------------------

1️⃣  Case 1: Dynamic options available
   BEFORE: 2 options
   AFTER:  2 options
   ✅ Both should work the same (have options)

2️⃣  Case 2: No dynamic options, user has selected value, loading=true (THE BUG)
   BEFORE: 0 options
   AFTER:  1 options
   🐛 BEFORE: User loses their selection!
   ✅ AFTER:  Selection preserved during loading

3️⃣  Case 3: No dynamic options, no selected value
   BEFORE: 0 options
   AFTER:  0 options
   ✅ Both should be empty (no options to show)

4️⃣  Case 4: Non-dynamic-select schema
   BEFORE: string
   AFTER:  string
   ✅ Both should pass through unchanged

🔍 Detailed Analysis of Critical Case:
----------------------------------------
Scenario: User switches between nodes, causing dynamicOptions reset
State: isLoading=true, dynamicOptions=null, value="user_selection"

BEFORE (Buggy):
  Options: []
  Result: User sees their selection as "invalid" ❌

AFTER (Fixed):
  Options: [
  {
    "value": "user_selection",
    "label": {
      "en_US": "user_selection",
      "zh_Hans": "user_selection"
    },
    "show_on": []
  }
]
  Result: User sees their selection preserved ✅

Checklist

  • This change requires a documentation update, included: Dify Document
  • I understand that this PR may be closed in case there was no previous discussion or issues. (This doesn't apply to typos!)
  • I've added a test for each change that was introduced, and I tried as much as possible to make a single atomic change.
  • I've updated the documentation accordingly.
  • I ran dev/reformat(backend) and cd web && npx lint-staged(frontend) to appease the lint gods
**Original Pull Request:** https://github.com/langgenius/dify/pull/23323 **State:** closed **Merged:** Yes --- > [!IMPORTANT] > > 1. Make sure you have read our [contribution guidelines](https://github.com/langgenius/dify/blob/main/CONTRIBUTING.md) > 2. Ensure there is an associated issue and you have been assigned to it > 3. Use the correct syntax to link this PR: `Fixes #<issue number>`. ## Summary <!-- Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. List any dependencies that are required for this change. --> ## Screenshots | Before | After | |--------|-------| | ... | ... | script to test function schemaWithDynamicSelect ``` // Mock FormTypeEnum const FormTypeEnum = { dynamicSelect: 'dynamic-select' }; // BEFORE: Original problematic implementation function schemaWithDynamicSelectBefore(schema, dynamicOptions) { if (schema?.type !== FormTypeEnum.dynamicSelect) return schema; if (dynamicOptions) { return { ...schema, options: dynamicOptions, }; } return schema; } function schemaWithDynamicSelectAfter(schema, dynamicOptions, isLoading, value) { if (schema?.type !== FormTypeEnum.dynamicSelect) return schema // rewrite schema.options with dynamicOptions if (dynamicOptions) { return { ...schema, options: dynamicOptions, } } // If we don't have dynamic options but we have a selected value, create a temporary option to preserve the selection during loading if (isLoading && value && typeof value === 'string') { const preservedOptions = [{ value, label: { en_US: value, zh_Hans: value }, show_on: [], }] return { ...schema, options: preservedOptions, } } // Default case: return schema with empty options return { ...schema, options: [], } } // Test cases function runTests() { console.log('🧪 Testing schemaWithDynamicSelect Before vs After Fix'); console.log('='.repeat(60)); // Test data const testSchema = { type: 'dynamic-select', name: 'test_parameter', options: [] // Initially empty }; const mockDynamicOptions = [ { value: 'option1', label: { en_US: 'Option 1', zh_Hans: '选项1' }, show_on: [] }, { value: 'option2', label: { en_US: 'Option 2', zh_Hans: '选项2' }, show_on: [] } ]; console.log('\n📋 Test Cases:'); console.log('-'.repeat(40)); // Test Case 1: Normal case with dynamic options available console.log('\n1️⃣ Case 1: Dynamic options available'); const result1Before = schemaWithDynamicSelectBefore(testSchema, mockDynamicOptions); const result1After = schemaWithDynamicSelectAfter(testSchema, mockDynamicOptions, false, null); console.log(' BEFORE:', result1Before.options?.length || 0, 'options'); console.log(' AFTER: ', result1After.options?.length || 0, 'options'); console.log(' ✅ Both should work the same (have options)'); // Test Case 2: Critical case - no dynamic options, but user has selected value console.log('\n2️⃣ Case 2: No dynamic options, user has selected value, loading=true (THE BUG)'); const selectedValue = 'previously_selected_value'; const result2Before = schemaWithDynamicSelectBefore(testSchema, null); const result2After = schemaWithDynamicSelectAfter(testSchema, null, true, selectedValue); console.log(' BEFORE:', result2Before.options?.length || 0, 'options'); console.log(' AFTER: ', result2After.options?.length || 0, 'options'); console.log(' 🐛 BEFORE: User loses their selection!'); console.log(' ✅ AFTER: Selection preserved during loading'); // Test Case 3: No dynamic options, no selected value console.log('\n3️⃣ Case 3: No dynamic options, no selected value'); const result3Before = schemaWithDynamicSelectBefore(testSchema, null); const result3After = schemaWithDynamicSelectAfter(testSchema, null, false, null); console.log(' BEFORE:', result3Before.options?.length || 0, 'options'); console.log(' AFTER: ', result3After.options?.length || 0, 'options'); console.log(' ✅ Both should be empty (no options to show)'); // Test Case 4: Non-dynamic-select schema (should pass through unchanged) console.log('\n4️⃣ Case 4: Non-dynamic-select schema'); const nonDynamicSchema = { type: 'string', name: 'regular_param' }; const result4Before = schemaWithDynamicSelectBefore(nonDynamicSchema, null); const result4After = schemaWithDynamicSelectAfter(nonDynamicSchema, null, false, null); console.log(' BEFORE:', result4Before.type); console.log(' AFTER: ', result4After.type); console.log(' ✅ Both should pass through unchanged'); // Detailed comparison for the critical bug case console.log('\n🔍 Detailed Analysis of Critical Case:'); console.log('-'.repeat(40)); console.log('Scenario: User switches between nodes, causing dynamicOptions reset'); console.log('State: isLoading=true, dynamicOptions=null, value="user_selection"'); const criticalBefore = schemaWithDynamicSelectBefore(testSchema, null); const criticalAfter = schemaWithDynamicSelectAfter(testSchema, null, true, 'user_selection'); console.log('\nBEFORE (Buggy):'); console.log(' Options:', JSON.stringify(criticalBefore.options || [], null, 2)); console.log(' Result: User sees their selection as "invalid" ❌'); console.log('\nAFTER (Fixed):'); console.log(' Options:', JSON.stringify(criticalAfter.options, null, 2)); console.log(' Result: User sees their selection preserved ✅'); console.log('\n🎯 Summary:'); console.log('The fix prevents data loss by creating temporary options during loading'); console.log('when a user has a selected value but dynamic options are being reloaded.'); } // Run the tests runTests(); ``` logs of above script ``` 🧪 Testing schemaWithDynamicSelect Before vs After Fix ============================================================ 📋 Test Cases: ---------------------------------------- 1️⃣ Case 1: Dynamic options available BEFORE: 2 options AFTER: 2 options ✅ Both should work the same (have options) 2️⃣ Case 2: No dynamic options, user has selected value, loading=true (THE BUG) BEFORE: 0 options AFTER: 1 options 🐛 BEFORE: User loses their selection! ✅ AFTER: Selection preserved during loading 3️⃣ Case 3: No dynamic options, no selected value BEFORE: 0 options AFTER: 0 options ✅ Both should be empty (no options to show) 4️⃣ Case 4: Non-dynamic-select schema BEFORE: string AFTER: string ✅ Both should pass through unchanged 🔍 Detailed Analysis of Critical Case: ---------------------------------------- Scenario: User switches between nodes, causing dynamicOptions reset State: isLoading=true, dynamicOptions=null, value="user_selection" BEFORE (Buggy): Options: [] Result: User sees their selection as "invalid" ❌ AFTER (Fixed): Options: [ { "value": "user_selection", "label": { "en_US": "user_selection", "zh_Hans": "user_selection" }, "show_on": [] } ] Result: User sees their selection preserved ✅ ``` ## Checklist - [ ] This change requires a documentation update, included: [Dify Document](https://github.com/langgenius/dify-docs) - [x] I understand that this PR may be closed in case there was no previous discussion or issues. (This doesn't apply to typos!) - [x] I've added a test for each change that was introduced, and I tried as much as possible to make a single atomic change. - [x] I've updated the documentation accordingly. - [x] I ran `dev/reformat`(backend) and `cd web && npx lint-staged`(frontend) to appease the lint gods
yindo added the pull-request label 2026-02-21 20:47:06 -05:00
yindo closed this issue 2026-02-21 20:47:06 -05:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: langgenius/dify#30237