sqlui 0.1.51 → 0.1.53

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1726ad80d8643d99cff2dd4dcf330d35cd109b8277bc6aaa09ae265a66b6018f
4
- data.tar.gz: 9b61be584f56fb65d3239566d79bab94c6a45352201555c853ab726e682c0e86
3
+ metadata.gz: aecad7b8b4d0000efae09f66acce234f8d0617e83103c2d21a7e19c2b14f4de0
4
+ data.tar.gz: 0e35efaf9d1116e7d758dee9a409343654963608ec14e2f5f9714866db85365f
5
5
  SHA512:
6
- metadata.gz: 57ea926197ec2949eea9af1ed9a55f0ddee7f3985fbfd8515d0401bbe4404a060f6514cbe71ac0f808aec1fc4d4670f3bd80029eda03b4f7a4bf88806f443225
7
- data.tar.gz: 850c957b833956feb133189cfb1ff0dbffbfa8d31e1ef7065038f26b7cd4a3f3035cc47dd122c344746a130b4bf4e6f6f40a4e24ff7a88a96d8a4c3d4f006475
6
+ metadata.gz: b8871b41338fff2bec4a4130d783e6fed8d5a9ee80e2b2786f48740ca2ed5d17887a37df2b9c0e3cc8c9a4b2bb13dcc3d74ca3a3f54b5f318f20f9d98df92f6e
7
+ data.tar.gz: 1bf1575c392641fb2d3710a093e467382ab69c8656315ec801c837e0afdde5a36c0e0a8134f271c0f38a9249f92156255d5f7ddfb3dd56685b26a20ea0fed791
data/.release-version ADDED
@@ -0,0 +1 @@
1
+ 0.1.53
data/app/server.rb CHANGED
@@ -4,12 +4,16 @@ require 'base64'
4
4
  require 'csv'
5
5
  require 'erb'
6
6
  require 'json'
7
+ require 'prometheus/middleware/collector'
8
+ require 'prometheus/middleware/exporter'
7
9
  require 'sinatra/base'
8
10
  require 'uri'
11
+ require 'webrick'
9
12
  require_relative 'database_metadata'
10
13
  require_relative 'mysql_types'
11
14
  require_relative 'sql_parser'
12
15
  require_relative 'sqlui'
16
+ require_relative 'version'
13
17
 
14
18
  # SQLUI Sinatra server.
15
19
  class Server < Sinatra::Base
@@ -18,6 +22,30 @@ class Server < Sinatra::Base
18
22
  end
19
23
 
20
24
  def self.init_and_run(config, resources_dir)
25
+ logger.info("Starting SQLUI v#{Version::SQLUI}")
26
+ logger.info("Airbrake enabled: #{config.airbrake[:server]&.[](:enabled) || false}")
27
+ if config.airbrake[:server]&.[](:enabled)
28
+ require 'airbrake'
29
+ require 'airbrake/rack'
30
+
31
+ Airbrake.configure do |c|
32
+ c.app_version = Version::SQLUI
33
+ c.environment = config.environment
34
+ c.logger.level = Logger::DEBUG if config.environment != :production?
35
+ config.airbrake[:server].each do |key, value|
36
+ c.send("#{key}=".to_sym, value) unless key == :enabled
37
+ end
38
+ end
39
+ Airbrake.add_filter(Airbrake::Rack::RequestBodyFilter.new)
40
+ Airbrake.add_filter(Airbrake::Rack::HttpParamsFilter.new)
41
+ Airbrake.add_filter(Airbrake::Rack::HttpHeadersFilter.new)
42
+ use Airbrake::Rack::Middleware
43
+ end
44
+
45
+ use Rack::Deflater
46
+ use Prometheus::Middleware::Collector
47
+ use Prometheus::Middleware::Exporter
48
+
21
49
  Mysql2::Client.default_query_options[:as] = :array
22
50
  Mysql2::Client.default_query_options[:cast_booleans] = true
