This tutorial provides instructions to create a Reporting Plugin called SampleJIRAReporting that has a single procedure called CollectReportingData, which sends user story data from a Jira instance to CloudBees Analytics to enable metrics to be shown in RCC.
Note: The source code for this plugin can be found in https://github.com/electric-cloud-community/flowpdf/tree/master/groovy/SampleJIRAReporting
Prerequisites
These are assumptions for this tutorial.
-
/pdfgroovy/tutorialbasicreporting
tutorial has been completed. -
pdk
is installed and setup. -
An active CloudBees CD/RO instance.
-
An active CloudBees Analytics Center that is configured and connected to CloudBees CD/RO instance.
-
Internet connection.
-
An accessible Jira Instance.
-
Write Access to a Git Repository.
Step 1 : Generate plugin from scratch
Create a plugin workspace with Plugin name as SampleJIRAReporting
and Plugin type as reporting
.
pdk generate workspace
Once done. the response from the command prompt and the workspace layout should look as follows:
Step 2 : Configuring plugin specifications and generating a plugin
Open the config/pluginspec.yaml
file. Generated file will have the
following content:
pluginInfo: # This is default sample specification # Feel free to change it # Call flowpdk showdoc pluginspec to see the list of available fields and their description pluginName: 'SampleJIRAPlugin' version: '1.0.0' description: 'Advanced - Reporting Tutorial Plugin' author: 'Sample Author' supportUrl: 'none' category: 'Utilities' shell: 'ec-groovy' # The reporting configuration, will generate a procedure and a bunch of configuration scripts required for DOIS to work devOpsInsight: supportedReports: - reportObjectType: 'build' parameters: - name: param1 documentation: null required: true type: entry label: Sample Reporting Parameter - name: param2 documentation: null required: false type: entry label: Sample Reporting Parameter 2 # The name of the source as it will appear in the dashboards datasourceName: 'My Source Name' language: 'groovy' # Plugin configuration description configuration: # A set of fields will be added to process debug level in the configuration hasDebugLevel: true parameters: - name: config documentation: The name for the created configuration required: true type: entry label: Configuration Name - name: desc documentation: Description for the configuration required: false type: entry label: Description - name: endpoint documentation: Third-party endpoint to connect to. required: false type: entry label: Endpoint - name: credential documentation: A sample credential required: true type: credential label: Credential
Make the following changes to config/pluginspec.yaml
:
-
Add
jiraProjectName
parameter to the procedure. -
Add the configuration section
restConfigInfo
to enable check connection feature.
You can replace the content or check for the difference and implement them one by one:
pluginInfo: # This is default sample specification # Feel free to change it # Call pdk showdoc pluginspec to see the list of available fields and their description pluginName: 'SampleJIRAReporting' version: '1.0.0' description: 'Sample plugin for RCC <->Jira feature reporting' author: 'Sample Author' supportUrl: '<>' category: 'Utilities' shell: 'ec-groovy' # The reporting configuration, will generate a procedure and a bunch of configuration scripts required for DOIS to work devOpsInsight: supportedReports: - reportObjectType: 'feature' parameters: - name: jiraProjectName documentation: AJira project that will be used required: true type: entry label: Jira project name # The name of the source as it will appear in the dashboards datasourceName: 'JIRA Datasource from tutorial' language: 'groovy' # Plugin configuration description configuration: # A set of fields will be added to process debug level in the configuration hasDebugLevel: true restConfigInfo: endpointDescription: sample endpointLabel: Jira Server Endpoint checkConnectionUri: /rest/api/2/configuration headers: {Accept: "application/json"} # Auth schemes for the plugin authSchemes: basic: userNameLabel: Jira User Name passwordLabel: Jira User Password description: A username and password to connect to the Jira instance. checkConnectionUri: /rest/api/2/configuration credentialLabel: Jira Credentials
Run pdk generate plugin
. The output will look
something like this:
Step 3 : Writing reporting logic using pdk
reporting component
Here’s the small overview of how the CollectReportingData
works:
-
Read the metadata.
-
If no metadata was found, the reporting procedure should get initial amount of issues.
-
If metadata is found, get the last updated issue and check if we’ve already reported it.
-
If metadata is old, get issues updated after the last reported issue.
Each of the operation in bold has a boilerplate code that should be extended by the plugin developer.
Investigating autogenerated code
Let’s see what pdk
has generated for the reporting
plugin.
ReportingSampleJIRAReporting.groovy
contains the following boilerplate
code:
import com.cloudbees.flowpdf.* import sun.reflect.generics.reflectiveObjects.NotImplementedException import com.cloudbees.flowpdf.components.reporting.Dataset import com.cloudbees.flowpdf.components.reporting.Metadata import com.cloudbees.flowpdf.components.reporting.Reporting /** * User implementation of the reporting classes */ class ReportingSampleJIRAPlugin extends Reporting { @Override int compareMetadata(Metadata param1, Metadata param2) { def value1 = param1.getValue() def value2 = param2.getValue() def pluginObject = this.getPluginObject() // Return 1 if there are newer records than record to which metadata is pointing. throw new NotImplementedException() } @Override List<Map<String, Object>> initialGetRecords(FlowPlugin flowPlugin, int i = 10) { def params = flowPlugin.getContext().getRuntimeParameters().getAsMap() throw new NotImplementedException() return records } @Override List<Map<String, Object>> getRecordsAfter(FlowPlugin flowPlugin, Metadata metadata) { def params = flowPlugin.getContext().getRuntimeParameters().getAsMap() def metadataValues = metadata.getValue() def log = flowPlugin.getLog() log.info("Got metadata value in getRecordsAfter: ${metadataValues.toString()}") throw NotImplementedException() log.info("Records after GetRecordsAfter ${records.toString()}") return records } @Override Map<String, Object> getLastRecord(FlowPlugin flowPlugin) { def params = flowPlugin.getContext().getRuntimeParameters().getAsMap() def log = flowPlugin.getLog() log.info("Last record runtime params: ${params.toString()}") throw new NotImplementedException() } @Override Dataset buildDataset(FlowPlugin plugin, List<Map> records) { def dataset = this.newDataset(['your report object type'], []) def context = plugin.getContext() def params = context.getRuntimeParameters().getAsMap() def log = plugin.getLog() log.info("Start procedure buildDataset") log.info("buildDataset received params: ${params}") throw new NotImplementedException() return dataset } }
And the content of generated SampleJIRAPlugin.groovy
contains:
import sun.reflect.generics.reflectiveObjects.NotImplementedException import com.cloudbees.flowpdf.components.ComponentManager import com.cloudbees.flowpdf.* /** * SampleJIRAPlugin */ class SampleJIRAPlugin extends FlowPlugin { @Override Map<String, Object> pluginInfo() { return [ pluginName : '@PLUGIN_KEY@', pluginVersion : '@PLUGIN_VERSION@', configFields : ['config'], configLocations: ['ec_plugin_cfgs'], defaultConfigValues: [:] ] } /** * Procedure parameters: * @param config * @param jiraProjectName * @param previewMode * @param transformScript * @param debug * @param releaseName * @param releaseProjectName */ def collectReportingData(StepParameters paramsStep, StepResult sr) { def params = paramsStep.getAsMap() throw new NotImplementedException() if (params['debug']) { log.setLogLevel(log.LOG_DEBUG) } Reporting reporting = (Reporting) ComponentManager.loadComponent(ReportingSampleJIRAPlugin.class, [ reportObjectTypes : ['feature'], metadataUniqueKey : 'fill me in', payloadKeys : ['fill me in'], ], this) reporting.collectReportingData() } // === step ends === }
Notice that at line #43, the reporting component gets loaded with three
special parameters, and the reportObjectTypes
key is already set to
feature
. We need to fill these two:
-
metadataUniqueKey
-
payloadKeys
Configuration of the reporting component
Let’s check into meaning of each one closely:
-
metadataUniqueKey
Metadata facilitates Change Data Capture where in the plugin is aware if there is any new change that would entail sending reporting data to the CloudBees Analytics Data Server. The fields that participate in Metadata essentially provide the mechanism to capture if that data has changed. Note that this mechanism is required as the plugin is using the PULL model, where in it is periodically looking for what has changed. Since the key has to be unique for the project and query, and the only variable in the query is project, we choose it to be the Jira project name from the data source configuration.
metadataUniqueKey: params['jiraProjectName']
-
payloadKeys
To perform check of the local and remote states for the synchronization we should be able to compare them. Usually, we would like to know if the remote system has new updates. For this we will use "Updated" field of The Jira issue, which is reported as a 'modifiedOn' attribute of the CloudBees Analytics report object (Note: we will check into report object attributes later). The
payloadKeys
array of strings contains the values we want to store in the metadata. To have an additional information, we will also include a key of the last reported issue, which is reported as akey
.payloadKeys : ['key', 'modifiedOn']
Replace the collectReportingData()
step method in
SampleJIRAReporting.groovy
with the following:
def collectReportingData(StepParameters paramsStep, StepResult sr) { def params = paramsStep.getAsMap() if (params['debug']) { log.setLogLevel(log.LOG_DEBUG) } Reporting reporting = (Reporting) ComponentManager.loadComponent(ReportingSampleJIRAReporting.class, [ reportObjectTypes: ['feature'], metadataUniqueKey: params['jiraProjectName'], payloadKeys : ['key', 'modifiedOn'] ] as Map<String, Object>, this) reporting.collectReportingData() }
Receiving issues from the Jira server
As we will use JQL to get information from Jira, ensure that the following JQL returns a list of issues.
project = "TEST"
To connect our plugin with the reporting system (Jira) we have to define a process of pulling the freshest issues in a correct order with a limit. To get issues we will use GET search API endpoint with jql
and maxResults
parameters.
Let’s implement function to perform the GET request. You can copy the following code to your SampleJIRAReporting.groovy
.
def get(String path, Map<String, String> queryParams) { Context context = getContext() REST rest = context.newRESTClient() return rest.request('GET', '/rest/api/2/' + path, queryParams) }
REST instance will get the values from the configuration, apply the authorization and encode the result.
Let’s implement the next method: searching for issues with limiting count of the results:
def getIssues(String projectName, Map<String, String> opts) { def requestParams = [jql: "project=$projectName AND issuetype=Story ORDER BY updatedDate ASC"] if (opts['limit']) { requestParams['maxResults'] = opts['limit'] } def issues = get('search', requestParams) return issues['issues'] }
And the method that will return the issue that was updated last:
def getLastUpdatedIssue(String projectName) { String lastJql = "project=$projectName AND issuetype=Story ORDER BY updatedDate DESC" def result = get('search', [jql: lastJql, maxResults: '1']) if (result['total'] > 0 && result.issues.size()) { return result['issues'][0] } throw new UnexpectedMissingValue("JIRA does not return the last updated issue") }
If our reporting component discovers fresh issues, we search for issues that were updated after last synchronization. Before we proceed, note that CloudBees Analytics server has date format in ISO8601 with a UTC timezone. For example:
2019-01-01T14:38:01.000Z
The Jira server uses different format for records and another for JQL. Here’re two simple methods that will convert Jira datetime
string to ISO8601 and the ISO8601 to a format that JQL accepts. Add the following code to your SampleJIRAReporting.groovy
:
String jiraDatetimeToISODatetime(String rawDate) { if (rawDate == null || !rawDate) return '' SimpleDateFormat jiraDateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ") jiraDateFormat.setTimeZone(TimeZone.getTimeZone("UTC")) Date parsedDate = jiraDateFormat.parse(rawDate) SimpleDateFormat devopsDateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'") devopsDateFormat.setTimeZone(TimeZone.getTimeZone("UTC")) String formatted = devopsDateFormat.format(parsedDate) return formatted } String isoDatetimeToJqlDatetime(String isoDate) { SimpleDateFormat devopsDateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'") devopsDateFormat.setTimeZone(TimeZone.getTimeZone("UTC")) Date parsedDate = devopsDateFormat.parse(isoDate) SimpleDateFormat jqlDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm") jqlDateFormat.setTimeZone(TimeZone.getTimeZone("UTC")) // The timezone here should be adjusted to Jira value String jiraFormattedDate = jqlDateFormat.format(parsedDate) return jiraFormattedDate }
Now we can implement method that perform JQL search for issues updated after the last reported issue. Add the following code to your SampleJIRAReporting.groovy
:
def getIssuesAfterDate(String projectName, String lastUpdateDateISO) { String jiraFormattedDate = isoDatetimeToJqlDatetime(lastUpdateDateISO) String storyJql = "project='$projectName' AND issuetype=Story AND updatedDate >= \"${jiraFormattedDate}\" ORDER BY updatedDate DESC" def result = get('search', [jql: storyJql]) if (result['total'] > 0 && result.issues.size()) { return result['issues'] } throw new UnexpectedMissingValue("JIRA did not return issue updated after ${jiraFormattedDate}. Check the timezone.") }
Implementing the reporting component
Now we will edit ReportingSampleJIRAReporting.groovy
file. Make sure that it has the following imports:
import com.cloudbees.flowpdf.FlowPlugin import com.cloudbees.flowpdf.components.reporting.Dataset import com.cloudbees.flowpdf.components.reporting.Metadata import com.cloudbees.flowpdf.components.reporting.Reporting import net.sf.json.JSONObject import java.text.SimpleDateFormat
Let’s implement the reporting methods one by one:
compareMetadata
The purpose of the compareMetadata
method is to tell the reporting component if one issue has been updated later than another.
This method is used in two cases:
-
Reporting compares stored metadata with a metadata built from last modified issue.
-
Reporting sorts the payloads before sending it to the CloudBees Analytics Server.
The method receives two metadata instances. The Metadata.getValue()
returns object that represents stored value. As we have supplied key
and modifiedOn
as the payloadKeys
, metadata value will have a following structure:
{"key":"TEST-26","modifiedOn":"2020-02-05T13:43:08.000Z"}
The value of 'modifiedOn' contains date in CloudBees Analytics format
(ISO8601 in the UTC timezone).
|
If the last updated Jira has modifiedOn
time in the future, Reporting should send new changes to the CloudBees Analytics Server. Replace the method compareMetadata()
in ReportingSampleJIRAReporting.groovy
by the following code:
@Override int compareMetadata(Metadata param1, Metadata param2) { SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'") format.setTimeZone(TimeZone.getTimeZone("UTC")) Date date1 = format.parse(param1.getValue()['modifiedOn']) Date date2 = format.parse(param2.getValue()['modifiedOn']) // Return 1 if there are newer records than record to which metadata is pointing. return date2.compareTo(date1) }
initialGetRecords
This method is called when there is no metadata stored for the context. We will use our SampleJIRAReporting.getIssues
method to retrieve issues. Replace the method initialGetRecords()
in ReportingSampleJIRAReporting.groovy
by the following code:
@Override List<Map<String, Object>> initialGetRecords(FlowPlugin flowPlugin, int i = 10) { def params = flowPlugin.getContext().getRuntimeParameters().getAsMap() return flowPlugin.getIssues(params['jiraProjectName'], [limit: i]) }
To prevent loading too much information, limit should be supplied. It can be changed in the schedule parameters or in the Reporting component initialization values.
If you want to change the limit to a huge amount, you should be aware that Jira returns search results in chunks if the number of the results is greater than the count specified in the Jira configuration. Look to the source code of SampleJIRAOAuth for implementation of the chunked response retrieval.
getLastRecord
This method will be called when we want to check if the state of the reporting system has changed and should return the last updated issue. We have implemented it in the plugin code (SampleJIRAReporting.groovy
) so here method will look very simple:
Replace the method getLastRecord()
in
ReportingSampleJIRAReporting.groovy
by the following code:
@Override Map<String, Object> getLastRecord(FlowPlugin flowPlugin) { def params = flowPlugin.getContext().getRuntimeParameters().getAsMap() return flowPlugin.getLastUpdatedIssue(params['jiraProjectName']) }
getRecordsAfter
When we know that there are updated issues in the reporting system, we want to grab only new ones. Modified date will be given in Metadata instance.
Replace the method getRecordsAfter() in
ReportingSampleJIRAReporting.groovy
by the following code:
@Override List<Map<String, Object>> getRecordsAfter(FlowPlugin flowPlugin, Metadata metadata) { def params = flowPlugin.getContext().getRuntimeParameters().getAsMap() def metadataValue = metadata.getValue() return flowPlugin.getIssuesAfterDate(params['jiraProjectName'], metadataValue['modifiedOn']) }
buildDataset
Now, the last piece. We should tell the Reporting component how our Jira issues should be transformed into the CloudBees Analytics Server payload.
First, we should know how the payload should look like. Go to Understand the CloudBees Analytics data model to view the list of fields for the feature
report. Additionally, you can use the following call to get the report attributes defined on CloudBees CD/RO server.
ectool --format json getReportObjectAttributes feature
* There are specific requirements for fields featuretype , resolution and status . They should contain one of the predefined values. As Jira can be tuned by the user to contain additional statuses and resolutions or they can differ between The Jira releases, we should map the original value to one that is expected by the CloudBees Analytics Server.
|
+
*Some of the fields, like pluginConfiguration
, pluginName
, releaseName
and releaseProjectName
are well-known and will be supplied by the Reporting component if omitted.
Replace the method buildDataset()
in ReportingSampleJIRAReporting.groovy
by the following code:
@Override Dataset buildDataset(FlowPlugin plugin, List<Map> records) { SampleJIRAReporting jiraPlugin = plugin as SampleJIRAReporting Dataset dataset = this.newDataset(['feature'], []) Context context = plugin.getContext() Map params = context.getRuntimeParameters().getAsMap() for (Map<String, Object> issue : records) { plugin.log.debug("Issue:", JSONObject.fromObject(issue).toString()) Map<String, String> statusMappings = [ 'Done' : 'Closed', 'Closed' : 'Closed', 'In Progress': 'In Progress', 'Open' : 'Open', 'To Do' : 'Open', 'Reopened' : 'Reopened', 'Resolved' : 'Resolved', ] Map<String, String> resolutionMappings = [ 'Cannot Reproduce': 'Cannot Reproduce', 'Duplicate' : 'Duplicate', 'Done' : 'Fixed', 'Won\'t Do' : 'Incomplete', 'Won\'t Do' : 'Won\'t Fix' ] String rawStatus = issue.fields.status?.statusCategory?.name ?: '' String rawResolution = issue.fields.resolution?.name ?: '' String jiraModifiedOn = issue.fields.updated ?: '' String jiraCreatedOn = issue.fields.created ?: '' String jiraResolvedOn = issue.fields.resolutionDate ?: '' String modifiedOn = jiraPlugin.jiraDatetimeToISODatetime(jiraModifiedOn) String createdOn = jiraPlugin.jiraDatetimeToISODatetime(jiraCreatedOn) String resolvedOn = jiraPlugin.jiraDatetimeToISODatetime(jiraResolvedOn) dataset.newData([ reportObjectType: 'feature', values: [ source : 'JIRA', sourceUrl : params['endpoint'], type : issue.fields.issuetype?.name, featureName: issue.fields.summary, storyPoints: issue.fields.storyPoints ?: '', key : issue.key, resolution : resolutionMappings[rawResolution] ?: '', status : statusMappings[rawStatus] ?: 'Open', modifiedOn : modifiedOn, createdOn : createdOn, resolvedOn : resolvedOn, ]]) } return dataset }
Step 4 : Review the resulting code
After all the modifications, this is what SampleJIRAReporting.groovy
should contain:
import com.cloudbees.flowpdf.* import com.cloudbees.flowpdf.client.REST import com.cloudbees.flowpdf.components.ComponentManager import com.cloudbees.flowpdf.components.reporting.Reporting import com.cloudbees.flowpdf.exceptions.UnexpectedMissingValue import java.text.SimpleDateFormat /** * SampleJIRAReporting */ class SampleJIRAReporting extends FlowPlugin { @Override Map<String, Object> pluginInfo() { return [ pluginName : '@PLUGIN_KEY@', pluginVersion : '@PLUGIN_VERSION@', configFields : ['config'], configLocations : ['ec_plugin_cfgs'], defaultConfigValues: [authScheme: 'basic'] ] } /** * Procedure parameters: * @param config * @param jiraProjectName * @param previewMode * @param transformScript * @param debug * @param releaseName * @param releaseProjectName */ def collectReportingData(StepParameters paramsStep, StepResult sr) { def params = paramsStep.getAsMap() if (params['debug']) { log.setLogLevel(log.LOG_DEBUG) } Reporting reporting = (Reporting) ComponentManager.loadComponent(ReportingSampleJIRAReporting.class, [ reportObjectTypes: ['feature'], metadataUniqueKey: params['jiraProjectName'], payloadKeys : ['key', 'modifiedOn'] ] as Map<String, Object>, this) reporting.collectReportingData() } // === step ends === def get(String path, Map<String, String> queryParams) { Context context = getContext() REST rest = context.newRESTClient() return rest.request('GET', '/rest/api/2/' + path, queryParams) } def getIssues(String projectName, Map<String, String> opts) { def requestParams = [jql: "project=$projectName AND issuetype=Story ORDER BY updatedDate ASC"] if (opts['limit']) { requestParams['maxResults'] = opts['limit'] } def issues = get('search', requestParams) return issues['issues'] } def getLastUpdatedIssue(String projectName) { String lastJql = "project=$projectName AND issuetype=Story ORDER BY updatedDate DESC" def result = get('search', [jql: lastJql, maxResults: '1']) if (result['total'] > 0 && result.issues.size()) { return result['issues'][0] } throw new UnexpectedMissingValue("JIRA does not returned the last updated issue") } String jiraDatetimeToISODatetime(String rawDate) { if (rawDate == null || !rawDate) return '' SimpleDateFormat jiraDateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ") jiraDateFormat.setTimeZone(TimeZone.getTimeZone("UTC")) Date parsedDate = jiraDateFormat.parse(rawDate) SimpleDateFormat devopsDateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'") devopsDateFormat.setTimeZone(TimeZone.getTimeZone("UTC")) String formatted = devopsDateFormat.format(parsedDate) return formatted } String isoDatetimeToJqlDatetime(String isoDate) { SimpleDateFormat devopsDateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'") devopsDateFormat.setTimeZone(TimeZone.getTimeZone("UTC")) Date parsedDate = devopsDateFormat.parse(isoDate) SimpleDateFormat jqlDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm") jqlDateFormat.setTimeZone(TimeZone.getTimeZone("UTC")) // The timezone here should be adjusted to Jira value String jiraFormattedDate = jqlDateFormat.format(parsedDate) return jiraFormattedDate } def getIssuesAfterDate(String projectName, String lastUpdateDateISO) { String jiraFormattedDate = isoDatetimeToJqlDatetime(lastUpdateDateISO) String storyJql = "project='$projectName' AND issuetype=Story AND updatedDate >= \"${jiraFormattedDate}\" ORDER BY updatedDate DESC" def result = get('search', [jql: storyJql]) if (result['total'] > 0 && result.issues.size()) { return result['issues'] } throw new UnexpectedMissingValue("JIRA did not return issue updated after ${jiraFormattedDate}. Check the timezone.") } }
And this is content of ReportingSampleJIRAReporting.groovy
:
import com.cloudbees.flowpdf.FlowPlugin import com.cloudbees.flowpdf.components.reporting.Dataset import com.cloudbees.flowpdf.components.reporting.Metadata import com.cloudbees.flowpdf.components.reporting.Reporting import net.sf.json.JSONObject import java.text.SimpleDateFormat /** * User implementation of the reporting classes */ class ReportingSampleJIRAReporting extends Reporting { @Override int compareMetadata(Metadata param1, Metadata param2) { SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'") format.setTimeZone(TimeZone.getTimeZone("UTC")) Date date1 = format.parse(param1.getValue()['modifiedOn']) Date date2 = format.parse(param2.getValue()['modifiedOn']) // Return 1 if there are newer records than record to which metadata is pointing. return date2.compareTo(date1) } @Override List<Map<String, Object>> initialGetRecords(FlowPlugin flowPlugin, int i = 10) { def params = flowPlugin.getContext().getRuntimeParameters().getAsMap() return flowPlugin.getIssues(params['jiraProjectName'], [limit: i]) } @Override Map<String, Object> getLastRecord(FlowPlugin flowPlugin) { def params = flowPlugin.getContext().getRuntimeParameters().getAsMap() return flowPlugin.getLastUpdatedIssue(params['jiraProjectName']) } @Override List<Map<String, Object>> getRecordsAfter(FlowPlugin flowPlugin, Metadata metadata) { def params = flowPlugin.getContext().getRuntimeParameters().getAsMap() def metadataValue = metadata.getValue() return flowPlugin.getIssuesAfterDate(params['jiraProjectName'], metadataValue['modifiedOn']) } @Override Dataset buildDataset(FlowPlugin plugin, List<Map> records) { SampleJIRAReporting jiraPlugin = plugin as SampleJIRAReporting Dataset dataset = this.newDataset(['feature'], []) Context context = plugin.getContext() Map params = context.getRuntimeParameters().getAsMap() for (Map<String, Object> issue : records) { plugin.log.debug("Issue:", JSONObject.fromObject(issue).toString()) Map<String, String> statusMappings = [ 'Done' : 'Closed', 'Closed' : 'Closed', 'In Progress': 'In Progress', 'Open' : 'Open', 'To Do' : 'Open', 'Reopened' : 'Reopened', 'Resolved' : 'Resolved', ] Map<String, String> resolutionMappings = [ 'Cannot Reproduce': 'Cannot Reproduce', 'Duplicate' : 'Duplicate', 'Done' : 'Fixed', 'Won\'t Do' : 'Incomplete', 'Won\'t Do' : 'Won\'t Fix' ] String rawStatus = issue.fields.status?.statusCategory?.name ?: '' String rawResolution = issue.fields.resolution?.name ?: '' String jiraModifiedOn = issue.fields.updated ?: '' String jiraCreatedOn = issue.fields.created ?: '' String jiraResolvedOn = issue.fields.resolutionDate ?: '' String modifiedOn = jiraPlugin.jiraDatetimeToISODatetime(jiraModifiedOn) String createdOn = jiraPlugin.jiraDatetimeToISODatetime(jiraCreatedOn) String resolvedOn = jiraPlugin.jiraDatetimeToISODatetime(jiraResolvedOn) dataset.newData([ reportObjectType: 'feature', values: [ source : 'JIRA', sourceUrl : params['endpoint'], type : issue.fields.issuetype?.name, featureName: issue.fields.summary, storyPoints: issue.fields.storyPoints ?: '', key : issue.key, resolution : resolutionMappings[rawResolution] ?: '', status : statusMappings[rawStatus] ?: 'Open', modifiedOn : modifiedOn, createdOn : createdOn, resolvedOn : resolvedOn, ]]) } return dataset } }
Step 5 : DOIS setup and reporting check
Install and promote the plugin. Then navigate to releases and create
Jira Test Project
release as follows:
Navigate to CloudBees Analytics Center:
Navigate to Release Command Center:
Select the Dashboard editor:
Select Setup:
Select Add button for User stories section:
Select our tutorial plugin, fill in the Jira project name parameter and click for new configuration creation:
Enter valid config values:
Your Jira endpoint should contain an URL of the Jira instance you have access to. |
Go to
, choose your project and select Schedules.
Run the schedule to immediately collect the issues.
Navigate to Release Command Center again to see your Release data being populated.
Your reporting numbers may be different. |
The Jira instance used for this tutorial has a Story Points field as a custom field. Custom fields are not covered for the simplicity.