PLT-6451 Migrate installed_oauth_app.jsx to be pure and use Redux (#7190)
* Migrate installed_oauth_app.jsx to be pure and use Redux, add test * Fix behavior for the error case
Этот коммит содержится в:
коммит произвёл
Christopher Speller
родитель
6df51f8617
Коммит
13d76a0cb2
@@ -9,7 +9,6 @@ const getState = store.getState;
|
|||||||
|
|
||||||
import * as AdminActions from 'mattermost-redux/actions/admin';
|
import * as AdminActions from 'mattermost-redux/actions/admin';
|
||||||
import * as UserActions from 'mattermost-redux/actions/users';
|
import * as UserActions from 'mattermost-redux/actions/users';
|
||||||
import * as IntegrationActions from 'mattermost-redux/actions/integrations';
|
|
||||||
import {Client4} from 'mattermost-redux/client';
|
import {Client4} from 'mattermost-redux/client';
|
||||||
|
|
||||||
export function saveConfig(config, success, error) {
|
export function saveConfig(config, success, error) {
|
||||||
@@ -238,19 +237,6 @@ export function oauthToEmail(currentService, email, password, success, error) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function regenerateOAuthAppSecret(oauthAppId, success, error) {
|
|
||||||
IntegrationActions.regenOAuthAppSecret(oauthAppId)(dispatch, getState).then(
|
|
||||||
(data) => {
|
|
||||||
if (data && success) {
|
|
||||||
success(data);
|
|
||||||
} else if (data == null && error) {
|
|
||||||
const serverError = getState().requests.admin.updateOAuthApp.error;
|
|
||||||
error({id: serverError.server_error_id, ...serverError});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function uploadBrandImage(brandImage, success, error) {
|
export function uploadBrandImage(brandImage, success, error) {
|
||||||
AdminActions.uploadBrandImage(brandImage)(dispatch, getState).then(
|
AdminActions.uploadBrandImage(brandImage)(dispatch, getState).then(
|
||||||
(data) => {
|
(data) => {
|
||||||
|
|||||||
@@ -4,70 +4,83 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
import FormError from 'components/form_error.jsx';
|
|
||||||
|
|
||||||
import * as Utils from 'utils/utils.jsx';
|
import * as Utils from 'utils/utils.jsx';
|
||||||
|
|
||||||
import {FormattedMessage, FormattedHTMLMessage} from 'react-intl';
|
import {FormattedMessage, FormattedHTMLMessage} from 'react-intl';
|
||||||
import {regenerateOAuthAppSecret} from 'actions/admin_actions.jsx';
|
import FormError from 'components/form_error.jsx';
|
||||||
|
|
||||||
import DeleteIntegration from './delete_integration.jsx';
|
import DeleteIntegration from './delete_integration.jsx';
|
||||||
|
|
||||||
const FAKE_SECRET = '***************';
|
const FAKE_SECRET = '***************';
|
||||||
|
|
||||||
export default class InstalledOAuthApp extends React.Component {
|
export default class InstalledOAuthApp extends React.PureComponent {
|
||||||
static get propTypes() {
|
static propTypes = {
|
||||||
return {
|
|
||||||
oauthApp: PropTypes.object.isRequired,
|
/**
|
||||||
onDelete: PropTypes.func.isRequired,
|
* The oauthApp data
|
||||||
filter: PropTypes.string
|
*/
|
||||||
};
|
oauthApp: PropTypes.object.isRequired,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The request state for regenOAuthAppSecret action. Contains status and error
|
||||||
|
*/
|
||||||
|
regenOAuthAppSecretRequest: PropTypes.object.isRequired,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The function to call when Regenerate Secret link is clicked
|
||||||
|
*/
|
||||||
|
onRegenerateSecret: PropTypes.func.isRequired,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The function to call when Delete link is clicked
|
||||||
|
*/
|
||||||
|
onDelete: PropTypes.func.isRequired,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set to filter OAuthApp
|
||||||
|
*/
|
||||||
|
filter: PropTypes.string
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.handleShowClientSecret = this.handleShowClientSecret.bind(this);
|
|
||||||
this.handleHideClientScret = this.handleHideClientScret.bind(this);
|
|
||||||
this.handleRegenerate = this.handleRegenerate.bind(this);
|
|
||||||
this.handleDelete = this.handleDelete.bind(this);
|
|
||||||
|
|
||||||
this.matchesFilter = this.matchesFilter.bind(this);
|
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
clientSecret: FAKE_SECRET
|
clientSecret: FAKE_SECRET
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
handleShowClientSecret(e) {
|
handleShowClientSecret = (e) => {
|
||||||
e.preventDefault();
|
if (e && e.preventDefault) {
|
||||||
|
e.preventDefault();
|
||||||
|
}
|
||||||
this.setState({clientSecret: this.props.oauthApp.client_secret});
|
this.setState({clientSecret: this.props.oauthApp.client_secret});
|
||||||
}
|
}
|
||||||
|
|
||||||
handleHideClientScret(e) {
|
handleHideClientSecret = (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
this.setState({clientSecret: FAKE_SECRET});
|
this.setState({clientSecret: FAKE_SECRET});
|
||||||
}
|
}
|
||||||
|
|
||||||
handleRegenerate(e) {
|
handleRegenerate = (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
this.props.onRegenerateSecret(this.props.oauthApp.id).then(
|
||||||
regenerateOAuthAppSecret(
|
|
||||||
this.props.oauthApp.id,
|
|
||||||
() => {
|
() => {
|
||||||
this.handleShowClientSecret(e);
|
const {error} = this.props.regenOAuthAppSecretRequest;
|
||||||
},
|
if (error) {
|
||||||
(err) => {
|
this.setState({error: error.message});
|
||||||
this.setState({error: err.message});
|
} else {
|
||||||
|
this.setState({error: null});
|
||||||
|
this.handleShowClientSecret();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
handleDelete() {
|
handleDelete = () => {
|
||||||
this.props.onDelete(this.props.oauthApp);
|
this.props.onDelete(this.props.oauthApp);
|
||||||
}
|
}
|
||||||
|
|
||||||
matchesFilter(oauthApp, filter) {
|
matchesFilter = (oauthApp, filter) => {
|
||||||
if (!filter) {
|
if (!filter) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -152,7 +165,7 @@ export default class InstalledOAuthApp extends React.Component {
|
|||||||
showHide = (
|
showHide = (
|
||||||
<a
|
<a
|
||||||
href='#'
|
href='#'
|
||||||
onClick={this.handleHideClientScret}
|
onClick={this.handleHideClientSecret}
|
||||||
>
|
>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='installed_integrations.hideSecret'
|
id='installed_integrations.hideSecret'
|
||||||
@@ -254,4 +267,4 @@ export default class InstalledOAuthApp extends React.Component {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -13,7 +13,8 @@ function mapStateToProps(state, ownProps) {
|
|||||||
return {
|
return {
|
||||||
...ownProps,
|
...ownProps,
|
||||||
oauthApps: getOAuthApps(state),
|
oauthApps: getOAuthApps(state),
|
||||||
isSystemAdmin: isCurrentUserSystemAdmin(state)
|
isSystemAdmin: isCurrentUserSystemAdmin(state),
|
||||||
|
regenOAuthAppSecretRequest: state.requests.integrations.updateOAuthApp
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -21,6 +22,7 @@ function mapDispatchToProps(dispatch) {
|
|||||||
return {
|
return {
|
||||||
actions: bindActionCreators({
|
actions: bindActionCreators({
|
||||||
getOAuthApps: Actions.getOAuthApps,
|
getOAuthApps: Actions.getOAuthApps,
|
||||||
|
regenOAuthAppSecret: Actions.regenOAuthAppSecret,
|
||||||
deleteOAuthApp: Actions.deleteOAuthApp
|
deleteOAuthApp: Actions.deleteOAuthApp
|
||||||
}, dispatch)
|
}, dispatch)
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -27,6 +27,11 @@ export default class InstalledOAuthApps extends React.PureComponent {
|
|||||||
*/
|
*/
|
||||||
isSystemAdmin: PropTypes.bool,
|
isSystemAdmin: PropTypes.bool,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The request state for regenOAuthAppSecret action. Contains status and error
|
||||||
|
*/
|
||||||
|
regenOAuthAppSecretRequest: PropTypes.object.isRequired,
|
||||||
|
|
||||||
actions: PropTypes.shape({
|
actions: PropTypes.shape({
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -34,6 +39,11 @@ export default class InstalledOAuthApps extends React.PureComponent {
|
|||||||
*/
|
*/
|
||||||
getOAuthApps: PropTypes.func.isRequired,
|
getOAuthApps: PropTypes.func.isRequired,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The function to call when Regenerate Secret link is clicked
|
||||||
|
*/
|
||||||
|
regenOAuthAppSecret: PropTypes.func.isRequired,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The function to call when Delete link is clicked
|
* The function to call when Delete link is clicked
|
||||||
*/
|
*/
|
||||||
@@ -80,6 +90,8 @@ export default class InstalledOAuthApps extends React.PureComponent {
|
|||||||
<InstalledOAuthApp
|
<InstalledOAuthApp
|
||||||
key={app.id}
|
key={app.id}
|
||||||
oauthApp={app}
|
oauthApp={app}
|
||||||
|
regenOAuthAppSecretRequest={this.props.regenOAuthAppSecretRequest}
|
||||||
|
onRegenerateSecret={this.props.actions.regenOAuthAppSecret}
|
||||||
onDelete={this.deleteOAuthApp}
|
onDelete={this.deleteOAuthApp}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -0,0 +1,155 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`components/integrations/InstalledOAuthApp should filter out OAuthApp 1`] = `null`;
|
||||||
|
|
||||||
|
exports[`components/integrations/InstalledOAuthApp should match snapshot 1`] = `
|
||||||
|
<div
|
||||||
|
className="backstage-list__item"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="integration__icon integration-list__icon"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
src="https://test.com/icon"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="item-details"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="item-details__row"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
className="item-details__name"
|
||||||
|
>
|
||||||
|
testApp
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="item-details__row"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
className="item-details__description"
|
||||||
|
>
|
||||||
|
testing
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="item-details__row"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
className="item-details__url"
|
||||||
|
>
|
||||||
|
<FormattedHTMLMessage
|
||||||
|
defaultMessage="Is Trusted: <strong>{isTrusted}</strong>"
|
||||||
|
id="installed_oauth_apps.is_trusted"
|
||||||
|
values={
|
||||||
|
Object {
|
||||||
|
"isTrusted": "Yes",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="item-details__row"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
className="item-details__token"
|
||||||
|
>
|
||||||
|
<FormattedHTMLMessage
|
||||||
|
defaultMessage="Client ID: <strong>{clientId}</strong>"
|
||||||
|
id="installed_integrations.client_id"
|
||||||
|
values={
|
||||||
|
Object {
|
||||||
|
"clientId": "facxd9wpzpbpfp8pad78xj75pr",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="item-details__row"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
className="item-details__token"
|
||||||
|
>
|
||||||
|
<FormattedHTMLMessage
|
||||||
|
defaultMessage="Client Secret: <strong>{clientSecret}</strong>"
|
||||||
|
id="installed_integrations.client_secret"
|
||||||
|
values={
|
||||||
|
Object {
|
||||||
|
"clientSecret": "***************",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="item-details__row"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
className="item-details__url"
|
||||||
|
>
|
||||||
|
<FormattedMessage
|
||||||
|
defaultMessage="Callback URLs: {urls}"
|
||||||
|
id="installed_integrations.callback_urls"
|
||||||
|
values={
|
||||||
|
Object {
|
||||||
|
"urls": "https://test.com/callback, https://test.com/callback2",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="item-details__row"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
className="item-details__creation"
|
||||||
|
>
|
||||||
|
<FormattedMessage
|
||||||
|
defaultMessage="Created by {creator} on {createAt, date, full}"
|
||||||
|
id="installed_integrations.creation"
|
||||||
|
values={
|
||||||
|
Object {
|
||||||
|
"createAt": 1501365458934,
|
||||||
|
"creator": "",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="item-actions"
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
href="#"
|
||||||
|
onClick={[Function]}
|
||||||
|
>
|
||||||
|
<FormattedMessage
|
||||||
|
defaultMessage="Show Secret"
|
||||||
|
id="installed_integrations.showSecret"
|
||||||
|
values={Object {}}
|
||||||
|
/>
|
||||||
|
</a>
|
||||||
|
-
|
||||||
|
<a
|
||||||
|
href="#"
|
||||||
|
onClick={[Function]}
|
||||||
|
>
|
||||||
|
<FormattedMessage
|
||||||
|
defaultMessage="Regenerate Secret"
|
||||||
|
id="installed_integrations.regenSecret"
|
||||||
|
values={Object {}}
|
||||||
|
/>
|
||||||
|
</a>
|
||||||
|
-
|
||||||
|
<DeleteIntegration
|
||||||
|
messageId="installed_oauth_apps.delete.confirm"
|
||||||
|
onDelete={[Function]}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
@@ -72,6 +72,7 @@ exports[`components/integrations/InstalledOAuthApps should match snapshot 1`] =
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
onDelete={[Function]}
|
onDelete={[Function]}
|
||||||
|
onRegenerateSecret={[Function]}
|
||||||
/>
|
/>
|
||||||
<InstalledOAuthApp
|
<InstalledOAuthApp
|
||||||
oauthApp={
|
oauthApp={
|
||||||
@@ -93,6 +94,7 @@ exports[`components/integrations/InstalledOAuthApps should match snapshot 1`] =
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
onDelete={[Function]}
|
onDelete={[Function]}
|
||||||
|
onRegenerateSecret={[Function]}
|
||||||
/>
|
/>
|
||||||
</BackstageList>
|
</BackstageList>
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -0,0 +1,71 @@
|
|||||||
|
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See License.txt for license information.
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import {shallow} from 'enzyme';
|
||||||
|
|
||||||
|
import InstalledOAuthApp from 'components/integrations/components/installed_oauth_app.jsx';
|
||||||
|
|
||||||
|
describe('components/integrations/InstalledOAuthApp', () => {
|
||||||
|
const emptyFunction = jest.fn();
|
||||||
|
const app = {
|
||||||
|
id: 'facxd9wpzpbpfp8pad78xj75pr',
|
||||||
|
name: 'testApp',
|
||||||
|
client_secret: '88cxd9wpzpbpfp8pad78xj75pr',
|
||||||
|
create_at: 1501365458934,
|
||||||
|
creator_id: '88oybd1dwfdoxpkpw1h5kpbyco',
|
||||||
|
description: 'testing',
|
||||||
|
homepage: 'https://test.com',
|
||||||
|
icon_url: 'https://test.com/icon',
|
||||||
|
is_trusted: true,
|
||||||
|
update_at: 1501365458934,
|
||||||
|
callback_urls: ['https://test.com/callback', 'https://test.com/callback2']
|
||||||
|
};
|
||||||
|
|
||||||
|
test('should match snapshot', () => {
|
||||||
|
const wrapper = shallow(
|
||||||
|
<InstalledOAuthApp
|
||||||
|
oauthApp={app}
|
||||||
|
onRegenerateSecret={emptyFunction}
|
||||||
|
onDelete={emptyFunction}
|
||||||
|
filter={''}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
expect(wrapper).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should call onRegenerateSecret function', () => {
|
||||||
|
const onRegenerateSecret = jest.genMockFunction().mockImplementation(
|
||||||
|
() => {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
process.nextTick(() => resolve());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const wrapper = shallow(
|
||||||
|
<InstalledOAuthApp
|
||||||
|
oauthApp={app}
|
||||||
|
onRegenerateSecret={onRegenerateSecret}
|
||||||
|
onDelete={emptyFunction}
|
||||||
|
filter={''}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
wrapper.find('div.item-actions a').at(1).simulate('click', {preventDefault() {
|
||||||
|
return jest.fn();
|
||||||
|
}});
|
||||||
|
expect(onRegenerateSecret).toBeCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should filter out OAuthApp', () => {
|
||||||
|
const wrapper = shallow(
|
||||||
|
<InstalledOAuthApp
|
||||||
|
oauthApp={app}
|
||||||
|
onRegenerateSecret={emptyFunction}
|
||||||
|
onDelete={emptyFunction}
|
||||||
|
filter={'filter'}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
expect(wrapper).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -44,7 +44,11 @@ describe('components/integrations/InstalledOAuthApps', () => {
|
|||||||
team={{name: 'test'}}
|
team={{name: 'test'}}
|
||||||
oauthApps={oauthApps}
|
oauthApps={oauthApps}
|
||||||
isSystemAdmin={true}
|
isSystemAdmin={true}
|
||||||
actions={{getOAuthApps: emptyFunction, deleteOAuthApp: emptyFunction}}
|
actions={{
|
||||||
|
getOAuthApps: emptyFunction,
|
||||||
|
regenOAuthAppSecret: emptyFunction,
|
||||||
|
deleteOAuthApp: emptyFunction
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
expect(wrapper.find('InstalledOAuthApp').length).toBe(2);
|
expect(wrapper.find('InstalledOAuthApp').length).toBe(2);
|
||||||
|
|||||||
Ссылка в новой задаче
Block a user