}
({
+ items={trace.spans.map(span => ({
valueOffset: span.relativeStartTime,
valueWidth: span.duration,
serviceName: span.process.serviceName,
diff --git a/src/components/TracePage/TraceSpanGraph.test.js b/src/components/TracePage/TraceSpanGraph.test.js
index 3e64cbd325..9c9c8e1450 100644
--- a/src/components/TracePage/TraceSpanGraph.test.js
+++ b/src/components/TracePage/TraceSpanGraph.test.js
@@ -27,21 +27,14 @@ import TraceSpanGraph from './TraceSpanGraph';
import SpanGraphTickHeader from './SpanGraph/SpanGraphTickHeader';
import TimelineScrubber from './TimelineScrubber';
import traceGenerator from '../../../src/demo/trace-generators';
-import { transformTrace } from './TraceTimelineViewer/transforms';
-import { hydrateSpansWithProcesses } from '../../selectors/trace';
+import transformTraceData from '../../model/transform-trace-data';
describe('', () => {
- const trace = hydrateSpansWithProcesses(traceGenerator.trace({}));
- const xformedTrace = transformTrace(trace);
-
- const props = {
- trace,
- xformedTrace,
- };
-
+ const trace = transformTraceData(traceGenerator.trace({}));
+ const props = { trace };
const options = {
context: {
- timeRangeFilter: [trace.timestamp, trace.timestamp + trace.duration],
+ timeRangeFilter: [trace.startTime, trace.startTime + trace.duration],
updateTimeRangeFilter: () => {},
},
};
@@ -68,7 +61,7 @@ describe('', () => {
it('renders a filtering box if leftBound exists', () => {
const context = {
...options.context,
- timeRangeFilter: [trace.timestamp + 0.2 * trace.duration, trace.timestamp + trace.duration],
+ timeRangeFilter: [trace.startTime + 0.2 * trace.duration, trace.startTime + trace.duration],
};
wrapper = shallow(, { ...options, context });
const leftBox = wrapper.find('.trace-page-timeline__graph--inactive');
@@ -82,7 +75,7 @@ describe('', () => {
it('renders a filtering box if rightBound exists', () => {
const context = {
...options.context,
- timeRangeFilter: [trace.timestamp, trace.timestamp + 0.8 * trace.duration],
+ timeRangeFilter: [trace.startTime, trace.startTime + 0.8 * trace.duration],
};
wrapper = shallow(, { ...options, context });
const rightBox = wrapper.find('.trace-page-timeline__graph--inactive');
@@ -132,7 +125,7 @@ describe('', () => {
it('passes items to SpanGraph', () => {
const spanGraph = wrapper.find(SpanGraph).first();
- const items = xformedTrace.spans.map(span => ({
+ const items = trace.spans.map(span => ({
valueOffset: span.relativeStartTime,
valueWidth: span.duration,
serviceName: span.process.serviceName,
@@ -151,9 +144,8 @@ describe('', () => {
it('returns true for new trace', () => {
const state = wrapper.state();
const instance = wrapper.instance();
- const trace2 = hydrateSpansWithProcesses(traceGenerator.trace({}));
- const xformedTrace2 = transformTrace(trace2);
- const altProps = { trace: trace2, xformedTrace: xformedTrace2 };
+ const trace2 = transformTraceData(traceGenerator.trace({}));
+ const altProps = { trace: trace2 };
expect(instance.shouldComponentUpdate(altProps, state, options.context)).toBe(true);
});
@@ -190,7 +182,7 @@ describe('', () => {
});
it('updates the timeRangeFilter for the left handle', () => {
- const timestamp = trace.timestamp;
+ const timestamp = trace.startTime;
const duration = trace.duration;
const updateTimeRangeFilter = sinon.spy();
const context = { ...options.context, updateTimeRangeFilter };
@@ -204,7 +196,7 @@ describe('', () => {
});
it('updates the timeRangeFilter for the right handle', () => {
- const timestamp = trace.timestamp;
+ const timestamp = trace.startTime;
const duration = trace.duration;
const updateTimeRangeFilter = sinon.spy();
const context = { ...options.context, updateTimeRangeFilter };
diff --git a/src/components/TracePage/TraceTimelineViewer/ListView/Positions.js b/src/components/TracePage/TraceTimelineViewer/ListView/Positions.js
index 994815b439..bfc630771d 100644
--- a/src/components/TracePage/TraceTimelineViewer/ListView/Positions.js
+++ b/src/components/TracePage/TraceTimelineViewer/ListView/Positions.js
@@ -33,8 +33,6 @@ export default class Positions {
/**
* Indicates how far past the explicitly required height or y-values should
* checked.
- * @type {number}
- * @memberof Positions
*/
bufferLen: number;
dataLen: number;
@@ -43,8 +41,6 @@ export default class Positions {
* `lastI` keeps track of which values have already been visited. In many
* scenarios, values do not need to be revisited. But, revisiting is required
* when heights have changed, so `lastI` can be forced.
- * @type {number}
- * @memberof Positions
*/
lastI: number;
ys: number[];
@@ -60,8 +56,6 @@ export default class Positions {
/**
* Used to make sure the length of y-values and heights is consistent with
* the context; in particular `lastI` needs to remain valid.
- *
- * @param {any[]} data
*/
profileData(dataLength: number) {
if (dataLength !== this.dataLen) {
@@ -78,10 +72,7 @@ export default class Positions {
* Calculate and save the heights and y-values, based on `heightGetter`, from
* `lastI` until the`max` index; the starting point (`lastI`) can be forced
* via the `forcedLastI` parameter.
- *
- * @param {number} max
- * @param {number} heightGetter
- * @param {number} [forcedLastI]
+ * @param {number=} forcedLastI
*/
calcHeights(max: number, heightGetter: number => number, forcedLastI?: number) {
if (forcedLastI != null) {
@@ -110,9 +101,6 @@ export default class Positions {
/**
* Verify the height and y-values from `lastI` up to `yValue`.
- *
- * @param {number} yValue
- * @param {number => number} heightGetter
*/
calcYs(yValue: number, heightGetter: number => number) {
while ((this.ys[this.lastI] == null || yValue > this.ys[this.lastI]) && this.lastI < this.dataLen - 1) {
@@ -124,10 +112,6 @@ export default class Positions {
* Given a target y-value (`yValue`), find the closest index (in the `.ys`
* array) that is prior to the y-value; e.g. map from y-value to index in
* `.ys`.
- *
- * @param {number} yValue
- * @param {number => number} heightGetter
- * @returns {number}
*/
findFloorIndex(yValue: number, heightGetter: number => number): number {
this.calcYs(yValue, heightGetter);
@@ -164,8 +148,6 @@ export default class Positions {
/**
* Get the estimated height of the whole shebang by extrapolating based on
* the average known height.
- *
- * @returns {number}
*/
getEstimatedHeight(): number {
const known = this.ys[this.lastI] + this.heights[this.lastI];
@@ -183,9 +165,6 @@ export default class Positions {
* known territory (_i <= lastI) and the height is different than what is
* known, recalculate subsequent y values, but don't confirm the heights of
* those items, just update based on the difference.
- *
- * @param {number} _i
- * @param {number => number} heightGetter
*/
confirmHeight(_i: number, heightGetter: number => number) {
let i = _i;
diff --git a/src/components/TracePage/TraceTimelineViewer/ListView/index.js b/src/components/TracePage/TraceTimelineViewer/ListView/index.js
index 8094a8839b..572b375739 100644
--- a/src/components/TracePage/TraceTimelineViewer/ListView/index.js
+++ b/src/components/TracePage/TraceTimelineViewer/ListView/index.js
@@ -24,44 +24,40 @@ import * as React from 'react';
import Positions from './Positions';
+/**
+ * @typedef
+ */
type ListViewProps = {
/**
* Number of elements in the list.
- * @type {number}
*/
dataLength: number,
/**
* Convert item index (number) to the key (string). ListView uses both indexes
* and keys to handle the addtion of new rows.
- * @type {string => number}
*/
getIndexFromKey: string => number,
/**
* Convert item key (string) to the index (number). ListView uses both indexes
* and keys to handle the addtion of new rows.
- * @type {number => string}
*/
getKeyFromIndex: number => string,
/**
* Number of items to draw and add to the DOM, initially.
- * @type {number}
*/
initialDraw: number,
/**
* The parent provides fallback height measurements when there is not a
* rendered element to measure.
- * @type {(number, string) => number}
*/
itemHeightGetter: (number, string) => number,
/**
* Function that renders an item; rendered items are added directly to the
* DOM, they are not wrapped in list item wrapper HTMLElement.
- * @type {(string, {}, number, {}) => React.Node}
*/
itemRenderer: (string, {}, number, {}) => React.Node,
/**
* `className` for the HTMLElement that holds the items.
- * @type {string}
*/
itemsWrapperClassName?: string,
/**
@@ -70,14 +66,12 @@ type ListViewProps = {
* halfway down (so items [46, 55] are in view), then when a new range of
* items is rendered, it will render items `46 - viewBuffer` to
* `55 + viewBuffer`.
- * @type {number}
*/
viewBuffer: number,
/**
* The minimum number of items offscreen in either direction; e.g. at least
* `viewBuffer` number of items must be off screen above and below the
* current view, or more items will be rendered.
- * @type {number}
*/
viewBufferMin: number,
/**
@@ -86,9 +80,8 @@ type ListViewProps = {
* scrolling as a result of the ListView. Similar to react-virtualized
* window scroller.
*
- * Ref: https://bvaughn.github.io/react-virtualized/#/components/WindowScroller
- * Ref:https://github.com/bvaughn/react-virtualized/blob/497e2a1942529560681d65a9ef9f5e9c9c9a49ba/docs/WindowScroller.md
- * @type {boolean}
+ * - Ref: https://bvaughn.github.io/react-virtualized/#/components/WindowScroller
+ * - Ref:https://github.com/bvaughn/react-virtualized/blob/497e2a1942529560681d65a9ef9f5e9c9c9a49ba/docs/WindowScroller.md
*/
windowScroller?: boolean,
};
@@ -107,77 +100,62 @@ type ListViewProps = {
*
* @export
* @class ListView
- * @extends {React.PureComponent}
*/
export default class ListView extends React.Component {
props: ListViewProps;
/**
* Keeps track of the height and y-value of items, by item index, in the
* ListView.
- * @type {Positions}
*/
_yPositions: Positions;
/**
* Keep track of the known / measured heights of the rendered items; populated
* with values through observation and keyed on the item key, not the item
* index.
- * @type {Map}
*/
_knownHeights: Map;
/**
* The start index of the items currently drawn.
- * @type {number}
*/
_startIndexDrawn: number;
/**
* The end index of the items currently drawn.
- * @type {number}
*/
_endIndexDrawn: number;
/**
* The start index of the items currently in view.
- * @type {number}
*/
_startIndex: number;
/**
* The end index of the items currently in view.
- * @type {number}
*/
_endIndex: number;
/**
* Height of the visual window, e.g. height of the scroller element.
- * @type {number}
*/
_viewHeight: number;
/**
* `scrollTop` of the current scroll position.
- * @type {number}
*/
_scrollTop: number;
/**
* Used to keep track of whether or not a re-calculation of what should be
* drawn / viewable has been scheduled.
- * @type {boolean}
*/
_isScrolledOrResized: boolean;
/**
* If `windowScroller` is true, this notes how far down the page the scroller
* is located. (Note: repositioning and below-the-fold views are untested)
- *
- * @type {number}
- * @memberof ListView
*/
_htmlTopOffset: number;
_windowScrollListenerAdded: boolean;
_htmlElm: HTMLElement;
/**
* HTMLElement holding the scroller.
- * @type HTMLElement
*/
_wrapperElm: ?HTMLElement;
/**
* HTMLElement holding the rendered items.
- * @type HTMLElement
*/
_itemHolderElm: ?HTMLElement;
@@ -253,8 +231,6 @@ export default class ListView extends React.Component {
/**
* Scroll event listener that schedules a remeasuring of which items should be
* rendered.
- *
- * @memberof ListView
*/
_onScroll = function _onScroll() {
if (!this._isScrolledOrResized) {
@@ -317,8 +293,6 @@ export default class ListView extends React.Component {
/**
* Checked to see if the currently rendered items are sufficient, if not,
* force an update to trigger more items to be rendered.
- *
- * @memberof ListView
*/
_positionList = function _positionList() {
this._isScrolledOrResized = false;
@@ -356,8 +330,6 @@ export default class ListView extends React.Component {
* Go through all items that are rendered and save their height based on their
* item-key (which is on a data-* attribute). If any new or adjusted heights
* are found, re-measure the current known y-positions (via .yPositions).
- *
- * @memberof ListView
*/
_scanItemHeights = function _scanItemHeights() {
const getIndexFromKey = this.props.getIndexFromKey;
@@ -411,8 +383,6 @@ export default class ListView extends React.Component {
/**
* Get the height of the element at index `i`; first check the known heigths,
* fallbck to `.props.itemHeightGetter(...)`.
- *
- * @memberof ListView
*/
_getHeight = function _getHeight(i: number) {
const key = this.props.getKeyFromIndex(i);
diff --git a/src/components/TracePage/TraceTimelineViewer/SpanDetail/AccordianKeyValues.css b/src/components/TracePage/TraceTimelineViewer/SpanDetail/AccordianKeyValues.css
index 25de81043e..9f4f73bb15 100644
--- a/src/components/TracePage/TraceTimelineViewer/SpanDetail/AccordianKeyValues.css
+++ b/src/components/TracePage/TraceTimelineViewer/SpanDetail/AccordianKeyValues.css
@@ -38,14 +38,18 @@ THE SOFTWARE.
white-space: nowrap;
}
-.AccordianKeyValues--header:hover {
+.AccordianKeyValues--header:not(.is-empty):hover {
background: #eee;
}
-.AccordianKeyValues--header.is-high-contrast:hover {
+.AccordianKeyValues--header.is-high-contrast:not(.is-empty):hover {
background: #ddd;
}
+.AccordianKeyValues--emptyArtifact {
+ color: #aaa;
+}
+
.AccordianKeyValues--summary {
display: inline;
list-style: none;
diff --git a/src/components/TracePage/TraceTimelineViewer/SpanDetail/AccordianKeyValues.js b/src/components/TracePage/TraceTimelineViewer/SpanDetail/AccordianKeyValues.js
index de0c4b4dc3..6bb6e6d43b 100644
--- a/src/components/TracePage/TraceTimelineViewer/SpanDetail/AccordianKeyValues.js
+++ b/src/components/TracePage/TraceTimelineViewer/SpanDetail/AccordianKeyValues.js
@@ -21,6 +21,7 @@
// THE SOFTWARE.
import React from 'react';
+import cx from 'classnames';
import KeyValuesTable from './KeyValuesTable';
@@ -35,7 +36,11 @@ type AccordianKeyValuesProps = {
onToggle: () => void,
};
-function getKVSummary(data: { key: string, value: any }[]) {
+function KeyValuesSummary(props: { data?: { key: string, value: any }[] }) {
+ const { data } = props;
+ if (!Array.isArray(data) || !data.length) {
+ return null;
+ }
return (