[MM-54848] Remove deprecated LocalizedInput component (#25052)

This commit is contained in:
M-ZubairAhmed 2023-10-24 14:41:29 +05:30 committed by GitHub
parent 22c9c80cd2
commit e552f6b86a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 60 additions and 204 deletions

View File

@ -163,7 +163,7 @@
"jest-junit": "12.2.0",
"jest-styled-components": "7.1.1",
"jest-watch-typeahead": "0.6.4",
"mmjstool": "github:mattermost/mattermost-utilities#d88289ce1d30da1936d9a17e9bed0cdb52de0a10",
"mmjstool": "github:mattermost/mattermost-utilities#83b1b311972b8f5e750aae4019457a40abb5aa44",
"nock": "13.2.8",
"prettier": "2.3.2",
"react-router-enzyme-context": "1.2.0",

View File

@ -20,20 +20,8 @@ exports[`components/SearchableChannelList should match init snapshot 1`] = `
className="form-control filter-textbox"
clearable={true}
id="searchChannelsTextbox"
inputComponent={
Object {
"$$typeof": Symbol(react.forward_ref),
"render": [Function],
}
}
onClear={[Function]}
onInput={[Function]}
placeholder={
Object {
"defaultMessage": "Search channels",
"id": "filtered_channels_list.search",
}
}
value=""
/>
</div>

View File

@ -43,7 +43,7 @@ exports[`components/BrowseChannels should match snapshot and state 1`] = `
onExited={[Function]}
show={true}
>
<SearchableChannelList
<injectIntl(SearchableChannelList)
canShowArchivedChannels={true}
changeFilter={[Function]}
channels={

View File

@ -12,8 +12,6 @@ import {emailToLdap} from 'actions/admin_actions.jsx';
import LoginMfa from 'components/login/login_mfa';
import {ClaimErrors} from 'utils/constants';
import {t} from 'utils/i18n';
import {localizeMessage} from 'utils/utils';
import ErrorLabel from './error_label';
@ -52,7 +50,7 @@ const EmailToLDAP = ({email, siteName, ldapLoginFieldName}: Props) => {
const password = emailPasswordInput.current?.value;
if (!password) {
setPasswordError(localizeMessage('claim.email_to_ldap.pwdError', 'Please enter your password.'));
setPasswordError(formatMessage({id: 'claim.email_to_ldap.pwdError', defaultMessage: 'Please enter your password.'}));
setLdapError('');
setLdapPasswordError('');
setServerError('');
@ -61,7 +59,7 @@ const EmailToLDAP = ({email, siteName, ldapLoginFieldName}: Props) => {
const ldapId = ldapIdInput.current?.value.trim();
if (!ldapId) {
setLdapError(localizeMessage('claim.email_to_ldap.ldapIdError', 'Please enter your AD/LDAP ID.'));
setLdapError(formatMessage({id: 'claim.email_to_ldap.ldapIdError', defaultMessage: 'Please enter your AD/LDAP ID.'}));
setPasswordError('');
setLdapPasswordError('');
setServerError('');
@ -70,7 +68,7 @@ const EmailToLDAP = ({email, siteName, ldapLoginFieldName}: Props) => {
const ldapPassword = ldapPasswordInput.current?.value;
if (!ldapPassword) {
setLdapPasswordError(localizeMessage('claim.email_to_ldap.ldapPasswordError', 'Please enter your AD/LDAP password.'));
setLdapPasswordError(formatMessage({id: 'claim.email_to_ldap.ldapPasswordError', defaultMessage: 'Please enter your AD/LDAP password.'}));
setLdapError('');
setPasswordError('');
setServerError('');
@ -126,15 +124,14 @@ const EmailToLDAP = ({email, siteName, ldapLoginFieldName}: Props) => {
);
};
const loginPlaceholder = ldapLoginFieldName || localizeMessage('claim.email_to_ldap.ldapId', 'AD/LDAP ID');
const titleMessage = {id: t('claim.email_to_ldap.title'), defaultMessage: 'Switch Email/Password Account to AD/LDAP'};
const loginPlaceholder = ldapLoginFieldName || formatMessage({id: 'claim.email_to_ldap.ldapId', defaultMessage: 'AD/LDAP ID'});
if (showMfa) {
return (
<LoginMfa
loginId={email}
password={password}
title={titleMessage}
title={formatMessage({id: 'claim.email_to_ldap.title', defaultMessage: 'Switch Email/Password Account to AD/LDAP'})}
onSubmit={submit}
/>
);
@ -212,7 +209,7 @@ const EmailToLDAP = ({email, siteName, ldapLoginFieldName}: Props) => {
name='ldapPassword'
ref={ldapPasswordInput}
autoComplete='off'
placeholder={formatMessage({id: t('claim.email_to_ldap.ldapPwd'), defaultMessage: 'AD/LDAP Password'})}
placeholder={formatMessage({id: 'claim.email_to_ldap.ldapPwd', defaultMessage: 'AD/LDAP Password'})}
spellCheck='false'
/>
</div>

View File

@ -12,8 +12,7 @@ import {emailToOAuth} from 'actions/admin_actions.jsx';
import LoginMfa from 'components/login/login_mfa';
import Constants, {ClaimErrors} from 'utils/constants';
import {t} from 'utils/i18n';
import {localizeMessage, toTitleCase} from 'utils/utils';
import {toTitleCase} from 'utils/utils';
import type {SubmitOptions} from './email_to_ldap';
import ErrorLabel from './error_label';
@ -37,7 +36,7 @@ const EmailToOAuth = (props: Props) => {
const password = passwordInput.current?.value;
if (!password) {
setServerError(localizeMessage('claim.email_to_oauth.pwdError', 'Please enter your password.'));
setServerError(formatMessage({id: 'claim.email_to_oauth.pwdError', defaultMessage: 'Please enter your password.'}));
return;
}
@ -72,17 +71,13 @@ const EmailToOAuth = (props: Props) => {
const type = (props.newType === Constants.SAML_SERVICE ? Constants.SAML_SERVICE.toUpperCase() : toTitleCase(props.newType || ''));
const uiType = `${type} SSO`;
const titleMessage = {
id: t('claim.email_to_oauth.title'),
defaultMessage: 'Switch Email/Password Account to {uiType}',
};
if (showMfa) {
return (
<LoginMfa
loginId={props.email}
password={password}
title={titleMessage}
title={formatMessage({id: 'claim.email_to_oauth.title', defaultMessage: 'Switch Email/Password Account to {uiType}'})}
onSubmit={submit}
/>
);

View File

@ -3,15 +3,13 @@
import classNames from 'classnames';
import React, {useRef, useState} from 'react';
import {FormattedMessage} from 'react-intl';
import {FormattedMessage, useIntl} from 'react-intl';
import type {AuthChangeResponse} from '@mattermost/types/users';
import LocalizedInput from 'components/localized_input/localized_input';
import LoginMfa from 'components/login/login_mfa';
import {ClaimErrors} from 'utils/constants';
import {t} from 'utils/i18n';
import {isValidPassword, localizeMessage} from 'utils/utils';
import type {SubmitOptions} from './email_to_ldap';
@ -38,6 +36,8 @@ const LDAPToEmail = (props: Props) => {
const passwordInput = useRef<HTMLInputElement>(null);
const passwordConfirmInput = useRef<HTMLInputElement>(null);
const {formatMessage} = useIntl();
const preSubmit = (e: React.FormEvent) => {
e.preventDefault();
@ -108,17 +108,12 @@ const LDAPToEmail = (props: Props) => {
});
};
const passwordPlaceholder = localizeMessage('claim.ldap_to_email.ldapPwd', 'AD/LDAP Password');
const titleMessage = {id: t('claim.ldap_to_email.title'), defaultMessage: 'Switch AD/LDAP Account to Email/Password'};
const placeholderPasswordMessage = {id: t('claim.ldap_to_email.pwd'), defaultMessage: 'Password'};
const placeholderConfirmMessage = {id: t('claim.ldap_to_email.confirm'), defaultMessage: 'Confirm Password'};
if (showMfa) {
return (
<LoginMfa
loginId={props.email}
password={password}
title={titleMessage}
title={formatMessage({id: 'claim.ldap_to_email.title', defaultMessage: 'Switch AD/LDAP Account to Email/Password'})}
onSubmit={submit}
/>
);
@ -145,8 +140,7 @@ const LDAPToEmail = (props: Props) => {
<p>
<FormattedMessage
id='claim.ldap_to_email.enterLdapPwd'
defaultMessage='{ldapPassword}:'
values={{ldapPassword: passwordPlaceholder}}
defaultMessage='AD/LDAP Password:'
/>
</p>
<div className={classNames('form-group', {'has-error': ldapPasswordError})}>
@ -155,7 +149,7 @@ const LDAPToEmail = (props: Props) => {
className='form-control'
name='ldapPassword'
ref={ldapPasswordInput}
placeholder={passwordPlaceholder}
placeholder={formatMessage({id: 'claim.ldap_to_email.ldapPwd', defaultMessage: 'AD/LDAP Password'})}
spellCheck='false'
/>
</div>
@ -167,23 +161,23 @@ const LDAPToEmail = (props: Props) => {
/>
</p>
<div className={classNames('form-group', {'has-error': passwordError})}>
<LocalizedInput
<input
ref={passwordInput}
type='password'
className='form-control'
name='password'
ref={passwordInput}
placeholder={placeholderPasswordMessage}
placeholder={formatMessage({id: 'claim.ldap_to_email.pwd', defaultMessage: 'Password'})}
spellCheck='false'
/>
</div>
<ErrorLabel errorText={passwordError}/>
<div className={classNames('form-group', {'has-error': confirmError})}>
<LocalizedInput
<input
ref={passwordConfirmInput}
type='password'
className='form-control'
name='passwordconfirm'
ref={passwordConfirmInput}
placeholder={placeholderConfirmMessage}
placeholder={formatMessage({id: 'claim.ldap_to_email.confirm', defaultMessage: 'Confirm Password'})}
spellCheck='false'
/>
</div>

View File

@ -1,15 +1,15 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React from 'react';
import React, {type ReactNode} from 'react';
import Constants from 'utils/constants';
import './column.scss';
type ColumnProps = {
title: string;
message: string;
title: ReactNode;
message: ReactNode;
SVGElement?: React.ReactNode;
extraContent?: React.ReactNode;
onEnterKeyDown?: (e: React.KeyboardEvent<HTMLInputElement>) => void;

View File

@ -1,22 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`components/localized_input/localized_input should match snapshot 1`] = `
<LocalizedInput
className="test-class"
onChange={[MockFunction]}
placeholder={
Object {
"defaultMessage": "placeholder to test",
"id": "test.placeholder",
}
}
value="test value"
>
<input
className="test-class"
onChange={[MockFunction]}
placeholder="placeholder to test"
value="test value"
/>
</LocalizedInput>
`;

View File

@ -1,55 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {mount} from 'enzyme';
import React from 'react';
import {IntlProvider} from 'react-intl';
import {mountWithIntl} from 'tests/helpers/intl-test-helper';
import LocalizedInput from './localized_input';
describe('components/localized_input/localized_input', () => {
const baseProps = {
className: 'test-class',
onChange: jest.fn(),
placeholder: {id: 'test.placeholder', defaultMessage: 'placeholder to test'},
value: 'test value',
};
test('should match snapshot', () => {
const wrapper = mount(
<IntlProvider
locale='en'
messages={{}}
>
<LocalizedInput {...baseProps}/>
</IntlProvider>,
).childAt(0);
expect(wrapper).toMatchSnapshot();
expect(wrapper.find('input').length).toBe(1);
expect(wrapper.find('input').get(0).props.value).toBe('test value');
expect(wrapper.find('input').get(0).props.className).toBe('test-class');
expect(wrapper.find('input').get(0).props.placeholder).toBe('placeholder to test');
});
it('ref should properly be forwarded', () => {
const ref = React.createRef<HTMLInputElement>();
const props = {
...baseProps,
ref,
};
const wrapper = mountWithIntl(
<IntlProvider
locale='en'
messages={{}}
>
<LocalizedInput {...props}/>
</IntlProvider>,
);
expect(ref.current).toBe(wrapper.find('input').instance());
});
});

View File

@ -1,38 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import type {PrimitiveType, FormatXMLElementFn} from 'intl-messageformat';
import React from 'react';
import type {InputHTMLAttributes} from 'react';
import {useIntl} from 'react-intl';
import type {MessageDescriptor} from 'react-intl';
export type Props = Omit<InputHTMLAttributes<HTMLInputElement>, 'placeholder'> & {
placeholder: MessageDescriptor & {
values?: Record<string, PrimitiveType | FormatXMLElementFn<string, string>>;
};
};
const LocalizedInput = React.forwardRef((props: Props, ref?: React.Ref<HTMLInputElement>) => {
const {
placeholder: {
id,
defaultMessage,
values,
},
...otherProps
} = props;
const {formatMessage} = useIntl();
return (
<input
{...otherProps}
ref={ref}
placeholder={formatMessage({id, defaultMessage}, values)}
/>
);
});
LocalizedInput.displayName = 'LocalizedInput';
export default LocalizedInput;

View File

@ -1,9 +1,8 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React, {useState} from 'react';
import React, {type ReactNode, useState} from 'react';
import {useIntl} from 'react-intl';
import type {MessageDescriptor} from 'react-intl';
import type {SubmitOptions} from 'components/claim/components/email_to_ldap';
import ShieldWithCheckmarkSVG from 'components/common/svg_images_components/shield_with_checkmark';
@ -16,8 +15,8 @@ import './login_mfa.scss';
type LoginMfaProps = {
loginId: string | null;
password: string;
title?: MessageDescriptor;
subtitle?: MessageDescriptor;
title?: ReactNode;
subtitle?: ReactNode;
onSubmit: ({loginId, password, token}: SubmitOptions) => void;
}
@ -49,8 +48,8 @@ const LoginMfa = ({loginId, password, title, subtitle, onSubmit}: LoginMfaProps)
return (
<ColumnLayout
title={formatMessage(title || {id: 'login_mfa.title', defaultMessage: 'Enter MFA Token'})}
message={formatMessage(subtitle || {id: 'login_mfa.subtitle', defaultMessage: 'To complete the sign in process, please enter a token from your smartphone\'s authenticator'})}
title={title || formatMessage({id: 'login_mfa.title', defaultMessage: 'Enter MFA Token'})}
message={subtitle || formatMessage({id: 'login_mfa.subtitle', defaultMessage: 'To complete the sign in process, please enter a token from your smartphone\'s authenticator'})}
SVGElement={<ShieldWithCheckmarkSVG/>}
extraContent={(
<div className='login-mfa-form'>

View File

@ -4,7 +4,9 @@
import {shallow} from 'enzyme';
import React from 'react';
import SearchableChannelList from 'components/searchable_channel_list';
import {SearchableChannelList} from 'components/searchable_channel_list';
import {type MockIntl} from 'tests/helpers/intl-test-helper';
import {Filter} from './browse_channels/browse_channels';
@ -26,6 +28,9 @@ describe('components/SearchableChannelList', () => {
rememberHideJoinedChannelsChecked: false,
noResultsText: <>{'no channel found'}</>,
filter: Filter.All,
intl: {
formatMessage: jest.fn(),
} as MockIntl,
};
test('should match init snapshot', () => {

View File

@ -3,7 +3,7 @@
import classNames from 'classnames';
import React from 'react';
import {FormattedMessage} from 'react-intl';
import {FormattedMessage, injectIntl, type WrappedComponentProps} from 'react-intl';
import {ArchiveOutlineIcon, CheckIcon, ChevronDownIcon, GlobeIcon, LockOutlineIcon, MagnifyIcon, AccountOutlineIcon, GlobeCheckedIcon} from '@mattermost/compass-icons/components';
import type {Channel, ChannelMembership} from '@mattermost/types/channels';
@ -13,7 +13,6 @@ import {isPrivateChannel} from 'mattermost-redux/utils/channel_utils';
import MagnifyingGlassSVG from 'components/common/svg_images_components/magnifying_glass_svg';
import LoadingScreen from 'components/loading_screen';
import LocalizedInput from 'components/localized_input/localized_input';
import * as Menu from 'components/menu';
import QuickInput from 'components/quick_input';
import CheckboxCheckedIcon from 'components/widgets/icons/checkbox_checked_icon';
@ -31,7 +30,7 @@ import {Filter} from './browse_channels/browse_channels';
const NEXT_BUTTON_TIMEOUT_MILLISECONDS = 500;
type Props = {
interface Props extends WrappedComponentProps {
channels: Channel[];
channelsPerPage: number;
nextPage: (page: number) => void;
@ -58,7 +57,7 @@ type State = {
isSearch?: boolean;
}
export default class SearchableChannelList extends React.PureComponent<Props, State> {
export class SearchableChannelList extends React.PureComponent<Props, State> {
private nextTimeoutId: number | NodeJS.Timeout;
private filter: React.RefObject<HTMLInputElement>;
private channelListScroll: React.RefObject<HTMLDivElement>;
@ -410,8 +409,7 @@ export default class SearchableChannelList extends React.PureComponent<Props, St
id='searchChannelsTextbox'
ref={this.filter}
className='form-control filter-textbox'
placeholder={{id: t('filtered_channels_list.search'), defaultMessage: 'Search channels'}}
inputComponent={LocalizedInput}
placeholder={this.props.intl.formatMessage({id: 'filtered_channels_list.search', defaultMessage: 'Search channels'})}
onInput={this.handleChange}
clearable={true}
onClear={this.handleClear}
@ -474,8 +472,9 @@ export default class SearchableChannelList extends React.PureComponent<Props, St
if (this.props.canShowArchivedChannels) {
channelDropdownItems.push(
<Menu.Separator/>,
<Menu.Separator key='channelsMoreDropdownSeparator'/>,
<Menu.Item
key='channelsMoreDropdownArchived'
id='channelsMoreDropdownArchived'
onClick={() => this.props.changeFilter(Filter.Archived)}
leadingElement={<ArchiveOutlineIcon size={16}/>}
@ -579,3 +578,5 @@ export default class SearchableChannelList extends React.PureComponent<Props, St
);
}
}
export default injectIntl(SearchableChannelList);

View File

@ -10,12 +10,9 @@ import type {Channel, ChannelMembership} from '@mattermost/types/channels';
import type {TeamMembership} from '@mattermost/types/teams';
import type {UserProfile} from '@mattermost/types/users';
import LocalizedInput from 'components/localized_input/localized_input';
import QuickInput from 'components/quick_input';
import UserList from 'components/user_list';
import {t} from 'utils/i18n';
const NEXT_BUTTON_TIMEOUT = 500;
type Props = {
@ -232,7 +229,6 @@ class SearchableUserList extends React.PureComponent<Props, State> {
let nextButton;
let previousButton;
let usersToDisplay;
const {formatMessage} = this.props.intl;
if (this.props.term || !this.props.users) {
usersToDisplay = this.props.users;
@ -281,7 +277,6 @@ class SearchableUserList extends React.PureComponent<Props, State> {
if (this.props.renderFilterRow) {
filterRow = this.props.renderFilterRow(this.handleInput);
} else {
const searchUsersPlaceholder = {id: t('filtered_user_list.search'), defaultMessage: 'Search users'};
filterRow = (
<div className='col-xs-12'>
<label
@ -294,14 +289,13 @@ class SearchableUserList extends React.PureComponent<Props, State> {
/>
</label>
<QuickInput
id='searchUsersInput'
ref={this.filterRef}
id='searchUsersInput'
className='form-control filter-textbox'
placeholder={searchUsersPlaceholder}
inputComponent={LocalizedInput}
value={this.props.term}
placeholder={this.props.intl.formatMessage({id: 'filtered_user_list.search', defaultMessage: 'Search users'})}
aria-label={this.props.intl.formatMessage({id: 'filtered_user_list.search', defaultMessage: 'Search users'})}
onInput={this.handleInput}
aria-label={formatMessage(searchUsersPlaceholder).toLowerCase()}
value={this.props.term}
/>
</div>
);

View File

@ -4,16 +4,12 @@
import {shallow} from 'enzyme';
import React from 'react';
import type {ChangeEvent, ComponentProps} from 'react';
import {type IntlShape} from 'react-intl';
import {GeneralTab} from 'components/team_general_tab/team_general_tab';
import {type MockIntl} from 'tests/helpers/intl-test-helper';
import {TestHelper} from 'utils/test_helper';
interface MockIntl extends IntlShape {
formatMessage: jest.Mock;
}
describe('components/TeamSettings', () => {
const getTeam = jest.fn().mockResolvedValue({data: true});
const patchTeam = jest.fn().mockReturnValue({data: true});

View File

@ -3061,7 +3061,7 @@
"claim.email_to_oauth.title": "Switch Email/Password Account to {uiType}",
"claim.ldap_to_email.confirm": "Confirm Password",
"claim.ldap_to_email.email": "After switching your authentication method, you will use {email} to login. Your AD/LDAP credentials will no longer allow access to Mattermost.",
"claim.ldap_to_email.enterLdapPwd": "{ldapPassword}:",
"claim.ldap_to_email.enterLdapPwd": "AD/LDAP Password:",
"claim.ldap_to_email.enterPwd": "New email login password:",
"claim.ldap_to_email.ldapPasswordError": "Please enter your AD/LDAP password.",
"claim.ldap_to_email.ldapPwd": "AD/LDAP Password",
@ -3899,6 +3899,8 @@
"local": "local",
"login_mfa.saving": "Logging in…",
"login_mfa.submit": "Submit",
"login_mfa.subtitle": "To complete the sign in process, please enter a token from your smartphone's authenticator",
"login_mfa.title": "Enter MFA Token",
"login_mfa.token": "MFA Token",
"login.cardtitle": "Log in",
"login.cardtitle.external": "Log in with one of the following:",

View File

@ -210,7 +210,7 @@
"jest-junit": "12.2.0",
"jest-styled-components": "7.1.1",
"jest-watch-typeahead": "0.6.4",
"mmjstool": "github:mattermost/mattermost-utilities#d88289ce1d30da1936d9a17e9bed0cdb52de0a10",
"mmjstool": "github:mattermost/mattermost-utilities#83b1b311972b8f5e750aae4019457a40abb5aa44",
"nock": "13.2.8",
"prettier": "2.3.2",
"react-router-enzyme-context": "1.2.0",
@ -16248,8 +16248,8 @@
},
"node_modules/mmjstool": {
"version": "1.0.0",
"resolved": "git+ssh://git@github.com/mattermost/mattermost-utilities.git#d88289ce1d30da1936d9a17e9bed0cdb52de0a10",
"integrity": "sha512-Fia3OL1qvMCBy8B9GpZVyqcMz/7NvQfxVJ6KWxegqQt0rSIkqI0f7UveAWGCxD6XoPHPcCCtdvuklBWjBxzgIQ==",
"resolved": "git+ssh://git@github.com/mattermost/mattermost-utilities.git#83b1b311972b8f5e750aae4019457a40abb5aa44",
"integrity": "sha512-SFAbT+eN1mvgSfRTe8k6IMKVWbhGItTJtxZ+Pt0mX+fe8pakB3MkIkWzHBHVnEDI9Ek9kmQFGF1aZwzKFHXyYQ==",
"dev": true,
"dependencies": {
"estree-walk": "^2.2.0",
@ -35701,7 +35701,7 @@
"mark.js": "8.11.1",
"marked": "github:mattermost/marked#2ef7f28cc7718e3f551c4ce9ea75fdd7580c2008",
"memoize-one": "6.0.0",
"mmjstool": "github:mattermost/mattermost-utilities#d88289ce1d30da1936d9a17e9bed0cdb52de0a10",
"mmjstool": "github:mattermost/mattermost-utilities#83b1b311972b8f5e750aae4019457a40abb5aa44",
"moment-timezone": "0.5.38",
"nock": "13.2.8",
"p-queue": "7.3.0",
@ -36349,10 +36349,10 @@
"dev": true
},
"mmjstool": {
"version": "git+ssh://git@github.com/mattermost/mattermost-utilities.git#d88289ce1d30da1936d9a17e9bed0cdb52de0a10",
"integrity": "sha512-Fia3OL1qvMCBy8B9GpZVyqcMz/7NvQfxVJ6KWxegqQt0rSIkqI0f7UveAWGCxD6XoPHPcCCtdvuklBWjBxzgIQ==",
"version": "git+ssh://git@github.com/mattermost/mattermost-utilities.git#83b1b311972b8f5e750aae4019457a40abb5aa44",
"integrity": "sha512-SFAbT+eN1mvgSfRTe8k6IMKVWbhGItTJtxZ+Pt0mX+fe8pakB3MkIkWzHBHVnEDI9Ek9kmQFGF1aZwzKFHXyYQ==",
"dev": true,
"from": "mmjstool@github:mattermost/mattermost-utilities#d88289ce1d30da1936d9a17e9bed0cdb52de0a10",
"from": "mmjstool@github:mattermost/mattermost-utilities#83b1b311972b8f5e750aae4019457a40abb5aa44",
"requires": {
"estree-walk": "^2.2.0",
"filehound": "^1.17.5",