diff --git a/packages/jaeger-ui/src/components/DeepDependencies/Header/index.test.js b/packages/jaeger-ui/src/components/DeepDependencies/Header/index.test.js index dbf037f106..fb02c63ffa 100644 --- a/packages/jaeger-ui/src/components/DeepDependencies/Header/index.test.js +++ b/packages/jaeger-ui/src/components/DeepDependencies/Header/index.test.js @@ -20,7 +20,7 @@ import MdVisibilityOff from 'react-icons/lib/md/visibility-off'; import Header from './index'; import HopsSelector from './HopsSelector'; -import NameSelector from './NameSelector'; +import NameSelector from '../../common/NameSelector'; import * as track from '../index.track'; describe('
', () => { diff --git a/packages/jaeger-ui/src/components/DeepDependencies/Header/index.tsx b/packages/jaeger-ui/src/components/DeepDependencies/Header/index.tsx index 46947bebbe..c15b59fa39 100644 --- a/packages/jaeger-ui/src/components/DeepDependencies/Header/index.tsx +++ b/packages/jaeger-ui/src/components/DeepDependencies/Header/index.tsx @@ -18,7 +18,7 @@ import MdVisibility from 'react-icons/lib/md/visibility'; import MdVisibilityOff from 'react-icons/lib/md/visibility-off'; import HopsSelector from './HopsSelector'; -import NameSelector from './NameSelector'; +import NameSelector from '../../common/NameSelector'; import LayoutSettings from './LayoutSettings'; import { trackFilter, trackHeaderSetOperation, trackShowMatches } from '../index.track'; import UiFindInput from '../../common/UiFindInput'; diff --git a/packages/jaeger-ui/src/components/QualityMetrics/Header.tsx b/packages/jaeger-ui/src/components/QualityMetrics/Header.tsx index 51ad574043..18435b7930 100644 --- a/packages/jaeger-ui/src/components/QualityMetrics/Header.tsx +++ b/packages/jaeger-ui/src/components/QualityMetrics/Header.tsx @@ -16,7 +16,7 @@ import * as React from 'react'; import { InputNumber } from 'antd'; import _debounce from 'lodash/debounce'; -import NameSelector from '../DeepDependencies/Header/NameSelector'; +import NameSelector from '../common/NameSelector'; import './Header.css'; diff --git a/packages/jaeger-ui/src/components/TracePage/TracePageHeader/AltViewOptions.css b/packages/jaeger-ui/src/components/TracePage/TracePageHeader/AltViewOptions.css new file mode 100644 index 0000000000..55d07e07fe --- /dev/null +++ b/packages/jaeger-ui/src/components/TracePage/TracePageHeader/AltViewOptions.css @@ -0,0 +1,23 @@ +/* +Copyright (c) 2020 The Jaeger Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +.AltViewOptions { + border: 1px solid #d9d9d9; + border-radius: 4px; + height: 32px; + line-height: 30px; + margin-right: 1rem; + padding: 0 8px; +} diff --git a/packages/jaeger-ui/src/components/TracePage/TracePageHeader/AltViewOptions.test.js b/packages/jaeger-ui/src/components/TracePage/TracePageHeader/AltViewOptions.test.js index 4a05c9a2b9..c2a0d93561 100644 --- a/packages/jaeger-ui/src/components/TracePage/TracePageHeader/AltViewOptions.test.js +++ b/packages/jaeger-ui/src/components/TracePage/TracePageHeader/AltViewOptions.test.js @@ -14,17 +14,18 @@ import * as React from 'react'; import { shallow } from 'enzyme'; -import { Button, Dropdown } from 'antd'; +import { Dropdown } from 'antd'; import { Link } from 'react-router-dom'; - import AltViewOptions from './AltViewOptions'; import * as track from './TracePageHeader.track'; +import { ETraceViewType } from '../types'; describe('AltViewOptions', () => { let trackGanttView; let trackGraphView; let trackJsonView; let trackRawJsonView; + let trackStatisticsView; let wrapper; const getLink = text => { @@ -34,14 +35,18 @@ describe('AltViewOptions', () => { const link = links.at(i); if (link.children().text() === text) return link; } - const link = menu.find('a'); - if (link.children().text() === text) return link; + const links2 = menu.find('a'); + for (let i = 0; i < links.length; i++) { + const link = links2.at(i); + if (link.children().text() === text) return link; + } throw new Error(`Could not find "${text}"`); }; + const props = { - traceGraphView: true, + viewType: ETraceViewType.TraceTimelineViewer, traceID: 'test trace ID', - onTraceGraphViewClicked: jest.fn(), + onTraceViewChange: jest.fn(), }; beforeAll(() => { @@ -49,6 +54,7 @@ describe('AltViewOptions', () => { trackGraphView = jest.spyOn(track, 'trackGraphView'); trackJsonView = jest.spyOn(track, 'trackJsonView'); trackRawJsonView = jest.spyOn(track, 'trackRawJsonView'); + trackStatisticsView = jest.spyOn(track, 'trackStatisticsView'); }); beforeEach(() => { @@ -74,33 +80,39 @@ describe('AltViewOptions', () => { expect(trackGraphView).not.toHaveBeenCalled(); }); - it('toggles and tracks toggle', () => { - expect(trackGanttView).not.toHaveBeenCalled(); - expect(props.onTraceGraphViewClicked).not.toHaveBeenCalled(); - getLink('Trace Timeline').simulate('click'); - expect(trackGanttView).toHaveBeenCalledTimes(1); - expect(props.onTraceGraphViewClicked).toHaveBeenCalledTimes(1); + it('track dropdown menu', () => { + const viewInteractions = [ + { + link: 'Trace Graph', + trackFn: trackGraphView, + onTraceViewChangeArg: ETraceViewType.TraceGraph, + }, + { + link: 'Trace Statistics', + trackFn: trackStatisticsView, + onTraceViewChangeArg: ETraceViewType.TraceStatisticsView, + propViewType: ETraceViewType.TraceGraph, + }, + { + link: 'Trace Timeline', + trackFn: trackGanttView, + onTraceViewChangeArg: ETraceViewType.TraceTimelineViewer, + propViewType: ETraceViewType.TraceStatisticsView, + }, + ]; - wrapper.setProps({ traceGraphView: false }); - expect(trackGraphView).not.toHaveBeenCalled(); - getLink('Trace Graph').simulate('click'); - expect(trackGraphView).toHaveBeenCalledTimes(1); - expect(props.onTraceGraphViewClicked).toHaveBeenCalledTimes(2); - - wrapper.setProps({ traceGraphView: true }); - expect(trackGanttView).toHaveBeenCalledTimes(1); - wrapper.find(Button).simulate('click'); - expect(trackGanttView).toHaveBeenCalledTimes(2); - expect(props.onTraceGraphViewClicked).toHaveBeenCalledTimes(3); - - wrapper.setProps({ traceGraphView: false }); - expect(trackGraphView).toHaveBeenCalledTimes(1); - wrapper.find(Button).simulate('click'); - expect(trackGraphView).toHaveBeenCalledTimes(2); - expect(props.onTraceGraphViewClicked).toHaveBeenCalledTimes(4); + viewInteractions.forEach(({ link, trackFn, propViewType }, i) => { + if (propViewType) { + wrapper.setProps({ viewType: propViewType }); + } + expect(props.onTraceViewChange).toHaveBeenCalledTimes(i); + expect(trackFn).not.toHaveBeenCalled(); - expect(trackGanttView).toHaveBeenCalledTimes(2); - expect(trackJsonView).not.toHaveBeenCalled(); - expect(trackRawJsonView).not.toHaveBeenCalled(); + getLink(link).simulate('click'); + expect(props.onTraceViewChange).toHaveBeenCalledTimes(i + 1); + viewInteractions.forEach(({ trackFn: fn }, j) => { + expect(fn).toHaveBeenCalledTimes(j <= i ? 1 : 0); + }); + }); }); }); diff --git a/packages/jaeger-ui/src/components/TracePage/TracePageHeader/AltViewOptions.tsx b/packages/jaeger-ui/src/components/TracePage/TracePageHeader/AltViewOptions.tsx index 6cd41df278..8d51a0292d 100644 --- a/packages/jaeger-ui/src/components/TracePage/TracePageHeader/AltViewOptions.tsx +++ b/packages/jaeger-ui/src/components/TracePage/TracePageHeader/AltViewOptions.tsx @@ -13,32 +13,64 @@ // limitations under the License. import * as React from 'react'; -import { Button, Dropdown, Icon, Menu } from 'antd'; +import { Dropdown, Icon, Menu } from 'antd'; import { Link } from 'react-router-dom'; +import './AltViewOptions.css'; -import { trackGanttView, trackGraphView, trackJsonView, trackRawJsonView } from './TracePageHeader.track'; +import { + trackGanttView, + trackGraphView, + trackStatisticsView, + trackJsonView, + trackRawJsonView, +} from './TracePageHeader.track'; import prefixUrl from '../../../utils/prefix-url'; +import { ETraceViewType } from '../types'; type Props = { - onTraceGraphViewClicked: () => void; - traceGraphView: boolean; + onTraceViewChange: (viewType: ETraceViewType) => void; traceID: string; + viewType: ETraceViewType; }; +const MENU_ITEMS = [ + { + viewType: ETraceViewType.TraceTimelineViewer, + label: 'Trace Timeline', + }, + { + viewType: ETraceViewType.TraceGraph, + label: 'Trace Graph', + }, + { + viewType: ETraceViewType.TraceStatistics, + label: 'Trace Statistics', + }, +]; + export default function AltViewOptions(props: Props) { - const { onTraceGraphViewClicked, traceGraphView, traceID } = props; - const handleToggleView = () => { - if (traceGraphView) trackGanttView(); - else trackGraphView(); - onTraceGraphViewClicked(); + const { onTraceViewChange, viewType, traceID } = props; + + const handleSelectView = (item: ETraceViewType) => { + if (item === ETraceViewType.TraceTimelineViewer) { + trackGanttView(); + } else if (item === ETraceViewType.TraceGraph) { + trackGraphView(); + } else if (item === ETraceViewType.TraceStatistics) { + trackStatisticsView(); + } + onTraceViewChange(item); }; + const menu = ( - - - {traceGraphView ? 'Trace Timeline' : 'Trace Graph'} - - + {MENU_ITEMS.filter(item => item.viewType !== viewType).map(item => ( + + handleSelectView(item.viewType)} role="button"> + {item.label} + + + ))} ); + + const currentItem = MENU_ITEMS.find(item => item.viewType === viewType); + const dropdownText = currentItem ? currentItem.label : 'Alternate Views'; return ( - +
+ {`${dropdownText} `} + +
); } diff --git a/packages/jaeger-ui/src/components/TracePage/TracePageHeader/TracePageHeader.track.tsx b/packages/jaeger-ui/src/components/TracePage/TracePageHeader/TracePageHeader.track.tsx index 0e60d178c2..4e42f2a2b8 100644 --- a/packages/jaeger-ui/src/components/TracePage/TracePageHeader/TracePageHeader.track.tsx +++ b/packages/jaeger-ui/src/components/TracePage/TracePageHeader/TracePageHeader.track.tsx @@ -24,12 +24,14 @@ export const ACTION_GANTT = 'gantt'; export const ACTION_GRAPH = 'graph'; export const ACTION_JSON = 'json'; export const ACTION_RAW_JSON = 'rawJson'; +export const ACTION_STATISTICS = 'traceStatistics'; // use a closure instead of bind to prevent forwarding any arguments to trackEvent() export const trackGanttView = () => trackEvent(CATEGORY_ALT_VIEW, ACTION_GANTT); export const trackGraphView = () => trackEvent(CATEGORY_ALT_VIEW, ACTION_GRAPH); export const trackJsonView = () => trackEvent(CATEGORY_ALT_VIEW, ACTION_JSON); export const trackRawJsonView = () => trackEvent(CATEGORY_ALT_VIEW, ACTION_RAW_JSON); +export const trackStatisticsView = () => trackEvent(CATEGORY_ALT_VIEW, ACTION_STATISTICS); export const trackSlimHeaderToggle = (isOpen: boolean) => trackEvent(CATEGORY_SLIM_HEADER, getToggleValue(isOpen)); diff --git a/packages/jaeger-ui/src/components/TracePage/TracePageHeader/TracePageHeader.tsx b/packages/jaeger-ui/src/components/TracePage/TracePageHeader/TracePageHeader.tsx index 47d3a2191a..da58c5b4e7 100644 --- a/packages/jaeger-ui/src/components/TracePage/TracePageHeader/TracePageHeader.tsx +++ b/packages/jaeger-ui/src/components/TracePage/TracePageHeader/TracePageHeader.tsx @@ -26,7 +26,7 @@ import AltViewOptions from './AltViewOptions'; import KeyboardShortcutsHelp from './KeyboardShortcutsHelp'; import SpanGraph from './SpanGraph'; import TracePageSearchBar from './TracePageSearchBar'; -import { TUpdateViewRangeTimeFunction, IViewRange, ViewRangeTimeUpdate } from '../types'; +import { TUpdateViewRangeTimeFunction, IViewRange, ViewRangeTimeUpdate, ETraceViewType } from '../types'; import LabeledList from '../../common/LabeledList'; import NewWindowIcon from '../../common/NewWindowIcon'; import TraceName from '../../common/TraceName'; @@ -49,7 +49,7 @@ type TracePageHeaderEmbedProps = { nextResult: () => void; onArchiveClicked: () => void; onSlimViewClicked: () => void; - onTraceGraphViewClicked: () => void; + onTraceViewChange: (viewType: ETraceViewType) => void; prevResult: () => void; resultCount: number; showArchiveButton: boolean; @@ -60,7 +60,7 @@ type TracePageHeaderEmbedProps = { textFilter: string | TNil; toSearch: string | null; trace: Trace; - traceGraphView: boolean; + viewType: ETraceViewType; updateNextViewRangeTime: (update: ViewRangeTimeUpdate) => void; updateViewRangeTime: TUpdateViewRangeTimeFunction; viewRange: IViewRange; @@ -117,7 +117,7 @@ export function TracePageHeaderFn(props: TracePageHeaderEmbedProps & { forwarded nextResult, onArchiveClicked, onSlimViewClicked, - onTraceGraphViewClicked, + onTraceViewChange, prevResult, resultCount, showArchiveButton, @@ -128,7 +128,7 @@ export function TracePageHeaderFn(props: TracePageHeaderEmbedProps & { forwarded textFilter, toSearch, trace, - traceGraphView, + viewType, updateNextViewRangeTime, updateViewRangeTime, viewRange, @@ -187,15 +187,11 @@ export function TracePageHeaderFn(props: TracePageHeaderEmbedProps & { forwarded ref={forwardedRef} resultCount={resultCount} textFilter={textFilter} - navigable={!traceGraphView} + navigable={viewType === ETraceViewType.TraceTimelineViewer} /> {showShortcutsHelp && } {showViewOptions && ( - + )} {showArchiveButton && ( + `; diff --git a/packages/jaeger-ui/src/components/TracePage/TraceStatistics/DetailTableData.css b/packages/jaeger-ui/src/components/TracePage/TraceStatistics/DetailTableData.css new file mode 100644 index 0000000000..09d941b271 --- /dev/null +++ b/packages/jaeger-ui/src/components/TracePage/TraceStatistics/DetailTableData.css @@ -0,0 +1,39 @@ +/* +Copyright (c) 2020 The Jaeger Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +.DetailTableData--tr { + border-top: 1px solid #ececec; +} + +.DetailTableData--tr:hover { + background: #fafafa; + color: black; +} + +.DetailTableData--serviceBorder { + border-left: 4px solid transparent; + padding-left: 0.6em; +} + +.DetailTableData--td { + border-left: 1px solid rgb(204, 204, 204); + border-right: 1px solid rgb(204, 204, 204); + border-top: 1px solid rgb(230, 230, 230); + border-bottom: 1px solid rgb(230, 230, 230); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + max-width: 25em; +} diff --git a/packages/jaeger-ui/src/components/TracePage/TraceStatistics/DetailTableData.test.js b/packages/jaeger-ui/src/components/TracePage/TraceStatistics/DetailTableData.test.js new file mode 100644 index 0000000000..0bfcfa640e --- /dev/null +++ b/packages/jaeger-ui/src/components/TracePage/TraceStatistics/DetailTableData.test.js @@ -0,0 +1,75 @@ +// Copyright (c) 2020 The Jaeger Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import React from 'react'; +import { shallow } from 'enzyme'; +import DetailTableData from './DetailTableData'; + +describe('', () => { + let wrapper; + let props; + + beforeEach(() => { + props = { + color: '', + colorToPercent: 'rgb(248,248,248)', + columnsArray: [ + { title: 'Name', attribute: 'name', suffix: '', isDecimal: false }, + { title: 'Count', attribute: 'count', suffix: '', isDecimal: false }, + { title: 'Total', attribute: 'total', suffix: 'ms', isDecimal: true }, + { title: 'Avg', attribute: 'avg', suffix: 'ms', isDecimal: true }, + { title: 'Min', attribute: 'min', suffix: 'ms', isDecimal: true }, + { title: 'Max', attribute: 'max', suffix: 'ms', isDecimal: true }, + { title: 'ST Total', attribute: 'selfTotal', suffix: 'ms', isDecimal: true }, + { title: 'ST Avg', attribute: 'selfAvg', suffix: 'ms', isDecimal: true }, + { title: 'ST Min', attribute: 'selfMin', suffix: 'ms', isDecimal: true }, + { title: 'ST Max', attribute: 'selfMax', suffix: 'ms', isDecimal: true }, + { title: 'ST in Duration', attribute: 'percent', suffix: '%', isDecimal: true }, + ], + name: 'GET /owners/5', + searchColor: 'rgb(248,248,248)', + valueNameSelector2: 'Operation Name', + togglePopup: () => {}, + values: [3, 27.27, 9.09, 2.56, 13.73, 8.69, 2.9, 0.88, 5.06, 31.88], + }; + wrapper = shallow(); + }); + + it('does not explode', () => { + expect(wrapper).toBeDefined(); + expect(wrapper.find('.DetailTableData--tr').length).toBe(1); + expect(wrapper.find('.DetailTableData--td').length).toBe(11); + }); + + it('renders TableOverviewHeadTag', () => { + const firstRowColumns = wrapper.find('.DetailTableData--td').map(column => column.text()); + expect(firstRowColumns.length).toBe(11); + + expect(firstRowColumns[0]).toBe('GET /owners/5'); + expect(firstRowColumns[1]).toBe('3'); + expect(firstRowColumns[2]).toBe('27.27ms'); + expect(firstRowColumns[3]).toBe('9.09ms'); + expect(firstRowColumns[4]).toBe('2.56ms'); + expect(firstRowColumns[5]).toBe('13.73ms'); + expect(firstRowColumns[6]).toBe('8.69ms'); + expect(firstRowColumns[7]).toBe('2.90ms'); + expect(firstRowColumns[8]).toBe('0.88ms'); + expect(firstRowColumns[9]).toBe('5.06ms'); + expect(firstRowColumns[10]).toBe('31.88%'); + expect(wrapper.find('.DetailTableData--tr').prop('style')).toEqual({ + background: 'rgb(248,248,248)', + borderColor: 'rgb(248,248,248)', + }); + }); +}); diff --git a/packages/jaeger-ui/src/components/TracePage/TraceStatistics/DetailTableData.tsx b/packages/jaeger-ui/src/components/TracePage/TraceStatistics/DetailTableData.tsx new file mode 100644 index 0000000000..a260ed7069 --- /dev/null +++ b/packages/jaeger-ui/src/components/TracePage/TraceStatistics/DetailTableData.tsx @@ -0,0 +1,108 @@ +// Copyright (c) 2020 The Jaeger Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import * as _ from 'lodash'; +import React, { Component } from 'react'; +import './DetailTableData.css'; +import { ITableValues, IColumnValue } from './types'; + +type Props = { + type: string; + name: string; + searchColor: string; + values: ITableValues[]; + columnsArray: IColumnValue[]; + color: string; + togglePopup: (name: string) => void; + valueNameSelector2: string | null; + colorToPercent: string; +}; + +type State = { + element: any; +}; + +/** + * Used to render the detail column. + */ +export default class DetailTableData extends Component { + componentWillMount() { + const element = this.props.values.map(item => { + return { uid: _.uniqueId('id'), value: item }; + }); + this.setState(prevState => { + return { + ...prevState, + element, + }; + }); + } + + render() { + const styleOption1 = { + background: this.props.colorToPercent, + borderColor: this.props.colorToPercent, + color: 'rgb(153,153,153)', + fontStyle: 'italic', + }; + + const styleOption2 = { + background: this.props.colorToPercent, + borderColor: this.props.colorToPercent, + }; + + const styleOption3 = { + background: this.props.searchColor, + borderColor: this.props.searchColor, + }; + const others = 'undefined'; + let styleCondition; + if (this.props.type === others) { + styleCondition = styleOption1; + } else if (this.props.searchColor === 'rgb(248,248,248)') { + styleCondition = styleOption2; + } else { + styleCondition = styleOption3; + } + const labelStyle1 = { borderColor: this.props.color }; + const labelStyle2 = { borderColor: this.props.color, marginLeft: '12px' }; + let labelCondition; + if (this.props.valueNameSelector2 === 'Service Name') { + labelCondition = labelStyle2; + } else { + labelCondition = labelStyle1; + } + const onClickOption = + this.props.valueNameSelector2 === 'sql' && this.props.type !== others + ? () => this.props.togglePopup(this.props.name) + : undefined; + return ( + + + + + + + {this.state.element.map((element: any, index: number) => ( + + {this.props.columnsArray[index + 1].isDecimal ? Number(element.value).toFixed(2) : element.value} + {this.props.columnsArray[index + 1].suffix} + + ))} + + ); + } +} diff --git a/packages/jaeger-ui/src/components/TracePage/TraceStatistics/HeaderTable.css b/packages/jaeger-ui/src/components/TracePage/TraceStatistics/HeaderTable.css new file mode 100644 index 0000000000..9024fc0dd6 --- /dev/null +++ b/packages/jaeger-ui/src/components/TracePage/TraceStatistics/HeaderTable.css @@ -0,0 +1,32 @@ +/* +Copyright (c) 2020 The Jaeger Authors. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at +http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +.HeaderTable--sortButton { + border: none; + background: transparent; +} + +.HeaderTable--th { + background: rgb(236, 236, 236); + border: 1px solid rgb(204, 204, 204); + color: rgb(35, 35, 35); + font-weight: 15; + line-height: 2em; + padding-left: 1em; +} + +.HeaderTable--sortButton { + display: flexbox; + flex-direction: row; + justify-content: space-around; +} diff --git a/packages/jaeger-ui/src/components/TracePage/TraceStatistics/HeaderTable.test.js b/packages/jaeger-ui/src/components/TracePage/TraceStatistics/HeaderTable.test.js new file mode 100644 index 0000000000..82c3fdc0d9 --- /dev/null +++ b/packages/jaeger-ui/src/components/TracePage/TraceStatistics/HeaderTable.test.js @@ -0,0 +1,39 @@ +// Copyright (c) 2020 The Jaeger Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import React from 'react'; +import { shallow } from 'enzyme'; +import HeaderTable from './HeaderTable'; + +describe('', () => { + let wrapper; + let props; + + beforeEach(() => { + props = { + element: { title: 'Name' }, + sortIndex: 1, + index: 1, + sortClick: () => {}, + sortAsc: false, + }; + wrapper = shallow(); + }); + + it('does not explode', () => { + expect(wrapper).toBeDefined(); + expect(wrapper.find('.HeaderTable--th').length).toBe(1); + expect(wrapper.find('.HeaderTable--sortButton').length).toBe(1); + }); +}); diff --git a/packages/jaeger-ui/src/components/TracePage/TraceStatistics/HeaderTable.tsx b/packages/jaeger-ui/src/components/TracePage/TraceStatistics/HeaderTable.tsx new file mode 100644 index 0000000000..71bc963d0a --- /dev/null +++ b/packages/jaeger-ui/src/components/TracePage/TraceStatistics/HeaderTable.tsx @@ -0,0 +1,40 @@ +// Copyright (c) 2020 The Jaeger Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import React from 'react'; +import { Icon } from 'antd'; +import './HeaderTable.css'; + +type Props = { + element: any; + key: string; + sortIndex: number; + index: number; + sortClick: (index: number) => void; + sortAsc: boolean; +}; + +export default function HeaderTable(props: Props) { + // const thStyle = { width: Math.round(window.innerWidth * 0.2) }; + const iconStyle = { opacity: props.sortIndex === props.index ? 1.0 : 0.2 }; + const iconType = props.sortAsc && props.sortIndex === props.index ? 'up' : 'down'; + return ( + + {props.element.title} + + + ); +} diff --git a/packages/jaeger-ui/src/components/TracePage/TraceStatistics/MainTableData.css b/packages/jaeger-ui/src/components/TracePage/TraceStatistics/MainTableData.css new file mode 100644 index 0000000000..411fbd797f --- /dev/null +++ b/packages/jaeger-ui/src/components/TracePage/TraceStatistics/MainTableData.css @@ -0,0 +1,33 @@ +/* +Copyright (c) 2020 The Jaeger Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +.MainTableData--td { + border-left: 1px solid rgb(204, 204, 204); + border-right: 1px solid rgb(204, 204, 204); + padding-left: 1em; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + max-width: 25em; +} + +.MainTableData--tr { + border-top: 1px solid rgb(255, 255, 255); +} + +.MainTableData--tr:hover { + background: rgb(250, 250, 250); + color: rgb(0, 0, 0); +} diff --git a/packages/jaeger-ui/src/components/TracePage/TraceStatistics/MainTableData.test.js b/packages/jaeger-ui/src/components/TracePage/TraceStatistics/MainTableData.test.js new file mode 100644 index 0000000000..c3a5110994 --- /dev/null +++ b/packages/jaeger-ui/src/components/TracePage/TraceStatistics/MainTableData.test.js @@ -0,0 +1,74 @@ +// Copyright (c) 2020 The Jaeger Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import React from 'react'; +import { shallow } from 'enzyme'; + +import MainTableData from './MainTableData'; + +describe('', () => { + let wrapper; + let props; + + beforeEach(() => { + props = { + color: '#17B8BE', + columnsArray: [ + { title: 'Name', attribute: 'name', suffix: '', isDecimal: false }, + { title: 'Count', attribute: 'count', suffix: '', isDecimal: false }, + { title: 'Total', attribute: 'total', suffix: 'ms', isDecimal: true }, + { title: 'Avg', attribute: 'avg', suffix: 'ms', isDecimal: true }, + { title: 'Min', attribute: 'min', suffix: 'ms', isDecimal: true }, + { title: 'Max', attribute: 'max', suffix: 'ms', isDecimal: true }, + { title: 'ST Total', attribute: 'selfTotal', suffix: 'ms', isDecimal: true }, + { title: 'ST Avg', attribute: 'selfAvg', suffix: 'ms', isDecimal: true }, + { title: 'ST Min', attribute: 'selfMin', suffix: 'ms', isDecimal: true }, + { title: 'ST Max', attribute: 'selfMax', suffix: 'ms', isDecimal: true }, + { title: 'ST in Duration', attribute: 'percent', suffix: '%', isDecimal: true }, + ], + valueNameSelector1: 'Service Name', + valueNameSelector2: null, + name: 'api-gateway', + searchColor: 'transparent', + togglePopup: '', + values: [5, 46.06, 9.21, 2.56, 15.43, 11.21, 2.24, 0.82, 5.06, 24.35], + }; + wrapper = shallow(); + }); + + it('does not explode', () => { + expect(wrapper).toBeDefined(); + expect(wrapper.find('.MainTableData--tr').length).toBe(1); + expect(wrapper.find('.MainTableData--td').length).toBe(11); + }); + + it('renders TableOverviewHeadTag', () => { + expect(wrapper.find('.MainTableData--label').text()).toBe('api-gateway'); + + const firstRowColumns = wrapper.find('.MainTableData--td').map(column => column.text()); + expect(firstRowColumns.length).toBe(11); + + expect(firstRowColumns[0]).toBe('api-gateway'); + expect(firstRowColumns[1]).toBe(' 5'); + expect(firstRowColumns[2]).toBe(' 46.06ms'); + expect(firstRowColumns[3]).toBe(' 9.21ms'); + expect(firstRowColumns[4]).toBe(' 2.56ms'); + expect(firstRowColumns[5]).toBe(' 15.43ms'); + expect(firstRowColumns[6]).toBe(' 11.21ms'); + expect(firstRowColumns[7]).toBe(' 2.24ms'); + expect(firstRowColumns[8]).toBe(' 0.82ms'); + expect(firstRowColumns[9]).toBe(' 5.06ms'); + expect(firstRowColumns[10]).toBe(' 24.35%'); + }); +}); diff --git a/packages/jaeger-ui/src/components/TracePage/TraceStatistics/MainTableData.tsx b/packages/jaeger-ui/src/components/TracePage/TraceStatistics/MainTableData.tsx new file mode 100644 index 0000000000..cb77212f2d --- /dev/null +++ b/packages/jaeger-ui/src/components/TracePage/TraceStatistics/MainTableData.tsx @@ -0,0 +1,136 @@ +// Copyright (c) 2020 The Jaeger Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import * as _ from 'lodash'; +import React, { Component } from 'react'; +import './MainTableData.css'; +import { ITableValues, IColumnValue } from './types'; + +type Props = { + type: string; + name: string; + searchColor: string; + values: ITableValues[]; + columnsArray: IColumnValue[]; + togglePopup: any; + valueNameSelector1: string; + valueNameSelector2: string | null; + color: string; + clickColumn: (name: string) => void; + colorToPercent: string; +}; + +type State = { + element: any; +}; + +/** + * Used to render the main column. + */ +export default class MainTableData extends Component { + componentWillMount() { + const element = this.props.values.map(item => { + return { uid: _.uniqueId('id'), value: item }; + }); + + this.setState(prevState => { + return { + ...prevState, + element, + }; + }); + } + + render() { + const styleOption1 = { + background: this.props.colorToPercent, + borderColor: this.props.colorToPercent, + cursor: 'default', + }; + + const styleOption2 = { + background: this.props.searchColor, + borderColor: this.props.searchColor, + cursor: 'default', + }; + + const labelOption1 = { + color: 'rgb(153,153,153)', + fontStyle: 'italic', + }; + + const labelOption2 = { + borderLeft: '4px solid transparent', + paddingLeft: '0.6em', + borderColor: this.props.color, + }; + + const others = 'undefined'; + + let styleCondition; + if (this.props.type === others) { + if (this.props.valueNameSelector2 !== null && this.props.type !== 'undefined') { + styleOption1.cursor = 'pointer'; + } + styleCondition = styleOption1; + } else if (this.props.searchColor === 'transparent') { + if (this.props.valueNameSelector2 !== null) { + styleOption1.cursor = 'pointer'; + } + styleCondition = styleOption1; + } else { + if (this.props.valueNameSelector2 !== null) { + styleOption1.cursor = 'pointer'; + } + styleCondition = styleOption2; + } + + let labelCondition; + if (this.props.color !== '') { + labelCondition = labelOption2; + } else if (this.props.type === 'undefined') { + labelCondition = labelOption1; + } else { + labelCondition = undefined; + } + + const onClickOption = + this.props.valueNameSelector1 === 'sql' && this.props.type !== others + ? () => this.props.togglePopup(this.props.name) + : undefined; + return ( + this.props.clickColumn(this.props.name)} + style={styleCondition} + > + + + + + + + {this.state.element.map((element: any, index: number) => ( + + {' '} + {this.props.columnsArray[index + 1].isDecimal ? element.value.toFixed(2) : element.value} + {this.props.columnsArray[index + 1].suffix} + + ))} + + ); + } +} diff --git a/packages/jaeger-ui/src/components/TracePage/TraceStatistics/PopupSql.css b/packages/jaeger-ui/src/components/TracePage/TraceStatistics/PopupSql.css new file mode 100644 index 0000000000..cc0ffb36c3 --- /dev/null +++ b/packages/jaeger-ui/src/components/TracePage/TraceStatistics/PopupSql.css @@ -0,0 +1,67 @@ +/* +Copyright (c) 2020 The Jaeger Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +.PopupSQL { + position: fixed; + width: 100%; + height: 100%; + top: 0; + left: 0; + right: 0; + bottom: 0; + margin: auto; + background-color: rgba(0, 0, 0, 0.5); + z-index: 3; +} + +.PopupSQL--inner { + position: absolute; + left: 33%; + right: 33%; + top: 33%; + bottom: 30%; + margin: auto; + background: rgb(255, 255, 255); + z-index: 3; + border-radius: 5px; +} + +.PopupSQL--header { + padding-top: 0.5em; + display: flex; + align-items: center; + justify-content: center; +} + +.PopupSQL--sqlContent { + display: block; + margin-left: auto; + margin-right: auto; + width: 80%; + height: 60%; + color: rgb(0, 128, 128); + font-family: monospace; + font-size: 13px; + outline-color: rgb(0, 128, 128); + padding: 5px; + resize: none; +} + +.PopupSQL--closeButton { + display: block; + margin-left: auto; + margin-right: auto; + margin-top: 1.5em; +} diff --git a/packages/jaeger-ui/src/components/TracePage/TraceStatistics/PopupSql.test.js b/packages/jaeger-ui/src/components/TracePage/TraceStatistics/PopupSql.test.js new file mode 100644 index 0000000000..c8b5a3667e --- /dev/null +++ b/packages/jaeger-ui/src/components/TracePage/TraceStatistics/PopupSql.test.js @@ -0,0 +1,39 @@ +// Copyright (c) 2020 The Jaeger Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import React from 'react'; +import { shallow } from 'enzyme'; +import PopupSql from './PopupSql'; + +describe(' { + let wrapper; + let props; + + beforeEach(() => { + props = { + closePopup: () => {}, + popupContent: + 'select specialtie0_.vet_id as vet_id1_1_0_, specialtie0_.specialty_id as specialt2_1_0_, specialty1_.id as id1_0_1_, specialty1_.name as name2_0_1_ from vet_specialties specialtie0_ inner join specialties specialty1_ on specialtie0_.specialty_id=specialty1_.id where specialtie0_.vet_id=?', + }; + wrapper = shallow(); + }); + + it('does not explode', () => { + expect(wrapper).toBeDefined(); + }); + + it('renders PopupSQL', () => { + expect(wrapper.find('.PopupSQL').length).toBe(1); + }); +}); diff --git a/packages/jaeger-ui/src/components/TracePage/TraceStatistics/PopupSql.tsx b/packages/jaeger-ui/src/components/TracePage/TraceStatistics/PopupSql.tsx new file mode 100644 index 0000000000..5ed7734ec9 --- /dev/null +++ b/packages/jaeger-ui/src/components/TracePage/TraceStatistics/PopupSql.tsx @@ -0,0 +1,40 @@ +// Copyright (c) 2020 The Jaeger Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import React from 'react'; +import { Button } from 'antd'; +import './PopupSql.css'; + +type Props = { + closePopup: (popupContent: string) => void; + popupContent: string; +}; + +/** + * Render the popup that is needed for sql. + */ +export default function PopupSql(props: Props) { + const value = `"${props.popupContent}"`; + return ( +
+
+

{'Tag: "SQL" '}

+