Skip to content

Commit

Permalink
Merge pull request #176 from OpenPathfinder/feat/generate-reports
Browse files Browse the repository at this point in the history
  • Loading branch information
UlisesGascon authored Jan 3, 2025
2 parents a2798f8 + 2825461 commit 42f2e7b
Show file tree
Hide file tree
Showing 14 changed files with 569 additions and 24 deletions.
14 changes: 7 additions & 7 deletions __fixtures__/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ const sampleGithubOrg = {
issues_url: 'https://api.github.com/orgs/github/issues',
members_url: 'https://api.github.com/orgs/github/members{/member}',
public_members_url: 'https://api.github.com/orgs/github/public_members{/member}',
avatar_url: 'https://github.com/images/error/octocat_happy.gif',
avatar_url: 'https://avatars.githubusercontent.com/u/9919?v=4',
description: 'A great organization',
name: 'github',
company: 'GitHub',
Expand Down Expand Up @@ -76,7 +76,7 @@ const sampleGithubListOrgRepos = [
login: 'octocat',
id: 1,
node_id: 'MDQ6VXNlcjE=',
avatar_url: 'https://github.com/images/error/octocat_happy.gif',
avatar_url: 'https://avatars.githubusercontent.com/u/9919?v=4',
gravatar_id: '',
url: 'https://api.github.com/users/octocat',
html_url: 'https://github.com/octocat',
Expand Down Expand Up @@ -197,7 +197,7 @@ const sampleGithubRepository = {
login: 'octocat',
id: 1,
node_id: 'MDQ6VXNlcjE=',
avatar_url: 'https://github.com/images/error/octocat_happy.gif',
avatar_url: 'https://avatars.githubusercontent.com/u/9919?v=4',
gravatar_id: '',
url: 'https://api.github.com/users/octocat',
html_url: 'https://github.com/octocat',
Expand Down Expand Up @@ -303,7 +303,7 @@ const sampleGithubRepository = {
login: 'octocat',
id: 1,
node_id: 'MDQ6VXNlcjE=',
avatar_url: 'https://github.com/images/error/octocat_happy.gif',
avatar_url: 'https://avatars.githubusercontent.com/u/9919?v=4',
gravatar_id: '',
url: 'https://api.github.com/users/octocat',
html_url: 'https://github.com/octocat',
Expand Down Expand Up @@ -435,7 +435,7 @@ const sampleGithubRepository = {
login: 'octocat',
id: 1,
node_id: 'MDQ6VXNlcjE=',
avatar_url: 'https://github.com/images/error/octocat_happy.gif',
avatar_url: 'https://avatars.githubusercontent.com/u/9919?v=4',
gravatar_id: '',
url: 'https://api.github.com/users/octocat',
html_url: 'https://github.com/octocat',
Expand All @@ -460,7 +460,7 @@ const sampleGithubRepository = {
login: 'octocat',
id: 1,
node_id: 'MDQ6VXNlcjE=',
avatar_url: 'https://github.com/images/error/octocat_happy.gif',
avatar_url: 'https://avatars.githubusercontent.com/u/9919?v=4',
gravatar_id: '',
url: 'https://api.github.com/users/octocat',
html_url: 'https://github.com/octocat',
Expand Down Expand Up @@ -582,7 +582,7 @@ const sampleGithubRepository = {
login: 'octocat',
id: 1,
node_id: 'MDQ6VXNlcjE=',
avatar_url: 'https://github.com/images/error/octocat_happy.gif',
avatar_url: 'https://avatars.githubusercontent.com/u/9919?v=4',
gravatar_id: '',
url: 'https://api.github.com/users/octocat',
html_url: 'https://github.com/octocat',
Expand Down
2 changes: 1 addition & 1 deletion __tests__/__snapshots__/providers.test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
exports[`GitHub Provider mappers Should map organization data correctly 1`] = `
{
"advanced_security_enabled_for_new_repositories": false,
"avatar_url": "https://github.com/images/error/octocat_happy.gif",
"avatar_url": "https://avatars.githubusercontent.com/u/9919?v=4",
"blog": "https://github.com/blog",
"collaborators": 8,
"company": "GitHub",
Expand Down
5 changes: 5 additions & 0 deletions __tests__/cli/__snapshots__/workflows.test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -27,5 +27,10 @@ exports[`list - Non-Interactive Mode Should provide a list of available workflow
"name": "show-reports",
"workflow": [Function],
},
{
"description": "Generate the reports for the stored data.",
"name": "generate-reports",
"workflow": [Function],
},
]
`;
77 changes: 70 additions & 7 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
"db:seed": "knex seed:run"
},
"keywords": [
"security",
"security",
"CLI"
],
"repository": {
Expand All @@ -45,6 +45,7 @@
"commander": "12.1.0",
"date-fns": "4.1.0",
"debug": "4.3.7",
"ejs": "3.1.10",
"finalhandler": "1.3.1",
"inquirer": "12.1.0",
"knex": "3.1.0",
Expand Down
5 changes: 5 additions & 0 deletions src/cli/workflows.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
const inquirer = require('inquirer').default
const debug = require('debug')('cli:workflows')
const { updateGithubOrgs, upsertGithubRepositories, runAllTheComplianceChecks, upsertOSSFScorecardAnalysis } = require('../workflows')
const { generateReports } = require('../reports')
const { logger } = require('../utils')

const commandList = [{
Expand All @@ -23,6 +24,10 @@ const commandList = [{
name: 'show-reports',
description: 'Starts a http server that shows all the files and folders in the output directory.',
workflow: require('../httpServer')
}, {
name: 'generate-reports',
description: 'Generate the reports for the stored data.',
workflow: generateReports
}]

const validCommandNames = commandList.map(({ name }) => name)
Expand Down
Binary file added src/reports/assets/favicon.ico
Binary file not shown.
108 changes: 108 additions & 0 deletions src/reports/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
const { logger } = require('../utils')
const ejs = require('ejs')
const { mkdir, readdir, copyFile, readFile, writeFile } = require('node:fs').promises
const { join } = require('path')
const { initializeStore } = require('../store')

const indexTemplatePath = join(process.cwd(), 'src', 'reports', 'templates', 'index.html.ejs')
const projectTemplatePath = join(process.cwd(), 'src', 'reports', 'templates', 'project.html.ejs')
const assetsFolder = join(process.cwd(), 'src', 'reports', 'assets')
const destinationFolder = join(process.cwd(), 'output')
const copyFolder = async (from, to) => {
try {
// Ensure the target directory exists
await mkdir(to, { recursive: true })

// Read the contents of the source directory
const entries = await readdir(from, { withFileTypes: true })

// Process each entry
await Promise.all(
entries.map(async (entry) => {
const source = join(from, entry.name)
const dest = join(to, entry.name)

if (entry.isDirectory()) {
// Recursively copy subdirectories
await copyFolder(source, dest)
} else {
// Copy files
await copyFile(source, dest)
}
})
)
} catch (error) {
logger.warn(`Error copying folder from "${from}" to "${to}":`)
throw error
}
}

const generateReports = async (knex) => {
logger.info('Generating reports')
const { getAllProjects, getAllChecklists, getAllComplianceChecks, getAllAlerts, getAllResults, getAllTasks, getAllGithubOrganizationsByProjectsId, getAllGithubRepositories, getAllOSSFResults } = initializeStore(knex)
// @TODO: Run the queries in parallel
const projects = await getAllProjects()
const checklists = await getAllChecklists()
const checks = await getAllComplianceChecks()
const alerts = await getAllAlerts()
const results = await getAllResults()
const tasks = await getAllTasks()
const ossfScorecardResults = await getAllOSSFResults()
const githubRepos = await getAllGithubRepositories()

// @TODO: Read the files in parallel
const indexTemplate = await readFile(indexTemplatePath, 'utf8')
const projectTemplate = await readFile(projectTemplatePath, 'utf8')
await mkdir(join(destinationFolder, 'projects'), { recursive: true })

// Collecting data from the database
const indexData = {
projects,
checklists,
checks
}

const projectsData = {}

for (const project of projects) {
const githubOrgs = await getAllGithubOrganizationsByProjectsId([project.id])
const githubOrgsIds = githubOrgs.map(org => org.id)
const githubReposInScope = githubRepos.filter(repo => githubOrgsIds.includes(repo.github_organization_id))
const githubReposInScopeIds = githubReposInScope.map(repo => repo.id)

projectsData[project.name] = {
project,
checks,
alerts: alerts.filter(alert => alert.project_id === project.id),
results: results.filter(result => result.project_id === project.id),
tasks: tasks.filter(task => task.project_id === project.id),
githubOrgs,
githubRepos: githubReposInScope,
ossfScorecardResults: ossfScorecardResults.filter(ossfResult => githubReposInScopeIds.includes(ossfResult.github_repository_id))
}

// Populate the project HTML template
const projectHtml = ejs.render(projectTemplate, projectsData[project.name])
const projectFilename = join(destinationFolder, 'projects', `${project.name}.html`)
await writeFile(projectFilename, projectHtml)
}

// @TODO: Validate against JSON Schemas

// @TODO: Save the files in parallel
await writeFile('output/index_data.json', JSON.stringify(indexData, null, 2))
await writeFile('output/projects/projects_data.json', JSON.stringify(projectsData, null, 2))

// copy assets folder
await copyFolder(assetsFolder, join(destinationFolder, 'assets'))

// Populate the index HTML template
const indexHtml = ejs.render(indexTemplate, indexData)

// Save the index HTML file
await writeFile('output/index.html', indexHtml)
}

module.exports = {
generateReports
}
Loading

0 comments on commit 42f2e7b

Please sign in to comment.