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

CBIIT / crdc-datahub-ui / 18789341118

24 Oct 2025 06:57PM UTC coverage: 78.178% (+15.5%) from 62.703%
18789341118

push

github

web-flow
Merge pull request #888 from CBIIT/3.4.0

3.4.0 Release

4977 of 5488 branches covered (90.69%)

Branch coverage included in aggregate %.

8210 of 9264 new or added lines in 257 files covered. (88.62%)

6307 existing lines in 120 files now uncovered.

30203 of 39512 relevant lines covered (76.44%)

213.36 hits per line

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

92.03
/src/classes/Excel/SectionBase.ts
1
import type ExcelJS from "exceljs";
2
import { CellValue } from "exceljs";
3

4
import { Logger } from "@/utils";
1✔
5

6
import { ErrorCatalog } from "./ErrorCatalog";
1✔
7

8
/**
9
 * Represents the context for a section in the Excel worksheet.
10
 */
11
export type SectionCtxBase = {
12
  /**
13
   * The Excel workbook.
14
   */
15
  workbook: ExcelJS.Workbook;
16
  /**
17
   * The utility functions for the section.
18
   */
19
  u: {
20
    /**
21
     * Set the header for the worksheet.
22
     *
23
     * @param ws The worksheet to modify.
24
     * @param color The color to use for the header.
25
     * @returns void
26
     */
27
    header: (ws: ExcelJS.Worksheet, color?: string) => void;
28
  };
29
};
30

31
/**
32
 * Represents the definition of a column in the section.
33
 */
34
export type ColumnDef<K extends string> = Omit<Partial<ExcelJS.Column>, "key"> & {
35
  key: K;
36
  /**
37
   * An optional annotation for the column header.
38
   */
39
  annotation?: string;
40
};
41

42
/**
43
 * Represents the character limits for each column in the section.
44
 */
45
export type CharacterLimitsMap<K extends string> = Readonly<Partial<{ [key in K]: number }>>;
46

47
/**
48
 * Represents the configuration for a section in the Excel worksheet.
49
 */
50
export type SectionConfig<K extends string, D> = {
51
  id: string;
52
  deps: D;
53
  sheetName: string;
54
  columns: ColumnDef<K>[];
55
  headerColor?: string;
56
  characterLimits?: CharacterLimitsMap<K>;
57
};
58

59
/**
60
 * Represents a section in the Excel worksheet.
61
 */
62
export type Section = {
63
  /**
64
   * The unique identifier for the section.
65
   *
66
   * @example "B"
67
   */
68
  id: string;
69
  /**
70
   * Serialize the section data to the worksheet.
71
   *
72
   * @param ctx The section context.
73
   * @returns The created worksheet.
74
   */
75
  serialize: (ctx: SectionCtxBase) => Promise<ExcelJS.Worksheet>;
76
};
77

78
/**
79
 * Represents a section in the Excel worksheet.
80
 */
