Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@
"react-lazy-load": "3.0.13",
"react-mentions": "^1.2.0",
"react-router": "3.2.0",
"react-select": "^1.2.1",
"react-sparklines": "1.7.0",
"reflux": "0.4.1",
"scroll-to-element": "^2.0.0",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import React from 'react';
import PropTypes from 'prop-types';
import Select from 'react-select';
import styled from 'react-emotion';

import InputField from './inputField';

export default class MultiSelectField extends InputField {
static propTypes = {
options: PropTypes.array.isRequired,
onChange: PropTypes.func,
value: PropTypes.any,
};

constructor(props) {
super(props);
this.state = {
values: [],
};
}

handleChange = value => {
this.setState({values: value}, () => {
if (typeof this.props.onChange === 'function') {
this.props.onChange(value);
}
});
};

renderArrow = () => {
return <span className="icon-arrow-down" />;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe this should be the inlineSvg version tho?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should keep this one to match the other dropdowns, the svg version looks different

};

render() {
return (
<MultiSelect
id={this.getId()}
onChange={this.handleChange}
value={this.state.values}
multi={true}
arrowRenderer={this.renderArrow}
style={{width: 200, zIndex: 100, overflow: 'visible'}}
{...this.props}
/>
);
}
}

const MultiSelect = styled(Select)`
font-size: 15px;
.Select-control {
overflow: visible;
}
.Select-input {
height: 37px;
input {
padding: 10px 0;
}
}

.Select-placeholder,
.Select--single > .Select-control .Select-value {
height: 37px;
&:focus {
border: 1px solid ${p => p.theme.gray};
}
}

.Select-option.is-focused {
color: white;
background-color: ${p => p.theme.purple};
}
.Select-multi-value-wrapper {
> a {
margin-left: 4px;
}
}
`;
2 changes: 0 additions & 2 deletions src/sentry/static/sentry/app/utils.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -224,8 +224,6 @@ export function sortProjects(projects) {
export const buildUserId = id => `user:${id}`;
export const buildTeamId = id => `team:${id}`;

export const actorEquality = (a, b) => a.type === b.type && a.id === b.id;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe leave this in? we might want it later. nbd tho

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we'll need it


// re-export under utils
export {parseLinkHeader, deviceNameMapper, Collection, PendingChangeQueue, CursorPoller};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,17 @@ import PropTypes from 'prop-types';
import React from 'react';
import styled from 'react-emotion';
import {Flex} from 'grid-emotion';

import memberListStore from '../../../../stores/memberListStore';
import ProjectsStore from '../../../../stores/projectsStore';
import Button from '../../../../components/buttons/button';
import SelectInput from '../../../../components/selectInput';
import TextOverflow from '../../../../components/textOverflow';
import InlineSvg from '../../../../components/inlineSvg';
import Input from '../../../../views/settings/components/forms/controls/input';
import DropdownAutoComplete from '../../../../components/dropdownAutoComplete';
import DropdownButton from '../../../../components/dropdownButton';
import ActorAvatar from '../../../../components/actorAvatar';
import SentryTypes from '../../../../proptypes';
import {buildUserId, buildTeamId, actorEquality} from '../../../../utils';
import {buildUserId, buildTeamId} from '../../../../utils';
import {addErrorMessage} from '../../../../actionCreators/indicator';

import {t} from '../../../../locale';
import SelectOwners from './selectOwners';

const initialState = {
text: '',
Expand Down Expand Up @@ -79,21 +74,10 @@ class RuleBuilder extends React.Component {
this.setState({text: e.target.value});
};

onAddActor = ({actor}) => {
this.setState(({owners}) => {
if (!owners.find(i => actorEquality(i, actor))) {
return {owners: [...owners, actor]};
} else return {};
});
handleChangeOwners = owners => {
this.setState({owners});
};

handleRemoveActor(toRemove, e) {
this.setState(({owners}) => ({
owners: owners.filter(actor => !actorEquality(actor, toRemove)),
}));
e.stopPropagation();
}

handleAddRule = () => {
let {type, text, owners} = this.state;

Expand All @@ -104,10 +88,10 @@ class RuleBuilder extends React.Component {

let ownerText = owners
.map(
actor =>
actor.type == 'team'
? `#${actor.name}`
: memberListStore.getById(actor.id).email
owner =>
owner.actor.type === 'team'
? `#${owner.actor.name}`
: memberListStore.getById(owner.actor.id).email
)
.join(' ');

Expand Down Expand Up @@ -160,38 +144,12 @@ class RuleBuilder extends React.Component {
placeholder={type === 'path' ? 'src/example/*' : 'example.com/settings/*'}
/>
<Divider src="icon-chevron-right" />
<Flex flex="1" align="center">
<DropdownAutoComplete
items={[
{
value: 'team',
label: 'Teams',
items: this.mentionableTeams(),
},
{
value: 'user',
label: 'Users',
items: this.mentionableUsers(),
},
]}
onSelect={this.onAddActor}
>
{({isOpen, selectedItem}) => (
<BuilderDropdownButton isOpen={isOpen} size="zero">
<Owners>
{owners.map(owner => (
<span
key={`${owner.type}-${owner.id}`}
onClick={this.handleRemoveActor.bind(this, owner)}
>
<ActorAvatar actor={owner} />
</span>
))}
</Owners>
<div>{t('Add Owners')}</div>
</BuilderDropdownButton>
)}
</DropdownAutoComplete>
<Flex flex="1" align="center" mr={1}>
<SelectOwners
options={[...this.mentionableTeams(), ...this.mentionableUsers()]}
value={owners}
onChange={this.handleChangeOwners}
/>
</Flex>

<RuleAddButton
Expand Down Expand Up @@ -261,31 +219,6 @@ const Divider = styled(InlineSvg)`
margin-right: 5px;
`;

const Owners = styled.div`
justify-content: flex-end;
display: flex;
padding: 3px;
span {
margin-right: 2px;
}

.avatar {
width: 28px;
height: 28px;
}
`;

const BuilderDropdownButton = styled(DropdownButton)`
margin-right: 5px;
padding-right: 8px !important;
padding-left: 3px !important;

flex: 1;
white-space: nowrap;
height: 37px;
color: ${p => p.theme.gray3} !important;
`;

const RuleAddButton = styled(Button)`
width: 37px;
height: 37px;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import React from 'react';
import PropTypes from 'prop-types';

import MultiSelectField from '../../../../components/forms/multiSelectField';
import ActorAvatar from '../../../../components/actorAvatar';

import {t} from '../../../../locale';

class ValueComponent extends React.Component {
static propTypes = {
value: PropTypes.object,
onRemove: PropTypes.func,
};

handleClick = () => {
this.props.onRemove(this.props.value);
};

render() {
return (
<a onClick={this.handleClick}>
<ActorAvatar actor={this.props.value.actor} size={28} />
</a>
);
}
}

export default class SelectOwners extends React.Component {
static propTypes = {
options: PropTypes.array.isRequired,
value: PropTypes.array,
onChange: PropTypes.func,
};

render() {
return (
<MultiSelectField
options={this.props.options}
multi={true}
style={{width: 200}}
valueComponent={ValueComponent}
placeholder={t('Add Owners')}
onChange={this.props.onChange}
value={this.props.value}
/>
);
}
}
Loading