blob: 1d9c3170660b4b6f060c33c5ec059508d52a8265 [file] [log] [blame]
package org.apache.maven.plugins.jira;
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import java.io.File;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.ResourceBundle;
import org.apache.commons.lang.StringUtils;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.plugins.changes.AbstractChangesReport;
import org.apache.maven.plugins.changes.ProjectUtils;
import org.apache.maven.plugins.issues.Issue;
import org.apache.maven.plugins.issues.IssueUtils;
import org.apache.maven.plugins.issues.IssuesReportGenerator;
import org.apache.maven.plugins.issues.IssuesReportHelper;
import org.apache.maven.reporting.MavenReportException;
import org.apache.maven.settings.Settings;
/**
* Goal which downloads issues from the Issue Tracking System and generates a report.
*
* @author <a href="mailto:jruiz@exist.com">Johnny R. Ruiz III</a>
* @version $Id$
*/
@Mojo( name = "jira-report", threadSafe = true )
public class JiraMojo
extends AbstractChangesReport
{
/**
* Valid JIRA columns.
*/
private static final Map<String, Integer> JIRA_COLUMNS = new HashMap<>( 16 );
static
{
JIRA_COLUMNS.put( "Assignee", IssuesReportHelper.COLUMN_ASSIGNEE );
JIRA_COLUMNS.put( "Component", IssuesReportHelper.COLUMN_COMPONENT );
JIRA_COLUMNS.put( "Created", IssuesReportHelper.COLUMN_CREATED );
JIRA_COLUMNS.put( "Fix Version", IssuesReportHelper.COLUMN_FIX_VERSION );
JIRA_COLUMNS.put( "Id", IssuesReportHelper.COLUMN_ID );
JIRA_COLUMNS.put( "Key", IssuesReportHelper.COLUMN_KEY );
JIRA_COLUMNS.put( "Priority", IssuesReportHelper.COLUMN_PRIORITY );
JIRA_COLUMNS.put( "Reporter", IssuesReportHelper.COLUMN_REPORTER );
JIRA_COLUMNS.put( "Resolution", IssuesReportHelper.COLUMN_RESOLUTION );
JIRA_COLUMNS.put( "Status", IssuesReportHelper.COLUMN_STATUS );
JIRA_COLUMNS.put( "Summary", IssuesReportHelper.COLUMN_SUMMARY );
JIRA_COLUMNS.put( "Type", IssuesReportHelper.COLUMN_TYPE );
JIRA_COLUMNS.put( "Updated", IssuesReportHelper.COLUMN_UPDATED );
JIRA_COLUMNS.put( "Version", IssuesReportHelper.COLUMN_VERSION );
}
/**
* Sets the names of the columns that you want in the report. The columns will appear in the report in the same
* order as you specify them here. Multiple values can be separated by commas.
* <p>
* Valid columns are: <code>Assignee</code>, <code>Component</code>, <code>Created</code>, <code>Fix Version</code>,
* <code>Id</code>, <code>Key</code>, <code>Priority</code>, <code>Reporter</code>, <code>Resolution</code>,
* <code>Status</code>, <code>Summary</code>, <code>Type</code>, <code>Updated</code> and <code>Version</code>.
* </p>
*
* @since 2.0
*/
@Parameter( defaultValue = "Key,Summary,Status,Resolution,Assignee" )
private String columnNames;
/**
* Use the JIRA query language instead of the JIRA query based on HTTP parameters. From JIRA 5.1 and up only JQL is
* supported. JIRA 4.4 supports both JQL and URL parameter based queries. From 5.1.1 this is obsolete, since REST
* queries only use JQL.
*
* @since 2.8
*/
@Parameter( property = "changes.useJql", defaultValue = "false" )
private boolean useJql;
/**
* Since JIRA 5.1.1, it is no longer possible to construct a URL that downloads RSS. Meanwhile JIRA added a REST API
* in 4.2. By default, this plugin uses the REST API if available. Setting this parameter to true forces it to
* attempt to use RSS.
*
* @since 2.9
*/
@Parameter( defaultValue = "false" )
private boolean forceRss;
/**
* Sets the component(s) that you want to limit your report to include. Multiple values can be separated by commas
* (such as 10011,10012). If this is set to empty - that means all components will be included.
*/
@Parameter
private String component;
/**
* Defines the filter parameters to restrict which issues are retrieved from JIRA. The filter parameter uses the
* same format of url parameters that is used in a JIRA search.
*/
@Parameter()
private String filter;
/**
* Sets the fix version id(s) that you want to limit your report to include. These are JIRA's internal version ids,
* <b>NOT</b> the human readable display ones. Multiple fix versions can be separated by commas. If this is set to
* empty - that means all fix versions will be included.
*
* @since 2.0
*/
@Parameter
private String fixVersionIds;
/**
* The pattern used by dates in the JIRA XML-file. This is used to parse the Created and Updated fields.
*
* @since 2.4
*/
@Parameter( defaultValue = "EEE, d MMM yyyy HH:mm:ss Z" )
private String jiraDatePattern;
/**
* Defines the JIRA password for authentication into a private JIRA installation.
*/
@Parameter
private String jiraPassword;
/**
* Defines the JIRA username for authentication into a private JIRA installation.
*/
@Parameter
private String jiraUser;
/**
* Path to the JIRA XML file, which will be parsed.
*/
@Parameter( defaultValue = "${project.build.directory}/jira-results.xml", required = true, readonly = true )
private File jiraXmlPath;
/**
* Maximum number of entries to be fetched from JIRA.
*/
@Parameter( defaultValue = "100" )
private int maxEntries;
/**
* If you only want to show issues for the current version in the report. The current version being used is
* <code>${project.version}</code> minus any "-SNAPSHOT" suffix.
*
* @since 2.0
*/
@Parameter( defaultValue = "false" )
private boolean onlyCurrentVersion;
/**
* Sets the priority(s) that you want to limit your report to include. Valid statuses are <code>Blocker</code>,
* <code>Critical</code>, <code>Major</code>, <code>Minor</code> and <code>Trivial</code>. Multiple values can be
* separated by commas. If this is set to empty - that means all priorities will be included.
*/
@Parameter
private String priorityIds;
/**
* Sets the resolution(s) that you want to fetch from JIRA. Valid resolutions are: <code>Unresolved</code>,
* <code>Fixed</code>, <code>Won't Fix</code>, <code>Duplicate</code>, <code>Incomplete</code> and
* <code>Cannot Reproduce</code>. Multiple values can be separated by commas.
* <p>
* <b>Note:</b> In versions 2.0-beta-3 and earlier this parameter had no default value.
* </p>
*/
@Parameter( defaultValue = "Fixed" )
private String resolutionIds;
/**
* Settings XML configuration.
*/
@Parameter( defaultValue = "${settings}", readonly = true, required = true )
private Settings settings;
/**
* If set to <code>true</code>, then the JIRA report will not be generated.
*
* @since 2.8
*/
@Parameter( property = "changes.jira.skip", defaultValue = "false" )
private boolean skip;
/**
* Sets the column names that you want to sort the report by. Add <code>DESC</code> following the column name to
* specify <i>descending</i> sequence. For example <code>Fix Version DESC, Type</code> sorts first by the Fix
* Version in descending order and then by Type in ascending order. By default sorting is done in ascending order,
* but is possible to specify <code>ASC</code> for consistency. The previous example would then become
* <code>Fix Version DESC, Type ASC</code>.
* <p>
* Valid columns are: <code>Assignee</code>, <code>Component</code>, <code>Created</code>, <code>Fix Version</code>,
* <code>Id</code>, <code>Key</code>, <code>Priority</code>, <code>Reporter</code>, <code>Resolution</code>,
* <code>Status</code>, <code>Summary</code>, <code>Type</code>, <code>Updated</code> and <code>Version</code>.
* </p>
* <p>
* <strong>Note:</strong> If you are using JIRA 4 you need to put your sort column names in the reverse order. The
* handling of this changed between JIRA 3 and JIRA 4. The current default value is suitable for JIRA 3. This may
* change in the future, so please configure your sort column names in an order that works for your own JIRA
* version. If you use JQL, by setting the <code>useJql</code> parameter to <code>true</code>, then the order of the
* fields are in normal order again. Starting with JIRA 5.1 you have to use JQL.
* </p>
*
* @since 2.0
*/
@Parameter( defaultValue = "Priority DESC, Created DESC" )
private String sortColumnNames;
/**
* Sets the status(es) that you want to fetch from JIRA. Valid statuses are: <code>Open</code>,
* <code>In Progress</code>, <code>Reopened</code>, <code>Resolved</code> and <code>Closed</code>. Multiple values
* can be separated by commas.
* <p>
* If your installation of JIRA uses custom status IDs, you can reference them here by their numeric values. You can
* obtain them on the Statuses page (in 4.0.2 it's under Administration > Issue Settings > Statuses) - just hover
* over the Edit link for the status you want and you'll see something like &lt;your JIRA
* URL&gt;/secure/admin/EditStatus!default.jspa?id=12345; in this case the value is 12345.
* </p>
* <p>
* <b>Note:</b> In versions 2.0-beta-3 and earlier this parameter had no default value.
* </p>
*/
@Parameter( defaultValue = "Closed" )
private String statusIds;
/**
* Sets the types(s) that you want to limit your report to include. Valid types are: <code>Bug</code>,
* <code>New Feature</code>, <code>Task</code>, <code>Improvement</code>, <code>Wish</code>, <code>Test</code> and
* <code>Sub-task</code>. Multiple values can be separated by commas. If this is set to empty - that means all types
* will be included.
*
* @since 2.0
*/
@Parameter
private String typeIds;
/**
* The prefix used when naming versions in JIRA.
* <p>
* If you have a project in JIRA with several components that have different release cycles, it is an often used
* pattern to prefix the version with the name of the component, e.g. maven-filtering-1.0 etc. To fetch issues from
* JIRA for a release of the "maven-filtering" component you would need to set this parameter to "maven-filtering-".
* </p>
*
* @since 2.4
*/
@Parameter
private String versionPrefix;
/**
* Defines the http password for basic authentication into the JIRA webserver.
*/
@Parameter
private String webPassword;
/**
* Defines the http user for basic authentication into the JIRA webserver.
*/
@Parameter
private String webUser;
/*
* Used for tests.
*/
private AbstractJiraDownloader mockDownloader;
/* --------------------------------------------------------------------- */
/* Public methods */
/* --------------------------------------------------------------------- */
/**
* @see org.apache.maven.reporting.AbstractMavenReport#canGenerateReport()
*/
public boolean canGenerateReport()
{
// Run only at the execution root
if ( runOnlyAtExecutionRoot && !isThisTheExecutionRoot() )
{
getLog().info( "Skipping the JIRA Report in this project because it's not the Execution Root" );
return false;
}
if ( skip )
{
return false;
}
if ( mockDownloader != null )
{
return true;
}
String message = ProjectUtils.validateIssueManagement( project, "JIRA", "JIRA Report" );
if ( message != null )
{
getLog().warn( message );
}
return message == null;
}
public void executeReport( Locale locale )
throws MavenReportException
{
// Validate parameters
List<Integer> columnIds = IssuesReportHelper.getColumnIds( columnNames, JIRA_COLUMNS );
if ( columnIds.isEmpty() )
{
// This can happen if the user has configured column names and they are all invalid
throw new MavenReportException( "maven-changes-plugin: None of the configured columnNames '" + columnNames
+ "' are valid." );
}
try
{
// Download issues
AbstractJiraDownloader issueDownloader;
if ( mockDownloader != null )
{
issueDownloader = mockDownloader;
}
else
{
AdaptiveJiraDownloader downloader = new AdaptiveJiraDownloader();
downloader.setForceClassic( forceRss );
issueDownloader = downloader;
}
configureIssueDownloader( issueDownloader );
issueDownloader.doExecute();
List<Issue> issueList = issueDownloader.getIssueList();
if ( StringUtils.isNotEmpty( versionPrefix ) )
{
int originalNumberOfIssues = issueList.size();
issueList = IssueUtils.filterIssuesWithVersionPrefix( issueList, versionPrefix );
getLog().debug( "Filtered out " + issueList.size() + " issues of " + originalNumberOfIssues
+ " that matched the versionPrefix '" + versionPrefix + "'." );
}
if ( onlyCurrentVersion )
{
String version = ( versionPrefix == null ? "" : versionPrefix ) + project.getVersion();
issueList = IssueUtils.getIssuesForVersion( issueList, version );
getLog().info( "The JIRA Report will contain issues only for the current version." );
}
// Generate the report
IssuesReportGenerator report = new IssuesReportGenerator( IssuesReportHelper.toIntArray( columnIds ) );
if ( issueList.isEmpty() )
{
report.doGenerateEmptyReport( getBundle( locale ), getSink() );
}
else
{
report.doGenerateReport( getBundle( locale ), getSink(), issueList );
}
}
catch ( Exception e )
{
getLog().warn( e );
}
}
public String getDescription( Locale locale )
{
return getBundle( locale ).getString( "report.issues.description" );
}
public String getName( Locale locale )
{
return getBundle( locale ).getString( "report.issues.name" );
}
public String getOutputName()
{
return "jira-report";
}
/* --------------------------------------------------------------------- */
/* Private methods */
/* --------------------------------------------------------------------- */
private ResourceBundle getBundle( Locale locale )
{
return ResourceBundle.getBundle( "jira-report", locale, this.getClass().getClassLoader() );
}
private void configureIssueDownloader( AbstractJiraDownloader issueDownloader )
{
issueDownloader.setLog( getLog() );
issueDownloader.setMavenProject( project );
issueDownloader.setOutput( jiraXmlPath );
issueDownloader.setNbEntries( maxEntries );
issueDownloader.setComponent( component );
issueDownloader.setFixVersionIds( fixVersionIds );
issueDownloader.setStatusIds( statusIds );
issueDownloader.setResolutionIds( resolutionIds );
issueDownloader.setPriorityIds( priorityIds );
issueDownloader.setSortColumnNames( sortColumnNames );
issueDownloader.setFilter( filter );
issueDownloader.setJiraDatePattern( jiraDatePattern );
issueDownloader.setJiraUser( jiraUser );
issueDownloader.setJiraPassword( jiraPassword );
issueDownloader.setTypeIds( typeIds );
issueDownloader.setWebUser( webUser );
issueDownloader.setWebPassword( webPassword );
issueDownloader.setSettings( settings );
issueDownloader.setUseJql( useJql );
issueDownloader.setOnlyCurrentVersion( onlyCurrentVersion );
issueDownloader.setVersionPrefix( versionPrefix );
}
public void setMockDownloader( AbstractJiraDownloader mockDownloader )
{
this.mockDownloader = mockDownloader;
}
public AbstractJiraDownloader getMockDownloader()
{
return mockDownloader;
}
}