Skip to content

Commit 5fd1f0e

Browse files
committed
👔 Update report logic
1 parent 979ec19 commit 5fd1f0e

5 files changed

Lines changed: 577 additions & 455 deletions

File tree

‎src/report/csv.js‎

Lines changed: 86 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import Reporter from './reporter.js'
44
* CSV report generator that extends the base formatter class.
55
* Handles creation and formatting of CSV reports for GitHub Actions data.
66
*/
7-
export default class Csv extends Reporter {
7+
export default class CsvReporter extends Reporter {
88
/**
99
* Creates a new CSV report instance.
1010
* @param {string} path - The file path where the CSV will be saved
@@ -16,105 +16,113 @@ export default class Csv extends Reporter {
1616
}
1717

1818
/**
19-
* Saves data as a CSV file.
19+
* Saves workflow data as a CSV file.
20+
* Includes workflow details and optional columns based on configuration.
2021
* @returns {Promise<void>} A promise that resolves when the file is saved
2122
*/
2223
async save() {
23-
// Create headers based on configuration
24-
const headers = this.#createHeaders()
25-
26-
// Process rows according to the headers to ensure consistent column order
27-
const rows = this.data.map(workflow => {
28-
const row = []
29-
30-
// Add each column in the order defined by headers
31-
for (const header of headers) {
32-
if (header === 'runs-on' && workflow.runsOn) {
33-
// Special case for runsOn which has a different property name
34-
row.push(this.#formatValue(workflow.runsOn))
35-
} else {
36-
// For all other columns, use the header name as the property key
37-
row.push(this.#formatValue(workflow[header]))
24+
try {
25+
// Create headers based on configuration
26+
const headers = this.createHeaders()
27+
28+
// Process rows according to the headers to ensure consistent column order
29+
const rows = this.data.map(workflow => {
30+
const row = []
31+
32+
// Add each column in the order defined by headers
33+
for (const header of headers) {
34+
if (header === 'runs-on' && workflow.runsOn) {
35+
// Special case for runsOn which has a different property name
36+
row.push(this.formatValue(workflow.runsOn))
37+
} else {
38+
// For all other columns, use the header name as the property key
39+
row.push(this.formatValue(workflow[header]))
40+
}
3841
}
39-
}
4042

41-
return row
42-
})
43+
return row
44+
})
4345

44-
// Format each row, properly escaping values
45-
const csvRows = rows.map(row =>
46-
row
47-
.map(value => {
48-
if (value === null || value === undefined) {
49-
return ''
50-
}
46+
// Format each row, properly escaping values
47+
const csvRows = rows.map(row =>
48+
row
49+
.map(value => {
50+
if (value === null || value === undefined) {
51+
return ''
52+
}
5153

52-
const strValue = String(value)
53-
if (strValue.includes(',') || strValue.includes('"') || strValue.includes('\n')) {
54-
return `"${strValue.replace(/"/g, '""')}"`
55-
}
54+
const strValue = String(value)
55+
if (strValue.includes(',') || strValue.includes('"') || strValue.includes('\n')) {
56+
return `"${strValue.replace(/"/g, '""')}"`
57+
}
5658

57-
return strValue
58-
})
59-
.join(','),
60-
)
59+
return strValue
60+
})
61+
.join(','),
62+
)
6163

62-
// Combine headers and data
63-
const csvContent = [headers.join(','), ...csvRows].join('\n')
64+
// Combine headers and data
65+
const csvContent = [headers.join(','), ...csvRows].join('\n')
6466

65-
// Write the CSV data to the specified file path
66-
await this.saveFile(this.path, csvContent)
67+
// Write the CSV data to the specified file path
68+
await this.saveFile(this.path, csvContent)
69+
} catch (error) {
70+
throw new Error(`Failed to save CSV report: ${error.message}`)
71+
}
6772
}
6873

6974
/**
70-
* Saves unique "uses" values as a separate CSV file.
75+
* Saves unique "uses" values as a separate file.
7176
* @returns {Promise<void>} A promise that resolves when the unique uses file is saved
7277
*/
7378
async saveUnique() {
74-
// Create a unique file name by inserting '-unique' before the extension
75-
const uniquePath = this.createUniquePath('csv')
76-
77-
// Extract unique "uses" entries from the data
78-
const allUniqueUses = this.extractUniqueUses()
79-
const uniqueUses = new Set()
80-
81-
// Filter out local actions starting with './'
82-
for (const use of allUniqueUses) {
83-
if (!use.startsWith('./')) {
84-
uniqueUses.add(use)
79+
try {
80+
// Create a unique file name using the base class method
81+
const uniquePath = this.createUniquePath('csv')
82+
83+
// Extract unique "uses" entries from the data
84+
const allUniqueUses = this.extractUniqueUses()
85+
const uniqueUses = new Set()
86+
87+
// Filter out local actions starting with './'
88+
for (const use of allUniqueUses) {
89+
if (!use.startsWith('./')) {
90+
uniqueUses.add(use)
91+
}
8592
}
86-
}
8793

88-
// Create headers for the unique CSV
89-
const headers = ['uses']
94+
// Create headers for the unique CSV
95+
const headers = ['uses']
9096

91-
// Format unique uses into CSV rows
92-
const uniqueRows = Array.from(uniqueUses).map(use => {
93-
const formattedValue = this.#formatValue(use)
97+
// Format unique uses into CSV rows
98+
const uniqueRows = Array.from(uniqueUses).map(use => {
99+
const formattedValue = this.formatValue(use)
94100

95-
// Properly escape values with quotes if they contain commas
96-
if (typeof formattedValue === 'string' && formattedValue.includes(',')) {
97-
return [`"${formattedValue.replace(/"/g, '""')}"`]
98-
}
99-
return [formattedValue]
100-
})
101+
// Properly escape values with quotes if they contain commas
102+
if (typeof formattedValue === 'string' && formattedValue.includes(',')) {
103+
return [`"${formattedValue.replace(/"/g, '""')}"`]
104+
}
105+
return [formattedValue]
106+
})
101107

102-
// Sort unique uses alphabetically
103-
uniqueRows.sort((a, b) => a[0].localeCompare(b[0]))
108+
// Sort unique uses alphabetically
109+
uniqueRows.sort((a, b) => a[0].localeCompare(b[0]))
104110

105-
// Combine headers with properly formatted rows
106-
const csvContent = [headers.join(','), ...uniqueRows.map(row => row.join(','))].join('\n')
111+
// Combine headers with properly formatted rows
112+
const csvContent = [headers.join(','), ...uniqueRows.map(row => row.join(','))].join('\n')
107113

108-
// Write the unique CSV data to the file
109-
await this.saveFile(uniquePath, csvContent)
114+
// Write the unique CSV data to the file
115+
await this.saveFile(uniquePath, csvContent)
116+
} catch (error) {
117+
throw new Error(`Failed to save unique uses CSV report: ${error.message}`)
118+
}
110119
}
111120

112121
/**
113-
* Creates consistent headers for CSV reports based on enabled options.
122+
* Creates headers for CSV reports based on enabled options.
114123
* @returns {string[]} Array of header columns
115-
* @private
116124
*/
117-
#createHeaders() {
125+
createHeaders() {
118126
// Define the table header with all columns
119127
const headers = ['owner', 'repo', 'name', 'workflow', 'state', 'created_at', 'updated_at', 'last_run_at']
120128

@@ -131,12 +139,10 @@ export default class Csv extends Reporter {
131139

132140
/**
133141
* Formats a value for CSV output, handling objects and other types appropriately.
134-
* Special formatting is applied to match the expected output format in reports.
135142
* @param {*} value - The value to format
136143
* @returns {string|number|boolean} - The formatted value
137-
* @private
138144
*/
139-
#formatValue(value) {
145+
formatValue(value) {
140146
if (value === null || value === undefined) {
141147
return ''
142148
}
@@ -159,12 +165,11 @@ export default class Csv extends Reporter {
159165
return ''
160166
}
161167

162-
// Special handling for listeners, permissions, and other complex objects
163-
// Format as simplified key-value pairs without excessive quoting to match sample output
168+
// Special handling for complex objects
164169
if (typeof value === 'object' && !Array.isArray(value)) {
165170
try {
166171
// Convert the object to a string without excessive quotes
167-
return this.#formatObjectForCsv(value)
172+
return this.formatObjectForCsv(value)
168173
} catch (error) {
169174
// Fallback to standard JSON string if custom formatting fails
170175
return JSON.stringify(value)
@@ -176,12 +181,11 @@ export default class Csv extends Reporter {
176181
}
177182

178183
/**
179-
* Formats an object for CSV output with custom formatting that matches the sample output.
184+
* Formats an object for CSV output with custom formatting.
180185
* @param {Object} obj - The object to format
181186
* @returns {string} - Formatted string representation
182-
* @private
183187
*/
184-
#formatObjectForCsv(obj) {
188+
formatObjectForCsv(obj) {
185189
// For workflow_call objects, use special formatting
186190
if (obj.workflow_call) {
187191
return 'workflow_call'
@@ -197,7 +201,7 @@ export default class Csv extends Reporter {
197201
result = objEntries
198202
.map(([key, value]) => {
199203
if (typeof value === 'object' && value !== null) {
200-
return `${key}: ${this.#formatObjectForCsv(value)}`
204+
return `${key}: ${this.formatObjectForCsv(value)}`
201205
}
202206
return `${key}: ${value}`
203207
})

‎src/report/json.js‎

Lines changed: 46 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import Reporter from './reporter.js'
44
* JSON report generator that extends the base formatter class.
55
* Handles creation and formatting of JSON reports for GitHub Actions data.
66
*/
7-
export default class Json extends Reporter {
7+
export default class JsonReporter extends Reporter {
88
/**
99
* Creates a new JSON report instance.
1010
* @param {string} path - The file path where the JSON will be saved
@@ -16,51 +16,65 @@ export default class Json extends Reporter {
1616
}
1717

1818
/**
19-
* Saves data as a JSON file.
19+
* Saves workflow data as a JSON file.
20+
* Includes workflow details and optional columns based on configuration.
2021
* @returns {Promise<void>} A promise that resolves when the file is saved
2122
*/
2223
async save() {
23-
// Convert data to JSON format with proper handling of Sets and Maps
24-
const jsonData = JSON.stringify(
25-
this.data,
26-
(_, value) => {
27-
if (value instanceof Set) {
28-
return Array.from(value)
29-
} else if (value instanceof Map) {
30-
return Object.fromEntries(value)
31-
}
32-
return value
33-
},
34-
2,
35-
)
24+
try {
25+
// Convert data to JSON format with proper handling of Sets and Maps
26+
const jsonData = JSON.stringify(
27+
this.data,
28+
(_, value) => {
29+
if (value instanceof Set) {
30+
return Array.from(value)
31+
} else if (value instanceof Map) {
32+
return Object.fromEntries(value)
33+
}
34+
return value
35+
},
36+
2,
37+
)
3638

37-
// Write the JSON data to the specified file path
38-
await this.saveFile(this.path, jsonData)
39+
// Write the JSON data to the specified file path
40+
await this.saveFile(this.path, jsonData)
41+
} catch (error) {
42+
// Provide a more specific error message for JSON serialization issues
43+
if (error.message.includes('circular')) {
44+
throw new Error(`Unable to serialize data to JSON: Circular reference detected`)
45+
} else {
46+
throw new Error(`Failed to save JSON report: ${error.message}`)
47+
}
48+
}
3949
}
4050

4151
/**
42-
* Saves unique "uses" values as a separate JSON file.
52+
* Saves unique "uses" values as a separate file.
4353
* @returns {Promise<void>} A promise that resolves when the unique uses file is saved
4454
*/
4555
async saveUnique() {
46-
// Create a unique file name using the base class method
47-
const uniquePath = this.createUniquePath('json')
56+
try {
57+
// Create a unique file name using the base class method
58+
const uniquePath = this.createUniquePath('json')
4859

49-
// Extract unique "uses" entries from the data using the base class method
50-
const allUniqueUses = this.extractUniqueUses()
51-
const uniqueUses = new Set()
60+
// Extract unique "uses" entries from the data using the base class method
61+
const allUniqueUses = this.extractUniqueUses()
62+
const uniqueUses = new Set()
5263

53-
// Filter out local actions starting with './'
54-
for (const use of allUniqueUses) {
55-
if (!use.startsWith('./')) {
56-
uniqueUses.add(use)
64+
// Filter out local actions starting with './'
65+
for (const use of allUniqueUses) {
66+
if (!use.startsWith('./')) {
67+
uniqueUses.add(use)
68+
}
5769
}
58-
}
5970

60-
// Convert the Set to an array and sort it
61-
const jsonUniqueData = Array.from(uniqueUses).sort()
71+
// Convert the Set to an array and sort it
72+
const jsonUniqueData = Array.from(uniqueUses).sort()
6273

63-
// Write the JSON data to the specified file path
64-
await this.saveFile(uniquePath, JSON.stringify(jsonUniqueData, null, 2))
74+
// Write the JSON data to the specified file path
75+
await this.saveFile(uniquePath, JSON.stringify(jsonUniqueData, null, 2))
76+
} catch (error) {
77+
throw new Error(`Failed to save unique uses report: ${error.message}`)
78+
}
6579
}
6680
}

0 commit comments

Comments
 (0)