-
Notifications
You must be signed in to change notification settings - Fork 6
Add saved query management functions. #191
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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 | ||
| 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) | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe have a |
||
| 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) | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same here maybe, if the file doesn't exist? Spitballing, but could maybe do something like: Maybe crystal does something reasonable though if not? Maybe worth validating? 🤷♂️ |
||
|
|
||
| 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 | ||
| 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) | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It might be worth definite a param type for the create method. As I think that leaving it unbounded chokes spectator when generating the mock client. Or at least that's one thing I feel like eased the CI failures a little that I was debugging in #192. |
||
| 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 | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Might be worth considering using Dir.tempdir? As it abstracts the system tempdir which I think is
/var/folderson macos and/tmpon Linux... 🤔