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

CSCfi / metadata-submitter-frontend / 17370782808

01 Sep 2025 07:29AM UTC coverage: 58.273% (+0.2%) from 58.089%
17370782808

push

github

Hang Le
Adapt frontend to no Files and Datacite step in the workflow schema (merge commit)

Merge branch 'bugfix/no-datafolder-step' into 'main'
* Remove Describe step content modification

* Adapt to removing Files and Datacite steps from workflow schemas

Closes #1074 and #1091
See merge request https://gitlab.ci.csc.fi/sds-dev/sd-submit/metadata-submitter-frontend/-/merge_requests/1161

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

594 of 806 branches covered (73.7%)

Branch coverage included in aggregate %.

48 of 66 new or added lines in 9 files covered. (72.73%)

6 existing lines in 2 files now uncovered.

5330 of 9360 relevant lines covered (56.94%)

5.66 hits per line

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

78.32
/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 { ObjectTypes, NotMetadataObjects } from "constants/wizardObject"
1✔
21
import { setFocus } from "features/focusSlice"
1✔
22
import { resetUnsavedForm } from "features/unsavedFormSlice"
1✔
23
import { resetCurrentObject, setCurrentObject } from "features/wizardCurrentObjectSlice"
1✔
24
import { setObjectType, resetObjectType } from "features/wizardObjectTypeSlice"
1✔
25
import { updateStep } from "features/wizardStepObjectSlice"
1✔
26
import { useAppDispatch, useAppSelector } from "hooks"
1✔
27
import type { DoiFormDetails, HandlerRef, StepObject } from "types"
28
import { hasDoiInfo, pathWithLocale } from "utils"
1✔
29

30
const ActionButton = (props: {
1✔
31
  step: number
32
  parent: string
33
  buttonText?: string
34
  disabled: boolean
35
  ref?: HandlerRef
36
}) => {
47✔
37
  const { step, parent, buttonText, disabled, ref } = props
47✔
38

39
  const navigate = useNavigate()
47✔
40
  const submission = useAppSelector(state => state.submission)
47✔
41
  const unsavedForm = useAppSelector(state => state.unsavedForm)
47✔
42
  const dispatch = useAppDispatch()
47✔
43
  const [alert, setAlert] = useState(false)
47✔
44

45
  const pathname = pathWithLocale(
47✔
46
    submission.submissionId ? `submission/${submission.submissionId}` : `submission`
47✔
47
  )
47✔
48

49
  const handleClick = () => {
47✔
50
    if (unsavedForm) {
×
51
      setAlert(true)
×
52
    } else {
×
53
      // no data to lose, navigate
54
      handleNavigation()
×
55
    }
×
56
  }
×
57

58
  const handleNavigation = () => {
47✔
59
    dispatch(resetUnsavedForm())
×
60
    dispatch(resetObjectType())
×
61
    dispatch(resetCurrentObject())
×
62
    dispatch(updateStep({ step: step, objectType: parent }))
×
63
    dispatch(setFocus())
×
64

65
    const stepParam = `step=${step}`
×
NEW
66
    navigate({ pathname: pathname, search: stepParam })
×
NEW
67
    dispatch(setObjectType(parent))
×
68
    // resets only hook-form
NEW
69
    if (ref?.current) ref.current?.dispatchEvent(new Event("reset", { bubbles: true }))
×
UNCOV
70
  }
×
71

72
  const handleAlert = (navigate: boolean) => {
47✔
73
    setAlert(false)
×
74
    if (navigate) {
×
75
      handleNavigation()
×
76
    }
×
77
  }
×
78

79
  return (
47✔
80
    <React.Fragment>
47✔
81
      <Button
47✔
82
        role="button"
47✔
83
        disabled={disabled}
47✔
84
        variant="contained"
47✔
85
        onClick={() => handleClick()}
47✔
86
        sx={theme => ({ marginTop: theme.spacing(2.4) })}
47✔
87
        data-testid={`${buttonText} ${parent}`}
47✔
88
      >
89
        {buttonText}
47✔
90
      </Button>
47✔
91
      {alert && <WizardAlert onAlert={handleAlert} parentLocation="submission" alertType="exit" />}
47!
92
    </React.Fragment>
47✔
93
  )
94
}
47✔
95

96
/*
97
 * Render items belonging to step.
98
 * Step can host for example submission details and its objects.
99
 */
