How to develop a JIRA plugin that display charts? - jira-plugin

I am a newbie in JIRA World, my mission is to develop a JIRA plugin that display charts (pies, histogram, etc.). The user would choose the criteria of search and the type of charts,
and the user should be able to save those charts (Word/PDF).
I don't know if I should develop a report plugin and use JFreeChart for charting or develop a charting plugin (I haven't seen any example of charting plugin). I have done some research about report plugin and I haven't found an example show how to Use JFreechart!! also I haven't found any example show how to use jasperReport (or another tool) to generate word or PDF report.
The user should access to the plugin from a tab like interface (administration, home, etc.)
Example of KPI (key performance indicator) to display:
Average of time spent to reslove an issue
Number of issues that still open
PS: I'm using JIRA 4.3.3

There is a JIRA plugin to export charts to Word, which covers part of what you wanted to do.
https://marketplace.atlassian.com/plugins/com.clariostechnology.officecharts.officecharts
Also see Intelligent Reporter which gives more options for formatting charts and allows you to use Word files as templates and fill in the charts and other data.
https://marketplace.atlassian.com/plugins/com.clariostechnology.intelligentreports

Try reading the article Creating a pie chart in JIRA from the great book "JIRA 5.x Development Cookbook" by Jobin Kuruvilla.
The most important thing is to populate your dataset, which will be used to generate the needed chart. Consider the example from that book, which shows the java side of that plugin:
public Chart generateChart(JiraAuthenticationContext authenticationContext, int width, int height) {
try {
final Map<String, Object> params = new HashMap<String, Object>();
// Create Dataset
DefaultPieDataset dataset = new DefaultPieDataset();
dataset.setValue("One", 10L);
dataset.setValue("Two", 15L);
final ChartHelper helper = new PieChartGenerator(dataset, authenticationContext.getI18nHelper()).generateChart();
helper.generate(width, height);
params.put("chart", helper.getLocation());
params.put("chartDataset", dataset);
params.put("imagemap", helper.getImageMap());
params.put("imagemapName", helper.getImageMapName());
params.put("width", width);
params.put("height", height);
return new Chart(helper.getLocation(), helper.getImageMap(), helper.getImageMapName(), params);
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("Error generating chart", e);
}
}
and the velocity template for such purpose:
#if ($chart)
#if ($imagemap)
$imagemap
#end
<p class="report-chart">
<img src='$baseurl/charts?filename=$chart' border='0' #if ($imagemap) usemap="\#$imagemapName" #end/>
</p>
#end
That's it in the most basic example. But also, take a look at the ChartFactory and ChartUtils interfaces, to get the deeper idea how to create various types of charts.

You don't need to write a plugin to do that-these are native capabilities in JIRA. If you're hellbent on writing a plugin I'd use JFreeChart. See the JIRA Charting Plugin and JQL filters to get your two listed KPIs.

in velocity:
#set($cht = $chart)
#if ($cht)
#if ($cht.imageMap)
$cht.imageMap
#end
<p class="report-chart">
<img src='$baseurl/charts?filename=$chart.location' border='0'
#if ($cht.imageMap) usemap="\#$cht.imageMapName" #end/>
</p>
#end
webwork:
#SuppressWarnings("unused")
public Chart getChart() {
JiraAuthenticationContext authenticationContext = ComponentAccessor.getJiraAuthenticationContext();
final int CHART_WIDTH = 300;
final int CHART_HEIGHT = 300;
try {
final Map<String, Object> params = new HashMap<String, Object>();
// Create Dataset
DefaultPieDataset dataset = new DefaultPieDataset();
dataset.setValue("One", 10L);
dataset.setValue("Two", 15L);
final I18nBean i18nBean = new I18nBean(authenticationContext.getUser().getDirectoryUser());
final ChartHelper helper = new PieChartGenerator(dataset, i18nBean).generateChart();
helper.generate(CHART_WIDTH, CHART_HEIGHT);
params.put("chart", helper.getLocation());
params.put("chartDataset", dataset);
params.put("imagemap", helper.getImageMap());
params.put("imagemapName", helper.getImageMapName());
params.put("width", CHART_WIDTH);
params.put("height", CHART_HEIGHT);
Chart ch = new Chart(helper.getLocation(), helper.getImageMapHtml(), helper.getImageMapName(), params);
return ch;
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("Error generating chart", e);
}
}
result:

