• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

barseghyanartur / faker-file-ui / 6165655993

12 Sep 2023 10:46PM UTC coverage: 67.803% (-7.8%) from 75.587%
6165655993

push

github-actions

web-flow
Merge pull request #14 from barseghyanartur/dev

Improve UI

66 of 108 branches covered (0.0%)

Branch coverage included in aggregate %.

15 of 15 new or added lines in 1 file covered. (100.0%)

113 of 156 relevant lines covered (72.44%)

2366.86 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

67.05
/src/FileGenerator.js
1
import * as React from "react";
2
import { useState, useEffect } from "react";
3
import { createTheme, ThemeProvider } from "@mui/material/styles";
4
import Checkbox from "@mui/material/Checkbox";
5
import FormControl from "@mui/material/FormControl";
6
import FormControlLabel from "@mui/material/FormControlLabel";
7
import {
8
  Grid,
9
  Link,
10
  List,
11
  ListItemButton,
12
  ListItemText,
13
  TextField,
14
  Typography,
15
  Button,
16
} from "@mui/material";
17
import Box from "@mui/material/Box";
18
import Backdrop from "@mui/material/Backdrop";
19
import CircularProgress from "@mui/material/CircularProgress";
20
import InputLabel from "@mui/material/InputLabel";
21
import Item from "@mui/material/Grid";
22
import MenuItem from "@mui/material/MenuItem";
23
import Select from "@mui/material/Select";
24
import Snackbar from "@mui/material/Snackbar";
25
import Alert from "@mui/material/Alert";
26
import axios from "axios";
27
import axiosRetry from "axios-retry";
28

