txbr 1.1.1 → 2.0.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 +4 -4
- data/README.md +16 -10
- data/lib/txbr.rb +11 -23
- data/lib/txbr/braze_api.rb +4 -20
- data/lib/txbr/campaign.rb +39 -0
- data/lib/txbr/campaign_handler.rb +24 -0
- data/lib/txbr/campaigns_api.rb +29 -0
- data/lib/txbr/content_tag.rb +64 -0
- data/lib/txbr/email_template.rb +9 -40
- data/lib/txbr/email_template_handler.rb +1 -1
- data/lib/txbr/email_templates_api.rb +33 -0
- data/lib/txbr/liquid.rb +5 -0
- data/lib/txbr/liquid/connected_content_tag.rb +99 -0
- data/lib/txbr/metadata.rb +30 -0
- data/lib/txbr/request_methods.rb +0 -2
- data/lib/txbr/template.rb +54 -0
- data/lib/txbr/template_group.rb +50 -0
- data/lib/txbr/version.rb +1 -1
- data/spec/campaign_spec.rb +136 -0
- data/spec/campaigns_api_spec.rb +71 -0
- data/spec/email_template_spec.rb +25 -10
- data/spec/{braze_api_spec.rb → email_templates_api_spec.rb} +11 -48
- data/spec/shared_examples/api_errors.rb +40 -0
- data/spec/uploader_spec.rb +10 -4
- metadata +16 -4
- data/lib/txbr/email_template_component.rb +0 -96
data/spec/email_template_spec.rb
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
require 'spec_helper'
|
|
2
2
|
require 'support/standard_setup'
|
|
3
|
+
require 'json'
|
|
3
4
|
|
|
4
5
|
describe Txbr::EmailTemplate do
|
|
5
6
|
include_context 'standard setup'
|
|
@@ -14,7 +15,8 @@ describe Txbr::EmailTemplate do
|
|
|
14
15
|
{% assign project_slug = "my_project" %}
|
|
15
16
|
{% assign resource_slug = "my_resource" %}
|
|
16
17
|
{% assign translation_enabled = true %}
|
|
17
|
-
{% connected_content http://my_strings_api.com
|
|
18
|
+
{% connected_content http://my_strings_api.com?project_slug={{project_slug}}&resource_slug={{resource_slug}} :save strings %}
|
|
19
|
+
{% connected_content http://my_strings_api.com?project_slug=my_project&resource_slug=my_footer_resource :save footer %}
|
|
18
20
|
</head>
|
|
19
21
|
<body>
|
|
20
22
|
{{strings.header | default: 'Buy our stuff!'}}
|
|
@@ -23,6 +25,7 @@ describe Txbr::EmailTemplate do
|
|
|
23
25
|
{% else %}
|
|
24
26
|
{{strings.no_discount | default: 'You get no discount'}}
|
|
25
27
|
{% endif %}
|
|
28
|
+
{{footer.company | default: 'Megamarketing Corp'}}
|
|
26
29
|
</body>
|
|
27
30
|
</html>
|
|
28
31
|
HTML
|
|
@@ -33,7 +36,7 @@ describe Txbr::EmailTemplate do
|
|
|
33
36
|
{% assign project_slug = "my_project" %}
|
|
34
37
|
{% assign resource_slug = "my_resource" %}
|
|
35
38
|
{% assign translation_enabled = true %}
|
|
36
|
-
{% connected_content http://my_strings_api.com
|
|
39
|
+
{% connected_content http://my_strings_api.com?project_slug={{project_slug}}&resource_slug={{resource_slug}} :save strings %}
|
|
37
40
|
{{strings.meta.subject_line | default: 'You lucky duck maybe'}}
|
|
38
41
|
HTML
|
|
39
42
|
end
|
|
@@ -43,7 +46,7 @@ describe Txbr::EmailTemplate do
|
|
|
43
46
|
{% assign project_slug = "my_project" %}
|
|
44
47
|
{% assign resource_slug = "my_resource" %}
|
|
45
48
|
{% assign translation_enabled = true %}
|
|
46
|
-
{% connected_content http://my_strings_api.com
|
|
49
|
+
{% connected_content http://my_strings_api.com?project_slug={{project_slug}}&resource_slug={{resource_slug}} :save strings %}
|
|
47
50
|
{{strings.meta.preheader | default: 'Our stuff is the bomb and you should buy it.'}}
|
|
48
51
|
HTML
|
|
49
52
|
end
|
|
@@ -53,7 +56,7 @@ describe Txbr::EmailTemplate do
|
|
|
53
56
|
[{
|
|
54
57
|
request: {
|
|
55
58
|
verb: 'get',
|
|
56
|
-
url: Txbr::
|
|
59
|
+
url: Txbr::EmailTemplatesApi::TEMPLATE_DETAILS_PATH,
|
|
57
60
|
params: { email_template_id: email_template_id }
|
|
58
61
|
},
|
|
59
62
|
response: {
|
|
@@ -69,10 +72,9 @@ describe Txbr::EmailTemplate do
|
|
|
69
72
|
end
|
|
70
73
|
|
|
71
74
|
it 'extracts and groups all strings with the same project, resource, and prefix' do
|
|
72
|
-
|
|
73
|
-
expect(
|
|
74
|
-
|
|
75
|
-
resource = resources.first
|
|
75
|
+
resource = email_template.each_resource.to_a.first
|
|
76
|
+
expect(resource.tx_resource.project_slug).to eq('my_project')
|
|
77
|
+
expect(resource.tx_resource.resource_slug).to eq('my_resource')
|
|
76
78
|
|
|
77
79
|
# notice how it combined strings from the subject, preheader,
|
|
78
80
|
# and template (i.e. HTML body)
|
|
@@ -96,6 +98,16 @@ describe Txbr::EmailTemplate do
|
|
|
96
98
|
expect(tx_resource.type).to eq(project.strings_format)
|
|
97
99
|
end
|
|
98
100
|
|
|
101
|
+
it 'constructs a separate resource for the footer' do
|
|
102
|
+
footer = email_template.each_resource.to_a.last
|
|
103
|
+
expect(footer.tx_resource.project_slug).to eq('my_project')
|
|
104
|
+
expect(footer.tx_resource.resource_slug).to eq('my_footer_resource')
|
|
105
|
+
|
|
106
|
+
expect(footer.phrases).to eq([
|
|
107
|
+
{ 'key' => 'company', 'string' => 'Megamarketing Corp' }
|
|
108
|
+
])
|
|
109
|
+
end
|
|
110
|
+
|
|
99
111
|
context 'with translations disabled for the subject' do
|
|
100
112
|
let(:subject_html) do
|
|
101
113
|
super().tap do |subj|
|
|
@@ -120,9 +132,9 @@ describe Txbr::EmailTemplate do
|
|
|
120
132
|
end
|
|
121
133
|
end
|
|
122
134
|
|
|
123
|
-
it 'includes
|
|
135
|
+
it 'includes the additional resource' do
|
|
124
136
|
resources = email_template.each_resource.to_a
|
|
125
|
-
expect(resources.size).to eq(
|
|
137
|
+
expect(resources.size).to eq(3)
|
|
126
138
|
|
|
127
139
|
expect(resources.first.phrases).to_not(
|
|
128
140
|
include({ 'key' => 'meta.subject_line', 'string' => 'You lucky duck maybe' })
|
|
@@ -131,6 +143,9 @@ describe Txbr::EmailTemplate do
|
|
|
131
143
|
expect(resources.last.phrases).to eq(
|
|
132
144
|
[{ 'key' => 'meta.subject_line', 'string' => 'You lucky duck maybe' }]
|
|
133
145
|
)
|
|
146
|
+
|
|
147
|
+
expect(resources.last.tx_resource.project_slug).to eq('my_project')
|
|
148
|
+
expect(resources.last.tx_resource.resource_slug).to eq('my_other_resource')
|
|
134
149
|
end
|
|
135
150
|
end
|
|
136
151
|
end
|
|
@@ -1,55 +1,18 @@
|
|
|
1
1
|
require 'spec_helper'
|
|
2
2
|
require 'support/fake_connection'
|
|
3
|
+
require 'shared_examples/api_errors'
|
|
3
4
|
|
|
4
|
-
|
|
5
|
+
require 'json'
|
|
6
|
+
|
|
7
|
+
describe Txbr::EmailTemplatesApi do
|
|
5
8
|
let(:api_key) { 'abc123' }
|
|
6
9
|
let(:api_url) { 'https://somewhere.braze.com' }
|
|
7
10
|
let(:connection) { FakeConnection.new(interactions) }
|
|
8
|
-
let(:
|
|
9
|
-
|
|
10
|
-
shared_examples 'a client request that handles errors' do
|
|
11
|
-
context 'when the resource is not found' do
|
|
12
|
-
let(:interactions) do
|
|
13
|
-
super().tap do |inter|
|
|
14
|
-
inter[0][:response][:status] = 404
|
|
15
|
-
end
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
it 'raises a not found error' do
|
|
19
|
-
expect { subject }.to raise_error(Txbr::BrazeNotFoundError)
|
|
20
|
-
end
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
context 'when the request is unauthorized' do
|
|
24
|
-
let(:interactions) do
|
|
25
|
-
super().tap do |inter|
|
|
26
|
-
inter.unshift(
|
|
27
|
-
request: inter[0][:request],
|
|
28
|
-
response: { status: 401 }
|
|
29
|
-
)
|
|
30
|
-
end
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
it 'raises an unauthorized error' do
|
|
34
|
-
expect { subject }.to raise_error(Txbr::BrazeUnauthorizedError)
|
|
35
|
-
end
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
context 'when some other bad thing happens' do
|
|
39
|
-
let(:interactions) do
|
|
40
|
-
super().tap do |inter|
|
|
41
|
-
inter[0][:response][:status] = 500
|
|
42
|
-
end
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
it 'raises a generic error' do
|
|
46
|
-
expect { subject }.to raise_error(Txbr::BrazeApiError)
|
|
47
|
-
end
|
|
48
|
-
end
|
|
49
|
-
end
|
|
11
|
+
let(:braze_api) { Txbr::BrazeApi.new(api_key, api_url, connection: connection) }
|
|
12
|
+
let(:client) { described_class.new(braze_api) }
|
|
50
13
|
|
|
51
|
-
describe '#
|
|
52
|
-
subject { client.
|
|
14
|
+
describe '#each' do
|
|
15
|
+
subject { client.each.to_a }
|
|
53
16
|
|
|
54
17
|
before do
|
|
55
18
|
stub_const("#{described_class.name}::TEMPLATE_BATCH_SIZE", 1)
|
|
@@ -78,8 +41,8 @@ describe Txbr::BrazeApi do
|
|
|
78
41
|
it_behaves_like 'a client request that handles errors'
|
|
79
42
|
end
|
|
80
43
|
|
|
81
|
-
describe '#
|
|
82
|
-
subject { client.
|
|
44
|
+
describe '#details' do
|
|
45
|
+
subject { client.details(email_template_id: email_template_id) }
|
|
83
46
|
let(:email_template_id) { 'abc123' }
|
|
84
47
|
|
|
85
48
|
let(:details) do
|
|
@@ -93,7 +56,7 @@ describe Txbr::BrazeApi do
|
|
|
93
56
|
|
|
94
57
|
let(:interactions) do
|
|
95
58
|
[{
|
|
96
|
-
request: { verb: 'get', url: described_class::
|
|
59
|
+
request: { verb: 'get', url: described_class::TEMPLATE_DETAILS_PATH, params: { email_template_id: email_template_id } },
|
|
97
60
|
response: { status: 200, body: details.to_json }
|
|
98
61
|
}]
|
|
99
62
|
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
shared_examples 'a client request that handles errors' do
|
|
2
|
+
context 'when the resource is not found' do
|
|
3
|
+
let(:interactions) do
|
|
4
|
+
super().tap do |inter|
|
|
5
|
+
inter[0][:response][:status] = 404
|
|
6
|
+
end
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
it 'raises a not found error' do
|
|
10
|
+
expect { subject }.to raise_error(Txbr::BrazeNotFoundError)
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
context 'when the request is unauthorized' do
|
|
15
|
+
let(:interactions) do
|
|
16
|
+
super().tap do |inter|
|
|
17
|
+
inter.unshift(
|
|
18
|
+
request: inter[0][:request],
|
|
19
|
+
response: { status: 401 }
|
|
20
|
+
)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
it 'raises an unauthorized error' do
|
|
25
|
+
expect { subject }.to raise_error(Txbr::BrazeUnauthorizedError)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
context 'when some other bad thing happens' do
|
|
30
|
+
let(:interactions) do
|
|
31
|
+
super().tap do |inter|
|
|
32
|
+
inter[0][:response][:status] = 500
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
it 'raises a generic error' do
|
|
37
|
+
expect { subject }.to raise_error(Txbr::BrazeApiError)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
data/spec/uploader_spec.rb
CHANGED
|
@@ -10,15 +10,21 @@ describe Txbr::Uploader do
|
|
|
10
10
|
[{
|
|
11
11
|
request: {
|
|
12
12
|
verb: 'get',
|
|
13
|
-
url: Txbr::
|
|
14
|
-
params: { offset: 1, limit: Txbr::
|
|
13
|
+
url: Txbr::EmailTemplatesApi::TEMPLATE_LIST_PATH,
|
|
14
|
+
params: { offset: 1, limit: Txbr::EmailTemplatesApi::TEMPLATE_BATCH_SIZE }
|
|
15
15
|
},
|
|
16
|
+
|
|
16
17
|
response: {
|
|
17
18
|
status: 200,
|
|
18
19
|
body: { templates: [{ email_template_id: email_template_id }] }.to_json
|
|
19
20
|
}
|
|
20
21
|
}, {
|
|
21
|
-
request: {
|
|
22
|
+
request: {
|
|
23
|
+
verb: 'get',
|
|
24
|
+
url: Txbr::EmailTemplatesApi::TEMPLATE_DETAILS_PATH,
|
|
25
|
+
params: { email_template_id: email_template_id }
|
|
26
|
+
},
|
|
27
|
+
|
|
22
28
|
response: {
|
|
23
29
|
status: 200,
|
|
24
30
|
body: {
|
|
@@ -49,7 +55,7 @@ describe Txbr::Uploader do
|
|
|
49
55
|
{% assign project_slug = "my_project" %}
|
|
50
56
|
{% assign resource_slug = "my_resource" %}
|
|
51
57
|
{% assign translation_enabled = true %}
|
|
52
|
-
{% connected_content http://my_strings_api.com
|
|
58
|
+
{% connected_content http://my_strings_api.com?project_slug={{project_slug}}&resource_slug={{resource_slug}} :save strings %}
|
|
53
59
|
</head>
|
|
54
60
|
<body>
|
|
55
61
|
{{strings.header | default: 'Buy our stuff!'}}
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: txbr
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version:
|
|
4
|
+
version: 2.0.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Cameron Dutro
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2019-01-14 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: abroad
|
|
@@ -120,22 +120,34 @@ files:
|
|
|
120
120
|
- lib/txbr.rb
|
|
121
121
|
- lib/txbr/application.rb
|
|
122
122
|
- lib/txbr/braze_api.rb
|
|
123
|
+
- lib/txbr/campaign.rb
|
|
124
|
+
- lib/txbr/campaign_handler.rb
|
|
125
|
+
- lib/txbr/campaigns_api.rb
|
|
123
126
|
- lib/txbr/commands.rb
|
|
124
127
|
- lib/txbr/config.rb
|
|
128
|
+
- lib/txbr/content_tag.rb
|
|
125
129
|
- lib/txbr/email_template.rb
|
|
126
|
-
- lib/txbr/email_template_component.rb
|
|
127
130
|
- lib/txbr/email_template_handler.rb
|
|
131
|
+
- lib/txbr/email_templates_api.rb
|
|
132
|
+
- lib/txbr/liquid.rb
|
|
133
|
+
- lib/txbr/liquid/connected_content_tag.rb
|
|
134
|
+
- lib/txbr/metadata.rb
|
|
128
135
|
- lib/txbr/project.rb
|
|
129
136
|
- lib/txbr/request_methods.rb
|
|
130
137
|
- lib/txbr/strings_manifest.rb
|
|
131
138
|
- lib/txbr/tasks.rb
|
|
139
|
+
- lib/txbr/template.rb
|
|
140
|
+
- lib/txbr/template_group.rb
|
|
132
141
|
- lib/txbr/uploader.rb
|
|
133
142
|
- lib/txbr/utils.rb
|
|
134
143
|
- lib/txbr/version.rb
|
|
135
144
|
- spec/application_spec.rb
|
|
136
|
-
- spec/
|
|
145
|
+
- spec/campaign_spec.rb
|
|
146
|
+
- spec/campaigns_api_spec.rb
|
|
137
147
|
- spec/config_spec.rb
|
|
138
148
|
- spec/email_template_spec.rb
|
|
149
|
+
- spec/email_templates_api_spec.rb
|
|
150
|
+
- spec/shared_examples/api_errors.rb
|
|
139
151
|
- spec/spec_helper.rb
|
|
140
152
|
- spec/strings_manifest_spec.rb
|
|
141
153
|
- spec/support/env_helpers.rb
|
|
@@ -1,96 +0,0 @@
|
|
|
1
|
-
module Txbr
|
|
2
|
-
class EmailTemplateComponent
|
|
3
|
-
ASSIGNMENTS = %w(project_slug resource_slug translation_enabled)
|
|
4
|
-
|
|
5
|
-
attr_reader :liquid_template
|
|
6
|
-
|
|
7
|
-
def initialize(liquid_template)
|
|
8
|
-
@liquid_template = liquid_template
|
|
9
|
-
end
|
|
10
|
-
|
|
11
|
-
def project_slug
|
|
12
|
-
# blow up with KeyError if not found
|
|
13
|
-
assignments.fetch('project_slug')
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
def resource_slug
|
|
17
|
-
# blow up with KeyError if not found
|
|
18
|
-
assignments.fetch('resource_slug')
|
|
19
|
-
end
|
|
20
|
-
|
|
21
|
-
def translation_enabled?
|
|
22
|
-
# translation is disabled by default
|
|
23
|
-
assignments.fetch('translation_enabled', true)
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
def assignments
|
|
27
|
-
@assignments ||= {}.tap do |assgn|
|
|
28
|
-
liquid_template.root.nodelist.each do |node|
|
|
29
|
-
case node
|
|
30
|
-
when Liquid::Assign
|
|
31
|
-
to = node.instance_variable_get(:@to)
|
|
32
|
-
|
|
33
|
-
if ASSIGNMENTS.include?(to)
|
|
34
|
-
from = node.instance_variable_get(:@from).name
|
|
35
|
-
assgn[to] = from
|
|
36
|
-
end
|
|
37
|
-
end
|
|
38
|
-
end
|
|
39
|
-
end
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
def strings
|
|
43
|
-
@strings ||= StringsManifest.new.tap do |manifest|
|
|
44
|
-
extract_strings_from(liquid_template.root, manifest)
|
|
45
|
-
end
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
private
|
|
49
|
-
|
|
50
|
-
def extract_strings_from(root, manifest)
|
|
51
|
-
return unless root.nodelist
|
|
52
|
-
|
|
53
|
-
root.nodelist.each do |node|
|
|
54
|
-
case node
|
|
55
|
-
# We only care about Liquid variables, which are written
|
|
56
|
-
# like {{prefix.foo.bar}}. We identify the prefix (i.e.
|
|
57
|
-
# the first lookup, or path segment) to verify it's
|
|
58
|
-
# associated with a connected_content call. Then we add
|
|
59
|
-
# the prefix and the rest of the lookups to the strings
|
|
60
|
-
# manifest along with the value. The prefix is used to
|
|
61
|
-
# divide the strings into individual Transifex resources
|
|
62
|
-
# while the rest of the lookups form the string's key.
|
|
63
|
-
when Liquid::Variable
|
|
64
|
-
next unless node.name.is_a?(Liquid::VariableLookup)
|
|
65
|
-
|
|
66
|
-
prefix = node.name.name
|
|
67
|
-
path = node.name.lookups
|
|
68
|
-
|
|
69
|
-
# the English translation (or whatever language your
|
|
70
|
-
# source strings are written in) is provided using
|
|
71
|
-
# Liquid's built-in "default" filter
|
|
72
|
-
next unless connected_content_prefixes.include?(prefix)
|
|
73
|
-
default = node.filters.find { |f| f.first == 'default' }
|
|
74
|
-
|
|
75
|
-
manifest.add(path, default&.last&.first)
|
|
76
|
-
end
|
|
77
|
-
|
|
78
|
-
if node.respond_to?(:nodelist)
|
|
79
|
-
extract_strings_from(node, manifest)
|
|
80
|
-
end
|
|
81
|
-
end
|
|
82
|
-
end
|
|
83
|
-
|
|
84
|
-
def connected_content_prefixes
|
|
85
|
-
@connected_content_prefixes ||= connected_content_tags.map(&:prefix)
|
|
86
|
-
end
|
|
87
|
-
|
|
88
|
-
def connected_content_tags
|
|
89
|
-
# this assumes these are basically at the top of the template and not
|
|
90
|
-
# nested inside other liquid tags
|
|
91
|
-
@connected_content_tags ||= liquid_template.root.nodelist.select do |node|
|
|
92
|
-
node.is_a?(Txbr::ConnectedContentTag)
|
|
93
|
-
end
|
|
94
|
-
end
|
|
95
|
-
end
|
|
96
|
-
end
|