import {Component, Fragment} from 'react'; import styled from '@emotion/styled'; import {addErrorMessage} from 'sentry/actionCreators/indicator'; import Button from 'sentry/components/button'; import SelectControl from 'sentry/components/forms/controls/selectControl'; import Input from 'sentry/components/input'; import Tag from 'sentry/components/tag'; import TextOverflow from 'sentry/components/textOverflow'; import {IconAdd, IconChevron} from 'sentry/icons'; import {t} from 'sentry/locale'; import MemberListStore from 'sentry/stores/memberListStore'; import space from 'sentry/styles/space'; import {Organization, Project} from 'sentry/types'; import SelectOwners, { Owner, } from 'sentry/views/settings/project/projectOwnership/selectOwners'; const initialState = { text: '', tagName: '', type: 'path', owners: [], isValid: false, }; function getMatchPlaceholder(type: string): string { switch (type) { case 'path': return 'src/example/*'; case 'module': return 'com.module.name.example'; case 'url': return 'https://example.com/settings/*'; case 'tag': return 'tag-value'; default: return ''; } } type Props = { disabled: boolean; onAddRule: (rule: string) => void; organization: Organization; paths: string[]; project: Project; urls: string[]; }; type State = { isValid: boolean; owners: Owner[]; tagName: string; text: string; type: string; }; class RuleBuilder extends Component { state: State = initialState; checkIsValid = () => { this.setState(state => ({ isValid: !!state.text && state.owners && !!state.owners.length, })); }; handleTypeChange = (val: string | number | boolean) => { this.setState({type: val as string}); // TODO(ts): Add select value type as generic to select controls this.checkIsValid(); }; handleTagNameChangeValue = (e: React.ChangeEvent) => { this.setState({tagName: e.target.value}, this.checkIsValid); }; handleChangeValue = (e: React.ChangeEvent) => { this.setState({text: e.target.value}); this.checkIsValid(); }; handleChangeOwners = (owners: Owner[]) => { this.setState({owners}); this.checkIsValid(); }; handleAddRule = () => { const {type, text, tagName, owners, isValid} = this.state; if (!isValid) { addErrorMessage('A rule needs a type, a value, and one or more issue owners.'); return; } const ownerText = owners .map(owner => owner.actor.type === 'team' ? `#${owner.actor.name}` : MemberListStore.getById(owner.actor.id)?.email ) .join(' '); const quotedText = text.match(/\s/) ? `"${text}"` : text; const rule = `${ type === 'tag' ? `tags.${tagName}` : type }:${quotedText} ${ownerText}`; this.props.onAddRule(rule); this.setState(initialState); }; handleSelectCandidate = (text: string, type: string) => { this.setState({text, type}); this.checkIsValid(); }; render() { const {urls, paths, disabled, project, organization} = this.props; const {type, text, tagName, owners, isValid} = this.state; const hasCandidates = paths || urls; return ( {hasCandidates && ( {paths.map(v => ( this.handleSelectCandidate(v, 'path')} > {v} {t('Path')} ))} {urls.map(v => ( this.handleSelectCandidate(v, 'url')} > {v} {t('URL')} ))} )} {type === 'tag' && ( )}