index.tsx 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132
  1. import {ClassNames} from '@emotion/react';
  2. import styled from '@emotion/styled';
  3. import partition from 'lodash/partition';
  4. import DropdownAutoComplete from 'sentry/components/dropdownAutoComplete';
  5. import DropdownButton from 'sentry/components/dropdownButton';
  6. import {t} from 'sentry/locale';
  7. import {Event, ExceptionType, Thread} from 'sentry/types';
  8. import theme from 'sentry/utils/theme';
  9. import filterThreadInfo from './filterThreadInfo';
  10. import Header from './header';
  11. import Option from './option';
  12. import SelectedOption from './selectedOption';
  13. type Props = {
  14. activeThread: Thread;
  15. event: Event;
  16. threads: Array<Thread>;
  17. exception?: Required<ExceptionType>;
  18. fullWidth?: boolean;
  19. onChange?: (thread: Thread) => void;
  20. };
  21. const DROPDOWN_MAX_HEIGHT = 400;
  22. const ThreadSelector = ({
  23. threads,
  24. event,
  25. exception,
  26. activeThread,
  27. onChange,
  28. fullWidth = false,
  29. }: Props) => {
  30. const getDropDownItem = (thread: Thread) => {
  31. const {label, filename, crashedInfo} = filterThreadInfo(event, thread, exception);
  32. const threadInfo = {label, filename};
  33. return {
  34. value: `#${thread.id}: ${thread.name} ${label} ${filename}`,
  35. threadInfo,
  36. thread,
  37. label: (
  38. <Option
  39. id={thread.id}
  40. details={threadInfo}
  41. name={thread.name}
  42. crashed={thread.crashed}
  43. crashedInfo={crashedInfo}
  44. />
  45. ),
  46. };
  47. };
  48. const getItems = () => {
  49. const [crashed, notCrashed] = partition(threads, thread => !!thread?.crashed);
  50. return [...crashed, ...notCrashed].map(getDropDownItem);
  51. };
  52. const handleChange = (thread: Thread) => {
  53. if (onChange) {
  54. onChange(thread);
  55. }
  56. };
  57. return (
  58. <ClassNames>
  59. {({css}) => (
  60. <StyledDropdownAutoComplete
  61. data-test-id="thread-selector"
  62. items={getItems()}
  63. onSelect={item => {
  64. handleChange(item.thread);
  65. }}
  66. maxHeight={DROPDOWN_MAX_HEIGHT}
  67. searchPlaceholder={t('Filter Threads')}
  68. emptyMessage={t('You have no threads')}
  69. noResultsMessage={t('No threads found')}
  70. menuHeader={<Header />}
  71. rootClassName={
  72. fullWidth
  73. ? css`
  74. width: 100%;
  75. `
  76. : undefined
  77. }
  78. closeOnSelect
  79. emptyHidesInput
  80. >
  81. {({isOpen, selectedItem}) => (
  82. <StyledDropdownButton isOpen={isOpen} size="sm" align="left">
  83. {selectedItem ? (
  84. <SelectedOption
  85. id={selectedItem.thread.id}
  86. details={selectedItem.threadInfo}
  87. />
  88. ) : (
  89. <SelectedOption
  90. id={activeThread.id}
  91. details={filterThreadInfo(event, activeThread, exception)}
  92. />
  93. )}
  94. </StyledDropdownButton>
  95. )}
  96. </StyledDropdownAutoComplete>
  97. )}
  98. </ClassNames>
  99. );
  100. };
  101. export default ThreadSelector;
  102. const StyledDropdownAutoComplete = styled(DropdownAutoComplete)`
  103. width: 100%;
  104. min-width: 300px;
  105. @media (min-width: ${theme.breakpoints.small}) {
  106. width: 500px;
  107. }
  108. @media (max-width: ${p => p.theme.breakpoints.large}) {
  109. top: calc(100% - 2px);
  110. }
  111. `;
  112. const StyledDropdownButton = styled(DropdownButton)`
  113. > *:first-child {
  114. grid-template-columns: 1fr 15px;
  115. }
  116. width: 100%;
  117. min-width: 150px;
  118. @media (min-width: ${props => props.theme.breakpoints.xlarge}) {
  119. max-width: 420px;
  120. }
  121. `;