-
-
Save Actine/e26325f93b0ff9d98029 to your computer and use it in GitHub Desktop.
TestRail Demo Plugin
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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