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

CSCfi / metadata-submitter-frontend / 15132822209

20 May 2025 08:35AM UTC coverage: 47.784% (-0.08%) from 47.859%
15132822209

push

github

Hang Le
Feature/fix formatting (merge commit)

Merge branch 'feature/fix-formatting' into 'main'
* Format all files missed by incorrect format script

* Update precommit for husky v9

* Fix formatting script to include all files

Closes #1033
See merge request https://gitlab.ci.csc.fi/sds-dev/sd-submit/metadata-submitter-frontend/-/merge_requests/1114

Approved-by: Hang Le <lhang@csc.fi>
Co-authored-by: Monika Radaviciute <mradavic@csc.fi>
Merged by Hang Le <lhang@csc.fi>

656 of 963 branches covered (68.12%)

Branch coverage included in aggregate %.

150 of 326 new or added lines in 48 files covered. (46.01%)

5 existing lines in 4 files now uncovered.

6353 of 13705 relevant lines covered (46.36%)

4.25 hits per line

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

0.0
/src/components/SubmissionWizard/WizardForms/WizardUploadObjectXMLForm.tsx
1
import React, { useState, useRef, useEffect } from "react"
×
2

3
import Alert from "@mui/material/Alert"
×
4
import Button from "@mui/material/Button"
×
5
import MuiCardHeader from "@mui/material/CardHeader"
×
6
import Container from "@mui/material/Container"
×
7
import MuiFormControl from "@mui/material/FormControl"
×
8
import LinearProgress from "@mui/material/LinearProgress"
×
9
import TextField from "@mui/material/TextField"
×
10
import { styled } from "@mui/system"
×
11
import { useDropzone } from "react-dropzone"
×
12
import { useForm } from "react-hook-form"
×
13

14
import { ResponseStatus } from "constants/responseStatus"
×
15
import { ObjectSubmissionTypes, ObjectStatus } from "constants/wizardObject"
×
16
import { resetFocus } from "features/focusSlice"
×
17
import { setLoading, resetLoading } from "features/loadingSlice"
×
18
import { resetStatusDetails, updateStatus } from "features/statusMessageSlice"
×
19
import { resetCurrentObject } from "features/wizardCurrentObjectSlice"
×
20
import { addObject, replaceObjectInSubmission } from "features/wizardSubmissionSlice"
×
21
import { useAppSelector, useAppDispatch } from "hooks"
×
22
import objectAPIService from "services/objectAPI"
×
23
import xmlSubmissionAPIService from "services/xmlSubmissionAPI"
×
24

25
const CardHeader = styled(MuiCardHeader)(({ theme }) => ({
×
26
  "&.MuiCardHeader-root": { ...theme.wizard.cardHeader },
×
27
  "&.MuiCardHeader-action": { margin: -1 },
×
28
}))
×
29

30
const FormControl = styled(MuiFormControl)({
×
31
  "&.MuiFormControl-root": {
×
32
    display: "flex",
×
33
    flexWrap: "wrap",
×
34
    "& > *": {
×
35
      margin: 5,
×
36
    },
×
37
  },
×
38
})
×
39

40
const FileField = styled("div")({
×
41
  display: "inline-flex",
×
42
})
×
43

44
/*
45
 * Return React Hook Form based form for uploading xml files. Handles form submitting, validating and error/success alerts.
46
 */
