Skip to content

Commit 9aa5117

Browse files
feat: add DBRP schema list (#6648)
* feat: add dbrp selector * feat: add selected dbrp item * chore: fix eslint error * chore: show dbrp instead of bucket dropdown list for InfluxQL language * chore: show InfluxQL if the org has DBRP mappings * chore: lint * chore: add dbrp tooltip and update error msg * chore: replace DBRP with Database / Retention policy for dropdown list button label * refactor: remove eslint-disable comment for FieldsTags * refactor: add/remove comments related to eslint-disable * chore: add notification message to help users with error handling
1 parent 3158a13 commit 9aa5117

File tree

7 files changed

+286
-38
lines changed

7 files changed

+286
-38
lines changed

src/dataExplorer/components/BucketSelector.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,11 @@ import SelectorTitle from 'src/dataExplorer/components/SelectorTitle'
1515
import {ScriptQueryBuilderContext} from 'src/dataExplorer/context/scriptQueryBuilder'
1616
import {BucketContext} from 'src/shared/contexts/buckets'
1717
import {event} from 'src/cloud/utils/reporting'
18+
import {PersistanceContext} from 'src/dataExplorer/context/persistance'
1819

1920
// Types
2021
import {RemoteDataState, Bucket} from 'src/types'
22+
import {LanguageType} from 'src/dataExplorer/components/resources'
2123

2224
const BUCKET_TOOLTIP = `A bucket is a named location where time series data \
2325
is stored. You can think of a bucket like you would a database in SQL systems.`
@@ -33,6 +35,11 @@ const BucketSelector: FC = () => {
3335
const {loading, buckets} = useContext(BucketContext)
3436
const [isSearchActive, setIsSearchActive] = useState(false)
3537
const [searchTerm, setSearchTerm] = useState('')
38+
const {resource} = useContext(PersistanceContext)
39+
40+
if (resource?.language === LanguageType.INFLUXQL) {
41+
return null
42+
}
3643

3744
const _buckets = buckets.filter(b =>
3845
`${b.name}`.toLocaleLowerCase().includes(searchTerm.toLocaleLowerCase())
@@ -164,4 +171,4 @@ const BucketSelector: FC = () => {
164171
)
165172
}
166173

167-
export default BucketSelector
174+
export {BucketSelector}
Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
// Libraries
2+
import React, {ChangeEvent, FC, useContext, useState} from 'react'
3+
import {useDispatch} from 'react-redux'
4+
5+
// Contexts
6+
import {BucketContext} from 'src/shared/contexts/buckets'
7+
import {DBRPContext} from 'src/shared/contexts/dbrps'
8+
import {ScriptQueryBuilderContext} from 'src/dataExplorer/context/scriptQueryBuilder'
9+
import {PersistanceContext} from 'src/dataExplorer/context/persistance'
10+
11+
// Types
12+
import {RemoteDataState} from 'src/types'
13+
import {DBRP} from 'src/client'
14+
import {LanguageType} from 'src/dataExplorer/components/resources'
15+
import {Notification} from 'src/types/notifications'
16+
17+
// Components
18+
import SelectorTitle from 'src/dataExplorer/components/SelectorTitle'
19+
import {
20+
ComponentSize,
21+
Dropdown,
22+
DropdownMenuTheme,
23+
IconFont,
24+
Input,
25+
TechnoSpinner,
26+
} from '@influxdata/clockface'
27+
28+
// Utils
29+
import {notify} from 'src/shared/actions/notifications'
30+
import {defaultErrorNotification} from 'src/shared/copy/notifications'
31+
32+
const DROPDOWN_LABEL: string = 'Database/Retention policy'
33+
const DBRP_TOOLTIP: string = `InfluxQL requires a database and retention policy \
34+
(DBRP) combination in order to query data. In InfluxDB Cloud, databases \
35+
and retention policies have been combined and replaced by InfluxDB buckets. \
36+
To query InfluxDB Cloud with InfluxQL, the specified DBRP combination \
37+
must be mapped to a bucket.`
38+
39+
export const DBRPSelector: FC = () => {
40+
const {loading, dbrps} = useContext(DBRPContext)
41+
const {buckets} = useContext(BucketContext)
42+
const {selectedDBRP, selectDBRP} = useContext(ScriptQueryBuilderContext)
43+
const [isSearchActive, setIsSearchActive] = useState(false)
44+
const [searchTerm, setSearchTerm] = useState('')
45+
const {resource} = useContext(PersistanceContext)
46+
const dispatch = useDispatch()
47+
48+
if (resource?.language !== LanguageType.INFLUXQL) {
49+
return null
50+
}
51+
52+
const _dbrps: DBRP[] = dbrps.filter(d =>
53+
`${d.database}`.toLocaleLowerCase().includes(searchTerm.toLocaleLowerCase())
54+
)
55+
56+
const handleSelectDBRP = (dbrp: DBRP) => {
57+
// grab bucket object
58+
const bucket = buckets.find(b => b.id === dbrp.bucketID)
59+
if (!bucket) {
60+
// this should never be happening
61+
const notification: Notification = {
62+
...defaultErrorNotification,
63+
message: `No matching bucket found for ${dbrp.database}/${dbrp.retention_policy}, \
64+
suggest to create a database and retention policy mapping \
65+
https://docs.influxdata.com/influxdb/cloud/query-data/influxql/dbrp/`,
66+
}
67+
dispatch(notify(notification))
68+
} else {
69+
selectDBRP(dbrp, bucket)
70+
}
71+
}
72+
73+
const handleSearchChange = (e: ChangeEvent<HTMLInputElement>): void => {
74+
setSearchTerm(e.target.value)
75+
}
76+
77+
let buttonText = 'Loading database/retention policy...'
78+
if (loading === RemoteDataState.Done && !selectedDBRP?.database) {
79+
buttonText = 'Select database/retention policy...'
80+
} else if (loading === RemoteDataState.Done && selectedDBRP?.database) {
81+
buttonText = `${selectedDBRP.database}/${selectedDBRP.retention_policy}`
82+
}
83+
84+
const button = (active: boolean, onClick: any) => (
85+
<Dropdown.Button
86+
onClick={onClick}
87+
active={active}
88+
testID="dbrp-selector--dropdown-button"
89+
>
90+
{buttonText}
91+
</Dropdown.Button>
92+
)
93+
94+
if (loading !== RemoteDataState.Done) {
95+
return (
96+
<div>
97+
<SelectorTitle label={DROPDOWN_LABEL} tooltipContents={DBRP_TOOLTIP} />
98+
<Dropdown
99+
button={button}
100+
menu={onCollapse => (
101+
<Dropdown.Menu onCollapse={onCollapse}>
102+
<Dropdown.ItemEmpty>
103+
<TechnoSpinner
104+
strokeWidth={ComponentSize.Small}
105+
diameterPixels={32}
106+
/>
107+
</Dropdown.ItemEmpty>
108+
</Dropdown.Menu>
109+
)}
110+
/>
111+
</div>
112+
)
113+
}
114+
115+
const body: JSX.Element | JSX.Element[] =
116+
_dbrps.length === 0 ? (
117+
<Dropdown.ItemEmpty>No DBRPs Found</Dropdown.ItemEmpty>
118+
) : (
119+
_dbrps.map((d: DBRP) => {
120+
const dbrpMapping = `${d.database}/${d.retention_policy}`
121+
return (
122+
<Dropdown.Item
123+
key={dbrpMapping}
124+
value={d}
125+
onClick={handleSelectDBRP}
126+
// bucketID is unique for each DBRP pair
127+
selected={selectedDBRP?.bucketID === d.bucketID}
128+
title={dbrpMapping}
129+
wrapText={true}
130+
testID={`dbrp-selector--dropdown--${dbrpMapping}`}
131+
>
132+
{dbrpMapping}
133+
</Dropdown.Item>
134+
)
135+
})
136+
)
137+
138+
return (
139+
<div>
140+
<SelectorTitle label={DROPDOWN_LABEL} tooltipContents={DBRP_TOOLTIP} />
141+
<Dropdown
142+
button={button}
143+
menu={onCollapse => (
144+
<Dropdown.Menu
145+
onCollapse={() => {
146+
if (isSearchActive === false) {
147+
onCollapse()
148+
}
149+
}}
150+
theme={DropdownMenuTheme.Onyx}
151+
scrollToSelected={true}
152+
>
153+
<div className="searchable-dropdown--input-container">
154+
<Input
155+
icon={IconFont.Search_New}
156+
onFocus={() => setIsSearchActive(true)}
157+
onChange={handleSearchChange}
158+
onBlur={() => setIsSearchActive(false)}
159+
value={searchTerm}
160+
placeholder="Search DBRPs"
161+
size={ComponentSize.Small}
162+
autoFocus={true}
163+
testID="dbrp-selector--search-bar"
164+
/>
165+
</div>
166+
{body}
167+
</Dropdown.Menu>
168+
)}
169+
/>
170+
</div>
171+
)
172+
}

src/dataExplorer/components/Schema.tsx

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
1-
import React, {FC, useContext, useEffect, useMemo} from 'react'
1+
import React, {FC, useCallback, useContext, useEffect, useMemo} from 'react'
22
import {useSelector} from 'react-redux'
33

44
// Components
55
import {DapperScrollbars} from '@influxdata/clockface'
66
import {SearchWidget} from 'src/shared/components/search_widget/SearchWidget'
7-
import BucketSelector from 'src/dataExplorer/components/BucketSelector'
7+
import {BucketSelector} from 'src/dataExplorer/components/BucketSelector'
88
import MeasurementSelector from 'src/dataExplorer/components/MeasurementSelector'
99
import FieldSelector from 'src/dataExplorer/components/FieldSelector'
1010
import TagSelector from 'src/dataExplorer/components/TagSelector'
1111
import SchemaBrowserHeading from 'src/dataExplorer/components/SchemaBrowserHeading'
12+
import {DBRPSelector} from 'src/dataExplorer/components/DBRPSelector'
1213

1314
// Context
1415
import {
@@ -35,11 +36,15 @@ const FieldsTags: FC = () => {
3536

3637
useEffect(() => {
3738
setSearchTerm('')
38-
}, [selectedBucket, selectedMeasurement])
39+
// setSearchTerm will cause infinite re-rendering if added to the dependency list
40+
}, [selectedBucket, selectedMeasurement]) // eslint-disable-line react-hooks/exhaustive-deps
3941

40-
const handleSearchFieldsTags = (searchTerm: string): void => {
41-
setSearchTerm(searchTerm)
42-
}
42+
const handleSearchFieldsTags = useCallback(
43+
(searchTerm: string): void => {
44+
setSearchTerm(searchTerm)
45+
},
46+
[setSearchTerm]
47+
)
4348

4449
return useMemo(() => {
4550
if (!selectedBucket || !selectedMeasurement) {
@@ -58,7 +63,7 @@ const FieldsTags: FC = () => {
5863
<TagSelector />
5964
</div>
6065
)
61-
}, [selectedBucket, selectedMeasurement, searchTerm])
66+
}, [selectedBucket, selectedMeasurement, searchTerm, handleSearchFieldsTags])
6267
}
6368

