Project Reports & Websites

Steven J Zeil

Last modified: Jan 7, 2022
Contents:

Abstract

In this lesson we will continue looking at project documentation:

We will look at how to use our automated build tools to keep project reporting up-to-date.

1 Project Reports

1.1 Test Reports

We’ve already looked at JUnit, which can be used to generate test reports like this one or this one.

This is generated in Gradle via the java plugin. It produces an XML summary and a formatted reports in build\reports.

1.2 Static Code Analyzers

Many tools that we will cover later for analyzing code can produce useful (or at least, impressive) documentation as a side effect.

1.3 Configuration Reports

Configuration managers generate reports about the dependencies among the software components.

For example, this report comes from Gradle by adding

plugins {
     ⋮
   id 'project-report'
     ⋮
}

to the build.xml file

2 Project Websites

2.1 Case study: Constructing a project website with Gradle

Let’s look at the process of building a simple website that provides

  1. A brief project description
  2. Links to (and copies of)

We can consider adding other content in later lessons.

2.1.1 Build plan

After a build, we can construct a website in build/reports by

  1. Copying build/doc/ into build/website

    • This gets a copy of the Javadocs

    • Note that the JUnit and project configuration reports will already be in subdirectories of build/reports.

  2. Copying HTML, CSS, and Javascript files from src/main/html into reports.

    • Hand-crafted pages with the project description and links to the three reports.

2.1.2 The HTML File

home_html.listing
<!DOCTYPE html>
<html>
	<head>
	    <title>Project Reports</title>
	    <meta http-equiv="content-type" content="text/html; charset=utf-8" />
	    <link rel="stylesheet" type="text/css" href="projectReports.css"/>
	    <script src="projectReports.js"></script>
	</head>
	<body>
	    <h1>CodeCompCommon</h1>

        <p>This project provides a common set of interfaces for all CS350
           CodeComp project variants.</p>

        <p>It also provides a SharedPhrases class used to store token lists
           obtained via lexical analysis of the source code.</p>

        <h2>Information</h2>
		
        <p>Current version: 1.3</p>
        
	    <h2>Detailed Reports</h2>
		
	    <div class="reportLinks">
	   		<ul>
	   		    <li>General:
	   		        <ul>
	   			       <li><a href="../docs/javadoc/">JavaDoc</a></li>
	   			       <li><a href="../project/dependencies/root.html">Project Dependencies</a></li>
	   			    </ul></li>
	   			<li>Tests:
	   			    <ul>
	   			        <li><a href="../tests/test/">JUnit tests</a></li>
                    </ul>					
				</li>	    			
	   		</ul>
	   	</div>
	    
	</body>
</html>

2.1.3 Gradle setup

After a build, we can construct a website in build/reports by

  1. Copying build/doc/ into build/website
  2. Copying HTML, CSS, and Javascript files from src/main/html into reports.
    test.ignoreFailures=true                ➀
    check.dependsOn htmlDependencyReport    ➁
    
    task copyWebPages (type: Copy) {        ➄
        from 'src/main/html'
        into 'build/reports'
    }
    
    task copyDocs (type: Copy) {            ➃
        from 'build/docs'
        into 'build/reports/docs'
        dependsOn 'javadoc'
    }
    
    task buildSite (dependsOn: ['javadoc', 'check',             ➂
                          'copyWebPages', 'copyDocs']) {
        description 'Generate reports website for this project'
    }
    

2.2 Case study: Constructing a project website with Gradle and Jbake

Example

For a more elaborate, multi-page site, we can use a website generator to make it easy to enforce a common look-and-feel across pages.

Jbake injects content uses a series of templates

2.2.1 Gradle & Jbake

Gradle has a jbake plugin that adds a bake task to generate a website.

2.2.2 The website setup

src
|-- jbake
|-- |-- assets
|-- |-- |-- css
|-- |-- |-- '-- base.css
|-- |-- '-- js
|-- |--     '-- jquery-1.11.1.min.js
|-- |-- content
|-- |-- |-- dependencies.html
|-- |-- |-- home0.md
|-- |-- |-- javadoc.html
|-- |-- '-- junit.html
|-- |-- jbake.properties
|-- '-- templates
|--     |-- footer.ftl
|--     |-- header.ftl
|--     |-- index.ftl
|--     |-- menu.ftl
|--     |-- page.ftl
|--     '-- sitemap.ftl

