feat(flows): create data source cell behind feature flag (#18678)

* feat: create data source cell behind feature flag

* fix: prettier

* fix: remove rogue comment

* feat: adding in bucket context

* fix: prettier

* refactor: display buckets list in loading state until context returns buckets

* refactor: use loading to determine loading state

* refactor: give variable more descriptive name

Co-authored-by: drdelambre <drdelambre>
pull/18745/head
alexpaxton 2020-06-25 14:47:34 -07:00 committed by GitHub
parent 5e4dd3f77a
commit db991b01ac
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 310 additions and 4 deletions

View File

@ -1,5 +1,7 @@
// Libraries
import React, {FC} from 'react'
// Components
import {Page} from '@influxdata/clockface'
import {NotebookProvider} from 'src/notebooks/context/notebook'
import {ScrollProvider} from 'src/notebooks/context/scroll'

View File

@ -11,6 +11,7 @@ import TimeZoneDropdown from 'src/notebooks/components/header/TimeZoneDropdown'
import TimeRangeDropdown from 'src/notebooks/components/header/TimeRangeDropdown'
import AutoRefreshDropdown from 'src/notebooks/components/header/AutoRefreshDropdown'
import Submit from 'src/notebooks/components/header/Submit'
import {FeatureFlag} from 'src/shared/utils/featureFlag'
export interface TimeContextProps {
context: TimeBlock
@ -38,8 +39,10 @@ const Buttons: FC = () => {
return (
<div className="notebook-header--buttons">
<TimeZoneDropdown />
<TimeRangeDropdown context={timeContext[id]} update={update} />
<AutoRefreshDropdown context={timeContext[id]} update={update} />
<FeatureFlag name="notebook-panel--data-source" equals={false}>
<TimeRangeDropdown context={timeContext[id]} update={update} />
<AutoRefreshDropdown context={timeContext[id]} update={update} />
</FeatureFlag>
<Submit />
</div>
)

View File

@ -56,6 +56,16 @@ export const Submit: FC = () => {
instances: [index],
requirements,
})
} else if (pipe.type === 'data') {
const {bucketName, timeStart, timeStop} = pipe
const text = `from(bucket: "${bucketName}")|>range(start: ${timeStart}, stop: ${timeStop})`
stages.push({
text,
instances: [index],
requirements: {},
})
} else if (stages.length) {
stages[stages.length - 1].instances.push(index)
}

View File

@ -56,6 +56,23 @@ export const NotebookContext = React.createContext<NotebookContextType>(
let GENERATOR_INDEX = 0
const getHumanReadableName = (type: string): string => {
++GENERATOR_INDEX
switch (type) {
case 'data':
return `Data Source ${GENERATOR_INDEX}`
case 'visualization':
return `Visualization ${GENERATOR_INDEX}`
case 'markdown':
return `Markdown ${GENERATOR_INDEX}`
case 'query':
return `Flux Script ${GENERATOR_INDEX}`
default:
return `Cell ${GENERATOR_INDEX}`
}
}
export const NotebookProvider: FC = ({children}) => {
const [id] = useState(DEFAULT_CONTEXT.id)
const [pipes, setPipes] = useState(DEFAULT_CONTEXT.pipes)
@ -79,7 +96,7 @@ export const NotebookProvider: FC = ({children}) => {
_setResults(add({...results[results.length - 1]}))
_setMeta(
add({
title: `Cell_${++GENERATOR_INDEX}`,
title: getHumanReadableName(pipe.type),
visible: true,
loading: meta[meta.length - 1].loading,
focus: false,
@ -89,7 +106,7 @@ export const NotebookProvider: FC = ({children}) => {
_setResults(add({}))
_setMeta(
add({
title: `Cell_${++GENERATOR_INDEX}`,
title: getHumanReadableName(pipe.type),
visible: true,
loading: RemoteDataState.NotStarted,
focus: false,

View File

@ -0,0 +1,81 @@
// Libraries
import React, {FC, useEffect, useContext} from 'react'
// Components
import {
DapperScrollbars,
TechnoSpinner,
ComponentSize,
RemoteDataState,
} from '@influxdata/clockface'
import SelectorListItem from 'src/notebooks/pipes/Data/SelectorListItem'
import {BucketContext} from 'src/notebooks/context/buckets'
// Types
import {PipeData} from 'src/notebooks'
import {Bucket} from 'src/types'
interface Props {
onUpdate: (data: any) => void
data: PipeData
}
const BucketSelector: FC<Props> = ({onUpdate, data}) => {
const selectedBucketName = data.bucketName
const {buckets, loading} = useContext(BucketContext)
const updateBucket = (updatedBucket: Bucket): void => {
onUpdate({bucketName: updatedBucket.name})
}
useEffect(() => {
// selectedBucketName will only evaluate false on the initial render
// because there is no default value
if (!!buckets.length && !selectedBucketName) {
updateBucket(buckets[0])
}
}, [buckets])
let body
if (loading === RemoteDataState.Loading) {
body = (
<div className="data-source--list__empty">
<TechnoSpinner strokeWidth={ComponentSize.Small} diameterPixels={32} />
</div>
)
}
if (loading === RemoteDataState.Error) {
body = (
<div className="data-source--list__empty">
<p>Could not fetch Buckets</p>
</div>
)
}
if (loading === RemoteDataState.Done && selectedBucketName) {
body = (
<DapperScrollbars className="data-source--list">
{buckets.map(bucket => (
<SelectorListItem
key={bucket.name}
value={bucket}
onClick={updateBucket}
selected={bucket.name === selectedBucketName}
text={bucket.name}
/>
))}
</DapperScrollbars>
)
}
return (
<div className="data-source--block">
<div className="data-source--block-title">Bucket</div>
{body}
</div>
)
}
export default BucketSelector

View File

@ -0,0 +1,28 @@
// Libraries
import React, {FC} from 'react'
import classnames from 'classnames'
interface Props {
value: any
onClick: (value: any) => void
selected: boolean
text: string
}
const SelectorListItem: FC<Props> = ({value, onClick, selected, text}) => {
const className = classnames('data-source--list-item', {
'data-source--list-item__selected': selected,
})
const handleClick = (): void => {
onClick(value)
}
return (
<div className={className} onClick={handleClick}>
{text}
</div>
)
}
export default SelectorListItem

View File

@ -0,0 +1,44 @@
// Libraries
import React, {FC} from 'react'
// Components
import {DapperScrollbars} from '@influxdata/clockface'
import SelectorListItem from 'src/notebooks/pipes/Data/SelectorListItem'
// Types
import {PipeData} from 'src/notebooks'
// Constants
import {SELECTABLE_TIME_RANGES} from 'src/shared/constants/timeRanges'
interface Props {
onUpdate: (data: any) => void
data: PipeData
}
const TimeSelector: FC<Props> = ({onUpdate, data}) => {
const timeStart = data.timeStart
const updateTimeRange = (duration: string): void => {
onUpdate({timeStart: duration})
}
return (
<div className="data-source--block">
<div className="data-source--block-title">Time Range</div>
<DapperScrollbars className="data-source--list">
{SELECTABLE_TIME_RANGES.map(range => (
<SelectorListItem
key={range.label}
value={`-${range.duration}`}
onClick={updateTimeRange}
selected={`-${range.duration}` === timeStart}
text={`Past ${range.duration}`}
/>
))}
</DapperScrollbars>
</div>
)
}
export default TimeSelector

View File

@ -0,0 +1,16 @@
import {register} from 'src/notebooks'
import View from './view'
import './style.scss'
register({
type: 'data',
priority: 1,
featureFlag: 'notebook-panel--data-source',
component: View,
button: 'Data Source',
initial: {
bucketName: '',
timeStart: '-1h',
timeStop: 'now()',
},
})

View File

@ -0,0 +1,67 @@
@import '@influxdata/clockface/dist/variables.scss';
.data-source {
background-color: $g2-kevlar;
padding: $cf-marg-c;
border-radius: $cf-radius;
}
.data-source--block {
flex: 1 0 220px;
display: flex;
flex-direction: column;
align-items: stretch;
max-width: 320px;
height: 180px;
}
.data-source--block-title {
font-size: 14px;
user-select: none;
font-weight: $cf-font-weight--medium;
padding-bottom: $cf-marg-b;
margin-bottom: $cf-marg-b;
color: $g13-mist;
border-bottom: $cf-border solid $g3-castle;
}
.data-source--list,
.data-source--list__empty {
background-color: $g1-raven;
border-radius: $cf-radius;
flex: 1 0 0;
}
.data-source--list__empty {
display: flex;
align-items: center;
justify-content: center;
user-select: none;
color: $g8-storm;
}
.data-source--list-item {
user-select: none;
font-size: 13px;
padding: $cf-marg-a $cf-marg-b;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
font-weight: $cf-font-weight--medium;
transition: color 0.25s ease, background-color 0.25s ease;
color: $g9-mountain;
margin: $cf-border;
border-radius: $cf-radius-sm;
&:hover {
cursor: pointer;
color: $g15-platinum;
background-color: $g3-castle;
}
}
.data-source--list-item__selected,
.data-source--list-item__selected:hover {
background-color: $c-pool;
color: $g20-white;
}

View File

@ -0,0 +1,36 @@
// Libraries
import React, {FC, useMemo} from 'react'
// Types
import {PipeProp} from 'src/notebooks'
// Components
import BucketSelector from 'src/notebooks/pipes/Data/BucketSelector'
import TimeSelector from 'src/notebooks/pipes/Data/TimeSelector'
import {FlexBox, ComponentSize} from '@influxdata/clockface'
import BucketProvider from 'src/notebooks/context/buckets'
// Styles
import 'src/notebooks/pipes/Query/style.scss'
const DataSource: FC<PipeProp> = ({data, onUpdate, Context}) => {
return useMemo(
() => (
<BucketProvider>
<Context>
<FlexBox
margin={ComponentSize.Large}
stretchToFitWidth={true}
className="data-source"
>
<BucketSelector onUpdate={onUpdate} data={data} />
<TimeSelector onUpdate={onUpdate} data={data} />
</FlexBox>
</Context>
</BucketProvider>
),
[data.bucketName, data.timeStart, data.timeStop]
)
}
export default DataSource

View File

@ -15,6 +15,7 @@ export const OSS_FLAGS = {
streamEvents: false,
'notebook-panel--spotify': false,
'notebook-panel--test-flux': false,
'notebook-panel--data-source': false,
}
export const CLOUD_FLAGS = {
@ -31,6 +32,7 @@ export const CLOUD_FLAGS = {
streamEvents: false,
'notebook-panel--spotify': false,
'notebook-panel--test-flux': false,
'notebook-panel--data-source': false,
}
export const activeFlags = (state: AppState): FlagMap => {