Address CodeRabbit findings on graph option parents and empty values

Reject duplicate parent names in OptionParentLinks, normalize a cleared
graph attribute to an empty array, and correct the session-attribute
option-count comment.

Co-authored-by: mattermost-code <matty-code@mattermost.com>
This commit is contained in:
Cursor Agentandmattermost-code committed 2026-08-26 15:32:36 +00:00
1 parent 071f3d84cf
commit 2635e3d7d3
5 files changed
+57 -2

No files matched your search

+7
View File
@@ -284,10 +284,16 @@ func (pf *PropertyField) OptionParentLinks() (add []*PropertyOptionEdge, replaci
replacing = append(replacing, optionID)
named, _ := option["name"].(string)
if len(parents) > PropertyGraphMaxParentsPerOption {
return nil, nil, fmt.Errorf("the option at index %d would have %d options directly above it, and no option may have more than %d", i, len(parents), PropertyGraphMaxParentsPerOption)
}
seen := make(map[string]bool, len(parents))
for _, parent := range parents {
switch {
case parent == "":
return nil, nil, fmt.Errorf("the option at index %d is put under an option with no name", i)
case seen[parent]:
return nil, nil, fmt.Errorf("the option at index %d names %q as a parent more than once", i, parent)
case ambiguous[parent]:
return nil, nil, fmt.Errorf("the option at index %d is put under %q, and two of the field's options are called that", i, parent)
case idsByName[parent] == "":
@@ -295,6 +301,7 @@ func (pf *PropertyField) OptionParentLinks() (add []*PropertyOptionEdge, replaci
case idsByName[parent] == optionID:
return nil, nil, fmt.Errorf("the option at index %d, %q, is put under itself", i, named)
}
seen[parent] = true
add = append(add, &PropertyOptionEdge{
FieldID: pf.ID,
ChildOptionID: optionID,
@@ -2144,6 +2144,11 @@ func TestPropertyField_OptionParentLinks(t *testing.T) {
},
reason: "two of the field's options are called that",
},
{
name: "the same parent named twice",
options: []map[string]any{{"id": NewId(), "name": "Air"}, {"id": NewId(), "name": "Sea", "parents": []any{"Air", "Air"}}},
reason: `names "Air" as a parent more than once`,
},
{
name: "a parent with no name",
options: []map[string]any{{"id": NewId(), "name": "Air", "parents": []any{""}}},
+1 -1
View File
@@ -188,7 +188,7 @@ func (f *SAField) EnabledForPlatform(platform string) bool {
// would be wrong for a field whose list was withheld for exceeding
// PropertyFieldMaxHydratedOptions. No session attribute field can be in that
// state: the schema is the fixed set SessionAttributeSystemFields declares,
// whose largest option list has six entries, nothing but the seeding code writes
// whose largest option list has 19 entries, nothing but the seeding code writes
// those lists, and an API caller may only change enabled, ttl_seconds, and
// grace_period_seconds on a seeded field.
func IsValidSessionAttributeValue(field *PropertyField, value any) bool {
@@ -859,6 +859,49 @@ describe('components/user_settings/general/UserSettingsGeneral', () => {
expect(props.actions.saveCustomProfileAttribute).toHaveBeenCalledWith('user_id', 'field1', ['opt1', 'opt2']);
});
test('updateSelectAttribute() should clear a graph attribute as an empty array', async () => {
const saveCustomProfileAttribute = jest.fn().mockResolvedValue({});
const graphAttribute: UserPropertyField = {
...customProfileAttribute,
type: 'graph',
attrs: {
value_type: '',
visibility: 'when_set',
sort_order: 0,
options: [
{id: 'opt1', name: 'Option 1', color: ''},
{id: 'opt2', name: 'Option 2', color: ''},
],
},
};
const testUser = {...user, custom_profile_attributes: {field1: ['opt1']}};
const props = {
...requiredProps,
enableCustomProfileAttributes: true,
customProfileAttributeFields: [graphAttribute],
user: testUser,
activeSection: 'customAttribute_field1',
actions: {
...requiredProps.actions,
saveCustomProfileAttribute,
},
};
const {container} = renderWithContext(<UserSettingsGeneral {...props}/>);
const clearIndicator = container.querySelector('.react-select__clear-indicator');
expect(clearIndicator).toBeInTheDocument();
await userEvent.click(clearIndicator!);
await screen.findByText('Select');
const saveButton = screen.getByRole('button', {name: 'Save'});
await userEvent.click(saveButton);
expect(props.actions.saveCustomProfileAttribute).toHaveBeenCalledWith('user_id', 'field1', []);
});
test('should not show custom attribute input field when LDAP attribute is set', async () => {
const props = {
...requiredProps,
@@ -469,7 +469,7 @@ export class UserSettingsGeneralTab extends PureComponent<Props, State> {
}
}
}
if (attributeField.type === 'multiselect' && !attributeValue) {
if ((attributeField.type === 'multiselect' || attributeField.type === 'graph') && !attributeValue) {
attributeValue = [];
}