txgh 2.0.1 → 2.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/txgh/events.rb +35 -0
- data/lib/txgh/github_api.rb +4 -0
- data/lib/txgh/github_status.rb +87 -0
- data/lib/txgh/resource_committer.rb +13 -0
- data/lib/txgh/resource_updater.rb +10 -0
- data/lib/txgh/transifex_api.rb +7 -1
- data/lib/txgh/version.rb +1 -1
- data/lib/txgh.rb +22 -0
- data/spec/events_spec.rb +45 -0
- data/spec/github_status_spec.rb +68 -0
- data/spec/handlers/transifex/hook_handler_spec.rb +4 -0
- data/spec/helpers/standard_txgh_setup.rb +3 -1
- data/spec/helpers/test_events.rb +12 -0
- data/spec/integration/cassettes/transifex_hook_endpoint.yml +210 -137
- data/spec/resource_committer_spec.rb +34 -8
- data/spec/resource_updater_spec.rb +17 -0
- data/spec/spec_helper.rb +6 -0
- data/spec/transifex_api_spec.rb +16 -1
- metadata +7 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 602ce638efdadb764883e26d1c3636e84bdd5ede
|
4
|
+
data.tar.gz: 11f75ef7992aad2dc8e08a5c63e49f5faf0b140d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4ffa50d6b1b08c2b754983f4b899ae9f4e25ae224c239efffda00ea3115a94b8873bd1c8a4583cc360036fcc548a7bbd7850d74df3f3299ab17a45325e7f24e3
|
7
|
+
data.tar.gz: 58f00d04c536bb5f66a7cb4762bee31bbbf1a59a226f24d62ddc4deb9c53baf0648a89925b995a44308ce68d525f8c03f5b13d700c2d2273f91321933efc97a8
|
data/lib/txgh/events.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
module Txgh
|
2
|
+
class Events
|
3
|
+
ERROR_CHANNEL = 'errors'
|
4
|
+
|
5
|
+
attr_reader :channel_hash
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
@channel_hash = Hash.new { |h, k| h[k] = [] }
|
9
|
+
end
|
10
|
+
|
11
|
+
def subscribe(channel, &block)
|
12
|
+
channel_hash[channel] << block
|
13
|
+
end
|
14
|
+
|
15
|
+
def publish(channel, options = {})
|
16
|
+
channel_hash.fetch(channel, []).each do |callback|
|
17
|
+
callback.call(options)
|
18
|
+
end
|
19
|
+
rescue => e
|
20
|
+
publish_error(e)
|
21
|
+
end
|
22
|
+
|
23
|
+
def channels
|
24
|
+
channel_hash.keys
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def publish_error(e)
|
30
|
+
# if nobody has subscribed to error events, raise original error
|
31
|
+
callbacks = channel_hash.fetch(ERROR_CHANNEL) { raise e }
|
32
|
+
callbacks.each { |callback| callback.call(e) }
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
data/lib/txgh/github_api.rb
CHANGED
@@ -0,0 +1,87 @@
|
|
1
|
+
module Txgh
|
2
|
+
class GithubStatus
|
3
|
+
class State
|
4
|
+
PENDING = 'pending'
|
5
|
+
SUCCESS = 'success'
|
6
|
+
ERROR = 'error'
|
7
|
+
FAILURE = 'failure'
|
8
|
+
|
9
|
+
class << self
|
10
|
+
def pending; PENDING; end
|
11
|
+
def success; SUCCESS; end
|
12
|
+
def error; ERROR; end
|
13
|
+
def failure; failure; end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
ALL_COMPLETE_DESCRIPTION = "Translations complete!"
|
18
|
+
TARGET_URL_TEMPLATE = "https://www.transifex.com/%{organization}/%{project_slug}/%{resource_slug}/"
|
19
|
+
DESCRIPTION_TEMPLATE = "%{complete}/%{total} translations complete."
|
20
|
+
CONTEXT = 'continuous-localization/txgh'
|
21
|
+
|
22
|
+
attr_reader :project, :repo, :tx_resource
|
23
|
+
|
24
|
+
def initialize(project, repo, tx_resource)
|
25
|
+
@project = project
|
26
|
+
@repo = repo
|
27
|
+
@tx_resource = tx_resource
|
28
|
+
end
|
29
|
+
|
30
|
+
def update(sha)
|
31
|
+
repo.api.create_status(
|
32
|
+
repo.name, sha, state, {
|
33
|
+
context: context, target_url: target_url, description: description
|
34
|
+
}
|
35
|
+
)
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def context
|
41
|
+
CONTEXT
|
42
|
+
end
|
43
|
+
|
44
|
+
def target_url
|
45
|
+
TARGET_URL_TEMPLATE % {
|
46
|
+
organization: project.organization,
|
47
|
+
project_slug: tx_resource.project_slug,
|
48
|
+
resource_slug: tx_resource.resource_slug
|
49
|
+
}
|
50
|
+
end
|
51
|
+
|
52
|
+
def state
|
53
|
+
if all_complete?
|
54
|
+
State.success
|
55
|
+
else
|
56
|
+
State.pending
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def description
|
61
|
+
if all_complete?
|
62
|
+
ALL_COMPLETE_DESCRIPTION
|
63
|
+
else
|
64
|
+
DESCRIPTION_TEMPLATE % stat_totals
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def all_complete?
|
69
|
+
stats.all? do |locale, details|
|
70
|
+
details['completed'] == '100%'
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def stat_totals
|
75
|
+
@stat_totals ||= { complete: 0, total: 0 }.tap do |counts|
|
76
|
+
stats.each_pair do |locale, details|
|
77
|
+
counts[:total] += details['translated_entities'] + details['untranslated_entities']
|
78
|
+
counts[:complete] += details['translated_entities']
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def stats
|
84
|
+
@stats ||= project.api.get_stats(*tx_resource.slugs)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -16,12 +16,25 @@ module Txgh
|
|
16
16
|
|
17
17
|
if translations
|
18
18
|
repo.api.commit(repo.name, branch, { file_name => translations })
|
19
|
+
fire_event_for(tx_resource, branch, language)
|
19
20
|
end
|
20
21
|
end
|
21
22
|
end
|
22
23
|
|
23
24
|
private
|
24
25
|
|
26
|
+
def fire_event_for(tx_resource, branch, language)
|
27
|
+
head = repo.api.get_ref(repo.name, branch)
|
28
|
+
sha = head[:object][:sha]
|
29
|
+
|
30
|
+
Txgh.events.publish(
|
31
|
+
'github.resource.committed', {
|
32
|
+
project: project, repo: repo, resource: tx_resource, sha: sha,
|
33
|
+
language: language
|
34
|
+
}
|
35
|
+
)
|
36
|
+
end
|
37
|
+
|
25
38
|
def download(tx_resource, branch, language)
|
26
39
|
downloader = ResourceDownloader.new(
|
27
40
|
project, repo, branch, {
|
@@ -31,12 +31,22 @@ module Txgh
|
|
31
31
|
else
|
32
32
|
upload_whole(tx_resource, file, categories)
|
33
33
|
end
|
34
|
+
|
35
|
+
fire_event_for(tx_resource, commit_sha)
|
34
36
|
end
|
35
37
|
end
|
36
38
|
end
|
37
39
|
|
38
40
|
private
|
39
41
|
|
42
|
+
def fire_event_for(tx_resource, commit_sha)
|
43
|
+
Txgh.events.publish(
|
44
|
+
'transifex.resource.updated', {
|
45
|
+
project: project, repo: repo, resource: tx_resource, sha: commit_sha
|
46
|
+
}
|
47
|
+
)
|
48
|
+
end
|
49
|
+
|
40
50
|
def upload_whole(tx_resource, file, categories)
|
41
51
|
content = contents_of(file['sha'])
|
42
52
|
|
data/lib/txgh/transifex_api.rb
CHANGED
@@ -50,9 +50,15 @@ module Txgh
|
|
50
50
|
end
|
51
51
|
|
52
52
|
def create(tx_resource, content, categories = [])
|
53
|
+
name = if tx_resource.branch
|
54
|
+
"#{tx_resource.source_file} (#{tx_resource.branch})"
|
55
|
+
else
|
56
|
+
tx_resource.source_file
|
57
|
+
end
|
58
|
+
|
53
59
|
payload = {
|
54
60
|
slug: tx_resource.resource_slug,
|
55
|
-
name:
|
61
|
+
name: name,
|
56
62
|
i18n_type: tx_resource.type,
|
57
63
|
categories: CategorySupport.join_categories(categories.uniq),
|
58
64
|
content: get_content_io(tx_resource, content)
|
data/lib/txgh/version.rb
CHANGED
data/lib/txgh.rb
CHANGED
@@ -7,9 +7,11 @@ module Txgh
|
|
7
7
|
autoload :Config, 'txgh/config'
|
8
8
|
autoload :DiffCalculator, 'txgh/diff_calculator'
|
9
9
|
autoload :EmptyResourceContents, 'txgh/empty_resource_contents'
|
10
|
+
autoload :Events, 'txgh/events'
|
10
11
|
autoload :GithubApi, 'txgh/github_api'
|
11
12
|
autoload :GithubRepo, 'txgh/github_repo'
|
12
13
|
autoload :GithubRequestAuth, 'txgh/github_request_auth'
|
14
|
+
autoload :GithubStatus, 'txgh/github_status'
|
13
15
|
autoload :Handlers, 'txgh/handlers'
|
14
16
|
autoload :Hooks, 'txgh/app'
|
15
17
|
autoload :MergeCalculator, 'txgh/merge_calculator'
|
@@ -40,6 +42,18 @@ module Txgh
|
|
40
42
|
def providers
|
41
43
|
Txgh::Config::Providers
|
42
44
|
end
|
45
|
+
|
46
|
+
def events
|
47
|
+
@events ||= Events.new
|
48
|
+
end
|
49
|
+
|
50
|
+
def update_status_callback(options)
|
51
|
+
project = options.fetch(:project)
|
52
|
+
repo = options.fetch(:repo)
|
53
|
+
resource = options.fetch(:resource)
|
54
|
+
|
55
|
+
GithubStatus.new(project, repo, resource).update(options.fetch(:sha))
|
56
|
+
end
|
43
57
|
end
|
44
58
|
|
45
59
|
# default set of tx config providers
|
@@ -50,4 +64,12 @@ module Txgh
|
|
50
64
|
# default set of base config providers
|
51
65
|
key_manager.register_provider(providers::FileProvider, YAML)
|
52
66
|
key_manager.register_provider(providers::RawProvider, YAML)
|
67
|
+
|
68
|
+
events.subscribe('transifex.resource.updated') do |options|
|
69
|
+
update_status_callback(options)
|
70
|
+
end
|
71
|
+
|
72
|
+
events.subscribe('github.resource.committed') do |options|
|
73
|
+
update_status_callback(options)
|
74
|
+
end
|
53
75
|
end
|
data/spec/events_spec.rb
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
include Txgh
|
4
|
+
|
5
|
+
describe Events do
|
6
|
+
let(:events) { Events.new }
|
7
|
+
|
8
|
+
describe '#subscribe and #channels' do
|
9
|
+
it "adds a channel if it doesn't already exist" do
|
10
|
+
expect(events.channels).to be_empty
|
11
|
+
events.subscribe('foo.bar')
|
12
|
+
expect(events.channels).to eq(['foo.bar'])
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'adds the proc to the list of callbacks for the channel' do
|
16
|
+
events.subscribe('foo.bar') { 'baz' }
|
17
|
+
expect(events.channel_hash['foo.bar'].first.call).to eq('baz')
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
describe '#publish' do
|
22
|
+
it 'notifies all subscribers' do
|
23
|
+
received = []
|
24
|
+
events.subscribe('foo.bar') { |arg| received << "foo.bar #{arg}" }
|
25
|
+
events.subscribe('foo.bar') { |arg| received << "foo.bar2 #{arg}" }
|
26
|
+
events.publish('foo.bar', 'baz')
|
27
|
+
expect(received).to eq([
|
28
|
+
'foo.bar baz', 'foo.bar2 baz'
|
29
|
+
])
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'publishes errors through a special errors channel' do
|
33
|
+
errors = []
|
34
|
+
events.subscribe('errors') { |e| errors << e }
|
35
|
+
events.subscribe('foo.bar') { raise 'jelly beans' }
|
36
|
+
expect { events.publish('foo.bar') }.to_not raise_error
|
37
|
+
expect(errors.first.message).to eq('jelly beans')
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'raises errors if there are no error subscribers' do
|
41
|
+
events.subscribe('foo.bar') { raise 'jelly beans' }
|
42
|
+
expect { events.publish('foo.bar') }.to raise_error('jelly beans')
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'helpers/standard_txgh_setup'
|
3
|
+
|
4
|
+
include Txgh
|
5
|
+
|
6
|
+
describe GithubStatus do
|
7
|
+
include StandardTxghSetup
|
8
|
+
|
9
|
+
describe '#update' do
|
10
|
+
let(:status) { GithubStatus.new(transifex_project, github_repo, resource) }
|
11
|
+
let(:resource) { tx_config.resource(resource_slug) }
|
12
|
+
let(:sha) { 'abc123shashasha' }
|
13
|
+
|
14
|
+
let(:stats) do
|
15
|
+
supported_languages.each_with_object({}) do |language, ret|
|
16
|
+
ret[language] = {
|
17
|
+
'translated_entities' => 10, 'untranslated_entities' => 0,
|
18
|
+
'completed' => '100%'
|
19
|
+
}
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
before(:each) do
|
24
|
+
allow(transifex_api).to receive(:get_stats).and_return(stats)
|
25
|
+
end
|
26
|
+
|
27
|
+
context 'with all resources at 100%' do
|
28
|
+
it 'reports status as success' do
|
29
|
+
expect(github_api).to receive(:create_status) do |repo, commit_sha, state, options|
|
30
|
+
expect(repo).to eq(repo_name)
|
31
|
+
expect(commit_sha).to eq(sha)
|
32
|
+
expect(state).to eq(GithubStatus::State.success)
|
33
|
+
expect(options[:description]).to eq('Translations complete!')
|
34
|
+
expect(options[:context]).to eq('continuous-localization/txgh')
|
35
|
+
expect(options[:target_url]).to eq(
|
36
|
+
"https://www.transifex.com/#{organization}/#{project_name}/#{resource_slug}/"
|
37
|
+
)
|
38
|
+
end
|
39
|
+
|
40
|
+
status.update(sha)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
context 'with one language at less than 100%' do
|
45
|
+
let(:stats) do
|
46
|
+
{
|
47
|
+
'pt' => {
|
48
|
+
'translated_entities' => 10, 'untranslated_entities' => 0,
|
49
|
+
'completed' => '100%'
|
50
|
+
},
|
51
|
+
'ja' => {
|
52
|
+
'translated_entities' => 5, 'untranslated_entities' => 5,
|
53
|
+
'completed' => '50%'
|
54
|
+
}
|
55
|
+
}
|
56
|
+
end
|
57
|
+
|
58
|
+
it 'reports status as pending' do
|
59
|
+
expect(github_api).to receive(:create_status) do |repo, commit_sha, state, options|
|
60
|
+
expect(state).to eq(GithubStatus::State.pending)
|
61
|
+
expect(options[:description]).to eq('15/20 translations complete.')
|
62
|
+
end
|
63
|
+
|
64
|
+
status.update(sha)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -31,6 +31,10 @@ describe HookHandler do
|
|
31
31
|
allow(downloader).to(receive(:first)).and_return([
|
32
32
|
"translations/#{language}/sample.yml", translations
|
33
33
|
])
|
34
|
+
|
35
|
+
allow(github_api).to receive(:get_ref).and_return(
|
36
|
+
object: { sha: '123abcshashasha' }
|
37
|
+
)
|
34
38
|
end
|
35
39
|
|
36
40
|
it 'downloads translations and pushes them to the correct branch (head)' do
|
@@ -18,6 +18,7 @@ module StandardTxghSetup
|
|
18
18
|
let(:supported_languages) { [language] }
|
19
19
|
let(:translations) { 'translation file contents' }
|
20
20
|
let(:diff_point) { nil }
|
21
|
+
let(:organization) { 'myorg' }
|
21
22
|
|
22
23
|
let(:project_config) do
|
23
24
|
{
|
@@ -28,7 +29,8 @@ module StandardTxghSetup
|
|
28
29
|
'tx_config' => "raw://#{tx_config_raw}",
|
29
30
|
'webhook_secret' => 'abc123',
|
30
31
|
'auto_delete_resources' => 'true',
|
31
|
-
'languages' => supported_languages
|
32
|
+
'languages' => supported_languages,
|
33
|
+
'organization' => organization
|
32
34
|
}
|
33
35
|
end
|
34
36
|
|