Skip to content

Commit 4995243

Browse files
feat(resultOptions): add UI of aggregate window (#6248)
* chore: add AggregateWindow interface and default value * chore: rename Aggregate to AggregateWindow * chore: apply the renamed import file AggregateWindow * chore: use persistance session storage to control aggregate window * feat: add column selector * feat: add aggregate function selector * feat: add auto window period * feat: add fill missing value toggle * refactor: rewrite setSelection for aggregation window toggle * refactor: rewrite setSelection for selected column * refactor: rewrite setSelection for selected aggregate function * refactor: rewrite setSelection for window period toggle and input form * chore: fix transparent dropdown menu style * chore: add search functionality for column and function dropdown * chore: reset the search terms when toggle aggregate or select a column and a function * feat: add context and provider for columns * style: add paddings * style: adjust paddings * chore: add de-select for column * chore: update default window period and aggregate function list * chore: disable the toggle when no measurement is selected * chore: use const for default window period * refactor: move ColumnSelector to a separate FC component * refactor: move AggregateFunctionSelector to a separate FC component * chore: import style scss explicitly * refactor: move WindowPeriod to a separate FC component * chore: remove console.log * refactor: move FillValuesToggle to a separate FC component * chore: remove the ? after `selection` bc it is always decleared * chore: replace disabled prop with status for ToggleWithLabelTooltip * refactor: use inline logic instead of using a variable that is used once
1 parent 98c8ae8 commit 4995243

File tree

12 files changed

+640
-40
lines changed

12 files changed

+640
-40
lines changed

src/dataExplorer/components/Aggregate.tsx

Lines changed: 0 additions & 24 deletions
This file was deleted.
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import React, {
2+
FC,
3+
useCallback,
4+
useContext,
5+
useEffect,
6+
useMemo,
7+
useState,
8+
} from 'react'
9+
10+
// Components
11+
import SearchableDropdown from 'src/shared/components/SearchableDropdown'
12+
13+
// Contexts
14+
import {
15+
AggregateWindow,
16+
DEFAULT_AGGREGATE_WINDOW,
17+
PersistanceContext,
18+
} from 'src/dataExplorer/context/persistance'
19+
20+
// Constants
21+
import {AGGREGATE_FUNCTIONS} from 'src/timeMachine/constants/queryBuilder'
22+
23+
// Utilities
24+
import {ComponentStatus} from '@influxdata/clockface'
25+
26+
export const AggregateFunctionsSelector: FC = () => {
27+
// Contexts
28+
const {selection, setSelection} = useContext(PersistanceContext)
29+
30+
// State
31+
const [functionSearchTerm, setFunctionSearchTerm] = useState('')
32+
33+
const {isOn, fn: selectedFunction}: AggregateWindow =
34+
selection.resultOptions?.aggregateWindow || DEFAULT_AGGREGATE_WINDOW
35+
36+
useEffect(() => {
37+
setFunctionSearchTerm('')
38+
}, [selection.bucket, selection.measurement])
39+
40+
useEffect(() => {
41+
if (!isOn) {
42+
setFunctionSearchTerm('')
43+
}
44+
}, [isOn])
45+
46+
const handleSelectFunction = useCallback(
47+
(fn: string) => {
48+
setSelection({
49+
resultOptions: {
50+
aggregateWindow: {
51+
...selection.resultOptions?.aggregateWindow,
52+
fn,
53+
},
54+
},
55+
})
56+
setFunctionSearchTerm('')
57+
},
58+
[selection.resultOptions?.aggregateWindow, setSelection]
59+
)
60+
61+
return useMemo(() => {
62+
return (
63+
isOn && (
64+
<div className="result-options--item--row">
65+
<SearchableDropdown
66+
options={AGGREGATE_FUNCTIONS.map(f => f.name)}
67+
selectedOption={selectedFunction || 'Select aggregate function'}
68+
onSelect={handleSelectFunction}
69+
searchPlaceholder="Search aggregate function"
70+
searchTerm={functionSearchTerm}
71+
onChangeSearchTerm={setFunctionSearchTerm}
72+
emptyText="No functions found"
73+
buttonStatus={ComponentStatus.Default}
74+
testID="aggregate-window--function--dropdown"
75+
buttonTestID="aggregate-window--function--dropdown-button"
76+
menuTestID="aggregate-window--function--dropdown-menu"
77+
/>
78+
</div>
79+
)
80+
)
81+
}, [isOn, selectedFunction, functionSearchTerm, handleSelectFunction])
82+
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import React, {FC, useCallback, useContext, useEffect, useMemo} from 'react'
2+
3+
// Components
4+
import {ComponentStatus} from '@influxdata/clockface'
5+
import {ToggleWithLabelTooltip} from 'src/dataExplorer/components/ToggleWithLabelTooltip'
6+
import {ColumnSelector} from 'src/dataExplorer/components/ColumnSelector'
7+
import {AggregateFunctionsSelector} from 'src/dataExplorer/components/AggregateFunctionSelector'
8+
import {WindowPeriod} from 'src/dataExplorer/components/WindowPeriod'
9+
import {FillValuesToggle} from 'src/dataExplorer/components/FillValuesToggle'
10+
11+
// Contexts
12+
import {
13+
AggregateWindow,
14+
DEFAULT_AGGREGATE_WINDOW,
15+
PersistanceContext,
16+
} from 'src/dataExplorer/context/persistance'
17+
18+
// Styles
19+
import './Sidebar.scss'
20+
21+
const AGGREGATE_WINDOW_TOOLTIP = `test`
22+
23+
const AggregateWindow: FC = () => {
24+
// Contexts
25+
const {selection, setSelection} = useContext(PersistanceContext)
26+
27+
const {isOn}: AggregateWindow =
28+
selection.resultOptions?.aggregateWindow || DEFAULT_AGGREGATE_WINDOW
29+
30+
useEffect(() => {
31+
setSelection({
32+
resultOptions: {
33+
aggregateWindow: JSON.parse(JSON.stringify(DEFAULT_AGGREGATE_WINDOW)),
34+
},
35+
})
36+
}, [selection.bucket, selection.measurement])
37+
38+
const handleToggleAggregateWindow = useCallback(() => {
39+
setSelection({
40+
resultOptions: {
41+
aggregateWindow: {
42+
...JSON.parse(JSON.stringify(DEFAULT_AGGREGATE_WINDOW)),
43+
isOn: !selection.resultOptions?.aggregateWindow?.isOn,
44+
},
45+
},
46+
})
47+
}, [selection.resultOptions?.aggregateWindow, setSelection])
48+
49+
return useMemo(() => {
50+
return (
51+
<div>
52+
<ToggleWithLabelTooltip
53+
label="Aggregate"
54+
active={isOn}
55+
onChange={handleToggleAggregateWindow}
56+
tooltipContents={AGGREGATE_WINDOW_TOOLTIP}
57+
status={
58+
selection.measurement
59+
? ComponentStatus.Default
60+
: ComponentStatus.Disabled
61+
}
62+
/>
63+
<ColumnSelector />
64+
<AggregateFunctionsSelector />
65+
<WindowPeriod />
66+
<FillValuesToggle />
67+
</div>
68+
)
69+
}, [isOn, selection.measurement, handleToggleAggregateWindow])
70+
}
71+
72+
export {AggregateWindow}
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
import React, {
2+
FC,
3+
useCallback,
4+
useContext,
5+
useEffect,
6+
useMemo,
7+
useState,
8+
} from 'react'
9+
10+
// Components
11+
import SearchableDropdown from 'src/shared/components/SearchableDropdown'
12+
13+
// Contexts
14+
import {
15+
AggregateWindow,
16+
DEFAULT_AGGREGATE_WINDOW,
17+
PersistanceContext,
18+
} from 'src/dataExplorer/context/persistance'
19+
import {ColumnsContext} from 'src/dataExplorer/context/columns'
20+
21+
// Utilities
22+
import {toComponentStatus} from 'src/shared/utils/toComponentStatus'
23+
24+
// Styles
25+
import './Sidebar.scss'
26+
27+
export const ColumnSelector: FC = () => {
28+
// Contexts
29+
const {selection, setSelection} = useContext(PersistanceContext)
30+
const {columns, loading, getColumns, resetColumns} =
31+
useContext(ColumnsContext)
32+
33+
// States
34+
const [columnSearchTerm, setColumnSearchTerm] = useState('')
35+
const {isOn, column: selectedColumn}: AggregateWindow =
36+
selection.resultOptions?.aggregateWindow || DEFAULT_AGGREGATE_WINDOW
37+
38+
useEffect(() => {
39+
if (!selection.bucket || !selection.measurement) {
40+
resetColumns()
41+
} else {
42+
getColumns(selection.bucket, selection.measurement)
43+
}
44+
45+
setColumnSearchTerm('')
46+
}, [selection.bucket, selection.measurement])
47+
48+
useEffect(() => {
49+
if (!isOn) {
50+
setColumnSearchTerm('')
51+
}
52+
}, [isOn])
53+
54+
const handleSelectColumn = useCallback(
55+
(column: string) => {
56+
let _column = column
57+
if (selection.resultOptions?.aggregateWindow?.column === column) {
58+
_column = ''
59+
}
60+
setSelection({
61+
resultOptions: {
62+
aggregateWindow: {
63+
...selection.resultOptions?.aggregateWindow,
64+
column: _column,
65+
},
66+
},
67+
})
68+
setColumnSearchTerm('')
69+
},
70+
[selection.resultOptions?.aggregateWindow, setSelection]
71+
)
72+
73+
return useMemo(() => {
74+
return (
75+
isOn && (
76+
<div className="result-options--item--row">
77+
<SearchableDropdown
78+
options={columns}
79+
selectedOption={selectedColumn || 'Select column'}
80+
onSelect={handleSelectColumn}
81+
searchPlaceholder="Search columns"
82+
searchTerm={columnSearchTerm}
83+
onChangeSearchTerm={setColumnSearchTerm}
84+
emptyText="No columns found"
85+
buttonStatus={toComponentStatus(loading)}
86+
testID="aggregate-window--column--dropdown"
87+
buttonTestID="aggregate-window--column--dropdown-button"
88+
menuTestID="aggregate-window--column--dropdown-menu"
89+
/>
90+
</div>
91+
)
92+
)
93+
}, [
94+
isOn,
95+
columns,
96+
loading,
97+
selectedColumn,
98+
columnSearchTerm,
99+
handleSelectColumn,
100+
])
101+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import React, {FC, useCallback, useContext, useMemo} from 'react'
2+
3+
// Components
4+
import {ToggleWithLabelTooltip} from 'src/dataExplorer/components/ToggleWithLabelTooltip'
5+
6+
// Contexts
7+
import {
8+
AggregateWindow,
9+
DEFAULT_AGGREGATE_WINDOW,
10+
PersistanceContext,
11+
} from 'src/dataExplorer/context/persistance'
12+
13+
// Styles
14+
import './Sidebar.scss'
15+
16+
export const FillValuesToggle: FC = () => {
17+
const {selection, setSelection} = useContext(PersistanceContext)
18+
const {isOn, createEmpty}: AggregateWindow =
19+
selection.resultOptions?.aggregateWindow || DEFAULT_AGGREGATE_WINDOW
20+
21+
const handleToggleCreateEmpty = useCallback(() => {
22+
const createEmpty = !selection.resultOptions?.aggregateWindow?.createEmpty
23+
setSelection({
24+
resultOptions: {
25+
aggregateWindow: {
26+
...selection.resultOptions?.aggregateWindow,
27+
createEmpty,
28+
},
29+
},
30+
})
31+
}, [selection.resultOptions?.aggregateWindow, setSelection])
32+
33+
return useMemo(() => {
34+
return (
35+
isOn && (
36+
<ToggleWithLabelTooltip
37+
label="Fill missing values"
38+
active={createEmpty}
39+
onChange={handleToggleCreateEmpty}
40+
/>
41+
)
42+
)
43+
}, [isOn, createEmpty, handleToggleCreateEmpty])
44+
}

src/dataExplorer/components/ResultOptions.tsx

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,11 @@ import {useSelector} from 'react-redux'
55
import {Accordion} from '@influxdata/clockface'
66
import {FieldsAsColumns} from 'src/dataExplorer/components/FieldsAsColumns'
77
import {GroupBy} from 'src/dataExplorer/components/GroupBy'
8-
import {Aggregate} from 'src/dataExplorer/components/Aggregate'
8+
import {AggregateWindow} from 'src/dataExplorer/components/AggregateWindow'
99

1010
// Context
1111
import {GroupKeysProvider} from 'src/dataExplorer/context/groupKeys'
12+
import {ColumnsProvider} from 'src/dataExplorer/context/columns'
1213

1314
// Utils
1415
import {getOrg} from 'src/organizations/selectors'
@@ -28,14 +29,16 @@ const ResultOptions: FC = () => {
2829

2930
return (
3031
<GroupKeysProvider scope={scope}>
31-
<Accordion className="result-options" expanded={true}>
32-
<Accordion.AccordionHeader className="result-options--header">
33-
Result Options
34-
</Accordion.AccordionHeader>
35-
<FieldsAsColumns />
36-
<GroupBy />
37-
<Aggregate />
38-
</Accordion>
32+
<ColumnsProvider scope={scope}>
33+
<Accordion className="result-options" expanded={true}>
34+
<Accordion.AccordionHeader className="result-options--header">
35+
Result Options
36+
</Accordion.AccordionHeader>
37+
<FieldsAsColumns />
38+
<GroupBy />
39+
<AggregateWindow />
40+
</Accordion>
41+
</ColumnsProvider>
3942
</GroupKeysProvider>
4043
)
4144
}

0 commit comments

Comments
 (0)