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

CBIIT / crdc-datahub-ui / 17135186002

21 Aug 2025 06:09PM UTC coverage: 77.612% (+1.7%) from 75.941%
17135186002

Pull #806

github

web-flow
Merge 3963dff0e into c10ceac73
Pull Request #806: Submission Request Excel Import & Export CRDCDH-3033, CRDCDH-3045, CRDCDH-3063

4850 of 5333 branches covered (90.94%)

Branch coverage included in aggregate %.

3174 of 3450 new or added lines in 33 files covered. (92.0%)

7 existing lines in 3 files now uncovered.

29048 of 38343 relevant lines covered (75.76%)

175.51 hits per line

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

5.36
/src/content/questionnaire/sections/B.tsx
1
import { parseForm } from "@jalik/form-parser";
1✔
2
import AddCircleIcon from "@mui/icons-material/AddCircle";
1✔
3
import dayjs from "dayjs";
1✔
4
import { unset } from "lodash";
1✔
5
import { FC, useEffect, useMemo, useRef, useState } from "react";
1✔
6

7
import AddRemoveButton from "../../../components/AddRemoveButton";
1✔
8
import { Status as FormStatus, useFormContext } from "../../../components/Contexts/FormContext";
1✔
9
import { useOrganizationListContext } from "../../../components/Contexts/OrganizationListContext";
1✔
10
import FormContainer from "../../../components/Questionnaire/FormContainer";
1✔
11
import FundingAgency from "../../../components/Questionnaire/FundingAgency";
1✔
12
import PlannedPublication from "../../../components/Questionnaire/PlannedPublication";
1✔
13
import Publication from "../../../components/Questionnaire/Publication";
1✔
14
import Repository from "../../../components/Questionnaire/Repository";
1✔
15
import SectionGroup from "../../../components/Questionnaire/SectionGroup";
1✔
16
import SelectInput from "../../../components/Questionnaire/SelectInput";
1✔
17
import TextInput from "../../../components/Questionnaire/TextInput";
1✔
18
import TransitionGroupWrapper from "../../../components/Questionnaire/TransitionGroupWrapper";
1✔
19
import { InitialQuestionnaire } from "../../../config/InitialValues";
1✔
20
import { NotApplicableProgram, OtherProgram } from "../../../config/ProgramConfig";
1✔
21
import SectionMetadata from "../../../config/SectionMetadata";
1✔
22
import useFormMode from "../../../hooks/useFormMode";
1✔
23
import {
1✔
24
  combineQuestionnaireData,
25
  filterAlphaNumeric,
26
  findProgram,
27
  Logger,
28
  mapObjectWithKey,
29
  validateUTF8,
30
} from "../../../utils";
31

32
export type KeyedPublication = {
33
  key: string;
34
} & Publication;
35

36
export type KeyedPlannedPublication = {
37
  key: string;
38
} & PlannedPublication;
39

40
export type KeyedRepository = {
41
  key: string;
42
} & Repository;
43

44
export type KeyedFunding = {
45
  key: string;
46
} & Funding;
47

48
/**
49
 * Form Section B View
50
 *
51
 * @param {FormSectionProps} props
52
 * @returns {JSX.Element}
53
 */
