Skip to content

Commit f95e589

Browse files
authored
feat(taskScheduler): fetch and list scripts (#6295)
* feat(Tasks): fetch and list scripts * feat: fetchScripts function to handle fetching * chore: update color of create script in editor * feat: notification for script fetching error * fix: prettier * chore: require scriptsRoutes * chore: clean up * chore: add scripts type file * chore: clean up * fix: remove type from fetchScript response
1 parent 28116ec commit f95e589

File tree

7 files changed

+194
-19
lines changed

7 files changed

+194
-19
lines changed

src/scripts/apis/index.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// Constants
2+
import {CLOUD} from 'src/shared/constants'
3+
4+
// Types
5+
import {ServerError, UnauthorizedError} from 'src/types/error'
6+
import {Scripts} from 'src/types/scripts'
7+
8+
let getScripts
9+
10+
if (CLOUD) {
11+
getScripts = require('src/client/scriptsRoutes').getScripts
12+
}
13+
14+
export const fetchScripts = async (): Promise<Scripts> => {
15+
const response = await getScripts({query: {limit: 250}})
16+
17+
if (response.status === 401) {
18+
throw new UnauthorizedError(response.data.message)
19+
}
20+
if (response.status === 500) {
21+
throw new ServerError(response.data.message)
22+
}
23+
24+
return response.data
25+
}

src/shared/copy/notifications/categories/scripts.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,9 @@ export const deleteScriptFail = (scriptName: string): Notification => ({
2020
...defaultErrorNotification,
2121
message: `${scriptName} failed to delete`,
2222
})
23+
24+
export const getScriptsFail = (): Notification => ({
25+
...defaultErrorNotification,
26+
message:
27+
'There was an error fetching Scripts. Please try reloading this page',
28+
})

src/tasks/components/TaskScheduler/ScriptSelector.tsx

Lines changed: 130 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,152 @@
1-
import React, {FC} from 'react'
1+
// Libraries
2+
import React, {FC, ChangeEvent, useEffect, useState} from 'react'
3+
import {useDispatch} from 'react-redux'
24
import {useHistory, useParams} from 'react-router-dom'
5+
6+
// Components
37
import {
48
ComponentSize,
59
Dropdown,
10+
EmptyState,
611
Form,
712
IconFont,
813
Input,
14+
RemoteDataState,
15+
TechnoSpinner,
916
} from '@influxdata/clockface'
1017

18+
// APIs
19+
import {fetchScripts} from 'src/scripts/apis/index'
20+
21+
// Notifications
22+
import {getScriptsFail} from 'src/shared/copy/notifications/categories/scripts'
23+
import {notify} from 'src/shared/actions/notifications'
24+
25+
// Types
26+
import {Script} from 'src/types/scripts'
27+
1128
// Utils
1229
import {SafeBlankLink} from 'src/utils/SafeBlankLink'
1330

14-
export const ScriptSelector: FC = () => {
31+
interface Props {
32+
selectedScript: Script
33+
setSelectedScript: (script: Script) => void
34+
}
35+
36+
export const ScriptSelector: FC<Props> = ({
37+
selectedScript,
38+
setSelectedScript,
39+
}) => {
40+
const [scripts, setScripts] = useState<Script[]>([])
41+
const [searchTerm, setSearchTerm] = useState('')
42+
const [scriptsLoadingStatus, setScriptsLoadingStatus] = useState(
43+
RemoteDataState.NotStarted
44+
)
45+
const dispatch = useDispatch()
1546
const {orgID} = useParams<{orgID: string}>()
1647
const history = useHistory()
48+
1749
const fluxScriptEditorLink = `/orgs/${orgID}/data-explorer?fluxScriptEditor`
1850

51+
useEffect(() => {
52+
setScriptsLoadingStatus(RemoteDataState.Loading)
53+
const getScripts = async () => {
54+
try {
55+
const scripts = await fetchScripts()
56+
setScripts(scripts.scripts)
57+
setScriptsLoadingStatus(RemoteDataState.Done)
58+
} catch (error) {
59+
setScriptsLoadingStatus(RemoteDataState.Error)
60+
dispatch(notify(getScriptsFail()))
61+
}
62+
}
63+
getScripts()
64+
}, [dispatch])
65+
66+
const filterScripts = () =>
67+
scripts.filter(script =>
68+
script.name.toLocaleLowerCase().includes(searchTerm.toLocaleLowerCase())
69+
)
70+
71+
const searchForTerm = (event: ChangeEvent<HTMLInputElement>) => {
72+
setSearchTerm(event.target.value)
73+
}
74+
1975
const openScriptEditor = () => {
2076
history.push(`/orgs/${orgID}/data-explorer?fluxScriptEditor`)
2177
}
2278

23-
const createScriptInEditor = (
24-
<Dropdown.Item onClick={openScriptEditor}>
25-
+ Create Script in Editor
26-
</Dropdown.Item>
27-
)
79+
const handleSelectScript = script => {
80+
setSelectedScript(script)
81+
}
82+
83+
let scriptsList
2884

29-
const dropdownButtonText = 'Select a Script'
85+
if (
86+
scriptsLoadingStatus === RemoteDataState.NotStarted ||
87+
scriptsLoadingStatus === RemoteDataState.Loading
88+
) {
89+
scriptsList = (
90+
<div>
91+
<TechnoSpinner strokeWidth={ComponentSize.Small} diameterPixels={32} />
92+
</div>
93+
)
94+
}
95+
96+
if (scriptsLoadingStatus === RemoteDataState.Error) {
97+
scriptsList = (
98+
<div>
99+
<p>Could not get scripts</p>
100+
</div>
101+
)
102+
}
103+
104+
if (scriptsLoadingStatus === RemoteDataState.Done) {
105+
const filteredScripts = filterScripts()
106+
scriptsList = (
107+
<>
108+
{filteredScripts.map(script => (
109+
<Dropdown.Item
110+
key={script.id}
111+
value={script.name}
112+
onClick={() => handleSelectScript(script)}
113+
selected={script.name === selectedScript?.name}
114+
>
115+
{script.name}
116+
</Dropdown.Item>
117+
))}
118+
</>
119+
)
120+
if (!filteredScripts.length) {
121+
if (searchTerm) {
122+
scriptsList = (
123+
<EmptyState>
124+
<p>{`No Scripts match "${searchTerm}"`}</p>
125+
</EmptyState>
126+
)
127+
} else {
128+
scriptsList = (
129+
<EmptyState>
130+
<p>No Scripts found</p>
131+
</EmptyState>
132+
)
133+
}
134+
}
135+
}
136+
137+
let dropdownButtonText = 'Select a Script'
138+
139+
if (scriptsLoadingStatus === RemoteDataState.Done && selectedScript?.name) {
140+
dropdownButtonText = selectedScript.name
141+
}
30142

31143
return (
32144
<div className="select-script">
33145
<div className="create-task-titles script">Select a Script</div>
34146
<Form.Element label="Script" required={true} className="script-dropdown">
35147
<Dropdown
36148
button={(active, onClick) => (
37-
<Dropdown.Button
38-
active={active}
39-
onClick={onClick}
40-
testID="variable-type-dropdown--button"
41-
>
149+
<Dropdown.Button active={active} onClick={onClick}>
42150
{dropdownButtonText}
43151
</Dropdown.Button>
44152
)}
@@ -47,12 +155,18 @@ export const ScriptSelector: FC = () => {
47155
<Input
48156
size={ComponentSize.Small}
49157
icon={IconFont.Search_New}
50-
value=""
158+
value={searchTerm}
51159
placeholder="Search Scripts..."
52-
onChange={() => {}}
160+
onChange={searchForTerm}
53161
/>
54162
<Dropdown.Menu onCollapse={onCollapse}>
55-
{createScriptInEditor}
163+
<Dropdown.Item
164+
onClick={openScriptEditor}
165+
className="create-script-btn"
166+
>
167+
+ Create Script in Editor
168+
</Dropdown.Item>
169+
{scriptsList}
56170
</Dropdown.Menu>
57171
</>
58172
)}

src/tasks/components/TaskScheduler/TaskScheduler.scss

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,3 +59,7 @@
5959
font-weight: 600;
6060
font-size: 15px;
6161
}
62+
63+
.create-script-btn {
64+
color: #00a3ff;
65+
}

src/tasks/components/TaskScheduler/TaskScheduler.tsx

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, {FC, ChangeEvent} from 'react'
1+
import React, {FC, ChangeEvent, useState} from 'react'
22

33
// Components
44
import {
@@ -21,6 +21,7 @@ import {TaskIntervalForm} from 'src/tasks/components/TaskScheduler/TaskIntervalF
2121
import {SafeBlankLink} from 'src/utils/SafeBlankLink'
2222

2323
// Types
24+
import {Script} from 'src/types/scripts'
2425
import {TaskOptions, TaskSchedule} from 'src/types'
2526

2627
import 'src/tasks/components/TaskScheduler/TaskScheduler.scss'
@@ -36,6 +37,8 @@ export const TaskScheduler: FC<Props> = ({
3637
updateInput,
3738
updateScheduleType,
3839
}) => {
40+
const [selectedScript, setSelectedScript] = useState<Script>()
41+
3942
const handleChangeScheduleType = (schedule: TaskSchedule): void => {
4043
updateScheduleType(schedule)
4144
}
@@ -55,7 +58,10 @@ export const TaskScheduler: FC<Props> = ({
5558
Learn More.
5659
</SafeBlankLink>{' '}
5760
</p>
58-
<ScriptSelector />
61+
<ScriptSelector
62+
selectedScript={selectedScript}
63+
setSelectedScript={setSelectedScript}
64+
/>
5965
<div className="schedule-task">
6066
<div className="create-task-titles">Schedule the Task</div>
6167
<p>

src/tasks/containers/TaskPage.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ import TaskForm from 'src/tasks/components/TaskForm'
1313
import TaskHeader from 'src/tasks/components/TaskHeader'
1414
import {TaskScheduler} from 'src/tasks/components/TaskScheduler/TaskScheduler'
1515

16+
// Constants
17+
import {CLOUD} from 'src/shared/constants'
18+
1619
// Actions and Selectors
1720
import {
1821
setNewScript,
@@ -59,7 +62,7 @@ class TaskPage extends PureComponent<Props> {
5962

6063
public render(): JSX.Element {
6164
const {newScript, taskOptions} = this.props
62-
const shouldShowNewTasksUI = isFlagEnabled('tasksUiEnhancements')
65+
const shouldShowNewTasksUI = isFlagEnabled('tasksUiEnhancements') && CLOUD
6366

6467
return (
6568
<Page titleTag={pageTitleSuffixer(['Create Task'])}>

src/types/scripts.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
export interface Scripts {
2+
scripts?: Script[]
3+
}
4+
export interface Script {
5+
readonly id?: string
6+
name: string
7+
description?: string
8+
orgID: string
9+
script: string
10+
language?: ScriptLanguage
11+
url?: string
12+
readonly createdAt?: string
13+
readonly updatedAt?: string
14+
labels?: string[]
15+
}
16+
17+
export type ScriptLanguage = 'flux' | 'sql'

0 commit comments

Comments
 (0)