23
51
  Mysql2::Client.default_query_options[:database_timezone] = :utc
@@ -53,17 +81,13 @@ class Server < Sinatra::Base
53
81
  end
54
82
 
55
83
  get "#{database.url_path}/sqlui.css" do
56
- @css ||= File.read(File.join(resources_dir, 'sqlui.css'))
57
- status 200
58
84
  headers 'Content-Type' => 'text/css; charset=utf-8'
59
- body @css
85
+ send_file File.join(resources_dir, 'sqlui.css')
60
86
  end
61
87
 
62
88
  get "#{database.url_path}/sqlui.js" do
63
- @js ||= File.read(File.join(resources_dir, 'sqlui.js'))
64
- status 200
65
89
  headers 'Content-Type' => 'text/javascript; charset=utf-8'
66
- body @js
90
+ send_file File.join(resources_dir, 'sqlui.js')
67
91
  end
68
92
 
69
93
  post "#{database.url_path}/metadata" do
@@ -99,7 +123,9 @@ class Server < Sinatra::Base
99
123
  end
100
124
 
101
125
  post "#{database.url_path}/query" do
102
- params.merge!(JSON.parse(request.body.read, symbolize_names: true))
126
+ data = request.body.read
127
+ request.body.rewind # since Airbrake will read the body on error
128
+ params.merge!(JSON.parse(data, symbolize_names: true))
103
129
  break client_error('missing sql') unless params[:sql]
104
130
 
105
131
  variables = params[:variables] || {}
@@ -111,29 +137,42 @@ class Server < Sinatra::Base
111
137
  database.with_client do |client|
112
138
  query_result = execute_query(client, variables, sql)
113
139
  stream do |out|
114
- json = <<~RES.chomp
115
- {
116
- "columns": #{query_result.fields.to_json},
117
- "column_types": #{MysqlTypes.map_to_google_charts_types(query_result.field_types).to_json},
118
- "total_rows": #{query_result.size.to_json},
119
- "selection": #{params[:selection].to_json},
120
- "query": #{params[:sql].to_json},
121
- "rows": [
122
- RES
123
- out << json
124
- bytes = json.bytesize
125
- query_result.each_with_index do |row, i|
126
- json = "#{i.zero? ? '' : ','}\n #{row.to_json}"
127
- bytes += json.bytesize
128
- break if i == Sqlui::MAX_ROWS || bytes > Sqlui::MAX_BYTES
129
-
140
+ if query_result
141
+ json = <<~RES.chomp
142
+ {
143
+ "columns": #{query_result.fields.to_json},
144
+ "column_types": #{MysqlTypes.map_to_google_charts_types(query_result.field_types).to_json},
145
+ "total_rows": #{query_result.size.to_json},
146
+ "selection": #{params[:selection].to_json},
147
+ "query": #{params[:sql].to_json},
148
+ "rows": [
149
+ RES
130
150
  out << json
131
- end
132
- out << <<~RES
151
+ bytes = json.bytesize
152
+ query_result.each_with_index do |row, i|
153
+ json = "#{i.zero? ? '' : ','}\n #{row.to_json}"
154
+ bytes += json.bytesize
155
+ break if i == Sqlui::MAX_ROWS || bytes > Sqlui::MAX_BYTES
156
+
157
+ out << json
158
+ end
159
+ out << <<~RES
133
160
 
134
- ]
135
- }
136
- RES
161
+ ]
162
+ }
163
+ RES
164
+ else
165
+ out << <<~RES
166
+ {
167
+ "columns": [],
168
+ "column_types": [],
169
+ "total_rows": 0,
170
+ "selection": #{params[:selection].to_json},
171
+ "query": #{params[:sql].to_json},
172
+ "rows": []
173
+ }
174
+ RES
175
+ end
137
176
  end
138
177
  end
139
178
  end
@@ -161,10 +200,14 @@ class Server < Sinatra::Base
161
200
  end
162
201
 
