Browse Source

[ALRT-4] ref: Deprecate release thresholds feature (#70894)

Removes all Release Threshold UI components / logic

![Screenshot 2024-05-14 at 3 14
48 PM](https://github.com/getsentry/sentry/assets/6186377/c9fc61e1-d862-4802-b7e5-c4f159c1b999)

![Screenshot 2024-05-14 at 3 18
05 PM](https://github.com/getsentry/sentry/assets/6186377/839de616-c58e-4c9f-b7a0-b55203be4a52)
Nathan Hsieh 9 months ago
parent
commit
1f80c77dbf

+ 0 - 1
.github/CODEOWNERS

@@ -378,7 +378,6 @@ static/app/components/events/eventStatisticalDetector/                    @getse
 /static/app/components/superuserStaffAccessForm.tsx                     @getsentry/enterprise
 /static/app/constants/superuserAccessErrors.tsx                         @getsentry/enterprise
 /static/app/views/organizationStats                                     @getsentry/enterprise
-/static/app/views/releases/thresholdsList/                              @getsentry/enterprise
 /static/app/views/settings/organizationAuth/                            @getsentry/enterprise
 /static/app/views/settings/organizationMembers/inviteBanner.tsx         @getsentry/enterprise
 /tests/sentry/api/endpoints/test_auth*.py                               @getsentry/enterprise

+ 0 - 8
static/app/routes.tsx

@@ -1363,13 +1363,6 @@ function buildRoutes() {
       />
     </Fragment>
   );
-  const releaseThresholdRoutes = (
-    <Route path="/release-thresholds/" withOrgPath>
-      <IndexRoute
-        component={make(() => import('sentry/views/releases/thresholdsList'))}
-      />
-    </Route>
-  );
 
   const activityRoutes = (
     <Route
@@ -2081,7 +2074,6 @@ function buildRoutes() {
       {cronsRoutes}
       {replayRoutes}
       {releasesRoutes}
-      {releaseThresholdRoutes}
       {activityRoutes}
       {statsRoutes}
       {discoverRoutes}

+ 1 - 89
static/app/views/releases/components/header.tsx

@@ -1,71 +1,8 @@
-import {useState} from 'react';
-import type {InjectedRouter} from 'react-router';
-import styled from '@emotion/styled';
-import * as qs from 'query-string';
-
-import FeatureBadge from 'sentry/components/badge/featureBadge';
 import * as Layout from 'sentry/components/layouts/thirds';
 import {PageHeadingQuestionTooltip} from 'sentry/components/pageHeadingQuestionTooltip';
-import {TabList, Tabs} from 'sentry/components/tabs';
-import {Tooltip} from 'sentry/components/tooltip';
-import {SLOW_TOOLTIP_DELAY} from 'sentry/constants';
 import {t} from 'sentry/locale';
-import type {Organization} from 'sentry/types/organization';
-import {normalizeUrl} from 'sentry/utils/withDomainRequired';
-
-type Props = {
-  organization: Organization;
-  router: InjectedRouter;
-  hasV2ReleaseUIEnabled?: boolean;
-};
-
-const enum ReleaseTab {
-  RELEASES = 'releases',
-  RELEASE_THRESHOLDS = 'release-thresholds',
-}
-
-function Header({router, hasV2ReleaseUIEnabled = false, organization}: Props) {
-  const [selected, setSelected] = useState<ReleaseTab>(
-    router.location.pathname.includes('release-thresholds')
-      ? ReleaseTab.RELEASE_THRESHOLDS
-      : ReleaseTab.RELEASES
-  );
-
-  const location = router.location;
-  const {
-    cursor: _cursor,
-    page: _page,
-    view: _view,
-    ...queryParams
-  } = location?.query ?? {};
-
-  const tabs = hasV2ReleaseUIEnabled
-    ? [
-        {
-          key: ReleaseTab.RELEASES,
-          label: t('Monitor'),
-          description: '',
-          to: normalizeUrl(
-            `/organizations/${organization.slug}/releases/?${qs.stringify(queryParams)}`
-          ),
-        },
-        {
-          key: ReleaseTab.RELEASE_THRESHOLDS,
-          label: t('Thresholds'),
-          description:
-            'thresholds represent action alerts that will trigger once a threshold has been breached',
-          to: normalizeUrl(
-            `/organizations/${organization.slug}/release-thresholds/?${qs.stringify(queryParams)}`
-          ),
-          badge: <FeatureBadge type="alpha" />,
-        },
-      ]
-    : [];
-
-  const onTabSelect = (key: ReleaseTab) => {
-    setSelected(key);
-  };
 
+function Header() {
   return (
     <Layout.Header noActionWrap>
       <Layout.HeaderContent>
@@ -79,33 +16,8 @@ function Header({router, hasV2ReleaseUIEnabled = false, organization}: Props) {
           />
         </Layout.Title>
       </Layout.HeaderContent>
-      {hasV2ReleaseUIEnabled && (
-        <StyledTabs value={selected} onChange={onTabSelect}>
-          <TabList hideBorder>
-            {tabs.map(({label, description, key, to, badge}) => {
-              return (
-                <TabList.Item key={key} to={to} textValue={label}>
-                  <Tooltip
-                    title={description}
-                    position="bottom"
-                    isHoverable
-                    delay={SLOW_TOOLTIP_DELAY}
-                  >
-                    {label}
-                    {badge}
-                  </Tooltip>
-                </TabList.Item>
-              );
-            })}
-          </TabList>
-        </StyledTabs>
-      )}
     </Layout.Header>
   );
 }
 
 export default Header;
-
-const StyledTabs = styled(Tabs<ReleaseTab>)`
-  grid-column: 1/-1;
-`;

+ 0 - 13
static/app/views/releases/detail/overview/index.tsx

@@ -55,7 +55,6 @@ import OtherProjects from './sidebar/otherProjects';
 import ProjectReleaseDetails from './sidebar/projectReleaseDetails';
 import ReleaseAdoption from './sidebar/releaseAdoption';
 import ReleaseStats from './sidebar/releaseStats';
-import ThresholdStatuses from './sidebar/thresholdStatuses';
 import TotalCrashFreeUsers from './sidebar/totalCrashFreeUsers';
 import ReleaseArchivedNotice from './releaseArchivedNotice';
 import ReleaseComparisonChart from './releaseComparisonChart';
@@ -363,10 +362,6 @@ class ReleaseOverview extends DeprecatedAsyncView<Props> {
   render() {
     const {organization, selection, location, api} = this.props;
     const {start, end, period, utc} = this.pageDateTime;
-    const hasV2ReleaseUIEnabled =
-      organization.features.includes('releases-v2-internal') ||
-      organization.features.includes('releases-v2') ||
-      organization.features.includes('releases-v2-st');
 
     return (
       <ReleaseContext.Consumer>
@@ -584,14 +579,6 @@ class ReleaseOverview extends DeprecatedAsyncView<Props> {
                             release={release}
                             project={project}
                           />
-                          {hasV2ReleaseUIEnabled && (
-                            <ThresholdStatuses
-                              project={project}
-                              release={release}
-                              organization={organization}
-                              selectedEnvs={selection.environments}
-                            />
-                          )}
                           {hasHealthData && (
                             <ReleaseAdoption
                               releaseSessions={thisRelease}

+ 0 - 110
static/app/views/releases/detail/overview/sidebar/thresholdStatuses.tsx

@@ -1,110 +0,0 @@
-import {useCallback, useEffect, useState} from 'react';
-import styled from '@emotion/styled';
-
-import type {Client} from 'sentry/api';
-import * as SidebarSection from 'sentry/components/sidebarSection';
-import {IconCheckmark, IconWarning} from 'sentry/icons';
-import {t} from 'sentry/locale';
-import {space} from 'sentry/styles/space';
-import type {Organization, Release, ReleaseProject} from 'sentry/types';
-import {getExactDuration} from 'sentry/utils/formatters';
-import useApi from 'sentry/utils/useApi';
-
-import {
-  ReleaseDetailsTable,
-  ReleaseDetailsTableRow,
-} from '../../../components/releaseDetailsSideTable';
-import {fetchThresholdStatuses} from '../../../utils/fetchThresholdStatus';
-import type {ThresholdStatus, ThresholdStatusesQuery} from '../../../utils/types';
-
-type Props = {
-  organization: Organization;
-  project: Required<ReleaseProject>;
-  release: Release;
-  selectedEnvs: string[];
-};
-
-function ThresholdStatuses({project, release, organization, selectedEnvs}: Props) {
-  const api: Client = useApi();
-  const [thresholdStatuses, setThresholdStatuses] = useState<ThresholdStatus[]>([]);
-  const fetchThresholdStatusCallback = useCallback(async () => {
-    const fuzzSec = 30;
-    const start = new Date(new Date(release.dateCreated).getTime() - fuzzSec * 1000);
-    const end = new Date(new Date(release.dateCreated).getTime() + fuzzSec * 1000);
-
-    const releaseVersion: string = release.version;
-
-    const query: ThresholdStatusesQuery = {
-      start: start.toISOString(),
-      end: end.toISOString(),
-      release: [releaseVersion],
-      projectSlug: [project.slug],
-    };
-    if (selectedEnvs.length) {
-      query.environment = selectedEnvs;
-    }
-    return await fetchThresholdStatuses(organization, api, query);
-  }, [release, project, selectedEnvs, organization, api]);
-
-  useEffect(() => {
-    fetchThresholdStatusCallback().then(thresholds => {
-      const list = thresholds[`${project.slug}-${release.version}`];
-      const sorted =
-        list?.sort((a, b) => {
-          const keyA: string = a.environment ? a.environment.name : '';
-          const keyB: string = b.environment ? b.environment.name : '';
-
-          return keyA.localeCompare(keyB);
-        }) || [];
-
-      setThresholdStatuses(sorted);
-    });
-  }, [fetchThresholdStatusCallback, project, release]);
-
-  if (thresholdStatuses.length > 0) {
-    return (
-      <SidebarSection.Wrap>
-        <SidebarSection.Title>{t('Threshold Statuses')}</SidebarSection.Title>
-        <SidebarSection.Content>
-          <ReleaseDetailsTable>
-            {thresholdStatuses?.map(status => (
-              <ReleaseDetailsTableRow
-                key={status.id}
-                type={status.is_healthy ? undefined : 'error'}
-              >
-                <RowGrid>
-                  <div>{status.environment?.name}</div>
-                  <div>{getExactDuration(status.window_in_seconds, true, 'seconds')}</div>
-                  <div>
-                    {status.threshold_type} {status.trigger_type === 'over' ? '>' : '<'}{' '}
-                    {status.value}
-                  </div>
-                  <AlignRight>
-                    {status.is_healthy ? (
-                      <IconCheckmark color="successText" size="xs" />
-                    ) : (
-                      <IconWarning color="errorText" size="xs" />
-                    )}
-                  </AlignRight>
-                </RowGrid>
-              </ReleaseDetailsTableRow>
-            ))}
-          </ReleaseDetailsTable>
-        </SidebarSection.Content>
-      </SidebarSection.Wrap>
-    );
-  }
-  return null;
-}
-
-const AlignRight = styled('div')`
-  text-align: right;
-`;
-
-const RowGrid = styled('div')`
-  display: grid;
-  grid-template-columns: 0.5fr 0.5fr max-content 0.1fr;
-  gap: ${space(1)};
-`;
-
-export default ThresholdStatuses;

+ 4 - 105
static/app/views/releases/list/index.tsx

@@ -54,13 +54,6 @@ import Header from '../components/header';
 import ReleaseFeedbackBanner from '../components/releaseFeedbackBanner';
 import ReleaseArchivedNotice from '../detail/overview/releaseArchivedNotice';
 import {isMobileRelease} from '../utils';
-import {fetchThresholdStatuses} from '../utils/fetchThresholdStatus';
-import type {
-  Threshold,
-  ThresholdQuery,
-  ThresholdStatus,
-  ThresholdStatusesQuery,
-} from '../utils/types';
 
 import ReleaseCard from './releaseCard';
 import ReleasesAdoptionChart from './releasesAdoptionChart';
@@ -83,24 +76,18 @@ type Props = RouteComponentProps<RouteParams, {}> & {
 
 type State = {
   releases: Release[];
-  thresholds: Threshold[];
-  thresholdStatuses?: {[key: string]: ThresholdStatus[]};
 } & DeprecatedAsyncView['state'];
 
 class ReleasesList extends DeprecatedAsyncView<Props, State> {
   shouldReload = true;
   shouldRenderBadRequests = true;
-  hasV2ReleaseUIEnabled =
-    this.props.organization.features.includes('releases-v2-internal') ||
-    this.props.organization.features.includes('releases-v2') ||
-    this.props.organization.features.includes('releases-v2-st');
 
   getTitle() {
     return routeTitleGen(t('Releases'), this.props.organization.slug, false);
   }
 
   getEndpoints(): ReturnType<DeprecatedAsyncView['getEndpoints']> {
-    const {organization, location, selection} = this.props;
+    const {organization, location} = this.props;
     const {statsPeriod} = location.query;
     const activeSort = this.getSort();
     const activeStatus = this.getStatus();
@@ -125,27 +112,6 @@ class ReleasesList extends DeprecatedAsyncView<Props, State> {
         {disableEntireQuery: true}, // options - prevent cursor from being passed into query
       ],
     ];
-
-    if (this.hasV2ReleaseUIEnabled) {
-      // prefetch all thresholds so we know whether to expect a threshold or not
-      const thresholdQuery: ThresholdQuery = {};
-      if (selection.projects.length) {
-        thresholdQuery.project = selection.projects;
-      } else {
-        thresholdQuery.project = [ALL_ACCESS_PROJECTS];
-      }
-      if (selection.environments.length) {
-        thresholdQuery.environment = selection.environments;
-      }
-
-      endpoints.push([
-        'thresholds',
-        `/organizations/${organization.slug}/release-thresholds/`,
-        {query: thresholdQuery},
-        {disableEntireQuery: true}, // options to prevent cursor from being passed
-      ]);
-    }
-
     return endpoints;
   }
 
@@ -163,52 +129,7 @@ class ReleasesList extends DeprecatedAsyncView<Props, State> {
        * uses shouldReload=true and there is no reloading happening.
        */
       forceCheck();
-      if (this.hasV2ReleaseUIEnabled) {
-        // Refetch new threshold statuses if  new releases are fetched
-        this.fetchThresholdStatuses();
-      }
-    }
-  }
-
-  fetchThresholdStatuses() {
-    const {selection, organization, api} = this.props;
-    const {releases} = this.state;
-    if (releases.length < 1) {
-      return;
-    }
-
-    // Grab earliest release and latest release - then fetch all statuses within
-    const fuzzSec = 30;
-    const initialRelease = releases[0];
-    let start = new Date(new Date(initialRelease.dateCreated).getTime() - fuzzSec * 1000);
-    let end = new Date(new Date(initialRelease.dateCreated).getTime() + fuzzSec * 1000);
-    const releaseVersions: string[] = [];
-    releases.forEach(release => {
-      const created = new Date(release.dateCreated);
-      if (created < start) {
-        start = created;
-      }
-      if (created > end) {
-        end = created;
-      }
-      releaseVersions.push(release.version);
-    });
-
-    const query: ThresholdStatusesQuery = {
-      start: start.toISOString(),
-      end: end.toISOString(),
-      release: releaseVersions,
-    };
-    if (selection.projects.length) {
-      query.projectSlug = this.getSelectedProjectSlugs();
-    }
-    if (selection.environments.length) {
-      query.environment = selection.environments;
     }
-
-    fetchThresholdStatuses(organization, api, query).then(thresholdStatuses => {
-      this.setState({thresholdStatuses});
-    });
   }
 
   getQuery() {
@@ -280,21 +201,6 @@ class ReleasesList extends DeprecatedAsyncView<Props, State> {
     return this.getSelectedProject()?.hasSessions ?? null;
   }
 
-  getThresholdsForRelease(release: Release): Threshold[] {
-    if (!this.hasV2ReleaseUIEnabled) {
-      return [];
-    }
-    const {thresholds} = this.state;
-    const lastDeploy = release.lastDeploy;
-    const projectSlugs = release.projects.map(p => p.slug);
-
-    return thresholds.filter(
-      threshold =>
-        projectSlugs.includes(threshold.project.slug) &&
-        lastDeploy?.environment === threshold.environment?.name
-    );
-  }
-
   handleSearch = (query: string) => {
     const {location, router} = this.props;
 
@@ -544,7 +450,7 @@ class ReleasesList extends DeprecatedAsyncView<Props, State> {
     showReleaseAdoptionStages: boolean
   ) {
     const {location, selection, organization, router} = this.props;
-    const {releases, reloading, releasesPageLinks, thresholdStatuses} = this.state;
+    const {releases, reloading, releasesPageLinks} = this.state;
 
     const selectedProject = this.getSelectedProject();
     const hasReleasesSetup = selectedProject?.features.includes('releases');
@@ -605,8 +511,6 @@ class ReleasesList extends DeprecatedAsyncView<Props, State> {
                   isTopRelease={index === 0}
                   getHealthData={getHealthData}
                   showReleaseAdoptionStages={showReleaseAdoptionStages}
-                  thresholds={this.getThresholdsForRelease(release)}
-                  thresholdStatuses={thresholdStatuses || {}}
                 />
               ))}
               <Pagination pageLinks={releasesPageLinks} />
@@ -618,7 +522,7 @@ class ReleasesList extends DeprecatedAsyncView<Props, State> {
   }
 
   renderBody() {
-    const {organization, selection, router} = this.props;
+    const {organization, selection} = this.props;
     const {releases, reloading, error} = this.state;
 
     const activeSort = this.getSort();
@@ -635,12 +539,7 @@ class ReleasesList extends DeprecatedAsyncView<Props, State> {
     return (
       <PageFiltersContainer showAbsolute={false}>
         <NoProjectMessage organization={organization}>
-          <Header
-            router={router}
-            hasV2ReleaseUIEnabled={this.hasV2ReleaseUIEnabled}
-            organization={organization}
-          />
-
+          <Header />
           <Layout.Body>
             <Layout.Main fullWidth>
               {organization.features.includes('releases-v2-banner') && (

+ 4 - 30
static/app/views/releases/list/releaseCard/index.tsx

@@ -18,7 +18,6 @@ import {t, tct, tn} from 'sentry/locale';
 import {space} from 'sentry/styles/space';
 import type {Organization, PageFilters, Release} from 'sentry/types';
 
-import type {Threshold, ThresholdStatus} from '../../utils/types';
 import type {ReleasesDisplayOption} from '../releasesDisplayOptions';
 import type {ReleasesRequestRenderProps} from '../releasesRequest';
 
@@ -55,8 +54,6 @@ type Props = {
   selection: PageFilters;
   showHealthPlaceholders: boolean;
   showReleaseAdoptionStages: boolean;
-  thresholdStatuses: {[key: string]: ThresholdStatus[]};
-  thresholds: Threshold[];
 };
 
 function ReleaseCard({
@@ -70,8 +67,6 @@ function ReleaseCard({
   isTopRelease,
   getHealthData,
   showReleaseAdoptionStages,
-  thresholdStatuses,
-  thresholds,
 }: Props) {
   const {
     version,
@@ -96,8 +91,6 @@ function ReleaseCard({
     );
   }, [projects, selection.projects]);
 
-  const hasThresholds = thresholds.length > 0;
-
   const getHiddenProjectsTooltip = () => {
     const limitedProjects = projectsToHide.map(p => p.slug).slice(0, 5);
     const remainderLength = projectsToHide.length - limitedProjects.length;
@@ -149,10 +142,7 @@ function ReleaseCard({
       <ReleaseProjects>
         {/* projects is the table */}
         <ReleaseProjectsHeader lightText>
-          <ReleaseProjectsLayout
-            showReleaseAdoptionStages={showReleaseAdoptionStages}
-            hasThresholds={hasThresholds}
-          >
+          <ReleaseProjectsLayout showReleaseAdoptionStages={showReleaseAdoptionStages}>
             <ReleaseProjectColumn>{t('Project Name')}</ReleaseProjectColumn>
             {showReleaseAdoptionStages && (
               <AdoptionStageColumn>{t('Adoption Stage')}</AdoptionStageColumn>
@@ -164,7 +154,6 @@ function ReleaseCard({
             <CrashFreeRateColumn>{t('Crash Free Rate')}</CrashFreeRateColumn>
             <DisplaySmallCol>{t('Crashes')}</DisplaySmallCol>
             <NewIssuesColumn>{t('New Issues')}</NewIssuesColumn>
-            {hasThresholds && <DisplaySmallCol>{t('Thresholds')}</DisplaySmallCol>}
           </ReleaseProjectsLayout>
         </ReleaseProjectsHeader>
 
@@ -187,27 +176,20 @@ function ReleaseCard({
           >
             {projectsToShow.map((project, index) => {
               const key = `${project.slug}-${version}`;
-              const projectThresholds = thresholds.filter(
-                threshold => threshold.project.slug === project.slug
-              );
               return (
                 <ReleaseCardProjectRow
                   key={`${key}-row`}
                   activeDisplay={activeDisplay}
                   adoptionStages={adoptionStages}
                   getHealthData={getHealthData}
-                  hasThresholds={hasThresholds}
-                  expectedThresholds={projectThresholds.length}
                   index={index}
                   isTopRelease={isTopRelease}
                   location={location}
                   organization={organization}
                   project={project}
                   releaseVersion={version}
-                  lastDeploy={lastDeploy}
                   showPlaceholders={showHealthPlaceholders}
                   showReleaseAdoptionStages={showReleaseAdoptionStages}
-                  thresholdStatuses={hasThresholds ? thresholdStatuses[`${key}`] : []}
                 />
               );
             })}
@@ -332,7 +314,6 @@ const CollapseButtonWrapper = styled('div')`
 `;
 
 export const ReleaseProjectsLayout = styled('div')<{
-  hasThresholds?: boolean;
   showReleaseAdoptionStages?: boolean;
 }>`
   display: grid;
@@ -343,24 +324,17 @@ export const ReleaseProjectsLayout = styled('div')<{
   width: 100%;
 
   @media (min-width: ${p => p.theme.breakpoints.small}) {
-    ${p => {
-      const thresholdSize = p.hasThresholds ? '0.5fr' : '';
-      return `grid-template-columns: 1fr 1fr 1fr 0.5fr 0.5fr ${thresholdSize} 0.5fr`;
-    }}
+    grid-template-columns: 1fr 1fr 1fr 0.5fr 0.5fr 0.5fr;
   }
 
   @media (min-width: ${p => p.theme.breakpoints.medium}) {
-    ${p => {
-      const thresholdSize = p.hasThresholds ? '0.5fr' : '';
-      return `grid-template-columns: 1fr 1fr 1fr 0.5fr 0.5fr ${thresholdSize} 0.5fr`;
-    }}
+    grid-template-columns: 1fr 1fr 1fr 0.5fr 0.5fr 0.5fr;
   }
 
   @media (min-width: ${p => p.theme.breakpoints.xlarge}) {
     ${p => {
       const adoptionStagesSize = p.showReleaseAdoptionStages ? '0.7fr' : '';
-      const thresholdSize = p.hasThresholds ? '0.7fr' : '';
-      return `grid-template-columns: 1fr ${adoptionStagesSize} 1fr 1fr 0.7fr 0.7fr ${thresholdSize} 0.5fr`;
+      return `grid-template-columns: 1fr ${adoptionStagesSize} 1fr 1fr 0.7fr 0.7fr 0.5fr`;
     }}
   }
 `;

+ 2 - 95
static/app/views/releases/list/releaseCard/releaseCardProjectRow.tsx

@@ -1,4 +1,3 @@
-import {useMemo} from 'react';
 import LazyLoad from 'react-lazyload';
 import {useTheme} from '@emotion/react';
 import styled from '@emotion/styled';
@@ -20,7 +19,7 @@ import {Tooltip} from 'sentry/components/tooltip';
 import {IconCheckmark, IconFire, IconWarning} from 'sentry/icons';
 import {t, tn} from 'sentry/locale';
 import {space} from 'sentry/styles/space';
-import type {Deploy, Organization, Release, ReleaseProject} from 'sentry/types';
+import type {Organization, Release, ReleaseProject} from 'sentry/types';
 import {defined} from 'sentry/utils';
 import type {IconSize} from 'sentry/utils/theme';
 
@@ -31,7 +30,6 @@ import {
   getReleaseUnhandledIssuesUrl,
   isMobileRelease,
 } from '../../utils';
-import type {ThresholdStatus} from '../../utils/types';
 import {ReleasesDisplayOption} from '../releasesDisplayOptions';
 import type {ReleasesRequestRenderProps} from '../releasesRequest';
 
@@ -62,9 +60,7 @@ function getCrashFreeIcon(crashFreePercent: number, iconSize: IconSize = 'sm') {
 
 type Props = {
   activeDisplay: ReleasesDisplayOption;
-  expectedThresholds: number;
   getHealthData: ReleasesRequestRenderProps['getHealthData'];
-  hasThresholds: boolean;
   index: number;
   isTopRelease: boolean;
   location: Location;
@@ -73,27 +69,21 @@ type Props = {
   releaseVersion: string;
   showPlaceholders: boolean;
   showReleaseAdoptionStages: boolean;
-  thresholdStatuses: ThresholdStatus[];
   adoptionStages?: Release['adoptionStages'];
-  lastDeploy?: Deploy | undefined;
 };
 
 function ReleaseCardProjectRow({
   activeDisplay,
   adoptionStages,
-  expectedThresholds,
   getHealthData,
-  hasThresholds,
   index,
   isTopRelease,
-  lastDeploy,
   location,
   organization,
   project,
   releaseVersion,
   showPlaceholders,
   showReleaseAdoptionStages,
-  thresholdStatuses,
 }: Props) {
   const theme = useTheme();
   const {id, newGroups} = project;
@@ -104,22 +94,6 @@ function ReleaseCardProjectRow({
     ReleasesDisplayOption.SESSIONS
   );
 
-  const thresholdEnvStatuses = useMemo(() => {
-    return (
-      thresholdStatuses?.filter(status => {
-        return status.environment?.name === lastDeploy?.environment;
-      }) || []
-    );
-  }, [thresholdStatuses, lastDeploy]);
-
-  const healthyThresholdStatuses = thresholdEnvStatuses.filter(status => {
-    return status.is_healthy;
-  });
-
-  const pendingThresholdStatuses = thresholdEnvStatuses.filter(status => {
-    return new Date(status.end || '') > new Date();
-  });
-
   const crashFreeRate = getHealthData.getCrashFreeRate(releaseVersion, id, activeDisplay);
   const get24hCountByProject = getHealthData.get24hCountByProject(id, activeDisplay);
   const timeSeries = getHealthData.getTimeSeries(releaseVersion, id, activeDisplay);
@@ -137,10 +111,7 @@ function ReleaseCardProjectRow({
 
   return (
     <ProjectRow data-test-id="release-card-project-row">
-      <ReleaseProjectsLayout
-        showReleaseAdoptionStages={showReleaseAdoptionStages}
-        hasThresholds={hasThresholds}
-      >
+      <ReleaseProjectsLayout showReleaseAdoptionStages={showReleaseAdoptionStages}>
         <ReleaseProjectColumn>
           <ProjectBadge project={project} avatarSize={16} />
         </ReleaseProjectColumn>
@@ -238,54 +209,6 @@ function ReleaseCardProjectRow({
           </Tooltip>
         </NewIssuesColumn>
 
-        {hasThresholds && (
-          <DisplaySmallCol>
-            {/* TODO: link to release details page */}
-            {expectedThresholds > 0 && (
-              <Tooltip
-                title={
-                  <div>
-                    <div>
-                      {pendingThresholdStatuses.length !== thresholdEnvStatuses.length &&
-                        `${
-                          healthyThresholdStatuses.length -
-                          pendingThresholdStatuses.length
-                        } / ${thresholdEnvStatuses.length} ` + t('thresholds succeeded')}
-                    </div>
-                    {pendingThresholdStatuses.length > 0 && (
-                      <div>
-                        {`${pendingThresholdStatuses.length} / ${thresholdEnvStatuses.length} ` +
-                          t('still pending')}
-                      </div>
-                    )}
-                    {thresholdEnvStatuses.length !== expectedThresholds && (
-                      <div>{`... / ${expectedThresholds}`}</div>
-                    )}
-                    {t('Open in Release Details')}
-                  </div>
-                }
-              >
-                <ThresholdHealth
-                  loading={thresholdEnvStatuses.length !== expectedThresholds}
-                  allHealthy={
-                    thresholdEnvStatuses.length === expectedThresholds &&
-                    healthyThresholdStatuses.length === expectedThresholds
-                  }
-                  allThresholdsFinished={
-                    pendingThresholdStatuses.length === 0 &&
-                    thresholdEnvStatuses.length === expectedThresholds
-                  }
-                >
-                  {thresholdEnvStatuses.length === expectedThresholds
-                    ? healthyThresholdStatuses.length
-                    : '...'}{' '}
-                  / {expectedThresholds}
-                </ThresholdHealth>
-              </Tooltip>
-            )}
-          </DisplaySmallCol>
-        )}
-
         <ViewColumn>
           <GuideAnchor disabled={!isTopRelease || index !== 0} target="view_release">
             <Button
@@ -350,19 +273,3 @@ const ViewColumn = styled('div')`
   line-height: 20px;
   text-align: right;
 `;
-
-const ThresholdHealth = styled('div')<{
-  allHealthy?: boolean;
-  allThresholdsFinished?: boolean;
-  loading?: boolean;
-}>`
-  color: ${p => {
-    if (!p.loading && !p.allHealthy) {
-      return p.theme.errorText;
-    }
-    if (!p.loading && p.allThresholdsFinished) {
-      return p.theme.successText;
-    }
-    return p.theme.activeText;
-  }};
-`;

+ 0 - 107
static/app/views/releases/thresholdsList/index.spec.tsx

@@ -1,107 +0,0 @@
-import {OrganizationFixture} from 'sentry-fixture/organization';
-
-import {initializeOrg} from 'sentry-test/initializeOrg';
-import {render, screen} from 'sentry-test/reactTestingLibrary';
-
-import PageFiltersStore from 'sentry/stores/pageFiltersStore';
-import {normalizeUrl} from 'sentry/utils/withDomainRequired';
-import ThresholdsList from 'sentry/views/releases/thresholdsList/';
-
-describe('ReleaseThresholdsList', () => {
-  const organization = OrganizationFixture({
-    slug: 'test-thresholds',
-    features: ['releases-v2'],
-  });
-
-  const {router, routerContext} = initializeOrg({
-    organization,
-  });
-
-  let mockThresholdFetch;
-
-  beforeEach(() => {
-    mockThresholdFetch = MockApiClient.addMockResponse({
-      url: `/organizations/${organization.slug}/release-thresholds/`,
-      method: 'GET',
-      body: [],
-    });
-    PageFiltersStore.init();
-  });
-
-  afterEach(() => {
-    PageFiltersStore.reset();
-    MockApiClient.clearMockResponses();
-  });
-
-  it('redirects to releases if flag is not set', () => {
-    const organization2 = OrganizationFixture({
-      slug: 'test-thresholds-no-flag',
-      features: [],
-    });
-
-    const {router: flaglessRouter, routerContext: flaglessRouterContext} = initializeOrg({
-      organization: organization2,
-    });
-    const expectedRedirect = normalizeUrl(
-      `/organizations/${organization2.slug}/releases/`
-    );
-    render(<ThresholdsList />, {
-      context: flaglessRouterContext,
-      organization: organization2,
-    });
-
-    expect(flaglessRouter.replace).toHaveBeenCalledTimes(1);
-    expect(flaglessRouter.replace).toHaveBeenCalledWith(expectedRedirect);
-  });
-
-  it('fetches release thresholds for selected projects', async () => {
-    const query = {
-      project: [-1],
-    };
-    PageFiltersStore.onInitializeUrlState(
-      {
-        projects: [],
-        environments: [],
-        datetime: {start: null, end: null, period: '1d', utc: null},
-      },
-      new Set()
-    );
-    routerContext.context.location.pathname = normalizeUrl(
-      `/organizations/${organization.slug}/release-thresholds/`
-    );
-    render(<ThresholdsList />, {
-      context: routerContext,
-      organization,
-    });
-    expect(await screen.findByText('Thresholds')).toBeInTheDocument();
-    expect(mockThresholdFetch).toHaveBeenCalledWith(
-      '/organizations/test-thresholds/release-thresholds/',
-      expect.objectContaining({query})
-    );
-    expect(router.replace).not.toHaveBeenCalled();
-  });
-
-  it('filters fetch based on projects/environments selected', async () => {
-    const expectedQuery = {
-      project: [1, 2, 3],
-      environment: ['foo'],
-    };
-    PageFiltersStore.onInitializeUrlState(
-      {
-        projects: [1, 2, 3],
-        environments: ['foo'],
-        datetime: {start: null, end: null, period: '14d', utc: null},
-      },
-      new Set()
-    );
-    routerContext.context.location.pathname = normalizeUrl(
-      `/organizations/${organization.slug}/release-thresholds/`
-    );
-    render(<ThresholdsList />, {context: routerContext, organization});
-    expect(await screen.findByText('Thresholds')).toBeInTheDocument();
-    expect(mockThresholdFetch).toHaveBeenCalledWith(
-      '/organizations/test-thresholds/release-thresholds/',
-      expect.objectContaining({query: expectedQuery})
-    );
-  });
-});

+ 0 - 312
static/app/views/releases/thresholdsList/index.tsx

@@ -1,312 +0,0 @@
-import {useEffect, useMemo, useState} from 'react';
-import styled from '@emotion/styled';
-
-import GuideAnchor from 'sentry/components/assistant/guideAnchor';
-import {Button} from 'sentry/components/button';
-import * as Layout from 'sentry/components/layouts/thirds';
-import LoadingError from 'sentry/components/loadingError';
-import LoadingIndicator from 'sentry/components/loadingIndicator';
-import NoProjectMessage from 'sentry/components/noProjectMessage';
-import {EnvironmentPageFilter} from 'sentry/components/organizations/environmentPageFilter';
-import PageFilterBar from 'sentry/components/organizations/pageFilterBar';
-import PageFiltersContainer from 'sentry/components/organizations/pageFilters/container';
-import {ProjectPageFilter} from 'sentry/components/organizations/projectPageFilter';
-import {ALL_ACCESS_PROJECTS} from 'sentry/constants/pageFilters';
-import {IconChevron} from 'sentry/icons';
-import {t} from 'sentry/locale';
-import ConfigStore from 'sentry/stores/configStore';
-import {space} from 'sentry/styles/space';
-import type {Project} from 'sentry/types/project';
-import useOrganization from 'sentry/utils/useOrganization';
-import usePageFilters from 'sentry/utils/usePageFilters';
-import useProjects from 'sentry/utils/useProjects';
-import useRouter from 'sentry/utils/useRouter';
-import {normalizeUrl} from 'sentry/utils/withDomainRequired';
-
-import Header from '../components/header';
-import type {Threshold} from '../utils/types';
-import useFetchThresholdsListData from '../utils/useFetchThresholdsListData';
-
-import NoThresholdCard from './noThresholdCard';
-import ThresholdGroupTable from './thresholdGroupTable';
-
-type Props = {};
-
-function ReleaseThresholdList({}: Props) {
-  const [listError, setListError] = useState<string>('');
-  const [newProjThresholdsPage, setNewProjThresholdsPage] = useState(0);
-  const PAGE_SIZE = 10;
-  const router = useRouter();
-  const organization = useOrganization();
-
-  useEffect(() => {
-    const hasV2ReleaseUIEnabled =
-      organization.features.includes('releases-v2-internal') ||
-      organization.features.includes('releases-v2') ||
-      organization.features.includes('releases-v2-st');
-    if (!hasV2ReleaseUIEnabled) {
-      const redirect = normalizeUrl(`/organizations/${organization.slug}/releases/`);
-      router.replace(redirect);
-    }
-  }, [router, organization]);
-
-  const {projects} = useProjects();
-  const {selection} = usePageFilters();
-  const {
-    data: thresholds = [],
-    error: requestError,
-    isLoading,
-    isError,
-    refetch,
-  } = useFetchThresholdsListData({
-    selectedProjectIds: selection.projects,
-    selectedEnvs: selection.environments,
-  });
-
-  const selectedProjects: Project[] = useMemo(
-    () =>
-      projects.filter(
-        project =>
-          selection.projects.some(id => {
-            const strId = String(id);
-            return strId === project.id || id === -1;
-          }) || !selection.projects.length
-      ),
-    [projects, selection.projects]
-  );
-
-  const projectsById: {[key: string]: Project} = useMemo(() => {
-    const byId = {};
-    selectedProjects.forEach(proj => {
-      byId[proj.id] = proj;
-      // adding slug for migration to MetricAlerts, we only have slug in MetricAlerts
-      byId[proj.slug] = proj;
-    });
-    return byId;
-  }, [selectedProjects]);
-
-  const getAllEnvironmentNames = useMemo((): string[] => {
-    const selectedProjectIds = selection.projects.map(id => String(id));
-    const {user} = ConfigStore.getState();
-    const allEnvSet = new Set(projects.flatMap(project => project.environments));
-    // NOTE: mostly taken from environmentSelector.tsx
-    const unSortedEnvs = new Set(
-      projects.flatMap(project => {
-        /**
-         * Include environments from:
-         * all projects I can access if -1 is the only selected project.
-         * all member projects if 'my projects' (empty list) is selected.
-         * all projects if the user is a superuser
-         * the requested projects
-         */
-        const allProjectsSelectedICanAccess =
-          selectedProjectIds.length === 1 &&
-          selectedProjectIds[0] === String(ALL_ACCESS_PROJECTS) &&
-          project.hasAccess;
-        const myProjectsSelected = selectedProjectIds.length === 0 && project.isMember;
-        const allMemberProjectsIfSuperuser =
-          selectedProjectIds.length === 0 && user.isSuperuser;
-        if (
-          allProjectsSelectedICanAccess ||
-          myProjectsSelected ||
-          allMemberProjectsIfSuperuser ||
-          selectedProjectIds.includes(project.id)
-        ) {
-          return project.environments;
-        }
-
-        return [];
-      })
-    );
-    const envDiff = new Set([...allEnvSet].filter(x => !unSortedEnvs.has(x)));
-
-    // bubble the selected projects envs first, then concat the rest of the envs
-    return Array.from(unSortedEnvs)
-      .sort()
-      .concat([...envDiff].sort());
-  }, [projects, selection.projects]);
-
-  const getEnvironmentsAvailableToProject = useMemo((): string[] => {
-    const selectedProjectIds = selection.projects.map(id => String(id));
-    const allEnvSet = new Set(projects.flatMap(project => project.environments));
-    // NOTE: mostly taken from environmentSelector.tsx
-    const unSortedEnvs = new Set(
-      projects.flatMap(project => {
-        /**
-         * Include environments from:
-         * all projects if -1 is the only selected project.
-         * all member projects if 'my projects' (empty list) is selected.
-         * the requested projects
-         */
-        const allProjectsSelected =
-          selectedProjectIds.length === 1 &&
-          selectedProjectIds[0] === String(ALL_ACCESS_PROJECTS) &&
-          project.hasAccess;
-        const myProjectsSelected = selectedProjectIds.length === 0 && project.isMember;
-        if (
-          allProjectsSelected ||
-          myProjectsSelected ||
-          selectedProjectIds.includes(project.id)
-        ) {
-          return project.environments;
-        }
-
-        return [];
-      })
-    );
-    const envDiff = new Set([...allEnvSet].filter(x => !unSortedEnvs.has(x)));
-
-    // bubble the selected projects envs first, then concat the rest of the envs
-    return Array.from(unSortedEnvs)
-      .sort()
-      .concat([...envDiff].sort());
-  }, [projects, selection.projects]);
-
-  /**
-   * Thresholds filtered by environment selection
-   * NOTE: currently no way to filter for 'None' environments
-   */
-  const filteredThresholds = selection.environments.length
-    ? thresholds.filter(threshold => {
-        return threshold.environment?.name
-          ? selection.environments.indexOf(threshold.environment.name) > -1
-          : !selection.environments.length;
-      })
-    : thresholds;
-
-  const thresholdsByProject: {[key: string]: Threshold[]} = useMemo(() => {
-    const byProj = {};
-
-    filteredThresholds.forEach(threshold => {
-      const selectedProject = selection.projects[0] !== -1 ? selection.projects[0] : null;
-      const projId = threshold.project.id ?? selectedProject;
-      (byProj[projId] ??= []).push(threshold);
-    });
-    return byProj;
-  }, [filteredThresholds, selection.projects]);
-
-  const projectsWithoutThresholds: Project[] = useMemo(() => {
-    // TODO: limit + paginate list
-    return selectedProjects.filter(
-      proj => !thresholdsByProject[proj.id] && !thresholdsByProject[proj.slug]
-    );
-  }, [thresholdsByProject, selectedProjects]);
-
-  const setTempError = msg => {
-    setListError(msg);
-    setTimeout(() => setListError(''), 5000);
-  };
-
-  if (isError) return <LoadingError onRetry={refetch} message={requestError.message} />;
-  if (isLoading) return <LoadingIndicator />;
-
-  return (
-    <PageFiltersContainer>
-      <NoProjectMessage organization={organization}>
-        <Header router={router} hasV2ReleaseUIEnabled organization={organization} />
-        <Layout.Body>
-          <Layout.Main fullWidth>
-            <FilterRow>
-              <ReleaseThresholdsPageFilterBar condensed>
-                <GuideAnchor target="release_projects">
-                  <ProjectPageFilter />
-                </GuideAnchor>
-                <EnvironmentPageFilter />
-              </ReleaseThresholdsPageFilterBar>
-              <ListError>{listError}</ListError>
-            </FilterRow>
-            {thresholdsByProject &&
-              Object.entries(thresholdsByProject).map(([projId, thresholdsByProj]) => (
-                <ThresholdGroupTable
-                  key={projId}
-                  project={projectsById[projId]}
-                  thresholds={thresholdsByProj}
-                  isLoading={isLoading}
-                  isError={isError}
-                  refetch={refetch}
-                  setTempError={setTempError}
-                  allEnvironmentNames={getEnvironmentsAvailableToProject}
-                />
-              ))}
-            {projectsWithoutThresholds.length > 0 && (
-              <div>
-                <strong>Projects without Thresholds</strong>
-                {projectsWithoutThresholds
-                  .slice(
-                    PAGE_SIZE * newProjThresholdsPage,
-                    PAGE_SIZE * newProjThresholdsPage + PAGE_SIZE
-                  )
-                  .map(proj => (
-                    <NoThresholdCard
-                      key={proj.id}
-                      project={proj}
-                      allEnvironmentNames={getAllEnvironmentNames}
-                      refetch={refetch}
-                      setTempError={setTempError}
-                    />
-                  ))}
-                <Paginator>
-                  <Button
-                    icon={<IconChevron direction="left" size="sm" />}
-                    aria-label={t('Previous')}
-                    size="sm"
-                    disabled={newProjThresholdsPage === 0}
-                    onClick={() => {
-                      setNewProjThresholdsPage(newProjThresholdsPage - 1);
-                    }}
-                  />
-                  <CurrentPage>
-                    {newProjThresholdsPage + 1} of{' '}
-                    {Math.ceil(projectsWithoutThresholds.length / PAGE_SIZE)}
-                  </CurrentPage>
-                  <Button
-                    icon={<IconChevron direction="right" size="sm" />}
-                    aria-label={t('Next')}
-                    size="sm"
-                    disabled={
-                      PAGE_SIZE * newProjThresholdsPage + PAGE_SIZE >=
-                      projectsWithoutThresholds.length
-                    }
-                    onClick={() => {
-                      setNewProjThresholdsPage(newProjThresholdsPage + 1);
-                    }}
-                  />
-                </Paginator>
-              </div>
-            )}
-          </Layout.Main>
-        </Layout.Body>
-      </NoProjectMessage>
-    </PageFiltersContainer>
-  );
-}
-
-export default ReleaseThresholdList;
-
-const FilterRow = styled('div')`
-  display: flex;
-  align-items: center;
-`;
-
-const ListError = styled('div')`
-  color: red;
-  margin: 0 ${space(2)};
-  width: 100%;
-  display: flex;
-  justify-content: center;
-`;
-
-const ReleaseThresholdsPageFilterBar = styled(PageFilterBar)`
-  margin-bottom: ${space(2)};
-`;
-
-const Paginator = styled('div')`
-  margin: ${space(2)} 0;
-  display: flex;
-  justify-content: flex-end;
-  align-items: center;
-`;
-
-const CurrentPage = styled('div')`
-  margin: 0 ${space(1)};
-`;

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