blazer 1.6.1 → 1.6.2

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of blazer might be problematic. Click here for more details.

checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 2c31bc7010e3093044d6a2983172c97ed52f3009
4
- data.tar.gz: a40086b84c9964fec00038bd4c4a2b2128ee7bf7
3
+ metadata.gz: 72563e8886e9e04823f21ad5e7ca11f5cc112b81
4
+ data.tar.gz: 51c6ed0f30b9eafc05a77aa3dff44ae44f2092e9
5
5
  SHA512:
6
- metadata.gz: 51b03a70d9acc454fe316edaacd87b102e3d832e420190037d674fe67d675e1a569c6a8055d7ee4c5be6237c53c2143f153bee6b5a31711567e2554f7c0cbfaa
7
- data.tar.gz: 814bd7bd06a5d42ba29504a4d1511890df47d9ae89076b2580532211f5aa9b54f59b0cc2d9b98a4d72fac17ce538e66d3e2d09d78858f48c45ae43e4ab45a446
6
+ metadata.gz: 50a816c915b87433037a0eff3e7477708a6c99e8197638a1a5f1a04865d59a70ce45feab5dfdefe864012e15835d556145fe0ae05ce3f4c903c23b068aafb04b
7
+ data.tar.gz: 0fc76958a41ebd094be658cd429ecb2c3984e67d152c067540fb921c3b93e6790c3c56b8e7eda1d2753c18a05d9b37658383ab9b2daa6c3cc81597125b79e16a
@@ -1,3 +1,10 @@
1
+ ## 1.6.2
2
+
3
+ - Added basic query permissions
4
+ - Added ability to use arrays and hashes for smart variables
5
+ - Added cancel button for queries
6
+ - Added `lat` and `lng` as map keys
7
+
1
8
  ## 1.6.1
2
9
 
3
10
  - Added support for Presto [beta]
data/README.md CHANGED
@@ -191,6 +191,14 @@ smart_variables:
191
191
 
192
192
  The first column is the value of the variable, and the second column is the label.
193
193
 
194
+ You can also use an array or hash for static data and enums.
195
+
196
+ ```yml
197
+ smart_variables:
198
+ period: ["day", "week", "month"]
199
+ status: {0: "Active", 1: "Archived"}
200
+ ```
201
+
194
202
  ### Linked Columns
195
203
 
