Skip to content

Commit

Permalink
Merge pull request #6105 from influxdata/fix/auto-execution
Browse files Browse the repository at this point in the history
feat: preventing ddl and dml statements from autosubmiting
  • Loading branch information
vlastahajek authored Dec 4, 2024
2 parents a62d824 + fbaa580 commit 126af48
Show file tree
Hide file tree
Showing 11 changed files with 169 additions and 59 deletions.
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
### Bug Fixes

1. [#6103](https://github.com/influxdata/chronograf/pull/6103): Set active database for InfluxQL meta queries.
1. [#6111](https://github.com/influxdata/chronograf/pull/6111): Fix loading Hosts page for large number of hosts.
2. [#6105](https://github.com/influxdata/chronograf/pull/6105): Prevent dangerous InfluxQL statements from auto-execution.
3. [#6111](https://github.com/influxdata/chronograf/pull/6111): Loading Hosts page for large number of hosts.

### Other

Expand Down
2 changes: 1 addition & 1 deletion ui/.eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@
"SwitchCase": 1
}
],
"linebreak-style": [2, "unix"],
"linebreak-style": 0,
"lines-around-comment": 0,
"max-depth": 0,
"max-len": 0,
Expand Down
103 changes: 69 additions & 34 deletions ui/src/dashboards/components/InfluxQLEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,25 +8,33 @@ import ReactCodeMirror from 'src/dashboards/components/ReactCodeMirror'
import TemplateDrawer from 'src/shared/components/TemplateDrawer'
import QueryStatus from 'src/shared/components/QueryStatus'
import {ErrorHandling} from 'src/shared/decorators/errors'
import {Dropdown, DropdownMode, ComponentStatus} from 'src/reusable_ui'
import {Button, ComponentColor, ComponentSize} from 'src/reusable_ui'
import {
Button,
ComponentColor,
ComponentSize,
ComponentStatus,
Dropdown,
DropdownMode,
} from 'src/reusable_ui'

// Utils
import {getDeep} from 'src/utils/wrappers'
import {makeCancelable} from 'src/utils/promises'

// Constants
import {MATCH_INCOMPLETE_TEMPLATES, applyMasks} from 'src/tempVars/constants'
import {METAQUERY_TEMPLATE_OPTIONS} from 'src/data_explorer/constants'

// Types
import {Template, QueryConfig} from 'src/types'
import {WrappedCancelablePromise} from 'src/types/promises'
import {applyMasks, MATCH_INCOMPLETE_TEMPLATES} from 'src/tempVars/constants'
import {
MetaQueryTemplateOption,
DropdownChildTypes,
METAQUERY_TEMPLATE_OPTIONS,
MetaQueryTemplateOption,
} from 'src/data_explorer/constants'

// Types
import {QueryConfig, Template} from 'src/types'
import {WrappedCancelablePromise} from 'src/types/promises'
import {isExcludedStatement} from 'src/utils/queryFilter'
import {ErrorSkipped} from 'src/types/queries'

interface TempVar {
tempVar: string
}
Expand All @@ -41,11 +49,12 @@ interface State {
filteredTemplates: Template[]
isSubmitted: boolean
configID: string
isExcluded: boolean
}

interface Props {
query: string
onUpdate: (text: string) => Promise<void>
onUpdate: (text: string, isAutoSubmitted: boolean) => Promise<void>
config: QueryConfig
templates: Template[]
onMetaQuerySelected: () => void
Expand All @@ -64,21 +73,31 @@ const TEMPLATE_VAR = /[:]\w+[:]/g
class InfluxQLEditor extends Component<Props, State> {
public static getDerivedStateFromProps(nextProps: Props, prevState: State) {
const {isSubmitted, editedQueryText} = prevState

const isQueryConfigChanged = nextProps.config.id !== prevState.configID
const isQueryTextChanged = editedQueryText.trim() !== nextProps.query.trim()

if ((isSubmitted && isQueryTextChanged) || isQueryConfigChanged) {
const {query, config, templates} = nextProps
const isQueryConfigChanged = config.id !== prevState.configID
const isQueryTextChanged = editedQueryText.trim() !== query.trim()
// if query has been switched, set submitted state for excluded query based on the previous submitted way
let submitted: boolean
if (isQueryConfigChanged) {
submitted = isExcludedStatement(query)
? config.status?.error !== ErrorSkipped
: true
} else {
submitted = isSubmitted
}
if ((submitted && isQueryTextChanged) || isQueryConfigChanged) {
return {
...BLURRED_EDITOR_STATE,
selectedTemplate: {
tempVar: getDeep<string>(nextProps.templates, FIRST_TEMP_VAR, ''),
tempVar: getDeep<string>(templates, FIRST_TEMP_VAR, ''),
},
filteredTemplates: nextProps.templates,
templatingQueryText: nextProps.query,
editedQueryText: nextProps.query,
configID: nextProps.config.id,
filteredTemplates: templates,
templatingQueryText: query,
editedQueryText: query,
configID: config.id,
focused: isQueryConfigChanged,
isSubmitted: submitted,
isExcluded: isExcludedStatement(query),
}
}

Expand All @@ -102,8 +121,9 @@ class InfluxQLEditor extends Component<Props, State> {
filteredTemplates: props.templates,
templatingQueryText: props.query,
editedQueryText: props.query,
isSubmitted: true,
configID: props.config.id,
isSubmitted: true,
isExcluded: isExcludedStatement(props.query),
}
}

Expand All @@ -126,6 +146,7 @@ class InfluxQLEditor extends Component<Props, State> {
isShowingTemplateValues,
focused,
isSubmitted,
isExcluded,
} = this.state

return (
Expand Down Expand Up @@ -159,9 +180,17 @@ class InfluxQLEditor extends Component<Props, State> {
<div className="varmoji-container">
<div className="varmoji-front">
<QueryStatus
status={config.status}
status={
config.status?.error === ErrorSkipped
? config.status.submittedStatus
: config.status
}
isShowingTemplateValues={isShowingTemplateValues}
isSubmitted={isSubmitted}
isSubmitted={
(isSubmitted && !isExcluded) ||
(isExcluded &&
templatingQueryText === config.status?.submittedQuery)
}
>
{this.queryStatusButtons}
</QueryStatus>
Expand Down Expand Up @@ -200,7 +229,7 @@ class InfluxQLEditor extends Component<Props, State> {

private handleBlurEditor = (): void => {
this.setState({focused: false, isShowingTemplateValues: false})
this.handleUpdate()
this.handleUpdate(true)
}

private handleCloseDrawer = (): void => {
Expand Down Expand Up @@ -245,7 +274,7 @@ class InfluxQLEditor extends Component<Props, State> {

const isTemplating = matched && !_.isEmpty(templates)
if (isTemplating) {
// maintain cursor poition
// maintain cursor position
const matchedVar = {tempVar: `${matched[0]}:`}
const filteredTemplates = this.filterTemplates(matched[0])
const selectedTemplate = this.selectMatchingTemplate(
Expand All @@ -262,31 +291,37 @@ class InfluxQLEditor extends Component<Props, State> {
isSubmitted,
})
} else {
const isExcluded = isExcludedStatement(value)
this.setState({
isTemplating,
isExcluded,
templatingQueryText: value,
editedQueryText: value,
isSubmitted,
})
}
}

private handleUpdate = async (): Promise<void> => {
const {onUpdate} = this.props

if (!this.isDisabled && !this.state.isSubmitted) {
const {editedQueryText} = this.state
private handleUpdate = async (isAutoSubmitted?: boolean): Promise<void> => {
const {onUpdate, config} = this.props
const {editedQueryText, isSubmitted, isExcluded} = this.state
if (
!this.isDisabled &&
(!isSubmitted || (isExcluded && config.status?.error === ErrorSkipped))
) {
this.cancelPendingUpdates()
const update = onUpdate(editedQueryText)
const update = onUpdate(editedQueryText, isAutoSubmitted)
const cancelableUpdate = makeCancelable(update)

this.pendingUpdates = [...this.pendingUpdates, cancelableUpdate]

try {
await cancelableUpdate.promise

// prevent changing submitted status when edited while awaiting update
if (this.state.editedQueryText === editedQueryText) {
if (
this.state.editedQueryText === editedQueryText &&
(!isExcluded || !isAutoSubmitted)
) {
this.setState({isSubmitted: true})
}
} catch (error) {
Expand Down Expand Up @@ -443,7 +478,7 @@ class InfluxQLEditor extends Component<Props, State> {
size={ComponentSize.ExtraSmall}
color={ComponentColor.Primary}
status={this.isDisabled && ComponentStatus.Disabled}
onClick={this.handleUpdate}
onClick={() => this.handleUpdate()}
text="Submit Query"
/>
</div>
Expand Down
1 change: 1 addition & 0 deletions ui/src/dashboards/utils/cellGetters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,7 @@ export const getConfig = async (
// return back the raw query
queryConfig.rawText = query
}

return {
...queryConfig,
originalQuery: query,
Expand Down
28 changes: 16 additions & 12 deletions ui/src/shared/apis/query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,9 @@ import {TEMP_VAR_INTERVAL, DEFAULT_DURATION_MS} from 'src/shared/constants'
import replaceTemplates, {replaceInterval} from 'src/tempVars/utils/replace'
import {proxy} from 'src/utils/queryUrlGenerator'

import {Source, Template} from 'src/types'
import {Query, Source, Template} from 'src/types'
import {TimeSeriesResponse} from 'src/types/series'

// REVIEW: why is this different than the `Query` in src/types?
interface Query {
text: string
id: string
database?: string
db?: string
rp?: string
}
import {ErrorSkipped} from 'src/types/queries'

interface QueryResult {
value: TimeSeriesResponse | null
Expand All @@ -33,6 +25,18 @@ export function executeQueries(
let counter = queries.length

for (let i = 0; i < queries.length; i++) {
const q = queries[i]
if (
q.queryConfig.isExcluded &&
!q.queryConfig.status.isManuallySubmitted
) {
results[i] = {value: null, error: ErrorSkipped}
counter -= 1
if (counter === 0) {
resolve(results)
}
continue
}
executeQuery(source, queries[i], templates, uuid)
.then(result => (results[i] = {value: result, error: null}))
.catch(result => (results[i] = {value: null, error: result}))
Expand All @@ -58,9 +62,9 @@ export const executeQuery = async (

const {data} = await proxy({
source: source.links.proxy,
rp: query.rp,
rp: query.queryConfig.retentionPolicy,
query: text,
db: query.db || query.database,
db: query.queryConfig.database,
uuid,
})

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ interface PassedProps {
templates: Template[]
onAddQuery: () => void
onDeleteQuery: (index: number) => void
onEditRawText: (text: string) => Promise<void>
onEditRawText: (text: string, isAutoSubmitted: boolean) => Promise<void>
onMetaQuerySelected: () => void
}

Expand Down
24 changes: 20 additions & 4 deletions ui/src/shared/components/TimeMachine/TimeMachine.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ import {
import {SourceOption} from 'src/types/sources'
import {Links, ScriptStatus} from 'src/types/flux'
import queryBuilderFetcher from './fluxQueryBuilder/apis/queryBuilderFetcher'
import {isExcludedStatement} from 'src/utils/queryFilter'

interface ConnectedProps {
script: string
Expand Down Expand Up @@ -420,10 +421,20 @@ class TimeMachine extends PureComponent<Props, State> {
return getDeep(queryDrafts, '0.source', '') === ''
}

private handleEditRawText = async (text: string): Promise<void> => {
const {templates, onUpdateQueryDrafts, queryDrafts, notify} = this.props
private handleEditRawText = async (
text: string,
isAutoSubmitted: boolean
): Promise<void> => {
const {
templates,
onUpdateQueryDrafts,
queryDrafts,
queryStatuses,
notify,
} = this.props
const activeID = this.activeQuery.id
const url: string = _.get(this.source, 'links.queries', '')
const isExcluded = isExcludedStatement(text)

let newQueryConfig

Expand All @@ -446,11 +457,16 @@ class TimeMachine extends PureComponent<Props, State> {
queryConfig: {
...newQueryConfig,
rawText: text,
status: {loading: true},
isExcluded,
},
}
})

this.handleEditQueryStatus(activeID, {
...queryStatuses[activeID],
isManuallySubmitted: !isAutoSubmitted,
submittedStatus: queryStatuses[activeID]?.submittedStatus,
submittedQuery: queryStatuses[activeID]?.submittedQuery,
})
onUpdateQueryDrafts(updatedQueryDrafts)
}

Expand Down
Loading

0 comments on commit 126af48

Please sign in to comment.