Create org task page which uses tasks component

pull/11700/head
Palak Bhojani 2019-02-04 09:28:37 -08:00
parent 8b991e5dc5
commit c30d49cc57
7 changed files with 302 additions and 249 deletions

View File

@ -0,0 +1,240 @@
// Libraries
import React, {PureComponent, ChangeEvent} from 'react'
import {connect} from 'react-redux'
import {InjectedRouter} from 'react-router'
import _ from 'lodash'
// Components
import {Input, IconFont} from 'src/clockface'
import FilterList from 'src/shared/components/Filter'
import TasksHeader from 'src/tasks/components/TasksHeader'
import TasksList from 'src/tasks/components/TasksList'
import {Page} from 'src/pageLayout'
import {ErrorHandling} from 'src/shared/decorators/errors'
import {OverlayTechnology} from 'src/clockface'
import ImportTaskOverlay from 'src/tasks/components/ImportTaskOverlay'
// Actions
import {
updateTaskStatus,
deleteTask,
selectTask,
cloneTask,
setSearchTerm as setSearchTermAction,
setShowInactive as setShowInactiveAction,
importScript,
addTaskLabelsAsync,
removeTaskLabelsAsync,
} from 'src/tasks/actions/v2'
// Types
import {Task as TaskAPI, Organization} from '@influxdata/influx'
import {Task} from 'src/tasks/containers/TasksPage'
import {AppState} from 'src/types/v2'
interface PassedInProps {
tasks: Task[]
orgName: string
onChange: () => void
router: InjectedRouter
}
interface ConnectedDispatchProps {
updateTaskStatus: typeof updateTaskStatus
deleteTask: typeof deleteTask
cloneTask: typeof cloneTask
selectTask: typeof selectTask
setSearchTerm: typeof setSearchTermAction
setShowInactive: typeof setShowInactiveAction
importScript: typeof importScript
onAddTaskLabels: typeof addTaskLabelsAsync
onRemoveTaskLabels: typeof removeTaskLabelsAsync
}
interface ConnectedStateProps {
searchTerm: string
showInactive: boolean
orgs: Organization[]
}
type Props = ConnectedDispatchProps & PassedInProps & ConnectedStateProps
interface State {
isImportOverlayVisible: boolean
taskLabelsEdit: Task
}
@ErrorHandling
class OrgTasksPage extends PureComponent<Props, State> {
constructor(props) {
super(props)
props.setSearchTerm('')
if (!props.showInactive) {
props.setShowInactive()
}
this.state = {
isImportOverlayVisible: false,
taskLabelsEdit: null,
}
}
public render() {
const {
setSearchTerm,
showInactive,
searchTerm,
onAddTaskLabels,
onRemoveTaskLabels,
} = this.props
return (
<>
<Page titleTag="Tasks">
<Input
icon={IconFont.Search}
placeholder="Filter tasks..."
widthPixels={290}
value={searchTerm}
onChange={this.handleFilterChange}
onBlur={this.handleFilterBlur}
/>
<TasksHeader
onCreateTask={this.handleCreateTask}
setSearchTerm={setSearchTerm}
setShowInactive={this.handleToggle}
showInactive={showInactive}
toggleOverlay={this.handleToggleOverlay}
showOrgDropdown={false}
showFilter={false}
/>
<FilterList<Task>
searchTerm={searchTerm}
searchKeys={['name']}
list={this.filteredTasks}
>
{ts => (
<TasksList
searchTerm={searchTerm}
tasks={ts}
totalCount={this.totalTaskCount}
onActivate={this.handleActivate}
onDelete={this.handleDelete}
onCreate={this.handleCreateTask}
onClone={this.handleClone}
onSelect={selectTask}
onAddTaskLabels={onAddTaskLabels}
onRemoveTaskLabels={onRemoveTaskLabels}
/>
)}
</FilterList>
</Page>
{this.renderImportOverlay}
</>
)
}
private get filteredTasks() {
const {tasks, showInactive} = this.props
if (showInactive) {
return tasks
}
const mappedTasks = tasks.filter(t => {
if (!showInactive) {
return t.status === TaskAPI.StatusEnum.Active
}
})
return mappedTasks
}
private handleToggle = async () => {
await this.props.setShowInactive()
this.props.onChange()
}
private get totalTaskCount(): number {
return this.props.tasks.length
}
private handleActivate = async (task: Task) => {
await this.props.updateTaskStatus(task)
this.props.onChange()
}
private handleDelete = async (task: Task) => {
await this.props.deleteTask(task)
this.props.onChange()
}
private handleClone = async (task: Task) => {
const {tasks} = this.props
await this.props.cloneTask(task, tasks)
this.props.onChange()
}
private handleCreateTask = () => {
const {router} = this.props
router.push('/tasks/new')
}
private handleToggleOverlay = () => {
this.setState({isImportOverlayVisible: !this.state.isImportOverlayVisible})
}
private handleSave = (script: string, fileName: string) => {
this.props.importScript(script, fileName)
}
private get renderImportOverlay(): JSX.Element {
const {isImportOverlayVisible} = this.state
return (
<OverlayTechnology visible={isImportOverlayVisible}>
<ImportTaskOverlay
onDismissOverlay={this.handleToggleOverlay}
onSave={this.handleSave}
/>
</OverlayTechnology>
)
}
private handleFilterBlur = (e: ChangeEvent<HTMLInputElement>): void => {
this.props.setSearchTerm(e.target.value)
}
private handleFilterChange = (e: ChangeEvent<HTMLInputElement>): void => {
this.props.setSearchTerm(e.target.value)
}
}
const mstp = ({
tasks: {searchTerm, showInactive},
orgs,
}: AppState): ConnectedStateProps => {
return {
searchTerm,
showInactive,
orgs,
}
}
const mdtp: ConnectedDispatchProps = {
updateTaskStatus,
deleteTask,
selectTask,
cloneTask,
setSearchTerm: setSearchTermAction,
setShowInactive: setShowInactiveAction,
importScript,
onRemoveTaskLabels: removeTaskLabelsAsync,
onAddTaskLabels: addTaskLabelsAsync,
}
export default connect<
ConnectedStateProps,
ConnectedDispatchProps,
PassedInProps
>(
mstp,
mdtp
)(OrgTasksPage)