81
export abstract class SectionBase<K extends string, D> implements Section {
1✔
82
  /**
83
   * The unique identifier for the section.
84
   * @example "B"
85
   */
86
  public readonly id: string;
72✔
87

88
  /**
89
   * The section dependencies.
90
   */
91
  protected readonly deps: D;
72✔
92

93
  /**
94
   * The name of the worksheet.
95
   * @example "Program and Study"
96
   */
97
  private readonly _sheetName: string;
72✔
98

99
  /**
100
   * The columns in the section.
101
   */
102
  private readonly _columns: ColumnDef<K>[];
72✔
103

104
  /**
105
   * The color of the header row.
106
   * @example "#D9EAD3"
107
   */
108
  private readonly _headerColor: string;
72✔
109

110
  /**
111
   * The character limits for each column in the section.
112
   */
113
  protected readonly CHARACTER_LIMITS: CharacterLimitsMap<K>;
72✔
114

115
  /**
116
   * Create a new section.
117
   *
118
   * @param cfg The section configuration.
119
   */
120
  constructor(cfg: SectionConfig<K, D>) {
72✔
121
    this.id = cfg.id;
72✔
122
    this.deps = cfg.deps;
72✔
123
    this._sheetName = cfg.sheetName;
72✔
124
    this._columns = cfg.columns;
72✔
125
    this._headerColor = cfg.headerColor || "#D9EAD3";
72!
126
    this.CHARACTER_LIMITS = Object.freeze({ ...(cfg.characterLimits ?? {}) });
72✔
127
  }
72✔
128

129
  /**
130
   * Serialize the section data to the worksheet.
131
   *
132
   * @param ctx The section context.
133
   * @returns The created worksheet.
134
   */
135
  public async serialize(ctx: SectionCtxBase): Promise<ExcelJS.Worksheet> {
72✔
136
    const ws = this.create(ctx);
72✔
137
    this.write(ctx, ws);
72✔
138
    this.applyValidation(ctx, ws);
72✔
139
    this.annotate(
72✔
140
      ws,
72✔
141
      this._columns
72✔
142
        .filter((c) => c.annotation)
72✔
143
        .reduce(
72✔
144
          (acc, col) => {
72✔
145
            acc[col.key] = col.annotation;
239✔
146
            return acc;
239✔
147
          },
239✔
148
          {} as Partial<Record<K, string>>
72✔
149
        )
72✔
150
    );
72✔
151

152
    return ws;
72✔
153
  }
72✔
154

155
  /**
156
   * Create worksheet, add columns, and style header.
157
   *
158
   * @param ctx The section context.
159
   * @returns The created worksheet.
160
   */
161
  protected create(ctx: SectionCtxBase): ExcelJS.Worksheet {
72✔
162
    const existing = ctx.workbook.worksheets.find((ws) => ws.name === this._sheetName);
71✔
163
    if (existing) {
71!
NEW
164
      ctx.workbook.removeWorksheet(existing.id);
×
NEW
165
    }
×
166

167
    const ws = ctx.workbook.addWorksheet(this._sheetName);
71✔
168
    ws.columns = this._columns;
71✔
169

170
    const color = (this._headerColor || "#D9EAD3").replace(/^#/, "").toUpperCase();
71!
171
    ctx.u.header(ws, color);
71✔
172

173
    ws.views = [{ state: "frozen", ySplit: 1 }];
71✔
174

175
    return ws;
71✔
176
  }
71✔
177

178
  /**
179
   * Write data to the worksheet.
180
   *
181
   * @param ctx The section context.
182
   * @param ws The worksheet.
183
   * @returns The created row.
184
   */
185
  protected abstract write(ctx: SectionCtxBase, ws: ExcelJS.Worksheet): ExcelJS.Row[];
186

187
  /**
188
   * Attach data validation to the data in the worksheet.
189
   *
190
   * @param ctx The section context.
191
   * @param ws The worksheet.
192
   * @param row The row to validate.
193
   */
194
  protected abstract applyValidation(
195
    ctx: SectionCtxBase,
196
    ws: ExcelJS.Worksheet
197
  ): void | Promise<void>;
198

199
  /**
200
   * Write annotations on the specified cells in the worksheet.
201
   *
202
   * @param ws The worksheet to annotate
203
   * @param annotations A map of Cell Key -> Note
204
   * @param rowNum The row to annotate. Default is the header row.
205
   */
206
  protected annotate(
72✔
207
    ws: ExcelJS.Worksheet,
72✔
208
    annotations: Partial<Record<K, string>>,
72✔
209
    rowNum = 1
72✔
210
  ): void {
72✔
211
    const row = ws.getRow(rowNum);
72✔
212
    this._columns.forEach((col, index) => {
72✔
213
      if (col.key in annotations) {
1,287✔
214
        const cell = row.getCell(index + 1);
239✔
215
        cell.note = {
239✔
216
          texts: [{ text: annotations[col.key] || "" }],
239!
217
          protection: { locked: "True", lockText: "True" },
239✔
218
        };
239✔
219
      }
239✔
220
    });
72✔
221
  }
72✔
222

223
  /**
224
   * Get the cells in row 2 of the worksheet.
225
   *
226
   * @note Follows the order of the defined columns.
227
   * @param ws The worksheet.
228
   * @param row The row number to get cells from.
229
   * @returns The cells in the specified row.
230
   */
231
  protected getRowCells(ws: ExcelJS.Worksheet, row = 2) {
72✔
232
    const r = ws.getRow(row);
65✔
233
    const cells = this._columns.map((col) => r.getCell(col.key)) || [];
65!
234

235
    return cells;
65✔
236
  }
65✔
237

238
  /**
239
   * Set the values for a specific row in the worksheet.
240
   *
241
   * @param ws The worksheet.
242
   * @param rowIndex The index of the row to update.
243
   * @param values The values to set.
244
   */
245
  protected setRowValues(
72✔
246
    ws: ExcelJS.Worksheet,
172✔
247
    rowIndex: number,
172✔
248
    values: Partial<Record<K, CellValue>>
172✔
249
  ) {
172✔
250
    const row = ws.getRow(rowIndex);
172✔
251
    this._columns.forEach((col, index) => {
172✔
252
      if (col.key in values) {
3,060✔
253
        row.getCell(index + 1).value = values[col.key] || null;
1,033✔
254
      }
1,033✔
255
    });
172✔
256
  }
172✔
257

258
  /**
259
   * Apply data validation to a specific column in the worksheet.
260
   *
261
   * @param ws The worksheet.
262
   * @param key The column key.
263
   * @param callback The callback to apply to each cell.
264
   * @param options The options for the cell range.
265
   */
266
  protected forEachCellInColumn(
72✔
267
    ws: ExcelJS.Worksheet,
422✔
268
    key: K,
422✔
269
    callback: (cell: ExcelJS.Cell, rowNumber: number) => void,
422✔
270
    options?: { startRow?: number; endRow?: number }
422✔
271
  ): void {
422✔
272
    const keyIsFound = this._columns?.find((c) => c.key === key);
422✔
273
    if (!keyIsFound) {
422!
NEW
274
      Logger.error(`SectionBase.tsx: Column with key "${key}" not found.`);
×
NEW
275
      return;
×
NEW
276
    }
×
277

278
    const { startRow = 2, endRow = 100 } = options ?? {};
422✔
279
    const column = ws.getColumn(key as string);
422✔
280

281
    Array.from({ length: endRow - startRow + 1 }, (_, i) => i + startRow).forEach((rowNum) => {
422✔
282
      const row = ws.getRow(rowNum);
41,778✔
283
      const cell = row.getCell(column.number);
41,778✔
284
      callback(cell, rowNum);
41,778✔
285
    });
422✔
286
  }
422✔
287

288
  /**
289
   * Applies a standard character limit validation to the specified cell.
290
   *
291
   * @param cell The cell to apply the ruleset to
292
   * @param limit The character limit to apply
293
   */
294
  // eslint-disable-next-line class-methods-use-this
295
  protected applyTextLengthValidation(cell: ExcelJS.Cell, limit: number): void {
72✔
296
    cell.dataValidation = {
4,557✔
297
      type: "textLength",
4,557✔
298
      operator: "lessThanOrEqual",
4,557✔
299
      showErrorMessage: true,
4,557✔
300
      error: ErrorCatalog.get("max", { max: limit }),
4,557✔
301
      allowBlank: false,
4,557✔
302
      formulae: [limit],
4,557✔
303
    };
4,557✔
304
  }
4,557✔
305
}
72✔
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