diff --git a/packages/grafana-data/src/types/featureToggles.gen.ts b/packages/grafana-data/src/types/featureToggles.gen.ts index 3fab1ac8bd5..eba76d2c198 100644 --- a/packages/grafana-data/src/types/featureToggles.gen.ts +++ b/packages/grafana-data/src/types/featureToggles.gen.ts @@ -377,10 +377,14 @@ export interface FeatureToggles { */ perPanelNonApplicableDrilldowns?: boolean; /** - * Enabled a group by action per panel + * Enables a group by action per panel */ panelGroupBy?: boolean; /** + * Enables filtering by grouping labels on the panel level through legend or tooltip + */ + perPanelFiltering?: boolean; + /** * Enables use of the `systemPanelFilterVar` variable to filter panels in a dashboard */ panelFilterVariable?: boolean; diff --git a/packages/grafana-e2e-selectors/src/selectors/components.ts b/packages/grafana-e2e-selectors/src/selectors/components.ts index 47c30187175..aca18844459 100644 --- a/packages/grafana-e2e-selectors/src/selectors/components.ts +++ b/packages/grafana-e2e-selectors/src/selectors/components.ts @@ -1490,6 +1490,16 @@ export const versionedComponents = { }, }, }, + VizTooltipFooter: { + buttons: { + apply: { + ['12.1.0']: 'data-testid viz-tooltip-footer-apply-filters-button', + }, + applyInverse: { + ['12.1.0']: 'data-testid viz-tooltip-footer-apply-inverse-filters-button', + }, + }, + }, } satisfies VersionedSelectorGroup; export type VersionedComponents = typeof versionedComponents; diff --git a/packages/grafana-ui/src/components/PanelChrome/PanelContext.ts b/packages/grafana-ui/src/components/PanelChrome/PanelContext.ts index a9d5334925a..3e34eed16f7 100644 --- a/packages/grafana-ui/src/components/PanelChrome/PanelContext.ts +++ b/packages/grafana-ui/src/components/PanelChrome/PanelContext.ts @@ -55,6 +55,15 @@ export interface PanelContext { */ onAddAdHocFilter?: (item: AdHocFilterItem) => void; + /** + * Returns filters based on existing grouping or an empty array + */ + getFiltersBasedOnGrouping?: (items: AdHocFilterItem[]) => AdHocFilterItem[]; + /** + * + * Used to apply multiple filters at once + */ + onAddAdHocFilters?: (items: AdHocFilterItem[]) => void; /** * Enables modifying thresholds directly from the panel * diff --git a/packages/grafana-ui/src/components/VizTooltip/VizTooltipFooter.test.tsx b/packages/grafana-ui/src/components/VizTooltip/VizTooltipFooter.test.tsx index f23961ef5e4..8e48305c0a9 100644 --- a/packages/grafana-ui/src/components/VizTooltip/VizTooltipFooter.test.tsx +++ b/packages/grafana-ui/src/components/VizTooltip/VizTooltipFooter.test.tsx @@ -3,6 +3,7 @@ import userEvent from '@testing-library/user-event'; import { MemoryRouter } from 'react-router-dom-v5-compat'; import { Field, FieldType, LinkModel } from '@grafana/data'; +import { selectors } from '@grafana/e2e-selectors'; import { VizTooltipFooter, AdHocFilterModel } from './VizTooltipFooter'; @@ -89,4 +90,65 @@ describe('VizTooltipFooter', () => { expect(screen.queryByRole('button', { name: /filter for 'testValue'/i })).not.toBeInTheDocument(); }); + + it('should render filter by grouping buttons and fire onclick', async () => { + const onForClick = jest.fn(); + const onOutClick = jest.fn(); + + const filterByGroupedLabels = { + onFilterForGroupedLabels: onForClick, + onFilterOutGroupedLabels: onOutClick, + }; + + render( + + + + ); + + const onForButton = screen.getByRole('button', { name: /Apply as filter/i }); + expect(onForButton).toBeInTheDocument(); + + const onOutButton = screen.getByRole('button', { name: /Apply as inverse filter/i }); + expect(onOutButton).toBeInTheDocument(); + + await userEvent.click(onForButton); + expect(onForClick).toHaveBeenCalled(); + + await userEvent.click(onOutButton); + expect(onOutClick).toHaveBeenCalled(); + }); + + it('should not render filter by grouping buttons when there are one-click links', () => { + const filterByGroupedLabels = { + onFilterForGroupedLabels: jest.fn(), + onFilterOutGroupedLabels: jest.fn(), + }; + + const onClick = jest.fn(); + const field: Field = { + name: '', + type: FieldType.string, + values: [], + config: {}, + }; + + const oneClickLink: LinkModel = { + href: '#', + onClick, + title: 'One Click Link', + origin: field, + target: undefined, + oneClick: true, + }; + + render( + + + + ); + + expect(screen.queryByTestId(selectors.components.VizTooltipFooter.buttons.apply)).not.toBeInTheDocument(); + expect(screen.queryByTestId(selectors.components.VizTooltipFooter.buttons.applyInverse)).not.toBeInTheDocument(); + }); }); diff --git a/packages/grafana-ui/src/components/VizTooltip/VizTooltipFooter.tsx b/packages/grafana-ui/src/components/VizTooltip/VizTooltipFooter.tsx index b4eccaee66a..2bd324129f2 100644 --- a/packages/grafana-ui/src/components/VizTooltip/VizTooltipFooter.tsx +++ b/packages/grafana-ui/src/components/VizTooltip/VizTooltipFooter.tsx @@ -2,6 +2,7 @@ import { css } from '@emotion/css'; import { useMemo } from 'react'; import { ActionModel, Field, GrafanaTheme2, LinkModel, ThemeSpacingTokens } from '@grafana/data'; +import { selectors } from '@grafana/e2e-selectors'; import { Trans } from '@grafana/i18n'; import { useStyles2 } from '../../themes/ThemeContext'; @@ -17,10 +18,16 @@ export interface AdHocFilterModel extends AdHocFilterItem { onClick: () => void; } +export interface FilterByGroupedLabelsModel { + onFilterForGroupedLabels?: () => void; + onFilterOutGroupedLabels?: () => void; +} + interface VizTooltipFooterProps { dataLinks: Array>; actions?: Array>; adHocFilters?: AdHocFilterModel[]; + filterByGroupedLabels?: FilterByGroupedLabelsModel; annotate?: () => void; } @@ -85,7 +92,13 @@ const renderActions = makeRenderLinksOrActions( (item, i) => ); -export const VizTooltipFooter = ({ dataLinks, actions = [], annotate, adHocFilters = [] }: VizTooltipFooterProps) => { +export const VizTooltipFooter = ({ + dataLinks, + actions = [], + annotate, + adHocFilters = [], + filterByGroupedLabels, +}: VizTooltipFooterProps) => { const styles = useStyles2(getStyles); const hasOneClickLink = useMemo(() => dataLinks.some((link) => link.oneClick === true), [dataLinks]); const hasOneClickAction = useMemo(() => actions.some((action) => action.oneClick === true), [actions]); @@ -105,6 +118,39 @@ export const VizTooltipFooter = ({ dataLinks, actions = [], annotate, adHocFilte ))} )} + + {!hasOneClickLink && !hasOneClickAction && filterByGroupedLabels && ( +
+ + + + +
+ )} {!hasOneClickLink && !hasOneClickAction && annotate != null && (