Browse Source

ref(interfaces): Consolidate crumbs and images loaded search + filter (#26960)

Priscila Oliveira 3 years ago
parent
commit
c01e903a39

+ 0 - 57
static/app/components/events/interfaces/breadcrumbs/convertCrumbType.tsx

@@ -1,57 +0,0 @@
-import {Breadcrumb, BreadcrumbType} from 'app/types/breadcrumbs';
-import {defined} from 'app/utils';
-
-function convertCrumbType(breadcrumb: Breadcrumb): Breadcrumb {
-  if (breadcrumb.type === BreadcrumbType.EXCEPTION) {
-    return {
-      ...breadcrumb,
-      type: BreadcrumbType.ERROR,
-    };
-  }
-  // special case for 'ui.' and `sentry.` category breadcrumbs
-  // TODO: find a better way to customize UI around non-schema data
-  if (breadcrumb.type === BreadcrumbType.DEFAULT && defined(breadcrumb?.category)) {
-    const [category, subcategory] = breadcrumb.category.split('.');
-    if (category === 'ui') {
-      return {
-        ...breadcrumb,
-        type: BreadcrumbType.UI,
-      };
-    }
-
-    if (category === 'console') {
-      return {
-        ...breadcrumb,
-        type: BreadcrumbType.DEBUG,
-      };
-    }
-
-    if (category === 'navigation') {
-      return {
-        ...breadcrumb,
-        type: BreadcrumbType.NAVIGATION,
-      };
-    }
-
-    if (
-      category === 'sentry' &&
-      (subcategory === 'transaction' || subcategory === 'event')
-    ) {
-      return {
-        ...breadcrumb,
-        type: BreadcrumbType.TRANSACTION,
-      };
-    }
-  }
-
-  if (!Object.values(BreadcrumbType).includes(breadcrumb.type)) {
-    return {
-      ...breadcrumb,
-      type: BreadcrumbType.DEFAULT,
-    };
-  }
-
-  return breadcrumb;
-}
-
-export default convertCrumbType;

+ 0 - 49
static/app/components/events/interfaces/breadcrumbs/filter/dropdownButton.tsx

@@ -1,49 +0,0 @@
-import styled from '@emotion/styled';
-
-import DropdownButton from 'app/components/dropdownButton';
-import {GetActorPropsFn} from 'app/components/dropdownMenu';
-import {t, tn} from 'app/locale';
-
-type Props = {
-  isOpen: boolean;
-  getActorProps: GetActorPropsFn;
-  checkedQuantity: number;
-};
-
-const DropDownButton = ({isOpen, getActorProps, checkedQuantity}: Props) => {
-  if (checkedQuantity > 0) {
-    return (
-      <StyledDropdownButton
-        {...getActorProps()}
-        isOpen={isOpen}
-        size="small"
-        hideBottomBorder={false}
-        priority="primary"
-      >
-        {tn('%s Active Filter', '%s Active Filters', checkedQuantity)}
-      </StyledDropdownButton>
-    );
-  }
-
-  return (
-    <StyledDropdownButton
-      {...getActorProps()}
-      isOpen={isOpen}
-      size="small"
-      hideBottomBorder={false}
-    >
-      {t('Filter By')}
-    </StyledDropdownButton>
-  );
-};
-
-export default DropDownButton;
-
-const StyledDropdownButton = styled(DropdownButton)`
-  border-right: 0;
-  z-index: ${p => p.theme.zIndex.dropdown};
-  max-width: 200px;
-  white-space: nowrap;
-  border-top-right-radius: 0;
-  border-bottom-right-radius: 0;
-`;

+ 0 - 148
static/app/components/events/interfaces/breadcrumbs/filter/index.tsx

@@ -1,148 +0,0 @@
-import * as React from 'react';
-import styled from '@emotion/styled';
-import isEqual from 'lodash/isEqual';
-
-import DropdownControl from 'app/components/dropdownControl';
-
-import DropDownButton from './dropdownButton';
-import OptionsGroup from './optionsGroup';
-import {Option, OptionLevel, OptionType} from './types';
-
-type OnClick = React.ComponentProps<typeof OptionsGroup>['onClick'];
-type Options = [Array<OptionType>, Array<OptionLevel>];
-
-type Props = {
-  options: Options;
-  onFilter: (options: Options) => void;
-};
-
-type State = {
-  hasTypeOption: boolean;
-  hasLevelOption: boolean;
-  checkedQuantity: number;
-};
-
-class Filter extends React.Component<Props, State> {
-  state: State = {
-    hasTypeOption: false,
-    hasLevelOption: false,
-    checkedQuantity: this.props.options.length,
-  };
-
-  componentDidMount() {
-    this.updateState();
-  }
-
-  componentDidUpdate(prevProps: Props) {
-    if (!isEqual(prevProps.options, this.props.options)) {
-      this.updateState();
-    }
-  }
-
-  updateState = () => {
-    const {options} = this.props;
-    this.setState({
-      hasTypeOption: options[0].length > 0,
-      hasLevelOption: options[1].length > 0,
-      checkedQuantity: this.getCheckedQuantity(),
-    });
-  };
-
-  getCheckedQuantity = () => {
-    const {options} = this.props;
-
-    let checkedQuantity = 0;
-
-    for (const index in options) {
-      for (const option in options[index]) {
-        if (options[index][option].isChecked) {
-          checkedQuantity += 1;
-        }
-      }
-    }
-
-    return checkedQuantity;
-  };
-
-  filterOptionsFirstStep = <T extends Option>(
-    options: Array<T>,
-    filterOption: T
-  ): Array<T> => {
-    return options.map(option => {
-      if (isEqual(option, filterOption)) {
-        return {
-          ...option,
-          isChecked: !option.isChecked,
-        };
-      }
-      return option;
-    });
-  };
-
-  handleClick = (...args: Parameters<OnClick>) => {
-    const [type, option] = args;
-    const {onFilter, options} = this.props;
-
-    if (type === 'type') {
-      const filteredTypes = this.filterOptionsFirstStep(options[0], option);
-      onFilter([filteredTypes, options[1]] as Options);
-      return;
-    }
-
-    const filteredLevels = this.filterOptionsFirstStep(options[1], option);
-    onFilter([options[0], filteredLevels] as Options);
-  };
-
-  render() {
-    const {options} = this.props;
-    const {hasTypeOption, hasLevelOption, checkedQuantity} = this.state;
-
-    if (!hasTypeOption && !hasLevelOption) {
-      return null;
-    }
-
-    return (
-      <Wrapper>
-        <DropdownControl
-          priority="form"
-          menuWidth="240px"
-          blendWithActor
-          button={({isOpen, getActorProps}) => (
-            <DropDownButton
-              isOpen={isOpen}
-              getActorProps={getActorProps}
-              checkedQuantity={checkedQuantity}
-            />
-          )}
-        >
-          <Content>
-            {hasTypeOption && (
-              <OptionsGroup type="type" onClick={this.handleClick} options={options[0]} />
-            )}
-
-            {hasLevelOption && (
-              <OptionsGroup
-                type="level"
-                onClick={this.handleClick}
-                options={options[1]}
-              />
-            )}
-          </Content>
-        </DropdownControl>
-      </Wrapper>
-    );
-  }
-}
-
-export default Filter;
-
-const Wrapper = styled('div')`
-  position: relative;
-  display: flex;
-`;
-
-const Content = styled('div')`
-  > * :last-child {
-    margin-bottom: -1px;
-  }
-`;

+ 0 - 90
static/app/components/events/interfaces/breadcrumbs/filter/optionsGroup.tsx

@@ -1,90 +0,0 @@
-import * as React from 'react';
-import styled from '@emotion/styled';
-
-import CheckboxFancy from 'app/components/checkboxFancy/checkboxFancy';
-import {t} from 'app/locale';
-import space from 'app/styles/space';
-
-import {Option} from './types';
-
-type Type = 'type' | 'level';
-
-type Props = {
-  options: Array<Option>;
-  type: Type;
-  onClick: (type: Type, option: Option) => void;
-};
-
-const OptionsGroup = ({type, options, onClick}: Props) => {
-  const handleClick = (option: Option) => (event: React.MouseEvent<HTMLLIElement>) => {
-    event.stopPropagation();
-    onClick(type, option);
-  };
-
-  return (
-    <div>
-      <Header>{type === 'type' ? t('Type') : t('Level')}</Header>
-      <List>
-        {options.map(option => (
-          <ListItem
-            key={option.type}
-            isChecked={option.isChecked}
-            onClick={handleClick(option)}
-          >
-            {option.symbol}
-            <ListItemDescription>{option.description}</ListItemDescription>
-            <CheckboxFancy isChecked={option.isChecked} />
-          </ListItem>
-        ))}
-      </List>
-    </div>
-  );
-};
-
-export default OptionsGroup;
-
-const Header = styled('div')`
-  display: flex;
-  align-items: center;
-  margin: 0;
-  background-color: ${p => p.theme.backgroundSecondary};
-  color: ${p => p.theme.gray300};
-  font-weight: normal;
-  font-size: ${p => p.theme.fontSizeMedium};
-  padding: ${space(1)} ${space(2)};
-  border-bottom: 1px solid ${p => p.theme.border};
-`;
-
-const List = styled('ul')`
-  list-style: none;
-  margin: 0;
-  padding: 0;
-`;
-
-const ListItem = styled('li')<{isChecked?: boolean}>`
-  display: grid;
-  grid-template-columns: max-content 1fr max-content;
-  grid-column-gap: ${space(1)};
-  align-items: center;
-  padding: ${space(1)} ${space(2)};
-  border-bottom: 1px solid ${p => p.theme.border};
-  :hover {
-    background-color: ${p => p.theme.backgroundSecondary};
-  }
-  ${CheckboxFancy} {
-    opacity: ${p => (p.isChecked ? 1 : 0.3)};
-  }
-
-  &:hover ${CheckboxFancy} {
-    opacity: 1;
-  }
-
-  &:hover span {
-    color: ${p => p.theme.blue300};
-    text-decoration: underline;
-  }
-`;
-
-const ListItemDescription = styled('div')`
-  font-size: ${p => p.theme.fontSizeMedium};
-`;

+ 0 - 18
static/app/components/events/interfaces/breadcrumbs/filter/types.tsx

@@ -1,18 +0,0 @@
-import {BreadcrumbLevelType, BreadcrumbType} from 'app/types/breadcrumbs';
-
-type OptionBase = {
-  symbol: React.ReactElement;
-  isChecked: boolean;
-  description?: string;
-};
-
-export type OptionType = {
-  type: BreadcrumbType;
-  levels: Array<BreadcrumbLevelType>;
-} & OptionBase;
-
-export type OptionLevel = {
-  type: BreadcrumbLevelType;
-} & OptionBase;
-
-export type Option = OptionType | OptionLevel;

+ 94 - 134
static/app/components/events/interfaces/breadcrumbs/index.tsx

@@ -7,7 +7,6 @@ import GuideAnchor from 'app/components/assistant/guideAnchor';
 import Button from 'app/components/button';
 import ErrorBoundary from 'app/components/errorBoundary';
 import EventDataSection from 'app/components/events/eventDataSection';
-import SearchBar from 'app/components/searchBar';
 import {IconWarning} from 'app/icons/iconWarning';
 import {t} from 'app/locale';
 import space from 'app/styles/space';
@@ -22,15 +21,23 @@ import {EntryType, Event} from 'app/types/event';
 import {defined} from 'app/utils';
 import EmptyMessage from 'app/views/settings/components/emptyMessage';
 
-import Filter from './filter';
+import SearchBarAction from '../searchBarAction';
+import SearchBarActionFilter from '../searchBarAction/searchBarActionFilter';
+
 import Icon from './icon';
 import Level from './level';
 import List from './list';
 import {aroundContentStyle} from './styles';
-import transformCrumbs from './transformCrumbs';
-
-type FilterProps = React.ComponentProps<typeof Filter>;
-type FilterOptions = FilterProps['options'];
+import {transformCrumbs} from './utils';
+
+type FilterOptions = React.ComponentProps<typeof SearchBarActionFilter>['options'];
+type FilterTypes = {
+  id: BreadcrumbType;
+  symbol: React.ReactElement;
+  isChecked: boolean;
+  description: string;
+  levels: BreadcrumbLevelType[];
+};
 
 type Props = {
   event: Event;
@@ -57,7 +64,7 @@ class Breadcrumbs extends React.Component<Props, State> {
     breadcrumbs: [],
     filteredByFilter: [],
     filteredBySearch: [],
-    filterOptions: [[], []],
+    filterOptions: {},
     displayRelativeTime: false,
   };
 
@@ -79,34 +86,45 @@ class Breadcrumbs extends React.Component<Props, State> {
     const filterOptions = this.getFilterOptions(transformedCrumbs);
 
     this.setState({
+      relativeTime: transformedCrumbs[transformedCrumbs.length - 1]?.timestamp,
       breadcrumbs: transformedCrumbs,
       filteredByFilter: transformedCrumbs,
       filteredBySearch: transformedCrumbs,
       filterOptions,
-      relativeTime: transformedCrumbs[transformedCrumbs.length - 1]?.timestamp,
     });
   }
 
-  getFilterOptions(breadcrumbs: ReturnType<typeof transformCrumbs>): FilterOptions {
+  getFilterOptions(breadcrumbs: ReturnType<typeof transformCrumbs>) {
     const types = this.getFilterTypes(breadcrumbs);
     const levels = this.getFilterLevels(types);
-    return [types, levels];
+
+    const options = {};
+
+    if (!!types.length) {
+      options[t('Types')] = types.map(type => omit(type, 'levels'));
+    }
+
+    if (!!levels.length) {
+      options[t('Levels')] = levels;
+    }
+
+    return options;
   }
 
   getFilterTypes(breadcrumbs: ReturnType<typeof transformCrumbs>) {
-    const filterTypes: FilterOptions[0] = [];
+    const filterTypes: FilterTypes[] = [];
 
     for (const index in breadcrumbs) {
       const breadcrumb = breadcrumbs[index];
-      const foundFilterType = filterTypes.findIndex(f => f.type === breadcrumb.type);
+      const foundFilterType = filterTypes.findIndex(f => f.id === breadcrumb.type);
 
       if (foundFilterType === -1) {
         filterTypes.push({
-          type: breadcrumb.type,
-          description: breadcrumb.description,
+          id: breadcrumb.type,
           symbol: <Icon {...omit(breadcrumb, 'description')} size="xs" />,
-          levels: breadcrumb?.level ? [breadcrumb.level] : [],
           isChecked: false,
+          description: breadcrumb.description,
+          levels: breadcrumb?.level ? [breadcrumb.level] : [],
         });
         continue;
       }
@@ -122,19 +140,19 @@ class Breadcrumbs extends React.Component<Props, State> {
     return filterTypes;
   }
 
-  getFilterLevels(types: FilterOptions[0]) {
-    const filterLevels: FilterOptions[1] = [];
+  getFilterLevels(types: FilterTypes[]) {
+    const filterLevels: FilterOptions[0] = [];
 
     for (const indexType in types) {
       for (const indexLevel in types[indexType].levels) {
         const level = types[indexType].levels[indexLevel];
 
-        if (filterLevels.some(f => f.type === level)) {
+        if (filterLevels.some(f => f.id === level)) {
           continue;
         }
 
         filterLevels.push({
-          type: level,
+          id: level,
           symbol: <Level level={level} />,
           isChecked: false,
         });
@@ -222,64 +240,42 @@ class Breadcrumbs extends React.Component<Props, State> {
     );
   }
 
-  filterCrumbsBy(
-    type: keyof Pick<BreadcrumbsWithDetails[0], 'level' | 'type'>,
-    breadcrumbs: BreadcrumbsWithDetails,
-    filterOptions: Array<FilterOptions[0][0] | FilterOptions[1][0]>
-  ) {
-    return breadcrumbs.filter(b => {
-      const crumbProperty = b[type];
-      if (!crumbProperty) {
-        return true;
-      }
-      const foundInFilterOptions = filterOptions.find(f => f.type === crumbProperty);
-
-      if (foundInFilterOptions) {
-        return foundInFilterOptions.isChecked;
-      }
+  getFilteredCrumbsByFilter(filterOptions: FilterOptions) {
+    const checkedTypeOptions = new Set(
+      Object.values(filterOptions)[0]
+        .filter(filterOption => filterOption.isChecked)
+        .map(option => option.id)
+    );
 
-      return false;
-    });
-  }
+    const checkedLevelOptions = new Set(
+      Object.values(filterOptions)[1]
+        .filter(filterOption => filterOption.isChecked)
+        .map(option => option.id)
+    );
 
-  getFilteredCrumbs(
-    hasCheckedType: boolean,
-    hasCheckedLevel: boolean,
-    filterOptions: FilterOptions
-  ) {
     const {breadcrumbs} = this.state;
 
-    if (!hasCheckedType && !hasCheckedLevel) {
-      return breadcrumbs;
+    if (!![...checkedTypeOptions].length && !![...checkedLevelOptions].length) {
+      return breadcrumbs.filter(
+        filteredCrumb =>
+          checkedTypeOptions.has(filteredCrumb.type) &&
+          checkedLevelOptions.has(filteredCrumb.level)
+      );
     }
 
-    if (hasCheckedType) {
-      const filteredCrumbsByType = this.filterCrumbsBy(
-        'type',
-        breadcrumbs,
-        filterOptions[0]
+    if (!![...checkedTypeOptions].length) {
+      return breadcrumbs.filter(filteredCrumb =>
+        checkedTypeOptions.has(filteredCrumb.type)
       );
-
-      if (hasCheckedLevel) {
-        const filteredCrumbsByLevel = this.filterCrumbsBy(
-          'level',
-          filteredCrumbsByType,
-          filterOptions[1]
-        );
-
-        return filteredCrumbsByLevel;
-      }
-
-      return filteredCrumbsByType;
     }
 
-    const filteredCrumbsByLevel = this.filterCrumbsBy(
-      'level',
-      breadcrumbs,
-      filterOptions[1]
-    );
+    if (!![...checkedLevelOptions].length) {
+      return breadcrumbs.filter(filteredCrumb =>
+        checkedLevelOptions.has(filteredCrumb.level)
+      );
+    }
 
-    return filteredCrumbsByLevel;
+    return breadcrumbs;
   }
 
   handleSearch = (value: string) => {
@@ -290,19 +286,12 @@ class Breadcrumbs extends React.Component<Props, State> {
   };
 
   handleFilter = (filterOptions: FilterOptions) => {
-    const hasCheckedType = filterOptions[0].some(filterOption => filterOption.isChecked);
-    const hasCheckedLevel = filterOptions[1].some(filterOption => filterOption.isChecked);
-
-    const filteredCrumbs = this.getFilteredCrumbs(
-      hasCheckedType,
-      hasCheckedLevel,
-      filterOptions
-    );
+    const filteredByFilter = this.getFilteredCrumbsByFilter(filterOptions);
 
     this.setState(prevState => ({
       filterOptions,
-      filteredByFilter: filteredCrumbs,
-      filteredBySearch: this.filterBySearch(prevState.searchTerm, filteredCrumbs),
+      filteredByFilter,
+      filteredBySearch: this.filterBySearch(prevState.searchTerm, filteredByFilter),
     }));
   };
 
@@ -313,23 +302,20 @@ class Breadcrumbs extends React.Component<Props, State> {
   };
 
   handleCleanSearch = () => {
-    this.setState({
-      searchTerm: '',
-    });
+    this.setState({searchTerm: ''});
   };
 
   handleResetFilter = () => {
-    this.setState(prevState => ({
-      filteredByFilter: prevState.breadcrumbs,
-      filterOptions: prevState.filterOptions.map(filterOption =>
-        (filterOption as Array<FilterOptions[0][0] | FilterOptions[1][0]>).map(
-          option => ({
-            ...option,
-            isChecked: false,
-          })
-        )
-      ) as FilterOptions,
-      filteredBySearch: this.filterBySearch(prevState.searchTerm, prevState.breadcrumbs),
+    this.setState(({breadcrumbs, filterOptions, searchTerm}) => ({
+      filteredByFilter: breadcrumbs,
+      filterOptions: Object.keys(filterOptions).reduce((accumulator, currentValue) => {
+        accumulator[currentValue] = filterOptions[currentValue].map(filterOption => ({
+          ...filterOption,
+          isChecked: false,
+        }));
+        return accumulator;
+      }, {}),
+      filteredBySearch: this.filterBySearch(searchTerm, breadcrumbs),
     }));
   };
 
@@ -340,12 +326,12 @@ class Breadcrumbs extends React.Component<Props, State> {
     }));
   };
 
-  renderEmptyMessage() {
+  getEmptyMessage() {
     const {searchTerm, filteredBySearch, filterOptions} = this.state;
 
     if (searchTerm && !filteredBySearch.length) {
-      const hasActiveFilter = filterOptions
-        .flatMap(filterOption => [...filterOption])
+      const hasActiveFilter = Object.values(filterOptions)
+        .flatMap(filterOption => filterOption)
         .find(filterOption => filterOption.isChecked);
 
       return (
@@ -394,14 +380,17 @@ class Breadcrumbs extends React.Component<Props, State> {
           </GuideAnchor>
         }
         actions={
-          <Search>
-            <Filter onFilter={this.handleFilter} options={filterOptions} />
-            <StyledSearchBar
-              placeholder={t('Search breadcrumbs')}
-              onSearch={this.handleSearch}
-              query={searchTerm}
-            />
-          </Search>
+          <StyledSearchBarAction
+            placeholder={t('Search breadcrumbs')}
+            onChange={this.handleSearch}
+            query={searchTerm}
+            filter={
+              <SearchBarActionFilter
+                onChange={this.handleFilter}
+                options={filterOptions}
+              />
+            }
+          />
         }
         wrapTitle={false}
         isCentered
@@ -419,7 +408,7 @@ class Breadcrumbs extends React.Component<Props, State> {
             />
           </ErrorBoundary>
         ) : (
-          this.renderEmptyMessage()
+          this.getEmptyMessage()
         )}
       </StyledEventDataSection>
     );
@@ -436,35 +425,6 @@ const StyledEmptyMessage = styled(EmptyMessage)`
   ${aroundContentStyle};
 `;
 
-const Search = styled('div')`
-  display: flex;
-  width: 100%;
-  margin-top: ${space(1)};
-
-  @media (min-width: ${props => props.theme.breakpoints[1]}) {
-    width: 400px;
-    margin-top: 0;
-  }
-
-  @media (min-width: ${props => props.theme.breakpoints[3]}) {
-    width: 600px;
-  }
-`;
-
-const StyledSearchBar = styled(SearchBar)`
-  width: 100%;
-  .search-input {
-    height: 32px;
-  }
-  .search-input,
-  .search-input:focus {
-    border-top-left-radius: 0;
-    border-bottom-left-radius: 0;
-  }
-  .search-clear-form,
-  .search-input-icon {
-    height: 32px;
-    display: flex;
-    align-items: center;
-  }
+const StyledSearchBarAction = styled(SearchBarAction)`
+  z-index: 2;
 `;

+ 0 - 18
static/app/components/events/interfaces/breadcrumbs/transformCrumbs.tsx

@@ -1,18 +0,0 @@
-import {Breadcrumb, BreadcrumbLevelType} from 'app/types/breadcrumbs';
-
-import convertCrumbType from './convertCrumbType';
-import getCrumbDetails from './getCrumbDetails';
-
-const transformCrumbs = (breadcrumbs: Array<Breadcrumb>) =>
-  breadcrumbs.map((breadcrumb, index) => {
-    const convertedCrumbType = convertCrumbType(breadcrumb);
-    const crumbDetails = getCrumbDetails(convertedCrumbType.type);
-    return {
-      id: index,
-      ...convertedCrumbType,
-      ...crumbDetails,
-      level: convertedCrumbType?.level ?? BreadcrumbLevelType.UNDEFINED,
-    };
-  });
-
-export default transformCrumbs;

+ 67 - 2
static/app/components/events/interfaces/breadcrumbs/getCrumbDetails.tsx → static/app/components/events/interfaces/breadcrumbs/utils.tsx

@@ -13,7 +13,61 @@ import {
   IconWarning,
 } from 'app/icons';
 import {t} from 'app/locale';
-import {BreadcrumbType} from 'app/types/breadcrumbs';
+import {Breadcrumb, BreadcrumbLevelType, BreadcrumbType} from 'app/types/breadcrumbs';
+import {defined} from 'app/utils';
+
+function convertCrumbType(breadcrumb: Breadcrumb): Breadcrumb {
+  if (breadcrumb.type === BreadcrumbType.EXCEPTION) {
+    return {
+      ...breadcrumb,
+      type: BreadcrumbType.ERROR,
+    };
+  }
+  // special case for 'ui.' and `sentry.` category breadcrumbs
+  // TODO: find a better way to customize UI around non-schema data
+  if (breadcrumb.type === BreadcrumbType.DEFAULT && defined(breadcrumb?.category)) {
+    const [category, subcategory] = breadcrumb.category.split('.');
+    if (category === 'ui') {
+      return {
+        ...breadcrumb,
+        type: BreadcrumbType.UI,
+      };
+    }
+
+    if (category === 'console') {
+      return {
+        ...breadcrumb,
+        type: BreadcrumbType.DEBUG,
+      };
+    }
+
+    if (category === 'navigation') {
+      return {
+        ...breadcrumb,
+        type: BreadcrumbType.NAVIGATION,
+      };
+    }
+
+    if (
+      category === 'sentry' &&
+      (subcategory === 'transaction' || subcategory === 'event')
+    ) {
+      return {
+        ...breadcrumb,
+        type: BreadcrumbType.TRANSACTION,
+      };
+    }
+  }
+
+  if (!Object.values(BreadcrumbType).includes(breadcrumb.type)) {
+    return {
+      ...breadcrumb,
+      type: BreadcrumbType.DEFAULT,
+    };
+  }
+
+  return breadcrumb;
+}
 
 function getCrumbDetails(type: BreadcrumbType) {
   switch (type) {
@@ -98,4 +152,15 @@ function getCrumbDetails(type: BreadcrumbType) {
   }
 }
 
-export default getCrumbDetails;
+export function transformCrumbs(breadcrumbs: Array<Breadcrumb>) {
+  return breadcrumbs.map((breadcrumb, index) => {
+    const convertedCrumbType = convertCrumbType(breadcrumb);
+    const crumbDetails = getCrumbDetails(convertedCrumbType.type);
+    return {
+      id: index,
+      ...convertedCrumbType,
+      ...crumbDetails,
+      level: convertedCrumbType?.level ?? BreadcrumbLevelType.UNDEFINED,
+    };
+  });
+}

+ 14 - 56
static/app/components/events/interfaces/debugMeta-v2/debugImageDetails/candidates.tsx

@@ -8,7 +8,6 @@ import Button from 'app/components/button';
 import ExternalLink from 'app/components/links/externalLink';
 import PanelTable from 'app/components/panels/panelTable';
 import QuestionTooltip from 'app/components/questionTooltip';
-import SearchBar from 'app/components/searchBar';
 import {t, tct} from 'app/locale';
 import space from 'app/styles/space';
 import {Organization, Project} from 'app/types';
@@ -16,7 +15,8 @@ import {BuiltinSymbolSource} from 'app/types/debugFiles';
 import {CandidateDownloadStatus, Image, ImageStatus} from 'app/types/debugImage';
 import {defined} from 'app/utils';
 
-import Filter from '../filter';
+import SearchBarAction from '../../searchBarAction';
+import SearchBarActionFilter from '../../searchBarAction/searchBarActionFilter';
 
 import Status from './candidate/status';
 import Candidate from './candidate';
@@ -27,7 +27,7 @@ const filterOptionCategories = {
   source: t('Source'),
 };
 
-type FilterOptions = React.ComponentProps<typeof Filter>['options'];
+type FilterOptions = React.ComponentProps<typeof SearchBarActionFilter>['options'];
 
 type ImageCandidates = Image['candidates'];
 
@@ -331,14 +331,17 @@ class Candidates extends React.Component<Props, State> {
             />
           </Title>
           {!!candidates.length && (
-            <Search>
-              <StyledFilter options={filterOptions} onFilter={this.handleChangeFilter} />
-              <StyledSearchBar
-                query={searchTerm}
-                onChange={value => this.handleChangeSearchTerm(value)}
-                placeholder={t('Search debug file candidates')}
-              />
-            </Search>
+            <SearchBarAction
+              query={searchTerm}
+              onChange={value => this.handleChangeSearchTerm(value)}
+              placeholder={t('Search debug file candidates')}
+              filter={
+                <SearchBarActionFilter
+                  options={filterOptions}
+                  onChange={this.handleChangeFilter}
+                />
+              }
+            />
           )}
         </Header>
         <StyledPanelTable
@@ -397,51 +400,6 @@ const Title = styled('div')`
   margin-bottom: ${space(2)};
 `;
 
-const Search = styled('div')`
-  flex-grow: 1;
-  display: flex;
-  flex-direction: column;
-  justify-content: flex-start;
-  .drop-down-filter-menu {
-    border-top-right-radius: ${p => p.theme.borderRadius};
-  }
-
-  @media (min-width: ${props => props.theme.breakpoints[0]}) {
-    flex-direction: row;
-    justify-content: flex-end;
-  }
-`;
-
-const StyledFilter = styled(Filter)`
-  margin-bottom: ${space(2)};
-`;
-
-// TODO(matej): remove this once we refactor SearchBar to not use css classes
-// - it could accept size as a prop
-const StyledSearchBar = styled(SearchBar)`
-  width: 100%;
-  margin-bottom: ${space(2)};
-  position: relative;
-  .search-input {
-    height: 32px;
-  }
-  .search-clear-form,
-  .search-input-icon {
-    height: 32px;
-    display: flex;
-    align-items: center;
-  }
-
-  @media (min-width: ${props => props.theme.breakpoints[0]}) {
-    max-width: 600px;
-    .search-input,
-    .search-input:focus {
-      border-top-left-radius: 0;
-      border-bottom-left-radius: 0;
-    }
-  }
-`;
-
 const StyledPanelTable = styled(PanelTable)`
   grid-template-columns: ${p =>
     p.headers.length === 3 ? 'max-content 1fr max-content' : 'max-content 1fr'};

+ 20 - 65
static/app/components/events/interfaces/debugMeta-v2/index.tsx

@@ -17,7 +17,6 @@ import EventDataSection from 'app/components/events/eventDataSection';
 import {getImageRange, parseAddress} from 'app/components/events/interfaces/utils';
 import {PanelTable} from 'app/components/panels';
 import QuestionTooltip from 'app/components/questionTooltip';
-import SearchBar from 'app/components/searchBar';
 import {t} from 'app/locale';
 import DebugMetaStore, {DebugMetaActions} from 'app/stores/debugMetaStore';
 import overflowEllipsis from 'app/styles/overflowEllipsis';
@@ -27,9 +26,11 @@ import {Image, ImageStatus} from 'app/types/debugImage';
 import {Event} from 'app/types/event';
 import {defined} from 'app/utils';
 
+import SearchBarAction from '../searchBarAction';
+import SearchBarActionFilter from '../searchBarAction/searchBarActionFilter';
+
 import Status from './debugImage/status';
 import DebugImage from './debugImage';
-import Filter from './filter';
 import layout from './layout';
 import {
   combineStatus,
@@ -47,7 +48,7 @@ type DefaultProps = {
   };
 };
 
-type FilterOptions = React.ComponentProps<typeof Filter>['options'];
+type FilterOptions = React.ComponentProps<typeof SearchBarActionFilter>['options'];
 type Images = Array<React.ComponentProps<typeof DebugImage>['image']>;
 
 type Props = DefaultProps &
@@ -341,6 +342,7 @@ class DebugMeta extends React.PureComponent<Props, State> {
     if (![...checkedOptions].length) {
       return filteredImages;
     }
+
     return filteredImages.filter(image => checkedOptions.has(image.status));
   }
 
@@ -535,17 +537,19 @@ class DebugMeta extends React.PureComponent<Props, State> {
           </TitleWrapper>
         }
         actions={
-          <Search>
-            {displayFilter && (
-              <Filter options={filterOptions} onFilter={this.handleChangeFilter} />
-            )}
-            <StyledSearchBar
-              query={searchTerm}
-              onChange={value => this.handleChangeSearchTerm(value)}
-              placeholder={t('Search images')}
-              blendWithFilter={displayFilter}
-            />
-          </Search>
+          <StyledSearchBarAction
+            placeholder={t('Search images loaded')}
+            onChange={value => this.handleChangeSearchTerm(value)}
+            query={searchTerm}
+            filter={
+              displayFilter ? (
+                <SearchBarActionFilter
+                  onChange={this.handleChangeFilter}
+                  options={filterOptions}
+                />
+              ) : undefined
+            }
+          />
         }
         wrapTitle={false}
         isCentered
@@ -622,55 +626,6 @@ const StyledList = styled(List as any)<React.ComponentProps<typeof List>>`
   outline: none;
 `;
 
-const Search = styled('div')`
-  display: grid;
-  grid-gap: ${space(2)};
-  width: 100%;
-  margin-top: ${space(1)};
-
-  @media (min-width: ${props => props.theme.breakpoints[0]}) {
-    margin-top: 0;
-    grid-gap: 0;
-    grid-template-columns: ${p =>
-      p.children && React.Children.toArray(p.children).length === 1
-        ? '1fr'
-        : 'max-content 1fr'};
-    justify-content: flex-end;
-  }
-
-  @media (min-width: ${props => props.theme.breakpoints[1]}) {
-    width: 400px;
-  }
-
-  @media (min-width: ${props => props.theme.breakpoints[3]}) {
-    width: 600px;
-  }
-`;
-
-// TODO(matej): remove this once we refactor SearchBar to not use css classes
-// - it could accept size as a prop
-const StyledSearchBar = styled(SearchBar)<{blendWithFilter?: boolean}>`
-  width: 100%;
-  position: relative;
-  .search-input {
-    height: 32px;
-  }
-  .search-clear-form,
-  .search-input-icon {
-    height: 32px;
-    display: flex;
-    align-items: center;
-  }
-
-  @media (min-width: ${props => props.theme.breakpoints[0]}) {
-    ${p =>
-      p.blendWithFilter &&
-      `
-        .search-input,
-        .search-input:focus {
-          border-top-left-radius: 0;
-          border-bottom-left-radius: 0;
-        }
-      `}
-  }
+const StyledSearchBarAction = styled(SearchBarAction)`
+  z-index: 1;
 `;

Some files were not shown because too many files changed in this diff