Skip to content

Commit

Permalink
feat: Add ability to group in Query Builder
Browse files Browse the repository at this point in the history
  • Loading branch information
hoorayimhelping committed Dec 13, 2019
1 parent 8e8cfc7 commit 50fcaf7
Show file tree
Hide file tree
Showing 6 changed files with 154 additions and 36 deletions.
10 changes: 8 additions & 2 deletions ui/src/timeMachine/actions/queryBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -285,15 +285,16 @@ export const selectTagValue = (index: number, value: string) => (
timeMachines: {activeTimeMachineID},
} = state
const tags = getActiveQuery(state).builderConfig.tags
const values = tags[index].values
const currentTag = tags[index]
const values = currentTag.values

let newValues: string[]

if (values.includes(value)) {
newValues = values.filter(v => v !== value)
} else if (
activeTimeMachineID === 'alerting' &&
tags[index].key === '_field'
currentTag.key === '_field'
) {
newValues = [value]
} else {
Expand All @@ -302,6 +303,11 @@ export const selectTagValue = (index: number, value: string) => (

dispatch(setBuilderTagValuesSelection(index, newValues))

// don't add a new tag filter if we're grouping
if (currentTag.aggregateFunctionType === 'group') {
return
}

if (index === tags.length - 1 && newValues.length) {
dispatch(addTagSelector())
} else {
Expand Down
45 changes: 37 additions & 8 deletions ui/src/timeMachine/components/TagSelector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,6 @@ type Props = StateProps & DispatchProps & OwnProps
class TagSelector extends PureComponent<Props> {
private debouncer = new DefaultDebouncer()

// bucky: this will currently always be 'Filter'
// updates to this are imminent
private renderAggregateFunctionType(
aggregateFunctionType: BuilderAggregateFunctionType
) {
Expand All @@ -96,9 +94,13 @@ class TagSelector extends PureComponent<Props> {

return (
<BuilderCard>
<BuilderCard.Header
title={this.renderAggregateFunctionType(aggregateFunctionType)}
<BuilderCard.DropdownHeader
options={['filter', 'group']}
selectedOption={this.renderAggregateFunctionType(
aggregateFunctionType
)}
onDelete={index !== 0 && this.handleRemoveTagSelector}
onSelect={this.handleAggregateFunctionSelect}
/>
{this.body}
</BuilderCard>
Expand Down Expand Up @@ -265,17 +267,25 @@ class TagSelector extends PureComponent<Props> {

onSearchValues(index)
}

private handleAggregateFunctionSelect = (
option: BuilderAggregateFunctionType
) => {
const {index, onSetBuilderAggregateFunctionType} = this.props
onSetBuilderAggregateFunctionType(option, index)
}
}

const mstp = (state: AppState, ownProps: OwnProps): StateProps => {
const activeQueryBuilder = getActiveTimeMachine(state).queryBuilder

const {
keys,
keysSearchTerm,
keysStatus,
values,
valuesSearchTerm,
valuesStatus,
} = getActiveTimeMachine(state).queryBuilder.tags[ownProps.index]
} = activeQueryBuilder.tags[ownProps.index]

const tags = getActiveQuery(state).builderConfig.tags
const {
Expand All @@ -285,13 +295,32 @@ const mstp = (state: AppState, ownProps: OwnProps): StateProps => {
} = tags[ownProps.index]

let emptyText: string

if (ownProps.index === 0 || !tags[ownProps.index - 1].key) {
const previousTagSelector = tags[ownProps.index - 1]
if (
ownProps.index === 0 ||
!previousTagSelector ||
!previousTagSelector.key
) {
emptyText = ''
} else {
emptyText = `Select a ${tags[ownProps.index - 1].key} value first`
}

// if we're grouping, we want to be able to group on all previous tags
let {values} = activeQueryBuilder.tags[ownProps.index]
if (aggregateFunctionType === 'group') {
values = []
activeQueryBuilder.tags.forEach((tag, i) => {
// if we don't skip the current set of tags, we'll double render them at the bottom of the selector list
if (i === ownProps.index) {
return
}
tag.values.forEach(value => {
values.push(value)
})
})
}

const isInCheckOverlay = getIsInCheckOverlay(state)

return {
Expand Down
2 changes: 2 additions & 0 deletions ui/src/timeMachine/components/builderCard/BuilderCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import classnames from 'classnames'

// Components
import BuilderCardHeader from 'src/timeMachine/components/builderCard/BuilderCardHeader'
import BuilderCardDropdownHeader from 'src/timeMachine/components/builderCard/BuilderCardDropdownHeader'
import BuilderCardMenu from 'src/timeMachine/components/builderCard/BuilderCardMenu'
import BuilderCardBody from 'src/timeMachine/components/builderCard/BuilderCardBody'
import BuilderCardEmpty from 'src/timeMachine/components/builderCard/BuilderCardEmpty'
Expand All @@ -16,6 +17,7 @@ interface Props {

export default class BuilderCard extends PureComponent<Props> {
public static Header = BuilderCardHeader
public static DropdownHeader = BuilderCardDropdownHeader
public static Menu = BuilderCardMenu
public static Body = BuilderCardBody
public static Empty = BuilderCardEmpty
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// Libraries
import React, {PureComponent} from 'react'

import {SelectDropdown} from '@influxdata/clockface'
import {BuilderAggregateFunctionType} from 'src/client'

interface Props {
options: string[]
selectedOption: string
testID: string
onSelect?: (option: BuilderAggregateFunctionType) => void
onDelete?: () => void
}

const emptyFunction = () => {}

export default class BuilderCardDropdownHeader extends PureComponent<Props> {
public static defaultProps = {
testID: 'builder-card--header',
}

public render() {
const {children, options, onSelect, selectedOption, testID} = this.props

return (
<div className="builder-card--header" data-testid={testID}>
<SelectDropdown
options={options}
selectedOption={selectedOption}
testID="select-option-dropdown"
onSelect={onSelect ? onSelect : emptyFunction}
/>

{children}
{this.deleteButton}
</div>
)
}

private get deleteButton(): JSX.Element | undefined {
const {onDelete} = this.props

if (onDelete) {
return <div className="builder-card--delete" onClick={onDelete} />
}
}
}
17 changes: 14 additions & 3 deletions ui/src/timeMachine/reducers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -671,11 +671,17 @@ export const timeMachineReducer = (
const {index, builderAggregateFunctionType} = action.payload
const draftQuery = draftState.draftQueries[draftState.activeQueryIndex]

buildActiveQuery(draftState)
if (
draftQuery &&
draftQuery.builderConfig &&
draftQuery.builderConfig.tags[index]
) {

// When switching between filtering and grouping
// we want to clear out any previously selected values
draftQuery.builderConfig.tags[index].values = []

draftQuery.builderConfig.tags[
index
].aggregateFunctionType = builderAggregateFunctionType
Expand Down Expand Up @@ -761,6 +767,8 @@ export const timeMachineReducer = (

draftState.queryBuilder.tags[index].values = values
draftState.queryBuilder.tags[index].valuesStatus = RemoteDataState.Done

buildActiveQuery(draftState)
})
}

Expand Down Expand Up @@ -829,10 +837,13 @@ export const timeMachineReducer = (
const {index} = action.payload
const draftQuery = draftState.draftQueries[draftState.activeQueryIndex]

const selectedValues = draftQuery.builderConfig.tags[index].values
let selectedValues = []
if (draftQuery) {
selectedValues = draftQuery.builderConfig.tags[index].values

draftQuery.builderConfig.tags.splice(index, 1)
draftState.queryBuilder.tags.splice(index, 1)
draftQuery.builderConfig.tags.splice(index, 1)
draftState.queryBuilder.tags.splice(index, 1)
}

if (selectedValues.length) {
buildActiveQuery(draftState)
Expand Down
69 changes: 46 additions & 23 deletions ui/src/timeMachine/utils/queryBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
WINDOW_PERIOD,
} from 'src/variables/constants'
import {AGG_WINDOW_AUTO} from 'src/timeMachine/constants/queryBuilder'
import {BuilderTagsType} from '@influxdata/influx'
import {BuilderTagsType} from 'src/client/'

export function isConfigValid(builderConfig: BuilderConfig): boolean {
const {buckets, tags} = builderConfig
Expand Down Expand Up @@ -81,31 +81,44 @@ export const isCheckSaveable = (
)
}

export function buildQuery(builderConfig: BuilderConfig): string {
export function buildQuery(
builderConfig: BuilderConfig,
): string {
const {functions} = builderConfig

let query: string

if (functions.length) {
query = functions.map(f => buildQueryHelper(builderConfig, f)).join('\n\n')
query = functions
.map(f => buildQueryHelper(builderConfig, f))
.join('\n\n')
} else {
query = buildQueryHelper(builderConfig)
query = buildQueryHelper(builderConfig, null)
}

return query
}

function buildQueryHelper(
builderConfig: BuilderConfig,
fn?: BuilderConfig['functions'][0]
fn?: BuilderConfig['functions'][0],
): string {
const [bucket] = builderConfig.buckets
const tagFilterCall = formatTagFilterCall(builderConfig.tags)

const tags = Array.from(builderConfig.tags)

// todo: (bucky) - check to see if we can combine filter calls
// https://github.com/influxdata/influxdb/issues/16076
let tagsFunctionCalls = ''
tags.forEach(tag => {
tagsFunctionCalls += convertTagsToFluxFunctionString(tag)
})

const {aggregateWindow} = builderConfig
const fnCall = fn ? formatFunctionCall(fn, aggregateWindow.period) : ''

const query = `from(bucket: "${bucket}")
|> range(start: ${OPTION_NAME}.${TIME_RANGE_START}, stop: ${OPTION_NAME}.${TIME_RANGE_STOP})${tagFilterCall}${fnCall}`
|> range(start: ${OPTION_NAME}.${TIME_RANGE_START}, stop: ${OPTION_NAME}.${TIME_RANGE_STOP})${tagsFunctionCalls}${fnCall}`

return query
}
Expand All @@ -125,29 +138,39 @@ export function formatFunctionCall(
return `\n ${fnSpec.flux(formattedPeriod)}\n |> yield(name: "${fn.name}")`
}

const formatPeriod = (period: string): string => {
if (period === AGG_WINDOW_AUTO || !period) {
return `${OPTION_NAME}.${WINDOW_PERIOD}`
const convertTagsToFluxFunctionString = function convertTagsToFluxFunctionString(tag: BuilderTagsType) {
if (!tag.key) {
return ''
}

return period
}
if (tag.aggregateFunctionType === 'filter') {
if (!tag.values.length) {
return ''
}

function formatTagFilterCall(tagsSelections: BuilderConfig['tags']) {
if (!tagsSelections.length) {
return ''
const fnBody = tag.values.map(value => `r.${tag.key} == "${value}"`).join(' or ')
return `\n |> filter(fn: (r) => ${fnBody})`
}

const calls = tagsSelections
.filter(({key, values}) => key && values.length)
.map(({key, values}) => {
const fnBody = values.map(value => `r.${key} == "${value}"`).join(' or ')
if (tag.aggregateFunctionType === 'group') {
const quotedValues = tag.values.map(value => `"${value}"`) // wrap the value in double quotes

if (quotedValues.length) {
return `\n |> group(columns: [${quotedValues.join(', ')}])` // join with a comma (e.g. "foo","bar","baz")
}

return '\n |> group()'
}

return `|> filter(fn: (r) => ${fnBody})`
})
.join('\n ')
return ''
}

return `\n ${calls}`
const formatPeriod = (period: string): string => {
if (period === AGG_WINDOW_AUTO || !period) {
return `${OPTION_NAME}.${WINDOW_PERIOD}`
}

return period
}

export enum ConfirmationState {
Expand Down

0 comments on commit 50fcaf7

Please sign in to comment.