jira_cache 0.2.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.
- checksums.yaml +7 -0
- data/.codeclimate.yml +10 -0
- data/.env.example +10 -0
- data/.env.test +10 -0
- data/.gitignore +15 -0
- data/.rspec +2 -0
- data/.rubocop.yml +12 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/.travis.yml +15 -0
- data/CODE_OF_CONDUCT.md +13 -0
- data/Gemfile +21 -0
- data/Guardfile +1 -0
- data/HISTORY.md +22 -0
- data/LICENSE.txt +22 -0
- data/README.md +73 -0
- data/Rakefile +7 -0
- data/VERSION +1 -0
- data/bin/console +11 -0
- data/bin/db/migrate +7 -0
- data/bin/db/psql +7 -0
- data/bin/db/reset +7 -0
- data/bin/setup +7 -0
- data/bin/sync +14 -0
- data/config.ru +7 -0
- data/config/Guardfile +35 -0
- data/config/boot.rb +11 -0
- data/config/db_migrations/001_create_issues.rb +21 -0
- data/docker-compose.yml +25 -0
- data/jira_cache.gemspec +41 -0
- data/lib/jira_cache.rb +53 -0
- data/lib/jira_cache/client.rb +185 -0
- data/lib/jira_cache/data.rb +10 -0
- data/lib/jira_cache/data/issue_repository.rb +94 -0
- data/lib/jira_cache/notifier.rb +30 -0
- data/lib/jira_cache/sync.rb +110 -0
- data/lib/jira_cache/version.rb +3 -0
- data/lib/jira_cache/webhook_app.rb +55 -0
- data/spec/fixtures/responses/get_issue_keys_jql_query_project=/"multiple_requests/"_start_at_0.json +1 -0
- data/spec/fixtures/responses/get_issue_keys_jql_query_project=/"multiple_requests/"_start_at_10.json +1 -0
- data/spec/fixtures/responses/get_issue_keys_jql_query_project=/"multiple_requests/"_start_at_5.json +1 -0
- data/spec/fixtures/responses/get_issue_keys_jql_query_project=/"single_request/"_start_at_0.json +1 -0
- data/spec/fixtures/responses/get_issue_many_worklogs.json +1 -0
- data/spec/fixtures/responses/get_issue_not_found.json +1 -0
- data/spec/fixtures/responses/get_issue_simple.json +1 -0
- data/spec/fixtures/responses/get_issue_worklog_many_worklogs.json +1 -0
- data/spec/spec_helper.rb +47 -0
- data/spec/support/response_fixture.rb +16 -0
- data/spec/unit/client_spec.rb +130 -0
- data/spec/unit/data/issue_repository_spec.rb +58 -0
- data/spec/unit/notifier_spec.rb +18 -0
- data/spec/unit/sync_spec.rb +116 -0
- data/spec/unit/webhook_app_spec.rb +96 -0
- metadata +280 -0
data/jira_cache.gemspec
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
# frozen_string_literal: true
|
3
|
+
lib = File.expand_path("../lib", __FILE__)
|
4
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
5
|
+
require "jira_cache/version"
|
6
|
+
|
7
|
+
Gem::Specification.new do |spec|
|
8
|
+
spec.name = "jira_cache"
|
9
|
+
spec.version = JiraCache::VERSION
|
10
|
+
spec.authors = ["Romain Champourlier"]
|
11
|
+
spec.email = ["pro@rchampourlier.com"]
|
12
|
+
spec.summary = "Fetches data from JIRA and caches it in a local database."
|
13
|
+
spec.homepage = "https://github.com/rchampourlier/jira_cache"
|
14
|
+
spec.license = "MIT"
|
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
|
+
unless spec.respond_to?(:metadata)
|
19
|
+
raise "RubyGems 2.0 or newer is required to protect against public gem pushes."
|
20
|
+
end
|
21
|
+
spec.metadata["allowed_push_host"] = "https://rubygems.org"
|
22
|
+
|
23
|
+
spec.files = `git ls-files -z`.split("\x0")
|
24
|
+
spec.executables = []
|
25
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
26
|
+
spec.require_paths = ["lib"]
|
27
|
+
|
28
|
+
spec.add_dependency "i18n"
|
29
|
+
spec.add_dependency "activesupport-inflector"
|
30
|
+
spec.add_dependency "pg"
|
31
|
+
spec.add_dependency "sequel"
|
32
|
+
spec.add_dependency "sequel_pg"
|
33
|
+
spec.add_dependency "rest-client"
|
34
|
+
spec.add_dependency "sinatra"
|
35
|
+
|
36
|
+
spec.add_development_dependency "bundler", "~> 1.7"
|
37
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
38
|
+
spec.add_development_dependency "pry"
|
39
|
+
spec.add_development_dependency "awesome_print"
|
40
|
+
spec.add_development_dependency "dotenv"
|
41
|
+
end
|
data/lib/jira_cache.rb
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
require "jira_cache/version"
|
2
|
+
require "jira_cache/sync"
|
3
|
+
require "jira_cache/webhook_app"
|
4
|
+
|
5
|
+
# JiraCache enables storing JIRA issues fetched from the API
|
6
|
+
# in a local storage for easier and faster processing.
|
7
|
+
#
|
8
|
+
# This is the main module and it provides some high level
|
9
|
+
# methods to either trigger a full project sync, a single
|
10
|
+
# issue sync or start a Sinatra webhook app to trigger sync
|
11
|
+
# on JIRA"s webhooks.
|
12
|
+
module JiraCache
|
13
|
+
|
14
|
+
# Sync issues using the specified client. If a `project_key` is
|
15
|
+
# specified, only syncs the issues for the corresponding project.
|
16
|
+
def self.sync_issues(client: default_client, project_key: nil)
|
17
|
+
Sync.new(client).sync_issues(project_key: project_key)
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.sync_issue(issue_key, client: default_client)
|
21
|
+
Sync.new(client).sync_issue(issue_key)
|
22
|
+
end
|
23
|
+
|
24
|
+
# @param client [JiraCache::Client]: defaults to a default
|
25
|
+
# client using environment variables for domain, username
|
26
|
+
# and password, a logger writing to STDOUT and a default
|
27
|
+
# `JiraCache::Notifier` instance as notifier.
|
28
|
+
def self.webhook_app(client: default_client)
|
29
|
+
Sinatra.new(JiraCache::WebhookApp) do
|
30
|
+
set(:client, client)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.default_client
|
35
|
+
JiraCache::Client.new(
|
36
|
+
domain: ENV["JIRA_DOMAIN"],
|
37
|
+
username: ENV["JIRA_USERNAME"],
|
38
|
+
password: ENV["JIRA_PASSWORD"],
|
39
|
+
logger: default_logger,
|
40
|
+
notifier: default_notifier
|
41
|
+
)
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.default_logger
|
45
|
+
logger = Logger.new(STDOUT)
|
46
|
+
logger.level = Logger::DEBUG
|
47
|
+
logger
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.default_notifier(logger: default_logger)
|
51
|
+
JiraCache::Notifier.new(logger)
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,185 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require "rest-client"
|
3
|
+
require "base64"
|
4
|
+
require "jira_cache/notifier"
|
5
|
+
|
6
|
+
module JiraCache
|
7
|
+
|
8
|
+
# The JIRA API Client.
|
9
|
+
class Client
|
10
|
+
JIRA_MAX_RESULTS = 1000
|
11
|
+
|
12
|
+
EXPANDED_FIELDS = %w(
|
13
|
+
renderedFields
|
14
|
+
changelog
|
15
|
+
).freeze
|
16
|
+
# Other possible fields: names, schema, operations, editmeta
|
17
|
+
|
18
|
+
attr_reader :logger, :notifier
|
19
|
+
|
20
|
+
# Returns a new instance of the client, configured with
|
21
|
+
# the specified parameters.
|
22
|
+
#
|
23
|
+
# @param domain [String] JIRA API domain (e.g. your-project.atlassian.net)
|
24
|
+
# @param username [String] JIRA user"s name, if required
|
25
|
+
# @param password [String] JIRA user"s password, if required
|
26
|
+
# @param logger [Logger] used to log message (defaults to a logger to STDOUT at
|
27
|
+
# info level)
|
28
|
+
# @param notifier [Notifier] a notifier instance that will be used to publish
|
29
|
+
# event notifications (see `JiraCache::Notifier` for more information)
|
30
|
+
#
|
31
|
+
def initialize(domain: ENV["JIRA_DOMAIN"],
|
32
|
+
username: ENV["JIRA_USERNAME"],
|
33
|
+
password: ENV["JIRA_PASSWORD"],
|
34
|
+
notifier: default_notifier,
|
35
|
+
logger: default_logger)
|
36
|
+
check_domain!(domain)
|
37
|
+
check_password!(username, password)
|
38
|
+
@domain = domain
|
39
|
+
@username = username
|
40
|
+
@password = password
|
41
|
+
@notifier = notifier
|
42
|
+
@logger = logger
|
43
|
+
end
|
44
|
+
|
45
|
+
# Fetches the issue represented by id_or_key from the
|
46
|
+
# client.
|
47
|
+
# If the data is already present in the cache,
|
48
|
+
# returns the cached version, unless if :allow_cache
|
49
|
+
# option is false.
|
50
|
+
def issue_data(id_or_key)
|
51
|
+
logger.info "Fetching data for issue #{id_or_key}"
|
52
|
+
issue_data = do_get("/issue/#{id_or_key}",
|
53
|
+
expand: EXPANDED_FIELDS.join(",")
|
54
|
+
).to_hash
|
55
|
+
return nil if issue_not_found?(issue_data)
|
56
|
+
issue_data = complete_worklogs(id_or_key, issue_data)
|
57
|
+
begin
|
58
|
+
notifier.publish "fetched_issue", key: id_or_key, data: issue_data
|
59
|
+
rescue => e
|
60
|
+
logger.error "Notifier failed: #{e}"
|
61
|
+
logger.error e.backtrace
|
62
|
+
end
|
63
|
+
issue_data
|
64
|
+
end
|
65
|
+
|
66
|
+
def issue_keys_for_query(jql_query)
|
67
|
+
start_at = 0
|
68
|
+
issues = []
|
69
|
+
loop do
|
70
|
+
total, page_issues = issue_ids_in_limits(jql_query, start_at)
|
71
|
+
logger.info "Total number of issues: #{total}" if issues.length == 0
|
72
|
+
issues += page_issues
|
73
|
+
logger.info " -- loaded #{page_issues.length} issues"
|
74
|
+
start_at = issues.length
|
75
|
+
break if issues.length == total
|
76
|
+
end
|
77
|
+
issues.collect { |issue| issue["key"] }
|
78
|
+
end
|
79
|
+
|
80
|
+
# Implementation methods
|
81
|
+
# ======================
|
82
|
+
|
83
|
+
# @return [total, issues]
|
84
|
+
# - total: [Int] the total number of issues in the query results
|
85
|
+
# - issues: [Array] array of issues in the response
|
86
|
+
# (max `JIRA_MAX_RESULTS`)
|
87
|
+
def issue_ids_in_limits(jql_query, start_at)
|
88
|
+
results = do_get "/search",
|
89
|
+
jql: jql_query,
|
90
|
+
startAt: start_at,
|
91
|
+
fields: "id",
|
92
|
+
maxResults: JIRA_MAX_RESULTS
|
93
|
+
[results["total"], results["issues"]]
|
94
|
+
end
|
95
|
+
|
96
|
+
def issue_not_found?(issue_data)
|
97
|
+
return false if issue_data["errorMessages"].nil?
|
98
|
+
issue_data["errorMessages"].first == "Issue Does Not Exist"
|
99
|
+
end
|
100
|
+
|
101
|
+
def complete_worklogs(id_or_key, issue_data)
|
102
|
+
if incomplete_worklogs?(issue_data)
|
103
|
+
issue_data["fields"]["worklog"] = issue_worklog_content(id_or_key)
|
104
|
+
end
|
105
|
+
issue_data
|
106
|
+
end
|
107
|
+
|
108
|
+
def incomplete_worklogs?(issue_data)
|
109
|
+
worklog = issue_data["fields"]["worklog"]
|
110
|
+
worklog["total"].to_i > worklog["maxResults"].to_i
|
111
|
+
end
|
112
|
+
|
113
|
+
def issue_worklog_content(id_or_key)
|
114
|
+
do_get("/issue/#{id_or_key}/worklog").to_hash
|
115
|
+
end
|
116
|
+
|
117
|
+
def project_data(id)
|
118
|
+
do_get "/project/#{id}"
|
119
|
+
end
|
120
|
+
|
121
|
+
def projects_data
|
122
|
+
do_get "/project"
|
123
|
+
end
|
124
|
+
|
125
|
+
def do_get(path, params = {})
|
126
|
+
logger.debug "GET #{uri(path)} #{params}"
|
127
|
+
response = RestClient.get uri(path),
|
128
|
+
params: params,
|
129
|
+
content_type: "application/json"
|
130
|
+
begin
|
131
|
+
JSON.parse(response.body)
|
132
|
+
rescue JSON::ParseError
|
133
|
+
response.body
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
# Returns the JIRA API"s base URI (build using `config[:domain]`)
|
138
|
+
def uri(path)
|
139
|
+
"https://#{authorization_prefix}#{@domain}/rest/api/2#{path}"
|
140
|
+
end
|
141
|
+
|
142
|
+
def authorization_prefix
|
143
|
+
return "" if missing_credential?
|
144
|
+
"#{CGI.escape(@username)}:#{CGI.escape(@password)}@"
|
145
|
+
end
|
146
|
+
|
147
|
+
def default_logger
|
148
|
+
return @logger unless @logger.nil?
|
149
|
+
@logger = ::Logger.new(STDOUT)
|
150
|
+
@logger.level = ::Logger::FATAL
|
151
|
+
@logger
|
152
|
+
end
|
153
|
+
|
154
|
+
def default_notifier
|
155
|
+
return @notifier unless @notifier.nil?
|
156
|
+
@notifier = JiraCache::Notifier.new(@logger)
|
157
|
+
end
|
158
|
+
|
159
|
+
# Returns an hash of info on the client
|
160
|
+
def info
|
161
|
+
{
|
162
|
+
domain: @domain,
|
163
|
+
username: @username
|
164
|
+
}
|
165
|
+
end
|
166
|
+
|
167
|
+
private
|
168
|
+
|
169
|
+
def check_domain!(domain)
|
170
|
+
raise "Missing domain" if domain.nil? || domain.empty?
|
171
|
+
end
|
172
|
+
|
173
|
+
def check_password!(username, password)
|
174
|
+
unless (username.nil? || username.empty?)
|
175
|
+
raise "Missing password (mandatory if username given)" if password.nil? || password.empty?
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
def missing_credential?
|
180
|
+
return true if @username.nil? || @username.empty?
|
181
|
+
return true if @password.nil? || @password.empty?
|
182
|
+
false
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require "jira_cache/data"
|
3
|
+
require "active_support/inflector"
|
4
|
+
|
5
|
+
module JiraCache
|
6
|
+
module Data
|
7
|
+
|
8
|
+
# Superclass for repositories. Simply provide some shared
|
9
|
+
# methods.
|
10
|
+
class IssueRepository
|
11
|
+
|
12
|
+
# It inserts a new issue row with the specified data.
|
13
|
+
# If the issue already exists (checking on the "key"),
|
14
|
+
# the row is updated instead.
|
15
|
+
def self.insert(key:, data:, synced_at:, deleted_from_jira_at: nil)
|
16
|
+
attributes = {
|
17
|
+
key: key,
|
18
|
+
data: Sequel.pg_json(data),
|
19
|
+
synced_at: synced_at,
|
20
|
+
deleted_from_jira_at: deleted_from_jira_at
|
21
|
+
}
|
22
|
+
if exist_with_key?(key)
|
23
|
+
update_where({ key: key }, attributes)
|
24
|
+
else
|
25
|
+
table.insert row(attributes)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.find_by_key(key)
|
30
|
+
table.where(key: key).first
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.exist_with_key?(key)
|
34
|
+
table.where(key: key).count != 0
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.keys_in_project(project_key)
|
38
|
+
table.where("(data #>> '{fields,project,key}') = ?", project_key).select(:key).map(&:values).flatten
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.keys_for_non_deleted_issues
|
42
|
+
table
|
43
|
+
.where("deleted_from_jira_at IS NULL")
|
44
|
+
.select(:key)
|
45
|
+
.map(&:values)
|
46
|
+
.flatten
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.keys_for_deleted_issues
|
50
|
+
table
|
51
|
+
.where("deleted_from_jira_at IS NOT NULL")
|
52
|
+
.select(:key)
|
53
|
+
.map(&:values)
|
54
|
+
.flatten
|
55
|
+
end
|
56
|
+
|
57
|
+
def self.delete_where(where_data)
|
58
|
+
table.where(where_data).delete
|
59
|
+
end
|
60
|
+
|
61
|
+
def self.update_where(where_data, values)
|
62
|
+
table.where(where_data).update(values)
|
63
|
+
end
|
64
|
+
|
65
|
+
def self.first_where(where_data)
|
66
|
+
table.where(where_data).first
|
67
|
+
end
|
68
|
+
|
69
|
+
def self.index
|
70
|
+
table.entries
|
71
|
+
end
|
72
|
+
|
73
|
+
def self.count
|
74
|
+
table.count
|
75
|
+
end
|
76
|
+
|
77
|
+
def self.latest_sync_time
|
78
|
+
table.order(:synced_at).select(:synced_at).last&.dig(:synced_at)
|
79
|
+
end
|
80
|
+
|
81
|
+
def self.row(attributes, time = nil)
|
82
|
+
time ||= Time.now
|
83
|
+
attributes.merge(
|
84
|
+
created_at: time,
|
85
|
+
updated_at: time
|
86
|
+
)
|
87
|
+
end
|
88
|
+
|
89
|
+
def self.table
|
90
|
+
DB[:jira_cache_issues]
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module JiraCache
|
2
|
+
|
3
|
+
# This notifiers simply logs messages using the specified
|
4
|
+
# logger.
|
5
|
+
#
|
6
|
+
# If you want to use this mechanism to trigger actions when
|
7
|
+
# events are triggered in JiraCache, you can use the
|
8
|
+
# `JiraCache::Client.set_notifier(notifier)` method and pass
|
9
|
+
# it an instance of a notifier class implementing the
|
10
|
+
# `#publish` method with the same signature as
|
11
|
+
# `JiraCache::Notifier#publish`.
|
12
|
+
class Notifier
|
13
|
+
|
14
|
+
# Initializes a notifier with the specified logger. The
|
15
|
+
# logger is used to log info messages when #publish
|
16
|
+
# is called.
|
17
|
+
def initialize(logger)
|
18
|
+
@logger = logger
|
19
|
+
end
|
20
|
+
|
21
|
+
# Simply logs the event name and data.
|
22
|
+
# @param event_name [String] e.g. "fetched_issue"
|
23
|
+
# @param data [Hash]
|
24
|
+
# - :key [String] issue key
|
25
|
+
# - :data [Hash] issue data
|
26
|
+
def publish(event_name, data = nil)
|
27
|
+
@logger.info "[#{event_name}] #{data[:key]}"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,110 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require "jira_cache/data/issue_repository"
|
3
|
+
require "jira_cache/client"
|
4
|
+
|
5
|
+
module JiraCache
|
6
|
+
|
7
|
+
# Performs the sync between JIRA and the local database
|
8
|
+
# where the issues are cached.
|
9
|
+
#
|
10
|
+
# The issues are cached in the database through the
|
11
|
+
# Data::IssueRepository interface. It currently implements
|
12
|
+
# storage into a PostgreSQL database.
|
13
|
+
class Sync
|
14
|
+
attr_reader :client, :logger
|
15
|
+
|
16
|
+
def initialize(client)
|
17
|
+
@client = client
|
18
|
+
@logger = client.logger
|
19
|
+
end
|
20
|
+
|
21
|
+
# Fetches new and updated raw issues, save them
|
22
|
+
# to the `issues` collection. Also mark issues
|
23
|
+
# deleted from JIRA as such.
|
24
|
+
#
|
25
|
+
# @param project_key [String] the JIRA project key
|
26
|
+
def sync_issues(project_key: nil)
|
27
|
+
sync_start = Time.now
|
28
|
+
|
29
|
+
log "Determining which issues to fetch..."
|
30
|
+
remote = remote_keys(project_key: project_key)
|
31
|
+
log " - #{remote.count} remote issues"
|
32
|
+
|
33
|
+
cached = cached_keys(project_key: project_key)
|
34
|
+
log " - #{cached.count} cached issues"
|
35
|
+
|
36
|
+
missing = remote - cached
|
37
|
+
log " => #{missing.count} missing issues"
|
38
|
+
|
39
|
+
updated = updated_keys(project_key: project_key)
|
40
|
+
log " - #{updated.count} updated issues"
|
41
|
+
|
42
|
+
log "Fetching #{missing.count + updated.count} issues"
|
43
|
+
fetch_issues(missing + updated, sync_start)
|
44
|
+
|
45
|
+
deleted = cached - remote
|
46
|
+
mark_deleted(deleted)
|
47
|
+
end
|
48
|
+
|
49
|
+
def sync_issue(key, sync_time: Time.now)
|
50
|
+
data = client.issue_data(key)
|
51
|
+
Data::IssueRepository.insert(
|
52
|
+
key: key,
|
53
|
+
data: data,
|
54
|
+
synced_at: sync_time
|
55
|
+
)
|
56
|
+
end
|
57
|
+
|
58
|
+
# IMPLEMENTATION FUNCTIONS
|
59
|
+
|
60
|
+
def remote_keys(project_key: nil)
|
61
|
+
fetch_issue_keys(project_key: project_key)
|
62
|
+
end
|
63
|
+
|
64
|
+
def cached_keys(project_key: nil)
|
65
|
+
Data::IssueRepository.keys_in_project(project_key: project_key)
|
66
|
+
end
|
67
|
+
|
68
|
+
def updated_keys(project_key: nil)
|
69
|
+
time = latest_sync_time(project_key: project_key)
|
70
|
+
fetch_issue_keys(project_key: project_key, updated_since: time)
|
71
|
+
end
|
72
|
+
|
73
|
+
# Fetch from JIRA
|
74
|
+
|
75
|
+
# Fetch issue keys from JIRA using the specified `JiraCache::Client`
|
76
|
+
# instance, for the specified project, with an optional `updated_since`
|
77
|
+
# parameter.
|
78
|
+
#
|
79
|
+
# @param project_key [String]
|
80
|
+
# @param updated_since [Time]
|
81
|
+
# @return [Array] array of issue keys as strings
|
82
|
+
def fetch_issue_keys(project_key: nil, updated_since: nil)
|
83
|
+
query_items = []
|
84
|
+
query_items << "project = \"#{project_key}\"" unless project_key.nil?
|
85
|
+
query_items << "updatedDate > \"#{updated_since.strftime('%Y-%m-%d %H:%M')}\"" unless updated_since.nil?
|
86
|
+
query = query_items.join(" AND ")
|
87
|
+
client.issue_keys_for_query(query)
|
88
|
+
end
|
89
|
+
|
90
|
+
# @param issue_keys [Array] array of strings representing the JIRA keys
|
91
|
+
def fetch_issues(issue_keys, sync_time)
|
92
|
+
issue_keys.each do |issue_key|
|
93
|
+
sync_issue(issue_key, sync_time: sync_time)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def mark_deleted(issue_keys)
|
98
|
+
Data::IssueRepository.update_where({ key: issue_keys }, deleted_from_jira_at: Time.now)
|
99
|
+
end
|
100
|
+
|
101
|
+
def latest_sync_time(project_key)
|
102
|
+
Data::IssueRepository.latest_sync_time
|
103
|
+
end
|
104
|
+
|
105
|
+
def log(message)
|
106
|
+
return if logger.nil?
|
107
|
+
logger.info(message)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|