Fix query bug resulting from missing org ID
The `/api/v2/query` endpoint requires an organization or organizationID query parameter. Previously, there existed a bug in the API where if the organization parameters were left off, the API would use the first organization created in the backend, rather than returning a 400 error. We built the entire UI on top of this bug, but it has now been fixed. So in every location where we use the `/api/v2/query` endpoint, we need to supply an organization. This commit updates all such locations to use the first organization present in our Redux store as the organization parameter, thus roughly reproducing the behavior of the load bearing bug. This is just a quick fix. Long term, we will want to think about what organization queries should run under and build an appropriate UI around that design.pull/12084/head
parent
0cc05ee0e7
commit
c664e8e0d8
|
@ -1,44 +1,39 @@
|
|||
// Libraries
|
||||
import React from 'react'
|
||||
import {shallow} from 'enzyme'
|
||||
|
||||
// Components
|
||||
import DataListening from 'src/dataLoaders/components/verifyStep/DataListening'
|
||||
import ConnectionInformation from 'src/dataLoaders/components/verifyStep/ConnectionInformation'
|
||||
import {Button} from '@influxdata/clockface'
|
||||
|
||||
const setup = (override = {}) => {
|
||||
const props = {
|
||||
bucket: 'defbuck',
|
||||
stepIndex: 4,
|
||||
...override,
|
||||
// Utils
|
||||
import {renderWithRedux} from 'src/mockState'
|
||||
import {fireEvent} from 'react-testing-library'
|
||||
|
||||
const setInitialState = state => {
|
||||
return {
|
||||
...state,
|
||||
orgs: [
|
||||
{
|
||||
id: 'foo',
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
const wrapper = shallow(<DataListening {...props} />)
|
||||
|
||||
return {wrapper}
|
||||
}
|
||||
|
||||
describe('Onboarding.Components.DataListening', () => {
|
||||
it('renders', () => {
|
||||
const {wrapper} = setup()
|
||||
const button = wrapper.find(Button)
|
||||
|
||||
expect(wrapper.exists()).toBe(true)
|
||||
expect(button.exists()).toBe(true)
|
||||
})
|
||||
|
||||
describe('if button is clicked', () => {
|
||||
it('displays connection information', () => {
|
||||
const {wrapper} = setup()
|
||||
const {getByTitle, getByText} = renderWithRedux(
|
||||
<DataListening bucket="bucket" />,
|
||||
setInitialState
|
||||
)
|
||||
|
||||
const button = wrapper.find(Button)
|
||||
button.simulate('click')
|
||||
const button = getByTitle('Listen for Data')
|
||||
|
||||
const connectionInfo = wrapper.find(ConnectionInformation)
|
||||
fireEvent.click(button)
|
||||
|
||||
expect(wrapper.exists()).toBe(true)
|
||||
expect(connectionInfo.exists()).toBe(true)
|
||||
const message = getByText('Awaiting Connection...')
|
||||
|
||||
expect(message).toBeDefined()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
// Libraries
|
||||
import React, {PureComponent} from 'react'
|
||||
import {connect} from 'react-redux'
|
||||
import _ from 'lodash'
|
||||
|
||||
// Apis
|
||||
import {executeQuery} from 'src/shared/apis/v2/query'
|
||||
import {getActiveOrg} from 'src/organizations/selectors'
|
||||
|
||||
// Components
|
||||
import {ErrorHandling} from 'src/shared/decorators/errors'
|
||||
|
@ -18,12 +20,19 @@ import ConnectionInformation, {
|
|||
} from 'src/dataLoaders/components/verifyStep/ConnectionInformation'
|
||||
|
||||
// Types
|
||||
import {AppState, Organization} from 'src/types/v2'
|
||||
import {InfluxLanguage} from 'src/types/v2/dashboards'
|
||||
|
||||
export interface Props {
|
||||
interface OwnProps {
|
||||
bucket: string
|
||||
}
|
||||
|
||||
interface StateProps {
|
||||
activeOrg: Organization
|
||||
}
|
||||
|
||||
type Props = OwnProps & StateProps
|
||||
|
||||
interface State {
|
||||
loading: LoadingState
|
||||
timePassedInSeconds: number
|
||||
|
@ -112,7 +121,7 @@ class DataListening extends PureComponent<Props, State> {
|
|||
}
|
||||
|
||||
private checkForData = async (): Promise<void> => {
|
||||
const {bucket} = this.props
|
||||
const {bucket, activeOrg} = this.props
|
||||
const {secondsLeft} = this.state
|
||||
const script = `from(bucket: "${bucket}")
|
||||
|> range(start: -1m)`
|
||||
|
@ -123,6 +132,7 @@ class DataListening extends PureComponent<Props, State> {
|
|||
try {
|
||||
const response = await executeQuery(
|
||||
'/api/v2/query',
|
||||
activeOrg.id,
|
||||
script,
|
||||
InfluxLanguage.Flux
|
||||
).promise
|
||||
|
@ -165,4 +175,11 @@ class DataListening extends PureComponent<Props, State> {
|
|||
}
|
||||
}
|
||||
|
||||
export default DataListening
|
||||
const mstp = (state: AppState) => ({
|
||||
activeOrg: getActiveOrg(state),
|
||||
})
|
||||
|
||||
export default connect<StateProps, {}, OwnProps>(
|
||||
mstp,
|
||||
null
|
||||
)(DataListening)
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
import {AppState, Organization} from 'src/types/v2'
|
||||
|
||||
export const getActiveOrg = (state: AppState): Organization => state.orgs[0]
|
|
@ -20,6 +20,7 @@ interface XHRError extends Error {
|
|||
|
||||
export const executeQuery = (
|
||||
url: string,
|
||||
orgID: string,
|
||||
query: string,
|
||||
language: InfluxLanguage = InfluxLanguage.Flux
|
||||
): WrappedCancelablePromise<ExecuteFluxQueryResult> => {
|
||||
|
@ -127,7 +128,7 @@ export const executeQuery = (
|
|||
const dialect = {annotations: ['group', 'datatype', 'default']}
|
||||
const body = JSON.stringify({query, dialect, type: language})
|
||||
|
||||
xhr.open('POST', url)
|
||||
xhr.open('POST', `${url}?orgID=${encodeURIComponent(orgID)}`)
|
||||
xhr.setRequestHeader('Content-Type', 'application/json')
|
||||
xhr.send(body)
|
||||
|
||||
|
|
|
@ -10,18 +10,20 @@ import {executeQuery, ExecuteFluxQueryResult} from 'src/shared/apis/v2/query'
|
|||
import {parseResponse} from 'src/shared/parsing/flux/response'
|
||||
import {getSources, getActiveSource} from 'src/sources/selectors'
|
||||
import {renderQuery} from 'src/shared/utils/renderQuery'
|
||||
import {getActiveOrg} from 'src/organizations/selectors'
|
||||
|
||||
// Types
|
||||
import {RemoteDataState, FluxTable} from 'src/types'
|
||||
import {DashboardQuery} from 'src/types/v2/dashboards'
|
||||
import {AppState, Source} from 'src/types/v2'
|
||||
import {AppState, Source, Organization} from 'src/types/v2'
|
||||
import {WrappedCancelablePromise, CancellationError} from 'src/types/promises'
|
||||
|
||||
type URLQuery = DashboardQuery & {url: string}
|
||||
|
||||
const executeRenderedQuery = (
|
||||
{text, type, url}: URLQuery,
|
||||
variables: {[key: string]: string}
|
||||
variables: {[key: string]: string},
|
||||
orgID: string
|
||||
): WrappedCancelablePromise<ExecuteFluxQueryResult> => {
|
||||
let isCancelled = false
|
||||
let cancelExecution
|
||||
|
@ -39,7 +41,7 @@ const executeRenderedQuery = (
|
|||
return Promise.reject(new CancellationError())
|
||||
}
|
||||
|
||||
const pendingResult = executeQuery(url, renderedQuery, type)
|
||||
const pendingResult = executeQuery(url, orgID, renderedQuery, type)
|
||||
|
||||
cancelExecution = pendingResult.cancel
|
||||
|
||||
|
@ -61,6 +63,7 @@ export interface QueriesState {
|
|||
interface StateProps {
|
||||
dynamicSourceURL: string
|
||||
sources: Source[]
|
||||
activeOrg: Organization
|
||||
}
|
||||
|
||||
interface OwnProps {
|
||||
|
@ -141,7 +144,7 @@ class TimeSeries extends Component<Props, State> {
|
|||
}
|
||||
|
||||
private reload = async () => {
|
||||
const {inView, variables} = this.props
|
||||
const {inView, variables, activeOrg} = this.props
|
||||
const queries = this.queries
|
||||
|
||||
if (!inView) {
|
||||
|
@ -167,7 +170,9 @@ class TimeSeries extends Component<Props, State> {
|
|||
this.pendingResults.forEach(({cancel}) => cancel())
|
||||
|
||||
// Issue new queries
|
||||
this.pendingResults = queries.map(q => executeRenderedQuery(q, variables))
|
||||
this.pendingResults = queries.map(q =>
|
||||
executeRenderedQuery(q, variables, activeOrg.id)
|
||||
)
|
||||
|
||||
// Wait for new queries to complete
|
||||
const results = await Promise.all(this.pendingResults.map(r => r.promise))
|
||||
|
@ -218,8 +223,9 @@ class TimeSeries extends Component<Props, State> {
|
|||
const mstp = (state: AppState) => {
|
||||
const sources = getSources(state)
|
||||
const dynamicSourceURL = getActiveSource(state).links.query
|
||||
const activeOrg = getActiveOrg(state)
|
||||
|
||||
return {sources, dynamicSourceURL}
|
||||
return {sources, dynamicSourceURL, activeOrg}
|
||||
}
|
||||
|
||||
export default connect<StateProps, {}, OwnProps>(
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
import {queryBuilderFetcher} from 'src/timeMachine/apis/QueryBuilderFetcher'
|
||||
|
||||
// Utils
|
||||
import {getActiveOrg} from 'src/organizations/selectors'
|
||||
import {
|
||||
getActiveQuerySource,
|
||||
getActiveQuery,
|
||||
|
@ -203,11 +204,13 @@ export const loadBuckets = () => async (
|
|||
dispatch: Dispatch<Action>,
|
||||
getState: GetState
|
||||
) => {
|
||||
const queryURL = getActiveQuerySource(getState()).links.query
|
||||
const orgID = getActiveOrg(getState()).id
|
||||
|
||||
dispatch(setBuilderBucketsStatus(RemoteDataState.Loading))
|
||||
|
||||
try {
|
||||
const queryURL = getActiveQuerySource(getState()).links.query
|
||||
const buckets = await queryBuilderFetcher.findBuckets(queryURL)
|
||||
const buckets = await queryBuilderFetcher.findBuckets(queryURL, orgID)
|
||||
const selectedBucket = getActiveQuery(getState()).builderConfig.buckets[0]
|
||||
|
||||
dispatch(setBuilderBuckets(buckets))
|
||||
|
@ -247,6 +250,7 @@ export const loadTagSelector = (index: number) => async (
|
|||
|
||||
const tagPredicates = tags.slice(0, index)
|
||||
const queryURL = getActiveQuerySource(getState()).links.query
|
||||
const orgID = getActiveOrg(getState()).id
|
||||
|
||||
dispatch(setBuilderTagKeysStatus(index, RemoteDataState.Loading))
|
||||
|
||||
|
@ -257,6 +261,7 @@ export const loadTagSelector = (index: number) => async (
|
|||
const keys = await queryBuilderFetcher.findKeys(
|
||||
index,
|
||||
queryURL,
|
||||
orgID,
|
||||
buckets[0],
|
||||
tagPredicates,
|
||||
searchTerm
|
||||
|
@ -299,6 +304,7 @@ const loadTagSelectorValues = (index: number) => async (
|
|||
const {buckets, tags} = getActiveQuery(getState()).builderConfig
|
||||
const tagPredicates = tags.slice(0, index)
|
||||
const queryURL = getActiveQuerySource(getState()).links.query
|
||||
const orgID = getActiveOrg(getState()).id
|
||||
|
||||
dispatch(setBuilderTagValuesStatus(index, RemoteDataState.Loading))
|
||||
|
||||
|
@ -309,6 +315,7 @@ const loadTagSelectorValues = (index: number) => async (
|
|||
const values = await queryBuilderFetcher.findValues(
|
||||
index,
|
||||
queryURL,
|
||||
orgID,
|
||||
buckets[0],
|
||||
tagPredicates,
|
||||
key,
|
||||
|
|
|
@ -19,7 +19,7 @@ class QueryBuilderFetcher {
|
|||
private findValuesCache: {[key: string]: string[]} = {}
|
||||
private findBucketsCache: {[key: string]: string[]} = {}
|
||||
|
||||
public async findBuckets(url: string): Promise<string[]> {
|
||||
public async findBuckets(url: string, orgID: string): Promise<string[]> {
|
||||
this.cancelFindBuckets()
|
||||
|
||||
const cacheKey = JSON.stringify([...arguments])
|
||||
|
@ -29,7 +29,7 @@ class QueryBuilderFetcher {
|
|||
return Promise.resolve(cachedResult)
|
||||
}
|
||||
|
||||
const pendingResult = findBuckets(url)
|
||||
const pendingResult = findBuckets(url, orgID)
|
||||
|
||||
pendingResult.promise.then(result => {
|
||||
this.findBucketsCache[cacheKey] = result
|
||||
|
@ -47,6 +47,7 @@ class QueryBuilderFetcher {
|
|||
public async findKeys(
|
||||
index: number,
|
||||
url: string,
|
||||
orgID: string,
|
||||
bucket: string,
|
||||
tagsSelections: BuilderConfig['tags'],
|
||||
searchTerm: string = ''
|
||||
|
@ -60,7 +61,13 @@ class QueryBuilderFetcher {
|
|||
return Promise.resolve(cachedResult)
|
||||
}
|
||||
|
||||
const pendingResult = findKeys(url, bucket, tagsSelections, searchTerm)
|
||||
const pendingResult = findKeys(
|
||||
url,
|
||||
orgID,
|
||||
bucket,
|
||||
tagsSelections,
|
||||
searchTerm
|
||||
)
|
||||
|
||||
this.findKeysQueries[index] = pendingResult
|
||||
|
||||
|
@ -80,6 +87,7 @@ class QueryBuilderFetcher {
|
|||
public async findValues(
|
||||
index: number,
|
||||
url: string,
|
||||
orgID: string,
|
||||
bucket: string,
|
||||
tagsSelections: BuilderConfig['tags'],
|
||||
key: string,
|
||||
|
@ -96,6 +104,7 @@ class QueryBuilderFetcher {
|
|||
|
||||
const pendingResult = findValues(
|
||||
url,
|
||||
orgID,
|
||||
bucket,
|
||||
tagsSelections,
|
||||
key,
|
||||
|
|
|
@ -14,12 +14,12 @@ export const LIMIT = 200
|
|||
|
||||
type CancelableQuery = WrappedCancelablePromise<string[]>
|
||||
|
||||
export function findBuckets(url: string): CancelableQuery {
|
||||
export function findBuckets(url: string, orgID: string): CancelableQuery {
|
||||
const query = `buckets()
|
||||
|> sort(columns: ["name"])
|
||||
|> limit(n: ${LIMIT})`
|
||||
|
||||
const {promise, cancel} = executeQuery(url, query, InfluxLanguage.Flux)
|
||||
const {promise, cancel} = executeQuery(url, orgID, query, InfluxLanguage.Flux)
|
||||
|
||||
return {
|
||||
promise: promise.then(resp => extractCol(resp, 'name')),
|
||||
|
@ -29,6 +29,7 @@ export function findBuckets(url: string): CancelableQuery {
|
|||
|
||||
export function findKeys(
|
||||
url: string,
|
||||
orgID: string,
|
||||
bucket: string,
|
||||
tagsSelections: BuilderConfig['tags'],
|
||||
searchTerm: string = ''
|
||||
|
@ -49,7 +50,7 @@ v1.tagKeys(bucket: "${bucket}", predicate: ${tagFilters}, start: -${SEARCH_DURAT
|
|||
|> sort()
|
||||
|> limit(n: ${LIMIT})`
|
||||
|
||||
const {promise, cancel} = executeQuery(url, query, InfluxLanguage.Flux)
|
||||
const {promise, cancel} = executeQuery(url, orgID, query, InfluxLanguage.Flux)
|
||||
|
||||
return {
|
||||
promise: promise.then(resp => extractCol(resp, '_value')),
|
||||
|
@ -59,6 +60,7 @@ v1.tagKeys(bucket: "${bucket}", predicate: ${tagFilters}, start: -${SEARCH_DURAT
|
|||
|
||||
export function findValues(
|
||||
url: string,
|
||||
orgID: string,
|
||||
bucket: string,
|
||||
tagsSelections: BuilderConfig['tags'],
|
||||
key: string,
|
||||
|
@ -73,7 +75,7 @@ v1.tagValues(bucket: "${bucket}", tag: "${key}", predicate: ${tagFilters}, start
|
|||
|> limit(n: ${LIMIT})
|
||||
|> sort()`
|
||||
|
||||
const {promise, cancel} = executeQuery(url, query, InfluxLanguage.Flux)
|
||||
const {promise, cancel} = executeQuery(url, orgID, query, InfluxLanguage.Flux)
|
||||
|
||||
return {
|
||||
promise: promise.then(resp => extractCol(resp, '_value')),
|
||||
|
|
|
@ -17,6 +17,11 @@ const setInitialState = state => {
|
|||
[source.id]: source,
|
||||
},
|
||||
},
|
||||
orgs: [
|
||||
{
|
||||
id: 'foo',
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue