Browse Source

feat(assistant): Add event vs issue guide (#7585)

* Add guide for events + issues definition + user context info
Dena Mwangi 7 years ago
parent
commit
42c22dbf9b

+ 33 - 4
src/sentry/assistant/guides.py

@@ -38,7 +38,7 @@ GUIDES = {
                 'title': _('Resolve'),
                 'message': _(
                     'Resolving an issue removes it from the default dashboard view of unresolved '
-                    'issues. You can ask Sentry to <a href="/settings/account/notifications/"> '
+                    'issues. You can ask Sentry to <a href="/settings/account/notifications/" target="_blank"> '
                     'alert you</a> when a resolved issue re-occurs.'),
                 'target': 'resolve',
             },
@@ -47,7 +47,7 @@ GUIDES = {
                 'message': _(
                     'This is a unique identifier for the issue and can be included in a commit '
                     'message to tell Sentry to resolve the issue when the commit gets deployed. '
-                    'See <a href="https://docs.sentry.io/learn/releases/">Releases</a> '
+                    'See <a href="https://docs.sentry.io/learn/releases/" target="_blank">Releases</a> '
                     'to learn more.'),
                 'target': 'issue_number',
             },
@@ -55,7 +55,7 @@ GUIDES = {
                 'title': _('Issue Tracking'),
                 'message': _(
                     'Create issues in your project management tool from within Sentry. See a list '
-                    'of all integrations <a href="https://docs.sentry.io/integrations/">here</a>.'),
+                    'of all integrations <a href="https://docs.sentry.io/integrations/" target="_blank">here</a>.'),
                 'target': 'issue_tracking',
             },
             {
@@ -89,9 +89,38 @@ GUIDES = {
                 'message': _('Sentry does this by tying together commits in the release, files '
                              'touched by those commits, files observed in the stacktrace, and '
                              'authors of those files. Learn more about releases '
-                             '<a href="https://docs.sentry.io/learn/releases/">here</a>.'),
+                             '<a href="https://docs.sentry.io/learn/releases/" target="_blank">here</a>.'),
                 'target': 'releases',
             },
         ]
     },
+
+    'event_issue': {
+        'id': 3,
+        'cue': _('Learn about the issue stream'),
+        'required_targets': ['issues'],
+        'steps': [
+            {
+                'title': _('Events'),
+                'message': _(
+                    'When your application throws an error, that error is captured by Sentry as an event.'),
+                'target': 'events',
+            },
+            {
+                'title': _('Issues'),
+                'message': _(
+                    'Individual events are then automatically rolled up and grouped into Issues with other similar events. '
+                    'A single issue can represent anywhere from one to thousands of individual events, depending on how many '
+                    'times a specific error is thrown. '),
+                'target': 'issues',
+            },
+            {
+                'title': _('Users'),
+                'message': _(
+                    'Sending user data to Sentry will unlock a number of features, primarily the ability to drill down into the number of users affected by an issue. '
+                    'Learn how easy it is to <a href="https://docs.sentry.io/learn/context/#capturing-the-user" target="_blank">set this up </a>today.'),
+                'target': 'users',
+            },
+        ]
+    },
 }

+ 0 - 1
src/sentry/static/sentry/app/components/organizations/homeSidebar.jsx

@@ -2,7 +2,6 @@ import PropTypes from 'prop-types';
 import React from 'react';
 
 import createReactClass from 'create-react-class';
-
 import ListLink from '../listLink';
 import OrganizationState from '../../mixins/organizationState';
 import HookStore from '../../stores/hookStore';

+ 8 - 1
src/sentry/static/sentry/app/components/stream/group.jsx

@@ -10,6 +10,7 @@ import GroupChart from './groupChart';
 import GroupCheckBox from './groupCheckBox';
 import ProjectState from '../../mixins/projectState';
 import GroupStore from '../../stores/groupStore';
+import GuideAnchor from '../../components/assistant/guideAnchor';
 import SelectedGroupStore from '../../stores/selectedGroupStore';
 import EventOrGroupHeader from '../eventOrGroupHeader';
 import EventOrGroupExtraDetails from '../eventOrGroupExtraDetails';
