Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
152 changes: 152 additions & 0 deletions spec/cb/saved_query_spec.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
require "../spec_helper"

Spectator.describe CB::SavedQueryList do
subject(action) { described_class.new client: client, output: IO::Memory.new }

mock_client

let(saved_queries) { [Factory.saved_query, Factory.saved_query(id: "sqpvoqooxzdrriu6w3bhqo55c4", name: "Other Query")] }

describe "#validate" do
it "validates that required arguments are present" do
expect_missing_arg_error
action.cluster_id = "pkdpq6yynjgjbps4otxd7il2u4"
expect(&.validate).to be_true
end
end

describe "#run" do
before_each do
action.cluster_id = "pkdpq6yynjgjbps4otxd7il2u4"
end

it "displays empty message when no queries" do
expect(client).to receive(:get_saved_queries).and_return([] of CB::Model::SavedQuery)
action.call
expect(&.output.to_s).to eq "no saved queries\n"
end

it "outputs table format" do
expect(client).to receive(:get_saved_queries).and_return(saved_queries)
action.call
expect(&.output.to_s).to contain "Test Query"
end

it "outputs json format" do
action.format = CB::Format::JSON
expect(client).to receive(:get_saved_queries).and_return(saved_queries)
action.call
expect(&.output.to_s).to contain "\"name\":"
end
end
end

Spectator.describe CB::SavedQueryExport do
subject(action) { described_class.new client: client, output: IO::Memory.new }

mock_client

let(saved_query) { Factory.saved_query }

describe "#validate" do
it "validates that required arguments are present" do
expect_missing_arg_error
action.cluster_id = "pkdpq6yynjgjbps4otxd7il2u4"
expect_missing_arg_error
action.query_id = "sqpvoqooxzdrriu6w3bhqo55c4"
expect(&.validate).to be_true
end
end

describe "#run" do
before_each do
action.cluster_id = "pkdpq6yynjgjbps4otxd7il2u4"
action.query_id = "sqpvoqooxzdrriu6w3bhqo55c4"
end

it "exports to specified file" do
action.file = "/tmp/test_export.sql"
expect(client).to receive(:get_saved_query).and_return(saved_query)
action.call
expect(File.read("/tmp/test_export.sql")).to eq "SELECT 1"
expect(&.output.to_s).to contain "exported"
File.delete("/tmp/test_export.sql")
end

it "uses sanitized name as default filename" do
expect(client).to receive(:get_saved_query).and_return(saved_query)
action.call
expect(File.exists?("Test_Query.sql")).to be_true
expect(&.output.to_s).to contain "Test_Query.sql"
File.delete("Test_Query.sql")
end
end
end

Spectator.describe CB::SavedQueryImport do
subject(action) { described_class.new client: client, output: IO::Memory.new }

mock_client

let(saved_query) { Factory.saved_query }

describe "#validate" do
it "validates that required arguments are present" do
expect_missing_arg_error
action.cluster_id = "pkdpq6yynjgjbps4otxd7il2u4"
expect_missing_arg_error
action.file = "/tmp/test_import.sql"
expect_missing_arg_error
action.name = "My Query"
expect(&.validate).to be_true
end
end

describe "#run" do
before_each do
action.cluster_id = "pkdpq6yynjgjbps4otxd7il2u4"
action.file = "/tmp/test_import.sql"
action.name = "My Query"
File.write("/tmp/test_import.sql", "SELECT 42")
end

after_each do
File.delete("/tmp/test_import.sql") if File.exists?("/tmp/test_import.sql")
end

it "imports from file and prints confirmation" do
expect(client).to receive(:create_saved_query).and_return(saved_query)
action.call
expect(&.output.to_s).to contain "created saved query"
end
end
end

Spectator.describe CB::SavedQueryDestroy do
subject(action) { described_class.new client: client, output: IO::Memory.new }

mock_client

describe "#validate" do
it "validates that required arguments are present" do
expect_missing_arg_error
action.cluster_id = "pkdpq6yynjgjbps4otxd7il2u4"
expect_missing_arg_error
action.query_id = "sqpvoqooxzdrriu6w3bhqo55c4"
expect(&.validate).to be_true
end
end

describe "#run" do
before_each do
action.cluster_id = "pkdpq6yynjgjbps4otxd7il2u4"
action.query_id = "sqpvoqooxzdrriu6w3bhqo55c4"
end

it "destroys and prints confirmation" do
expect(client).to receive(:destroy_saved_query).and_return("")
action.call
expect(&.output.to_s).to eq "saved query destroyed\n"
end
end
end
15 changes: 15 additions & 0 deletions spec/support/factory.cr
Original file line number Diff line number Diff line change
Expand Up @@ -283,4 +283,19 @@ module Factory

CB::Tempkey.new **params
end

def saved_query(**params)
params = {
id: "sqpvoqooxzdrriu6w3bhqo55c4",
name: "Test Query",
sql: "SELECT 1",
cluster_id: "pkdpq6yynjgjbps4otxd7il2u4",
team_id: "l2gnkxjv3beifk6abkraerv7de",
saved_query_folder_id: nil,
created_at: Time.utc(2023, 1, 1, 0, 0, 0),
updated_at: Time.utc(2023, 1, 1, 0, 0, 0),
}.merge(params)

CB::Model::SavedQuery.new **params
end
end
121 changes: 121 additions & 0 deletions src/cb/saved_query.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
require "./action"
require "./table"

module CB
class SavedQueryList < APIAction
eid_setter cluster_id
format_setter format
bool_setter? no_header

def validate
check_required_args do |missing|
missing << "cluster" unless cluster_id
end
end

