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

IQSS / dataverse-frontend / 10267025895

06 Aug 2024 12:59PM UTC coverage: 97.89% (+0.2%) from 97.727%
10267025895

push

github

GermanSaracca
chore: use ghcr dataverse image tag

1216 of 1257 branches covered (96.74%)

Branch coverage included in aggregate %.

3284 of 3340 relevant lines covered (98.32%)

9101.53 hits per line

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

99.62
/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)
644✔
40

41
    for (const block of metadataBlocksCopy) {
644✔
42
      if (block.metadataFields) {
1,315✔
43
        this.metadataBlocksInfoDotReplacer(block.metadataFields)
1,315✔
44
      }
45
    }
46
    return metadataBlocksCopy
644✔
47
  }
48

49
  private static metadataBlocksInfoDotReplacer(metadataFields: Record<string, MetadataField>) {
50
    for (const key in metadataFields) {
6,676✔
51
      const field = metadataFields[key]
28,435✔
52
      if (field.name.includes('.')) {
28,435✔
53
        field.name = this.replaceDotWithSlash(field.name)
422✔
54
      }
55
      if (field.childMetadataFields) {
28,435✔
56
        this.metadataBlocksInfoDotReplacer(field.childMetadataFields)
5,361✔
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)
170✔
66
    const dataWithoutKeysWithDots: DatasetMetadataBlocks = [] as unknown as DatasetMetadataBlocks
170✔
67

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

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

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

80
    return dataWithoutKeysWithDots
170✔
81
  }
82

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

88
    for (const key in datasetMetadataFields) {
322✔
89
      const newKey = key.includes('.') ? this.replaceDotWithSlash(key) : key
1,790✔
90

91
      const value = datasetMetadataFields[key]
1,790✔
92

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

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

104
        datasetMetadataFieldsNormalized[newKey] = nestedKeysMapped
170✔
105
      } else if (
1,620✔
106
        Array.isArray(value) &&
2,354✔
107
        (value as readonly (string | DatasetMetadataSubField)[]).every((v) => typeof v === 'object')
868✔
108
      ) {
109
        // Case of DatasetMetadataSubField[]
110
        const nestedKeysMapped = value.map((subFields) => {
492✔
111
          return Object.entries(subFields).reduce((acc, [nestedKey, nestedValue]) => {
626✔
112
            const newNestedKey = nestedKey.includes('.')
1,744✔
113
              ? this.replaceDotWithSlash(nestedKey)
114
              : nestedKey
115

116
            acc[newNestedKey] = nestedValue
1,744✔
117
            return acc
1,744✔
118
          }, {} as DatasetMetadataSubField)
119
        })
120
        datasetMetadataFieldsNormalized[newKey] = nestedKeysMapped
492✔
121
      } else {
122
        datasetMetadataFieldsNormalized[newKey] = value
1,128✔
123
      }
124
    }
125

126
    return datasetMetadataFieldsNormalized
322✔
127
  }
128

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

134
    for (const block of metadataBlocks) {
1,226✔
135
      const blockValues: MetadataBlockFormValues = {}
1,321✔
136

137
      for (const field of Object.values(block.metadataFields)) {
1,321✔
138
        const fieldName = field.name
10,897✔
139
        const fieldValue = field.value
10,897✔
140

141
        if (field.typeClass === 'compound') {
10,897✔
142
          const childFieldsWithEmptyValues: Record<string, string> = {}
5,373✔
143

144
          if (field.childMetadataFields) {
5,373✔
145
            for (const childField of Object.values(field.childMetadataFields)) {
5,373✔
146
              if (childField.typeClass === 'primitive') {
17,670✔
147
                childFieldsWithEmptyValues[childField.name] = ''
15,664✔
148
              }
149

150
              if (childField.typeClass === 'controlledVocabulary') {
17,670✔
151
                childFieldsWithEmptyValues[childField.name] = ''
2,006✔
152
              }
153
            }
154
          }
155

156
          if (fieldValue) {
5,373✔
157
            const castedFieldValue = fieldValue as
644✔
158
              | DatasetMetadataSubField
159
              | DatasetMetadataSubField[]
160

161
            let fieldValues: ComposedFieldValues
162

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

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

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

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

197
            blockValues[fieldName] = fieldValues
644✔
198
          } else {
199
            blockValues[fieldName] = field.multiple
4,729✔
200
              ? [childFieldsWithEmptyValues]
201
              : childFieldsWithEmptyValues
202
          }
203
        }
204

205
        if (field.typeClass === 'primitive') {
10,897✔
206
          blockValues[fieldName] = this.getPrimitiveFieldDefaultFormValue(field)
4,713✔
207
        }
208

209
        if (field.typeClass === 'controlledVocabulary') {
10,897✔
210
          blockValues[fieldName] = this.getControlledVocabFieldDefaultFormValue(field)
811✔
211
        }
212
      }
213

214
      formDefaultValues[block.name] = blockValues
1,321✔
215
    }