163
202
  get(%r{#{Regexp.escape(database.url_path)}/(query|graph|structure|saved)}) do
164
- @html ||= File.read(File.join(resources_dir, 'sqlui.html'))
165
203
  status 200
166
- headers 'Content-Type' => 'text/html; charset=utf-8'
167
- body @html
204
+ client_config = config.airbrake[:client] || {}
205
+ erb :sqlui, locals: {
206
+ environment: config.environment.to_s,
207
+ airbrake_enabled: client_config[:enabled] || false,
208
+ airbrake_project_id: client_config[:project_id] || '',
209
+ airbrake_project_key: client_config[:project_key] || ''
210
+ }
168
211
  end
169
212
  end
170
213
 
data/app/sqlui.rb CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  require_relative 'sqlui_config'
4
4
  require_relative 'server'
5
+ require_relative 'version'
5
6
 
6
7
  # Main entry point.
7
8
  class Sqlui
@@ -25,7 +26,7 @@ class Sqlui
25
26
 
26
27
  def self.from_command_line(args)
27
28
  if args.include?('-v') || args.include?('--version')
28
- puts File.read('.version')
29
+ puts Version::SQLUI
29
30
  exit
30
31
  end
31
32
 
data/app/sqlui_config.rb CHANGED
@@ -8,7 +8,7 @@ require_relative 'deep'
8
8
 
9
9
  # App config including database configs.
10
10
  class SqluiConfig
11
- attr_reader :name, :port, :environment, :list_url_path, :database_configs
11
+ attr_reader :name, :port, :environment, :list_url_path, :database_configs, :airbrake
12
12
 
13
13
  def initialize(filename, overrides = {})
14
14
  config = YAML.safe_load(ERB.new(File.read(filename)).result, aliases: true).deep_merge!(overrides)
@@ -29,6 +29,7 @@ class SqluiConfig
29
29
  @database_configs = databases.map do |_, current|
30
30
  DatabaseConfig.new(current)
31
31
  end
32
+ @airbrake = Args.fetch_optional_hash(config, :airbrake) || { enabled: false }
32
33
  end
33
34
 
34
35
  def database_config_for(url_path:)
data/app/version.rb ADDED
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Version
4
+ SQLUI = File.read(File.expand_path(File.join(__dir__, '..', '.release-version'))).strip
5
+ end
@@ -1,3 +1,4 @@
1
+ <!DOCTYPE html>
1
2
  <html lang="en">
2
3
  <head>
3
4
  <meta charset="utf-8">
data/app/views/error.erb CHANGED
@@ -1,3 +1,4 @@
1
+ <!DOCTYPE html>
1
2
  <html>
2
3
  <html lang="en">
3
4
  <head>
@@ -3,7 +3,22 @@
3
3
  <meta charset="utf-8">
4
4
  <title>SQLUI</title>
5
5
  <link rel="icon" type="image/x-icon" href="/favicon.svg">
6
- <script src="sqlui.js"></script>
6
+ <!-- Initialize Airbrake before loading the main app JS so that we can catch errors as early as possible. -->
7
+ <script type="text/javascript" src="https://cdn.jsdelivr.net/npm/@airbrake/browser"></script>
8
+ <script type="text/javascript">
9
+ <% if airbrake_enabled %>
10
+ window.airbrake = new Airbrake.Notifier({
11
+ environment: "<%= environment %>",
12
+ projectId: "<%= airbrake_project_id %>",
13
+ projectKey: "<%= airbrake_project_key %>"
14
+ })
15
+ <% end %>
16
+
17
+ window.notifyAirbrake = function (error) {
18
+ window.airbrake?.notify(error)
19
+ }
20
+ </script>
21
+ <script type="text/javascript" src="sqlui.js"></script>
7
22
  <script type="text/javascript" src="https://www.gstatic.com/charts/loader.js"></script>
8
23
  <link rel="stylesheet" href="sqlui.css">
9
24
  </head>
@@ -245,6 +245,9 @@ p {
245
245
  display: flex;
246
246
  flex-direction: column;
247
247
  }
248
+ #result-table tbody tr td{
249
+ height: calc(21px + 10px); // 21 for text, 10 for top and bottom padding of 5
250
+ }
248
251
 
249
252
  #result-table tbody tr td abbr a {
250
253
  color: #555;
@@ -23922,6 +23922,7 @@
23922
23922
  const schemas = Object.entries(metadata.schemas);
23923
23923
  const editorSchema = {};
23924
23924
  const tables = [];
23925
+ const aliases = [];
23925
23926
  schemas.forEach(([schemaName, schema]) => {
23926
23927
  Object.entries(schema.tables).forEach(([tableName, table]) => {
23927
23928
  const qualifiedTableName = schemas.length === 1 ? tableName : `${schemaName}.${tableName}`;
@@ -23929,30 +23930,48 @@
23929
23930
  const columns = Object.keys(table.columns);
23930
23931
  editorSchema[qualifiedTableName] = columns;
23931
23932
  const alias = metadata.tables[qualifiedTableName]?.alias;
23932
- const boost = metadata.tables[qualifiedTableName]?.boost;
23933
+ if (alias) {
23934
+ aliases.push(alias);
23935
+ aliases.push(`\`${alias}\``);
23936
+ }
23937
+
23938
+ let boost = metadata.tables[qualifiedTableName]?.boost;
23939
+ boost = boost ? boost * 2 : null;
23933
23940
  if (alias) {
23934
23941
  editorSchema[alias] = columns;
23942
+ // Add a completion which inserts the table name and alias for use just after join or from.
23935
23943
  tables.push({
23936
- label: qualifiedTableName,
23937
- detail: alias,
23938
- boost,
23939
- alias_type: 'with',
23944
+ label: `${qualifiedTableName} ${alias}`,
23945
+ boost: boost + 1,
23946
+ completion_types: ['table_with_alias'],
23940
23947
  quoted: `${quotedQualifiedTableName} \`${alias}\``,
23941
23948
  unquoted: `${qualifiedTableName} ${alias}`
23942
23949
  });
23950
+ // Add a completion which only inserts the alias for use with "select" or "where" or "on".
23943
23951
  tables.push({
23944
- label: qualifiedTableName,
23945
- detail: alias,
23946
- boost,
23947
- alias_type: 'only',
23952
+ label: `${qualifiedTableName} ${alias}`,
23953
+ boost: boost + 1,
23954
+ type: 'constant',
23955
+ completion_types: ['alias_only'],
23948
23956
  quoted: '`' + alias + '`',
23949
23957
  unquoted: alias
23950
23958
  });
23951
- } else {
23952
23959
  tables.push({
23953
- label: qualifiedTableName
23960
+ label: alias,
23961
+ boost: boost + 1,
23962
+ type: 'constant',
23963
+ completion_types: ['alias_only'],
23964
+ quoted: '`' + alias + '`',
23965
+ unquoted: alias
23954
23966
  });
23955
23967
  }
23968
+ tables.push({
23969
+ label: qualifiedTableName,
23970
+ boost,
23971
+ completion_types: ['table_with_alias', 'alias_only', 'table_only'],
23972
+ quoted: quotedQualifiedTableName,
23973
+ unquoted: qualifiedTableName
23974
+ });
23956
23975
  });
23957
23976
  });
23958
23977
  // I prefer to use Cmd-Enter/Ctrl-Enter to submit the query. Here I am replacing the default mapping.
@@ -23992,66 +24011,52 @@
23992
24011
  tables
23993
24012
  };