100
const StepItems = (props: {
1✔
101
  step: number
102
  objects: StepObject[]
103
  submissionId: string
104
  doiInfo?: (Record<string, unknown> & DoiFormDetails) | undefined
105
  objectType: string
106
}) => {
60✔
107
  const { step, objects, submissionId, doiInfo, objectType } = props
60✔
108
  const dispatch = useAppDispatch()
60✔
109
  const unsavedForm = useAppSelector(state => state.unsavedForm)
60✔
110
  const navigate = useNavigate()
60✔
111
  const [alert, setAlert] = useState(false)
60✔
112
  const [clickedItem, setClickedItem] = useState({})
60✔
113
  const { t } = useTranslation()
60✔
114

115
  const handleClick = (item: StepObject) => {
60✔
116
    setClickedItem(item)
×
117
    if (unsavedForm) {
×
118
      setAlert(true)
×
119
    } else {
×
120
      handleItemEdit(item)
×
121
    }
×
122
  }
×
123

124
  const handleItemEdit = formObject => {
60✔
125
    dispatch(updateStep({ step: step, objectType: objectType }))
×
NEW
126
    if (NotMetadataObjects.includes(objectType)) {
×
NEW
127
      dispatch(resetCurrentObject())
×
NEW
128
      dispatch(setObjectType(objectType))
×
NEW
129
      if (objectType === ObjectTypes.datacite) {
×
130
        dispatch(setCurrentObject(doiInfo))
×
131
      }
×
NEW
132
      navigate({
×
NEW
133
        pathname: pathWithLocale(`submission/${submissionId}`),
×
NEW
134
        search: `step=${step}`,
×
NEW
135
      })
×
NEW
136
    } else {
×
NEW
137
      editObjectHook(objectType, formObject, step, submissionId, dispatch, navigate)
×
138
    }
×
139
  }
×
140

141
  const handleAlert = (navigate: boolean) => {
60✔
142
    setAlert(false)
×
143
    if (navigate) {
×
144
      handleItemEdit(clickedItem)
×
145
    }
×
146
  }
×
147

148
  const ObjectItem = styled("div")(({ theme }) => ({
60✔
149
    paddingTop: theme.spacing(2.5),
7✔
150
  }))
60✔
151

152
  return (
60✔
153
    <React.Fragment>
60✔
154
      <TransitionGroup component={null}>
60✔
155
        {objects.map(item => {
60✔
156
          return (
9✔
157
            <Collapse component={"li"} key={item.id}>
9✔
158
              <ObjectItem>
9✔
159
                <Grid container justifyContent="space-between">
9✔
160
                  <Grid display="flex" alignItems="center" size={{ xs: 6 }}>
9✔
161
                    <Link
9✔
162
                      tabIndex={0}
9✔
163
                      onClick={() => handleClick(item)}
9✔
164
                      data-testid={`${objectType}-list-item`}
9✔
165
                      aria-label={t("ariaLabels.editObject")}
9✔
166
                      sx={theme => ({
9✔
167
                        fontWeight: "300",
7✔
168
                        textDecoration: "underline",
7✔
169
                        wordBreak: "break-all",
7✔
170
                        cursor: "pointer",
7✔
171
                        color: theme.palette.primary.main,
7✔
172
                      })}
7✔
173
                    >
174
                      {item.displayTitle}
9✔
175
                    </Link>
9✔
176
                  </Grid>
9✔
177
                  <Grid>
9✔
178
                    <WizardObjectStatusBadge />
9✔
179
                  </Grid>
9✔
180
                </Grid>
9✔
181
              </ObjectItem>
9✔
182
            </Collapse>
9✔
183
          )
184
        })}
60✔
185
      </TransitionGroup>
60✔
186
      {alert && <WizardAlert onAlert={handleAlert} parentLocation="submission" alertType="exit" />}
60!
187
    </React.Fragment>
60✔
188
  )
189
}
60✔
190

191
const ObjectWrapper = styled("div")(({ theme }) => {
1✔
192
  const treeBorder = `1px solid ${theme.palette.primary.main}`
47✔
193
  return {
47✔
194
    padding: theme.spacing(2.4),
47✔
195
    width: "100%",
47✔
196
    "&.activeObject": {
47✔
197
      backgroundColor: theme.palette.primary.mediumLight,
47✔
198
    },
47✔
199
    "& .stepItemHeader": {
47✔
200
      display: "flex",
47✔
201
      fontWeight: "bold",
47✔
202
    },
47✔
203
    "& .tree": {
47✔
204
      listStyle: "none",
47✔
205
      marginTop: theme.spacing(0.5),
47✔
206
      padding: 0,
47✔
207
      "& ul": {
47✔
208
        marginLeft: theme.spacing(1),
47✔
209
      },
47✔
210
      "& li": {
47✔
211
        position: "relative",
47✔
212
        marginLeft: theme.spacing(1),
47✔
213
        paddingLeft: theme.spacing(3),
47✔
214
        borderLeft: treeBorder,
47✔
215
      },
47✔
216
      "& li:last-of-type": {
47✔
217
        borderLeft: "none !important",
47✔
218
      },
47✔
219
      "& li:before": {
47✔
220
        position: "absolute",
47✔
221
        left: 0,
47✔
222
        width: theme.spacing(2),
47✔
223
        height: theme.spacing(4.25),
47✔
224
        verticalAlign: "top",
47✔
225
        borderBottom: treeBorder,
47✔
226
        content: "''",
47✔
227
      },
47✔
228
      "& li:last-of-type:before": {
47✔
229
        borderLeft: treeBorder,
47✔
230
      },
47✔
231
    },
47✔
232
  }
47✔
233
})
47✔
234

