• 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

68.56
/src/components/SubmissionWizard/WizardComponents/WizardStep.tsx
1
import React, { useState } from "react"
1✔
2

3
import ChevronRightIcon from "@mui/icons-material/ChevronRight"
1✔
4
import Button from "@mui/material/Button"
1✔
5
import Collapse from "@mui/material/Collapse"
1✔
6
import Grid from "@mui/material/Grid"
1✔
7
import Link from "@mui/material/Link"
1✔
8
import List from "@mui/material/List"
1✔
9
import ListItem from "@mui/material/ListItem"
1✔
10
import { styled } from "@mui/material/styles"
1✔
11
import { useTranslation } from "react-i18next"
1✔
12
import { useNavigate } from "react-router"
1✔
13
import { TransitionGroup } from "react-transition-group"
1✔
14

15
import editObjectHook from "../WizardHooks/WizardEditObjectHook"
1✔
16

17
import WizardAlert from "./WizardAlert"
1✔
18
import WizardObjectStatusBadge from "./WizardObjectStatusBadge"
1✔
19

20
import { ObjectSubmissionTypes, ObjectTypes } from "constants/wizardObject"
1✔
21
import { resetDraftStatus } from "features/draftStatusSlice"
1✔
22
import { setFocus } from "features/focusSlice"
1✔
23
import { resetCurrentObject } from "features/wizardCurrentObjectSlice"
1✔
24
import { setObjectType, resetObjectType } from "features/wizardObjectTypeSlice"
1✔
25
import { updateStep } from "features/wizardStepObjectSlice"
1✔
26
import { setSubmissionType } from "features/wizardSubmissionTypeSlice"
1✔
27
import { useAppDispatch, useAppSelector } from "hooks"
1✔
28
import type { FormRef, ObjectInsideSubmissionWithTags, StepItemObject } from "types"
29
import { pathWithLocale } from "utils"
1✔
30

31
const ActionButton = (props: {
1✔
32
  step: number
33
  parent: string
34
  buttonText?: string
35
  disabled: boolean
36
  formRef?: FormRef
37
}) => {
27✔
38
  const { step, parent, buttonText, disabled, formRef } = props
27✔
39

40
  const navigate = useNavigate()
27✔
41
  const submission = useAppSelector(state => state.submission)
27✔
42
  const formState = useAppSelector(state => state.submissionType)
27✔
43
  const draftStatus = useAppSelector(state => state.draftStatus)
27✔
44
  const dispatch = useAppDispatch()
27✔
45
  const [alert, setAlert] = useState(false)
27✔
46

47
  const unsavedSubmission = formState.trim().length > 0 && draftStatus === "notSaved"
27✔
48
  const pathname = pathWithLocale(
27✔
49
    submission.submissionId ? `submission/${submission.submissionId}` : `submission`
27✔
50
  )
27✔
51

52
  const handleClick = () => {
27✔
53
    if (unsavedSubmission) {
×
54
      setAlert(true)
×
55
    } else {
×
56
      handleNavigation()
×
57
    }
×
58
  }
×
59

60
  const handleNavigation = () => {
27✔
61
    dispatch(resetDraftStatus())
×
62
    dispatch(resetObjectType())
×
63
    dispatch(resetCurrentObject())
×
64
    dispatch(updateStep({ step: step, objectType: parent }))
×
65
    dispatch(setFocus())
×
66

67
    const stepParam = `step=${step}`
×
68
    switch (parent) {
×
69
      case "submissionDetails": {
×
70
        navigate({ pathname: pathname, search: stepParam })
×
71
        break
×
72
      }
×
73
      case "publish": {
×
74
        navigate({ pathname: pathname, search: stepParam })
×
75
        break
×
76
      }
×
77
      default: {
×
78
        navigate({ pathname: pathname, search: stepParam })
×
79
        dispatch(setSubmissionType(ObjectSubmissionTypes.form))
×
80
        dispatch(setObjectType(parent))
×
81
        if (formRef?.current) formRef.current?.dispatchEvent(new Event("reset", { bubbles: true }))
×
82
      }
×
83
    }
×
84
  }
×
85

86
  const handleAlert = (navigate: boolean) => {
27✔
87
    setAlert(false)
×
88
    if (navigate) {
×
89
      handleNavigation()
×
90
    }
×
91
  }
×
92

93
  return (
27✔
94
    <React.Fragment>
27✔
95
      <Button
27✔
96
        role="button"
27✔
97
        disabled={disabled}
27✔
98
        variant="contained"
27✔
99
        onClick={() => handleClick()}
27✔
100
        sx={theme => ({ marginTop: theme.spacing(2.4) })}
27✔
101
        form="hook-form"
27✔
102
        type="reset"
27✔
103
        data-testid={`${buttonText} ${parent}`}
27✔
104
      >
105
        {buttonText}
27✔
106
      </Button>
27✔
107
      {alert && (
27!
108
        <WizardAlert
×
109
          onAlert={handleAlert}
×
110
          parentLocation="submission"
×
111
          alertType={ObjectSubmissionTypes.form}
×
112
        />
113
      )}
114
    </React.Fragment>
27✔
115
  )
116
}
27✔
117

