Skip to content

Commit

Permalink
Merge branch 'master' of https://github.com/keepcosmos/terjira into d…
Browse files Browse the repository at this point in the history
…evelop
  • Loading branch information
Maciej Szlosarczyk committed Dec 27, 2016
2 parents b73c3de + 2009747 commit a00e603
Show file tree
Hide file tree
Showing 20 changed files with 329 additions and 80 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
full_test

.vagrant

*.gem
Expand Down
2 changes: 1 addition & 1 deletion Gemfile.lock
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
PATH
remote: .
specs:
terjira (0.2.5)
terjira (0.2.6)
activesupport (= 4.0.13)
jira-ruby (~> 1.1)
pastel (~> 0.6.1)
Expand Down
9 changes: 6 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@

# Terjira

Terjira is an interactive and easy to use command line interface (or Application) for Jira. You do not need to remember resource key or id. Terjira suggests it with interactive prompt.
Terjira is an interactive and easy to use command line interface (or Application) for Jira. You do not need to remember the resource key or id. Terjira suggests it with an interactive prompt.

Your Jira must support Rest API 2.0 and Agile Rest API 1.0

## Domo
## Demo
[Watch full demo](https://www.youtube.com/watch?v=T0hbhaXtH-Y)

[![Sample](./dev/demo.gif)](https://www.youtube.com/watch?v=T0hbhaXtH-Y)
Expand Down Expand Up @@ -58,6 +58,9 @@ Issue:
# default assignee option is current loggined user
# To show issues of all users(include no assignee)
                                    # pass `--assignee ALL` option.
jira issue jql "[QUERY]" # Search issues with JQL
# ex)
# jira issue jql "project = 'TEST' AND summary ~ 'authentication'"
jira issue [ISSUE_KEY] # Show detail of the issue
jira issue assign [ISSUE_KEY] ([ASSIGNEE]) # Assign the issue to user
jira issue comment [ISSUE_KEY] # Write comment on the issue
Expand All @@ -72,7 +75,7 @@ Issue:

## Todo
**Contributions are welcome!**
- [ ] Add JQL command for find issues
- [x] Add JQL command for find issues
- [ ] Manage worklog and estimate of issues
- [ ] Manage component and version of issues
- [ ] Track history of transitions
Expand Down
9 changes: 1 addition & 8 deletions lib/terjira/base_cli.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,9 @@

require_relative 'option_supportable'
Dir[File.dirname(__FILE__) + '/presenters/*.rb'].each { |f| require f }
Dir[File.dirname(__FILE__) + '/client/*.rb'].each { |f| require f }

module Terjira
# Jira client based on jira-ruby gem
module Client
%w(Base Field Issuetype Project Board Sprint Issue User
Status Resolution Priority RapidView Agile).each do |klass|
autoload klass, "terjira/client/#{klass.gsub(/(.)([A-Z](?=[a-z]))/,'\1_\2').downcase}"
end
end

class BaseCLI < Thor
include OptionSupportable

Expand Down
18 changes: 14 additions & 4 deletions lib/terjira/client/field.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
module Terjira
module Client
class Field < Base
CACHE_PATH = "resource/fields".freeze

class << self
def all
@all_fields ||= file_cache.fetch("all") do
Expand All @@ -14,16 +16,24 @@ def find_by_key(key)
all.find { |field| field.key == key }
end

def find_by_name(name)
all.find { |field| field.name == name }
end

def epic_name
all.find { |field| field.name == 'Epic Name' }
find_by_name('Epic Name')
end

def epic_link
find_by_name('Epic Link')
end

def epiclink
all.find { |field| field.name == 'Epic Link' }
def story_points
find_by_name('Story Points')
end

def file_cache
Terjira::FileCache.new("resource/fields", 60 * 60 * 24)
Terjira::FileCache.new(CACHE_PATH, 60 * 60 * 24)
end
end
end
Expand Down
4 changes: 2 additions & 2 deletions lib/terjira/client/issue.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@ module Terjira
module Client
class Issue < Base
class << self
delegate :build, :find, to: :resource
delegate :jql, :find, to: :resource

def all(options = {})
return resource.all if options.blank?
max_results = options.delete(:max_results) || 500
resource.jql(build_jql(options), max_results: max_results)
jql(build_jql(options), max_results: max_results)
end

def all_epic_issues(epic)
Expand Down
20 changes: 20 additions & 0 deletions lib/terjira/client/status_category.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
require_relative 'base'

module Terjira
module Client
class StatusCategory < Base
class << self
def all
@all_statuscategory ||= file_cache.fetch("all") do
resp = api_get "statuscategory"
resp.map { |category| build(category) }
end
end

def file_cache
Terjira::FileCache.new("resource/statuscategory", 60 * 60 * 48)
end
end
end
end
end
27 changes: 21 additions & 6 deletions lib/terjira/ext/jira_ruby.rb
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,6 @@ def key_with_key_value
end

module Resource
class BoardFactory < JIRA::BaseFactory # :nodoc:
end

class EpicFactory < JIRA::BaseFactory # :nodoc:
end

class Board < JIRA::Base
def self.key_attribute; :id; end
end
Expand All @@ -41,6 +35,10 @@ class Epic < JIRA::Base
def self.key_attribute; :key; end
end

class StatusCategory < JIRA::Base
def self.key_attribute; :name; end
end

class User
def self.key_attribute; :name; end
end
Expand All @@ -60,12 +58,29 @@ def self.key_attribute; :name; end
class Resolution
def self.key_attribute; :name; end
end

class BoardFactory < JIRA::BaseFactory
end

class EpicFactory < JIRA::BaseFactory
end

class StatusCategoryFactory < JIRA::BaseFactory
end
end

class Client
def Board # :nodoc:
JIRA::Resource::BoardFactory.new(self)
end

def Epic
JIRA::Resource::EpicFactory.new(self)
end

def StatusCategory
JIRA::Resource::StatusCategoryFactory.new(self)
end
end
end

Expand Down
17 changes: 16 additions & 1 deletion lib/terjira/issue_cli.rb
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,23 @@ def open(issue)
map ls: :list
def list
opts = suggest_options
opts[:statusCategory] ||= %w(To\ Do In\ Progress) unless opts[:status]
opts[:statusCategory] ||= default_status_categories unless opts[:status]
opts[:assignee] ||= current_username
opts.delete(:assignee) if opts[:assignee] =~ /^all/i

issues = client_class.all(opts)
render_issues(issues)
end

desc 'jql "[QUERY]"', "Search issues with JQL"
long_desc <<-EXAMPLE
jira issue jql "project = 'IST' AND assignee = currentuser()"
EXAMPLE
def jql(*query)
jql = query.join(" ")
render_issues Client::Issue.jql(jql)
end

desc 'new', 'Create an issue'
jira_options :summary, :description, :project, :issuetype,
:priority, :assignee, :parent, :epiclink
Expand Down Expand Up @@ -127,5 +136,11 @@ def search(*keys)
search_term = client_class.search(summary: keys[0])
render_issues(search_term)
end

no_commands do
def default_status_categories
Client::StatusCategory.all.reject { |category| category.key =~ /done/i }.map(&:name)
end
end
end
end
2 changes: 1 addition & 1 deletion lib/terjira/option_supportable.rb
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ def suggest_related_value_options(opts = {})
end

if opts[:epiclink]
epiclink_field = Client::Field.epiclink
epiclink_field = Client::Field.epic_link
opts[epiclink_field.key] ||= opts.delete(:epiclink)
end

Expand Down
2 changes: 1 addition & 1 deletion lib/terjira/presenters/common_presenter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ def username(user)
if user.nil?
dim_none
else
"#{user.name}, #{user.displayName}"
"#{user.displayName}(#{user.name})"
end
end

Expand Down
50 changes: 31 additions & 19 deletions lib/terjira/presenters/issue_presenter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -62,36 +62,31 @@ def summarise_issue(issue)
def issue_detail_template
%{<%= bold(issue.key) + ' in ' + issue.project.name %>
<%= pastel.underline.bold(issue.summary) %>
<%= bold(issue.summary) %>
<%= bold('Type') %>: <%= colorize_issue_type(issue.issuetype) %>\s\s\s<%= bold('Status') %>: <%= colorize_issue_stastus(issue.status) %>\s\s\s<%= bold('priority') %>: <%= colorize_priority(issue.priority, title: true) %>
Type: <%= colorize_issue_type(issue.issuetype) %>\s\sStatus: <%= colorize_issue_stastus(issue.status) %>\s\sPriority: <%= colorize_priority(issue.priority, title: true) %>
<% if issue.parent.nil? -%>
<%= bold('Epic Link') %>: <%= issue.try(:epic).try(:key) %> <%= issue.try(:epic).try(:name) || dim_none %>
Epic: <%= issue.try(:epic).try(:key) %> <%= issue.try(:epic).try(:name) || dim_none %>
<% end -%>
<% if issue.try(:parent) && issue.epic.nil? -%>
<%= bold('Parent') %>: <%= issue.parent.key %>
Parent: <%= issue.parent.key %>
<% end %>
<% if issue.try(:sprint) -%>
<%= bold('Sprint') %>: <%= colorize_sprint_state(issue.try(:sprint).try(:state)) %> <%= issue.try(:sprint).try(:id) %>. <%= issue.try(:sprint).try(:name) %>
Sprint: <%= colorize_sprint_state(issue.try(:sprint).try(:state)) %> <%= issue.try(:sprint).try(:id) %>. <%= issue.try(:sprint).try(:name) %>
<% end -%>
<% if estimate = issue_estimate(issue) -%>
<%= bold('Assignee') %>: <%= username(issue.assignee) %>
<%= bold('Reporter') %>: <%= username(issue.reporter) %>
<%= estimate[0] %>: <%= estimate[1] %>
<% end -%>
<%= bold('Description') %>
<%= issue.description || dim_none %>
<% if issue.try(:timetracking).is_a? Hash -%>
Assignee: <%= username(issue.assignee) %>
Reporter: <%= username(issue.reporter) %>
<%= bold('Estimate') %>
<% if issue.timetracking['originalEstimate'] -%>
<%= issue.timetracking['originalEstimate'] %> / <%= issue.timetracking['remainingEstimate'] %>
<% else -%>
<%= dim_none %>
<% end -%>
<% end -%>
<%= issue.description || dim("No description") %>
<% if issue.try(:environment) -%>
<%= bold('Environment') %>
<%= Environment %>:
<%= issue.environment %>
<% end -%>
<% if issue.try(:attachment).present? -%>
Expand Down Expand Up @@ -120,7 +115,7 @@ def comments_template
"""
<% remain_comments = issue.comments -%>
<% visiable_comments = remain_comments.pop(COMMENTS_SIZE) -%>
<%= bold('Comments') %>
Comments:
<% if visiable_comments.empty? -%>
<%= dim_none %>
<% elsif remain_comments.size != 0 -%>
Expand Down Expand Up @@ -184,6 +179,23 @@ def colorize_priority(priority, opts = {})
pastel.decorate(title, infos[0])
end

# Extract estimate or story points
# @return Array, first element is title and second is value
def issue_estimate(issue)
field = Client::Field.story_points
story_points = issue.try(field.key) if field.respond_to? :key
return ['Story Points', story_points] if story_points

return unless issue.try(:timetracking).is_a? Hash

if origin = issue.timetracking['originalEstimate']
remain = issue.timetracking['remainingEstimate']
['Estimate', "#{remain} / #{origin}"]
else
['Estimate', dim_none]
end
end

def extract_status_names(issues)
issue_names = issues.sort_by do |issue|
status_key = %w(new indeterminate done)
Expand Down
2 changes: 1 addition & 1 deletion lib/terjira/presenters/sprint_presenter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ def summarise_sprint(sprint)
end

def colorize_sprint_state(state)
state = state.to_s.capitalize
state = " #{state.to_s.capitalize} "
if state =~ /active/i
pastel.on_blue.bold(state)
elsif state =~ /close/i
Expand Down
2 changes: 1 addition & 1 deletion lib/terjira/version.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
require 'terjira/utils/file_cache'

module Terjira
VERSION = '0.2.5'.freeze
VERSION = '0.2.6'.freeze

class VersionChecker
VERSION_CHECK_DURATION = (60 * 60 * 24 * 5).freeze
Expand Down
29 changes: 29 additions & 0 deletions spec/client/field_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
require 'spec_helper'
require 'terjira/client/field'
require 'terjira/utils/file_cache'

describe Terjira::Client::Field do
let(:fields) { MockResource.fields }

before :each do
allow(described_class).to receive(:all).and_return(fields)
end

it 'find by key' do
field = described_class.find_by_key('issuetype')
expect(field.key).to eq 'issuetype'
expect(field.name).to eq 'Issue Type'
end

it 'find by name' do
field = described_class.find_by_name('Story Points')
expect(field.key).to eq 'customfield_10022'
expect(field.name).to eq 'Story Points'
end

it 'find by named field method' do
expect(described_class.epic_name).to be_present
expect(described_class.epic_link).to be_present
expect(described_class.story_points).to be_present
end
end
4 changes: 4 additions & 0 deletions spec/issue_cli_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,14 @@
let(:boards) { MockResource.boards }
let(:sprints) { MockResource.sprints }
let(:issues) { MockResource.issues }
let(:fields) { MockResource.fields }
let(:status_categories) { MockResource.status_categories }

before :each do
allow(TTY::Screen).to receive(:width).and_return(10**4)
allow(TTY::Prompt).to receive(:new).and_return(prompt)
allow(Terjira::Client::Field).to receive(:all).and_return(fields)
allow(Terjira::Client::StatusCategory).to receive(:all).and_return(status_categories)
end

context '#show' do
Expand Down
Loading

0 comments on commit a00e603

Please sign in to comment.