rails_pulse 0.1.3 → 0.2.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 +4 -4
- data/README.md +134 -16
- data/Rakefile +315 -83
- data/app/assets/images/rails_pulse/rails-pulse-logo.png +0 -0
- data/app/assets/stylesheets/rails_pulse/components/datepicker.css +191 -0
- data/app/assets/stylesheets/rails_pulse/components/switch.css +36 -0
- data/app/assets/stylesheets/rails_pulse/components/tags.css +98 -0
- data/app/assets/stylesheets/rails_pulse/components/utilities.css +26 -0
- data/app/controllers/concerns/response_range_concern.rb +15 -2
- data/app/controllers/concerns/tag_filter_concern.rb +26 -0
- data/app/controllers/concerns/time_range_concern.rb +27 -8
- data/app/controllers/rails_pulse/application_controller.rb +73 -0
- data/app/controllers/rails_pulse/queries_controller.rb +18 -21
- data/app/controllers/rails_pulse/requests_controller.rb +80 -35
- data/app/controllers/rails_pulse/routes_controller.rb +4 -2
- data/app/controllers/rails_pulse/tags_controller.rb +51 -0
- data/app/helpers/rails_pulse/application_helper.rb +2 -0
- data/app/helpers/rails_pulse/breadcrumbs_helper.rb +1 -1
- data/app/helpers/rails_pulse/chart_helper.rb +1 -1
- data/app/helpers/rails_pulse/form_helper.rb +75 -0
- data/app/helpers/rails_pulse/formatting_helper.rb +21 -2
- data/app/helpers/rails_pulse/tags_helper.rb +29 -0
- data/app/javascript/rails_pulse/application.js +6 -0
- data/app/javascript/rails_pulse/controllers/custom_range_controller.js +115 -0
- data/app/javascript/rails_pulse/controllers/datepicker_controller.js +48 -0
- data/app/javascript/rails_pulse/controllers/global_filters_controller.js +110 -0
- data/app/javascript/rails_pulse/controllers/index_controller.js +11 -3
- data/app/models/concerns/taggable.rb +61 -0
- data/app/models/rails_pulse/dashboard/tables/slow_queries.rb +1 -1
- data/app/models/rails_pulse/dashboard/tables/slow_routes.rb +1 -1
- data/app/models/rails_pulse/queries/cards/average_query_times.rb +1 -1
- data/app/models/rails_pulse/queries/cards/execution_rate.rb +56 -17
- data/app/models/rails_pulse/queries/cards/percentile_query_times.rb +1 -1
- data/app/models/rails_pulse/queries/charts/average_query_times.rb +3 -7
- data/app/models/rails_pulse/queries/tables/index.rb +10 -2
- data/app/models/rails_pulse/query.rb +2 -0
- data/app/models/rails_pulse/request.rb +10 -2
- data/app/models/rails_pulse/requests/charts/average_response_times.rb +2 -2
- data/app/models/rails_pulse/requests/tables/index.rb +77 -0
- data/app/models/rails_pulse/route.rb +2 -0
- data/app/models/rails_pulse/routes/cards/average_response_times.rb +1 -1
- data/app/models/rails_pulse/routes/cards/error_rate_per_route.rb +1 -1
- data/app/models/rails_pulse/routes/cards/percentile_response_times.rb +1 -1
- data/app/models/rails_pulse/routes/cards/request_count_totals.rb +16 -5
- data/app/models/rails_pulse/routes/tables/index.rb +14 -4
- data/app/models/rails_pulse/summary.rb +7 -7
- data/app/services/rails_pulse/analysis/query_characteristics_analyzer.rb +11 -3
- data/app/services/rails_pulse/summary_service.rb +2 -0
- data/app/views/layouts/rails_pulse/_global_filters.html.erb +84 -0
- data/app/views/layouts/rails_pulse/_menu_items.html.erb +5 -5
- data/app/views/layouts/rails_pulse/application.html.erb +8 -5
- data/app/views/rails_pulse/components/_metric_card.html.erb +2 -2
- data/app/views/rails_pulse/components/_operation_details_popover.html.erb +1 -1
- data/app/views/rails_pulse/components/_page_header.html.erb +20 -0
- data/app/views/rails_pulse/components/_sparkline_stats.html.erb +1 -1
- data/app/views/rails_pulse/dashboard/index.html.erb +1 -1
- data/app/views/rails_pulse/operations/show.html.erb +1 -1
- data/app/views/rails_pulse/queries/_analysis_results.html.erb +53 -23
- data/app/views/rails_pulse/queries/_show_table.html.erb +33 -5
- data/app/views/rails_pulse/queries/_table.html.erb +4 -6
- data/app/views/rails_pulse/queries/index.html.erb +3 -7
- data/app/views/rails_pulse/queries/show.html.erb +3 -7
- data/app/views/rails_pulse/requests/_table.html.erb +32 -19
- data/app/views/rails_pulse/requests/index.html.erb +45 -55
- data/app/views/rails_pulse/requests/show.html.erb +1 -3
- data/app/views/rails_pulse/routes/_requests_table.html.erb +41 -0
- data/app/views/rails_pulse/routes/_table.html.erb +4 -8
- data/app/views/rails_pulse/routes/index.html.erb +4 -8
- data/app/views/rails_pulse/routes/show.html.erb +6 -12
- data/app/views/rails_pulse/tags/_tag_manager.html.erb +73 -0
- data/config/initializers/rails_charts_csp_patch.rb +32 -40
- data/config/routes.rb +5 -0
- data/db/migrate/20250930105043_install_rails_pulse_tables.rb +23 -0
- data/db/rails_pulse_schema.rb +4 -1
- data/lib/generators/rails_pulse/convert_to_migrations_generator.rb +25 -9
- data/lib/generators/rails_pulse/install_generator.rb +30 -7
- data/lib/generators/rails_pulse/templates/db/rails_pulse_schema.rb +75 -2
- data/lib/generators/rails_pulse/templates/migrations/install_rails_pulse_tables.rb +3 -2
- data/lib/generators/rails_pulse/templates/rails_pulse.rb +21 -0
- data/lib/generators/rails_pulse/upgrade_generator.rb +147 -30
- data/lib/rails_pulse/configuration.rb +16 -1
- data/lib/rails_pulse/engine.rb +21 -0
- data/lib/rails_pulse/version.rb +1 -1
- data/public/rails-pulse-assets/rails-pulse-icons.js +16 -15
- data/public/rails-pulse-assets/rails-pulse-icons.js.map +1 -1
- data/public/rails-pulse-assets/rails-pulse.css +1 -1
- data/public/rails-pulse-assets/rails-pulse.css.map +1 -1
- data/public/rails-pulse-assets/rails-pulse.js +73 -69
- data/public/rails-pulse-assets/rails-pulse.js.map +4 -4
- metadata +20 -5
- data/app/views/rails_pulse/components/_breadcrumbs.html.erb +0 -12
- data/db/migrate/20241222000001_create_rails_pulse_summaries.rb +0 -54
- data/db/migrate/20250916031656_add_analysis_to_rails_pulse_queries.rb +0 -13
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
@import url("https://esm.sh/flatpickr@4.6.13/dist/flatpickr.min.css");
|
|
2
|
+
|
|
3
|
+
.flatpickr-calendar {
|
|
4
|
+
--calendar-size: 250px;
|
|
5
|
+
--container-size: 220px;
|
|
6
|
+
--day-size: var(--size-8);
|
|
7
|
+
|
|
8
|
+
background: var(--color-bg);
|
|
9
|
+
border: 1px solid var(--color-border);
|
|
10
|
+
border-radius: var(--rounded-md);
|
|
11
|
+
box-shadow: var(--shadow-md);
|
|
12
|
+
font-size: var(--text-sm);
|
|
13
|
+
inline-size: var(--calendar-size);
|
|
14
|
+
|
|
15
|
+
.flatpickr-innerContainer {
|
|
16
|
+
justify-content: center;
|
|
17
|
+
padding-block-end: var(--size-3);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.flatpickr-days {
|
|
21
|
+
inline-size: var(--container-size);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.dayContainer {
|
|
25
|
+
inline-size: var(--container-size);
|
|
26
|
+
min-inline-size: var(--container-size);
|
|
27
|
+
max-inline-size: var(--container-size);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.dayContainer + .dayContainer {
|
|
31
|
+
box-shadow: -1px 0 0 var(--color-border);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.flatpickr-months {
|
|
35
|
+
.flatpickr-month {
|
|
36
|
+
color: var(--color-text);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
span.cur-month {
|
|
40
|
+
font-size: var(--text-sm);
|
|
41
|
+
font-weight: var(--font-medium);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
svg {
|
|
45
|
+
fill: var(--color-border-dark);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.flatpickr-prev-month:hover svg {
|
|
49
|
+
fill: var(--color-text);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.flatpickr-next-month:hover svg {
|
|
53
|
+
fill: var(--color-text);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.flatpickr-monthDropdown-months {
|
|
58
|
+
appearance: none;
|
|
59
|
+
border-radius: var(--rounded-md);
|
|
60
|
+
font-size: var(--text-sm);
|
|
61
|
+
font-weight: var(--font-medium);
|
|
62
|
+
line-height: var(--leading-normal);
|
|
63
|
+
padding: 0;
|
|
64
|
+
text-align: center;
|
|
65
|
+
|
|
66
|
+
&:hover {
|
|
67
|
+
background: var(--color-border-light);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
.numInputWrapper {
|
|
72
|
+
input {
|
|
73
|
+
border-radius: var(--rounded-md);
|
|
74
|
+
color: var(--color-text);
|
|
75
|
+
font-size: var(--text-sm);
|
|
76
|
+
font-weight: var(--font-medium);
|
|
77
|
+
line-height: var(--leading-normal);
|
|
78
|
+
padding: 0;
|
|
79
|
+
text-align: center;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
span {
|
|
83
|
+
border-color: var(--color-border);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
span:hover {
|
|
87
|
+
background: transparent;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
span.arrowUp::after {
|
|
91
|
+
border-bottom-color: var(--color-text);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
span.arrowDown::after {
|
|
95
|
+
border-top-color: var(--color-text);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
&:hover {
|
|
99
|
+
background: transparent;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
.flatpickr-weekday {
|
|
104
|
+
color: var(--color-text-subtle);
|
|
105
|
+
font-weight: var(--font-normal);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
.flatpickr-time {
|
|
109
|
+
.hasTime & {
|
|
110
|
+
border-top-color: var(--color-border);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
.hasTime.noCalendar & {
|
|
114
|
+
border: 0;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
.numInput {
|
|
118
|
+
background: transparent;
|
|
119
|
+
color: var(--color-text);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
.flatpickr-time-separator {
|
|
123
|
+
color: var(--color-text);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
.flatpickr-am-pm {
|
|
127
|
+
background: transparent;
|
|
128
|
+
color: var(--color-text);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
.flatpickr-day {
|
|
133
|
+
border-radius: var(--rounded-md);
|
|
134
|
+
border-color: transparent !important;
|
|
135
|
+
box-shadow: none !important;
|
|
136
|
+
color: var(--color-text);
|
|
137
|
+
height: var(--day-size);
|
|
138
|
+
line-height: var(--day-size);
|
|
139
|
+
margin-block-start: var(--size-2);
|
|
140
|
+
max-width: var(--day-size);
|
|
141
|
+
|
|
142
|
+
&:is(.inRange) {
|
|
143
|
+
border-radius: 0;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
&:is(.today, .inRange, :hover, :focus) {
|
|
147
|
+
background: var(--color-secondary);
|
|
148
|
+
color: var(--color-text);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
&:is(
|
|
152
|
+
.flatpickr-disabled,
|
|
153
|
+
.flatpickr-disabled:hover,
|
|
154
|
+
.prevMonthDay,
|
|
155
|
+
.nextMonthDay,
|
|
156
|
+
.notAllowed,
|
|
157
|
+
.notAllowed.prevMonthDay,
|
|
158
|
+
.notAllowed.nextMonthDay
|
|
159
|
+
) {
|
|
160
|
+
color: var(--color-text-subtle);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
&:is(
|
|
164
|
+
.selected,
|
|
165
|
+
.startRange,
|
|
166
|
+
.endRange,
|
|
167
|
+
.selected.inRange,
|
|
168
|
+
.startRange.inRange,
|
|
169
|
+
.endRange.inRange,
|
|
170
|
+
.selected:focus,
|
|
171
|
+
.startRange:focus,
|
|
172
|
+
.endRange:focus,
|
|
173
|
+
.selected:hover,
|
|
174
|
+
.startRange:hover,
|
|
175
|
+
.endRange:hover,
|
|
176
|
+
.selected.prevMonthDay,
|
|
177
|
+
.startRange.prevMonthDay,
|
|
178
|
+
.endRange.prevMonthDay,
|
|
179
|
+
.selected.nextMonthDay,
|
|
180
|
+
.startRange.nextMonthDay,
|
|
181
|
+
.endRange.nextMonthDay
|
|
182
|
+
) {
|
|
183
|
+
background: var(--color-primary);
|
|
184
|
+
color: var(--color-text-reversed);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
&::before, &::after {
|
|
189
|
+
display: none;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
.switch {
|
|
2
|
+
appearance: none;
|
|
3
|
+
background-color: var(--color-border);
|
|
4
|
+
border-color: transparent;
|
|
5
|
+
border-radius: var(--rounded-full);
|
|
6
|
+
border-width: var(--border-2);
|
|
7
|
+
block-size: var(--size-5);
|
|
8
|
+
inline-size: var(--size-9);
|
|
9
|
+
transition: background-color var(--time-150);
|
|
10
|
+
|
|
11
|
+
&:checked {
|
|
12
|
+
background-color: var(--color-primary);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
&:checked::before {
|
|
16
|
+
margin-inline-start: var(--size-4);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
&::before {
|
|
20
|
+
aspect-ratio: var(--aspect-square);
|
|
21
|
+
background-color: var(--color-text-reversed);
|
|
22
|
+
block-size: var(--size-full);
|
|
23
|
+
border-radius: var(--rounded-full);
|
|
24
|
+
content: "";
|
|
25
|
+
display: block;
|
|
26
|
+
transition: margin var(--time-150);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
&:focus-visible {
|
|
30
|
+
outline: var(--border-2) solid var(--color-selected-dark);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
&:disabled {
|
|
34
|
+
cursor: not-allowed; opacity: var(--opacity-50);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/* Tag Manager Container */
|
|
2
|
+
.breadcrumb-container {
|
|
3
|
+
display: flex;
|
|
4
|
+
align-items: center;
|
|
5
|
+
justify-content: space-between;
|
|
6
|
+
gap: 1rem;
|
|
7
|
+
flex-wrap: wrap;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
.breadcrumb-tags {
|
|
11
|
+
display: flex;
|
|
12
|
+
align-items: center;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/* Tag Manager */
|
|
16
|
+
.tag-manager {
|
|
17
|
+
display: flex;
|
|
18
|
+
align-items: center;
|
|
19
|
+
gap: 0.5rem;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.tag-list {
|
|
23
|
+
display: flex;
|
|
24
|
+
align-items: center;
|
|
25
|
+
gap: 0.5rem;
|
|
26
|
+
flex-wrap: wrap;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/* Individual Tag */
|
|
30
|
+
.tag {
|
|
31
|
+
display: inline-flex;
|
|
32
|
+
align-items: center;
|
|
33
|
+
gap: 0.25rem;
|
|
34
|
+
padding: 0.25rem 0.5rem;
|
|
35
|
+
background-color: var(--color-background-secondary);
|
|
36
|
+
border: 1px solid var(--color-border);
|
|
37
|
+
border-radius: 0.375rem;
|
|
38
|
+
font-size: 0.875rem;
|
|
39
|
+
line-height: 1.25;
|
|
40
|
+
white-space: nowrap;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/* Tag Remove Button */
|
|
44
|
+
.tag-remove {
|
|
45
|
+
all: unset;
|
|
46
|
+
display: inline-flex;
|
|
47
|
+
align-items: center;
|
|
48
|
+
justify-content: center;
|
|
49
|
+
width: 1rem;
|
|
50
|
+
height: 1rem;
|
|
51
|
+
padding: 0;
|
|
52
|
+
margin: 0;
|
|
53
|
+
background: none;
|
|
54
|
+
border: none;
|
|
55
|
+
cursor: pointer;
|
|
56
|
+
color: currentColor;
|
|
57
|
+
opacity: 0.6;
|
|
58
|
+
transition: opacity 0.15s ease;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.tag-remove:hover {
|
|
62
|
+
opacity: 1;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
.tag-remove span {
|
|
66
|
+
font-size: 1.25rem;
|
|
67
|
+
line-height: 1;
|
|
68
|
+
font-weight: bold;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/* Add Tag Container */
|
|
72
|
+
.tag-add-container {
|
|
73
|
+
position: relative;
|
|
74
|
+
display: inline-block;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.tag-add-button {
|
|
78
|
+
padding: 0.25rem 0.5rem;
|
|
79
|
+
font-size: 0.875rem;
|
|
80
|
+
line-height: 1.25;
|
|
81
|
+
white-space: nowrap;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/* Responsive Design */
|
|
85
|
+
@media (max-width: 768px) {
|
|
86
|
+
.breadcrumb-container {
|
|
87
|
+
flex-direction: column;
|
|
88
|
+
align-items: flex-start;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.breadcrumb-tags {
|
|
92
|
+
width: 100%;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
.tag-manager {
|
|
96
|
+
width: 100%;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
@@ -34,3 +34,29 @@
|
|
|
34
34
|
.max-w-md { max-width: 28rem; }
|
|
35
35
|
.max-w-lg { max-width: 32rem; }
|
|
36
36
|
.max-w-xl { max-width: 36rem; }
|
|
37
|
+
|
|
38
|
+
/* Global filters active indicator */
|
|
39
|
+
.global-filters-active {
|
|
40
|
+
position: relative;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.global-filters-active::after {
|
|
44
|
+
content: "";
|
|
45
|
+
position: absolute;
|
|
46
|
+
top: -2px;
|
|
47
|
+
right: -2px;
|
|
48
|
+
width: 8px;
|
|
49
|
+
height: 8px;
|
|
50
|
+
background-color: var(--color-primary);
|
|
51
|
+
border-radius: 50%;
|
|
52
|
+
border: 2px solid var(--color-bg);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/* Flatpickr z-index fix - ensure calendar appears above dialogs */
|
|
56
|
+
.flatpickr-calendar,
|
|
57
|
+
.flatpickr-calendar.open,
|
|
58
|
+
.flatpickr-calendar.inline,
|
|
59
|
+
.flatpickr-calendar.static,
|
|
60
|
+
.flatpickr-calendar.static.open {
|
|
61
|
+
z-index: 999999 !important;
|
|
62
|
+
}
|
|
@@ -5,9 +5,11 @@ module ResponseRangeConcern
|
|
|
5
5
|
ransack_params = params[:q] || {}
|
|
6
6
|
thresholds = RailsPulse.configuration.public_send("#{type}_thresholds")
|
|
7
7
|
|
|
8
|
-
# Check
|
|
9
|
-
|
|
8
|
+
# Check all duration-related parameters
|
|
9
|
+
# avg_duration for Summary, duration for Request/Operation, duration_gteq for direct Ransack filtering
|
|
10
|
+
duration_param = ransack_params[:avg_duration] || ransack_params[:duration] || ransack_params[:duration_gteq]
|
|
10
11
|
|
|
12
|
+
# Priority 1: Page-specific duration filter
|
|
11
13
|
if duration_param.present?
|
|
12
14
|
selected_range = duration_param
|
|
13
15
|
start_duration =
|
|
@@ -17,6 +19,17 @@ module ResponseRangeConcern
|
|
|
17
19
|
when :critical then thresholds[:critical]
|
|
18
20
|
else 0
|
|
19
21
|
end
|
|
22
|
+
# Priority 2: Global performance threshold filter
|
|
23
|
+
elsif (global_threshold = session_global_filters["performance_threshold"]).present?
|
|
24
|
+
selected_range = global_threshold.to_sym
|
|
25
|
+
start_duration =
|
|
26
|
+
case global_threshold.to_sym
|
|
27
|
+
when :slow then thresholds[:slow]
|
|
28
|
+
when :very_slow then thresholds[:very_slow]
|
|
29
|
+
when :critical then thresholds[:critical]
|
|
30
|
+
else 0
|
|
31
|
+
end
|
|
32
|
+
# Priority 3: No filter (show all)
|
|
20
33
|
else
|
|
21
34
|
start_duration = 0
|
|
22
35
|
selected_range = :all
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
module TagFilterConcern
|
|
2
|
+
extend ActiveSupport::Concern
|
|
3
|
+
|
|
4
|
+
private
|
|
5
|
+
|
|
6
|
+
# Apply tag filters to a query
|
|
7
|
+
# Excludes records that have ANY of the disabled tags
|
|
8
|
+
def apply_tag_filters(query)
|
|
9
|
+
disabled_tags = session_disabled_tags
|
|
10
|
+
query = disabled_tags.reduce(query) do |q, tag|
|
|
11
|
+
q.without_tag(tag)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
apply_non_tagged_filter(query)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# Apply non-tagged filter to a query
|
|
18
|
+
# If show_non_tagged is false, exclude records with no tags
|
|
19
|
+
def apply_non_tagged_filter(query)
|
|
20
|
+
if session[:show_non_tagged] == false
|
|
21
|
+
query.with_tags
|
|
22
|
+
else
|
|
23
|
+
query
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -6,7 +6,8 @@ module TimeRangeConcern
|
|
|
6
6
|
const_set(:TIME_RANGE_OPTIONS, [
|
|
7
7
|
[ "Last 24 hours", :last_day ],
|
|
8
8
|
[ "Last Week", :last_week ],
|
|
9
|
-
[ "Last Month", :last_month ]
|
|
9
|
+
[ "Last Month", :last_month ],
|
|
10
|
+
[ "Custom Range...", :custom ]
|
|
10
11
|
].freeze)
|
|
11
12
|
end
|
|
12
13
|
|
|
@@ -17,11 +18,8 @@ module TimeRangeConcern
|
|
|
17
18
|
|
|
18
19
|
ransack_params = params[:q] || {}
|
|
19
20
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
start_time = parse_time_param(ransack_params[:occurred_at_gteq])
|
|
23
|
-
end_time = parse_time_param(ransack_params[:occurred_at_lt])
|
|
24
|
-
elsif ransack_params[:period_start_range]
|
|
21
|
+
# Priority 1: Page-specific preset from dropdown (check this first!)
|
|
22
|
+
if ransack_params[:period_start_range].present? && ransack_params[:period_start_range].to_sym != :custom
|
|
25
23
|
# Predefined time range from dropdown
|
|
26
24
|
selected_time_range = ransack_params[:period_start_range]
|
|
27
25
|
start_time =
|
|
@@ -31,7 +29,26 @@ module TimeRangeConcern
|
|
|
31
29
|
when :last_month then 1.month.ago
|
|
32
30
|
else 1.day.ago # Default fallback
|
|
33
31
|
end
|
|
32
|
+
# Priority 2: Page-specific custom datetime range from picker (only if period_start_range is :custom)
|
|
33
|
+
elsif ransack_params[:period_start_range].present? && ransack_params[:period_start_range].to_sym == :custom && ransack_params[:custom_date_range].present? && ransack_params[:custom_date_range].include?(" to ")
|
|
34
|
+
# Custom datetime range from custom range picker
|
|
35
|
+
dates = ransack_params[:custom_date_range].split(" to ")
|
|
36
|
+
start_time = parse_time_param(dates[0].strip)
|
|
37
|
+
end_time = parse_time_param(dates[1].strip)
|
|
38
|
+
selected_time_range = :custom
|
|
39
|
+
# Priority 3: Page-specific filters (chart zoom)
|
|
40
|
+
elsif ransack_params[:occurred_at_gteq].present? && ransack_params[:occurred_at_lt].present?
|
|
41
|
+
# Custom time range from chart zoom
|
|
42
|
+
start_time = parse_time_param(ransack_params[:occurred_at_gteq])
|
|
43
|
+
end_time = parse_time_param(ransack_params[:occurred_at_lt])
|
|
44
|
+
selected_time_range = :custom
|
|
45
|
+
# Priority 4: Global filters (from session)
|
|
46
|
+
elsif session_global_filters["start_time"].present? || session_global_filters["end_time"].present?
|
|
47
|
+
start_time = parse_time_param(session_global_filters["start_time"]) if session_global_filters["start_time"].present?
|
|
48
|
+
end_time = parse_time_param(session_global_filters["end_time"]) if session_global_filters["end_time"].present?
|
|
49
|
+
selected_time_range = :custom
|
|
34
50
|
end
|
|
51
|
+
# Priority 5: Default time range (already set above)
|
|
35
52
|
|
|
36
53
|
time_diff = (end_time.to_i - start_time.to_i) / 3600.0
|
|
37
54
|
|
|
@@ -43,7 +60,7 @@ module TimeRangeConcern
|
|
|
43
60
|
end_time = end_time.end_of_day
|
|
44
61
|
end
|
|
45
62
|
|
|
46
|
-
[ start_time, end_time, selected_time_range, time_diff ]
|
|
63
|
+
[ start_time.to_i, end_time.to_i, selected_time_range, time_diff ]
|
|
47
64
|
end
|
|
48
65
|
|
|
49
66
|
private
|
|
@@ -53,7 +70,9 @@ module TimeRangeConcern
|
|
|
53
70
|
when Time, DateTime
|
|
54
71
|
param.in_time_zone
|
|
55
72
|
when String
|
|
56
|
-
Time.zone
|
|
73
|
+
# Parse as server local time (not UTC, not Time.zone)
|
|
74
|
+
# This ensures flatpickr datetime strings are interpreted in server's timezone
|
|
75
|
+
Time.parse(param).localtime
|
|
57
76
|
else
|
|
58
77
|
# Assume it's an integer timestamp
|
|
59
78
|
Time.zone.at(param.to_i)
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
module RailsPulse
|
|
2
2
|
class ApplicationController < ActionController::Base
|
|
3
3
|
before_action :authenticate_rails_pulse_user!
|
|
4
|
+
before_action :set_show_non_tagged_default
|
|
5
|
+
helper_method :session_global_filters, :session_disabled_tags
|
|
4
6
|
|
|
5
7
|
def set_pagination_limit(limit = nil)
|
|
6
8
|
limit = limit || params[:limit]
|
|
@@ -12,6 +14,48 @@ module RailsPulse
|
|
|
12
14
|
end
|
|
13
15
|
end
|
|
14
16
|
|
|
17
|
+
def set_global_filters
|
|
18
|
+
if params[:clear] == "true"
|
|
19
|
+
session.delete(:global_filters)
|
|
20
|
+
else
|
|
21
|
+
filters = session[:global_filters] || {}
|
|
22
|
+
|
|
23
|
+
# Update time filters if provided
|
|
24
|
+
if params[:start_time].present? && params[:end_time].present?
|
|
25
|
+
filters["start_time"] = params[:start_time]
|
|
26
|
+
filters["end_time"] = params[:end_time]
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Update performance threshold if provided (or remove if empty)
|
|
30
|
+
if params[:performance_threshold].present?
|
|
31
|
+
filters["performance_threshold"] = params[:performance_threshold]
|
|
32
|
+
else
|
|
33
|
+
filters.delete("performance_threshold")
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Update tag visibility - convert enabled tags to disabled tags
|
|
37
|
+
all_tags = RailsPulse.configuration.tags
|
|
38
|
+
enabled_tags = params[:enabled_tags] || []
|
|
39
|
+
|
|
40
|
+
# Handle "non_tagged" separately
|
|
41
|
+
session[:show_non_tagged] = enabled_tags.include?("non_tagged")
|
|
42
|
+
enabled_tags = enabled_tags - [ "non_tagged" ]
|
|
43
|
+
|
|
44
|
+
disabled_tags = all_tags - enabled_tags
|
|
45
|
+
|
|
46
|
+
if disabled_tags.any?
|
|
47
|
+
filters["disabled_tags"] = disabled_tags
|
|
48
|
+
else
|
|
49
|
+
filters.delete("disabled_tags")
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
session[:global_filters] = filters
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Redirect back to the referring page or root
|
|
56
|
+
redirect_back(fallback_location: root_path)
|
|
57
|
+
end
|
|
58
|
+
|
|
15
59
|
private
|
|
16
60
|
|
|
17
61
|
def authenticate_rails_pulse_user!
|
|
@@ -71,5 +115,34 @@ module RailsPulse
|
|
|
71
115
|
validated_limit = limit.to_i.clamp(5, 50)
|
|
72
116
|
session[:pagination_limit] = validated_limit if limit.present?
|
|
73
117
|
end
|
|
118
|
+
|
|
119
|
+
def session_global_filters
|
|
120
|
+
session[:global_filters] || {}
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def session_disabled_tags
|
|
124
|
+
session_global_filters["disabled_tags"] || []
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# Get the minimum duration based on global performance threshold
|
|
128
|
+
# Returns nil if no threshold is set (show all)
|
|
129
|
+
# context: :route, :request, or :query
|
|
130
|
+
def global_performance_threshold_duration(context)
|
|
131
|
+
threshold = session_global_filters["performance_threshold"]
|
|
132
|
+
return nil unless threshold.present?
|
|
133
|
+
|
|
134
|
+
config_key = "#{context}_thresholds".to_sym
|
|
135
|
+
thresholds = RailsPulse.configuration.public_send(config_key)
|
|
136
|
+
|
|
137
|
+
thresholds[threshold.to_sym]
|
|
138
|
+
rescue StandardError => e
|
|
139
|
+
Rails.logger.warn "Failed to get performance threshold: #{e.message}"
|
|
140
|
+
nil
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
# Set default value for show_non_tagged if not already set
|
|
144
|
+
def set_show_non_tagged_default
|
|
145
|
+
session[:show_non_tagged] = true if session[:show_non_tagged].nil?
|
|
146
|
+
end
|
|
74
147
|
end
|
|
75
148
|
end
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
module RailsPulse
|
|
2
2
|
class QueriesController < ApplicationController
|
|
3
3
|
include ChartTableConcern
|
|
4
|
+
include TagFilterConcern
|
|
4
5
|
|
|
5
6
|
before_action :set_query, only: [ :show, :analyze ]
|
|
6
7
|
|
|
@@ -55,7 +56,7 @@ module RailsPulse
|
|
|
55
56
|
end
|
|
56
57
|
|
|
57
58
|
def table_model
|
|
58
|
-
|
|
59
|
+
Summary
|
|
59
60
|
end
|
|
60
61
|
|
|
61
62
|
def chart_class
|
|
@@ -76,7 +77,10 @@ module RailsPulse
|
|
|
76
77
|
base_params[:avg_duration_gteq] = @start_duration if @start_duration && @start_duration > 0
|
|
77
78
|
|
|
78
79
|
if show_action?
|
|
79
|
-
base_params.merge(
|
|
80
|
+
base_params.merge(
|
|
81
|
+
summarizable_id_eq: @query.id,
|
|
82
|
+
summarizable_type_eq: "RailsPulse::Query"
|
|
83
|
+
)
|
|
80
84
|
else
|
|
81
85
|
base_params
|
|
82
86
|
end
|
|
@@ -84,13 +88,14 @@ module RailsPulse
|
|
|
84
88
|
|
|
85
89
|
def build_table_ransack_params(ransack_params)
|
|
86
90
|
if show_action?
|
|
87
|
-
# For
|
|
91
|
+
# For Summary model on show page
|
|
88
92
|
params = ransack_params.merge(
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
93
|
+
period_start_gteq: Time.at(@table_start_time),
|
|
94
|
+
period_start_lt: Time.at(@table_end_time),
|
|
95
|
+
summarizable_id_eq: @query.id,
|
|
96
|
+
summarizable_type_eq: "RailsPulse::Query"
|
|
92
97
|
)
|
|
93
|
-
params[:
|
|
98
|
+
params[:avg_duration_gteq] = @start_duration if @start_duration && @start_duration > 0
|
|
94
99
|
params
|
|
95
100
|
else
|
|
96
101
|
# For Summary model on index page
|
|
@@ -104,29 +109,21 @@ module RailsPulse
|
|
|
104
109
|
end
|
|
105
110
|
|
|
106
111
|
def default_table_sort
|
|
107
|
-
|
|
112
|
+
"period_start desc"
|
|
108
113
|
end
|
|
109
114
|
|
|
110
115
|
def build_table_results
|
|
111
116
|
if show_action?
|
|
112
|
-
#
|
|
113
|
-
#
|
|
114
|
-
@ransack_query.result
|
|
115
|
-
.joins(<<~SQL)
|
|
116
|
-
INNER JOIN rails_pulse_summaries ON
|
|
117
|
-
rails_pulse_summaries.summarizable_id = rails_pulse_operations.query_id AND
|
|
118
|
-
rails_pulse_summaries.summarizable_type = 'RailsPulse::Query' AND
|
|
119
|
-
rails_pulse_summaries.period_type = '#{period_type}' AND
|
|
120
|
-
rails_pulse_operations.occurred_at >= rails_pulse_summaries.period_start AND
|
|
121
|
-
rails_pulse_operations.occurred_at < rails_pulse_summaries.period_end
|
|
122
|
-
SQL
|
|
123
|
-
.distinct
|
|
117
|
+
# For Summary model on show page - ransack params already include query ID and type filters
|
|
118
|
+
# Summaries aren't taggable, so we don't apply tag filters here
|
|
119
|
+
@ransack_query.result.where(period_type: period_type)
|
|
124
120
|
else
|
|
125
121
|
Queries::Tables::Index.new(
|
|
126
122
|
ransack_query: @ransack_query,
|
|
127
123
|
period_type: period_type,
|
|
128
124
|
start_time: @start_time,
|
|
129
|
-
params: params
|
|
125
|
+
params: params,
|
|
126
|
+
disabled_tags: session_disabled_tags
|
|
130
127
|
).to_table
|
|
131
128
|
end
|
|
132
129
|
end
|