mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Tracing: Span filters reset filters and button (#66781)
* Reset filters and button * Add tests
This commit is contained in:
@@ -27,22 +27,25 @@ const defaultProps = {
|
|||||||
describe('<NewTracePageSearchBar>', () => {
|
describe('<NewTracePageSearchBar>', () => {
|
||||||
it('renders buttons', () => {
|
it('renders buttons', () => {
|
||||||
render(<NewTracePageSearchBar {...(defaultProps as unknown as TracePageSearchBarProps)} />);
|
render(<NewTracePageSearchBar {...(defaultProps as unknown as TracePageSearchBarProps)} />);
|
||||||
const nextResButton = screen.queryByRole('button', { name: 'Next result button' });
|
const nextResButton = screen.getByRole('button', { name: 'Next result button' });
|
||||||
const prevResButton = screen.queryByRole('button', { name: 'Prev result button' });
|
const prevResButton = screen.getByRole('button', { name: 'Prev result button' });
|
||||||
|
const resetFiltersButton = screen.getByRole('button', { name: 'Reset filters button' });
|
||||||
expect(nextResButton).toBeInTheDocument();
|
expect(nextResButton).toBeInTheDocument();
|
||||||
expect(prevResButton).toBeInTheDocument();
|
expect(prevResButton).toBeInTheDocument();
|
||||||
|
expect(resetFiltersButton).toBeInTheDocument();
|
||||||
expect((nextResButton as HTMLButtonElement)['disabled']).toBe(true);
|
expect((nextResButton as HTMLButtonElement)['disabled']).toBe(true);
|
||||||
expect((prevResButton as HTMLButtonElement)['disabled']).toBe(true);
|
expect((prevResButton as HTMLButtonElement)['disabled']).toBe(true);
|
||||||
|
expect((resetFiltersButton as HTMLButtonElement)['disabled']).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders buttons that can be used to search if filters added', () => {
|
it('renders buttons that can be used to search if results found', () => {
|
||||||
const props = {
|
const props = {
|
||||||
...defaultProps,
|
...defaultProps,
|
||||||
spanFilterMatches: new Set(['2ed38015486087ca']),
|
spanFilterMatches: new Set(['2ed38015486087ca']),
|
||||||
};
|
};
|
||||||
render(<NewTracePageSearchBar {...(props as unknown as TracePageSearchBarProps)} />);
|
render(<NewTracePageSearchBar {...(props as unknown as TracePageSearchBarProps)} />);
|
||||||
const nextResButton = screen.queryByRole('button', { name: 'Next result button' });
|
const nextResButton = screen.getByRole('button', { name: 'Next result button' });
|
||||||
const prevResButton = screen.queryByRole('button', { name: 'Prev result button' });
|
const prevResButton = screen.getByRole('button', { name: 'Prev result button' });
|
||||||
expect(nextResButton).toBeInTheDocument();
|
expect(nextResButton).toBeInTheDocument();
|
||||||
expect(prevResButton).toBeInTheDocument();
|
expect(prevResButton).toBeInTheDocument();
|
||||||
expect((nextResButton as HTMLButtonElement)['disabled']).toBe(false);
|
expect((nextResButton as HTMLButtonElement)['disabled']).toBe(false);
|
||||||
|
|||||||
@@ -13,12 +13,13 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
import { css } from '@emotion/css';
|
import { css } from '@emotion/css';
|
||||||
import React, { memo, Dispatch, SetStateAction, useEffect } from 'react';
|
import React, { memo, Dispatch, SetStateAction, useEffect, useMemo } from 'react';
|
||||||
|
|
||||||
import { config, reportInteraction } from '@grafana/runtime';
|
import { config, reportInteraction } from '@grafana/runtime';
|
||||||
import { Button, useStyles2 } from '@grafana/ui';
|
import { Button, useStyles2 } from '@grafana/ui';
|
||||||
|
|
||||||
import { SearchProps } from '../../useSearch';
|
import { SearchProps } from '../../useSearch';
|
||||||
|
import { convertTimeFilter } from '../utils/filter-spans';
|
||||||
|
|
||||||
export type TracePageSearchBarProps = {
|
export type TracePageSearchBarProps = {
|
||||||
search: SearchProps;
|
search: SearchProps;
|
||||||
@@ -27,10 +28,11 @@ export type TracePageSearchBarProps = {
|
|||||||
focusedSpanIdForSearch: string;
|
focusedSpanIdForSearch: string;
|
||||||
setFocusedSpanIdForSearch: Dispatch<SetStateAction<string>>;
|
setFocusedSpanIdForSearch: Dispatch<SetStateAction<string>>;
|
||||||
datasourceType: string;
|
datasourceType: string;
|
||||||
|
reset: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default memo(function NewTracePageSearchBar(props: TracePageSearchBarProps) {
|
export default memo(function NewTracePageSearchBar(props: TracePageSearchBarProps) {
|
||||||
const { search, spanFilterMatches, focusedSpanIdForSearch, setFocusedSpanIdForSearch, datasourceType } = props;
|
const { search, spanFilterMatches, focusedSpanIdForSearch, setFocusedSpanIdForSearch, datasourceType, reset } = props;
|
||||||
const styles = useStyles2(getStyles);
|
const styles = useStyles2(getStyles);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -77,34 +79,60 @@ export default memo(function NewTracePageSearchBar(props: TracePageSearchBarProp
|
|||||||
setFocusedSpanIdForSearch(spanMatches[prevMatchedIndex - 1]);
|
setFocusedSpanIdForSearch(spanMatches[prevMatchedIndex - 1]);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
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 buttonEnabled = spanFilterMatches && spanFilterMatches?.size > 0;
|
const buttonEnabled = spanFilterMatches && spanFilterMatches?.size > 0;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.searchBar}>
|
<div className={styles.searchBar}>
|
||||||
<>
|
<div className={styles.buttons}>
|
||||||
<Button
|
<>
|
||||||
className={styles.button}
|
<div className={styles.resetButton}>
|
||||||
variant="secondary"
|
<Button
|
||||||
disabled={!buttonEnabled}
|
variant="destructive"
|
||||||
type="button"
|
disabled={!resetEnabled}
|
||||||
fill={'outline'}
|
type="button"
|
||||||
aria-label="Prev result button"
|
fill="outline"
|
||||||
onClick={prevResult}
|
aria-label="Reset filters button"
|
||||||
>
|
onClick={reset}
|
||||||
Prev
|
>
|
||||||
</Button>
|
Reset
|
||||||
<Button
|
</Button>
|
||||||
className={styles.button}
|
</div>
|
||||||
variant="secondary"
|
<div className={styles.nextPrevButtons}>
|
||||||
disabled={!buttonEnabled}
|
<Button
|
||||||
type="button"
|
variant="secondary"
|
||||||
fill={'outline'}
|
disabled={!buttonEnabled}
|
||||||
aria-label="Next result button"
|
type="button"
|
||||||
onClick={nextResult}
|
fill="outline"
|
||||||
>
|
aria-label="Prev result button"
|
||||||
Next
|
onClick={prevResult}
|
||||||
</Button>
|
>
|
||||||
</>
|
Prev
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="secondary"
|
||||||
|
disabled={!buttonEnabled}
|
||||||
|
type="button"
|
||||||
|
fill="outline"
|
||||||
|
aria-label="Next result button"
|
||||||
|
onClick={nextResult}
|
||||||
|
>
|
||||||
|
Next
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -112,12 +140,23 @@ export default memo(function NewTracePageSearchBar(props: TracePageSearchBarProp
|
|||||||
export const getStyles = () => {
|
export const getStyles = () => {
|
||||||
return {
|
return {
|
||||||
searchBar: css`
|
searchBar: css`
|
||||||
|
display: inline;
|
||||||
|
`,
|
||||||
|
buttons: css`
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
margin-top: 5px;
|
margin: 5px 0 0 0;
|
||||||
`,
|
`,
|
||||||
button: css`
|
resetButton: css`
|
||||||
margin-left: 8px;
|
order: 1;
|
||||||
|
`,
|
||||||
|
nextPrevButtons: css`
|
||||||
|
margin-left: auto;
|
||||||
|
order: 2;
|
||||||
|
|
||||||
|
button {
|
||||||
|
margin-left: 8px;
|
||||||
|
}
|
||||||
`,
|
`,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -162,6 +162,29 @@ describe('SpanFilters', () => {
|
|||||||
jest.advanceTimersByTime(1000);
|
jest.advanceTimersByTime(1000);
|
||||||
expect(screen.getAllByLabelText('Select tag key').length).toBe(1);
|
expect(screen.getAllByLabelText('Select tag key').length).toBe(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should allow resetting filters', async () => {
|
||||||
|
render(<SpanFiltersWithProps />);
|
||||||
|
const resetFiltersButton = screen.getByRole('button', { name: 'Reset filters button' });
|
||||||
|
expect(resetFiltersButton).toBeInTheDocument();
|
||||||
|
expect((resetFiltersButton as HTMLButtonElement)['disabled']).toBe(true);
|
||||||
|
|
||||||
|
const serviceValue = screen.getByLabelText('Select service name');
|
||||||
|
const spanValue = screen.getByLabelText('Select span name');
|
||||||
|
const tagKey = screen.getByLabelText('Select tag key');
|
||||||
|
const tagValue = screen.getByLabelText('Select tag value');
|
||||||
|
await selectAndCheckValue(user, serviceValue, 'Service0');
|
||||||
|
await selectAndCheckValue(user, spanValue, 'Span0');
|
||||||
|
await selectAndCheckValue(user, tagKey, 'TagKey0');
|
||||||
|
await selectAndCheckValue(user, tagValue, 'TagValue0');
|
||||||
|
|
||||||
|
expect((resetFiltersButton as HTMLButtonElement)['disabled']).toBe(false);
|
||||||
|
await user.click(resetFiltersButton);
|
||||||
|
expect(screen.queryByText('Service0')).not.toBeInTheDocument();
|
||||||
|
expect(screen.queryByText('Span0')).not.toBeInTheDocument();
|
||||||
|
expect(screen.queryByText('TagKey0')).not.toBeInTheDocument();
|
||||||
|
expect(screen.queryByText('TagValue0')).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
const selectAndCheckValue = async (user: ReturnType<typeof userEvent.setup>, elem: HTMLElement, text: string) => {
|
const selectAndCheckValue = async (user: ReturnType<typeof userEvent.setup>, elem: HTMLElement, text: string) => {
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
|
|
||||||
import { css } from '@emotion/css';
|
import { css } from '@emotion/css';
|
||||||
import { uniq } from 'lodash';
|
import { uniq } from 'lodash';
|
||||||
import React, { useState, memo } from 'react';
|
import React, { useState, useEffect, memo, useCallback } from 'react';
|
||||||
|
|
||||||
import { SelectableValue, toOption } from '@grafana/data';
|
import { SelectableValue, toOption } from '@grafana/data';
|
||||||
import { AccessoryButton } from '@grafana/experimental';
|
import { AccessoryButton } from '@grafana/experimental';
|
||||||
@@ -30,7 +30,7 @@ import {
|
|||||||
useStyles2,
|
useStyles2,
|
||||||
} from '@grafana/ui';
|
} from '@grafana/ui';
|
||||||
|
|
||||||
import { randomId, SearchProps, Tag } from '../../../useSearch';
|
import { defaultFilters, randomId, SearchProps, Tag } from '../../../useSearch';
|
||||||
import { Trace } from '../../types';
|
import { Trace } from '../../types';
|
||||||
import NewTracePageSearchBar from '../NewTracePageSearchBar';
|
import NewTracePageSearchBar from '../NewTracePageSearchBar';
|
||||||
|
|
||||||
@@ -64,6 +64,18 @@ export const SpanFilters = memo((props: SpanFilterProps) => {
|
|||||||
const [tagKeys, setTagKeys] = useState<Array<SelectableValue<string>>>();
|
const [tagKeys, setTagKeys] = useState<Array<SelectableValue<string>>>();
|
||||||
const [tagValues, setTagValues] = useState<{ [key: string]: Array<SelectableValue<string>> }>({});
|
const [tagValues, setTagValues] = useState<{ [key: string]: Array<SelectableValue<string>> }>({});
|
||||||
|
|
||||||
|
const reset = useCallback(() => {
|
||||||
|
setServiceNames(undefined);
|
||||||
|
setSpanNames(undefined);
|
||||||
|
setTagKeys(undefined);
|
||||||
|
setTagValues({});
|
||||||
|
setSearch(defaultFilters);
|
||||||
|
}, [setSearch]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
reset();
|
||||||
|
}, [reset, trace]);
|
||||||
|
|
||||||
if (!trace) {
|
if (!trace) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -237,7 +249,7 @@ export const SpanFilters = memo((props: SpanFilterProps) => {
|
|||||||
onOpenMenu={getServiceNames}
|
onOpenMenu={getServiceNames}
|
||||||
options={serviceNames}
|
options={serviceNames}
|
||||||
placeholder="All service names"
|
placeholder="All service names"
|
||||||
value={search.serviceName}
|
value={search.serviceName || null}
|
||||||
/>
|
/>
|
||||||
</HorizontalGroup>
|
</HorizontalGroup>
|
||||||
</InlineField>
|
</InlineField>
|
||||||
@@ -258,7 +270,7 @@ export const SpanFilters = memo((props: SpanFilterProps) => {
|
|||||||
onOpenMenu={getSpanNames}
|
onOpenMenu={getSpanNames}
|
||||||
options={spanNames}
|
options={spanNames}
|
||||||
placeholder="All span names"
|
placeholder="All span names"
|
||||||
value={search.spanName}
|
value={search.spanName || null}
|
||||||
/>
|
/>
|
||||||
</HorizontalGroup>
|
</HorizontalGroup>
|
||||||
</InlineField>
|
</InlineField>
|
||||||
@@ -309,7 +321,7 @@ export const SpanFilters = memo((props: SpanFilterProps) => {
|
|||||||
onOpenMenu={getTagKeys}
|
onOpenMenu={getTagKeys}
|
||||||
options={tagKeys}
|
options={tagKeys}
|
||||||
placeholder="Select tag"
|
placeholder="Select tag"
|
||||||
value={tag.key}
|
value={tag.key || null}
|
||||||
/>
|
/>
|
||||||
<Select
|
<Select
|
||||||
aria-label={`Select tag operator`}
|
aria-label={`Select tag operator`}
|
||||||
@@ -374,6 +386,7 @@ export const SpanFilters = memo((props: SpanFilterProps) => {
|
|||||||
focusedSpanIdForSearch={focusedSpanIdForSearch}
|
focusedSpanIdForSearch={focusedSpanIdForSearch}
|
||||||
setFocusedSpanIdForSearch={setFocusedSpanIdForSearch}
|
setFocusedSpanIdForSearch={setFocusedSpanIdForSearch}
|
||||||
datasourceType={datasourceType}
|
datasourceType={datasourceType}
|
||||||
|
reset={reset}
|
||||||
/>
|
/>
|
||||||
</Collapse>
|
</Collapse>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user