Skip to content

Commit e0d7e40

Browse files
committed
Save app drafts and versions
1 parent f32c95c commit e0d7e40

File tree

11 files changed

+150
-55
lines changed

11 files changed

+150
-55
lines changed

apps/apis.py

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -368,6 +368,8 @@ def patch(self, request, uid):
368368
app.discord_integration_config = DiscordIntegrationConfig(**request.data['discord_config']).to_dict(
369369
app_owner_profile.encrypt_value,
370370
) if 'discord_config' in request.data and request.data['discord_config'] else {}
371+
draft = request.data['draft'] if 'draft' in request.data else True
372+
comment = request.data['comment'] if 'comment' in request.data else ''
371373

372374
# Find the versioned app data and update it
373375
app_data = {
@@ -381,12 +383,13 @@ def patch(self, request, uid):
381383
versioned_app_data = AppData.objects.filter(
382384
app_uuid=app.uuid, is_draft=True).first()
383385
if versioned_app_data:
384-
versioned_app_data.comment = request.data['comment'] if 'comment' in request.data else ''
386+
versioned_app_data.comment = comment
385387
versioned_app_data.data = app_data
388+
versioned_app_data.is_draft = draft
386389
versioned_app_data.save()
387390
else:
388391
AppData.objects.create(
389-
app_uuid=app.uuid, data=app_data, is_draft=True, comment=request.data['comment'] if 'comment' in request.data else ''
392+
app_uuid=app.uuid, data=app_data, comment=comment, is_draft=draft
390393
)
391394

392395
app.last_modified_by = request.user
@@ -408,6 +411,8 @@ def post(self, request):
408411
}
409412
app_processors = request.data['processors'] if 'processors' in request.data else [
410413
]
414+
draft = request.data['draft'] if 'draft' in request.data else True
415+
comment = request.data['comment'] if 'comment' in request.data else 'First version'
411416

412417
template_slug = request.data['template_slug'] if 'template_slug' in request.data else None
413418
template = AppTemplate.objects.filter(
@@ -432,7 +437,7 @@ def post(self, request):
432437
'processors': app_processors,
433438
}
434439
AppData.objects.create(
435-
app_uuid=app.uuid, data=app_data, is_draft=True, comment='First version'
440+
app_uuid=app.uuid, data=app_data, is_draft=draft, comment=comment,
436441
)
437442

438443
return DRFResponse(AppSerializer(instance=app).data, status=201)
@@ -462,15 +467,20 @@ def run(self, request, uid, session_id=None, platform=None):
462467
logger.exception('Error while running app')
463468
return DRFResponse({'errors': [str(e)]}, status=400)
464469

465-
async def run_app_internal_async(self, uid, session_id, request_uuid, request):
466-
return await database_sync_to_async(self.run_app_internal)(uid, session_id, request_uuid, request)
470+
async def run_app_internal_async(self, uid, session_id, request_uuid, request, preview=False):
471+
return await database_sync_to_async(self.run_app_internal)(uid, session_id, request_uuid, request, preview=preview)
467472

468-
def run_app_internal(self, uid, session_id, request_uuid, request, platform=None, preview=True):
473+
def run_app_internal(self, uid, session_id, request_uuid, request, platform=None, preview=False):
469474
app = get_object_or_404(App, uuid=uuid.UUID(uid))
470475
app_owner = get_object_or_404(Profile, user=app.owner)
471476
app_data_obj = AppData.objects.filter(
472477
app_uuid=app.uuid, is_draft=preview).order_by('-created_at').first()
473478

479+
# If we are running a published app, use the published app data
480+
if not app_data_obj and preview:
481+
app_data_obj = AppData.objects.filter(
482+
app_uuid=app.uuid, is_draft=False).order_by('-created_at').first()
483+
474484
app_runner_class = None
475485
if platform == 'discord':
476486
app_runner_class = AppRunerFactory.get_app_runner('discord')

