Browse Source

fix(ui): Team details doesn't load depending on navigation (#31081)

* fix(ui): Team details doesn't load depending on navigation

If there are more than 100 teams in an organization, the Team Details page will not load and will show an error alert for the Members tab. This fix should now load the page properly.

FIXES WOR-1541

* change to functional component

* refactor to use useTeams and useApi
Kelly Carino 3 years ago
parent
commit
a589bd1253
1 changed files with 103 additions and 156 deletions
  1. 103 156
      static/app/views/settings/organizationTeams/teamDetails.tsx

+ 103 - 156
static/app/views/settings/organizationTeams/teamDetails.tsx

@@ -1,92 +1,45 @@
-import * as React from 'react';
+import {cloneElement, isValidElement, useState} from 'react';
 import {browserHistory, RouteComponentProps} from 'react-router';
 import styled from '@emotion/styled';
-import isEqual from 'lodash/isEqual';
 
 import {addErrorMessage, addSuccessMessage} from 'sentry/actionCreators/indicator';
-import {fetchTeamDetails, joinTeam} from 'sentry/actionCreators/teams';
-import {Client} from 'sentry/api';
+import {joinTeam} from 'sentry/actionCreators/teams';
 import Alert from 'sentry/components/alert';
 import Button from 'sentry/components/button';
 import IdBadge from 'sentry/components/idBadge';
 import ListLink from 'sentry/components/links/listLink';
-import LoadingError from 'sentry/components/loadingError';
 import LoadingIndicator from 'sentry/components/loadingIndicator';
 import NavTabs from 'sentry/components/navTabs';
 import SentryDocumentTitle from 'sentry/components/sentryDocumentTitle';
 import {t, tct} from 'sentry/locale';
 import TeamStore from 'sentry/stores/teamStore';
-import {Organization, Team} from 'sentry/types';
+import {Team} from 'sentry/types';
 import recreateRoute from 'sentry/utils/recreateRoute';
-import withApi from 'sentry/utils/withApi';
-import withOrganization from 'sentry/utils/withOrganization';
-import withTeams from 'sentry/utils/withTeams';
+import useApi from 'sentry/utils/useApi';
+import useTeams from 'sentry/utils/useTeams';
 
 type Props = {
-  api: Client;
-  teams: Team[];
   children: React.ReactNode;
-  organization: Organization;
 } & RouteComponentProps<{orgId: string; teamId: string}, {}>;
 
-type State = {
-  loading: boolean;
-  error: boolean;
-  requesting: boolean;
-  team: Team | null;
-};
+function TeamDetails({children, ...props}: Props) {
+  const api = useApi();
+  const [currentTeam, setCurrentTeam] = useState(
+    TeamStore.getBySlug(props.params.teamId)
+  );
+  const [requesting, setRequesting] = useState(false);
 
-class TeamDetails extends React.Component<Props, State> {
-  state = this.getInitialState();
-
-  getInitialState(): State {
-    const team = TeamStore.getBySlug(this.props.params.teamId);
-
-    return {
-      loading: !TeamStore.initialized,
-      error: false,
-      requesting: false,
-      team,
-    };
-  }
-
-  componentDidUpdate(prevProps: Props) {
-    const {params} = this.props;
-
-    if (
-      prevProps.params.teamId !== params.teamId ||
-      prevProps.params.orgId !== params.orgId
-    ) {
-      this.fetchData();
-    }
-    if (!isEqual(this.props.teams, prevProps.teams)) {
-      this.setActiveTeam();
-    }
-  }
-
-  setActiveTeam() {
-    const team = TeamStore.getBySlug(this.props.params.teamId);
-    const loading = !TeamStore.initialized;
-    const error = !loading && !team;
-
-    this.setState({team, loading, error});
-  }
-
-  handleRequestAccess = () => {
-    const {api, params} = this.props;
-    const {team} = this.state;
+  function handleRequestAccess(team: Team) {
     if (!team) {
       return;
     }
 
-    this.setState({
-      requesting: true,
-    });
+    setRequesting(true);
 
     joinTeam(
       api,
       {
-        orgId: params.orgId,
+        orgId: props.params.orgId,
         teamId: team.slug,
       },
       {
@@ -96,9 +49,7 @@ class TeamDetails extends React.Component<Props, State> {
               team: `#${team.slug}`,
             })
           );
-          this.setState({
-            requesting: false,
-          });
+          setRequesting(false);
         },
         error: () => {
           addErrorMessage(
@@ -106,109 +57,105 @@ class TeamDetails extends React.Component<Props, State> {
               team: `#${team.slug}`,
             })
           );
-          this.setState({
-            requesting: false,
-          });
+          setRequesting(false);
         },
       }
     );
-  };
-
-  fetchData = () => {
-    this.setState({
-      loading: true,
-      error: false,
-    });
-    fetchTeamDetails(this.props.api, this.props.params);
-  };
+  }
 
