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

CSCfi / metadata-submitter-frontend / 13917461632

18 Mar 2025 07:13AM UTC coverage: 47.423% (-1.1%) from 48.473%
13917461632

push

github

Hang Le
Add Dac and Policies form (merge commit)

Merge branch 'feature/dac-policy-form' into 'main'
* Add Dac and Policies form

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

Reviewed-by: Liisa Lado-Villar <145-lilado@users.noreply.gitlab.ci.csc.fi>
Approved-by: Liisa Lado-Villar <145-lilado@users.noreply.gitlab.ci.csc.fi>
Merged by Hang Le <lhang@csc.fi>

652 of 953 branches covered (68.42%)

Branch coverage included in aggregate %.

238 of 670 new or added lines in 21 files covered. (35.52%)

2 existing lines in 2 files now uncovered.

6166 of 13424 relevant lines covered (45.93%)

3.75 hits per line

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

68.36
/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
      }
×
NEW
165
      case 2: {
×
NEW
166
        if (objectType === ObjectTypes.dacPolicies) {
×
NEW
167
          dispatch(resetCurrentObject())
×
NEW
168
          navigate({ pathname: pathWithLocale(`submission/${submissionId}`), search: "step=2" })
×
NEW
169
          dispatch(setSubmissionType(ObjectSubmissionTypes.form))
×
NEW
170
          dispatch(setObjectType(objectType))
×
NEW
171
        }
×
NEW
172
        break
×
NEW
173
      }
×
174
      default: {
×
175
        const item = formObject.objectData
×
176
        editObjectHook(
×
177
          draft,
×
178
          objectType,
×
179
          item,
×
180
          step,
×
181
          submissionId,
×
182
          dispatch,
×
183
          navigate,
×
NEW
184
          objects.findIndex(object => object.id === item.accessionId),
×
185
        )
×
186
      }
×
187
    }
×
188
  }
×
189

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

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

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

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

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

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

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

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

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

367
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