6469
const Schema: FC = () => {
@@ -74,11 +79,13 @@ const Schema: FC = () => {
7479
<TagsProvider scope={scope}>
7580
<ScriptQueryBuilderProvider>
7681
<BucketProvider scope={scope} omitSampleData>
82+
{/* DBRPProvider is in upstream */}
7783
<div className="scroll--container">
7884
<DapperScrollbars>
7985
<div className="schema-browser" data-testid="schema-browser">
8086
<SchemaBrowserHeading />
8187
<BucketSelector />
88+
<DBRPSelector />
8289
<div className="container-side-bar">
8390
<MeasurementSelector />
8491
<FieldsTags />
@@ -94,4 +101,4 @@ const Schema: FC = () => {
94101
)
95102
}
96103

97-
export default Schema
104+
export {Schema}

src/dataExplorer/components/ScriptQueryBuilder.tsx

Lines changed: 39 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,12 @@ import {
1717
Overlay,
1818
ComponentStatus,
1919
} from '@influxdata/clockface'
20+
import {ResultsPane} from 'src/dataExplorer/components/ResultsPane'
21+
import Sidebar from 'src/dataExplorer/components/Sidebar'
22+
import {Schema} from 'src/dataExplorer/components/Schema'
23+
import SaveAsScript from 'src/dataExplorer/components/SaveAsScript'
24+
25+
// Contexts
2026
import {QueryProvider} from 'src/shared/contexts/query'
2127
import {EditorProvider} from 'src/shared/contexts/editor'
2228
import {ResultsProvider, ResultsContext} from 'src/dataExplorer/context/results'
@@ -30,19 +36,21 @@ import {
3036
PersistanceProvider,
3137
PersistanceContext,
3238
} from 'src/dataExplorer/context/persistance'
33-
import {LanguageType} from 'src/dataExplorer/components/resources'
34-
import {ResultsPane} from 'src/dataExplorer/components/ResultsPane'
35-
import Sidebar from 'src/dataExplorer/components/Sidebar'
36-
import Schema from 'src/dataExplorer/components/Schema'
37-
import SaveAsScript from 'src/dataExplorer/components/SaveAsScript'
3839
import {QueryContext} from 'src/shared/contexts/query'
40+
import {DBRPContext, DBRPProvider} from 'src/shared/contexts/dbrps'
41+
42+
// Utilies
3943
import {getOrg, isOrgIOx} from 'src/organizations/selectors'
40-
import {RemoteDataState} from 'src/types'
4144
import {SCRIPT_EDITOR_PARAMS} from 'src/dataExplorer/components/resources'
4245
import {selectIsNewIOxOrg} from 'src/shared/selectors/app'
4346
import {isFlagEnabled} from 'src/shared/utils/featureFlag'
4447
import {CLOUD} from 'src/shared/constants'
4548

49+
// Types
50+
import {RemoteDataState} from 'src/types'
51+
import {QueryScope} from 'src/shared/contexts/query'
52+
import {LanguageType} from 'src/dataExplorer/components/resources'
53+
4654
// Styles
4755
import './ScriptQueryBuilder.scss'
4856

@@ -66,6 +74,7 @@ const ScriptQueryBuilder: FC = () => {
6674
const {cancel} = useContext(QueryContext)
6775
const {setStatus, setResult} = useContext(ResultsContext)
6876
const {clear: clearViewOptions} = useContext(ResultsViewContext)
77+
const {hasDBRPs} = useContext(DBRPContext)
6978
const org = useSelector(getOrg)
7079
const isNewIOxOrg =
7180
useSelector(selectIsNewIOxOrg) &&
@@ -160,7 +169,7 @@ const ScriptQueryBuilder: FC = () => {
160169
{option}
161170
</Dropdown.Item>
162171
))}
163-
{isFlagEnabled('influxqlUI') ? (
172+
{isFlagEnabled('influxqlUI') && hasDBRPs() ? (
164173
<Dropdown.Item
165174
className={`script-dropdown__${LanguageType.INFLUXQL}`}
166175
key={LanguageType.INFLUXQL}
@@ -260,16 +269,26 @@ const ScriptQueryBuilder: FC = () => {
260269
)
261270
}
262271

263-
export default () => (
264-
<QueryProvider>
265-
<ResultsProvider>
266-
<ResultsViewProvider>
267-
<PersistanceProvider>
268-
<ChildResultsProvider>
269-
<ScriptQueryBuilder />
270-
</ChildResultsProvider>
271-
</PersistanceProvider>
272-
</ResultsViewProvider>
273-
</ResultsProvider>
274-
</QueryProvider>
275-
)
272+
export default () => {
273+
const org = useSelector(getOrg)
274+
const scope = {
275+
org: org.id,
276+
region: window.location.origin,
277+
} as QueryScope
278+
279+
return (
280+
<QueryProvider>
281+
<ResultsProvider>
282+
<ResultsViewProvider>
283+
<DBRPProvider scope={scope}>
284+
<PersistanceProvider>
285+
<ChildResultsProvider>
286+
<ScriptQueryBuilder />
287+
</ChildResultsProvider>
288+
</PersistanceProvider>
289+
</DBRPProvider>
290+
</ResultsViewProvider>
291+
</ResultsProvider>
292+
</QueryProvider>
293+
)
294+
}

src/dataExplorer/context/persistance.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {useSelector} from 'react-redux'
55
// Types
66
import {TimeRange, RecursivePartial} from 'src/types'
77
import {Bucket, TagKeyValuePair} from 'src/types'
8+
import {DBRP} from 'src/client'
89

910
// Utils
1011
import {DEFAULT_TIME_RANGE} from 'src/shared/constants/timeRanges'
@@ -64,6 +65,7 @@ interface ResultOptions {
6465

6566
export interface CompositionSelection {
6667
bucket: Bucket
68+
dbrp: DBRP
6769
measurement: string
6870
fields: string[]
6971
tagValues: TagKeyValuePair[]
@@ -94,6 +96,7 @@ interface ContextType {
9496

9597
export const DEFAULT_SELECTION: CompositionSelection = {
9698
bucket: null,
99+
dbrp: null,
97100
measurement: null,
98101
fields: [] as string[],
99102
tagValues: [] as TagKeyValuePair[],

0 commit comments

Comments
 (0)