Skip to content

Instantly share code, notes, and snippets.

@Actine
Last active August 29, 2015 14:23
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Actine/e26325f93b0ff9d98029 to your computer and use it in GitHub Desktop.
Save Actine/e26325f93b0ff9d98029 to your computer and use it in GitHub Desktop.
TestRail Demo Plugin
name: TestRail demo plugin
description: Host script for TestRail demo plugin
author: Paul "Actine" Danyliuk
version: 0.1
includes: ^suites/view
excludes:
js:
var dateFormat = 'mm/dd/yyyy';
(function (window, $, uiscripts) {
/**
* Loads and manages communication with the plugin
*/
var DemoPlugin = (function () {
var pluginOrigin = 'https://actinarium.github.io';
var pluginSource = pluginOrigin + '/testrail-plugin-demo/';
var pluginTitle = 'TestRail Demo Plugin';
var $body = $('body');
var $dimmer;
var $pluginHost;
var pluginWindow;
var _prepare = function() {
$dimmer = $('#plugin-dimmer');
if ($dimmer.length == 0) {
$dimmer = $('<div id="plugin-dimmer"></div>').hide().appendTo($body);
}
$pluginHost = $('#plugin-host-dialog');
if ($pluginHost.length == 0) {
$pluginHost = $(
'<div id="plugin-host-dialog">\
<div id="plugin-host-title">\
<a href="#" id="plugin-host-title-close"></a>\
<div id="plugin-host-title-text"></div>\
</div>\
<div id="plugin-host-content"></div>\
</div>'
).hide().appendTo($body);
}
};
var _loadPlugin = function(src, title, onCloseFn) {
_prepare();
var $pluginIFrame = $('<iframe class="plugin" src="' + src + '"></iframe>');
$pluginHost.find('#plugin-host-content').append($pluginIFrame);
$pluginHost.find('#plugin-host-title-text').text(title);
$pluginHost.find('#plugin-host-title-close').off('click').on('click', onCloseFn);
};
var startPlugin = function () {
// Load plugin into HTML, invoking loading the iframe
_loadPlugin(pluginSource, pluginTitle, closePlugin);
// Remove any listeners that might be present already
window.removeEventListener('message', _receiveMessage, false);
window.addEventListener('message', _receiveMessage, false);
// Display only dimmer for now - the iframe will be displayed later
$body.addClass('plugin-active');
$dimmer.show();
};
var closePlugin = function () {
window.removeEventListener('message', _receiveMessage, false);
pluginWindow = null;
$pluginHost.find('#plugin-host-content').empty();
$pluginHost.hide();
$dimmer.hide();
$body.removeClass('plugin-active');
};
/**
* Listener for events from the plugin
* @param event
*/
var _receiveMessage = function (event) {
var defer;
if (event.origin !== pluginOrigin) {
console.warn("Plugin event received from unsafe origin, ignoring");
return;
}
// For old IE which cannot transmit objects
//var data = (typeof event.data === 'object') ? event.data : JSON.parse(event.data);
var data = event.data;
// Arbitrate messages by type
switch (data.type) {
case 'attach' : {
pluginWindow = event.source;
pluginWindow.postMessage({
type: 'attached',
host: document.location.origin,
user: uiscripts.context.user,
pageBase: uiscripts.env.page_base || 'index.php?',
dateFormat: dateFormat
}, pluginOrigin);
$pluginHost.show();
break;
}
case 'request_cases_config' : {
defer = RestClient.requestCasesFieldsConfig();
$.when(defer).done(function(response) {
pluginWindow.postMessage({
type: 'request_cases_config_response',
body: response
}, pluginOrigin);
});
break;
}
case 'save_case' : {
defer = RestClient.saveTestCase(data.body);
$.when(defer).done(function(data) {
pluginWindow.postMessage({
type: 'save_case_response',
success: (data.result !== false),
payload: data
}, pluginOrigin);
}).fail(function() {
pluginWindow.postMessage({
type: 'save_case_response',
success: false,
payload: {error: 'Unexpected response from REST API. Maybe try again later, or give up and seek help?'}
}, pluginOrigin);
});
break;
}
case 'detach' : {
closePlugin();
if (!!data.redirectTo) {
window.location = data.redirectTo;
}
break;
}
default : {
console.warn('Unexpected plugin event type', data);
}
}
};
return {
startPlugin : startPlugin,
closePlugin : closePlugin
}
})();
/**
* Methods that remove unnecessary data (e.g. users' emails or fields not attached to current project) before passing it to the plugin
*/
var Strip = (function () {
var _stripField = function (field, config) {
return {
type_id: field.type_id,
system_name: field.system_name,
label: field.label,
description: field.description,
display_order: field.display_order,
options: config.options
};
};
var _stripMilestoneOrUser = function (milestoneOrUser) {
return {
id: milestoneOrUser.id,
name: milestoneOrUser.name
}
};
/**
* Walk over the case fields and filter away everything that's irrelevant to the current project
* @param fields Array of fields as acquired from RESTv2
* @return {Array} Array of output data
*/
var stripFields = function (fields) {
var filtered = [];
for (var i = 0; i < fields.length; i++) {
// Underconfigured field. Discard.
if (typeof fields[i].configs === 'undefined') {
continue;
}
// Field can be either global, or we need to search for the matching config
if (fields[i].configs.length == 1 && fields[i].configs[0].context.is_global) {
filtered.push(_stripField(fields[i], fields[i].configs[0]));
} else {
var matchingConfig = -1;
for (var j = 0; j < fields[i].configs.length; j++) {
if (fields[i].configs[j].context.project_ids.indexOf(uiscripts.context.project.id) !== -1) {
matchingConfig = j;
break;
}
}
// Neither of configs matched current project? Discard. Otherwise add.
if (matchingConfig !== -1) {
filtered.push(_stripField(fields[i], fields[i].configs[matchingConfig]));
}
}
}
return filtered;
};
/**
* Walk over milestones, filter away completed ones, and strip excessive data
* @param milestones Array of milestones as acquired from RESTv2
* @return {Array} Array of output data
*/
var stripMilestones = function (milestones) {
var filtered = [];
for (var i = 0; i < milestones.length; i++) {
if (!milestones[i].is_completed) {
filtered.push(_stripMilestoneOrUser(milestones[i]));
}
}
return filtered;
};
/**
* Walk over users, filter away inactive ones, and strip excessive data such as emails
* @param users Array of users as acquired from RESTv2
* @return {Array} Array of output data
*/
var stripUsers = function (users) {
var filtered = [];
for (var i = 0; i < users.length; i++) {
if (users[i].is_active) {
filtered.push(_stripMilestoneOrUser(users[i]));
}
}
return filtered;
};
// Expose the API
return {
stripFields: stripFields,
stripMilestones: stripMilestones,
stripUsers: stripUsers
}
})();
/**
* So far a collection of methods for this user script to talk to TestRail's REST API
* @type {{requestAjax: Function, requestCasesFieldsConfig: Function}}
*/
var RestClient = (function() {
var cached = false;
var fields;
var types;
var priorities;
var sections;
var milestones;
var users;
var requestAjax = function (url, defer) {
defer = defer || $.Deferred();
$.ajax({
url: url,
contentType: 'application/json',
success: function (data) {
defer.resolve(data);
},
error: function (data, textStatus, errorThrown) {
defer.reject({data: data, textStatus: textStatus, errorThrown: errorThrown});
}
});
return defer.promise();
};
var requestCasesFieldsConfig = function () {
var defer = $.Deferred();
// Spare the server of re-requesting data each time the plugin is opened and closed
if (!cached) {
fields = requestAjax('index.php?/api/v2/get_case_fields');
types = requestAjax('index.php?/api/v2/get_case_types');
priorities = requestAjax('index.php?/api/v2/get_priorities');
sections = requestAjax('index.php?/api/v2/get_sections/' + uiscripts.context.project.id + "&suite_id=" + uiscripts.context.suite.id);
milestones = requestAjax('index.php?/api/v2/get_milestones/' + uiscripts.context.project.id);
// Also request users if at least one case field is of type User
users = $.Deferred();
$.when(fields).done(function (fields) {
for (var i = 0; i < fields.length; i++) {
if (fields[i].type_id == 7) {
requestAjax('index.php?/api/v2/get_users', users);
return;
}
}
users.resolve([]);
});
cached = true;
}
$.when(fields, types, priorities, sections, milestones, users)
.done(function (fields, types, priorities, sections, milestones, users) {
var response = {
fields: Strip.stripFields(fields),
types: types,
priorities: priorities,
sections: sections,
milestones: Strip.stripMilestones(milestones),
users: Strip.stripUsers(users)
};
defer.resolve(response);
})
.fail(function (args) {
defer.reject(args);
});
return defer;
};
var saveTestCase = function(testCase) {
return $.ajax({
url: 'index.php?/api/v2/add_case/' + testCase.section_id,
type: 'POST',
contentType: 'application/json',
data: JSON.stringify(testCase)
});
};
// API
return {
requestCasesFieldsConfig: requestCasesFieldsConfig,
saveTestCase: saveTestCase
}
})();
window.DemoPlugin = DemoPlugin;
/* =================================================== LAUNCHER =================================================== */
$(window.document).ready(function () {
// Create and attach the button to launch PoC plugin
var $DemoPluginBtn = $('<a id="launchDemoPlugin" href="#" style="margin-left: 8px" class="toolbar-button button-add">Demo plugin</a>');
$DemoPluginBtn.on('click', function () {
DemoPlugin.startPlugin();
});
var $addCase = $('#addCase');
$addCase.css('margin', '0');
$addCase.after($DemoPluginBtn);
});
})(window, $, uiscripts);
/* ======================================================= CSS ====================================================== */
css:
body.plugin-active {
overflow: hidden;
}
#plugin-dimmer {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 99998;
background: #000000;
opacity: .2;
}
#plugin-host-dialog {
position: fixed;
top: 48px;
right: 48px;
bottom: 48px;
left: 48px;
background: #fff;
-webkit-box-shadow: 2px 2px 8px rgba(0, 0, 0, .5);
-moz-box-shadow: 2px 2px 8px rgba(0, 0, 0, .5);
box-shadow: 2px 2px 8px rgba(0, 0, 0, .5);
border: 1px solid #505050;
z-index: 99999;
}
#plugin-host-title {
font-family: Helvetica, Arial, sans-serif;
position: absolute;
top: 0;
left: 0;
right: 0;
color: #ffffff;
font-weight: bold;
height: 32px;
padding-left: 12px;
background: #205081;
line-height: 32px;
}
#plugin-host-content {
position: absolute;
top: 32px;
bottom: 0;
width: 100%;
}
#plugin-host-title-close {
background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAsAAAALCAYAAACprHcmAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAYdEVYdFNvZnR3YXJlAHBhaW50Lm5ldCA0LjAuNWWFMmUAAABhSURBVChTjZBBCgAhDAN9gv+/7GGf4sO6jWxCLD0YGFScKHRExDXczAR5EhdwRnAv2cMCRUZyvejOkrsCs0XgcleQCFx8ky4rOeQq1h92gbKHL2H1SOac9eUPC8ecL4jxATPHoAhI4e/oAAAAAElFTkSuQmCC) no-repeat center;
display: block;
float: right;
width: 32px;
height: 32px;
text-align: center;
}
iframe.plugin {
width: 100%;
height: 100%
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment