Skip to content

Commit 00ce46c

Browse files
feat: add basic schema browser in new data explorer (#4650)
* feat: add Schema in basic structure * feat: add BucketSelector component * feat: add Dropdown button and menu in BucketSelector * feat: add search icon in SearchableDropdown * feat: replace Dropdown with SearchableDropdown * feat: add icon next to select title * chore: update Bucket onSelect * feat: add measurement selector * feat: add search bar for fields and tags * feat: add FieldsSelector and TagKeysSelector * chore: separate SelectorTitle component into its own file and move each instance into each selector component * feat: add accordion in FieldsSelector and TagKeys Selector * style: add scrollbar * chore: update import path to be absolute * feat: add empty view * feat: add empty view for TagKeysSelector * chore: run prettier * feat: add sample measurement and enable search bar * feat: enable search bar in BucketSelector * feat: add question tooltip and test info * style: expand accordion by default * feat: wire measurement selector context * chore: run prettier * chore: depend fields and tags on measurements * feat: add loading status for tags and fields * chore: rename and move newDataExplorer context into context folder * feat: make measurement selector and filedtag selector depend on bucket * chore: rename to FieldSelector and TagSelector * feat: add accordion to tag keys * chore: rename style class name * style: wrap long key value text * chore: run prettier * chore: add more sample data * chore: remove not valid title * style: add side bar to search bar, fields, and tags * feat: get real buckets from bucket context * style: add more space for list items * chore: run prettier * chore: fix lint error * chore: restructure data structure for NewDataExplorerContext * chore: move constant into a separate file * chore: change selectedBucket from string to Bucket object * feat: get real measurements using query * chore: move around import file * chore: add useCallback and remove not used constant * chore: update comment * feat: get real fields from query * fix: update the query to get fields and use constant to represent queries * feat: get real tag keys from query * chore: reorder sort() and limit() func and make limit and constant * feat: get real tag values from query * feat: add loading status for tags * feat: add loading status for fields * feat: add load more button * style: add hover effect for field and tag values, rename classnames for tag keys and values * chore: reset load more config when field values get updated * feat: get tag values on demand * chore: remove useCallback on getBucket and getMeasurement * chore: remove console.log query * chore: only fetch tag values if no values originally * fix: change data structure to store status for each tag keys * fix: run prettier * refactor: use find to get the selected bucket * refactor: replace lodash with vanilla js * refactor: use `slice` only for load more logic * refactor: remove extra logic for load more button in useEffect * refactor: simplify the load more logic in FieldSelector * refactor: simplify the load more logic in TagSelector * refactor: replace lodash `isEmpty` with vanilla js * refactor: add more detail in comment on `handleSelectTagValue` * refactor: rename context function name to `fetchTagValues` * refactor: replace `Hash` with `Record` for `type Tags` * refactor: remove unused `loading` context * refactor: fix lint * style: update margin-bottom for tag keys * feat: add loading status for measurements * refactor: move the logic for handling selection from parent to child `TagValues` * feat: add icon in the search bar (confirmed by Julia and the design team) * chore: move measurent context into an independent file * chore: move fields context into an independent file * refactor: rename FieldContext to FieldsContext and related names * chore: move tagss context into an independent file * chore: reset load status when reset fields * chore: import RemoteDataState from src/types to keep the code consistent with the rest of the code * chore: remove `...` in search bar placeholder * chore: update comment for time range
1 parent a22d8d6 commit 00ce46c

File tree

17 files changed

+1208
-49
lines changed

17 files changed

+1208
-49
lines changed
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import React, {FC, useContext, useState} from 'react'
2+
3+
// Components
4+
import {ComponentStatus} from '@influxdata/clockface'
5+
import SelectorTitle from 'src/dataExplorer/components/SelectorTitle'
6+
import SearchableDropdown from 'src/shared/components/SearchableDropdown'
7+
8+
// Contexts
9+
import {NewDataExplorerContext} from 'src/dataExplorer/context/newDataExplorer'
10+
import {BucketContext} from 'src/shared/contexts/buckets'
11+
12+
const BucketSelector: FC = () => {
13+
const {selectedBucket, selectBucket} = useContext(NewDataExplorerContext)
14+
const {buckets} = useContext(BucketContext)
15+
const [searchTerm, setSearchTerm] = useState('')
16+
17+
const handleSelectBucket = (name: string) => {
18+
const bucket = buckets.find(b => b.name === name)
19+
if (!bucket) {
20+
return
21+
}
22+
selectBucket(bucket)
23+
}
24+
25+
const handleChangeSearchTerm = (value: string) => {
26+
setSearchTerm(value)
27+
}
28+
29+
return (
30+
<div>
31+
<SelectorTitle title="Bucket" info="Test info" />
32+
<SearchableDropdown
33+
searchTerm={searchTerm}
34+
searchPlaceholder="Search buckets"
35+
selectedOption={selectedBucket?.name || 'Select bucket...'}
36+
onSelect={handleSelectBucket}
37+
onChangeSearchTerm={handleChangeSearchTerm}
38+
options={buckets.map(b => b.name)}
39+
buttonStatus={ComponentStatus.Default}
40+
testID="bucket-selector--dropdown"
41+
buttonTestID="bucket-selector--dropdown-button"
42+
menuTestID="bucket-selector--dropdown-menu"
43+
emptyText="No Buckets Found"
44+
/>
45+
</div>
46+
)
47+
}
48+
49+
export default BucketSelector
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import React, {FC, useContext, useEffect, useMemo, useState} from 'react'
2+
3+
// Components
4+
import {Accordion} from '@influxdata/clockface'
5+
import SelectorTitle from 'src/dataExplorer/components/SelectorTitle'
6+
7+
// Contexts
8+
import {LOCAL_LIMIT} from 'src/dataExplorer/context/newDataExplorer'
9+
import {FieldsContext} from 'src/dataExplorer/context/fields'
10+
import WaitingText from 'src/shared/components/WaitingText'
11+
12+
// Types
13+
import {RemoteDataState} from 'src/types'
14+
15+
// Syles
16+
import './Schema.scss'
17+
18+
const FieldSelector: FC = () => {
19+
const {fields, loading} = useContext(FieldsContext)
20+
const [fieldsToShow, setFieldsToShow] = useState([])
21+
22+
useEffect(() => {
23+
// Reset
24+
setFieldsToShow(fields.slice(0, LOCAL_LIMIT))
25+
}, [fields])
26+
27+
let list: JSX.Element | JSX.Element[] = (
28+
<div className="field-selector--list-item">No Fields Found</div>
29+
)
30+
31+
if (loading === RemoteDataState.Error) {
32+
list = (
33+
<div className="field-selector--list-item">Failed to load fields</div>
34+
)
35+
} else if (
36+
loading === RemoteDataState.Loading ||
37+
loading === RemoteDataState.NotStarted
38+
) {
39+
list = <WaitingText text="Loading fields" />
40+
} else if (loading === RemoteDataState.Done && fieldsToShow.length) {
41+
list = fieldsToShow.map(field => (
42+
<div key={field} className="field-selector--list-item">
43+
{field}
44+
</div>
45+
))
46+
}
47+
48+
const handleLoadMore = () => {
49+
const newIndex = fieldsToShow.length + LOCAL_LIMIT
50+
setFieldsToShow(fields.slice(0, newIndex))
51+
}
52+
53+
return useMemo(() => {
54+
const shouldLoadMore = fieldsToShow.length < fields.length
55+
const loadMoreButton = shouldLoadMore && (
56+
<div className="load-more-button" onClick={handleLoadMore}>
57+
+ Load more
58+
</div>
59+
)
60+
61+
return (
62+
<Accordion className="field-selector" expanded={true}>
63+
<Accordion.AccordionHeader className="field-selector--header">
64+
<SelectorTitle title="Fields" info="Test info" />
65+
</Accordion.AccordionHeader>
66+
<div className="container-side-bar">
67+
{list}
68+
{loadMoreButton}
69+
</div>
70+
</Accordion>
71+
)
72+
}, [fields, list])
73+
}
74+
75+
export default FieldSelector
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import React, {FC, useContext, useMemo, useState} from 'react'
2+
3+
// Components
4+
import {ComponentStatus} from '@influxdata/clockface'
5+
import SelectorTitle from 'src/dataExplorer/components/SelectorTitle'
6+
import SearchableDropdown from 'src/shared/components/SearchableDropdown'
7+
8+
// Context
9+
import {NewDataExplorerContext} from 'src/dataExplorer/context/newDataExplorer'
10+
import {MeasurementContext} from 'src/dataExplorer/context/measurements'
11+
12+
// Types
13+
import {RemoteDataState} from 'src/types'
14+
15+
const convertStatus = (remoteDataState: RemoteDataState): ComponentStatus => {
16+
switch (remoteDataState) {
17+
case RemoteDataState.Error:
18+
return ComponentStatus.Error
19+
case RemoteDataState.Loading:
20+
return ComponentStatus.Loading
21+
default:
22+
return ComponentStatus.Default
23+
}
24+
}
25+
26+
const MeasurementSelector: FC = () => {
27+
const {selectedBucket, selectedMeasurement, selectMeasurement} = useContext(
28+
NewDataExplorerContext
29+
)
30+
const {measurements, loading} = useContext(MeasurementContext)
31+
const [searchTerm, setSearchTerm] = useState('')
32+
33+
const handleSelect = (option: string): void => {
34+
selectMeasurement(option)
35+
}
36+
37+
const handleChangeSearchTerm = (value: string) => {
38+
setSearchTerm(value)
39+
}
40+
41+
return useMemo(() => {
42+
if (!selectedBucket) {
43+
return null
44+
}
45+
46+
return (
47+
<div>
48+
<SelectorTitle title="Measurement" info="Test info" />
49+
<SearchableDropdown
50+
searchTerm={searchTerm}
51+
searchPlaceholder="Search measurements"
52+
selectedOption={selectedMeasurement || 'Select measurement...'}
53+
onSelect={handleSelect}
54+
onChangeSearchTerm={handleChangeSearchTerm}
55+
options={measurements}
56+
buttonStatus={convertStatus(loading)}
57+
testID="measurement-selector--dropdown"
58+
buttonTestID="measurement-selector--dropdown-button"
59+
menuTestID="measurement-selector--dropdown--menu"
60+
emptyText="No Measurements Found"
61+
/>
62+
</div>
63+
)
64+
}, [selectedBucket, selectedMeasurement, measurements, loading])
65+
}
66+
67+
export default MeasurementSelector

src/dataExplorer/components/NewDataExplorer.scss

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,4 @@
1212
min-height: auto;
1313
width: calc(25% - 60px);
1414
}
15-
16-
h1 {
17-
text-align: center;
18-
}
1915
}

