MaximilianMeister/nailed

View on GitHub
app.rb

Summary

Maintainability
C
1 day
Test Coverage
#!/usr/bin/env ruby

require "sinatra/base"
require "sprockets"
require "haml"
require "json"
require "time"
require_relative "lib/nailed"
require_relative "db/model"

class App < Sinatra::Base

  Nailed::Config.parse_config
  Nailed::Cache.initialize

  enable :logging
  set :bind, "0.0.0.0"
  set :port, Nailed::Config.content["port"] || 4567
  theme = Nailed::Config.content["theme"] || "default"

  before do
    @title = Nailed::Config.content["title"] || "Dashboard"
    @bugzilla = Nailed::Config.content.fetch("bugzilla")
    @products = Nailed::Config.products
    @product_query = @products.join("&product=")
    @orgs = Nailed::Config.organizations["github"]
    @org_query = @orgs.map { |o| o.name.dup.prepend("user%3A") }.join("+") unless @orgs.nil?
    @supported_vcs = Nailed::Config.supported_vcs
    @changes_repos = get_repos
    @colors = Nailed.get_colors
    @jenkins_jobs = Nailed::Config.jobs

    DB.tables.select{|s| s.to_s.include?('trends')}.each do |table|
      DB.run("CREATE TEMP VIEW IF NOT EXISTS #{table.to_s.concat("_view")} "\
             "AS SELECT * FROM #{table} WHERE date(time) > date('now', '-1 year')")
    end
  end

  # sprockets asset management:
  assets = Sprockets::Environment.new

  assets.append_path File.join(__dir__, "assets/stylesheets/")
  assets.append_path File.join(__dir__, "assets/javascript/")
  assets.append_path File.join(__dir__, "assets/images/")
  assets.append_path File.join(__dir__, "assets/fonts/")

  get "/assets/*" do
    env["PATH_INFO"].sub!("/assets", "")
    assets.call(env)
  end

  get "/fonts/*" do
    env["PATH_INFO"].sub!("/fonts", "")
    assets.call(env)
  end

  # generic helpers:
  helpers do

    def get_trends(action, item)
      call = "#{__method__}-#{action}-#{item})".to_sym
      json = Nailed::Cache.get_cache(call)

      return json unless json.nil?

      json = []
      case action
      when :bug
        table = "bugtrends_view";
        bugs = ""
        open_fixed = "#{item.each_index.map{|x| ["t#{x}.open", "t#{x}.fixed"]}
          .transpose.map{|x| x.join(" + ")}.join(' AS open_bugs, ')} AS fixed_bugs"
        item.each_with_index do |product, i|
          bugs.concat("LEFT JOIN (SELECT date(time) AS date#{i}, open, fixed " \
                      "FROM #{table} WHERE product_name = '#{product}' AND " \
                      "open != 0 GROUP BY date(time)) AS t#{i} ON times = date#{i} ")
        end
        trends = DB.fetch("SELECT DISTINCT date(time) as times, " \
                          "#{open_fixed} FROM #{table} #{bugs}WHERE " \
                          "open_bugs IS NOT NULL;").naked.all
        filter(trends).each do |col|
          json << { time: col[:times],
                    open: col[:open_bugs], fixed: col[:fixed_bugs] }
        end
      when :change
        trends = DB[:changetrends_view].select(:time, :open)
          .where(oname: item[0], rname: item[1]).naked.all
        filter(trends).each do |col|
          json << { time: col[:time].strftime("%Y-%m-%d %H:%M:%S"),
                    open: col[:open] }
        end
      when :allopenchanges
        table = "allchangetrends_view"
        origin = ""
        @supported_vcs.each do |vcs|
          origin.concat("LEFT JOIN (SELECT time as t_#{vcs}, open as #{vcs} " \
                        "FROM #{table} WHERE origin='#{vcs}') ON time=t_#{vcs} ")
        end
        trends = DB.fetch("SELECT time, #{@supported_vcs.join(', ')} " \
                          "FROM ((SELECT DISTINCT time FROM #{table}) " \
                          "#{origin})").naked.all
        filter(trends).each do |col|
          json << col.merge({time: col[:time].strftime("%Y-%m-%d %H:%M:%S")})
        end
      when :allbugs
        trends = DB[:allbugtrends_view].order_by(:time).naked.all
        filter(trends).each do |col|
          json << { time: col[:time].strftime("%Y-%m-%d %H:%M:%S"),
                    open: col[:open] }
        end
      when :l3
        trends = DB[:l3trends_view].naked.all
        filter(trends).each do |col|
          json << { time: col[:time].strftime("%Y-%m-%d %H:%M:%S"),
                    open: col[:open] }
        end
      end

      json = json.to_json
      Nailed::Cache.set_cache(call, json)

      return json
    end

    # Receives a product and checks for given components. 
    # If there is none the label becomes the name of the product.
    # If there is one '/$components' is added to the products name.
    # If there is more than one component '/subset' is added.
    def get_label(product)
      components = Nailed::Config.components[product]
      label = components.nil? ? product : product + "/#{components.length > 1 ? 'subset' : components.fetch(0)}"
    end

    def combine(product)
      (comb = Nailed::Config.combined.fetch(
        product, false)) ? comb : Array(product)
    end

    def get_repos
      Hash[ @supported_vcs.collect {
        |vcs| [vcs, Changerequest.select(:oname, :rname).
               distinct.where(origin: vcs).naked.map(&:values)]
      }]
    end

    def filter(data, datapoints = 40)
        filtered = Hash.new
        num = data.length/datapoints
        data.first.keys.each do |key|
          filtered[key] = data.each_slice(num.zero? ? num.succ : num)
            .map{|slice| slice.map{|value| value[key].nil? ? 0 : value[key]}.max}
        end
        filtered.values.transpose.map{|value| Hash[filtered.keys.zip(value)]}
    end

    ### jenkins helpers ###

    # returns a formatted string with all build parameters for the popover
    def get_jenkins_build_parameters(job, build_number)
      all_build_parameters = Jenkinsparametervalue.where(
        jenkinsbuild_job: job,
        jenkinsbuild_number: build_number)

      description = Jenkinsbuild.where(
        number: build_number,
        job: job
      ).map(&:description)[0] || ""

      ret = ""
      all_build_parameters.each do |bp|
        ret += bp.jenkinsparameter_name + " = " + bp.value + "\n" unless bp.value.empty?
      end

      ret + "\nequal_builds:" + get_equal_builds(job, build_number).to_s +
        "\ndescription: " + description.split("/").to_s
    end

    # find equal jenkins builds
    def get_equal_builds(job, build_number)
      Jenkinsbuild.where(
        job: job,
        number: build_number
      ).map(&:equal_builds)[0].split(",").take(10)
    end

    # generates a view object for a specific build parameter within a job
    # TODO: Refactor function to execute faster (e.g. execute for all params combined)
    #       For now the data is just being cached for one hour.
    def get_jenkins_view_object(job, parameter)

      call = "#{__method__}-#{job}-#{parameter})".to_sym
      view_object = Nailed::Cache.get_cache(call, 3600)

      return view_object unless view_object.nil?

      view_object = {}
      all_parameters = Jenkinsparametervalue.where(
        jenkinsparameter_name: parameter,
        jenkinsparameter_job: job,
        jenkinsbuild_number: Jenkinsbuild.exclude(result: nil).select(:number)
        ).map(&:value).uniq.sort

      all_parameters.each do |parameter_name|
        view_object[parameter_name] = {}
        all_builds_with_parameter = Jenkinsparametervalue.where(
          jenkinsparameter_name: parameter,
          jenkinsparameter_job: job,
          value: parameter_name)
          .order(Sequel.desc(:jenkinsbuild_number)).limit(15)

        all_builds_with_parameter.each do |build|
          build_number = build.jenkinsbuild_number
          build_details = Jenkinsbuild.where(job: job, number: build_number)
          build_details.each do |build_detail|
            view_object[parameter_name][build_number] = {
              build_url: build_detail.url,
              build_result: build_detail.result,
              build_parameters: get_jenkins_build_parameters(job, build_number)
            }
          end
        end
      end

      Nailed::Cache.set_cache(call, view_object)
      view_object
    end
  end

  #
  # BUGZILLA Routes
  #

  #
  # bar
  #
  Nailed::Config.products.each do |product|
    get "/json/bugzilla/#{product.tr(" ", "_")}/bar/priority" do
      bugprio = []
      { "P0 - Crit Sit" => "p0",
        "P1 - Urgent"   => "p1",
        "P2 - High"     => "p2",
        "P3 - Medium"   => "p3",
        "P4 - Low"      => "p4",
        "P5 - None"     => "p5" }.each_pair do |key, val|
        bugprio << { "bugprio" => key, val => Bugreport.where(product_name: combine(product), priority: key, is_open: true).count }
      end
      bugprio.to_json
    end
  end
  #
  # status
  #
  Nailed::Config.products.each do |product|
    get "/json/bugzilla/#{product.tr(" ", "_")}/bar/status" do
      bugstatus = []
      { "NEW"         => 's0',
        "CONFIRMED"   => 's1',
        "IN_PROGRESS" => 's2',
        "REOPENED"    => 's3' }.each_pair do |key, val|
        bugstatus << { "bugstatus" => key,
                       val => Bugreport.where(
                         product_name: combine(product),
                         status: key,
                         is_open: true).count }
      end
      bugstatus.to_json
    end
  end

  #
  # trends
  #
  Nailed::Config.products.each do |product|
    get "/json/bugzilla/#{product.tr(" ", "_")}/trend/open" do
      get_trends(:bug, combine(product))
    end
  end

  get "/json/bugzilla/trend/allopenl3" do
    get_trends(:l3, nil)
  end

  get "/json/bugzilla/trend/allbugs" do
    get_trends(:allbugs, nil)
  end

  #
  # donut
  #
  Nailed::Config.products.each do |product|
    get "/json/bugzilla/#{product.tr(" ", "_")}/donut/component" do
      top5_components = []
      top_components = Bugreport
                         .select(:component, :is_open)
                         .where(is_open: true,
                                product_name: combine(product))
                         .group_and_count(:component)
                         .order(Sequel.desc(:count))
                         .limit(5).all
      top_components.each do |component|
        top5_components << { label: component.component, value: component[:count] }
      end
      top5_components.to_json
    end
  end

  get "/json/bugzilla/donut/allbugs" do
    bugtop = []
      Nailed::Config.products.each do |product|
        open = Bugreport.where(product_name: combine(product), is_open: true).count
        bugtop << { label: get_label(product), value: open } unless open == 0
      end
    bugtop.to_json
  end

  #
  # tables
  #

  # allopen
  get "/json/bugzilla/allopen" do
    Bugreport.where(is_open: true).naked.all.to_json
  end

  # allopenwithoutl3
  get "/json/bugzilla/allopenwithoutl3" do
    (Bugreport.where(is_open: true).naked.all - Bugreport.where(is_open: true).where(Sequel.like(:whiteboard, "%openL3%")).naked.all).to_json
  end

  # allopenl3
  get "/json/bugzilla/allopenl3" do
    Bugreport.where(is_open: true).where(Sequel.like(:whiteboard, "%openL3%")).naked.all.to_json
  end

  # product -> openwithoutl3
  Nailed::Config.products.each do |product|
    get "/json/bugzilla/#{product.tr(" ", "_")}/openwithoutl3" do
      combination = combine(product)
      open_bugs = Bugreport.where(is_open: true,
                                  product_name: combination).naked.all
      open_l3_bugs = Bugreport
                       .where(is_open: true,
                              product_name: combination)
                       .where(Sequel.like(:whiteboard, "%openL3%")).naked.all
      (open_bugs - open_l3_bugs).to_json
    end
  end

  # product -> openl3
    Nailed::Config.products.each do |product|
      get "/json/bugzilla/#{product.tr(" ", "_")}/openl3" do
        Bugreport
          .where(is_open: true,
                 product_name: combine(product))
          .where(Sequel.like(:whiteboard, "%openL3%")).naked.all.to_json
      end
    end

  #
  # CHANGES Routes
  #

  #
  # trends
  #
  changes_repos = Changerequest.order(Sequel.desc(:created_at)).all.map do |row|
    [row.oname, row.rname]
  end.uniq

  changes_repos.each do |repo|
    get "/json/changes/#{repo[0]}/#{repo[1]}/trend/open" do
      get_trends(:change, repo)
    end
  end

  # all open change requests
  get "/json/changes/trend/allopenchanges" do
    get_trends(:allopenchanges, nil)
  end

  #
  # donut
  #
  get "/json/changes/donut/allchanges" do
    Changerequest.where(state: "open", origin: @supported_vcs)
      .group_and_count(:rname, :oname, :origin).all.map {
      |change| { label: "#{change.oname}/#{change.rname}",
                 value: change[:count],
                origin: change.origin }
    }.to_json
  end

  #
  # tables
  #

  # allopenchanges
  Nailed::Config.supported_vcs.each do |vcs|
    get "/json/#{vcs}/allopenchanges" do
      Changerequest.where(state: "open", origin: vcs).naked.all.to_json
    end
  end

  # all open pull requests for repo
  Changerequest.select(:oname, :rname).distinct.where(state: "open").order(Sequel.desc(:created_at)).map do |repo|
    get "/json/changes/#{repo.oname}/#{repo.rname}/open" do
      Changerequest.where(
        state: "open",
        rname: repo.rname,
        oname: repo.oname).naked.all.to_json
    end
  end

  #
  # JENKINS Routes
  #

  Nailed::Config.jobs.each do |job|
    get "/jenkins/#{job}" do

      @job = job
      @view_object = {}
      blacklist = Nailed::Config.content["jenkins"]["blacklist"]["parameter"] || []
      all_parameters = Jenkinsparameter.where(job: job).map(&:name).sort_by(&:downcase)
      all_parameters.each do |parameter|
        next if blacklist.include? parameter
        @view_object[parameter] = get_jenkins_view_object(job, parameter)
      end

      haml :jenkins
    end
  end unless Nailed::Config.jobs.empty?

  #
  # MAIN Routes
  #

  get "/" do
    haml :index
  end

  Nailed::Config.products.each do |product|
    get "/#{product.tr(" ", "_")}/bugzilla" do
      @product = product
      @product_ = product.tr(" ", "_")
      @bugzilla_url = "#{@bugzilla}/buglist.cgi?product="

      haml :bugzilla
    end
  end

  Nailed::Config.supported_vcs.each do |vcs|
    Changerequest.select(:oname, :rname, :url).distinct.where(origin: vcs).order(Sequel.desc(:created_at)).all.map do |repo|
      get "/#{vcs}/#{repo.oname}/#{repo.rname}" do
        url = repo.url.rpartition("/").first

        @org = repo.oname
        @repo = repo.rname
        @url = url.concat(url.end_with?("s") ? "" : "s")

        haml :changes
      end
    end
  end

  get "/json/help" do
    routes = []
    App.routes["GET"].each do |route|
      # avoids drawing asset routes:
      if route[0].to_s.include? '*'
        next
      end
      routes << { route: route[0].to_s }
    end
    routes.uniq.to_json
  end

  get "/help" do
    haml :help
  end

  run! if app_file == $PROGRAM_NAME
end