Submit
Path:
~
/
/
usr
/
share
/
grafana
/
public
/
app
/
features
/
explore
/
TraceView
/
components
/
TracePageHeader
/
SearchBar
/
File Content:
NextPrevResult.tsx
// 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, cx } from '@emotion/css'; import { get, maxBy, values } from 'lodash'; import React, { memo, Dispatch, SetStateAction, useEffect, useCallback } from 'react'; import { GrafanaTheme2 } from '@grafana/data'; import { config, reportInteraction } from '@grafana/runtime'; import { Icon, PopoverContent, Tooltip, useTheme2 } from '@grafana/ui'; import { getButtonStyles } from '@grafana/ui/src/components/Button'; import { Trace } from '../../types'; export type NextPrevResultProps = { trace: Trace; spanFilterMatches: Set<string> | undefined; setFocusedSpanIdForSearch: Dispatch<SetStateAction<string>>; focusedSpanIndexForSearch: number; setFocusedSpanIndexForSearch: Dispatch<SetStateAction<number>>; datasourceType: string; showSpanFilters: boolean; }; export default memo(function NextPrevResult(props: NextPrevResultProps) { const { trace, spanFilterMatches, setFocusedSpanIdForSearch, focusedSpanIndexForSearch, setFocusedSpanIndexForSearch, datasourceType, showSpanFilters, } = props; const styles = getStyles(useTheme2(), showSpanFilters); useEffect(() => { if (spanFilterMatches && focusedSpanIndexForSearch !== -1) { const spanMatches = Array.from(spanFilterMatches!); setFocusedSpanIdForSearch(spanMatches[focusedSpanIndexForSearch]); } }, [focusedSpanIndexForSearch, setFocusedSpanIdForSearch, spanFilterMatches]); const nextResult = (event: React.UIEvent, buttonEnabled: boolean) => { event.preventDefault(); event.stopPropagation(); if (buttonEnabled) { 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 ( focusedSpanIndexForSearch === -1 || (spanFilterMatches && focusedSpanIndexForSearch === spanFilterMatches.size - 1) ) { setFocusedSpanIndexForSearch(0); return; } // get next setFocusedSpanIndexForSearch(focusedSpanIndexForSearch + 1); } }; const prevResult = (event: React.UIEvent, buttonEnabled: boolean) => { event.preventDefault(); event.stopPropagation(); if (buttonEnabled) { 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 && (focusedSpanIndexForSearch === -1 || focusedSpanIndexForSearch === 0)) { setFocusedSpanIndexForSearch(spanFilterMatches.size - 1); return; } // get prev setFocusedSpanIndexForSearch(focusedSpanIndexForSearch - 1); } }; const nextResultOnKeyDown = (event: React.KeyboardEvent, buttonEnabled: boolean) => { if (event.key === 'Enter') { nextResult(event, buttonEnabled); } }; const prevResultOnKeyDown = (event: React.KeyboardEvent, buttonEnabled: boolean) => { if (event.key === 'Enter') { prevResult(event, buttonEnabled); } }; const buttonEnabled = (spanFilterMatches && spanFilterMatches?.size > 0) ?? false; const buttonClass = buttonEnabled ? styles.button : cx(styles.button, styles.buttonDisabled); const getTooltip = useCallback( (content: PopoverContent) => { return ( <Tooltip content={content} placement="top"> <span className={styles.tooltip}> <Icon name="info-circle" size="md" /> </span> </Tooltip> ); }, [styles.tooltip] ); const getMatchesMetadata = useCallback( (depth: number, services: number) => { let metadata = ( <> <span>{`${trace.spans.length} spans`}</span> {getTooltip( <> <div>Services: {services}</div> <div>Depth: {depth}</div> </> )} </> ); if (spanFilterMatches) { if (spanFilterMatches.size === 0) { metadata = ( <> <span>0 matches</span> {getTooltip( 'There are 0 span matches for the filters selected. Please try removing some of the selected filters.' )} </> ); } else { const type = spanFilterMatches.size === 1 ? 'match' : 'matches'; const text = focusedSpanIndexForSearch !== -1 ? `${focusedSpanIndexForSearch + 1}/${spanFilterMatches.size} ${type}` : `${spanFilterMatches.size} ${type}`; const matchedServices: string[] = []; spanFilterMatches.forEach((spanID) => { if (trace.processes[spanID]) { matchedServices.push(trace.processes[spanID].serviceName); } }); metadata = ( <> <span>{text}</span> {getTooltip( <> <div> Services: {new Set(matchedServices).size}/{services} </div> <div>Depth: {depth}</div> </> )} </> ); } } return metadata; }, [focusedSpanIndexForSearch, getTooltip, spanFilterMatches, trace.processes, trace.spans] ); const services = new Set(values(trace.processes).map((p) => p.serviceName)).size; const depth = get(maxBy(trace.spans, 'depth'), 'depth', 0) + 1; return ( <> <span className={styles.matches}>{getMatchesMetadata(depth, services)}</span> <div className={buttonEnabled ? styles.buttons : cx(styles.buttons, styles.buttonsDisabled)}> <div aria-label="Prev result button" className={buttonClass} onClick={(event) => prevResult(event, buttonEnabled)} onKeyDown={(event) => prevResultOnKeyDown(event, buttonEnabled)} role="button" tabIndex={buttonEnabled ? 0 : -1} > Prev </div> <div aria-label="Next result button" className={buttonClass} onClick={(event) => nextResult(event, buttonEnabled)} onKeyDown={(event) => nextResultOnKeyDown(event, buttonEnabled)} role="button" tabIndex={buttonEnabled ? 0 : -1} > Next </div> </div> </> ); }); export const getStyles = (theme: GrafanaTheme2, showSpanFilters: boolean) => { const buttonStyles = getButtonStyles({ theme, variant: 'secondary', size: showSpanFilters ? 'md' : 'sm', iconOnly: false, fill: 'outline', }); return { buttons: css` display: inline-flex; gap: 4px; `, buttonsDisabled: css` cursor: not-allowed; `, button: css` ${buttonStyles.button}; `, buttonDisabled: css` ${buttonStyles.disabled}; pointer-events: none; `, matches: css` margin-right: ${theme.spacing(2)}; text-wrap: nowrap; `, tooltip: css` color: #aaa; margin: 0 0 0 5px; `, }; };
Edit
Rename
Chmod
Delete
FILE
FOLDER
INFO
Name
Size
Permission
Action
NextPrevResult.test.tsx
5667 bytes
0644
NextPrevResult.tsx
7760 bytes
0644
TracePageSearchBar.test.tsx
2332 bytes
0644
TracePageSearchBar.tsx
5900 bytes
0644
N4ST4R_ID | Naxtarrr