Show list of tasks to user

pull/10616/head
Brandon Farmer 2018-10-11 18:40:00 -07:00
parent 7853cff0de
commit 6d02ca5efc
11 changed files with 245 additions and 28 deletions

View File

@ -86,7 +86,15 @@ class SideNav extends PureComponent<Props> {
link: `/logs/${this.sourceParam}`,
icon: IconFont.Wood,
location: location.pathname,
highlightWhen: ['manage-sources'],
highlightWhen: ['logs'],
},
{
type: NavItemType.Icon,
title: 'Tasks',
link: `/tasks/${this.sourceParam}`,
icon: IconFont.Wood,
location: location.pathname,
highlightWhen: ['tasks'],
},
{
type: NavItemType.Icon,

View File

@ -16,3 +16,8 @@ export const taskNotCreated = (): Notification => ({
...defaultErrorNotification,
message: 'Failed to create new task',
})
export const tasksFetchFailed = (): Notification => ({
...defaultErrorNotification,
message: 'Failed to get tasks from server',
})

View File

@ -1,15 +1,20 @@
import {push} from 'react-router-redux'
import {submitNewTask} from 'src/tasks/api/v2'
import {Task} from 'src/types/v2/tasks'
import {submitNewTask, getUserTasks} from 'src/tasks/api/v2'
import {getMe} from 'src/shared/apis/v2/user'
import {getOrganizations} from 'src/shared/apis/v2/organization'
import {notify} from 'src/shared/actions/notifications'
import {taskNotCreated} from 'src/shared/copy/v2/notifications'
import {
taskNotCreated,
tasksFetchFailed,
} from 'src/shared/copy/v2/notifications'
export type Action = SetNewScript
export type Action = SetNewScript | SetTasks
export enum ActionTypes {
SetNewScript = 'SET_NEW_SCRIPT',
SetTasks = 'SET_TASKS',
}
export interface SetNewScript {
@ -19,11 +24,51 @@ export interface SetNewScript {
}
}
export interface SetTasks {
type: ActionTypes.SetTasks
payload: {
tasks: Task[]
}
}
export const setTasks = (tasks: Task[]): SetTasks => ({
type: ActionTypes.SetTasks,
payload: {tasks},
})
export const setNewScript = (script: string): SetNewScript => ({
type: ActionTypes.SetNewScript,
payload: {script},
})
export const populateTasks = () => async (
dispatch,
getState
): Promise<void> => {
try {
const {
links: {tasks: url, me: meUrl, orgs: orgsUrl},
} = await getState()
const orgs = await getOrganizations(orgsUrl)
const user = await getMe(meUrl)
const tasks = await getUserTasks(url, user)
const mappedTasks = tasks.map(task => {
return {
...task,
organization: orgs.find(org => org.id === task.organizationId),
}
})
dispatch(setTasks(mappedTasks))
} catch (e) {
console.error(e)
dispatch(notify(tasksFetchFailed()))
}
}
export const saveNewScript = () => async (
dispatch,
getState

View File

@ -1,12 +1,22 @@
import AJAX from 'src/utils/ajax'
export const submitNewTask = (url, owner, org, flux) => {
const data = {
export const submitNewTask = async (url, owner, org, flux) => {
const request = {
flux,
organizationId: org.id,
status: 'enabled',
status: 'active',
owner,
}
return AJAX({url, data, method: 'POST'})
const {data} = await AJAX({url, data: request, method: 'POST'})
return data
}
export const getUserTasks = async (url, user) => {
const completeUrl = `${url}?user=${user.id}`
const {data} = await AJAX({url: completeUrl})
return data
}

View File

@ -0,0 +1,42 @@
import React, {PureComponent} from 'react'
import {Task} from 'src/types/v2/tasks'
import {ComponentColor, ComponentSize, Button, SlideToggle} from 'src/clockface'
import {getDeep} from 'src/utils/wrappers'
interface Props {
task: Task
}
export default class TaskRow extends PureComponent<Props> {
public render() {
return (
<tr>
<td>{this.name}</td>
<td>{this.organizationName}</td>
<td>
<SlideToggle active={true} />
</td>
<td>
<Button
color={ComponentColor.Danger}
text="Delete"
size={ComponentSize.Medium}
/>
</td>
</tr>
)
}
private get name(): string {
const {task} = this.props
return task.name
}
private get organizationName(): string {
const {task} = this.props
return getDeep(task, 'organization.name', '')
}
}

View File

@ -0,0 +1,30 @@
import React, {PureComponent} from 'react'
import {Page} from 'src/pageLayout'
import {Button, ComponentColor, IconFont} from 'src/clockface'
interface Props {
onCreateTask: () => void
}
export default class TasksHeader extends PureComponent<Props> {
public render() {
const {onCreateTask} = this.props
return (
<Page.Header fullWidth={true}>
<Page.Header.Left>
<Page.Title title="Tasks" />
</Page.Header.Left>
<Page.Header.Right>
<Button
color={ComponentColor.Primary}
onClick={onCreateTask}
icon={IconFont.Plus}
text="Create Task"
titleText="Create a new task"
/>
</Page.Header.Right>
</Page.Header>
)
}
}

View File

@ -0,0 +1,32 @@
import React, {PureComponent} from 'react'
import {Task} from 'src/types/v2/tasks'
import TaskRow from 'src/tasks/components/TaskRow'
interface Props {
tasks: Task[]
}
export default class TasksList extends PureComponent<Props> {
public render() {
return (
<table>
<thead>
<tr>
<th>Name</th>
<th>Organization</th>
<th>Enabled</th>
<th />
</tr>
</thead>
<tbody>{this.tasks}</tbody>
</table>
)
}
private get tasks(): JSX.Element[] {
const {tasks} = this.props
return tasks.map(task => <TaskRow key={`task-${task.id}`} task={task} />)
}
}

View File

@ -1,5 +1,6 @@
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'
@ -15,6 +16,10 @@ import {
} from 'src/clockface'
import {Page} from 'src/pageLayout'
interface PassedInProps {
router: InjectedRouter
}
interface ConnectStateProps {
newScript: string
tasksLink: string
@ -26,7 +31,7 @@ interface ConnectDispatchProps {
}
class TaskPage extends PureComponent<
{} & ConnectStateProps & ConnectDispatchProps
PassedInProps & ConnectStateProps & ConnectDispatchProps
> {
constructor(props) {
super(props)
@ -45,6 +50,7 @@ class TaskPage extends PureComponent<
<Button
color={ComponentColor.Default}
text="Cancel"
onClick={this.handleCancel}
size={ComponentSize.Medium}
/>
<Button
@ -83,6 +89,10 @@ class TaskPage extends PureComponent<
private handleSave = () => {
this.props.saveNewScript()
}
private handleCancel = () => {
this.props.router.push('/tasks')
}
}
const mstp = ({

View File

@ -1,40 +1,57 @@
import React, {PureComponent} from 'react'
import {connect} from 'react-redux'
import {InjectedRouter} from 'react-router'
import {Page} from 'src/pageLayout'
import {Button, ComponentColor, IconFont} from 'src/clockface'
import TasksHeader from 'src/tasks/components/TasksHeader'
import TasksList from 'src/tasks/components/TasksList'
interface Props {
import {populateTasks} from 'src/tasks/actions/v2'
import {Task} from 'src/types/v2/tasks'
interface PassedInProps {
router: InjectedRouter
}
interface ConnectedDispatchProps {
populateTasks: typeof populateTasks
}
interface ConnectedStateProps {
tasks: Task[]
}
type Props = ConnectedDispatchProps & PassedInProps & ConnectedStateProps
class TasksPage extends PureComponent<Props> {
public render(): JSX.Element {
const {tasks} = this.props
return (
<div className="page">
<Page.Header fullWidth={true}>
<Page.Header.Left>
<Page.Title title="Tasks" />
</Page.Header.Left>
<Page.Header.Right>
<Button
color={ComponentColor.Primary}
onClick={this.handleCreateTask}
icon={IconFont.Plus}
text="Create Task"
titleText="Create a new task"
/>
</Page.Header.Right>
</Page.Header>
YO!
<TasksHeader onCreateTask={this.handleCreateTask} />
<TasksList tasks={tasks} />
</div>
)
}
public componentDidMount() {
this.props.populateTasks()
}
private handleCreateTask = () => {
const {router} = this.props
router.push('/tasks/new')
}
}
export default TasksPage
const mstp = ({tasks: {tasks}}): ConnectedStateProps => {
return {tasks}
}
const mdtp: ConnectedDispatchProps = {
populateTasks,
}
export default connect(mstp, mdtp)(TasksPage) as React.ComponentClass<
PassedInProps
>

View File

@ -1,17 +1,22 @@
import {Action, ActionTypes} from 'src/tasks/actions/v2'
import {Task} from 'src/types/v2/tasks'
export interface State {
newScript: string
tasks: Task[]
}
const defaultState: State = {
newScript: '',
tasks: [],
}
export default (state: State = defaultState, action: Action): State => {
switch (action.type) {
case ActionTypes.SetNewScript:
return {...state, newScript: action.payload.script}
case ActionTypes.SetTasks:
return {...state, tasks: action.payload.tasks}
default:
return state
}

View File

@ -0,0 +1,13 @@
export interface Task {
id: string
name: string
organizationId: string
organization: {
id: string
name: string
}
owner: {
id: string
name: string
}
}