Add the ability to add labels on variables

pull/13161/head
Palak Bhojani 2019-04-04 10:44:03 -07:00
parent 891599cf3a
commit dbfe926ea0
29 changed files with 235 additions and 75 deletions

View File

@ -4,6 +4,7 @@
1. [13024](https://github.com/influxdata/influxdb/pull/13024): Add the ability to edit token's description
1. [13078](https://github.com/influxdata/influxdb/pull/13078): Add the option to create a Dashboard from a Template.
1. [13161](https://github.com/influxdata/influxdb/pull/13161): Add the ability to add labels on variables
### Bug Fixes

6
ui/package-lock.json generated
View File

@ -985,9 +985,9 @@
}
},
"@influxdata/influx": {
"version": "0.2.59",
"resolved": "https://registry.npmjs.org/@influxdata/influx/-/influx-0.2.59.tgz",
"integrity": "sha512-ow3iqzryF5a17kBbWLxCRjrLqAVAuMsVa2wSeaWikL7SgWDK3+oEi5T5StvM9SreB6+3Q0RIyct5TC+7T1QPiA==",
"version": "0.2.61",
"resolved": "https://registry.npmjs.org/@influxdata/influx/-/influx-0.2.61.tgz",
"integrity": "sha512-ScEMa+8sqC4cWX4WdMcw/rInXzZzDtQ3XswBq/OPoIFaDXt01R1iNce0Su3QdMCHPL6fpbzRUNknk3xsPhRbiQ==",
"requires": {
"axios": "^0.18.0"
}

View File

@ -137,7 +137,7 @@
},
"dependencies": {
"@influxdata/clockface": "0.0.8",
"@influxdata/influx": "0.2.59",
"@influxdata/influx": "0.2.61",
"@influxdata/react-custom-scrollbars": "4.3.8",
"@influxdata/vis": "^0.2.3",
"axios": "^0.18.0",

View File

@ -6,7 +6,7 @@ import {Overlay} from 'src/clockface'
import VariableForm from 'src/organizations/components/VariableForm'
// Types
import {Variable, Organization} from '@influxdata/influx'
import {IVariable as Variable, Organization} from '@influxdata/influx'
interface Props {
onCreateVariable: (variable: Variable) => void

View File

@ -18,11 +18,12 @@ import CreateVariableOverlay from 'src/configuration/components/CreateVariableOv
import VariableList from 'src/organizations/components/VariableList'
import FilterList from 'src/shared/components/Filter'
import AddResourceDropdown from 'src/shared/components/AddResourceDropdown'
import GetLabels from 'src/configuration/components/GetLabels'
// Types
import {OverlayState} from 'src/types'
import {AppState} from 'src/types'
import {Variable, Organization} from '@influxdata/influx'
import {IVariable as Variable, Organization} from '@influxdata/influx'
import {IconFont, ComponentSize, RemoteDataState} from '@influxdata/clockface'
import {VariablesState} from 'src/variables/reducers'
@ -75,21 +76,24 @@ class Variables extends PureComponent<Props, State> {
onSelectNew={this.handleOpenCreateOverlay}
/>
</TabbedPageHeader>
<FilterList<Variable>
searchTerm={searchTerm}
searchKeys={['name']}
list={this.variableList(variables)}
sortByKey="name"
>
{v => (
<VariableList
variables={v}
emptyState={this.emptyState}
onDeleteVariable={this.handleDeleteVariable}
onUpdateVariable={this.handleUpdateVariable}
/>
)}
</FilterList>
<GetLabels>
<FilterList<Variable>
searchTerm={searchTerm}
searchKeys={['name']}
list={this.variableList(variables)}
sortByKey="name"
>
{v => (
<VariableList
variables={v}
emptyState={this.emptyState}
onDeleteVariable={this.handleDeleteVariable}
onUpdateVariable={this.handleUpdateVariable}
onFilterChange={this.handleFilterUpdate}
/>
)}
</FilterList>
</GetLabels>
<CreateVariableOverlay
onCreateVariable={this.handleCreateVariable}
onHideOverlay={this.handleCloseCreateOverlay}
@ -136,11 +140,16 @@ class Variables extends PureComponent<Props, State> {
}
private handleFilterChange = (e: ChangeEvent<HTMLInputElement>) => {
const {value} = e.target
this.setState({searchTerm: value})
this.handleFilterUpdate(e.target.value)
}
private handleFilterBlur() {}
private handleFilterBlur(e: ChangeEvent<HTMLInputElement>) {
this.setState({searchTerm: e.target.value})
}
private handleFilterUpdate = (searchTerm: string) => {
this.setState({searchTerm})
}
private handleOpenImportOverlay = (): void => {}

View File

@ -28,7 +28,7 @@ import {moveVariable} from 'src/variables/actions'
// Types
import {AppState} from 'src/types'
import {Variable} from '@influxdata/influx'
import {IVariable as Variable} from '@influxdata/influx'
import {ComponentSize} from '@influxdata/clockface'
// Decorators

View File

@ -11,7 +11,7 @@ import {createVariable} from 'src/variables/actions'
// Types
import {AppState} from 'src/types'
import {Variable} from '@influxdata/influx'
import {IVariable as Variable} from '@influxdata/influx'
import {getActiveQuery} from 'src/timeMachine/selectors'
interface OwnProps {

View File

@ -9,7 +9,7 @@ import {Overlay} from 'src/clockface'
import VariableForm from 'src/organizations/components/VariableForm'
// Types
import {Variable} from '@influxdata/influx'
import {IVariable as Variable} from '@influxdata/influx'
interface Props {
onCreateVariable: (variable: Variable) => void

View File

@ -8,7 +8,7 @@ import {Overlay} from 'src/clockface'
import FluxEditor from 'src/shared/components/FluxEditor'
// Types
import {Variable} from '@influxdata/influx'
import {IVariable as Variable} from '@influxdata/influx'
import {
ButtonType,
ComponentColor,

View File

@ -7,7 +7,7 @@ import VariableRow from 'src/organizations/components/VariableRow'
import UpdateVariableOverlay from 'src/organizations/components/UpdateVariableOverlay'
// Types
import {Variable} from '@influxdata/influx'
import {IVariable as Variable} from '@influxdata/influx'
import {OverlayState} from 'src/types'
interface Props {
@ -15,6 +15,7 @@ interface Props {
emptyState: JSX.Element
onDeleteVariable: (variable: Variable) => void
onUpdateVariable: (variable: Variable) => void
onFilterChange: (searchTerm: string) => void
}
interface State {
@ -38,6 +39,7 @@ class VariableList extends PureComponent<Props, State> {
variables,
onDeleteVariable,
onUpdateVariable,
onFilterChange,
} = this.props
return (
@ -55,6 +57,7 @@ class VariableList extends PureComponent<Props, State> {
onDeleteVariable={onDeleteVariable}
onUpdateVariableName={onUpdateVariable}
onEditVariable={this.handleStartEdit}
onFilterChange={onFilterChange}
/>
))}
</IndexList.Body>

View File

@ -1,39 +1,77 @@
// Libraries
import React, {PureComponent} from 'react'
import {connect} from 'react-redux'
import {withRouter, WithRouterProps} from 'react-router'
// Components
import {IndexList, Alignment, Context, IconFont} from 'src/clockface'
import {
IndexList,
Alignment,
Context,
IconFont,
Stack,
ComponentSpacer,
} from 'src/clockface'
import {ComponentColor} from '@influxdata/clockface'
import InlineLabels from 'src/shared/components/inlineLabels/InlineLabels'
// Types
import {Variable} from '@influxdata/influx'
import {IVariable as Variable, ILabel} from '@influxdata/influx'
import EditableName from 'src/shared/components/EditableName'
import {AppState} from 'src/types'
// Selectors
import {viewableLabels} from 'src/labels/selectors'
// Actions
import {
addVariableLabelsAsync,
removeVariableLabelsAsync,
} from 'src/variables/actions'
import {createLabel as createLabelAsync} from 'src/labels/actions'
interface OwnProps {
variable: Variable
onDeleteVariable: (variable: Variable) => void
onUpdateVariableName: (variable: Partial<Variable>) => void
onEditVariable: (variable: Variable) => void
onFilterChange: (searchTerm: string) => void
}
type Props = OwnProps & WithRouterProps
interface StateProps {
labels: ILabel[]
}
class VariableRow extends PureComponent<Props> {
interface DispatchProps {
onAddVariableLabels: typeof addVariableLabelsAsync
onRemoveVariableLabels: typeof removeVariableLabelsAsync
onCreateLabel: typeof createLabelAsync
}
type Props = OwnProps & DispatchProps & StateProps
class VariableRow extends PureComponent<Props & WithRouterProps> {
public render() {
const {variable, onDeleteVariable} = this.props
return (
<IndexList.Row testID="variable-row">
<IndexList.Cell alignment={Alignment.Left}>
<EditableName
onUpdate={this.handleUpdateVariableName}
name={variable.name}
noNameString="NAME THIS VARIABLE"
onEditName={this.handleEditVariable}
<ComponentSpacer
stackChildren={Stack.Rows}
align={Alignment.Left}
stretchToFitWidth={true}
>
{variable.name}
</EditableName>
<EditableName
onUpdate={this.handleUpdateVariableName}
name={variable.name}
noNameString="NAME THIS VARIABLE"
onEditName={this.handleEditVariable}
>
{variable.name}
</EditableName>
{this.labels}
</ComponentSpacer>
</IndexList.Cell>
<IndexList.Cell alignment={Alignment.Left}>Query</IndexList.Cell>
<IndexList.Cell revealOnHover={true} alignment={Alignment.Right}>
@ -59,6 +97,43 @@ class VariableRow extends PureComponent<Props> {
)
}
private get labels(): JSX.Element {
const {variable, labels, onFilterChange} = this.props
const collectorLabels = viewableLabels(variable.labels)
return (
<InlineLabels
selectedLabels={collectorLabels}
labels={labels}
onFilterChange={onFilterChange}
onAddLabel={this.handleAddLabel}
onRemoveLabel={this.handleRemoveLabel}
onCreateLabel={this.handleCreateLabel}
/>
)
}
private handleAddLabel = (label: ILabel): void => {
const {variable, onAddVariableLabels} = this.props
onAddVariableLabels(variable.id, [label])
}
private handleRemoveLabel = (label: ILabel): void => {
const {variable, onRemoveVariableLabels} = this.props
onRemoveVariableLabels(variable.id, [label])
}
private handleCreateLabel = async (label: ILabel): Promise<void> => {
try {
const {name, properties} = label
await this.props.onCreateLabel(name, properties)
} catch (err) {
throw err
}
}
private handleExport = () => {
const {
router,
@ -79,4 +154,19 @@ class VariableRow extends PureComponent<Props> {
}
}
export default withRouter<OwnProps>(VariableRow)
const mstp = ({labels}: AppState): StateProps => {
return {
labels: viewableLabels(labels.list),
}
}
const mdtp: DispatchProps = {
onCreateLabel: createLabelAsync,
onAddVariableLabels: addVariableLabelsAsync,
onRemoveVariableLabels: removeVariableLabelsAsync,
}
export default connect<StateProps, DispatchProps, OwnProps>(
mstp,
mdtp
)(withRouter<Props>(VariableRow))

View File

@ -21,11 +21,12 @@ import CreateVariableOverlay from 'src/organizations/components/CreateVariableOv
import VariableList from 'src/organizations/components/VariableList'
import FilterList from 'src/shared/components/Filter'
import AddResourceDropdown from 'src/shared/components/AddResourceDropdown'
import GetLabels from 'src/configuration/components/GetLabels'
// Types
import {OverlayState, RemoteDataState} from 'src/types'
import {AppState} from 'src/types'
import {Variable, Organization} from '@influxdata/influx'
import {IVariable as Variable, Organization} from '@influxdata/influx'
import {IconFont, ComponentSize} from '@influxdata/clockface'
interface StateProps {
@ -92,21 +93,24 @@ class Variables extends PureComponent<Props, State> {
onSelectNew={this.handleOpenCreateOverlay}
/>
</TabbedPageHeader>
<FilterList<Variable>
searchTerm={searchTerm}
searchKeys={['name']}
list={variables}
sortByKey="name"
>
{variables => (
<VariableList
variables={variables}
emptyState={this.emptyState}
onDeleteVariable={this.handleDeleteVariable}
onUpdateVariable={this.handleUpdateVariable}
/>
)}
</FilterList>
<GetLabels>
<FilterList<Variable>
searchTerm={searchTerm}
searchKeys={['name', 'labels[].name']}
list={variables}
sortByKey="name"
>
{variables => (
<VariableList
variables={variables}
emptyState={this.emptyState}
onDeleteVariable={this.handleDeleteVariable}
onUpdateVariable={this.handleUpdateVariable}
onFilterChange={this.handleFilterUpdate}
/>
)}
</FilterList>
</GetLabels>
<Overlay visible={createOverlayState === OverlayState.Open}>
<CreateVariableOverlay
onCreateVariable={this.handleCreateVariable}
@ -148,11 +152,16 @@ class Variables extends PureComponent<Props, State> {
}
private handleFilterChange = (e: ChangeEvent<HTMLInputElement>) => {
const {value} = e.target
this.setState({searchTerm: value})
this.handleFilterUpdate(e.target.value)
}
private handleFilterBlur() {}
private handleFilterBlur = (e: ChangeEvent<HTMLInputElement>): void => {
this.setState({searchTerm: e.target.value})
}
private handleFilterUpdate = (searchTerm: string) => {
this.setState({searchTerm})
}
private handleOpenImportOverlay = (): void => {
const {router, org} = this.props

View File

@ -19,7 +19,7 @@ exports[`VariableList rendering renders 1`] = `
columnCount={3}
emptyState={<React.Fragment />}
>
<withRouter(VariableRow)
<Connect(withRouter(VariableRow))
key="variable-undefined"
onDeleteVariable={[MockFunction]}
onEditVariable={[Function]}

View File

@ -272,3 +272,13 @@ export const memberRemoveFailed = (message: string): Notification => ({
...defaultErrorNotification,
message: `Failed to remove members: "${message}"`,
})
export const addVariableLabelFailed = (): Notification => ({
...defaultErrorNotification,
message: `Failed to add label to variables`,
})
export const removeVariableLabelFailed = (): Notification => ({
...defaultErrorNotification,
message: `Failed to remove label from variables`,
})

View File

@ -3,7 +3,7 @@ import {isInQuery} from 'src/variables/utils/hydrateVars'
// Types
import {QueryView} from 'src/types/dashboards'
import {Variable, View} from '@influxdata/influx'
import {IVariable as Variable, View} from '@influxdata/influx'
/*
Given a collection variables and a collection of views, return only the

View File

@ -4,7 +4,7 @@ import {
taskToTemplate,
variableToTemplate,
} from 'src/shared/utils/resourceToTemplate'
import {TemplateType, Variable} from '@influxdata/influx'
import {TemplateType, IVariable as Variable} from '@influxdata/influx'
import {Label, Task, TaskStatus} from 'src/types'
import {createVariable} from 'src/variables/mocks'
@ -39,6 +39,7 @@ const myVariable: Variable = {
orgID: '039aa15b38cb0000',
name: 'beep',
selected: null,
labels: [],
arguments: {
type: 'query',
values: {

View File

@ -5,7 +5,7 @@ import {
TemplateType,
DocumentCreate,
ITemplate,
Variable,
IVariable as Variable,
} from '@influxdata/influx'
import {viewableLabels} from 'src/labels/selectors'

View File

@ -1,5 +1,5 @@
import {TemplateType, LabelIncluded, VariableIncluded} from 'src/types'
import {ILabel, Variable} from '@influxdata/influx'
import {ILabel, IVariable as Variable} from '@influxdata/influx'
export function findIncludedsFromRelationships<
T extends {id: string; type: TemplateType}

View File

@ -7,7 +7,7 @@ import {client} from 'src/utils/api'
// Types
import {RemoteDataState} from 'src/types'
import {Variable} from '@influxdata/influx'
import {IVariable as Variable} from '@influxdata/influx'
interface Props {
children: (variables: Variable[], loading: RemoteDataState) => JSX.Element

View File

@ -6,7 +6,7 @@ import VariableTooltipContents from 'src/timeMachine/components/variableToolbar/
import BoxTooltip from 'src/shared/components/BoxTooltip'
// Types
import {Variable} from '@influxdata/influx'
import {IVariable as Variable} from '@influxdata/influx'
import VariableLabel from 'src/timeMachine/components/variableToolbar/VariableLabel'
interface Props {

View File

@ -11,7 +11,7 @@ import VariableItem from 'src/timeMachine/components/variableToolbar/VariableIte
import {getVariablesForOrg} from 'src/variables/selectors'
// Types
import {Variable} from '@influxdata/influx'
import {IVariable as Variable} from '@influxdata/influx'
import {AppState} from 'src/types'
interface OwnProps {

View File

@ -1,6 +1,6 @@
import {
ILabel,
Variable,
IVariable as Variable,
IDashboard,
DocumentListEntry,
Document,

View File

@ -32,8 +32,12 @@ import * as copy from 'src/shared/copy/notifications'
import {Dispatch} from 'redux-thunk'
import {RemoteDataState, VariableTemplate} from 'src/types'
import {GetState} from 'src/types'
import {Variable} from '@influxdata/influx'
import {IVariable as Variable, ILabel as Label} from '@influxdata/influx'
import {VariableValuesByID} from 'src/variables/types'
import {
addVariableLabelFailed,
removeVariableLabelFailed,
} from 'src/shared/copy/v2/notifications'
export type Action =
| SetVariables
@ -295,3 +299,33 @@ export const convertToTemplate = (variableID: string) => async (
dispatch(notify(copy.createTemplateFailed(error)))
}
}
export const addVariableLabelsAsync = (
variableID: string,
labels: Label[]
) => async (dispatch): Promise<void> => {
try {
await client.variables.addLabels(variableID, labels.map(l => l.id))
const variable = await client.variables.get(variableID)
dispatch(setVariable(variableID, RemoteDataState.Done, variable))
} catch (error) {
console.error(error)
dispatch(addVariableLabelFailed())
}
}
export const removeVariableLabelsAsync = (
variableID: string,
labels: Label[]
) => async (dispatch): Promise<void> => {
try {
await client.variables.removeLabels(variableID, labels.map(l => l.id))
const variable = await client.variables.get(variableID)
dispatch(setVariable(variableID, RemoteDataState.Done, variable))
} catch (error) {
console.error(error)
dispatch(removeVariableLabelFailed())
}
}

View File

@ -1,5 +1,5 @@
// Types
import {Variable} from '@influxdata/influx'
import {IVariable as Variable} from '@influxdata/influx'
export const createVariable = (
name: string,
@ -10,6 +10,7 @@ export const createVariable = (
id: name,
orgID: 'howdy',
selected: selected ? [selected] : [],
labels: [],
arguments: {
type: 'query',
values: {

View File

@ -6,7 +6,7 @@ import {get} from 'lodash'
import {RemoteDataState} from 'src/types'
import {VariableValuesByID} from 'src/variables/types'
import {Action} from 'src/variables/actions'
import {Variable} from '@influxdata/influx'
import {IVariable as Variable} from '@influxdata/influx'
export const initialState = (): VariablesState => ({
status: RemoteDataState.NotStarted,

View File

@ -14,7 +14,7 @@ import {
VariableValuesByID,
ValueSelections,
} from 'src/variables/types'
import {Variable} from '@influxdata/influx'
import {IVariable as Variable} from '@influxdata/influx'
type VariablesState = AppState['variables']['variables']
type ValuesState = AppState['variables']['values']['contextID']

View File

@ -6,7 +6,7 @@ import {
} from 'src/variables/utils/hydrateVars'
// Types
import {Variable} from '@influxdata/influx'
import {IVariable as Variable} from '@influxdata/influx'
const getDescendantsFromGraph = (
variable: Variable,

View File

@ -183,6 +183,7 @@ describe('hydrate vars', () => {
id: 'b',
name: 'b',
orgID: '',
labels: [],
arguments: {
type: 'map',
values: {
@ -224,6 +225,7 @@ describe('hydrate vars', () => {
id: 'b',
name: 'b',
orgID: '',
labels: [],
arguments: {
type: 'constant',
values: ['v1', 'v2'],

View File

@ -9,7 +9,7 @@ import {OPTION_NAME, BOUNDARY_GROUP} from 'src/variables/constants/index'
// Types
import {RemoteDataState} from 'src/types'
import {Variable} from '@influxdata/influx'
import {IVariable as Variable} from '@influxdata/influx'
import {WrappedCancelablePromise, CancellationError} from 'src/types/promises'
import {
VariableValues,