Browse Source

ref(guides): Add hovercard arrow offset, move anchors (#17904)

Megan Heskett 5 years ago
parent
commit
ac3fd46963

+ 4 - 1
src/sentry/static/sentry/app/components/assistant/guideAnchor.tsx

@@ -28,6 +28,7 @@ type Props = {
   target?: string;
   position?: string;
   disabled?: boolean;
+  offset?: string;
 };
 
 type State = {
@@ -48,6 +49,7 @@ const GuideAnchor = createReactClass<Props, State>({
     target: PropTypes.string,
     position: PropTypes.string,
     disabled: PropTypes.bool,
+    offset: PropTypes.string,
   },
 
   mixins: [Reflux.listenTo(GuideStore, 'onGuideStateChange') as any],
@@ -204,7 +206,7 @@ const GuideAnchor = createReactClass<Props, State>({
   },
 
   renderHovercardExp() {
-    const {children, position} = this.props;
+    const {children, position, offset} = this.props;
 
     return (
       <StyledHovercard
@@ -212,6 +214,7 @@ const GuideAnchor = createReactClass<Props, State>({
         body={this.getHovercardExpBody()}
         tipColor={theme.purple}
         position={position}
+        offset={offset}
       >
         <span ref={el => (this.containerElement = el)}>{children}</span>
       </StyledHovercard>

+ 11 - 2
src/sentry/static/sentry/app/components/eventOrGroupTitle.tsx

@@ -4,10 +4,12 @@ import PropTypes from 'prop-types';
 import {Event, Group} from 'app/types';
 import {Metadata} from 'app/sentryTypes';
 import {getTitle} from 'app/utils/events';
+import GuideAnchor from 'app/components/assistant/guideAnchor';
 
 type Props = {
   data: Event | Group;
   style?: React.CSSProperties;
+  hasGuideAnchor?: boolean;
 };
 
 class EventOrGroupTitle extends React.Component<Props> {
@@ -31,16 +33,23 @@ class EventOrGroupTitle extends React.Component<Props> {
 
   render() {
     const {title, subtitle} = getTitle(this.props.data as Event);
+    const {hasGuideAnchor} = this.props;
 
     return subtitle ? (
       <span style={this.props.style}>
-        <span>{title}</span>
+        <GuideAnchor disabled={!hasGuideAnchor} target="issue_title" position="bottom">
+          <span>{title}</span>
+        </GuideAnchor>
         <Spacer />
         <em title={subtitle}>{subtitle}</em>
         <br />
       </span>
     ) : (
-      <span style={this.props.style}>{title}</span>
+      <span style={this.props.style}>
+        <GuideAnchor disabled={!hasGuideAnchor} target="issue_title" position="bottom">
+          {title}
+        </GuideAnchor>
+      </span>
     );
   }
 }

+ 1 - 7
src/sentry/static/sentry/app/components/events/eventMessage.tsx

@@ -4,7 +4,6 @@ import styled from '@emotion/styled';
 
 import {Level} from 'app/types';
 import ErrorLevel from 'app/components/events/errorLevel';
-import GuideAnchor from 'app/components/assistant/guideAnchor';
 import overflowEllipsis from 'app/styles/overflowEllipsis';
 import space from 'app/styles/space';
 
@@ -23,7 +22,6 @@ const EventMessage = ({
   levelIndicatorSize,
   message,
   annotations,
-  hasGuideAnchor,
 }: Props) => (
   <div className={className}>
     {level && (
@@ -32,11 +30,7 @@ const EventMessage = ({
       </StyledErrorLevel>
     )}
 
-    {message && (
-      <GuideAnchor target="issue_title" disabled={!hasGuideAnchor} position="bottom">
-        <Message>{message}</Message>
-      </GuideAnchor>
-    )}
+    {message && <Message>{message}</Message>}
 
     {annotations}
   </div>

+ 4 - 4
src/sentry/static/sentry/app/components/group/suggestedOwners.jsx

@@ -3,20 +3,20 @@ import React from 'react';
 import styled from '@emotion/styled';
 import {ClassNames} from '@emotion/core';
 
-import space from 'app/styles/space';
 import {assignToUser, assignToActor} from 'app/actionCreators/group';
+import {IconInfo} from 'app/icons/iconInfo';
 import {openCreateOwnershipRule} from 'app/actionCreators/modal';
 import {t} from 'app/locale';
 import Access from 'app/components/acl/access';
 import ActorAvatar from 'app/components/avatar/actorAvatar';
 import Button from 'app/components/button';
 import GuideAnchor from 'app/components/assistant/guideAnchor';
+import Hovercard from 'app/components/hovercard';
 import SentryTypes from 'app/sentryTypes';
+import space from 'app/styles/space';
 import SuggestedOwnerHovercard from 'app/components/group/suggestedOwnerHovercard';
 import withApi from 'app/utils/withApi';
 import withOrganization from 'app/utils/withOrganization';
-import Hovercard from 'app/components/hovercard';
-import {IconInfo} from 'app/icons/iconInfo';
 
 class SuggestedOwners extends React.Component {
   static propTypes = {
@@ -223,7 +223,7 @@ class SuggestedOwners extends React.Component {
                 )}
               </ClassNames>
             </OwnerRuleHeading>
-            <GuideAnchor target="owners" position="bottom">
+            <GuideAnchor target="owners" position="bottom" offset={space(3)}>
               <Button
                 onClick={() =>
                   openCreateOwnershipRule({

+ 11 - 4
src/sentry/static/sentry/app/components/hovercard.jsx

@@ -50,6 +50,10 @@ class Hovercard extends React.Component {
      * Color of the arrow tip
      */
     tipColor: PropTypes.string,
+    /**
+     * Offset for the arrow
+     */
+    offset: PropTypes.string,
   };
 
   static defaultProps = {
@@ -101,6 +105,7 @@ class Hovercard extends React.Component {
       position,
       show,
       tipColor,
+      offset,
     } = this.props;
 
     // Maintain the hovercard class name for BC with less styles
@@ -147,6 +152,7 @@ class Hovercard extends React.Component {
                   style={style}
                   placement={placement}
                   withHeader={!!header}
+                  offset={offset}
                   className={cx}
                   {...hoverProps}
                 >
@@ -188,6 +194,7 @@ const slideIn = p => keyframes`
 const getTipColor = p => (p.placement === 'bottom' ? p.theme.offWhite : '#fff');
 const getTipDirection = p =>
   VALID_DIRECTIONS.includes(p.placement) ? p.placement : 'top';
+const getOffset = p => p.offset ?? space(2);
 
 const StyledHovercard = styled('div')`
   border-radius: 4px;
@@ -214,10 +221,10 @@ const StyledHovercard = styled('div')`
   animation-play-state: ${p => (p.visible ? 'running' : 'paused')};
 
   /* Offset for the arrow */
-  ${p => (p.placement === 'top' ? 'margin-bottom: 15px' : '')};
-  ${p => (p.placement === 'bottom' ? 'margin-top: 15px' : '')};
-  ${p => (p.placement === 'left' ? 'margin-right: 15px' : '')};
-  ${p => (p.placement === 'right' ? 'margin-left: 15px' : '')};
+  ${p => (p.placement === 'top' ? `margin-bottom: ${getOffset(p)}` : '')};
+  ${p => (p.placement === 'bottom' ? `margin-top: ${getOffset(p)}` : '')};
+  ${p => (p.placement === 'left' ? `margin-right: ${getOffset(p)}` : '')};
+  ${p => (p.placement === 'right' ? `margin-left: ${getOffset(p)}` : '')};
 `;
 
 const Header = styled('div')`

+ 18 - 18
src/sentry/static/sentry/app/views/organizationGroupDetails/actions.jsx

@@ -270,7 +270,7 @@ const GroupDetailsActions = createReactClass({
 
     return (
       <div className="group-actions">
-        <GuideAnchor target="resolve" position="bottom">
+        <GuideAnchor target="resolve" position="bottom" offset={space(3)}>
           <ResolveActions
             hasRelease={hasRelease}
             latestRelease={project.latestRelease}
@@ -282,26 +282,26 @@ const GroupDetailsActions = createReactClass({
           />
         </GuideAnchor>
 
-        <GuideAnchor target="ignore_delete_discard" position="bottom">
+        <GuideAnchor target="ignore_delete_discard" position="bottom" offset={space(3)}>
           <IgnoreActions isIgnored={isIgnored} onUpdate={this.onUpdate} />
+        </GuideAnchor>
 
-          <div className="btn-group">
-            <div
-              className={bookmarkClassName}
-              title={t('Bookmark')}
-              onClick={this.onToggleBookmark}
-            >
-              <span className="icon-star-solid" />
-            </div>
+        <div className="btn-group">
+          <div
+            className={bookmarkClassName}
+            title={t('Bookmark')}
+            onClick={this.onToggleBookmark}
+          >
+            <span className="icon-star-solid" />
           </div>
-
-          <DeleteActions
-            organization={organization}
-            project={project}
-            onDelete={this.onDelete}
-            onDiscard={this.onDiscard}
-          />
-        </GuideAnchor>
+        </div>
+
+        <DeleteActions
+          organization={organization}
+          project={project}
+          onDelete={this.onDelete}
+          onDiscard={this.onDiscard}
+        />
 
         {orgFeatures.has('shared-issues') && (
           <div className="btn-group">

+ 1 - 2
src/sentry/static/sentry/app/views/organizationGroupDetails/header.jsx

@@ -102,11 +102,10 @@ class GroupHeader extends React.Component {
         <div className="row">
           <div className="col-sm-7">
             <h3>
-              <EventOrGroupTitle data={group} />
+              <EventOrGroupTitle hasGuideAnchor data={group} />
             </h3>
 
             <EventMessage
-              hasGuideAnchor
               message={message}
               level={group.level}
               annotations={

+ 22 - 0
src/sentry/testutils/cases.py

@@ -715,6 +715,28 @@ class AcceptanceTestCase(TransactionTestCase):
         # Forward session cookie to django client.
         self.client.cookies[settings.SESSION_COOKIE_NAME] = self.session.session_key
 
+    def dismiss_assistant(self):
+        res = self.client.put(
+            "/api/0/assistant/?v2",
+            content_type="application/json",
+            data=json.dumps({"guide": "discover_sidebar", "status": "viewed", "useful": True}),
+        )
+        assert res.status_code == 201
+
+        res = self.client.put(
+            "/api/0/assistant/?v2",
+            content_type="application/json",
+            data=json.dumps({"guide": "issue", "status": "viewed", "useful": True}),
+        )
+        assert res.status_code == 201
+
+        res = self.client.put(
+            "/api/0/assistant/?v2",
+            content_type="application/json",
+            data=json.dumps({"guide": "issue_stream", "status": "viewed", "useful": True}),
+        )
+        assert res.status_code == 201
+
 
 class IntegrationTestCase(TestCase):
     provider = None

+ 24 - 0
tests/acceptance/page_objects/base.py

@@ -1,5 +1,7 @@
 from __future__ import absolute_import
 
+import json
+
 
 class BasePage(object):
     """Base class for PageObjects"""
@@ -14,6 +16,28 @@ class BasePage(object):
     def wait_until_loaded(self):
         self.browser.wait_until_not(".loading-indicator")
 
+    def dismiss_assistant(self):
+        res = self.client.put(
+            "/api/0/assistant/?v2",
+            content_type="application/json",
+            data=json.dumps({"guide": "discover_sidebar", "status": "viewed", "useful": True}),
+        )
+        assert res.status_code == 201
+
+        res = self.client.put(
+            "/api/0/assistant/?v2",
+            content_type="application/json",
+            data=json.dumps({"guide": "issue", "status": "viewed", "useful": True}),
+        )
+        assert res.status_code == 201
+
+        res = self.client.put(
+            "/api/0/assistant/?v2",
+            content_type="application/json",
+            data=json.dumps({"guide": "issue_stream", "status": "viewed", "useful": True}),
+        )
+        assert res.status_code == 201
+
 
 class BaseElement(object):
     def __init__(self, element):

+ 0 - 17
tests/acceptance/page_objects/issue_details.py

@@ -1,7 +1,5 @@
 from __future__ import absolute_import
 
-import json
-
 from .base import BasePage
 
 
@@ -65,21 +63,6 @@ class IssueDetailsPage(BasePage):
         element = self.browser.element('[data-test-id="activity-note-body"]')
         return text in element.text
 
-    def dismiss_assistant(self):
-        res = self.client.put(
-            "/api/0/assistant/",
-            content_type="application/json",
-            data=json.dumps({"guide_id": 1, "status": "viewed", "useful": True}),
-        )
-        assert res.status_code == 201
-
-        res = self.client.put(
-            "/api/0/assistant/",
-            content_type="application/json",
-            data=json.dumps({"guide_id": 3, "status": "viewed", "useful": True}),
-        )
-        assert res.status_code == 201
-
     def wait_until_loaded(self):
         self.browser.wait_until_not(".loading-indicator")
         self.browser.wait_until_test_id("event-entries")

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