The best alternative to save your time from plugin programming, yet giving you incredible features and flexibility, is generating the charts in Excel via exporting JIRA data with the Better Excel Plugin.
How does it work?
You create a template XLSX file with special placeholder, tags for the actual data.
Define so-called named ranges and generate (empty) charts from them, using all the charting features in Excel. (As MS Excel is still the most versatile and most widely used data visualization tool, it is almost guaranteed that your use case will be supported and you will find tons of help by Googling the web.)
When you export this template, the add-on will fill the named range with your issues' data and the chart "wakes up"!
The end-result may look like this (in its own worksheet):

See this tutorial about drawing various types of charts to visualize JIRA data: http://www.midori.hu/products/jira-pdf-view-plugin/documentation/charts
The technique explained there relies on capturing your custom KPI calculation logic in concise Groovy scripts, rendering the charts with the de-facto standard JFreeChart, all backed by our JIRA PDF View Plugin.
(Discl: the plugin mentioned there is our commercially supported software.)

Related

Adding an Annotation to a PdfFormXObject so the Annotation is reusable

I'm using iText 7 to construct reusable PDF components that I reuse across multiple pages within a document. I'm using iText-dotnet for this task (v7), using F# as the language. (This shouldn't be hard to follow for non-F# people as it's just iText calls :D)
I know how to add annotations to a Page, that isn't the issue. Adding the annotation to the page is as simple as page.AddAnnotation(newAnnotation).
Where I'm having difficulty, is that there is no "Page" associated with a Canvas when you are using a PdfFormXObject() to render a Pdf fragment.
let template = new PdfFormXObject(rect)
let templateCanvas = PdfCanvas(template, pageContext.Canvas.GetPdfDocument())
let newCanvas = new Canvas(templateCanvas, rect)
Once I have the new Canvas, I try to write to the Canvas and add the Annotation via Page.AddAnnotation(). The problem is that there is no Page attached to the PdfFormXObject!
// Create the destination and annotation (destPage is the pageNumber)
let dest = PdfExplicitDestination.CreateFitB(destPage)
let action = PdfAction.CreateGoTo(dest)
let annotation = PdfLinkAnnotation(rect)
let border = iText.Kernel.Pdf.PdfAnnotationBorder(0f, 0f, 0f)
// set up the Annotation with action and display information
annotation
.SetHighlightMode(PdfAnnotation.HIGHLIGHT_PUSH)
.SetAction(action)
.SetBorder(border)
|> ignore
// Try adding the annotation to the page BOOM! (There is *NO* page (null) associated with newCanvas)
newCanvas.GetPage().AddAnnotation(annotation) |> ignore // HELP HERE: Is there another way to do this?
The issue is that I do not know of a different way to set the Annotation on the canvas. Is there a way to render the annotation and just add the annotation directly to the canvas as raw PDF instructions?
Alternatively, is there a way create a different reusable PDF fragment in iText so I can also reuse the GoTo annotation.
N.B. I could split off the annotations and then apply them every time I use the PdfFormXObject() on a new page, but that sort of defeats the purpose of reusing Pdf fragments (template) in my final PDF to reduce it's size.
If you can point me in the right direction, that would be great.
Again, this is not how to add an annotation to a Page(), that's easy. It's how to add an annotation to a PdfFormXObject (or similar mechanism that I'm unaware of for constructing rusable Pdf fragments).
-- As per John's comments below:
I cannot seem to find any reference to single use annotations.
I'm aware of the following example link, so I modified it to look like this:
private static void Main(string[] args)
{
try
{
PdfDocument pdfDocument = new PdfDocument(new PdfWriter("TestMultiLink.pdf"));
Document document = new Document(pdfDocument);
string destinationName = "MyForwardDestination";
// Create a PdfStringDestination to use more than once.
var stringDestination = new PdfStringDestination(destinationName);
for (int page = 1; page <= 50; page++)
{
document.Add(new Paragraph().SetFontSize(100).Add($"{page}"));
switch (page)
{
case 1: // First use of PdfStringDestination
document.Add(new Paragraph(new Link("Click here for a forward jump", stringDestination).SetFontSize(20)));
break;
case 3: // Re-use the stringDestination
document.Add(new Paragraph(new Link("Click here for a forward jump", stringDestination).SetFontSize(10)));
break;
case 42:
pdfDocument.AddNamedDestination(destinationName, PdfExplicitDestination.CreateFit(pdfDocument.GetLastPage()).GetPdfObject());
break;
}
if (page < 50)
document.Add(new AreaBreak(AreaBreakType.NEXT_PAGE));
}
document.Close();
}
catch (Exception e)
{
Console.WriteLine($"Ouch: {e.Message}");
}
}
If you dig into the iText source for iText.Layout.Link, you'll see that the String Destination is added as an Annotation. Therefore, I'm not sure if John's answer is true anymore.
Does anyone know how I can convert the Annotation to a Dictionary and how I would go about adding the PdfDictionary (raw) info into the PftFormXObject?
Thanks
#johnwhitington is correct.
Per PDF specification, annotations can only be added to a page, they cannot be added to a form XObject. It is not a limitation of iText or any other PDF library.
Annotations cannot be reused, each annotation is a distinct object.

How to access components inside a custom ToolWindow from an action?

I have registered an action in the EditorPopupMenu (this is right click menu). I also have a bunch of components inside a ToolWindow (that I designed using the GUI Designer plugin) that I want to update the values of.
There have been some posts on the IntelliJ forums about this, and the typical answer seems to advice using the ToolWindow's ContentManager, and obtain the JPanel containing all your components. E.g. the following:
Project p = e.getProject();
ToolWindow toolWindow;
toolWindow = ToolWindowManager.getInstance(p).getToolWindow("My ToolWindow ID");
ContentManager contentManager = toolWindow.getContentManager();
JPanel jp = (JPanel) contentManager.getContent(0).getComponent();
This feels counterintuitive... Having to navigate inside JPanel's to find a bunch of components. What if I decided to put my components inside a different container? Suddenly the way I navigate to my components would break down.
Is it really the most practical way to constrain myself to the way my GUI is built? Can't I access these components in a different way?
I found a way to access my custom myToolWindow. This should help quite some people.
Make sure that your custom MyToolWindow extends the class SimpleToolWindowPanel.
In your custom myToolWindowFactory class, pass your custom MyToolWindow to ContentFactory.createContent() as the first argument. NOT one of the JPanel's inside MyToolWindow as is done in the ToolWindow examples given in the official IntelliJ documentation...
In your MyToolWindow constructor, call the method setContent(<YourJPanelContainingYourComponents>).
I found the answer by experimenting on example 5 from this link:
public JBTabbedTerminalWidget getTerminalWidget(ToolWindow window) {
window.show(null);
if (myTerminalWidget == null) {
JComponent parentPanel = window.getContentManager().getContents()[0].getComponent();
if (parentPanel instanceof SimpleToolWindowPanel) {
SimpleToolWindowPanel panel = (SimpleToolWindowPanel) parentPanel;
JPanel jPanel = (JPanel) panel.getComponents()[0];
myTerminalWidget = (JBTabbedTerminalWidget) jPanel.getComponents()[0];
} else {
NotificationUtils.infoNotification("Wait for Freeline to initialize");
}
}
return myTerminalWidget;
}

How to clean existing properties and replace with metadata template on Photoshop (scripting)?

While creating a script that would automate all the different tasks I do when I start working on a new picture on Photoshop, I encountered the following problem.
Manually, I would Ctrl + Alt + Shift + I, click on the template I want and choose the option "Clear existing properties and replace with template properties".
I can't find the way to do precisely this. The best thing I managed to find is something like this :
app.activeDocument.info.author = "test";
app.activeDocument.info.caption = "";
app.activeDocument.info.captionWriter = "";
app.activeDocument.info.headline = "";
app.activeDocument.info.instructions = "";
app.activeDocument.info.keywords = "";
app.activeDocument.info.authorPosition = "";
app.activeDocument.info.credit = "";
app.activeDocument.info.source = "";
app.activeDocument.info.category = "";
app.activeDocument.info.supplementalCategories = "";
app.activeDocument.info.title = "";
// etc.
And it actually doesn't really work like the "Clear existing properties and replace with template properties".
I didn't find anything on the Photoshop scripting guide, nor on the internet. Any help would be greatly appreciated !
What I think is the problem is Photoshop separates file-metadata from its activeDocument-metadata. What you see in "File info..." (via Ctrl+Alt+Shift+I) is supposed to represent the file in the filesystem, which metadata is embedded in.
There are several scripting guides to Photoshop scripting. I think the one relevant for you would be "Javascript Tools Guide", specifically the chapter 10 "Scripting Access to XMP Metadata".
Is it important for you to set up the metadata already when creating a new picture? If not, you may want to look at a solution using a customized export script.
It customizes XMP-metadata upon exporting like
Create a basic metadata object:
var meta = new XMPMeta();
Provide a namespaceURI (see XMP specs) known to photoshop along with tag name, and value:
meta.setProperty(XMPConst.NS_XMP, "CreatorTool", app.version);
Save the image temporarily (using other script):
var imgFile = new File(fileName);
saveImage(fileName);
Finish saving by adding the metadata-object:
var metaFile = new XMPFile(imgFile.fsName, XMPConst.FILE_UNKNOWN, XMPConst.OPEN_FOR_UPDATE);
if (metaFile.canPutXMP(meta)) { metaFile.putXMP(meta); }
metaFile.closeFile(XMPConst.CLOSE_UPDATE_SAFELY);
Doing it this way also erases any existing or default metadata.

iTextSharp: What is Lost When Copying PDF Content From Another PDF?

I am currently evaluating iTextSharp for potential use in a project. The code that I have written to achieve my goal is making use of PDFCopy.GetImportedPage to copy all of the pages from an existing PDF. What I want to know is what all do I need to be aware of that will be lost from a PDF and/or page when duplicating PDF content like this? For example, one thing that I already noticed is that I need to manually add in any bookmarks and named destinations into my new PDF.
Here's some rough sample code:
using (PdfReader reader = new PdfReader(inputFilename))
{
using (MemoryStream ms = new MemoryStream())
{
using (Document document = new Document())
{
using (PdfCopy copy = new PdfCopy(document, ms))
{
document.Open();
int n;
n = reader.NumberOfPages;
for (int page = 0; page < n; )
{
copy.AddPage(copy.GetImportedPage(reader, ++page));
}
// add content and make further modifications here
}
}
// write the content to disk
}
}
Basically anything that's document-level instead of page-level will get lost and both Bookmarks and Destinations are document-level. Pull up the PDF spec and look at section 3.6.1 for other entries in the document catalog including Threads, Open and Additional Actions and Meta Data.
You might already have seen these but here are some samples (in Java) of how to merge Named Destinations and how to merge Bookmarks.

Starting with Kinect for Windows - SDK 1.7

I'm starting with Kinect SDK 1.7, using KinectRegion and other controls like KinectTileButton and KinectScrollViewer from the toolkit. My questions are:
How to enable KinectRegion to work with left and right hands?
Does the SDK 1.7 have something ready to work with zooming?
How to detect grip and release?
Any code available on the Internet?
Thank you!
To Enable the Kinect Region :
Import "Microsoft.Kinect.Toolkit.Controls" project into your solution. (Use Add -> Existing Project)
Add Reference of "Microsoft.Kinect.Toolkit.Controls" into your project.
Add KinectRegion into your XAML using this code :
Import/Use "Microsoft.Kinect.Toolkit.Controls" in your xaml.cs file :
using Microsoft.Kinect.Toolkit;
Bind the sensor chooser's current sensor to the KinectRegion :
var regionSensorBinding = new Binding("Kinect") { Source = this.sensorChooser };
BindingOperations.SetBinding(this.kinectRegion, KinectRegion.KinectSensorProperty,
regionSensorBinding);
I don't get what you mean about "zooming". Please give more detail.
To detect hand gripping and hand release, you can add "AddHandPointerGripHandler" and "AddHandPointerGripReleaseHandler" to your KinectRegion. Please take a look to the KinectScrollViewer.cs.
You can explore the code about the hand pointer and stuff from the "Kinect Developer Toolkit Browser App".
As far as I can remember, KinectRegion works with both hand and automatically detects which one is the main one.
The grip and release detection is also automatic on KinectScrollViewer controls.
About the zooming I have no idea.
You'll find good tutorial on Kinect SDK 1.7 Interactions features on this link
Absolutely outstanding course is on the links below:
The first part shows the basic of Kinect SDK
The second part is similiar to firs part, but with use of MS Blend
And the third part is tutorial for interaction stream, where you can get information of both hands.
But if you´d like to use both hand at Kinect region, you have to edit Microsoft.Kinect.Toolkit.Controls -> KinectRegion.cs -> line 1000 (more info in MSDN Blog question)
It helped to me! (I have the same problem)!
For Grip detection is available in kinectRegion -> kinectRegion.HandPointers[idex of hand(0 is left, 1 is right)].IsInGripInteraction - it´s bool - I added some code:
private Skeleton []skeleton;
private void kinect_SkeletonFrameReady(object sender, SkeletonFrameReadyEventArgs e)
{
using (SkeletonFrame sf = e.OpenSkeletonFrame())
{
if (sf != null && this.skeleton != null) // check that a frame is available
{
sf.CopySkeletonDataTo(this.skeleton); // get the skeletal information in this frame
}
}
}
sensor.SkeletonFrameReady += new EventHandler<SkeletonFrameReadyEventArgs>(kinect_SkeletonFrameReady);
foreach (var sk in skeleton)
{
if (sk.TrackingId == 0) continue;
else
{
if (kinectRegion.HandPointers[0].IsInGripInteraction == true)
{
.......
}
}
}