Merge pull request #5746 from influxdata/5745/write_data_v2

feat(ui/explorer): use v2 write API with buckets when Flux tab is selected
pull/5748/head
Pavel Závora 2021-05-06 05:55:50 +02:00 committed by GitHub
commit ceef251ab6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 109 additions and 45 deletions

View File

@ -20,6 +20,7 @@
1. [#5726](https://github.com/influxdata/chronograf/pull/5726): Allow to setup InfluxDB v2 connection from chronograf command-line. 1. [#5726](https://github.com/influxdata/chronograf/pull/5726): Allow to setup InfluxDB v2 connection from chronograf command-line.
1. [#5735](https://github.com/influxdata/chronograf/pull/5735): Allow to add custom auto-refresh intervals. 1. [#5735](https://github.com/influxdata/chronograf/pull/5735): Allow to add custom auto-refresh intervals.
1. [#5737](https://github.com/influxdata/chronograf/pull/5737): Allow to send multiple queries to dashboard. 1. [#5737](https://github.com/influxdata/chronograf/pull/5737): Allow to send multiple queries to dashboard.
1. [#5746](https://github.com/influxdata/chronograf/pull/5746): Write to buckets when Flux tab is selected.
### Bug Fixes ### Bug Fixes

View File

@ -120,8 +120,19 @@ func (s *Service) Write(w http.ResponseWriter, r *http.Request) {
Error(w, http.StatusUnprocessableEntity, msg, s.Logger) Error(w, http.StatusUnprocessableEntity, msg, s.Logger)
return return
} }
u.Path = "/write" query := r.URL.Query()
u.RawQuery = r.URL.RawQuery version := query.Get("v")
query.Del("v")
if strings.HasPrefix(version, "2") {
u.Path = "/api/v2/write"
// v2 organization name is stored in username (org does not matter against v1)
query.Set("org", src.Username)
query.Set("bucket", query.Get("db"))
query.Del("db")
} else {
u.Path = "/write"
}
u.RawQuery = query.Encode()
director := func(req *http.Request) { director := func(req *http.Request) {
// Set the Host header of the original source URL // Set the Host header of the original source URL

View File

@ -495,15 +495,15 @@ func paramID(key string, r *http.Request) (int, error) {
return id, nil return id, nil
} }
func paramInt64(key string, r *http.Request) (int64, error) { // func paramInt64(key string, r *http.Request) (int64, error) {
ctx := r.Context() // ctx := r.Context()
param := httprouter.GetParamFromContext(ctx, key) // param := httprouter.GetParamFromContext(ctx, key)
v, err := strconv.ParseInt(param, 10, 64) // v, err := strconv.ParseInt(param, 10, 64)
if err != nil { // if err != nil {
return -1, fmt.Errorf("Error converting parameter %s", param) // return -1, fmt.Errorf("Error converting parameter %s", param)
} // }
return v, nil // return v, nil
} // }
func paramStr(key string, r *http.Request) (string, error) { func paramStr(key string, r *http.Request) (string, error) {
ctx := r.Context() ctx := r.Context()

View File

@ -12,13 +12,18 @@ export const writeLineProtocolAsync = (
source: Source, source: Source,
db: string, db: string,
data: string, data: string,
precision?: string precision?: string,
v2?: boolean
) => async (dispatch): Promise<void> => { ) => async (dispatch): Promise<void> => {
try { try {
await writeLineProtocolAJAX(source, db, data, precision) await writeLineProtocolAJAX(source, db, data, precision, v2)
dispatch(notify(notifyDataWritten())) dispatch(notify(notifyDataWritten()))
} catch (response) { } catch (response) {
dispatch(notify(notifyDataWriteFailed(response.data.error))) dispatch(
notify(
notifyDataWriteFailed(response.data?.error || response.data?.message)
)
)
throw response throw response
} }
} }

View File

@ -5,10 +5,11 @@ export const writeLineProtocol = async (
source: Source, source: Source,
db: string, db: string,
data: string, data: string,
precision?: string precision?: string,
v2?: boolean
): Promise<void> => { ): Promise<void> => {
const url = `${source.links.write}?db=${db}&precision=${ const url = `${source.links.write}?db=${db}&precision=${
precision ? precision : 'ns' precision ? precision : 'ns'
}` }${v2 ? '&v=2' : ''}`
await AJAX({url, method: 'POST', data}) await AJAX({url, method: 'POST', data})
} }

View File

@ -22,11 +22,13 @@ interface Props {
selectedDatabase: string selectedDatabase: string
onClose: () => void onClose: () => void
errorThrown: () => void errorThrown: () => void
useV2: boolean
writeLineProtocol: ( writeLineProtocol: (
source: Source, source: Source,
database: string, database: string,
content: string, content: string,
precision?: string precision?: string,
useV2?: boolean
) => void ) => void
} }
@ -62,7 +64,7 @@ class WriteDataForm extends PureComponent<Props, State> {
} }
public render() { public render() {
const {onClose, errorThrown, source} = this.props const {onClose, errorThrown, source, useV2} = this.props
const {dragClass} = this.state const {dragClass} = this.state
return ( return (
@ -81,6 +83,7 @@ class WriteDataForm extends PureComponent<Props, State> {
onClose={onClose} onClose={onClose}
errorThrown={errorThrown} errorThrown={errorThrown}
onToggleMode={this.handleToggleMode} onToggleMode={this.handleToggleMode}
useBuckets={useV2}
handlePrecisionChange={this.handlePrecisionChange} handlePrecisionChange={this.handlePrecisionChange}
handleSelectDatabase={this.handleSelectDatabase} handleSelectDatabase={this.handleSelectDatabase}
/> />
@ -119,7 +122,7 @@ class WriteDataForm extends PureComponent<Props, State> {
} }
private handleSubmit = async () => { private handleSubmit = async () => {
const {onClose, source, writeLineProtocol} = this.props const {onClose, source, writeLineProtocol, useV2} = this.props
const { const {
inputContent, inputContent,
uploadContent, uploadContent,
@ -135,13 +138,19 @@ class WriteDataForm extends PureComponent<Props, State> {
this.setState({isUploading: true}) this.setState({isUploading: true})
try { try {
await writeLineProtocol(source, selectedDatabase, content, precision) await writeLineProtocol(
source,
selectedDatabase,
content,
precision,
useV2
)
this.setState({isUploading: false}) this.setState({isUploading: false})
onClose() onClose()
window.location.reload() window.location.reload()
} catch (error) { } catch (error) {
this.setState({isUploading: false}) this.setState({isUploading: false})
console.error(error.data.error) console.error(error.data)
} }
} }

View File

@ -15,6 +15,7 @@ interface Props {
onClose: () => void onClose: () => void
mode: string mode: string
source: Source source: Source
useBuckets: boolean
} }
class WriteDataHeader extends PureComponent<Props> { class WriteDataHeader extends PureComponent<Props> {
@ -26,6 +27,7 @@ class WriteDataHeader extends PureComponent<Props> {
onClose, onClose,
source, source,
precision, precision,
useBuckets,
} = this.props } = this.props
return ( return (
@ -36,6 +38,7 @@ class WriteDataHeader extends PureComponent<Props> {
source={source} source={source}
onSelectDatabase={handleSelectDatabase} onSelectDatabase={handleSelectDatabase}
database={selectedDatabase} database={selectedDatabase}
useBuckets={useBuckets}
onErrorThrown={errorThrown} onErrorThrown={errorThrown}
/> />
{this.modeSelector} {this.modeSelector}

View File

@ -297,7 +297,7 @@ export class DataExplorer extends PureComponent<Props, State> {
} }
private get writeDataForm(): JSX.Element { private get writeDataForm(): JSX.Element {
const {source, errorThrownAction, writeLineProtocol} = this.props const {source, errorThrownAction, writeLineProtocol, queryType} = this.props
const {isWriteFormVisible} = this.state const {isWriteFormVisible} = this.state
return ( return (
@ -308,6 +308,7 @@ export class DataExplorer extends PureComponent<Props, State> {
selectedDatabase={this.selectedDatabase} selectedDatabase={this.selectedDatabase}
onClose={this.handleCloseWriteData} onClose={this.handleCloseWriteData}
writeLineProtocol={writeLineProtocol} writeLineProtocol={writeLineProtocol}
useV2={queryType === 'flux'}
/> />
</OverlayTechnology> </OverlayTechnology>
) )

View File

@ -5,6 +5,7 @@ import {showDatabases} from 'src/shared/apis/metaQuery'
import parsers from 'src/shared/parsing' import parsers from 'src/shared/parsing'
import {Source} from 'src/types/sources' import {Source} from 'src/types/sources'
import {ErrorHandling} from 'src/shared/decorators/errors' import {ErrorHandling} from 'src/shared/decorators/errors'
import {getBuckets} from 'src/flux/components/DatabaseList'
const {databases: showDatabasesParser} = parsers const {databases: showDatabasesParser} = parsers
interface Database { interface Database {
@ -17,10 +18,12 @@ interface Props {
onStartEdit?: () => void onStartEdit?: () => void
onErrorThrown: (error: string) => void onErrorThrown: (error: string) => void
source: Source source: Source
// true loads buckets in place of databases
useBuckets?: boolean
} }
interface State { interface State {
databases: Database[] databases: string[]
} }
@ErrorHandling @ErrorHandling
@ -58,30 +61,60 @@ class DatabaseDropdown extends Component<Props, State> {
} }
private getDatabasesAsync = async (): Promise<void> => { private getDatabasesAsync = async (): Promise<void> => {
const {source, database, onSelectDatabase, onErrorThrown} = this.props const {
const proxy = source.links.proxy source,
try { database,
const {data} = await showDatabases(proxy) onSelectDatabase,
const {databases, errors} = showDatabasesParser(data) onErrorThrown,
if (errors.length > 0) { useBuckets,
throw errors[0] // only one error can come back from this, but it's returned as an array } = this.props
let databases: string[]
if (useBuckets) {
try {
databases = await getBuckets(source)
databases.sort((a, b) => {
// databases starting with '_' are the last
if (b.startsWith('_')) {
if (a.startsWith('_')) {
return a.localeCompare(b)
}
return -1
}
return a.localeCompare(b)
})
} catch (e) {
console.error(e)
onErrorThrown(e)
return
}
} else {
try {
const proxy = source.links.proxy
const {data} = await showDatabases(proxy)
const parserResult = showDatabasesParser(data)
if (parserResult.errors.length > 0) {
throw parserResult.errors[0] // only one error can come back from this, but it's returned as an array
}
databases = parserResult.databases
} catch (error) {
console.error(error)
onErrorThrown(error)
return
} }
const nonSystemDatabases = databases.filter(name => name !== '_internal')
this.setState({
databases: nonSystemDatabases,
})
const selectedDatabaseText = nonSystemDatabases.includes(database)
? database
: nonSystemDatabases[0] || 'No databases'
onSelectDatabase({
text: selectedDatabaseText,
})
} catch (error) {
console.error(error)
onErrorThrown(error)
} }
const nonSystemDatabases = databases.filter(
name => !name.startsWith('_internal')
)
this.setState({
databases: nonSystemDatabases,
})
const selectedDatabaseText = nonSystemDatabases.includes(database)
? database
: nonSystemDatabases[0] || 'No databases'
onSelectDatabase({
text: selectedDatabaseText,
})
} }
} }