Skip to content

Commit

Permalink
MM-10368: react-router subpath (mattermost#1348)
Browse files Browse the repository at this point in the history
* use Link in Textbox

Introduce react-router-enzyme-context to enable unit testing of same
with `mount` vs. `shallow`.

* leverage browserHistory in SystemUsersDropdown

Note that the demotion modal isn't actually being shown at present: the
transition of self from system admin to regular member is done without
confirmation after a partial refactor last year.

* leverage browserHistory in Authorize

* leverage Link in SignupController

* leverage browserHistory in UserSettingsSecurity

* extract base path from config's SiteURL on logout

* move __webpack_public_path__ into entry.js

This ensures that the other imports aren't hoisted above the definition.
Additionally re-export the public path back onto window.publicPath to
cover development environments.

* rely on window.basename in autolinkChannelMentions

While globals should generally be avoided, this is one of only two
places where `window.basename` is used. Moving it into the store and
rewriting the text rendering to pass down (or wrap) the necessary state
may be premature, given the pre-existing need for this variable to be
global.

* update getSiteURL to respect subpath

Additionally leverage getSiteURL over accessing window.* directly.

* strip any trailing slash before appending the ws pathname

* lint fixes

* use window.location.originw when SiteURL not defined
  • Loading branch information
lieut-data authored and sudheerDev committed Jun 22, 2018
1 parent 983635d commit e8e1219
Show file tree
Hide file tree
Showing 21 changed files with 211 additions and 75 deletions.
23 changes: 15 additions & 8 deletions actions/websocket_actions.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,23 +58,30 @@ export function initialize() {
if (config.WebsocketURL) {
connUrl = config.WebsocketURL;
} else {
connUrl = getSiteURL();
connUrl = new URL(getSiteURL());

// replace the protocol with a websocket one
if (connUrl.startsWith('https:')) {
connUrl = connUrl.replace(/^https:/, 'wss:');
if (connUrl.protocol === 'https:') {
connUrl.protocol = 'wss:';
} else {
connUrl = connUrl.replace(/^http:/, 'ws:');
connUrl.protocol = 'ws:';
}

// append a port number if one isn't already specified
if (!(/:\d+$/).test(connUrl)) {
if (connUrl.startsWith('wss:')) {
connUrl += ':' + config.WebsocketSecurePort;
if (!(/:\d+$/).test(connUrl.host)) {
if (connUrl.protocol === 'wss:') {
connUrl.host += ':' + config.WebsocketSecurePort;
} else {
connUrl += ':' + config.WebsocketPort;
connUrl.host += ':' + config.WebsocketPort;
}
}

connUrl = connUrl.toString();
}

// Strip any trailing slash before appending the pathname below.
if (connUrl.length > 0 && connUrl[connUrl.length - 1] === '/') {
connUrl = connUrl.substring(0, connUrl.length - 1);
}

connUrl += Client4.getUrlVersion() + '/websocket';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import * as Utils from 'utils/utils.jsx';
import {clientLogout} from 'actions/global_actions.jsx';
import ConfirmModal from 'components/confirm_modal.jsx';
import SystemPermissionGate from 'components/permissions_gates/system_permission_gate';
import {browserHistory} from 'utils/browser_history';

export default class SystemUsersDropdown extends React.Component {
static propTypes = {
Expand Down Expand Up @@ -146,9 +147,9 @@ export default class SystemUsersDropdown extends React.Component {
const teamUrl = TeamStore.getCurrentTeamUrl();
if (teamUrl) {
// the channel is added to the URL cause endless loading not being fully fixed
window.location.href = teamUrl + `/channels/${Constants.DEFAULT_CHANNEL}`;
browserHistory.push(teamUrl + `/channels/${Constants.DEFAULT_CHANNEL}`);
} else {
window.location.href = '/';
browserHistory.push('/');
}
}

Expand Down
7 changes: 4 additions & 3 deletions components/authorize.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {FormattedHTMLMessage, FormattedMessage} from 'react-intl';
import {allowOAuth2, getOAuthAppInfo} from 'actions/admin_actions.jsx';
import icon50 from 'images/icon50x50.png';
import FormError from 'components/form_error.jsx';
import {browserHistory} from 'utils/browser_history';

export default class Authorize extends React.Component {
static get propTypes() {
Expand Down Expand Up @@ -54,7 +55,7 @@ export default class Authorize extends React.Component {
allowOAuth2(params,
(data) => {
if (data.redirect) {
window.location.href = data.redirect;
browserHistory.push(data.redirect);
}
},
(err) => {
Expand All @@ -66,11 +67,11 @@ export default class Authorize extends React.Component {
handleDeny() {
const redirectUri = (new URLSearchParams(this.props.location.search)).get('redirect_uri');
if (redirectUri.startsWith('https://') || redirectUri.startsWith('http:https://')) {
window.location.replace(redirectUri + '?error=access_denied');
browserHistory.replace(redirectUri + '?error=access_denied');
return;
}

window.location.replace('/error');
browserHistory.replace('/error');
}

render() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {Link} from 'react-router-dom';
import {browserHistory} from 'utils/browser_history';
import Constants from 'utils/constants.jsx';
import BackstageHeader from 'components/backstage/components/backstage_header.jsx';
import {getSiteURL} from 'utils/url.jsx';

export default class ConfirmIntegration extends React.Component {
static get propTypes() {
Expand Down Expand Up @@ -101,7 +102,7 @@ export default class ConfirmIntegration extends React.Component {
id='add_incoming_webhook.url'
defaultMessage='<b>URL</b>: {url}'
values={{
url: window.location.origin + '/hooks/' + incomingHook.id,
url: getSiteURL() + '/hooks/' + incomingHook.id,
}}
/>
</p>
Expand Down
4 changes: 3 additions & 1 deletion components/root/root.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ import loadAuthorize from 'bundle-loader?lazy!components/authorize';
import loadCreateTeam from 'bundle-loader?lazy!components/create_team';
import loadMfa from 'bundle-loader?lazy!components/mfa/mfa_controller';
import store from 'stores/redux_store.jsx';
import {getSiteURL} from 'utils/url.jsx';

const CreateTeam = makeAsyncComponent(loadCreateTeam);
const ErrorPage = makeAsyncComponent(loadErrorPage);
Expand Down Expand Up @@ -93,7 +94,8 @@ export default class Root extends React.Component {
super(props);

// Redux
setUrl(window.location.origin);
setUrl(getSiteURL());

setSystemEmojis(EmojiIndicesByAlias);

// Force logout of all tabs if one tab is logged out
Expand Down
6 changes: 3 additions & 3 deletions components/signup/signup_controller/signup_controller.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -253,10 +253,10 @@ export default class SignupController extends React.Component {
}

signupControls.push(
<a
<Link
className='btn btn-custom-login btn--full saml'
key='saml'
href={'/login/sso/saml' + window.location.search + query}
to={'/login/sso/saml' + window.location.search + query}
>
<span>
<span
Expand All @@ -267,7 +267,7 @@ export default class SignupController extends React.Component {
{this.props.samlLoginButtonText}
</span>
</span>
</a>
</Link>
);
}

Expand Down
7 changes: 4 additions & 3 deletions components/textbox.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import $ from 'jquery';
import PropTypes from 'prop-types';
import React from 'react';
import {FormattedMessage} from 'react-intl';
import {Link} from 'react-router-dom';

import AutosizeTextarea from 'components/autosize_textarea.jsx';
import PostMarkdown from 'components/post_markdown';
Expand Down Expand Up @@ -330,18 +331,18 @@ export default class Textbox extends React.Component {
<div className={'help__text ' + helpTextClass}>
{helpText}
{previewLink}
<a
<Link
id='helpTextLink'
target='_blank'
rel='noopener noreferrer'
href='/help/messaging'
to='/help/messaging'
className='textbox-help-link'
>
<FormattedMessage
id='textbox.help'
defaultMessage='Help'
/>
</a>
</Link>
</div>
</div>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ export default class SecurityTab extends React.Component {
if (this.props.mfaLicensed &&
this.props.enableMultifactorAuthentication &&
this.props.enforceMultifactorAuthentication) {
window.location.href = '/mfa/setup';
browserHistory.push('/mfa/setup');
return;
}

Expand Down
15 changes: 15 additions & 0 deletions entry.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.

// Allow overriding the path used by webpack to dynamically resolve assets. This is driven by
// an environment variable in development, or by a window variable defined in root.html in
// production. The window variable is updated by the server after configuring SiteURL and
// restarting or by running the `mattermost config subpath` command.
window.publicPath = process.env.PUBLIC_PATH || window.publicPath || '/static/'; // eslint-disable-line no-process-env
__webpack_public_path__ = window.publicPath; // eslint-disable-line camelcase, no-undef

// Define the subpath at which Mattermost is running. Extract this from the publicPath above to
// avoid depending on Redux state before it is even loaded. This actual global export is used
// in a minimum of places, as it is preferred to leverage react-router, configured to use this
// basename accordingly.
window.basename = window.publicPath.substr(0, window.publicPath.length - '/static/'.length);
10 changes: 10 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@
"mini-css-extract-plugin": "0.4.0",
"nightwatch": "0.9.21",
"node-sass": "4.9.0",
"react-router-enzyme-context": "^1.2.0",
"redux-mock-store": "1.5.3",
"redux-persist-node-storage": "2.0.0",
"remote-redux-devtools": "0.5.12",
Expand Down
6 changes: 1 addition & 5 deletions root.jsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.

// Allow overriding the path used by webpack to dynamically resolve assets. This is driven by
// an environment variable in development, or by a window variable defined in root.html in
// production. The window variable is updated by the server after configuring SiteURL and
// restarting or by running the `mattermost config subpath` command.
__webpack_public_path__ = process.env.PUBLIC_PATH || window.publicPath || '/static/'; // eslint-disable-line camelcase, no-undef, no-process-env
import './entry.js';

import React from 'react';
import ReactDOM from 'react-dom';
Expand Down
6 changes: 6 additions & 0 deletions selectors/general.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,9 @@ export function areTimezonesEnabledAndSupported(state) {
const config = getConfig(state);
return config.ExperimentalTimezone === 'true';
}

export function getBasePath(state) {
const config = getConfig(state) || {};

return new URL(config.SiteURL || window.location.origin).pathname;
}
5 changes: 4 additions & 1 deletion store/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import appReducer from 'reducers';
import {transformSet} from 'store/utils';
import {detect} from 'utils/network.js';
import {ActionTypes} from 'utils/constants.jsx';
import {getBasePath} from 'selectors/general';

function getAppReducer() {
return require('../reducers'); // eslint-disable-line global-require
Expand Down Expand Up @@ -118,12 +119,14 @@ export default function configureStore(initialState) {
// check to see if the logout request was successful
store.subscribe(() => {
const state = store.getState();
const basePath = getBasePath(state);

if (state.requests.users.logout.status === RequestStatus.SUCCESS && !purging) {
purging = true;

persistor.purge().then(() => {
document.cookie = 'MMUSERID=;expires=Thu, 01 Jan 1970 00:00:01 GMT;';
window.location.href = '/';
window.location.href = basePath;

store.dispatch({
type: General.OFFLINE_STORE_RESET,
Expand Down
21 changes: 12 additions & 9 deletions tests/components/__snapshots__/textbox.test.jsx.snap
Original file line number Diff line number Diff line change
Expand Up @@ -109,19 +109,20 @@ exports[`components/TextBox should match snapshot with required props 1`] = `
/>
</span>
</div>
<a
<Link
className="textbox-help-link"
href="/help/messaging"
id="helpTextLink"
rel="noopener noreferrer"
replace={false}
target="_blank"
to="/help/messaging"
>
<FormattedMessage
defaultMessage="Help"
id="textbox.help"
values={Object {}}
/>
</a>
</Link>
</div>
</div>
`;
Expand Down Expand Up @@ -235,19 +236,20 @@ exports[`components/TextBox should throw error when new property is too long 1`]
/>
</span>
</div>
<a
<Link
className="textbox-help-link"
href="/help/messaging"
id="helpTextLink"
rel="noopener noreferrer"
replace={false}
target="_blank"
to="/help/messaging"
>
<FormattedMessage
defaultMessage="Help"
id="textbox.help"
values={Object {}}
/>
</a>
</Link>
</div>
</div>
`;
Expand Down Expand Up @@ -361,19 +363,20 @@ exports[`components/TextBox should throw error when value is too long 1`] = `
/>
</span>
</div>
<a
<Link
className="textbox-help-link"
href="/help/messaging"
id="helpTextLink"
rel="noopener noreferrer"
replace={false}
target="_blank"
to="/help/messaging"
>
<FormattedMessage
defaultMessage="Help"
id="textbox.help"
values={Object {}}
/>
</a>
</Link>
</div>
</div>
`;
Loading

0 comments on commit e8e1219

Please sign in to comment.