@@ -26,6 +27,7 @@ const StreamGroup = createReactClass({
     statsPeriod: PropTypes.string.isRequired,
     canSelect: PropTypes.bool,
     query: PropTypes.string,
+    hasGuideAnchor: PropTypes.bool,
   },
 
   mixins: [Reflux.listenTo(GroupStore, 'onGroupChange'), ProjectState],
@@ -102,16 +104,18 @@ const StreamGroup = createReactClass({
     className += ' type-' + data.type;
     className += ' level-' + data.level;
 
-    let {id, orgId, projectId} = this.props;
+    let {id, orgId, projectId, hasGuideAnchor} = this.props;
 
     return (
       <li className={className} onClick={this.toggleSelect}>
         <div className="col-md-7 col-xs-8 event-details">
           {this.props.canSelect && (
             <div className="checkbox">
+              {hasGuideAnchor && <GuideAnchor target="issues" type="text" />}
               <GroupCheckBox id={data.id} />
             </div>
           )}
+
           <EventOrGroupHeader
             data={data}
             orgId={orgId}
@@ -133,13 +137,16 @@ const StreamGroup = createReactClass({
           <GroupChart id={data.id} statsPeriod={this.props.statsPeriod} data={data} />
         </div>
         <div className="col-md-1 col-xs-2 event-count align-right">
+          {hasGuideAnchor && <GuideAnchor target="events" type="text" />}
           <Count value={data.count} />
         </div>
         <div className="col-md-1 col-xs-2 event-users align-right">
+          {hasGuideAnchor && <GuideAnchor target="users" type="text" />}
           <Count value={userCount} />
         </div>
       </li>
     );
   },
 });
+
 export default StreamGroup;

+ 10 - 0
src/sentry/static/sentry/app/views/stream/stream.jsx

@@ -11,6 +11,7 @@ import {omit, isEqual} from 'lodash';
 
 import SentryTypes from '../../proptypes';
 import ApiMixin from '../../mixins/apiMixin';
+import ConfigStore from '../../stores/configStore';
 import GroupStore from '../../stores/groupStore';
 import EnvironmentStore from '../../stores/environmentStore';
 import HookStore from '../../stores/hookStore';
@@ -637,8 +638,16 @@ const Stream = createReactClass({
   },
 
   renderGroupNodes(ids, statsPeriod) {
+    // Restrict this guide to only show for new users (joined<30 days) and add guide anhor only to the first issue
+    let userDateJoined = new Date(ConfigStore.get('user').dateJoined);
+    let dateCutoff = new Date();
+    dateCutoff.setDate(dateCutoff.getDate() - 30);
+
+    let topIssue = ids[0];
+
     let {orgId, projectId} = this.props.params;
     let groupNodes = ids.map(id => {
+      let hasGuideAnchor = userDateJoined > dateCutoff && id === topIssue;
       return (
         <StreamGroup
           key={id}
@@ -647,6 +656,7 @@ const Stream = createReactClass({
           projectId={projectId}
           statsPeriod={statsPeriod}
           query={this.state.query}
+          hasGuideAnchor={hasGuideAnchor}
         />
       );
     });

+ 168 - 0
tests/js/spec/components/__snapshots__/streamGroup.spec.jsx.snap

@@ -0,0 +1,168 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`StreamGroup renders with anchors 1`] = `
+<li
+  className="group row type-undefined level-undefined"
+  onClick={[Function]}
+>
+  <div
+    className="col-md-7 col-xs-8 event-details"
+  >
+    <div
+      className="checkbox"
+    >
+      <GuideAnchor
+        target="issues"
+        type="text"
+      />
+      <GroupCheckBox
+        id="1337"
+      />
+    </div>
+    <EventOrGroupHeader
+      data={
+        Object {
+          "assignedTo": null,
+          "id": "1337",
+          "project": Object {
+            "id": "13",
+            "slug": "test",
+          },
+          "stats": Object {
+            "24h": Array [
+              Array [
+                1517281200,
+                2,
+              ],
+              Array [
+                1517310000,
+                1,
+              ],
+            ],
+            "30d": Array [
+              Array [
+                1514764800,
+                1,
+              ],
+              Array [
+                1515024000,
+                122,
+              ],
+            ],
+          },
+          "tags": Array [],
+        }
+      }
+      includeLink={true}
+      orgId="orgId"
+      projectId="projectId"
+    />
+    <EventOrGroupExtraDetails
+      assignedTo={null}
+      group={true}
+      groupId="1L"
+      id="1337"
+      orgId="orgId"
+      project={
+        Object {
+          "id": "13",
+          "slug": "test",
+        }
+      }
+      projectId="projectId"
+      stats={
+        Object {
+          "24h": Array [
+            Array [
+              1517281200,
+              2,
+            ],
+            Array [
+              1517310000,
+              1,
+            ],
+          ],
+          "30d": Array [
+            Array [
+              1514764800,
+              1,
+            ],
+            Array [
+              1515024000,
+              122,
+            ],
+          ],
+        }
+      }
+      tags={Array []}
+    />
+  </div>
+  <div
+    className="event-assignee col-md-1 hidden-sm hidden-xs"
+  >
+    <AssigneeSelector
+      id="1337"
+      size={24}
+    />
+  </div>
+  <div
+    className="col-md-2 hidden-sm hidden-xs event-graph align-right"
+  >
+    <GroupChart
+      data={
+        Object {
+          "assignedTo": null,
+          "id": "1337",
+          "project": Object {
+            "id": "13",
+            "slug": "test",
+          },
+          "stats": Object {
+            "24h": Array [
+              Array [
+                1517281200,
+                2,
+              ],
+              Array [
+                1517310000,
+                1,
+              ],
+            ],
+            "30d": Array [
+              Array [
+                1514764800,
+                1,
+              ],
+              Array [
+                1515024000,
+                122,
+              ],
+            ],
+          },
+          "tags": Array [],
+        }
+      }
+      id="1337"
+      statsPeriod="24h"
+    />
+  </div>
+  <div
+    className="col-md-1 col-xs-2 event-count align-right"
+  >
+    <GuideAnchor
+      target="events"
+      type="text"
+    />
+    <count />
+  </div>
+  <div
+    className="col-md-1 col-xs-2 event-users align-right"
+  >
+    <GuideAnchor
+      target="users"
+      type="text"
+    />
+    <count />
+  </div>
+</li>
+`;

+ 46 - 0
tests/js/spec/components/streamGroup.spec.jsx

@@ -0,0 +1,46 @@
+import React from 'react';
+import {shallow} from 'enzyme';
+
+import GroupStore from 'app/stores/groupStore';
+import StreamGroup from 'app/components/stream/group';
+
+// jest.mock('app/mixins/projectState');
+
+describe('StreamGroup', function() {
+  let sandbox;
+  let GROUP_1;
+
+  beforeEach(function() {
+    sandbox = sinon.sandbox.create();
+    GROUP_1 = TestStubs.Group({
+      id: '1337',
+      project: {
+        id: '13',
+        slug: 'test',
+      },
+    });
+    sandbox.stub(GroupStore, 'get').returns(GROUP_1);
+  });
+
+  afterEach(function() {
+    sandbox.restore();
+  });
+
+  it('renders with anchors', function() {
+    let component = shallow(
+      <StreamGroup
+        id="1L"
+        orgId="orgId"
+        projectId="projectId"
+        groupId="groupId"
+        lastSeen="2017-07-25T22:56:12Z"
+        firstSeen="2017-07-01T02:06:02Z"
+        hasGuideAnchor={true}
+      />
+    );
+
+    expect(component.find('GuideAnchor').exists()).toBe(true);
+    expect(component.find('GuideAnchor')).toHaveLength(3);
+    expect(component).toMatchSnapshot();
+  });
+});

+ 2 - 0
tests/js/spec/views/stream/__snapshots__/stream.spec.jsx.snap

@@ -56,6 +56,7 @@ exports[`Stream render() displays the group list 1`] = `
       >
         <StreamGroup
           canSelect={true}
+          hasGuideAnchor={false}
           id="1"
           key="1"
           orgId="org-slug"
@@ -207,6 +208,7 @@ exports[`Stream toggles environment select all environments 1`] = `
       >
         <StreamGroup
           canSelect={true}
+          hasGuideAnchor={false}
           id="1"
           key="1"
           orgId="org-slug"