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

IQSS / dataverse-frontend / 15049606369

15 May 2025 03:55PM UTC coverage: 97.932% (+0.5%) from 97.394%
15049606369

push

github

web-flow
Merge pull request #696 from IQSS/feat/690-featured-items-aspect-ratio-recommendations

Featured Items aspect ratio recommendations

3013 of 3153 branches covered (95.56%)

Branch coverage included in aggregate %.

37 of 37 new or added lines in 2 files covered. (100.0%)

24 existing lines in 12 files now uncovered.

6602 of 6665 relevant lines covered (99.05%)

18892.33 hits per line

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

100.0
/src/sections/shared/form/DatasetMetadataForm/MetadataFieldsHelper.ts
1
import {
2
  MetadataBlockInfo,
3
  MetadataBlockInfoWithMaybeValues,
4
  MetadataField,
5
  MetadataFieldWithMaybeValue
6
} from '../../../../metadata-block-info/domain/models/MetadataBlockInfo'
7
import {
8
  DatasetDTO,
9
  DatasetMetadataBlockValuesDTO,
10
  DatasetMetadataChildFieldValueDTO
11
} from '../../../../dataset/domain/useCases/DTOs/DatasetDTO'
12
import {
13
  DatasetMetadataBlocks,
14
  DatasetMetadataFields,
15
  DatasetMetadataSubField,
16
  defaultLicense
17
} from '../../../../dataset/domain/models/Dataset'
18

19
export type DatasetMetadataFormValues = Record<string, MetadataBlockFormValues>
20

21
export type MetadataBlockFormValues = Record<
22
  string,
23
  string | PrimitiveMultipleFormValue | VocabularyMultipleFormValue | ComposedFieldValues
24
>
25

26
type VocabularyMultipleFormValue = string[]
27

28
type PrimitiveMultipleFormValue = { value: string }[]
29

30
type ComposedFieldValues = ComposedSingleFieldValue | ComposedSingleFieldValue[]
31

32
type ComposedSingleFieldValue = Record<string, string>
33