apps/consumers.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ def _build_request_from_input(post_data, scope):
4444
class AppConsumer(AsyncWebsocketConsumer):
4545
async def connect(self):
4646
self.app_id = self.scope['url_route']['kwargs']['app_id']
47+
self.preview = True if 'preview' in self.scope['url_route']['kwargs'] else False
4748
await self.accept()
4849

4950
async def disconnect(self, close_code):
@@ -62,7 +63,7 @@ async def _respond_to_event(self, text_data):
6263
try:
6364
request_uuid = str(uuid.uuid4())
6465
request = await _build_request_from_input({'input': input, 'stream': True}, self.scope)
65-
output_stream = await AppViewSet().run_app_internal_async(self.app_id, self._session_id, request_uuid, request)
66+
output_stream = await AppViewSet().run_app_internal_async(self.app_id, self._session_id, request_uuid, request, self.preview)
6667
async for output in output_stream:
6768
if 'errors' in output or 'session' in output:
6869
await self.send(text_data=output)

client/src/components/apps/AppDiscordConfigEditor.jsx

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
import { Box, Button, Stack, TextField } from "@mui/material";
1+
import { Box, Stack, TextField } from "@mui/material";
22
import { EmbedCodeSnippet } from "./EmbedCodeSnippets";
3+
import { AppSaveButtons } from "./AppSaveButtons";
34

45
export function AppDiscordConfigEditor(props) {
56
const { app, discordConfig, saveApp, setDiscordConfig } = props;
@@ -97,13 +98,7 @@ export function AppDiscordConfigEditor(props) {
9798
margin: "auto",
9899
}}
99100
>
100-
<Button
101-
onClick={saveApp}
102-
variant="contained"
103-
style={{ textTransform: "none", margin: "20px 0" }}
104-
>
105-
Save App
106-
</Button>
101+
<AppSaveButtons saveApp={saveApp} />
107102
</Stack>
108103
</Box>
109104
);

client/src/components/apps/AppEditor.jsx

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import { useEffect, useState } from "react";
2-
import { useParams } from "react-router-dom";
3-
import { Box, Button, Stack } from "@mui/material";
2+
import { Box, Stack } from "@mui/material";
43
import { ProcessorEditor } from "./ProcessorEditor";
54
import { AppConfigEditor } from "./AppConfigEditor";
65
import { AppOutputEditor } from "./AppOutputEditor";
76
import { AddProcessorDivider } from "./AddProcessorDivider";
87
import { getJSONSchemaFromInputFields } from "../../data/utils";
8+
import { AppSaveButtons } from "./AppSaveButtons";
99

1010
export function AppEditor(props) {
1111
const {
@@ -24,7 +24,6 @@ export function AppEditor(props) {
2424
tourOutputRef,
2525
tourSaveRef,
2626
} = props;
27-
const { appId } = useParams();
2827
const [activeStep, setActiveStep] = useState(1);
2928
const [outputSchemas, setOutputSchemas] = useState([]);
3029

@@ -114,15 +113,9 @@ export function AppEditor(props) {
114113
maxWidth: "900px",
115114
margin: "auto",
116115
}}
116+
ref={tourSaveRef}
117117
>
118-
<Button
119-
onClick={saveApp}
120-
variant="contained"
121-
style={{ textTransform: "none", margin: "20px 0" }}
122-
ref={tourSaveRef}
123-
>
124-
{appId ? "Save App" : "Create App"}
125-
</Button>
118+
<AppSaveButtons saveApp={saveApp} />
126119
</Stack>
127120
</Box>
128121
);

