• 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

85.83
/src/components/SubmissionWizard/WizardComponents/WizardObjectDetailsJSONSchemaParser.tsx
1
import * as React from "react"
1✔
2

3
import Box from "@mui/material/Box"
1✔
4
import Checkbox from "@mui/material/Checkbox"
1✔
5
import List from "@mui/material/List"
1✔
6
import ListItem from "@mui/material/ListItem"
1✔
7
import ListItemText from "@mui/material/ListItemText"
1✔
8
import Paper from "@mui/material/Paper"
1✔
9
import { TypographyVariant } from "@mui/material/styles/createTypography"
10
import Typography from "@mui/material/Typography"
1✔
11
import { styled } from "@mui/system"
1✔
12
import { get } from "lodash"
1✔
13

14
import { FormObject, NestedField, ObjectDetails } from "types"
15
import { pathToName, traverseValues } from "utils/JSONSchemaUtils"
1✔
16

17
const SectionHeader = styled(Typography)(({ theme }) => ({
1✔
18
  margin: 1,
8✔
19
  fontWeight: 600,
8✔
20
  lineHeight: "1.75",
8✔
21
  letterSpacing: "0.00938em",
8✔
22
  color: theme.palette.primary.main,
8✔
23
}))
1✔
24

25
/*
26
 * Build object details based on given schema
27
 */
28
const buildDetails = (schema: FormObject, objectValues: ObjectDetails) => {
1✔
29
  try {
2✔
30
    return traverseFields(schema, [], objectValues)
2✔
31
  } catch (error) {
2!
32
    console.error(error)
×
33
  }
×
34
}
2✔
35

36
/*
37
 * Traverse fields recursively, return correct fields for given object or log error, if object type is not supported.
38
 */
39
const traverseFields = (
1✔
40
  object: FormObject,
20✔
41
  path: string[],
20✔
42
  objectValues: ObjectDetails,
20✔
43
  nestedField?: NestedField
20✔
44
) => {
20✔
45
  const name = pathToName(path)
20✔
46
  const [lastPathItem] = path.slice(-1)
20✔
47
  const label = object.title ?? lastPathItem
20!
48

49
  const getValues = () => {
20✔
50
    return get(objectValues, name)
25✔
51
  }
25✔
52

53
  if (object.oneOf)
20✔
54
    return <OneOfField key={name} path={path} object={object} objectValues={objectValues} />
20✔
55

56
  // Enable initial traverse
57
  if (name.length === 0 || getValues()) {
20✔
58
    switch (object.type) {
15✔
59
      case "object": {
15✔
60
        const properties = object.properties
7✔
61
        return (
7✔
62
          <DetailsSection key={name} name={name} label={label} level={path.length + 1}>
7✔
63
            {Object.keys(properties).map(propertyKey => {
7✔
64
              const property = properties[propertyKey] as FormObject
16✔
65

66
              return traverseFields(property, [...path, propertyKey], objectValues, nestedField)
16✔
67
            })}
7✔
68
          </DetailsSection>
7✔
69
        )
70
      }
7✔
71
      case "string":
15✔
72
      case "integer":
15✔
73
      case "number":
15✔
74
      case "boolean": {
15✔
75
        return label ? (
6✔
76
          <ObjectDetailsListItem key={name} name={name} label={object.title} value={getValues()} />
6!
77
        ) : (
78
          <></>
×
79
        )
80
      }
6✔
81
      case "array": {
15✔
82
        return object.items.enum ? (
2✔
83
          <CheckboxArray
1✔
84
            key={name}
1✔
85
            name={name}
1✔
86
            label={object.title}
1✔
87
            values={getValues()}
1✔
88
          ></CheckboxArray>
1✔
89
        ) : (
90
          <DetailsArray
1✔
91
            key={name}
1✔
92
            object={object}
1✔
93
            objectValues={objectValues}
1✔
94
            values={getValues()}
1✔
95
            path={path}
1✔
96
          />
97
        )
98
      }
2✔
99
      case "null": {
15!
100
        return null
×
101
      }
×
102
      default: {
15!
103
        console.error(`
×
104
          No field parsing support for type ${object.type} yet.
×
105

106
          Pretty printed version of object with unsupported type:
107
          ${JSON.stringify(object, null, 2)}
×
108
          `)
×
109
        return null
×
110
      }
×
111
    }
15✔
112
  }
15✔
113
}
20✔
114

115
const ObjectDetailsListItem = ({
1✔
116
  name,
6✔
117
  label,
6✔
118
  value,
6✔
119
}: {
120
  name: string
121
  label: string
122
  value: string | number
123
}) => {
6✔
124
  return (
6✔
125
    <ListItem
6✔
126
      sx={{
6✔
127
        "&.MuiListItem-root": {
6✔
128
          pt: "0",
6✔
129
          pb: "0",
6✔
130
        },
6✔
131
      }}
6✔
132
      data-testid={name}
6✔
133
    >
134
      <ListItemText disableTypography primary={`${label}: ${value}`}></ListItemText>
6✔
135
    </ListItem>
6✔
136
  )
137
}
6✔
138

139
type DetailsSectionProps = {
140
  name: string
141
  label: string
142
  level: number
143
  children?: React.ReactNode
144
}
145

146
const DetailsSection = ({ name, label, level, children }: DetailsSectionProps) => (
1✔
147
  <div className="detailsSection" key={`${name}-section`} data-testid="section">
7✔
148
    <SectionHeader key={`${name}-header`} variant={`h${level}` as TypographyVariant}>
7✔
149
      {label}
7✔
150
    </SectionHeader>
7✔
151
    {children}
7✔
152
  </div>
7✔
153
)
154

