gooddata 0.6.16 → 0.6.17
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/.rubocop.yml +4 -1
- data/lib/gooddata/cli/commands/project_cmd.rb +1 -1
- data/lib/gooddata/core/logging.rb +15 -5
- data/lib/gooddata/core/rest.rb +4 -28
- data/lib/gooddata/helpers/global_helpers.rb +14 -138
- data/lib/gooddata/helpers/global_helpers_params.rb +145 -0
- data/lib/gooddata/mixins/md_object_indexer.rb +2 -2
- data/lib/gooddata/models/domain.rb +1 -1
- data/lib/gooddata/models/execution.rb +29 -1
- data/lib/gooddata/models/from_wire.rb +6 -0
- data/lib/gooddata/models/from_wire_parse.rb +125 -0
- data/lib/gooddata/models/metadata/attribute.rb +1 -1
- data/lib/gooddata/models/metadata/label.rb +11 -10
- data/lib/gooddata/models/model.rb +4 -0
- data/lib/gooddata/models/profile.rb +12 -2
- data/lib/gooddata/models/project.rb +6 -3
- data/lib/gooddata/models/project_blueprint.rb +4 -4
- data/lib/gooddata/models/project_creator.rb +8 -10
- data/lib/gooddata/models/report_data_result.rb +4 -2
- data/lib/gooddata/models/schedule.rb +121 -66
- data/lib/gooddata/models/to_wire.rb +12 -3
- data/lib/gooddata/models/user_filters/user_filter_builder.rb +3 -234
- data/lib/gooddata/models/user_filters/user_filter_builder_create.rb +115 -0
- data/lib/gooddata/models/user_filters/user_filter_builder_execute.rb +133 -0
- data/lib/gooddata/rest/client.rb +27 -13
- data/lib/gooddata/rest/connection.rb +102 -23
- data/lib/gooddata/version.rb +1 -1
- data/spec/data/gd_gse_data_blueprint.json +1 -0
- data/spec/data/test_project_model_spec.json +5 -2
- data/spec/data/wire_models/model_view.json +3 -0
- data/spec/data/wire_test_project.json +8 -1
- data/spec/integration/full_project_spec.rb +1 -1
- data/spec/unit/core/connection_spec.rb +16 -0
- data/spec/unit/core/logging_spec.rb +54 -6
- data/spec/unit/models/domain_spec.rb +10 -4
- data/spec/unit/models/execution_spec.rb +102 -0
- data/spec/unit/models/from_wire_spec.rb +11 -2
- data/spec/unit/models/model_spec.rb +2 -2
- data/spec/unit/models/project_blueprint_spec.rb +1 -1
- data/spec/unit/models/schedule_spec.rb +34 -24
- data/spec/unit/models/to_wire_spec.rb +9 -1
- metadata +8 -3
@@ -40,7 +40,7 @@ describe "Full project implementation", :constraint => 'slow' do
|
|
40
40
|
expect(results).to be_nil
|
41
41
|
|
42
42
|
# When we change the model using the original blueprint. Basically change the title back.
|
43
|
-
results =
|
43
|
+
results = @project.update_from_blueprint(@spec)
|
44
44
|
# It should offer no changes using the original blueprint
|
45
45
|
results = GoodData::Model::ProjectCreator.migrate_datasets(@spec, project: @project, client: @client, dry_run: true)
|
46
46
|
expect(results).to be_nil
|
@@ -32,4 +32,20 @@ describe GoodData::Rest::Connection do
|
|
32
32
|
c.disconnect
|
33
33
|
end
|
34
34
|
end
|
35
|
+
|
36
|
+
describe '#generate_request_id' do
|
37
|
+
it "Generates a non-empty string" do
|
38
|
+
c = ConnectionHelper.create_default_connection
|
39
|
+
|
40
|
+
# generate a request id, and pass it to a request
|
41
|
+
id = c.generate_request_id
|
42
|
+
resp = c.get('/gdc/md', :request_id => id)
|
43
|
+
|
44
|
+
id.should be_a(String)
|
45
|
+
id.should_not be_empty
|
46
|
+
|
47
|
+
c.disconnect
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
35
51
|
end
|
@@ -1,6 +1,30 @@
|
|
1
1
|
# encoding: UTF-8
|
2
2
|
|
3
3
|
require 'gooddata/core/logging'
|
4
|
+
require 'logger'
|
5
|
+
|
6
|
+
# Logger that remembers the last logged message
|
7
|
+
class TestLogger < Logger
|
8
|
+
attr_reader :last_message
|
9
|
+
def debug(*args)
|
10
|
+
@last_message = args[0]
|
11
|
+
super(*args)
|
12
|
+
end
|
13
|
+
def info(*args)
|
14
|
+
@last_message = args[0]
|
15
|
+
super(*args)
|
16
|
+
end
|
17
|
+
def warn(*args)
|
18
|
+
@last_message = args[0]
|
19
|
+
super(*args)
|
20
|
+
end
|
21
|
+
def error(*args)
|
22
|
+
@last_message = args[0]
|
23
|
+
super(*args)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
|
4
28
|
|
5
29
|
describe 'GoodData - logging' do
|
6
30
|
TEST_MESSAGE = 'Hello World!'
|
@@ -17,21 +41,45 @@ describe 'GoodData - logging' do
|
|
17
41
|
GoodData.logger.warn TEST_MESSAGE
|
18
42
|
end
|
19
43
|
|
44
|
+
def test_request_id_logging
|
45
|
+
c = ConnectionHelper.create_default_connection
|
46
|
+
id = c.generate_request_id
|
47
|
+
GoodData.logger.info "Request id: #{id} Doing something very useful"
|
48
|
+
c.get('/gdc/md', :request_id => id)
|
49
|
+
id
|
50
|
+
end
|
51
|
+
|
20
52
|
def test_all
|
21
53
|
test_error
|
22
54
|
test_info
|
23
|
-
|
55
|
+
test_warn
|
56
|
+
test_request_id_logging
|
24
57
|
end
|
25
58
|
|
26
59
|
before(:each) do
|
27
|
-
|
28
|
-
|
29
|
-
# TODO: Use some kind of 'reset' instead
|
30
|
-
GoodData.logging_on if !@is_logging_on
|
60
|
+
# remember the state of logging before
|
61
|
+
@logging_on_at_start = GoodData.logging_on?
|
31
62
|
end
|
32
63
|
|
33
64
|
after(:each) do
|
34
|
-
|
65
|
+
# restore the logging state
|
66
|
+
if @logging_on_at_start
|
67
|
+
GoodData.logging_on
|
68
|
+
else
|
69
|
+
GoodData.logging_off
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
describe '#logger' do
|
74
|
+
it "can assign a custom logger" do
|
75
|
+
GoodData.logger = TestLogger.new(STDOUT)
|
76
|
+
test_all
|
77
|
+
end
|
78
|
+
it 'has the request id logged when I passed it' do
|
79
|
+
GoodData.logger = TestLogger.new(STDOUT)
|
80
|
+
id = test_request_id_logging
|
81
|
+
expect(GoodData.logger.last_message).to include(id)
|
82
|
+
end
|
35
83
|
end
|
36
84
|
|
37
85
|
|
@@ -125,12 +125,18 @@ describe GoodData::Domain do
|
|
125
125
|
.reject { |u| u.login == ConnectionHelper::DEFAULT_USERNAME }.sample
|
126
126
|
|
127
127
|
old_email = user.email
|
128
|
+
old_sso_provider = user.sso_provider || ''
|
128
129
|
user.email = 'john.doe@gooddata.com'
|
130
|
+
user.sso_provider = user.sso_provider ? user.sso_provider.reverse : 'some_sso_provider'
|
129
131
|
@domain.update_user(user)
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
132
|
+
updated_user = @domain.find_user_by_login(user.login)
|
133
|
+
expect(updated_user.email).to eq 'john.doe@gooddata.com'
|
134
|
+
expect(updated_user.sso_provider).to eq 'some_sso_provider'
|
135
|
+
updated_user.email = old_email
|
136
|
+
updated_user.sso_provider = old_sso_provider
|
137
|
+
@domain.update_user(updated_user)
|
138
|
+
expect(@domain.find_user_by_login(user.login).email).to eq old_email
|
139
|
+
expect(@domain.find_user_by_login(user.login).sso_provider).to eq old_sso_provider
|
134
140
|
end
|
135
141
|
end
|
136
142
|
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
require 'gooddata/models/schedule'
|
2
|
+
|
3
|
+
describe GoodData::Execution do
|
4
|
+
|
5
|
+
before(:each) do
|
6
|
+
@data = {"execution"=>
|
7
|
+
{"startTime"=>"2015-02-27T15:44:21.759Z",
|
8
|
+
"endTime"=>"2015-02-27T15:47:49.383Z",
|
9
|
+
"log"=>
|
10
|
+
"/gdc/projects/tk3b994vmdpcb0xjwexc9moen8t5bpiw/dataload/processes/2b031451-b1a2-4039-8e36-0672542a0e60/executions/54f090d5e4b0c9cbdcb0f45b/log",
|
11
|
+
"status"=>"OK",
|
12
|
+
"trigger"=>"MANUAL",
|
13
|
+
"links"=>
|
14
|
+
{"self"=>
|
15
|
+
"/gdc/projects/tk3b994vmdpcb0xjwexc9moen8t5bpiw/schedules/54f08d1de4b0c9cbdcb0f323/executions/54f090d5e4b0c9cbdcb0f45b"},
|
16
|
+
"createdTime"=>"2015-02-27T15:44:21.361Z"}}
|
17
|
+
@execution = GoodData::Execution.new(@data)
|
18
|
+
end
|
19
|
+
|
20
|
+
describe '#created' do
|
21
|
+
it 'returns created as a Time instance' do
|
22
|
+
expect(@execution.created.class).to eq Time
|
23
|
+
expect(@execution.created.to_s).to eq '2015-02-27 15:44:21 UTC'
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
describe '#error?' do
|
28
|
+
it 'returns true if executione errored out' do
|
29
|
+
expect(@execution.error?).to be_falsy
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
describe '#ok?' do
|
34
|
+
it 'returns true if executione finished ok' do
|
35
|
+
expect(@execution.ok?).to be_truthy
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
describe '#finished' do
|
40
|
+
it 'returns time when execution finished' do
|
41
|
+
expect(@execution.finished.class).to eq Time
|
42
|
+
expect(@execution.finished.to_s).to eq '2015-02-27 15:47:49 UTC'
|
43
|
+
end
|
44
|
+
|
45
|
+
it 'returns nil if it is not finished' do
|
46
|
+
@data['execution']['status'] = 'RUNNING'
|
47
|
+
@data['execution']['endTime'] = nil
|
48
|
+
running_execution = GoodData::Execution.new(@data)
|
49
|
+
expect(running_execution.finished).to be_nil
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
describe '#schedule_uri' do
|
54
|
+
it 'returns uri of schedule that was executed' do
|
55
|
+
expect(@execution.schedule_uri).to eq '/gdc/projects/tk3b994vmdpcb0xjwexc9moen8t5bpiw/schedules/54f08d1de4b0c9cbdcb0f323'
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
describe '#running?' do
|
60
|
+
it 'returns false if executione is already finished' do
|
61
|
+
expect(@execution.running?).to be_falsy
|
62
|
+
end
|
63
|
+
|
64
|
+
it 'returns true if executione is currently finished' do
|
65
|
+
@data['execution']['status'] = 'RUNNING'
|
66
|
+
running_execution = GoodData::Execution.new(@data)
|
67
|
+
expect(running_execution.running?).to be_truthy
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
describe '#started' do
|
72
|
+
it 'returns time when execution started' do
|
73
|
+
expect(@execution.started.class).to eq Time
|
74
|
+
expect(@execution.started.to_s).to eq '2015-02-27 15:44:21 UTC'
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
describe '#status' do
|
79
|
+
it 'returns :ok for finished execution' do
|
80
|
+
expect(@execution.status).to eq :ok
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
describe '#uri' do
|
85
|
+
it 'returns time when execution started' do
|
86
|
+
expect(@execution.uri).to eq '/gdc/projects/tk3b994vmdpcb0xjwexc9moen8t5bpiw/schedules/54f08d1de4b0c9cbdcb0f323/executions/54f090d5e4b0c9cbdcb0f45b'
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
describe '#duration' do
|
91
|
+
it 'returns time it took to run execution' do
|
92
|
+
expect(@execution.duration).to eq 207.624
|
93
|
+
end
|
94
|
+
|
95
|
+
it 'returns nil if it is not finished' do
|
96
|
+
@data['execution']['status'] = 'RUNNING'
|
97
|
+
@data['execution']['endTime'] = nil
|
98
|
+
running_execution = GoodData::Execution.new(@data)
|
99
|
+
expect(running_execution.duration.class).to eq Float
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
@@ -42,11 +42,11 @@ describe GoodData::Model::FromWire do
|
|
42
42
|
end
|
43
43
|
|
44
44
|
it "should enable sorting" do
|
45
|
-
|
45
|
+
skip("UAAA")
|
46
46
|
end
|
47
47
|
|
48
48
|
it "should allow defining date dimensions" do
|
49
|
-
|
49
|
+
skip('UAAA')
|
50
50
|
end
|
51
51
|
|
52
52
|
it "should generate the same thing it parsed" do
|
@@ -61,6 +61,7 @@ describe GoodData::Model::FromWire do
|
|
61
61
|
{
|
62
62
|
type: 'anchor',
|
63
63
|
name: "techoppanalysis",
|
64
|
+
folder: "Opportunity Benchmark",
|
64
65
|
title: "Tech Opp. Analysis",
|
65
66
|
gd_data_type: "VARCHAR(128)",
|
66
67
|
gd_type: "GDC.text",
|
@@ -73,6 +74,7 @@ describe GoodData::Model::FromWire do
|
|
73
74
|
expect(x).to eq [
|
74
75
|
{
|
75
76
|
:type=>'attribute',
|
77
|
+
:folder => "Opportunity Benchmark",
|
76
78
|
:name=>"month",
|
77
79
|
:gd_data_type=>"VARCHAR(128)",
|
78
80
|
:gd_type=>"GDC.text",
|
@@ -88,6 +90,7 @@ describe GoodData::Model::FromWire do
|
|
88
90
|
},
|
89
91
|
{
|
90
92
|
:type=>'attribute',
|
93
|
+
:folder => "Opportunity Benchmark",
|
91
94
|
:name=>"cohorttype",
|
92
95
|
:title=>"Cohort Type",
|
93
96
|
:gd_data_type=>"VARCHAR(128)",
|
@@ -117,6 +120,12 @@ describe GoodData::Model::FromWire do
|
|
117
120
|
}]
|
118
121
|
end
|
119
122
|
|
123
|
+
it "should be able to parse description from both attributes and facts" do
|
124
|
+
expect(@blueprint.find_dataset('opportunity').anchor[:description]).to eq 'This is opportunity attribute description'
|
125
|
+
expect(@blueprint.find_dataset('stage_history').facts.find {|f| f[:name] == 'stage_velocity'}[:description]).to eq 'Velocity description'
|
126
|
+
expect(@blueprint.find_dataset('opp_owner').attributes.find {|f| f[:name] == 'region'}[:description]).to eq 'Owner Region description'
|
127
|
+
end
|
128
|
+
|
120
129
|
it "should be able to deal with fiscal dimensions with weird names" do
|
121
130
|
model_view = MultiJson.load(File.read('./spec/data/wire_models/nu_model.json'))
|
122
131
|
blueprint = FromWire.from_wire(model_view)
|
@@ -16,7 +16,7 @@ describe GoodData::Model do
|
|
16
16
|
{
|
17
17
|
:name => "commits",
|
18
18
|
:columns => [
|
19
|
-
{:type => "fact", :name => "lines_changed"}
|
19
|
+
{:type => "fact", :name => "lines_changed", :description=>"Fact description"}
|
20
20
|
]
|
21
21
|
}
|
22
22
|
]})
|
@@ -49,7 +49,7 @@ describe GoodData::Model do
|
|
49
49
|
stuff = GoodData::Model.merge_dataset_columns(first_dataset, additional_blueprint)
|
50
50
|
|
51
51
|
stuff[:columns].count.should == 4
|
52
|
-
stuff[:columns].include?({:type => "fact", :name => "lines_changed"}).should == true
|
52
|
+
stuff[:columns].include?({:type => "fact", :name => "lines_changed", :description=>"Fact description"}).should == true
|
53
53
|
stuff[:columns].group_by { |col| col[:name] }["lines_changed"].count.should == 1
|
54
54
|
end
|
55
55
|
|
@@ -174,7 +174,7 @@ describe GoodData::Model::ProjectBlueprint do
|
|
174
174
|
end
|
175
175
|
dataset = builder.to_blueprint
|
176
176
|
@blueprint.datasets.count.should == 3
|
177
|
-
@blueprint.add_dataset(dataset)
|
177
|
+
@blueprint.add_dataset!(dataset)
|
178
178
|
@blueprint.datasets.count.should == 4
|
179
179
|
end
|
180
180
|
|
@@ -4,8 +4,6 @@ describe GoodData::Schedule do
|
|
4
4
|
SCHEDULE_ID = ScheduleHelper::SCHEDULE_ID
|
5
5
|
SCHEDULE_URL = "/gdc/projects/#{ProjectHelper::PROJECT_ID}/schedules/#{SCHEDULE_ID}"
|
6
6
|
|
7
|
-
@test_cron = '0 15 27 7 *'
|
8
|
-
|
9
7
|
before(:all) do
|
10
8
|
@client = ConnectionHelper.create_default_connection
|
11
9
|
|
@@ -20,11 +18,16 @@ describe GoodData::Schedule do
|
|
20
18
|
|
21
19
|
@project = ProjectHelper.get_default_project(:client => @client)
|
22
20
|
@project_executable = 'graph/graph.grf'
|
21
|
+
@test_cron = '0 15 27 7 *'
|
23
22
|
@test_data = {
|
24
23
|
:timezone => 'UTC',
|
25
24
|
:cron => '2 2 2 2 *',
|
26
25
|
:client => @client,
|
27
|
-
:project => @project
|
26
|
+
:project => @project,
|
27
|
+
:params => {
|
28
|
+
'a' => 'b',
|
29
|
+
'b' => 'c'
|
30
|
+
}
|
28
31
|
}
|
29
32
|
|
30
33
|
@test_data_with_optional_param = {
|
@@ -76,12 +79,8 @@ describe GoodData::Schedule do
|
|
76
79
|
|
77
80
|
describe '#create' do
|
78
81
|
it 'Creates new schedule if mandatory params passed' do
|
79
|
-
schedule = nil
|
80
82
|
begin
|
81
|
-
|
82
|
-
schedule = @project.create_schedule(ProcessHelper::PROCESS_ID, @test_cron, @project_executable, @test_data)
|
83
|
-
}.not_to raise_error
|
84
|
-
|
83
|
+
schedule = @project.create_schedule(ProcessHelper::PROCESS_ID, @test_cron, @project_executable, @test_data)
|
85
84
|
expect(schedule).to be_truthy
|
86
85
|
ensure
|
87
86
|
schedule && schedule.delete
|
@@ -89,12 +88,8 @@ describe GoodData::Schedule do
|
|
89
88
|
end
|
90
89
|
|
91
90
|
it 'Creates new schedule if mandatory params passed and optional params are present' do
|
92
|
-
schedule = nil
|
93
91
|
begin
|
94
|
-
|
95
|
-
schedule = @project.create_schedule(ProcessHelper::PROCESS_ID, @test_cron, @project_executable, @test_data_with_optional_param)
|
96
|
-
}.not_to raise_error
|
97
|
-
|
92
|
+
schedule = @project.create_schedule(ProcessHelper::PROCESS_ID, @test_cron, @project_executable, @test_data_with_optional_param)
|
98
93
|
expect(schedule).to be_truthy
|
99
94
|
ensure
|
100
95
|
schedule && schedule.delete
|
@@ -138,25 +133,26 @@ describe GoodData::Schedule do
|
|
138
133
|
|
139
134
|
it 'Throws exception when no timezone specified' do
|
140
135
|
data = @test_data.deep_dup
|
141
|
-
|
142
|
-
schedule = nil
|
136
|
+
schedule = @project.create_schedule(ProcessHelper::PROCESS_ID, @test_cron, @project_executable, data)
|
137
|
+
schedule.timezone = nil
|
143
138
|
begin
|
144
139
|
expect {
|
145
|
-
schedule
|
140
|
+
schedule.save
|
146
141
|
}.to raise_error 'A timezone has to be provided'
|
147
142
|
ensure
|
148
143
|
schedule && schedule.delete
|
149
144
|
end
|
150
145
|
end
|
151
146
|
|
152
|
-
it 'Throws exception when no
|
153
|
-
data = @test_data.deep_dup
|
154
|
-
data[:type] = nil
|
147
|
+
it 'Throws exception when no schedule type is specified' do
|
155
148
|
schedule = nil
|
149
|
+
data = @test_data.deep_dup
|
156
150
|
begin
|
157
|
-
expect {
|
158
151
|
schedule = @project.create_schedule(ProcessHelper::PROCESS_ID, @test_cron, @project_executable, data)
|
159
|
-
|
152
|
+
schedule.type = nil
|
153
|
+
expect {
|
154
|
+
schedule.save
|
155
|
+
}.to raise_error 'Schedule type has to be provided'
|
160
156
|
ensure
|
161
157
|
schedule && schedule.delete
|
162
158
|
end
|
@@ -165,13 +161,13 @@ describe GoodData::Schedule do
|
|
165
161
|
|
166
162
|
describe '#cron' do
|
167
163
|
it 'Should return cron as string' do
|
168
|
-
schedule = nil
|
169
164
|
begin
|
170
165
|
schedule = @project.create_schedule(ProcessHelper::PROCESS_ID, @test_cron, @project_executable, @test_data)
|
171
166
|
res = schedule.cron
|
172
167
|
res.should_not be_nil
|
173
168
|
res.should_not be_empty
|
174
169
|
res.should be_a_kind_of(String)
|
170
|
+
expect(schedule.time_based?).to be_truthy
|
175
171
|
ensure
|
176
172
|
schedule && schedule.delete
|
177
173
|
end
|
@@ -334,12 +330,14 @@ describe GoodData::Schedule do
|
|
334
330
|
old_params = schedule.params
|
335
331
|
|
336
332
|
test_params = {
|
337
|
-
'
|
333
|
+
'some_new_param' => '1-2-3-4'
|
338
334
|
}
|
339
335
|
|
340
336
|
schedule.params = test_params
|
341
|
-
expect(schedule.params).to eq(
|
337
|
+
expect(schedule.params.keys).to eq(%w(PROCESS_ID EXECUTABLE some_new_param))
|
338
|
+
expect(schedule.params['some_new_param']).to eq '1-2-3-4'
|
342
339
|
expect(schedule.dirty).to eq(true)
|
340
|
+
schedule.save
|
343
341
|
ensure
|
344
342
|
schedule && schedule.delete
|
345
343
|
end
|
@@ -486,4 +484,16 @@ describe GoodData::Schedule do
|
|
486
484
|
end
|
487
485
|
end
|
488
486
|
end
|
487
|
+
|
488
|
+
describe '#executions' do
|
489
|
+
it 'Returns executions' do
|
490
|
+
begin
|
491
|
+
schedule = @project.create_schedule(ProcessHelper::PROCESS_ID, @test_cron, @project_executable, @test_data_with_optional_param)
|
492
|
+
expect(schedule.executions).to be_empty
|
493
|
+
schedule.execute
|
494
|
+
ensure
|
495
|
+
schedule && schedule.delete
|
496
|
+
end
|
497
|
+
end
|
498
|
+
end
|
489
499
|
end
|