196
204
  [Example](https://blazerme.herokuapp.com/queries/3-linked-column) - title column
@@ -280,7 +288,7 @@ SELECT gender, zip_code, COUNT(*) FROM users GROUP BY 1, 2
280
288
 
281
289
  ### Maps
282
290
 
283
- Columns named `latitude` and `longitude` or `lat` and `lon` - [Example](https://blazerme.herokuapp.com/queries/15-map)
291
+ Columns named `latitude` and `longitude` or `lat` and `lon` or `lat` and `lng` - [Example](https://blazerme.herokuapp.com/queries/15-map)
284
292
 
285
293
  ```sql
286
294
  SELECT name, latitude, longitude FROM cities
@@ -420,6 +428,14 @@ data_sources:
420
428
  url: presto://user@hostname:8080/catalog
421
429
  ```
422
430
 
431
+ ## Permissions [master]
432
+
433
+ Blazer supports a simple permissions model.
434
+
435
+ 1. Queries without a name are unlisted
436
+ 2. Queries whose name starts with `#` are only listed to the creator
437
+ 3. Queries whose name starts with `*` can only be edited by the creator
438
+
423
439
  ## Learn SQL
424
440
 
425
441
  Have team members who want to learn SQL? Here are a few great, free resources.
@@ -25,8 +25,16 @@ $( function () {
25
25
  });
26
26
  });
27
27
 
28
- function runQuery(data, success, error) {
29
- return $.ajax({
28
+ function cancelQuery(runningQuery) {
29
+ runningQuery.canceled = true;
30
+ var xhr = runningQuery.xhr;
31
+ if (xhr) {
32
+ xhr.abort();
33
+ }
34
+ }
35
+
36
+ function runQuery(data, success, error, runningQuery) {
37
+ var xhr = $.ajax({
30
38
  url: window.runQueriesPath,
31
39
  method: "POST",
32
40
  data: data,
@@ -36,7 +44,9 @@ function runQuery(data, success, error) {
36
44
  var response = $.parseJSON(d);
37
45
  data.blazer = response;
38
46
  setTimeout( function () {
39
- runQuery(data, success, error);
47
+ if (!(runningQuery && runningQuery.canceled)) {
48
+ runQuery(data, success, error, runningQuery);
49
+ }
40
50
  }, 1000);
41
51
  } else {
42
52
  success(d);
@@ -45,6 +55,22 @@ function runQuery(data, success, error) {
45
55
  var message = (typeof errorThrown === "string") ? errorThrown : errorThrown.message;
46
56
  error(message);
47
57
  });
58
+ if (runningQuery) {
59
+ runningQuery.xhr = xhr;
60
+ }
61
+ return xhr;
62
+ }
63
+
64
+ function submitIfCompleted($form) {
65
+ var completed = true;
66
+ $form.find("input[name], select").each( function () {
67
+ if ($(this).val() == "") {
68
+ completed = false;
69
+ }
70
+ });
71
+ if (completed) {
72
+ $form.submit();
73
+ }
48
74
  }
49
75
 
50
76
  // Prevent backspace from navigating backwards.
@@ -55,6 +55,10 @@ input.search:focus {
55
55
  text-align: left;
56
56
  }
57
57
 
58
+ #statement-box .selectize-control {
59
+ margin-right: 0;
60
+ }
61
+
58
62
  #editor-container {
59
63
  background-color: #141414;
60
64
  padding-top: 10px;
@@ -133,3 +137,31 @@ input.search:focus {
133
137
  .check-state.disabled {
134
138
  color: #000;
135
139
  }
140
+
141
+ .topbar {
142
+ position: fixed;
143
+ top: 0;
144
+ left: 0;
145
+ right: 0;
146
+ background-color: whitesmoke;
147
+ height: 60px;
148
+ z-index: 1001;
149
+ }
150
+
151
+ .glyphicon-remove {
152
+ cursor: pointer;
153
+ color: #d9534f;
154
+ display: none;
155
+ }
156
+
157
+ .list-group li:hover .glyphicon-remove {
158
+ display: inline;
159
+ }
160
+
161
+ .list-group {
162
+ cursor: move;
163
+ }
164
+
165
+ #header a.active {
166
+ color: #999;
167
+ }
@@ -50,6 +50,20 @@ module Blazer
50
50
  end
51
51
  end
52
52
 
53
+ def parse_smart_variables(var, data_source)
54
+ query = data_source.smart_variables[var]
55
+ if query.is_a? Hash
56
+ smart_var = query.map { |k,v| [v, k] }
57
+ elsif query.is_a? Array
58
+ smart_var = query.map { |v| [v, v] }
59
+ elsif query
60
+ result = data_source.run_statement(query)
61
+ smart_var = result.rows.map { |v| v.reverse }
62
+ error = result.error if result.error
63
+ end
64
+ return smart_var, error
65
+ end
66
+
53
67
  def extract_vars(statement)
54
68
  # strip commented out lines
55
69
  # and regex {1} or {1,2}
@@ -35,12 +35,9 @@ module Blazer
35
35
  @data_sources = @queries.map { |q| Blazer.data_sources[q.data_source] }.uniq
36
36
  @bind_vars.each do |var|
37
37
  @data_sources.each do |data_source|
38
- query = data_source.smart_variables[var]
39
- if query
40
- result = data_source.run_statement(query)
41
- ((@smart_vars[var] ||= []).concat(result.rows.map { |v| v.reverse })).uniq!
42
- @sql_errors << result.error if result.error
43
- end
38
+ smart_var, error = parse_smart_variables(var, data_source)
39
+ ((@smart_vars[var] ||= []).concat(smart_var)).uniq! if smart_var
40
+ @sql_errors << error if error
44
41
  end
45
42
  end
46
43
  end
@@ -7,6 +7,9 @@ module Blazer
7
7
 
8
8
  @dashboards = Blazer::Dashboard.order(:name)
9
9
  @dashboards = @dashboards.includes(:creator) if Blazer.user_class
10
+ if params[:filter] == "mine"
11
+ @dashboards = [] # TODO show my dashboards
12
+ end
10
13
  @dashboards =
11
14
  @dashboards.map do |d|
12
15
  {
@@ -52,12 +55,9 @@ module Blazer
52
55
  @sql_errors = []
53
56
  data_source = Blazer.data_sources[@query.data_source]
54
57
  @bind_vars.each do |var|
55
- query = data_source.smart_variables[var]
56
- if query
57
- result = data_source.run_statement(query)
58
- @smart_vars[var] = result.rows.map { |v| v.reverse }
59
- @sql_errors << result.error if result.error
60
- end
58
+ smart_var, error = parse_smart_variables(var, data_source)
59
+ @smart_vars[var] = smart_var if smart_var
60
+ @sql_errors << error if error
61
61
  end
62
62
 
63
63
  Blazer.transform_statement.call(data_source, @statement) if Blazer.transform_statement
@@ -170,6 +170,10 @@ module Blazer
170
170
  render partial: "tables", layout: false
171
171
  end
172
172
 
173
+ def schema
174
+ @schema = Blazer.data_sources[params[:data_source]].schema
175
+ end
176
+
173
177
  private
174
178
 
175
179
  def continue_run
@@ -204,7 +208,7 @@ module Blazer
204
208
  @linked_columns = @data_source.linked_columns
205
209
 
206
210
  @markers = []
207
- [["latitude", "longitude"], ["lat", "lon"]].each do |keys|
211
+ [["latitude", "longitude"], ["lat", "lon"], ["lat", "lng"]].each do |keys|
208
212
  lat_index = @columns.index(keys.first)
209
213
  lon_index = @columns.index(keys.last)
210
214
  if lat_index && lon_index
@@ -233,7 +237,7 @@ module Blazer
233
237
 
234
238
  def set_queries(limit = nil)
235
239
  @my_queries =
236
- if limit && blazer_user
240
+ if limit && blazer_user && !params[:filter]
237
241
  favorite_query_ids = Blazer::Audit.where(user_id: blazer_user.id).where("created_at > ?", 30.days.ago).where("query_id IS NOT NULL").group(:query_id).order("count_all desc").count.keys
238
242
  queries = Blazer::Query.named.where(id: favorite_query_ids)
239
243
  queries = queries.includes(:creator) if Blazer.user_class
@@ -244,12 +248,21 @@ module Blazer
244
248
  end
245
249
 
246
250
  @queries = Blazer::Query.named.order(:name)
251
+ if params[:filter] == "mine"
252
+ @queries = @queries.where(creator_id: blazer_user.try(:id)).reorder(updated_at: :desc)
253
+ limit = nil
254
+ end
247
255
  @queries = @queries.where("id NOT IN (?)", @my_queries.map(&:id)) if @my_queries.any?
248
256
  @queries = @queries.includes(:creator) if Blazer.user_class
249
257
  @queries = @queries.limit(limit) if limit
258
+ @queries = @queries.to_a
259
+
260
+ @more = limit && @queries.size >= limit
261
+
262
+ @queries = (@my_queries + @queries).select { |q| !q.name.to_s.start_with?("#") || q.try(:creator).try(:id) == blazer_user.try(:id) }
250
263
 
251
264
  @queries =
252
- (@my_queries + @queries).map do |q|
265
+ @queries.map do |q|
253
266
  {
254
267
  id: q.id,
255
268
  name: view_context.link_to(q.name, q),
@@ -19,7 +19,7 @@ module Blazer
19
19
  end
20
20
 
21
21
  def editable?(user)
22
- (name.present? && name.first != "*") || user == creator
22
+ !persisted? || (name.present? && name.first != "*" && name.first != "#") || user == creator
23
23
  end
24
24
  end
25
25
  end
@@ -2,22 +2,6 @@
2
2
  <div class="alert alert-danger"><%= @dashboard.errors.full_messages.first %></div>
3
3
  <% end %>
4
4
 
5
- <style>
6
- .glyphicon-remove {
7
- cursor: pointer;
8
- color: #d9534f;
9
- display: none;
10
- }
11
-
12
- li:hover .glyphicon-remove {
13
- display: inline;
14
- }
15
-
16
- .list-group {
17
- cursor: move;
18
- }
19
- </style>
20
-
21
5
  <%= form_for @dashboard, url: (@dashboard.persisted? ? dashboard_path(@dashboard, variable_params) : dashboards_path(variable_params)) do |f| %>
22
6
  <div class="form-group">
23
7
  <%= f.label :name %>
@@ -1,20 +1,6 @@
1
1
  <% blazer_title @dashboard.name %>
2
2
 
3
- <script>
4
- function submitIfCompleted($form) {
5
- var completed = true;
6
- $form.find("input[name], select").each( function () {
7
- if ($(this).val() == "") {
8
- completed = false;
9
- }
10
- });
11
- if (completed) {
12
- $form.submit();
13
- }
14
- }
15
- </script>
16
-
17
- <div style="position: fixed; top: 0; left: 0; right: 0; background-color: whitesmoke; height: 60px; z-index: 1001;">
3
+ <div class="topbar">
18
4
  <div class="container">
19
5
  <div class="row" style="padding-top: 13px;">
20
6
  <div class="col-sm-9">
@@ -4,7 +4,7 @@
4
4
 
5
5
  <%= form_for @query, url: (@query.persisted? ? query_path(@query, variable_params) : queries_path(variable_params)), html: {class: "the_form", autocomplete: "off"} do |f| %>
6
6
  <div class="row">
7
- <div class="col-xs-8">
7
+ <div id="statement-box" class="col-xs-8">
8
8
  <div class= "form-group">
9
9
  <%= f.hidden_field :statement %>
10
10
  <div id="editor-container">
@@ -16,7 +16,7 @@
16
16
  <%= link_to "Back", :back %>
17
17
  </div>
18
18
  <%= f.select :data_source, Blazer.data_sources.values.map { |ds| [ds.name, ds.id] }, {}, class: ("hide" if Blazer.data_sources.size == 1), style: "width: 140px;" %>
19
- <div id="tables" style="display: inline-block; width: 260px; margin-right: 10px;" class="hide">
19
+ <div id="tables" style="display: inline-block; width: 250px; margin-right: 10px;" class="hide">
20
20
  <%= render partial: "tables" %>
21
21
  </div>
22
22
  <script>
@@ -26,7 +26,8 @@
26
26
  updatePreviewSelect();
27
27
  $("#query_data_source").selectize().change(updatePreviewSelect);
28
28
  </script>
29
- <%= link_to "Run", "#", class: "btn btn-info", id: "run", style: "vertical-align: top;" %>
29
+ <%= link_to "Run", "#", class: "btn btn-info", id: "run", style: "vertical-align: top; width: 70px;" %>
30
+ <%= link_to "Cancel", "#", class: "btn btn-danger hide", id: "cancel", style: "vertical-align: top; width: 70px;" %>
30
31
  </div>
31
32
  </div>
32
33
  <div class="col-xs-4">
@@ -124,26 +125,45 @@
124
125
  editor.focus();
125
126
 
126
127
  var error_line = null;
127
- var xhr;
128
+ var runningQuery;
128
129
  var params = <%= raw blazer_json_escape(variable_params.to_json) %>;
129
130
  var previewStatement = <%= raw blazer_json_escape(Hash[Blazer.data_sources.map { |k, v| [k, v.preview_statement] }].to_json) %>;
130
131
 
131
- $("#run").click(function (e) {
132
+
133
+ function queryDone() {
134
+ $("#run").removeClass("hide")
135
+ $("#cancel").addClass("hide")
136
+ }
137
+
138
+ $("#cancel").click( function (e) {
139
+ e.preventDefault()
140
+
141
+ cancelQuery(runningQuery)
142
+ queryDone()
143
+
144
+ $("#results").html("")
145
+ })
146
+
147
+ $("#run").click( function (e) {
132
148
  e.preventDefault();
133
149
 
150
+ $(this).addClass("hide")
151
+ $("#cancel").removeClass("hide")
152
+
134
153
  if (error_line) {
135
154
  editor.getSession().removeGutterDecoration(error_line - 1, "error");
136
155
  error_line = null;
137
156
  }
138
157
 
139
158
  $("#results").html('<p class="text-muted">Loading...</p>');
140
- if (xhr) {
141
- xhr.abort();
142
- }
143
159
 
144
160
  var data = $.extend({}, params, {statement: getSQL(), data_source: $("#query_data_source").val()});
145
161
 
146
- xhr = runQuery(data, function (data) {
162
+ runningQuery = {};
163
+
164
+ runQuery(data, function (data) {
165
+ queryDone()
166
+
147
167
  $("#results").html(data);
148
168
 
149
169
  error_line = getErrorLine();
@@ -153,18 +173,17 @@
153
173
  editor.gotoLine(error_line, 0, true);
154
174
  editor.focus();
155
175
  }
156
- });
176
+ }, function (data) {
177
+ // TODO show error
178
+ queryDone()
179
+ }, runningQuery);
157
180
  });
158
181
 
159
- if ($("#query_statement").val() != "") {
160
- $("#run").click();
161
- }
162
-
163
182
  $(document).on("change", "#table_names", function () {
164
183
  var val = $(this).val();
165
184
  if (val.length > 0) {
166
185
  var dataSource = $("#query_data_source").val();
167
- editor.setValue(previewStatement[dataSource].replace("{table}", val));
186
+ editor.setValue(previewStatement[dataSource].replace("{table}", val), 1);
168
187
  $("#run").click();
169
188
  }
170
189
  });
@@ -1,18 +1,22 @@
1
1
  <div id="queries">
2
2
  <div id="header" style="margin-bottom: 20px;">
3
- <div class="btn-group pull-right">
4
- <%= link_to "New Query", new_query_path, class: "btn btn-info" %>
5
- <button type="button" class="btn btn-info dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
6
- <span class="caret"></span>
7
- <span class="sr-only">Toggle Dropdown</span>
8
- </button>
9
- <ul class="dropdown-menu">
10
- <li><%= link_to "Dashboards", dashboards_path %></li>
11
- <li><%= link_to "Checks", checks_path %></li>
12
- <li role="separator" class="divider"></li>
13
- <li><%= link_to "New Dashboard", new_dashboard_path %></li>
14
- <li><%= link_to "New Check", new_check_path %></li>
15
- </ul>
3
+ <div class="pull-right">
4
+ <%= link_to "All", root_path, class: params[:filter] != "mine" ? "active" : nil, style: "margin-right: 40px;" %>
5
+ <%= link_to "Mine", root_path(filter: "mine"), class: params[:filter] == "mine" ? "active" : nil, style: "margin-right: 40px;" %>
6
+ <div class="btn-group">
7
+ <%= link_to "New Query", new_query_path, class: "btn btn-info" %>
8
+ <button type="button" class="btn btn-info dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
9
+ <span class="caret"></span>
10
+ <span class="sr-only">Toggle Dropdown</span>
11
+ </button>
12
+ <ul class="dropdown-menu">
13
+ <li><%= link_to "Dashboards", dashboards_path %></li>
14
+ <li><%= link_to "Checks", checks_path %></li>
15
+ <li role="separator" class="divider"></li>
16
+ <li><%= link_to "New Dashboard", new_dashboard_path %></li>
17
+ <li><%= link_to "New Check", new_check_path %></li>
18
+ </ul>
19
+ </div>
16
20
  </div>
17
21
  <input type="text" placeholder="Start typing a query or person" style="width: 300px; display: inline-block;" autofocus=true class="search form-control" />
18
22
  </div>
@@ -55,7 +59,7 @@
55
59
  }
56
60
  </script>
57
61
 
58
- <% if @queries.size >= 200 %>
62
+ <% if @more %>
59
63
  <p id="loading" class="text-muted">Loading...</p>
60
64
  <script>
61
65
  $.getJSON("<%= queries_path %>", function (data) {
@@ -0,0 +1,18 @@
1
+ <% @schema.each do |table| %>
2
+ <h4>
3
+ <%= table[:table] %>
4
+ <% if table[:schema] != "public" %>
5
+ <small><%= table[:schema] %></small>
6
+ <% end %>
7
+ </h4>
8
+ <table class="table" style="max-width: 500px;">
9
+ <tbody>
10
+ <% table[:columns].each do |column| %>
11
+ <tr>
12
+ <td style="width: 60%;"><%= column[:name] %></td>
13
+ <td class="text-muted"><%= column[:data_type] %></td>
14
+ </tr>
15
+ <% end %>
16
+ </tbody>
17
+ </table>
18
+ <% end %>
@@ -1,20 +1,6 @@
1
1
  <% blazer_title @query.name %>
2
2
 
3
- <script>
4
- function submitIfCompleted($form) {
5
- var completed = true;
6
- $form.find("input[name], select").each( function () {
7
- if ($(this).val() == "") {
8
- completed = false;
9
- }
10
- });
11
- if (completed) {
12
- $form.submit();
13
- }
14
- }
15
- </script>
16
-
17
- <div style="position: fixed; top: 0; left: 0; right: 0; background-color: whitesmoke; height: 60px; z-index: 1001;">
3
+ <div class="topbar">
18
4
  <div class="container">
19
5
  <div class="row" style="padding-top: 13px;">
20
6
  <div class="col-sm-9">
@@ -3,6 +3,7 @@ Blazer::Engine.routes.draw do
3
3
  post :run, on: :collection # err on the side of caution
4
4
  post :refresh, on: :member
5
5
  get :tables, on: :collection
6
+ get :schema, on: :collection
6
7
  end
7
8
  resources :checks, except: [:show] do
8
9
  get :run, on: :member
@@ -15,6 +15,10 @@ module Blazer
15
15
  [] # optional, but nice to have
16
16
  end
17
17
 
18
+ def schema
19
+ [] # optional, but nice to have
20
+ end
21
+
18
22
  def preview_statement
19
23
  "" # also optional, but nice to have
20
24
  end
@@ -28,7 +28,7 @@ module Blazer
28
28
  protected
29
29
 
30
30
  def client
31
- @client ||= Mongo::Client.new(settings["url"])
31
+ @client ||= Mongo::Client.new(settings["url"], connect_timeout: 1, socket_timeout: 1, server_selection_timeout: 1)
32
32
  end
33
33
 
34
34
  def db
@@ -44,6 +44,11 @@ module Blazer
44
44
  result.rows.map(&:first)
45
45
  end
46
46
 
47
+ def schema
48
+ result = data_source.run_statement(connection_model.send(:sanitize_sql_array, ["SELECT table_schema, table_name, column_name, data_type, ordinal_position FROM information_schema.columns WHERE table_schema IN (?) ORDER BY 1, 2", schemas]))
49
+ result.rows.group_by { |r| [r[0], r[1]] }.map { |k, vs| {schema: k[0], table: k[1], columns: vs.sort_by { |v| v[4].to_i }.map { |v| {name: v[2], data_type: v[3]} }} }
50
+ end
51
+
47
52
  def preview_statement
48
53
  "SELECT * FROM {table} LIMIT 10"
49
54
  end
@@ -1,3 +1,3 @@
1
1
  module Blazer
2
- VERSION = "1.6.1"
2
+ VERSION = "1.6.2"
3
3
  end
@@ -23,6 +23,8 @@ data_sources:
23
23
 
24
24
  smart_variables:
25
25
  # zone_id: "SELECT id, name FROM zones ORDER BY name ASC"
26
+ # period: ["day", "week", "month"]
27
+ # status: {0: "Active", 1: "Archived"}
26
28
 
27
29
  linked_columns:
28
30
  # user_id: "/admin/users/{value}"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: blazer
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.6.1
4
+ version: 1.6.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrew Kane
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-07-30 00:00:00.000000000 Z
11
+ date: 2016-08-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -154,6 +154,7 @@ files:
154
154
  - app/views/blazer/queries/home.html.erb
155
155
  - app/views/blazer/queries/new.html.erb
156
156
  - app/views/blazer/queries/run.html.erb
157
+ - app/views/blazer/queries/schema.html.erb
157
158
  - app/views/blazer/queries/show.html.erb
158
159
  - app/views/layouts/blazer/application.html.erb
159
160
  - blazer.gemspec