Doug Lauder
2023-03-22 17:22:27 -04:00
коммит произвёл GitHub
родитель b61c096497
Коммит c943ed6859
13276 изменённых файлов: 1695615 добавлений и 223189 удалений

Просмотреть файл

@@ -0,0 +1,29 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`components/external_link should match snapshot 1`] = `
<Provider
store={
Object {
"clearActions": [Function],
"dispatch": [Function],
"getActions": [Function],
"getState": [Function],
"replaceReducer": [Function],
"subscribe": [Function],
}
}
>
<ExternalLink
href="https://mattermost.com"
>
<a
href="https://mattermost.com?utm_source=mattermost&utm_medium=in-product-cloud&utm_content=&uid=currentUserId&sid="
onClick={[Function]}
rel="noopener noreferrer"
target="_blank"
>
Click Me
</a>
</ExternalLink>
</Provider>
`;

Просмотреть файл

@@ -0,0 +1,164 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React from 'react';
import {mount} from 'enzyme';
import {screen} from '@testing-library/react';
import {Provider} from 'react-redux';
import {DeepPartial} from 'redux';
import mockStore from 'tests/test_store';
import {renderWithIntlAndStore} from 'tests/react_testing_utils';
import {GlobalState} from 'types/store';
import ExternalLink from '.';
describe('components/external_link', () => {
const initialState: DeepPartial<GlobalState> = {
entities: {
general: {
config: {},
license: {
Cloud: 'true',
},
},
users: {
currentUserId: 'currentUserId',
},
},
};
it('should match snapshot', () => {
const store = mockStore(initialState);
const wrapper = mount(
<Provider store={store}>
<ExternalLink
href='https://mattermost.com'
>{'Click Me'}</ExternalLink>
</Provider>,
);
expect(wrapper).toMatchSnapshot();
});
it('should attach parameters', () => {
const state = {
...initialState,
entities: {
...initialState.entities,
general: {
...initialState?.entities?.general,
config: {
DiagnosticsEnabled: 'true',
},
},
},
};
const store: GlobalState = JSON.parse(JSON.stringify(state));
renderWithIntlAndStore(
<ExternalLink href='https://mattermost.com'>
{'Click Me'}
</ExternalLink>,
store,
);
expect(screen.queryByText('Click Me')).toHaveAttribute(
'href',
expect.stringMatching('utm_source=mattermost&utm_medium=in-product-cloud&utm_content=&uid=currentUserId&sid='),
);
});
it('should preserve query params that already exist in the href', () => {
const state = {
...initialState,
entities: {
...initialState.entities,
general: {
...initialState?.entities?.general,
config: {
DiagnosticsEnabled: 'true',
},
},
},
};
const store: GlobalState = JSON.parse(JSON.stringify(state));
renderWithIntlAndStore(
<ExternalLink href='https://mattermost.com?test=true'>
{'Click Me'}
</ExternalLink>,
store,
);
expect(screen.queryByText('Click Me')).toHaveAttribute(
'href',
'https://mattermost.com?utm_source=mattermost&utm_medium=in-product-cloud&utm_content=&uid=currentUserId&sid=&test=true',
);
});
it("should not attach parameters if href isn't *.mattermost.com enabled", () => {
const state = {
...initialState,
entities: {
...initialState.entities,
general: {
...initialState?.entities?.general,
config: {
DiagnosticsEnabled: 'true',
},
},
},
};
const store: GlobalState = JSON.parse(JSON.stringify(state));
renderWithIntlAndStore(
<ExternalLink href='https://google.com'>
{'Click Me'}
</ExternalLink>,
store,
);
expect(screen.queryByText('Click Me')).not.toHaveAttribute(
'href',
'utm_source=mattermost&utm_medium=in-product-cloud&utm_content=&uid=currentUserId&sid=',
);
});
it('should be able to override target, rel', () => {
const state = {
...initialState,
entities: {
...initialState.entities,
general: {
...initialState?.entities?.general,
config: {
DiagnosticsEnabled: 'true',
},
},
},
};
const store: GlobalState = JSON.parse(JSON.stringify(state));
renderWithIntlAndStore(
<ExternalLink
target='test'
rel='test'
href='https://google.com'
>{'Click Me'}</ExternalLink>,
store,
);
expect(screen.queryByText('Click Me')).toHaveAttribute(
'target',
expect.stringMatching(
'test',
),
);
expect(screen.queryByText('Click Me')).toHaveAttribute(
'rel',
expect.stringMatching('test'),
);
});
});

Просмотреть файл

@@ -0,0 +1,76 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
/* eslint-disable mattermost/use-external-link */
import React from 'react';
import {useSelector} from 'react-redux';
import {trackEvent} from 'actions/telemetry_actions';
import {getConfig, getLicense} from 'mattermost-redux/selectors/entities/general';
import {getCurrentUserId} from 'mattermost-redux/selectors/entities/common';
type ExternalLinkQueryParams = {
utm_source?: string;
utm_medium?: string;
utm_campaign?: string;
utm_content?: string;
userId?: string;
}
type Props = React.AnchorHTMLAttributes<HTMLAnchorElement> & {
href: string;
target?: string;
rel?: string;
onClick?: (event: React.MouseEvent<HTMLElement>) => void;
queryParams?: ExternalLinkQueryParams;
location?: string;
children: React.ReactNode;
};
export default function ExternalLink(props: Props) {
const userId = useSelector(getCurrentUserId);
const config = useSelector(getConfig);
const license = useSelector(getLicense);
let href = props.href;
let queryParams = {};
if (href?.includes('mattermost.com')) {
const existingURLSearchParams = new URL(href).searchParams;
const existingQueryParamsObj = Object.fromEntries(existingURLSearchParams.entries());
queryParams = {
utm_source: 'mattermost',
utm_medium: license.Cloud === 'true' ? 'in-product-cloud' : 'in-product',
utm_content: props.location || '',
uid: userId,
sid: config.TelemetryId || '',
...props.queryParams,
...existingQueryParamsObj,
};
const queryString = new URLSearchParams(queryParams).toString();
if (Object.keys(existingQueryParamsObj).length) {
// If the href already has query params, remove them before adding them back with the addition of the new ones
href = href?.split('?')[0];
}
href = `${href}?${queryString}`;
}
const handleClick = (e: React.MouseEvent<HTMLElement>) => {
trackEvent('link_out', 'click_external_link', queryParams);
if (typeof props.onClick === 'function') {
props.onClick(e);
}
};
return (
<a
{...props}
target={props.target || '_blank'}
rel={props.rel || 'noopener noreferrer'}
onClick={handleClick}
href={href}
>
{props.children}
</a>
);
}