Writing Widgets for Kitto

Widgets are the core of how Kitto displays data on dashboards. They are small React components that define how events from a specified data source should be displayed. Kitto has a lot of great widgets built in for displaying data. You can see how the widgets work on the sample dashboard at Kitto.io. Sometimes, you'll need more than the built in widgets. Last time, we wrote a job to pull issues in from JIRA. Let's now take the issue list returned and put it into a table.

Before diving in to create your widgets, I've made a change to my JIRA job from the last post. In lib/apis/jira.ex, I modified issue_for_dashboard/1 to the following:

def issue_for_dashboard(issue) do  
  %{
    id: issue["key"],
    summary: issue["fields"],
    priority: issue["fields"]["priority"]
  }
end  

Now on with the show!


To create a new widget create a folder in the widgets directory with a JavaScript and SCSS file, using the same name for all three.

$ tree widgets/table
widgets/jira_table  
├── jira_table.js
└── jira_table.scss

The JS file will define the React components and the SCSS file (optional) will define any styles to apply to the widget. Let start with the React component:

import React from 'react';  
import Widget from '../../assets/javascripts/widget';  
import {updatedAt, truncate} from '../../assets/javascripts/helpers';

import './jira_table.scss';

class TableRow extends React.Component {  
  render() {}
}

export class JiraTable extends Widget {  
  render() {}
}

Widget.mount(JiraTable);  

What we have so far is some import statements which bring in the React framework, the Widget object, and a couple of helpers that we will use later, and, finally, our SCSS file to stylize the widget. The Table class is what we will reference for our Widget which will loop through the data and use the TableRow class to build each row of data. Let's start building out the table:

export class JiraTable extends Widget {  
  render() {
    return (
    <div className={this.props.className}>
      <h1 className="title">{this.props.title}</h1>
      <h3>{this.props.text}</h3>
      <table>
        <thead>
          <tr>
            <th></th>
            <th>Key</th>
            <th>Summary</th>
          </tr>
        </thead>

      </table>
      <p className="more-info">{this.props.moreinfo}</p>
      <p className="updated-at">{updatedAt(this.state.updated_at)}</p>
    </div>
    )
  }
}

If this is your first foray into React, then, like I felt recently, this code looks weird. I'm not a React expert, but the render method is simply defining the structure for our widget, similar to HTML with a couple of differences. For example, to add classes to an element in React, you use the className attribute. The curly brackets in the template are for variable injection. this.props contains the properties we set on our widget when we added it to the dashboard, which this.state contains the data sent from our JIRA job.

At this point to see the empty widget on your dashboard, add the following to dashboards/issues.html.eex in place of the List widget that we had previously defined:

<div class="widget-jira"  
     data-source="high_priority_list"
     data-widget="JiraTable"
     data-title="High Priority Issues"
     data-moreinfo="Blocker and Critical"></div>

You will also need to add a couple styles to the jira_list.scss file for the widget to look right:

// ----------------------------------------------------------------------------
// Sass declarations
// ----------------------------------------------------------------------------
$background-color:  #12b0c5;

$title-color:       rgba(255, 255, 255, 0.7);
$zebra-color:       rgba(255, 255, 255, 0.3);
$moreinfo-color:    rgba(255, 255, 255, 0.7);

// ----------------------------------------------------------------------------
// Widget-JiraTable styles
// ----------------------------------------------------------------------------
.widget-jiratable {
  background-color: $background-color;

  tbody tr td:last-child { text-align: left; }
  tbody tr:nth-child(even) { background-color: $zebra-color; }

  .priority-icon {
    width: 20px;
    height: 20px;
  }

  .issue-key { font-size: 0.8rem; }

  .more-info, .updated-at { color: $moreinfo-color; }
}

Now back to building our widget.

Let's start adding records to our table:

<table>  
  <thead>
    <tr>
      <th></th>
      <th>Key</th>
      <th>Summary</th>
    </tr>
  </thead>
  <tbody>{this.renderRows(this.state.items || [])}</tbody>
</table>  

Our renderRows method in the JiraTable class maps over the items in the state and renders a TableRow for each of them, passing the elements of the issue as properties to the TableRow:

renderRows(rows) {  
  return rows.map((item, i) => {
    return <TableRow key={i}
                     id={item.id}
                     summary={item.summary}
                     summaryLength={+this.props.summaryLength}
                     priority={item.priority.name}
                     priorityIcon={item.priority.iconUrl}/>;
  });
}

The TableRow definition ends up being simple. All we need to do is add the tr tag and td tags for each property:

class TableRow extends React.Component {  
  render() {
    return (
      <tr>
        <td><img className="priority-icon" src={this.props.priorityIcon} alt={this.props.priority} /></td>
        <td className="issue-key">{this.props.id}</td>
        <td className="issue-summary">{truncate(this.props.summary, this.props.summaryLength || 80)}</td>
      </tr>
    )
  }
}

There's a lot that you can add to the JiraTable definition to improve the appearance, but I hope that this gives you a good starting point to start writing your own definitions. If you want to see the sample dashboard that I've built, you can take a look at the GitHub repo

Work is being done towards building a package manager for Kitto for installing custom widgets and jobs. In the meantime, you can look for and upload widgets and jobs on the wiki.

Dave Long

Read more posts by this author.

Subscribe to Dave Long

Get the latest posts delivered right to your inbox.

or subscribe via RSS with Feedly!