src/dataExplorer/components/NewDataExplorer.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
11
import React, {FC, useState} from 'react'
2+
3+
// Components
24
import {DraggableResizer, Orientation} from '@influxdata/clockface'
35
import {QueryProvider} from 'src/shared/contexts/query'
46
import {ResultsProvider} from 'src/dataExplorer/components/ResultsContext'
57
import ResultsPane from 'src/dataExplorer/components/ResultsPane'
8+
import Schema from 'src/dataExplorer/components/Schema'
69

10+
// Styles
711
import './NewDataExplorer.scss'
812

913
const INITIAL_VERT_RESIZER_HANDLE = 0.2
@@ -21,7 +25,7 @@ const NewDataExplorer: FC = () => {
2125
onChangePositions={setVertDragPosition}
2226
>
2327
<DraggableResizer.Panel>
24-
<h1>[ schema ]</h1>
28+
<Schema />
2529
</DraggableResizer.Panel>
2630
<DraggableResizer.Panel className="new-data-explorer-rightside">
2731
<ResultsProvider>
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
@import '@influxdata/clockface/dist/variables.scss';
2+
3+
.scroll--container {
4+
position: absolute;
5+
top: 0;
6+
right: 0;
7+
left: 0;
8+
bottom: 0;
9+
}
10+
11+
.data-schema {
12+
padding-right: $cf-space-xs;
13+
}
14+
15+
.container-side-bar {
16+
border-left: 1px solid $cf-grey-25;
17+
padding-left: $cf-space-2xs;
18+
margin-top: $cf-space-xs;
19+
}
20+
21+
.selector-title {
22+
padding-bottom: $cf-space-2xs;
23+
}
24+
25+
.selector-title--icon {
26+
padding-left: $cf-space-2xs;
27+
}
28+
29+
.selector-title--icon .cf-question-mark-tooltip {
30+
background-color: $cf-grey-35;
31+
}
32+
33+
.field-selector,
34+
.tag-selector-key {
35+
padding-top: $cf-space-2xs;
36+
}
37+
38+
.field-selector .cf-accordion--icon,
39+
.tag-selector-key .cf-accordion--icon,
40+
.tag-selector-value .cf-accordion--icon {
41+
color: gray;
42+
}
43+
44+
.field-selector--header,
45+
.tag-selector-key--header,
46+
.tag-selector-value--header {
47+
background-color: transparent;
48+
min-height: 0;
49+
padding: 0;
50+
}
51+
52+
.field-selector--list-item,
53+
.tag-selector-value--list-item {
54+
padding: $cf-space-2xs 0 $cf-space-2xs $cf-space-2xs;
55+
white-space: nowrap;
56+
overflow: hidden;
57+
text-overflow: ellipsis;
58+
}
59+
60+
.tag-selector-key--list-item {
61+
padding: $cf-space-3xs 0;
62+
white-space: nowrap;
63+
overflow: hidden;
64+
text-overflow: ellipsis;
65+
}
66+
67+
.field-selector--list-item:hover,
68+
.tag-selector-value--list-item:hover {
69+
cursor: pointer;
70+
color: $c-krypton;
71+
background-color: $cf-grey-25;
72+
}
73+
74+
.field-selector .container-side-bar,
75+
.tag-selector-key .container-side-bar {
76+
margin-top: 0;
77+
}
78+
79+
.load-more-button {
80+
color: $c-pool;
81+
cursor: pointer;
82+
padding: $cf-space-2xs 0 $cf-space-xs 2px;
83+
white-space: nowrap;
84+
overflow: hidden;
85+
text-overflow: ellipsis;
86+
}

0 commit comments

Comments
 (0)