34
export class MetadataFieldsHelper {
35
  public static replaceMetadataBlocksInfoDotNamesKeysWithSlash(
36
    metadataBlocks: MetadataBlockInfo[]
37
  ): MetadataBlockInfo[] {
38
    // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
39
    const metadataBlocksCopy: MetadataBlockInfo[] = structuredClone(metadataBlocks)
5,238✔
40

41
    for (const block of metadataBlocksCopy) {
5,238✔
42
      if (block.metadataFields) {
24,546✔
43
        this.metadataBlocksInfoDotReplacer(block.metadataFields)
24,546✔
44
      }
45
    }
46
    return metadataBlocksCopy
5,238✔
47
  }
48

49
  private static metadataBlocksInfoDotReplacer(metadataFields: Record<string, MetadataField>) {
50
    for (const key in metadataFields) {
119,655✔
51
      const field = metadataFields[key]
640,002✔
52
      if (field.name.includes('.')) {
640,002✔
53
        field.name = this.replaceDotWithSlash(field.name)
74,772✔
54
      }
55
      if (field.childMetadataFields) {
640,002✔
56
        this.metadataBlocksInfoDotReplacer(field.childMetadataFields)
95,109✔
57
      }
58
    }
59
  }
60

61
  public static replaceDatasetMetadataBlocksCurrentValuesDotKeysWithSlash(
62
    datasetMetadataBlocks: DatasetMetadataBlocks
63
  ): DatasetMetadataBlocks {
64
    // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
65
    const datasetMetadataBlocksCopy: DatasetMetadataBlocks = structuredClone(datasetMetadataBlocks)
513✔
66
    const dataWithoutKeysWithDots: DatasetMetadataBlocks = [] as unknown as DatasetMetadataBlocks
513✔
67

68
    for (const block of datasetMetadataBlocksCopy) {
513✔
69
      const newBlockFields: DatasetMetadataFields =
70
        this.datasetMetadataBlocksCurrentValuesDotReplacer(block.fields)
969✔
71

72
      const newBlock = {
969✔
73
        name: block.name,
74
        fields: newBlockFields
75
      }
76

77
      dataWithoutKeysWithDots.push(newBlock)
969✔
78
    }
79

80
    return dataWithoutKeysWithDots
513✔
81
  }
82

83
  private static datasetMetadataBlocksCurrentValuesDotReplacer(
84
    datasetMetadataFields: DatasetMetadataFields
85
  ): DatasetMetadataFields {
86
    const datasetMetadataFieldsNormalized: DatasetMetadataFields = {}
969✔
87

88
    for (const key in datasetMetadataFields) {
969✔
89
      const newKey = key.includes('.') ? this.replaceDotWithSlash(key) : key
5,415✔
90

91
      const value = datasetMetadataFields[key]
5,415✔
92

93
      // Case of DatasetMetadataSubField
94
      if (typeof value === 'object' && !Array.isArray(value)) {
5,415✔
95
        const nestedKeysMapped = Object.entries(value).reduce((acc, [nestedKey, nestedValue]) => {
513✔
96
          const newNestedKey = nestedKey.includes('.')
1,083✔
97
            ? this.replaceDotWithSlash(nestedKey)
98
            : nestedKey
99

100
          acc[newNestedKey] = nestedValue
1,083✔
101
          return acc
1,083✔
102
        }, {} as DatasetMetadataSubField)
103

104
        datasetMetadataFieldsNormalized[newKey] = nestedKeysMapped
513✔
105
      } else if (
4,902✔
106
        Array.isArray(value) &&
7,125✔
107
        (value as readonly (string | DatasetMetadataSubField)[]).every((v) => typeof v === 'object')
2,622✔
108
      ) {
109
        // Case of DatasetMetadataSubField[]
110
        const nestedKeysMapped = value.map((subFields) => {
1,482✔
111
          return Object.entries(subFields).reduce((acc, [nestedKey, nestedValue]) => {
1,881✔
112
            const newNestedKey = nestedKey.includes('.')
5,244✔
113
              ? this.replaceDotWithSlash(nestedKey)
114
              : nestedKey
115

116
            acc[newNestedKey] = nestedValue
5,244✔
117
            return acc
5,244✔
118
          }, {} as DatasetMetadataSubField)
119
        })
120
        datasetMetadataFieldsNormalized[newKey] = nestedKeysMapped
1,482✔
121
      } else {
122
        datasetMetadataFieldsNormalized[newKey] = value
3,420✔
123
      }
124
    }
125

126
    return datasetMetadataFieldsNormalized
969✔
127
  }
128

129
  public static getFormDefaultValues(
130
    metadataBlocks: MetadataBlockInfoWithMaybeValues[]
131
  ): DatasetMetadataFormValues {
132
    const formDefaultValues: DatasetMetadataFormValues = {}
3,522✔
133

134
    for (const block of metadataBlocks) {
3,522✔
135
      const blockValues: MetadataBlockFormValues = {}
3,577✔
136

137
      for (const field of Object.values(block.metadataFields)) {
3,577✔
138
        const fieldName = field.name
32,541✔
139
        const fieldValue = field.value
32,541✔
140

141
        if (field.typeClass === 'compound') {
32,541✔
142
          const childFieldsWithEmptyValues: Record<string, string> = {}
14,693✔
143

144
          if (field.childMetadataFields) {
14,693✔
145
            for (const childField of Object.values(field.childMetadataFields)) {
14,693✔
146
              if (childField.typeClass === 'primitive') {
48,199✔
147
                childFieldsWithEmptyValues[childField.name] = ''
42,786✔
148
              }
149

150
              if (childField.typeClass === 'controlledVocabulary') {
48,199✔
151
                childFieldsWithEmptyValues[childField.name] = ''
5,413✔
152
              }
153
            }
154
          }
155

156
          if (fieldValue) {
14,693✔
157
            const castedFieldValue = fieldValue as
1,842✔
158
              | DatasetMetadataSubField
159
              | DatasetMetadataSubField[]
160

161
            let fieldValues: ComposedFieldValues
162

163
            if (Array.isArray(castedFieldValue)) {
1,842✔
164
              const subFieldsWithValuesPlusEmptyOnes = castedFieldValue.map((subFields) => {
1,356✔
165
                const fieldsValueNormalized: Record<string, string> = Object.entries(
1,791✔
166
                  subFields
167
                ).reduce((acc, [key, value]) => {
168
                  if (value !== undefined) {
4,989✔
169
                    acc[key] = value
4,989✔
170
                  }
171
                  return acc
4,989✔
172
                }, {} as Record<string, string>)
173

174
                return {
1,791✔
175
                  ...childFieldsWithEmptyValues,
176
                  ...fieldsValueNormalized
177
                }
178
              })
179

180
              fieldValues = subFieldsWithValuesPlusEmptyOnes
1,356✔
181
            } else {
182
              const fieldsValueNormalized: Record<string, string> = Object.entries(
486✔
183
                castedFieldValue
184
              ).reduce((acc, [key, value]) => {
185
                if (value !== undefined) {
1,023✔
186
                  acc[key] = value
1,023✔
187
                }
188
                return acc
1,023✔
189
              }, {} as Record<string, string>)
190

191
              fieldValues = {
486✔
192
                ...childFieldsWithEmptyValues,
193
                ...fieldsValueNormalized
194
              }
195
            }
196

197
            blockValues[fieldName] = fieldValues
1,842✔
198
          } else {
199
            blockValues[fieldName] = field.multiple
12,851✔
200
              ? [childFieldsWithEmptyValues]
201
              : childFieldsWithEmptyValues
202
          }
203
        }
204

205
        if (field.typeClass === 'primitive') {
32,541✔
206
          blockValues[fieldName] = this.getPrimitiveFieldDefaultFormValue(field)
15,626✔
207
        }
208

209
        if (field.typeClass === 'controlledVocabulary') {
32,541✔
210
          blockValues[fieldName] = this.getControlledVocabFieldDefaultFormValue(field)
2,222✔
211
        }
212
      }
213

214
      formDefaultValues[block.name] = blockValues
3,577✔
215
    }
216

217
    return formDefaultValues
3,522✔
218
  }
219

220
  private static getPrimitiveFieldDefaultFormValue(
221
    field: MetadataFieldWithMaybeValue
222
  ): string | PrimitiveMultipleFormValue {
223
    if (field.multiple) {
15,626✔
224
      const castedFieldValue = field.value as string[] | undefined
3,398✔
225

226
      if (!castedFieldValue) return [{ value: '' }]
3,398✔
227

228
      return castedFieldValue.map((stringValue) => ({ value: stringValue }))
408✔
229
    }
230
    const castedFieldValue = field.value as string | undefined
12,228✔
231
    return castedFieldValue ?? ''
12,228✔
232
  }
233

234
  private static getControlledVocabFieldDefaultFormValue(
235
    field: MetadataFieldWithMaybeValue
236
  ): string | VocabularyMultipleFormValue {
237
    if (field.multiple) {
2,222✔
238
      const castedFieldValue = field.value as string[] | undefined
2,143✔
239

240
      if (!castedFieldValue) return []
2,143✔
241

242
      return castedFieldValue
486✔
243
    }
244
    const castedFieldValue = field.value as string | undefined
79✔
245
    return castedFieldValue ?? ''
79✔
246
  }
247

248
  public static replaceSlashKeysWithDot(obj: DatasetMetadataFormValues): DatasetMetadataFormValues {
249
    const formattedNewObject: DatasetMetadataFormValues = {}
225✔
250

251
    for (const key in obj) {
225✔
252
      const blockKey = this.replaceSlashWithDot(key)
360✔
253
      const metadataBlockFormValues = obj[key]
360✔
254

255
      formattedNewObject[blockKey] = {}
360✔
256

257
      Object.entries(metadataBlockFormValues).forEach(([fieldName, fieldValue]) => {
360✔
258
        const newFieldName = this.replaceSlashWithDot(fieldName)
3,822✔
259

260
        if (
3,822✔
261
          this.isPrimitiveFieldValue(fieldValue) ||
8,316✔
262
          this.isVocabularyMultipleFieldValue(fieldValue) ||
263
          this.isPrimitiveMultipleFieldValue(fieldValue)
264
        ) {
265
          formattedNewObject[blockKey][newFieldName] = fieldValue
2,295✔
266
          return
2,295✔
267
        }
268

269
        if (this.isComposedSingleFieldValue(fieldValue)) {
1,527✔
270
          formattedNewObject[blockKey][newFieldName] = {}
180✔
271
          Object.entries(fieldValue).forEach(([nestedFieldName, nestedFieldValue]) => {
180✔
272
            const newNestedFieldName = this.replaceSlashWithDot(nestedFieldName)
675✔
273
            const parentOfNestedField = formattedNewObject[blockKey][
675✔
274
              newFieldName
275
            ] as ComposedSingleFieldValue
276

277
            parentOfNestedField[newNestedFieldName] = nestedFieldValue
675✔
278
          })
279
          return
180✔
280
        }
281

282
        if (this.isComposedMultipleFieldValue(fieldValue)) {
1,347✔
283
          formattedNewObject[blockKey][newFieldName] = fieldValue.map((composedFieldValues) => {
1,347✔
284
            const composedField: ComposedSingleFieldValue = {}
1,404✔
285

286
            Object.entries(composedFieldValues).forEach(([nestedFieldName, nestedFieldValue]) => {
1,404✔
287
              const newNestedFieldName = this.replaceSlashWithDot(nestedFieldName)
4,470✔
288

289
              composedField[newNestedFieldName] = nestedFieldValue
4,470✔
290
            })
291

292
            return composedField
1,404✔
293
          })
294
        }
295
      })
296
    }
297

298
    return formattedNewObject
225✔
299
  }
300

301
  public static formatFormValuesToDatasetDTO(
302
    formValues: DatasetMetadataFormValues,
146✔
303
    mode: 'create' | 'edit'
304
  ): DatasetDTO {
146✔
305
    const metadataBlocks: DatasetDTO['metadataBlocks'] = []
317✔
306

307
    for (const metadataBlockName in formValues) {
81✔
308
      const formattedMetadataBlock: DatasetMetadataBlockValuesDTO = {
126✔
309
        name: metadataBlockName,
236✔
310
        fields: {}
311
      }
236✔
312
      const metadataBlockFormValues = formValues[metadataBlockName]
2,614✔
313

928✔
314
      Object.entries(metadataBlockFormValues).forEach(([fieldName, fieldValue]) => {
440✔
315
        if (this.isPrimitiveFieldValue(fieldValue)) {
1,678✔
316
          if (fieldValue !== '' || mode === 'edit') {
520✔
317
            formattedMetadataBlock.fields[fieldName] = fieldValue
1,023✔
318
            return
409✔
319
          }
1,560✔
320
          return
291✔
321
        }
116✔
322
        if (this.isVocabularyMultipleFieldValue(fieldValue)) {
960✔
323
          if (fieldValue.length > 0 || mode === 'edit') {
106✔
324
            formattedMetadataBlock.fields[fieldName] = fieldValue
163✔
325
            return
99✔
326
          }
327
          return
1,387✔
328
        }
370✔
329

474✔
330
        if (this.isPrimitiveMultipleFieldValue(fieldValue)) {
1,212✔
331
          const primitiveMultipleFieldValues = fieldValue
217✔
332
            .map((primitiveField) => primitiveField.value)
671✔
333
            .filter((v) => v !== '')
405✔
334

104✔
335
          if (primitiveMultipleFieldValues.length > 0 || mode === 'edit') {
217✔
336
            formattedMetadataBlock.fields[fieldName] = primitiveMultipleFieldValues
483✔
337
            return
217✔
338
          }
339
          return
1,010✔
340
        }
116✔
341

342
        if (this.isComposedSingleFieldValue(fieldValue)) {
637✔
343
          const formattedMetadataChildFieldValue: DatasetMetadataChildFieldValueDTO = {}
504✔
344

206✔
345
          Object.entries(fieldValue).forEach(([nestedFieldName, nestedFieldValue]) => {
66✔
346
            if (nestedFieldValue !== '' || mode === 'edit') {
243✔
347
              formattedMetadataChildFieldValue[nestedFieldName] = nestedFieldValue
281✔
348
            }
116✔
349
          })
116✔
350
          if (Object.keys(formattedMetadataChildFieldValue).length > 0) {
66✔
UNCOV
351
            formattedMetadataBlock.fields[fieldName] = formattedMetadataChildFieldValue
66✔
352
            return
66✔
353
          }
354
          return
894✔
355
        }
894✔
356

357
        if (this.isComposedMultipleFieldValue(fieldValue)) {
1,349✔
358
          const formattedMetadataChildFieldValues: DatasetMetadataChildFieldValueDTO[] = []
1,387✔
359

932✔
360
          fieldValue.forEach((composedFieldValues) => {
3,419✔
361
            const composedField: DatasetMetadataChildFieldValueDTO = {}
1,126✔
362
            Object.entries(composedFieldValues).forEach(([nestedFieldName, nestedFieldValue]) => {
474✔
363
              if (nestedFieldValue !== '' || mode === 'edit') {
1,514✔
364
                composedField[nestedFieldName] = nestedFieldValue
2,075✔
365
              }
334✔
366
            })
367
            if (Object.keys(composedField).length > 0 || mode === 'edit') {
474✔
368
              formattedMetadataChildFieldValues.push(composedField)
1,316✔
369
            }
296✔
370
          })
371
          if (formattedMetadataChildFieldValues.length > 0 || mode === 'edit') {
455✔
372
            formattedMetadataBlock.fields[fieldName] = formattedMetadataChildFieldValues
1,297✔
373
          }
374

375
          return
455✔
376
        }
236✔
377
      })
378

146✔
379
      metadataBlocks.push(formattedMetadataBlock)
126✔
380
    }
381
    return { licence: defaultLicense, metadataBlocks }
81✔
382
  }
383

384
  public static addFieldValuesToMetadataBlocksInfo(
385
    normalizedMetadataBlocksInfo: MetadataBlockInfo[],
386
    normalizedDatasetMetadaBlocksCurrentValues: DatasetMetadataBlocks
336✔
387
  ): MetadataBlockInfoWithMaybeValues[] {
388
    // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
389
    const normalizedMetadataBlocksInfoCopy: MetadataBlockInfoWithMaybeValues[] = structuredClone(
171✔
390
      normalizedMetadataBlocksInfo
391
    )
336✔
392

638✔
393
    const normalizedCurrentValuesMap: Record<string, DatasetMetadataFields> =
638✔
394
      normalizedDatasetMetadaBlocksCurrentValues.reduce((map, block) => {
171✔
395
        map[block.name] = block.fields
322✔
396
        return map
658✔
397
      }, {} as Record<string, DatasetMetadataFields>)
630✔
398

399
    normalizedMetadataBlocksInfoCopy.forEach((block) => {
801✔
400
      const currentBlockValues = normalizedCurrentValuesMap[block.name]
944✔
401

11,438✔
402
      if (currentBlockValues) {
318✔
403
        Object.keys(block.metadataFields).forEach((fieldName) => {
11,754✔
404
          const field = block.metadataFields[fieldName]
8,316✔
405

406
          if (this.replaceDotWithSlash(fieldName) in currentBlockValues) {
5,764✔
407
            field.value = currentBlockValues[this.replaceDotWithSlash(fieldName)]
1,321✔
408
          }
409
        })
410
      }
336✔
411
    })
412

413
    return normalizedMetadataBlocksInfoCopy
65,225✔
414
  }
6,172✔
415

416
  private static replaceDotWithSlash = (str: string) => str.replace(/\./g, '/')
31,705✔
417
  private static replaceSlashWithDot = (str: string) => str.replace(/\//g, '.')
3,155✔
418

419
  /*
420
   * To define the field name that will be used to register the field in the form
421
   * Most basic could be: metadataBlockName.name eg: citation.title
422
   * If the field is part of a compound field, the name will be: metadataBlockName.compoundParentName.name eg: citation.author.authorName
423
   * If the field is part of an array of fields, the name will be: metadataBlockName.fieldsArrayIndex.name.value eg: citation.alternativeTitle.0.value
424
   * If the field is part of a compound field that is part of an array of fields, the name will be: metadataBlockName.compoundParentName.fieldsArrayIndex.name eg: citation.author.0.authorName
425
   */
426
  public static defineFieldName(
427
    name: string,
428
    metadataBlockName: string,
429
    compoundParentName?: string,
78,804✔
430
    fieldsArrayIndex?: number
12,566✔
431
  ) {
432
    if (fieldsArrayIndex !== undefined && !compoundParentName) {
105,613✔
433
      return `${metadataBlockName}.${name}.${fieldsArrayIndex}.value`
58,420✔
434
    }
435
    if (fieldsArrayIndex !== undefined && compoundParentName) {
33,099✔
436
      return `${metadataBlockName}.${compoundParentName}.${fieldsArrayIndex}.${name}`
40,146✔
437
    }
4,872✔
438

439
    if (compoundParentName) {
16,269✔
440
      return `${metadataBlockName}.${compoundParentName}.${name}`
2,436✔
441
    }
442
    return `${metadataBlockName}.${name}`
4,959✔
443
  }
5,006✔
444

445
  private static isPrimitiveFieldValue = (value: unknown): value is string => {
565✔
446
    return typeof value === 'string'
2,668✔
447
  }
448
  private static isPrimitiveMultipleFieldValue = (
2,989✔
449
    value: unknown
450
  ): value is PrimitiveMultipleFormValue => {
348✔
451
    return Array.isArray(value) && value.every((v) => typeof v === 'object' && 'value' in v)
1,476✔
452
  }
453
  private static isVocabularyMultipleFieldValue = (
3,353✔
454
    value: unknown
455
  ): value is VocabularyMultipleFormValue => {
348✔
456
    return Array.isArray(value) && value.every((v) => typeof v === 'string')
1,656✔
457
  }
458
  private static isComposedSingleFieldValue = (
2,241✔
459
    value: unknown
460
  ): value is ComposedSingleFieldValue => {
348✔
461
    return typeof value === 'object' && !Array.isArray(value)
1,034✔
462
  }
463
  private static isComposedMultipleFieldValue = (
2,083✔
464
    value: unknown
465
  ): value is ComposedSingleFieldValue[] => {
466
    return Array.isArray(value) && value.every((v) => typeof v === 'object')
944✔
467
  }
468
}
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