import {Component, Fragment} from 'react'; import styled from '@emotion/styled'; import debounce from 'lodash/debounce'; import {PlatformIcon} from 'platformicons'; import {Button} from 'sentry/components/button'; import EmptyMessage from 'sentry/components/emptyMessage'; import ListLink from 'sentry/components/links/listLink'; import NavTabs from 'sentry/components/navTabs'; import SearchBar from 'sentry/components/searchBar'; import {DEFAULT_DEBOUNCE_DURATION} from 'sentry/constants'; import categoryList, { createablePlatforms, filterAliases, } from 'sentry/data/platformPickerCategories'; import platforms, {otherPlatform} from 'sentry/data/platforms'; import {IconClose, IconProject} from 'sentry/icons'; import {t, tct} from 'sentry/locale'; import {space} from 'sentry/styles/space'; import {Organization, PlatformIntegration, PlatformKey} from 'sentry/types'; import {trackAnalytics} from 'sentry/utils/analytics'; const PlatformList = styled('div')` display: grid; gap: ${space(1)}; grid-template-columns: repeat(auto-fill, 112px); margin-bottom: ${space(2)}; `; const selectablePlatforms = platforms.filter(platform => createablePlatforms.has(platform.id) ); export type Category = (typeof categoryList)[number]['id']; export type Platform = PlatformIntegration & { category: Category; }; interface PlatformPickerProps { setPlatform: (props: Platform | null) => void; defaultCategory?: Category; listClassName?: string; listProps?: React.HTMLAttributes; noAutoFilter?: boolean; organization?: Organization; platform?: string | null; showOther?: boolean; source?: string; } type State = { category: Category; filter: string; }; class PlatformPicker extends Component { static defaultProps = { showOther: true, }; state: State = { category: this.props.defaultCategory ?? categoryList[0].id, filter: this.props.noAutoFilter ? '' : (this.props.platform || '').split('-')[0], }; get platformList() { const {category} = this.state; const currentCategory = categoryList.find(({id}) => id === category); const filter = this.state.filter.toLowerCase(); const subsetMatch = (platform: PlatformIntegration) => platform.id.includes(filter) || platform.name.toLowerCase().includes(filter) || filterAliases[platform.id as PlatformKey]?.some(alias => alias.includes(filter)); const categoryMatch = (platform: PlatformIntegration) => { return currentCategory?.platforms?.has(platform.id); }; const filtered = selectablePlatforms .filter(this.state.filter ? subsetMatch : categoryMatch) .sort((a, b) => a.id.localeCompare(b.id)); return this.props.showOther ? filtered : filtered.filter(({id}) => id !== 'other'); } logSearch = debounce(() => { if (this.state.filter) { trackAnalytics('growth.platformpicker_search', { search: this.state.filter.toLowerCase(), num_results: this.platformList.length, source: this.props.source, organization: this.props.organization ?? null, }); } }, DEFAULT_DEBOUNCE_DURATION); render() { const platformList = this.platformList; const {setPlatform, listProps, listClassName} = this.props; const {filter, category} = this.state; return ( {categoryList.map(({id, name}) => ( { trackAnalytics('growth.platformpicker_category', { category: id, source: this.props.source, organization: this.props.organization ?? null, }); this.setState({category: id, filter: ''}); e.preventDefault(); }} to="" isActive={() => id === (filter ? 'all' : category)} > {name} ))} this.setState({filter: val}, this.logSearch)} /> {platformList.map(platform => { return ( { setPlatform(null); e.stopPropagation(); }} onClick={() => { trackAnalytics('growth.select_platform', { platform_id: platform.id, source: this.props.source, organization: this.props.organization ?? null, }); setPlatform({...platform, category}); }} /> ); })} {platformList.length === 0 && ( } title={t("We don't have an SDK for that yet!")} > {tct( `Sure you haven't misspelled? If you're using a lesser-known platform, consider choosing a more generic SDK like Browser JavaScript, Python, Node, .NET & Java or create a generic project, by selecting [linkOther:“Other”].`, { linkOther: (