require 'curb' require 'sinatra/base' require 'sinatra/partial' require 'rack-flash' require 'json' require 'jira' require 'tilt/erb' require_relative 'helpers' require_relative 'metadata' module OpsAsk class App < Sinatra::Base include OpsAsk::Helpers set :root, OpsAsk::ROOT # 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 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 erb :days, locals: { jiras_by_day: issues_for_days(2), untracked_jiras: untracked_issues, stragglers: straggling_issues } end get '/week' do erb :days, locals: { jiras_by_day: issues_for_days(4), untracked_jiras: untracked_issues, stragglers: straggling_issues } end get '/days/:n' do 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 '/untracked' do erb :untracked, locals: { untracked_jiras: untracked_issues, stragglers: straggling_issues } end get '/stragglers' do erb :stragglers, locals: { untracked_jiras: untracked_issues, stragglers: straggling_issues } end get '/sprint/:sprint_num' do 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 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 content_type :txt "opsask #{settings.config[:app_version]}" end # Try to create a JIRA post '/' do duedate = validate_room_for_new_jiras component, summary, description, assign_to_me, epic, ops_only = validate_jira_params 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 redirect '/' end # Public assets %w[ css img js fonts ].each do |asset| get "/#{asset}/:file" do send_file "public/#{asset}/#{params[:file]}", :disposition => 'inline' end end get '/favicon.ico' do 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 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] } @jira_client = JIRA::Client.new(options) # @jira_client.consumer.http.set_debug_output($stderr) # 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 # 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 case back_to when /callback/ when /login/ 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 request_token = @jira_client.set_request_token( session[:request_token], session[:request_secret] ) access_token = @jira_client.init_access_token( :oauth_verifier => params[:oauth_verifier] ) session[:jira_auth] = { :access_token => access_token.token, :access_key => access_token.secret } session.delete(:request_token) session.delete(:request_secret) back_to = session.delete(:back_to) || '/' redirect back_to end # Initialize the JIRA session get '/login' do if logged_in? session.clear redirect '/logout' end request_token = @jira_client.request_token session[:request_token] = request_token.token session[:request_secret] = request_token.secret redirect request_token.authorize_url end # Expire the JIRA session get '/logout' do session.delete(:jira_auth) 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 end end end