Skip to content

Commit 1413ae1

Browse files
authored
feat: save scripts (#5543)
1 parent 234e048 commit 1413ae1

File tree

5 files changed

+243
-88
lines changed

5 files changed

+243
-88
lines changed

src/dataExplorer/components/FluxQueryBuilder.scss

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,3 +30,11 @@
3030
border-top: 1px solid $cf-grey-15;
3131
border-bottom: 1px solid $cf-grey-15;
3232
}
33+
34+
.flux-query-builder__save-button {
35+
margin-left: $cf-space-s;
36+
}
37+
38+
.save-script-name__input {
39+
margin-bottom: $cf-space-s;
40+
}

src/dataExplorer/components/FluxQueryBuilder.tsx

Lines changed: 59 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -29,66 +29,76 @@ import SaveAsScript from 'src/dataExplorer/components/SaveAsScript'
2929
// Styles
3030
import './FluxQueryBuilder.scss'
3131
import {isFlagEnabled} from 'src/shared/utils/featureFlag'
32+
import {ScriptProvider} from 'src/dataExplorer/context/scripts'
33+
34+
export enum OverlayType {
35+
NEW = 'new',
36+
SAVE = 'save',
37+
}
3238

3339
const FluxQueryBuilder: FC = () => {
3440
const {query, vertical, setVertical} = useContext(PersistanceContext)
35-
const [isPromptVisible, setIsPromptVisible] = useState(false)
41+
const [overlayType, setOverlayType] = useState<OverlayType | null>(null)
3642

3743
return (
3844
<EditorProvider>
3945
<SidebarProvider>
40-
<Overlay visible={isPromptVisible}>
41-
<SaveAsScript onClose={() => setIsPromptVisible(false)} />
42-
</Overlay>
43-
<FlexBox
44-
className="flux-query-builder--container"
45-
direction={FlexDirection.Column}
46-
justifyContent={JustifyContent.SpaceBetween}
47-
alignItems={AlignItems.Stretch}
48-
>
49-
<div
50-
className="flux-query-builder--menu"
51-
data-testid="flux-query-builder--menu"
52-
>
53-
<Button
54-
onClick={() => setIsPromptVisible(true)}
55-
text="New Script"
56-
icon={IconFont.Plus_New}
57-
status={
58-
query.length === 0
59-
? ComponentStatus.Disabled
60-
: ComponentStatus.Default
61-
}
46+
<ScriptProvider>
47+
<Overlay visible={overlayType !== null}>
48+
<SaveAsScript
49+
type={overlayType}
50+
onClose={() => setOverlayType(null)}
6251
/>
63-
{isFlagEnabled('saveAsScript') && (
52+
</Overlay>
53+
<FlexBox
54+
className="flux-query-builder--container"
55+
direction={FlexDirection.Column}
56+
justifyContent={JustifyContent.SpaceBetween}
57+
alignItems={AlignItems.Stretch}
58+
>
59+
<div
60+
className="flux-query-builder--menu"
61+
data-testid="flux-query-builder--menu"
62+
>
6463
<Button
65-
onClick={() => {
66-
// TODO(ariel): hook this up
67-
}}
68-
text="Save Script"
69-
icon={IconFont.Save}
64+
onClick={() => setOverlayType(OverlayType.NEW)}
65+
text="New Script"
66+
icon={IconFont.Plus_New}
67+
status={
68+
query.length === 0
69+
? ComponentStatus.Disabled
70+
: ComponentStatus.Default
71+
}
7072
/>
71-
)}
72-
</div>
73-
<DraggableResizer
74-
handleOrientation={Orientation.Vertical}
75-
handlePositions={vertical}
76-
onChangePositions={setVertical}
77-
>
78-
<DraggableResizer.Panel>
79-
<Schema />
80-
</DraggableResizer.Panel>
81-
<DraggableResizer.Panel
82-
testID="flux-query-builder-middle-panel"
83-
className="new-data-explorer-rightside"
73+
{isFlagEnabled('saveAsScript') && (
74+
<Button
75+
className="flux-query-builder__save-button"
76+
onClick={() => setOverlayType(OverlayType.SAVE)}
77+
text="Save Script"
78+
icon={IconFont.Save}
79+
/>
80+
)}
81+
</div>
82+
<DraggableResizer
83+
handleOrientation={Orientation.Vertical}
84+
handlePositions={vertical}
85+
onChangePositions={setVertical}
8486
>
85-
<ResultsPane />
86-
</DraggableResizer.Panel>
87-
<DraggableResizer.Panel>
88-
<Sidebar />
89-
</DraggableResizer.Panel>
90-
</DraggableResizer>
91-
</FlexBox>
87+
<DraggableResizer.Panel>
88+
<Schema />
89+
</DraggableResizer.Panel>
90+
<DraggableResizer.Panel
91+
testID="flux-query-builder-middle-panel"
92+
className="new-data-explorer-rightside"
93+
>
94+
<ResultsPane />
95+
</DraggableResizer.Panel>
96+
<DraggableResizer.Panel>
97+
<Sidebar />
98+
</DraggableResizer.Panel>
99+
</DraggableResizer>
100+
</FlexBox>
101+
</ScriptProvider>
92102
</SidebarProvider>
93103
</EditorProvider>
94104
)

src/dataExplorer/components/SaveAsScript.tsx

Lines changed: 104 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1-
import React, {FC, useContext, useCallback, useState} from 'react'
1+
import React, {FC, useContext, useCallback, useState, ChangeEvent} from 'react'
22
import {
33
Button,
44
ComponentColor,
5+
ComponentStatus,
6+
Form,
57
Input,
68
InputLabel,
79
InputType,
@@ -15,71 +17,134 @@ import {
1517
} from 'src/dataExplorer/context/persistance'
1618
import {RemoteDataState} from 'src/types'
1719
import './SaveAsScript.scss'
20+
import {CLOUD} from 'src/shared/constants'
21+
import {ScriptContext} from 'src/dataExplorer/context/scripts'
22+
import {OverlayType} from './FluxQueryBuilder'
23+
import {useDispatch} from 'react-redux'
24+
import {notify} from 'src/shared/actions/notifications'
25+
import {
26+
scriptSaveFail,
27+
scriptSaveSuccess,
28+
} from 'src/shared/copy/notifications/categories/scripts'
1829

1930
interface Props {
2031
onClose: () => void
32+
type: OverlayType | null
2133
}
2234

23-
const SaveAsScript: FC<Props> = ({onClose}) => {
35+
const SaveAsScript: FC<Props> = ({onClose, type}) => {
36+
const dispatch = useDispatch()
2437
const {setQuery, setSelection} = useContext(PersistanceContext)
2538
const {cancel} = useContext(QueryContext)
39+
const {handleSave} = useContext(ScriptContext)
40+
const [name, setName] = useState('')
41+
const [description, setDescription] = useState('')
2642
const {setStatus, setResult} = useContext(ResultsContext)
27-
const [scriptName, setScriptName] = useState(new Date().toISOString())
43+
44+
const handleClose = useCallback(() => {
45+
setDescription('')
46+
setName(`Untitle Script: ${new Date().toISOString()}`)
47+
onClose()
48+
}, [onClose, setDescription, setName])
49+
50+
const handleUpdateDescription = (event: ChangeEvent<HTMLInputElement>) => {
51+
setDescription(event.target.value)
52+
}
53+
54+
const handleUpdateName = (event: ChangeEvent<HTMLInputElement>) => {
55+
setName(event.target.value)
56+
}
2857

2958
const clear = useCallback(() => {
3059
cancel()
3160
setStatus(RemoteDataState.NotStarted)
3261
setResult(null)
3362
setQuery('')
3463
setSelection(JSON.parse(JSON.stringify(DEFAULT_SCHEMA)))
35-
onClose()
36-
}, [onClose, setQuery, setStatus, setResult, setSelection, cancel])
64+
handleClose()
65+
}, [handleClose, setQuery, setStatus, setResult, setSelection, cancel])
66+
67+
const handleSaveScript = () => {
68+
try {
69+
handleSave(name, description)
70+
71+
dispatch(notify(scriptSaveSuccess(name)))
3772

38-
const updateScriptName = event => {
39-
setScriptName(event.target.value)
73+
if (type === OverlayType.NEW) {
74+
clear()
75+
}
76+
} catch (error) {
77+
dispatch(notify(scriptSaveFail(name)))
78+
console.error({error})
79+
} finally {
80+
handleClose()
81+
}
82+
}
83+
84+
if (type == null) {
85+
return null
86+
}
87+
88+
let overlayTitle = 'Save Script'
89+
90+
if (type === OverlayType.NEW) {
91+
overlayTitle = 'Do you want to save your Script first?'
4092
}
4193

4294
return (
4395
<Overlay.Container maxWidth={500}>
44-
<Overlay.Header
45-
title="Do you want to save your Script first?"
46-
onDismiss={onClose}
47-
/>
96+
<Overlay.Header title={overlayTitle} onDismiss={handleClose} />
4897
<Overlay.Body>
49-
<div className="save-script-overlay__warning-text">
50-
"Untitled Script: {scriptName}" will be overwritten by a new one if
51-
you don’t save it.
52-
</div>
53-
<InputLabel>Save as</InputLabel>
54-
<Input
55-
name="scriptName"
56-
type={InputType.Text}
57-
value={scriptName}
58-
onChange={updateScriptName}
59-
/>
98+
{type === OverlayType.NEW && (
99+
<div className="save-script-overlay__warning-text">
100+
"{name}" will be overwritten by a new one if you don’t save it.
101+
</div>
102+
)}
103+
<Form>
104+
<InputLabel>Save as</InputLabel>
105+
<Input
106+
className="save-script-name__input"
107+
name="name"
108+
required
109+
type={InputType.Text}
110+
value={name}
111+
onChange={handleUpdateName}
112+
/>
113+
<InputLabel>Description</InputLabel>
114+
<Input
115+
name="description"
116+
required
117+
type={InputType.Text}
118+
value={description}
119+
onChange={handleUpdateDescription}
120+
/>
121+
</Form>
60122
</Overlay.Body>
61123
<Overlay.Footer>
62124
<Button
63125
color={ComponentColor.Tertiary}
64-
onClick={onClose}
126+
onClick={handleClose}
65127
text="Cancel"
66-
testID="cancel-service-confirmation--button"
67-
/>
68-
<Button
69-
color={ComponentColor.Default}
70-
onClick={clear}
71-
text="No, Delete"
72-
testID="cancel-service-confirmation--button"
73-
/>
74-
<Button
75-
color={ComponentColor.Primary}
76-
onClick={() => {
77-
alert('this is a WIP and will be connected soon')
78-
// TODO(ariel): hook this up
79-
}}
80-
text="Yes, Save"
81-
testID="cancel-service-confirmation--button"
82128
/>
129+
{type === OverlayType.NEW && (
130+
<Button
131+
color={ComponentColor.Default}
132+
onClick={clear}
133+
text="No, Delete"
134+
/>
135+
)}
136+
{CLOUD && (
137+
<Button
138+
color={ComponentColor.Primary}
139+
status={
140+
name.length === 0 || description.length === 0
141+
? ComponentStatus.Disabled
142+
: ComponentStatus.Default
143+
}
144+
onClick={handleSaveScript}
145+
text={type === OverlayType.NEW ? 'Yes, Save' : 'Save'}
146+
/>
147+
)}
83148
</Overlay.Footer>
84149
</Overlay.Container>
85150
)
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import React, {FC, createContext, useContext, useCallback} from 'react'
2+
import {PersistanceContext} from 'src/dataExplorer/context/persistance'
3+
import {CLOUD} from 'src/shared/constants'
4+
5+
let postScript
6+
7+
if (CLOUD) {
8+
postScript = require('src/client/scriptsRoutes').postScript
9+
}
10+
11+
interface ContextType {
12+
handleSave: (name: string, description: string) => void
13+
}
14+
15+
const DEFAULT_CONTEXT = {
16+
handleSave: (_name: string, _description: string) => {},
17+
}
18+
19+
export const ScriptContext = createContext<ContextType>(DEFAULT_CONTEXT)
20+
21+
export const ScriptProvider: FC = ({children}) => {
22+
const {query} = useContext(PersistanceContext)
23+
24+
const handleSave = useCallback(
25+
async (name: string, description: string) => {
26+
if (postScript) {
27+
const resp = await postScript({
28+
data: {
29+
name: name,
30+
description,
31+
script: query,
32+
language: 'flux',
33+
},
34+
})
35+
36+
if (resp.status !== 201) {
37+
throw new Error(resp.data.message)
38+
}
39+
} else {
40+
alert('You are in an unsupported environment')
41+
}
42+
},
43+
[query]
44+
)
45+
46+
return (
47+
<ScriptContext.Provider
48+
value={{
49+
handleSave,
50+
}}
51+
>
52+
{children}
53+
</ScriptContext.Provider>
54+
)
55+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import {Notification} from 'src/types'
2+
import {
3+
defaultErrorNotification,
4+
defaultSuccessNotification,
5+
} from 'src/shared/copy/notifications'
6+
7+
// Scripts
8+
9+
export const scriptSaveFail = (scriptName: string): Notification => ({
10+
...defaultErrorNotification,
11+
message: `${scriptName} failed to save`,
12+
})
13+
14+
export const scriptSaveSuccess = (scriptName: string): Notification => ({
15+
...defaultSuccessNotification,
16+
message: `${scriptName} has been saved`,
17+
})

0 commit comments

Comments
 (0)