fix: Variable search item not showing after braces/commas (#11864)

# Pull Request Template

## Description

This PR fixes an issue where typing variables, like `{{contact.name}}`,
caused the variable list to miss showing `contact.name`. The search key
in this case became `contact.name}},` which didn't match any available
options. The logic in `VariableList.vue` only checked the part after the
last comma and didn’t fully sanitize the input.

**Solution**
Updated `searchKey` to remove all {} and commas for accurate matching.

Fixes
[CW-4574](https://linear.app/chatwoot/issue/CW-4574/i-dont-see-an-option-for-contactname-it-shows-initially-but-it-doesnt)

## Type of change

- [x] Bug fix (non-breaking change which fixes an issue)

## How Has This Been Tested?

### Loom video

https://www.loom.com/share/fc86e53853ad49e6acf6de57ebbd8fcb?sid=6702f896-d1a3-4c5a-9eb7-b96b5ed91531


## Checklist:

- [x] My code follows the style guidelines of this project
- [x] I have performed a self-review of my code
- [x] I have commented on my code, particularly in hard-to-understand
areas
- [ ] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [ ] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
- [ ] Any dependent changes have been merged and published in downstream
modules
This commit is contained in:
Sivin Varghese
2025-07-03 19:39:36 +05:30
committed by GitHub
parent cb01a2bc66
commit 7343e53659
3 changed files with 54 additions and 2 deletions

View File

@@ -1,6 +1,7 @@
<script>
import { mapGetters } from 'vuex';
import { MESSAGE_VARIABLES } from 'shared/constants/messages';
import { sanitizeVariableSearchKey } from 'dashboard/helper/commons';
import MentionBox from '../mentions/MentionBox.vue';
export default {
@@ -16,6 +17,9 @@ export default {
...mapGetters({
customAttributes: 'attributes/getAttributes',
}),
sanitizedSearchKey() {
return sanitizeVariableSearchKey(this.searchKey);
},
items() {
return [
...this.standardAttributeVariables,
@@ -25,8 +29,8 @@ export default {
standardAttributeVariables() {
return MESSAGE_VARIABLES.filter(variable => {
return (
variable.label.includes(this.searchKey) ||
variable.key.includes(this.searchKey)
variable.label.includes(this.sanitizedSearchKey) ||
variable.key.includes(this.sanitizedSearchKey)
);
}).map(variable => ({
label: variable.key,

View File

@@ -83,3 +83,16 @@ export const convertToPortalSlug = text => {
.replace(/[^\w ]+/g, '')
.replace(/ +/g, '-');
};
/**
* Strip curly braces, commas and leading/trailing whitespace from a search key.
* Eg. "{{contact.name}}," => "contact.name"
* @param {string} searchKey
* @returns {string}
*/
export const sanitizeVariableSearchKey = (searchKey = '') => {
return searchKey
.replace(/[{}]/g, '') // remove all curly braces
.replace(/,/g, '') // remove commas
.trim();
};

View File

@@ -4,6 +4,7 @@ import {
convertToAttributeSlug,
convertToCategorySlug,
convertToPortalSlug,
sanitizeVariableSearchKey,
} from '../commons';
describe('#getTypingUsersText', () => {
@@ -107,3 +108,37 @@ describe('convertToPortalSlug', () => {
expect(convertToPortalSlug('Room rental')).toBe('room-rental');
});
});
describe('sanitizeVariableSearchKey', () => {
it('removes braces', () => {
expect(sanitizeVariableSearchKey('{{contact.name}}')).toBe('contact.name');
});
it('removes right braces', () => {
expect(sanitizeVariableSearchKey('contact.name}}')).toBe('contact.name');
});
it('removes braces, comma and whitespace', () => {
expect(sanitizeVariableSearchKey(' {{contact.name }},')).toBe(
'contact.name'
);
});
it('trims whitespace', () => {
expect(sanitizeVariableSearchKey(' contact.name ')).toBe('contact.name');
});
it('handles multiple commas', () => {
expect(sanitizeVariableSearchKey('{{contact.name}},,')).toBe(
'contact.name'
);
});
it('returns empty string when only braces/commas/whitespace', () => {
expect(sanitizeVariableSearchKey(' { }, , ')).toBe('');
});
it('returns empty string for undefined input', () => {
expect(sanitizeVariableSearchKey()).toBe('');
});
});