Lyn Nagara 7 лет назад
Родитель
Сommit
68dc1b06ae

+ 0 - 1
package.json

@@ -73,7 +73,6 @@
     "react-mentions": "^1.2.0",
     "react-router": "3.2.0",
     "react-sparklines": "1.7.0",
-    "react-sticky": "6.0.1",
     "reflux": "0.4.1",
     "scroll-to-element": "^2.0.0",
     "select2": "3.5.1",

+ 42 - 45
src/sentry/static/sentry/app/components/assigneeSelector.jsx

@@ -351,53 +351,50 @@ const AssigneeSelector = createReactClass({
     });
 
     return (
-      <div>
-        <div className={className}>
-          <DropdownLink
-            className="assignee-selector-toggle"
-            onOpen={this.onDropdownOpen}
-            onClose={this.onDropdownClose}
-            isOpen={this.state.isOpen}
-            alwaysRenderMenu={false}
-            title={
-              assignedTo ? (
-                <ActorAvatar actor={assignedTo} className="avatar" size={24} />
-              ) : (
-                <span className="icon-user" />
-              )
-            }
-          >
-            {assigneeListLoading ? (
-              <li>
-                <FlowLayout center className="list-loading-container">
-                  <LoadingIndicator mini />
-                </FlowLayout>
-              </li>
+      <div className={className}>
+        <DropdownLink
+          className="assignee-selector-toggle"
+          onOpen={this.onDropdownOpen}
+          onClose={this.onDropdownClose}
+          isOpen={this.state.isOpen}
+          alwaysRenderMenu={false}
+          title={
+            assignedTo ? (
+              <ActorAvatar actor={assignedTo} className="avatar" size={24} />
             ) : (
-              this.renderDropdownItems()
+              <span className="icon-user" />
+            )
+          }
+        >
+          {assigneeListLoading ? (
+            <li>
+              <FlowLayout center className="list-loading-container">
+                <LoadingIndicator mini />
+              </FlowLayout>
+            </li>
+          ) : (
+            this.renderDropdownItems()
+          )}
+          {ConfigStore.get('invitesEnabled') &&
+            access.has('org:write') && (
+              <React.Fragment>
+                <Divider />
+                <MenuItem
+                  className="invite-member"
+                  disabled={!loading}
+                  to={`/settings/${this.context.organization.slug}/members/new/`}
+                  query={{referrer: 'assignee_selector'}}
+                >
+                  <MenuItemWrapper>
+                    <IconContainer>
+                      <InviteMemberIcon />
+                    </IconContainer>
+                    <Label>{t('Invite Member')}</Label>
+                  </MenuItemWrapper>
+                </MenuItem>
+              </React.Fragment>
             )}
-
-            {ConfigStore.get('invitesEnabled') &&
-              access.has('org:write') && (
-                <React.Fragment>
-                  <Divider />
-                  <MenuItem
-                    className="invite-member"
-                    disabled={!loading}
-                    to={`/settings/${this.context.organization.slug}/members/new/`}
-                    query={{referrer: 'assignee_selector'}}
-                  >
-                    <MenuItemWrapper>
-                      <IconContainer>
-                        <InviteMemberIcon />
-                      </IconContainer>
-                      <Label>{t('Invite Member')}</Label>
-                    </MenuItemWrapper>
-                  </MenuItem>
-                </React.Fragment>
-              )}
-          </DropdownLink>
-        </div>
+        </DropdownLink>
       </div>
     );
   },

+ 1 - 1
src/sentry/static/sentry/app/components/errorRobot.jsx

@@ -89,7 +89,7 @@ const ErrorRobot = createReactClass({
     }
     return (
       <div
-        className="box awaiting-events"
+        className="awaiting-events"
         style={
           gradient && {backgroundImage: 'linear-gradient(to bottom, #F8F9FA, #ffffff)'}
         }

+ 74 - 36
src/sentry/static/sentry/app/components/eventOrGroupExtraDetails.jsx

@@ -2,10 +2,13 @@ import PropTypes from 'prop-types';
 import React from 'react';
 import createReactClass from 'create-react-class';
 import {Link} from 'react-router';
+import styled from 'react-emotion';
+import {Flex, Box} from 'grid-emotion';
 
 import ProjectState from '../mixins/projectState';
 import TimeSince from './timeSince';
 import ShortId from './shortId';
+import {t, tct} from '../locale';
 
 const EventOrGroupExtraDetails = createReactClass({
   displayName: 'EventOrGroupExtraDetails',
@@ -14,9 +17,8 @@ const EventOrGroupExtraDetails = createReactClass({
     orgId: PropTypes.string.isRequired,
     projectId: PropTypes.string.isRequired,
     groupId: PropTypes.string.isRequired,
-    firstSeen: PropTypes.string,
     lastSeen: PropTypes.string,
-    shortId: PropTypes.string,
+    firstSeen: PropTypes.string,
     subscriptionDetails: PropTypes.shape({
       reason: PropTypes.string,
     }),
@@ -27,6 +29,7 @@ const EventOrGroupExtraDetails = createReactClass({
       name: PropTypes.string,
     }),
     showAssignee: PropTypes.bool,
+    shortId: PropTypes.string,
   },
 
   mixins: [ProjectState],
@@ -38,13 +41,13 @@ const EventOrGroupExtraDetails = createReactClass({
       groupId,
       lastSeen,
       firstSeen,
-      shortId,
       subscriptionDetails,
       numComments,
       logger,
       assignedTo,
       annotations,
       showAssignee,
+      shortId,
     } = this.props;
     let styles = {};
     if (subscriptionDetails && subscriptionDetails.reason === 'mentioned') {
@@ -52,32 +55,43 @@ const EventOrGroupExtraDetails = createReactClass({
     }
 
     return (
-      <div className="event-extra">
-        <ul>
-          {shortId && (
-            <li>
-              <ShortId shortId={shortId} />
-            </li>
+      <GroupExtra align="center">
+        {shortId && (
+          <Box mr={2}>
+            <GroupShortId shortId={shortId} />
+          </Box>
+        )}
+        <Flex align="center" mr={2}>
+          {lastSeen && (
+            <React.Fragment>
+              <GroupExtraIcon className="icon icon-clock" />
+              <TimeSince date={lastSeen} suffix={t('ago')} />
+            </React.Fragment>
+          )}
+          {firstSeen &&
+            lastSeen && <span className="hidden-xs hidden-sm">&nbsp;—&nbsp;</span>}
+          {firstSeen && (
+            <TimeSince
+              date={firstSeen}
+              suffix={t('old')}
+              className="hidden-xs hidden-sm"
+            />
           )}
-          <li>
-            <span className="icon icon-clock" />
-            {lastSeen && <TimeSince date={lastSeen} />}
-            {firstSeen && lastSeen && <span>&nbsp;—&nbsp;</span>}
-            {firstSeen && <TimeSince date={firstSeen} suffix="old" />}
-          </li>
+        </Flex>
+        <GroupExtraCommentsAndLogger>
           {numComments > 0 && (
-            <li>
+            <Box mr={2}>
               <Link
                 to={`/${orgId}/${projectId}/issues/${groupId}/activity/`}
                 className="comments"
               >
-                <span className="icon icon-comments" style={styles} />
-                <span className="tag-count">{numComments}</span>
+                <GroupExtraIcon className="icon icon-comments" style={styles} />
+                <GroupExtraIcon className="tag-count">{numComments}</GroupExtraIcon>
               </Link>
-            </li>
+            </Box>
           )}
           {logger && (
-            <li className="event-annotation">
+            <Box className="event-annotation" mr={2}>
               <Link
                 to={{
                   pathname: `/${orgId}/${projectId}/`,
@@ -88,25 +102,49 @@ const EventOrGroupExtraDetails = createReactClass({
               >
                 {logger}
               </Link>
-            </li>
+            </Box>
           )}
-          {annotations &&
-            annotations.map((annotation, key) => {
-              return (
-                <li
-                  className="event-annotation"
-                  dangerouslySetInnerHTML={{
-                    __html: annotation,
-                  }}
-                  key={key}
-                />
-              );
-            })}
+        </GroupExtraCommentsAndLogger>
+        {annotations &&
+          annotations.map((annotation, key) => {
+            return (
+              <Box
+                className="event-annotation"
+                dangerouslySetInnerHTML={{
+                  __html: annotation,
+                }}
+                key={key}
+              />
+            );
+          })}
 
-          {showAssignee && assignedTo && <li>Assigned to {assignedTo.name}</li>}
-        </ul>
-      </div>
+        {showAssignee &&
+          assignedTo && <Box>{tct('Assigned to [name]', {name: assignedTo.name})}</Box>}
+      </GroupExtra>
     );
   },
 });
+
+const GroupExtra = styled(Flex)`
+  color: ${p => p.theme.gray3}
+  font-size: 12px;
+  a {
+    color: inherit;
+  }
+`;
+
+const GroupExtraCommentsAndLogger = styled(Flex)`
+  color: ${p => p.theme.gray4};
+`;
+
+const GroupShortId = styled(ShortId)`
+  font-size: 12px;
+  color: ${p => p.theme.gray3};
+`;
+
+const GroupExtraIcon = styled.span`
+  font-size: 11px;
+  margin-right: 4px;
+`;
+
 export default EventOrGroupExtraDetails;

+ 86 - 12
src/sentry/static/sentry/app/components/eventOrGroupHeader.jsx

@@ -1,10 +1,13 @@
 import PropTypes from 'prop-types';
 import React from 'react';
+import styled, {css} from 'react-emotion';
 import classNames from 'classnames';
+import {capitalize} from 'lodash';
 
 import ProjectLink from '../components/projectLink';
 import {Metadata} from '../proptypes';
 import EventOrGroupTitle from './eventOrGroupTitle';
+import Tooltip from '../components/tooltip';
 
 /**
  * Displays an event or group/issue title (i.e. in Stream)
@@ -48,7 +51,7 @@ class EventOrGroupHeader extends React.Component {
   }
 
   getTitle() {
-    let {hideLevel, hideIcons, includeLink, orgId, projectId, data} = this.props;
+    let {hideIcons, hideLevel, includeLink, orgId, projectId, data} = this.props;
     let {id, level, groupID} = data || {};
     let isEvent = !!data.eventID;
 
@@ -67,11 +70,22 @@ class EventOrGroupHeader extends React.Component {
     }
 
     return (
-      <Wrapper {...props}>
-        {!hideLevel && level && <span className="error-level truncate">{level}</span>}
-        {!hideIcons && <span className="icon icon-soundoff" />}
-        {!hideIcons && <span className="icon icon-star-solid" />}
-        <EventOrGroupTitle {...this.props} />
+      <Wrapper
+        {...props}
+        style={data.status === 'resolved' ? {textDecoration: 'line-through'} : null}
+      >
+        {!hideLevel &&
+          level && (
+            <Tooltip title={`Error level: ${capitalize(level)}`}>
+              <GroupLevel level={data.level} />
+            </Tooltip>
+          )}
+        {!hideIcons && data.status === 'ignored' && <Muted className="icon-soundoff" />}
+        {!hideIcons && data.isBookmarked && <Starred className="icon-star-solid" />}
+        <EventOrGroupTitle
+          {...this.props}
+          style={{fontWeight: data.hasSeen ? 400 : 600}}
+        />
       </Wrapper>
     );
   }
@@ -83,15 +97,75 @@ class EventOrGroupHeader extends React.Component {
 
     return (
       <div className={cx}>
-        <h3 className="truncate">{this.getTitle()}</h3>
-        {message && (
-          <div className="event-message truncate">
-            <span className="message">{message}</span>
-          </div>
-        )}
+        <Title>{this.getTitle()}</Title>
+        {message && <Message>{message}</Message>}
       </div>
     );
   }
 }
 
+const truncateStyles = css`
+  overflow: hidden;
+  max-width: 100%;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+`;
+
+const Title = styled.div`
+  ${truncateStyles};
+  margin: 0 0 5px;
+  & em {
+    font-size: 14px;
+    font-style: normal;
+    font-weight: 300;
+    color: ${p => p.theme.gray3};
+  }
+`;
+
+const Message = styled.div`
+  ${truncateStyles};
+  font-size: 14px;
+  margin: 0 0 5px;
+`;
+
+const iconStyles = css`
+  font-size: 14px;
+  margin-right: 5px;
+`;
+
+const Muted = styled.span`
+  ${iconStyles};
+  color: ${p => p.theme.red};
+`;
+
+const Starred = styled.span`
+  ${iconStyles};
+  color: ${p => p.theme.yellowOrange};
+`;
+
+const GroupLevel = styled.div`
+  position: absolute;
+  left: -1px;
+  width: 9px;
+  height: 15px;
+  border-radius: 0 3px 3px 0;
+
+  background-color: ${p => {
+    switch (p.level) {
+      case 'sample':
+        return p.theme.purple;
+      case 'info':
+        return p.theme.blue;
+      case 'warning':
+        return p.theme.yellowOrange;
+      case 'error':
+        return p.theme.orange;
+      case 'fatal':
+        return p.theme.red;
+      default:
+        return p.theme.gray2;
+    }
+  }};
+`;
+
 export default EventOrGroupHeader;

+ 2 - 2
src/sentry/static/sentry/app/components/eventOrGroupTitle.jsx

@@ -29,14 +29,14 @@ class EventOrGroupTitle extends React.Component {
 
     if (subtitle) {
       return (
-        <span>
+        <span style={this.props.style}>
           <span style={{marginRight: 10}}>{title}</span>
           <em>{subtitle}</em>
           <br />
         </span>
       );
     }
-    return <span>{title}</span>;
+    return <span style={this.props.style}>{title}</span>;
   }
 }
 

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

@@ -25,7 +25,6 @@ const GroupList = createReactClass({
     canSelectGroups: PropTypes.bool,
     orgId: PropTypes.string.isRequired,
     projectId: PropTypes.string.isRequired,
-    bulkActions: PropTypes.bool.isRequired,
     environment: SentryTypes.Environment,
   },
 
@@ -140,18 +139,12 @@ const GroupList = createReactClass({
         </Panel>
       );
 
-    let wrapperClass;
-
-    if (!this.props.bulkActions) {
-      wrapperClass = 'stream-no-bulk-actions';
-    }
-
     let {orgId, projectId} = this.props;
 
     return (
-      <div className={wrapperClass}>
+      <Panel>
         <GroupListHeader />
-        <ul className="group-list">
+        <PanelBody>
           {this.state.groupIds.map(id => {
             return (
               <StreamGroup
@@ -163,8 +156,8 @@ const GroupList = createReactClass({
               />
             );
           })}
-        </ul>
-      </div>
+        </PanelBody>
+      </Panel>
     );
   },
 });

+ 24 - 18
src/sentry/static/sentry/app/components/groupListHeader.jsx

@@ -1,27 +1,33 @@
 import React from 'react';
+import {Flex, Box} from 'grid-emotion';
 import {t} from '../locale';
-import Toolbar from './toolbar';
-import ToolbarHeader from './toolbarHeader';
+import {PanelHeader} from './panels';
 
 class GroupListHeader extends React.Component {
   render() {
     return (
-      <div className="group-header">
-        <Toolbar className="stream-actions row">
-          <ToolbarHeader className="stream-actions-left col-md-7 col-sm-8 col-xs-8">
-            {t('Event')}
-          </ToolbarHeader>
-          <ToolbarHeader className="hidden-sm hidden-xs stream-actions-graph col-md-2 col-md-offset-1 align-right">
-            {t('Last 24 hours')}
-          </ToolbarHeader>
-          <ToolbarHeader className="stream-actions-count align-right col-md-1 col-sm-2 col-xs-2">
-            {t('events')}
-          </ToolbarHeader>
-          <ToolbarHeader className="stream-actions-users align-right col-md-1 col-sm-2 col-xs-2">
-            {t('users')}
-          </ToolbarHeader>
-        </Toolbar>
-      </div>
+      <PanelHeader disablePadding>
+        <Box w={[8 / 12, 8 / 12, 6 / 12]} mx={2} flex="1" className="toolbar-header">
+          {t('Event')}
+        </Box>
+        <Box w={160} mx={2} className="toolbar-header hidden-xs hidden-sm">
+          {t('Last 24 hours')}
+        </Box>
+        <Flex w={80} mx={2} justify="flex-end" className="toolbar-header">
+          {t('events')}
+        </Flex>
+        <Flex w={80} mx={2} justify="flex-end" className="toolbar-header">
+          {t('users')}
+        </Flex>
+        <Flex
+          w={80}
+          mx={2}
+          justify="flex-end"
+          className="hidden-xs hidden-sm toolbar-header"
+        >
+          {t('Assignee')}
+        </Flex>
+      </PanelHeader>
     );
   }
 }

+ 7 - 3
src/sentry/static/sentry/app/components/shortId.jsx

@@ -1,6 +1,7 @@
 import PropTypes from 'prop-types';
 import React from 'react';
 import createReactClass from 'create-react-class';
+import styled from 'react-emotion';
 import ProjectState from '../mixins/projectState';
 
 import AutoSelectText from './autoSelectText';
@@ -10,7 +11,6 @@ const ShortId = createReactClass({
 
   propTypes: {
     shortId: PropTypes.string,
-    project: PropTypes.object,
   },
 
   mixins: [ProjectState],
@@ -27,11 +27,15 @@ const ShortId = createReactClass({
       return null;
     }
     return (
-      <span className="short-id" onClick={this.preventPropagation}>
+      <StyledShortId onClick={this.preventPropagation} {...this.props}>
         <AutoSelectText>{shortId}</AutoSelectText>
-      </span>
+      </StyledShortId>
     );
   },
 });
 
+const StyledShortId = styled.div`
+  font-family: ${p => p.theme.text.familyMono};
+`;
+
 export default ShortId;

+ 48 - 44
src/sentry/static/sentry/app/components/stream/group.jsx

@@ -3,6 +3,8 @@ import PropTypes from 'prop-types';
 import React from 'react';
 import createReactClass from 'create-react-class';
 import Reflux from 'reflux';
+import styled from 'react-emotion';
+import {Flex, Box} from 'grid-emotion';
 
 import AssigneeSelector from '../assigneeSelector';
 import Count from '../count';
@@ -14,6 +16,7 @@ import GuideAnchor from '../../components/assistant/guideAnchor';
 import SelectedGroupStore from '../../stores/selectedGroupStore';
 import EventOrGroupHeader from '../eventOrGroupHeader';
 import EventOrGroupExtraDetails from '../eventOrGroupExtraDetails';
+import {PanelItem} from '../panels';
 
 import {valueIsEqual} from '../../utils';
 
@@ -84,43 +87,23 @@ const StreamGroup = createReactClass({
   },
 
   render() {
-    let data = this.state.data;
-    let userCount = data.userCount;
-
-    let className = 'group row';
-    if (data.isBookmarked) {
-      className += ' isBookmarked';
-    }
-    if (data.hasSeen) {
-      className += ' hasSeen';
-    }
-    if (data.status === 'resolved') {
-      className += ' isResolved';
-    }
-    if (data.status === 'ignored') {
-      className += ' isIgnored';
-    }
-
-    className += ' type-' + data.type;
-    className += ' level-' + data.level;
-
-    let {id, orgId, projectId, hasGuideAnchor} = this.props;
+    const {data} = this.state;
+    const {id, orgId, projectId, query, 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>
-          )}
-
+      <Group onClick={this.toggleSelect} py={1} px={0} align="center">
+        {this.props.canSelect && (
+          <GroupCheckbox ml={2}>
+            {hasGuideAnchor && <GuideAnchor target="issues" type="text" />}
+            <GroupCheckBox id={data.id} />
+          </GroupCheckbox>
+        )}
+        <GroupSummary w={[8 / 12, 8 / 12, 6 / 12]} mx={1} flex="1">
           <EventOrGroupHeader
             data={data}
             orgId={orgId}
             projectId={projectId}
-            query={this.props.query}
+            query={query}
           />
           <EventOrGroupExtraDetails
             group
@@ -129,24 +112,45 @@ const StreamGroup = createReactClass({
             orgId={orgId}
             projectId={projectId}
           />
-        </div>
-        <div className="event-assignee col-md-1 hidden-sm hidden-xs">
-          <AssigneeSelector size={24} id={data.id} />
-        </div>
-        <div className="col-md-2 hidden-sm hidden-xs event-graph align-right">
+        </GroupSummary>
+        <Box w={160} mx={2} className="hidden-xs hidden-sm">
           <GroupChart id={data.id} statsPeriod={this.props.statsPeriod} data={data} />
-        </div>
-        <div className="col-md-1 col-xs-2 event-count align-right">
+        </Box>
+        <Flex w={[40, 60, 80, 80]} mx={2} justify="flex-end">
           {hasGuideAnchor && <GuideAnchor target="events" type="text" />}
-          <Count value={data.count} />
-        </div>
-        <div className="col-md-1 col-xs-2 event-users align-right">
+          <StyledCount value={data.count} />
+        </Flex>
+        <Flex w={[40, 60, 80, 80]} mx={2} justify="flex-end">
           {hasGuideAnchor && <GuideAnchor target="users" type="text" />}
-          <Count value={userCount} />
-        </div>
-      </li>
+          <StyledCount value={data.userCount} />
+        </Flex>
+        <Box w={80} mx={2} className="hidden-xs hidden-sm">
+          <AssigneeSelector id={data.id} />
+        </Box>
+      </Group>
     );
   },
 });
 
+const Group = styled(PanelItem)`
+  line-height: 1.1;
+`;
+
+const GroupSummary = styled(Box)`
+  overflow: hidden;
+`;
+
+const GroupCheckbox = styled(Box)`
+  align-self: flex-start;
+  & input[type='checkbox'] {
+    margin: 0;
+    display: block;
+  }
+`;
+
+const StyledCount = styled(Count)`
+  font-size: 18px;
+  color: ${p => p.theme.gray3};
+`;
+
 export default StreamGroup;

Некоторые файлы не были показаны из-за большого количества измененных файлов