Merge pull request #14189 from influxdata/feat/task-token

Add dropdown to select token when creating a new task
pull/14263/head
Palakp41 2019-06-24 14:50:31 -07:00 committed by GitHub
commit 79dec36d36
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 265 additions and 74 deletions

View File

@ -14,6 +14,7 @@
### Features
1. [14130](https://github.com/influxdata/influxdb/pull/14130): Add static templates for system, docker, redis, kubernetes
1. [14189] (https://github.com/influxdata/influxdb/pull/14189): Add option to select a token when creating a task
## v2.0.0-alpha.12 [2019-06-13]

View File

@ -107,6 +107,15 @@ export const getAuthorizations = () => async (
}
}
export const getAuthorization = async (authorizationID: string) => {
try {
const authorization = await client.authorizations.get(authorizationID)
return authorization
} catch (e) {
console.error(e)
}
}
export const createAuthorization = (auth: Authorization) => async (
dispatch: Dispatch<Action | NotificationAction>
) => {

View File

@ -13,7 +13,9 @@ import {
setTaskOption,
clearTask,
setNewScript,
setTaskToken,
} from 'src/tasks/actions'
import {getAuthorizations} from 'src/authorizations/actions'
// Utils
import {getActiveTimeMachine} from 'src/timeMachine/selectors'
@ -26,13 +28,15 @@ import {
} from 'src/utils/taskOptionsToFluxScript'
// Types
import {AppState, TimeRange} from 'src/types'
import {AppState, TimeRange, RemoteDataState} from 'src/types'
import {
TaskSchedule,
TaskOptions,
TaskOptionKeys,
} from 'src/utils/taskOptionsToFluxScript'
import {DashboardDraftQuery} from 'src/types/dashboards'
import {Authorization} from '@influxdata/influx'
import {SpinnerContainer, TechnoSpinner} from '@influxdata/clockface'
interface OwnProps {
dismiss: () => void
@ -43,6 +47,8 @@ interface DispatchProps {
setTaskOption: typeof setTaskOption
clearTask: typeof clearTask
setNewScript: typeof setNewScript
getTokens: typeof getAuthorizations
setTaskToken: typeof setTaskToken
}
interface StateProps {
@ -51,12 +57,15 @@ interface StateProps {
activeQueryIndex: number
newScript: string
timeRange: TimeRange
tokens: Authorization[]
tokenStatus: RemoteDataState
selectedToken: Authorization
}
type Props = StateProps & OwnProps & DispatchProps
class SaveAsTaskForm extends PureComponent<Props & WithRouterProps> {
public componentDidMount() {
public async componentDidMount() {
const {setTaskOption, setNewScript} = this.props
setTaskOption({
@ -65,6 +74,10 @@ class SaveAsTaskForm extends PureComponent<Props & WithRouterProps> {
})
setNewScript(this.activeScript)
await this.props.getTokens()
if (this.props.tokens.length > 0) {
this.props.setTaskToken(this.props.tokens[0])
}
}
public componentWillUnmount() {
@ -74,19 +87,33 @@ class SaveAsTaskForm extends PureComponent<Props & WithRouterProps> {
}
public render() {
const {taskOptions, dismiss} = this.props
const {
taskOptions,
dismiss,
tokens,
tokenStatus,
selectedToken,
} = this.props
return (
<TaskForm
taskOptions={taskOptions}
onChangeScheduleType={this.handleChangeScheduleType}
onChangeInput={this.handleChangeInput}
onChangeToBucketName={this.handleChangeToBucketName}
isInOverlay={true}
onSubmit={this.handleSubmit}
canSubmit={this.isFormValid}
dismiss={dismiss}
/>
<SpinnerContainer
loading={tokenStatus}
spinnerComponent={<TechnoSpinner />}
>
<TaskForm
taskOptions={taskOptions}
onChangeScheduleType={this.handleChangeScheduleType}
onChangeInput={this.handleChangeInput}
onChangeToBucketName={this.handleChangeToBucketName}
isInOverlay={true}
onSubmit={this.handleSubmit}
canSubmit={this.isFormValid}
dismiss={dismiss}
tokens={tokens}
selectedToken={selectedToken}
onTokenChange={this.handleTokenChange}
/>
</SpinnerContainer>
)
}
@ -106,7 +133,13 @@ class SaveAsTaskForm extends PureComponent<Props & WithRouterProps> {
}
private handleSubmit = async () => {
const {saveNewScript, newScript, taskOptions, timeRange} = this.props
const {
saveNewScript,
newScript,
taskOptions,
timeRange,
selectedToken,
} = this.props
// When a task runs, it does not have access to variables that we typically
// inject into the script via the front end. So any variables that are used
@ -127,7 +160,7 @@ class SaveAsTaskForm extends PureComponent<Props & WithRouterProps> {
const preamble = `${varOption}\n\n${taskOption}`
const script = addDestinationToFluxScript(newScript, taskOptions)
saveNewScript(script, preamble)
saveNewScript(script, preamble, selectedToken.token)
}
private handleChangeToBucketName = (bucketName: string) => {
@ -150,11 +183,16 @@ class SaveAsTaskForm extends PureComponent<Props & WithRouterProps> {
setTaskOption({key, value})
}
private handleTokenChange = (selectedToken: Authorization) => {
this.props.setTaskToken(selectedToken)
}
}
const mstp = (state: AppState): StateProps => {
const {
tasks: {newScript, taskOptions},
tasks: {newScript, taskOptions, taskToken},
tokens,
orgs: {org},
} = state
@ -168,6 +206,9 @@ const mstp = (state: AppState): StateProps => {
timeRange,
draftQueries,
activeQueryIndex,
tokens: tokens.list,
tokenStatus: tokens.status,
selectedToken: taskToken,
}
}
@ -176,6 +217,8 @@ const mdtp: DispatchProps = {
setTaskOption,
clearTask,
setNewScript,
getTokens: getAuthorizations,
setTaskToken,
}
export default connect<StateProps, DispatchProps>(

View File

@ -3,7 +3,7 @@ import {push, goBack} from 'react-router-redux'
import _ from 'lodash'
// APIs
import {LogEvent, ITask as Task} from '@influxdata/influx'
import {LogEvent, ITask as Task, Authorization} from '@influxdata/influx'
import {client} from 'src/utils/api'
import {notify} from 'src/shared/actions/notifications'
import {
@ -12,8 +12,6 @@ import {
taskDeleteFailed,
taskNotFound,
taskUpdateFailed,
taskImportFailed,
taskImportSuccess,
taskUpdateSuccess,
taskCreatedSuccess,
taskDeleteSuccess,
@ -46,6 +44,7 @@ import {TaskOptionKeys, TaskSchedule} from 'src/utils/taskOptionsToFluxScript'
import {taskToTemplate} from 'src/shared/utils/resourceToTemplate'
import {isLimitError} from 'src/cloud/utils/limits'
import {checkTaskLimits} from 'src/cloud/actions/limits'
import {getAuthorization} from 'src/authorizations/actions'
export type Action =
| SetNewScript
@ -63,6 +62,7 @@ export type Action =
| SetLogs
| UpdateTask
| SetTaskStatus
| SetTaskToken
type GetStateFunc = () => AppState
@ -163,6 +163,12 @@ export interface UpdateTask {
task: Task
}
}
export interface SetTaskToken {
type: 'SET_TASK_TOKEN'
payload: {
token: Authorization
}
}
export const setTaskOption = (taskOption: {
key: TaskOptionKeys
@ -231,6 +237,11 @@ export const updateTask = (task: Task): UpdateTask => ({
payload: {task},
})
export const setTaskToken = (token: Authorization): SetTaskToken => ({
type: 'SET_TASK_TOKEN',
payload: {token},
})
// Thunks
export const getTasks = () => async (
dispatch,
@ -345,7 +356,8 @@ export const selectTaskByID = (id: string) => async (
): Promise<void> => {
try {
const task = await client.tasks.get(id)
const token = await getAuthorization(task.authorizationID)
dispatch(setTaskToken(token))
dispatch(setCurrentTask(task))
} catch (e) {
console.error(e)
@ -411,18 +423,18 @@ export const updateScript = () => async (dispatch, getState: GetStateFunc) => {
}
}
export const saveNewScript = (script: string, preamble: string) => async (
dispatch,
getState: GetStateFunc
): Promise<void> => {
export const saveNewScript = (
script: string,
preamble: string,
token: string
) => async (dispatch, getState: GetStateFunc): Promise<void> => {
try {
const fluxScript = await insertPreambleInScript(script, preamble)
const {
orgs: {org},
} = getState()
await client.tasks.createByOrgID(org.id, fluxScript, null)
await client.tasks.createByOrgID(org.id, fluxScript, token)
dispatch(setNewScript(''))
dispatch(clearTask())
@ -441,32 +453,6 @@ export const saveNewScript = (script: string, preamble: string) => async (
}
}
export const importTask = (script: string) => async (
dispatch,
getState: GetStateFunc
): Promise<void> => {
try {
if (_.isEmpty(script)) {
dispatch(notify(taskImportFailed('File is empty')))
return
}
const {
orgs: {org},
} = await getState()
await client.tasks.createByOrgID(org.id, script, null)
dispatch(getTasks())
dispatch(notify(taskImportSuccess()))
} catch (error) {
console.error(error)
const message = getErrorMessage(error)
dispatch(notify(taskImportFailed(message)))
}
}
export const getRuns = (taskID: string) => async (dispatch): Promise<void> => {
try {
dispatch(setRuns([], RemoteDataState.Loading))

View File

@ -16,6 +16,9 @@ const setup = (override?) => {
onChangeScheduleType: jest.fn(),
onChangeInput: jest.fn(),
onChangeTaskOrgID: jest.fn(),
tokens: [],
selectedToken: '',
onTokenChange: jest.fn(),
...override,
}

View File

@ -27,6 +27,8 @@ import {
ComponentSize,
} from '@influxdata/clockface'
import {TaskOptions, TaskSchedule} from 'src/utils/taskOptionsToFluxScript'
import {Authorization} from '@influxdata/influx'
import TaskTokenDropdown from './TaskTokenDropdown'
interface Props {
taskOptions: TaskOptions
@ -37,6 +39,9 @@ interface Props {
onChangeScheduleType: (schedule: TaskSchedule) => void
onChangeInput: (e: ChangeEvent<HTMLInputElement>) => void
onChangeToBucketName: (bucketName: string) => void
tokens: Authorization[]
selectedToken: Authorization
onTokenChange: (token: Authorization) => void
}
interface State {
@ -73,6 +78,9 @@ export default class TaskForm extends PureComponent<Props, State> {
toBucketName,
},
isInOverlay,
tokens,
onTokenChange,
selectedToken,
} = this.props
return (
@ -127,6 +135,15 @@ export default class TaskForm extends PureComponent<Props, State> {
offset={offset}
cron={cron}
/>
<Grid.Column widthXS={Columns.Twelve}>
<Form.Element label="Token">
<TaskTokenDropdown
tokens={tokens}
onTokenChange={onTokenChange}
selectedToken={selectedToken}
/>
</Form.Element>
</Grid.Column>
{isInOverlay && (
<Grid.Column widthXS={Columns.Six}>
<Form.Element label="Output Bucket">

View File

@ -0,0 +1,35 @@
// Libraries
import _ from 'lodash'
import React, {PureComponent} from 'react'
// Types
import {ComponentColor, ComponentSize} from '@influxdata/clockface'
import {Authorization} from '@influxdata/influx'
import {Dropdown} from 'src/clockface'
interface Props {
tokens: Authorization[]
selectedToken: Authorization
onTokenChange: (token: Authorization) => void
}
export default class TaskTokenDropdown extends PureComponent<Props> {
public render() {
const {tokens, selectedToken, onTokenChange} = this.props
return (
<Dropdown
selectedID={selectedToken.id}
buttonColor={ComponentColor.Primary}
buttonSize={ComponentSize.Small}
onChange={onTokenChange}
>
{tokens.map(t => (
<Dropdown.Item id={t.id} key={t.id} value={t}>
{t.description}
</Dropdown.Item>
))}
</Dropdown>
)
}
}

View File

@ -89,6 +89,21 @@ exports[`TaskForm rendering renders 1`] = `
<TaskScheduleFormFields
onChangeInput={[MockFunction]}
/>
<t
testID="grid--column"
widthXS={12}
>
<t
label="Token"
testID="form--element"
>
<TaskTokenDropdown
onTokenChange={[MockFunction]}
selectedToken=""
tokens={Array []}
/>
</t>
</t>
</t>
</t>
</t>

View File

@ -19,6 +19,7 @@ import {
setTaskOption,
clearTask,
setAllTaskOptions,
setTaskToken,
} from 'src/tasks/actions'
// Types
@ -27,7 +28,10 @@ import {
TaskOptionKeys,
TaskSchedule,
} from 'src/utils/taskOptionsToFluxScript'
import {AppState, Task} from 'src/types'
import {AppState, Task, RemoteDataState} from 'src/types'
import {Authorization} from '@influxdata/influx'
import {getAuthorizations} from 'src/authorizations/actions'
import {SpinnerContainer, TechnoSpinner} from '@influxdata/clockface'
interface PassedInProps {
router: InjectedRouter
@ -38,6 +42,9 @@ interface ConnectStateProps {
taskOptions: TaskOptions
currentTask: Task
currentScript: string
tokens: Authorization[]
tokenStatus: RemoteDataState
selectedToken: Authorization
}
interface ConnectDispatchProps {
@ -48,6 +55,8 @@ interface ConnectDispatchProps {
selectTaskByID: typeof selectTaskByID
clearTask: typeof clearTask
setAllTaskOptions: typeof setAllTaskOptions
getTokens: typeof getAuthorizations
setTaskToken: typeof setTaskToken
}
class TaskEditPage extends PureComponent<
@ -66,6 +75,10 @@ class TaskEditPage extends PureComponent<
const {currentTask} = this.props
this.props.setAllTaskOptions(currentTask)
const {selectedToken, getTokens} = this.props
this.props.setTaskToken(selectedToken)
await getTokens()
}
public componentWillUnmount() {
@ -73,7 +86,13 @@ class TaskEditPage extends PureComponent<
}
public render(): JSX.Element {
const {currentScript, taskOptions} = this.props
const {
currentScript,
taskOptions,
tokens,
tokenStatus,
selectedToken,
} = this.props
return (
<Page titleTag={`Edit ${taskOptions.name}`}>
@ -86,12 +105,20 @@ class TaskEditPage extends PureComponent<
<Page.Contents fullWidth={true} scrollable={false}>
<div className="task-form">
<div className="task-form--options">
<TaskForm
canSubmit={this.isFormValid}
taskOptions={taskOptions}
onChangeInput={this.handleChangeInput}
onChangeScheduleType={this.handleChangeScheduleType}
/>
<SpinnerContainer
loading={tokenStatus}
spinnerComponent={<TechnoSpinner />}
>
<TaskForm
canSubmit={this.isFormValid}
taskOptions={taskOptions}
onChangeInput={this.handleChangeInput}
onChangeScheduleType={this.handleChangeScheduleType}
tokens={tokens}
selectedToken={selectedToken}
onTokenChange={this.handleTokenChange}
/>
</SpinnerContainer>
</div>
<div className="task-form--editor">
<FluxEditor
@ -139,13 +166,19 @@ class TaskEditPage extends PureComponent<
this.props.setTaskOption({key, value})
}
private handleTokenChange = (selectedToken: Authorization) => {
this.props.setTaskToken(selectedToken)
}
}
const mstp = ({tasks}: AppState): ConnectStateProps => {
const mstp = ({tasks, tokens}: AppState): ConnectStateProps => {
return {
taskOptions: tasks.taskOptions,
currentScript: tasks.currentScript,
currentTask: tasks.currentTask,
tokens: tokens.list,
tokenStatus: tokens.status,
selectedToken: tasks.taskToken,
}
}
@ -157,6 +190,8 @@ const mdtp: ConnectDispatchProps = {
selectTaskByID,
setAllTaskOptions,
clearTask,
getTokens: getAuthorizations,
setTaskToken,
}
export default connect<ConnectStateProps, ConnectDispatchProps, {}>(

View File

@ -17,6 +17,7 @@ import {
setTaskOption,
clearTask,
cancel,
setTaskToken,
} from 'src/tasks/actions'
// Utils
@ -31,6 +32,13 @@ import {
TaskOptionKeys,
TaskSchedule,
} from 'src/utils/taskOptionsToFluxScript'
import {getAuthorizations} from 'src/authorizations/actions'
import {Authorization} from '@influxdata/influx'
import {
RemoteDataState,
SpinnerContainer,
TechnoSpinner,
} from '@influxdata/clockface'
interface PassedInProps {
router: InjectedRouter
@ -39,6 +47,9 @@ interface PassedInProps {
interface ConnectStateProps {
taskOptions: TaskOptions
newScript: string
tokens: Authorization[]
tokenStatus: RemoteDataState
selectedToken: Authorization
}
interface ConnectDispatchProps {
@ -47,16 +58,25 @@ interface ConnectDispatchProps {
setTaskOption: typeof setTaskOption
clearTask: typeof clearTask
cancel: typeof cancel
getTokens: typeof getAuthorizations
setTaskToken: typeof setTaskToken
}
class TaskPage extends PureComponent<
PassedInProps & ConnectStateProps & ConnectDispatchProps
> {
public componentDidMount() {
constructor(props) {
super(props)
}
public async componentDidMount() {
this.props.setTaskOption({
key: 'taskScheduleType',
value: TaskSchedule.interval,
})
await this.props.getTokens()
if (this.props.tokens.length > 0) {
this.props.setTaskToken(this.props.tokens[0])
}
}
public componentWillUnmount() {
@ -64,7 +84,13 @@ class TaskPage extends PureComponent<
}
public render(): JSX.Element {
const {newScript, taskOptions} = this.props
const {
newScript,
taskOptions,
tokens,
tokenStatus,
selectedToken,
} = this.props
return (
<Page titleTag="Create Task">
@ -77,12 +103,20 @@ class TaskPage extends PureComponent<
<Page.Contents fullWidth={true} scrollable={false}>
<div className="task-form">
<div className="task-form--options">
<TaskForm
taskOptions={taskOptions}
canSubmit={this.isFormValid}
onChangeInput={this.handleChangeInput}
onChangeScheduleType={this.handleChangeScheduleType}
/>
<SpinnerContainer
loading={tokenStatus}
spinnerComponent={<TechnoSpinner />}
>
<TaskForm
taskOptions={taskOptions}
canSubmit={this.isFormValid}
onChangeInput={this.handleChangeInput}
onChangeScheduleType={this.handleChangeScheduleType}
tokens={tokens}
selectedToken={selectedToken}
onTokenChange={this.handleTokenChange}
/>
</SpinnerContainer>
</div>
<div className="task-form--editor">
<FluxEditor
@ -117,13 +151,13 @@ class TaskPage extends PureComponent<
}
private handleSave = () => {
const {newScript, taskOptions} = this.props
const {newScript, taskOptions, selectedToken} = this.props
const taskOption: string = taskOptionsToFluxScript(taskOptions)
const script: string = addDestinationToFluxScript(newScript, taskOptions)
const preamble = `${taskOption}`
this.props.saveNewScript(script, preamble)
this.props.saveNewScript(script, preamble, selectedToken.token)
}
private handleCancel = () => {
@ -136,12 +170,19 @@ class TaskPage extends PureComponent<
this.props.setTaskOption({key, value})
}
private handleTokenChange = (selectedToken: Authorization) => {
this.props.setTaskToken(selectedToken)
}
}
const mstp = ({tasks}: AppState): ConnectStateProps => {
const mstp = ({tasks, tokens}: AppState): ConnectStateProps => {
return {
taskOptions: tasks.taskOptions,
newScript: tasks.newScript,
tokens: tokens.list,
tokenStatus: tokens.status,
selectedToken: tasks.taskToken,
}
}
@ -151,6 +192,8 @@ const mdtp: ConnectDispatchProps = {
setTaskOption,
clearTask,
cancel,
getTokens: getAuthorizations,
setTaskToken,
}
export default connect<ConnectStateProps, ConnectDispatchProps, {}>(

View File

@ -1,5 +1,5 @@
import {TaskOptions, TaskSchedule} from 'src/utils/taskOptionsToFluxScript'
import {LogEvent} from '@influxdata/influx'
import {LogEvent, Authorization} from '@influxdata/influx'
//Types
import {Action} from 'src/tasks/actions'
@ -19,6 +19,7 @@ export interface TasksState {
runs: Run[]
runStatus: RemoteDataState
logs: LogEvent[]
taskToken: Authorization
}
export const defaultTaskOptions: TaskOptions = {
@ -44,6 +45,7 @@ export const defaultState: TasksState = {
runs: [],
runStatus: RemoteDataState.NotStarted,
logs: [],
taskToken: {},
}
export default (
@ -121,7 +123,6 @@ export default (
currentScript = task.flux
}
return {...state, currentScript, currentTask: task}
case 'SET_SEARCH_TERM':
const {searchTerm} = action.payload
return {...state, searchTerm}
@ -139,6 +140,9 @@ export default (
case 'SET_LOGS':
const {logs} = action.payload
return {...state, logs}
case 'SET_TASK_TOKEN':
const {token} = action.payload
return {...state, taskToken: token}
default:
return state
}