Esta versão do GitHub Enterprise foi descontinuada em 2021-06-09. Nenhum lançamento de patch será feito, mesmo para questões críticas de segurança. Para obter melhor desempenho, melhorar a segurança e novos recursos, upgrade to the latest version of GitHub Enterprise. Para ajuda com a atualização, contact GitHub Enterprise support.

Representar dados como gráficos

Aprenda a visualizar as linguagens de programação do seu repositório usando a biblioteca D3.js e o Ruby Octokit.

Neste guia, vamos usar a API para obter informações sobre repositórios dos quais somos proprietários e as linguagens de programação que as compõem. Em seguida, vamos visualizar essas informações de algumas formas diferentes usando a biblioteca D3.js. Para interagir com a API de GitHub Enterprise Server, vamos usar a excelente biblioteca do Ruby, Octokit.

Caso você ainda não o tenha feito, você deve ler o guia "Princípios básicos da autentifcação" antes de iniciar este exemplo. Você pode encontrar o código-fonte completo para este projeto no repositório de platform-samples.

Vamos começar imediatamente!

Configurar um aplicativo OAuth

Primeiro, registra um novo aplicativo no GitHub Enterprise Server. Define as URLs principais e a chamada de retorno para http://localhost:4567/. Assim como fizemosanteriormente, vamos gerenciar a autenticação da API implementando um Rack middleware usando sinatra-auth-github:

require 'sinatra/auth/github' module Example class MyGraphApp < Sinatra::Base # !!! DO NOT EVER USE HARD-CODED VALUES IN A REAL APP !!! # Instead, set and test environment variables, like below # if ENV['GITHUB_CLIENT_ID'] && ENV['GITHUB_CLIENT_SECRET'] # CLIENT_ID = ENV['GITHUB_CLIENT_ID'] # CLIENT_SECRET = ENV['GITHUB_CLIENT_SECRET'] # end CLIENT_ID = ENV['GH_GRAPH_CLIENT_ID'] CLIENT_SECRET = ENV['GH_GRAPH_SECRET_ID'] enable :sessions set :github_options, { :scopes => "repo", :secret => CLIENT_SECRET, :client_id => CLIENT_ID, :callback_url => "/" } register Sinatra::Auth::Github get '/' do if !authenticated? authenticate! else access_token = github_user["token"] end end end end 

Configure um arquivo config.ru semelhante ao exemplo anterior:

ENV['RACK_ENV'] ||= 'development' require "rubygems" require "bundler/setup" require File.expand_path(File.join(File.dirname(__FILE__), 'server')) run Example::MyGraphApp 

Buscar informações do repositório

Dessa vez, para falar com a API GitHub Enterprise Server, vamos usar a a biblioteca do Ruby, Octokit. Isso é muito mais fácil do que fazer diretamente um monte de chamadas de REST. Além disso, o Octokit foi desenvolvido por um GitHubber e é mantido ativamente, para que você saiba que vai funcionar.

É fácil a autenticação com a API através do Octokit. Basta passar seu login e token para o Octokit::Client do cliente:

if !authenticated? authenticate! else octokit_client = Octokit::Client.new(:login => github_user.login, :oauth_token => github_user.token) end 

Vamos fazer algo interessante com os dados sobre nossos repositórios. Vamos ver as diferentes linguagens de programação que eles usam e contar quais são usadas com maior frequência. Para fazer isso, primeiro precisaremos de uma lista dos nossos repositórios na API. Com o Octokit, será algo parecido com isso:

repos = client.repositories 

Em seguida, vamos iterar sobre cada repositório e contar a linguagem que GitHub Enterprise Server associa a ele:

language_obj = {} repos.each do |repo| # sometimes language can be nil if repo.language if !language_obj[repo.language] language_obj[repo.language] = 1 else language_obj[repo.language] += 1 end end end languages.to_s 

Ao reiniciar seu servidor, sua página web deve exibir algo que se parece com isso:

{"JavaScript"=>13, "PHP"=>1, "Perl"=>1, "CoffeeScript"=>2, "Python"=>1, "Java"=>3, "Ruby"=>3, "Go"=>1, "C++"=>1} 

Até agora, tudo bem, mas isso não é não muito intuitivo para uma pessoa. Uma visualização seria excelente para nos ajudar a entender como as contagens de linguagens são distribuídas. Vamos alimentar nossas contagens em D3 para obter um gráfico de barras que represente a popularidade dos idiomas que usamos.

Visualizar contagens de linguagem

D3.js, ou apenas D3, é uma biblioteca abrangente para criar muitos tipos de gráficos, gráficos e visualizações interativas. Usar D3 em detalhes está fora do âmbito deste guia, mas para um bom artigo introdutório consulte "D3 para mortais".

D3 é uma biblioteca JavaScript, e gosta de trabalhar com dados como arrays. Então, vamos converter o nosso hash do Ruby em um array de JSON para uso por JavaScript no navegador.

languages = [] language_obj.each do |lang, count| languages.push :language => lang, :count => count end erb :lang_freq, :locals => { :languages => languages.to_json} 

Estamos simplesmente iterando sobre cada par chave-valor no nosso objeto e empurrando-os para um novo array. A razão pela qual não fizemos isso anteriormente foi porque não queríamos iterar sobre o nosso objeto language_obj enquanto o estávamos criando.

Agora, lang_freq.erb vai precisar de um pouco de JavaScript para ajudar a interpretação de um gráfico de barras. Por enquanto, você pode simplesmente usar o código fornecido aqui e consultar os recursos vinculados acima se quiser saber mais sobre como o D3 funciona:

