diff --git a/webapp/channels/src/components/admin_console/access_control/policy_details/policy_details.tsx b/webapp/channels/src/components/admin_console/access_control/policy_details/policy_details.tsx index cdd90794bda..dea613d2026 100644 --- a/webapp/channels/src/components/admin_console/access_control/policy_details/policy_details.tsx +++ b/webapp/channels/src/components/admin_console/access_control/policy_details/policy_details.tsx @@ -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(() => ( +
+

+ +

+
+ ), []); + // 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} /> )} diff --git a/webapp/channels/src/components/channel_selector_modal/channel_selector_modal.test.tsx b/webapp/channels/src/components/channel_selector_modal/channel_selector_modal.test.tsx index 82f623c1b85..9feb62080f1 100644 --- a/webapp/channels/src/components/channel_selector_modal/channel_selector_modal.test.tsx +++ b/webapp/channels/src/components/channel_selector_modal/channel_selector_modal.test.tsx @@ -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 = ( +
+ {'No private channels available'} +
+ ); + + const wrapper = shallowWithIntl( + , + ); + + // 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 = ( +
+ {'No private channels available'} +
+ ); + + const wrapper = shallowWithIntl( + , + ); + + // 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 = ( +
+ {'No private channels available'} +
+ ); + + const wrapper = shallowWithIntl( + , + ); + + // 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( { } 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 ( { saving={false} loading={this.state.loadingChannels} placeholderText={defineMessage({id: 'multiselect.addChannelsPlaceholder', defaultMessage: 'Search and add channels'})} + customNoOptionsMessage={customNoOptionsMessage} /> diff --git a/webapp/channels/src/components/multiselect/multiselect.tsx b/webapp/channels/src/components/multiselect/multiselect.tsx index f59dfc5a435..2f71006882c 100644 --- a/webapp/channels/src/components/multiselect/multiselect.tsx +++ b/webapp/channels/src/components/multiselect/multiselect.tsx @@ -562,7 +562,7 @@ export class MultiSelect extends React.PureComponent, )} - {this.props.saveButtonPosition === 'top' && + {this.props.saveButtonPosition === 'top' && (previousButton || nextButton) &&
{previousButton} {nextButton} diff --git a/webapp/channels/src/i18n/en.json b/webapp/channels/src/i18n/en.json index 35444696e71..86fd06ca5c3 100644 --- a/webapp/channels/src/i18n/en.json +++ b/webapp/channels/src/i18n/en.json @@ -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.", diff --git a/webapp/channels/src/sass/components/_modal.scss b/webapp/channels/src/sass/components/_modal.scss index b40483f1574..218fa87039b 100644 --- a/webapp/channels/src/sass/components/_modal.scss +++ b/webapp/channels/src/sass/components/_modal.scss @@ -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; } }