155
type OneOfFieldProps = {
156
  path: string[]
157
  object: FormObject
158
  objectValues: ObjectDetails
159
}
160

161
const OneOfField = ({ path, object, objectValues }: OneOfFieldProps) => {
1✔
162
  const name = pathToName(path)
2✔
163

164
  // Number & boolean field values are handled as string
165
  const stringTypes = ["number", "boolean"]
2✔
166
  const optionValue = stringTypes.includes(typeof get(objectValues, name))
2!
167
    ? "string"
×
168
    : get(objectValues, name)
2✔
169
  const optionType = Array.isArray(optionValue) ? "array" : typeof optionValue
2!
170
  const options = object.oneOf
2✔
171

172
  let currentOption = options.find((item: { type: string }) => item.type === optionType)
2✔
173

174
  // Find option when options share type
175
  if (options.filter((option: { type: string }) => option.type === optionType).length > 1) {
2✔
176
    const optionValueKeys = Object?.keys(optionValue)
2✔
177
    for (const option of options) {
2✔
178
      // Find option details in array of values by comparing required fields. E.g. Study > Study Links
179
      if (option.required) {
4✔
180
        if (option.required.every((val: string) => optionValueKeys.includes(val))) {
2✔
181
          currentOption = option
1✔
182
        }
1✔
183
      }
2✔
184
      // Find option by comparing properties
185
      else if (Object.keys(option.properties)?.some(val => optionValueKeys.includes(val))) {
2✔
186
        currentOption = option
1✔
187
      }
1✔
188
    }
4✔
189
  }
2✔
190

191
  return <div key={name}>{currentOption && traverseFields(currentOption, path, objectValues)}</div>
2✔
192
}
2✔
193

194
type CheckboxArrayProps = {
195
  name: string
196
  label: string
197
  values: string[]
198
}
199

200
const CheckboxArray = ({ label, values }: CheckboxArrayProps) => {
1✔
201
  return (
1✔
202
    <List>
1✔
203
      <Typography color="primary">{label}</Typography>
1✔
204
      {values.map((item: string) => (
1✔
205
        <ListItem
2✔
206
          sx={{
2✔
207
            "&.MuiListItem-root": {
2✔
208
              pt: "0",
2✔
209
              pb: "0",
2✔
210
            },
2✔
211
          }}
2✔
212
          key={item}
2✔
213
          data-testid="checkbox-item"
2✔
214
        >
215
          <Checkbox checked={true} color="primary" disabled></Checkbox>
2✔
216
          {item}
2✔
217
        </ListItem>
2✔
218
      ))}
1✔
219
    </List>
1✔
220
  )
221
}
1✔
222

223
type DetailsArrayProps = {
224
  object: FormObject
225
  path: Array<string>
226
  objectValues: ObjectDetails
227
  values: Record<string, unknown>[]
228
}
229

230
const DetailsArray = ({ object, path, objectValues, values }: DetailsArrayProps) => {
1✔
231
  const name = pathToName(path)
1✔
232
  const [lastPathItem] = path.slice(-1)
1✔
233
  const label = object.title ?? lastPathItem
1!
234
  const level = path.length + 1
1✔
235

236
  const items = traverseValues(object.items) as Record<string, unknown>
1✔
237
  return (
1✔
238
    <div className="array" key={`${name}-array`}>
1✔
239
      <SectionHeader
1✔
240
        key={`${name}-header`}
1✔
241
        variant={`h${level}` as TypographyVariant}
1✔
242
        data-testid={name}
1✔
243
      >
244
        {label}
1✔
245
      </SectionHeader>
1✔
246

247
      {values.map((_field: unknown, index: number) => {
1✔
248
        const [lastPathItem] = path.slice(-1)
1✔
249
        const pathWithoutLastItem = path.slice(0, -1)
1✔
250
        const lastPathItemWithIndex = `${lastPathItem}[${index}]`
1✔
251

252
        if (items.oneOf) {
1✔
253
          const pathForThisIndex = [...pathWithoutLastItem, lastPathItemWithIndex]
1✔
254

255
          return (
1✔
256
            <div className="arrayRow" key={`${name}[${index}]`}>
1✔
257
              <Paper elevation={2}>
1✔
258
                <OneOfField
1✔
259
                  key={`${name}[${index}]`}
1✔
260
                  path={pathForThisIndex}
1✔
261
                  object={items as FormObject}
1✔
262
                  objectValues={objectValues}
1✔
263
                />
264
              </Paper>
1✔
265
            </div>
1✔
266
          )
267
        }
1!
268

269
        const properties = object.items.properties
×
270

271
        return (
×
272
          <Box px={1} className="arrayRow" key={`${name}[${index}]`}>
×
273
            <Paper elevation={2}>
×
274
              {Object.keys(items).map(item => {
×
275
                const pathForThisIndex = [...pathWithoutLastItem, lastPathItemWithIndex, item]
×
276

277
                return traverseFields(
×
278
                  properties[item] as FormObject,
×
279
                  pathForThisIndex,
×
NEW
280
                  objectValues
×
281
                )
×
282
              })}
×
283
            </Paper>
×
284
          </Box>
×
285
        )
286
      })}
1✔
287
    </div>
1✔
288
  )
289
}
1✔
290

291
export default {
1✔
292
  buildDetails,
1✔
293
}
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