View File

@ -1,47 +0,0 @@
// Libraries
import React, {PureComponent} from 'react'
// Components
import {IndexList} from 'src/clockface'
// Types
import {Task} from '@influxdata/influx'
import TaskRow from 'src/organizations/components/TaskRow'
interface Props {
tasks: Task[]
emptyState: JSX.Element
onDelete: (taskID: string) => void
onUpdate: (task: Task) => void
onClone: (task: Task) => void
}
export default class TaskList extends PureComponent<Props> {
public render() {
return (
<IndexList>
<IndexList.Header>
<IndexList.HeaderCell columnName="Name" width="40%" />
<IndexList.HeaderCell columnName="Owner" width="40%" />
<IndexList.HeaderCell width="20%" />
</IndexList.Header>
<IndexList.Body columnCount={3} emptyState={this.props.emptyState}>
{this.rows}
</IndexList.Body>
</IndexList>
)
}
private get rows(): JSX.Element[] {
const {tasks, onDelete, onClone, onUpdate} = this.props
return tasks.map(task => (
<TaskRow
key={task.id}
task={task}
onDelete={onDelete}
onUpdate={onUpdate}
onClone={onClone}
/>
))
}
}

View File

@ -1,73 +0,0 @@
// Libraries
import React, {PureComponent} from 'react'
// Components
import {
ComponentSize,
IndexList,
ConfirmationButton,
Alignment,
Button,
IconFont,
ComponentColor,
} from 'src/clockface'
// Api
import {Task} from '@influxdata/influx'
import EditableName from 'src/shared/components/EditableName'
interface Props {
task: Task
onDelete: (taskID: string) => void
onUpdate: (task: Task) => void
onClone: (task: Task) => void
}
export default class TaskRow extends PureComponent<Props> {
public render() {
const {task} = this.props
return (
<>
<IndexList.Row key={task.id}>
<IndexList.Cell>
<EditableName
onUpdate={this.handleUpdateTask}
name={task.name}
hrefValue={`/tasks/${task.id}`}
/>
</IndexList.Cell>
<IndexList.Cell>{task.name}</IndexList.Cell>
<IndexList.Cell revealOnHover={true} alignment={Alignment.Right}>
<Button
size={ComponentSize.ExtraSmall}
color={ComponentColor.Secondary}
text="Clone"
icon={IconFont.Duplicate}
onClick={this.handleClone}
/>
<ConfirmationButton
size={ComponentSize.ExtraSmall}
text="Delete"
confirmText="Confirm"
onConfirm={this.handleDeleteTask}
/>
</IndexList.Cell>
</IndexList.Row>
</>
)
}
private handleUpdateTask = (name: string) => {
const {onUpdate, task} = this.props
onUpdate({...task, name})
}
private handleClone = (): void => {
this.props.onClone(this.props.task)
}
private handleDeleteTask = (): void => {
this.props.onDelete(this.props.task.id)
}
}

