Skip to content

Commit

Permalink
feat: Add group() to Query Builder
Browse files Browse the repository at this point in the history
  • Loading branch information
hoorayimhelping committed Dec 23, 2019
1 parent c9431bc commit b4924ad
Show file tree
Hide file tree
Showing 12 changed files with 366 additions and 52 deletions.
11 changes: 6 additions & 5 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@
### Features

1. [16234](https://github.com/influxdata/influxdb/pull/16234): Add support for notification endpoints to influx templates/pkgs.
2. [16242](https://github.com/influxdata/influxdb/pull/16242): Drop id prefix for secret key requirement for notification endpoints
3. [16259](https://github.com/influxdata/influxdb/pull/16259): Add support for check resource to pkger parser
4. [16262](https://github.com/influxdata/influxdb/pull/16262): Add support for check resource pkger dry run functionality
5. [16275](https://github.com/influxdata/influxdb/pull/16275): Add support for check resource pkger apply functionality
6. [16283](https://github.com/influxdata/influxdb/pull/16283): Add support for check resource pkger export functionality
1. [16242](https://github.com/influxdata/influxdb/pull/16242): Drop id prefix for secret key requirement for notification endpoints
1. [16259](https://github.com/influxdata/influxdb/pull/16259): Add support for check resource to pkger parser
1. [16262](https://github.com/influxdata/influxdb/pull/16262): Add support for check resource pkger dry run functionality
1. [16275](https://github.com/influxdata/influxdb/pull/16275): Add support for check resource pkger apply functionality
1. [16283](https://github.com/influxdata/influxdb/pull/16283): Add support for check resource pkger export functionality
1. [16212](https://github.com/influxdata/influxdb/pull/16212): Add new kv.ForwardCursor interface
1. [16297](https://github.com/influxdata/influxdb/pull/16297): Add support for notification rule to pkger parser
1. [16298](https://github.com/influxdata/influxdb/pull/16298): Add support for notification rule pkger dry run functionality
Expand All @@ -16,6 +16,7 @@
1. [16320](https://github.com/influxdata/influxdb/pull/16320): Add support for tasks to pkger parser
1. [16322](https://github.com/influxdata/influxdb/pull/16322): Add support for tasks to pkger dry run functionality
1. [16323](https://github.com/influxdata/influxdb/pull/16323): Add support for tasks to pkger apply functionality
1. [16226](https://github.com/influxdata/influxdb/pull/16226): Add group() to Query Builder

### Bug Fixes

Expand Down
28 changes: 15 additions & 13 deletions ui/cypress/e2e/explorer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,12 @@ describe('DataExplorer', () => {
describe('raw script editing', () => {
beforeEach(() => {
cy.getByTestID('switch-to-script-editor').click()

cy.get<$CM>('.CodeMirror').then($cm => {
const cm = $cm[0].CodeMirror
cy.wrap(cm.doc).as('flux')
expect(cm.doc.getValue()).to.eq('')
})
})

it('enables the submit button when a query is typed', () => {
Expand All @@ -376,12 +382,6 @@ describe('DataExplorer', () => {
it('imports the appropriate packages to build a query', () => {
cy.getByTestID('functions-toolbar-tab').click()

cy.get<$CM>('.CodeMirror').then($cm => {
const cm = $cm[0].CodeMirror
cy.wrap(cm.doc).as('flux')
expect(cm.doc.getValue()).to.eq('')
})

cy.getByTestID('flux-function from').click()
cy.getByTestID('flux-function range').click()
cy.getByTestID('flux-function math.abs').click()
Expand All @@ -408,12 +408,6 @@ describe('DataExplorer', () => {
it('can use the function selector to build a query', () => {
cy.getByTestID('functions-toolbar-tab').click()

cy.get<$CM>('.CodeMirror').then($cm => {
const cm = $cm[0].CodeMirror
cy.wrap(cm.doc).as('flux')
expect(cm.doc.getValue()).to.eq('')
})

cy.getByTestID('flux-function from').click()

cy.get<Doc>('@flux').then(doc => {
Expand Down Expand Up @@ -449,6 +443,10 @@ describe('DataExplorer', () => {

it('shows the empty state when the query returns no results', () => {
cy.getByTestID('time-machine--bottom').within(() => {
// cy.wait is necessary - the {force: true} (which is also necessary with codemirror)
// causes cypress not to wait for actionability. The result is that it starts typing
// before the textarea is loaded.
cy.wait(400)
cy.get('textarea').type(
`from(bucket: "defbuck")
|> range(start: -10s)
Expand All @@ -464,7 +462,11 @@ describe('DataExplorer', () => {
it('can save query as task even when it has a variable', () => {
const taskName = 'tax'
// begin flux
cy.getByTestID('flux-editor').within(() => {
cy.getByTestID('time-machine--bottom').within(() => {
// cy.wait is necessary - the {force: true} (which is also necessary with codemirror)
// causes cypress not to wait for actionability. The result is that it starts typing
// before the textarea is loaded.
cy.wait(400)
cy.get('textarea').type(
`from(bucket: "defbuck")
|> range(start: -15m, stop: now())
Expand Down
2 changes: 2 additions & 0 deletions ui/src/shared/utils/featureFlag.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export const OSS_FLAGS = {
monacoEditor: false,
downloadCellCSV: false,
telegrafEditor: false,
queryBuilderGrouping: false,
}

export const CLOUD_FLAGS = {
Expand All @@ -16,6 +17,7 @@ export const CLOUD_FLAGS = {
cloudBilling: CLOUD_BILLING_VISIBLE, // should be visible in dev and acceptance, but not in cloud
downloadCellCSV: false,
telegrafEditor: false,
queryBuilderGrouping: false,
}

export const isFlagEnabled = (flagName: string, equals?: string | boolean) => {
Expand Down
17 changes: 13 additions & 4 deletions ui/src/timeMachine/actions/queryBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,11 @@ import {

// Types
import {Dispatch} from 'redux-thunk'
import {GetState, RemoteDataState} from 'src/types'
import {BuilderAggregateFunctionType} from 'src/client/generatedRoutes'
import {
BuilderAggregateFunctionType,
GetState,
RemoteDataState,
} from 'src/types'
import {BuilderFunctionsType} from '@influxdata/influx'

export type Action =
Expand Down Expand Up @@ -285,15 +288,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 +306,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
74 changes: 54 additions & 20 deletions ui/src/timeMachine/components/TagSelector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,17 +31,22 @@ import {
} from 'src/timeMachine/actions/queryBuilder'

// Utils
import {toComponentStatus} from 'src/shared/utils/toComponentStatus'
import DefaultDebouncer from 'src/shared/utils/debouncer'
import {isFlagEnabled} from 'src/shared/utils/featureFlag'
import {toComponentStatus} from 'src/shared/utils/toComponentStatus'
import {
getActiveQuery,
getActiveTagValues,
getActiveTimeMachine,
getIsInCheckOverlay,
} from 'src/timeMachine/selectors'

// Types
import {AppState, RemoteDataState} from 'src/types'
import {BuilderAggregateFunctionType} from 'src/client'
import {
AppState,
BuilderAggregateFunctionType,
RemoteDataState,
} from 'src/types'

const SEARCH_DEBOUNCE_MS = 500

Expand Down Expand Up @@ -80,8 +85,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 @@ -92,19 +95,32 @@ class TagSelector extends PureComponent<Props> {
}

public render() {
const {aggregateFunctionType, index} = this.props

return (
<BuilderCard>
<BuilderCard.Header
title={this.renderAggregateFunctionType(aggregateFunctionType)}
onDelete={index !== 0 && this.handleRemoveTagSelector}
/>
{this.header}
{this.body}
</BuilderCard>
)
}

private get header() {
const {aggregateFunctionType, index} = this.props

return isFlagEnabled('queryBuilderGrouping') ? (
<BuilderCard.DropdownHeader
options={['filter', 'group']}
selectedOption={this.renderAggregateFunctionType(aggregateFunctionType)}
onDelete={index !== 0 && this.handleRemoveTagSelector}
onSelect={this.handleAggregateFunctionSelect}
/>
) : (
<BuilderCard.Header
title={this.renderAggregateFunctionType(aggregateFunctionType)}
onDelete={index !== 0 && this.handleRemoveTagSelector}
/>
)
}

private get body() {
const {
index,
Expand Down Expand Up @@ -265,33 +281,51 @@ 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 {
key: selectedKey,
values: selectedValues,
aggregateFunctionType,
} = 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`
}

const {
key: selectedKey,
values: selectedValues,
aggregateFunctionType,
} = tags[ownProps.index]

const values = getActiveTagValues(
activeQueryBuilder.tags,
aggregateFunctionType,
ownProps.index
)
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/types'

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} />
}
}
}
16 changes: 13 additions & 3 deletions ui/src/timeMachine/reducers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -671,11 +671,16 @@ 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 +766,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 +836,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
Loading

0 comments on commit b4924ad

Please sign in to comment.