23994
24013
  const originalSchemaCompletionSource = schemaCompletionSource(sqlConfig);
23995
- const originalKeywordCompletionSource = keywordCompletionSource(MySQL, true);
23996
- const keywordCompletions = [];
24014
+
24015
+ const joinCompletions = [];
23997
24016
  metadata.joins.forEach((join) => {
23998
- ['JOIN', 'INNER JOIN', 'LEFT JOIN', 'RIGHT JOIN', 'CROSS JOIN'].forEach((type) => {
23999
- keywordCompletions.push({ label: `${type} ${join.label}`, apply: `${type} ${join.apply}`, type: 'keyword' });
24000
- });
24017
+ joinCompletions.push({ label: join.label, apply: join.apply, type: 'keyword' });
24001
24018
  });
24002
- let combinedKeywordCompletionSource;
24003
- if (keywordCompletions.length > 0) {
24004
- const customKeywordCompletionSource = ifNotIn(['QuotedIdentifier', 'SpecialVar', 'String', 'LineComment', 'BlockComment', '.'], completeFromList(keywordCompletions));
24005
- combinedKeywordCompletionSource = function (context) {
24006
- const original = originalKeywordCompletionSource(context);
24007
- const custom = customKeywordCompletionSource(context);
24008
- if (original?.options && custom?.options) {
24009
- original.options = original.options.concat(custom.options);
24010
- }
24011
- return original
24012
- };
24013
- } else {
24014
- combinedKeywordCompletionSource = originalKeywordCompletionSource;
24015
- }
24019
+ const customJoinCompletionSource = completeFromList(joinCompletions);
24020
+
24016
24021
  const sqlExtension = new LanguageSupport(
24017
24022
  MySQL.language,
24018
24023
  [
24019
24024
  MySQL.language.data.of({
24020
24025
  autocomplete: (context) => {
24021
24026
  const result = originalSchemaCompletionSource(context);
24022
- if (!result?.options) return result
24027
+ if (!result?.options || result.options.length === 0) return result
24023
24028
 
24024
24029
  const tree = syntaxTree(context.state);
24025
24030
  let node = tree.resolveInner(context.pos, -1);
24026
24031
  if (!node) return result
24027
24032
 
24028
- // We are trying to identify the case where we are autocompleting a table name after "from" or "join"
24029
-
24030
- // TODO: we don't handle the case where a user typed "select table.foo from". In that case we probably
24031
- // shouldn't autocomplete the alias. Though, if the user typed "select table.foo, t.bar", we won't know
24032
- // what to do. Maybe it is ok to force users to simply delete the alias after autocompleting.
24033
-
24034
- // TODO: if table aliases aren't enabled, we don't need to override autocomplete.
24035
-
24033
+ // We want to customize the autocomplete options based on context. For instance, after select or where, we
24034
+ // should autocomplete aliases, if they are defined, otherwise table names. After from, we should autocomplete
24035
+ // table names with aliases. Etc. We start by trying to identify the node before our current position. The
24036
+ // method for accomplishing this seems to vary based on the user's context.
24036
24037
  let foundSchema;
24037
24038
  if (node.name === 'Statement') {
24038
- // The node can be a Statement if the cursor is at the end of "from " and there is a complete
24039
- // statement in the editor (semicolon present). In that case we want to find the node just before the
24040
- // current position so that we can check whether it is "from" or "join".
24039
+ // The current node can be a Statement if the cursor is at the end of "from " (for instance) and there is a
24040
+ // complete statement in the editor (semicolon present). In that case we want to find the node just before
24041
+ // the current position.
24041
24042
  node = node.childBefore(context.pos);
24042
24043
  } else if (node.name === 'Script') {
24043
24044
  // It seems the node can sometimes be a Script if the cursor is at the end of the last statement in the
24044
24045
  // editor and the statement doesn't end in a semicolon. In that case we can find the last statement in the
24045
- // Script so that we can check whether it is "from" or "join".
24046
+ // Script.
24046
24047
  node = node.lastChild?.childBefore(context.pos);
24048
+ } else if (node.name === 'Parens') {
24049
+ // The current node can be a Parens if we are inside of a function or sub query, for instance just after a
24050
+ // space after a "select" in "select * from (select ) as foo". In that case we can find the last statement
24051
+ // in the Parens.
24052
+ node = node.childBefore(context.pos);
24047
24053
  } else if (['Identifier', 'QuotedIdentifier', 'Keyword', '.'].includes(node.name)) {
24048
24054
  // If the node is an Identifier, we might be in the middle of typing the table name. If the node is a
24049
- // Keyword but isn't "from" or "join", we might be in the middle of typing a table name that is similar
24050
- // to a Keyword, for instance "orders" or "selections" or "fromages". In these cases, look for the previous
24051
- // sibling so that we can check whether it is "from" or "join". If we found a '.' or if the previous
24052
- // sibling is a '.', we might be in the middle of typing something like "schema.table" or
24053
- // "`schema`.table" or "`schema`.`table`". In these cases we need to record the schema used so that we
24054
- // can autocomplete table names with aliases.
24055
+ // Keyword, we might be in the middle of typing a table name that is similar to a Keyword, for instance
24056
+ // "orders" or "selections" or "fromages". In these cases, look for the previous sibling. If the node is a
24057
+ // '.' or if the previous sibling is a '.', we might be in the middle of typing something like
24058
+ // "schema.table" or "`schema`.table" or "`schema`.`table`". In these cases we need to record the schema
24059
+ // used so that we can autocomplete table names with aliases.
24055
24060
  if (node.name !== '.') {
24056
24061
  node = node.prevSibling;
24057
24062
  }
@@ -24068,29 +24073,54 @@
24068
24073
  }
24069
24074
  }
24070
24075
 
24076
+ // We now have the node before the node the user is currently creating. Step back or up until we find a Keyword.
24077
+ while (true) {
24078
+ const nodeText = node ? context.state.doc.sliceString(node.from, node.to).toLowerCase() : null;
24079
+ if (!node || (node.name === 'Keyword' &&
24080
+ ['where', 'select', 'from', 'into', 'join', 'straight_join', 'database', 'as'].includes(nodeText))) {
24081
+ break
24082
+ }
24083
+
24084
+ node = node?.prevSibling || node?.parent;
24085
+ }
24071
24086
  const nodeText = node ? context.state.doc.sliceString(node.from, node.to).toLowerCase() : null;
24072
- if (node?.name === 'Keyword' && ['from', 'join'].includes(nodeText)) {
24073
- result.options = result.options.filter((option) => {
24074
- return option.alias_type === undefined || option.alias_type === 'with'
24075
- });
24076
- } else {
24077
- result.options = result.options.filter((option) => {
24078
- return option.alias_type === undefined || option.alias_type === 'only'
24079
- });
24087
+ let completionType = 'table_only';
24088
+ if (node?.name === 'Keyword') {
24089
+ if (['from', 'join', 'straight_join'].includes(nodeText)) {
24090
+ completionType = 'table_with_alias';
24091
+ } else if (['where', 'select'].includes(nodeText)) {
24092
+ completionType = 'alias_only';
24093
+ }
24094
+
24095
+ if (['join', 'straight_join'].includes(nodeText)) {
24096
+ const customJoins = customJoinCompletionSource(context);
24097
+ if (customJoins?.options) {
24098
+ result.options = result.options.concat(customJoins.options);
24099
+ }
24100
+ }
24080
24101
  }
24102
+ result.options = result.options.filter((option) => {
24103
+ if (option.completion_types === undefined && option.type === 'constant' && aliases.includes(option.label)) {
24104
+ // The default options already include an alias if the statement includes one in the from clause.
24105
+ // In that case we want to remove it in favor of our own alias option.
24106
+ return false
24107
+ }
24108
+ // Allow any options we didn't create plus options we created which are of the expected type.
24109
+ return option.completion_types === undefined || option.completion_types.includes(completionType)
24110
+ });
24081
24111
  result.options = result.options.map((option) => {
24082
24112
  // Some shenanigans. If the default autocomplete function quoted the label, we want to ensure the quote
24083
24113
  // only applies to the table name and not the alias. You might think we could do this by overriding the
24084
24114
  // apply function but apply is set to null when quoting.
24085
24115
  // See https://github.com/codemirror/lang-sql/blob/ebf115fffdbe07f91465ccbd82868c587f8182bc/src/complete.ts#L90
24086
- if (option.alias_type) {
24116
+ if (option.quoted) {
24087
24117
  if (option.label.match(/^`.*`$/)) {
24088
24118
  option.apply = option.quoted;
24089
24119
  } else {
24090
24120
  option.apply = option.unquoted;
24091
24121
  }
24092
24122
  }
24093
- if (foundSchema) {
24123
+ if (foundSchema && completionType === 'table_with_alias') {
24094
24124
  const unquotedLabel = unquoteSqlId(option.label);
24095
24125
  const quoted = unquotedLabel !== option.label;
24096
24126
  const tableConfig = metadata.tables[`${foundSchema}.${unquotedLabel}`];
@@ -24113,7 +24143,7 @@
24113
24143
  }
24114
24144
  }),
24115
24145
  MySQL.language.data.of({
24116
- autocomplete: combinedKeywordCompletionSource
24146
+ autocomplete: keywordCompletionSource(MySQL, true)
24117
24147
  })
24118
24148
  ]
24119
24149
  );
@@ -24952,9 +24982,11 @@
24952
24982
  const cellRenderer = function (rowElement, column, value) {
24953
24983
  if (window.metadata.columns[column]?.links?.length > 0) {
24954
24984
  const linksColumnElement = document.createElement('td');
24955
- window.metadata.columns[column].links.forEach((link) => {
24956
- linksColumnElement.appendChild(createLink(link, value));
24957
- });
24985
+ if (value) {
24986
+ window.metadata.columns[column].links.forEach((link) => {
24987
+ linksColumnElement.appendChild(createLink(link, value));
24988
+ });
24989
+ }
24958
24990
  rowElement.appendChild(linksColumnElement);
24959
24991
  const textColumnElement = document.createElement('td');
24960
24992
  textColumnElement.innerText = value;
metadata CHANGED
@@ -1,15 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sqlui
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.51
4
+ version: 0.1.53
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nick Dower
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-11-28 00:00:00.000000000 Z
11
+ date: 2022-12-01 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: airbrake
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '13.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '13.0'
13
27
  - !ruby/object:Gem::Dependency
14
28
  name: mysql2
15
29
  requirement: !ruby/object:Gem::Requirement
@@ -24,6 +38,20 @@ dependencies:
24
38
  - - "~>"
25
39
  - !ruby/object:Gem::Version
26
40
  version: '0.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: prometheus-client
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '4.0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '4.0'
27
55
  - !ruby/object:Gem::Dependency
28
56
  name: sinatra
29
57
  requirement: !ruby/object:Gem::Requirement
@@ -129,7 +157,7 @@ executables:
129
157
  extensions: []
130
158
  extra_rdoc_files: []
131
159
  files:
132
- - ".version"
160
+ - ".release-version"
133
161
  - app/args.rb
134
162
  - app/database_config.rb
135
163
  - app/database_metadata.rb
@@ -139,12 +167,13 @@ files:
139
167
  - app/sql_parser.rb
140
168
  - app/sqlui.rb
141
169
  - app/sqlui_config.rb
170
+ - app/version.rb
142
171
  - app/views/databases.erb
143
172
  - app/views/error.erb
173
+ - app/views/sqlui.erb
144
174
  - bin/sqlui
145
175
  - client/resources/favicon.svg
146
176
  - client/resources/sqlui.css
147
- - client/resources/sqlui.html
148
177
  - client/resources/sqlui.js
149
178
  homepage: https://github.com/nicholasdower/sqlui
150
179
  licenses:
data/.version DELETED
@@ -1 +0,0 @@
1
- 0.1.51