29
function App() {
30
  if (process.env.NODE_ENV === "production") {
3,960!
31
    console.log = function () {};
×
32
    console.debug = function () {};
×
33
    console.info = function () {};
×
34
    console.warn = console.error;
×
35
  }
36

37
  const apiUrl = process.env.REACT_APP_API_URL;
3,960✔
38
  const [loading, setLoading] = useState(true);
3,960✔
39
  const [inProgress, setInProgress] = useState(false);
3,948✔
40
  const [endpoints, setEndpoints] = useState(null);
3,948✔
41
  const [selectedEndpoint, setSelectedEndpoint] = useState(null);
3,948✔
42
  const [selectedModel, setSelectedModel] = useState(null);
3,948✔
43
  const [selectedFileExtension, setSelectedFileExtension] = useState(null);
3,948✔
44
  const [formOptions, setFormOptions] = useState({});
3,948✔
45
  const [downloadUrl, setDownloadUrl] = useState(null);
3,948✔
46
  const [models, setModels] = useState(null);
3,948✔
47
  const [filename, setFilename] = useState(null);
3,948✔
48
  const [showError, setShowError] = useState(false);
3,948✔
49
  const [errorMessage, setErrorMessage] = useState(null);
3,948✔
50
  const multilines = [
3,948✔
51
    "content",
52
    "data_columns",
53
    "options",
54
    "mp3_generator_kwargs",
55
    "pdf_generator_kwargs",
56
    "image_generator_kwargs",
57
  ]; // Multi-line fields
58

59
  axiosRetry(axios, {
3,948✔
60
    retries: 20,
61
    retryDelay: (retryCount) => {
62
      console.log("retryCount");
×
63
      console.log(retryCount);
×
64
      return retryCount * 1000;
×
65
    },
66
  });
67

68
  useEffect(() => {
3,948✔
69
    const fetchEndpoints = async () => {
276✔
70
      const response = await axios.get(`${apiUrl}/openapi.json`, {
276✔
71
        retry: {
72
          retries: 20,
73
        },
74
      });
75
      const schema = response.data;
276✔
76
      const paths = schema.paths;
276✔
77
      const endpoints = Object.keys(paths).reduce((acc, path) => {
276✔
78
        const endpoint = path.split("/")[1];
6,900✔
79
        if (endpoint !== "heartbeat" && endpoint !== "providers") {
6,900✔
80
          acc[endpoint] =
6,348✔
81
            paths[path].post.requestBody.content["application/json"].schema;
82
        }
83
        return acc;
6,900✔
84
      }, {});
85
      setEndpoints(endpoints);
276✔
86
      console.log("ENDPOINTS");
276✔
87
      console.log(endpoints);
276✔
88

89
      const models = schema.components.schemas;
276✔
90
      setModels(models);
276✔
91
      setLoading(false);
276✔
92
      console.log("MODELS");
276✔
93
      console.log(models);
276✔
94
    };
95
    fetchEndpoints();
276✔
96
  }, []);
97

98
  useEffect(() => {
3,948✔
99
    if (endpoints && selectedEndpoint) {
828✔
100
      console.log(endpoints[selectedEndpoint]);
276✔
101
      setFormOptions(
276✔
102
        Object.fromEntries(
103
          Object.entries(endpoints[selectedEndpoint].properties || {}).map(
276✔
104
            ([name, property]) => [name, property.default]
×
105
          )
106
        )
107
      );
108
    }
109
  }, [endpoints, selectedEndpoint]);
110

111
  const handleEndpointClick = (endpoint) => {
3,948✔
112
    console.log(endpoint);
276✔
113
    setSelectedEndpoint(endpoint);
276✔
114
    setSelectedFileExtension(endpoint.slice(0, endpoint.indexOf("_file")));
276✔
115
    setSelectedModel(`${endpoint}_model`);
276✔
116
    console.log(`model: ${endpoint}_model`);
276✔
117
    console.log(models[`${endpoint}_model`]);
276✔
118
    setFormOptions(models[`${endpoint}_model`].properties || {});
276!
119
    setDownloadUrl(null);
276✔
120
    setFilename(null);
276✔
121
  };
122

123
  const handleOptionChange = (event) => {
3,948✔
124
    const { name, value, checked } = event.target;
2,568✔
125
    console.log(models[selectedModel].properties);
2,568✔
126
    console.log(name);
2,568✔
127
    console.log(models[selectedModel].properties[name]);
2,568✔
128
    console.log("checked");
2,568✔
129
    console.log(checked);
2,568✔
130
    console.log("value", value);
2,568✔
131
    let valueType = models[selectedModel].properties[name]?.type;
2,568✔
132
    setFormOptions((prevOptions) => ({
2,568✔
133
      ...prevOptions,
134
      [name]:
135
        valueType === "boolean"
1,284!
136
          ? checked
137
          : value,
138
    }));
139
  };
140

141
  const handleSubmit = async () => {
3,948✔
142
    try {
138✔
143
      setInProgress(true);
138✔
144
      const body = JSON.stringify(
138✔
145
        Object.fromEntries(
146
          Object.entries(formOptions).map(([name, value]) => {
147
            if (["data_columns"].includes(name) && typeof value === "string") {
300✔
148
              if (["csv_file"].includes(selectedModel)) {
6!
149
                value = value.split(",").map((str) => str.trim());
×
150
              } else {
151
                value = JSON.parse(value);
6✔
152
              }
153
            } else if (
294!
154
              [
147!
155
                "options",
156
                "mp3_generator_kwargs",
157
                "pdf_generator_kwargs",
158
                "image_generator_kwargs",
159
              ].includes(name) &&
160
              typeof value === "string" &&
161
              value.trim() !== ""
162
            ) {
163
              try {
×
164
                value = JSON.parse(value);
×
165
              } catch (e) {
166
                console.log("invalid value");
×
167
                console.log(value);
×
168
                value = null;
×
169
              }
170
            } else if (["size"].includes(name) && typeof value === "string" && value.trim() !== "") {
294!
171
              value = value.split(",");
×
172
            }
173
            console.log("name");
300✔
174
            console.log(name);
300✔
175
            console.log("value");
300✔
176
            console.log(value);
300✔
177
            if (value && value.constructor === String && value.trim() === "") {
300!
178
              value = null;
×
179
            }
180
            return [name, value];
300✔
181
          })
182
        )
183
      );
184
      console.log("body");
138✔
185
      console.log(body);
138✔
186
      const response = await fetch(`${apiUrl}/${selectedEndpoint}/`, {
138✔
187
        method: "POST",
188
        headers: {
189
          "Content-Type": "application/json",
190
        },
191
        body: body,
192
      });
193

194
      if (!response.ok) {
138!
195
        const errorResponse = await response.json();
×
196
        console.error("Server responded with an error:", errorResponse);
×
197
        // If errorResponse contains a message, use it. Otherwise use a default message
198
        const _errorMessage = errorResponse.message || "An error occurred";
×
199
        // Handle the error based on errorResponse here
200
        setInProgress(false);
×
201
        setErrorMessage(_errorMessage);
×
202
        setShowError(true);
×
203
        return;
×
204
      }
205

206
      const blob = await response.blob();
138✔
207
      console.log("response");
138✔
208
      console.log(response);
138✔
209
      const downloadUrl = window.URL.createObjectURL(blob);
138✔
210
      setDownloadUrl(downloadUrl);
138✔
211
      setInProgress(false);
138✔
212

213
      // Get the content-disposition header
214
      const contentDisposition = response.headers.get("content-disposition");
138✔
215
      console.log(contentDisposition);
138✔
216

217
      // Extract the 'filename' value
218
      const match = /filename=([^;]+)/.exec(contentDisposition);
138✔
219
      console.log(match);
138✔
220

221
      let filename;
222
      if (match && match.length > 1) {
138!
223
        filename = match[1];
138✔
224
        setFilename(filename);
138✔
225
      } else {
226
        setFilename(`${selectedEndpoint}.${selectedFileExtension}`);
×
227
      }
228
      console.log("filename");
138✔
229
      console.log(filename);
138✔
230
    } catch (err) {
231
      console.log("An error occurred:");
×
232
      console.log(err);
×
233
      console.error("An error occurred:", err);
×
234
      // If err object contains a message, use it. Otherwise use a default message
235
      const _errorMessage = err.message || "An error occurred";
×
236

237
      // Handle the error here
238
      setInProgress(false);
×
239
      setErrorMessage(_errorMessage);
×
240
      setShowError(true);
×
241
    }
242
  };
243

244
  const theme = createTheme();
3,948✔
245

246
  if (loading) {
3,948✔
247
    return (
276✔
248
      <ThemeProvider theme={theme}>
249
        <Box
250
          sx={{
251
            display: "flex",
252
            justifyContent: "center",
253
            alignItems: "center",
254
          }}
255
        >
256
          <CircularProgress size={200} />
257
        </Box>
258
      </ThemeProvider>
259
    );
260
  }
261

262
  return (
3,672✔
263
    <ThemeProvider theme={theme}>
264
      <Grid
265
        container
266
        spacing={0}
267
        columns={12}
268
        sx={{
269
          "--Grid-borderWidth": "1px",
270
          borderTop: "var(--Grid-borderWidth) solid",
271
          borderLeft: "var(--Grid-borderWidth) solid",
272
          borderColor: "divider",
273
          "& > div": {
274
            borderRight: "var(--Grid-borderWidth) solid",
275
            borderBottom: "var(--Grid-borderWidth) solid",
276
            borderColor: "divider",
277
          },
278
        }}
279
      >
280
        <Grid item xs={2}>
281
          <Item sx={{ py: 2, px: 2 }}>
282
            <Typography variant="h4" component="h1" gutterBottom>
283
              File type
284
            </Typography>
285
            <List component="nav" aria-label="main providers folders">
286
              {endpoints &&
3,672✔
287
                Object.keys(endpoints).map((endpoint) => (
288
                  <ListItemButton
84,456✔
289
                    key={endpoint}
290
                    selected={selectedEndpoint === endpoint}
291
                    onClick={() => handleEndpointClick(endpoint)}
276✔
292
                  >
293
                    <ListItemText primary={endpoint.split("_file")[0].replace(/_/g, " ")} />
294
                  </ListItemButton>
295
                ))}
296
            </List>
297
          </Item>
298
        </Grid>
299
        <Grid item xs={8}>
300
          <Item sx={{ py: 2, px: 2 }}>
301
            <Typography variant="h4" component="h1" gutterBottom>
302
              Options
303
            </Typography>
304
            {selectedEndpoint && endpoints && (
5,232✔
305
              <div>
306
                <form>
307
                  {Object.entries(models[selectedModel].properties || {}).map(
1,698!
308
                    ([name, property]) => {
309
                      const inputProps = {
20,136✔
310
                        style: { height: "auto" },
311
                      };
312
                      const props = multilines.includes(name)
20,136✔
313
                        ? { multiline: true, rows: 4, maxRows: 20, inputProps }
314
                        : {};
315
                      const key = `${selectedModel}_${name}`;
20,136✔
316
                      if (property?.type === "boolean") {
20,136✔
317
                        return (
72✔
318
                          <FormControlLabel
319
                            key={key}
320
                            control={
321
                              <Checkbox
322
                                name={name}
323
                                label={property.title || name}
36!
324
                                checked={
325
                                  formOptions[name] || property.default || false
96✔
326
                                }
327
                                margin="normal"
328
                                variant="outlined"
329
                                onChange={handleOptionChange}
330
                                {...props}
331
                              />
332
                            }
333
                            label={property.title || name}
36!
334
                          />
335
                        );
336
                      } else if (property?.allOf) {
20,064!
337
                        let defaultValue;
338
                        let enumModel;
339
                        let label;
340
                        for (const condition of property.allOf) {
×
341
                          if (condition.$ref) {
×
342
                            const modelName = condition.$ref.split("/").pop();
×
343
                            enumModel = models[modelName]?.enum;
×
344
                            label = models[modelName]?.title || name;
×
345
                            if (enumModel) break;
×
346
                          }
347
                        }
348
                        // console.log("enumModel", enumModel);
349
                        defaultValue = property?.default || enumModel?.[0] || "";
×
350
                        // Initialize the state if it's not set
351
                        if (formOptions[name] === undefined) {
×
352
                          setFormOptions(prevOptions => ({
×
353
                            ...prevOptions,
354
                            [name]: defaultValue,
355
                          }));
356
                        }
357
                        return (
×
358
                          <FormControl key={key} sx={{ m: 0, mt: 2, mb: 2, width: '100%' }}>
359
                            <InputLabel htmlFor={name}>{label}</InputLabel>
360
                            <Select
361
                              labelId={name}
362
                              id={name}
363
                              name={name}
364
                              value={formOptions[name] || defaultValue}
×
365
                              onChange={handleOptionChange}
366
                              margin="normal"
367
                              variant="outlined"
368
                            >
369
                              {enumModel?.map((option, index) => (
370
                                <MenuItem key={index} value={option}>
×
371
                                  {option}
372
                                </MenuItem>
373
                              ))}
374
                            </Select>
375
                          </FormControl>
376
                        );
377
                      } else {
378
                        return (
20,064✔
379
                          <TextField
380
                            key={key}
381
                            name={name}
382
                            label={property.title || name}
10,032!
383
                            defaultValue={property.default || null}
17,469✔
384
                            fullWidth
385
                            margin="normal"
386
                            variant="outlined"
387
                            onChange={handleOptionChange}
388
                            {...props}
389
                          />
390
                        );
391
                      }
392
                    }
393
                  )}
394
                  <Button
395
                    variant="contained"
396
                    color="primary"
397
                    onClick={handleSubmit}
398
                  >
399
                    Generate
400
                  </Button>
401
                  <Snackbar
402
                    anchorOrigin={{ vertical: "bottom", horizontal: "center" }}
403
                    open={showError}
404
                    autoHideDuration={5000}
405
                    onClose={() => setShowError(false)}
×
406
                  >
407
                    <Alert severity="error">{errorMessage}</Alert>
408
                  </Snackbar>
409
                </form>
410

411
                <Backdrop
412
                  sx={{
413
                    color: "#fff",
414
                    zIndex: (theme) => theme.zIndex.drawer + 1,
3,674✔
415
                  }}
416
                  open={inProgress}
417
                >
418
                  <CircularProgress color="inherit" />
419
                </Backdrop>
420
              </div>
421
            )}
422
          </Item>
423
        </Grid>
424
        <Grid item xs={2}>
425
          <Item sx={{ py: 2, px: 2 }}>
426
            <Typography variant="h4" component="h1" gutterBottom>
427
              Result
428
            </Typography>
429
            {downloadUrl && (
1,905✔
430
              <Link href={downloadUrl} download={`${filename}`}>
431
                Download
432
              </Link>
433
            )}
434
          </Item>
435
        </Grid>
436
      </Grid>
437
    </ThemeProvider>
438
  );
439
}
440

441
export default App;
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc