caperoma 0.1.0 → 4.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.ruby-version +1 -0
- data/Capefile +48 -0
- data/Capefile.template +48 -0
- data/Capefile.test +20 -0
- data/Gemfile +25 -10
- data/Gemfile.lock +196 -77
- data/HELP +321 -0
- data/README.md +528 -0
- data/Rakefile +73 -18
- data/VERSION +1 -1
- data/bin/caperoma +47 -11
- data/caperoma.gemspec +144 -45
- data/config/crontab +10 -0
- data/config/schedule.rb +21 -0
- data/lib/caperoma.rb +409 -9
- data/lib/caperoma/models/account.rb +47 -0
- data/lib/caperoma/models/application_record.rb +5 -0
- data/lib/caperoma/models/branch.rb +6 -0
- data/lib/caperoma/models/project.rb +14 -0
- data/lib/caperoma/models/property.rb +5 -0
- data/lib/caperoma/models/report.rb +177 -0
- data/lib/caperoma/models/report_recipient.rb +6 -0
- data/lib/caperoma/models/reports/daily_report.rb +23 -0
- data/lib/caperoma/models/reports/retrospective_report.rb +19 -0
- data/lib/caperoma/models/reports/three_day_report.rb +19 -0
- data/lib/caperoma/models/task.rb +368 -0
- data/lib/caperoma/models/tasks/bug.rb +36 -0
- data/lib/caperoma/models/tasks/chore.rb +40 -0
- data/lib/caperoma/models/tasks/feature.rb +27 -0
- data/lib/caperoma/models/tasks/fix.rb +56 -0
- data/lib/caperoma/models/tasks/meeting.rb +40 -0
- data/lib/caperoma/models/tasks/modules/git.rb +65 -0
- data/lib/caperoma/models/tasks/task_with_commit.rb +40 -0
- data/lib/caperoma/models/tasks/task_with_separate_branch.rb +42 -0
- data/lib/caperoma/services/airbrake_email_processor.rb +47 -0
- data/lib/caperoma/services/pivotal_fetcher.rb +108 -0
- data/lib/caperoma/version.rb +9 -0
- data/spec/caperoma_spec.rb +3 -21
- data/spec/factories/accounts.rb +10 -0
- data/spec/factories/branches.rb +9 -0
- data/spec/factories/projects.rb +8 -0
- data/spec/factories/report_recipients.rb +7 -0
- data/spec/factories/reports.rb +16 -0
- data/spec/factories/tasks.rb +37 -0
- data/spec/features/bug_spec.rb +60 -0
- data/spec/features/chore_spec.rb +60 -0
- data/spec/features/command_unknown_spec.rb +14 -0
- data/spec/features/config_spec.rb +161 -0
- data/spec/features/feature_spec.rb +60 -0
- data/spec/features/finish_spec.rb +18 -0
- data/spec/features/fix_spec.rb +60 -0
- data/spec/features/meeting_spec.rb +22 -0
- data/spec/features/projects_spec.rb +17 -0
- data/spec/features/report_recipientss_spec.rb +117 -0
- data/spec/features/reports_spec.rb +65 -0
- data/spec/features/status_spec.rb +33 -0
- data/spec/features/version_spec.rb +11 -0
- data/spec/models/account_spec.rb +51 -0
- data/spec/models/branch_spec.rb +8 -0
- data/spec/models/bug_spec.rb +33 -0
- data/spec/models/chore_spec.rb +33 -0
- data/spec/models/daily_report_spec.rb +38 -0
- data/spec/models/feature_spec.rb +33 -0
- data/spec/models/fix_spec.rb +55 -0
- data/spec/models/meeting_spec.rb +33 -0
- data/spec/models/project_spec.rb +11 -0
- data/spec/models/report_recipient_spec.rb +22 -0
- data/spec/models/report_spec.rb +16 -0
- data/spec/models/retrospective_report_spec.rb +38 -0
- data/spec/models/task_spec.rb +613 -0
- data/spec/models/task_with_commit_spec.rb +105 -0
- data/spec/models/task_with_separate_branch_spec.rb +97 -0
- data/spec/models/three_day_report_spec.rb +49 -0
- data/spec/spec_helper.rb +26 -16
- data/spec/support/capefile_generator.rb +36 -0
- data/spec/support/database_cleaner.rb +21 -0
- data/spec/support/stubs.rb +178 -9
- metadata +283 -42
- data/.document +0 -5
- data/README.rdoc +0 -26
- data/lib/caperoma/credentials.rb +0 -13
- data/lib/caperoma/jira_client.rb +0 -57
- data/spec/caperoma/credentials_spec.rb +0 -25
- data/spec/caperoma/jira_spec.rb +0 -35
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Bug < TaskWithSeparateBranch
|
4
|
+
before_create :inform_creation_started
|
5
|
+
after_create :inform_creation_finished
|
6
|
+
|
7
|
+
private
|
8
|
+
|
9
|
+
def create_issue_on_pivotal_data
|
10
|
+
Jbuilder.encode do |j|
|
11
|
+
j.current_state 'unstarted'
|
12
|
+
j.name title.to_s
|
13
|
+
j.story_type story_type
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def this_is_a_type_a_user_wants_to_create?
|
18
|
+
project.create_bugs_in_pivotal
|
19
|
+
end
|
20
|
+
|
21
|
+
def story_type
|
22
|
+
'bug'
|
23
|
+
end
|
24
|
+
|
25
|
+
def issue_type
|
26
|
+
project.bug_jira_task_id
|
27
|
+
end
|
28
|
+
|
29
|
+
def inform_creation_started
|
30
|
+
puts 'Starting a new bug'
|
31
|
+
end
|
32
|
+
|
33
|
+
def inform_creation_finished
|
34
|
+
puts 'A new bug started'
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
class Chore < Task
|
3
|
+
before_create :inform_creation_started
|
4
|
+
after_create :inform_creation_finished
|
5
|
+
|
6
|
+
private
|
7
|
+
def create_issue_on_pivotal_data
|
8
|
+
Jbuilder.encode do |j|
|
9
|
+
j.current_state 'unstarted'
|
10
|
+
j.name title.to_s
|
11
|
+
j.story_type story_type
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def finish_on_pivotal_data
|
16
|
+
Jbuilder.encode do |j|
|
17
|
+
j.current_state 'accepted'
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def this_is_a_type_a_user_wants_to_create?
|
22
|
+
project.create_chores_in_pivotal
|
23
|
+
end
|
24
|
+
|
25
|
+
def story_type
|
26
|
+
'chore'
|
27
|
+
end
|
28
|
+
|
29
|
+
def issue_type
|
30
|
+
project.chore_jira_task_id
|
31
|
+
end
|
32
|
+
|
33
|
+
def inform_creation_started
|
34
|
+
puts 'Starting a new chore'
|
35
|
+
end
|
36
|
+
|
37
|
+
def inform_creation_finished
|
38
|
+
puts 'A new chore started'
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
class Feature < TaskWithSeparateBranch
|
3
|
+
before_create :inform_creation_started
|
4
|
+
after_create :inform_creation_finished
|
5
|
+
|
6
|
+
private
|
7
|
+
|
8
|
+
def this_is_a_type_a_user_wants_to_create?
|
9
|
+
project.create_features_in_pivotal
|
10
|
+
end
|
11
|
+
|
12
|
+
def story_type
|
13
|
+
'feature'
|
14
|
+
end
|
15
|
+
|
16
|
+
def issue_type
|
17
|
+
project.feature_jira_task_id
|
18
|
+
end
|
19
|
+
|
20
|
+
def inform_creation_started
|
21
|
+
puts 'Starting a new feature'
|
22
|
+
end
|
23
|
+
|
24
|
+
def inform_creation_finished
|
25
|
+
puts 'A new feature started'
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
class Fix < TaskWithCommit
|
3
|
+
before_create :update_parent_branch
|
4
|
+
before_create :inform_creation_started
|
5
|
+
after_create :inform_creation_finished
|
6
|
+
|
7
|
+
def description
|
8
|
+
result = super
|
9
|
+
last_commit = git_last_commit_name
|
10
|
+
"#{result}\n(For: #{last_commit})"
|
11
|
+
end
|
12
|
+
|
13
|
+
def finish(comment)
|
14
|
+
git_rebase_to_upstream
|
15
|
+
super
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
def create_issue_on_pivotal_data
|
20
|
+
Jbuilder.encode do |j|
|
21
|
+
j.current_state 'unstarted'
|
22
|
+
j.name title.to_s
|
23
|
+
j.story_type story_type
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def finish_on_pivotal_data
|
28
|
+
Jbuilder.encode do |j|
|
29
|
+
j.current_state 'accepted'
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def this_is_a_type_a_user_wants_to_create?
|
34
|
+
project.create_fixes_in_pivotal_as_chores
|
35
|
+
end
|
36
|
+
|
37
|
+
def story_type
|
38
|
+
'chore'
|
39
|
+
end
|
40
|
+
|
41
|
+
def update_parent_branch
|
42
|
+
git_rebase_to_upstream
|
43
|
+
end
|
44
|
+
|
45
|
+
def issue_type
|
46
|
+
project.fix_jira_task_id
|
47
|
+
end
|
48
|
+
|
49
|
+
def inform_creation_started
|
50
|
+
puts 'Starting a new fix'
|
51
|
+
end
|
52
|
+
|
53
|
+
def inform_creation_finished
|
54
|
+
puts 'A new fix started'
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
class Meeting < Task
|
3
|
+
before_create :inform_creation_started
|
4
|
+
after_create :inform_creation_finished
|
5
|
+
|
6
|
+
private
|
7
|
+
def create_issue_on_pivotal_data
|
8
|
+
Jbuilder.encode do |j|
|
9
|
+
j.current_state 'unstarted'
|
10
|
+
j.name title.to_s
|
11
|
+
j.story_type story_type
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def finish_on_pivotal_data
|
16
|
+
Jbuilder.encode do |j|
|
17
|
+
j.current_state 'accepted'
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def this_is_a_type_a_user_wants_to_create?
|
22
|
+
project.create_meetings_in_pivotal_as_chores
|
23
|
+
end
|
24
|
+
|
25
|
+
def story_type
|
26
|
+
'chore'
|
27
|
+
end
|
28
|
+
|
29
|
+
def issue_type
|
30
|
+
project.meeting_jira_task_id
|
31
|
+
end
|
32
|
+
|
33
|
+
def inform_creation_started
|
34
|
+
puts 'Starting a new meeting'
|
35
|
+
end
|
36
|
+
|
37
|
+
def inform_creation_finished
|
38
|
+
puts 'A new meeting started'
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Git
|
4
|
+
def git_branch(name)
|
5
|
+
`git -C "#{project.folder_path}" checkout -b #{name}` if ENV['CAPEROMA_INTEGRATION_TEST'].blank? && ENV['CAPEROMA_TEST'].blank?
|
6
|
+
end
|
7
|
+
|
8
|
+
def git_commit(msg)
|
9
|
+
`git -C "#{project.folder_path}" add -A && git -C "#{project.folder_path}" commit --allow-empty -m "#{msg}"` if ENV['CAPEROMA_INTEGRATION_TEST'].blank? && ENV['CAPEROMA_TEST'].blank?
|
10
|
+
end
|
11
|
+
|
12
|
+
def git_push
|
13
|
+
`git -C "#{project.folder_path}" push --set-upstream origin #{git_current_branch}` if ENV['CAPEROMA_INTEGRATION_TEST'].blank? && ENV['CAPEROMA_TEST'].blank?
|
14
|
+
end
|
15
|
+
|
16
|
+
def git_last_commit_name
|
17
|
+
`git -C "#{project.folder_path}" log --pretty=format:'%s' -1` if ENV['CAPEROMA_INTEGRATION_TEST'].blank? && ENV['CAPEROMA_TEST'].blank?
|
18
|
+
end
|
19
|
+
|
20
|
+
def git_current_branch
|
21
|
+
`git -C "#{project.folder_path}" rev-parse --abbrev-ref HEAD`.gsub("\n", '') if ENV['CAPEROMA_INTEGRATION_TEST'].blank? && ENV['CAPEROMA_TEST'].blank?
|
22
|
+
end
|
23
|
+
|
24
|
+
def git_pull_request(into, title, description = '')
|
25
|
+
pull_request_data = Jbuilder.encode do |j|
|
26
|
+
j.title title
|
27
|
+
j.body description
|
28
|
+
j.head git_current_branch
|
29
|
+
j.base into
|
30
|
+
end
|
31
|
+
|
32
|
+
conn = Faraday.new(url: 'https://api.github.com') do |c|
|
33
|
+
c.basic_auth(Account.git.email, Account.git.password)
|
34
|
+
c.adapter Faraday.default_adapter
|
35
|
+
end
|
36
|
+
|
37
|
+
conn.post do |request|
|
38
|
+
request.url "/repos/#{project.github_repo}/pulls"
|
39
|
+
request.body = pull_request_data
|
40
|
+
request.headers['User-Agent'] = 'Caperoma'
|
41
|
+
request.headers['Accept'] = 'application/vnd.github.v3+json'
|
42
|
+
request.headers['Content-Type'] = 'application/json'
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def git_rebase_to_upstream
|
47
|
+
if ENV['CAPEROMA_INTEGRATION_TEST'].blank? && ENV['CAPEROMA_TEST'].blank?
|
48
|
+
has_untracked_files = !`git -C "#{project.folder_path}" ls-files --others --exclude-standard`.empty?
|
49
|
+
has_changes = !`git -C "#{project.folder_path}" diff`.empty?
|
50
|
+
has_staged_changes = !`git -C "#{project.folder_path}" diff HEAD`.empty?
|
51
|
+
|
52
|
+
changes_were_made = has_untracked_files || has_changes || has_staged_changes
|
53
|
+
|
54
|
+
`git -C "#{project.folder_path}" add -A && git -C "#{project.folder_path}" stash` if changes_were_made
|
55
|
+
|
56
|
+
`git -C "#{project.folder_path}" fetch && git -C "#{project.folder_path}" rebase $(git -C "#{project.folder_path}" rev-parse --abbrev-ref --symbolic-full-name @{u})`
|
57
|
+
|
58
|
+
`git -C "#{project.folder_path}" stash apply` if changes_were_made
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def git_checkout(branch)
|
63
|
+
`git -C "#{project.folder_path}" checkout #{branch}` if ENV['CAPEROMA_INTEGRATION_TEST'].blank? && ENV['CAPEROMA_TEST'].blank?
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
class TaskWithCommit < Task
|
3
|
+
belongs_to :branch
|
4
|
+
|
5
|
+
def finish(comment)
|
6
|
+
super
|
7
|
+
git_commit(commit_message)
|
8
|
+
# here I should pass the path
|
9
|
+
`rubocop -a "#{project.folder_path}"` if ENV['CAPEROMA_INTEGRATION_TEST'].blank? && ENV['CAPEROMA_TEST'].blank?
|
10
|
+
git_commit(commit_rubocop_message)
|
11
|
+
git_push
|
12
|
+
end
|
13
|
+
|
14
|
+
def pause(comment)
|
15
|
+
super
|
16
|
+
git_commit(commit_message)
|
17
|
+
`rubocop -a "#{project.folder_path}"` if ENV['CAPEROMA_INTEGRATION_TEST'].blank? && ENV['CAPEROMA_TEST'].blank?
|
18
|
+
git_commit(commit_rubocop_message)
|
19
|
+
git_push
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def commit_message
|
25
|
+
# E.g.: [RUC-123][#1345231] Some Subject
|
26
|
+
string = ''
|
27
|
+
string += "[#{jira_key}]" if jira_key.present?
|
28
|
+
string += "[##{pivotal_id}]" if pivotal_id.present?
|
29
|
+
string += " #{title}"
|
30
|
+
string.strip
|
31
|
+
end
|
32
|
+
|
33
|
+
def commit_rubocop_message
|
34
|
+
string = ''
|
35
|
+
string += "[#{jira_key}]" if jira_key.present?
|
36
|
+
string += "[##{pivotal_id}]" if pivotal_id.present?
|
37
|
+
string += ' Applying good practices'
|
38
|
+
string.strip
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
class TaskWithSeparateBranch < TaskWithCommit
|
3
|
+
before_create :update_parent_branch
|
4
|
+
before_create :remember_parent_branch
|
5
|
+
after_create :new_git_branch
|
6
|
+
|
7
|
+
def finish(comment)
|
8
|
+
puts comment
|
9
|
+
super
|
10
|
+
puts git_pull_request(parent_branch, title, description_for_pull_request)
|
11
|
+
puts git_checkout(parent_branch)
|
12
|
+
end
|
13
|
+
|
14
|
+
def abort(comment)
|
15
|
+
super
|
16
|
+
puts git_checkout(parent_branch)
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def description_for_pull_request
|
22
|
+
pivotal_url
|
23
|
+
end
|
24
|
+
|
25
|
+
def update_parent_branch
|
26
|
+
git_rebase_to_upstream
|
27
|
+
end
|
28
|
+
|
29
|
+
def remember_parent_branch
|
30
|
+
self.parent_branch = git_current_branch
|
31
|
+
end
|
32
|
+
|
33
|
+
def new_git_branch
|
34
|
+
git_branch(branch_name)
|
35
|
+
end
|
36
|
+
|
37
|
+
def branch_name
|
38
|
+
# E.g.: ruc-123-first-three-four-words
|
39
|
+
result = [jira_key, title[0, 25]].join(' ')
|
40
|
+
ActiveSupport::Inflector.parameterize(result)
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class AirbrakeEmailProcessor
|
4
|
+
# stub for test env
|
5
|
+
def process
|
6
|
+
account = Account.gmail
|
7
|
+
Gmail.new(account.email, account.password) do |gmail|
|
8
|
+
gmail.inbox.emails(:unread, from: 'donotreply@alerts.airbrake.io').select { |email| email.subject.include? '[Ruck.us]' }.each do |email|
|
9
|
+
# get reply address
|
10
|
+
reply_to = "#{email.to.first.mailbox}@#{email.to.first.host}"
|
11
|
+
|
12
|
+
# generate reply body
|
13
|
+
body = ''
|
14
|
+
|
15
|
+
story = PivotalFetcher.get_story_by_title(email.subject)
|
16
|
+
if story.present? && story.respond_to?(:url)
|
17
|
+
body = "Duplicate of: #{story.url}"
|
18
|
+
else
|
19
|
+
story = PivotalFetcher.create_story(email.subject, email.body.raw_source[0..1000])
|
20
|
+
body = "Created new story: #{story.url}"
|
21
|
+
end
|
22
|
+
|
23
|
+
# compose reply email (to get identifiers)
|
24
|
+
reply = email.reply do
|
25
|
+
subject "Re: #{email.subject}"
|
26
|
+
body body
|
27
|
+
end
|
28
|
+
|
29
|
+
# bugfix to make reply work
|
30
|
+
new_email = gmail.compose do
|
31
|
+
to reply_to # note it's generated email, not airbrake one
|
32
|
+
subject reply.subject
|
33
|
+
in_reply_to reply.in_reply_to
|
34
|
+
references reply.references
|
35
|
+
body reply.body.raw_source
|
36
|
+
end
|
37
|
+
|
38
|
+
# deliver reply
|
39
|
+
new_email.deliver!
|
40
|
+
|
41
|
+
# archive email
|
42
|
+
email.mark(:read)
|
43
|
+
email.archive!
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class PivotalFetcher
|
4
|
+
def self.process(story_id)
|
5
|
+
story = get_story(story_id)
|
6
|
+
|
7
|
+
if story.present?
|
8
|
+
title = story.name
|
9
|
+
description = story.description
|
10
|
+
type = story.story_type
|
11
|
+
# labels = story.labels # in case I do Critical thing
|
12
|
+
# something liek if lables.include?("hot") pass "critical" status to Jira.
|
13
|
+
|
14
|
+
story.update(current_state: 'started', owned_by: 'Serge Vinogradoff')
|
15
|
+
|
16
|
+
args = [type, title, description, story_id] # or args = [type, title, description, "1", story_id] ? I use that 1 in older versions
|
17
|
+
case type
|
18
|
+
when 'feature'
|
19
|
+
Caperoma.feature(args)
|
20
|
+
when 'bug'
|
21
|
+
Caperoma.bug(args)
|
22
|
+
else
|
23
|
+
puts 'Unknown story type in Pivotal'
|
24
|
+
end
|
25
|
+
|
26
|
+
# copy Jira ID to Pivotal story
|
27
|
+
task = Task.where(pivotal_id: story_id).first
|
28
|
+
if task.present?
|
29
|
+
task.jira_key
|
30
|
+
story.notes.create(text: task.jira_key)
|
31
|
+
else
|
32
|
+
puts 'task does not exist'
|
33
|
+
end
|
34
|
+
else
|
35
|
+
puts 'Did not find a story'
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.finish(story_id)
|
40
|
+
story = nil
|
41
|
+
|
42
|
+
PivotalTracker::Project.all.each do |project|
|
43
|
+
story = project.stories.find(story_id)
|
44
|
+
break if story.present?
|
45
|
+
end
|
46
|
+
|
47
|
+
story
|
48
|
+
|
49
|
+
if story.present? && story.tasks.all.empty?
|
50
|
+
story.update current_state: 'finished'
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.create_story(title, description)
|
55
|
+
connect
|
56
|
+
|
57
|
+
project_id = 993_892
|
58
|
+
|
59
|
+
# this isn't needed anymore... these are PT ID's of Ruck.us.
|
60
|
+
if title.include? '[Ruck.us] Production '
|
61
|
+
project_id = 993_892
|
62
|
+
elsif title.include? '[Ruck.us] Staging '
|
63
|
+
project_id = 1_110_744
|
64
|
+
elsif title.include? '[Ruck.us] Staging2 '
|
65
|
+
project_id = 1_266_704
|
66
|
+
end
|
67
|
+
|
68
|
+
# TODO: icebox, need probably to move to backlog
|
69
|
+
project = PivotalTracker::Project.find(project_id)
|
70
|
+
story = project.stories.create name: title,
|
71
|
+
description: description,
|
72
|
+
requested_by: 'Serge Vinogradoff',
|
73
|
+
owned_by: 'Serge Vinogradoff',
|
74
|
+
story_type: 'bug'
|
75
|
+
|
76
|
+
story
|
77
|
+
end
|
78
|
+
|
79
|
+
def self.get_story_by_title(title)
|
80
|
+
connect
|
81
|
+
|
82
|
+
story = nil
|
83
|
+
|
84
|
+
PivotalTracker::Project.all.each do |project|
|
85
|
+
story = project.stories.all.select { |x| x.name == title }.first
|
86
|
+
break if story.present?
|
87
|
+
end
|
88
|
+
|
89
|
+
story
|
90
|
+
end
|
91
|
+
|
92
|
+
def self.get_story(story_id)
|
93
|
+
connect
|
94
|
+
|
95
|
+
story = nil
|
96
|
+
|
97
|
+
PivotalTracker::Project.all.each do |project|
|
98
|
+
story = project.stories.find(story_id)
|
99
|
+
break if story.present?
|
100
|
+
end
|
101
|
+
|
102
|
+
story
|
103
|
+
end
|
104
|
+
|
105
|
+
def self.connect
|
106
|
+
PivotalTracker::Client.token(Account.pivotal.email, Account.pivotal.password)
|
107
|
+
end
|
108
|
+
end
|