txgh 2.0.1 → 2.1.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 41d11b7b8102cca5e57f005f206a07ae1e80b37b
4
- data.tar.gz: 00d3f323be5b4e668d4ad92e89d4211027899346
3
+ metadata.gz: 602ce638efdadb764883e26d1c3636e84bdd5ede
4
+ data.tar.gz: 11f75ef7992aad2dc8e08a5c63e49f5faf0b140d
5
5
  SHA512:
6
- metadata.gz: 7a609ee7470a0787d7c350ab62fd871eabd615fcf9645d5f9807de6644587da41e52596dcb28437bb568f8a0cc937598db56b9f1e9ccd5358d5a682e02d8cf55
7
- data.tar.gz: 99d8736582500541ba8967d5d621d55a49eec61dd3c0138a0c54f886963112cd52aff95348ce6faa880728e47b7ac4be0cd767ef57b7711c66427dad7dcbfb94
6
+ metadata.gz: 4ffa50d6b1b08c2b754983f4b899ae9f4e25ae224c239efffda00ea3115a94b8873bd1c8a4583cc360036fcc548a7bbd7850d74df3f3299ab17a45325e7f24e3
7
+ data.tar.gz: 58f00d04c536bb5f66a7cb4762bee31bbbf1a59a226f24d62ddc4deb9c53baf0648a89925b995a44308ce68d525f8c03f5b13d700c2d2273f91321933efc97a8
@@ -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
@@ -79,5 +79,9 @@ module Txgh
79
79
  end
80
80
  end
81
81
 
82
+ def create_status(repo, sha, state, options = {})
83
+ client.create_status(repo, sha, state, options)
84
+ end
85
+
82
86
  end
83
87
  end
@@ -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
 
@@ -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: tx_resource.source_file,
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
@@ -1,3 +1,3 @@
1
1
  module Txgh
2
- VERSION = '2.0.1'
2
+ VERSION = '2.1.0'
3
3
  end
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
@@ -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
 
@@ -0,0 +1,12 @@
1
+ class TestEvents < Txgh::Events
2
+ attr_reader :published
3
+
4
+ def initialize
5
+ @published = []
6
+ super
7
+ end
8
+
9
+ def publish(channel, options = {})
10
+ published << { channel: channel, options: options }
11
+ end
12
+ end