View File

@ -1,113 +0,0 @@
// Libraries
import React, {PureComponent, ChangeEvent} from 'react'
import _ from 'lodash'
// Components
import TabbedPageHeader from 'src/shared/components/tabbed_page/TabbedPageHeader'
import {Input, IconFont, ComponentSize, EmptyState} from 'src/clockface'
import TaskList from 'src/organizations/components/TaskList'
import FilterList from 'src/shared/components/Filter'
import {client} from 'src/utils/api'
// Types
import {Task} from '@influxdata/influx'
interface Props {
tasks: Task[]
orgName: string
onChange: () => void
}
interface State {
searchTerm: string
}
export default class Tasks extends PureComponent<Props, State> {
constructor(props) {
super(props)
this.state = {
searchTerm: '',
}
}
public render() {
const {searchTerm} = this.state
const {tasks} = this.props
return (
<>
<TabbedPageHeader>
<Input
icon={IconFont.Search}
placeholder="Filter tasks..."
widthPixels={290}
value={searchTerm}
onChange={this.handleFilterChange}
onBlur={this.handleFilterBlur}
/>
</TabbedPageHeader>
<FilterList<Task>
searchTerm={searchTerm}
searchKeys={['name']}
list={tasks}
>
{ts => (
<TaskList
tasks={ts}
emptyState={this.emptyState}
onDelete={this.handleDeleteTask}
onUpdate={this.handleUpdateTask}
onClone={this.handleCloneTask}
/>
)}
</FilterList>
</>
)
}
private handleFilterBlur = (e: ChangeEvent<HTMLInputElement>): void => {
this.setState({searchTerm: e.target.value})
}
private handleFilterChange = (e: ChangeEvent<HTMLInputElement>): void => {
this.setState({searchTerm: e.target.value})
}
private get emptyState(): JSX.Element {
const {orgName} = this.props
const {searchTerm} = this.state
if (_.isEmpty(searchTerm)) {
return (
<EmptyState size={ComponentSize.Medium}>
<EmptyState.Text
text={`${orgName} does not own any Tasks , why not create one?`}
highlightWords={'Tasks'}
/>
</EmptyState>
)
}
return (
<EmptyState size={ComponentSize.Medium}>
<EmptyState.Text text="No Tasks match your query" />
</EmptyState>
)
}
private handleUpdateTask = async (task: Task) => {
await client.tasks.update(task.id, task)
this.props.onChange()
}
private handleDeleteTask = async (taskID: string) => {
await client.tasks.delete(taskID)
this.props.onChange()
}
private handleCloneTask = async (task: Task) => {
await client.tasks.create(task.orgID, task.flux)
this.props.onChange()
}
}

View File

