require 'highline' require 'harvested' require 'github_api' module FireWatch class Runner def self.invoke cli = HighLine.new # Github Client github_login = cli.ask('Github Login: ') github_password = cli.ask('Github Password: ') { |q| q.echo = false } @github_client = Github.new(auto_pagination: true) do |config| config.basic_auth = "#{github_login}:#{github_password}" if cli.agree("Do you use Two-Factor authentication (non-sms)?") config.connection_options = { headers: {"X-GitHub-OTP" => cli.ask('Two-Factor Code')} } end end repos = @github_client.repos.list(org: 'wildland').sort_by{|r| r.name} selected_repo = nil cli.choose do |menu| menu.prompt = "Select Github Repo:" repos.each do |r| menu.choice(r.name) do selected_repo = r end end end milestones = @github_client.issues.milestones.list(user: 'wildland',repo: selected_repo.name, state: 'all').sort_by{|m| m.title} selected_milestones = [] done = false loop do cli.choose do |menu| menu.prompt = "Add/Remove Milestones (#{selected_milestones.map{|p| p.title}.join(', ')}): " milestones.each do |m| menu.choice(m.title) do if selected_milestones.include? m selected_milestones.delete(m) else selected_milestones.push(m) end end end menu.choice("Done Adding Milestones") { done = true } end break if done end # Harvest Client harvest_username = cli.ask('Harvest Username: ') harvest_password = cli.ask('Harvest Password: ') { |q| q.echo = false } @harvest_client = Harvest.client(username: harvest_username, password: harvest_password, subdomain: 'wildland') projects = @harvest_client.projects.all.sort_by{|p| p.name} selected_projects = [] done = false loop do cli.choose do |menu| menu.prompt = "Add/Remove Project (#{selected_projects.map{|p| p.name}.join(', ')}): " projects.each do |p| menu.choice(p.name) do if selected_projects.include? p selected_projects.delete(p) else selected_projects.push(p) end end end menu.choice("Done Adding Projects") { done = true } end break if done end harvest_users = @harvest_client.users.all time_entries = [] selected_projects.each do |project| if project.starts_on.nil? puts "No start date for #{project.name}" next end time_entries += @harvest_client.reports.time_by_project( project, Time.parse(project.starts_on), project.ends_on.nil? ? Time.now : Time.parse(project.ends_on) ) end #Process Data csv_file_name = cli.ask('File name for report?').concat('.csv') CSV.open(csv_file_name, "wb", force_quotes: true) do |csv| csv << [ "Repo", "Milestone", "Issue Number", "Issue Title", "Project", "Who", "Scope Time", "Actual Time", "Status", "Merged", "Labels" ] selected_milestones.each do |selected_milestone| issues, pull_request_issues = @github_client.issues.list(user: 'wildland', repo: selected_repo.name, milestone: selected_milestone.number, state: 'all').partition{|i| i.pull_request == nil} pull_requests = pull_request_issues.map do |i| @github_client.pull_requests.get(user: 'wildland', repo: selected_repo.name, number: i.number).body end issues.each do |issue| relevant_time_entries, time_entries = time_entries.partition do |entry| match_data = /.?#(?\d+)/.match(entry.notes) if match_data.nil? false else match_data["issue_number"].eql? issue.number.to_s end end if relevant_time_entries.empty? csv << [ selected_repo.name, selected_milestone.title, issue.number, issue.title, "", "", issue.labels.nil? || issue.labels.find{|l| l.name.include?"size:"}.nil? ? "" : issue.labels.find{|l| l.name.include?"size:"}.name, "", issue.state, "", issue.labels.nil? ? "" : issue.labels.compact.map{|l| l.name}.join(', ') ] else relevant_time_entries.chunk{|i| i.user_id }.each do |user_id, user_time_entries| user = harvest_users.find{|u| u.id == user_id} csv << [ selected_repo.name, selected_milestone.title, issue.number, issue.title, projects.select{|p| user_time_entries.map(&:project_id).uniq.include? p.id}.map(&:name).join(', '), "#{user.first_name} #{user.last_name}", # Who issue.labels.nil? ? "" : issue.labels.find{|l| l.name.include?"size:"}.name, user_time_entries.inject(0){|sum, e| sum + e.hours }, issue.state, "", issue.labels.nil? ? "" : issue.labels.map{|l| l.name}.join(', ') ] end end end pull_requests.each do |pr| relevant_time_entries, time_entries = time_entries.partition do |entry| match_data = /.?#(?\d+)/.match(entry.notes) if match_data.nil? false else match_data["issue_number"].eql? pr.number.to_s end end if relevant_time_entries.empty? csv << [ selected_repo.name, selected_milestone.title, pr.number, pr.title, "", "", pr.labels.nil? ? "" : pr.labels.find{|l| l.name.include?"size:"}.name, "", pr.state, pr.merged ? "Y" : "N", pr.labels.nil? ? "" : pr.labels.map{|l| l.name}.join(', ') ] else relevant_time_entries.chunk{|i| i.user_id }.each do |user_id, user_time_entries| user = harvest_users.find{|u| u.id == user_id} csv << [ selected_repo.name, selected_milestone.title, pr.number, pr.title, projects.select{|p| user_time_entries.map(&:project_id).uniq.include? p.id}.map(&:name).join(', '), "#{user.first_name} #{user.last_name}", # Who pr.labels.nil? ? "" : issue.labels.find{|l| l.name.include?"size:"}.name, user_time_entries.inject(0){|sum, e| sum + e.hours }, pr.state, pr.merged ? "Y" : "N", pr.labels.nil? ? "" : issue.labels.map{|l| l.name}.join(', ') ] end end end end end if time_entries.count > 0 && cli.agree("#{time_entries.count} Time entries not in this milestone. Create a list of them?") time_entries_file_name = cli.ask('File name for report?').concat('.csv') CSV.open(time_entries_file_name, "wb", force_quotes: true) do |csv| csv << [ "Created At", "Who", "Hours", "Notes" ] time_entries.each do |entry| csv << [ entry.created_at, "#{harvest_users.find{|u| u.id == entry.user_id}.first_name} #{harvest_users.find{|u| u.id == entry.user_id}.last_name}", entry.hours, entry.notes ] end end end rescue Github::Error::GithubError => e puts 'Github API Error' puts e.to_s rescue Harvest::AuthenticationFailed puts 'Unable to authenticate to Harvest. Check username/password.' rescue Harvest::HTTPError => e puts 'Harvest API Error' puts e.to_s end end end