Skip to content

Instantly share code, notes, and snippets.

@thilo
Last active September 11, 2020 10:00
Show Gist options
  • Save thilo/799efaa774dca11c61969780e3623229 to your computer and use it in GitHub Desktop.
Save thilo/799efaa774dca11c61969780e3623229 to your computer and use it in GitHub Desktop.
An example how to use the bots platform to build a custom report

README

How it works

This example shows you how to build a custom report for Cobot with a CSV download within our Bots plattform. This platform is used to host client-side HTML/JS webapps within Cobot - you upload/edit the HTML/JS code online, and the platform takes care of running it. Data is loaded from Cobot's API. This makes it very fast and easy to create smaller add-ons.

To create a new bot:

  • log in at bots.apps.cobot.me
  • create a bot there by following the instructions
  • copy & paste the code below into the code editors for the bot
  • edit the code, depending on what data you want in your report, making use of the Cobot API documentation.

The example makes use of the ember.js framework. You should be able to leave most ember.js code as it is, only changing the pieces that are concerned with loading and reformatting data.

<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scaleable=no">
<title>Example Report</title>
</head>
<body>
<div class="page-header">
<h1>Example Report</h1>
</div>
<div id="app" class="main">
</div>
<!-- html template used by JavaScript code below -->
<script type="text/x-handlebars" data-template-name="report">
<p class="f--right"><a href="{{csv}}" download={{filename}} class="btn btn--primary btn--small">CSV</a></p>
<nav class="admin-nav-tertiary">
{{#if previousMonth}}
{{link-to previousMonth.label 'member_cancellations' previousMonth.year_month}}
{{/if}}
<a href="" class="active">{{currentMonth.label}}</a>
{{#if nextMonth}}
{{link-to nextMonth.label 'member_cancellations' nextMonth.year_month}}
{{/if}}
</nav>
<table class="admin-table table">
<thead>
<tr>
<th>Name</th>
<th>Plan</th>
<th>Date</th>
</tr>
</thead>
<tbody>
<!-- Showing the list of members -->
{{#each sortedMemberships as |m|}}
{{#if m.canceled_to}}
<tr>
<td>{{m.name}}</td>
<td>{{m.plan.name}}</td>
<td>{{date m.canceled_to}}</td>
</tr>
{{/if}}
{{else}}
<tr><td colspan="2">No members canceled this month.</td></tr>
{{/each}}
</tbody>
</table>
</script>
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<!-- the javascript helpers for the bot, you at least want to have that -->
<script src="https://bots.apps.cobot.me/javascripts/cobot_api.js"></script>
<!-- JS Framework files - we use ember.js in that example -->
<script src="//cdnjs.cloudflare.com/ajax/libs/ember.js/1.13.13/ember-template-compiler.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/ember.js/1.13.13/ember.js"></script>
<!-- lib for date/time conversion -->
<script src="//cdnjs.cloudflare.com/ajax/libs/moment.js/2.4.0/moment.min.js"></script>
</body>
</html>
(function() {
'use strict';
// initialize the cobot api client with the storred access token
var cobot = Cobot.Api(window.cobot.access_token);
// resize Cobot's iframe according to height of this app
window.setInterval(function() {
window.Cobot.iframeResize($('body').outerHeight());
}, 200);
// app
window.MemberCancellations = Ember.Application.create({rootElement: '#app'});
// helpers for the template
Ember.Handlebars.helper('amount', function(value) {
return parseFloat(value).toFixed(2);
});
Ember.Handlebars.helper('date', function(value) {
if(value) {
return value.substr(0, 10);
}
});
// routes
MemberCancellations.Router.map(function () {
this.route('index', { path: '/' });
this.route('member_cancellations', {path: '/member_cancellations/:year_month'})
});
MemberCancellations.IndexRoute = Ember.Route.extend({
redirect: function() {
this.transitionTo('member_cancellations', moment().format('YYYY-MM'));
}
});
MemberCancellations.MemberCancellationsRoute = Ember.Route.extend({
model: function(params) {
var monthMoment = moment(params.year_month + '-01'),
from = monthMoment.clone().startOf('month'),
to = monthMoment.clone().endOf('month');
return Ember.RSVP.hash({
memberships: this.loadMembers(from, to),
month: monthMoment
});
},
loadMembers: function(from, to) {
return new Ember.RSVP.Promise(function(resolve, reject) {
//making an api request to the activities for a space
cobotClient.get(cobot.subdomain, '/activities', {types: 'membership_canceled',
from: from.format('YYYY-MM-DD'), to: to.format('YYYY-MM-DD')}).then(function(activities) {
var ids = activities.map(function(a) {
return a.attributes.membership_id;
});
//Api request to get all memberships with the passed in ids.
cobotClient.get(cobot.subdomain, '/memberships', {ids: ids.join(',')}).then(function(memberships) {
resolve(memberships);
});
})
})
}
});
// controllers
MemberCancellations.MemberCancellationsController = Ember.Controller.extend({
memberships: Ember.computed('model.memberships', function() {
return this.get('model.memberships');
}),
membershipsSortBy: ['canceled_to'],
sortedMemberships: Ember.computed.sort('memberships', 'membershipsSortBy'),
csv: function() {
var csv = planChangesCSV(this.get('memberships'));
return 'data:text/csv;base64,' + csv;
// encoding the csv
function planChangesCSV(memberships) {
return b64EncodeUnicode(header() + _.chain(memberships).select(function(m) {
return m.get('canceled_to'); }).map(toRow).join("\n").value());
function toRow(m) {
return [
m.get('name'),
m.get('plan.name'),
m.get('canceled_to'),
].map(addQuotes).join(',');
}
function addQuotes(thing) {
if(typeof thing === 'string') {
return '"' + (thing + '').replace(/"/g, '\'') + '"';
} else {
return thing;
}
}
function header() {
return "Name,Plan,Date\n";
}
}
}.property('model'),
filename: function() {
return 'member_cancellations_' + this.get('currentMonth.date').format('YYYY-MM') + '.csv';
}.property('month'),
month: Ember.computed.readOnly('model.month'),
previousMonth: function() {
var previous = this.get('currentMonth.date').clone().subtract('month', 1);
return this._monthMetadata(previous);
}.property('month'),
currentMonth: function() {
var current = this.get('month');
return {label: current.format("MMM YYYY"), date: current};
}.property('month'),
nextMonth: function() {
var next = this.get('currentMonth.date').clone().add('month', 1);
if(!next.isAfter(moment().endOf('month'))) {
return this._monthMetadata(next);
}
}.property('month'),
_monthMetadata: function(month) {
return {label: month.format("MMM YYYY"), year_month: month.format('YYYY-MM')};
}
});
})();
// b64EncodeUnicode is used to encode unicode characters properly for the csv download.
function b64EncodeUnicode(str) {
return btoa(encodeURIComponent(str).replace(/%([0-9A-F]{2})/g, function(match, p1) {
return String.fromCharCode('0x' + p1);
}));
}
// The scss will be compiled and added to the bot when being embedded
// on Cobot using the new responsive layout (member section) or when not embedded.
// With cobot_assets/imports you have access to the following variables, which are based on the space's
// customizations:
// $color-primary (-4, -10, -20, -40, -50, -60, -70, -80, -90)
// $color-seconday (-4, -10, -20, -40, -50, -60, -70, -80, -90)
// $color-highlight (-4, -10, -20, -40, -50, -60, -70, -80, -90)
// $color-text
// $color-background
// the numbers represent color shades, where 90 is darkest and 4 is the brightest
// so, $color-primary-90 is very dark, $color-primary-4 is very bright
// Documentation for the default Cobot SCSS files can be found at https://www.cobot.me/styleguide?section=components
@import "cobot_assets/imports";
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment