Skip to content

Commit 30a402d

Browse files
Convert SeachResults{ResultItem, ResultItemTitle} to functional components (#3071)
## Which problem is this PR solving? - Resolved #2610 <!-- Example: Resolves #123 --> ## Description of the changes - convert `packages/jaeger-ui/src/components/SearchTracePage/SearchResults/ResultItem.tsx` and `packages/jaeger-ui/src/components/SearchTracePage/SearchResults/ResultItemTitle.tsx` from class to functional ## How was this change tested? - Ensured there is no break in the functionality in UI - Ran the test suite using npm run test to ensure all test cases pass. ## Checklist - [x] I have read https://github.com/jaegertracing/jaeger/blob/master/CONTRIBUTING_GUIDELINES.md - [x] I have signed all commits - [x] I have added unit tests for the new functionality - [x] I have run lint and test steps successfully - for `jaeger`: `make lint test` - for `jaeger-ui`: `npm run lint` and `npm run test` --------- Signed-off-by: JeevaRamanathan <[email protected]>
1 parent 83cdac4 commit 30a402d

File tree

2 files changed

+130
-151
lines changed

2 files changed

+130
-151
lines changed

packages/jaeger-ui/src/components/SearchTracePage/SearchResults/ResultItem.tsx

Lines changed: 79 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -44,102 +44,94 @@ type Props = {
4444
disableComparision: boolean;
4545
};
4646

47-
type State = {
48-
erroredServices: Set<string>;
49-
numSpans: number;
50-
numErredSpans: number;
51-
timeStr: string;
52-
fromNow: string | boolean;
53-
};
54-
5547
const isErrorTag = ({ key, value }: KeyValuePair<boolean | string>) =>
5648
key === 'error' && (value === true || value === 'true');
5749
const trackTraceConversions = () => trackConversions(EAltViewActions.Traces);
5850

59-
export default class ResultItem extends React.PureComponent<Props, State> {
60-
constructor(props: Props, state: State) {
61-
super(props, state);
62-
const { startTime, spans } = props.trace;
63-
51+
export default function ResultItem({
52+
durationPercent,
53+
isInDiffCohort,
54+
linkTo,
55+
toggleComparison,
56+
trace,
57+
disableComparision,
58+
}: Props) {
59+
const { duration, services = [], startTime, traceName, traceID, spans = [] } = trace;
60+
61+
// Initialize state values
62+
const [erroredServices, setErroredServices] = React.useState<Set<string>>(new Set());
63+
const [numSpans] = React.useState(spans.length);
64+
const [numErredSpans, setNumErredSpans] = React.useState(0);
65+
const [timeStr, setTimeStr] = React.useState('');
66+
const [fromNow, setFromNow] = React.useState<string | boolean>('');
67+
68+
React.useEffect(() => {
6469
const startTimeDayjs = dayjs(startTime / 1000);
70+
setTimeStr(startTimeDayjs.format('h:mm:ss a'));
71+
setFromNow(startTimeDayjs.fromNow());
6572

66-
const erroredServices: Set<string> = new Set<string>();
67-
68-
const numErredSpans = spans.filter(sp => {
73+
const errored = new Set<string>();
74+
const erredCount = spans.filter(sp => {
6975
const hasError = sp.tags.some(isErrorTag);
70-
if (hasError) {
71-
erroredServices.add(sp.process.serviceName);
72-
}
76+
if (hasError) errored.add(sp.process.serviceName);
7377
return hasError;
7478
}).length;
7579

76-
this.state = {
77-
numSpans: spans.length,
78-
timeStr: startTimeDayjs.format('h:mm:ss a'),
79-
fromNow: startTimeDayjs.fromNow(),
80-
numErredSpans,
81-
erroredServices,
82-
};
83-
}
84-
85-
render() {
86-
const { disableComparision, durationPercent, isInDiffCohort, linkTo, toggleComparison, trace } =
87-
this.props;
88-
const { duration, services, startTime, traceName, traceID } = trace;
89-
return (
90-
<div className="ResultItem" onClick={trackTraceConversions} role="button">
91-
<ResultItemTitle
92-
duration={duration}
93-
durationPercent={durationPercent}
94-
isInDiffCohort={isInDiffCohort}
95-
linkTo={linkTo}
96-
toggleComparison={toggleComparison}
97-
traceID={traceID}
98-
traceName={traceName}
99-
disableComparision={disableComparision}
100-
/>
101-
<Link to={linkTo}>
102-
<Row>
103-
<Col span={4} className="ub-p2">
104-
<Tag className="ub-m1" data-testid={markers.NUM_SPANS}>
105-
{this.state.numSpans} Span{this.state.numSpans > 1 && 's'}
80+
setErroredServices(errored);
81+
setNumErredSpans(erredCount);
82+
}, [startTime, spans]);
83+
84+
return (
85+
<div className="ResultItem" onClick={trackTraceConversions} role="button">
86+
<ResultItemTitle
87+
duration={duration}
88+
durationPercent={durationPercent}
89+
isInDiffCohort={isInDiffCohort}
90+
linkTo={linkTo}
91+
toggleComparison={toggleComparison}
92+
traceID={traceID}
93+
traceName={traceName}
94+
disableComparision={disableComparision}
95+
/>
96+
<Link to={linkTo}>
97+
<Row>
98+
<Col span={4} className="ub-p2">
99+
<Tag className="ub-m1" data-testid={markers.NUM_SPANS}>
100+
{numSpans} Span{numSpans > 1 && 's'}
101+
</Tag>
102+
{Boolean(numErredSpans) && (
103+
<Tag className="ub-m1" color="red">
104+
{numErredSpans} Error{numErredSpans > 1 && 's'}
106105
</Tag>
107-
{Boolean(this.state.numErredSpans) && (
108-
<Tag className="ub-m1" color="red">
109-
{this.state.numErredSpans} Error{this.state.numErredSpans > 1 && 's'}
110-
</Tag>
111-
)}
112-
</Col>
113-
<Col span={16} className="ub-p2">
114-
<ul className="ub-list-reset" data-testid={markers.SERVICE_TAGS}>
115-
{_sortBy(services, s => s.name).map(service => {
116-
const { name, numberOfSpans: count } = service;
117-
return (
118-
<li key={name} className="ub-inline-block ub-m1">
119-
<Tag
120-
className="ResultItem--serviceTag"
121-
style={{ borderLeftColor: colorGenerator.getColorByKey(name) }}
122-
>
123-
{this.state.erroredServices.has(name) && (
124-
<IoAlert className="ResultItem--errorIcon" />
125-
)}
126-
{name} ({count})
127-
</Tag>
128-
</li>
129-
);
130-
})}
131-
</ul>
132-
</Col>
133-
<Col span={4} className="ub-p3 ub-tx-right-align">
134-
{formatRelativeDate(startTime / 1000)}
135-
<Divider type="vertical" />
136-
{this.state.timeStr.slice(0, -3)}&nbsp;{this.state.timeStr.slice(-2)}
137-
<br />
138-
<small>{this.state.fromNow}</small>
139-
</Col>
140-
</Row>
141-
</Link>
142-
</div>
143-
);
144-
}
106+
)}
107+
</Col>
108+
<Col span={16} className="ub-p2">
109+
<ul className="ub-list-reset" data-testid={markers.SERVICE_TAGS}>
110+
{_sortBy(services, s => s.name).map(service => {
111+
const { name, numberOfSpans: count } = service;
112+
return (
113+
<li key={name} className="ub-inline-block ub-m1">
114+
<Tag
115+
className="ResultItem--serviceTag"
116+
style={{ borderLeftColor: colorGenerator.getColorByKey(name) }}
117+
>
118+
{erroredServices.has(name) && <IoAlert className="ResultItem--errorIcon" />}
119+
{name} ({count})
120+
</Tag>
121+
</li>
122+
);
123+
})}
124+
</ul>
125+
</Col>
126+
<Col span={4} className="ub-p3 ub-tx-right-align">
127+
{formatRelativeDate(startTime / 1000)}
128+
<Divider type="vertical" />
129+
{timeStr.slice(0, -3)}&nbsp;{timeStr.slice(-2)}
130+
<br />
131+
<small>{fromNow}</small>
132+
</Col>
133+
</Row>
134+
</Link>
135+
</div>
136+
);
145137
}

packages/jaeger-ui/src/components/SearchTracePage/SearchResults/ResultItemTitle.tsx

Lines changed: 51 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -46,72 +46,59 @@ const DEFAULT_DURATION_PERCENT = 0;
4646

4747
const stopCheckboxPropagation = (evt: React.MouseEvent) => evt.stopPropagation();
4848

49-
export default class ResultItemTitle extends React.PureComponent<Props> {
50-
static defaultProps: Partial<Props> = {
51-
disableComparision: false,
52-
durationPercent: DEFAULT_DURATION_PERCENT,
53-
error: undefined,
54-
state: undefined,
55-
targetBlank: false,
56-
};
57-
58-
toggleComparison = () => {
59-
const { isInDiffCohort, toggleComparison, traceID } = this.props;
49+
export default function ResultItemTitle({
50+
duration,
51+
durationPercent = DEFAULT_DURATION_PERCENT,
52+
error,
53+
isInDiffCohort,
54+
linkTo,
55+
state,
56+
targetBlank = false,
57+
toggleComparison,
58+
traceID,
59+
traceName,
60+
disableComparision = false,
61+
}: Props) {
62+
const onToggleComparison = React.useCallback(() => {
6063
toggleComparison(traceID, isInDiffCohort);
61-
};
64+
}, [toggleComparison, traceID, isInDiffCohort]);
6265

63-
render() {
64-
const {
65-
disableComparision,
66-
duration,
67-
durationPercent,
68-
error,
69-
isInDiffCohort,
70-
linkTo,
71-
state,
72-
targetBlank,
73-
traceID,
74-
traceName,
75-
} = this.props;
76-
// Use a div when the ResultItemTitle doesn't link to anything
77-
let WrapperComponent: string | typeof Link = 'div';
78-
const wrapperProps: Record<string, string | LocationDescriptor> = {
79-
className: 'ResultItemTitle--item ub-flex-auto',
80-
};
81-
if (linkTo) {
82-
wrapperProps.to = linkTo;
83-
WrapperComponent = Link;
84-
if (targetBlank) {
85-
wrapperProps.target = getTargetEmptyOrBlank();
86-
wrapperProps.rel = 'noopener noreferrer';
87-
}
66+
// Use a div when the ResultItemTitle doesn't link to anything
67+
let WrapperComponent: string | typeof Link = 'div';
68+
const wrapperProps: Record<string, any> = {
69+
className: 'ResultItemTitle--item ub-flex-auto',
70+
};
71+
if (linkTo) {
72+
WrapperComponent = Link;
73+
wrapperProps.to = linkTo;
74+
if (targetBlank) {
75+
wrapperProps.target = getTargetEmptyOrBlank();
76+
wrapperProps.rel = 'noopener noreferrer';
8877
}
89-
const isErred = state === fetchedState.ERROR;
90-
// Separate propagation management and toggle manegement due to ant-design#16400
91-
const checkboxProps = {
92-
className: 'ResultItemTitle--item ub-flex-none',
93-
checked: !isErred && isInDiffCohort,
94-
disabled: isErred,
95-
onChange: this.toggleComparison,
96-
onClick: stopCheckboxPropagation,
97-
};
98-
99-
return (
100-
<div className="ResultItemTitle">
101-
{!disableComparision && <Checkbox {...checkboxProps} />}
102-
{/* TODO: Shouldn't need cast */}
103-
<WrapperComponent {...(wrapperProps as any)}>
104-
<span
105-
className="ResultItemTitle--durationBar"
106-
style={{ width: `${durationPercent || DEFAULT_DURATION_PERCENT}%` }}
107-
/>
108-
{duration != null && <span className="ub-right ub-relative">{formatDuration(duration)}</span>}
109-
<h3 className="ResultItemTitle--title">
110-
<TraceName error={error} state={state} traceName={traceName} />
111-
<TraceId traceId={traceID} className="ResultItemTitle--idExcerpt" />
112-
</h3>
113-
</WrapperComponent>
114-
</div>
115-
);
11678
}
79+
80+
const isErred = state === fetchedState.ERROR;
81+
// Separate propagation management and toggle manegement due to ant-design#16400
82+
const checkboxProps = {
83+
className: 'ResultItemTitle--item ub-flex-none',
84+
checked: !isErred && isInDiffCohort,
85+
disabled: isErred,
86+
onChange: onToggleComparison,
87+
onClick: stopCheckboxPropagation,
88+
};
89+
90+
return (
91+
<div className="ResultItemTitle">
92+
{!disableComparision && <Checkbox {...checkboxProps} />}
93+
{/* TODO: Shouldn't need cast */}
94+
<WrapperComponent {...(wrapperProps as any)}>
95+
<span className="ResultItemTitle--durationBar" style={{ width: `${durationPercent}%` }} />
96+
{duration != null && <span className="ub-right ub-relative">{formatDuration(duration)}</span>}
97+
<h3 className="ResultItemTitle--title">
98+
<TraceName error={error} state={state} traceName={traceName} />
99+
<TraceId traceId={traceID} className="ResultItemTitle--idExcerpt" />
100+
</h3>
101+
</WrapperComponent>
102+
</div>
103+
);
117104
}

0 commit comments

Comments
 (0)