sage-rails 0.0.3

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.
Files changed (83) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +202 -0
  3. data/app/assets/images/chevron-down-zinc-500.svg +1 -0
  4. data/app/assets/images/chevron-right.svg +1 -0
  5. data/app/assets/images/loading.svg +4 -0
  6. data/app/assets/images/sage/chevron-down-zinc-500.svg +1 -0
  7. data/app/assets/images/sage/chevron-right.svg +1 -0
  8. data/app/assets/images/sage/loading.svg +4 -0
  9. data/app/assets/javascripts/sage/application.js +18 -0
  10. data/app/assets/stylesheets/sage/application.css +308 -0
  11. data/app/controllers/sage/actions_controller.rb +5 -0
  12. data/app/controllers/sage/application_controller.rb +4 -0
  13. data/app/controllers/sage/base_controller.rb +10 -0
  14. data/app/controllers/sage/checks_controller.rb +65 -0
  15. data/app/controllers/sage/dashboards_controller.rb +130 -0
  16. data/app/controllers/sage/queries/messages_controller.rb +62 -0
  17. data/app/controllers/sage/queries_controller.rb +596 -0
  18. data/app/helpers/sage/application_helper.rb +30 -0
  19. data/app/helpers/sage/queries_helper.rb +23 -0
  20. data/app/javascript/controllers/element_removal_controller.js +7 -0
  21. data/app/javascript/sage/controllers/clipboard_controller.js +26 -0
  22. data/app/javascript/sage/controllers/dashboard_controller.js +132 -0
  23. data/app/javascript/sage/controllers/reverse_infinite_scroll_controller.js +146 -0
  24. data/app/javascript/sage/controllers/search_controller.js +47 -0
  25. data/app/javascript/sage/controllers/select_controller.js +215 -0
  26. data/app/javascript/sage.js +19 -0
  27. data/app/jobs/sage/application_job.rb +4 -0
  28. data/app/jobs/sage/process_report_job.rb +80 -0
  29. data/app/mailers/sage/application_mailer.rb +6 -0
  30. data/app/models/sage/application_record.rb +5 -0
  31. data/app/models/sage/message.rb +8 -0
  32. data/app/schemas/sage/report_response_schema.rb +8 -0
  33. data/app/views/layouts/application.html.erb +34 -0
  34. data/app/views/layouts/sage/application.html.erb +94 -0
  35. data/app/views/sage/checks/_form.html.erb +81 -0
  36. data/app/views/sage/checks/_search.html.erb +8 -0
  37. data/app/views/sage/checks/edit.html.erb +10 -0
  38. data/app/views/sage/checks/index.html.erb +58 -0
  39. data/app/views/sage/checks/new.html.erb +8 -0
  40. data/app/views/sage/dashboards/_form.html.erb +50 -0
  41. data/app/views/sage/dashboards/_search.html.erb +8 -0
  42. data/app/views/sage/dashboards/index.html.erb +58 -0
  43. data/app/views/sage/dashboards/new.html.erb +8 -0
  44. data/app/views/sage/dashboards/show.html.erb +58 -0
  45. data/app/views/sage/messages/_form.html.erb +14 -0
  46. data/app/views/sage/queries/_caching.html.erb +17 -0
  47. data/app/views/sage/queries/_form.html.erb +72 -0
  48. data/app/views/sage/queries/_input.html.erb +17 -0
  49. data/app/views/sage/queries/_message.html.erb +25 -0
  50. data/app/views/sage/queries/_message.turbo_stream.erb +10 -0
  51. data/app/views/sage/queries/_new_form.html.erb +43 -0
  52. data/app/views/sage/queries/_run.html.erb +232 -0
  53. data/app/views/sage/queries/_search.html.erb +8 -0
  54. data/app/views/sage/queries/_statement_box.html.erb +241 -0
  55. data/app/views/sage/queries/_streaming_message.html.erb +14 -0
  56. data/app/views/sage/queries/create.turbo_stream.erb +114 -0
  57. data/app/views/sage/queries/edit.html.erb +48 -0
  58. data/app/views/sage/queries/index.html.erb +59 -0
  59. data/app/views/sage/queries/messages/create.turbo_stream.erb +22 -0
  60. data/app/views/sage/queries/messages/index.html.erb +44 -0
  61. data/app/views/sage/queries/messages/index.turbo_stream.erb +15 -0
  62. data/app/views/sage/queries/new.html.erb +195 -0
  63. data/app/views/sage/queries/run.html.erb +1 -0
  64. data/app/views/sage/queries/run.turbo_stream.erb +3 -0
  65. data/app/views/sage/queries/show.html.erb +49 -0
  66. data/app/views/sage/queries/table_schema.html.erb +77 -0
  67. data/app/views/sage/shared/_navigation.html.erb +26 -0
  68. data/app/views/sage/shared/_overlay.html.erb +11 -0
  69. data/config/importmap.rb +11 -0
  70. data/config/initializers/pagy.rb +2 -0
  71. data/config/initializers/ransack.rb +152 -0
  72. data/config/routes.rb +31 -0
  73. data/lib/generators/sage/USAGE +13 -0
  74. data/lib/generators/sage/install/install_generator.rb +128 -0
  75. data/lib/generators/sage/install/templates/sage.rb +22 -0
  76. data/lib/sage/database_schema_context.rb +56 -0
  77. data/lib/sage/engine.rb +260 -0
  78. data/lib/sage/model_scopes_context.rb +185 -0
  79. data/lib/sage/report_processor.rb +263 -0
  80. data/lib/sage/version.rb +3 -0
  81. data/lib/sage.rb +25 -0
  82. data/lib/tasks/sage_tasks.rake +4 -0
  83. metadata +245 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: d3809df36d3eecf4a5b214bf1fc0538973a7f58199e64647c4e91e4dbfa5f582
