clubhouse2 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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: []