Merge pull request #12611 from influxdata/feat/add-variables-config-page

Add variables to configuration page and navigation for config tabs
pull/12614/head
Palakp41 2019-03-13 15:21:28 -07:00 committed by GitHub
commit bddc73a614
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 310 additions and 16 deletions

41
ui/package-lock.json generated
View File

@ -6106,7 +6106,8 @@
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
"integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=",
"dev": true
"dev": true,
"optional": true
},
"aproba": {
"version": "1.2.0",
@ -6130,13 +6131,15 @@
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
"integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=",
"dev": true
"dev": true,
"optional": true
},
"brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"dev": true,
"optional": true,
"requires": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
@ -6153,19 +6156,22 @@
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz",
"integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=",
"dev": true
"dev": true,
"optional": true
},
"concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
"dev": true
"dev": true,
"optional": true
},
"console-control-strings": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz",
"integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=",
"dev": true
"dev": true,
"optional": true
},
"core-util-is": {
"version": "1.0.2",
@ -6296,7 +6302,8 @@
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
"integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=",
"dev": true
"dev": true,
"optional": true
},
"ini": {
"version": "1.3.5",
@ -6310,6 +6317,7 @@
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz",
"integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=",
"dev": true,
"optional": true,
"requires": {
"number-is-nan": "^1.0.0"
}
@ -6326,6 +6334,7 @@
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
"dev": true,
"optional": true,
"requires": {
"brace-expansion": "^1.1.7"
}
@ -6334,13 +6343,15 @@
"version": "0.0.8",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
"integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=",
"dev": true
"dev": true,
"optional": true
},
"minipass": {
"version": "2.2.4",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-2.2.4.tgz",
"integrity": "sha512-hzXIWWet/BzWhYs2b+u7dRHlruXhwdgvlTMDKC6Cb1U7ps6Ac6yQlR39xsbjWJE377YTCtKwIXIpJ5oP+j5y8g==",
"dev": true,
"optional": true,
"requires": {
"safe-buffer": "^5.1.1",
"yallist": "^3.0.0"
@ -6361,6 +6372,7 @@
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
"integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
"dev": true,
"optional": true,
"requires": {
"minimist": "0.0.8"
}
@ -6449,7 +6461,8 @@
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz",
"integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=",
"dev": true
"dev": true,
"optional": true
},
"object-assign": {
"version": "4.1.1",
@ -6463,6 +6476,7 @@
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
"integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
"dev": true,
"optional": true,
"requires": {
"wrappy": "1"
}
@ -6558,7 +6572,8 @@
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz",
"integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==",
"dev": true
"dev": true,
"optional": true
},
"safer-buffer": {
"version": "2.1.2",
@ -6600,6 +6615,7 @@
"resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
"integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
"dev": true,
"optional": true,
"requires": {
"code-point-at": "^1.0.0",
"is-fullwidth-code-point": "^1.0.0",
@ -6621,6 +6637,7 @@
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
"integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
"dev": true,
"optional": true,
"requires": {
"ansi-regex": "^2.0.0"
}
@ -6669,13 +6686,15 @@
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
"dev": true
"dev": true,
"optional": true
},
"yallist": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.2.tgz",
"integrity": "sha1-hFK0u36Dx8GI2AQcGoN8dz1ti7k=",
"dev": true
"dev": true,
"optional": true
}
}
},

View File