47
const WizardUploadObjectXMLForm: React.FC = () => {
×
48
  const [isSubmitting, setSubmitting] = useState(false)
×
49
  const objectType = useAppSelector(state => state.objectType)
×
50
  const { submissionId } = useAppSelector(state => state.submission)
×
51
  const dispatch = useAppDispatch()
×
52
  const currentObject = useAppSelector(state => state.currentObject)
×
53
  const {
×
54
    register,
×
55
    watch,
×
56
    setValue,
×
57
    formState: { errors, isValidating },
×
58
    handleSubmit,
×
59
    reset,
×
60
  } = useForm({ mode: "onChange" })
×
61
  const [placeHolder, setPlaceHolder] = useState("Name")
×
62

63
  const watchFile = watch("fileUpload")
×
64

65
  const focusTarget = useRef<HTMLLabelElement | null>(null)
×
66
  const shouldFocus = useAppSelector(state => state.focus)
×
67

68
  useEffect(() => {
×
69
    if (shouldFocus && focusTarget.current) focusTarget.current.focus()
×
70
  }, [shouldFocus])
×
71

72
  useEffect(() => {
×
73
    if (watchFile && watchFile[0] && watchFile[0].name) {
×
74
      setPlaceHolder(watchFile[0].name)
×
75
    } else {
×
76
      setPlaceHolder(currentObject?.tags?.fileName || "Name")
×
77
    }
×
78
  }, [currentObject, watchFile])
×
79

80
  const onDrop = async (acceptedFiles: File[]) => {
×
81
    const filelist = new DataTransfer()
×
82
    // Accept all file types. Validation is done in file upload form by React Hook Form.
83
    filelist.items.add(acceptedFiles[0])
×
84
    setValue("fileUpload", filelist.files, { shouldValidate: true })
×
85
  }
×
86

87
  const { getRootProps, isDragActive } = useDropzone({
×
88
    maxFiles: 1,
×
89
    noClick: true,
×
90
    noKeyboard: true,
×
91
    onDrop: onDrop,
×
92
  })
×
93

94
  const resetForm = () => {
×
95
    reset()
×
96
    setPlaceHolder("Name")
×
97
  }
×
98

99
  type FileUpload = { fileUpload: FileList }
100

101
  const fileName = watchFile && watchFile[0] ? watchFile[0].name : "No file name"
×
102
  const onSubmit = async (data: FileUpload) => {
×
103
    dispatch(resetStatusDetails())
×
104
    setSubmitting(true)
×
105
    dispatch(setLoading())
×
106
    const file = data.fileUpload[0] || {}
×
107
    const waitForServertimer = setTimeout(() => {
×
108
      dispatch(updateStatus({ status: ResponseStatus.info }))
×
109
    }, 5000)
×
110

111
    if (currentObject.accessionId) {
×
NEW
112
      const response = await objectAPIService.replaceXML(
×
NEW
113
        objectType,
×
NEW
114
        currentObject.accessionId,
×
NEW
115
        file
×
NEW
116
      )
×
117

118
      if (response.ok) {
×
119
        dispatch(
×
120
          replaceObjectInSubmission(
×
121
            currentObject.accessionId,
×
122

123
            {
×
124
              submissionType: ObjectSubmissionTypes.xml,
×
125
              fileName: fileName,
×
126
              displayTitle: fileName,
×
127
            },
×
128
            ObjectStatus.submitted
×
129
          )
×
130
        )
×
131
        dispatch(updateStatus({ status: ResponseStatus.success, response: response }))
×
132
        dispatch(resetCurrentObject())
×
133
        resetForm()
×
134
      } else {
×
135
        dispatch(updateStatus({ status: ResponseStatus.error, response: response }))
×
136
      }
×
137
    } else {
×
138
      const response = await objectAPIService.createFromXML(objectType, submissionId, file)
×
139

140
      if (response.ok) {
×
141
        dispatch(updateStatus({ status: ResponseStatus.success, response: response }))
×
142
        dispatch(
×
143
          addObject({
×
144
            accessionId: response.data.accessionId,
×
145
            schema: objectType,
×
146
            tags: { submissionType: ObjectSubmissionTypes.xml, fileName, displayTitle: fileName },
×
147
          })
×
148
        )
×
149
        resetForm()
×
150
      } else {
×
151
        dispatch(updateStatus({ status: ResponseStatus.error, response: response }))
×
152
      }
×
153
    }
×
154
    clearTimeout(waitForServertimer)
×
155
    setSubmitting(false)
×
156
    dispatch(resetLoading())
×
157
  }
×
158

159
  const handleButton = () => {
×
160
    const fileSelect = document && document.getElementById("file-select-button")
×
161
    if (fileSelect) {
×
162
      fileSelect.click()
×
163
    }
×
164
    dispatch(resetFocus())
×
165
  }
×
166

167
  const submitButton = (
×
168
    <Button
×
169
      sx={{ bgcolor: "#FFF", color: "primary.main" }}
×
170
      variant="contained"
×
171
      size="small"
×
NEW
172
      disabled={
×
NEW
173
        isSubmitting ||
×
NEW
174
        !watchFile ||
×
NEW
175
        watchFile.length === 0 ||
×
NEW
176
        errors.fileUpload != null ||
×
NEW
177
        isValidating
×
178
      }
UNCOV
179
      onClick={handleSubmit(async data => onSubmit(data as FileUpload))}
×
180
    >
181
      {currentObject?.status === ObjectStatus.submitted ? "Replace" : "Submit"}
×
182
    </Button>
×
183
  )
184

185
  return (
×
186
    <Container
×
187
      sx={theme =>
×
NEW
188
        isDragActive
×
NEW
189
          ? { flex: 1, bgcolor: theme.palette.primary.light, border: "2px dashed #51A808" }
×
NEW
190
          : { p: 0 }
×
191
      }
192
      maxWidth={false}
×
193
      {...getRootProps()}
×
194
    >
NEW
195
      <CardHeader
×
NEW
196
        title="Upload XML File"
×
NEW
197
        titleTypographyProps={{ variant: "inherit" }}
×
NEW
198
        action={submitButton}
×
199
      />
200
      {/* React Hook Form */}
201
      <form onSubmit={handleSubmit(async data => onSubmit(data as FileUpload))}>
×
202
        <FormControl>
×
203
          <FileField>
×
204
            <TextField
×
205
              placeholder={placeHolder}
×
206
              inputProps={{ readOnly: true, tabIndex: -1 }}
×
207
              data-testid="xml-file-name"
×
208
            />
209
            <Button
×
210
              ref={focusTarget}
×
211
              variant="contained"
×
212
              color="primary"
×
213
              component="label"
×
214
              onClick={() => handleButton()}
×
215
              onBlur={() => dispatch(resetFocus())}
×
216
            >
217
              Choose file
218
            </Button>
×
219
            <input
×
220
              type="file"
×
221
              id="file-select-button"
×
222
              data-testid="xml-upload"
×
223
              hidden
×
224
              {...register("fileUpload", {
×
225
                validate: {
×
226
                  isFile: value => value.length > 0,
×
227
                  isXML: value => value[0]?.type === "text/xml",
×
228
                  isValidXML: async value => {
×
NEW
229
                    const response = await xmlSubmissionAPIService.validateXMLFile(
×
NEW
230
                      objectType,
×
NEW
231
                      value[0]
×
NEW
232
                    )
×
233

234
                    if (!response.data.isValid) {
×
235
                      return `The file you attached is not valid ${objectType},
×
236
                      our server reported following error:
237
                      ${response.data.detail.reason}.`
×
238
                    }
×
239
                  },
×
240
                },
×
241
              })}
×
242
            />
243
          </FileField>
×
244
          {/* Helper text for selecting / submitting file */}
245
          {!watchFile || watchFile.length === 0 || errors.fileUpload != null ? (
×
246
            <p>Choose a file or drag it here.</p>
×
247
          ) : (
248
            <p>Use Submit button to upload the file.</p>
×
249
          )}
250
          {/* Errors */}
NEW
251
          {errors.fileUpload?.type === "isFile" && (
×
NEW
252
            <Alert severity="error">Please attach a file.</Alert>
×
253
          )}
NEW
254
          {errors.fileUpload?.type === "isXML" && (
×
NEW
255
            <Alert severity="error">Please attach an XML file.</Alert>
×
256
          )}
257
          {errors.fileUpload?.type === "isValidXML" && (
×
258
            <Alert severity="error">{errors?.fileUpload?.message?.toString()}</Alert>
×
259
          )}
260
          {/* Progress bar */}
261
          {isSubmitting && <LinearProgress />}
×
262
        </FormControl>
×
263
      </form>
×
264
    </Container>
×
265
  )
266
}
×
267

268
export default WizardUploadObjectXMLForm
×
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

© 2026 Coveralls, Inc