client/src/components/apps/AppPreview.jsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ export function AppPreview(props) {
2020

2121
useEffect(() => {
2222
if (!ws) {
23-
setWs(new Ws(`${wsUrlPrefix}/apps/${app?.uuid}`));
23+
setWs(new Ws(`${wsUrlPrefix}/apps/${app?.uuid}/preview`));
2424
}
2525
}, [app, ws, wsUrlPrefix]);
2626

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
import { useState } from "react";
2+
import {
3+
Button,
4+
TextField,
5+
Dialog,
6+
DialogActions,
7+
DialogContent,
8+
DialogContentText,
9+
DialogTitle,
10+
Stack,
11+
} from "@mui/material";
12+
13+
function AppSaveDialog({ open, setOpen, saveApp, postSave = null }) {
14+
const [comment, setComment] = useState("Update app");
15+
const handleClose = () => {
16+
setOpen(false);
17+
};
18+
19+
return (
20+
<Dialog open={open} onClose={handleClose}>
21+
<DialogTitle>Create New Version</DialogTitle>
22+
<DialogContent>
23+
<DialogContentText>
24+
To save a new version of the app, please provide a description for the
25+
change.
26+
</DialogContentText>
27+
<TextField
28+
autoFocus
29+
margin="dense"
30+
id="name"
31+
label="Describe changes"
32+
type="text"
33+
fullWidth
34+
variant="standard"
35+
value={comment}
36+
required={true}
37+
onChange={(e) => setComment(e.target.value)}
38+
multiline
39+
/>
40+
</DialogContent>
41+
<DialogActions>
42+
<Button onClick={handleClose} sx={{ textTransform: "none" }}>
43+
Cancel
44+
</Button>
45+
<Button
46+
onClick={() => {
47+
saveApp(false, comment).finally(() => {
48+
setComment("Update app");
49+
setOpen(false);
50+
51+
if (postSave) {
52+
postSave();
53+
}
54+
});
55+
}}
56+
variant="contained"
57+
sx={{ textTransform: "none" }}
58+
>
59+
Save Version
60+
</Button>
61+
</DialogActions>
62+
</Dialog>
63+
);
64+
}
65+
66+
export function AppSaveButtons({ saveApp, postSave = null }) {
67+
const [open, setOpen] = useState(false);
68+
return (
69+
<Stack direction="row" gap={1}>
70+
<AppSaveDialog
71+
open={open}
72+
setOpen={setOpen}
73+
saveApp={saveApp}
74+
postSave={postSave}
75+
/>
76+
<Button
77+
onClick={() => {
78+
saveApp().finally(() => {
79+
if (postSave) {
80+
postSave();
81+
}
82+
});
83+
}}
84+
variant="contained"
85+
style={{
86+
textTransform: "none",
87+
margin: "20px 0",
88+
backgroundColor: "#ccc",
89+
color: "#000",
90+
}}
91+
>
92+
Save Draft
93+
</Button>
94+
<Button
95+
onClick={() => setOpen(true)}
96+
variant="contained"
97+
style={{ textTransform: "none", margin: "20px 0" }}
98+
>
99+
Save App
100+
</Button>
101+
</Stack>
102+
);
103+
}

client/src/components/apps/AppSlackConfigEditor.jsx

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
import { Box, Button, Stack, TextField } from "@mui/material";
1+
import { Box, Stack, TextField } from "@mui/material";
22
import { EmbedCodeSnippet } from "./EmbedCodeSnippets";
3+
import { AppSaveButtons } from "./AppSaveButtons";
34

45
export function AppSlackConfigEditor(props) {
56
const { app, saveApp, slackConfig, setSlackConfig } = props;
@@ -61,13 +62,7 @@ export function AppSlackConfigEditor(props) {
6162
margin: "auto",
6263
}}
6364
>
64-
<Button
65-
onClick={saveApp}
66-
variant="contained"
67-
style={{ textTransform: "none", margin: "20px 0" }}
68-
>
69-
Save App
70-
</Button>
65+
<AppSaveButtons saveApp={saveApp} />
7166
</Stack>
7267
</Box>
7368
);

