User can update a task

pull/10616/head
Brandon Farmer 2018-10-23 10:00:37 -07:00
parent 58b9adbb52
commit 8b8733d171
13 changed files with 342 additions and 54 deletions

View File

@ -23,6 +23,7 @@ import TaskPage from 'src/tasks/containers/TaskPage'
import TasksPage from 'src/tasks/containers/TasksPage'
import OrganizationsIndex from 'src/organizations/containers/OrganizationsIndex'
import OrganizationView from 'src/organizations/containers/OrganizationView'
import TaskEditPage from 'src/tasks/containers/TaskEditPage'
import {DashboardsPage, DashboardPage} from 'src/dashboards'
import DataExplorerPage from 'src/dataExplorer/components/DataExplorerPage'
import {SourcePage, ManageSources} from 'src/sources'
@ -96,6 +97,7 @@ class Root extends PureComponent {
component={OrganizationView}
/>
<Route path="tasks/new" component={TaskPage} />
<Route path="tasks/:id" component={TaskEditPage} />
<Route path="sources/new" component={SourcePage} />
<Route
path="data-explorer"

View File

@ -17,6 +17,11 @@ export const taskNotCreated = (): Notification => ({
message: 'Failed to create new task',
})
export const taskNotFound = (): Notification => ({
...defaultErrorNotification,
message: 'Failed to find task',
})
export const tasksFetchFailed = (): Notification => ({
...defaultErrorNotification,
message: 'Failed to get tasks from server',
@ -26,3 +31,8 @@ export const taskDeleteFailed = (): Notification => ({
...defaultErrorNotification,
message: 'Failed to delete task',
})
export const taskUpdateFailed = (): Notification => ({
...defaultErrorNotification,
message: 'Failed to update task',
})

View File

@ -4,7 +4,9 @@ import {push} from 'react-router-redux'
import {Task} from 'src/types/v2/tasks'
import {
submitNewTask,
updateTaskFlux,
getUserTasks,
getTask,
deleteTask as deleteTaskAPI,
} from 'src/tasks/api/v2'
import {getMe} from 'src/shared/apis/v2/user'
@ -13,15 +15,25 @@ import {
taskNotCreated,
tasksFetchFailed,
taskDeleteFailed,
taskNotFound,
taskUpdateFailed,
} from 'src/shared/copy/v2/notifications'
export type Action = SetNewScript | SetTasks | SetSearchTerm
export type Action =
| SetNewScript
| SetTasks
| SetSearchTerm
| SetCurrentScript
| SetCurrentTask
type GetStateFunc = () => Promise<AppState>
export enum ActionTypes {
SetNewScript = 'SET_NEW_SCRIPT',
SetTasks = 'SET_TASKS',
SetSearchTerm = 'SET_TASKS_SEARCH_TERM',
SetCurrentScript = 'SET_CURRENT_SCRIPT',
SetCurrentTask = 'SET_CURRENT_TASK',
}
export interface SetNewScript {
@ -30,6 +42,18 @@ export interface SetNewScript {
script: string
}
}
export interface SetCurrentScript {
type: ActionTypes.SetCurrentScript
payload: {
script: string
}
}
export interface SetCurrentTask {
type: ActionTypes.SetCurrentTask
payload: {
task: Task
}
}
export interface SetTasks {
type: ActionTypes.SetTasks
@ -50,6 +74,16 @@ export const setNewScript = (script: string): SetNewScript => ({
payload: {script},
})
export const setCurrentScript = (script: string): SetCurrentScript => ({
type: ActionTypes.SetCurrentScript,
payload: {script},
})
export const setCurrentTask = (task: Task): SetCurrentTask => ({
type: ActionTypes.SetCurrentTask,
payload: {task},
})
export const setTasks = (tasks: Task[]): SetTasks => ({
type: ActionTypes.SetTasks,
payload: {tasks},
@ -105,6 +139,55 @@ export const populateTasks = () => async (
}
}
export const selectTaskByID = (id: string) => async (
dispatch,
getState: GetStateFunc
): Promise<void> => {
try {
const {
links: {tasks: url},
} = await getState()
const task = await getTask(url, id)
return dispatch(setCurrentTask(task))
} catch (e) {
console.error(e)
dispatch(goToTasks())
dispatch(notify(taskNotFound()))
}
}
export const selectTask = (task: Task) => async dispatch => {
dispatch(push(`/tasks/${task.id}`))
}
export const goToTasks = () => async dispatch => {
dispatch(push('/tasks'))
}
export const cancelUpdateTask = () => async dispatch => {
dispatch(setCurrentTask(null))
dispatch(goToTasks())
}
export const updateScript = () => async (dispatch, getState: GetStateFunc) => {
try {
const {
links: {tasks: url},
tasks: {currentScript: script, currentTask: task},
} = await getState()
await updateTaskFlux(url, task.id, script)
dispatch(setCurrentTask(null))
dispatch(goToTasks())
} catch (e) {
console.error(e)
dispatch(notify(taskUpdateFailed()))
}
}
export const saveNewScript = () => async (
dispatch,
getState: GetStateFunc
@ -121,7 +204,7 @@ export const saveNewScript = () => async (
await submitNewTask(url, user, orgs[0], script)
dispatch(setNewScript(''))
dispatch(push('/tasks'))
dispatch(goToTasks())
} catch (e) {
console.error(e)
dispatch(notify(taskNotCreated()))

View File

@ -1,6 +1,12 @@
import AJAX from 'src/utils/ajax'
import {Task} from 'src/types/v2/tasks'
export const submitNewTask = async (url, owner, org, flux) => {
export const submitNewTask = async (
url,
owner,
org,
flux: string
): Promise<Task> => {
const request = {
flux,
organizationId: org.id,
@ -13,7 +19,18 @@ export const submitNewTask = async (url, owner, org, flux) => {
return data
}
export const getUserTasks = async (url, user) => {
export const updateTaskFlux = async (url, id, flux: string): Promise<Task> => {
const completeUrl = `${url}/${id}`
const request = {
flux,
}
const {data} = await AJAX({url: completeUrl, data: request, method: 'PATCH'})
return data
}
export const getUserTasks = async (url, user): Promise<Task[]> => {
const completeUrl = `${url}?user=${user.id}`
const {data} = await AJAX({url: completeUrl})
@ -21,6 +38,13 @@ export const getUserTasks = async (url, user) => {
return data
}
export const getTask = async (url, id): Promise<Task> => {
const completeUrl = `${url}/${id}`
const {data} = await AJAX({url: completeUrl})
return data
}
export const deleteTask = (url: string, taskID: string) => {
const completeUrl = `${url}/${taskID}`

View File

@ -0,0 +1,36 @@
import _ from 'lodash'
import React, {PureComponent} from 'react'
import {Form, Columns} from 'src/clockface'
import FluxEditor from 'src/flux/components/v2/FluxEditor'
interface Props {
script: string
onChange: (script) => void
}
export default class TaskForm extends PureComponent<Props> {
public render() {
return (
<>
<Form style={{height: '90%'}}>
<Form.Element
label="Script"
colsXS={Columns.Six}
offsetXS={Columns.Three}
errorMessage={''}
>
<FluxEditor
script={this.props.script}
onChangeScript={this.props.onChange}
visibility={'visible'}
status={{text: '', type: ''}}
onSubmitScript={_.noop}
suggestions={[]}
/>
</Form.Element>
</Form>
</>
)
}
}

View File

@ -0,0 +1,36 @@
import React, {PureComponent} from 'react'
import {Page} from 'src/pageLayout'
import {ComponentColor, ComponentSize, Button} from 'src/clockface'
interface Props {
title: string
onCancel: () => void
onSave: () => void
}
export default class TaskHeader extends PureComponent<Props> {
public render() {
return (
<Page.Header fullWidth={true}>
<Page.Header.Left>
<Page.Title title={this.props.title} />
</Page.Header.Left>
<Page.Header.Right>
<Button
color={ComponentColor.Default}
text="Cancel"
onClick={this.props.onCancel}
size={ComponentSize.Medium}
/>
<Button
color={ComponentColor.Success}
text="Save"
onClick={this.props.onSave}
size={ComponentSize.Medium}
/>
</Page.Header.Right>
</Page.Header>
)
}
}

View File

@ -14,6 +14,7 @@ import {Task} from 'src/types/v2/tasks'
interface Props {
task: Task
onDelete: (task: Task) => void
onSelect: (task: Task) => void
}
export default class TaskRow extends PureComponent<Props> {
@ -23,7 +24,9 @@ export default class TaskRow extends PureComponent<Props> {
return (
<IndexList.Row>
<IndexList.Cell>
<a href="#">{task.name}</a>
<a href="#" onClick={this.handleClick}>
{task.name}
</a>
</IndexList.Cell>
<IndexList.Cell>
<a href="#">{task.organization.name}</a>
@ -43,6 +46,12 @@ export default class TaskRow extends PureComponent<Props> {
)
}
private handleClick = e => {
e.preventDefault()
this.props.onSelect(this.props.task)
}
private handleDelete = () => {
this.props.onDelete(this.props.task)
}

View File

@ -14,6 +14,7 @@ interface Props {
searchTerm: string
onDelete: (task: Task) => void
onCreate: () => void
onSelect: (task: Task) => void
}
export default class TasksList extends PureComponent<Props> {
@ -41,10 +42,15 @@ export default class TasksList extends PureComponent<Props> {
}
private get rows(): JSX.Element[] {
const {tasks, onDelete} = this.props
const {tasks, onDelete, onSelect} = this.props
return tasks.map(t => (
<TaskRow key={`task-id--${t.id}`} task={t} onDelete={onDelete} />
<TaskRow
key={`task-id--${t.id}`}
task={t}
onDelete={onDelete}
onSelect={onSelect}
/>
))
}
}

View File

@ -0,0 +1,99 @@
import _ from 'lodash'
import React, {PureComponent} from 'react'
import {InjectedRouter} from 'react-router'
import {connect} from 'react-redux'
import TaskForm from 'src/tasks/components/TaskForm'
import TaskHeader from 'src/tasks/components/TaskHeader'
import {Task} from 'src/types/v2/tasks'
import {Links} from 'src/types/v2/links'
import {State as TasksState} from 'src/tasks/reducers/v2'
import {
updateScript,
selectTaskByID,
setCurrentScript,
cancelUpdateTask,
} from 'src/tasks/actions/v2'
interface PassedInProps {
router: InjectedRouter
params: {id: string}
}
interface ConnectStateProps {
currentTask: Task
currentScript: string
tasksLink: string
}
interface ConnectDispatchProps {
setCurrentScript: typeof setCurrentScript
updateScript: typeof updateScript
cancelUpdateTask: typeof cancelUpdateTask
selectTaskByID: typeof selectTaskByID
}
class TaskPage extends PureComponent<
PassedInProps & ConnectStateProps & ConnectDispatchProps
> {
constructor(props) {
super(props)
}
public componentDidMount() {
this.props.selectTaskByID(this.props.params.id)
}
public render(): JSX.Element {
const {currentScript} = this.props
return (
<div className="page">
<TaskHeader
title="Update Task"
onCancel={this.handleCancel}
onSave={this.handleSave}
/>
<TaskForm script={currentScript} onChange={this.handleChange} />
</div>
)
}
private handleChange = (script: string) => {
this.props.setCurrentScript(script)
}
private handleSave = () => {
this.props.updateScript()
}
private handleCancel = () => {
this.props.cancelUpdateTask()
}
}
const mstp = ({
tasks,
links,
}: {
tasks: TasksState
links: Links
}): ConnectStateProps => {
return {
currentScript: tasks.currentScript,
tasksLink: links.tasks,
currentTask: tasks.currentTask,
}
}
const mdtp: ConnectDispatchProps = {
setCurrentScript,
updateScript,
cancelUpdateTask,
selectTaskByID,
}
export default connect<ConnectStateProps, ConnectDispatchProps, {}>(mstp, mdtp)(
TaskPage
)

View File

@ -2,19 +2,13 @@ import _ from 'lodash'
import React, {PureComponent} from 'react'
import {InjectedRouter} from 'react-router'
import {connect} from 'react-redux'
import FluxEditor from 'src/flux/components/v2/FluxEditor'
import TaskForm from 'src/tasks/components/TaskForm'
import TaskHeader from 'src/tasks/components/TaskHeader'
import {Links} from 'src/types/v2/links'
import {State as TasksState} from 'src/tasks/reducers/v2'
import {setNewScript, saveNewScript} from 'src/tasks/actions/v2'
import {
Form,
Columns,
ComponentColor,
ComponentSize,
Button,
} from 'src/clockface'
import {Page} from 'src/pageLayout'
import {setNewScript, saveNewScript, goToTasks} from 'src/tasks/actions/v2'
interface PassedInProps {
router: InjectedRouter
@ -28,6 +22,7 @@ interface ConnectStateProps {
interface ConnectDispatchProps {
setNewScript: typeof setNewScript
saveNewScript: typeof saveNewScript
goToTasks: typeof goToTasks
}
class TaskPage extends PureComponent<
@ -42,42 +37,12 @@ class TaskPage extends PureComponent<
return (
<div className="page">
<Page.Header fullWidth={true}>
<Page.Header.Left>
<Page.Title title="Create Task" />
</Page.Header.Left>
<Page.Header.Right>
<Button
color={ComponentColor.Default}
text="Cancel"
onClick={this.handleCancel}
size={ComponentSize.Medium}
/>
<Button
color={ComponentColor.Success}
text="Save"
onClick={this.handleSave}
size={ComponentSize.Medium}
/>
</Page.Header.Right>
</Page.Header>
<Form style={{height: '90%'}}>
<Form.Element
label="Script"
colsXS={Columns.Six}
offsetXS={Columns.Three}
errorMessage={''}
>
<FluxEditor
script={newScript}
onChangeScript={this.handleChange}
visibility={'visible'}
status={{text: '', type: ''}}
onSubmitScript={_.noop}
suggestions={[]}
/>
</Form.Element>
</Form>
<TaskHeader
title="Create Task"
onCancel={this.handleCancel}
onSave={this.handleSave}
/>
<TaskForm script={newScript} onChange={this.handleChange} />
</div>
)
}
@ -91,7 +56,7 @@ class TaskPage extends PureComponent<
}
private handleCancel = () => {
this.props.router.push('/tasks')
this.props.goToTasks()
}
}
@ -111,6 +76,7 @@ const mstp = ({
const mdtp: ConnectDispatchProps = {
setNewScript,
saveNewScript,
goToTasks,
}
export default connect<ConnectStateProps, ConnectDispatchProps, {}>(mstp, mdtp)(

View File

@ -12,6 +12,7 @@ import {Page} from 'src/pageLayout'
import {
populateTasks,
deleteTask,
selectTask,
setSearchTerm as setSearchTermAction,
} from 'src/tasks/actions/v2'
@ -27,6 +28,7 @@ interface PassedInProps {
interface ConnectedDispatchProps {
populateTasks: typeof populateTasks
deleteTask: typeof deleteTask
selectTask: typeof selectTask
setSearchTerm: typeof setSearchTermAction
}
@ -61,6 +63,7 @@ class TasksPage extends PureComponent<Props> {
tasks={this.filteredTasks}
onDelete={this.handleDelete}
onCreate={this.handleCreateTask}
onSelect={this.props.selectTask}
/>
</div>
</Page.Contents>
@ -100,6 +103,7 @@ const mstp = ({tasks: {tasks, searchTerm}}): ConnectedStateProps => {
const mdtp: ConnectedDispatchProps = {
populateTasks,
deleteTask,
selectTask,
setSearchTerm: setSearchTermAction,
}

View File

@ -3,12 +3,15 @@ import {Task} from 'src/types/v2/tasks'
export interface State {
newScript: string
currentScript: string
currentTask?: Task
tasks: Task[]
searchTerm: string
}
const defaultState: State = {
newScript: '',
currentScript: '',
tasks: [],
searchTerm: '',
}
@ -17,6 +20,15 @@ export default (state: State = defaultState, action: Action): State => {
switch (action.type) {
case ActionTypes.SetNewScript:
return {...state, newScript: action.payload.script}
case ActionTypes.SetCurrentScript:
return {...state, currentScript: action.payload.script}
case ActionTypes.SetCurrentTask:
const {task} = action.payload
let currentScript = ''
if (task) {
currentScript = task.flux
}
return {...state, currentScript, currentTask: task}
case ActionTypes.SetTasks:
return {...state, tasks: action.payload.tasks}
case ActionTypes.SetSearchTerm:

View File

@ -10,4 +10,5 @@ export interface Task {
id: string
name: string
}
flux?: string
}