Skip to content

Commit fc27a27

Browse files
On click popover improvements (#23689)
* Show value action in the same popover as values * fix issue with wrong metric definition when we have only one metric
1 parent 8ae1292 commit fc27a27

File tree

8 files changed

+160
-67
lines changed

8 files changed

+160
-67
lines changed

graylog2-web-interface/src/views/components/visualizations/OnClickPopover/CartesianOnClickPopoverDropdown.tsx

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,9 @@ import React, { useMemo } from 'react';
1818
import styled, { css } from 'styled-components';
1919

2020
import Popover from 'components/common/Popover';
21-
import type AggregationWidgetConfig from 'views/logic/aggregationbuilder/AggregationWidgetConfig';
2221
import { keySeparator } from 'views/Constants';
2322
import OnClickPopoverValueGroups from 'views/components/visualizations/OnClickPopover/OnClickPopoverValueGroups';
24-
import type { ValueGroups, ClickPoint } from 'views/components/visualizations/OnClickPopover/Types';
23+
import type { ValueGroups, OnClickPopoverDropdownProps } from 'views/components/visualizations/OnClickPopover/Types';
2524
import getHoverSwatchColor from 'views/components/visualizations/utils/getHoverSwatchColor';
2625

2726
const DivContainer = styled.div(
@@ -32,20 +31,14 @@ const DivContainer = styled.div(
3231
`,
3332
);
3433

35-
const CartesianOnClickPopoverDropdown = ({
36-
clickPoint,
37-
config,
38-
}: {
39-
clickPoint: ClickPoint;
40-
config: AggregationWidgetConfig;
41-
}) => {
34+
const CartesianOnClickPopoverDropdown = ({ clickPoint, config, setFieldData }: OnClickPopoverDropdownProps) => {
4235
const traceColor = getHoverSwatchColor(clickPoint);
4336
const { rowPivotValues, columnPivotValues, metricValue } = useMemo<ValueGroups>(() => {
4437
if (!clickPoint || !config) return {};
4538
const splitNames: Array<string | number> = (clickPoint.data.originalName ?? clickPoint.data.name).split(
4639
keySeparator,
4740
);
48-
const metric: string = splitNames.pop() as string;
41+
const metric: string = config.series.length === 1 ? config.series[0].function : (splitNames.pop() as string);
4942

5043
const columnPivotsToFields = config?.columnPivots?.flatMap((pivot) => pivot.fields) ?? [];
5144

@@ -81,6 +74,7 @@ const CartesianOnClickPopoverDropdown = ({
8174
columnPivotValues={columnPivotValues}
8275
metricValue={metricValue}
8376
rowPivotValues={rowPivotValues}
77+
setFieldData={setFieldData}
8478
/>
8579
</DivContainer>
8680
</Popover.Dropdown>

graylog2-web-interface/src/views/components/visualizations/OnClickPopover/DropdownSwitcher.tsx

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,19 +16,22 @@
1616
*/
1717
import React, { useState, useEffect } from 'react';
1818

19-
import type { ClickPoint } from 'views/components/visualizations/OnClickPopover/Types';
19+
import type {
20+
ClickPoint,
21+
OnClickPopoverDropdown,
22+
FieldData,
23+
} from 'views/components/visualizations/OnClickPopover/Types';
2024
import type AggregationWidgetConfig from 'views/logic/aggregationbuilder/AggregationWidgetConfig';
2125
import ClickPointSelector from 'views/components/visualizations/OnClickPopover/ClickPointSelector';
26+
import ValueActionsDropdown from 'views/components/visualizations/OnClickPopover/ValueActionsDropdown';
2227

2328
type Props = {
24-
component: React.ComponentType<{
25-
clickPoint: ClickPoint;
26-
config: AggregationWidgetConfig;
27-
}>;
29+
component: OnClickPopoverDropdown;
2830
clickPoint: ClickPoint;
2931
config: AggregationWidgetConfig;
3032
clickPointsInRadius?: Array<ClickPoint>;
3133
metricMapper?: (clickPoint: ClickPoint) => { value: string; metric: string };
34+
onPopoverClose: () => void;
3235
};
3336

3437
const defaultMetricMapper = (clickPoint: ClickPoint) => ({
@@ -42,25 +45,36 @@ const DropdownSwitcher = ({
4245
config,
4346
clickPointsInRadius = [],
4447
metricMapper = defaultMetricMapper,
48+
onPopoverClose,
4549
}: Props) => {
4650
const [selectedClickPoint, setSelectedClickPoint] = useState<ClickPoint>();
47-
const [showComponent, setShowComponent] = useState<boolean>();
51+
const [showValuesComponent, setShowValuesComponent] = useState<boolean>();
52+
const [fieldData, setFieldData] = useState<FieldData>(null);
4853

4954
const onSelect = (pt: ClickPoint) => {
50-
setShowComponent(true);
55+
setShowValuesComponent(true);
5156
setSelectedClickPoint(pt);
5257
};
5358

5459
useEffect(() => {
5560
setSelectedClickPoint(clickPoint);
5661
const len = clickPointsInRadius?.length;
57-
setShowComponent(!len || len === 1);
62+
setShowValuesComponent(!len || len === 1);
63+
setFieldData(null);
5864
}, [clickPoint, clickPointsInRadius]);
5965

6066
if (!selectedClickPoint) return null;
6167

62-
return showComponent ? (
63-
<Component clickPoint={selectedClickPoint} config={config} />
68+
const onActionRun = () => {
69+
onPopoverClose();
70+
setFieldData(null);
71+
};
72+
73+
if (fieldData)
74+
return <ValueActionsDropdown field={fieldData.field} value={fieldData.value} onActionRun={onActionRun} />;
75+
76+
return showValuesComponent ? (
77+
<Component clickPoint={selectedClickPoint} config={config} setFieldData={setFieldData} />
6478
) : (
6579
<ClickPointSelector clickPointsInRadius={clickPointsInRadius} metricMapper={metricMapper} onSelect={onSelect} />
6680
);

graylog2-web-interface/src/views/components/visualizations/heatmap/HeatmapOnClickPopover.tsx renamed to graylog2-web-interface/src/views/components/visualizations/OnClickPopover/HeatmapOnClickPopover.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,17 +17,16 @@
1717
import React, { useMemo } from 'react';
1818

1919
import Popover from 'components/common/Popover';
20-
import type { ClickPoint, ValueGroups } from 'views/components/visualizations/OnClickPopover/Types';
20+
import type { ValueGroups, OnClickPopoverDropdownProps } from 'views/components/visualizations/OnClickPopover/Types';
2121
import { keySeparator } from 'views/Constants';
22-
import type AggregationWidgetConfig from 'views/logic/aggregationbuilder/AggregationWidgetConfig';
2322
import OnClickPopoverValueGroups from 'views/components/visualizations/OnClickPopover/OnClickPopoverValueGroups';
2423

25-
const HeatmapOnClickPopover = ({ clickPoint, config }: { config: AggregationWidgetConfig; clickPoint: ClickPoint }) => {
24+
const HeatmapOnClickPopover = ({ clickPoint, config, setFieldData }: OnClickPopoverDropdownProps) => {
2625
const { rowPivotValues, columnPivotValues, metricValue } = useMemo<ValueGroups>(() => {
2726
if (!clickPoint || !config) return {};
2827
const splitXValues: Array<string | number> = (clickPoint.x as string).split(keySeparator);
2928
const traceColor = null;
30-
const metric: string = splitXValues.pop() as string;
29+
const metric: string = config.series.length === 1 ? config.series[0].function : (splitXValues.pop() as string);
3130

3231
const columnPivotsToFields = config?.columnPivots?.flatMap((pivot) => pivot.fields) ?? [];
3332

@@ -63,6 +62,7 @@ const HeatmapOnClickPopover = ({ clickPoint, config }: { config: AggregationWidg
6362
columnPivotValues={columnPivotValues}
6463
metricValue={metricValue}
6564
rowPivotValues={rowPivotValues}
65+
setFieldData={setFieldData}
6666
/>
6767
</Popover.Dropdown>
6868
)

graylog2-web-interface/src/views/components/visualizations/OnClickPopover/OnClickPopoverValueGroups.tsx

Lines changed: 32 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -15,40 +15,46 @@
1515
* <http://www.mongodb.com/licensing/server-side-public-license>.
1616
*/
1717
import React from 'react';
18-
import styled, { css } from 'styled-components';
18+
import styled from 'styled-components';
1919

20-
import Value from 'views/components/Value';
21-
import type { ValueGroupItem, ValueGroups } from 'views/components/visualizations/OnClickPopover/Types';
20+
import { ListGroup, ListGroupItem } from 'components/bootstrap';
21+
import type { ValueGroupItem, ValueGroups, FieldData } from 'views/components/visualizations/OnClickPopover/Types';
2222
import ValueRenderer from 'views/components/visualizations/OnClickPopover/ValueRenderer';
2323

24-
type Props = ValueGroups;
24+
type Props = ValueGroups & { setFieldData: React.Dispatch<React.SetStateAction<FieldData>> };
2525

26-
const DivContainer = styled.div(
27-
({ theme }) => css`
28-
display: flex;
29-
flex-direction: column;
30-
gap: ${theme.spacings.xxs};
31-
`,
32-
);
26+
const StyledListGroup = styled(ListGroup)`
27+
max-height: 300px;
28+
overflow-y: auto;
29+
`;
3330

34-
const Group = ({ group, keyPrefix }: { group: Array<ValueGroupItem>; keyPrefix: string }) => {
31+
const Group = ({
32+
group,
33+
keyPrefix,
34+
setFieldData,
35+
}: {
36+
group: Array<ValueGroupItem>;
37+
keyPrefix: string;
38+
setFieldData: Props['setFieldData'];
39+
}) => {
3540
if (!group?.length) return null;
3641

37-
return group.map(({ text, value, field, traceColor }) => (
38-
<Value
39-
key={`${keyPrefix}-${value}-${field}`}
40-
field={field}
41-
value={value}
42-
render={() => <ValueRenderer value={text} label={field} traceColor={traceColor} />}
43-
/>
44-
));
42+
return (
43+
<>
44+
{group.map(({ text, value, field, traceColor }) => (
45+
<ListGroupItem onClick={() => setFieldData({ value, field })} key={`${keyPrefix}-${value}-${field}`}>
46+
<ValueRenderer value={text} label={field} traceColor={traceColor} />
47+
</ListGroupItem>
48+
))}
49+
</>
50+
);
4551
};
46-
const OnClickPopoverValueGroups = ({ metricValue, rowPivotValues, columnPivotValues }: Props) => (
47-
<DivContainer>
48-
{metricValue && <Group group={[metricValue]} keyPrefix="metricValue" />}
49-
<Group group={rowPivotValues} keyPrefix="rowPivotValues" />
50-
<Group group={columnPivotValues} keyPrefix="columnPivotValues" />
51-
</DivContainer>
52+
const OnClickPopoverValueGroups = ({ metricValue, rowPivotValues, columnPivotValues, setFieldData }: Props) => (
53+
<StyledListGroup>
54+
{metricValue && <Group group={[metricValue]} keyPrefix="metricValue" setFieldData={setFieldData} />}
55+
<Group group={rowPivotValues} keyPrefix="rowPivotValues" setFieldData={setFieldData} />
56+
<Group group={columnPivotValues} keyPrefix="columnPivotValues" setFieldData={setFieldData} />
57+
</StyledListGroup>
5258
);
5359

5460
export default OnClickPopoverValueGroups;

graylog2-web-interface/src/views/components/visualizations/OnClickPopover/PieOnClickPopoverDropdown.tsx

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -17,28 +17,21 @@
1717
import React, { useMemo } from 'react';
1818

1919
import Popover from 'components/common/Popover';
20-
import type AggregationWidgetConfig from 'views/logic/aggregationbuilder/AggregationWidgetConfig';
2120
import { keySeparator } from 'views/Constants';
2221
import OnClickPopoverValueGroups from 'views/components/visualizations/OnClickPopover/OnClickPopoverValueGroups';
2322
import formatValueWithUnitLabel from 'views/components/visualizations/utils/formatValueWithUnitLabel';
2423
import { getPrettifiedValue } from 'views/components/visualizations/utils/unitConverters';
2524
import getHoverSwatchColor from 'views/components/visualizations/utils/getHoverSwatchColor';
26-
import type { ValueGroups, ClickPoint } from 'views/components/visualizations/OnClickPopover/Types';
25+
import type { ValueGroups, OnClickPopoverDropdownProps } from 'views/components/visualizations/OnClickPopover/Types';
2726

28-
const PieOnClickPopoverDropdown = ({
29-
clickPoint,
30-
config,
31-
}: {
32-
clickPoint: ClickPoint;
33-
config: AggregationWidgetConfig;
34-
}) => {
27+
const PieOnClickPopoverDropdown = ({ clickPoint, config, setFieldData }: OnClickPopoverDropdownProps) => {
3528
const { rowPivotValues, columnPivotValues, metricValue } = useMemo<ValueGroups>(() => {
3629
if (!clickPoint || !config) return {};
3730
const traceColor = getHoverSwatchColor(clickPoint);
3831
const splitNames: Array<string | number> = (clickPoint.data.originalName ?? clickPoint.data.name).split(
3932
keySeparator,
4033
);
41-
const metric = splitNames.pop() as string;
34+
const metric: string = config.series.length === 1 ? config.series[0].function : (splitNames.pop() as string);
4235

4336
const columnPivotsToFields = config?.columnPivots?.flatMap((pivot) => pivot.fields) ?? [];
4437

@@ -80,6 +73,7 @@ const PieOnClickPopoverDropdown = ({
8073
rowPivotValues={rowPivotValues}
8174
columnPivotValues={columnPivotValues}
8275
metricValue={metricValue}
76+
setFieldData={setFieldData}
8377
/>
8478
</Popover.Dropdown>
8579
);

graylog2-web-interface/src/views/components/visualizations/OnClickPopover/Types.ts

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,12 @@
1616
*/
1717

1818
import type { PlotData, PlotMouseEvent, Datum, Color } from 'plotly.js';
19+
import type React from 'react';
20+
21+
import type AggregationWidgetConfig from 'views/logic/aggregationbuilder/AggregationWidgetConfig';
1922

2023
export type ValueGroupItem = { field: string; value: Datum; text: string; traceColor: string | number };
2124

22-
export type ValueGroups = {
23-
metricValue?: ValueGroupItem;
24-
rowPivotValues?: Array<ValueGroupItem>;
25-
columnPivotValues?: Array<ValueGroupItem>;
26-
};
2725
export type ExtraPlotData = {
2826
fullData: PlotData;
2927
data: {
@@ -39,3 +37,23 @@ export type ExtraPlotData = {
3937
};
4038
export type ClickPoint = PlotMouseEvent['points'][number] & ExtraPlotData;
4139
export type Rel = { x: number; y: number };
40+
export type FieldData = {
41+
field: string;
42+
value: Datum;
43+
};
44+
45+
export type OnClickPopoverDropdownProps = {
46+
clickPoint: ClickPoint;
47+
config: AggregationWidgetConfig;
48+
setFieldData: React.Dispatch<React.SetStateAction<FieldData>>;
49+
};
50+
51+
export type OnClickPopoverDropdown = React.ComponentType<OnClickPopoverDropdownProps>;
52+
53+
export type ValueGroups = {
54+
metricValue?: ValueGroupItem;
55+
rowPivotValues?: Array<ValueGroupItem>;
56+
columnPivotValues?: Array<ValueGroupItem>;
57+
};
58+
59+
export type ValueGroupsProps = ValueGroups & { setFieldData: React.Dispatch<React.SetStateAction<FieldData>> };
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/*
2+
* Copyright (C) 2020 Graylog, Inc.
3+
*
4+
* This program is free software: you can redistribute it and/or modify
5+
* it under the terms of the Server Side Public License, version 1,
6+
* as published by MongoDB, Inc.
7+
*
8+
* This program is distributed in the hope that it will be useful,
9+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
10+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+
* Server Side Public License for more details.
12+
*
13+
* You should have received a copy of the Server Side Public License
14+
* along with this program. If not, see
15+
* <http://www.mongodb.com/licensing/server-side-public-license>.
16+
*/
17+
import React, { useContext, useMemo } from 'react';
18+
19+
import type { FieldData } from 'views/components/visualizations/OnClickPopover/Types';
20+
import useCurrentQueryId from 'views/logic/queries/useCurrentQueryId';
21+
import { ActionContext } from 'views/logic/ActionContext';
22+
import fieldTypeFor from 'views/logic/fieldtypes/FieldTypeFor';
23+
import FieldTypesContext from 'views/components/contexts/FieldTypesContext';
24+
import ActionDropdown from 'views/components/actions/ActionDropdown';
25+
import TypeSpecificValue from 'views/components/TypeSpecificValue';
26+
import useOverflowingComponents from 'views/hooks/useOverflowingComponents';
27+
import { Menu } from 'components/bootstrap';
28+
import Popover from 'components/common/Popover';
29+
30+
const useQueryFieldTypes = () => {
31+
const fieldTypes = useContext(FieldTypesContext);
32+
33+
return useMemo(() => fieldTypes.currentQuery, [fieldTypes.currentQuery]);
34+
};
35+
36+
const ValueActionsDropdown = ({ value, field, onActionRun }: FieldData & { onActionRun: () => void }) => {
37+
const queryId = useCurrentQueryId();
38+
const actionContext = useContext(ActionContext);
39+
const types = useQueryFieldTypes();
40+
const { overflowingComponents, setOverflowingComponents } = useOverflowingComponents();
41+
42+
const handlerArgs = useMemo(() => {
43+
const type = fieldTypeFor(field, types);
44+
45+
return { queryId, field, type, value, contexts: actionContext };
46+
}, [actionContext, field, queryId, types, value]);
47+
48+
return (
49+
<Popover.Dropdown>
50+
<Menu opened>
51+
<ActionDropdown
52+
handlerArgs={handlerArgs}
53+
type="value"
54+
onMenuToggle={onActionRun}
55+
overflowingComponents={overflowingComponents}
56+
setOverflowingComponents={setOverflowingComponents}>
57+
{field} = <TypeSpecificValue field={field} value={value} type={handlerArgs?.type} truncate />
58+
</ActionDropdown>
59+
</Menu>
60+
</Popover.Dropdown>
61+
);
62+
};
63+
64+
export default ValueActionsDropdown;

graylog2-web-interface/src/views/components/visualizations/hooks/usePlotOnClickPopover.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ import type { Rel, ClickPoint } from 'views/components/visualizations/OnClickPop
2525
import type { OnClickMarkerEvent } from 'views/components/visualizations/GenericPlot';
2626
import OnClickPopoverWrapper from 'views/components/visualizations/OnClickPopover/OnClickPopoverWrapper';
2727
import CartesianOnClickPopoverDropdown from 'views/components/visualizations/OnClickPopover/CartesianOnClickPopoverDropdown';
28-
import HeatmapOnClickPopover from 'views/components/visualizations/heatmap/HeatmapOnClickPopover';
28+
import HeatmapOnClickPopover from 'views/components/visualizations/OnClickPopover/HeatmapOnClickPopover';
2929
import PieOnClickPopoverDropdown from 'views/components/visualizations/OnClickPopover/PieOnClickPopoverDropdown';
3030
import type AggregationWidgetConfig from 'views/logic/aggregationbuilder/AggregationWidgetConfig';
3131
import { CANDIDATE_PICK_RADIUS } from 'views/components/visualizations/Constants';
@@ -346,6 +346,8 @@ const usePlotOnClickPopover = (chartType: ChartType, config: AggregationWidgetCo
346346
gdRef.current = gd;
347347
};
348348

349+
const onPopoverClose = () => setAnchor(null);
350+
349351
const onChartClick = (_: OnClickMarkerEvent, e: PlotMouseEvent) => {
350352
const gd =
351353
gdRef.current ?? ((e.event?.target as HTMLElement)?.closest('.js-plotly-plot') as PlotlyHTMLElement | null);
@@ -356,7 +358,7 @@ const usePlotOnClickPopover = (chartType: ChartType, config: AggregationWidgetCo
356358
};
357359

358360
const onPopoverChange = (isOpen: boolean) => {
359-
if (!isOpen) setAnchor(null);
361+
if (!isOpen) onPopoverClose();
360362
};
361363

362364
const isPopoverOpen = !!anchor;
@@ -374,6 +376,7 @@ const usePlotOnClickPopover = (chartType: ChartType, config: AggregationWidgetCo
374376
clickPoint={anchor?.pt}
375377
config={config}
376378
clickPointsInRadius={anchor?.pointsInRadius}
379+
onPopoverClose={onPopoverClose}
377380
/>
378381
</OnClickPopoverWrapper>
379382
);

0 commit comments

Comments
 (0)