-  onTeamChange = (data: Team) => {
-    const team = this.state.team;
-    if (data.slug !== team?.slug) {
-      const orgId = this.props.params.orgId;
+  function onTeamChange(data: Team) {
+    if (currentTeam !== data) {
+      const orgId = props.params.orgId;
       browserHistory.replace(`/organizations/${orgId}/teams/${data.slug}/settings/`);
     } else {
-      this.setState({
-        team: {
-          ...team,
-          ...data,
-        },
-      });
+      setCurrentTeam({...currentTeam, ...data});
     }
-  };
-
-  render() {
-    const {children, params, routes} = this.props;
-    const {team, loading, requesting, error} = this.state;
+  }
 
-    if (loading) {
-      return <LoadingIndicator />;
-    }
-    if (!team || !team.hasAccess) {
-      return (
-        <Alert type="warning">
-          {team ? (
-            <RequestAccessWrapper>
-              {tct('You do not have access to the [teamSlug] team.', {
-                teamSlug: <strong>{`#${team.slug}`}</strong>,
-              })}
-              <Button
-                disabled={requesting || team.isPending}
-                size="small"
-                onClick={this.handleRequestAccess}
-              >
-                {team.isPending ? t('Request Pending') : t('Request Access')}
-              </Button>
-            </RequestAccessWrapper>
-          ) : (
+  // `/organizations/${orgId}/teams/${teamId}`;
+  const routePrefix = recreateRoute('', {
+    routes: props.routes,
+    params: props.params,
+    stepBack: -1,
+  });
+
+  const navigationTabs = [
+    <ListLink key={0} to={`${routePrefix}members/`}>
+      {t('Members')}
+    </ListLink>,
+    <ListLink key={1} to={`${routePrefix}projects/`}>
+      {t('Projects')}
+    </ListLink>,
+    <ListLink key={2} to={`${routePrefix}notifications/`}>
+      {t('Notifications')}
+    </ListLink>,
+    <ListLink key={3} to={`${routePrefix}settings/`}>
+      {t('Settings')}
+    </ListLink>,
+  ];
+
+  const {teams, initiallyLoaded} = useTeams({slugs: [props.params.teamId]});
+
+  return (
+    <div>
+      {initiallyLoaded ? (
+        teams.length ? (
+          teams.map((team, i) => {
+            if (!team || !team.hasAccess) {
+              return (
+                <Alert type="warning">
+                  {team ? (
+                    <RequestAccessWrapper>
+                      {tct('You do not have access to the [teamSlug] team.', {
+                        teamSlug: <strong>{`#${team.slug}`}</strong>,
+                      })}
+                      <Button
+                        disabled={requesting || team.isPending}
+                        size="small"
+                        onClick={() => handleRequestAccess(team)}
+                      >
+                        {team.isPending ? t('Request Pending') : t('Request Access')}
+                      </Button>
+                    </RequestAccessWrapper>
+                  ) : (
+                    <div>{t('You do not have access to this team.')}</div>
+                  )}
+                </Alert>
+              );
+            }
+            return (
+              <div key={i}>
+                <SentryDocumentTitle
+                  title={t('Team Details')}
+                  orgSlug={props.params.orgId}
+                />
+                <h3>
+                  <IdBadge hideAvatar team={team} avatarSize={36} />
+                </h3>
+
+                <NavTabs underlined>{navigationTabs}</NavTabs>
+
+                {isValidElement(children) &&
+                  cloneElement(children, {
+                    team,
+                    onTeamChange: () => onTeamChange(team),
+                  })}
+              </div>
+            );
+          })
+        ) : (
+          <Alert type="warning">
             <div>{t('You do not have access to this team.')}</div>
-          )}
-        </Alert>
-      );
-    }
-    if (error) {
-      return <LoadingError onRetry={this.fetchData} />;
-    }
-
-    // `/organizations/${orgId}/teams/${teamId}`;
-    const routePrefix = recreateRoute('', {routes, params, stepBack: -1});
-
-    const navigationTabs = [
-      <ListLink key={0} to={`${routePrefix}members/`}>
-        {t('Members')}
-      </ListLink>,
-      <ListLink key={1} to={`${routePrefix}projects/`}>
-        {t('Projects')}
-      </ListLink>,
-      <ListLink key={2} to={`${routePrefix}notifications/`}>
-        {t('Notifications')}
-      </ListLink>,
-      <ListLink key={3} to={`${routePrefix}settings/`}>
-        {t('Settings')}
-      </ListLink>,
-    ];
-
-    return (
-      <div>
-        <SentryDocumentTitle title={t('Team Details')} orgSlug={params.orgId} />
-        <h3>
-          <IdBadge hideAvatar team={team} avatarSize={36} />
-        </h3>
-
-        <NavTabs underlined>{navigationTabs}</NavTabs>
-
-        {React.isValidElement(children) &&
-          React.cloneElement(children, {
-            team,
-            onTeamChange: this.onTeamChange,
-          })}
-      </div>
-    );
-  }
+          </Alert>
+        )
+      ) : (
+        <LoadingIndicator />
+      )}
+    </div>
+  );
 }
 
-// TODO(davidenwang): change to functional component and replace withTeams with useTeams
-export default withApi(withOrganization(withTeams(TeamDetails)));
+export default TeamDetails;
 
 const RequestAccessWrapper = styled('div')`
   display: flex;