From 1635f1914f889ba4ef63185f0d1198445f3fcf4a Mon Sep 17 00:00:00 2001 From: Alberto Gutierrez Date: Mon, 8 Oct 2018 12:33:10 +0200 Subject: [PATCH] SearchTrace and TracePage Embedded components Signed-off-by: Alberto Gutierrez --- packages/jaeger-ui/src/components/App/Page.js | 11 +- .../SearchResults/ResultItem.js | 11 +- .../SearchResults/ResultItemTitle.js | 16 ++- .../SearchTracePage/SearchResults/index.js | 57 +++++++--- .../SearchResults/index.test.js | 11 ++ .../src/components/SearchTracePage/index.js | 59 +++++++--- .../components/SearchTracePage/index.test.js | 25 ++++ .../components/TracePage/TracePageHeader.css | 15 +++ .../components/TracePage/TracePageHeader.js | 107 +++++++++++++----- .../src/components/TracePage/index.js | 37 ++++-- .../src/components/TracePage/index.test.js | 23 ++++ 11 files changed, 290 insertions(+), 82 deletions(-) diff --git a/packages/jaeger-ui/src/components/App/Page.js b/packages/jaeger-ui/src/components/App/Page.js index 47fe93fd98..c4ea04bfe4 100644 --- a/packages/jaeger-ui/src/components/App/Page.js +++ b/packages/jaeger-ui/src/components/App/Page.js @@ -52,14 +52,17 @@ export class PageImpl extends React.Component { } render() { + const isEmbed = this.props.search.includes('embed'); return (
-
- -
- {this.props.children} + {!isEmbed && ( +
+ +
+ )} + {this.props.children}
); diff --git a/packages/jaeger-ui/src/components/SearchTracePage/SearchResults/ResultItem.js b/packages/jaeger-ui/src/components/SearchTracePage/SearchResults/ResultItem.js index fce348dbe2..fd6c7fddd1 100644 --- a/packages/jaeger-ui/src/components/SearchTracePage/SearchResults/ResultItem.js +++ b/packages/jaeger-ui/src/components/SearchTracePage/SearchResults/ResultItem.js @@ -36,6 +36,7 @@ type Props = { linkTo: string, toggleComparison: string => void, trace: Trace, + disableComparision: boolean, }; const isErrorTag = ({ key, value }) => key === 'error' && (value === true || value === 'true'); @@ -44,7 +45,14 @@ export default class ResultItem extends React.PureComponent { props: Props; render() { - const { durationPercent, isInDiffCohort, linkTo, toggleComparison, trace } = this.props; + const { + durationPercent, + isInDiffCohort, + linkTo, + disableComparision, + toggleComparison, + trace, + } = this.props; const { duration, services, startTime, spans, traceName, traceID } = trace; const mDate = moment(startTime / 1000); const timeStr = mDate.format('h:mm:ss a'); @@ -61,6 +69,7 @@ export default class ResultItem extends React.PureComponent { toggleComparison={toggleComparison} traceID={traceID} traceName={traceName} + disableComparision={disableComparision} /> diff --git a/packages/jaeger-ui/src/components/SearchTracePage/SearchResults/ResultItemTitle.js b/packages/jaeger-ui/src/components/SearchTracePage/SearchResults/ResultItemTitle.js index b544eaa9d5..0839c98bc9 100644 --- a/packages/jaeger-ui/src/components/SearchTracePage/SearchResults/ResultItemTitle.js +++ b/packages/jaeger-ui/src/components/SearchTracePage/SearchResults/ResultItemTitle.js @@ -37,6 +37,7 @@ type Props = { toggleComparison: (string, boolean) => void, traceID: string, traceName: string, + disableComparision?: boolean, }; export default class ResultItemTitle extends React.PureComponent { @@ -64,6 +65,7 @@ export default class ResultItemTitle extends React.PureComponent { state, traceID, traceName, + disableComparision, } = this.props; let WrapperComponent = 'div'; const wrapperProps: { [string]: string } = { className: 'ResultItemTitle--item ub-flex-auto' }; @@ -74,12 +76,14 @@ export default class ResultItemTitle extends React.PureComponent { const isErred = state === fetchedState.ERROR; return (
- + {!disableComparision && ( + + )} {duration != null && {formatDuration(duration)}} diff --git a/packages/jaeger-ui/src/components/SearchTracePage/SearchResults/index.js b/packages/jaeger-ui/src/components/SearchTracePage/SearchResults/index.js index bfaa2a670c..82f8923bcd 100644 --- a/packages/jaeger-ui/src/components/SearchTracePage/SearchResults/index.js +++ b/packages/jaeger-ui/src/components/SearchTracePage/SearchResults/index.js @@ -15,7 +15,7 @@ // limitations under the License. import * as React from 'react'; -import { Select } from 'antd'; +import { Button, Select } from 'antd'; import { Field, reduxForm, formValueSelector } from 'redux-form'; import DiffSelection from './DiffSelection'; @@ -37,7 +37,11 @@ type SearchResultsProps = { cohortRemoveTrace: string => void, diffCohort: FetchedTrace[], goToTrace: string => void, + isEmbed?: boolean, + hideGraph?: boolean, + disableComparision?: boolean, loading: boolean, + onGoFullClicked: () => void, maxTraceDuration: number, skipMessage?: boolean, traces: TraceSummary[], @@ -107,28 +111,44 @@ export default class SearchResults extends React.PureComponent ); } - const { goToTrace, maxTraceDuration } = this.props; + const { + goToTrace, + isEmbed, + hideGraph, + disableComparision, + onGoFullClicked, + maxTraceDuration, + } = this.props; const cohortIds = new Set(diffCohort.map(datum => datum.id)); return (
-
- ({ - x: t.startTime, - y: t.duration, - traceID: t.traceID, - size: t.spans.length, - name: t.traceName, - }))} - onValueClick={t => { - goToTrace(t.traceID); - }} - /> -
+ {!hideGraph && ( +
+ ({ + x: t.startTime, + y: t.duration, + traceID: t.traceID, + size: t.spans.length, + name: t.traceName, + }))} + onValueClick={t => { + goToTrace(t.traceID); + }} + /> +
+ )}
+ {isEmbed && ( + + )}

{traces.length} Trace{traces.length > 1 && 's'}

@@ -136,16 +156,17 @@ export default class SearchResults extends React.PureComponent
- {diffSelection} + {!disableComparision && diffSelection}
    {traces.map(trace => (
  • ))} diff --git a/packages/jaeger-ui/src/components/SearchTracePage/SearchResults/index.test.js b/packages/jaeger-ui/src/components/SearchTracePage/SearchResults/index.test.js index df5ad9f588..b70d365df7 100644 --- a/packages/jaeger-ui/src/components/SearchTracePage/SearchResults/index.test.js +++ b/packages/jaeger-ui/src/components/SearchTracePage/SearchResults/index.test.js @@ -20,6 +20,7 @@ import * as markers from './index.markers'; import ResultItem from './ResultItem'; import ScatterPlot from './ScatterPlot'; import LoadingIndicator from '../../common/LoadingIndicator'; +import DiffSelection from './DiffSelection'; describe('', () => { let wrapper; @@ -48,6 +49,16 @@ describe('', () => { expect(wrapper.find(LoadingIndicator).length).toBe(1); }); + it('hide scatter plot if queryparam hideGraph', () => { + wrapper.setProps({ hideGraph: true }); + expect(wrapper.find(ScatterPlot).length).toBe(0); + }); + + it('hide DiffSelection if queryparam disableComparision', () => { + wrapper.setProps({ disableComparision: true }); + expect(wrapper.find(DiffSelection).length).toBe(0); + }); + describe('search finished with results', () => { it('shows a scatter plot', () => { expect(wrapper.find(ScatterPlot).length).toBe(1); diff --git a/packages/jaeger-ui/src/components/SearchTracePage/index.js b/packages/jaeger-ui/src/components/SearchTracePage/index.js index 65cd1b59d8..90738b4268 100644 --- a/packages/jaeger-ui/src/components/SearchTracePage/index.js +++ b/packages/jaeger-ui/src/components/SearchTracePage/index.js @@ -60,7 +60,15 @@ export class SearchTracePageImpl extends Component { } goToTrace = traceID => { - this.props.history.push(prefixUrl(`/trace/${traceID}`)); + const url = this.props.isEmbed ? `/trace/${traceID}?embed` : `/trace/${traceID}`; + this.props.history.push(prefixUrl(url)); + }; + + goFullView = () => { + const urlQuery = this.props.query; + delete urlQuery.embed; + const url = `/search?${queryString.stringify(urlQuery)}`; + window.open(prefixUrl(url), '_blank'); }; render() { @@ -75,6 +83,9 @@ export class SearchTracePageImpl extends Component { maxTraceDuration, services, traceResults, + isEmbed, + hideGraph, + disableComparision, } = this.props; const hasTraceResults = traceResults && traceResults.length > 0; const showErrors = errors && !loadingTraces; @@ -82,12 +93,14 @@ export class SearchTracePageImpl extends Component { return (
    - -
    -

    Find Traces

    - {!loadingServices && services ? : } -
    - + {!isEmbed && ( + +
    +

    Find Traces

    + {!loadingServices && services ? : } +
    + + )} {showErrors && (
    @@ -104,17 +117,22 @@ export class SearchTracePageImpl extends Component { cohortRemoveTrace={cohortRemoveTrace} diffCohort={diffCohort} skipMessage={isHomepage} + onGoFullClicked={this.goFullView} traces={traceResults} + isEmbed={isEmbed} + hideGraph={hideGraph} + disableComparision={disableComparision} /> )} - {showLogo && ( - presentation - )} + {showLogo && + !isEmbed && ( + presentation + )}
    @@ -123,7 +141,11 @@ export class SearchTracePageImpl extends Component { } SearchTracePageImpl.propTypes = { + query: PropTypes.object, isHomepage: PropTypes.bool, + isEmbed: PropTypes.bool, + hideGraph: PropTypes.bool, + disableComparision: PropTypes.bool, // eslint-disable-next-line react/forbid-prop-types traceResults: PropTypes.array, diffCohort: PropTypes.array, @@ -196,6 +218,9 @@ const stateServicesXformer = getLastXformCacher(stateServices => { // export to test export function mapStateToProps(state) { const query = queryString.parse(state.router.location.search); + const isEmbed = 'embed' in query; + const hideGraph = 'hideGraph' in query; + const disableComparision = 'disableComparision' in query; const isHomepage = !Object.keys(query).length; const { traces, maxDuration, traceError, loadingTraces } = stateTraceXformer(state.trace); const diffCohort = stateTraceDiffXformer(state.trace, state.traceDiff); @@ -210,7 +235,11 @@ export function mapStateToProps(state) { const sortBy = sortFormSelector(state, 'sortBy'); const traceResults = sortedTracesXformer(traces, sortBy); return { + query, diffCohort, + isEmbed, + hideGraph, + disableComparision, isHomepage, loadingServices, loadingTraces, diff --git a/packages/jaeger-ui/src/components/SearchTracePage/index.test.js b/packages/jaeger-ui/src/components/SearchTracePage/index.test.js index b5c4630f10..904151dfa2 100644 --- a/packages/jaeger-ui/src/components/SearchTracePage/index.test.js +++ b/packages/jaeger-ui/src/components/SearchTracePage/index.test.js @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +import prefixUrl from '../../utils/prefix-url'; + jest.mock('redux-form', () => { function reduxForm() { return component => component; @@ -48,6 +50,8 @@ describe('', () => { traceResults = [{ traceID: 'a', spans: [], processes: {} }, { traceID: 'b', spans: [], processes: {} }]; props = { traceResults, + isEmbed: false, + disableComparision: false, isHomepage: false, loadingServices: false, loadingTraces: false, @@ -100,6 +104,23 @@ describe('', () => { wrapper.setProps({ isHomepage: true, traceResults: [] }); expect(wrapper.find('.js-test-logo').length).toBe(1); }); + + it('hide SearchForm if is embed', () => { + wrapper.setProps({ isEmbed: true }); + expect(wrapper.find(SearchForm).length).toBe(0); + }); + + it('hide logo if is embed', () => { + wrapper.setProps({ isEmbed: true }); + expect(wrapper.find('.js-test-logo').length).toBe(0); + }); + + it('open a window when goFullView is called', () => { + wrapper.setProps({ query: { embed: 'embed', service: 'jaeger-query' } }); + global.open = jest.fn(); + wrapper.instance().goFullView(); + expect(global.open).toBeCalledWith(prefixUrl('/search?service=jaeger-query'), '_blank'); + }); }); describe('mapStateToProps()', () => { @@ -140,6 +161,10 @@ describe('mapStateToProps()', () => { expect(diffCohort[0].data.traceID).toBe(trace.traceID); expect(rest).toEqual({ + isEmbed: false, + hideGraph: false, + disableComparision: false, + query: {}, isHomepage: true, // the redux-form `formValueSelector` mock returns `null` for "sortBy" sortTracesBy: null, diff --git a/packages/jaeger-ui/src/components/TracePage/TracePageHeader.css b/packages/jaeger-ui/src/components/TracePage/TracePageHeader.css index 11ba9965de..fb663ad358 100644 --- a/packages/jaeger-ui/src/components/TracePage/TracePageHeader.css +++ b/packages/jaeger-ui/src/components/TracePage/TracePageHeader.css @@ -19,11 +19,26 @@ limitations under the License. display: flex; } +Divider { + color: red; +} + +.TracePageHeader--titleRowEmbed { + align-items: center; + background-color: #ececec; + border-bottom: 1px solid #d8d8d8; + display: flex; +} + .TracePageHeader--title { margin: 0; padding: 0.25rem 0.5rem; } +.TracePageHeader--titleEmbed { + margin: 0; + padding: 0.25rem 0.5rem; +} .TracePageHeader--title:hover { background: #f5f5f5; } diff --git a/packages/jaeger-ui/src/components/TracePage/TracePageHeader.js b/packages/jaeger-ui/src/components/TracePage/TracePageHeader.js index f308d852f0..a657ffa704 100644 --- a/packages/jaeger-ui/src/components/TracePage/TracePageHeader.js +++ b/packages/jaeger-ui/src/components/TracePage/TracePageHeader.js @@ -15,9 +15,10 @@ // limitations under the License. import * as React from 'react'; -import { Button, Dropdown, Icon, Input, Menu } from 'antd'; +import { Button, Dropdown, Icon, Input, Menu, Divider } from 'antd'; import IoChevronDown from 'react-icons/lib/io/chevron-down'; import IoChevronRight from 'react-icons/lib/io/chevron-right'; +import IoChevronLeft from 'react-icons/lib/io/chevron-left'; import IoIosFilingOutline from 'react-icons/lib/io/ios-filing-outline'; import { Link } from 'react-router-dom'; @@ -45,6 +46,10 @@ type TracePageHeaderProps = { resultCount: number, archiveButtonVisible: boolean, onArchiveClicked: () => void, + onGoBackClicked: () => void, + onGoFullViewClicked: () => void, + embed: boolean, + details: boolean, // these props are used by the `HEADER_ITEMS` // eslint-disable-next-line react/no-unused-prop-types timestamp: number, @@ -104,6 +109,8 @@ export function TracePageHeaderFn(props: TracePageHeaderProps) { name, slimView, onSlimViewClicked, + onGoBackClicked, + onGoFullViewClicked, updateTextFilter, textFilter, prevResult, @@ -111,6 +118,8 @@ export function TracePageHeaderFn(props: TracePageHeaderProps) { clearSearch, resultCount, forwardedRef, + embed, + details, } = props; if (!traceID) { @@ -170,38 +179,74 @@ export function TracePageHeaderFn(props: TracePageHeaderProps) { }, ]; + const embedComponent = ( +
    + + Go back + + +

    + {name || FALLBACK_TRACE_NAME} +

    + + View Trace + + +
    + ); + return (
    -
    - -

    - {slimView ? : } - {name || FALLBACK_TRACE_NAME} -

    -
    - - - - - - {archiveButtonVisible && ( - - )} -
    - {!slimView && } + {embed ? ( + embedComponent + ) : ( +
    + +

    + {slimView ? : } + {name || FALLBACK_TRACE_NAME} +

    +
    + + + + + + + {archiveButtonVisible && ( + + )} +
    + )} + {((!slimView && !embed) || (embed && details)) && ( + + )}
    ); } diff --git a/packages/jaeger-ui/src/components/TracePage/index.js b/packages/jaeger-ui/src/components/TracePage/index.js index a53d1393d0..931c8b9355 100644 --- a/packages/jaeger-ui/src/components/TracePage/index.js +++ b/packages/jaeger-ui/src/components/TracePage/index.js @@ -20,6 +20,7 @@ import _mapValues from 'lodash/mapValues'; import _maxBy from 'lodash/maxBy'; import _values from 'lodash/values'; import { connect } from 'react-redux'; +import queryString from 'query-string'; import type { RouterHistory, Match } from 'react-router-dom'; import { bindActionCreators } from 'redux'; import { Input } from 'antd'; @@ -58,6 +59,10 @@ type TracePageProps = { history: RouterHistory, id: string, trace: ?FetchedTrace, + isEmbed?: boolean, + minimap?: boolean, + details?: boolean, + mapCollapsed?: boolean, }; type TracePageState = { @@ -157,6 +162,9 @@ export class TracePageImpl extends React.PureComponent { + filterSpans: string => ?Set = (textFilter: string) => { const spans = this.props.trace && this.props.trace.data && this.props.trace.data.spans; if (!spans) return null; @@ -258,9 +266,7 @@ export class TracePageImpl extends React.PureComponent isTextInKeyValues(log.fields)) || isTextInKeyValues(span.process.tags); - // declare as const because need to disambiguate the type - const rv: Set = new Set(spans.filter(isSpanAMatch).map((span: Span) => span.spanID)); - return rv; + return new Set(spans.filter(isSpanAMatch).map((span: Span) => span.spanID)); }; updateTextFilter = (textFilter: string) => { @@ -326,8 +332,16 @@ export class TracePageImpl extends React.PureComponent { + this.props.history.goBack(); + }; + + goFullView = () => { + window.open(prefixUrl(`/trace/${this.props.id.toLowerCase()}`), '_blank'); + }; + render() { - const { archiveEnabled, archiveTraceState, trace } = this.props; + const { archiveEnabled, archiveTraceState, trace, isEmbed, minimap, details } = this.props; const { slimView, headerHeight, textFilter, viewRange, findMatchesIDs } = this.state; if (!trace || trace.state === fetchedState.LOADING) { return ; @@ -363,9 +377,13 @@ export class TracePageImpl extends React.PureComponent - {!slimView && ( + {((!slimView && !isEmbed) || (isEmbed && minimap)) && ( ', () => { expect(cancelScroll.mock.calls).toEqual([[]]); }); + it('collapse map if queryparam mapCollapsed', () => { + wrapper.setProps({ mapCollapsed: true }); + expect(wrapper.find(SpanGraph).length).toBe(0); + }); + + it('open a window when goFullView is called', () => { + wrapper.setProps({ id: '12345' }); + global.open = jest.fn(); + wrapper.instance().goFullView(); + expect(global.open).toBeCalledWith(prefixUrl('/trace/12345'), '_blank'); + }); + describe('_adjustViewRange()', () => { let instance; let time; @@ -359,6 +373,11 @@ describe('mapStateToProps()', () => { [id]: { data: trace, state: fetchedState.DONE }, }, }, + router: { + location: { + search: '', + }, + }, config: { archiveEnabled: false, }, @@ -373,6 +392,10 @@ describe('mapStateToProps()', () => { expect(props).toEqual({ id, archiveEnabled: false, + isEmbed: false, + details: false, + minimap: false, + mapCollapsed: false, archiveTraceState: undefined, trace: { data: {}, state: fetchedState.DONE }, });