clubhouse2 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 55bfc748af3d711fd8f90a3594f1272d53b94544
4
+ data.tar.gz: 45a1626fc86b551ae3b53ebf230c64e099d49682
5
+ SHA512:
6
+ metadata.gz: 0f3e1a806808f35df55b77564d5a82c041f6a1db89fa6781ec3506851a3fbb012cafd9192b1737e64ac1bb4746b4e0f07a7a858021196f5e4150e4dbc6610f61
7
+ data.tar.gz: a18e42aea643ef78213a2b8b4f035dba1cb5f13c62c8789746c6dc12132ca79dd0ad124b29a1bae2c3e518476bcbae0b3b16f5f0abd743e77aa672d910755f97
@@ -0,0 +1,5 @@
1
+ *.gem
2
+ bin/
3
+ Gemfile.lock
4
+ Gemfile
5
+
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.1
@@ -0,0 +1,31 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "clubhouse2"
7
+ spec.version = File.read(File.expand_path(File.dirname(__FILE__)) + '/VERSION')
8
+ spec.authors = ["James Denness"]
9
+ spec.email = ["jd@masabi.com"]
10
+
11
+ spec.summary = %q{Clubhouse library for API version 2}
12
+ spec.description = %q{Does exactly what it says on the label}
13
+ spec.homepage = "https://www.masabi.com"
14
+ spec.license = "BSD"
15
+
16
+ # Prevent pushing this gem to RubyGems.org by setting 'allowed_push_host', or
17
+ # delete this section to allow pushing this gem to any host.
18
+ if spec.respond_to?(:metadata)
19
+ else
20
+ raise "RubyGems 2.0 or newer is required to protect against public gem pushes."
21
+ end
22
+
23
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
24
+ spec.bindir = 'bin'
25
+ spec.require_paths = [ 'lib' ]
26
+
27
+ spec.add_dependency 'http', '~> 1'
28
+ spec.add_dependency 'pry', '~> 0.10.4'
29
+
30
+ spec.add_development_dependency "bundler", "~> 1.10"
31
+ end
@@ -0,0 +1,23 @@
1
+ require 'clubhouse2/clubhouse_resource.rb'
2
+ require 'clubhouse2/story_comment.rb'
3
+ require 'clubhouse2/epic_comment.rb'
4
+ require 'clubhouse2/linked_file.rb'
5
+ require 'clubhouse2/story_link.rb'
6
+ require 'clubhouse2/exceptions.rb'
7
+ require 'clubhouse2/milestone.rb'
8
+ require 'clubhouse2/category.rb'
9
+ require 'clubhouse2/workflow.rb'
10
+ require 'clubhouse2/project.rb'
11
+ require 'clubhouse2/profile.rb'
12
+ require 'clubhouse2/member.rb'
13
+ require 'clubhouse2/client.rb'
14
+ require 'clubhouse2/story.rb'
15
+ require 'clubhouse2/label.rb'
16
+ require 'clubhouse2/state.rb'
17
+ require 'clubhouse2/team.rb'
18
+ require 'clubhouse2/file.rb'
19
+ require 'clubhouse2/task.rb'
20
+ require 'clubhouse2/epic.rb'
21
+
22
+ module Clubhouse
23
+ end
@@ -0,0 +1,9 @@
1
+ module Clubhouse
2
+ class Category < ClubhouseResource
3
+ attr_reader :archived, :color, :created_at, :entity_type, :external_id, :id, :name, :type, :updated_at
4
+
5
+ def self.api_url
6
+ 'categories'
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,125 @@
1
+ require 'http'
2
+ require 'uri'
3
+ require 'json'
4
+
5
+ module Clubhouse
6
+ class Client
7
+ def initialize(api_key:, base_url: 'https://api.clubhouse.io/api/v2/')
8
+ @api_key = api_key
9
+ @base_url = base_url
10
+ @resources = {}
11
+ end
12
+
13
+ def url(endpoint)
14
+ URI.join(@base_url, endpoint,'?token=%s' % @api_key)
15
+ end
16
+
17
+ def api_request(method, *params)
18
+ puts [method, *params].join(', ')
19
+ response = HTTP.headers(content_type: 'application/json').send(method, *params)
20
+ case response.code
21
+ when 429
22
+ sleep 30
23
+ api_request(method, *params)
24
+ when 200
25
+ when 201
26
+ else
27
+ raise ClubhouseAPIError.new(response)
28
+ end
29
+
30
+ response
31
+ end
32
+
33
+ def flush(resource_class)
34
+ @resources[resource_class] = nil
35
+ end
36
+
37
+ def filter(object_array, args)
38
+ object_array.reject { |s| args.collect { |k, v| not [ *s.send(k) ].include? v }.reduce(:|) }
39
+ end
40
+
41
+ # or v.empty? if v.respond_to?(:empty?)
42
+ def create_object(resource_class, args)
43
+ this_class = Clubhouse::ClubhouseResource.subclass(resource_class)
44
+ this_class.validate(args)
45
+ flush(this_class)
46
+ new_params = args.compact.reject { |k, v| this_class.property_filter_create.include? k.to_sym }
47
+ response = api_request(:post, url(this_class.api_url), :json => new_params)
48
+ JSON.parse(response.to_s)
49
+ end
50
+
51
+ def get_objects(resource_class, args = {})
52
+ this_class = Clubhouse::ClubhouseResource.subclass(resource_class)
53
+ unless @resources[this_class]
54
+ response = api_request(:get, url(this_class.api_url))
55
+ @resources[this_class] = JSON.parse(response.to_s).collect do |resource|
56
+ this_class.new(client: self, object: resource)
57
+ end
58
+ end
59
+
60
+ filter(@resources[this_class], args)
61
+ end
62
+
63
+ def get_object(resource_class, args = {})
64
+ get_objects(resource_class, args).first
65
+ end
66
+
67
+ # ---
68
+
69
+ def create_milestone(**args); create_object(:milestone, args); end
70
+ def milestones(**args); get_objects(:milestone, args); end
71
+ def milestone(**args); get_object(:milestone, args); end
72
+
73
+ def create_project(**args); create_object(:project, args); end
74
+ def projects(**args); get_objects(:project, args); end
75
+ def project(**args); get_object(:project, args); end
76
+
77
+ def create_story(**args); create_object(:story, args); end
78
+ def stories(**args)
79
+ filter(get_objects(:project).collect(&:stories).flatten, args)
80
+ end
81
+
82
+ def story(**args); stories(**args).first; end
83
+
84
+ def create_story_link(**args); create_object(:storylink, args); end
85
+ def story_links(**args)
86
+ filter(stories.collect(&:story_links).flatten, args)
87
+ end
88
+
89
+ def story_link(**args); story_links(**args).first; end
90
+
91
+ def create_member(**args); create_object(:member, args); end
92
+ def members(**args); get_objects(:member, args); end
93
+ def member(**args); get_object(:member, args); end
94
+
95
+ def create_team(**args); create_object(:team, args); end
96
+ def teams(**args); get_objects(:team, args); end
97
+ def team(**args); get_object(:team, args); end
98
+
99
+ def create_epic(**args); create_object(:epic, args); end
100
+ def epics(**args); get_objects(:epic, args); end
101
+ def epic(**args); get_object(:epic, args); end
102
+
103
+ def create_category(**args); create_object(:category, args); end
104
+ def categories(**args); get_objects(:category, args); end
105
+ def category(**args); get_object(:category, args); end
106
+
107
+ def create_label(**args); create_object(:label, args); end
108
+ def update_label(**args); update_object(:label, args); end
109
+ def labels(**args); get_objects(:label, args); end
110
+ def label(**args); get_object(:label, args); end
111
+
112
+ def create_file(**args); create_object(:file, args); end
113
+ def files(**args); get_objects(:file, args); end
114
+ def file(**args); get_object(:file, args); end
115
+
116
+ def create_linked_file(**args); create_object(:linkedfile, args); end
117
+ def linked_files(**args); get_objects(:linkedfile, args); end
118
+ def linked_file(**args); get_object(:linkedfile, args); end
119
+
120
+ def create_workflow(**args); create_object(:workflow, args); end
121
+ def workflows(**args); get_objects(:workflow, args); end
122
+ def workflow(**args); get_object(:workflow, args); end
123
+
124
+ end
125
+ end
@@ -0,0 +1,74 @@
1
+ module Clubhouse
2
+ class ClubhouseResource
3
+ @@subclasses = []
4
+
5
+ def self.inherited(other)
6
+ @@subclasses << other
7
+ end
8
+
9
+ # A list of properties to exlude from any create request
10
+ def self.property_filter_create
11
+ [
12
+ :archived, :days_to_thermometer, :entity_type, :id, :show_thermometer, :stats, :created_at, :updated_at,
13
+ :started_at, :completed_at, :comments, :position, :started, :project_ids, :completed, :blocker, :moved_at,
14
+ :task_ids, :files, :comment_ids, :workflow_state_id, :story_links, :mention_ids, :file_ids, :linked_file_ids
15
+ ]
16
+ end
17
+
18
+ # A list of properties to exlude from any update request
19
+ def self.property_filter_update
20
+ self.property_filter_create
21
+ end
22
+
23
+ def self.subclass(sub_class)
24
+ @@subclasses.find { |s| s.name == 'Clubhouse::%s' % sub_class.capitalize }
25
+ end
26
+
27
+ def api_url
28
+ self.class.api_url + "/#{@id}"
29
+ end
30
+
31
+ def self.validate(args); end
32
+
33
+ def initialize(client:, object:)
34
+ @client = client
35
+
36
+ self.class.properties.each do |this_property|
37
+ self.class.class_eval { attr_accessor(this_property.to_sym) }
38
+ self.class.send(:define_method, (this_property.to_s + '=').to_sym) do |value|
39
+ update({ this_property => value })
40
+ end
41
+ end
42
+
43
+ set_properties(object)
44
+ self
45
+ end
46
+
47
+ def set_properties(object)
48
+ object.each_pair do |k, v|
49
+ instance_variable_set('@' + k.to_s, v)
50
+ end
51
+ end
52
+
53
+ # Empties resource cache
54
+ def flush
55
+ @client.flush(self.class)
56
+ end
57
+
58
+ def update(args = {})
59
+ new_params = to_h.merge(args).reject { |k, v| self.class.property_filter_update.include? k.to_sym }
60
+ validate(new_params)
61
+ flush
62
+ @client.api_request(:put, @client.url(api_url), :json => new_params)
63
+ end
64
+
65
+ def delete!
66
+ flush
67
+ @client.api_request(:delete, @client.url(api_url))
68
+ end
69
+
70
+ def to_h
71
+ Hash[ (self.class.properties - self.class.property_filter_create).map { |name| [ name, instance_variable_get('@' + name.to_s) ] } ].compact
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,54 @@
1
+ module Clubhouse
2
+ class Epic < ClubhouseResource
3
+ def self.properties
4
+ [
5
+ :archived, :comments, :completed, :completed_at, :completed_at_override, :created_at, :deadline, :description,
6
+ :entity_type, :external_id, :follower_ids, :id, :labels, :milestone_id, :name, :owner_ids, :position, :project_ids,
7
+ :started, :started_at, :started_at_override, :state, :stats, :updated_at
8
+ ]
9
+ end
10
+
11
+ def self.api_url
12
+ 'epics'
13
+ end
14
+
15
+ def stories
16
+ @client.projects.collect(&:stories).reduce(:+).select { |s| s.epic_id == @id }
17
+ end
18
+
19
+ def validate(args)
20
+ raise NoSuchTeam.NoSuchTeam(args[:team_id]) unless @client.get_team(id: args[:team_id])
21
+
22
+ (args[:follower_ids] || []).each do |this_member|
23
+ raise NoSuchMember.NoSuchMember(this_member) unless @client.get_member(id: this_member)
24
+ end
25
+
26
+ (args[:owner_ids] || []).each do |this_member|
27
+ raise NoSuchMember.NoSuchMember(this_member) unless @client.get_member(id: this_member)
28
+ end
29
+ end
30
+
31
+ def comments(**args)
32
+ # The API is missing a parent epic ID property, so we need to fake it here
33
+ args[:epic_id] = @id
34
+ @comments ||= JSON.parse(@client.api_request(:get, @client.url("#{api_url}/#{Epiccomment.api_url}"))).collect { |task| Epiccomment.new(client: @client, object: comment) }
35
+ @comments.reject { |s| args.collect { |k,v| s.send(k) != v }.reduce(:|) }
36
+ end
37
+
38
+ def to_h
39
+ super.merge({
40
+ comments: @comments.collect(&:to_h)
41
+ })
42
+ end
43
+
44
+ def create_comment(**args)
45
+ Task.validate(args)
46
+ response = JSON.parse(@client.api_request(:post, @client.url("#{api_url}/#{Epiccomment.api_url}"), :json => args))
47
+ raise ClubhouseAPIError.new(response) unless response.code == 201
48
+ @comments = nil
49
+ response
50
+ end
51
+
52
+ def comment(**args); comments(args).first; end
53
+ end
54
+ end
@@ -0,0 +1,15 @@
1
+ module Clubhouse
2
+ class Epiccomment < ClubhouseResource
3
+ def self.properties
4
+ [ :author_id, :comments, :created_at, :entity_type, :external_id, :id, :mention_ids, :position, :text, :updated_at, :epic_id ]
5
+ end
6
+
7
+ def self.api_url
8
+ 'comments'
9
+ end
10
+
11
+ def api_url
12
+ "#{self.api_url}/#{@epic_id}/#{id}"
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,55 @@
1
+ module Clubhouse
2
+ class ClubhouseValidationError < StandardError
3
+ def initialize(message)
4
+ super('validation error: %s' % message)
5
+ end
6
+ end
7
+
8
+ class ClubhouseAPIError < StandardError
9
+ def initialize(response)
10
+ super('api error (%d): %s' % [ response.code, response.to_s ])
11
+ end
12
+ end
13
+
14
+ class NoSuchMember < ClubhouseValidationError
15
+ def initialize(member)
16
+ super('no such member (%s)' % member)
17
+ end
18
+ end
19
+
20
+ class NoSuchFile < ClubhouseValidationError
21
+ def initialize(file)
22
+ super('no such file (%s)' % file)
23
+ end
24
+ end
25
+
26
+ class NoSuchLinkedFile < ClubhouseValidationError
27
+ def initialize(file)
28
+ super('no such linked file (%s)' % file)
29
+ end
30
+ end
31
+
32
+ class NoSuchTeam < ClubhouseValidationError
33
+ def initialize(team)
34
+ super('no such team (%s)' % team)
35
+ end
36
+ end
37
+
38
+ class NoSuchProject < ClubhouseValidationError
39
+ def initialize(project)
40
+ super('no such project (%s)' % project)
41
+ end
42
+ end
43
+
44
+ class NoSuchMilestone < ClubhouseValidationError
45
+ def initialize(milestone)
46
+ super('no such milestone (%s)' % milestone)
47
+ end
48
+ end
49
+
50
+ class NoSuchEpic < ClubhouseValidationError
51
+ def initialize(epic)
52
+ super('no such epic (%s)' % epic)
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,14 @@
1
+ module Clubhouse
2
+ class File < ClubhouseResource
3
+ def self.properties
4
+ [
5
+ :content_type, :created_at, :description, :entity_type, :external_id, :filename, :id, :mention_ids, :name,
6
+ :size, :story_ids, :thumbnail_url, :updated_at, :uploader_id, :url
7
+ ]
8
+ end
9
+
10
+ def self.api_url
11
+ 'files'
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,15 @@
1
+ module Clubhouse
2
+ class Label < ClubhouseResource
3
+ def self.properties
4
+ [ :archived, :color, :created_at, :entity_type, :external_id, :id, :name, :stats, :updated_at ]
5
+ end
6
+
7
+ def self.api_url
8
+ 'labels'
9
+ end
10
+
11
+ def stories
12
+ @client.projects.collect(&:stories).reduce(:+).select { |s| s.labels.collect(&:id).include? @id }
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,14 @@
1
+ module Clubhouse
2
+ class Linkedfile < ClubhouseResource
3
+ def self.properties
4
+ [
5
+ :content_type, :created_at, :description, :entity_type, :id, :mention_ids, :name,
6
+ :size, :story_ids, :thumbnail_url, :type, :updated_at, :uploader_id
7
+ ]
8
+ end
9
+
10
+ def self.api_url
11
+ 'linkedfiles'
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,29 @@
1
+ module Clubhouse
2
+ class Member < ClubhouseResource
3
+ def self.properties
4
+ [ :created_at, :disabled, :id, :profile, :role, :updated_at ]
5
+ end
6
+
7
+ def initialize(client:, object:)
8
+ super
9
+ @profile = Profile.new(client: client, object: @profile)
10
+
11
+ # Create accessors for profile properties
12
+ Profile.properties.each do |property|
13
+ self.class.send(:define_method, (property.to_sym)) { @profile.send(property) }
14
+ end
15
+ end
16
+
17
+ def self.api_url
18
+ 'members'
19
+ end
20
+
21
+ def stories_requested
22
+ @client.projects.collect(&:stories).reduce(:+).select { |s| s.requested_by_id == @id }
23
+ end
24
+
25
+ def stories_following
26
+ @client.projects.collect(&:stories).reduce(:+).select { |s| s.follower_ids.include? @id }
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,22 @@
1
+ module Clubhouse
2
+ class Milestone < ClubhouseResource
3
+ def self.properties
4
+ [
5
+ :categories, :completed, :completed_at, :completed_at_override, :created_at, :description, :entity_type,
6
+ :id, :name, :position, :started, :started_at, :started_at_override, :state, :updated_at
7
+ ]
8
+ end
9
+
10
+ def self.api_url
11
+ 'milestones'
12
+ end
13
+
14
+ def epics
15
+ @client.epics.select { |e| e.milestone_id == @id }
16
+ end
17
+
18
+ def stories
19
+ epics.collect(&:stories).reduce(:+)
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,110 @@
1
+ require 'clubhouse2'
2
+
3
+ source_account = Clubhouse::Client.new(api_key: 'source_account_api_key')
4
+ target_account = Clubhouse::Client.new(api_key: 'target_account_api_key')
5
+
6
+ resolve_map = {}
7
+
8
+ # Map of workflows which don't directly relate to workflows in the other account
9
+ resolve_map[:workflow] = {
10
+ 'Unscheduled' => 'Backlog',
11
+ 'Ready for Deployment' => 'Awaiting UAT Deployment',
12
+ 'Waiting for other team' => 'Blocked',
13
+ 'Completed' => 'Done',
14
+ 'Rejected' => 'Icebox'
15
+ }
16
+
17
+ # Map of members which aren't named the same in both accounts
18
+ resolve_map[:member] = {
19
+ 'James' => 'James Denness',
20
+ }
21
+
22
+ # This method looks up the corresponding ID of a resource in the other account, by matching its name.
23
+ def resolve(source_client, target_client, resolve_map, type, id)
24
+ item = source_client.send(type, id: id)
25
+ if resolve_map[type]
26
+ counterpart = resolve_map[type][item.name] || item.name
27
+ else
28
+ counterpart = item.name
29
+ end
30
+ target_client.send(type, name: counterpart)&.id
31
+ end
32
+
33
+ # We need to create the empty projects before we can create epics
34
+ source_account.projects.each do |this_project|
35
+ target_account.create_project(
36
+ this_project.to_h.merge({
37
+ follower_ids: this_project.follower_ids.collect { |f| resolve(source_account, target_account, resolve_map, :member, f) }
38
+ })
39
+ ) unless resolve(source_account, target_account, resolve_map, :project, this_project.id)
40
+ end
41
+
42
+ # Create labels
43
+ source_account.labels.each do |this_label|
44
+ target_account.create_label(this_label.to_h) unless resolve(source_account, target_account, resolve_map, :label, this_label.id)
45
+ end
46
+
47
+ # Create categories
48
+ source_account.categories.each do |this_category|
49
+ target_account.create_category(this_category.to_h) unless resolve(source_account, target_account, resolve_map, :category, this_category.id)
50
+ end
51
+
52
+ # Create milestones
53
+ source_account.milestones.each do |this_milestone|
54
+ new_milestone = target_account.create_milestone(
55
+ this_milestone.to_h.merge({
56
+ labels: this_milestone.categories.collect { |c| resolve(source_account, target_account, resolve_map, :category, c ) }
57
+ })
58
+ ) unless target_account.milestone(name: this_milestone.name)
59
+ end
60
+
61
+ # Create epics
62
+ source_account.epics.each do |this_epic|
63
+ new_epic = target_account.create_epic(
64
+ this_epic.to_h.merge({
65
+ follower_ids: this_epic.follower_ids.collect { |f| resolve(source_account, target_account, resolve_map, :member, f) },
66
+ owner_ids: this_epic.owner_ids.collect { |o| resolve(source_account, target_account, resolve_map, :member, o) },
67
+ labels: this_epic.labels.collect { |l| resolve(source_account, target_account, resolve_map, :label, l ) },
68
+ started_at_override: this_epic.started_at,
69
+ completed_at_override: this_epic.completed_at,
70
+ milestone_id: resolve(source_account, target_account, resolve_map, :milestone, this_epic.milestone_id)
71
+ })
72
+ ) unless target_account.epic(name: this_epic.name)
73
+ end
74
+
75
+ # Fill the projects with stories
76
+ source_account.projects.each do |this_project|
77
+ target_account_project = target_account.project(id: resolve(source_account, target_account, resolve_map, :project, this_project.id))
78
+ this_project.stories.each do |this_story|
79
+ new_story = target_account.create_story(
80
+ this_story.to_h.merge({
81
+ project_id: target_account_project.id,
82
+ epic_id: (resolve(source_account, target_account, resolve_map, :epic, this_story.epic_id) if this_story.epic_id),
83
+ follower_ids: this_story.follower_ids.collect { |f| resolve(source_account, target_account, resolve_map, :member, f) },
84
+ owner_ids: this_story.owner_ids.collect { |o| resolve(source_account, target_account, resolve_map, :member, o) },
85
+ requested_by_id: resolve(source_account, target_account, resolve_map, :member, this_story.requested_by_id),
86
+ })
87
+ ) unless target_account_project.story(name: this_story.name)
88
+ end
89
+ end
90
+
91
+ # Restore story links
92
+ source_account.story_links.each do |this_link|
93
+ begin
94
+ target_account.create_story_link(
95
+ this_link.to_h.merge({
96
+ object_id: resolve(source_account, target_account, resolve_map, :story, this_link.object_id),
97
+ subject_id: resolve(source_account, target_account, resolve_map, :story, this_link.subject_id)
98
+ })
99
+ ) unless target_account.story_link(object_id: resolve(source_account, target_account, resolve_map, :story, this_link.object_id), subject_id: resolve(source_account, target_account, resolve_map, :story, this_link.subject_id))
100
+ rescue Clubhouse::ClubhouseAPIError => e
101
+ if e.message[/duplicated/]
102
+ next
103
+ else
104
+ raise e
105
+ end
106
+ end
107
+ end
108
+
109
+
110
+
@@ -0,0 +1,7 @@
1
+ module Clubhouse
2
+ class Profile < ClubhouseResource
3
+ def self.properties
4
+ [ :deactivated, :name, :mention_name, :email_address, :gravatar_hash, :display_icon, :two_factor_auth_activated ]
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,28 @@
1
+ module Clubhouse
2
+ class Project < ClubhouseResource
3
+ def self.properties
4
+ [
5
+ :abbreviation, :archived, :color, :created_at, :days_to_thermometer, :description, :entity_type, :external_id,
6
+ :follower_ids, :id, :iteration_length, :name, :show_thermometer, :start_time, :stats, :updated_at
7
+ ]
8
+ end
9
+
10
+ def self.api_url
11
+ 'projects'
12
+ end
13
+
14
+ def stories(**args)
15
+ @stories ||= JSON.parse(@client.api_request(:get, @client.url("#{api_url}/stories"))).collect { |story| Story.new(client: @client, object: story) }
16
+ @stories.reject { |s| args.collect { |k,v| s.send(k) != v }.reduce(:|) }
17
+ end
18
+
19
+ def create_story(**args)
20
+ @stories = nil
21
+ args[:project_id] = @id
22
+ Story.validate(**args)
23
+ @client.create_object(:story, args)
24
+ end
25
+
26
+ def story(**args); stories(args).first; end
27
+ end
28
+ end
@@ -0,0 +1,11 @@
1
+ module Clubhouse
2
+ class Repository < ClubhouseResource
3
+ def self.properties
4
+ [ :created_at, :entity_type, :external_id, :full_name, :id, :name, :type, :updated_at, :url ]
5
+ end
6
+
7
+ def self.api_url
8
+ 'repositories'
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,10 @@
1
+ module Clubhouse
2
+ class State < ClubhouseResource
3
+ def self.properties
4
+ [
5
+ :categories, :completed, :completed_at, :completed_at_override, :created_at, :description, :entity_type,
6
+ :id, :name, :position, :started, :started_at, :started_at_override, :state, :updated_at
7
+ ]
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,112 @@
1
+ module Clubhouse
2
+ class Story < ClubhouseResource
3
+ def self.properties
4
+ [
5
+ :archived, :blocker, :blocker, :comment_ids, :completed, :completed_at, :completed_at_override, :created_at,
6
+ :deadline, :entity_type, :epic_id, :estimate, :external_id, :file_ids, :follower_ids, :id,
7
+ :linked_file_ids, :moved_at, :name, :owner_ids, :position, :project_id, :requested_by_id, :started,
8
+ :started_at, :started_at_override, :story_type, :task_ids, :updated_at, :workflow_state_id,
9
+ ]
10
+ end
11
+
12
+ def self.api_url
13
+ 'stories'
14
+ end
15
+
16
+ def validate(**args)
17
+ raise NoSuchEpic.new(args[:epic_id]) unless @client.epic(id: args[:epic_id]) if args[:epic_id]
18
+ raise NoSuchProject.new(args[:project_id]) unless @client.project(id: args[:project_id]) if args[:project_id]
19
+ raise NoSuchMember.new(args[:requested_by_id]) unless @client.member(id: args[:requested_by_id]) if args[:requested_by_id]
20
+
21
+ (args[:follower_ids] || []).each do |this_member|
22
+ raise NoSuchMember.new(this_member) unless @client.member(id: this_member)
23
+ end
24
+
25
+ (args[:owner_ids] || []).each do |this_member|
26
+ raise NoSuchMember.new(this_member) unless @client.member(id: this_member)
27
+ end
28
+
29
+ (args[:file_ids] || []).each do |this_file|
30
+ raise NoSuchFile.new(this_file) unless @client.file(id: this_file)
31
+ end
32
+
33
+ (args[:linked_file_ids] || []).each do |this_linked_file|
34
+ raise NoSuchLinkedFile.new(this_linked_file) unless @client.linked_file(id: this_linked_file)
35
+ end
36
+
37
+ (args[:story_links] || []).each do |this_linked_story|
38
+ raise NoSuchLinkedStory.new(this_linked_story) unless @client.story(id: this_linked_story['subject_id'])
39
+ end
40
+
41
+ (args[:labels] || []).collect! do |this_label|
42
+ this_label.is_a? Label ? this_label : @client.label(id: this_label['name'])
43
+ end
44
+ end
45
+
46
+ def create_task(**args)
47
+ Task.validate(**args)
48
+ @tasks = nil
49
+ JSON.parse(@client.api_request(:post, @client.url("#{api_url}/#{Task.api_url}"), :json => args))
50
+ end
51
+
52
+ def comments(**args)
53
+ @comments ||= @comment_ids.collect do |this_comment_id|
54
+ comment_data = JSON.parse(@client.api_request(:get, @client.url("#{api_url}/comments/#{this_comment_id}")))
55
+ Storycomment.new(client: @client, object: comment_data)
56
+ end
57
+
58
+ @comments.reject { |s| args.collect { |k,v| s.send(k) != v }.reduce(:|) }
59
+ end
60
+
61
+ def story_links(**args)
62
+ @story_link_objects ||= @story_links.collect do |this_story_link|
63
+ link_data = JSON.parse(@client.api_request(:get, @client.url("#{Storylink.api_url}/#{this_story_link['id']}")))
64
+ link_data.reject { |k, v| v == @id}
65
+ Storylink.new(client: @client, object: link_data)
66
+ end
67
+
68
+ @story_link_objects.reject { |s| args.collect { |k,v| s.send(k) != v }.reduce(:|) }
69
+ end
70
+
71
+ def tasks(**args)
72
+ @tasks ||= @task_ids.collect do |this_task_id|
73
+ task_data = JSON.parse(@client.api_request(:get, @client.url("#{api_url}/tasks/#{this_task_id}")))
74
+ Task.new(client: @client, object: task_data)
75
+ end
76
+
77
+ @tasks.reject { |s| args.collect { |k,v| s.send(k) != v }.reduce(:|) }
78
+ end
79
+
80
+ def linked_files(**args)
81
+ @client.linked_files(story_ids: @id, **args)
82
+ end
83
+
84
+ def files(**args)
85
+ @client.files(story_ids: @id, **args)
86
+ end
87
+
88
+ def labels(**args)
89
+ @labels.collect { |l| @client.label(id: l['id'], **args) }
90
+ end
91
+
92
+ def to_h
93
+ super.merge({
94
+ comments: [ *comments ].collect(&:to_h),
95
+ tasks: [ *tasks ].collect(&:to_h),
96
+ files: [ *files ].collect(&:to_h),
97
+ story_links: [ *story_links ].collect(&:to_h),
98
+ labels: [ *labels ].collect(&:to_h),
99
+ })
100
+ end
101
+
102
+ def create_comment(**args)
103
+ Task.validate(**args)
104
+ @comments = nil
105
+ JSON.parse(@client.api_request(:post, @client.url("#{api_url}/#{Storycomment.api_url}"), :json => args))
106
+ end
107
+
108
+ def comment(**args); comments(args).first; end
109
+ def story_link(**args); story_links(args).first; end
110
+ def task(**args); tasks(args).first; end
111
+ end
112
+ end
@@ -0,0 +1,15 @@
1
+ module Clubhouse
2
+ class Storycomment < ClubhouseResource
3
+ def self.properties
4
+ [ :author_id, :comments, :created_at, :entity_type, :external_id, :id, :mention_ids, :position, :story_id, :text, :updated_at ]
5
+ end
6
+
7
+ def self.api_url
8
+ 'comments'
9
+ end
10
+
11
+ def api_url
12
+ "#{self.api_url}/#{@story_id}/#{id}"
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,11 @@
1
+ module Clubhouse
2
+ class Storylink < ClubhouseResource
3
+ def self.properties
4
+ [ :object_id, :subject_id, :verb ]
5
+ end
6
+
7
+ def self.api_url
8
+ 'story-links'
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,18 @@
1
+ module Clubhouse
2
+ class Task < ClubhouseResource
3
+ def self.properties
4
+ [
5
+ :complete, :completed_at, :created_at, :description, :entity_type, :external_id, :id, :mention_ids, :owner_ids,
6
+ :position, :story_id, :updated_at
7
+ ]
8
+ end
9
+
10
+ def self.api_url
11
+ 'tasks'
12
+ end
13
+
14
+ def to_h
15
+ super.reject { |k, v| [ :story_id ].include? k }
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,14 @@
1
+ module Clubhouse
2
+ class Team < ClubhouseResource
3
+ def self.properties
4
+ [
5
+ :created_at, :description, :entity_type, :id, :name, :position, :project_ids, :updated_at, :workflow,
6
+ :team_id, :updated_at
7
+ ]
8
+ end
9
+
10
+ def self.api_url
11
+ 'teams'
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,26 @@
1
+ module Clubhouse
2
+ class Workflow < ClubhouseResource
3
+ def self.properties
4
+ [ :created_at, :default_state_id, :description, :entity_type, :id, :name, :states, :team_id, :updated_at ]
5
+ end
6
+
7
+ def initialize(client:, object:)
8
+ super
9
+ @states = []
10
+ object['states'].each do |this_state|
11
+ this_state[:workflow_id] = @id
12
+ @states << State.new(client: client, object: this_state)
13
+ end
14
+ end
15
+
16
+ def self.api_url
17
+ 'workflows'
18
+ end
19
+
20
+ def states(**args)
21
+ @states.reject { |s| args.collect { |k,v| s.send(k) != v }.reduce(:|) }
22
+ end
23
+
24
+ def state(**args); states(args).first; end
25
+ end
26
+ end
metadata ADDED
@@ -0,0 +1,112 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: clubhouse2
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - James Denness
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2017-10-09 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: http
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1'
27
+ - !ruby/object:Gem::Dependency
28
+ name: pry
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 0.10.4
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 0.10.4
41
+ - !ruby/object:Gem::Dependency
42
+ name: bundler
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1.10'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '1.10'
55
+ description: Does exactly what it says on the label
56
+ email:
57
+ - jd@masabi.com
58
+ executables: []
59
+ extensions: []
60
+ extra_rdoc_files: []
61
+ files:
62
+ - ".gitignore"
63
+ - VERSION
64
+ - clubhouse2.gemspec
65
+ - lib/clubhouse2.rb
66
+ - lib/clubhouse2/category.rb
67
+ - lib/clubhouse2/client.rb
68
+ - lib/clubhouse2/clubhouse_resource.rb
69
+ - lib/clubhouse2/epic.rb
70
+ - lib/clubhouse2/epic_comment.rb
71
+ - lib/clubhouse2/exceptions.rb
72
+ - lib/clubhouse2/file.rb
73
+ - lib/clubhouse2/label.rb
74
+ - lib/clubhouse2/linked_file.rb
75
+ - lib/clubhouse2/member.rb
76
+ - lib/clubhouse2/milestone.rb
77
+ - lib/clubhouse2/ops-migration.rb
78
+ - lib/clubhouse2/profile.rb
79
+ - lib/clubhouse2/project.rb
80
+ - lib/clubhouse2/repository.rb
81
+ - lib/clubhouse2/state.rb
82
+ - lib/clubhouse2/story.rb
83
+ - lib/clubhouse2/story_comment.rb
84
+ - lib/clubhouse2/story_link.rb
85
+ - lib/clubhouse2/task.rb
86
+ - lib/clubhouse2/team.rb
87
+ - lib/clubhouse2/workflow.rb
88
+ homepage: https://www.masabi.com
89
+ licenses:
90
+ - BSD
91
+ metadata: {}
92
+ post_install_message:
93
+ rdoc_options: []
94
+ require_paths:
95
+ - lib
96
+ required_ruby_version: !ruby/object:Gem::Requirement
97
+ requirements:
98
+ - - ">="
99
+ - !ruby/object:Gem::Version
100
+ version: '0'
101
+ required_rubygems_version: !ruby/object:Gem::Requirement
102
+ requirements:
103
+ - - ">="
104
+ - !ruby/object:Gem::Version
105
+ version: '0'
106
+ requirements: []
107
+ rubyforge_project:
108
+ rubygems_version: 2.6.10
109
+ signing_key:
110
+ specification_version: 4
111
+ summary: Clubhouse library for API version 2
112
+ test_files: []