event_system 0.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 +7 -0
- data/.rspec +3 -0
- data/README.md +362 -0
- data/Rakefile +4 -0
- data/docs/event_visualizations/example_timeline.html +142 -0
- data/docs/example_logs/events_20251027_151744.jsonl +3 -0
- data/docs/example_logs/events_20251027_151802.jsonl +3 -0
- data/docs/examples/basic_usage.rb +64 -0
- data/exe/event_system +139 -0
- data/lib/event_system/configuration.rb +125 -0
- data/lib/event_system/event.rb +139 -0
- data/lib/event_system/event_manager.rb +191 -0
- data/lib/event_system/event_subscriber.rb +29 -0
- data/lib/event_system/storage/base.rb +64 -0
- data/lib/event_system/storage/file_store.rb +187 -0
- data/lib/event_system/storage/memory_store.rb +134 -0
- data/lib/event_system/version.rb +5 -0
- data/lib/event_system/visualization/timeline_generator.rb +237 -0
- data/lib/event_system.rb +37 -0
- data/spec/event_system/configuration_spec.rb +197 -0
- data/spec/event_system/event_manager_spec.rb +341 -0
- data/spec/event_system/event_spec.rb +193 -0
- data/spec/event_system/event_subscriber_spec.rb +295 -0
- data/spec/event_system/storage/file_store_spec.rb +341 -0
- data/spec/event_system/storage/memory_store_spec.rb +248 -0
- data/spec/event_system/visualization/timeline_generator_spec.rb +252 -0
- data/spec/event_system_spec.rb +57 -0
- data/spec/integration/readme_examples_spec.rb +447 -0
- data/spec/spec_helper.rb +80 -0
- metadata +171 -0
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
|
|
5
|
+
RSpec.describe EventSystem::Visualization::TimelineGenerator do
|
|
6
|
+
let(:temp_dir) { @temp_dir }
|
|
7
|
+
let(:storage) { EventSystem::Storage::FileStore.new(temp_dir, 'test_session') }
|
|
8
|
+
let(:generator) { EventSystem::Visualization::TimelineGenerator.new(storage) }
|
|
9
|
+
|
|
10
|
+
before do
|
|
11
|
+
# Add some test events
|
|
12
|
+
storage.store(create_test_event(:user_created, 'UserService', { user_id: 123, name: 'John Doe' }))
|
|
13
|
+
storage.store(create_test_event(:order_placed, 'OrderService', { order_id: 456, amount: 99.99 }))
|
|
14
|
+
storage.store(create_test_event(:payment_received, 'PaymentService', { payment_id: 789, amount: 99.99 }))
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
describe '#initialize' do
|
|
18
|
+
it 'initializes with storage' do
|
|
19
|
+
expect(generator.storage).to eq(storage)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
describe '#generate_timeline' do
|
|
24
|
+
it 'generates HTML timeline for current session' do
|
|
25
|
+
output_path = generator.generate_timeline
|
|
26
|
+
|
|
27
|
+
expect(output_path).to be_a(String)
|
|
28
|
+
expect(File.exist?(output_path)).to be true
|
|
29
|
+
expect(output_path).to end_with('.html')
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
it 'generates HTML timeline for specific session' do
|
|
33
|
+
output_path = generator.generate_timeline('test_session')
|
|
34
|
+
|
|
35
|
+
expect(output_path).to be_a(String)
|
|
36
|
+
expect(File.exist?(output_path)).to be true
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
it 'generates HTML timeline with custom output file' do
|
|
40
|
+
output_path = generator.generate_timeline('test_session', 'custom_timeline.html')
|
|
41
|
+
|
|
42
|
+
expect(output_path).to include('custom_timeline.html')
|
|
43
|
+
expect(File.exist?(output_path)).to be true
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
it 'returns nil when no events found' do
|
|
47
|
+
# Create a storage with no session files
|
|
48
|
+
empty_storage = EventSystem::Storage::FileStore.new(temp_dir)
|
|
49
|
+
# Mock list_sessions to return empty array
|
|
50
|
+
allow(empty_storage).to receive(:list_sessions).and_return([])
|
|
51
|
+
empty_generator = EventSystem::Visualization::TimelineGenerator.new(empty_storage)
|
|
52
|
+
|
|
53
|
+
result = empty_generator.generate_timeline
|
|
54
|
+
expect(result).to be_nil
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
it 'creates event_visualizations directory' do
|
|
58
|
+
generator.generate_timeline
|
|
59
|
+
expect(Dir.exist?('event_visualizations')).to be true
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
it 'generates valid HTML content' do
|
|
63
|
+
output_path = generator.generate_timeline
|
|
64
|
+
content = File.read(output_path)
|
|
65
|
+
|
|
66
|
+
expect(content).to include('<!DOCTYPE html>')
|
|
67
|
+
expect(content).to include('<html lang="en">')
|
|
68
|
+
expect(content).to include('Event Timeline')
|
|
69
|
+
expect(content).to include('test_session')
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
it 'includes event statistics' do
|
|
73
|
+
output_path = generator.generate_timeline
|
|
74
|
+
content = File.read(output_path)
|
|
75
|
+
|
|
76
|
+
expect(content).to include('Total Events')
|
|
77
|
+
expect(content).to include('Event Types')
|
|
78
|
+
expect(content).to include('Sources')
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
it 'includes filter controls' do
|
|
82
|
+
output_path = generator.generate_timeline
|
|
83
|
+
content = File.read(output_path)
|
|
84
|
+
|
|
85
|
+
expect(content).to include('Filter by type:')
|
|
86
|
+
expect(content).to include('Search events...')
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
it 'includes JavaScript for filtering' do
|
|
90
|
+
output_path = generator.generate_timeline
|
|
91
|
+
content = File.read(output_path)
|
|
92
|
+
|
|
93
|
+
expect(content).to include('function filterEvents()')
|
|
94
|
+
expect(content).to include('addEventListener')
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
describe '#latest_session_id' do
|
|
99
|
+
it 'returns the most recent session ID' do
|
|
100
|
+
# Create files with different names (alphabetically sorted)
|
|
101
|
+
older_file = File.join(@temp_dir, 'events_a_session.jsonl')
|
|
102
|
+
newer_file = File.join(@temp_dir, 'events_z_session.jsonl')
|
|
103
|
+
|
|
104
|
+
# Create older file
|
|
105
|
+
File.write(older_file, '{"type":"test","source":"test","timestamp":"2023-01-01T00:00:00.000Z","id":"old-id","data":{}}')
|
|
106
|
+
|
|
107
|
+
# Create newer file
|
|
108
|
+
File.write(newer_file, '{"type":"test","source":"test","timestamp":"2023-01-02T00:00:00.000Z","id":"new-id","data":{}}')
|
|
109
|
+
|
|
110
|
+
# Create generator with the directory
|
|
111
|
+
generator = EventSystem::Visualization::TimelineGenerator.new(EventSystem::Storage::FileStore.new(@temp_dir))
|
|
112
|
+
latest = generator.latest_session_id
|
|
113
|
+
expect(latest).to eq('z_session')
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
it 'returns nil when no sessions exist' do
|
|
117
|
+
empty_storage = EventSystem::Storage::FileStore.new(temp_dir)
|
|
118
|
+
allow(empty_storage).to receive(:list_sessions).and_return([])
|
|
119
|
+
empty_generator = EventSystem::Visualization::TimelineGenerator.new(empty_storage)
|
|
120
|
+
|
|
121
|
+
result = empty_generator.latest_session_id
|
|
122
|
+
expect(result).to be_nil
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
describe '#event_summary' do
|
|
127
|
+
it 'returns summary of events by type' do
|
|
128
|
+
summary = generator.event_summary('test_session')
|
|
129
|
+
|
|
130
|
+
expect(summary).to be_a(Hash)
|
|
131
|
+
expect(summary['user_created']).to eq(1)
|
|
132
|
+
expect(summary['order_placed']).to eq(1)
|
|
133
|
+
expect(summary['payment_received']).to eq(1)
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
it 'returns summary for latest session by default' do
|
|
137
|
+
summary = generator.event_summary
|
|
138
|
+
|
|
139
|
+
expect(summary).to be_a(Hash)
|
|
140
|
+
expect(summary.keys).to include('user_created', 'order_placed', 'payment_received')
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
it 'returns empty hash for non-existent session' do
|
|
144
|
+
summary = generator.event_summary('non_existent')
|
|
145
|
+
expect(summary).to eq({})
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
describe '#timeline_data' do
|
|
150
|
+
it 'returns timeline data structure' do
|
|
151
|
+
data = generator.timeline_data('test_session')
|
|
152
|
+
|
|
153
|
+
expect(data).to be_an(Array)
|
|
154
|
+
expect(data.length).to eq(3)
|
|
155
|
+
|
|
156
|
+
event_data = data.first
|
|
157
|
+
expect(event_data).to have_key(:id)
|
|
158
|
+
expect(event_data).to have_key(:type)
|
|
159
|
+
expect(event_data).to have_key(:timestamp)
|
|
160
|
+
expect(event_data).to have_key(:source)
|
|
161
|
+
expect(event_data).to have_key(:data)
|
|
162
|
+
expect(event_data).to have_key(:duration)
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
it 'includes proper event data' do
|
|
166
|
+
data = generator.timeline_data('test_session')
|
|
167
|
+
event_data = data.find { |e| e[:type] == 'user_created' }
|
|
168
|
+
|
|
169
|
+
expect(event_data[:source]).to eq('UserService')
|
|
170
|
+
expect(event_data[:data][:user_id]).to eq(123)
|
|
171
|
+
expect(event_data[:data][:name]).to eq('John Doe')
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
it 'returns data for latest session by default' do
|
|
175
|
+
data = generator.timeline_data
|
|
176
|
+
|
|
177
|
+
expect(data).to be_an(Array)
|
|
178
|
+
expect(data.length).to eq(3)
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
it 'returns empty array for non-existent session' do
|
|
182
|
+
data = generator.timeline_data('non_existent')
|
|
183
|
+
expect(data).to eq([])
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
describe 'HTML generation' do
|
|
188
|
+
let(:output_path) { generator.generate_timeline }
|
|
189
|
+
|
|
190
|
+
it 'includes all event types in filter dropdown' do
|
|
191
|
+
content = File.read(output_path)
|
|
192
|
+
|
|
193
|
+
expect(content).to include('<option value="user_created">user_created</option>')
|
|
194
|
+
expect(content).to include('<option value="order_placed">order_placed</option>')
|
|
195
|
+
expect(content).to include('<option value="payment_received">payment_received</option>')
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
it 'includes event data in timeline' do
|
|
199
|
+
content = File.read(output_path)
|
|
200
|
+
|
|
201
|
+
expect(content).to include('user_id')
|
|
202
|
+
expect(content).to include('John Doe')
|
|
203
|
+
expect(content).to include('order_id')
|
|
204
|
+
expect(content).to include('99.99')
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
it 'includes proper CSS styling' do
|
|
208
|
+
content = File.read(output_path)
|
|
209
|
+
|
|
210
|
+
expect(content).to include('body { font-family: Arial, sans-serif;')
|
|
211
|
+
expect(content).to include('.timeline-item')
|
|
212
|
+
expect(content).to include('.event-header')
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
it 'includes JavaScript functionality' do
|
|
216
|
+
content = File.read(output_path)
|
|
217
|
+
|
|
218
|
+
expect(content).to include('const events =')
|
|
219
|
+
expect(content).to include('filterEvents()')
|
|
220
|
+
expect(content).to include('generateEventHTML')
|
|
221
|
+
end
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
describe 'error handling' do
|
|
225
|
+
it 'handles storage errors gracefully' do
|
|
226
|
+
# Mock storage to raise an error
|
|
227
|
+
allow(storage).to receive(:load_session).and_raise(StandardError, 'Storage error')
|
|
228
|
+
|
|
229
|
+
expect { generator.generate_timeline }.to raise_error(StandardError, 'Storage error')
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
it 'handles file writing errors gracefully' do
|
|
233
|
+
# Mock File.write to raise an error
|
|
234
|
+
allow(File).to receive(:write).and_raise(StandardError, 'File write error')
|
|
235
|
+
|
|
236
|
+
expect { generator.generate_timeline }.to raise_error(StandardError, 'File write error')
|
|
237
|
+
end
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
describe 'integration with different storage types' do
|
|
241
|
+
it 'works with memory storage' do
|
|
242
|
+
memory_storage = EventSystem::Storage::MemoryStore.new('memory_session')
|
|
243
|
+
memory_storage.store(create_test_event(:test_event, 'TestSource', { key: 'value' }))
|
|
244
|
+
|
|
245
|
+
memory_generator = EventSystem::Visualization::TimelineGenerator.new(memory_storage)
|
|
246
|
+
output_path = memory_generator.generate_timeline
|
|
247
|
+
|
|
248
|
+
expect(output_path).to be_a(String)
|
|
249
|
+
expect(File.exist?(output_path)).to be true
|
|
250
|
+
end
|
|
251
|
+
end
|
|
252
|
+
end
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
|
|
5
|
+
RSpec.describe EventSystem do
|
|
6
|
+
describe '.version' do
|
|
7
|
+
it 'returns the current version' do
|
|
8
|
+
expect(EventSystem.version).to eq('0.1.0')
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
describe '.create_manager' do
|
|
13
|
+
it 'creates a manager with default configuration' do
|
|
14
|
+
manager = EventSystem.create_manager
|
|
15
|
+
expect(manager).to be_a(EventSystem::EventManager)
|
|
16
|
+
expect(manager.config.storage_type).to eq(:memory)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
it 'creates a manager with custom configuration hash' do
|
|
20
|
+
config = { storage_type: :file, storage_options: { directory: 'test_logs' } }
|
|
21
|
+
manager = EventSystem.create_manager(config)
|
|
22
|
+
expect(manager.config.storage_type).to eq(:file)
|
|
23
|
+
expect(manager.config.storage_options[:directory]).to eq('test_logs')
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
it 'creates a manager with Configuration object' do
|
|
27
|
+
config = EventSystem::Configuration.new
|
|
28
|
+
config.storage_type = :file
|
|
29
|
+
manager = EventSystem.create_manager(config)
|
|
30
|
+
expect(manager.config).to eq(config)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
describe '.create_event' do
|
|
35
|
+
it 'creates an event with type only' do
|
|
36
|
+
event = EventSystem.create_event(:test_type)
|
|
37
|
+
expect(event).to be_a(EventSystem::Event)
|
|
38
|
+
expect(event.type).to eq('test_type')
|
|
39
|
+
expect(event.source).to be_nil
|
|
40
|
+
expect(event.data).to eq({})
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
it 'creates an event with type and source' do
|
|
44
|
+
event = EventSystem.create_event(:test_type, 'TestSource')
|
|
45
|
+
expect(event.type).to eq('test_type')
|
|
46
|
+
expect(event.source).to eq('TestSource')
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
it 'creates an event with all parameters' do
|
|
50
|
+
data = { key: 'value' }
|
|
51
|
+
event = EventSystem.create_event(:test_type, 'TestSource', data)
|
|
52
|
+
expect(event.type).to eq('test_type')
|
|
53
|
+
expect(event.source).to eq('TestSource')
|
|
54
|
+
expect(event.data).to eq(data)
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|