Skip to content

Commit 72eab6b

Browse files
authored
Simplify identification of each aggregation element in aggregation builder. (#10387)
* Display background for each element section instead of each element. * Display add section button as icon next to element headline * Cleaning up previous changes * Improving naming of add element section button title. * Removing no longer needed "Add a sort" button. * Removing no longer needed import. * Renaming `addEmptySection` to `addEmptyElement`. * Renaming `allowAddSection` to `allowAddEmptyElement`. * Renaming `onAddElementSection` to `onAddEmptyElement`. * Implementing proper theme colors for recent aggregation builder layout changes. * Add gap between aggregation builder controls and visualization. * Add border for element configuration form containers. * Fixing style linter warnings. * Fixing remaining style linter warnings.
1 parent ce3579c commit 72eab6b

12 files changed

+125
-115
lines changed

graylog2-web-interface/src/views/components/aggregationwizard/AggregationWizard.tsx

+6-4
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,10 @@ const Wrapper = styled.div`
4141

4242
const Controls = styled.div`
4343
height: 100%;
44-
min-width: 300px;
44+
min-width: 315px;
4545
max-width: 500px;
46-
flex: 1;
46+
flex: 1.2;
47+
padding-right: 15px;
4748
overflow-y: auto;
4849
`;
4950

@@ -60,7 +61,7 @@ const Section = styled.div`
6061
}
6162
`;
6263

63-
const _onElementCreate = (
64+
const onAddEmptyElement = (
6465
elementKey: string,
6566
values: WidgetConfigFormValues,
6667
setValues: (formValues: WidgetConfigFormValues) => void,
@@ -116,13 +117,14 @@ const AggregationWizard = ({ onChange, config, children }: EditWidgetComponentPr
116117
{({ values, setValues }) => (
117118
<>
118119
<Section data-testid="add-element-section">
119-
<AggregationElementSelect onElementCreate={(elementKey) => _onElementCreate(elementKey, values, setValues)}
120+
<AggregationElementSelect onElementCreate={(elementKey) => onAddEmptyElement(elementKey, values, setValues)}
120121
aggregationElements={aggregationElements}
121122
formValues={values} />
122123
</Section>
123124
<Section data-testid="configure-elements-section">
124125
<ElementsConfiguration aggregationElementsByKey={aggregationElementsByKey}
125126
config={config}
127+
onAddEmptyElement={onAddEmptyElement}
126128
onConfigChange={onChange} />
127129
</Section>
128130
</>

graylog2-web-interface/src/views/components/aggregationwizard/ElementsConfiguration.tsx

+9-11
Original file line numberDiff line numberDiff line change
@@ -43,19 +43,16 @@ type Props = {
4343
aggregationElementsByKey: { [elementKey: string]: AggregationElement }
4444
config: AggregationWidgetConfig,
4545
onConfigChange: (config: AggregationWidgetConfig) => void,
46+
onAddEmptyElement: (
47+
elementKey: string,
48+
values: WidgetConfigFormValues,
49+
setValues: (formValues: WidgetConfigFormValues) => void,
50+
) => void,
4651
}
4752

48-
const ElementsConfiguration = ({ aggregationElementsByKey, config, onConfigChange }: Props) => {
53+
const ElementsConfiguration = ({ aggregationElementsByKey, config, onConfigChange, onAddEmptyElement }: Props) => {
4954
const { values, setValues, dirty } = useFormikContext<WidgetConfigFormValues>();
5055

51-
const _onDeleteElement = (aggregationElement) => {
52-
if (typeof aggregationElement.onDeleteAll !== 'function') {
53-
return;
54-
}
55-
56-
setValues(aggregationElement.onDeleteAll(values));
57-
};
58-
5956
return (
6057
<Container>
6158
<div>
@@ -73,9 +70,10 @@ const ElementsConfiguration = ({ aggregationElementsByKey, config, onConfigChang
7370
const AggregationElementComponent = aggregationElement.component;
7471

7572
return (
76-
<ElementConfigurationContainer isPermanentElement={aggregationElement.onDeleteAll === undefined}
73+
<ElementConfigurationContainer allowAddEmptyElement={aggregationElement.allowCreate(values)}
7774
title={aggregationElement.title}
78-
onDeleteAll={() => _onDeleteElement(aggregationElement)}
75+
titleSingular={aggregationElement.titleSingular}
76+
onAddEmptyElement={() => onAddEmptyElement(aggregationElement.key, values, setValues)}
7977
key={aggregationElement.key}>
8078
<AggregationElementComponent config={config} onConfigChange={onConfigChange} />
8179
</ElementConfigurationContainer>

graylog2-web-interface/src/views/components/aggregationwizard/ElementsConfigurationActions.tsx

+3-3
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,12 @@ import type { WidgetConfigFormValues } from './WidgetConfigForm';
2727
const ConfigActions = styled.div<{ scrolledToBottom: boolean }>(({ theme, scrolledToBottom }) => css`
2828
position: sticky;
2929
width: 100%;
30-
bottom: 0px;
30+
bottom: 0;
3131
padding-top: 5px;
3232
background: ${theme.colors.global.contentBackground};
3333
z-index: 1;
3434
35-
:before {
35+
::before {
3636
box-shadow: 1px -2px 3px rgb(0 0 0 / 25%);
3737
content: ' ';
3838
display: ${scrolledToBottom ? 'block' : 'none'};
@@ -47,7 +47,7 @@ const ConfigActions = styled.div<{ scrolledToBottom: boolean }>(({ theme, scroll
4747
const ScrolledToBottomIndicator = styled.div`
4848
width: 100%;
4949
position: absolute;
50-
bottom: 0px;
50+
bottom: 0;
5151
height: 5px;
5252
z-index: 0;
5353
`;

graylog2-web-interface/src/views/components/aggregationwizard/aggregationElements/AggregationElementType.ts

+1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import type { WidgetConfigFormValues, WidgetConfigValidationErrors } from '../Wi
2020

2121
export type AggregationElement = {
2222
title: string,
23+
titleSingular?: string,
2324
key: string,
2425
allowCreate: (formValues: WidgetConfigFormValues) => boolean,
2526
order: number,

graylog2-web-interface/src/views/components/aggregationwizard/aggregationElements/GroupByElement.ts

+1
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,7 @@ export const emptyGrouping: ValuesGrouping = {
177177

178178
const GroupByElement: AggregationElement = {
179179
title: 'Group By',
180+
titleSingular: 'Grouping',
180181
key: 'groupBy',
181182
order: 1,
182183
allowCreate: () => true,

graylog2-web-interface/src/views/components/aggregationwizard/elementConfiguration/ElementConfigurationContainer.test.tsx

+15-15
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,8 @@ import ElementConfigurationContainer from './ElementConfigurationContainer';
2222
describe('ElementConfigurationContainer', () => {
2323
it('should render elements passed as children', () => {
2424
render(
25-
<ElementConfigurationContainer isPermanentElement={false}
26-
onDeleteAll={() => {}}
25+
<ElementConfigurationContainer allowAddEmptyElement
26+
onAddEmptyElement={() => {}}
2727
title="Aggregation Element Title">
2828
Children of Dune
2929
</ElementConfigurationContainer>,
@@ -34,8 +34,8 @@ describe('ElementConfigurationContainer', () => {
3434

3535
it('should render title', () => {
3636
render(
37-
<ElementConfigurationContainer isPermanentElement={false}
38-
onDeleteAll={() => {}}
37+
<ElementConfigurationContainer allowAddEmptyElement
38+
onAddEmptyElement={() => {}}
3939
title="Aggregation Element Title">
4040
Children of Dune
4141
</ElementConfigurationContainer>,
@@ -44,33 +44,33 @@ describe('ElementConfigurationContainer', () => {
4444
expect(screen.getByText('Aggregation Element Title')).toBeInTheDocument();
4545
});
4646

47-
it('should call on delete when clicking delete icon', async () => {
48-
const onDeleteAllMock = jest.fn();
47+
it('should call on onAddEmptyElement when adding a section', async () => {
48+
const onAddEmptyElementMock = jest.fn();
4949

5050
render(
51-
<ElementConfigurationContainer isPermanentElement={false}
52-
onDeleteAll={onDeleteAllMock}
51+
<ElementConfigurationContainer allowAddEmptyElement
52+
onAddEmptyElement={onAddEmptyElementMock}
5353
title="Aggregation Element Title">
5454
Children of Dune
5555
</ElementConfigurationContainer>,
5656
);
5757

58-
const deleteButton = screen.getByTitle('Remove Aggregation Element Title');
58+
const addButton = screen.getByTitle('Add a Aggregation Element Title');
5959

60-
fireEvent.click(deleteButton);
60+
fireEvent.click(addButton);
6161

62-
expect(onDeleteAllMock).toHaveBeenCalledTimes(1);
62+
expect(onAddEmptyElementMock).toHaveBeenCalledTimes(1);
6363
});
6464

65-
it('should not display delete icon if element is permanent', async () => {
65+
it('should not display add section icon if adding element section is not allowed', async () => {
6666
render(
67-
<ElementConfigurationContainer isPermanentElement
68-
onDeleteAll={() => {}}
67+
<ElementConfigurationContainer allowAddEmptyElement={false}
68+
onAddEmptyElement={() => {}}
6969
title="Aggregation Element Title">
7070
Children of Dune
7171
</ElementConfigurationContainer>,
7272
);
7373

74-
expect(screen.queryByTitle('Remove Aggregation Element Title')).not.toBeInTheDocument();
74+
expect(screen.queryByTitle('Add a Aggregation Element Title')).not.toBeInTheDocument();
7575
});
7676
});

graylog2-web-interface/src/views/components/aggregationwizard/elementConfiguration/ElementConfigurationContainer.tsx

+49-16
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,8 @@ import styled, { css } from 'styled-components';
2020
import IconButton from 'components/common/IconButton';
2121

2222
const Wrapper = styled.div(({ theme }) => css`
23-
background-color: ${theme.colors.variant.lightest.default};
24-
border: 1px solid ${theme.colors.variant.lighter.default};
25-
padding: 6px 6px 3px 6px;
26-
border-radius: 6px;
2723
margin-bottom: 6px;
24+
border-radius: 6px;
2825
2926
:last-child {
3027
margin-bottom: 0;
@@ -56,34 +53,66 @@ const Wrapper = styled.div(({ theme }) => css`
5653
}
5754
`);
5855

59-
const Header = styled.div`
56+
const Header = styled.div(({ theme }) => css`
6057
display: flex;
6158
justify-content: space-between;
62-
margin-bottom: 5px;
63-
`;
59+
align-items: center;
60+
margin-bottom: 1px;
61+
min-height: 26px;
62+
font-weight: bold;
63+
position: relative;
64+
65+
::before {
66+
content: ' ';
67+
top: 50%;
68+
width: 100%;
69+
border-bottom: 1px solid ${theme.utils.contrastingColor(theme.colors.global.contentBackground, 'AA')};
70+
position: absolute;
71+
}
72+
`);
73+
74+
const ElementTitle = styled.div(({ theme }) => css`
75+
background-color: ${theme.colors.global.contentBackground};
76+
z-index: 1;
77+
padding-right: 8px;
78+
`);
79+
80+
const ElementActions = styled.div(({ theme }) => css`
81+
background-color: ${theme.colors.global.contentBackground};
82+
z-index: 1;
83+
padding-left: 5px;
84+
`);
85+
86+
const StyledIconButton = styled(IconButton)(({ theme }) => `
87+
color: ${theme.colors.global.textDefault};
88+
`);
6489

6590
type Props = {
91+
allowAddEmptyElement: boolean,
6692
children: React.ReactNode,
67-
isPermanentElement: boolean,
68-
onDeleteAll: () => void
93+
onAddEmptyElement: () => void,
6994
title: string,
95+
titleSingular?: string,
7096
}
7197

7298
const ElementConfigurationContainer = ({
7399
children,
74-
isPermanentElement,
75-
onDeleteAll,
100+
allowAddEmptyElement,
101+
onAddEmptyElement,
76102
title,
103+
titleSingular,
77104
}: Props) => {
78105
return (
79106
<Wrapper>
80107
<Header>
81-
<div>{title}</div>
82-
<div>
83-
{!isPermanentElement && (
84-
<IconButton title={`Remove ${title}`} name="trash" onClick={onDeleteAll} />
108+
<ElementTitle>
109+
{title}
110+
</ElementTitle>
111+
<ElementActions>
112+
{allowAddEmptyElement && (
113+
<StyledIconButton title={`Add a ${titleSingular ?? title}`} name="plus" onClick={onAddEmptyElement} />
85114
)}
86-
</div>
115+
</ElementActions>
87116
</Header>
88117
<div>
89118
{children}
@@ -92,4 +121,8 @@ const ElementConfigurationContainer = ({
92121
);
93122
};
94123

124+
ElementConfigurationContainer.defaultProps = {
125+
titleSingular: undefined,
126+
};
127+
95128
export default ElementConfigurationContainer;

graylog2-web-interface/src/views/components/aggregationwizard/elementConfiguration/ElementConfigurationSection.tsx

+4-2
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,13 @@ import styled, { css } from 'styled-components';
2020
import { IconButton } from 'components/common';
2121

2222
const SectionContainer = styled.div(({ theme }) => css`
23-
border-bottom: 1px solid ${theme.colors.variant.lighter.default};
23+
background-color: ${theme.colors.variant.lightest.default};
2424
margin-bottom: 5px;
25+
padding: 6px 6px 3px 6px;
26+
border-radius: 3px;
27+
border: 1px solid ${theme.colors.variant.lighter.default};
2528
2629
:last-of-type {
27-
border-bottom: 0;
2830
margin-bottom: 0;
2931
}
3032
`);

graylog2-web-interface/src/views/components/aggregationwizard/elementConfiguration/GroupByConfiguration.tsx

+17-31
Original file line numberDiff line numberDiff line change
@@ -20,21 +20,14 @@ import { useFormikContext, FieldArray, Field } from 'formik';
2020
import styled from 'styled-components';
2121

2222
import { HoverForHelp } from 'components/common';
23-
import { Button, ButtonToolbar, Checkbox } from 'components/graylog';
23+
import { Checkbox } from 'components/graylog';
2424

2525
import ElementConfigurationSection from './ElementConfigurationSection';
2626
import GroupBy from './GroupBy';
2727

28-
import GroupByElement, { emptyGrouping } from '../aggregationElements/GroupByElement';
28+
import GroupByElement from '../aggregationElements/GroupByElement';
2929
import { WidgetConfigFormValues } from '../WidgetConfigForm';
3030

31-
const ActionsBar = styled.div`
32-
display: flex;
33-
justify-content: space-between;
34-
align-items: center;
35-
margin-bottom: 3px;
36-
`;
37-
3831
const RollupColumnsLabel = styled.div`
3932
display: flex;
4033
align-items: center;
@@ -53,8 +46,22 @@ const GroupByConfiguration = () => {
5346

5447
return (
5548
<>
49+
<Field name="groupBy.columnRollup">
50+
{({ field: { name, onChange, value } }) => (
51+
<Checkbox onChange={() => onChange({ target: { name, value: !groupBy.columnRollup } })}
52+
checked={value}
53+
disabled={disableColumnRollup}>
54+
<RollupColumnsLabel>
55+
Rollup Columns
56+
<RollupHoverForHelp title="Rollup Columns">
57+
When rollup is enabled, an additional trace totalling individual subtraces will be included.
58+
</RollupHoverForHelp>
59+
</RollupColumnsLabel>
60+
</Checkbox>
61+
)}
62+
</Field>
5663
<FieldArray name="groupBy.groupings"
57-
render={(arrayHelpers) => (
64+
render={() => (
5865
<>
5966
<div>
6067
{groupBy.groupings.map((grouping, index) => {
@@ -66,27 +73,6 @@ const GroupByConfiguration = () => {
6673
);
6774
})}
6875
</div>
69-
<ActionsBar>
70-
<Field name="groupBy.columnRollup">
71-
{({ field: { name, onChange, value } }) => (
72-
<Checkbox onChange={() => onChange({ target: { name, value: !groupBy.columnRollup } })}
73-
checked={value}
74-
disabled={disableColumnRollup}>
75-
<RollupColumnsLabel>
76-
Rollup Columns
77-
<RollupHoverForHelp title="Rollup Columns">
78-
When rollup is enabled, an additional trace totalling individual subtraces will be included.
79-
</RollupHoverForHelp>
80-
</RollupColumnsLabel>
81-
</Checkbox>
82-
)}
83-
</Field>
84-
<ButtonToolbar>
85-
<Button className="pull-right" bsSize="small" type="button" onClick={() => arrayHelpers.push(emptyGrouping)}>
86-
Add Grouping
87-
</Button>
88-
</ButtonToolbar>
89-
</ActionsBar>
9076
</>
9177
)} />
9278
</>

0 commit comments

Comments
 (0)