Repository: getsentry/sentry
Pull request: feat(feedback): Add github feedback components
Add pre-styled components to  ask users for feedback on GitHub.
Add `GithubFeedbackButton`
Add `GithubFeedbackTooltip`

<img width="214" alt="Screenshot 2023-10-10 at 13 12 28" src="https://github.com/getsentry/sentry/assets/7033940/556ed17c-19f5-440b-b465-adae5d51bb37">
<img width="248" alt="Screenshot 2023-10-10 at 13 12 36" src="https://github.com/getsentry/sentry/assets/7033940/1d9f947d-8977-424e-bc3b-995f4fadc1d4">


- Closes https://github.com/getsentry/sentry/issues/57778

Problem statement:
GitHub Feedback Component
Let's do a PoC of a feedback component that enables you to give feedback in a public way (github discussion).

We likely want two components:
- Button
  - button with GitHub icon
  - accepts discussion URL as a prop
  - is `ExternalLink`
  - accepts buttonProps so that consumers can modify it  
- Tooltip
  - you can wrap it around anything
  - accepts discussion URL as a prop
  - accepts tooltipProps so that consumers can modify it
  - users can disable it if they are using it in a render map
  - the link lives inside the tooltip text, so the tooltip needs to be `hoverable`
 
By default, both of them should be visible only to early adopters (unless you specify otherwise via props).

Hints from issue discussion:

Commit message: feat(feedback): Add github feedback components

