goodot 0.0.2
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/.gitignore +46 -0
- data/.rspec +3 -0
- data/DEPENDENCIES.md +604 -0
- data/Gemfile +4 -0
- data/LICENSE +22 -0
- data/LICENSE.rb +5 -0
- data/README.md +550 -0
- data/Rakefile +74 -0
- data/bin/goodot +24 -0
- data/dependency_decisions.yml +104 -0
- data/goodot.gemspec +31 -0
- data/img/hello_world.graffle +710 -0
- data/img/hello_world.png +0 -0
- data/img/master_segment.graffle +432 -0
- data/img/master_segment.png +0 -0
- data/img/project_etl.graffle +385 -0
- data/img/project_etl.png +0 -0
- data/img/relase.graffle +1120 -0
- data/img/release.png +0 -0
- data/img/rollback.graffle +924 -0
- data/img/rollback.png +0 -0
- data/img/service_etl.graffle +605 -0
- data/img/service_etl.png +0 -0
- data/lib/goodot.rb +7 -0
- data/lib/goodot/cli/app.rb +33 -0
- data/lib/goodot/cli/cli.rb +7 -0
- data/lib/goodot/cli/cmd/app_cmd.rb +170 -0
- data/lib/goodot/cli/cmd/clients_cmd.rb +89 -0
- data/lib/goodot/cli/cmd/jack_in.rb +17 -0
- data/lib/goodot/cli/cmd/masters_cmd.rb +67 -0
- data/lib/goodot/cli/cmd/segments_cmd.rb +81 -0
- data/lib/goodot/cli/cmd/version_cmd.rb +20 -0
- data/lib/goodot/cli/shared.rb +93 -0
- data/lib/goodot/version.rb +11 -0
- data/spec/spec_helper.rb +50 -0
- metadata +200 -0
data/img/service_etl.png
ADDED
Binary file
|
data/lib/goodot.rb
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
#
|
3
|
+
# Copyright (c) 2010-2016 GoodData Corporation. All rights reserved.
|
4
|
+
# This source code is licensed under the BSD-style license found in the
|
5
|
+
# LICENSE file in the root directory of this source tree.
|
6
|
+
|
7
|
+
$LOAD_PATH.unshift File.dirname(__FILE__)
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
#
|
3
|
+
# Copyright (c) 2010-2016 GoodData Corporation. All rights reserved.
|
4
|
+
# This source code is licensed under the BSD-style license found in the
|
5
|
+
# LICENSE file in the root directory of this source tree.
|
6
|
+
|
7
|
+
require 'gli'
|
8
|
+
require 'pathname'
|
9
|
+
require 'pp'
|
10
|
+
|
11
|
+
require_relative 'shared'
|
12
|
+
require_relative '../version'
|
13
|
+
|
14
|
+
module GoodData
|
15
|
+
module GoodotCLI
|
16
|
+
include GLI::App
|
17
|
+
extend self
|
18
|
+
|
19
|
+
def self.launch(args)
|
20
|
+
exit run args
|
21
|
+
end
|
22
|
+
|
23
|
+
subcommand_option_handling :normal
|
24
|
+
program_desc 'Toolset for helping with lifecycle management of the project'
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
cmds = File.absolute_path(File.join(File.dirname(__FILE__), 'cmd'))
|
29
|
+
Dir.glob(cmds + '/*.rb').each do |file|
|
30
|
+
require file
|
31
|
+
end
|
32
|
+
|
33
|
+
GoodData::GoodotCLI.launch(ARGV)
|
@@ -0,0 +1,170 @@
|
|
1
|
+
module GoodData
|
2
|
+
module GoodotCLI
|
3
|
+
desc 'Manage app'
|
4
|
+
command :app do |c|
|
5
|
+
c.desc 'Synchronize clients'
|
6
|
+
c.command :synchronize do |sync|
|
7
|
+
sync.desc 'Segment'
|
8
|
+
sync.flag [:s, :segment]
|
9
|
+
|
10
|
+
sync.action do |global_options, options, _args|
|
11
|
+
ok_key = 'OK'
|
12
|
+
|
13
|
+
domain = global_options[:gd_domain]
|
14
|
+
results = if options[:segment]
|
15
|
+
segment = domain.segments(options[:segment])
|
16
|
+
[segment.synchronize_clients]
|
17
|
+
else
|
18
|
+
domain.synchronize_clients
|
19
|
+
end
|
20
|
+
res = results.pmapcat { |r| r.details.items }.group_by { |x| x['status'] }
|
21
|
+
if res.key?(ok_key) && res.keys.count == 1
|
22
|
+
puts HighLine.color("#{res[ok_key].count} syncs ended up in state #{ok_key}", :green)
|
23
|
+
else
|
24
|
+
res.except(ok_key).each do |k, v|
|
25
|
+
puts "#{v.count} syncs ended up in state #{k}."
|
26
|
+
puts 'Printing first 10'
|
27
|
+
v.each { |x| puts x['id'] }
|
28
|
+
end
|
29
|
+
fail HighLine.color("Some synchronizations ended up in different state than #{ok_key}", :red) unless res.except(ok_key).empty?
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
c.desc 'Export association for backup'
|
35
|
+
c.command :'export-association' do |export|
|
36
|
+
export.desc 'Without project ids'
|
37
|
+
export.switch [:'without-project']
|
38
|
+
export.default_value false
|
39
|
+
|
40
|
+
export.desc 'File for output'
|
41
|
+
export.flag [:f, :file]
|
42
|
+
|
43
|
+
export.action do |global_options, options, _args|
|
44
|
+
domain = global_options[:gd_domain]
|
45
|
+
results = domain.segments.pmapcat { |s| s.clients.map { |cl| [s, cl] } }
|
46
|
+
res = results.map do |s, cl|
|
47
|
+
{
|
48
|
+
segment: s.id,
|
49
|
+
id: cl.id
|
50
|
+
}.tap do |h|
|
51
|
+
h[:project] = cl.project.uri unless options['without-project']
|
52
|
+
end
|
53
|
+
end
|
54
|
+
file = options[:file]
|
55
|
+
if file
|
56
|
+
File.open(file, 'w') { |f| f << MultiJson.dump(res, pretty: true) }
|
57
|
+
else
|
58
|
+
puts MultiJson.dump(res, pretty: true)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
c.desc 'Import association from backup'
|
64
|
+
c.command :'import-association' do |export|
|
65
|
+
export.desc 'File for output'
|
66
|
+
export.flag [:f, :file]
|
67
|
+
|
68
|
+
export.action do |global_options, options, _args|
|
69
|
+
domain = global_options[:gd_domain]
|
70
|
+
specification = MultiJson.load(File.read(options[:file]), symbolize_keys: true)
|
71
|
+
domain.update_clients(specification, delete_extra: true)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
c.desc 'Spin up projects'
|
76
|
+
c.command :'spin-up-projects' do |spin|
|
77
|
+
spin.desc 'Segment'
|
78
|
+
spin.flag [:s, :segment]
|
79
|
+
|
80
|
+
spin.action do |global_options, _options, _args|
|
81
|
+
domain = global_options[:gd_domain]
|
82
|
+
results = domain.provision_client_projects
|
83
|
+
if results.to_a.empty?
|
84
|
+
puts HighLine.color('Everything seems to be up to date. Nothing was done!', :green)
|
85
|
+
else
|
86
|
+
puts HighLine.color("#{results.count} operations were performed", :green)
|
87
|
+
puts
|
88
|
+
results.group_by(&:status).each do |status, group|
|
89
|
+
puts HighLine.color("#{group.count} #{status} operations were performed", :green)
|
90
|
+
puts table_with_headers(group, [:id, :project_uri])
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
c.desc 'Add segment'
|
97
|
+
c.command :'create-segment' do |seg|
|
98
|
+
seg.desc 'Segment'
|
99
|
+
seg.flag [:s, :segment]
|
100
|
+
|
101
|
+
seg.desc 'Master project'
|
102
|
+
seg.flag [:p, :project]
|
103
|
+
|
104
|
+
seg.action do |global_options, options, _args|
|
105
|
+
client = global_options[:gd_client]
|
106
|
+
domain = global_options[:gd_domain]
|
107
|
+
segment_id = options[:segment]
|
108
|
+
project_id = options[:project]
|
109
|
+
fail 'Segment needs to be provided' unless segment_id
|
110
|
+
fail 'Project id needs to be provided' unless project_id
|
111
|
+
project = client.projects(project_id)
|
112
|
+
|
113
|
+
begin
|
114
|
+
s = domain.create_segment(segment_id: segment_id, master_project: project)
|
115
|
+
puts HighLine.color("Segment #{s.id} created!", :green)
|
116
|
+
rescue RuntimeError => e
|
117
|
+
raise create_api_error_message(e)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
c.desc 'Add client'
|
123
|
+
c.command :'add-client' do |add|
|
124
|
+
add.desc 'Segment'
|
125
|
+
add.flag [:s, :segment]
|
126
|
+
|
127
|
+
add.desc 'Client ID'
|
128
|
+
add.flag [:c, :client]
|
129
|
+
|
130
|
+
add.action do |global_options, options, _args|
|
131
|
+
domain = global_options[:gd_domain]
|
132
|
+
segment_id = options[:segment]
|
133
|
+
client_id = options[:client]
|
134
|
+
fail 'Segment needs to be provided' unless segment_id
|
135
|
+
fail 'client ID needs to be provided.' unless client_id
|
136
|
+
|
137
|
+
s = domain.segments(segment_id)
|
138
|
+
begin
|
139
|
+
c = s.create_client(id: client_id)
|
140
|
+
puts HighLine.color("client #{c.id} created!", :green)
|
141
|
+
rescue RuntimeError => e
|
142
|
+
raise create_api_error_message(e)
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
c.desc 'App stats'
|
148
|
+
c.command :stats do |add|
|
149
|
+
add.action do |global_options, _options, _args|
|
150
|
+
client = global_options[:gd_client]
|
151
|
+
domain = global_options[:gd_domain]
|
152
|
+
|
153
|
+
all_projects = client.projects.to_a
|
154
|
+
master_projects = domain.segments.pmap(&:master_project)
|
155
|
+
client_projects = domain.segments.pmapcat { |s| s.clients.map(&:project).compact }
|
156
|
+
|
157
|
+
puts HighLine.color('Projects', :bold)
|
158
|
+
puts "This app contains #{all_projects.count} projects"
|
159
|
+
puts "Out of those #{master_projects.count} are linked as masters"
|
160
|
+
puts "Out of those #{client_projects.count} are linked as clients"
|
161
|
+
|
162
|
+
unused_projects_pids = (all_projects.map(&:pid) - master_projects.map(&:pid) - client_projects.map(&:pid))
|
163
|
+
puts "Additional #{unused_projects_pids.count} projects are in this account"
|
164
|
+
puts 'Printing first 10'
|
165
|
+
puts table_with_headers(unused_projects_pids.take(10).pmap { |pid| client.projects(pid) }, [:pid, :title])
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
# Copyright (c) 2015, Tomas Korcak <korczis@gmail.com>
|
4
|
+
#
|
5
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
# of this software and associated documentation files (the "Software"), to deal
|
7
|
+
# in the Software without restriction, including without limitation the rights
|
8
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
# copies of the Software, and to permit persons to whom the Software is
|
10
|
+
# furnished to do so, subject to the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be included in
|
13
|
+
# all copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
# THE SOFTWARE.
|
22
|
+
|
23
|
+
require_relative '../shared'
|
24
|
+
|
25
|
+
module GoodData
|
26
|
+
module GoodotCLI
|
27
|
+
desc 'Manage clients in your application'
|
28
|
+
command :clients do |c|
|
29
|
+
c.desc 'List clients'
|
30
|
+
c.command :list do |add|
|
31
|
+
add.desc 'Filter on particular segment'
|
32
|
+
add.default_value :all
|
33
|
+
add.flag [:s, :segment]
|
34
|
+
|
35
|
+
add.action do |global_options, options, _args|
|
36
|
+
domain = global_options[:gd_domain]
|
37
|
+
segment_id = options[:segment]
|
38
|
+
table = Terminal::Table.new do |t|
|
39
|
+
t << ['Client Id', 'Segment URI', 'Project URI']
|
40
|
+
t << :separator
|
41
|
+
Array(domain.segments(segment_id)).pmapcat(&:clients).each { |s| t.add_row([s.client_id, s.segment_uri, s.project_uri]) }
|
42
|
+
end
|
43
|
+
puts table
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
c.desc 'Remove client'
|
48
|
+
c.command :remove do |remove|
|
49
|
+
remove.desc 'Client'
|
50
|
+
remove.flag [:c, :client]
|
51
|
+
|
52
|
+
remove.desc 'Delete the project'
|
53
|
+
remove.default_value false
|
54
|
+
remove.switch [:d, :'delete-project']
|
55
|
+
|
56
|
+
remove.action do |global_options, options, _args|
|
57
|
+
domain = global_options[:gd_domain]
|
58
|
+
client_name = options[:client]
|
59
|
+
fail 'Client needs to be provided' unless client_name
|
60
|
+
client = domain.clients(segment_name)
|
61
|
+
project = client.project
|
62
|
+
client.delete
|
63
|
+
project.delete if options[:d]
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
c.desc 'Reset client. Removes the project from the client.'
|
68
|
+
c.command :reset do |reset|
|
69
|
+
reset.desc 'Client'
|
70
|
+
reset.flag [:c, :client]
|
71
|
+
|
72
|
+
reset.desc 'Delete the project'
|
73
|
+
reset.default_value false
|
74
|
+
reset.switch [:d, :'delete-project']
|
75
|
+
|
76
|
+
reset.action do |global_options, options, _args|
|
77
|
+
domain = global_options[:gd_domain]
|
78
|
+
client_name = options[:client]
|
79
|
+
fail 'Client needs to be provided' unless client_name
|
80
|
+
client = domain.clients.find { |cl| cl.id == client_name }
|
81
|
+
project = client.project
|
82
|
+
client.project = nil
|
83
|
+
client.save
|
84
|
+
project.delete if options[:d]
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require_relative '../shared'
|
2
|
+
|
3
|
+
module GoodData
|
4
|
+
module GoodotCLI
|
5
|
+
desc 'Jacks you in to work interactively with your project.'
|
6
|
+
command ['jack-in', 'jack_in'] do |c|
|
7
|
+
c.action do |global_options, _options, _args|
|
8
|
+
client = global_options[:gd_client]
|
9
|
+
domain = global_options[:gd_domain]
|
10
|
+
binding.pry(quiet: true, # rubocop:disable Lint/Debugger
|
11
|
+
prompt: [proc do |_target_self, _nest_level, _pry|
|
12
|
+
'app_live_session: '
|
13
|
+
end])
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# # encoding: utf-8
|
2
|
+
#
|
3
|
+
# # Copyright (c) 2015, Tomas Korcak <korczis@gmail.com>
|
4
|
+
# #
|
5
|
+
# # Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
# # of this software and associated documentation files (the "Software"), to deal
|
7
|
+
# # in the Software without restriction, including without limitation the rights
|
8
|
+
# # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
# # copies of the Software, and to permit persons to whom the Software is
|
10
|
+
# # furnished to do so, subject to the following conditions:
|
11
|
+
# #
|
12
|
+
# # The above copyright notice and this permission notice shall be included in
|
13
|
+
# # all copies or substantial portions of the Software.
|
14
|
+
# #
|
15
|
+
# # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
# # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
# # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
# # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
# # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
# # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
# # THE SOFTWARE.
|
22
|
+
|
23
|
+
require_relative '../shared'
|
24
|
+
|
25
|
+
module GoodData
|
26
|
+
module GoodotCLI
|
27
|
+
desc 'Manage masters'
|
28
|
+
command :masters do |c|
|
29
|
+
c.desc 'List masters'
|
30
|
+
c.command :list do |add|
|
31
|
+
add.action do |global_options, _options, _args|
|
32
|
+
domain = global_options[:gd_domain]
|
33
|
+
currently_used = domain.segments.pmap { |s| [s, s.master_project] }.compact
|
34
|
+
|
35
|
+
puts table_with_headers(currently_used,
|
36
|
+
[
|
37
|
+
proc { |_, p| p && p.pid },
|
38
|
+
proc { |_, p| p && p.title },
|
39
|
+
proc { |s, _| s.id },
|
40
|
+
proc { |_, p| p && p.metadata['GD_LCM_VERSION'] }
|
41
|
+
],
|
42
|
+
headers: ['PID', 'Title', 'Segment ID', 'Version'])
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
c.desc 'Clone master of particular segment'
|
47
|
+
c.command :clone do |add|
|
48
|
+
add.desc 'Segment which master will be cloned'
|
49
|
+
add.default_value nil
|
50
|
+
add.flag [:s, :segment]
|
51
|
+
|
52
|
+
add.desc 'Authorization token'
|
53
|
+
add.default_value nil
|
54
|
+
add.flag [:t, :token]
|
55
|
+
|
56
|
+
add.action do |global_options, options, _args|
|
57
|
+
domain = global_options[:gd_domain]
|
58
|
+
fail 'You have to specify segment' unless options[:segment]
|
59
|
+
master_project = domain.segments(options[:segment]).master_project
|
60
|
+
clone = GoodData::Project.clone_with_etl(master_project, auth_token: options[:token])
|
61
|
+
puts "Project \"#{master_project.title}\" (#{master_project.pid}) was cloned."
|
62
|
+
puts "\"#{clone.title}\" (#{clone.pid}) was created."
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
module GoodData
|
2
|
+
module GoodotCLI
|
3
|
+
desc 'Manage segments'
|
4
|
+
command :segments do |c|
|
5
|
+
c.desc 'List segments'
|
6
|
+
c.command :list do |add|
|
7
|
+
add.desc 'Format'
|
8
|
+
add.default_value :table
|
9
|
+
add.flag [:f, :format]
|
10
|
+
|
11
|
+
add.action do |global_options, options, _args|
|
12
|
+
domain = global_options[:gd_domain]
|
13
|
+
|
14
|
+
segments = domain.segments
|
15
|
+
case options[:format].to_sym
|
16
|
+
when :table
|
17
|
+
puts table_with_headers(segments, [:id, :master_project_id])
|
18
|
+
when :json
|
19
|
+
output = segments.map do |s|
|
20
|
+
{
|
21
|
+
name: s.id,
|
22
|
+
master_project_id: s.master_project_id
|
23
|
+
}
|
24
|
+
end
|
25
|
+
puts MultiJson.dump(output, pretty: true)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
c.desc 'Remove segment'
|
31
|
+
c.command [:remove, :delete] do |add|
|
32
|
+
add.desc 'Segment'
|
33
|
+
add.flag [:s, :segment]
|
34
|
+
|
35
|
+
add.desc 'Delete also the clients in that particular segment'
|
36
|
+
add.default_value false
|
37
|
+
add.switch [:c, :cascade]
|
38
|
+
|
39
|
+
add.action do |global_options, options, _args|
|
40
|
+
domain = global_options[:gd_domain]
|
41
|
+
segment_name = options[:segment]
|
42
|
+
fail 'Segment needs to be provided' unless segment_name
|
43
|
+
segment = domain.segments(segment_name)
|
44
|
+
segment.clients.peach(&:delete) if options[:cascade]
|
45
|
+
segment.delete
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
c.desc 'Exchange master on segment.'
|
50
|
+
c.command :exchange do |exchange|
|
51
|
+
exchange.desc 'Segment'
|
52
|
+
exchange.flag [:s, :segment]
|
53
|
+
|
54
|
+
exchange.desc 'Master project ID'
|
55
|
+
exchange.flag [:p, :project]
|
56
|
+
|
57
|
+
exchange.desc 'Delete the old project'
|
58
|
+
exchange.default_value false
|
59
|
+
exchange.switch [:d, :delete]
|
60
|
+
|
61
|
+
exchange.action do |global_options, options, _args|
|
62
|
+
client = global_options[:gd_client]
|
63
|
+
domain = global_options[:gd_domain]
|
64
|
+
|
65
|
+
segment_name = options[:segment]
|
66
|
+
fail 'Segment needs to be provided' unless segment_name
|
67
|
+
segment = domain.segments(segment_name)
|
68
|
+
|
69
|
+
project_id = options[:project]
|
70
|
+
fail 'Project Master id needs to be provided' unless project_id
|
71
|
+
old_master = segment.master_project
|
72
|
+
new_master_project = client.projects(project_id)
|
73
|
+
|
74
|
+
segment.master_project = new_master_project
|
75
|
+
segment.save
|
76
|
+
old_master.delete if options[:delete]
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|