The content directory` holds the actual information we want to disseminate.

The templates directory determines the “look and feel” of the website.

Templates:

page.ftl.listing
<#include "header.ftl">

	<div class="page-header">
		<h1><#escape x as x?xml>${content.title}</#escape></h1>
	</div>
	<p><em>${content.date?string("dd MMMM yyyy")}</em></p>
	<div class="center">
	    <#include "menu.ftl">
		<div class="rightpart">
			<p>${content.body}</p>
		</div>
	</div>
	<hr />

<#include "footer.ftl">
header.ftl.listing
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8"/>
    <title><#if (content.title)??><#escape x as x?xml>${content.title}</#escape><#else>codecentric</#if></title>
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta name="description" content="">
    <meta name="author" content="">
    <meta name="keywords" content="">
    <meta name="generator" content="JBake">

    <link href="<#if (content.rootpath)??>${content.rootpath}<#else></#if>css/base.css" rel="stylesheet">

    <!-- HTML5 shim, for IE6-8 support of HTML5 elements -->
    <!--[if lt IE 9]>
      <script src="<#if (content.rootpath)??>${content.rootpath}<#else></#if>js/html5shiv.min.js"></script>
    <![endif]-->

    <link rel="shortcut icon" href="<#if (content.rootpath)??>${content.rootpath}<#else></#if>favicon.ico">
  </head>
  <body>
    <div id="mainBody">
   
menu.ftl.listing
<div class="leftPart">
  <div class="menuBlock">
    <span class="menuBlockHeader"><a href="<#if
										   (content.rootpath)??>${content.rootpath}<#else></#if>home.html">Home</a></span>
  </div>
  <div class="menuBlock">
    <span class="menuBlockHeader">Project Info</span>
	<ul>
      <li><a href="<#if (content.rootpath)??>${content.rootpath}<#else></#if>javadoc.html">API (Javadoc)</a></li>
      <li><a href="<#if (content.rootpath)??>${content.rootpath}<#else></#if>dependencies.html">Dependencies</a></li>
	</ul>
  </div>
  <div class="menuBlock">
    <span class="menuBlockHeader">Testing</span>
	<ul>
      <li><a href="<#if
      (content.rootpath)??>${content.rootpath}<#else></#if>junit.html">Unit
		  Tests</a></li>
	  <!--
      <li><a href="<#if
		(content.rootpath)??>${content.rootpath}<#else></#if>jacoco.html">Coverage</a></li>
-->
	</ul>
  </div>
  <!--
  <div class="menuBlock">
 	<span class="menuBlockHeader">Analysis Reports</span>
	<ul>
      <li><a href="<#if (content.rootpath)??>${content.rootpath}<#else></#if>checkstyle.html">Checkstyle</a></li>
      <li><a href="<#if (content.rootpath)??>${content.rootpath}<#else></#if>dependencies.html">Dependencies</a></li>
	</ul>
  </div>
-->
</div>
footer.ftl.listing
    </div>
    
    <div id="footer">
      <div class="container">
        <p class="muted credit">&copy; 2019 Old Dominion University</p>
      </div>
    </div>
    
    <script src="<#if (content.rootpath)??>${content.rootpath}<#else></#if>js/jquery-1.11.1.min.js"></script>
    
  </body>
</html>

Content:

junit.html.listing
title=CodeCompCommon Unit Tests
type=page
status=published
~~~~~~
</p>
<iframe class="docFrame" src="tests/test/index.html"/>
<p>
home0.md.listing
title=Project Documentation: CodeCompCommon
date=2019-03-18
type=page
status=published
~~~~~~

2.2.3 Build Steps

To generate a website in build/jbake

  1. Copy the build/reports and build/docs to build/tmp/website
  2. Copy src/jbake to build/tmp/website
  3. Concatenate build/tmp/website/content/home0.md and the project README.md to form build/tmp/website/content/home.md
  4. Run Jbake on build/tmp/website/content/home.md to generate the full website in build/jbake

In build.gradle:

build.gradle.jbake.listing
test.ignoreFailures=true
check.dependsOn htmlDependencyReport

task reports (dependsOn: ['javadoc', 'check']) {
    description 'Generate all reports for this project'
}


task copyJDocs (type: Copy) {
    from 'build/docs'
    into 'build/tmp/website/assets'
    dependsOn 'javadoc'
}

task copyReports (type: Copy) {
    from 'build/reports'
    into 'build/tmp/website/assets'
    dependsOn 'reports'
}

task copyJbakeTemplates (type: Copy) {
    from 'src/jbake'
    into 'build/tmp/website'
}

task buildHomePage (dependsOn: copyJbakeTemplates) {
	 inputs.files ( 'build/tmp/website/content/home0.md', '../README.md')
	 outputs.file ( 'build/tmp/website/content/home.md' )
	 doLast  {
	     outputs.files.singleFile.withOutputStream { out ->
	         for ( file in inputs.files ) file.withInputStream {
		 	     out << it << '\n' }
	    }
	 }
}

jbake {
	 srcDirName = "build/tmp/website"
}

task setupWebsite (dependsOn: ['buildHomePage', 'copyReports', 'copyJDocs']){
}

bake.dependsOn "setupWebsite"

Example

3 Case Study: Enhanced reporting

Goal: add project website pages with reports from analysis tools and trend information (historical graphs) similar to those offered by Jenkins.

3.1 Generating Graphs

Highcharts is a Javascript package that can generate plots from data captured in CSV (Comma-Separated-Values) format.

Highcharts requires a bit of Javascript to inject a chart into an HTML div element.

3.2 Generating the Data

Where does the data for the plots come from?

3.3 Report Accumulator

Back to build.gradle to load another plugin:

build.gradle.listing
buildscript {
	repositories {
		⋮
        ivy { // for report-accumulator
            url 'https://www.cs.odu.edu/~zeil/ivyrepo'     ➀
        }
	}
	
	dependencies {
		⋮
	    classpath 'edu.odu.cs.zeil:report_accumulator:1.2'
		⋮
    }
}

⋮


// Reporting


import edu.odu.cs.zeil.report_accumulator.ReportStats     ➁


task collectStats (type: ReportStats, dependsOn: ['build','reports']) {  ➂
    description "Collect statistics from various reports & analysis tools"
    reportsURL = 'https://www.cs.odu.edu/~zeil/gitlab/' + project.name + '/reports'
}


task site (dependsOn: ['copyBake', 'copyJDocs', 'collectStats']){ ➃
    description "Build the project website (in build/reports)"
    group "reporting"
}

  1. The usual steps to include a plugin.
  2. Import the new task types created by the plugin
  3. A ReportStats task
    • scans the build/reports directory for reports from various tools,
    • extracts a new data point where it can,
    • downloads an existing CSV file for that report from the reportsURL
    • adds the new data point to the end of the CSV file
  4. A slight tweak to our earlier site target to make sure that the ReportStats task is performed

3.4 Forges

A software forge is a collection of web services for the support of collaborative software devlopment:


Forge Examples

Among the best known forges are