54
const FormSectionB: FC<FormSectionProps> = ({ SectionOption, refs }: FormSectionProps) => {
1✔
55
  const {
×
56
    status,
×
57
    data: { questionnaireData: data },
×
58
  } = useFormContext();
×
59
  const { data: programs } = useOrganizationListContext();
×
60
  const { readOnlyInputs } = useFormMode();
×
61
  const { B: SectionBMetadata } = SectionMetadata;
×
62

63
  const [program, setProgram] = useState<ProgramInput>(null);
×
NEW
64
  const [study, setStudy] = useState<Study>(data.study);
×
65
  const [publications, setPublications] = useState<KeyedPublication[]>(
×
66
    data.study?.publications?.map(mapObjectWithKey) || []
×
67
  );
×
68
  const [plannedPublications, setPlannedPublications] = useState<KeyedPlannedPublication[]>(
×
69
    data.study?.plannedPublications?.map(mapObjectWithKey) || []
×
70
  );
×
71
  const [repositories, setRepositories] = useState<KeyedRepository[]>(
×
72
    data.study?.repositories?.map(mapObjectWithKey) || []
×
73
  );
×
74
  const [fundings, setFundings] = useState<KeyedFunding[]>(
×
75
    data.study?.funding?.map(mapObjectWithKey) || []
×
76
  );
×
77

78
  const customProgramIds: string[] = [NotApplicableProgram._id, OtherProgram._id];
×
79
  const programKeyRef = useRef(new Date().getTime());
×
80
  const formContainerRef = useRef<HTMLDivElement>();
×
81
  const formRef = useRef<HTMLFormElement>();
×
82
  const { getFormObjectRef } = refs;
×
83

84
  useEffect(() => {
×
85
    if (!programs?.length) {
×
86
      return;
×
87
    }
×
88

89
    setProgram(findProgram(data?.program, programs));
×
90
  }, [programs]);
×
91

92
  const getFormObject = (): FormObject | null => {
×
93
    if (!formRef.current) {
×
94
      return null;
×
95
    }
×
96

97
    const formObject = parseForm(formRef.current, { nullify: false });
×
NEW
98
    const combinedData: QuestionnaireData = combineQuestionnaireData(data, formObject);
×
99

100
    // Reset study if the data failed to load
101
    if (!formObject.study) {
×
102
      combinedData.study = InitialQuestionnaire.study;
×
103
    }
×
104

105
    // Reset publications if the user has not entered any publications
106
    if (!formObject.study.publications || formObject.study.publications.length === 0) {
×
107
      combinedData.study.publications = [];
×
108
    }
×
109

110
    // Reset repositories if the user has not entered any repositories
111
    if (!formObject.study.repositories || formObject.study.repositories.length === 0) {
×
112
      combinedData.study.repositories = [];
×
113
    }
×
114

115
    // Reset planned publications if the user has not entered any planned publications
116
    if (
×
117
      !formObject.study.plannedPublications ||
×
118
      formObject.study.plannedPublications.length === 0
×
119
    ) {
×
120
      combinedData.study.plannedPublications = [];
×
121
    }
×
122

123
    // Reset planned publications if the user has not entered any planned publications
124
    // Also reset expectedDate when invalid to avoid form submission unsaved changes warning
125
    combinedData.study.plannedPublications =
×
126
      combinedData.study.plannedPublications?.map((plannedPublication) => ({
×
127
        ...plannedPublication,
×
128
        expectedDate: dayjs(plannedPublication.expectedDate).isValid()
×
129
          ? plannedPublication.expectedDate
×
130
          : "",
×
131
      })) || [];
×
132

NEW
133
    unset(combinedData.program, "key");
×
NEW
134
    unset(combinedData.study, "key");
×
NEW
135
    combinedData.study?.plannedPublications?.forEach((x) => unset(x, "key"));
×
NEW
136
    combinedData.study?.publications?.forEach((x) => unset(x, "key"));
×
NEW
137
    combinedData.study?.repositories?.forEach((x) => unset(x, "key"));
×
NEW
138
    combinedData.study?.funding?.forEach((x) => unset(x, "key"));
×
139

140
    return { ref: formRef, data: combinedData };
×
141
  };
×
142

143
  /**
144
   * Handles the program change event and updates program/study states
145
   *
146
   * @param e event
147
   * @param value new program title
148
   * @param r Reason for the event dispatch
149
   * @returns {void}
150
   */
151
  const handleProgramChange = (value: string) => {
×
152
    if (program?._id === value) {
×
153
      return;
×
154
    }
×
155

156
    const allProgramOptions = [NotApplicableProgram, ...programs, OtherProgram];
×
157
    const newProgram = allProgramOptions.find((program) => program._id === value);
×
158
    if (!newProgram?._id) {
×
159
      Logger.error(`B.tsx: Unable to change program due to invalid ID.`);
×
160
      return;
×
161
    }
×
162
    programKeyRef.current = new Date().getTime();
×
163

164
    if (newProgram?._id === NotApplicableProgram._id || newProgram?._id === OtherProgram._id) {
×
165
      setProgram({
×
166
        _id: newProgram._id,
×
167
        name: "",
×
168
        abbreviation: "",
×
169
        description: "",
×
170
      });
×
171
      return;
×
172
    }
×
173

174
    setProgram({
×
175
      _id: newProgram._id,
×
176
      name: newProgram?.name || "",
×
177
      abbreviation: newProgram?.abbreviation || "",
×
178
      description: newProgram?.description || "",
×
179
    });
×
180
  };
×
181

182
  /**
183
   * Add a empty publication to the publications state
184
   *
185
   * @returns {void}
186
   */
187
  const addPublication = () => {
×
NEW
188
    setPublications((prev) => [
×
NEW
189
      ...prev,
×
190
      {
×
191
        key: `${publications.length}_${new Date().getTime()}`,
×
192
        title: "",
×
193
        pubmedID: "",
×
194
        DOI: "",
×
195
      },
×
196
    ]);
×
197
  };
×
198

199
  /**
200
   * Remove a publication from the publications state
201
   *
202
   * @param key generated key for the publication
203
   */
204
  const removePublication = (key: string) => {
×
NEW
205
    setPublications((prev) => prev?.filter((c) => c.key !== key));
×
206
  };
×
207

208
  /**
209
   * Add a empty planned publication to the planned publications state
210
   *
211
   * @returns {void}
212
   */
213
  const addPlannedPublication = () => {
×
NEW
214
    setPlannedPublications((prev) => [
×
NEW
215
      ...prev,
×
216
      {
×
217
        key: `${plannedPublications.length}_${new Date().getTime()}`,
×
218
        title: "",
×
219
        expectedDate: dayjs().format("MM/DD/YYYY"),
×
220
      },
×
221
    ]);
×
222
  };
×
223

224
  /**
225
   * Remove a planned publication from the planned publications state
226
   *
227
   * @param key generated key for the planned publication
228
   */
229
  const removePlannedPublication = (key: string) => {
×
NEW
230
    setPlannedPublications((prev) => prev?.filter((c) => c.key !== key));
×
231
  };
×
232

233
  /**
234
   * Add a empty repository to the repositories state
235
   *
236
   * @returns {void}
237
   */
238
  const addRepository = () => {
×
NEW
239
    setRepositories((prev) => [
×
NEW
240
      ...prev,
×
241
      {
×
242
        key: `${repositories.length}_${new Date().getTime()}`,
×
243
        name: "",
×
244
        studyID: "",
×
245
        dataTypesSubmitted: [],
×
246
        otherDataTypesSubmitted: "",
×
247
      },
×
248
    ]);
×
249
  };
×
250

251
  /**
252
   * Remove a repository from the repositories state
253
   *
254
   * @param key generated key for the repository
255
   */
256
  const removeRepository = (key: string) => {
×
257
    setRepositories(repositories.filter((c) => c.key !== key));
×
258
  };
×
259

260
  /**
261
   * Add a empty funding to the fundings state
262
   *
263
   * @returns {void}
264
   */
265
  const addFunding = () => {
×
NEW
266
    setFundings((prev) => [
×
NEW
267
      ...prev,
×
268
      {
×
269
        key: `${fundings.length}_${new Date().getTime()}`,
×
270
        agency: "",
×
271
        grantNumbers: "",
×
272
        nciProgramOfficer: "",
×
273
      },
×
274
    ]);
×
275
  };
×
276

277
  /**
278
   * Remove a funding from the fundings state
279
   *
280
   * @param key generated key for the funding
281
   */
282
  const removeFunding = (key: string) => {
×
NEW
283
    setFundings((prev) => prev?.filter((f) => f.key !== key));
×
284
  };
×
285

286
  /**
287
   *  Uses a form program to create a label
288
   *
289
   * @param program The form program that will be used to create the label
290
   * @returns A label with the program name and abbreviation, if available. Otherwise an empty string.
291
   */
292
  const formatProgramLabel = (program: ProgramInput) => {
×
293
    if (!program) {
×
294
      return "";
×
295
    }
×
296
    if (customProgramIds.includes(program._id)) {
×
297
      return program._id;
×
298
    }
×
299

300
    return `${program.name || ""}${
×
301
      program.abbreviation ? ` (${program.abbreviation.toUpperCase()})` : ""
×
302
    }`?.trim();
×
303
  };
×
304

305
  useEffect(() => {
×
306
    getFormObjectRef.current = getFormObject;
×
307
  }, [refs]);
×
308

309
  useEffect(() => {
×
310
    formContainerRef.current?.scrollIntoView({ block: "start" });
×
311
  }, []);
×
312

NEW
313
  const assignStableKeys = <T extends object>(
×
NEW
314
    previous: ReadonlyArray<T & { key: string }>,
×
NEW
315
    incoming: ReadonlyArray<T | (T & { key: string })>
×
316
  ): Array<T & { key: string }> =>
NEW
317
    incoming?.map((item, index) => ({
×
NEW
318
      ...item,
×
NEW
319
      key: previous?.[index]?.key || mapObjectWithKey(item, index).key,
×
NEW
320
    }));
×
321

NEW
322
  useEffect(() => {
×
NEW
323
    setProgram(data?.program);
×
NEW
324
  }, [data?.program]);
×
325

NEW
326
  useEffect(() => {
×
NEW
327
    setStudy(data?.study);
×
NEW
328
  }, [data?.study]);
×
329

NEW
330
  useEffect(() => {
×
NEW
331
    const incoming = data?.study?.publications ?? [];
×
NEW
332
    setPublications((prev) => assignStableKeys(prev, incoming));
×
NEW
333
  }, [data.study?.publications]);
×
334

NEW
335
  useEffect(() => {
×
NEW
336
    const incoming = data?.study?.plannedPublications ?? [];
×
NEW
337
    setPlannedPublications((prev) => assignStableKeys(prev, incoming));
×
NEW
338
  }, [data?.study?.plannedPublications]);
×
339

NEW
340
  useEffect(() => {
×
NEW
341
    const incoming = data?.study?.repositories ?? [];
×
NEW
342
    setRepositories((prev) => assignStableKeys(prev, incoming));
×
NEW
343
  }, [data.study?.repositories]);
×
344

NEW
345
  useEffect(() => {
×
NEW
346
    const incoming = data?.study?.funding ?? [];
×
NEW
347
    setFundings((prev) => assignStableKeys(prev, incoming));
×
NEW
348
  }, [data.study?.funding]);
×
349

UNCOV
350
  const allProgramOptions = useMemo(() => {
×
351
    // Filter out system-managed programs
352
    const filteredPrograms = programs?.filter((p) => !p.readOnly);
×
353

354
    return [NotApplicableProgram, ...filteredPrograms, OtherProgram];
×
355
  }, [NotApplicableProgram, OtherProgram, programs]);
×
356
  const readOnlyProgram = readOnlyInputs || program?._id !== OtherProgram._id;
×
357

358
  return (
×
359
    <FormContainer ref={formContainerRef} formRef={formRef} description={SectionOption.title}>
×
360
      {/* Program Registration Section */}
361
      <SectionGroup
×
362
        title={SectionBMetadata.sections.PROGRAM_INFORMATION.title}
×
363
        description={SectionBMetadata.sections.PROGRAM_INFORMATION.description}
×
364
      >
365
        <SelectInput
×
366
          id="section-b-program"
×
367
          label="Program"
×
368
          name="program[_id]"
×
369
          options={allProgramOptions.map((program) => ({
×
370
            label: formatProgramLabel(program),
×
371
            value: program._id,
×
372
          }))}
×
373
          value={program?._id}
×
374
          onChange={handleProgramChange}
×
375
          placeholder="Select a program"
×
376
          gridWidth={12}
×
377
          tooltipText="The name of the broad administrative group that manages the data collection.  Example - Clinical Proteomic Tumor Analysis Consortium."
×
378
          required
×
379
          readOnly={readOnlyInputs}
×
380
        />
381
        <TextInput
×
382
          key={`program-name-${program?.name}_${programKeyRef.current}`}
×
383
          id="section-b-program-title"
×
384
          label="Program Title"
×
385
          name="program[name]"
×
386
          value={program?.name}
×
387
          validate={(input: string) => !validateUTF8(input)}
×
388
          maxLength={100}
×
389
          placeholder="100 characters allowed"
×
390
          hideValidation={readOnlyProgram}
×
391
          required
×
392
          readOnly={readOnlyProgram}
×
393
        />
394
        <TextInput
×
395
          key={`program-abbreviation-${program?.abbreviation}_${programKeyRef.current}`}
×
396
          id="section-b-program-abbreviation"
×
397
          label="Program Abbreviation"
×
398
          name="program[abbreviation]"
×
399
          value={program?.abbreviation}
×
400
          filter={(input: string) => filterAlphaNumeric(input, "- ")}
×
401
          onChange={(e) => {
×
402
            e.target.value = e.target.value.toUpperCase();
×
403
          }}
×
404
          maxLength={100}
×
405
          placeholder="100 characters allowed"
×
406
          hideValidation={readOnlyProgram}
×
407
          required
×
408
          readOnly={readOnlyProgram}
×
409
        />
410
        <TextInput
×
411
          key={`program-description-${program?.description}_${programKeyRef.current}`}
×
412
          id="section-b-program-description"
×
413
          label="Program Description"
×
414
          name="program[description]"
×
415
          value={program?.description}
×
416
          gridWidth={12}
×
417
          maxLength={500}
×
418
          placeholder="500 characters allowed"
×
419
          rows={4}
×
420
          hideValidation={readOnlyProgram}
×
421
          multiline
×
422
          resize
×
423
          required
×
424
          readOnly={readOnlyProgram}
×
425
        />
426
      </SectionGroup>
×
427

428
      {/* Study Registration Section */}
429
      <SectionGroup
×
430
        title={SectionBMetadata.sections.STUDY_INFORMATION.title}
×
431
        description={SectionBMetadata.sections.STUDY_INFORMATION.description}
×
432
      >
433
        <TextInput
×
434
          id="section-b-study-title"
×
435
          label="Study Title"
×
436
          name="study[name]"
×
437
          value={study.name}
×
438
          maxLength={100}
×
439
          placeholder="100 characters allowed"
×
440
          validate={(input: string) => !validateUTF8(input)}
×
441
          readOnly={readOnlyInputs}
×
442
          hideValidation={readOnlyInputs}
×
443
          tooltipText="A descriptive name that will be used to identify the study."
×
444
          required
×
445
        />
446
        <TextInput
×
447
          id="section-b-study-abbreviation-or-acronym"
×
448
          label="Study Abbreviation"
×
449
          name="study[abbreviation]"
×
450
          value={study.abbreviation}
×
451
          filter={(input: string) => filterAlphaNumeric(input, "- ")}
×
452
          onChange={(e) => {
×
453
            e.target.value = e.target.value.toUpperCase();
×
454
          }}
×
455
          maxLength={20}
×
456
          placeholder="20 characters allowed"
×
457
          readOnly={readOnlyInputs}
×
458
          hideValidation={readOnlyInputs}
×
459
          tooltipText="Provide a short abbreviation or acronym (e.g., NCI-MATCH) for the study."
×
460
        />
461
        <TextInput
×
462
          id="section-b-study-description"
×
463
          label="Study Description"
×
464
          name="study[description]"
×
465
          value={study.description}
×
466
          gridWidth={12}
×
467
          maxLength={2500}
×
468
          placeholder="2,500 characters allowed"
×
469
          rows={4}
×
470
          readOnly={readOnlyInputs}
×
471
          hideValidation={readOnlyInputs}
×
472
          required
×
473
          multiline
×
474
          resize
×
475
          tooltipText="Describe your study and the data being submitted. Include objectives of the study and provide a brief description of the scientific value of the study."
×
476
        />
477
      </SectionGroup>
×
478

479
      {/* Funding Agency */}
480
      <SectionGroup
×
481
        title={SectionBMetadata.sections.FUNDING_AGENCY.title}
×
482
        description={SectionBMetadata.sections.FUNDING_AGENCY.description}
×
483
        endButton={
×
484
          <AddRemoveButton
×
485
            id="section-b-add-funding-agency-button"
×
486
            label="Add Agency"
×
487
            startIcon={<AddCircleIcon />}
×
488
            onClick={addFunding}
×
489
            disabled={readOnlyInputs || status === FormStatus.SAVING}
×
490
          />
491
        }
492
      >
493
        <TransitionGroupWrapper
×
494
          items={fundings}
×
495
          renderItem={(funding: KeyedFunding, idx: number) => (
×
NEW
496
            <>
×
NEW
497
              <input
×
NEW
498
                type="hidden"
×
NEW
499
                name={`study[funding][${idx}][key]`}
×
NEW
500
                value={funding.key}
×
NEW
501
                readOnly
×
502
              />
NEW
503
              <FundingAgency
×
NEW
504
                idPrefix="section-b-"
×
NEW
505
                index={idx}
×
NEW
506
                funding={funding}
×
NEW
507
                onDelete={() => removeFunding(funding.key)}
×
NEW
508
                readOnly={readOnlyInputs}
×
509
              />
NEW
510
            </>
×
UNCOV
511
          )}
×
512
        />
513
      </SectionGroup>
×
514

515
      {/* Existing Publications */}
516
      <SectionGroup
×
517
        title={SectionBMetadata.sections.EXISTING_PUBLICATIONS.title}
×
518
        description={SectionBMetadata.sections.EXISTING_PUBLICATIONS.description}
×
519
        endButton={
×
520
          <AddRemoveButton
×
521
            id="section-b-add-publication-button"
×
522
            label="Add Existing Publication"
×
523
            startIcon={<AddCircleIcon />}
×
524
            onClick={addPublication}
×
525
            disabled={readOnlyInputs || status === FormStatus.SAVING}
×
526
          />
527
        }
528
      >
529
        <TransitionGroupWrapper
×
530
          items={publications}
×
531
          renderItem={(pub: KeyedPublication, idx: number) => (
×
NEW
532
            <>
×
NEW
533
              <input
×
NEW
534
                type="hidden"
×
NEW
535
                name={`study[publications][${idx}][key]`}
×
NEW
536
                value={pub.key}
×
NEW
537
                readOnly
×
538
              />
NEW
539
              <Publication
×
NEW
540
                idPrefix="section-b-"
×
NEW
541
                index={idx}
×
NEW
542
                publication={pub}
×
NEW
543
                onDelete={() => removePublication(pub.key)}
×
NEW
544
                readOnly={readOnlyInputs}
×
545
              />
NEW
546
            </>
×
UNCOV
547
          )}
×
548
        />
549
      </SectionGroup>
×
550

551
      {/* Planned Publications */}
552
      <SectionGroup
×
553
        title={SectionBMetadata.sections.PLANNED_PUBLICATIONS.title}
×
554
        description={SectionBMetadata.sections.PLANNED_PUBLICATIONS.description}
×
555
        endButton={
×
556
          <AddRemoveButton
×
557
            id="section-b-add-planned-publication-button"
×
558
            label="Add Planned Publication"
×
559
            startIcon={<AddCircleIcon />}
×
560
            onClick={addPlannedPublication}
×
561
            disabled={readOnlyInputs || status === FormStatus.SAVING}
×
562
          />
563
        }
564
      >
565
        <TransitionGroupWrapper
×
566
          items={plannedPublications}
×
567
          renderItem={(pub: KeyedPlannedPublication, idx: number) => (
×
NEW
568
            <>
×
NEW
569
              <input
×
NEW
570
                type="hidden"
×
NEW
571
                name={`study[plannedPublications][${idx}][key]`}
×
NEW
572
                value={pub.key}
×
NEW
573
                readOnly
×
574
              />
NEW
575
              <PlannedPublication
×
NEW
576
                idPrefix="section-b-"
×
NEW
577
                index={idx}
×
NEW
578
                plannedPublication={pub}
×
NEW
579
                onDelete={() => removePlannedPublication(pub.key)}
×
NEW
580
                readOnly={readOnlyInputs}
×
581
              />
NEW
582
            </>
×
UNCOV
583
          )}
×
584
        />
585
      </SectionGroup>
×
586

587
      {/* Study Repositories */}
588
      <SectionGroup
×
589
        title={SectionBMetadata.sections.REPOSITORY.title}
×
590
        description={SectionBMetadata.sections.REPOSITORY.description}
×
591
        endButton={
×
592
          <AddRemoveButton
×
593
            id="section-b-add-repository-button"
×
594
            label="Add Repository"
×
595
            startIcon={<AddCircleIcon />}
×
596
            onClick={addRepository}
×
597
            disabled={readOnlyInputs || status === FormStatus.SAVING}
×
598
          />
599
        }
600
      >
601
        <TransitionGroupWrapper
×
602
          items={repositories}
×
603
          renderItem={(repo: KeyedRepository, idx: number) => (
×
NEW
604
            <>
×
NEW
605
              <input
×
NEW
606
                type="hidden"
×
NEW
607
                name={`study[repositories][${idx}][key]`}
×
NEW
608
                value={repo.key}
×
NEW
609
                readOnly
×
610
              />
NEW
611
              <Repository
×
NEW
612
                idPrefix="section-b-"
×
NEW
613
                index={idx}
×
NEW
614
                repository={repo}
×
NEW
615
                onDelete={() => removeRepository(repo.key)}
×
NEW
616
                readOnly={readOnlyInputs}
×
617
              />
NEW
618
            </>
×
UNCOV
619
          )}
×
620
        />
621
      </SectionGroup>
×
622
    </FormContainer>
×
623
  );
624
};
×
625

626
export default FormSectionB;
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

© 2025 Coveralls, Inc