MM-66442 - Add empty state message for ABAC channel modal (#34965)

* MM-66442 - Add empty state message for ABAC channel modal

* fix translation and ts issue

* Remove empty filter controls border in channel modal

* extract and memoize message

* adjust modal header title style and text color

---------

Co-authored-by: Mattermost Build <build@mattermost.com>
This commit is contained in:
Pablo Vélez 2026-02-17 20:06:40 -05:00 committed by GitHub
parent cef5134865
commit 5efc41c1bb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 118 additions and 6 deletions

View file

@ -2,7 +2,7 @@
// See LICENSE.txt for license information.
import cloneDeep from 'lodash/cloneDeep';
import React, {useState, useEffect} from 'react';
import React, {useState, useEffect, useMemo} from 'react';
import {FormattedMessage, useIntl} from 'react-intl';
import {GenericModal} from '@mattermost/components';
@ -96,6 +96,21 @@ function PolicyDetails({
const abacActions = useChannelAccessControlActions();
// Memoize the custom no options message to avoid recreating it on every render
const customNoPrivateChannelsMessage = useMemo(() => (
<div
key='no-private-channels'
className='no-channel-message'
>
<p className='primary-message'>
<FormattedMessage
id='admin.access_control.policy.edit_policy.no_private_channels'
defaultMessage='There are no private channels available to add to this policy.'
/>
</p>
</div>
), []);
// Check if there are any usable attributes for ABAC
const noUsableAttributes = attributesLoaded && !hasUsableAttributes(autocompleteResult, accessControlSettings.EnableUserManagedAttributes);
@ -603,6 +618,7 @@ function PolicyDetails({
groupID={''}
alreadySelected={Object.values(channelChanges.added).map((channel) => channel.id)}
excludeTypes={['O', 'D', 'G']}
customNoOptionsMessage={customNoPrivateChannelsMessage}
excludeGroupConstrained={true}
/>
)}

View file

@ -66,6 +66,92 @@ describe('components/ChannelSelectorModal', () => {
expect(wrapper).toMatchSnapshot();
});
test('should show custom no options message when no channels and no search term', () => {
const customMessage = (
<div className='custom-message'>
{'No private channels available'}
</div>
);
const wrapper = shallowWithIntl(
<ChannelSelectorModal
{...defaultProps}
searchTerm={''}
customNoOptionsMessage={customMessage}
/>,
);
// Set empty channels array to simulate no private channels
wrapper.setState({
channels: [],
loadingChannels: false,
});
// Find the MultiSelect component
const multiSelect = wrapper.find('MultiSelect');
// Should pass the custom message to MultiSelect
expect(multiSelect.prop('customNoOptionsMessage')).toEqual(customMessage);
});
test('should not show custom message when user is searching', () => {
const customMessage = (
<div className='custom-message'>
{'No private channels available'}
</div>
);
const wrapper = shallowWithIntl(
<ChannelSelectorModal
{...defaultProps}
searchTerm={'test'}
customNoOptionsMessage={customMessage}
/>,
);
// Set empty channels array
wrapper.setState({
channels: [],
loadingChannels: false,
});
// Find the MultiSelect component
const multiSelect = wrapper.find('MultiSelect');
// Should NOT pass the custom message when searching (let default message show)
expect(multiSelect.prop('customNoOptionsMessage')).toBeUndefined();
});
test('should not show custom message when channels are available', () => {
const customMessage = (
<div className='custom-message'>
{'No private channels available'}
</div>
);
const wrapper = shallowWithIntl(
<ChannelSelectorModal
{...defaultProps}
searchTerm={''}
customNoOptionsMessage={customMessage}
/>,
);
// Set channels array with data
wrapper.setState({
channels: [channel1, channel2],
loadingChannels: false,
});
// Find the MultiSelect component
const multiSelect = wrapper.find('MultiSelect');
// Custom message is passed but MultiSelect won't show it because options exist
// The important thing is that the component renders normally with channels
const options = multiSelect.prop('options') as any[];
expect(options.length).toBeGreaterThan(0);
});
test('excludes group constrained channels when requested', () => {
const wrapper = shallowWithIntl(
<ChannelSelectorModal

View file

@ -34,6 +34,7 @@ type Props = {
excludeGroupConstrained?: boolean;
excludeTeamIds?: string[];
excludeTypes?: string[];
customNoOptionsMessage?: React.ReactNode;
}
type State = {
@ -232,6 +233,13 @@ export class ChannelSelectorModal extends React.PureComponent<Props, State> {
}
const values = this.state.values.map((i): ChannelWithTeamDataValue => ({...i, label: i.display_name, value: i.id}));
// Only show custom message when there are no options and user hasn't started searching
// If user is searching (searchTerm exists), show the default "No results found matching..." message
let customNoOptionsMessage;
if (this.props.customNoOptionsMessage && !this.props.searchTerm) {
customNoOptionsMessage = this.props.customNoOptionsMessage;
}
return (
<Modal
dialogClassName={'a11y__modal more-modal more-direct-channels channel-selector-modal'}
@ -275,6 +283,7 @@ export class ChannelSelectorModal extends React.PureComponent<Props, State> {
saving={false}
loading={this.state.loadingChannels}
placeholderText={defineMessage({id: 'multiselect.addChannelsPlaceholder', defaultMessage: 'Search and add channels'})}
customNoOptionsMessage={customNoOptionsMessage}
/>
</Modal.Body>
</Modal>

View file

@ -562,7 +562,7 @@ export class MultiSelect<T extends Value> extends React.PureComponent<Props<T>,
</span>
</div>
)}
{this.props.saveButtonPosition === 'top' &&
{this.props.saveButtonPosition === 'top' && (previousButton || nextButton) &&
<div className='filter-controls'>
{previousButton}
{nextButton}

View file

@ -311,6 +311,7 @@
"admin.access_control.policy.edit_policy.error.name_required": "Please add a name to the policy",
"admin.access_control.policy.edit_policy.error.unassign_channels": "Error unassigning channels: {error}",
"admin.access_control.policy.edit_policy.error.update_active_status": "Error updating policy active status: {error}",
"admin.access_control.policy.edit_policy.no_private_channels": "There are no private channels available to add to this policy.",
"admin.access_control.policy.edit_policy.no_usable_attributes_tooltip": "Please configure user attributes to use the editor.",
"admin.access_control.policy.edit_policy.notice.button": "Configure user attributes",
"admin.access_control.policy.edit_policy.notice.text": "You havent configured any user attributes yet. Attribute-Based Access Control requires user attributes that are either synced from an external system (like LDAP or SAML) or manually configured and enabled on this server. To start using attribute based access, please configure user attributes in System Attributes.",

View file

@ -325,7 +325,7 @@
background: transparent;
color: functions.v(center-channel-color);
font-size: 22px;
line-height: 28px;
line-height: 44px;
}
}
@ -335,7 +335,7 @@
color: functions.v(center-channel-color);
font-size: 22px;
font-weight: 600;
line-height: 28px;
line-height: 44px;
word-break: break-word;
}
}
@ -1065,8 +1065,8 @@
.no-channel-message {
width: 100%;
margin-top: 40px;
color: variables.$gray;
font-size: 1.25em;
color: (--center-channel-color, 0.72);
font-size: 1em;
text-align: center;
}
}