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
parent
5e4dd3f77a
commit
db991b01ac
|
@ -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'
|
||||
|
|
|
@ -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>
|
||||
)
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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()',
|
||||
},
|
||||
})
|
|
@ -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;
|
||||
}
|
|
@ -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
|
|
@ -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 => {
|
||||
|
|
Loading…
Reference in New Issue