Issue Browser - just for fun

Want to browse your issues in a grid format?
Export them to Excel?
Export them to PDF?

  1. Save the text below to an html document to your desktop
  2. Edit line 72 and with the URL to your YouTrack restful server
  3. Use the examples near line 100 to support your custom fields
  4. Launch it in a browser window

It's not a perfect solution to this type of reporting, but helps us.
Do you have any fixes / improvements?

<!DOCTYPE html>
<!– Written by:  Michael Petryk  11/22/2014   –>

<html ng-app="app">
    <base href="">
    <style>html {
        font-size: 12px;
        font-family: Arial, Helvetica, sans-serif;
    <link rel="stylesheet" href="2014.3.1119/styles/kendo.common.min.css"/>
    <link rel="stylesheet" href="2014.3.1119/styles/kendo.blueopal.min.css"/>
    <link rel="stylesheet" href="2014.3.1119/styles/"/>
    <link rel="stylesheet" href="2014.3.1119/styles/kendo.dataviz.min.css"/>
    <link rel="stylesheet" href="2014.3.1119/styles/kendo.dataviz.default.min.css"/>

    <script src="2014.3.1119/js/jquery.min.js"></script>
    <script src="2014.3.1119/js/angular.min.js"></script>
    <script src="2014.3.1119/js/jszip.min.js"></script>
    <script src="2014.3.1119/js/kendo.all.min.js"></script>

<div ng-controller="appCtrl">
    <label for="projectList">Project:</label>
    <select id="projectList" kendo-combo-box
            k-placeholder="'Select project'"
            style="width: 200px;">
    <label for="typeList">Type:</label>
    <select id="typeList" kendo-combo-box
            k-placeholder="'Select type'"
            style="width: 200px;">
    <label for="tagList">Tag:</label>
    <select id="tagList" kendo-combo-box
            k-placeholder="'Select tag'"
            style="width: 200px;">
    <label for="resolved">Resolved:</label>
    <input id="resolved" kendo-mobile-switch ng-model="Resolved" k-on-change="reloadGrid();"/>
    <label for="string">Search String:</label>
    <input id="string" kendo-masked-text-box ng-model="searchString" k-on-change="reloadGrid();"/>
    <button kendo-button class="k-primary" ng-click="reloadGrid()" k-icon="'refresh'">Refresh</button>
        <div kendo-grid="grid" k-options="gridOptions" k-ng-delay="gridOptions"></div>
    angular.module("app", ["kendo.directives"])
            .controller("appCtrl", function ($scope, $http) {
<!–  Put your URL to YOUTRACK here –>
var YouTrackUrl = "";
var project = ;
var filter =
var issues;
var types = [];
var tags = [];
$scope.tags = tags;
$scope.projects = new{
     transport: {
         read: {
             url: YouTrackUrl + "rest/project/all",
             dataType: "json"
     pageSize: 15
$scope.reloadGrid = function (clearCombo) {
     if ($scope.Project) {
         filter = '?filter=';
         if ($scope.Type) filter += "type:+" + $scope.Type + "+";
         if ($scope.Tag) filter += "tag:+" + $scope.Tag + "+";
         filter += ($scope.Resolved) ? "%23Resolved" : "%23Unresolved";
         if ($scope.searchString) filter += "+" + $scope.searchString;
         project = $scope.Project;
         types = [];
         tags = [
         var issues = $http.get(YouTrackUrl + "rest/issue/byproject/" + project + filter + "&max=1000")
  .success(function (data, status, headers, config) {
      angular.forEach(data, function (issue, key) {
          angular.forEach(issue.field, function (field, key) {
              issue[] = field.value;
          issue.created = new Date(parseInt(issue.created));
          issue.updated = new Date(parseInt(issue.updated));
          if (issue.Assignee) issue.Assignee = issue.Assignee[0].fullName;
          if (issue["Fix versions"]) issue.FixVersion = issue["Fix versions"][0];
          if (issue.Percent) issue.Percent = issue.Percent[0];
          if (issue.Priority) issue.Priority = issue.Priority[0];
          if (issue.Promote) issue.Promote = issue.Promote[0];
          if (issue.QMSR) issue.QMSR = issue.QMSR[0];
          if (issue.Reference)issue.Reference = issue.Reference[0];
          if (issue.Subsystem) issue.Subsystem = issue.Subsystem[0];
          if (issue.State) issue.State = issue.State[0];
          if (issue.tag.length > 0) {
              issue.Tag = issue.tag[0].value;
              if (tags.indexOf(issue.Tag) == -1) tags.push(issue.Tag);
          if (issue.Type.length > 0) {
              issue.Type = issue.Type[0];
              if (types.indexOf(issue.Type) == -1) types.push(issue.Type);
          if (issue.comment.length > 0) {
              var dat = new Date(parseInt(issue.comment[issue.comment.length - 1].created));
              issue.lastComment = dat.toDateString() + " - " + issue.comment[issue.comment.length - 1].text;
      if (clearCombo) {
          $scope.tags = tags;
          $scope.types = types;
      return data.issue;
function onChanged(arg) {
     var selected = $.map(, function (item) {
         return $(item).text();
     var a = selected[0];
     if (a.indexOf(project) == 0) { + 'issue/' + a, "PM");

$scope.gridOptions = {
         change: onChanged,
         columnMenu: true,
         dataSource: issues,
         editable: "inline",
         filterable: true,
         groupable: true,
         pageable: {pageSize: 30, pageSizes: [15, 30, 100, 250, 1000]},
         reorderable: true,
         selectable: 'cell',
         resizable: true,
         sortable: true,
         toolbar: ["excel", "pdf"
excel: {
     fileName: "Kendo UI Grid Export.xlsx",
columns: [
         field: "id",
         width: "100px",
         title: "ID",
         attributes: {"class": "k-button k-button-icontext"}
     {field: "State", width: "150px"},
     {field: "summary", title: "Summary"},
     {field: "Reference", width: "150px"},
     {field: "lastComment", title: "Last Comment", hidden: true},
     {field: "QMSR", width: "150px"},
     {field: "Assignee", width: "150px"},
     {field: "Promote", hidden: true},
     {field: "Subsystem", hidden: true, width: "150px"},
     {field: "Type", hidden: true, width: "150px"},
     {field: "Tag", hidden: true, width: "150px"},
     {field: "description", hidden: true},
     {field: "FixVersion", hidden: true, width: "100px"},
     {field: "Priority", hidden: true, width: "100px"},
     {field: "Percent", hidden: true, width: "100px"},
     {field: "updated", type: 'date', format: "{0:MM/dd/yyyy hh:mm tt}", hidden: true},
     {field: "created", type: 'date', format: "{0:MM/dd/yyyy hh:mm tt}", hidden: true}
Comment actions Permalink
I'm not able to get this to work. Inspecting the network requests, I can see that accesses to the REST API respond "Unauthorized". However, I can open the REST API url directly and it shows the XML results.

What am I doing wrong? I've enabled Allow All Origins in my YT.
Comment actions Permalink
Loading this from a file:// url doesn't work. Cookies aren't being sent along with the request to the API.
Comment actions Permalink
Correct.   You load it as an HTML file onto your web server

Comment actions Permalink
Thanks! I read "Save the text below to an html document to your desktop" wrong.

Please sign in to leave a comment.