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

IQSS / dataverse-frontend / 9745078048

01 Jul 2024 01:23PM UTC coverage: 97.751% (+0.2%) from 97.581%
9745078048

Pull #424

github

web-flow
Merge 5e8b5e1f2 into 107aaca3f
Pull Request #424: Feature/420 Select Advanced (single and multiple selection with search)

988 of 1024 branches covered (96.48%)

Branch coverage included in aggregate %.

554 of 560 new or added lines in 32 files covered. (98.93%)

2 existing lines in 2 files now uncovered.

2620 of 2667 relevant lines covered (98.24%)

8586.37 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
} from '../../../../dataset/domain/models/Dataset'
17

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

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

25
type VocabularyMultipleFormValue = string[]
26

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

29
type ComposedFieldValues = ComposedSingleFieldValue | ComposedSingleFieldValue[]
30

31
type ComposedSingleFieldValue = Record<string, string>
32

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

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

48
  private static metadataBlocksInfoDotReplacer(metadataFields: Record<string, MetadataField>) {
49
    for (const key in metadataFields) {
6,336✔
50
      const field = metadataFields[key]
27,052✔
51
      if (field.name.includes('.')) {
27,052✔
52
        field.name = this.replaceDotWithSlash(field.name)
422✔
53
      }
54
      if (field.childMetadataFields) {
27,052✔
55
        this.metadataBlocksInfoDotReplacer(field.childMetadataFields)
5,095✔
56
      }
57
    }
58
  }
59

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

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

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

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

79
    return dataWithoutKeysWithDots
170✔
80
  }
81

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

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

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

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

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

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

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

125
    return datasetMetadataFieldsNormalized
322✔
126
  }
127

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

133
    for (const block of metadataBlocks) {
1,189✔
134
      const blockValues: MetadataBlockFormValues = {}
1,247✔
135

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

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

143
          if (field.childMetadataFields) {
5,107✔
144
            for (const childField of Object.values(field.childMetadataFields)) {
5,107✔
145
              if (childField.typeClass === 'primitive') {
16,769✔
146
                childFieldsWithEmptyValues[childField.name] = ''
14,878✔
147
              }
148

149
              if (childField.typeClass === 'controlledVocabulary') {
16,769✔
150
                childFieldsWithEmptyValues[childField.name] = ''
1,891✔
151
              }
152
            }
153
          }
154

155
          if (fieldValue) {
5,107✔
156
            const castedFieldValue = fieldValue as
628✔
157
              | DatasetMetadataSubField
158
              | DatasetMetadataSubField[]
159

160
            let fieldValues: ComposedFieldValues
161

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

173
                return {
612✔
174
                  ...childFieldsWithEmptyValues,
175
                  ...fieldsValueNormalized
176
                }
177
              })
178

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

190
              fieldValues = {
165✔
191
                ...childFieldsWithEmptyValues,
192
                ...fieldsValueNormalized
193
              }
194
            }
195

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

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

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

213
      formDefaultValues[block.name] = blockValues
1,247✔
214
    }
215

216
    return formDefaultValues
1,189✔
217
  }
218

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

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

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

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

239
      if (!castedFieldValue) return []
745✔
240

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

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

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

254
      formattedNewObject[blockKey] = {}
119✔
255

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

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

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

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

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

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

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

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

297
    return formattedNewObject
74✔
298
  }
299

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

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

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

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

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

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

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

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

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

371
          return
447✔
372
        }
373
      })
374

375
      metadataBlocks.push(formattedMetadataBlock)
118✔
376
    }
377
    return { metadataBlocks }
73✔
378
  }
379

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

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

395
    normalizedMetadataBlocksInfoCopy.forEach((block) => {
172✔
396
      const currentBlockValues = normalizedCurrentValuesMap[block.name]
323✔
397

398
      if (currentBlockValues) {
323✔
399
        Object.keys(block.metadataFields).forEach((fieldName) => {
321✔
400
          const field = block.metadataFields[fieldName]
5,867✔
401

402
          if (this.replaceDotWithSlash(fieldName) in currentBlockValues) {
5,867✔
403
            field.value = currentBlockValues[this.replaceDotWithSlash(fieldName)]
1,304✔
404
          }
405
        })
406
      }
407
    })
408

409
    return normalizedMetadataBlocksInfoCopy
172✔
410
  }
411

412
  private static replaceDotWithSlash = (str: string) => str.replace(/\./g, '/')
7,881✔
413
  private static replaceSlashWithDot = (str: string) => str.replace(/\//g, '.')
3,034✔
414

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

435
    if (compoundParentName) {
6,129✔
436
      return `${metadataBlockName}.${compoundParentName}.${name}`
2,384✔
437
    }
438
    return `${metadataBlockName}.${name}`
3,745✔
439
  }
440

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