<!DOCTYPE html> <meta charset="utf-8"> <html> <head> <script src="//cdnjs.cloudflare.com/ajax/libs/d3/3.0.1/d3.v3.min.js"></script> <style> svg { padding: 20px; } rect { fill: #2d578b } text { fill: white; } text.yAxis { font-size: 12px; font-family: Helvetica, sans-serif; fill: black; } </style> </head> <body> <p>Check this sweet data out:</p> <div id="lang_freq"></div> </body> <script> var data = <%= languages %>; var barWidth = 40; var width = (barWidth + 10) * data.length; var height = 300; var x = d3.scale.linear().domain([0, data.length]).range([0, width]); var y = d3.scale.linear().domain([0, d3.max(data, function(datum) { return datum.count; })]). rangeRound([0, height]); // add the canvas to the DOM var languageBars = d3.select("#lang_freq"). append("svg:svg"). attr("width", width). attr("height", height); languageBars.selectAll("rect"). data(data). enter(). append("svg:rect"). attr("x", function(datum, index) { return x(index); }). attr("y", function(datum) { return height - y(datum.count); }). attr("height", function(datum) { return y(datum.count); }). attr("width", barWidth); languageBars.selectAll("text"). data(data). enter(). append("svg:text"). attr("x", function(datum, index) { return x(index) + barWidth; }). attr("y", function(datum) { return height - y(datum.count); }). attr("dx", -barWidth/2). attr("dy", "1.2em"). attr("text-anchor", "middle"). text(function(datum) { return datum.count;}); languageBars.selectAll("text.yAxis"). data(data). enter().append("svg:text"). attr("x", function(datum, index) { return x(index) + barWidth; }). attr("y", height). attr("dx", -barWidth/2). attr("text-anchor", "middle"). text(function(datum) { return datum.language;}). attr("transform", "translate(0, 18)"). attr("class", "yAxis"); </script> </html> 

Ufa! Novamente, não se preocupe com o que a maior parte deste código está fazendo. Aqui, a parte relevante é uma linha na parte superior--var data = <%= languages %>;--que indica que estamos passando o nosso array de linguagens criado previamente para o ERB para manipulação.

Como o guia "D3 para mortais" sugere, este não é necessariamente a melhor forma de utilizar o D3. No entanto, serve para ilustrar como você pode usar a biblioteca, junto com Octokit, para fazer algumas coisas realmente incríveis.

Combinar diferentes chamadas de API

Agora é hora de fazer uma confissão: o atributo da linguagem dentro dos repositórios identifica apenas a linguagem "primária" definida. Isso significa que se você tiver um repositório que combina várias linguagens. aquela que tiver mais bytes de código será considerada a linguagem primária.

Vamos combinar algumas chamadas de API para obter uma verdadeira representação de qual linguagem tem o maior número de bytes escritos em todo o nosso código. A treemap should be a great way to visualize the sizes of our coding languages used, rather than simply the count. Precisamos construir um array de objetos que se pareçam com isto:

[ { "name": "language1", "size": 100}, { "name": "language2", "size": 23} ... ] 

Como já temos uma lista de repositórios acima, vamos inspecionar cada um e chamar o método da API para listar a linguagem:

repos.each do |repo| repo_name = repo.name repo_langs = octokit_client.languages("#{github_user.login}/#{repo_name}") end 

A partir daí, adicionaremos cumulativamente cada linguagem encontrado a uma "lista-mestre":

repo_langs.each do |lang, count| if !language_obj[lang] language_obj[lang] = count else language_obj[lang] += count end end 

Em seguida vamos formatar o conteúdo em uma estrutura que o D3 entende:

language_obj.each do |lang, count| language_byte_count.push :name => "#{lang} (#{count})", :count => count end # some mandatory formatting for D3 language_bytes = [ :name => "language_bytes", :elements => language_byte_count] 

(Para obter mais informações sobre um mapa de árvore do D3, confira este tutorial simples.)

Para concluir, passamos esta informação JSON para o mesmo modelo de ERB:

erb :lang_freq, :locals => { :languages => languages.to_json, :language_byte_count => language_bytes.to_json} 

Conforme fizemos anteriormente, aqui está um monte de JavaScript que você pode soltar diretamente no seu modelo:

<div id="byte_freq"></div> <script> var language_bytes = <%= language_byte_count %> var childrenFunction = function(d){return d.elements}; var sizeFunction = function(d){return d.count;}; var colorFunction = function(d){return Math.floor(Math.random()*20)}; var nameFunction = function(d){return d.name;}; var color = d3.scale.linear() .domain([0,10,15,20]) .range(["grey","green","yellow","red"]); drawTreemap(5000, 2000, '#byte_freq', language_bytes, childrenFunction, nameFunction, sizeFunction, colorFunction, color); function drawTreemap(height,width,elementSelector,language_bytes,childrenFunction,nameFunction,sizeFunction,colorFunction,colorScale){ var treemap = d3.layout.treemap() .children(childrenFunction) .size([width,height]) .value(sizeFunction); var div = d3.select(elementSelector) .append("div") .style("position","relative") .style("width",width + "px") .style("height",height + "px"); div.data(language_bytes).selectAll("div") .data(function(d){return treemap.nodes(d);}) .enter() .append("div") .attr("class","cell") .style("background",function(d){ return colorScale(colorFunction(d));}) .call(cell) .text(nameFunction); } function cell(){ this .style("left",function(d){return d.x + "px";}) .style("top",function(d){return d.y + "px";}) .style("width",function(d){return d.dx - 1 + "px";}) .style("height",function(d){return d.dy - 1 + "px";}); } </script> 

Et voila! São lindos retângulos que contém suas linguagens de repositório, com proporções relativas de que são fáceis de ver rapidamente. Talvez você precise ajustar a altura e a largura do diagrama da sua árvore, passado como os dois primeiros argumentos para drawTreemap acima, para obter todas as informações para serem exibidas corretamente.