Browse Source

ref(onboarding): Make single platform selection default (#47827)

Priscila Oliveira 1 year ago
parent
commit
b823a897e3

+ 15 - 8
static/app/actionCreators/projects.tsx

@@ -306,14 +306,21 @@ export function sendSampleEvent(api: Client, orgSlug: string, projectSlug: strin
  * @param platform The platform key of the project
  * @param options Additional options such as creating default alert rules
  */
-export function createProject(
-  api: Client,
-  orgSlug: string,
-  team: string,
-  name: string,
-  platform: string,
-  options: {defaultRules?: boolean} = {}
-) {
+export function createProject({
+  api,
+  name,
+  options = {},
+  orgSlug,
+  platform,
+  team,
+}: {
+  api: Client;
+  name: string;
+  options: {defaultRules?: boolean};
+  orgSlug: string;
+  platform: string;
+  team: string;
+}) {
   return api.requestPromise(`/teams/${orgSlug}/${team}/projects/`, {
     method: 'POST',
     data: {name, platform, default_rules: options.defaultRules},

+ 0 - 85
static/app/components/multiPlatformPicker.spec.jsx

@@ -1,85 +0,0 @@
-import {render, screen, userEvent} from 'sentry-test/reactTestingLibrary';
-
-import MultiPlatformPicker from 'sentry/components/multiPlatformPicker';
-
-describe('MultiPlatformPicker', function () {
-  const baseProps = {
-    platforms: [],
-  };
-  it('should only render Mobile platforms under Mobile tab', async function () {
-    const props = {...baseProps};
-    render(<MultiPlatformPicker {...props} />);
-    await userEvent.click(screen.getByText('Mobile'));
-    expect(screen.getByText('Android')).toBeInTheDocument();
-    expect(screen.queryByText('Electron')).not.toBeInTheDocument();
-  });
-  it('should render renderPlatformList with Python when filtered with py', async function () {
-    const props = {
-      ...baseProps,
-    };
-
-    render(<MultiPlatformPicker {...props} />);
-    await userEvent.type(screen.getByPlaceholderText('Filter Platforms'), 'py');
-    expect(screen.getByText('Python')).toBeInTheDocument();
-    expect(screen.queryByText('Electron')).not.toBeInTheDocument();
-  });
-
-  it('should render renderPlatformList with Native when filtered with c++ alias', async function () {
-    const props = {
-      ...baseProps,
-    };
-
-    render(<MultiPlatformPicker {...props} />);
-    await userEvent.type(screen.getByPlaceholderText('Filter Platforms'), 'c++');
-    expect(screen.getByText('Native (C/C++)')).toBeInTheDocument();
-    expect(screen.queryByText('Electron')).not.toBeInTheDocument();
-  });
-
-  it('should render renderPlatformList with community SDKs message if platform not found', async function () {
-    const props = {
-      ...baseProps,
-    };
-
-    render(<MultiPlatformPicker {...props} />);
-    await userEvent.type(screen.getByPlaceholderText('Filter Platforms'), 'aaaaa');
-
-    expect(screen.getByText("We don't have an SDK for that yet!")).toBeInTheDocument();
-    expect(screen.queryByText('Python')).not.toBeInTheDocument();
-  });
-
-  it('should clear the platform when clear is clicked', async function () {
-    const props = {
-      ...baseProps,
-      platforms: ['java'],
-      removePlatform: jest.fn(),
-      noAutoFilter: true,
-    };
-
-    render(<MultiPlatformPicker {...props} />);
-    await userEvent.click(screen.getByRole('button', {name: 'Clear'}));
-    expect(props.removePlatform).toHaveBeenCalledWith('java');
-  });
-
-  it('clicking on icon calls addPlatform', async function () {
-    const props = {
-      ...baseProps,
-      addPlatform: jest.fn(),
-    };
-
-    render(<MultiPlatformPicker {...props} />);
-    await userEvent.click(screen.getByText('Java'));
-    expect(props.addPlatform).toHaveBeenCalledWith('java');
-  });
-
-  it('clicking on icon calls does not call addPlatform if already in list', async function () {
-    const props = {
-      ...baseProps,
-      platforms: ['java'],
-      addPlatform: jest.fn(),
-    };
-
-    render(<MultiPlatformPicker {...props} />);
-    await userEvent.click(screen.getByText('Java'));
-    expect(props.addPlatform).not.toHaveBeenCalled();
-  });
-});

+ 0 - 362
static/app/components/multiPlatformPicker.tsx

@@ -1,362 +0,0 @@
-import {Fragment, useEffect, useState} 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 ExternalLink from 'sentry/components/links/externalLink';
-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, {
-  filterAliases,
-  PlatformKey,
-  popularPlatformCategories,
-} from 'sentry/data/platformCategories';
-import platforms 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} from 'sentry/types';
-import {trackAnalytics} from 'sentry/utils/analytics';
-
-const PLATFORM_CATEGORIES = [{id: 'all', name: t('All')}, ...categoryList] as const;
-
-// Category needs the all option while CategoryObj does not
-type Category = (typeof PLATFORM_CATEGORIES)[number]['id'];
-type CategoryObj = (typeof categoryList)[number];
-type Platform = CategoryObj['platforms'][number];
-
-// create a lookup table for each platform
-const indexByPlatformByCategory = {} as Record<
-  CategoryObj['id'],
-  Record<Platform, number>
->;
-categoryList.forEach(category => {
-  const indexByPlatform = {} as Record<Platform, number>;
-  indexByPlatformByCategory[category.id] = indexByPlatform;
-  category.platforms.forEach((platform: Platform, index: number) => {
-    indexByPlatform[platform] = index;
-  });
-});
-
-const getIndexOfPlatformInCategory = (
-  category: CategoryObj['id'],
-  platform: PlatformIntegration
-) => {
-  const indexByPlatform = indexByPlatformByCategory[category];
-  return indexByPlatform[platform.id];
-};
-
-const isPopular = (platform: PlatformIntegration) =>
-  popularPlatformCategories.includes(
-    platform.id as (typeof popularPlatformCategories)[number]
-  );
-
-const popularIndex = (platform: PlatformIntegration) =>
-  getIndexOfPlatformInCategory('popular', platform);
-
-const PlatformList = styled('div')`
-  display: grid;
-  gap: ${space(1)};
-  grid-template-columns: repeat(auto-fill, 112px);
-  justify-content: center;
-  margin-bottom: ${space(2)};
-`;
-
-interface PlatformPickerProps {
-  addPlatform: (key: PlatformKey) => void;
-  organization: Organization;
-  platforms: PlatformKey[];
-  removePlatform: (key: PlatformKey) => void;
-  source: string;
-  defaultCategory?: Category;
-  listClassName?: string;
-  listProps?: React.HTMLAttributes<HTMLDivElement>;
-  noAutoFilter?: boolean;
-  showOther?: boolean;
-}
-
-function PlatformPicker(props: PlatformPickerProps) {
-  const {organization, source} = props;
-  const [category, setCategory] = useState<Category>(
-    props.defaultCategory ?? PLATFORM_CATEGORIES[0].id
-  );
-  const [filter, setFilter] = useState<string>(
-    props.noAutoFilter ? '' : (props.platforms[0] || '').split('-')[0]
-  );
-
-  function getPlatformList() {
-    const currentCategory = categoryList.find(({id}) => id === category);
-
-    const filterLowerCase = filter.toLowerCase();
-
-    const subsetMatch = (platform: PlatformIntegration) =>
-      platform.id.includes(filterLowerCase) ||
-      platform.name.toLowerCase().includes(filterLowerCase) ||
-      filterAliases[platform.id]?.some(alias => alias.includes(filterLowerCase));
-
-    const categoryMatch = (platform: PlatformIntegration) =>
-      category === 'all' ||
-      (currentCategory?.platforms as undefined | string[])?.includes(platform.id);
-
-    const customCompares = (a: PlatformIntegration, b: PlatformIntegration) => {
-      // the all category and serverless category both require custom sorts
-      if (category === 'all') {
-        return popularTopOfAllCompare(a, b);
-      }
-      if (category === 'serverless') {
-        return serverlessCompare(a, b);
-      }
-      // maintain ordering otherwise
-      return (
-        getIndexOfPlatformInCategory(category, a) -
-        getIndexOfPlatformInCategory(category, b)
-      );
-    };
-
-    const popularTopOfAllCompare = (a: PlatformIntegration, b: PlatformIntegration) => {
-      // for the all category, put popular ones at the top in the order they appear in the popular list
-      if (isPopular(a) && isPopular(b)) {
-        // if both popular, maintain ordering from popular list
-        return popularIndex(a) - popularIndex(b);
-      }
-      // if one popular, that one should be first
-      if (isPopular(a) !== isPopular(b)) {
-        return isPopular(a) ? -1 : 1;
-      }
-      // since the all list is coming from a different source (platforms.json)
-      // we can't go off the index of the item in platformCategories.tsx since there is no all list
-      return a.id.localeCompare(b.id);
-    };
-
-    const serverlessCompare = (a: PlatformIntegration, b: PlatformIntegration) => {
-      // for the serverless category, sort by service, then language
-      // the format of the ids is language-service
-      const aProvider = a.id.split('-')[1];
-      const bProvider = b.id.split('-')[1];
-      // if either of the ids are not hyphenated, standard sort
-      if (!aProvider || !bProvider) {
-        return a.id.localeCompare(b.id);
-      }
-      // compare the portions after the hyphen
-      const compareServices = aProvider.localeCompare(bProvider);
-      // if they have the same service provider
-      if (!compareServices) {
-        return a.id.localeCompare(b.id);
-      }
-      return compareServices;
-    };
-
-    const filtered = platforms
-      .filter(filterLowerCase ? subsetMatch : categoryMatch)
-      .sort(customCompares);
-    return props.showOther ? filtered : filtered.filter(({id}) => id !== 'other');
-  }
-
-  const platformList = getPlatformList();
-  const {addPlatform, removePlatform, listProps, listClassName} = props;
-
-  const logSearch = debounce(() => {
-    if (filter) {
-      trackAnalytics('growth.platformpicker_search', {
-        search: filter.toLowerCase(),
-        num_results: platformList.length,
-        source,
-        organization,
-      });
-    }
-  }, DEFAULT_DEBOUNCE_DURATION);
-
-  useEffect(logSearch, [filter, logSearch]);
-
-  return (
-    <Fragment>
-      <NavContainer>
-        <CategoryNav>
-          {PLATFORM_CATEGORIES.map(({id, name}) => (
-            <ListLink
-              key={id}
-              onClick={(e: React.MouseEvent) => {
-                trackAnalytics('growth.platformpicker_category', {
-                  category: id,
-                  source,
-                  organization,
-                });
-                setCategory(id);
-                setFilter('');
-                e.preventDefault();
-              }}
-              to=""
-              isActive={() => id === (filter ? 'all' : category)}
-            >
-              {name}
-            </ListLink>
-          ))}
-        </CategoryNav>
-        <StyledSearchBar
-          size="sm"
-          query={filter}
-          placeholder={t('Filter Platforms')}
-          onChange={setFilter}
-        />
-      </NavContainer>
-      <PlatformList className={listClassName} {...listProps}>
-        {platformList.map(platform => (
-          <PlatformCard
-            data-test-id={`platform-${platform.id}`}
-            key={platform.id}
-            platform={platform}
-            selected={props.platforms.includes(platform.id)}
-            onClear={(e: React.MouseEvent) => {
-              removePlatform(platform.id);
-              e.stopPropagation();
-            }}
-            onClick={() => {
-              // do nothing if already selected
-              if (props.platforms.includes(platform.id)) {
-                return;
-              }
-              trackAnalytics('growth.select_platform', {
-                platform_id: platform.id,
-                source,
-                organization,
-              });
-              addPlatform(platform.id);
-            }}
-          />
-        ))}
-      </PlatformList>
-      {platformList.length === 0 && (
-        <EmptyMessage
-          icon={<IconProject size="xl" />}
-          title={t("We don't have an SDK for that yet!")}
-        >
-          {tct(
-            `Not finding your platform? You can still create your project,
-            but looks like we don't have an official SDK for your platform
-            yet. However, there's a rich ecosystem of community supported
-            SDKs (including Perl, CFML, Clojure, and ActionScript). Try
-            [search:searching for Sentry clients] or contacting support.`,
-            {
-              search: (
-                <ExternalLink href="https://github.com/search?q=-org%3Agetsentry+topic%3Asentry&type=Repositories" />
-              ),
-            }
-          )}
-        </EmptyMessage>
-      )}
-    </Fragment>
-  );
-}
-
-const NavContainer = styled('div')`
-  margin-bottom: ${space(2)};
-  display: flex;
-  flex-direction: row;
-  align-items: start;
-  border-bottom: 1px solid ${p => p.theme.border};
-`;
-
-const StyledSearchBar = styled(SearchBar)`
-  max-width: 300px;
-  min-width: 150px;
-  margin-top: -${space(0.25)};
-  margin-left: auto;
-  flex-shrink: 0;
-  flex-basis: 0;
-  flex-grow: 1;
-`;
-
-const CategoryNav = styled(NavTabs)`
-  margin: 0;
-  margin-top: 4px;
-  white-space: nowrap;
-  overflow-x: scroll;
-  overflow-y: hidden;
-  margin-right: ${space(1)};
-  flex-shrink: 1;
-  flex-grow: 0;
-
-  > li {
-    float: none;
-    display: inline-block;
-  }
-  ::-webkit-scrollbar {
-    display: none;
-  }
-  -ms-overflow-style: none;
-  scrollbar-width: none;
-`;
-
-const StyledPlatformIcon = styled(PlatformIcon)`
-  margin: ${space(2)};
-`;
-
-const ClearButton = styled(Button)`
-  position: absolute;
-  top: -6px;
-  right: -6px;
-  min-height: 0;
-  height: 22px;
-  width: 22px;
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  border-radius: 50%;
-  background: ${p => p.theme.background};
-  color: ${p => p.theme.textColor};
-`;
-
-ClearButton.defaultProps = {
-  icon: <IconClose isCircled size="xs" />,
-  borderless: true,
-  size: 'xs',
-};
-
-const PlatformCard = styled(({platform, selected, onClear, ...props}) => (
-  <div {...props}>
-    <StyledPlatformIcon
-      platform={platform.id}
-      size={56}
-      radius={5}
-      withLanguageIcon
-      format="lg"
-    />
-
-    <h3>{platform.name}</h3>
-    {selected && <ClearButton onClick={onClear} aria-label={t('Clear')} />}
-  </div>
-))`
-  position: relative;
-  display: flex;
-  flex-direction: column;
-  align-items: center;
-  padding: 0 0 14px;
-  border-radius: 4px;
-  cursor: pointer;
-  background: ${p => p.selected && p.theme.alert.info.backgroundLight};
-
-  &:hover {
-    background: ${p => p.theme.alert.muted.backgroundLight};
-  }
-
-  h3 {
-    flex-grow: 1;
-    display: flex;
-    align-items: center;
-    justify-content: center;
-    width: 100%;
-    color: ${p => (p.selected ? p.theme.textColor : p.theme.subText)};
-    text-align: center;
-    font-size: ${p => p.theme.fontSizeExtraSmall};
-    text-transform: uppercase;
-    margin: 0;
-    padding: 0 ${space(0.5)};
-    line-height: 1.2;
-  }
-`;
-
-export default PlatformPicker;

+ 35 - 51
static/app/components/onboardingWizard/onboardingProjectsCard.tsx

@@ -5,17 +5,15 @@ import {PlatformIcon} from 'platformicons';
 import {Button} from 'sentry/components/button';
 import Card from 'sentry/components/card';
 import Link from 'sentry/components/links/link';
-import {IconChevron, IconClose, IconEllipsis} from 'sentry/icons';
-import {t, tct} from 'sentry/locale';
+import {IconChevron, IconClose} from 'sentry/icons';
+import {t} from 'sentry/locale';
 import pulsingIndicatorStyles from 'sentry/styles/pulsingIndicator';
 import {space} from 'sentry/styles/space';
-import {OnboardingCustomComponentProps, Project} from 'sentry/types';
+import {OnboardingCustomComponentProps} from 'sentry/types';
 import {trackAnalytics} from 'sentry/utils/analytics';
 
 import SkipConfirm from './skipConfirm';
 
-const MAX_PROJECT_COUNT = 3;
-
 export default function OnboardingProjectsCard({
   organization: org,
   onboardingState,
@@ -29,56 +27,46 @@ export default function OnboardingProjectsCard({
   const handleSkip = () => {
     setOnboardingState({
       ...onboardingState,
-      selectedPlatforms: [],
+      selectedPlatform: undefined,
     });
   };
-  // Projects selected during onboarding but not received first event
-  const projects = onboardingState.selectedPlatforms
-    .map(platform => onboardingState.platformToProjectIdMap[platform.key])
-    .map(projectId => allProjects.find(p => p.slug === projectId))
-    .filter(project => project && !project.firstEvent) as Project[];
-  if (projects.length === 0) {
+
+  const selectedProjectSlug = onboardingState.selectedPlatform
+    ? onboardingState.platformToProjectIdMap[onboardingState.selectedPlatform.key]
+    : undefined;
+
+  const project = selectedProjectSlug
+    ? allProjects.find(p => p.slug === selectedProjectSlug)
+    : undefined;
+
+  // Project selected during onboarding but not received first event
+  const projectHasFirstEvent = !project?.firstEvent;
+
+  if (!project || !projectHasFirstEvent) {
     return null;
   }
+
   return (
     <TaskCard key="onboarding-continue-card">
       <Title>{t('Project to Setup')}</Title>
       <OnboardingTaskProjectList>
-        {projects.slice(0, MAX_PROJECT_COUNT).map(p => (
-          <OnboardingTaskProjectListItem
-            key={p.id}
-            to={`/onboarding/${org.slug}/setup-docs/?project_id=${p.id}`}
-            onClick={() => {
-              trackAnalytics('growth.onboarding_quick_start_cta', {
-                platform: p.platform,
-                organization: org,
-              });
-            }}
-          >
-            <OnboardingTaskProjectListItemInner>
-              <StyledPlatformIcon platform={p.platform || 'default'} />
-              {p.slug}
-              <PulsingIndicator />
-              <PulsingIndicatorText>{t('Waiting for event')}</PulsingIndicatorText>
-              <IconChevron direction="right" />
-            </OnboardingTaskProjectListItemInner>
-          </OnboardingTaskProjectListItem>
-        ))}
-        {projects.length > MAX_PROJECT_COUNT && (
-          <OnboardingTaskProjectListItem
-            to={`/onboarding/${org.slug}/setup-docs/`}
-            onClick={() => {
-              trackAnalytics('growth.onboarding_quick_start_cta', {
-                organization: org,
-              });
-            }}
-          >
-            <OnboardingTaskProjectListItemInner>
-              <StyledAndMoreIcon />
-              {tct('and [num] more', {num: projects.length - MAX_PROJECT_COUNT})}
-            </OnboardingTaskProjectListItemInner>
-          </OnboardingTaskProjectListItem>
-        )}
+        <OnboardingTaskProjectListItem
+          to={`/onboarding/${org.slug}/setup-docs/?project_id=${project.id}`}
+          onClick={() => {
+            trackAnalytics('growth.onboarding_quick_start_cta', {
+              platform: project.platform,
+              organization: org,
+            });
+          }}
+        >
+          <OnboardingTaskProjectListItemInner>
+            <StyledPlatformIcon platform={project.platform || 'default'} />
+            {project.slug}
+            <PulsingIndicator />
+            <PulsingIndicatorText>{t('Waiting for event')}</PulsingIndicatorText>
+            <IconChevron direction="right" />
+          </OnboardingTaskProjectListItemInner>
+        </OnboardingTaskProjectListItem>
       </OnboardingTaskProjectList>
       <SkipConfirm onSkip={handleSkip}>
         {({skip}) => (
@@ -184,7 +172,3 @@ const CloseButton = styled(Button)`
 const StyledPlatformIcon = styled(PlatformIcon)`
   margin-right: ${space(1)};
 `;
-
-const StyledAndMoreIcon = styled(IconEllipsis)`
-  margin-right: ${space(1)};
-`;

+ 0 - 5
static/app/utils/analytics/growthAnalyticsEvents.tsx

@@ -89,7 +89,6 @@ export type GrowthEventParameters = {
     preset: string;
   };
   'growth.onboarding_clicked_instrument_app': {source?: string};
-  'growth.onboarding_clicked_project_in_sidebar': {platform: string};
   'growth.onboarding_clicked_setup_platform_later': PlatformParam & {
     project_index: number;
   };
@@ -97,7 +96,6 @@ export type GrowthEventParameters = {
   'growth.onboarding_load_choose_platform': {};
   'growth.onboarding_quick_start_cta': SampleEventParam;
   'growth.onboarding_set_up_your_project': PlatformParam;
-  'growth.onboarding_set_up_your_projects': {platform_count: number; platforms: string};
   'growth.onboarding_start_onboarding': {
     source?: string;
   };
@@ -168,8 +166,6 @@ export const growthEventMap: Record<GrowthAnalyticsKey, string | null> = {
   'growth.onboarding_load_choose_platform':
     'Growth: Onboarding Load Choose Platform Page',
   'growth.onboarding_set_up_your_project': 'Growth: Onboarding Click Set Up Your Project',
-  'growth.onboarding_set_up_your_projects':
-    'Growth: Onboarding Click Set Up Your Projects',
   'growth.select_platform': 'Growth: Onboarding Choose Platform',
   'growth.platformpicker_category': 'Growth: Onboarding Platform Category',
   'growth.platformpicker_search': 'Growth: Onboarding Platform Search',
@@ -192,7 +188,6 @@ export const growthEventMap: Record<GrowthAnalyticsKey, string | null> = {
   'growth.demo_modal_clicked_close': 'Growth: Demo Modal Clicked Close',
   'growth.demo_modal_clicked_demo': 'Growth: Demo Modal Clicked Demo',
   'growth.clicked_enter_sandbox': 'Growth: Clicked Enter Sandbox',
-  'growth.onboarding_clicked_project_in_sidebar': 'Growth: Clicked Project Sidebar',
   'growth.sample_transaction_docs_link_clicked':
     'Growth: Sample Transaction Docs Link Clicked',
   'growth.sample_error_onboarding_link_clicked':

+ 82 - 147
static/app/views/onboarding/components/createProjectsFooter.tsx

@@ -13,14 +13,11 @@ import {openModal} from 'sentry/actionCreators/modal';
 import {createProject} from 'sentry/actionCreators/projects';
 import {Button} from 'sentry/components/button';
 import {SUPPORTED_LANGUAGES} from 'sentry/components/onboarding/frameworkSuggestionModal';
-import TextOverflow from 'sentry/components/textOverflow';
-import {PlatformKey} from 'sentry/data/platformCategories';
-import {t, tct, tn} from 'sentry/locale';
+import {t} from 'sentry/locale';
 import ProjectsStore from 'sentry/stores/projectsStore';
 import {space} from 'sentry/styles/space';
 import {OnboardingSelectedPlatform, Organization} from 'sentry/types';
 import {trackAnalytics} from 'sentry/utils/analytics';
-import getPlatformName from 'sentry/utils/getPlatformName';
 import testableTransition from 'sentry/utils/testableTransition';
 import useApi from 'sentry/utils/useApi';
 import useProjects from 'sentry/utils/useProjects';
@@ -32,23 +29,20 @@ import {usePersistedOnboardingState} from '../utils';
 import GenericFooter from './genericFooter';
 
 type Props = {
-  clearPlatforms: () => void;
+  clearPlatform: () => void;
   genSkipOnboardingLink: () => React.ReactNode;
-  onComplete: (selectedPlatforms: OnboardingSelectedPlatform[]) => void;
+  onComplete: (selectedPlatform: OnboardingSelectedPlatform) => void;
   organization: Organization;
-  selectedPlatforms: OnboardingSelectedPlatform[];
+  selectedPlatform?: OnboardingSelectedPlatform;
 };
 
 export function CreateProjectsFooter({
   organization,
-  selectedPlatforms,
+  selectedPlatform,
   onComplete,
   genSkipOnboardingLink,
-  clearPlatforms,
+  clearPlatform,
 }: Props) {
-  const singleSelectPlatform = !!organization?.features.includes(
-    'onboarding-remove-multiselect-platform'
-  );
   const frameworkSelectionEnabled = !!organization?.features.includes(
     'onboarding-sdk-selection'
   );
@@ -58,98 +52,98 @@ export function CreateProjectsFooter({
   const [clientState, setClientState] = usePersistedOnboardingState();
   const {projects} = useProjects();
 
-  const createProjects = useCallback(
+  const createPlatformProject = useCallback(
     async (selectedFramework?: OnboardingSelectedPlatform) => {
-      if (!clientState) {
+      if (!clientState || !selectedPlatform) {
         // Do nothing if client state is not loaded yet.
         return;
       }
 
-      const createProjectForPlatforms = selectedFramework
+      const createProjectForPlatform = selectedFramework
         ? projects.find(p => p.platform === selectedFramework.key)
-          ? []
-          : [selectedFramework]
-        : selectedPlatforms
-            .filter(platform => !clientState.platformToProjectIdMap[platform.key])
-            // filter out platforms that already have a project
-            .filter(platform => !projects.find(p => p.platform === platform.key));
-
-      if (createProjectForPlatforms.length === 0) {
+          ? undefined
+          : selectedFramework
+        : projects.find(p => p.platform === selectedPlatform.key)
+        ? undefined
+        : clientState.platformToProjectIdMap[selectedPlatform.key]
+        ? undefined
+        : selectedPlatform;
+
+      if (!createProjectForPlatform) {
+        const platform = selectedFramework ? selectedFramework : selectedPlatform;
+
         setClientState({
           platformToProjectIdMap: clientState.platformToProjectIdMap,
-          selectedPlatforms: selectedFramework
-            ? [selectedFramework]
-            : clientState.selectedPlatforms,
+          selectedPlatform: platform,
           state: 'projects_selected',
           url: 'setup-docs/',
         });
-        trackAnalytics('growth.onboarding_set_up_your_projects', {
-          platforms: selectedPlatforms.join(','),
-          platform_count: selectedPlatforms.length,
+
+        trackAnalytics('growth.onboarding_set_up_your_project', {
+          platform: selectedPlatform.key,
           organization,
         });
-        onComplete(
-          selectedFramework ? [selectedFramework] : clientState.selectedPlatforms
-        );
+
+        onComplete(platform);
         return;
       }
 
       try {
-        addLoadingMessage(
-          singleSelectPlatform ? t('Creating project') : t('Creating projects')
-        );
-
-        const responses = await Promise.all(
-          createProjectForPlatforms.map(p =>
-            createProject(api, organization.slug, teams[0].slug, p.key, p.key, {
-              defaultRules: true,
-            })
-          )
-        );
+        addLoadingMessage(t('Creating project'));
+
+        const response = await createProject({
+          api,
+          orgSlug: organization.slug,
+          team: teams[0].slug,
+          platform: createProjectForPlatform.key,
+          name: createProjectForPlatform.key,
+          options: {
+            defaultRules: true,
+          },
+        });
 
         const nextState: OnboardingState = {
           platformToProjectIdMap: clientState.platformToProjectIdMap,
-          selectedPlatforms: createProjectForPlatforms,
+          selectedPlatform: createProjectForPlatform,
           state: 'projects_selected',
           url: 'setup-docs/',
         };
-        responses.forEach(p => (nextState.platformToProjectIdMap[p.platform] = p.slug));
+
+        nextState.platformToProjectIdMap[createProjectForPlatform.key] =
+          createProjectForPlatform.key;
+
         setClientState(nextState);
 
-        responses.forEach(data => ProjectsStore.onCreateSuccess(data, organization.slug));
+        ProjectsStore.onCreateSuccess(response, organization.slug);
 
-        trackAnalytics('growth.onboarding_set_up_your_projects', {
-          platforms: selectedPlatforms.join(','),
-          platform_count: selectedPlatforms.length,
+        trackAnalytics('growth.onboarding_set_up_your_project', {
+          platform: selectedPlatform.key,
           organization,
         });
+
         clearIndicators();
-        setTimeout(() => onComplete(createProjectForPlatforms));
+        setTimeout(() => onComplete(createProjectForPlatform));
       } catch (err) {
-        addErrorMessage(
-          singleSelectPlatform
-            ? t('Failed to create project')
-            : t('Failed to create projects')
-        );
+        addErrorMessage(t('Failed to create project'));
         Sentry.captureException(err);
       }
     },
     [
       clientState,
       setClientState,
-      selectedPlatforms,
+      selectedPlatform,
       api,
       organization,
       teams,
       projects,
       onComplete,
-      singleSelectPlatform,
     ]
   );
 
   const handleProjectCreation = useCallback(async () => {
-    // we assume that the single select platform is always enabled here
-    const selectedPlatform = selectedPlatforms[0];
+    if (!selectedPlatform) {
+      return;
+    }
 
     if (
       selectedPlatform.type !== 'language' ||
@@ -157,7 +151,7 @@ export function CreateProjectsFooter({
         selectedPlatform.language as SUPPORTED_LANGUAGES
       )
     ) {
-      createProjects();
+      createPlatformProject();
       return;
     }
 
@@ -172,9 +166,9 @@ export function CreateProjectsFooter({
           organization={organization}
           selectedPlatform={selectedPlatform}
           onConfigure={selectedFramework => {
-            createProjects(selectedFramework);
+            createPlatformProject(selectedFramework);
           }}
-          onSkip={createProjects}
+          onSkip={createPlatformProject}
         />
       ),
       {
@@ -187,88 +181,40 @@ export function CreateProjectsFooter({
         },
       }
     );
-  }, [selectedPlatforms, createProjects, organization]);
-
-  const renderPlatform = (platform: PlatformKey) => {
-    platform = platform || 'other';
-    return <SelectedPlatformIcon key={platform} platform={platform} size={23} />;
-  };
+  }, [selectedPlatform, createPlatformProject, organization]);
 
   return (
     <GenericFooter>
       {genSkipOnboardingLink()}
       <SelectionWrapper>
-        {selectedPlatforms.length ? (
-          singleSelectPlatform ? (
-            <Fragment>
-              <div>
-                {selectedPlatforms.map(selectedPlatform =>
-                  renderPlatform(selectedPlatform.key)
-                )}
-              </div>
-              <PlatformSelected>
-                {tct('[platform] selected', {
-                  platform: (
-                    <PlatformName>
-                      {getPlatformName(selectedPlatforms[0].key) ?? 'other'}
-                    </PlatformName>
-                  ),
-                })}
-                <ClearButton priority="link" onClick={clearPlatforms} size="zero">
-                  {t('Clear')}
-                </ClearButton>
-              </PlatformSelected>
-            </Fragment>
-          ) : (
-            <Fragment>
-              <div>
-                {selectedPlatforms.map(selectedPlatform =>
-                  renderPlatform(selectedPlatform.key)
-                )}
-              </div>
-              <PlatformsSelected>
-                {tn(
-                  '%s platform selected',
-                  '%s platforms selected',
-                  selectedPlatforms.length
-                )}
-                <ClearButton priority="link" onClick={clearPlatforms} size="zero">
-                  {t('Clear')}
-                </ClearButton>
-              </PlatformsSelected>
-            </Fragment>
-          )
+        {selectedPlatform ? (
+          <Fragment>
+            <div>
+              <SelectedPlatformIcon
+                platform={selectedPlatform.key ?? 'other'}
+                size={23}
+              />
+            </div>
+            <PlatformsSelected>
+              {t('1 platform selected')}
+              <ClearButton priority="link" onClick={clearPlatform} size="zero">
+                {t('Clear')}
+              </ClearButton>
+            </PlatformsSelected>
+          </Fragment>
         ) : null}
       </SelectionWrapper>
       <ButtonWrapper>
-        {singleSelectPlatform ? (
-          <Button
-            priority="primary"
-            onClick={() =>
-              frameworkSelectionEnabled ? handleProjectCreation() : createProjects()
-            }
-            disabled={selectedPlatforms.length === 0}
-            data-test-id="platform-select-next"
-            title={
-              selectedPlatforms.length === 0
-                ? t('Select the platform you want to monitor')
-                : undefined
-            }
-          >
-            {t('Create Project')}
-          </Button>
-        ) : (
-          <Button
-            priority="primary"
-            onClick={() =>
-              frameworkSelectionEnabled ? handleProjectCreation() : createProjects()
-            }
-            disabled={selectedPlatforms.length === 0}
-            data-test-id="platform-select-next"
-          >
-            {tn('Create Project', 'Create Projects', selectedPlatforms.length)}
-          </Button>
-        )}
+        <Button
+          priority="primary"
+          onClick={() =>
+            frameworkSelectionEnabled ? handleProjectCreation() : createPlatformProject()
+          }
+          disabled={!selectedPlatform}
+          data-test-id="platform-select-next"
+        >
+          {t('Create Project')}
+        </Button>
       </ButtonWrapper>
     </GenericFooter>
   );
@@ -313,18 +259,7 @@ const PlatformsSelected = styled('div')`
   margin-top: ${space(1)};
 `;
 
-const PlatformSelected = styled('div')`
-  margin-top: ${space(1)};
-  display: grid;
-  grid-template-columns: 1fr max-content max-content;
-  align-items: center;
-`;
-
 const ClearButton = styled(Button)`
   margin-left: ${space(2)};
   padding: 0;
 `;
-
-const PlatformName = styled(TextOverflow)`
-  margin-right: ${space(0.5)};
-`;

+ 0 - 153
static/app/views/onboarding/components/projectSidebarSection.tsx

@@ -1,153 +0,0 @@
-import {Fragment} from 'react';
-import styled from '@emotion/styled';
-import {motion, Variants} from 'framer-motion';
-import {PlatformIcon} from 'platformicons';
-
-import {PlatformKey} from 'sentry/data/platformCategories';
-import platforms from 'sentry/data/platforms';
-import {IconCheckmark} from 'sentry/icons';
-import {t} from 'sentry/locale';
-import pulsingIndicatorStyles from 'sentry/styles/pulsingIndicator';
-import {space} from 'sentry/styles/space';
-import {Project} from 'sentry/types';
-import testableTransition from 'sentry/utils/testableTransition';
-
-type Props = {
-  activeProject: Project | null;
-  checkProjectHasFirstEvent: (project: Project) => boolean;
-  projects: Project[];
-  selectProject: (newProjectId: string) => void;
-  // A map from selected platform keys to the projects created by onboarding.
-  selectedPlatformToProjectIdMap: {[key in PlatformKey]?: string};
-};
-
-function ProjectSidebarSection({
-  projects,
-  activeProject,
-  selectProject,
-  checkProjectHasFirstEvent,
-  selectedPlatformToProjectIdMap,
-}: Props) {
-  const oneProject = (platformOnCreate: string, projectSlug: string) => {
-    const project = projects.find(p => p.slug === projectSlug);
-    const platform = project ? project.platform || 'other' : platformOnCreate;
-    const platformName = platforms.find(p => p.id === platform)?.name ?? '';
-    const isActive = !!project && activeProject?.id === project.id;
-    const errorReceived = !!project && checkProjectHasFirstEvent(project);
-    return (
-      <ProjectWrapper
-        key={projectSlug}
-        isActive={isActive}
-        onClick={() => project && selectProject(project.id)}
-        disabled={!project}
-      >
-        <StyledPlatformIcon platform={platform} size={36} />
-        <MiddleWrapper>
-          <NameWrapper>{platformName}</NameWrapper>
-          <SubHeader errorReceived={errorReceived} data-test-id="sidebar-error-indicator">
-            {!project
-              ? t('Project Deleted')
-              : errorReceived
-              ? t('Error Received')
-              : t('Waiting for error')}
-          </SubHeader>
-        </MiddleWrapper>
-        {errorReceived ? (
-          <StyledIconCheckmark isCircled color="green400" />
-        ) : (
-          isActive && <WaitingIndicator />
-        )}
-      </ProjectWrapper>
-    );
-  };
-  return (
-    <Fragment>
-      <Title>{t('Projects to Setup')}</Title>
-      {Object.entries(selectedPlatformToProjectIdMap).map(
-        ([platformOnCreate, projectSlug]) => {
-          return oneProject(platformOnCreate, projectSlug);
-        }
-      )}
-    </Fragment>
-  );
-}
-
-export default ProjectSidebarSection;
-
-const Title = styled('span')`
-  font-size: 12px;
-  font-weight: 600;
-  text-transform: uppercase;
-  margin-left: ${space(2)};
-`;
-
-const SubHeader = styled('div')<{errorReceived: boolean}>`
-  color: ${p => (p.errorReceived ? p.theme.successText : p.theme.pink400)};
-`;
-
-const StyledPlatformIcon = styled(PlatformIcon)``;
-
-const ProjectWrapper = styled('div')<{disabled: boolean; isActive: boolean}>`
-  display: flex;
-  flex-direction: row;
-  align-items: center;
-  background-color: ${p => p.isActive && p.theme.gray100};
-  padding: ${space(2)};
-  cursor: pointer;
-  border-radius: 4px;
-  user-select: none;
-  ${p =>
-    p.disabled &&
-    `
-    cursor: not-allowed;
-    ${StyledPlatformIcon} {
-      filter: grayscale(1);
-    }
-    ${SubHeader} {
-      color: ${p.theme.gray400};
-    }
-    ${Beat} {
-      color: ${p.theme.gray400};
-    }
-    ${NameWrapper} {
-      text-decoration-line: line-through;
-    }
-  `}
-`;
-
-const indicatorAnimation: Variants = {
-  initial: {opacity: 0, y: -10},
-  animate: {opacity: 1, y: 0},
-  exit: {opacity: 0, y: 10},
-};
-
-const WaitingIndicator = styled(motion.div)`
-  margin: 0 6px;
-  flex-shrink: 0;
-  ${pulsingIndicatorStyles};
-  background-color: ${p => p.theme.pink300};
-`;
-const StyledIconCheckmark = styled(IconCheckmark)`
-  flex-shrink: 0;
-`;
-
-WaitingIndicator.defaultProps = {
-  variants: indicatorAnimation,
-  transition: testableTransition(),
-};
-
-const MiddleWrapper = styled('div')`
-  margin: 0 ${space(1)};
-  flex-grow: 1;
-  overflow: hidden;
-`;
-
-const NameWrapper = styled('div')`
-  overflow: hidden;
-  white-space: nowrap;
-  text-overflow: ellipsis;
-`;
-
-const Beat = styled('div')<{color: string}>`
-  color: ${p => p.color};
-`;

+ 0 - 115
static/app/views/onboarding/deprecatedPlatform.tsx

@@ -1,115 +0,0 @@
-import {useEffect, useState} from 'react';
-import styled from '@emotion/styled';
-import {motion} from 'framer-motion';
-
-import ExternalLink from 'sentry/components/links/externalLink';
-import MultiPlatformPicker from 'sentry/components/multiPlatformPicker';
-import {PlatformKey} from 'sentry/data/platformCategories';
-import platforms from 'sentry/data/platforms';
-import {t, tct} from 'sentry/locale';
-import {OnboardingSelectedPlatform} from 'sentry/types';
-import {defined} from 'sentry/utils';
-import testableTransition from 'sentry/utils/testableTransition';
-import useOrganization from 'sentry/utils/useOrganization';
-import StepHeading from 'sentry/views/onboarding/components/stepHeading';
-
-import {CreateProjectsFooter} from './components/createProjectsFooter';
-import {StepProps} from './types';
-import {usePersistedOnboardingState} from './utils';
-
-function OnboardingPlatform(props: StepProps) {
-  const organization = useOrganization();
-  const [selectedPlatforms, setSelectedPlatforms] = useState<
-    OnboardingSelectedPlatform[]
-  >([]);
-  const addPlatform = (platform: PlatformKey) => {
-    const foundPlatform = platforms.find(p => p.id === platform);
-    if (!foundPlatform) {
-      return;
-    }
-    setSelectedPlatforms([
-      ...selectedPlatforms,
-      {
-        key: platform,
-        type: foundPlatform.type,
-        language: foundPlatform.language,
-        category: 'all',
-      },
-    ]);
-  };
-  const removePlatform = (platform: PlatformKey) => {
-    setSelectedPlatforms(selectedPlatforms.filter(p => p.key !== platform));
-  };
-
-  const [clientState] = usePersistedOnboardingState();
-  useEffect(() => {
-    if (clientState) {
-      setSelectedPlatforms(clientState.selectedPlatforms);
-    }
-  }, [clientState]);
-
-  const clearPlatforms = () => setSelectedPlatforms([]);
-  return (
-    <Wrapper>
-      <StepHeading step={props.stepIndex}>
-        {t('Select the platforms you want to monitor')}
-      </StepHeading>
-      <motion.div
-        transition={testableTransition()}
-        variants={{
-          initial: {y: 30, opacity: 0},
-          animate: {y: 0, opacity: 1},
-          exit: {opacity: 0},
-        }}
-      >
-        <p>
-          {tct(
-            `Variety is the spice of application monitoring. Identify what’s broken
-          faster by selecting all the platforms that support your application.
-           [link:View the full list].`,
-            {link: <ExternalLink href="https://docs.sentry.io/platforms/" />}
-          )}
-        </p>
-        <MultiPlatformPicker
-          noAutoFilter
-          source="targeted-onboarding"
-          {...props}
-          organization={organization}
-          removePlatform={removePlatform}
-          addPlatform={addPlatform}
-          platforms={selectedPlatforms.map(selectedPlatform => selectedPlatform.key)}
-        />
-      </motion.div>
-      <CreateProjectsFooter
-        {...props}
-        organization={organization}
-        clearPlatforms={clearPlatforms}
-        selectedPlatforms={selectedPlatforms
-          .map(selectedPlatform => {
-            const foundPlatform = platforms.find(p => p.id === selectedPlatform.key);
-
-            if (!foundPlatform) {
-              return undefined;
-            }
-
-            return {
-              key: foundPlatform.id,
-              type: foundPlatform.type,
-              language: foundPlatform.language,
-              category: selectedPlatform.category,
-            };
-          })
-          .filter(defined)}
-      />
-    </Wrapper>
-  );
-}
-
-export default OnboardingPlatform;
-
-const Wrapper = styled('div')`
-  max-width: 850px;
-  margin-left: auto;
-  margin-right: auto;
-  width: 100%;
-`;

+ 0 - 132
static/app/views/onboarding/onboarding.spec.jsx

@@ -1,132 +0,0 @@
-import {initializeOrg} from 'sentry-test/initializeOrg';
-import {render, screen} from 'sentry-test/reactTestingLibrary';
-
-import OrganizationStore from 'sentry/stores/organizationStore';
-import {PersistedStoreProvider} from 'sentry/stores/persistedStore';
-import ProjectsStore from 'sentry/stores/projectsStore';
-import Onboarding from 'sentry/views/onboarding/onboarding';
-
-describe('Onboarding', function () {
-  afterEach(function () {
-    MockApiClient.clearMockResponses();
-  });
-  it('renders the welcome page', function () {
-    const {router, routerContext} = initializeOrg({
-      router: {
-        params: {
-          step: 'welcome',
-        },
-      },
-    });
-    render(
-      <PersistedStoreProvider>
-        <Onboarding {...router} />
-      </PersistedStoreProvider>,
-      {
-        context: routerContext,
-      }
-    );
-    expect(screen.getByLabelText('Start')).toBeInTheDocument();
-    expect(screen.getByLabelText('Invite Team')).toBeInTheDocument();
-  });
-  it('renders the select platform step', async () => {
-    const {organization, router, routerContext} = initializeOrg({
-      router: {
-        params: {
-          step: 'select-platform',
-        },
-      },
-    });
-    MockApiClient.addMockResponse({
-      url: `/organizations/${organization.slug}/client-state/`,
-      body: {},
-    });
-    OrganizationStore.onUpdate(organization);
-    render(
-      <PersistedStoreProvider>
-        <Onboarding {...router} />
-      </PersistedStoreProvider>,
-      {
-        context: routerContext,
-      }
-    );
-    expect(
-      await screen.findByText('Select the platforms you want to monitor')
-    ).toBeInTheDocument();
-  });
-  it('renders the setup docs step', async () => {
-    const projects = [
-      TestStubs.Project({
-        platform: 'javascript-react',
-        id: '4',
-        slug: 'javascript-reactslug',
-      }),
-      TestStubs.Project({platform: 'ruby', id: '5', slug: 'ruby-slug'}),
-      TestStubs.Project({
-        platform: 'javascript-nextjs',
-        id: '6',
-        slug: 'javascript-nextslug',
-      }),
-    ];
-    const {organization, router, routerContext} = initializeOrg({
-      projects,
-      router: {
-        params: {
-          step: 'setup-docs',
-        },
-      },
-    });
-    MockApiClient.addMockResponse({
-      url: `/organizations/${organization.slug}/client-state/`,
-      body: {
-        onboarding: {
-          platformToProjectIdMap: {
-            'javascript-react': projects[0].slug,
-            ruby: projects[1].slug,
-            'javascript-nextjs': projects[2].slug,
-          },
-          selectedPlatforms: [
-            {key: 'ruby', type: 'language', language: 'ruby', category: 'server'},
-            {
-              key: 'javascript-nextjs',
-              type: 'framework',
-              language: 'javascript',
-              category: 'browser',
-            },
-          ],
-        },
-      },
-    });
-    MockApiClient.addMockResponse({
-      url: `/projects/${organization.slug}/ruby-slug/`,
-      body: {
-        firstEvent: false,
-      },
-    });
-    MockApiClient.addMockResponse({
-      url: `/projects/${organization.slug}/javascript-nextslug/docs/javascript-nextjs/`,
-      body: null,
-    });
-    MockApiClient.addMockResponse({
-      url: `/projects/${organization.slug}/ruby-slug/docs/ruby/`,
-      body: null,
-    });
-    MockApiClient.addMockResponse({
-      url: `/projects/${organization.slug}/ruby-slug/issues/`,
-      body: [],
-    });
-    ProjectsStore.loadInitialData(projects);
-    OrganizationStore.onUpdate(organization);
-
-    render(
-      <PersistedStoreProvider>
-        <Onboarding {...router} />
-      </PersistedStoreProvider>,
-      {
-        context: routerContext,
-      }
-    );
-
-    expect(await screen.findAllByTestId('sidebar-error-indicator')).toHaveLength(2);
-  });
-});

+ 49 - 68
static/app/views/onboarding/onboarding.spec.tsx

@@ -49,7 +49,7 @@ describe('Onboarding', function () {
     expect(screen.getByLabelText('Invite Team')).toBeInTheDocument();
   });
 
-  it('renders the select platform step', async () => {
+  it('renders the select platform step', async function () {
     const routeParams = {
       step: 'select-platform',
     };
@@ -85,24 +85,22 @@ describe('Onboarding', function () {
     );
 
     expect(
-      await screen.findByText('Select the platforms you want to monitor')
+      await screen.findByText('Select the platform you want to monitor')
     ).toBeInTheDocument();
   });
 
-  it('renders the setup docs step', async () => {
-    const projects = [
-      TestStubs.Project({
-        platform: 'javascript-react',
-        id: '4',
-        slug: 'javascript-reactslug',
-      }),
-      TestStubs.Project({platform: 'ruby', id: '5', slug: 'ruby-slug'}),
-      TestStubs.Project({
-        platform: 'javascript-nextjs',
-        id: '6',
-        slug: 'javascript-nextslug',
-      }),
-    ];
+  it('renders the setup docs step', async function () {
+    const reactProject = TestStubs.Project({
+      platform: 'javascript-react',
+      id: '1',
+      slug: 'javascript-react-slug',
+    });
+
+    const nextJsProject = TestStubs.Project({
+      platform: 'javascript-nextjs',
+      id: '2',
+      slug: 'javascript-nextjs-slug',
+    });
 
     const routeParams = {
       step: 'setup-docs',
@@ -120,46 +118,35 @@ describe('Onboarding', function () {
       body: {
         onboarding: {
           platformToProjectIdMap: {
-            'javascript-react': projects[0].slug,
-            ruby: projects[1].slug,
-            'javascript-nextjs': projects[2].slug,
+            'javascript-react': reactProject.slug,
+            [nextJsProject.slug]: nextJsProject.slug,
+          },
+          selectedPlatform: {
+            key: nextJsProject.slug,
+            type: 'framework',
+            language: 'javascript',
+            category: 'browser',
           },
-          selectedPlatforms: [
-            {key: 'ruby', type: 'language', language: 'ruby', category: 'server'},
-            {
-              key: 'javascript-nextjs',
-              type: 'framework',
-              language: 'javascript',
-              category: 'browser',
-            },
-          ],
         },
       },
     });
 
     MockApiClient.addMockResponse({
-      url: `/projects/${organization.slug}/ruby-slug/`,
-      body: {
-        firstEvent: false,
-      },
-    });
-
-    MockApiClient.addMockResponse({
-      url: `/projects/${organization.slug}/javascript-nextslug/docs/javascript-nextjs/`,
+      url: `/projects/${organization.slug}/${nextJsProject.slug}/docs/javascript-nextjs-with-error-monitoring/`,
       body: null,
     });
 
     MockApiClient.addMockResponse({
-      url: `/projects/${organization.slug}/ruby-slug/docs/ruby/`,
-      body: null,
+      url: `/projects/org-slug/${nextJsProject.slug}/`,
+      body: [nextJsProject],
     });
 
     MockApiClient.addMockResponse({
-      url: `/projects/${organization.slug}/ruby-slug/issues/`,
+      url: `/projects/${organization.slug}/${nextJsProject.slug}/issues/`,
       body: [],
     });
 
-    ProjectsStore.loadInitialData(projects);
+    ProjectsStore.loadInitialData([nextJsProject]);
 
     OrganizationStore.onUpdate(organization);
 
@@ -179,7 +166,7 @@ describe('Onboarding', function () {
       }
     );
 
-    expect(await screen.findAllByTestId('sidebar-error-indicator')).toHaveLength(2);
+    expect(await screen.findByText('Configure Next.js SDK')).toBeInTheDocument();
   });
 
   it('renders framework selection modal if vanilla js is selected', async function () {
@@ -190,14 +177,12 @@ describe('Onboarding', function () {
           platformToProjectIdMap: {
             javascript: 'javascript',
           },
-          selectedPlatforms: [
-            {
-              key: 'javascript',
-              type: 'language',
-              language: 'javascript',
-              category: 'browser',
-            },
-          ],
+          selectedPlatform: {
+            key: 'javascript',
+            type: 'language',
+            language: 'javascript',
+            category: 'browser',
+          },
         },
         jest.fn(),
       ]);
@@ -210,7 +195,7 @@ describe('Onboarding', function () {
       ...initializeOrg(),
       organization: {
         ...initializeOrg().organization,
-        features: ['onboarding-remove-multiselect-platform', 'onboarding-sdk-selection'],
+        features: ['onboarding-sdk-selection'],
       },
       router: {
         params: routeParams,
@@ -224,7 +209,7 @@ describe('Onboarding', function () {
           platformToProjectIdMap: {
             javascript: 'javascript',
           },
-          selectedPlatforms: [
+          selectedPlatform: [
             {
               key: 'javascript',
               type: 'language',
@@ -276,14 +261,12 @@ describe('Onboarding', function () {
           platformToProjectIdMap: {
             'javascript-react': 'javascript-react',
           },
-          selectedPlatforms: [
-            {
-              key: 'javascript-react',
-              type: 'framework',
-              language: 'javascript',
-              category: 'browser',
-            },
-          ],
+          selectedPlatform: {
+            key: 'javascript-react',
+            type: 'framework',
+            language: 'javascript',
+            category: 'browser',
+          },
         },
         jest.fn(),
       ]);
@@ -296,7 +279,7 @@ describe('Onboarding', function () {
       ...initializeOrg(),
       organization: {
         ...initializeOrg().organization,
-        features: ['onboarding-remove-multiselect-platform', 'onboarding-sdk-selection'],
+        features: ['onboarding-sdk-selection'],
       },
       router: {
         params: routeParams,
@@ -310,14 +293,12 @@ describe('Onboarding', function () {
           platformToProjectIdMap: {
             'javascript-react': 'javascript-react',
           },
-          selectedPlatforms: [
-            {
-              key: 'javascript-react',
-              type: 'framework',
-              language: 'javascript',
-              category: 'browser',
-            },
-          ],
+          selectedPlatform: {
+            key: 'javascript-react',
+            type: 'framework',
+            language: 'javascript',
+            category: 'browser',
+          },
         },
       },
     });

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