216

217
    return formDefaultValues
1,226✔
218
  }
219

220
  private static getPrimitiveFieldDefaultFormValue(
221
    field: MetadataFieldWithMaybeValue
222
  ): string | PrimitiveMultipleFormValue {
223
    if (field.multiple) {
4,713✔
224
      const castedFieldValue = field.value as string[] | undefined
1,182✔
225

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

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

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

240
      if (!castedFieldValue) return []
786✔
241

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

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

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

255
      formattedNewObject[blockKey] = {}
119✔
256

257
      Object.entries(metadataBlockFormValues).forEach(([fieldName, fieldValue]) => {
119✔
258
        const newFieldName = this.replaceSlashWithDot(fieldName)
1,207✔
259

260
        if (
1,207✔
261
          this.isPrimitiveFieldValue(fieldValue) ||
2,691✔
262
          this.isVocabularyMultipleFieldValue(fieldValue) ||
263
          this.isPrimitiveMultipleFieldValue(fieldValue)
264
        ) {
265
          formattedNewObject[blockKey][newFieldName] = fieldValue
700✔
266
          return
700✔
267
        }
268

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

277
            parentOfNestedField[newNestedFieldName] = nestedFieldValue
222✔
278
          })
279
          return
59✔
280
        }
281

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

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

289
              composedField[newNestedFieldName] = nestedFieldValue
1,486✔
290
            })
291

292
            return composedField
467✔
293
          })
294
        }
295
      })
296
    }
297

298
    return formattedNewObject
74✔
299
  }
300

301
  public static formatFormValuesToDatasetDTO(formValues: DatasetMetadataFormValues): DatasetDTO {
302
    const metadataBlocks: DatasetDTO['metadataBlocks'] = []
73✔
303

304
    for (const metadataBlockName in formValues) {
73✔
305
      const formattedMetadataBlock: DatasetMetadataBlockValuesDTO = {
118✔
306
        name: metadataBlockName,
307
        fields: {}
308
      }
309
      const metadataBlockFormValues = formValues[metadataBlockName]
118✔
310

311
      Object.entries(metadataBlockFormValues).forEach(([fieldName, fieldValue]) => {
118✔
312
        if (this.isPrimitiveFieldValue(fieldValue)) {
1,192✔
313
          if (fieldValue !== '') {
412✔
314
            formattedMetadataBlock.fields[fieldName] = fieldValue
157✔
315
            return
157✔
316
          }
317
          return
255✔
318
        }
319
        if (this.isVocabularyMultipleFieldValue(fieldValue)) {
780✔
320
          if (fieldValue.length > 0) {
90✔
321
            formattedMetadataBlock.fields[fieldName] = fieldValue
58✔
322
            return
58✔
323
          }
324
          return
32✔
325
        }
326

327
        if (this.isPrimitiveMultipleFieldValue(fieldValue)) {
690✔
328
          const primitiveMultipleFieldValues = fieldValue
185✔
329
            .map((primitiveField) => primitiveField.value)
237✔
330
            .filter((v) => v !== '')
237✔
331

332
          if (primitiveMultipleFieldValues.length > 0) {
185✔
333
            formattedMetadataBlock.fields[fieldName] = primitiveMultipleFieldValues
52✔
334
            return
52✔
335
          }
336
          return
133✔
337
        }
338

339
        if (this.isComposedSingleFieldValue(fieldValue)) {
505✔
340
          const formattedMetadataChildFieldValue: DatasetMetadataChildFieldValueDTO = {}
58✔
341

342
          Object.entries(fieldValue).forEach(([nestedFieldName, nestedFieldValue]) => {
58✔
343
            if (nestedFieldValue !== '') {
219✔
344
              formattedMetadataChildFieldValue[nestedFieldName] = nestedFieldValue
103✔
345
            }
346
          })
347
          if (Object.keys(formattedMetadataChildFieldValue).length > 0) {
58✔
348
            formattedMetadataBlock.fields[fieldName] = formattedMetadataChildFieldValue
58✔
349
            return
58✔
350
          }
351
          return
×
352
        }
353

354
        if (this.isComposedMultipleFieldValue(fieldValue)) {
447✔
355
          const formattedMetadataChildFieldValues: DatasetMetadataChildFieldValueDTO[] = []
447✔
356

357
          fieldValue.forEach((composedFieldValues) => {
447✔
358
            const composedField: DatasetMetadataChildFieldValueDTO = {}
466✔
359
            Object.entries(composedFieldValues).forEach(([nestedFieldName, nestedFieldValue]) => {
466✔
360
              if (nestedFieldValue !== '') {
1,482✔
361
                composedField[nestedFieldName] = nestedFieldValue
326✔
362
              }
363
            })
364
            if (Object.keys(composedField).length > 0) {
466✔
365
              formattedMetadataChildFieldValues.push(composedField)
167✔
366
            }
367
          })
368
          if (formattedMetadataChildFieldValues.length > 0) {
447✔
369
            formattedMetadataBlock.fields[fieldName] = formattedMetadataChildFieldValues
148✔
370
          }
371

372
          return
447✔
373
        }
374
      })
375

376
      metadataBlocks.push(formattedMetadataBlock)
118✔
377
    }
378
    return { licence: defaultLicense, metadataBlocks }
73✔
379
  }