client/src/components/apps/AppTemplate.jsx

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import { isMobileState } from "../../data/atoms";
1818
import { get, set } from "lodash";
1919
import ThemedJsonForm from "../ThemedJsonForm";
2020
import { TextFieldWithVars } from "./TextFieldWithVars";
21+
import { AppSaveButtons } from "./AppSaveButtons";
2122

2223
function AppTemplatePage(props) {
2324
const { appData, setAppData, page } = props;
@@ -152,10 +153,8 @@ export function AppTemplate(props) {
152153
const handleSave = () => {
153154
setLoading(true);
154155
setActiveStep(steps.length);
155-
saveApp(app).finally(() => {
156-
setLoading(false);
157-
setAppSaved(true);
158-
});
156+
setLoading(false);
157+
setAppSaved(true);
159158
};
160159

161160
if (!app?.template || !appTemplate) {
@@ -203,19 +202,22 @@ export function AppTemplate(props) {
203202
color="inherit"
204203
disabled={activeStep === 0}
205204
onClick={handleBack}
206-
sx={{ mr: 1, textTransform: "none" }}
205+
sx={{ mr: 1, textTransform: "none", margin: "20px 0" }}
207206
variant="outlined"
208207
>
209208
Back
210209
</Button>
211210
<Box sx={{ flex: "1 1 auto" }} />
212-
{activeStep < steps.length && (
211+
{activeStep === steps.length - 1 && (
212+
<AppSaveButtons saveApp={saveApp} postSave={handleSave} />
213+
)}
214+
{activeStep < steps.length - 1 && (
213215
<Button
214-
onClick={activeStep < steps.length - 1 ? handleNext : handleSave}
216+
onClick={handleNext}
215217
variant="contained"
216-
sx={{ textTransform: "none" }}
218+
sx={{ textTransform: "none", margin: "20px 0" }}
217219
>
218-
{activeStep >= steps.length - 1 ? "Save App" : "Next"}
220+
Next
219221
</Button>
220222
)}
221223
</Box>

client/src/components/apps/AppWebConfigEditor.jsx

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
import { Box, Button, Stack, TextField } from "@mui/material";
1+
import { Box, Stack, TextField } from "@mui/material";
22
import { EmbedCodeSnippet } from "./EmbedCodeSnippets";
3+
import { AppSaveButtons } from "./AppSaveButtons";
34

45
export function AllowedDomainsList(props) {
56
const { allowedDomains, setAllowedDomains } = props;
@@ -56,13 +57,7 @@ export function AppWebConfigEditor(props) {
5657
margin: "auto",
5758
}}
5859
>
59-
<Button
60-
onClick={saveApp}
61-
variant="contained"
62-
style={{ textTransform: "none", margin: "20px 0" }}
63-
>
64-
Save App
65-
</Button>
60+
<AppSaveButtons saveApp={saveApp} />
6661
</Stack>
6762
</Box>
6863
);

client/src/pages/AppEdit.jsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -232,11 +232,13 @@ export default function AppEditPage(props) {
232232
});
233233
};
234234

235-
const saveApp = () => {
235+
const saveApp = (draft = true, comment = "") => {
236236
return new Promise((resolve, reject) => {
237237
const updatedApp = {
238238
name: app?.name,
239239
description: "",
240+
draft: draft,
241+
comment: comment,
240242
config: app?.data?.config,
241243
app_type: app?.type?.id,
242244
type_slug: app?.type?.slug,
@@ -249,10 +251,8 @@ export default function AppEditPage(props) {
249251
id: `_inputs${index + 1}`,
250252
provider_slug: processor.api_backend?.api_provider?.slug,
251253
processor_slug: processor.api_backend?.slug,
252-
// api_backend: processor.api_backend.id,
253254
config: processor.config,
254255
input: processor.input,
255-
// endpoint: processor.endpoint?.uuid || processor.endpoint,
256256
})),
257257
};
258258

0 commit comments

Comments
 (0)