118
/*
119
 * Render items belonging to step.
120
 * Step can host for example submission details and ready & draft objects.
121
 */
122
const StepItems = (props: {
1✔
123
  step: number
124
  objects: {
125
    id: string | number
126
    displayTitle: string
127
    objectData?: ObjectInsideSubmissionWithTags
128
  }[]
129
  draft: boolean
130
  submissionId: string
131
  objectType: string
132
}) => {
27✔
133
  const { step, objects, draft, submissionId, objectType } = props
27✔
134
  const dispatch = useAppDispatch()
27✔
135
  const formState = useAppSelector(state => state.submissionType)
27✔
136
  const draftStatus = useAppSelector(state => state.draftStatus)
27✔
137
  const navigate = useNavigate()
27✔
138
  const [alert, setAlert] = useState(false)
27✔
139
  const [clickedItem, setClickedItem] = useState({
27✔
140
    objectData: { accessionId: "", schema: "", tags: {} },
27✔
141
  })
27✔
142
  const unsavedSubmission = formState.trim().length > 0 && draftStatus === "notSaved"
27✔
143

144
  const handleClick = item => {
27✔
145
    setClickedItem(item)
×
146
    if (unsavedSubmission) {
×
147
      setAlert(true)
×
148
    } else {
×
149
      handleItemEdit(item)
×
150
    }
×
151
  }
×
152

153
  const handleItemEdit = formObject => {
27✔
154
    dispatch(updateStep({ step: step, objectType: objectType }))
×
155

156
    switch (step) {
×
157
      case 1: {
×
158
        dispatch(resetObjectType())
×
159
        navigate({
×
160
          pathname: pathWithLocale(`submission/${submissionId}`),
×
161
          search: "step=1",
×
162
        })
×
163
        break
×
164
      }
×
165
      default: {
×
166
        if (objectType === ObjectTypes.dacPolicies) {
×
167
          dispatch(resetCurrentObject())
×
168
          navigate({ pathname: pathWithLocale(`submission/${submissionId}`), search: "step=2" })
×
169
          dispatch(setSubmissionType(ObjectSubmissionTypes.form))
×
170
          dispatch(setObjectType(objectType))
×
171
        } else {
×
172
          const item = formObject.objectData
×
173
          editObjectHook(
×
174
            draft,
×
175
            objectType,
×
176
            item,
×
177
            step,
×
178
            submissionId,
×
179
            dispatch,
×
180
            navigate,
×
NEW
181
            objects.findIndex(object => object.id === item.accessionId)
×
182
          )
×
183
        }
×
184
        break
×
185
      }
×
186
    }
×
187
  }
×
188

189
  const handleAlert = (navigate: boolean) => {
27✔
190
    setAlert(false)
×
191
    if (navigate) {
×
192
      handleItemEdit(clickedItem)
×
193
    }
×
194
  }
×
195

196
  const ObjectItem = styled("div")(({ theme }) => ({
27✔
197
    paddingTop: theme.spacing(2.5),
10✔
198
  }))
27✔
199

200
  return (
27✔
201
    <React.Fragment>
27✔
202
      <TransitionGroup component={null}>
27✔
203
        {objects.map(item => {
27✔
204
          return (
10✔
205
            <Collapse component={"li"} key={item.id}>
10✔
206
              <ObjectItem>
10✔
207
                <Grid container justifyContent="space-between">
10✔
208
                  <Grid display="flex" alignItems="center" size={{ xs: 6 }}>
10✔
209
                    <Link
10✔
210
                      tabIndex={0} // "href with # target will cause Firefox to refresh the page"
10✔
211
                      onClick={() => handleClick(item)}
10✔
212
                      data-testid={`${draft ? "draft" : "submitted"}-${objectType}-list-item`}
10!
213
                      aria-label={`Edit ${draft ? "draft" : "submitted"} ${objectType} object`}
10!
214
                      sx={theme => ({
10✔
215
                        fontWeight: "300",
10✔
216
                        textDecoration: "underline",
10✔
217
                        wordBreak: "break-all",
10✔
218
                        cursor: "pointer",
10✔
219
                        color: theme.palette.primary.main,
10✔
220
                      })}
10✔
221
                    >
222
                      {item.objectData?.tags.submissionType === ObjectSubmissionTypes.xml
10!
223
                        ? item.objectData.tags.fileName
×
224
                        : item.displayTitle !== ""
10✔
225
                          ? item.displayTitle
10!
226
                          : item.id}
×
227
                    </Link>
10✔
228
                  </Grid>
10✔
229
                  <Grid>
10✔
230
                    <WizardObjectStatusBadge draft={draft} />
10✔
231
                  </Grid>
10✔
232
                </Grid>
10✔
233
              </ObjectItem>
10✔
234
            </Collapse>
10✔
235
          )
236
        })}
27✔
237
      </TransitionGroup>
27✔
238
      {alert && (
27!
239
        <WizardAlert
×
240
          onAlert={handleAlert}
×
241
          parentLocation="submission"
×
242
          alertType={ObjectSubmissionTypes.form}
×
243
        />
244
      )}
245
    </React.Fragment>
27✔
246
  )
247
}
27✔
248

249
const ObjectWrapper = styled("div")(({ theme }) => {
1✔
250
  const treeBorder = `1px solid ${theme.palette.primary.main}`
27✔
251
  return {
27✔
252
    padding: theme.spacing(2.4),
27✔
253
    width: "100%",
27✔
254
    "&.activeObject": {
27✔
255
      backgroundColor: theme.palette.primary.light,
27✔
256
    },
27✔
257
    "& .stepItemHeader": {
27✔
258
      display: "flex",
27✔
259
      fontWeight: "bold",
27✔
260
    },
27✔
261
    "& .tree": {
27✔
262
      listStyle: "none",
27✔
263
      marginTop: theme.spacing(0.5),
27✔
264
      padding: 0,
27✔
265
      "& ul": {
27✔
266
        marginLeft: theme.spacing(1),
27✔
267
      },
27✔
268
      "& li": {
27✔
269
        position: "relative",
27✔
270
        marginLeft: theme.spacing(1),
27✔
271
        paddingLeft: theme.spacing(3),
27✔
272
        borderLeft: treeBorder,
27✔
273
      },
27✔
274
      "& li:last-of-type": {
27✔
275
        borderLeft: "none !important",
27✔
276
      },
27✔
277
      "& li:before": {
27✔
278
        position: "absolute",
27✔
279
        left: 0,
27✔
280
        width: theme.spacing(2),
27✔
281
        height: theme.spacing(4.25),
27✔
282
        verticalAlign: "top",
27✔
283
        borderBottom: treeBorder,
27✔
284
        content: "''",
27✔
285
      },
27✔
286
      "& li:last-of-type:before": {
27✔
287
        borderLeft: treeBorder,
27✔
288
      },
27✔
289
    },
27✔
290
  }
27✔
291
})
27✔
292

293
const WizardStep = (params: {
1✔
294
  step: number
295
  schemas: {
296
    objectType: string
297
    name: string
298
    required?: boolean
299
    allowMultipleObjects?: boolean
300
    objects?: { ready?: StepItemObject[]; drafts?: StepItemObject[] }
301
  }[]
302
  formRef?: FormRef
303
}) => {
27✔
304
  const { step, schemas, formRef } = params
27✔
305
  const submission = useAppSelector(state => state.submission)
27✔
306
  const currentStepObject = useAppSelector(state => state.stepObject)
27✔
307
  const { t } = useTranslation()
27✔
308

309
  return (
27✔
310
    <React.Fragment>
27✔
311
      {schemas.map((item, index) => {
27✔
312
        const { objectType, name, objects, allowMultipleObjects } = item
27✔
313
        const isActive = currentStepObject.stepObjectType === objectType
27✔
314
        const hasObjects = !!(objects?.ready?.length || objects?.drafts?.length)
27!
315
        const buttonText =
27✔
316
          step === 1 ? t("edit") : objectType === ObjectTypes.file ? t("view") : t("add")
27!
317

318
        return (
27✔
319
          <List key={objectType} disablePadding data-testid={`${objectType}-details`}>
27✔
320
            <ListItem divider={index !== schemas?.length - 1} disableGutters disablePadding>
27✔
321
              <ObjectWrapper className={isActive ? "activeObject" : ""}>
27✔
322
                <div className="stepItemHeader">
27✔
323
                  {isActive && <ChevronRightIcon fontSize="large" />}
27✔
324
                  {name}
27✔
325
                </div>
27✔
326

327
                {objects && (
27✔
328
                  <ul className="tree" data-testid={`${objectType}-objects-list`}>
27✔
329
                    {objects.drafts && (
27!
330
                      <StepItems
×
331
                        step={step}
×
332
                        objects={objects.drafts}
×
333
                        draft={true}
×
334
                        submissionId={submission.submissionId}
×
335
                        objectType={objectType}
×
336
                      />
337
                    )}
338
                    {objects.ready && (
27✔
339
                      <StepItems
27✔
340
                        step={step}
27✔
341
                        objects={objects.ready}
27✔
342
                        draft={false}
27✔
343
                        submissionId={submission.submissionId}
27✔
344
                        objectType={objectType}
27✔
345
                      />
346
                    )}
347
                  </ul>
27✔
348
                )}
349

350
                <ActionButton
27✔
351
                  step={step}
27✔
352
                  parent={step === 1 ? "submissionDetails" : objectType}
27✔
353
                  buttonText={buttonText}
27✔
354
                  disabled={hasObjects && !allowMultipleObjects}
27✔
355
                  formRef={formRef}
27✔
356
                />
357
              </ObjectWrapper>
27✔
358
            </ListItem>
27✔
359
          </List>
27✔
360
        )
361
      })}
27✔
362
    </React.Fragment>
27✔
363
  )
364
}
27✔
365

366
export default WizardStep
1✔
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