380

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

390
    const normalizedCurrentValuesMap: Record<string, DatasetMetadataFields> =
391
      normalizedDatasetMetadaBlocksCurrentValues.reduce((map, block) => {
176✔
392
        map[block.name] = block.fields
335✔
393
        return map
335✔
394
      }, {} as Record<string, DatasetMetadataFields>)
395

396
    normalizedMetadataBlocksInfoCopy.forEach((block) => {
176✔
397
      const currentBlockValues = normalizedCurrentValuesMap[block.name]
331✔
398

399
      if (currentBlockValues) {
331✔
400
        Object.keys(block.metadataFields).forEach((fieldName) => {
329✔
401
          const field = block.metadataFields[fieldName]
6,019✔
402

403
          if (this.replaceDotWithSlash(fieldName) in currentBlockValues) {
6,019✔
404
            field.value = currentBlockValues[this.replaceDotWithSlash(fieldName)]
1,332✔
405
          }
406
        })
407
      }
408
    })
409

410
    return normalizedMetadataBlocksInfoCopy
176✔
411
  }
412

413
  private static replaceDotWithSlash = (str: string) => str.replace(/\./g, '/')
8,061✔
414
  private static replaceSlashWithDot = (str: string) => str.replace(/\//g, '.')
3,034✔
415

416
  /*
417
   * To define the field name that will be used to register the field in the form
418
   * Most basic could be: metadataBlockName.name eg: citation.title
419
   * If the field is part of a compound field, the name will be: metadataBlockName.compoundParentName.name eg: citation.author.authorName
420
   * If the field is part of an array of fields, the name will be: metadataBlockName.fieldsArrayIndex.name.value eg: citation.alternativeTitle.0.value
421
   * 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
422
   */
423
  public static defineFieldName(
424
    name: string,
425
    metadataBlockName: string,
426
    compoundParentName?: string,
427
    fieldsArrayIndex?: number
428
  ) {
429
    if (fieldsArrayIndex !== undefined && !compoundParentName) {
39,402✔
430
      return `${metadataBlockName}.${name}.${fieldsArrayIndex}.value`
6,332✔
431
    }
432
    if (fieldsArrayIndex !== undefined && compoundParentName) {
33,070✔
433
      return `${metadataBlockName}.${compoundParentName}.${fieldsArrayIndex}.${name}`
26,605✔
434
    }
435

436
    if (compoundParentName) {
6,465✔
437
      return `${metadataBlockName}.${compoundParentName}.${name}`
2,532✔
438
    }
439
    return `${metadataBlockName}.${name}`
3,933✔
440
  }
441

442
  private static isPrimitiveFieldValue = (value: unknown): value is string => {
88✔
443
    return typeof value === 'string'
2,399✔
444
  }
445
  private static isPrimitiveMultipleFieldValue = (
88✔
446
    value: unknown
447
  ): value is PrimitiveMultipleFormValue => {
448
    return Array.isArray(value) && value.every((v) => typeof v === 'object' && 'value' in v)
1,386✔
449
  }
450
  private static isVocabularyMultipleFieldValue = (
88✔
451
    value: unknown
452
  ): value is VocabularyMultipleFormValue => {
453
    return Array.isArray(value) && value.every((v) => typeof v === 'string')
1,568✔
454
  }
455
  private static isComposedSingleFieldValue = (
88✔
456
    value: unknown
457
  ): value is ComposedSingleFieldValue => {
458
    return typeof value === 'object' && !Array.isArray(value)
1,012✔
459
  }
460
  private static isComposedMultipleFieldValue = (
88✔
461
    value: unknown
462
  ): value is ComposedSingleFieldValue[] => {
463
    return Array.isArray(value) && value.every((v) => typeof v === 'object')
933✔
464
  }
465
}
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

© 2025 Coveralls, Inc