Browse Source

ref(ui): Convert Badge components to typescript (#20645)

Priscila Oliveira 4 years ago
parent
commit
8d5e8a8fce

+ 17 - 34
src/sentry/static/sentry/app/components/avatar/index.tsx

@@ -1,53 +1,36 @@
-import PropTypes from 'prop-types';
 import React from 'react';
 
-import SentryTypes from 'app/sentryTypes';
 import OrganizationAvatar from 'app/components/avatar/organizationAvatar';
 import ProjectAvatar from 'app/components/avatar/projectAvatar';
 import TeamAvatar from 'app/components/avatar/teamAvatar';
 import UserAvatar from 'app/components/avatar/userAvatar';
 import {Team, OrganizationSummary, AvatarProject} from 'app/types';
 
-const BasicModelShape = PropTypes.shape({slug: PropTypes.string});
-
 type Props = {
   team?: Team;
   organization?: OrganizationSummary;
   project?: AvatarProject;
 } & UserAvatar['props'];
 
-class Avatar extends React.Component<Props> {
-  static propTypes = {
-    team: PropTypes.oneOfType([BasicModelShape, SentryTypes.Team]),
-    organization: PropTypes.oneOfType([BasicModelShape, SentryTypes.Organization]),
-    project: PropTypes.oneOfType([BasicModelShape, SentryTypes.Project]),
-
-    ...UserAvatar.propTypes,
-  };
-
-  static defaultProps = {
-    hasTooltip: false,
-  };
-
-  render() {
-    const {user, team, project, organization, ...props} = this.props;
+const Avatar = React.forwardRef(function Avatar(
+  {hasTooltip = false, user, team, project, organization, ...props}: Props,
+  ref: React.Ref<HTMLSpanElement>
+) {
+  const commonProps = {hasTooltip, forwardedRef: ref, ...props};
 
-    if (user) {
-      return <UserAvatar user={user} {...props} />;
-    }
-
-    if (team) {
-      return <TeamAvatar team={team} {...props} />;
-    }
+  if (user) {
+    return <UserAvatar user={user} {...commonProps} />;
+  }
 
-    if (project) {
-      return <ProjectAvatar project={project} {...props} />;
-    }
+  if (team) {
+    return <TeamAvatar team={team} {...commonProps} />;
+  }
 
-    return <OrganizationAvatar organization={organization} {...props} />;
+  if (project) {
+    return <ProjectAvatar project={project} {...commonProps} />;
   }
-}
 
-export default React.forwardRef<HTMLSpanElement, Props>((props, ref) => (
-  <Avatar forwardedRef={ref} {...props} />
-));
+  return <OrganizationAvatar organization={organization} {...commonProps} />;
+});
+
+export default Avatar;

+ 8 - 4
src/sentry/static/sentry/app/components/idBadge/badgeDisplayName.tsx

@@ -4,12 +4,16 @@ import overflowEllipsis from 'app/styles/overflowEllipsis';
 import space from 'app/styles/space';
 
 const BadgeDisplayName = styled('span')<{hideOverflow?: string | boolean}>`
-  ${p => p.hideOverflow && overflowEllipsis};
   ${p =>
     p.hideOverflow &&
-    `max-width: ${
-      typeof p.hideOverflow === 'string' ? p.hideOverflow : p.theme.settings.maxCrumbWidth
-    }`};
+    `
+      ${overflowEllipsis};
+      max-width: ${
+        typeof p.hideOverflow === 'string'
+          ? p.hideOverflow
+          : p.theme.settings.maxCrumbWidth
+      }
+  `};
   padding: ${space(0.25)} 0;
 `;
 

+ 0 - 127
src/sentry/static/sentry/app/components/idBadge/baseBadge.jsx

@@ -1,127 +0,0 @@
-import React from 'react';
-import PropTypes from 'prop-types';
-import styled from '@emotion/styled';
-
-import Avatar from 'app/components/avatar';
-import space from 'app/styles/space';
-import overflowEllipsis from 'app/styles/overflowEllipsis';
-import SentryTypes from 'app/sentryTypes';
-
-const BasicModelShape = PropTypes.shape({slug: PropTypes.string});
-
-class BaseBadge extends React.PureComponent {
-  static propTypes = {
-    team: PropTypes.oneOfType([BasicModelShape, SentryTypes.Team]),
-    organization: PropTypes.oneOfType([BasicModelShape, SentryTypes.Organization]),
-    project: PropTypes.oneOfType([BasicModelShape, SentryTypes.Project]),
-    member: PropTypes.oneOfType([BasicModelShape, SentryTypes.Member]),
-    user: PropTypes.oneOfType([BasicModelShape, SentryTypes.User]),
-
-    /**
-     * Avatar size
-     */
-    avatarSize: PropTypes.number,
-
-    /**
-     * Hides the avatar
-     */
-    hideAvatar: PropTypes.bool,
-
-    /**
-     * Additional props for Avatar component
-     */
-    avatarProps: PropTypes.object,
-
-    /**
-     * Hides the main display name
-     */
-    hideName: PropTypes.bool,
-
-    className: PropTypes.string,
-    displayName: PropTypes.node,
-    description: PropTypes.node,
-    avatarClassName: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
-  };
-
-  static defaultProps = {
-    avatarProps: {},
-    avatarSize: 24,
-    hideAvatar: false,
-  };
-
-  render() {
-    const {
-      className,
-      hideAvatar,
-      hideName,
-      avatarSize,
-      avatarClassName,
-      displayName,
-      description,
-      avatarProps,
-      team,
-      organization,
-      project,
-    } = this.props;
-
-    const data = {
-      team,
-      organization,
-      project,
-    };
-
-    return (
-      <BaseBadgeWrapper className={className}>
-        {!hideAvatar && (
-          <StyledAvatar
-            css={avatarClassName}
-            size={avatarSize}
-            hideName={hideName}
-            {...(avatarProps || {})}
-            {...data}
-          />
-        )}
-
-        <DisplayNameAndDescription>
-          {!hideName && (
-            <DisplayName data-test-id="badge-display-name">{displayName}</DisplayName>
-          )}
-          {!!description && <Description>{description}</Description>}
-        </DisplayNameAndDescription>
-      </BaseBadgeWrapper>
-    );
-  }
-}
-
-const BaseBadgeWrapper = styled('div')`
-  display: flex;
-  align-items: center;
-`;
-
-export default BaseBadge;
-
-const StyledAvatar = styled(Avatar)`
-  margin-right: ${p => (p.hideName ? 0 : space(1))};
-  flex-shrink: 0;
-`;
-
-const DisplayNameAndDescription = styled('div')`
-  display: flex;
-  flex-direction: column;
-  line-height: 1;
-  overflow: hidden;
-`;
-
-const DisplayName = styled('span')`
-  overflow: hidden;
-  text-overflow: ellipsis;
-  line-height: 1.2;
-`;
-
-const Description = styled('div')`
-  font-size: 0.875em;
-  margin-top: ${space(0.25)};
-  color: ${p => p.theme.gray500};
-  line-height: 14px;
-  ${overflowEllipsis};
-`;

+ 90 - 0
src/sentry/static/sentry/app/components/idBadge/baseBadge.tsx

@@ -0,0 +1,90 @@
+import React from 'react';
+import styled from '@emotion/styled';
+
+import Avatar from 'app/components/avatar';
+import space from 'app/styles/space';
+import overflowEllipsis from 'app/styles/overflowEllipsis';
+import {Team, Organization, AvatarProject} from 'app/types';
+
+type Props = {
+  displayName: React.ReactNode;
+  hideName?: boolean; // Hides the main display name
+  hideAvatar?: boolean;
+  avatarProps?: Record<string, any>;
+  avatarSize?: number;
+  description?: React.ReactNode;
+  team?: Team;
+  organization?: Organization;
+  project?: AvatarProject;
+  className?: string;
+};
+
+const BaseBadge = React.memo(
+  ({
+    displayName,
+    hideName = false,
+    hideAvatar = false,
+    avatarProps = {},
+    avatarSize = 24,
+    description,
+    team,
+    organization,
+    project,
+    className,
+  }: Props) => (
+    <Wrapper className={className}>
+      {!hideAvatar && (
+        <StyledAvatar
+          {...avatarProps}
+          size={avatarSize}
+          hideName={hideName}
+          team={team}
+          organization={organization}
+          project={project}
+        />
+      )}
+
+      {(!hideName || !!description) && (
+        <DisplayNameAndDescription>
+          {!hideName && (
+            <DisplayName data-test-id="badge-display-name">{displayName}</DisplayName>
+          )}
+          {!!description && <Description>{description}</Description>}
+        </DisplayNameAndDescription>
+      )}
+    </Wrapper>
+  )
+);
+
+export default BaseBadge;
+
+const Wrapper = styled('div')`
+  display: flex;
+  align-items: center;
+`;
+
+const StyledAvatar = styled(Avatar)<{hideName: boolean}>`
+  margin-right: ${p => (p.hideName ? 0 : space(1))};
+  flex-shrink: 0;
+`;
+
+const DisplayNameAndDescription = styled('div')`
+  display: flex;
+  flex-direction: column;
+  line-height: 1;
+  overflow: hidden;
+`;
+
+const DisplayName = styled('span')`
+  overflow: hidden;
+  text-overflow: ellipsis;
+  line-height: 1.2;
+`;
+
+const Description = styled('div')`
+  font-size: 0.875em;
+  margin-top: ${space(0.25)};
+  color: ${p => p.theme.gray500};
+  line-height: 14px;
+  ${overflowEllipsis};
+`;

+ 40 - 0
src/sentry/static/sentry/app/components/idBadge/getBadge.tsx

@@ -0,0 +1,40 @@
+import React from 'react';
+
+import BaseBadge from 'app/components/idBadge/baseBadge';
+import MemberBadge from 'app/components/idBadge/memberBadge';
+import UserBadge from 'app/components/idBadge/userBadge';
+import TeamBadge from 'app/components/idBadge/teamBadge/badge';
+import ProjectBadge from 'app/components/idBadge/projectBadge';
+import OrganizationBadge from 'app/components/idBadge/organizationBadge';
+import {Member, User} from 'app/types';
+
+type BaseBadgeProps = React.ComponentProps<typeof BaseBadge>;
+type DisplayName = BaseBadgeProps['displayName'];
+
+type Props = Omit<BaseBadgeProps, 'displayName'> & {
+  user?: User;
+  member?: Member;
+  displayName?: DisplayName;
+};
+
+function getBadge({organization, team, project, user, member, ...props}: Props) {
+  if (organization) {
+    return <OrganizationBadge organization={organization} {...props} />;
+  }
+  if (team) {
+    return <TeamBadge team={team} {...props} />;
+  }
+  if (project) {
+    return <ProjectBadge project={project} {...props} />;
+  }
+  if (user) {
+    return <UserBadge user={user} {...props} />;
+  }
+  if (member) {
+    return <MemberBadge member={member} {...props} />;
+  }
+
+  return null;
+}
+
+export default getBadge;

+ 0 - 58
src/sentry/static/sentry/app/components/idBadge/index.jsx

@@ -1,58 +0,0 @@
-import React from 'react';
-import styled from '@emotion/styled';
-
-import ErrorBoundary from 'app/components/errorBoundary';
-import BaseBadge from 'app/components/idBadge/baseBadge';
-import MemberBadge from 'app/components/idBadge/memberBadge';
-import UserBadge from 'app/components/idBadge/userBadge';
-import TeamBadge from 'app/components/idBadge/teamBadge';
-import ProjectBadge from 'app/components/idBadge/projectBadge';
-import OrganizationBadge from 'app/components/idBadge/organizationBadge';
-
-const COMPONENT_MAP = new Map([
-  ['organization', OrganizationBadge],
-  ['project', ProjectBadge],
-  ['member', MemberBadge],
-  ['user', UserBadge],
-  ['team', TeamBadge],
-]);
-
-/**
- * Public interface for all "id badges":
- * Organization, project, team, user
- */
-export default class IdBadge extends React.Component {
-  static propTypes = {
-    ...BaseBadge.propTypes,
-  };
-
-  render() {
-    // Given the set of sentry types, find the prop name that was passed to this component,
-    // of which we have a mapped component for
-    const propNameWithData = Object.keys(this.props).find(key => COMPONENT_MAP.has(key));
-
-    if (!propNameWithData) {
-      throw new Error(
-        'IdBadge: required property missing (organization, project, team, member, user) or misconfigured'
-      );
-    }
-
-    const Component = COMPONENT_MAP.get(propNameWithData);
-
-    return (
-      <InlineErrorBoundary mini>
-        <Component {...this.props} />
-      </InlineErrorBoundary>
-    );
-  }
-}
-
-const InlineErrorBoundary = styled(ErrorBoundary)`
-  background-color: transparent;
-  border-color: transparent;
-  display: flex;
-  align-items: center;
-  margin-bottom: 0;
-  box-shadow: none;
-  padding: 0; /* Because badges dont have any padding, so this should make the boundary fit well */
-`;

+ 37 - 0
src/sentry/static/sentry/app/components/idBadge/index.tsx

@@ -0,0 +1,37 @@
+import React from 'react';
+import styled from '@emotion/styled';
+
+import ErrorBoundary from 'app/components/errorBoundary';
+
+import getBadge from './getBadge';
+
+type Props = React.ComponentProps<typeof getBadge> & Record<string, any>;
+
+/**
+ * Public interface for all "id badges":
+ * Organization, project, team, user
+ */
+
+const IdBadge = (props: Props) => {
+  const componentBadge = getBadge(props);
+
+  if (!componentBadge) {
+    throw new Error(
+      'IdBadge: required property missing (organization, project, team, member, user) or misconfigured'
+    );
+  }
+
+  return <InlineErrorBoundary mini>{componentBadge}</InlineErrorBoundary>;
+};
+
+export default IdBadge;
+
+const InlineErrorBoundary = styled(ErrorBoundary)`
+  background-color: transparent;
+  border-color: transparent;
+  display: flex;
+  align-items: center;
+  margin-bottom: 0;
+  box-shadow: none;
+  padding: 0; /* Because badges dont have any padding, so this should make the boundary fit well */
+`;

+ 10 - 10
src/sentry/static/sentry/app/components/idBadge/memberBadge.tsx

@@ -3,7 +3,7 @@ import React from 'react';
 import styled from '@emotion/styled';
 import omit from 'lodash/omit';
 
-import {AvatarUser, Member} from 'app/types';
+import {Member, AvatarUser} from 'app/types';
 import UserAvatar from 'app/components/avatar/userAvatar';
 import Link from 'app/components/links/link';
 import overflowEllipsis from 'app/styles/overflowEllipsis';
@@ -11,17 +11,17 @@ import space from 'app/styles/space';
 import SentryTypes from 'app/sentryTypes';
 
 type Props = {
-  avatarSize: UserAvatar['props']['size'];
   member: Member;
-  className?: string;
-  displayName?: string;
+  avatarSize?: UserAvatar['props']['size'];
+  displayName?: React.ReactNode;
   displayEmail?: string;
   orgId?: string;
   useLink?: boolean;
   hideEmail?: boolean;
+  className?: string;
 };
 
-function getUser(member: Member): AvatarUser {
+function getMemberUser(member: Member): AvatarUser {
   if (member.user) {
     return member.user;
   }
@@ -36,16 +36,16 @@ function getUser(member: Member): AvatarUser {
 }
 
 const MemberBadge = ({
-  className,
+  avatarSize = 24,
+  useLink = true,
+  hideEmail = false,
   displayName,
   displayEmail,
   member,
   orgId,
-  avatarSize,
-  useLink = true,
-  hideEmail = false,
+  className,
 }: Props) => {
-  const user = getUser(member);
+  const user = getMemberUser(member);
   const title =
     displayName ||
     user.name ||

+ 14 - 43
src/sentry/static/sentry/app/components/idBadge/organizationBadge.tsx

@@ -1,56 +1,27 @@
-import PropTypes from 'prop-types';
 import React from 'react';
 
 import BaseBadge from 'app/components/idBadge/baseBadge';
 import BadgeDisplayName from 'app/components/idBadge/badgeDisplayName';
-import SentryTypes from 'app/sentryTypes';
-import {Organization} from 'app/types';
-import OrganizationAvatar from 'app/components/avatar/organizationAvatar';
 
-type DefaultProps = {
-  avatarSize: OrganizationAvatar['props']['size'];
-  // If true, will use default max-width, or specify one as a string
-  hideOverflow: boolean | string;
-  hideAvatar: boolean;
-};
+type BaseBadgeProps = React.ComponentProps<typeof BaseBadge>;
+type Organization = NonNullable<BaseBadgeProps['organization']>;
 
-type Props = DefaultProps & {
+type Props = Partial<Omit<BaseBadgeProps, 'project' | 'organization' | 'team'>> & {
   // A full organization is not used, but required to satisfy types with
   // withOrganization()
   organization: Organization;
-  className?: string;
+  // If true, will use default max-width, or specify one as a string
+  hideOverflow?: boolean | string;
 };
 
-class OrganizationBadge extends React.Component<Props> {
-  static propTypes = {
-    ...BaseBadge.propTypes,
-    organization: SentryTypes.Organization.isRequired,
-    avatarSize: PropTypes.number,
-    hideOverflow: PropTypes.oneOfType([PropTypes.bool, PropTypes.string]),
-    hideAvatar: PropTypes.bool,
-  };
-
-  static defaultProps: DefaultProps = {
-    avatarSize: 24,
-    hideAvatar: false,
-    hideOverflow: true,
-  };
-
-  render() {
-    const {hideOverflow, organization, ...props} = this.props;
-
-    return (
-      <BaseBadge
-        displayName={
-          <BadgeDisplayName hideOverflow={hideOverflow}>
-            {organization.slug}
-          </BadgeDisplayName>
-        }
-        organization={organization}
-        {...props}
-      />
-    );
-  }
-}
+const OrganizationBadge = ({hideOverflow = true, organization, ...props}: Props) => (
+  <BaseBadge
+    displayName={
+      <BadgeDisplayName hideOverflow={hideOverflow}>{organization.slug}</BadgeDisplayName>
+    }
+    organization={organization}
+    {...props}
+  />
+);
 
 export default OrganizationBadge;

+ 15 - 38
src/sentry/static/sentry/app/components/idBadge/projectBadge.tsx

@@ -1,50 +1,27 @@
 import React from 'react';
-import PropTypes from 'prop-types';
 
-import {AvatarProject} from 'app/types';
 import BaseBadge from 'app/components/idBadge/baseBadge';
 import BadgeDisplayName from 'app/components/idBadge/badgeDisplayName';
 
-type Props = {
-  project: AvatarProject;
-  avatarSize?: number;
-  className?: string;
-  hideOverflow?: boolean | string;
-  // Inherited from BaseBadge
-  hideAvatar?: boolean;
-  displayName?: React.ReactNode;
-  description?: React.ReactNode;
-  hideName?: boolean;
-  avatarClassName?: string;
-};
-
-function ProjectBadge({
-  hideOverflow = true,
-  hideAvatar = false,
-  project,
-  ...props
-}: Props) {
-  return (
-    <BaseBadge
-      displayName={
-        <BadgeDisplayName hideOverflow={hideOverflow}>{project.slug}</BadgeDisplayName>
-      }
-      project={project}
-      hideAvatar={hideAvatar}
-      {...props}
-    />
-  );
-}
+type BaseBadgeProps = React.ComponentProps<typeof BaseBadge>;
+type Project = NonNullable<BaseBadgeProps['project']>;
 
-ProjectBadge.propTypes = {
-  ...BaseBadge.propTypes,
-  project: BaseBadge.propTypes.project.isRequired,
-  avatarSize: PropTypes.number,
+type Props = Partial<Omit<BaseBadgeProps, 'project' | 'organization' | 'team'>> & {
+  project: Project;
   /**
    * If true, will use default max-width, or specify one as a string
    */
-  hideOverflow: PropTypes.oneOfType([PropTypes.bool, PropTypes.string]),
-  hideAvatar: PropTypes.bool,
+  hideOverflow?: boolean | string;
 };
 
+const ProjectBadge = ({hideOverflow = true, project, ...props}: Props) => (
+  <BaseBadge
+    displayName={
+      <BadgeDisplayName hideOverflow={hideOverflow}>{project.slug}</BadgeDisplayName>
+    }
+    project={project}
+    {...props}
+  />
+);
+
 export default ProjectBadge;

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