require 'curb' require 'sinatra/base' require 'sinatra/partial' require 'rack-flash' require 'json' require 'jira' require 'tilt/erb' require 'logger' require_relative 'helpers' require_relative 'metadata' module OpsAsk class App < Sinatra::Base include OpsAsk::Helpers @room = nil @room_json = '{}' set :root, OpsAsk::ROOT enable :logging configure :development do set :logging, Logger::DEBUG end # Add flash support enable :sessions use Rack::Flash # Add partials support register Sinatra::Partial set :partial_template_engine, :erb enable :partial_underscores # Serve up our form get '/' do debug 'get-/' erb :index, locals: { jiras_for_today: issues_for(today), jiras_for_tomorrow: issues_for(tomorrow), untracked_jiras: untracked_issues, stragglers: straggling_issues } end get '/glance' do debug 'get-/glance' erb :days, locals: { jiras_by_day: issues_for_days(2), untracked_jiras: untracked_issues, stragglers: straggling_issues } end get '/week' do debug 'get-/week' erb :days, locals: { jiras_by_day: issues_for_days(4), untracked_jiras: untracked_issues, stragglers: straggling_issues } end get '/days/:n' do debug 'get-/days/:n' n, m = params[:n].split('+', 2) n = n.to_i m = m.nil? ? 0 : m.to_i erb :days, locals: { jiras_by_day: issues_for_days(n, m), untracked_jiras: untracked_issues, stragglers: straggling_issues } end get '/room' do debug 'get-/room' content_type :json @room ||= {} if @room.empty? debug 'RECALC ROOM!' (0..settings.config[:room_lookahead]).each do |n| date = today n * one_day @room[date] = room_for_new_jiras_for? date end @room_json = JSON.generate(@room) end @room_json end get '/untracked' do debug 'get-/untracked' erb :untracked, locals: { untracked_jiras: untracked_issues, stragglers: straggling_issues } end get '/stragglers' do debug 'get-/stragglers' erb :stragglers, locals: { untracked_jiras: untracked_issues, stragglers: straggling_issues } end get '/sprint/:sprint_num' do debug 'get-/sprint/:n' num = params[:sprint_num] sprint = get_sprint(num) id = sprint['id'] erb :stats, locals: { sprint: sprint, ask_stats: stats_for( asks_in_sprint(num), "#{settings.config[:jira_url]}/issues/?jql=#{URI::escape normalized_jql("labels in (#{sprint_label_prefix}#{num}) and resolution is not empty", project: nil)}", "#{settings.config[:jira_url]}/issues/?jql=#{URI::escape normalized_jql("labels in (#{sprint_label_prefix}#{num}) and resolution is empty", project: nil)}", "#{settings.config[:jira_url]}/issues/?jql=#{URI::escape normalized_jql("labels in (#{sprint_label_prefix}#{num}) and \"Story Points\" is empty", project: nil)}" ), sprint_stats: stats_for( items_in_sprint(num), "#{settings.config[:jira_url]}/issues/?jql=#{URI::escape normalized_jql("sprint = #{id} and resolution is not empty", project: nil)}", "#{settings.config[:jira_url]}/issues/?jql=#{URI::escape normalized_jql("sprint = #{id} and resolution is empty", project: nil)}", "#{settings.config[:jira_url]}/issues/?jql=#{URI::escape normalized_jql("sprint = #{id} and \"Story Points\" is empty", project: nil)}" ), untracked_jiras: untracked_issues, stragglers: straggling_issues, outlier_stats: { outlier_link: outlier_link(sprint, num), outlier_issues: outlier_issues(sprint, num) } } end get '/sprint' do debug 'get-/sprint' num = current_sprint_num id = current_sprint_id sprint = current_sprint erb :stats, locals: { sprint: sprint, ask_stats: stats_for( asks_in_current_sprint, "#{settings.config[:jira_url]}/issues/?jql=#{URI::escape normalized_jql("labels in (#{sprint_label_prefix}#{num}) and resolution is not empty", project: nil)}", "#{settings.config[:jira_url]}/issues/?jql=#{URI::escape normalized_jql("labels in (#{sprint_label_prefix}#{num}) and resolution is empty", project: nil)}", "#{settings.config[:jira_url]}/issues/?jql=#{URI::escape normalized_jql("labels in (#{sprint_label_prefix}#{num}) and \"Story Points\" is empty", project: nil)}" ), sprint_stats: stats_for( items_in_current_sprint, "#{settings.config[:jira_url]}/issues/?jql=#{URI::escape normalized_jql("sprint = #{id} and resolution is not empty", project: nil)}", "#{settings.config[:jira_url]}/issues/?jql=#{URI::escape normalized_jql("sprint = #{id} and resolution is empty", project: nil)}", "#{settings.config[:jira_url]}/issues/?jql=#{URI::escape normalized_jql("sprint = #{id} and \"Story Points\" is empty", project: nil)}" ), untracked_jiras: untracked_issues, stragglers: straggling_issues, outlier_stats: { outlier_link: outlier_link(sprint, num), outlier_issues: outlier_issues(sprint, num) } } end # I think everyone should do this get '/version' do debug 'get-/version' content_type :txt "opsask #{settings.config[:app_version]}" end get '/v' do debug 'get-/v' content_type :txt settings.config[:app_version] end # Try to create a JIRA post '/' do debug 'post-/' component, summary, description, assign_to_me, epic, ops_only, datepicker = validate_jira_params if datepicker.nil? || datepicker.empty? duedate = date_for_new_jiras else duedate = datepicker end jira = create_jira duedate, component, summary, description, assign_to_me, epic, ops_only if jira.nil? or !jira.has_key?('key') flash[:error] = [ %Q| Failure! JIRA had an issue processing your request. Try again? | ] else flash[:notice] = [ %Q| Success! #{jira['key']} has been created on your behalf. | ] end @room = nil # will recalculate redirect '/' end # Public assets %w[ css img js fonts ].each do |asset| get "/#{asset}/:file" do debug 'get-/%s/:file' % asset send_file "public/#{asset}/#{params[:file]}", :disposition => 'inline' end end get '/favicon.ico' do debug 'get-/favicon' send_file 'public/favicon.ico', :disposition => 'inline' end # This section gets called before every request. Here, we set up the # OAuth consumer details including the consumer key, private key, # site uri, and the request token, access token, and authorize paths before do debug 'before-1' options = { :site => settings.config[:jira_url], :context_path => '', :signature_method => 'RSA-SHA1', :request_token_path => "#{settings.config[:jira_url]}/plugins/servlet/oauth/request-token", :authorize_url => "#{settings.config[:jira_url]}/plugins/servlet/oauth/authorize", :access_token_path => "#{settings.config[:jira_url]}/plugins/servlet/oauth/access-token", :private_key_file => settings.config[:jira_private_key], :rest_base_path => "#{settings.config[:jira_url]}/rest/api/latest", :consumer_key => settings.config[:jira_consumer_key] } debug 'before-2' @jira_client = JIRA::Client.new(options) @jira_client.consumer.http.set_debug_output(logger) debug 'before-3' # Add AccessToken if authorised previously. if session[:jira_auth] @jira_client.set_access_token( session[:jira_auth][:access_token], session[:jira_auth][:access_key] ) if @project.nil? @project = @jira_client.Project.find(settings.config[:project_key]) end end debug 'before-4' # Keep a pointer to myself begin response = @jira_client.get( @jira_client.options[:rest_base_path] + '/myself?expand=groups' ) @myself = JSON::parse response.body @me = @myself['name'] rescue JIRA::OauthClient::UninitializedAccessTokenError end back_to = request.fullpath debug 'before-5 (back_to=%s)' % back_to.inspect case back_to when /callback/ when /login/ when /logout/ when /room/ else session[:back_to] = back_to end end # Retrieves the @access_token then stores it inside a session cookie. In a real app, # you'll want to persist the token in a datastore associated with the user. get '/callback/' do debug 'callback-1' request_token = @jira_client.set_request_token( session[:request_token], session[:request_secret] ) debug 'callback-2' access_token = @jira_client.init_access_token( :oauth_verifier => params[:oauth_verifier] ) debug 'callback-3' session[:jira_auth] = { :access_token => access_token.token, :access_key => access_token.secret } debug 'callback-4' session.delete(:request_token) session.delete(:request_secret) back_to = session.delete(:back_to) || '/' debug 'callback-5' redirect back_to end # Initialize the JIRA session get '/login' do debug 'get-/login-1' if logged_in? debug 'get-/login-3 (was logged in)' session.clear redirect '/logout' end debug 'get-/login-2 (was logged out)' request_token = @jira_client.request_token session[:request_token] = request_token.token session[:request_secret] = request_token.secret debug 'get-login-3 request_token: %s' % request_token.inspect redirect request_token.authorize_url end # Expire the JIRA session get '/logout' do debug 'get-/logout' session.clear redirect '/' end helpers do def pluralize n, item case n when 0 ; '%s %ss' when 1 ; '%s %s' else ; '%s %ss' end % [ n, item ] end def debug message logger.debug JSON.generate({ message: message, myself: @myself, me: @me }) end end end end