@ -20,10 +20,6 @@ const getBuckets = async (org: Organization) => {
return client.buckets.getAllByOrg(org)
}
const getTasks = async (org: Organization) => {
return client.tasks.getAllByOrg(org)
}
// Actions
import {updateOrg} from 'src/organizations/actions'
import * as notifyActions from 'src/shared/actions/notifications'
@ -36,7 +32,7 @@ import TabbedPageSection from 'src/shared/components/tabbed_page/TabbedPageSecti
import Members from 'src/organizations/components/Members'
import Buckets from 'src/organizations/components/Buckets'
import Dashboards from 'src/organizations/components/Dashboards'
import Tasks from 'src/organizations/components/Tasks'
import OrgTasksPage from 'src/organizations/components/OrgTasksPage'
import Collectors from 'src/organizations/components/Collectors'
import Scrapers from 'src/organizations/components/Scrapers'
import GetOrgResources from 'src/organizations/components/GetOrgResources'
@ -48,7 +44,6 @@ import {
ResourceOwner,
Bucket,
Organization,
Task,
Telegraf,
ScraperTargetResponse,
} from '@influxdata/influx'
@ -56,11 +51,23 @@ import * as NotificationsActions from 'src/types/actions/notifications'
// Decorators
import {ErrorHandling} from 'src/shared/decorators/errors'
import {Task} from 'src/tasks/containers/TasksPage'
interface StateProps {
org: Organization
}
const getTasks = async (org: Organization): Promise<Task[]> => {
const tasks = await client.tasks.getAllByOrg(org)
const mappedTasks = tasks.map(task => {
return {
...task,
organization: org,
}
})
return mappedTasks
}
interface DispatchProps {
onUpdateOrg: typeof updateOrg
notify: NotificationsActions.PublishNotificationActionCreator
@ -71,7 +78,7 @@ type Props = StateProps & WithRouterProps & DispatchProps
@ErrorHandling
class OrganizationView extends PureComponent<Props> {
public render() {
const {org, params, notify} = this.props
const {org, params, notify, router} = this.props
return (
<Page titleTag={org.name}>
@ -171,10 +178,11 @@ class OrganizationView extends PureComponent<Props> {
loading={loading}
spinnerComponent={<TechnoSpinner />}
>
<Tasks
<OrgTasksPage
tasks={tasks}
orgName={org.name}
onChange={fetch}
router={router}
/>
</SpinnerContainer>
)}

View File

@ -21,13 +21,22 @@ interface Props {
setShowInactive: () => void
showInactive: boolean
toggleOverlay: () => void
showOrgDropdown?: boolean
showFilter?: boolean
}
export default class TasksHeader extends PureComponent<Props> {
public static defaultProps: {
showOrgDropdown: boolean
showFilter: boolean
} = {
showOrgDropdown: true,
showFilter: true,
}
public render() {
const {
onCreateTask,
setSearchTerm,
setShowInactive,
showInactive,
toggleOverlay,
@ -36,7 +45,7 @@ export default class TasksHeader extends PureComponent<Props> {
return (
<Page.Header fullWidth={false}>
<Page.Header.Left>
<Page.Title title="Tasks" />
<Page.Title title={this.pageTitle} />
</Page.Header.Left>
<Page.Header.Right>
<SlideToggle.Label text="Show Inactive" />
@ -45,11 +54,8 @@ export default class TasksHeader extends PureComponent<Props> {
size={ComponentSize.ExtraSmall}
onChange={setShowInactive}
/>
<SearchWidget
placeholderText="Filter tasks by name..."
onSearch={setSearchTerm}
/>
<TaskOrgDropdown />
{this.filterSearch}
{this.orgDropDown}
<Button
text="Import"
icon={IconFont.Import}
@ -66,4 +72,36 @@ export default class TasksHeader extends PureComponent<Props> {
</Page.Header>
)
}
private get pageTitle() {
const {showOrgDropdown} = this.props
if (showOrgDropdown) {
return 'Tasks'
}
return ''
}
private get filterSearch(): JSX.Element {
const {setSearchTerm, showFilter} = this.props
if (showFilter) {
return (
<SearchWidget
placeholderText="Filter tasks by name..."
onSearch={setSearchTerm}
/>
)
}
return <></>
}
private get orgDropDown(): JSX.Element {
const {showOrgDropdown} = this.props
if (showOrgDropdown) {
return <TaskOrgDropdown />
}
return <></>
}
}

View File

@ -33,7 +33,7 @@ import {allOrganizationsID} from 'src/tasks/constants'
import {Task as TaskAPI, User, Organization} from '@influxdata/influx'
import {AppState} from 'src/types/v2'
interface Task extends TaskAPI {
export interface Task extends TaskAPI {
organization: Organization
owner?: User
offset?: string