4
+ data.tar.gz: cbe5378c43e18800e501a93dd6fb054251886dff09a22a051682a41dbba8aa3e
5
+ SHA512:
6
+ metadata.gz: ec94c0b90087cfc1cb1dab13808b5f10bbe3706e83f0c4b10e97e42f01fc1ab252e92163874db03bc408afbf498f9e8efd019b1170af4f3286a5247f0dde4907
7
+ data.tar.gz: a4c193ef6d4603ee02a1f72fa3820c963394cf2f523ccef743b8f55a9933053b206ad59a87ace4219e07828a68e6c5c024c6b8e3793d1ce27bb5a12f78ac6f72
data/README.md ADDED
@@ -0,0 +1,202 @@
1
+ # Sage
2
+
3
+ ![Sage Query Interface](screenshots/sage-query-interface.png)
4
+
5
+ **Natural language reporting to help your team build accurate reports, faster.**
6
+
7
+ Sage is a Rails engine built on top of the excellent [Blazer](https://github.com/ankane/blazer) gem, adding an LLM interface to make data exploration accessible via natural language.
8
+
9
+ ## Installation
10
+
11
+ Add Sage to your application's Gemfile:
12
+
13
+ ```ruby
14
+ gem "sage-rails"
15
+ ```
16
+
17
+ Run bundle install:
18
+ ```bash
19
+ $ bundle install
20
+ ```
21
+
22
+ ## Getting Started
23
+
24
+ Run the install generator to set up Sage in your Rails application:
25
+
26
+ ```bash
27
+ $ rails generate sage:install
28
+ ```
29
+
30
+ This generator will:
31
+ - Install Blazer if not already present
32
+ - Mount Sage at `/sage` in your routes
33
+ - Create an initializer at `config/initializers/sage.rb`
34
+ - Set up database migrations for message storage
35
+ - Configure JavaScript and CSS dependencies
36
+
37
+ After installation, run the migrations:
38
+ ```bash
39
+ $ rails db:migrate
40
+ ```
41
+
42
+ ## LLM Configuration
43
+
44
+ Sage supports both Anthropic Claude and OpenAI models for SQL generation. Configure your preferred AI service in `config/initializers/sage.rb`:
45
+
46
+ ### Using Anthropic
47
+
48
+ ```ruby
49
+ Sage.configure do |config|
50
+ config.provider = :anthropic
51
+
52
+ # API key configuration
53
+ config.anthropic_api_key = Rails.application.credentials.dig(:anthropic, :api_key) ||
54
+ ENV["ANTHROPIC_API_KEY"]
55
+
56
+ # Model selection (defaults to claude-3-opus-20240229)
57
+ config.anthropic_model = "claude-3-opus-20240229"
58
+ end
59
+ ```
60
+
61
+ ### Using OpenAI
62
+
63
+ ```ruby
64
+ Sage.configure do |config|
65
+ config.provider = :openai
66
+
67
+ # API Key Configuration
68
+ config.openai_api_key = Rails.application.credentials.dig(:openai, :api_key) ||
69
+ ENV["OPENAI_API_KEY"]
70
+
71
+ # Model selection
72
+ config.openai_model = "gpt-4" # or "gpt-3.5-turbo" for faster responses
73
+ end
74
+ ```
75
+
76
+ ## Blazer
77
+
78
+ Sage is built on top of Blazer and honors all existing Blazer configurations.
79
+
80
+ All Blazer features are fully supported:
81
+
82
+ - **Database Setup**: Configure your database connections through Blazer
83
+ - **Multiple Data Sources**: Switch between different databases seamlessly
84
+ - **Smart Variables**: Use dynamic variables in generated queries
85
+ - **Checks & Alerts**: Set up automated monitoring on your queries
86
+ - **Auditing**: Track query usage and performance
87
+ - **Security**: Leverage Blazer's authentication and authorization features
88
+
89
+ For detailed information on Blazer-specific features, refer to the [Blazer documentation](https://github.com/ankane/blazer).
90
+
91
+ ## Database Context
92
+
93
+ Sage introspects your database schema to provide context for more accurate SQL generation. This feature works out of the box with Blazer's data sources.
94
+
95
+ ### Multiple Data Sources
96
+
97
+ If you have multiple Blazer data sources configured, Sage will use the appropriate schema for each:
98
+
99
+ ```ruby
100
+ # config/blazer.yml
101
+ data_sources:
102
+ main:
103
+ url: <%= ENV["DATABASE_URL"] %>
104
+ analytics:
105
+ url: <%= ENV["ANALYTICS_DATABASE_URL"] %>
106
+ ```
107
+
108
+ When querying from different data sources in Blazer, Sage automatically switches schema context.
109
+
110
+ ## Model Scope Context
111
+
112
+ Sage leverages your Rails model scopes as documentation for query patterns, dramatically improving the accuracy of generated SQL queries, especially for complex multi-table reports.
113
+
114
+ ### Example
115
+
116
+ Given these model scopes:
117
+
118
+ ```ruby
119
+ # app/models/user.rb
120
+ class User < ApplicationRecord
121
+ scope :active, -> { where(status: 'active') }
122
+ scope :recent, -> { where('created_at > ?', 30.days.ago) }
123
+ scope :with_orders, -> { joins(:orders).distinct }
124
+ scope :high_value, -> {
125
+ joins(:orders)
126
+ .group('users.id')
127
+ .having('SUM(orders.total) > ?', 1000)
128
+ }
129
+ end
130
+
131
+ # app/models/order.rb
132
+ class Order < ApplicationRecord
133
+ scope :completed, -> { where(status: 'completed') }
134
+ scope :recent, -> { where('created_at > ?', 7.days.ago) }
135
+ scope :high_value, -> { where('total > ?', 500) }
136
+ end
137
+ ```
138
+
139
+ When prompted: *"Show me high-value customers from the last month"*
140
+
141
+ Sage understands:
142
+ - "high-value customers" → Use the `high_value` scope pattern from User model
143
+ - "last month" → Similar to the `recent` scope pattern, adjust the time range
144
+ - Combines these patterns to generate accurate SQL with proper JOINs and aggregations
145
+
146
+ ### Benefits
147
+
148
+ 1. **Business Logic Awareness**: Scopes encode your business rules (what makes a customer "active" or "high-value")
149
+ 2. **Correct JOIN Patterns**: Scopes show the proper way to join tables in your application
150
+ 3. **Aggregation Patterns**: Complex scopes with GROUP BY and HAVING clauses guide report generation
151
+ 4. **Consistency**: Generated queries follow the same patterns as your application code
152
+
153
+ Scopes now serve dual purposes:
154
+ 1. Reusable query logic in your Rails application
155
+ 2. Documentation for report generation
156
+
157
+ ## Development
158
+
159
+ After checking out the repo:
160
+
161
+ ```bash
162
+ $ bundle install
163
+ $ cd test/dummy
164
+ $ rails db:create
165
+ $ rails db:migrate
166
+ $ rails server
167
+ ```
168
+
169
+ Visit http://localhost:3000/sage to see the engine in action.
170
+
171
+ ## Testing
172
+
173
+ Run the test suite:
174
+
175
+ ```bash
176
+ $ rails test
177
+ ```
178
+
179
+ ## Troubleshooting
180
+
181
+ ### API Key Issues
182
+ - Verify your API key is correctly set in credentials or environment variables
183
+ - Check Rails logs for authentication errors
184
+ - Ensure your API key has appropriate permissions
185
+
186
+ ### Query Generation Issues
187
+ - Verify database schema is being loaded (check logs for schema context)
188
+ - Ensure model files are in standard Rails locations (`app/models/`)
189
+ - Check that Blazer is properly configured and can execute queries
190
+
191
+ ### Performance
192
+ - Use lighter models (Claude Haiku, GPT-3.5) for faster response times
193
+ - Consider caching frequently used queries
194
+ - Scope context is cached per request to minimize processing
195
+
196
+ ## Contributing
197
+
198
+ Bug reports and pull requests are welcome on GitHub. This project is intended to be a safe, welcoming space for collaboration.
199
+
200
+ ## License
201
+
202
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#71717a" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-chevron-down-icon lucide-chevron-down"><path d="m6 9 6 6 6-6"/></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-chevron-right-icon lucide-chevron-right"><path d="m9 18 6-6-6-6"/></svg>
@@ -0,0 +1,4 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none">
2
+ <circle opacity=".25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
3
+ <path opacity=".75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
4
+ </svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#71717a" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-chevron-down-icon lucide-chevron-down"><path d="m6 9 6 6 6-6"/></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-chevron-right-icon lucide-chevron-right"><path d="m9 18 6-6-6-6"/></svg>
@@ -0,0 +1,4 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none">
2
+ <circle opacity=".25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
3
+ <path opacity=".75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
4
+ </svg>
@@ -0,0 +1,18 @@
1
+ // Sage application JavaScript
2
+ // Configure your import map in config/importmap.rb. Read more: https://github.com/rails/importmap-rails
3
+ import "@hotwired/turbo-rails"
4
+ import { Application } from "@hotwired/stimulus"
5
+
6
+ // Import and register Sage controllers
7
+ import { SearchController, ClipboardController, SelectController, DashboardController, ReverseInfiniteScrollController } from "sage"
8
+
9
+ const application = Application.start()
10
+ application.debug = true
11
+ window.Stimulus = application
12
+
13
+ // Register controllers
14
+ application.register("sage--search", SearchController)
15
+ application.register("sage--clipboard", ClipboardController)
16
+ application.register("sage--select", SelectController)
17
+ application.register("sage--dashboard", DashboardController)
18
+ application.register("sage--reverse-infinite-scroll", ReverseInfiniteScrollController)
@@ -0,0 +1,308 @@
1
+ /*
2
+ * This is a manifest file that'll be compiled into application.css, which will include all the files
3
+ * listed below.
4
+ *
5
+ * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
6
+ * or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path.
7
+ *
8
+ * You're free to add application-wide styles to this file and they'll appear at the bottom of the
9
+ * compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS
10
+ * files in this directory. Styles in this file should be added after the last require_* statement.
11
+ * It is generally better to create a new file per style scope.
12
+ *
13
+ *= require_tree .
14
+ *= require_self
15
+ */
16
+
17
+ /* select container */
18
+ .select-container {
19
+ position: relative;
20
+ }
21
+
22
+ .select-dropdown {
23
+ position: absolute;
24
+ top: 100%;
25
+ left: 0;
26
+ right: 0;
27
+ background: var(--surface-container);
28
+ border: 1px solid var(--outline);
29
+ border-radius: var(--small);
30
+ max-height: 200px;
31
+ overflow-y: auto;
32
+ z-index: 1000;
33
+ box-shadow: var(--elevation-2);
34
+ margin-top: 4px;
35
+ }
36
+
37
+ .select-dropdown.hidden {
38
+ display: none;
39
+ }
40
+
41
+ .select-dropdown.visible {
42
+ display: block;
43
+ }
44
+
45
+ .select-option {
46
+ padding: 12px 16px;
47
+ cursor: pointer;
48
+ color: var(--on-surface);
49
+ transition: background-color 0.2s;
50
+ }
51
+
52
+ .select-option:hover,
53
+ .select-option.active {
54
+ background-color: var(--surface-container-high);
55
+ }
56
+
57
+ .select-option.no-results {
58
+ color: var(--on-surface-variant);
59
+ cursor: default;
60
+ }
61
+
62
+ .select-option.no-results:hover {
63
+ background-color: transparent;
64
+ }
65
+
66
+
67
+ /* blazer/github.css */
68
+ .hljs {
69
+ display: block;
70
+ color: #333;
71
+ }
72
+
73
+ .hljs-comment,
74
+ .hljs-template_comment,
75
+ .diff .hljs-header,
76
+ .hljs-javadoc {
77
+ color: #998;
78
+ font-style: italic
79
+ }
80
+
81
+ .hljs-keyword,
82
+ .css .rule .hljs-keyword,
83
+ .hljs-winutils,
84
+ .javascript .hljs-title,
85
+ .nginx .hljs-title,
86
+ .hljs-subst,
87
+ .hljs-request,
88
+ .hljs-status {
89
+ color: #333;
90
+ font-weight: bold
91
+ }
92
+
93
+ .hljs-number,
94
+ .hljs-hexcolor,
95
+ .ruby .hljs-constant {
96
+ color: #099;
97
+ }
98
+
99
+ .hljs-string,
100
+ .hljs-tag .hljs-value,
101
+ .hljs-phpdoc,
102
+ .tex .hljs-formula {
103
+ color: #d14
104
+ }
105
+
106
+ .hljs-title,
107
+ .hljs-id,
108
+ .coffeescript .hljs-params,
109
+ .scss .hljs-preprocessor {
110
+ color: #900;
111
+ font-weight: bold
112
+ }
113
+
114
+ .javascript .hljs-title,
115
+ .lisp .hljs-title,
116
+ .clojure .hljs-title,
117
+ .hljs-subst {
118
+ font-weight: normal
119
+ }
120
+
121
+ .hljs-class .hljs-title,
122
+ .haskell .hljs-type,
123
+ .vhdl .hljs-literal,
124
+ .tex .hljs-command {
125
+ color: #458;
126
+ font-weight: bold
127
+ }
128
+
129
+ .hljs-tag,
130
+ .hljs-tag .hljs-title,
131
+ .hljs-rules .hljs-property,
132
+ .django .hljs-tag .hljs-keyword {
133
+ color: #000080;
134
+ font-weight: normal
135
+ }
136
+
137
+ .hljs-attribute,
138
+ .lisp .hljs-body {
139
+ color: #008080
140
+ }
141
+
142
+ .hljs-variable,
143
+ .hljs-regexp {
144
+ color: #009926;
145
+ font-weight: bold
146
+ }
147
+
148
+ .hljs-symbol,
149
+ .ruby .hljs-symbol .hljs-string,
150
+ .lisp .hljs-keyword,
151
+ .tex .hljs-special,
152
+ .hljs-prompt {
153
+ color: #990073
154
+ }
155
+
156
+ .hljs-built_in,
157
+ .lisp .hljs-title,
158
+ .clojure .hljs-built_in {
159
+ color: #0086b3
160
+ }
161
+
162
+ .hljs-preprocessor,
163
+ .hljs-pragma,
164
+ .hljs-pi,
165
+ .hljs-doctype,
166
+ .hljs-shebang,
167
+ .hljs-cdata {
168
+ color: #999;
169
+ font-weight: bold
170
+ }
171
+
172
+ .hljs-deletion {
173
+ background: #fdd
174
+ }
175
+
176
+ .hljs-addition {
177
+ background: #dfd
178
+ }
179
+
180
+ .diff .hljs-change {
181
+ background: #0086b3
182
+ }
183
+
184
+ .hljs-chunk {
185
+ color: #aaa
186
+ }
187
+
188
+ /* Dark mode styles for syntax highlighting */
189
+ body.dark .hljs {
190
+ display: block;
191
+ color: #e6e6e6;
192
+ }
193
+
194
+ body.dark .hljs-comment,
195
+ body.dark .hljs-template_comment,
196
+ body.dark .diff .hljs-header,
197
+ body.dark .hljs-javadoc {
198
+ color: #8b8b8b;
199
+ font-style: italic;
200
+ }
201
+
202
+ body.dark .hljs-keyword,
203
+ body.dark .css .rule .hljs-keyword,
204
+ body.dark .hljs-winutils,
205
+ body.dark .javascript .hljs-title,
206
+ body.dark .nginx .hljs-title,
207
+ body.dark .hljs-subst,
208
+ body.dark .hljs-request,
209
+ body.dark .hljs-status {
210
+ color: #e6e6e6;
211
+ font-weight: bold;
212
+ }
213
+
214
+ body.dark .hljs-number,
215
+ body.dark .hljs-hexcolor,
216
+ body.dark .ruby .hljs-constant {
217
+ color: #6dd3ce;
218
+ }
219
+
220
+ body.dark .hljs-string,
221
+ body.dark .hljs-tag .hljs-value,
222
+ body.dark .hljs-phpdoc,
223
+ body.dark .tex .hljs-formula {
224
+ color: #87d068;
225
+ }
226
+
227
+ body.dark .hljs-title,
228
+ body.dark .hljs-id,
229
+ body.dark .coffeescript .hljs-params,
230
+ body.dark .scss .hljs-preprocessor {
231
+ color: #ffa657;
232
+ font-weight: bold;
233
+ }
234
+
235
+ body.dark .javascript .hljs-title,
236
+ body.dark .lisp .hljs-title,
237
+ body.dark .clojure .hljs-title,
238
+ body.dark .hljs-subst {
239
+ font-weight: normal;
240
+ }
241
+
242
+ body.dark .hljs-class .hljs-title,
243
+ body.dark .haskell .hljs-type,
244
+ body.dark .vhdl .hljs-literal,
245
+ body.dark .tex .hljs-command {
246
+ color: #61afef;
247
+ font-weight: bold;
248
+ }
249
+
250
+ body.dark .hljs-tag,
251
+ body.dark .hljs-tag .hljs-title,
252
+ body.dark .hljs-rules .hljs-property,
253
+ body.dark .django .hljs-tag .hljs-keyword {
254
+ color: #e06c75;
255
+ font-weight: normal;
256
+ }
257
+
258
+ body.dark .hljs-attribute,
259
+ body.dark .lisp .hljs-body {
260
+ color: #56b6c2;
261
+ }
262
+
263
+ body.dark .hljs-variable,
264
+ body.dark .hljs-regexp {
265
+ color: #98c379;
266
+ font-weight: bold;
267
+ }
268
+
269
+ body.dark .hljs-symbol,
270
+ body.dark .ruby .hljs-symbol .hljs-string,
271
+ body.dark .lisp .hljs-keyword,
272
+ body.dark .tex .hljs-special,
273
+ body.dark .hljs-prompt {
274
+ color: #c678dd;
275
+ }
276
+
277
+ body.dark .hljs-built_in,
278
+ body.dark .lisp .hljs-title,
279
+ body.dark .clojure .hljs-built_in {
280
+ color: #61afef;
281
+ }
282
+
283
+ body.dark .hljs-preprocessor,
284
+ body.dark .hljs-pragma,
285
+ body.dark .hljs-pi,
286
+ body.dark .hljs-doctype,
287
+ body.dark .hljs-shebang,
288
+ body.dark .hljs-cdata {
289
+ color: #7c7c7c;
290
+ font-weight: bold;
291
+ }
292
+
293
+ body.dark .hljs-deletion {
294
+ background: #5c2626;
295
+ }
296
+
297
+ body.dark .hljs-addition {
298
+ background: #2b5c2b;
299
+ }
300
+
301
+ body.dark .diff .hljs-change {
302
+ background: #1e4f6b;
303
+ }
304
+
305
+ body.dark .hljs-chunk {
306
+ color: #848484;
307
+ }
308
+
@@ -0,0 +1,5 @@
1
+ module Sage
2
+ class ActionsController < ApplicationController
3
+ def close_overlay; end
4
+ end
5
+ end
@@ -0,0 +1,4 @@
1
+ module Sage
2
+ class ApplicationController < ActionController::Base
3
+ end
4
+ end
@@ -0,0 +1,10 @@
1
+ module Sage
2
+ class BaseController < Blazer::BaseController
3
+ include Pagy::Backend
4
+
5
+ layout "sage/application"
6
+ helper Importmap::ImportmapTagsHelper
7
+ helper Ransack::Helpers::FormHelper
8
+ helper Sage::ApplicationHelper
9
+ end
10
+ end
@@ -0,0 +1,65 @@
1
+ module Sage
2
+ class ChecksController < BaseController
3
+ before_action :set_check, only: [ :edit, :update, :destroy, :run ]
4
+
5
+ def index
6
+ @q = Blazer::Check.ransack(params[:q])
7
+ @checks = @q.result.joins(:query).includes(:query)
8
+
9
+ # Apply basic ordering first
10
+ @checks = @checks.order("blazer_queries.name, blazer_checks.id")
11
+
12
+ # Apply pagination with Pagy
13
+ @pagy, @checks = pagy(@checks)
14
+
15
+ # Apply state-based sorting on the paginated results
16
+ state_order = [ nil, "disabled", "error", "timed out", "failing", "passing" ]
17
+ @checks = @checks.sort_by { |q| state_order.index(q.state) || 99 }
18
+ end
19
+
20
+ def new
21
+ @check = Blazer::Check.new(query_id: params[:query_id])
22
+ end
23
+
24
+ def create
25
+ @check = Blazer::Check.new(check_params)
26
+ # use creator_id instead of creator
27
+ # since we setup association without checking if column exists
28
+ @check.creator = blazer_user if @check.respond_to?(:creator_id=) && blazer_user
29
+
30
+ if @check.save
31
+ redirect_to query_path(@check.query)
32
+ else
33
+ render_errors @check
34
+ end
35
+ end
36
+
37
+ def update
38
+ if @check.update(check_params)
39
+ redirect_to query_path(@check.query)
40
+ else
41
+ render_errors @check
42
+ end
43
+ end
44
+
45
+ def destroy
46
+ @check.destroy
47
+ redirect_to checks_path
48
+ end
49
+
50
+ def run
51
+ @query = @check.query
52
+ redirect_to query_path(@query)
53
+ end
54
+
55
+ private
56
+
57
+ def check_params
58
+ params.require(:check).permit(:query_id, :emails, :slack_channels, :invert, :check_type, :schedule)
59
+ end
60
+
61
+ def set_check
62
+ @check = Blazer::Check.find(params[:id])
63
+ end
64
+ end
65
+ end