From 2ff7b113a1fb004db26522d6509155742773255c Mon Sep 17 00:00:00 2001
From: David Cramer
Date: Fri, 10 Jun 2016 16:47:58 -0700
Subject: [PATCH 1/4] Initial pass at new stacktrace rendering
- New cell-based stack rendering
- Add PHP specific rendering
- Remove legacy CSS
- Left-truncate filenames (expand on hover)
- Move message above exception
- Add clipping to stack locals
- Prevent propagation of clicks on ClippedBox
- Dont collapse context on click (require header click)
@getsentry/ui
---
src/sentry/interfaces/message.py | 2 +-
.../sentry/app/components/clippedBox.jsx | 4 +-
.../components/events/interfaces/frame.jsx | 78 ++---
.../components/events/interfaces/oldFrame.jsx | 295 ++++++++++++++++++
.../interfaces/rawStacktraceContent.jsx | 23 +-
.../events/interfaces/stacktraceContent.jsx | 9 +
.../static/sentry/app/components/truncate.jsx | 71 +++++
.../static/sentry/less/group-detail.less | 148 +++++----
.../static/sentry/less/shared-components.less | 22 ++
9 files changed, 533 insertions(+), 119 deletions(-)
create mode 100644 src/sentry/static/sentry/app/components/events/interfaces/oldFrame.jsx
create mode 100644 src/sentry/static/sentry/app/components/truncate.jsx
diff --git a/src/sentry/interfaces/message.py b/src/sentry/interfaces/message.py
index fc2f09569f2763..551e403bc2d2c9 100644
--- a/src/sentry/interfaces/message.py
+++ b/src/sentry/interfaces/message.py
@@ -35,7 +35,7 @@ class Message(Interface):
>>> }
"""
score = 0
- display_score = 1050
+ display_score = 2050
@classmethod
def to_python(cls, data):
diff --git a/src/sentry/static/sentry/app/components/clippedBox.jsx b/src/sentry/static/sentry/app/components/clippedBox.jsx
index 41fc870045c55f..f282667c8953dc 100644
--- a/src/sentry/static/sentry/app/components/clippedBox.jsx
+++ b/src/sentry/static/sentry/app/components/clippedBox.jsx
@@ -35,7 +35,9 @@ const ClippedBox = React.createClass({
}
},
- reveal() {
+ reveal(e) {
+ e.stopPropagation();
+
this.setState({
clipped: false
});
diff --git a/src/sentry/static/sentry/app/components/events/interfaces/frame.jsx b/src/sentry/static/sentry/app/components/events/interfaces/frame.jsx
index b2b9dbc051ecdf..218869da607b8e 100644
--- a/src/sentry/static/sentry/app/components/events/interfaces/frame.jsx
+++ b/src/sentry/static/sentry/app/components/events/interfaces/frame.jsx
@@ -1,13 +1,16 @@
import React from 'react';
import _ from 'underscore';
import classNames from 'classnames';
-import {defined, objectIsEmpty, isUrl} from '../../../utils';
+import ClippedBox from '../../../components/clippedBox';
import TooltipMixin from '../../../mixins/tooltip';
-import FrameVariables from './frameVariables';
-import ContextLine from './contextLine';
import StrictClick from '../../strictClick';
+import Truncate from '../../../components/truncate';
import {t} from '../../../locale';
+import {defined, objectIsEmpty, isUrl} from '../../../utils';
+
+import ContextLine from './contextLine';
+import FrameVariables from './frameVariables';
function trimPackage(pkg) {
let pieces = pkg.split(/\//g);
@@ -33,17 +36,27 @@ const Frame = React.createClass({
})
],
+ getDefaultProps() {
+ return {
+ isExpanded: false
+ };
+ },
+
getInitialState() {
// isExpanded can be initialized to true via parent component;
// data synchronization is not important
// https://facebook.github.io/react/tips/props-in-getInitialState-as-anti-pattern.html
return {
- isExpanded: defined(this.props.isExpanded) ? this.props.isExpanded : false
+ isExpanded: this.props.isExpanded
};
},
toggleContext(evt) {
evt && evt.preventDefault();
+ if (!this.isExpandable()) {
+ return null;
+ }
+
this.setState({
isExpanded: !this.state.isExpanded
});
@@ -53,19 +66,14 @@ const Frame = React.createClass({
return defined(this.props.data.context) && this.props.data.context.length;
},
- hasExtendedSource() {
- return this.hasContextSource() && this.props.data.context.length > 1;
- },
-
hasContextVars() {
return !objectIsEmpty(this.props.data.vars);
},
isExpandable() {
- return this.hasExtendedSource() || this.hasContextVars();
+ return this.hasContextSource() || this.hasContextVars();
},
-
renderOriginalSourceInfo() {
let data = this.props.data;
@@ -94,7 +102,11 @@ const Frame = React.createClass({
// lazy to change this up right now. This should be a format string
if (defined(data.filename || data.module)) {
- title.push({data.filename || data.module}
);
+ title.push((
+
+
+
+ ));
if (isUrl(data.absPath)) {
title.push();
}
@@ -104,26 +116,26 @@ const Frame = React.createClass({
}
if (defined(data.function)) {
- title.push({data.function}
);
+ title.push({data.function}
);
}
// we don't want to render out zero line numbers which are used to
// indicate lack of source information for native setups. We could
// TODO(mitsuhiko): only do this for events from native platforms?
- if (defined(data.lineNo) && data.lineNo != 0) {
+ else if (defined(data.lineNo) && data.lineNo != 0) {
// TODO(dcramer): we need to implement source mappings
// title.push( View Code);
title.push( {t('at line')} );
if (defined(data.colNo)) {
- title.push({data.lineNo}:{data.colNo}
);
+ title.push({data.lineNo}:{data.colNo}
);
} else {
- title.push({data.lineNo}
);
+ title.push({data.lineNo}
);
}
}
if (defined(data.package)) {
title.push( {t('within')} );
- title.push({trimPackage(data.package)}
);
+ title.push({trimPackage(data.package)}
);
}
if (defined(data.origAbsPath)) {
@@ -134,35 +146,9 @@ const Frame = React.createClass({
);
}
- if (data.inApp) {
- title.push({t('application')});
- }
return title;
},
- renderContextLine(line, activeLineNo) {
- let liClassName = 'expandable';
- if (line[0] === activeLineNo) {
- liClassName += ' active';
- }
-
- let lineWs;
- let lineCode;
- if (defined(line[1]) && line[1].match) {
- [, lineWs, lineCode] = line[1].match(/^(\s*)(.*?)$/m);
- } else {
- lineWs = '';
- lineCode = '';
- }
- return (
-
- {
- lineWs}{lineCode
- }
-
- );
- },
-
renderContext() {
let data = this.props.data;
let context = '';
@@ -192,11 +178,11 @@ const Frame = React.createClass({
}
{data.context && contextLines.map((line, index) => {
- return ;
+ return ;
})}
{hasContextVars &&
-
+
}
@@ -221,7 +207,7 @@ const Frame = React.createClass({
renderDefaultLine() {
return (
-
+
{this.renderDefaultTitle()}
{this.renderExpander()}
@@ -273,6 +259,8 @@ const Frame = React.createClass({
let className = classNames({
'frame': true,
+ 'is-expandable': this.isExpandable(),
+ 'expanded': this.state.isExpanded,
'system-frame': !data.inApp,
'frame-errors': data.errors,
'leads-to-app': !data.inApp && this.props.nextFrameInApp
diff --git a/src/sentry/static/sentry/app/components/events/interfaces/oldFrame.jsx b/src/sentry/static/sentry/app/components/events/interfaces/oldFrame.jsx
new file mode 100644
index 00000000000000..934708ae753c49
--- /dev/null
+++ b/src/sentry/static/sentry/app/components/events/interfaces/oldFrame.jsx
@@ -0,0 +1,295 @@
+import React from 'react';
+import _ from 'underscore';
+import classNames from 'classnames';
+import PureRenderMixin from 'react-addons-pure-render-mixin';
+import {defined, objectIsEmpty, isUrl} from '../../../utils';
+
+import StrictClick from '../../strictClick';
+import TooltipMixin from '../../../mixins/tooltip';
+import FrameVariables from './frameVariables';
+import ContextLine from './contextLine';
+import {t} from '../../../locale';
+
+
+function trimPackage(pkg) {
+ let pieces = pkg.split(/\//g);
+ let rv = pieces[pieces.length - 1] || pieces[pieces.length - 2] || pkg;
+ let match = rv.match(/^(.*?)\.(dylib|so|a)$/);
+ return match && match[1] || rv;
+}
+
+
+const OldFrame = React.createClass({
+ propTypes: {
+ data: React.PropTypes.object.isRequired,
+ nextFrameInApp: React.PropTypes.bool,
+ platform: React.PropTypes.string,
+ isExpanded: React.PropTypes.bool,
+ },
+
+ mixins: [
+ TooltipMixin({
+ html: true,
+ selector: '.tip',
+ trigger: 'hover'
+ })
+ ],
+
+ getInitialState() {
+ // isExpanded can be initialized to true via parent component;
+ // data synchronization is not important
+ // https://facebook.github.io/react/tips/props-in-getInitialState-as-anti-pattern.html
+ return {
+ isExpanded: defined(this.props.isExpanded) ? this.props.isExpanded : false
+ };
+ },
+
+ toggleContext(evt) {
+ evt && evt.preventDefault();
+ this.setState({
+ isExpanded: !this.state.isExpanded
+ });
+ },
+
+ hasContextSource() {
+ return defined(this.props.data.context) && this.props.data.context.length;
+ },
+
+ hasExtendedSource() {
+ return this.hasContextSource() && this.props.data.context.length > 1;
+ },
+
+ hasContextVars() {
+ return !objectIsEmpty(this.props.data.vars);
+ },
+
+ isExpandable() {
+ return this.hasExtendedSource() || this.hasContextVars();
+ },
+
+
+ renderOriginalSourceInfo() {
+ let data = this.props.data;
+
+ let sourceMapText = t('Source Map');
+
+ let out = `
+
+ ${sourceMapText}
`;
+
+ // mapUrl not always present; e.g. uploaded source maps
+ if (data.mapUrl)
+ out += `${_.escape(data.mapUrl)}
`;
+ else
+ out += `${_.escape(data.map)}
`;
+
+ out += '
';
+
+ return out;
+ },
+
+ renderDefaultTitle() {
+ let data = this.props.data;
+ let title = [];
+
+ // TODO(mitsuhiko): this is terrible for translators but i'm too
+ // lazy to change this up right now. This should be a format string
+
+ if (defined(data.filename || data.module)) {
+ title.push({data.filename || data.module}
);
+ if (isUrl(data.absPath)) {
+ title.push();
+ }
+ if (defined(data.function)) {
+ title.push( {t('in')} );
+ }
+ }
+
+ if (defined(data.function)) {
+ title.push({data.function}
);
+ }
+
+ // we don't want to render out zero line numbers which are used to
+ // indicate lack of source information for native setups. We could
+ // TODO(mitsuhiko): only do this for events from native platforms?
+ if (defined(data.lineNo) && data.lineNo != 0) {
+ // TODO(dcramer): we need to implement source mappings
+ // title.push( View Code);
+ title.push( {t('at line')} );
+ if (defined(data.colNo)) {
+ title.push({data.lineNo}:{data.colNo}
);
+ } else {
+ title.push({data.lineNo}
);
+ }
+ }
+
+ if (defined(data.package)) {
+ title.push( {t('within')} );
+ title.push({trimPackage(data.package)}
);
+ }
+
+ if (defined(data.origAbsPath)) {
+ title.push(
+
+
+
+ );
+ }
+
+ if (data.inApp) {
+ title.push({t('application')});
+ }
+ return title;
+ },
+
+ renderContextLine(line, activeLineNo) {
+ let liClassName = 'expandable';
+ if (line[0] === activeLineNo) {
+ liClassName += ' active';
+ }
+
+ let lineWs;
+ let lineCode;
+ if (defined(line[1]) && line[1].match) {
+ [, lineWs, lineCode] = line[1].match(/^(\s*)(.*?)$/m);
+ } else {
+ lineWs = '';
+ lineCode = '';
+ }
+ return (
+
+ {
+ lineWs}{lineCode
+ }
+
+ );
+ },
+
+ renderContext() {
+ let data = this.props.data;
+ let context = '';
+ let {isExpanded} = this.state;
+
+ let outerClassName = 'context';
+ if (isExpanded) {
+ outerClassName += ' expanded';
+ }
+
+ let hasContextSource = this.hasContextSource();
+ let hasContextVars = this.hasContextVars();
+ let expandable = this.isExpandable();
+
+ let contextLines = isExpanded
+ ? data.context
+ : data.context && data.context.filter(l => l[0] === data.lineNo);
+
+ if (hasContextSource || hasContextVars) {
+ let startLineNo = hasContextSource ? data.context[0][0] : '';
+ context = (
+
+
+ {defined(data.errors) &&
+ - {data.errors.join(', ')}
+ }
+
+ {data.context && contextLines.map((line, index) => {
+ return ;
+ })}
+
+ {hasContextVars &&
+
+ }
+
+
+ );
+ }
+ return context;
+ },
+
+ renderExpander() {
+ if (!this.isExpandable()) {
+ return null;
+ }
+ return (
+
+
+
+ );
+ },
+
+ renderDefaultLine() {
+ return (
+
+ {this.renderDefaultTitle()}
+ {this.renderExpander()}
+
+ );
+ },
+
+ renderCocoaLine() {
+ let data = this.props.data;
+ let className = 'stacktrace-table';
+ return (
+
+ {defined(data.package)
+ ? (
+
+ {trimPackage(data.package)}
+
+ ) : (
+
+ )
+ }
+
+ {data.instructionAddr}
+
+
+ {data.function || ''}
+ {data.instructionOffset &&
+ {' + ' + data.instructionOffset}}
+ {data.filename &&
+ {data.filename}
+ {data.lineNo ? ':' + data.lineNo : ''}}
+ {this.renderExpander()}
+
+
+ );
+ },
+
+ renderLine() {
+ switch (this.props.platform) {
+ case 'objc':
+ case 'cocoa':
+ return this.renderCocoaLine();
+ default:
+ return this.renderDefaultLine();
+ }
+ },
+
+ render() {
+ let data = this.props.data;
+
+ let className = classNames({
+ 'frame': true,
+ 'system-frame': !data.inApp,
+ 'frame-errors': data.errors,
+ 'leads-to-app': !data.inApp && this.props.nextFrameInApp
+ });
+ let props = {className: className};
+
+ let context = this.renderContext();
+
+ return (
+
+ {this.renderLine()}
+ {context}
+
+ );
+ }
+});
+
+export default OldFrame;
diff --git a/src/sentry/static/sentry/app/components/events/interfaces/rawStacktraceContent.jsx b/src/sentry/static/sentry/app/components/events/interfaces/rawStacktraceContent.jsx
index 0c51c2488cb11c..9ac806b2ff8d95 100644
--- a/src/sentry/static/sentry/app/components/events/interfaces/rawStacktraceContent.jsx
+++ b/src/sentry/static/sentry/app/components/events/interfaces/rawStacktraceContent.jsx
@@ -43,6 +43,11 @@ function getRubyFrame(frame) {
return result;
}
+export function getPHPFrame(frame, idx) {
+ let funcName = (frame.function === 'null' ? '{main}' : frame.function);
+ return `#${idx} ${frame.filename || frame.module}(${frame.lineNo}): ${funcName}`;
+}
+
export function getPythonFrame(frame) {
let result = '';
if (defined(frame.filename)) {
@@ -132,21 +137,23 @@ function getPreamble(exception, platform) {
}
}
-function getFrame(frame, platform) {
+function getFrame(frame, frameIdx, platform) {
switch (platform) {
case 'javascript':
- return getJavaScriptFrame(frame);
+ return getJavaScriptFrame(frame, frameIdx);
case 'ruby':
- return getRubyFrame(frame);
+ return getRubyFrame(frame, frameIdx);
+ case 'php':
+ return getPHPFrame(frame, frameIdx);
case 'python':
- return getPythonFrame(frame);
+ return getPythonFrame(frame, frameIdx);
case 'java':
- return getJavaFrame(frame);
+ return getJavaFrame(frame, frameIdx);
case 'objc':
case 'cocoa':
- return getCocoaFrame(frame);
+ return getCocoaFrame(frame, frameIdx);
default:
- return getPythonFrame(frame);
+ return getPythonFrame(frame, frameIdx);
}
}
@@ -163,7 +170,7 @@ export default function render (data, platform, exception) {
}
data.frames.forEach((frame, frameIdx) => {
- frames.push(getFrame(frame, platform));
+ frames.push(getFrame(frame, frameIdx, platform));
if (frameIdx === firstFrameOmitted) {
frames.push((
'.. frames ' + firstFrameOmitted + ' until ' + lastFrameOmitted + ' were omitted and not available ..'
diff --git a/src/sentry/static/sentry/app/components/events/interfaces/stacktraceContent.jsx b/src/sentry/static/sentry/app/components/events/interfaces/stacktraceContent.jsx
index 4083f3f763ec18..daf0a03344eaf2 100644
--- a/src/sentry/static/sentry/app/components/events/interfaces/stacktraceContent.jsx
+++ b/src/sentry/static/sentry/app/components/events/interfaces/stacktraceContent.jsx
@@ -51,6 +51,14 @@ const StacktraceContent = React.createClass({
lastFrameOmitted = null;
}
+ let lastFrameIdx = null;
+ data.frames.forEach((frame, frameIdx) => {
+ if (frame.inApp) lastFrameIdx = frameIdx;
+ });
+ if (lastFrameIdx === null) {
+ lastFrameIdx = data.frames.length - 1;
+ }
+
let frames = [];
data.frames.forEach((frame, frameIdx) => {
let nextFrame = data.frames[frameIdx + 1];
@@ -59,6 +67,7 @@ const StacktraceContent = React.createClass({
);
diff --git a/src/sentry/static/sentry/app/components/truncate.jsx b/src/sentry/static/sentry/app/components/truncate.jsx
new file mode 100644
index 00000000000000..47922b464393c6
--- /dev/null
+++ b/src/sentry/static/sentry/app/components/truncate.jsx
@@ -0,0 +1,71 @@
+import React from 'react';
+
+const Truncate = React.createClass({
+ propTypes: {
+ value: React.PropTypes.string.isRequired,
+ leftTrim: React.PropTypes.bool,
+ maxLength: React.PropTypes.number,
+ },
+
+ getDefaultProps() {
+ return {
+ leftTrim: false,
+ maxLength: 50,
+ };
+ },
+
+ getInitialState() {
+ return {
+ isExpanded: false,
+ };
+ },
+
+ onFocus(e) {
+ let {value, maxLength} = this.props;
+ if (value.length <= maxLength) return;
+ this.setState({isExpanded: true});
+ },
+
+ onBlur(e) {
+ if (this.state.isExpanded)
+ this.setState({isExpanded: false});
+ },
+
+ render() {
+ let {leftTrim, maxLength, value} = this.props;
+ let isTruncated = (value.length > maxLength);
+ let shortValue = '';
+
+ if (isTruncated) {
+ if (leftTrim) {
+ shortValue = … {value.slice(value.length - (maxLength - 4), value.length)};
+ } else {
+ shortValue = {value.slice(0, maxLength - 4)} …;
+ }
+ } else {
+ shortValue = value;
+ }
+
+ let className = this.props.className || '';
+ className += ' truncated';
+ if (this.state.isExpanded)
+ className += ' expanded';
+
+ return (
+
+ {shortValue}
+ {isTruncated &&
+ {value}
+ }
+
+ );
+ }
+});
+
+export default Truncate;
+
diff --git a/src/sentry/static/sentry/less/group-detail.less b/src/sentry/static/sentry/less/group-detail.less
index 49e98586231e33..c208a58d8117f3 100644
--- a/src/sentry/static/sentry/less/group-detail.less
+++ b/src/sentry/static/sentry/less/group-detail.less
@@ -831,51 +831,19 @@
.traceback {
list-style-type: none;
padding-left: 0;
+ margin: 0 -20px;
}
-.traceback > h3 {
- margin-top: 0;
- font-weight: normal;
-
- span {
- font-weight: 600;
- }
-}
-
-.traceback > pre {
- color: @gray-dark;
- font-size: 12px;
- padding: 0;
- background: inherit;
- margin: -10px 0 20px;
+// TODO(dcramer): we probably shouldnt overload these
+pre.traceback {
+ margin: 0 0 20px;
}
-.traceback ul {
+div.traceback > ul {
padding: 0;
-}
-.traceback > .traceback {
- padding-bottom: 5px;
-}
-
-.subtraceback {
- padding-left: 15px;
- padding-right: 15px;
- position: relative;
-
- > h3 {
- line-height: 26px;
- }
-
- > h3:before {
- display: block;
- content: "";
- width: 20px;
- height: 0;
- border-bottom: 1px solid #E9EBEC;
- position: absolute;
- left: -15px;
- top: 11px;
+ &:last-child {
+ margin-bottom: 0;
}
}
@@ -918,11 +886,12 @@
.frame {
list-style-type: none;
position: relative;
- margin-bottom: 0;
+ margin: 0;
+ border-top: 1px solid lighten(@trim, 4);
&.frame-errors {}
- &.system-frame {}
+ &.system-frame { }
&.leads-to-app {}
@@ -930,10 +899,24 @@
font-size: 22px;
}
+ &.is-expandable p {
+ cursor: pointer;
+ &:hover {
+ background: lighten(@blue-light, 25);
+ }
+ }
+
+ &.system-frame.is-expandable p:hover {
+ background: darken(@white-dark, 5);
+ }
+
p {
+ padding: 8px 20px;
font-size: 12px;
- margin-bottom: 12px;
+ margin: 0;
line-height: 1.4;
+ color: @gray-darkest;
+ background: lighten(@blue-light, 30);
a.annotation {
&.trigger-popover {
@@ -946,6 +929,10 @@
}
}
+ &.system-frame p {
+ background: @white-dark;
+ }
+
.original-src {
font-size: 12px;
padding-left: 3px;
@@ -962,7 +949,7 @@
}
.in-at {
- opacity: .8;
+ opacity: .6;
margin: 0 2px;
}
@@ -997,17 +984,57 @@
code {
padding: 0;
- background: #fff;
+ background: inherit;
font-size: inherit;
color: inherit;
}
.context {
- margin-top: -5px;
- margin-bottom: 15px;
+ display: none;
+ background: #fff;
+ margin: 0;
+ padding: 8px 0;
+
+ > li {
+ padding: 0 20px;
+ background: inherit;
+ }
table.key-value {
- margin-top: 20px;
+ border-top: 1px solid lighten(@trim, 4);
+ padding: 0 20px;
+ margin: 0 0 -8px;
+
+ td {
+ border-bottom: 1px solid lighten(@trim, 4) !important;
+
+ &.key {
+ width: 125px;
+ max-width: 125px;
+ }
+
+ &.value pre {
+ background: inherit;
+ }
+ }
+
+ tr:last-child {
+ td {
+ border-bottom: 0 !important;
+ }
+ }
+ }
+
+ &.expanded {
+ display: block;
+ }
+ }
+
+ .box-clippable {
+ margin-left: 0;
+ margin-right: 0;
+ &:first-of-type {
+ margin-top: 0;
}
}
@@ -1029,9 +1056,9 @@
.btn-toggle {
display: block;
float: right;
- .square(18px);
+ .square(16px);
padding: 0;
- line-height: 18px;
+ line-height: 16px;
font-size: 9px;
text-align: center;
}
@@ -1041,7 +1068,6 @@
}
&.expanded {
-
> p {
color: #000;
}
@@ -1116,7 +1142,6 @@ ol.context {
}
> li {
- cursor: pointer;
padding-left: 15px;
font-family: @font-family-code;
color: #222;
@@ -1126,13 +1151,20 @@ ol.context {
white-space: pre;
white-space: pre-wrap;
word-wrap: break-word;
+ min-height: 24px;
}
+
> li.active {
background-color: #f6f7f8;
- min-height: 24px;
list-style-type: none;
border-radius: 2px;
+ &:first-child:last-child {
+ background-color: inherit;
+ color: inherit;
+ border-radius: 0;
+ }
+
pre {
color: @gray-dark;
}
@@ -1172,18 +1204,6 @@ ol.context-line {
}
}
-.expanded {
- ol.context {
- > li {
- min-height: 22px;
- }
- > li.active {
- background-color: @blue;
- color: #fff;
- }
- }
-}
-
.stacktrace-table {
display: flex;
align-items: baseline;
diff --git a/src/sentry/static/sentry/less/shared-components.less b/src/sentry/static/sentry/less/shared-components.less
index 38ccc5b4094ebe..fb3553c22f0333 100644
--- a/src/sentry/static/sentry/less/shared-components.less
+++ b/src/sentry/static/sentry/less/shared-components.less
@@ -3294,6 +3294,28 @@ div.qrcode {
background: @red;
}
+/**
+ * Truncate component.
+ */
+.truncated {
+ position: relative;
+ .full-value {
+ display: none;
+ position: absolute;
+ background: @white;
+ left: -5px;
+ top: -5px;
+ padding: 4px;
+ border: 1px solid @trim;
+ white-space: nowrap;
+ border-radius: 4px;
+ }
+ &.expanded .full-value {
+ z-index: 10;
+ display: block;
+ }
+}
+
/**
* Responsive small screens
* ============================================================================
From 67c814eaa20e1302c25cb393586f29ad9c0c4283 Mon Sep 17 00:00:00 2001
From: Armin Ronacher
Date: Tue, 14 Jun 2016 23:22:33 +0200
Subject: [PATCH 2/4] Updated cocoa stacks to look more like the new style
stacks now
---
.../components/events/interfaces/frame.jsx | 19 ++-
.../static/sentry/less/group-detail.less | 125 +++++++++---------
2 files changed, 68 insertions(+), 76 deletions(-)
diff --git a/src/sentry/static/sentry/app/components/events/interfaces/frame.jsx b/src/sentry/static/sentry/app/components/events/interfaces/frame.jsx
index 218869da607b8e..538d978297c35a 100644
--- a/src/sentry/static/sentry/app/components/events/interfaces/frame.jsx
+++ b/src/sentry/static/sentry/app/components/events/interfaces/frame.jsx
@@ -216,22 +216,21 @@ const Frame = React.createClass({
renderCocoaLine() {
let data = this.props.data;
- let className = 'stacktrace-table';
return (
-
+
{defined(data.package)
? (
-
+
{trimPackage(data.package)}
-
+
) : (
-
+
)
}
-
+
{data.instructionAddr}
-
-
+
+
{data.function || ''}
{data.instructionOffset &&
{' + ' + data.instructionOffset}}
@@ -239,8 +238,8 @@ const Frame = React.createClass({
{data.filename}
{data.lineNo ? ':' + data.lineNo : ''}}
{this.renderExpander()}
-
-
+
+
);
},
diff --git a/src/sentry/static/sentry/less/group-detail.less b/src/sentry/static/sentry/less/group-detail.less
index c208a58d8117f3..9a66ba0aa14938 100644
--- a/src/sentry/static/sentry/less/group-detail.less
+++ b/src/sentry/static/sentry/less/group-detail.less
@@ -914,7 +914,8 @@ div.traceback > ul {
padding: 8px 20px;
font-size: 12px;
margin: 0;
- line-height: 1.4;
+ // line-height: 1.4;
+ line-height: 16px;
color: @gray-darkest;
background: lighten(@blue-light, 30);
@@ -933,6 +934,63 @@ div.traceback > ul {
background: @white-dark;
}
+ p.as-table {
+ display: flex;
+ align-items: baseline;
+ width: 100%;
+
+ > span {
+ display: block;
+ padding: 0 5px;
+ }
+
+ .package {
+ width: 15%;
+ font-size: 13px;
+ font-weight: bold;
+ .truncate;
+ flex-grow: 0;
+ flex-shrink: 0;
+ }
+
+ .address {
+ font-family: @font-family-code;
+ font-size: 11px;
+ color: @gray-dark;
+ letter-spacing: -0.25px;
+ width: 85px;
+ flex-grow: 0;
+ flex-shrink: 0;
+ }
+
+ .symbol {
+ word-break: break-word;
+ flex: 1;
+
+ code {
+ background: transparent;
+ color: @blue-dark;
+ padding-right: 5px;
+ }
+
+ span.offset {
+ font-weight: bold;
+ padding-right: 10px;
+ }
+
+ span.filename {
+ color: @purple;
+
+ &:before {
+ content: "(";
+ }
+ &:after {
+ content: ")";
+ }
+ }
+ }
+ }
+
.original-src {
font-size: 12px;
padding-left: 3px;
@@ -1204,71 +1262,6 @@ ol.context-line {
}
}
-.stacktrace-table {
- display: flex;
- align-items: baseline;
- line-height: 16px;
- box-shadow: inset 0 1px 0 #E6EEF4;
-
- .trace-col {
- padding: 6px 5px 3px;
- font-size: 12px;
- }
-
- .package {
- width: 15%;
- font-size: 13px;
- font-weight: bold;
- .truncate;
- flex-grow: 0;
- flex-shrink: 0;
- }
-
- .address {
- font-family: @font-family-code;
- font-size: 11px;
- color: @gray-dark;
- letter-spacing: -0.25px;
- width: 85px;
- flex-grow: 0;
- flex-shrink: 0;
- }
-
- .symbol {
- word-break: break-word;
-
- code {
- background: transparent;
- color: @blue-dark;
- padding-right: 5px;
- }
-
- span.offset {
- font-weight: bold;
- padding-right: 10px;
- }
-
- span.filename {
- color: @purple;
-
- &:before {
- content: "(";
- }
- &:after {
- content: ")";
- }
- }
- }
-}
-
-.system-frame .stacktrace-table {
- background-color: @white-dark;
-}
-
-.leads-to-app .stacktrace-table {
- background: lighten(@blue-light, 30);
-}
-
.exception-mechanism {
margin: 15px 0;
}
From 51a7a3c3da3f78680a71399139f13107f8f2993d Mon Sep 17 00:00:00 2001
From: Armin Ronacher
Date: Wed, 15 Jun 2016 00:31:06 +0200
Subject: [PATCH 3/4] Added feature flag for new and old frame rendering
---
.../api/serializers/models/organization.py | 2 +
src/sentry/conf/server.py | 1 +
src/sentry/features/__init__.py | 1 +
.../components/events/interfaces/oldFrame.jsx | 1 -
.../events/interfaces/stacktraceContent.jsx | 14 +-
.../static/sentry/less/group-detail.less | 888 ++++++++++++------
6 files changed, 637 insertions(+), 270 deletions(-)
diff --git a/src/sentry/api/serializers/models/organization.py b/src/sentry/api/serializers/models/organization.py
index c398cdc7775e46..c40a6478e52d7f 100644
--- a/src/sentry/api/serializers/models/organization.py
+++ b/src/sentry/api/serializers/models/organization.py
@@ -59,6 +59,8 @@ def serialize(self, obj, attrs, user):
feature_list.append('sso')
if features.has('organizations:callsigns', obj, actor=user):
feature_list.append('callsigns')
+ if features.has('organizations:new-tracebacks', obj, actor=user):
+ feature_list.append('new-tracebacks')
if features.has('organizations:onboarding', obj, actor=user) and \
not OrganizationOption.objects.filter(organization=obj).exists():
feature_list.append('onboarding')
diff --git a/src/sentry/conf/server.py b/src/sentry/conf/server.py
index 869d509905e07c..94ef3d15acd03a 100644
--- a/src/sentry/conf/server.py
+++ b/src/sentry/conf/server.py
@@ -621,6 +621,7 @@ def create_partitioned_queues(name):
'organizations:create': True,
'organizations:sso': True,
'organizations:callsigns': False,
+ 'organizations:new-tracebacks': False,
'projects:global-events': False,
'projects:quotas': True,
'projects:plugins': True,
diff --git a/src/sentry/features/__init__.py b/src/sentry/features/__init__.py
index f5b9e312b2abfc..95686c9ec53e5e 100644
--- a/src/sentry/features/__init__.py
+++ b/src/sentry/features/__init__.py
@@ -13,6 +13,7 @@
default_manager.add('organizations:sso', OrganizationFeature)
default_manager.add('organizations:onboarding', OrganizationFeature)
default_manager.add('organizations:callsigns', OrganizationFeature)
+default_manager.add('organizations:new-tracebacks', OrganizationFeature)
default_manager.add('projects:global-events', ProjectFeature)
default_manager.add('projects:quotas', ProjectFeature)
default_manager.add('projects:plugins', ProjectPluginFeature)
diff --git a/src/sentry/static/sentry/app/components/events/interfaces/oldFrame.jsx b/src/sentry/static/sentry/app/components/events/interfaces/oldFrame.jsx
index 934708ae753c49..b1c679e9dc6101 100644
--- a/src/sentry/static/sentry/app/components/events/interfaces/oldFrame.jsx
+++ b/src/sentry/static/sentry/app/components/events/interfaces/oldFrame.jsx
@@ -10,7 +10,6 @@ import FrameVariables from './frameVariables';
import ContextLine from './contextLine';
import {t} from '../../../locale';
-
function trimPackage(pkg) {
let pieces = pkg.split(/\//g);
let rv = pieces[pieces.length - 1] || pieces[pieces.length - 2] || pkg;
diff --git a/src/sentry/static/sentry/app/components/events/interfaces/stacktraceContent.jsx b/src/sentry/static/sentry/app/components/events/interfaces/stacktraceContent.jsx
index daf0a03344eaf2..9a0c0a8c034959 100644
--- a/src/sentry/static/sentry/app/components/events/interfaces/stacktraceContent.jsx
+++ b/src/sentry/static/sentry/app/components/events/interfaces/stacktraceContent.jsx
@@ -1,7 +1,10 @@
import React from 'react';
//import GroupEventDataSection from "../eventDataSection";
import Frame from './frame';
+import OldFrame from './oldFrame';
import {t} from '../../../locale';
+import OrganizationState from '../../../mixins/organizationState';
+
const StacktraceContent = React.createClass({
propTypes: {
@@ -10,6 +13,7 @@ const StacktraceContent = React.createClass({
platform: React.PropTypes.string,
newestFirst: React.PropTypes.bool
},
+ mixins: [OrganizationState],
getDefaultProps() {
return {
@@ -59,12 +63,18 @@ const StacktraceContent = React.createClass({
lastFrameIdx = data.frames.length - 1;
}
+ let oldFrames = !this.getFeatures().has('new-tracebacks');
+ let FrameComponent = Frame;
+ if (oldFrames) {
+ FrameComponent = OldFrame;
+ }
+
let frames = [];
data.frames.forEach((frame, frameIdx) => {
let nextFrame = data.frames[frameIdx + 1];
if (this.frameIsVisible(frame, nextFrame)) {
frames.push(
-
+
);
diff --git a/src/sentry/static/sentry/less/group-detail.less b/src/sentry/static/sentry/less/group-detail.less
index 9a66ba0aa14938..9d49421b0668b1 100644
--- a/src/sentry/static/sentry/less/group-detail.less
+++ b/src/sentry/static/sentry/less/group-detail.less
@@ -883,387 +883,741 @@ div.traceback > ul {
margin-right: -21px;
}
-.frame {
- list-style-type: none;
- position: relative;
- margin: 0;
- border-top: 1px solid lighten(@trim, 4);
+.traceback {
+ .frame {
+ list-style-type: none;
+ position: relative;
+ margin: 0;
+ border-top: 1px solid lighten(@trim, 4);
- &.frame-errors {}
+ &.frame-errors {}
- &.system-frame { }
+ &.system-frame { }
- &.leads-to-app {}
+ &.leads-to-app {}
- h3 {
- font-size: 22px;
- }
-
- &.is-expandable p {
- cursor: pointer;
- &:hover {
- background: lighten(@blue-light, 25);
+ h3 {
+ font-size: 22px;
}
- }
- &.system-frame.is-expandable p:hover {
- background: darken(@white-dark, 5);
- }
+ &.is-expandable p {
+ cursor: pointer;
+ &:hover {
+ background: lighten(@blue-light, 25);
+ }
+ }
- p {
- padding: 8px 20px;
- font-size: 12px;
- margin: 0;
- // line-height: 1.4;
- line-height: 16px;
- color: @gray-darkest;
- background: lighten(@blue-light, 30);
+ &.system-frame.is-expandable p:hover {
+ background: darken(@white-dark, 5);
+ }
- a.annotation {
- &.trigger-popover {
- cursor: pointer;
+ p {
+ padding: 8px 20px;
+ font-size: 12px;
+ margin: 0;
+ // line-height: 1.4;
+ line-height: 16px;
+ color: @gray-darkest;
+ background: lighten(@blue-light, 30);
+
+ a.annotation {
+ &.trigger-popover {
+ cursor: pointer;
+ }
+ color: inherit;
+ padding: 0 1px;
+ border-bottom: 1px dotted #666;
+ &:hover { text-decoration: none; }
}
- color: inherit;
- padding: 0 1px;
- border-bottom: 1px dotted #666;
- &:hover { text-decoration: none; }
}
- }
- &.system-frame p {
- background: @white-dark;
- }
+ &.system-frame p {
+ background: @white-dark;
+ }
- p.as-table {
- display: flex;
- align-items: baseline;
- width: 100%;
+ p.as-table {
+ display: flex;
+ align-items: baseline;
+ width: 100%;
- > span {
- display: block;
- padding: 0 5px;
+ > span {
+ display: block;
+ padding: 0 5px;
+ }
+
+ .package {
+ width: 15%;
+ font-size: 13px;
+ font-weight: bold;
+ .truncate;
+ flex-grow: 0;
+ flex-shrink: 0;
+ }
+
+ .address {
+ font-family: @font-family-code;
+ font-size: 11px;
+ color: @gray-dark;
+ letter-spacing: -0.25px;
+ width: 85px;
+ flex-grow: 0;
+ flex-shrink: 0;
+ }
+
+ .symbol {
+ word-break: break-word;
+ flex: 1;
+
+ code {
+ background: transparent;
+ color: @blue-dark;
+ padding-right: 5px;
+ }
+
+ span.offset {
+ font-weight: bold;
+ padding-right: 10px;
+ }
+
+ span.filename {
+ color: @purple;
+
+ &:before {
+ content: "(";
+ }
+ &:after {
+ content: ")";
+ }
+ }
+ }
}
- .package {
- width: 15%;
- font-size: 13px;
- font-weight: bold;
- .truncate;
- flex-grow: 0;
- flex-shrink: 0;
+ .original-src {
+ font-size: 12px;
+ padding-left: 3px;
+ position: relative;
+ top: 1px;
}
- .address {
- font-family: @font-family-code;
- font-size: 11px;
- color: @gray-dark;
- letter-spacing: -0.25px;
- width: 85px;
- flex-grow: 0;
- flex-shrink: 0;
+ .icon-open {
+ font-size: 12px;
+ margin-right: 3px;
+ margin-left: 3px;
+ position: relative;
+ top: 1px;
}
- .symbol {
- word-break: break-word;
- flex: 1;
+ .in-at {
+ opacity: .6;
+ margin: 0 2px;
+ }
- code {
- background: transparent;
- color: @blue-dark;
- padding-right: 5px;
+ .blame {
+ color: lighten(@gray, 5);
+
+ a {
+ color: @gray;
}
- span.offset {
- font-weight: bold;
- padding-right: 10px;
+ .icon-mark-github {
+ position: relative;
+ top: 1px;
}
+ }
- span.filename {
- color: @purple;
+ .tooltip-inner {
+ word-wrap: break-word;
+ text-align: left;
+ max-width: 300px;
+ }
- &:before {
- content: "(";
+ .divider {
+ border-left: 1px solid @trim;
+ display: inline-block;
+ width: 1px;
+ height: 10px;
+ margin: 0 6px;
+ position: relative;
+ top: 1px;
+ }
+
+ code {
+ padding: 0;
+ background: inherit;
+ font-size: inherit;
+ color: inherit;
+ }
+
+ .context {
+ display: none;
+ background: #fff;
+ margin: 0;
+ padding: 8px 0;
+
+ > li {
+ padding: 0 20px;
+ background: inherit;
+ }
+
+ table.key-value {
+ border-top: 1px solid lighten(@trim, 4);
+ padding: 0 20px;
+ margin: 0 0 -8px;
+
+ td {
+ border-bottom: 1px solid lighten(@trim, 4) !important;
+
+ &.key {
+ width: 125px;
+ max-width: 125px;
+ }
+
+ &.value pre {
+ background: inherit;
+ }
}
- &:after {
- content: ")";
+
+ tr:last-child {
+ td {
+ border-bottom: 0 !important;
+ }
}
}
+
+ &.expanded {
+ display: block;
+ }
}
- }
- .original-src {
- font-size: 12px;
- padding-left: 3px;
- position: relative;
- top: 1px;
- }
+ .box-clippable {
+ margin-left: 0;
+ margin-right: 0;
+ &:first-of-type {
+ margin-top: 0;
+ }
+ }
- .icon-open {
- font-size: 12px;
- margin-right: 3px;
- margin-left: 3px;
- position: relative;
- top: 1px;
- }
+ .tag-app {
+ color: #aaa;
+ font-size: 0.9em;
+ margin-left: 10px;
+ }
- .in-at {
- opacity: .6;
- margin: 0 2px;
- }
+ > div > table.key-value {
+ margin-bottom: 5px;
+ > tbody > tr > th {
+ color: @gray-dark;
+ text-align: right;
+ padding-right: 12px !important;
+ }
+ }
- .blame {
- color: lighten(@gray, 5);
+ .btn-toggle {
+ display: block;
+ float: right;
+ .square(16px);
+ padding: 0;
+ line-height: 16px;
+ font-size: 9px;
+ text-align: center;
+ }
- a {
- color: @gray;
+ .expand-button:hover {
+ cursor: pointer;
}
- .icon-mark-github {
- position: relative;
- top: 1px;
+ &.expanded {
+ > p {
+ color: #000;
+ }
+
+ .expandable {
+ height: auto;
+ }
}
- }
- .tooltip-inner {
- word-wrap: break-word;
- text-align: left;
- max-width: 300px;
+ &:last-child {
+ .context {
+ margin-bottom: 0;
+ }
+
+ .toggle-expand .btn {
+ margin-bottom: -13px;
+ }
+ }
}
- .divider {
- border-left: 1px solid @trim;
- display: inline-block;
- width: 1px;
- height: 10px;
- margin: 0 6px;
+ .expandable {
+ height: 0;
+ overflow: hidden;
position: relative;
- top: 1px;
+
+ .icon-plus {
+ position: absolute;
+ left: 8px;
+ top: 6px;
+ opacity: .25;
+ .transition(.1s opacity linear);
+ }
+
+ &.key-value {
+ display: none;
+ }
+
+ .ws {
+ display: none;
+ }
+
+ &:hover {
+ .icon-plus {
+ opacity: .5;
+ }
+ }
}
- code {
- padding: 0;
- background: inherit;
- font-size: inherit;
- color: inherit;
+ .expanded {
+ .expandable {
+ overflow: none;
+ height: auto;
+ }
+
+ .ws {
+ display: inline;
+ }
}
- .context {
- display: none;
- background: #fff;
+ ol.context {
margin: 0;
- padding: 8px 0;
+ list-style-position: inside;
+ border-radius: 3px;
+ padding-left: 0;
- > li {
- padding: 0 20px;
- background: inherit;
- }
+ .key-value {
+ display: none;
- table.key-value {
- border-top: 1px solid lighten(@trim, 4);
- padding: 0 20px;
- margin: 0 0 -8px;
+ pre {
+ overflow: auto;
+ }
+ }
- td {
- border-bottom: 1px solid lighten(@trim, 4) !important;
+ > li {
+ padding-left: 15px;
+ font-family: @font-family-code;
+ color: #222;
+ background-color: #f6f7f8;
+ line-height: 24px;
+ font-size: 12px;
+ white-space: pre;
+ white-space: pre-wrap;
+ word-wrap: break-word;
+ min-height: 24px;
+ }
- &.key {
- width: 125px;
- max-width: 125px;
- }
+ > li.active {
+ background-color: #f6f7f8;
+ list-style-type: none;
+ border-radius: 2px;
- &.value pre {
- background: inherit;
- }
+ &:first-child:last-child {
+ background-color: inherit;
+ color: inherit;
+ border-radius: 0;
}
- tr:last-child {
- td {
- border-bottom: 0 !important;
- }
+ pre {
+ color: @gray-dark;
}
}
+ > li:first-child {
+ border-radius: 2px 2px 0 0;
+ }
+
+ > li:last-of-type {
+ border-radius: 0 0 2px 2px;
+ }
+
+ li.closed {
+ border-radius: 2px;
+ }
+
&.expanded {
- display: block;
+ .key-value {
+ display: table;
+ }
+
+ > li.active {
+ background-color: @purple;
+ color: #fff;
+ list-style-type: inherit;
+ border-radius: 0;
+ }
}
}
- .box-clippable {
- margin-left: 0;
- margin-right: 0;
- &:first-of-type {
- margin-top: 0;
+ ol.context-line {
+ > li {
+ > span {
+ float: right;
+ }
}
}
- .tag-app {
- color: #aaa;
- font-size: 0.9em;
- margin-left: 10px;
+ .exception-mechanism {
+ margin: 15px 0;
}
+}
- > div > table.key-value {
- margin-bottom: 5px;
- > tbody > tr > th {
- color: @gray-dark;
- text-align: right;
- padding-right: 12px !important;
- }
- }
+//TODO(mitsuhiko): kill us when the A/B test is over
+.old-traceback {
+ list-style-type: none;
+ padding-left: 0;
+ margin-bottom: 20px;
- .btn-toggle {
- display: block;
- float: right;
- .square(16px);
+ > ul {
padding: 0;
- line-height: 16px;
- font-size: 9px;
- text-align: center;
+ &:last-child {
+ margin-bottom: 0;
+ }
}
- .expand-button:hover {
- cursor: pointer;
- }
+ .frame {
+ list-style-type: none;
+ position: relative;
+ margin-bottom: 0;
+
+ &.frame-errors {}
+
+ &.system-frame {}
- &.expanded {
- > p {
- color: #000;
+ &.leads-to-app {}
+
+ h3 {
+ font-size: 22px;
}
- .expandable {
- height: auto;
+ p {
+ font-size: 12px;
+ margin-bottom: 12px;
+ line-height: 1.4;
+
+ a.annotation {
+ &.trigger-popover {
+ cursor: pointer;
+ }
+ color: inherit;
+ padding: 0 1px;
+ border-bottom: 1px dotted #666;
+ &:hover { text-decoration: none; }
+ }
+ }
+
+ .original-src {
+ font-size: 12px;
+ padding-left: 3px;
+ position: relative;
+ top: 1px;
+ }
+
+ .icon-open {
+ font-size: 12px;
+ margin-right: 3px;
+ margin-left: 3px;
+ position: relative;
+ top: 1px;
+ }
+
+ .in-at {
+ opacity: .8;
+ margin: 0 2px;
+ }
+
+ .blame {
+ color: lighten(@gray, 5);
+
+ a {
+ color: @gray;
+ }
+
+ .icon-mark-github {
+ position: relative;
+ top: 1px;
+ }
+ }
+
+ .tooltip-inner {
+ word-wrap: break-word;
+ text-align: left;
+ max-width: 300px;
+ }
+
+ .divider {
+ border-left: 1px solid @trim;
+ display: inline-block;
+ width: 1px;
+ height: 10px;
+ margin: 0 6px;
+ position: relative;
+ top: 1px;
+ }
+
+ code {
+ padding: 0;
+ background: #fff;
+ font-size: inherit;
+ color: inherit;
}
- }
- &:last-child {
.context {
- margin-bottom: 0;
+ margin-top: -5px;
+ margin-bottom: 15px;
+
+ table.key-value {
+ margin-top: 20px;
+ }
}
- .toggle-expand .btn {
- margin-bottom: -13px;
+ .tag-app {
+ color: #aaa;
+ font-size: 0.9em;
+ margin-left: 10px;
}
- }
-}
-.expandable {
- height: 0;
- overflow: hidden;
- position: relative;
+ > div > table.key-value {
+ margin-bottom: 5px;
+ > tbody > tr > th {
+ color: @gray-dark;
+ text-align: right;
+ padding-right: 12px !important;
+ }
+ }
- .icon-plus {
- position: absolute;
- left: 8px;
- top: 6px;
- opacity: .25;
- .transition(.1s opacity linear);
- }
+ .btn-toggle {
+ display: block;
+ float: right;
+ .square(18px);
+ padding: 0;
+ line-height: 18px;
+ font-size: 9px;
+ text-align: center;
+ }
- &.key-value {
- display: none;
- }
+ .expand-button:hover {
+ cursor: pointer;
+ }
- .ws {
- display: none;
- }
+ &.expanded {
- &:hover {
- .icon-plus {
- opacity: .5;
+ > p {
+ color: #000;
+ }
+
+ .expandable {
+ height: auto;
+ }
+ }
+
+ &:last-child {
+ .context {
+ margin-bottom: 0;
+ }
+
+ .toggle-expand .btn {
+ margin-bottom: -13px;
+ }
}
}
-}
-.expanded {
.expandable {
- overflow: none;
- height: auto;
- }
+ height: 0;
+ overflow: hidden;
+ position: relative;
- .ws {
- display: inline;
- }
-}
+ .icon-plus {
+ position: absolute;
+ left: 8px;
+ top: 6px;
+ opacity: .25;
+ .transition(.1s opacity linear);
+ }
-ol.context {
- margin: 0;
- list-style-position: inside;
- border-radius: 3px;
- padding-left: 0;
+ &.key-value {
+ display: none;
+ }
- .key-value {
- display: none;
+ .ws {
+ display: none;
+ }
- pre {
- overflow: auto;
+ &:hover {
+ .icon-plus {
+ opacity: .5;
+ }
}
}
- > li {
- padding-left: 15px;
- font-family: @font-family-code;
- color: #222;
- background-color: #f6f7f8;
- line-height: 24px;
- font-size: 12px;
- white-space: pre;
- white-space: pre-wrap;
- word-wrap: break-word;
- min-height: 24px;
+ .expanded {
+ .expandable {
+ overflow: none;
+ height: auto;
+ }
+
+ .ws {
+ display: inline;
+ }
}
- > li.active {
- background-color: #f6f7f8;
- list-style-type: none;
- border-radius: 2px;
+ ol.context {
+ margin: 0;
+ list-style-position: inside;
+ border-radius: 3px;
+ padding-left: 0;
- &:first-child:last-child {
- background-color: inherit;
- color: inherit;
- border-radius: 0;
+ .key-value {
+ display: none;
+
+ pre {
+ overflow: auto;
+ }
}
- pre {
- color: @gray-dark;
+ > li {
+ cursor: pointer;
+ padding-left: 15px;
+ font-family: @font-family-code;
+ color: #222;
+ background-color: #f6f7f8;
+ line-height: 24px;
+ font-size: 12px;
+ white-space: pre;
+ white-space: pre-wrap;
+ word-wrap: break-word;
}
- }
+ > li.active {
+ background-color: #f6f7f8;
+ min-height: 24px;
+ list-style-type: none;
+ border-radius: 2px;
- > li:first-child {
- border-radius: 2px 2px 0 0;
+ pre {
+ color: @gray-dark;
+ }
+ }
+
+ > li:first-child {
+ border-radius: 2px 2px 0 0;
+ }
+
+ > li:last-of-type {
+ border-radius: 0 0 2px 2px;
+ }
+
+ li.closed {
+ border-radius: 2px;
+ }
+
+ &.expanded {
+ .key-value {
+ display: table;
+ }
+
+ > li.active {
+ background-color: @purple;
+ color: #fff;
+ list-style-type: inherit;
+ border-radius: 0;
+ }
+ }
}
- > li:last-of-type {
- border-radius: 0 0 2px 2px;
+ ol.context-line {
+ > li {
+ > span {
+ float: right;
+ }
+ }
}
- li.closed {
- border-radius: 2px;
+ .expanded {
+ ol.context {
+ > li {
+ min-height: 22px;
+ }
+ > li.active {
+ background-color: @blue;
+ color: #fff;
+ }
+ }
}
- &.expanded {
- .key-value {
- display: table;
+ .stacktrace-table {
+ display: flex;
+ align-items: baseline;
+ line-height: 16px;
+ box-shadow: inset 0 1px 0 #E6EEF4;
+
+ .trace-col {
+ padding: 6px 5px 3px;
+ font-size: 12px;
}
- > li.active {
- background-color: @purple;
- color: #fff;
- list-style-type: inherit;
- border-radius: 0;
+ .package {
+ width: 15%;
+ font-size: 13px;
+ font-weight: bold;
+ .truncate;
+ flex-grow: 0;
+ flex-shrink: 0;
}
- }
-}
-ol.context-line {
- > li {
- > span {
- float: right;
+ .address {
+ font-family: @font-family-code;
+ font-size: 11px;
+ color: @gray-dark;
+ letter-spacing: -0.25px;
+ width: 85px;
+ flex-grow: 0;
+ flex-shrink: 0;
+ }
+
+ .symbol {
+ word-break: break-word;
+
+ code {
+ background: transparent;
+ color: @blue-dark;
+ padding-right: 5px;
+ }
+
+ span.offset {
+ font-weight: bold;
+ padding-right: 10px;
+ }
+
+ span.filename {
+ color: @purple;
+
+ &:before {
+ content: "(";
+ }
+ &:after {
+ content: ")";
+ }
+ }
}
}
-}
-.exception-mechanism {
- margin: 15px 0;
+ .system-frame .stacktrace-table {
+ background-color: @white-dark;
+ }
+
+ .leads-to-app .stacktrace-table {
+ background: lighten(@blue-light, 30);
+ }
+
+ .exception-mechanism {
+ margin: 15px 0;
+ }
}
#full-message {
From 924021f23bb8325d3b0afbeea7234e594b367743 Mon Sep 17 00:00:00 2001
From: Armin Ronacher
Date: Thu, 16 Jun 2016 00:31:39 +0200
Subject: [PATCH 4/4] Fixed a js lint error
---
.../static/sentry/app/components/events/interfaces/oldFrame.jsx | 1 -
1 file changed, 1 deletion(-)
diff --git a/src/sentry/static/sentry/app/components/events/interfaces/oldFrame.jsx b/src/sentry/static/sentry/app/components/events/interfaces/oldFrame.jsx
index b1c679e9dc6101..254f5119ae3cca 100644
--- a/src/sentry/static/sentry/app/components/events/interfaces/oldFrame.jsx
+++ b/src/sentry/static/sentry/app/components/events/interfaces/oldFrame.jsx
@@ -1,7 +1,6 @@
import React from 'react';
import _ from 'underscore';
import classNames from 'classnames';
-import PureRenderMixin from 'react-addons-pure-render-mixin';
import {defined, objectIsEmpty, isUrl} from '../../../utils';
import StrictClick from '../../strictClick';