mirror of
https://github.com/mattermost/mattermost.git
synced 2026-08-26 13:17:29 -05:00
Disable linking a new external source while an attribute still applies to a resource
This commit is contained in:
+16
@@ -1309,6 +1309,22 @@ describe('AttributeDetails', () => {
|
||||
expect(screen.queryByTestId('attributeTypeLockWrap')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('locks adding an external source while a pending Applies-to resource is on the form, so linking cannot silently change the type', async () => {
|
||||
mockLoadedField(makeTemplate({type: 'select', attrs: {display_name: 'Department', options: [{id: 'opt-1', name: 'Engineering'}]}}), [makeLinked('user', 'user-field')]);
|
||||
renderEdit();
|
||||
await waitForForm();
|
||||
|
||||
expect(screen.getByTestId('attributeExternalSourceTrigger')).toBeDisabled();
|
||||
expect(screen.getByTestId('attributeExternalSourceTriggerLockWrap')).toBeInTheDocument();
|
||||
|
||||
await userEvent.click(screen.getByTestId('attributeAppliesToRow-user-toggle'));
|
||||
await userEvent.click(screen.getByTestId('attributeAppliesToRow-user-remove'));
|
||||
|
||||
await waitFor(() => expect(screen.queryByTestId('attributeAppliesToRow-user')).not.toBeInTheDocument());
|
||||
expect(screen.getByTestId('attributeExternalSourceTrigger')).not.toBeDisabled();
|
||||
expect(screen.queryByTestId('attributeExternalSourceTriggerLockWrap')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('locks Name editing while the attribute is currently applied to a resource', async () => {
|
||||
mockLoadedField(makeTemplate(), [makeLinked('user', 'user-field')]);
|
||||
renderEdit();
|
||||
|
||||
+1
@@ -1129,6 +1129,7 @@ function AttributeDetails({disabled = false}: Props): JSX.Element {
|
||||
fieldType={fieldType}
|
||||
onLink={handleLink}
|
||||
disabled={saving || disabled}
|
||||
disableAdding={typeLockedByAppliesTo}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+9
@@ -43,6 +43,15 @@ describe('AttributeExternalSource', () => {
|
||||
expect(screen.getByRole('menuitem', {name: /^SAML/})).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('disables the add-source trigger when disableAdding is set, without disabling an existing chip\'s edit/remove actions', () => {
|
||||
renderComponent({ldapAttr: 'department', disableAdding: true});
|
||||
|
||||
expect(screen.getByTestId('attributeExternalSourceTrigger')).toBeDisabled();
|
||||
expect(screen.getByTestId('attributeExternalSourceTriggerLockWrap')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('attributeExternalSourceChip-ldap-edit')).not.toBeDisabled();
|
||||
expect(screen.getByTestId('attributeExternalSourceChip-ldap-remove')).not.toBeDisabled();
|
||||
});
|
||||
|
||||
it('renders a chip for a linked source prefixed by Synced with, and offers only the remaining source', async () => {
|
||||
renderComponent({ldapAttr: 'department'});
|
||||
|
||||
|
||||
+60
-36
@@ -9,6 +9,7 @@ import {components} from 'react-select';
|
||||
|
||||
import {PencilOutlineIcon, RefreshIcon, SyncIcon} from '@mattermost/compass-icons/components';
|
||||
import {buttonClassNames} from '@mattermost/shared/components/button';
|
||||
import {WithTooltip} from '@mattermost/shared/components/tooltip';
|
||||
|
||||
import {openModal} from 'actions/views/modals';
|
||||
|
||||
@@ -34,13 +35,21 @@ type Props = {
|
||||
fieldType: AttributeFieldType;
|
||||
onLink: (source: ExternalSource, value: string) => void;
|
||||
disabled?: boolean;
|
||||
|
||||
// Linking a new source forces fieldType to 'text' (see attribute_details.tsx's
|
||||
// handleLink) -- while this attribute is applied to a resource, that would
|
||||
// change its type out from under the server's type_change_with_dependents
|
||||
// guard the same way the Type menu itself is locked for. Only gates the
|
||||
// "add" trigger below: editing or removing an already-linked source never
|
||||
// touches fieldType, so those stay enabled.
|
||||
disableAdding?: boolean;
|
||||
};
|
||||
|
||||
function sourceValue(source: ExternalSource, ldapAttr: string, samlAttr: string): string {
|
||||
return source === 'ldap' ? ldapAttr : samlAttr;
|
||||
}
|
||||
|
||||
function AttributeExternalSource({ldapAttr, samlAttr, fieldType, onLink, disabled = false}: Props): JSX.Element {
|
||||
function AttributeExternalSource({ldapAttr, samlAttr, fieldType, onLink, disabled = false, disableAdding = false}: Props): JSX.Element {
|
||||
const {formatMessage} = useIntl();
|
||||
const dispatch = useDispatch();
|
||||
|
||||
@@ -156,41 +165,52 @@ function AttributeExternalSource({ldapAttr, samlAttr, fieldType, onLink, disable
|
||||
</div>
|
||||
)}
|
||||
{unlinkedSources.length > 0 && (
|
||||
<Menu.Container
|
||||
menuButton={{
|
||||
id: TRIGGER_ID,
|
||||
class: classNames(buttonClassNames({emphasis: 'quaternary'}), 'AttributeExternalSource__trigger'),
|
||||
disabled,
|
||||
onMouseDown: handleTriggerMouseDown,
|
||||
children: (
|
||||
<>
|
||||
<RefreshIcon size={16}/>
|
||||
<FormattedMessage {...messages.triggerLabel}/>
|
||||
<i className='icon icon-chevron-down'/>
|
||||
</>
|
||||
),
|
||||
dataTestId: 'attributeExternalSourceTrigger',
|
||||
}}
|
||||
menu={{
|
||||
id: 'attribute-external-source-menu',
|
||||
'aria-label': formatMessage(messages.triggerLabel),
|
||||
}}
|
||||
>
|
||||
{unlinkedSources.map((source) => (
|
||||
<Menu.Item
|
||||
id={`attribute-external-source-${source}`}
|
||||
key={source}
|
||||
leadingElement={<SyncIcon size={18}/>}
|
||||
onClick={() => openLinkModal(source)}
|
||||
labels={(
|
||||
<>
|
||||
<FormattedMessage {...sourceMessages[source].title}/>
|
||||
<FormattedMessage {...sourceMessages[source].subtitle}/>
|
||||
</>
|
||||
)}
|
||||
/>
|
||||
))}
|
||||
</Menu.Container>
|
||||
(() => {
|
||||
const trigger = (
|
||||
<Menu.Container
|
||||
menuButton={{
|
||||
id: TRIGGER_ID,
|
||||
class: classNames(buttonClassNames({emphasis: 'quaternary'}), 'AttributeExternalSource__trigger'),
|
||||
disabled: disabled || disableAdding,
|
||||
onMouseDown: handleTriggerMouseDown,
|
||||
children: (
|
||||
<>
|
||||
<RefreshIcon size={16}/>
|
||||
<FormattedMessage {...messages.triggerLabel}/>
|
||||
<i className='icon icon-chevron-down'/>
|
||||
</>
|
||||
),
|
||||
dataTestId: 'attributeExternalSourceTrigger',
|
||||
}}
|
||||
menu={{
|
||||
id: 'attribute-external-source-menu',
|
||||
'aria-label': formatMessage(messages.triggerLabel),
|
||||
}}
|
||||
>
|
||||
{unlinkedSources.map((source) => (
|
||||
<Menu.Item
|
||||
id={`attribute-external-source-${source}`}
|
||||
key={source}
|
||||
leadingElement={<SyncIcon size={18}/>}
|
||||
onClick={() => openLinkModal(source)}
|
||||
labels={(
|
||||
<>
|
||||
<FormattedMessage {...sourceMessages[source].title}/>
|
||||
<FormattedMessage {...sourceMessages[source].subtitle}/>
|
||||
</>
|
||||
)}
|
||||
/>
|
||||
))}
|
||||
</Menu.Container>
|
||||
);
|
||||
return disableAdding ? (
|
||||
<WithTooltip title={formatMessage(messages.disabledWhileAppliesToTooltip)}>
|
||||
<span data-testid='attributeExternalSourceTriggerLockWrap'>
|
||||
{trigger}
|
||||
</span>
|
||||
</WithTooltip>
|
||||
) : trigger;
|
||||
})()
|
||||
)}
|
||||
<span
|
||||
role='status'
|
||||
@@ -263,6 +283,10 @@ const messages = defineMessages({
|
||||
id: 'admin.global_attributes.attribute_details.external_source.links_removed',
|
||||
defaultMessage: '{count, plural, one {External source link removed} other {External source links removed}}',
|
||||
},
|
||||
disabledWhileAppliesToTooltip: {
|
||||
id: 'admin.global_attributes.attribute_details.external_source.disabled_applies_to_tooltip',
|
||||
defaultMessage: 'Cannot link an external source while this attribute applies to a resource.',
|
||||
},
|
||||
});
|
||||
|
||||
const sourceMessages = {
|
||||
|
||||
@@ -1468,6 +1468,7 @@
|
||||
"admin.global_attributes.attribute_details.display_name.placeholder": "Add a display name",
|
||||
"admin.global_attributes.attribute_details.edit_title": "Edit attribute",
|
||||
"admin.global_attributes.attribute_details.external_source.chip_label": "{source}: {value}",
|
||||
"admin.global_attributes.attribute_details.external_source.disabled_applies_to_tooltip": "Cannot link an external source while this attribute applies to a resource.",
|
||||
"admin.global_attributes.attribute_details.external_source.edit_link": "Edit {source} link",
|
||||
"admin.global_attributes.attribute_details.external_source.ldap.help_text": "The attribute in your AD/LDAP directory to sync this value from.",
|
||||
"admin.global_attributes.attribute_details.external_source.ldap.modal_title": "Link to AD/LDAP",
|
||||
|
||||
Reference in New Issue
Block a user