Files
grafana/public/app/features/explore/TraceView/components/TracePageHeader/NewTracePageSearchBar.tsx
T
Joey 886b91eca5 Tracing: Next/prev text for span filters (#67208)
* Next/prev text

* Tests

* Update test

* Updated state vars and also made other improvements
2023-04-27 13:28:19 +01:00

222 lines
6.5 KiB
TypeScript

// Copyright (c) 2018 Uber Technologies, Inc.
//
// 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 { css } from '@emotion/css';
import React, { memo, Dispatch, SetStateAction, useEffect, useMemo, useState } from 'react';
import { config, reportInteraction } from '@grafana/runtime';
import { Button, Icon, Switch, Tooltip, useStyles2 } from '@grafana/ui';
import { SearchProps } from '../../useSearch';
import { convertTimeFilter } from '../utils/filter-spans';
export type TracePageSearchBarProps = {
search: SearchProps;
spanFilterMatches: Set<string> | undefined;
showSpanFilterMatchesOnly: boolean;
setShowSpanFilterMatchesOnly: (showMatchesOnly: boolean) => void;
setFocusedSpanIdForSearch: Dispatch<SetStateAction<string>>;
datasourceType: string;
reset: () => void;
totalSpans: number;
};
export default memo(function NewTracePageSearchBar(props: TracePageSearchBarProps) {
const {
search,
spanFilterMatches,
showSpanFilterMatchesOnly,
setShowSpanFilterMatchesOnly,
setFocusedSpanIdForSearch,
datasourceType,
reset,
totalSpans,
} = props;
const [currentSpanIndex, setCurrentSpanIndex] = useState(-1);
const styles = useStyles2(getStyles);
useEffect(() => {
setCurrentSpanIndex(-1);
setFocusedSpanIdForSearch('');
}, [setFocusedSpanIdForSearch, spanFilterMatches]);
useEffect(() => {
if (spanFilterMatches) {
const spanMatches = Array.from(spanFilterMatches!);
setFocusedSpanIdForSearch(spanMatches[currentSpanIndex]);
}
}, [currentSpanIndex, setFocusedSpanIdForSearch, spanFilterMatches]);
const nextResult = () => {
reportInteraction('grafana_traces_trace_view_find_next_prev_clicked', {
datasourceType: datasourceType,
grafana_version: config.buildInfo.version,
direction: 'next',
});
// new query || at end, go to start
if (currentSpanIndex === -1 || (spanFilterMatches && currentSpanIndex === spanFilterMatches.size - 1)) {
setCurrentSpanIndex(0);
return;
}
// get next
setCurrentSpanIndex(currentSpanIndex + 1);
};
const prevResult = () => {
reportInteraction('grafana_traces_trace_view_find_next_prev_clicked', {
datasourceType: datasourceType,
grafana_version: config.buildInfo.version,
direction: 'prev',
});
// new query || at start, go to end
if (spanFilterMatches && (currentSpanIndex === -1 || currentSpanIndex === 0)) {
setCurrentSpanIndex(spanFilterMatches.size - 1);
return;
}
// get prev
setCurrentSpanIndex(currentSpanIndex - 1);
};
const buttonEnabled = spanFilterMatches && spanFilterMatches?.size > 0;
const resetEnabled = useMemo(() => {
return (
(search.serviceName && search.serviceName !== '') ||
(search.spanName && search.spanName !== '') ||
convertTimeFilter(search.from || '') ||
convertTimeFilter(search.to || '') ||
search.tags.length > 1 ||
search.tags.some((tag) => {
return tag.key;
})
);
}, [search.serviceName, search.spanName, search.from, search.to, search.tags]);
const amountText = spanFilterMatches?.size === 1 ? 'match' : 'matches';
const matches =
spanFilterMatches?.size === 0 ? (
<>
<span>0 matches</span>
<Tooltip
content="There are 0 span matches for the filters selected. Please try removing some of the selected filters."
placement="left"
>
<span className={styles.matchesTooltip}>
<Icon name="info-circle" size="lg" />
</span>
</Tooltip>
</>
) : currentSpanIndex !== -1 ? (
`${currentSpanIndex + 1}/${spanFilterMatches?.size} ${amountText}`
) : (
`${spanFilterMatches?.size} ${amountText}`
);
return (
<div className={styles.searchBar}>
<div className={styles.buttons}>
<>
<div className={styles.resetButton}>
<Button
variant="destructive"
disabled={!resetEnabled}
type="button"
fill="outline"
aria-label="Reset filters button"
onClick={reset}
>
Reset
</Button>
<div className={styles.matchesOnly}>
<Switch
value={showSpanFilterMatchesOnly}
onChange={(value) => setShowSpanFilterMatchesOnly(value.currentTarget.checked ?? false)}
label="Show matches only switch"
/>
<span onClick={() => setShowSpanFilterMatchesOnly(!showSpanFilterMatchesOnly)}>Show matches only</span>
</div>
</div>
<div className={styles.nextPrevButtons}>
<span className={styles.matches}>{spanFilterMatches ? matches : `${totalSpans} spans`}</span>
<Button
variant="secondary"
disabled={!buttonEnabled}
type="button"
fill="outline"
aria-label="Prev result button"
onClick={prevResult}
>
Prev
</Button>
<Button
variant="secondary"
disabled={!buttonEnabled}
type="button"
fill="outline"
aria-label="Next result button"
onClick={nextResult}
>
Next
</Button>
</div>
</>
</div>
</div>
);
});
export const getStyles = () => {
return {
searchBar: css`
display: inline;
`,
matchesOnly: css`
display: inline-flex;
margin: 0 0 0 10px;
vertical-align: middle;
span {
cursor: pointer;
margin: -3px 0 0 5px;
}
`,
buttons: css`
display: flex;
justify-content: flex-end;
margin: 5px 0 0 0;
`,
resetButton: css`
order: 1;
`,
nextPrevButtons: css`
margin-left: auto;
order: 2;
button {
margin-left: 8px;
}
`,
matches: css`
margin-right: 5px;
`,
matchesTooltip: css`
color: #aaa;
margin: -2px 0 0 10px;
`,
};
};