@ -14,6 +14,7 @@ import Settings from 'src/me/components/account/Settings'
import Tokens from 'src/me/components/account/Tokens'
import Buckets from 'src/configuration/components/Buckets'
import Telegrafs from 'src/configuration/components/Telegrafs'
import Variables from 'src/configuration/components/Variables'
// Decorators
import {ErrorHandling} from 'src/shared/decorators/errors'
@ -75,6 +76,15 @@ class ConfigurationPage extends Component<Props> {
</GetResources>
</GetResources>
</TabbedPageSection>
<TabbedPageSection
id="variables_tab"
url="variables_tab"
title="Variables"
>
<GetResources resource={ResourceTypes.Variables}>
<Variables />
</GetResources>
</TabbedPageSection>
<TabbedPageSection
id="tokens_tab"
url="tokens_tab"

View File

@ -0,0 +1,48 @@
// Libraries
import React, {PureComponent} from 'react'
// Styles
import 'src/organizations/components/CreateVariableOverlay.scss'
// Components
import {Overlay} from 'src/clockface'
import VariableForm from 'src/organizations/components/VariableForm'
// Types
import {Variable, Organization} from '@influxdata/influx'
interface Props {
onCreateVariable: (variable: Variable) => void
onHideOverlay: () => void
orgs: Organization[]
visible: boolean
initialScript?: string
}
export default class CreateVariableOverlay extends PureComponent<Props> {
public render() {
const {
onHideOverlay,
onCreateVariable,
initialScript,
orgs,
visible,
} = this.props
return (
<Overlay visible={visible}>
<Overlay.Container maxWidth={1000}>
<Overlay.Heading title="Create Variable" onDismiss={onHideOverlay} />
<Overlay.Body>
<VariableForm
onCreateVariable={onCreateVariable}
onHideOverlay={onHideOverlay}
orgID={orgs[0].id} //TODO: display a list of orgs and have the user pick one
initialScript={initialScript}
/>
</Overlay.Body>
</Overlay.Container>
</Overlay>
)
}
}

View File

@ -7,6 +7,7 @@ import {connect} from 'react-redux'
import {getLabels} from 'src/labels/actions'
import {getBuckets} from 'src/buckets/actions'
import {getTelegrafs} from 'src/telegrafs/actions'
import {getVariables} from 'src/variables/actions'
// Types
import {AppState} from 'src/types/v2'
@ -20,12 +21,14 @@ import {ErrorHandling} from 'src/shared/decorators/errors'
import {TechnoSpinner, SpinnerContainer} from '@influxdata/clockface'
import {getAuthorizations} from 'src/authorizations/actions'
import {AuthorizationsState} from 'src/authorizations/reducers'
import {VariablesState} from 'src/variables/reducers'
interface StateProps {
org: Organization
labels: LabelsState
buckets: BucketsState
telegrafs: TelegrafsState
variables: VariablesState
tokens: AuthorizationsState
}
@ -33,6 +36,7 @@ interface DispatchProps {
getLabels: typeof getLabels
getBuckets: typeof getBuckets
getTelegrafs: typeof getTelegrafs
getVariables: typeof getVariables
getAuthorizations: typeof getAuthorizations
}
@ -46,6 +50,7 @@ export enum ResourceTypes {
Labels = 'labels',
Buckets = 'buckets',
Telegrafs = 'telegrafs',
Variables = 'variables',
Authorizations = 'tokens',
}
@ -65,6 +70,10 @@ class GetResources extends PureComponent<Props, StateProps> {
return await this.props.getTelegrafs()
}
case ResourceTypes.Variables: {
return await this.props.getVariables()
}
case ResourceTypes.Authorizations: {
return await this.props.getAuthorizations()
}
@ -94,6 +103,7 @@ const mstp = ({
labels,
buckets,
telegrafs,
variables,
tokens,
}: AppState): StateProps => {
const org = orgs[0]
@ -102,6 +112,7 @@ const mstp = ({
labels,
buckets,
telegrafs,
variables,
tokens,
org,
}
@ -111,6 +122,7 @@ const mdtp = {
getLabels: getLabels,
getBuckets: getBuckets,
getTelegrafs: getTelegrafs,
getVariables: getVariables,
getAuthorizations: getAuthorizations,
}

View File

@ -0,0 +1,199 @@
// Libraries
import React, {PureComponent, ChangeEvent} from 'react'
import _ from 'lodash'
import {connect} from 'react-redux'
// Utils
import {
getVariables,
createVariable,
updateVariable,
deleteVariable,
} from 'src/variables/actions'
// Components
import TabbedPageHeader from 'src/shared/components/tabbed_page/TabbedPageHeader'
import CreateVariableOverlay from 'src/configuration/components/CreateVariableOverlay'
import {Button, ComponentSize, RemoteDataState} from '@influxdata/clockface'
import VariableList from 'src/organizations/components/VariableList'
import {Input, EmptyState} from 'src/clockface'
import FilterList from 'src/shared/components/Filter'
import AddResourceDropdown from 'src/shared/components/AddResourceDropdown'
// Types
import {ComponentColor, IconFont} from '@influxdata/clockface'
import {OverlayState} from 'src/types'
import {AppState} from 'src/types/v2'
import {Variable, Organization} from '@influxdata/influx'
import {VariablesState} from 'src/variables/reducers'
interface StateProps {
variables: VariablesState
orgs: Organization[]
}
interface DispatchProps {
onCreateVariable: typeof createVariable
onUpdateVariable: typeof updateVariable
onDeleteVariable: typeof deleteVariable
}
type Props = StateProps & DispatchProps
interface State {
searchTerm: string
createOverlayState: OverlayState
importOverlayState: OverlayState
variables: Variable[]
}
class Variables extends PureComponent<Props, State> {
public state: State = {
searchTerm: '',
createOverlayState: OverlayState.Closed,
importOverlayState: OverlayState.Closed,
variables: this.variableList(this.props.variables),
}
public render() {
const {orgs, variables} = this.props
const {searchTerm, createOverlayState} = this.state
return (
<>
<TabbedPageHeader>
<Input
icon={IconFont.Search}
placeholder="Filter variables..."
widthPixels={290}
value={searchTerm}
onChange={this.handleFilterChange}
onBlur={this.handleFilterBlur}
/>
<AddResourceDropdown
resourceName="Variable"
onSelectImport={this.handleOpenImportOverlay}
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>
<CreateVariableOverlay
onCreateVariable={this.handleCreateVariable}
onHideOverlay={this.handleCloseCreateOverlay}
orgs={orgs}
visible={createOverlayState === OverlayState.Open}
/>
</>
)
}
private get emptyState(): JSX.Element {
const {orgs} = this.props
const {searchTerm} = this.state
if (!searchTerm) {
return (
<EmptyState size={ComponentSize.Large}>
<EmptyState.Text
text={`${
orgs[0].name
} does not own any Variables , why not create one?`}
highlightWords={['Variables']}
/>
<Button
size={ComponentSize.Medium}
text="Create Variable"
icon={IconFont.Plus}
color={ComponentColor.Primary}
onClick={this.handleOpenCreateOverlay}
/>
</EmptyState>
)
}
return (
<EmptyState size={ComponentSize.Large}>
<EmptyState.Text text="No Variables match your query" />
</EmptyState>
)
}
private variableList(variables: VariablesState): Variable[] {
return Object.values(variables.variables)
.filter(d => d.status === RemoteDataState.Done)
.map(d => d.variable)
}
private handleFilterChange = (e: ChangeEvent<HTMLInputElement>) => {
const {value} = e.target
this.setState({searchTerm: value})
}
private handleFilterBlur() {}
private handleOpenImportOverlay = (): void => {}
private handleOpenCreateOverlay = (): void => {
this.setState({createOverlayState: OverlayState.Open})
}
private handleCloseCreateOverlay = (): void => {
this.setState({createOverlayState: OverlayState.Closed})
}
private handleCreateVariable = async (variable: Variable): Promise<void> => {
// TODO(chnn): Remove this handler in favor of connecting child components
// directly to Redux, and the same for `handleUpdateVariable` and
// `handleDeleteVariable`
const {onCreateVariable} = this.props
try {
await onCreateVariable(variable)
this.handleCloseCreateOverlay()
} catch (e) {}
}
private handleUpdateVariable = (variable: Partial<Variable>): void => {
const {onUpdateVariable} = this.props
onUpdateVariable(variable.id, variable)
}
private handleDeleteVariable = (variable: Variable): void => {
const {onDeleteVariable} = this.props
onDeleteVariable(variable.id)
}
}
const mstp = ({variables, orgs}: AppState): StateProps => {
return {
variables: variables,
orgs,
}
}
const mdtp = {
onGetVariables: getVariables,
onCreateVariable: createVariable,
onUpdateVariable: updateVariable,
onDeleteVariable: deleteVariable,
}
export default connect<StateProps, DispatchProps, {}>(
mstp,
mdtp
)(Variables)

View File

@ -102,6 +102,12 @@ class SideNav extends PureComponent<Props> {
location={location.pathname}
highlightPaths={['telegrafs_tab']}
/>
<NavMenu.SubItem
title="Variables"
link="/configuration/variables_tab"
location={location.pathname}
highlightPaths={['variables_tab']}
/>
<NavMenu.SubItem
title="Profile"
link="/configuration/settings_tab"

View File

@ -95,7 +95,7 @@ export const getVariables = () => async (dispatch: Dispatch<Action>) => {
dispatch(setVariables(RemoteDataState.Done, variables))
} catch (e) {
console.log(e)
console.error(e)
dispatch(setVariables(RemoteDataState.Error))
dispatch(notify(getVariablesFailed()))
}
@ -111,7 +111,7 @@ export const getVariable = (id: string) => async (
dispatch(setVariable(id, RemoteDataState.Done, variable))
} catch (e) {
console.log(e)
console.error(e)
dispatch(setVariable(id, RemoteDataState.Error))
dispatch(notify(getVariableFailed()))
}
@ -128,7 +128,7 @@ export const createVariable = (variable: Variable) => async (
)
dispatch(notify(createVariableSuccess(variable.name)))
} catch (e) {
console.log(e)
console.error(e)
dispatch(notify(createVariableFailed()))
}
}
@ -143,7 +143,7 @@ export const updateVariable = (id: string, props: Partial<Variable>) => async (
dispatch(setVariable(id, RemoteDataState.Done, variable))
} catch (e) {
console.log(e)
console.error(e)
dispatch(setVariable(id, RemoteDataState.Error))
dispatch(notify(updateVariableFailed()))
}
@ -159,7 +159,7 @@ export const deleteVariable = (id: string) => async (
dispatch(removeVariable(id))
} catch (e) {
console.log(e)
console.error(e)
dispatch(setVariable(id, RemoteDataState.Done))
dispatch(notify(deleteVariableFailed()))
}