def run
validate
queries = client.get_saved_queries cluster_id

if queries.empty?
output.puts "no saved queries"
return
end

case @format
when Format::JSON
output << queries.to_pretty_json << '\n'
else
table = Table::TableBuilder.new(border: :none) do
columns do
add "ID"
add "Name"
add "Query"
end

header unless no_header

queries.each do |q|
row [q.id, q.name, truncate_sql(q.sql)]
end
end

output << table.render << '\n'
end
end

private def truncate_sql(sql : String?) : String
return "" if sql.nil?
collapsed = sql.gsub(/\s+/, " ").strip
collapsed.size > 30 ? "#{collapsed[0, 50]}..." : collapsed
end
end

class SavedQueryExport < APIAction
eid_setter cluster_id
eid_setter query_id
property file : String?

def validate
check_required_args do |missing|
missing << "cluster" unless cluster_id
missing << "query" unless query_id
end
end

def run
validate
query = client.get_saved_query query_id

filename = @file || "#{query.name.gsub(/[^a-zA-Z0-9_\-]/, "_")}.sql"
File.write(filename, query.sql)
output << "exported " << query.name << " to " << filename << '\n'
end
end

class SavedQueryImport < APIAction
eid_setter cluster_id
property file : String?
property name : String?

def validate
check_required_args do |missing|
missing << "cluster" unless cluster_id
missing << "file" unless file
missing << "name" unless name
end
end

def run
validate
sql = File.read(@file.to_s)

query = client.create_saved_query({
cluster_id: cluster_id,
name: @name,
sql: sql,
skip_enqueue: true,
})

output << "created saved query " << query.id << '\n'
end
end

class SavedQueryDestroy < APIAction
eid_setter cluster_id
eid_setter query_id

def validate
check_required_args do |missing|
missing << "cluster" unless cluster_id
missing << "query" unless query_id
end
end

def run
validate
client.destroy_saved_query query_id
output << "saved query destroyed" << '\n'
end
end
end
35 changes: 35 additions & 0 deletions src/cli.cr
Original file line number Diff line number Diff line change
Expand Up @@ -913,6 +913,41 @@ op = OptionParser.new do |parser|
end
end

parser.on("saved-query", "Manage saved queries") do
parser.banner = "cb saved-query <list|export|import|destroy>"

parser.on("list", "List saved queries for a cluster") do
list = set_action SavedQueryList
parser.banner = "cb saved-query list <--cluster>"
parser.on("--cluster ID", "Choose cluster") { |arg| list.cluster_id = arg }
parser.on("--format FORMAT", "Choose output format (default: table)") { |arg| list.format = arg }
parser.on("--no-header", "Do not display table header") { list.no_header = true }
end

parser.on("export", "Export a saved query to a .sql file") do
export = set_action SavedQueryExport
parser.banner = "cb saved-query export <--cluster> <--query>"
parser.on("--cluster ID", "Choose cluster") { |arg| export.cluster_id = arg }
parser.on("--query ID", "Saved query ID") { |arg| export.query_id = arg }
parser.on("--file PATH", "Output file path (default: <name>.sql)") { |arg| export.file = arg }
end

parser.on("import", "Import a saved query from a .sql file") do
import = set_action SavedQueryImport
parser.banner = "cb saved-query import <--cluster> <--file> <--name>"
parser.on("--cluster ID", "Choose cluster") { |arg| import.cluster_id = arg }
parser.on("--file PATH", "Path to .sql file") { |arg| import.file = arg }
parser.on("--name NAME", "Name for the saved query") { |arg| import.name = arg }
end

parser.on("destroy", "Destroy a saved query") do
destroy = set_action SavedQueryDestroy
parser.banner = "cb saved-query destroy <--cluster> <--query>"
parser.on("--cluster ID", "Choose cluster") { |arg| destroy.cluster_id = arg }
parser.on("--query ID", "Saved query ID") { |arg| destroy.query_id = arg }
end
end

parser.on("suspend", "Temporarily turn off a cluster") do
parser.banner = "cb suspend <cluster id>"
suspend = set_action ClusterSuspend
Expand Down
43 changes: 43 additions & 0 deletions src/client/saved_query.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
require "./client"

module CB
class Client
struct SavedQueryListResponse
include JSON::Serializable
pagination_properties
property saved_queries : Array(CB::Model::SavedQuery) = [] of CB::Model::SavedQuery
end

def get_saved_queries(cluster_id)
saved_queries = [] of CB::Model::SavedQuery
query_params = Hash(String, String).new
query_params["cluster_id"] = cluster_id.to_s
query_params["order_field"] = "name"

loop do
resp = get "saved-queries?#{HTTP::Params.encode(query_params)}"
data = SavedQueryListResponse.from_json resp.body
saved_queries.concat(data.saved_queries)
break unless data.has_more
query_params["cursor"] = data.next_cursor.to_s
end

saved_queries
end

def get_saved_query(saved_query_id)
resp = get "saved-queries/#{saved_query_id}"
CB::Model::SavedQuery.from_json resp.body
end

def create_saved_query(params)
resp = post "saved-queries", params
CB::Model::SavedQuery.from_json resp.body
end

def destroy_saved_query(saved_query_id)
resp = delete "saved-queries/#{saved_query_id}"
resp.body
end
end
end
11 changes: 11 additions & 0 deletions src/models/saved_query.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
module CB::Model
jrecord SavedQuery,
id : String,
name : String,
sql : String? = nil,
cluster_id : String = "",
team_id : String = "",
saved_query_folder_id : String? = nil,
created_at : Time = Time::ZERO,
updated_at : Time = Time::ZERO
end
Loading