mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Trace view scroll to top button (#46077)
* Trace view scroll to top button * Updated button css selector * Updates for scrolToTop * Updated snapshot * Updated render * Add smooth scrolling
This commit is contained in:
parent
cfbf58fb92
commit
6f3ebbe6cc
@ -17,7 +17,7 @@ import { css } from '@emotion/css';
|
||||
|
||||
import { isEqual } from 'lodash';
|
||||
import memoizeOne from 'memoize-one';
|
||||
import { stylesFactory, withTheme2 } from '@grafana/ui';
|
||||
import { stylesFactory, withTheme2, ToolbarButton } from '@grafana/ui';
|
||||
import { GrafanaTheme2, LinkModel } from '@grafana/data';
|
||||
|
||||
import ListView from './ListView';
|
||||
@ -38,6 +38,7 @@ import { SpanLinkFunc, TNil } from '../types';
|
||||
import { TraceLog, TraceSpan, Trace, TraceKeyValuePair, TraceLink, TraceSpanReference } from '../types/trace';
|
||||
import TTraceTimeline from '../types/TTraceTimeline';
|
||||
import { PEER_SERVICE } from '../constants/tag-keys';
|
||||
import { createRef, RefObject } from 'react';
|
||||
|
||||
type TExtractUiFindFromStateReturn = {
|
||||
uiFind: string | undefined;
|
||||
@ -51,6 +52,18 @@ const getStyles = stylesFactory(() => {
|
||||
row: css`
|
||||
width: 100%;
|
||||
`,
|
||||
scrollToTopButton: css`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
position: fixed;
|
||||
bottom: 30px;
|
||||
right: 30px;
|
||||
z-index: 1;
|
||||
`,
|
||||
};
|
||||
});
|
||||
|
||||
@ -89,6 +102,7 @@ type TVirtualizedTraceViewOwnProps = {
|
||||
scrollElement?: Element;
|
||||
focusedSpanId?: string;
|
||||
createFocusSpanLink: (traceId: string, spanId: string) => LinkModel;
|
||||
topOfExploreViewRef?: RefObject<HTMLDivElement>;
|
||||
};
|
||||
|
||||
type VirtualizedTraceViewProps = TVirtualizedTraceViewOwnProps & TExtractUiFindFromStateReturn & TTraceTimeline;
|
||||
@ -168,6 +182,7 @@ const memoizedGetClipping = memoizeOne(getClipping, isEqual);
|
||||
// export from tests
|
||||
export class UnthemedVirtualizedTraceView extends React.Component<VirtualizedTraceViewProps> {
|
||||
listView: ListView | TNil;
|
||||
topTraceViewRef = createRef<HTMLDivElement>();
|
||||
|
||||
constructor(props: VirtualizedTraceViewProps) {
|
||||
super(props);
|
||||
@ -495,11 +510,16 @@ export class UnthemedVirtualizedTraceView extends React.Component<VirtualizedTra
|
||||
);
|
||||
}
|
||||
|
||||
scrollToTop = () => {
|
||||
const { topOfExploreViewRef } = this.props;
|
||||
topOfExploreViewRef?.current?.scrollIntoView({ behavior: 'smooth' });
|
||||
};
|
||||
|
||||
render() {
|
||||
const styles = getStyles();
|
||||
const { scrollElement } = this.props;
|
||||
return (
|
||||
<div>
|
||||
<>
|
||||
<ListView
|
||||
ref={this.setListView}
|
||||
dataLength={this.getRowStates().length}
|
||||
@ -513,7 +533,14 @@ export class UnthemedVirtualizedTraceView extends React.Component<VirtualizedTra
|
||||
windowScroller={false}
|
||||
scrollElement={scrollElement}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<ToolbarButton
|
||||
className={styles.scrollToTopButton}
|
||||
onClick={this.scrollToTop}
|
||||
title="Scroll to top"
|
||||
icon="arrow-up"
|
||||
></ToolbarButton>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -12,7 +12,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import React from 'react';
|
||||
import React, { RefObject } from 'react';
|
||||
import { css } from '@emotion/css';
|
||||
import { GrafanaTheme2, LinkModel } from '@grafana/data';
|
||||
import { stylesFactory, withTheme2 } from '@grafana/ui';
|
||||
@ -106,6 +106,7 @@ type TProps = TExtractUiFindFromStateReturn & {
|
||||
scrollElement?: Element;
|
||||
focusedSpanId?: string;
|
||||
createFocusSpanLink: (traceId: string, spanId: string) => LinkModel;
|
||||
topOfExploreViewRef?: RefObject<HTMLDivElement>;
|
||||
};
|
||||
|
||||
type State = {
|
||||
@ -161,6 +162,7 @@ export class UnthemedTraceTimelineViewer extends React.PureComponent<TProps, Sta
|
||||
createLinkToExternalSpan,
|
||||
traceTimeline,
|
||||
theme,
|
||||
topOfExploreViewRef,
|
||||
...rest
|
||||
} = this.props;
|
||||
const { trace } = rest;
|
||||
@ -191,6 +193,7 @@ export class UnthemedTraceTimelineViewer extends React.PureComponent<TProps, Sta
|
||||
{...traceTimeline}
|
||||
setSpanNameColumnWidth={setSpanNameColumnWidth}
|
||||
currentViewRangeTime={viewRange.time.current}
|
||||
topOfExploreViewRef={topOfExploreViewRef}
|
||||
/>
|
||||
</div>
|
||||
</ExternalLinkContext.Provider>
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import React, { createRef } from 'react';
|
||||
import { css, cx } from '@emotion/css';
|
||||
import { compose } from 'redux';
|
||||
import { connect, ConnectedProps } from 'react-redux';
|
||||
@ -101,6 +101,7 @@ export type Props = ExploreProps & ConnectedProps<typeof connector>;
|
||||
export class Explore extends React.PureComponent<Props, ExploreState> {
|
||||
scrollElement: HTMLDivElement | undefined;
|
||||
absoluteTimeUnsubsciber: Unsubscribable | undefined;
|
||||
topOfExploreViewRef = createRef<HTMLDivElement>();
|
||||
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
@ -305,6 +306,7 @@ export class Explore extends React.PureComponent<Props, ExploreState> {
|
||||
dataFrames={dataFrames}
|
||||
splitOpenFn={splitOpen}
|
||||
scrollElement={this.scrollElement}
|
||||
topOfExploreViewRef={this.topOfExploreViewRef}
|
||||
/>
|
||||
)
|
||||
);
|
||||
@ -337,7 +339,11 @@ export class Explore extends React.PureComponent<Props, ExploreState> {
|
||||
autoHeightMin={'100%'}
|
||||
scrollRefCallback={(scrollElement) => (this.scrollElement = scrollElement || undefined)}
|
||||
>
|
||||
<ExploreToolbar exploreId={exploreId} onChangeTime={this.onChangeTime} />
|
||||
<ExploreToolbar
|
||||
exploreId={exploreId}
|
||||
onChangeTime={this.onChangeTime}
|
||||
topOfExploreViewRef={this.topOfExploreViewRef}
|
||||
/>
|
||||
{datasourceMissing ? this.renderEmptyState() : null}
|
||||
{datasourceInstance && (
|
||||
<div className="explore-container">
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
import React, { PureComponent, RefObject } from 'react';
|
||||
import { connect, ConnectedProps } from 'react-redux';
|
||||
import { ExploreId } from 'app/types/explore';
|
||||
import { PageToolbar, SetInterval, ToolbarButton, ToolbarButtonRow } from '@grafana/ui';
|
||||
@ -23,6 +23,7 @@ import { AddToDashboard } from './AddToDashboard';
|
||||
interface OwnProps {
|
||||
exploreId: ExploreId;
|
||||
onChangeTime: (range: RawTimeRange, changedByScanner?: boolean) => void;
|
||||
topOfExploreViewRef?: RefObject<HTMLDivElement>;
|
||||
}
|
||||
|
||||
type Props = OwnProps & ConnectedProps<typeof connector>;
|
||||
@ -71,94 +72,97 @@ class UnConnectedExploreToolbar extends PureComponent<Props> {
|
||||
containerWidth,
|
||||
onChangeTimeZone,
|
||||
onChangeFiscalYearStartMonth,
|
||||
topOfExploreViewRef,
|
||||
} = this.props;
|
||||
|
||||
const showSmallDataSourcePicker = (splitted ? containerWidth < 700 : containerWidth < 800) || false;
|
||||
const showSmallTimePicker = splitted || containerWidth < 1210;
|
||||
|
||||
return (
|
||||
<PageToolbar
|
||||
title={exploreId === ExploreId.left ? 'Explore' : undefined}
|
||||
pageIcon={exploreId === ExploreId.left ? 'compass' : undefined}
|
||||
leftItems={[
|
||||
exploreId === ExploreId.left && (
|
||||
<DashNavButton
|
||||
key="share"
|
||||
tooltip="Copy shortened link"
|
||||
icon="share-alt"
|
||||
onClick={() => createAndCopyShortLink(window.location.href)}
|
||||
aria-label="Copy shortened link"
|
||||
<div ref={topOfExploreViewRef}>
|
||||
<PageToolbar
|
||||
title={exploreId === ExploreId.left ? 'Explore' : undefined}
|
||||
pageIcon={exploreId === ExploreId.left ? 'compass' : undefined}
|
||||
leftItems={[
|
||||
exploreId === ExploreId.left && (
|
||||
<DashNavButton
|
||||
key="share"
|
||||
tooltip="Copy shortened link"
|
||||
icon="share-alt"
|
||||
onClick={() => createAndCopyShortLink(window.location.href)}
|
||||
aria-label="Copy shortened link"
|
||||
/>
|
||||
),
|
||||
!datasourceMissing && (
|
||||
<DataSourcePicker
|
||||
key={`${exploreId}-ds-picker`}
|
||||
onChange={this.onChangeDatasource}
|
||||
current={this.props.datasourceName}
|
||||
hideTextValue={showSmallDataSourcePicker}
|
||||
width={showSmallDataSourcePicker ? 8 : undefined}
|
||||
/>
|
||||
),
|
||||
].filter(Boolean)}
|
||||
>
|
||||
<ToolbarButtonRow>
|
||||
{!splitted ? (
|
||||
<ToolbarButton title="Split" onClick={() => split()} icon="columns" disabled={isLive}>
|
||||
Split
|
||||
</ToolbarButton>
|
||||
) : (
|
||||
<ToolbarButton title="Close split pane" onClick={() => closeSplit(exploreId)} icon="times">
|
||||
Close
|
||||
</ToolbarButton>
|
||||
)}
|
||||
|
||||
{!isLive && (
|
||||
<ExploreTimeControls
|
||||
exploreId={exploreId}
|
||||
range={range}
|
||||
timeZone={timeZone}
|
||||
fiscalYearStartMonth={fiscalYearStartMonth}
|
||||
onChangeTime={onChangeTime}
|
||||
splitted={splitted}
|
||||
syncedTimes={syncedTimes}
|
||||
onChangeTimeSync={this.onChangeTimeSync}
|
||||
hideText={showSmallTimePicker}
|
||||
onChangeTimeZone={onChangeTimeZone}
|
||||
onChangeFiscalYearStartMonth={onChangeFiscalYearStartMonth}
|
||||
/>
|
||||
)}
|
||||
|
||||
<AddToDashboard exploreId={exploreId} />
|
||||
|
||||
<RunButton
|
||||
refreshInterval={refreshInterval}
|
||||
onChangeRefreshInterval={this.onChangeRefreshInterval}
|
||||
isSmall={splitted || showSmallTimePicker}
|
||||
isLive={isLive}
|
||||
loading={loading || (isLive && !isPaused)}
|
||||
onRun={this.onRunQuery}
|
||||
showDropdown={!isLive}
|
||||
/>
|
||||
),
|
||||
!datasourceMissing && (
|
||||
<DataSourcePicker
|
||||
key={`${exploreId}-ds-picker`}
|
||||
onChange={this.onChangeDatasource}
|
||||
current={this.props.datasourceName}
|
||||
hideTextValue={showSmallDataSourcePicker}
|
||||
width={showSmallDataSourcePicker ? 8 : undefined}
|
||||
/>
|
||||
),
|
||||
].filter(Boolean)}
|
||||
>
|
||||
<ToolbarButtonRow>
|
||||
{!splitted ? (
|
||||
<ToolbarButton title="Split" onClick={() => split()} icon="columns" disabled={isLive}>
|
||||
Split
|
||||
</ToolbarButton>
|
||||
) : (
|
||||
<ToolbarButton title="Close split pane" onClick={() => closeSplit(exploreId)} icon="times">
|
||||
Close
|
||||
</ToolbarButton>
|
||||
)}
|
||||
|
||||
{!isLive && (
|
||||
<ExploreTimeControls
|
||||
exploreId={exploreId}
|
||||
range={range}
|
||||
timeZone={timeZone}
|
||||
fiscalYearStartMonth={fiscalYearStartMonth}
|
||||
onChangeTime={onChangeTime}
|
||||
splitted={splitted}
|
||||
syncedTimes={syncedTimes}
|
||||
onChangeTimeSync={this.onChangeTimeSync}
|
||||
hideText={showSmallTimePicker}
|
||||
onChangeTimeZone={onChangeTimeZone}
|
||||
onChangeFiscalYearStartMonth={onChangeFiscalYearStartMonth}
|
||||
/>
|
||||
)}
|
||||
{refreshInterval && <SetInterval func={this.onRunQuery} interval={refreshInterval} loading={loading} />}
|
||||
|
||||
<AddToDashboard exploreId={exploreId} />
|
||||
|
||||
<RunButton
|
||||
refreshInterval={refreshInterval}
|
||||
onChangeRefreshInterval={this.onChangeRefreshInterval}
|
||||
isSmall={splitted || showSmallTimePicker}
|
||||
isLive={isLive}
|
||||
loading={loading || (isLive && !isPaused)}
|
||||
onRun={this.onRunQuery}
|
||||
showDropdown={!isLive}
|
||||
/>
|
||||
|
||||
{refreshInterval && <SetInterval func={this.onRunQuery} interval={refreshInterval} loading={loading} />}
|
||||
|
||||
{hasLiveOption && (
|
||||
<LiveTailControls exploreId={exploreId}>
|
||||
{(controls) => (
|
||||
<LiveTailButton
|
||||
splitted={splitted}
|
||||
isLive={isLive}
|
||||
isPaused={isPaused}
|
||||
start={controls.start}
|
||||
pause={controls.pause}
|
||||
resume={controls.resume}
|
||||
stop={controls.stop}
|
||||
/>
|
||||
)}
|
||||
</LiveTailControls>
|
||||
)}
|
||||
</ToolbarButtonRow>
|
||||
</PageToolbar>
|
||||
{hasLiveOption && (
|
||||
<LiveTailControls exploreId={exploreId}>
|
||||
{(controls) => (
|
||||
<LiveTailButton
|
||||
splitted={splitted}
|
||||
isLive={isLive}
|
||||
isPaused={isPaused}
|
||||
start={controls.start}
|
||||
pause={controls.pause}
|
||||
resume={controls.resume}
|
||||
stop={controls.stop}
|
||||
/>
|
||||
)}
|
||||
</LiveTailControls>
|
||||
)}
|
||||
</ToolbarButtonRow>
|
||||
</PageToolbar>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -24,7 +24,7 @@ import { getDatasourceSrv } from 'app/features/plugins/datasource_srv';
|
||||
import { getTimeZone } from 'app/features/profile/state/selectors';
|
||||
import { StoreState } from 'app/types';
|
||||
import { ExploreId } from 'app/types/explore';
|
||||
import React, { useCallback, useMemo, useState } from 'react';
|
||||
import React, { RefObject, useCallback, useMemo, useState } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { changePanelState } from '../state/explorePane';
|
||||
import { createSpanLinkFactory } from './createSpanLink';
|
||||
@ -43,6 +43,7 @@ type Props = {
|
||||
splitOpenFn: SplitOpen;
|
||||
exploreId: ExploreId;
|
||||
scrollElement?: Element;
|
||||
topOfExploreViewRef?: RefObject<HTMLDivElement>;
|
||||
};
|
||||
|
||||
export function TraceView(props: Props) {
|
||||
@ -176,6 +177,7 @@ export function TraceView(props: Props) {
|
||||
scrollElement={props.scrollElement}
|
||||
focusedSpanId={focusedSpanId}
|
||||
createFocusSpanLink={createFocusSpanLink}
|
||||
topOfExploreViewRef={props.topOfExploreViewRef}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import React, { RefObject } from 'react';
|
||||
import { Collapse } from '@grafana/ui';
|
||||
import { DataFrame, SplitOpen } from '@grafana/data';
|
||||
import { TraceView } from './TraceView';
|
||||
@ -9,9 +9,10 @@ interface Props {
|
||||
splitOpenFn: SplitOpen;
|
||||
exploreId: ExploreId;
|
||||
scrollElement?: Element;
|
||||
topOfExploreViewRef?: RefObject<HTMLDivElement>;
|
||||
}
|
||||
export function TraceViewContainer(props: Props) {
|
||||
const { dataFrames, splitOpenFn, exploreId, scrollElement } = props;
|
||||
const { dataFrames, splitOpenFn, exploreId, scrollElement, topOfExploreViewRef } = props;
|
||||
|
||||
return (
|
||||
<Collapse label="Trace View" isOpen>
|
||||
@ -20,6 +21,7 @@ export function TraceViewContainer(props: Props) {
|
||||
dataFrames={dataFrames}
|
||||
splitOpenFn={splitOpenFn}
|
||||
scrollElement={scrollElement}
|
||||
topOfExploreViewRef={topOfExploreViewRef}
|
||||
/>
|
||||
</Collapse>
|
||||
);
|
||||
|
@ -8,6 +8,11 @@ exports[`Explore should render component 1`] = `
|
||||
<Connect(UnConnectedExploreToolbar)
|
||||
exploreId="left"
|
||||
onChangeTime={[Function]}
|
||||
topOfExploreViewRef={
|
||||
Object {
|
||||
"current": null,
|
||||
}
|
||||
}
|
||||
/>
|
||||
<div
|
||||
className="explore-container"
|
||||
|
Loading…
Reference in New Issue
Block a user