235
type WizardStepProps = {
236
  step: number
237
  schemas: {
238
    objectType: string
239
    name: string
240
    required?: boolean
241
    allowMultipleObjects?: boolean
242
    objects: StepObject[]
243
  }[]
244
  ref?: HandlerRef
245
}
246

247
/*
248
 * Render a single step inside WizardStepper in the Accordion
249
 */
250
const WizardStep = (props: WizardStepProps) => {
1✔
251
  const { step, schemas, ref } = props
42✔
252

253
  const submission = useAppSelector(state => state.submission)
42✔
254
  const currentStepObject = useAppSelector(state => state.stepObject)
42✔
255
  const { t } = useTranslation()
42✔
256

257
  return (
42✔
258
    <React.Fragment>
42✔
259
      {schemas.map((item, index) => {
42✔
260
        const { objectType, name, objects, allowMultipleObjects } = item
60✔
261
        const isActive = currentStepObject.stepObjectType === objectType
60✔
262

263
        // Check if we should show linked folder instead of objects
264
        const shouldShowLinkedFolder =
60✔
265
          objectType === ObjectTypes.linkedFolder && submission.linkedFolder
60✔
266

267
        const buttonText =
60✔
268
          objectType === ObjectTypes.linkedFolder || objectType === ObjectTypes.summary
60✔
269
            ? t("view")
18✔
270
            : objectType === ObjectTypes.publish ||
42✔
271
                objectType === ObjectTypes.submissionDetails ||
33✔
272
                hasDoiInfo(submission.doiInfo)
18✔
273
              ? t("edit")
24✔
274
              : t("add")
18✔
275

276
        return (
60✔
277
          <List key={objectType} disablePadding data-testid={`${objectType}-details`}>
60✔
278
            <ListItem divider={index !== schemas?.length - 1} disableGutters disablePadding>
60✔
279
              <ObjectWrapper className={isActive ? "activeObject" : ""}>
60✔
280
                <div className="stepItemHeader">
60✔
281
                  {isActive && <ChevronRightIcon fontSize="large" />}
60✔
282
                  {name}
60✔
283
                </div>
60✔
284

285
                {(objects || shouldShowLinkedFolder) && (
60!
286
                  <ul className="tree" data-testid={`${objectType}-objects-list`}>
60✔
287
                    {objects && (
60✔
288
                      <StepItems
60✔
289
                        step={step}
60✔
290
                        objects={objects}
60✔
291
                        submissionId={submission.submissionId}
60✔
292
                        doiInfo={submission.doiInfo}
60✔
293
                        objectType={objectType}
60✔
294
                      />
295
                    )}
296
                    {shouldShowLinkedFolder && (
60!
297
                      <StepItems
×
298
                        step={step}
×
299
                        objects={[
×
300
                          {
×
301
                            id: `linked-folder-${submission.submissionId}`,
×
302
                            displayTitle: submission.linkedFolder ?? "",
×
303
                          } as StepObject,
×
304
                        ]}
305
                        submissionId={submission.submissionId}
×
306
                        doiInfo={submission.doiInfo}
×
307
                        objectType={objectType}
×
308
                      />
309
                    )}
310
                  </ul>
60✔
311
                )}
312

313
                <ActionButton
60✔
314
                  step={step}
60✔
315
                  parent={objectType}
60✔
316
                  buttonText={buttonText}
60✔
317
                  disabled={!!objects?.length && !allowMultipleObjects}
60✔
318
                  ref={ref}
60✔
319
                />
320
              </ObjectWrapper>
60✔
321
            </ListItem>
60✔
322
          </List>
60✔
323
        )
324
      })}
42✔
325
    </React.Fragment>
42✔
326
  )
327
}
42✔
328

329
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