Patch to review:
diff --git a/static/app/components/githubFeedbackButton.spec.tsx b/static/app/components/githubFeedbackButton.spec.tsx
new file mode 100644
index 00000000000000..802009fd7f8f18
--- /dev/null
+++ b/static/app/components/githubFeedbackButton.spec.tsx
@@ -0,0 +1,26 @@
+import {render, screen, userEvent} from 'sentry-test/reactTestingLibrary';
+
+import {GithubFeedbackButton} from 'sentry/components/githubFeedbackButton';
+
+describe('GithubFeedbackButton', function () {
+  it('renders', async function () {
+    render(
+      <GithubFeedbackButton href="https://example.com/my-test-url">
+        My button label
+      </GithubFeedbackButton>
+    );
+
+    const anchor = screen.getByRole<HTMLAnchorElement>('button', {
+      name: 'My button label',
+    });
+
+    // Renders a link with given href
+    expect(anchor).toBeInTheDocument();
+    expect(anchor.tagName).toBe('A');
+    expect(anchor.href).toBe('https://example.com/my-test-url');
+
+    // Renders tooltip
+    await userEvent.hover(anchor);
+    expect(await screen.findByText('Give us feedback on GitHub')).toBeInTheDocument();
+  });
+});
diff --git a/static/app/components/githubFeedbackButton.tsx b/static/app/components/githubFeedbackButton.tsx
new file mode 100644
index 00000000000000..8d718863264a45
--- /dev/null
+++ b/static/app/components/githubFeedbackButton.tsx
@@ -0,0 +1,16 @@
+import {LinkButton, LinkButtonProps} from 'sentry/components/button';
+import {Tooltip} from 'sentry/components/tooltip';
+import {IconGithub} from 'sentry/icons';
+import {t} from 'sentry/locale';
+
+type GithubFeedbackButtonProps = LinkButtonProps & {href: string};
+
+const title = t('Give us feedback on GitHub');
+
+export function GithubFeedbackButton(props: GithubFeedbackButtonProps) {
+  return (
+    <Tooltip title={title}>
+      <LinkButton size="sm" external icon={<IconGithub />} {...props} />
+    </Tooltip>
+  );
+}
diff --git a/static/app/components/githubFeedbackTooltip.spec.tsx b/static/app/components/githubFeedbackTooltip.spec.tsx
new file mode 100644
index 00000000000000..bc6a7482254ce9
--- /dev/null
+++ b/static/app/components/githubFeedbackTooltip.spec.tsx
@@ -0,0 +1,27 @@
+import {render, screen, userEvent} from 'sentry-test/reactTestingLibrary';
+
+import {GithubFeedbackTooltip} from 'sentry/components/githubFeedbackTooltip';
+
+describe('GithubFeedbackTooltip', function () {
+  it('renders', async function () {
+    render(
+      <GithubFeedbackTooltip
+        title="My custom title text"
+        href="https://example.com/my-test-url"
+      >
+        <span data-test-id="anchor" />
+      </GithubFeedbackTooltip>
+    );
+
+    const anchor = screen.getByTestId('anchor');
+    await userEvent.hover(anchor);
+
+    // Renders custom title text
+    expect(await screen.findByText('My custom title text')).toBeInTheDocument();
+
+    // Renders link with given href
+    const link = screen.getByRole<HTMLAnchorElement>('link', {name: 'GitHub'});
+    expect(link).toBeInTheDocument();
+    expect(link.href).toBe('https://example.com/my-test-url');
+  });
+});
diff --git a/static/app/components/githubFeedbackTooltip.tsx b/static/app/components/githubFeedbackTooltip.tsx
new file mode 100644
index 00000000000000..c1e9c1887d83f4
--- /dev/null
+++ b/static/app/components/githubFeedbackTooltip.tsx
@@ -0,0 +1,56 @@
+import {Fragment} from 'react';
+import styled from '@emotion/styled';
+
+import ExternalLink from 'sentry/components/links/externalLink';
+import {Tooltip, TooltipProps} from 'sentry/components/tooltip';
+import {IconGithub} from 'sentry/icons';
+import {tct} from 'sentry/locale';
+import {space} from 'sentry/styles/space';
+
+type GithubFeedbackTooltipProps = TooltipProps & {
+  href: string;
+  title?: React.ReactNode;
+};
+
+export function GithubFeedbackTooltip({
+  href,
+  title,
+  ...props
+}: GithubFeedbackTooltipProps) {
+  return (
+    <Tooltip
+      isHoverable
+      title={
+        <Fragment>
+          {title}
+          <FeedbackLine hasTitle={!!title}>
+            {tct('Give us feedback on [githubLink]', {
+              githubLink: (
+                <GithubLink href={href}>
+                  GitHub <IconGithub size="xs" />
+                </GithubLink>
+              ),
+            })}
+          </FeedbackLine>
+        </Fragment>
+      }
+      {...props}
+    />
+  );
+}
+
+const FeedbackLine = styled('div')<{hasTitle: boolean}>`
+  ${p => p.hasTitle && `padding-top: ${space(1)};`}
+`;
+
+const GithubLink = styled(ExternalLink)`
+  display: inline-flex;
+  align-items: center;
+  justify-content: center;
+  gap: ${space(0.5)};
+  line-height: 0px;
+
+  & > svg {
+    margin-top: -1px;
+  }
+`;


Code review comments:
diff_hunk: @@ -0,0 +1,16 @@
+import {LinkButton, LinkButtonProps} from 'sentry/components/button';
+import {Tooltip} from 'sentry/components/tooltip';
+import {IconGithub} from 'sentry/icons';
+import {t} from 'sentry/locale';
+
+type GithubFeedbackButtonProps = LinkButtonProps & {href: string};
+
+const title = t('Give us feedback on GitHub');
+
+export function GithubFeedbackButton(props: GithubFeedbackButtonProps) {
+  return (
+    <Tooltip title={title}>
+      <LinkButton size="sm" external icon={<IconGithub />} {...props} />
path: static/app/components/githubFeedbackButton.tsx
text: @user1:
Should the text of the button be optional and default to `t('Give Feedback')`?

@author:
hmm, then icon-only usage is not possible anymore but maybe we never want this 🤔

@user1:
That's a good point. Would you be able to pass null for children if you want just an icon button?

@author:
You could but then the buttons typescript types will not force you to add an `aria-label`.