GH-6452 Migrate installed_command.jsx to be pure and use Redux (#6903)

* Migrate installed_command.jsx to be pure and use Redux

* Add test for InstalledCommand component

* Fix failing test and typo

* Whoops. Revert back deleted filter

* Add filter test

* Remove commands related code from /stores/integration_store.jsx
Этот коммит содержится в:
94117nl
2017-08-03 08:25:07 -05:00
коммит произвёл Harrison Healey
родитель b54d134299
Коммит ea095d6206
6 изменённых файлов: 230 добавлений и 57 удалений

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

@@ -33,7 +33,7 @@ export default class CommandsContainer extends React.PureComponent {
users: PropTypes.object, users: PropTypes.object,
/** /**
* Installed splash commands to display * Installed slash commands to display
*/ */
commands: PropTypes.array, commands: PropTypes.array,

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

@@ -1,43 +1,59 @@
import PropTypes from 'prop-types';
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved. // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information. // See License.txt for license information.
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types';
import {Link} from 'react-router'; import {Link} from 'react-router';
import {FormattedMessage} from 'react-intl'; import {FormattedMessage} from 'react-intl';
import DeleteIntegration from './delete_integration.jsx'; import DeleteIntegration from './delete_integration.jsx';
export default class InstalledCommand extends React.Component { export default class InstalledCommand extends React.PureComponent {
static get propTypes() { static propTypes = {
return {
team: PropTypes.object.isRequired, /**
command: PropTypes.object.isRequired, * The team data
onRegenToken: PropTypes.func.isRequired, */
onDelete: PropTypes.func.isRequired, team: PropTypes.object.isRequired,
filter: PropTypes.string,
creator: PropTypes.object.isRequired, /**
canChange: PropTypes.bool.isRequired * Installed slash command to display
}; */
command: PropTypes.object.isRequired,
/**
* The function to call when Regenerate Token link is clicked
*/
onRegenToken: PropTypes.func.isRequired,
/**
* The function to call when Delete link is clicked
*/
onDelete: PropTypes.func.isRequired,
/**
* Set to filter command, comes from BackstageList
*/
filter: PropTypes.string,
/**
* The creator user data
*/
creator: PropTypes.object.isRequired,
/**
* Set to show edit link
*/
canChange: PropTypes.bool.isRequired
} }
constructor(props) { handleRegenToken = (e) => {
super(props);
this.handleRegenToken = this.handleRegenToken.bind(this);
this.handleDelete = this.handleDelete.bind(this);
this.matchesFilter = this.matchesFilter.bind(this);
}
handleRegenToken(e) {
e.preventDefault(); e.preventDefault();
this.props.onRegenToken(this.props.command); this.props.onRegenToken(this.props.command);
} }
handleDelete() { handleDelete = () => {
this.props.onDelete(this.props.command); this.props.onDelete(this.props.command);
} }
@@ -60,6 +76,7 @@ export default class InstalledCommand extends React.Component {
} }
let name; let name;
if (command.display_name) { if (command.display_name) {
name = command.display_name; name = command.display_name;
} else { } else {

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

@@ -30,7 +30,7 @@ export default class InstalledCommands extends React.PureComponent {
users: PropTypes.object, users: PropTypes.object,
/** /**
* Installed splash commands to display * Installed slash commands to display
*/ */
commands: PropTypes.array, commands: PropTypes.array,
@@ -40,7 +40,7 @@ export default class InstalledCommands extends React.PureComponent {
loading: PropTypes.bool, loading: PropTypes.bool,
/** /**
* Set to allow changes to installed splash commands * Set to allow changes to installed slash commands
*/ */
isAdmin: PropTypes.bool, isAdmin: PropTypes.bool,

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

@@ -91,36 +91,6 @@ class IntegrationStore extends EventEmitter {
return store.getState().entities.integrations.outgoingHooks[id]; return store.getState().entities.integrations.outgoingHooks[id];
} }
hasReceivedCommands(teamId) {
const commands = store.getState().entities.integrations.commands;
let hasTeam = false;
Object.values(commands).forEach((command) => {
if (command.team_id === teamId) {
hasTeam = true;
}
});
return hasTeam;
}
getCommands(teamId) {
const commands = store.getState().entities.integrations.commands;
const teamCommands = [];
Object.values(commands).forEach((command) => {
if (command.team_id === teamId) {
teamCommands.push(command);
}
});
return teamCommands;
}
getCommand(teamId, id) {
return store.getState().entities.integrations.commands[id];
}
hasReceivedOAuthApps() { hasReceivedOAuthApps() {
return Object.keys(store.getState().entities.integrations.oauthApps).length > 0; return Object.keys(store.getState().entities.integrations.oauthApps).length > 0;
} }

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

@@ -0,0 +1,103 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`components/integrations/InstalledCommand should filter out command 1`] = `null`;
exports[`components/integrations/InstalledCommand should match snapshot 1`] = `
<div
className="backstage-list__item"
>
<div
className="item-details"
>
<div
className="item-details__row"
>
<span
className="item-details__name"
>
test
</span>
<span
className="item-details__trigger"
>
- /trigger test
</span>
</div>
<div
className="item-details__row"
>
<span
className="item-details__description"
>
test
</span>
</div>
<div
className="item-details__row"
>
<span
className="item-details__token"
>
<FormattedMessage
defaultMessage="Token: {token}"
id="installed_integrations.token"
values={
Object {
"token": "testToken",
}
}
/>
</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": "1499722850203",
"creator": "test",
}
}
/>
</span>
</div>
</div>
<div
className="item-actions"
>
<a
href="#"
onClick={[Function]}
>
<FormattedMessage
defaultMessage="Regenerate Token"
id="installed_integrations.regenToken"
values={Object {}}
/>
</a>
-
<Link
onlyActiveOnIndex={false}
style={Object {}}
to="/test/integrations/commands/edit?id=r5tpgt4iepf45jt768jz84djic"
>
<FormattedMessage
defaultMessage="Edit"
id="installed_integrations.edit"
values={Object {}}
/>
</Link>
-
<DeleteIntegration
messageId="installed_commands.delete.confirm"
onDelete={[Function]}
/>
</div>
</div>
`;

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

@@ -0,0 +1,83 @@
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import React from 'react';
import {shallow} from 'enzyme';
import InstalledCommand from 'components/integrations/components/installed_command.jsx';
describe('components/integrations/InstalledCommand', () => {
const emptyFunction = jest.fn();
const command = {
id: 'r5tpgt4iepf45jt768jz84djic',
display_name: 'test',
description: 'test',
trigger: 'trigger',
auto_complete: 'test',
auto_complete_hint: 'test',
token: 'testToken',
create_at: '1499722850203'
};
test('should match snapshot', () => {
const wrapper = shallow(
<InstalledCommand
team={{
name: 'test'
}}
command={command}
onRegenToken={emptyFunction}
onDelete={emptyFunction}
filter={'trigger'}
creator={{
username: 'test'
}}
canChange={true}
/>
);
expect(wrapper).toMatchSnapshot();
});
test('should call onRegenToken function', () => {
const onRegenToken = jest.fn();
const wrapper = shallow(
<InstalledCommand
team={{
name: 'test'
}}
command={command}
onRegenToken={onRegenToken}
onDelete={emptyFunction}
filter={''}
creator={{
username: 'test'
}}
canChange={true}
/>
);
wrapper.find('div.item-actions a').first().simulate('click', {preventDefault() {
return jest.fn();
}});
expect(onRegenToken).toBeCalled();
});
test('should filter out command', () => {
const wrapper = shallow(
<InstalledCommand
team={{
name: 'test'
}}
command={command}
onRegenToken={emptyFunction}
onDelete={emptyFunction}
filter={'filter'}
creator={{
username: 'test'
}}
canChange={true}
/>
);
expect(wrapper).toMatchSnapshot();
});
});