This repository was archived by the owner on Aug 7, 2026. It is now read-only.
-
-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathcsv.js
More file actions
222 lines (188 loc) · 6.87 KB
/
Copy pathcsv.js
File metadata and controls
222 lines (188 loc) · 6.87 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
import Reporter from './reporter.js'
/**
* CSV report generator that extends the base formatter class.
* Handles creation and formatting of CSV reports for GitHub Actions data.
*/
export default class CsvReporter extends Reporter {
/**
* Creates a new CSV report instance.
* @param {string} path - The file path where the CSV will be saved
* @param {Object} options - Configuration options for the report
* @param {Array} data - The data to be exported as CSV
*/
constructor(path, options, data) {
super(path, options, data)
}
/**
* Saves workflow data as a CSV file.
* Includes workflow details and optional columns based on configuration.
* @returns {Promise<void>} A promise that resolves when the file is saved
*/
async save() {
try {
// Create headers based on configuration
const headers = this.createHeaders()
// Process rows according to the headers to ensure consistent column order
const rows = this.data.map(workflow => {
const row = []
// Add each column in the order defined by headers
for (const header of headers) {
if (header === 'runs-on' && workflow.runsOn) {
// Special case for runsOn which has a different property name
row.push(this.formatValue(workflow.runsOn))
} else {
// For all other columns, use the header name as the property key
row.push(this.formatValue(workflow[header]))
}
}
return row
})
// Format each row, properly escaping values
const csvRows = rows.map(row =>
row
.map(value => {
if (value === null || value === undefined) {
return ''
}
const strValue = String(value)
if (strValue.includes(',') || strValue.includes('"') || strValue.includes('\n')) {
return `"${strValue.replace(/"/g, '""')}"`
}
return strValue
})
.join(','),
)
// Combine headers and data
const csvContent = [headers.join(','), ...csvRows].join('\n')
// Write the CSV data to the specified file path
await this.saveFile(this.path, csvContent)
} catch (error) {
throw new Error(`Failed to save CSV report: ${error.message}`)
}
}
/**
* Saves unique "uses" values as a separate file.
* @returns {Promise<void>} A promise that resolves when the unique uses file is saved
*/
async saveUnique() {
try {
// Create a unique file name using the base class method
const uniquePath = this.createUniquePath('csv')
// Extract unique "uses" entries from the data
const allUniqueUses = this.extractUniqueUses()
const uniqueUses = new Set()
// Filter out local actions starting with './'
for (const use of allUniqueUses) {
if (!use.startsWith('./')) {
uniqueUses.add(use)
}
}
// Create headers for the unique CSV
const headers = ['uses']
// Format unique uses into CSV rows
const uniqueRows = Array.from(uniqueUses).map(use => {
const formattedValue = this.formatValue(use)
// Properly escape values with quotes if they contain commas
if (typeof formattedValue === 'string' && formattedValue.includes(',')) {
return [`"${formattedValue.replace(/"/g, '""')}"`]
}
return [formattedValue]
})
// Sort unique uses alphabetically
uniqueRows.sort((a, b) => a[0].localeCompare(b[0]))
// Combine headers with properly formatted rows
const csvContent = [headers.join(','), ...uniqueRows.map(row => row.join(','))].join('\n')
// Write the unique CSV data to the file
await this.saveFile(uniquePath, csvContent)
} catch (error) {
throw new Error(`Failed to save unique uses CSV report: ${error.message}`)
}
}
/**
* Creates headers for CSV reports based on enabled options.
* @returns {string[]} Array of header columns
*/
createHeaders() {
// Define the table header with all columns
const headers = ['owner', 'repo', 'name', 'workflow', 'state', 'created_at', 'updated_at', 'last_run_at']
// Add optional columns based on options
if (this.options.listeners) headers.push('listeners')
if (this.options.permissions) headers.push('permissions')
if (this.options.runsOn) headers.push('runs-on')
if (this.options.secrets) headers.push('secrets')
if (this.options.vars) headers.push('vars')
if (this.options.uses) headers.push('uses')
return headers
}
/**
* Formats a value for CSV output, handling objects and other types appropriately.
* @param {*} value - The value to format
* @returns {string|number|boolean} - The formatted value
*/
formatValue(value) {
if (value === null || value === undefined) {
return ''
}
// Handle dates - ensure they have the .000Z format
if (value instanceof Date) {
return value.toISOString()
}
// Handle Sets - convert to comma-separated string
if (value instanceof Set) {
if (value.size === 0) return ''
return Array.from(value).join(', ')
}
// Handle objects
if (typeof value === 'object') {
// Skip empty objects
if (Object.keys(value).length === 0) {
return ''
}
// Special handling for complex objects
if (typeof value === 'object' && !Array.isArray(value)) {
try {
// Convert the object to a string without excessive quotes
return this.formatObjectForCsv(value)
} catch (error) {
// Fallback to standard JSON string if custom formatting fails
return JSON.stringify(value)
}
}
}
return value
}
/**
* Formats an object for CSV output with custom formatting.
* @param {Object} obj - The object to format
* @returns {string} - Formatted string representation
*/
formatObjectForCsv(obj) {
// For workflow_call objects, use special formatting
if (obj.workflow_call) {
return 'workflow_call'
}
// For objects with specific structures, provide custom formatting
if (obj.inputs || obj.secrets) {
let result = ''
// Format key names without quotes and stringify values
const objEntries = Object.entries(obj)
if (objEntries.length > 0) {
result = objEntries
.map(([key, value]) => {
if (typeof value === 'object' && value !== null) {
return `${key}: ${this.formatObjectForCsv(value)}`
}
return `${key}: ${value}`
})
.join(', ')
// Wrap in brackets if it's a complex object
if (objEntries.length > 1) {
result = `{${result}}`
}
}
return result
}
// For simple objects, convert to simplified string without excessive quotes
return JSON.stringify(obj).replace(/"/g, '').replace(/:/g, ': ').replace(/,/g, ', ')
}
}